diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..146af7e --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,2 @@ +[features] +hooks = true diff --git a/.codex/hooks.json b/.codex/hooks.json new file mode 100644 index 0000000..13c7229 --- /dev/null +++ b/.codex/hooks.json @@ -0,0 +1,51 @@ +{ + "hooks": { + "PostCompact": [ + { + "hooks": [ + { + "command": "bd codex-hook PostCompact", + "statusMessage": "Scheduling Beads context refresh", + "type": "command" + } + ], + "matcher": "manual|auto" + } + ], + "PreCompact": [ + { + "hooks": [ + { + "command": "bd codex-hook PreCompact", + "statusMessage": "Checking Beads context", + "type": "command" + } + ], + "matcher": "manual|auto" + } + ], + "SessionStart": [ + { + "hooks": [ + { + "command": "bd codex-hook SessionStart", + "statusMessage": "Loading Beads context", + "type": "command" + } + ], + "matcher": "startup|resume|clear" + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "command": "bd codex-hook UserPromptSubmit", + "statusMessage": "Refreshing Beads context", + "type": "command" + } + ] + } + ] + } +} diff --git a/AGENTS.md b/AGENTS.md index bc05711..88a20c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,21 @@ - This is an OpenCode V2 plugin integrating the beads issue tracker - Nothing is vendored from upstream Beads; see `docs/beads-artifact-policy.md`. Review each Beads release's upgrade notes against the surfaces listed there +## Upstream responsibility boundary + +The installed Beads CLI owns its database, routing and consistency semantics. This plugin integrates normal supported CLI behavior; it does not repair, emulate or defensively compensate for speculative or broken upstream behavior. Do not introduce local upstream patches, routing/confinement frameworks or acceptance blockers for hypothetical upstream failures. Preserve our actual responsibilities: correct invocation/project context, OpenCode permissions, executor attribution, cancellation and honest outcomes. Normal permissioned CLI equivalence does not promise absolute filesystem confinement. Report demonstrated upstream defects with evidence and supported remedies; any downstream patch or stronger protective policy needs explicit scope. + +## Beads execution identity and planning ownership + +This project-owned policy applies to commands in generated integration blocks too. + +- Supply explicit command-scoped `BEADS_ACTOR` or `--actor` for every agent-authored write, claim and heartbeat. Never inherit the human Git identity or set a shared user-global actor. Hermes uses `snarkimedes`; delegated executors use readable identities such as `codex:9c2e1` or `claude:7edcc`, with short unambiguous session suffixes when needed. Keep full session IDs in evidence, not actor names; model names alone are not executor identities. +- Carry the actual executor actor into delegated instructions and commands. The executing worker claims explicitly authorized work atomically and keeps that actor through updates/heartbeats. Parent coordination/review does not transfer ownership; record deliberate handoffs. +- Leave new work unassigned unless assignment is intentional; assign Matt only for explicitly human-owned work. Read back `created_by` (audit author), `owner` (record/account ownership) and `assignee` (executor) separately. Actor attribution is not authentication or permission. Preserve historical authorship, including unknown values; record provenance corrections in notes rather than rewriting audit history. +- A quick OpenSpec draft does not require its own ticket. Substantial multi-session/delegated planning does: use one bounded planning Bead, separate from the implementation epic, with artifact revisions, decisions and pending acceptance. This effort uses `opencode-beads-d9n`. +- Give a substantive independently owned review one bounded review Bead linked to its planning work and exact candidate. Reuse it through correction/recheck cycles; do not create a ticket per document, invocation, transient retry or minor nit. Record the actual reviewer actor/session, verdict, findings and evidence. Do not invent retroactive claims or timestamps for reviews already performed. +- OpenSpec owns intended behavior; committed docs own detailed rationale/evidence; Beads owns execution, assignment and acceptance status. Close planning/review from their own evidence and owner acceptance, not from implementation completion. Their closure does not authorize implementation, release or deployment. Readiness alone is never dispatch permission. + ## Releasing a New Version **Triggers**: User says "bump the version", "cut a new release", "release a new version", "let's do a release", or similar. @@ -202,3 +217,27 @@ This protocol applies when ending a Beads implementation workflow. It is subordi - If a required sync or push is blocked, stop and report the exact command and error. + + +## Beads Issue Tracker + +Use Beads (`bd`) for durable task tracking in repositories that include it. Use the `beads` skill at `.agents/skills/beads/SKILL.md` (project install) or `~/.agents/skills/beads/SKILL.md` (global install) for Beads workflow guidance, then use the `bd` CLI for issue operations. + +### Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work +bd close # Complete work +bd prime # Refresh Beads context +``` + +### Rules + +- Use `bd` for all task tracking; do not create markdown TODO lists. +- Run `bd prime` when Beads context is missing or stale. Codex 0.129.0+ can load Beads context automatically through native hooks; use `/hooks` to inspect or toggle them. +- Keep persistent project memory in Beads via `bd remember`; do not create ad hoc memory files. + +**Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/core-concepts/sync-concepts.md for details and anti-patterns. + diff --git a/README.md b/README.md index 8488650..417879c 100644 --- a/README.md +++ b/README.md @@ -7,22 +7,39 @@ [Beads](https://github.com/gastownhall/beads) issue tracker integration for [OpenCode](https://opencode.ai). -`@snarkipus/opencode-beads` is a maintained OpenCode adapter that gives OpenCode 2 the integration `bd setup claude` gives Claude Code: `bd prime` workflow context on every request, refreshed after compaction and when a session resumes, plus a bounded task agent that completes one Bead per invocation. It does not replace Beads: the installed `bd` CLI remains authoritative for issues, project initialization, skills, migrations, backups, and Dolt synchronization. +`@snarkipus/opencode-beads` is a maintained OpenCode adapter that gives OpenCode 2 the integration `bd setup claude` gives Claude Code: `bd prime` workflow context on every request, refreshed after compaction and when a session resumes, plus a bounded task agent. Published 1.0.2 completes one Bead per invocation; the beta/next candidate below follows the authorized scope. It does not replace Beads: the installed `bd` CLI remains authoritative for issues, project initialization, skills, migrations, backups, and Dolt synchronization. This is an independent project maintained by Matt Jackson under the MIT license. It began as a fork of [Josh Thomas's `opencode-beads`](https://github.com/joshuadavidthomas/opencode-beads), and the original work remains credited to its author. 1.0 rebuilt the plugin for OpenCode 2, and development now continues here independently. It vendors nothing from upstream Beads; instead, every Beads release is checked against the `bd` behavior the plugin relies on (see [Tracking upstream Beads](#tracking-upstream-beads)). +This branch contains an unpublished **beta/next source candidate**, separate from the published 1.0.2 behavior described below. The [bounded installed/model evaluation](docs/opencode-installed-model-continuation-vuy9.md) now passes on worker evidence for exact OpenCode 2.1.1/Beads 1.3.1 and genuine OpenAI Sol/high; parent acceptance is complete for all 16 implementation tasks. See [final acceptance and beta release gates](docs/code-mode-final-acceptance-vuy9.md). All 19 requirements and the selected corpus plus CODE_MODE have evidence mappings. Its package includes `assets/oc-beads/` and explicitly registers the `oc-beads` skill at the installed package path, with all 15 corpus resources plus CODE_MODE and ADR (17 links) linked from that base. No manual skill copy is needed. The external `bd` CLI and project initialization remain prerequisites; the plugin does not install or upgrade them. + +The candidate registers all 29 selected `oc-beads:` workflows and `oc-beads:status` through one ordinary native command transform. Ordinary user configuration/file commands run later and win on initial load and rebuild; removing an override restores the package definition. Other plugin conflicts follow native ordering, with initial collisions diagnosed rather than withheld. Status reports registration and effective name/description mismatches; public owner identity and identical-description overrides are unobservable, and matching descriptions do not prove ownership. Ordinary typed continuity execution remains withheld. Live typed qualification used the existing internal dependency through a scratch-only bootstrap importing the unchanged actual installed package; it supplies no operator activation switch. The current report separates actual semantic/provider-native observations, reused cleanup/resource/Stop evidence, historical failed probes and unmeasured cross-route or long-duration behavior. No activation option, version bump or publication is supplied by this source work. + +The source candidate defines eleven fixed operations in the native `beads` +code-mode namespace: find, get, edges, memories, recall, claim, note, selected-field +update, close, create and remember. They declare structured results, preserve +per-operation shell permissions and report partial, failed or uncertain outcomes. +Native shell is direct-only; it cannot be called inside execute. Request guidance +reports observed execute/shell capability and the actual session-and-agent Beads +actor; direct shell writes use that same actor. Catalog discovery and leaf +permissions still determine which operations can run. Status reports unknown +capability when no request has been observed. Read [CODE_MODE.md](assets/oc-beads/resources/CODE_MODE.md) +for composition and recovery. Production typed activation remains withheld +pending separate owner approval after acceptance review. + ## Design -The plugin's primary design is **context-preserving atomic delegation**: +The beta/next candidate uses scope-bounded task execution: - The primary OpenCode thread retains planning context, decisions, and control. -- `beads-task-agent` processes exactly one Bead per invocation, then closes, blocks, or returns. -- Without an external orchestrator, this intentionally trades broad autonomy for Bead atomicity and context preservation. -- An orchestrator may repeat `select → delegate one Bead → verify → select next`, but should not weaken the worker boundary. +- An exact assignment or explicit one-bead mode stops with that item; read-only graph requests never authorize claims or writes. +- An already-authorized batch or goal may continue within scope, with an atomic claim, append checkpoints, validation and acceptance evidence for each Bead. Discovery does not expand that authority. +- Direct work or normal blocking delegation is chosen for the task. The actual executor actor follows claims, updates and heartbeats; handoffs are deliberate. Parent-owned acceptance and closure remain with the parent. +- Denied authority, cancellation, permission waits, blockers and incomplete acceptance remain visible stops. Child results do not restart work. The [current bounded semantic evidence](docs/opencode-installed-model-continuation-vuy9.md) measures these outcomes, with failed validation and partial work preserved; broader model quality and multi-hour reliability remain unmeasured. ## Installation -This plugin requires OpenCode 2 and `bd` 1.3.0 or later. +This plugin requires OpenCode 2 and `bd` 1.3.1 or later. This source branch prepares an opt-in beta/next candidate for OpenCode `>=2.1.1 <2.2`; the exact 2.1.1 released host now has bounded source/native/Stop and installed/model evidence, with final parent acceptance pending. Other 2.1 patches are not inferred qualified. The published stable package remains separately qualified against OpenCode 2.0.26. 1. Install the [`bd` CLI](https://github.com/gastownhall/beads/blob/main/docs/getting-started/installation.md) once on the host, then initialize each project: @@ -31,7 +48,7 @@ This plugin requires OpenCode 2 and `bd` 1.3.0 or later. bd init ``` - `bd init` owns project initialization and creates the canonical shared Beads skill at `.agents/skills/beads`, which OpenCode discovers. It also writes a minimal Beads section to `AGENTS.md` that points to `bd prime`, the same pointer Beads gives Claude Code in `CLAUDE.md`. This plugin has no separate skill lifecycle and does not write startup files. + `bd init` owns project initialization and creates the canonical shared Beads skill at `.agents/skills/beads`, which OpenCode discovers. It also writes a minimal Beads section to `AGENTS.md` that points to `bd prime`, the same pointer Beads gives Claude Code in `CLAUDE.md`. The published stable package relies on that shared skill. The beta/next source candidate additionally owns its packaged `oc-beads` skill; it does not write startup files or change `.agents/skills/beads/SKILL.md`. 2. Add the version-pinned plugin to `~/.config/opencode/opencode.json` for every project, or to a project's `.opencode/opencode.json`: @@ -64,7 +81,7 @@ Plugin options use the object form: | --- | --- | --- | | `luna` | `false` | Also register the `beads-task-agent-luna` profile. | | `stealth` | `false` | Add `--stealth` to `bd prime` (no git operations in the session-close protocol), like `bd setup claude --stealth`. `bd config set no-git-ops true` is the project-level equivalent. | -| `taskAgentSteps` | `100` | Hard cap on task-agent steps per invocation, a backstop for "one bead, then return". `0` removes the cap. | +| `taskAgentSteps` | `100` | Interruption cap on task-agent steps per invocation. `0` removes the cap; neither value changes authorized scope or establishes completion. | | `refreshAfterIdleMinutes` | `30` | Re-run `bd prime` when a session resumes after this much inactivity, as Claude Code does on resume. `0` disables idle refresh. | | `autoMigrate` | `false` | Let automatic `bd prime` open a store that may need a schema migration. See [Schema migration guard](#schema-migration-guard). | @@ -89,25 +106,31 @@ The [Claude Code parity matrix](docs/claude-code-parity.md) records every `bd se ## Context injection -The plugin runs `bd prime --full` in the project directory and attaches its output to each model request through OpenCode's session context hook. The output is cached per session, so prime runs once per session rather than per request. It runs again after compaction (automatic or manual), when a session resumes after `refreshAfterIdleMinutes` of inactivity, and when the plugin reloads, mirroring the `startup`, `resume`, `clear`, and `compact` sources of Claude Code's `SessionStart` hook. Steady work keeps the same text, which keeps provider prompt caches warm. Attached context is not stored in the conversation, so compaction cannot drop it. `--full` matters: without it, `bd` switches to brief MCP reminders whenever Claude Code's settings list a Beads MCP server, and OpenCode has no Beads MCP tool. Prime supplies the canonical current workflow, command guidance, and persistent memories; the plugin adds only compact OpenCode-specific safety and delegation guidance. +The plugin runs `bd prime --full` in the project directory and attaches its output to each model request through OpenCode's session context hook. Guarded prime is cached per session, while actual actor and request-capability guidance is rendered for each request. Prime runs once per session rather than per request. It runs again after compaction (automatic or manual), when a session resumes after `refreshAfterIdleMinutes` of inactivity, and when the plugin reloads, mirroring the `startup`, `resume`, `clear`, and `compact` sources of Claude Code's `SessionStart` hook. Steady work keeps the same text, which keeps provider prompt caches warm. Attached context is not stored in the conversation, so compaction cannot drop it. `--full` matters: without it, `bd` switches to brief MCP reminders whenever Claude Code's settings list a Beads MCP server, and OpenCode has no Beads MCP tool. Prime supplies the canonical current workflow, command guidance, and persistent memories; the plugin adds only compact OpenCode-specific safety and delegation guidance. While prime is attached, two bd-managed `AGENTS.md` blocks only repeat it: the full-profile section `bd setup opencode` writes, and the Codex section `bd init` writes. The plugin replaces those marked blocks in the outgoing request with a one-line pointer, matching the minimal `CLAUDE.md` pointer Beads uses for Claude Code. `bd init`'s minimal section is left as is. Your `AGENTS.md` file and any text outside those markers are not changed, and nothing is replaced when prime is withheld or unavailable. Known regular subagents remain excluded, including `explore` and `general`, while `beads-task-agent` and `beads-task-agent-luna` remain eligible. If agent lookup fails, context injection fails open to preserve compatibility with unknown custom primary agents. -If `bd` is unavailable or the project is uninitialized, nothing is added. If `bd prime` fails or times out, the agent gets a short `` saying context is unavailable and to run `bd prime`, as Beads' Codex hooks report refresh failures. Either way the plugin retries after 30 seconds, and failures are also logged as `[opencode-beads]` warnings. The task agent retains a bounded standalone quick reference for `ready`, `show`, atomic claim, discovered follow-up, validate before closure, and close. +If `bd` is unavailable or the project is uninitialized, nothing is added. If `bd prime` fails or times out, the agent gets a short `` saying context is unavailable and to run `bd prime`, as Beads' Codex hooks report refresh failures. Either way the plugin retries after 30 seconds, and failures are also logged as `[opencode-beads]` warnings. The task agent retains standalone guidance for authorized scope, atomic claim, append checkpoints, validate before closure, and partial handoff under the caller's acceptance authority. ## Schema migration guard Beads 1.3 migrates an embedded store in place, irreversibly, the first time any command opens it, and `bd --readonly` does not prevent that. Because this plugin runs `bd prime` automatically, it would otherwise be the unattended first command after you upgrade `bd`. -Before running prime, the plugin checks `bd version` and `bd where --json`, which never open the store, and reads `.beads/.local_version`. If the installed `bd` is older than 1.3.0, or the store was last opened by an older `bd`, the plugin withholds prime and gives the agent a notice telling it not to run `bd` and to ask you to act. To proceed, back up the `.beads` directory and run `bd migrate schema`; the plugin picks up the change on its next retry. Setting the `autoMigrate` option skips the store-version check. +Before running prime, the plugin checks `bd version` and the configuration-only `bd context --json` probe, which do not open the store, and reads `.beads/.local_version`. If the installed `bd` is older than 1.3.1, or the store was last opened by an older `bd`, the plugin withholds prime and gives the agent a notice telling it not to run `bd` and to ask you to act. To proceed, back up the `.beads` directory and run `bd migrate schema`; the plugin picks up the change on its next retry. Setting the `autoMigrate` option skips the store-version check. `bd prime` itself does not update `.beads/.local_version`. A store migrated only by prime keeps the old marker until you run `bd migrate schema` (or any ordinary `bd` command), which is safe and idempotent. ## Beads workflows -The plugin registers no slash commands; `bd setup claude` installs none either. Ask the agent to work with Beads, have it load the canonical `beads` skill that `bd init` installs at `.agents/skills/beads` (OpenCode lists it and loads it with its `skill` tool), or use the `bd` CLI and the generated [Beads CLI reference](https://beads.gascity.com/cli-reference/index) directly. Versions before 1.0.0 shipped `/beads:*` commands adapted from upstream's Claude Code plugin; they duplicated `bd prime` and the skill. +Published stable 1.0.2 registers no slash commands; `bd setup claude` installs none either. Ask the agent to work with Beads, have it load the canonical `beads` skill that `bd init` installs at `.agents/skills/beads` (OpenCode lists it and loads it with its `skill` tool), or use the `bd` CLI and the generated [Beads CLI reference](https://beads.gascity.com/cli-reference/index) directly. Versions before 1.0.0 shipped `/beads:*` commands adapted from upstream's Claude Code plugin; they duplicated `bd prime` and the skill. + +## Removal and customization in the source candidate + +Remove the plugin's `plugins` entry to unload it. Cleanup aborts owned context/process work and its event observer, then awaits the scoped agent, skill, tool and hook disposers. It is idempotent and reports disposal failures. It never promises a final checkpoint after interruption. Reload starts with fresh session/location state. Package updates/removal belong to OpenCode's package lifecycle; this plugin has no installer or recursive asset watcher. + +Existing `oc-beads` skill IDs or names are checked and diagnosed in each rebuilt candidate; removing a customization allows the packaged skill to return. There is no permanent setup-time skill gate. Ordinary user configuration/file commands retain their later native precedence; same-name other-plugin conflicts follow native order rather than universal collision prevention. Disposing a registration removes only the package's transform; it does not delete user command/skill bodies, shared Beads skills, project data, startup files or global settings. Do not delete those files as plugin cleanup. Installed resource reads still require normal host permissions: external-directory ask/deny and actual model use remain unexecuted live acceptance checks. Missing/unreadable packaged guidance is diagnosed and its skill is withheld. ## Agent configuration @@ -115,9 +138,9 @@ Entries under `agents` in OpenCode configuration override the plugin's task-agen ## Task agent -`beads-task-agent` supports read-only status and graph analysis or one-Bead completion. Analysis requests never mutate Beads. Completion requests inspect and process one caller-selected or highest-priority ready Bead, quarantine work discovered during that invocation, validate before closure, and return after closing or blocking the selected Bead. +The beta/next `beads-task-agent` supports read-only status and graph analysis or execution within an assigned task, batch or goal. Analysis requests never mutate Beads. Exact assignments stop without substitution. Authorized batches may continue only within scope, with an explicit executor actor, atomic claim/readback, append checkpoints and relevant validation for each Bead. Discoveries do not authorize their execution; unresolved acceptance remains incomplete, and parent-owned closure stays with the parent. -Its prompt and description are owned by this project. Upstream Beads ships a task agent only in its Claude Code marketplace plugin, as an autonomous multi-task loop; this adapter deliberately keeps the bounded one-Bead contract instead. +Its prompt and description are owned by this project. Direct work and normal blocking delegation depend on the task, without a command-count threshold. The worker returns scope, actor/session, attempts, rationale, validation, durable references, blockers and next action. The caller reconciles acceptance; NO_TEXT, failure, cancellation, late results and step-cap interruption do not establish completion or authorize a restart. OpenCode lets the plugin enforce parts of that contract, beyond what Claude Code's hooks can do: @@ -128,7 +151,7 @@ OpenCode lets the plugin enforce parts of that contract, beyond what Claude Code ### Opt-in Luna Max profile -The default `beads-task-agent` leaves its model unset, so a subagent inherits the parent session's model. Set the plugin option `"luna": true` to also register `beads-task-agent-luna`, which uses `openai/gpt-6-luna#max` with exactly the same prompt, subagent mode, step cap, and permissions. Per-agent overrides remain independent: customizations to `beads-task-agent` are not copied to Luna. Both profiles receive full prime context and retain the same exact-one-Bead workflow. +The default `beads-task-agent` leaves its model unset, so a subagent inherits the parent session's model. Set the plugin option `"luna": true` to also register `beads-task-agent-luna`, which uses `openai/gpt-6-luna#max` with exactly the same prompt, subagent mode, step cap, and permissions. Per-agent overrides remain independent: customizations to `beads-task-agent` are not copied to Luna. Both profiles receive full prime context and use the same scope and acceptance boundaries, including explicit one-bead mode when assigned. The profile makes no model-quality guarantee. Ask the primary agent to delegate to `beads-task-agent-luna` by name through the `subagent` tool. Default delegation guidance still names `beads-task-agent`; enabling Luna does not reroute existing calls or change the primary agent's model. Remove the option to unregister the profile. @@ -144,17 +167,18 @@ The package vendors nothing from upstream. It depends on the installed `bd` CLI | Component | Expected range | Validated baseline | | --- | --- | --- | -| OpenCode | 2.0.26 through 2.x | 2.0.26 | -| `bd` CLI | 1.3.0 through 1.x | 1.3.1 | +| OpenCode (beta/next candidate) | `>=2.1.1 <2.2` | None (exact 2.1.1 target not qualified) | +| OpenCode (stable 1.0.2 historical) | 2.0.26 | 2.0.26 | +| `bd` CLI | 1.3.1 through 1.x | 1.3.1 | | Bun (OpenCode host runtime) | >=1.3.14 | 1.4.2 | -The adapter type-checks against `@opencode/plugin` 2.0.26 and declares it as an optional peer from 2.0.26 through 2.x; the import is type-only, so OpenCode's own runtime is used. See the [OpenCode V2 contract](docs/opencode-sdk-contract.md). The `bd` baseline was reviewed against Beads v1.3.1 (commit `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c`). +The beta/next candidate type-checks against `@opencode/plugin` 2.1.1 and declares an optional peer range of `>=2.1.1 <2.2`. It checks `ctx.app.version` at startup and reports an unsupported-host diagnostic before registering Beads agents or hooks. This source guard does not qualify the host range; the exact 2.1.1 target remains untested, and future 2.1 patches are admitted candidates rather than tested versions. The stable 1.0.2 qualification on 2.0.26 is historical. See the [OpenCode V2 contract](docs/opencode-sdk-contract.md). The `bd` baseline was reviewed against Beads v1.3.1 (commit `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c`). ## Troubleshooting - **No Beads context:** Run `bd prime`. Install `bd` if missing or run `bd init` if the project has no Beads workspace. - **Agent reports that `bd prime` failed or timed out:** Run `bd prime` in the project to see the error; the plugin retries every 30 seconds. -- **Agent reports a Beads notice instead of context:** The schema migration guard withheld prime. Back up `.beads`, run `bd migrate schema`, and continue; or upgrade `bd` if the notice says it is older than 1.3.0. +- **Agent reports a Beads notice instead of context:** The schema migration guard withheld prime. Back up `.beads`, run `bd migrate schema`, and continue; or upgrade `bd` if the notice says it is older than 1.3.1. - **Canonical skill missing:** For a new workspace, run `bd init`. For an existing workspace, follow current upstream Beads guidance; this plugin does not install or repair skills. - **Task agent asks before committing or pushing:** This is intentional; see [Task agent](#task-agent). Approve the command, or set the agent's `permissions` in OpenCode configuration. - **Unexpected definition:** Check OpenCode configuration for a colliding `beads-task-agent` definition. Explicit configuration wins. diff --git a/assets/oc-beads/SKILL.md b/assets/oc-beads/SKILL.md new file mode 100644 index 0000000..4a4f8e0 --- /dev/null +++ b/assets/oc-beads/SKILL.md @@ -0,0 +1,149 @@ +--- +name: oc-beads +description: > + Use Beads when work needs durable tracking across sessions, has dependencies or + blockers, needs a meaningful progress checkpoint, or must be resumed with its + prior attempts and decisions. Also use when searching or updating reusable + project knowledge. Follow the current project's instructions and the installed + bd CLI for supported operations. +--- + +# Beads work in OpenCode + +> Adapted from [Beads marketplace-main `SKILL.md` at commit `4a67d0d0288c1b4ee23e17fc896f47616931ba2d`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/SKILL.md). + +Beads is the external issue and execution record. The installed `bd` CLI owns its +commands and data. Repository and user instructions set the authority for each +operation. The project's specification system (for example OpenSpec, when used) +records intended behavior; project documents retain detailed rationale and +evidence; Beads records execution, assignment, and acceptance. + +Use the current Beads context when the host provides it. If it is absent, retrieve +current workflow context through the permissioned CLI only when that operation is +authorized and no hold is active. Distinguish context that is missing, failed to +load, or deliberately withheld. A migration or permission hold is a stop signal; +do not bypass it by manually running `bd prime`. If execute is unavailable, use direct shell only when separately available and +authorized. Native shell is direct-only and cannot be called inside execute. Do +not assume an unstated tool name, approval, or setup step. + +When typed composition is available, discover current signatures through native +`search({namespace: "beads"})`; each leaf retains its own permission checks. +Follow [code-mode composition](resources/CODE_MODE.md) for bounded reads, +projection, ordered writes, completeness, uncertainty and decline/Stop semantics. + +## Work within the assigned scope + +- Read the relevant issue and its dependencies before changing tracked state. +- An assignment to one specific item is an exact assignment: finish that item, + report the outcome, and stop. Continue across a batch only when the user or + repository instructions explicitly authorize that batch or goal. Discovering + more work can justify reporting or recording a finding when authorized; it + does not expand the work you may perform. +- For an authorized tracked task, claim atomically with the actual executor + actor. Keep that actor on every agent-authored Beads write and heartbeat: + + ```sh + bd update '' --claim --actor='' --sandbox --json + bd heartbeat '' --actor='' --sandbox --json + ``` + + Use the host-rendered actor derived from your actual request session and agent, + matching typed write results. If unavailable, refuse writes until the host + supplies it; never copy a sample or impersonate another executor. Use a + readable, unambiguous identity, not the human Git identity or a model name. + Give delegated instructions the delegate's real actor and have the + executing worker claim its own authorized work. A deliberate handoff changes + ownership; parent review does not. +- Read back `created_by` (audit author), `owner` (record/account ownership), and + `assignee` (executor) separately. Leave new work unassigned unless assignment + is intentional. Preserve historical authorship; document provenance + corrections in notes instead of rewriting it. +- Direct work is suitable for small, clear tasks. Delegate when separate work + benefits from another executor or independent review, and state the scope, + evidence expected, actor, and stop condition. There is no required + command-count threshold or one-issue-per-invocation loop. + +## Keep useful progress + +At a meaningful change in direction, record what was attempted, what happened, +why the next step follows, any blocker, and the concrete next action. Append to +the issue's notes so useful earlier learning remains available: + +```sh +bd update '' --append-notes="Tried …; result …; because …; blocker …; next …" --actor='' --sandbox --json +``` + +Use `--append-notes` for checkpoints. Replacing notes is a deliberate edit, not +the routine checkpoint path. Keep records concise and evidence-based; do not +store credentials, raw dumps, or guesses as facts. Do not write just to meet a +count or frequency target when nothing material changed. If a write fails or its +result is uncertain, report that state and inspect before retrying. + +## Separate task notes from reusable knowledge + +Attempts, issue-specific test results, decisions, and the next action belong on +the issue. Use persistent Beads memory only for validated project knowledge that +will likely help beyond this issue. Search for existing knowledge first, then +update it deliberately with scope and evidence; a temporary failure usually +belongs in the task record rather than global memory. Recall before updating an +existing stable key: writing the same key replaces its content in place. + +```sh +bd memories '' +bd recall '' +bd remember "Validated learning, where it applies, and supporting evidence" --key='' --actor='' --sandbox +``` + +Do not dump all memories into every handoff or delete old knowledge as routine +cleanup. Use the current CLI help for exact memory options. + +Do not initialize or reroute a store, synchronize it, change permissions, or enable automation as a side effect. +Those separate administrative operations require explicit authorization. +Examples assume a project policy prohibiting automatic remote updates and include +`--sandbox`; it only suppresses Dolt auto-push and provides no migration safety. + +## Validation, handoff, and stopping + +Before marking work complete, compare the result with its acceptance criteria and +run the relevant authorized validation. Keep failed or unrun checks explicit. +Close an issue only when closure is within your authority and its own acceptance +is met. If a parent, owner, or human controls acceptance, return evidence and +leave that decision to them. A discovered issue does not become authorized work +just because it is linked. + +For a handoff, provide the current state, material attempts and outcomes, +rationale, validation evidence, blockers, next action, and useful issue or memory +references. Preserve enough context to continue without replaying a failed +approach. Stop when the exact assignment is done, authority is absent, a hold is +active, or a required guarantee cannot be established. + +## Supporting guidance + +These resources are separate so advanced or reference material can be opened +only when relevant. Every selected support file is linked explicitly because a +host may display only a limited automatic file list. + +### Daily work + +- [Boundaries: durable tracking and short-lived work](resources/BOUNDARIES.md) +- [Current CLI reference sources](resources/CLI_REFERENCE.md) +- [Dependency direction and provenance](resources/DEPENDENCIES.md) +- [Issue creation and useful fields](resources/ISSUE_CREATION.md) +- [Common execution patterns](resources/PATTERNS.md) +- [Resumability and recovery notes](resources/RESUMABILITY.md) +- [Meaningful workflows and checkpoints](resources/WORKFLOWS.md) +- [OpenCode and Beads integration patterns](resources/INTEGRATION_PATTERNS.md) +- [Current troubleshooting and stop conditions](resources/TROUBLESHOOTING.md) + +### Advanced, on demand + +- [Agent beads, executor identity, and unsupported slot APIs](resources/AGENTS.md) +- [Asynchronous gates](resources/ASYNC_GATES.md) +- [Molecule and wisp patterns](resources/CHEMISTRY_PATTERNS.md) +- [Molecule and formula reference](resources/MOLECULES.md) +- [Static reference data](resources/STATIC_DATA.md) +- [Worktree routing](resources/WORKTREES.md) + +### Rationale + +- [ADR-0001: keep exact CLI syntax dynamic](adr/0001-bd-prime-as-source-of-truth.md) diff --git a/assets/oc-beads/adr/0001-bd-prime-as-source-of-truth.md b/assets/oc-beads/adr/0001-bd-prime-as-source-of-truth.md new file mode 100644 index 0000000..3798c99 --- /dev/null +++ b/assets/oc-beads/adr/0001-bd-prime-as-source-of-truth.md @@ -0,0 +1,40 @@ +# ADR-0001: Keep exact CLI syntax dynamic + +## Status + +Accepted for this guidance corpus. + +## Context + +The upstream Beads skill and its resources have useful decision guidance, but +they are not a generated reference for every installed `bd` release. Copying +flags and command forms throughout prose lets examples age independently of the +CLI. A successful `bd prime` or command exit is also not proof that a particular +record was found, a memory was returned, or a write was acknowledged. + +## Decision + +- Keep cognitive and workflow guidance in `SKILL.md` and its on-demand resources. +- Use `bd prime` for current workflow context and `bd '' --help` for + exact syntax. Do not duplicate a broad CLI reference here. +- Respect an explicit context, permission, or migration hold. A manual prime is + not a fallback around a hold. +- Keep examples limited to supported CLI forms verified against the installed + help. When an operation is unavailable, say what supported path or limitation + applies instead of inventing a flag or tool. +- Treat host registration, package inclusion, installed resource access, and + behavioral qualification as separate evidence. The presence of these source + assets does not establish those outcomes. + +## Consequences + +This keeps the startup guidance short and moves depth to linked resources. CLI +help can still be incomplete or ambiguous, so examples and compatibility claims +need review against their relevant Beads release. No claim that drift is +impossible is made. Advanced features remain opt-in and do not enable automation, +store initialization, synchronization, or permission changes by themselves. + +## Source attribution + +Adapted from [Beads marketplace-main ADR-0001 at commit +`4a67d0d0288c1b4ee23e17fc896f47616931ba2d`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/adr/0001-bd-prime-as-source-of-truth.md). diff --git a/assets/oc-beads/resources/AGENTS.md b/assets/oc-beads/resources/AGENTS.md new file mode 100644 index 0000000..00d8f96 --- /dev/null +++ b/assets/oc-beads/resources/AGENTS.md @@ -0,0 +1,43 @@ +# Agent beads and executor identity + +> Adapted from [Beads marketplace-main `AGENTS.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/AGENTS.md). + +## Keep the concepts separate + +Beads executor attribution, issue assignment, and specialized agent/role beads +serve different purposes. For ordinary delegated work, use the authorized issue +and assign or claim it for the actual executor. Supply that executor's explicit +actor on each Beads write and heartbeat. Do not substitute the parent, human Git +identity, or model name for the worker identity. + +Read `created_by`, `owner`, and `assignee` as separate fields. `created_by` is +audit authorship, `owner` identifies the record/account owner, and `assignee` +identifies the executor. A claim does not rewrite historical authorship. Record +known provenance corrections in append-style notes. + +## Specialized agent/role beads are optional + +The upstream resource describes a richer agent state machine and named slots. +In the inspected Beads 1.3.1 CLI, `bd agent --help` and `bd slot --help` both +return unknown-command errors. The installed CLI does support ordinary issue +assignment, atomic task claims, and `bd heartbeat` for a claimed issue lease. +Those are useful executor conventions, not a one-to-one implementation of +agent/role beads, their state machine, or slot queries. + +The upstream model describes lifecycle labels such as idle, spawning, +running/working, done, stuck, stopped, and dead, and slots such as a current-work +hook or a role. Treat these as design concepts from the source material, not +states or slot records this CLI can create or query. + +Do not assume `bd agent` or `bd slot` is available and do not invent replacement +flags. Workflows that require those specialized APIs must stop or use a separately +approved, documented system. Routine Beads work does not need agent beads, +Witness, a slot service, or a new orchestration layer. + +## When a separate agent record could help + +Only consider specialized agent records when a project already has a supported +coordination design that needs durable role capabilities, liveness, or multiple +named assignments. Establish who owns those records and how they are retired +before writing any. A heartbeat on an assigned issue refreshes that issue's +claim lease; it is not a general process monitor. diff --git a/assets/oc-beads/resources/ASYNC_GATES.md b/assets/oc-beads/resources/ASYNC_GATES.md new file mode 100644 index 0000000..ad5e10e --- /dev/null +++ b/assets/oc-beads/resources/ASYNC_GATES.md @@ -0,0 +1,63 @@ +# Asynchronous gates + +> Adapted from [Beads marketplace-main `ASYNC_GATES.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/ASYNC_GATES.md). + +An async gate models a wait condition that blocks a later step. It does not do +the work, grant permission, or make a human decision. Use one only when an +authorized workflow needs a durable wait relationship; ordinary blockers can +remain issue dependencies. + +## Gate kinds + +The pinned guidance distinguishes a human decision from a condition that can +be checked: + +| Kind | Successful condition | Failure or timeout behavior | +| --- | --- | --- | +| `human` | Explicit human decision, recorded by manual resolution. | A `--timeout` can be stored as metadata but is not enforced by `bd gate check`; silence is not consent. | +| `timer` | The timeout has elapsed when an authorized executor runs `bd gate check --type=timer --actor='' --sandbox --json`. | Otherwise it remains pending. | +| `gh:run` | Run is completed with success (the pinned guidance also treats skipped as resolved). | Completed failure or cancellation escalates; the gate remains open for a decision. | +| `gh:pr` | Pull request is merged. | Closed without merge escalates; the gate remains open for a decision. | +| `bead` | The referenced issue is closed, when supported by the installed create path. | An open target remains pending. | + +The installed `bd gate check --help` confirms timer evaluation and GitHub +success/failure conditions. The pinned source says a human gate's stored timeout +is not enforced by `check`. The installed `bd gate create --help` lists human, +timer, `gh:run`, and `gh:pr`; it does not list `bead` as a create type. Check +current help before relying on a gate type; this guidance does not qualify host +behavior. + +## Inspect and resolve deliberately + +Use `bd gate --help` and `bd gate '' --help` for current syntax. The +installed help supports this deliberate human-decision flow: + +```sh +# An authorized executor creates the wait condition. +bd gate create --type=human --blocks='' \ + --reason="Needs owner review" --actor='' --sandbox --json +bd gate list --json +bd gate show '' + +# The actual human runs this command only after making the decision. +bd gate resolve '' --reason="Reviewed; decision recorded" \ + --actor='' --sandbox --json +``` + +Read both the gate and blocked issue before changing either. An agent must never +supply a human actor or resolve a human gate on the human's behalf. Never infer +consent from silence. A failed CI run or unmerged closed pull request is a failure +to investigate, not a successful gate. If an authorized executor runs a check, keep its actor explicit and +inspect the resulting state before continuing. + +Do not create gates automatically for every handoff, approval, test, or issue. +Do not add polling, watchers, or approval automation as a side effect of reading +this resource. Gates and issue dependencies are distinct: use a gate for a +documented async condition, and an issue dependency for work ordering. + +## Stop conditions + +Stop if the gate cannot be read, its referenced event cannot be verified, a +human decision is required, or the current CLI lacks the required gate action. +Report the gate ID, observed state, evidence, and the decision needed. Never +route around a permission denial by calling a different interface. diff --git a/assets/oc-beads/resources/BOUNDARIES.md b/assets/oc-beads/resources/BOUNDARIES.md new file mode 100644 index 0000000..a5fdfc3 --- /dev/null +++ b/assets/oc-beads/resources/BOUNDARIES.md @@ -0,0 +1,57 @@ +# Boundaries: durable tracking and short-lived work + +> Adapted from [Beads marketplace-main `BOUNDARIES.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/BOUNDARIES.md). + +Use Beads for work whose ownership, dependencies, blocker, history, or next +session matters to the project. Keep a short linear breakdown in the current +conversation when it is only an execution aid and no repository rule requires a +durable issue. Do not make a second Markdown task list the durable project record. + +## Choose the right record + +| Situation | Record | Why | +| --- | --- | --- | +| Short, linear steps that will finish in this session | Conversation checklist, if useful | Keeps execution visible without creating durable issue noise. | +| Work needs an owner, blocker, dependency, acceptance, or later resume | Existing or authorized Bead | Preserves execution history and handoff context. | +| Validated learning is likely to help on future issues | Relevant Beads memory | Reusable knowledge is separate from a task's attempts. | +| Intended behavior and acceptance contract | Project's specification system (for example OpenSpec, when used) | Keeps the project specification authoritative. | +| Detailed rationale, source evidence, or qualification result | Project documentation | Gives durable evidence a stable home. | +| Temporary command output | Current session; summarize only if it changes a decision | Avoids storing noise. | +| Host explicitly exposes experimental todo support and the user/project opts in | Host-owned transient checklist | Optional convenience only; it is not installed or enabled here. | + +OpenCode 2.1 experimental todo support is opt-in and host-owned. Do not require +it, turn it on, or assume Claude's `TodoWrite` exists in every host. If no +session checklist is available, keep a brief checklist in the conversation or +use an authorized Bead when the work is durable. + +Do not create a new Bead for every command, transient retry, small nit, or +document. Create or update a record when the work is genuinely durable and you +have authority to do so. Existing assignment and project policy take priority +over any general heuristic here. + +Ask: “Could I resume this after two weeks away?” If the answer needs context +that would otherwise be lost, preserve it in the appropriate authorized record. + +## Transition when the work grows + +It is reasonable to begin with a transient checklist for “update the retry +message and run its focused test.” If the test uncovers an independent data-loss +case, a prerequisite, or work that must continue next session, stop treating the +checklist as the durable record. If authorized, create or update the appropriate +Bead with the attempted fix, test evidence, discovered blocker, acceptance, and +next step; link new work to its source where useful. Keep the checklist only for +the remaining steps in this session. Without authority to create or change a +Bead, preserve the discovery in the assigned record if permitted, then report +and stop at the scope boundary. + +## Preserve the authority boundary + +A ready issue is available work, not permission. A single-item assignment ends +after that item; continue only inside an explicitly authorized batch or goal. +Finding related work does not authorize acting on it. Link or report discoveries +only within the authority already granted. + +For a tracked item, preserve its execution identity and history. Claim +atomically when authorized, use an explicit actor on writes, and append material +progress rather than replacing previous notes. A task record does not own the +Beads database or authorize sync, setup, permission changes, or Git operations. diff --git a/assets/oc-beads/resources/CHEMISTRY_PATTERNS.md b/assets/oc-beads/resources/CHEMISTRY_PATTERNS.md new file mode 100644 index 0000000..efe3175 --- /dev/null +++ b/assets/oc-beads/resources/CHEMISTRY_PATTERNS.md @@ -0,0 +1,44 @@ +# Molecule and wisp patterns + +> Adapted from [Beads marketplace-main `CHEMISTRY_PATTERNS.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/CHEMISTRY_PATTERNS.md). + +Molecules and wisps are advanced ways to organize repeated or temporary work. +They are optional. A normal issue and its dependencies are usually enough for +one task. Do not introduce a molecule workflow simply because it is available. + +## Choose by phase and lifetime + +| Need | Record | Lifetime | +| --- | --- | --- | +| One-off owned work with no reusable workflow | Normal issue and dependencies | Durable issue history. | +| Repeated multi-step work whose children and audit trail should persist | Formula/proto instantiated as a persistent molecule | Reusable source, then durable execution records. | +| Authorized short-lived research, review, triage, or operations with low audit value | Wisp from an existing formula/proto | Ephemeral; useful findings must be promoted before cleanup. | + +Treat formulas/protos as reusable workflow sources, molecules as instantiated +work, and wisps as the temporary phase. Do not use a wisp when the full trail +must persist or assume that a formula will stay inside the exact assignment. + +Before using a formula, inspect the available formula and its steps. Decide what +evidence should survive and who owns each child. Do not let a template silently +expand an exact assignment into unrelated work. + +An approved one-time review can use an existing review formula: capture evidence +and uncertainty in its wisp, promote validated actionable findings to a durable +record, then discard only noise with no future value. A bounded research spike +can preserve sources, rejected hypotheses, and a conclusion the same way. These +are optional work patterns, not authority to create a formula, spawn workers, or +start a background patrol. + +## Preserve useful results + +At the end of a wisp, retain validated findings, rejected hypotheses, sources, +and resulting authorized work in the appropriate durable record. Discard only +noise that has no future value. A digest should not turn unverified notes into +facts. `bd mol squash` creates a durable digest; `bd mol burn` deletes a wisp +without a digest. Use either only when that lifecycle is authorized and current +help confirms the operation. + +Use `bd mol --help`, `bd formula --help`, and relevant subcommand help for exact +syntax. Creating a proto, molecule, wisp, or child issue is a Beads write and +requires the actual executor actor. These features do not start workers, set +permissions, or enable background automation by themselves. diff --git a/assets/oc-beads/resources/CLI_REFERENCE.md b/assets/oc-beads/resources/CLI_REFERENCE.md new file mode 100644 index 0000000..b4be6aa --- /dev/null +++ b/assets/oc-beads/resources/CLI_REFERENCE.md @@ -0,0 +1,20 @@ +# CLI reference + +> Adapted from [Beads marketplace-main `CLI_REFERENCE.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/CLI_REFERENCE.md). + +This resource deliberately does not copy the full command catalog. The installed +`bd` binary and active repository policy determine current syntax. + +- Use `bd prime` for current agent workflow context, only when authorized and + not blocked by a context or migration hold. +- Use `bd '' --help` for a command's current flags and behavior. +- Use `bd --help` to discover commands available in the installed binary. +- Use `--json` when structured output is useful and supported by that command. +- Supply `--actor=''` on every agent-authored write, claim, and + heartbeat. Do not rely on `git user.name`, `$USER`, or an inherited global + actor. + +If a command or flag in an old example is absent from current help, do not guess +its spelling or use a hidden interface. State the limitation and use a supported +equivalent only when it preserves the requested outcome and authority. A static +resource cannot prove installed host behavior or package delivery. diff --git a/assets/oc-beads/resources/CODE_MODE.md b/assets/oc-beads/resources/CODE_MODE.md new file mode 100644 index 0000000..3e50df6 --- /dev/null +++ b/assets/oc-beads/resources/CODE_MODE.md @@ -0,0 +1,78 @@ +# Beads in native code mode + +Use the actual request capability block. When the beads namespace is registered +and execute is visible, use native `search({namespace: "beads"})` to discover +current signatures. Catalog discovery and each operation's permissions determine +what is callable; execute visibility alone does not establish leaf availability. +Do not copy a signature catalog from prose or invent tools. If execute is denied, +code-mode-only Beads operations disappear. Permissioned direct shell is a fallback +only when available, authorized and no guard hold applies. Native shell is +**direct-only**: never call `tools.shell` inside execute. Repository editing, +tests and Git run in separate direct shell turns. + +## Read, choose, then act + +Start with bounded discovery and inspect scope and `complete`. `true` refers to +that query, `false` means truncated or partial, and `null` means completeness is +unknown. Ready, list, search and blocked have different supported filters; +unsupported combinations are rejected. Blocked and edge reads can return unknown +completeness. Unknown or failed edges never prove that work is unblocked. + +Get chosen IDs together (at most 25); the adapter performs sequential single-ID +reads. Inspect per-ID failures and `complete`; an all-failed get is failed, not +missing. There is no snapshot-consistency or transaction promise. For independent +reads, use small bounded fan-out (for example at most eight candidates with +`Promise.allSettled`), retain rejected/failed outcomes as unknowns, and return a +projection of relevant fields. Return memory keys first, then recall selected +keys. A found empty memory differs from a missing key; memories retains a +key-to-value map, including a successful empty map. + +Selecting a candidate does not authorize claiming it. Read scope, prior attempts, +acceptance and dependencies before acting. Await an authorized claim and stop +dependent work unless it acknowledges the actual executor as assignee in +`in_progress`. A claim never hides a second readback prompt; use get explicitly +when needed. `not-claimed` requires evidenced non-claim, and `uncertain` requires +current-state inspection before considering another write. + +## Ordered writes and honest outcomes + +Await dependent writes in order; inspect each result before the next call. Do not +parallelize claim, note, update, close or dependent creates. Each invocation has +its own canonical permission resources and `save: []`: an "always" response may +apply to that call only, so ask may prompt again. Saved shell grants remain +subject to denies. Orchestrator authority does not bypass host permission prompts. +Do not catch a rejected operation and continue its dependent writes. Plain +permission decline on OpenCode 2.1.1 may still permit later independently +permissioned work or model steps; it does not promise whole-program termination. +Explicit Stop/Cancel is distinct and its installed qualification remains separate. + +Use note for append checkpoints. Selected-field update excludes ownership changes +and closure. Close needs caller-authorized acceptance evidence; it is never an +automatic workflow step. Create generates an ID and does not automatically assign +an executor. An uncertain create retains its identifying query/title: search and +read back before considering a manual retry, to avoid duplicates. + +Every typed write returns the host-derived actor. Use that same rendered actor +for direct shell writes, claims and heartbeats with command-scoped `--actor` or +`BEADS_ACTOR`, and `--sandbox` to suppress automatic Dolt push. No global actor +configuration, model-controlled actor argument or human Git fallback. A child +uses its own actual session/agent actor and claims only authorized work; parent +review does not transfer assignment. Audit author (`created_by`), account owner +(`owner`) and executor (`assignee`) are separate observations; missing fields +remain unknown. Attribution is not permission or authentication. + +Only authoritative CLI evidence acknowledges a write. Started interrupted, +timed-out or malformed writes may have landed: retain target, actor and original +diagnostic, then inspect durable state under fresh authorization. Do not blindly +retry, force, commit or synchronize. A landed but uncommitted memory retains +`commit_error`/`commit_indeterminate`; it is not committed durability. Fulfilled +Promises, outer execute completion and compaction summaries do not establish +successful persistence. Write inputs remain in host trace metadata. + +Full prime remains the canonical workflow reference. Meaningful checkpoints +carry attempts, measured outcomes, rationale, blockers and next action. Important +recovery, external changes and child returns require fresh relevant state; +acknowledged writes do not require a generic redundant read of their own result. +Preserve distinct repository/user authority, guard holds and exact scope. Missing +capability or failed context is reported honestly, without manual-prime bypass, +implicit sync, automatic retries or a replacement workflow manager. diff --git a/assets/oc-beads/resources/DEPENDENCIES.md b/assets/oc-beads/resources/DEPENDENCIES.md new file mode 100644 index 0000000..2611b27 --- /dev/null +++ b/assets/oc-beads/resources/DEPENDENCIES.md @@ -0,0 +1,82 @@ +# Dependency types and direction + +> Adapted from [Beads marketplace-main `DEPENDENCIES.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/DEPENDENCIES.md). + +Beads dependencies describe relationships between issues. Confirm direction +before writing: in the current `bd create` help, a bare ID, `'depends-on:'`, or +`'blocked-by:'` makes the new issue depend on that ID; `'blocks:'` reverses +that direction. For existing issues, the installed `bd dep add --help` confirms +the dependent comes first and the prerequisite second (default type: `blocks`). + +## Choose a relationship by meaning + +| Question | Relationship | Effect to preserve | +| --- | --- | --- | +| Can B start before A is complete? | `blocks` (A blocks B) | Hard ordering constraint; the dependent waits. | +| Is B part of A's planned work? | `parent-child` (A parent, B child) | Decomposition and ownership; not an ordering edge. | +| Did work on A uncover B? | `discovered-from` (B from A) | Discovery provenance; not permission to start B. | +| Are A and B connected but independently actionable? | `related` | Informational association, not a blocker. | + +For a new dependent issue, current `bd create --help` says that +`--deps='depends-on:'` means **this new issue depends on that +prerequisite**. For example, an API task can depend on a schema task like this: + +```sh +bd create "Implement API" --type=task \ + --parent='' \ + --deps='depends-on:' \ + --actor='' --sandbox --json +``` + +The command above makes the API task a child of an epic while also adding a +separate hard blocker on schema setup. Read back both edges: hierarchy answers +“what work belongs here?”; a blocker answers “what must finish first?”. Add a +hard block only when a real prerequisite exists. + +### Add and check existing-issue edges + +X needs Y means X depends on Y: dependent first, prerequisite second. + +```sh +bd dep add '' '' --actor='' --sandbox --json +bd blocked --json +bd dep list '' --json +``` + +Phase trap: “Phase 1 before Phase 2” means Phase 2 needs Phase 1. +Wrong: `bd dep add '' '' --actor='' --sandbox --json` would block Phase 1. +Right: `bd dep add '' '' --actor='' --sandbox --json` blocks Phase 2 on Phase 1. +Readback: `bd blocked` should show the dependent waiting while its prerequisite +is open; `bd dep list` defaults to dependencies (down), so it should list that +prerequisite, never the dependent's later work. These reads do not authorize work. + +### Diamond and parallel merge + +```text + setup + / \ + API work UI work + \ / + integration tests + +setup blocks API work and UI work; each implementation blocks the merge tests. +The two implementations can proceed in parallel after setup. +``` + +`related` is useful for an optional improvement that should remain independently +ready. A discovery cascade is different: B may be discovered from A, and C from +B, preserving how the questions emerged. `discovered-from` does not make that +cascade required work, grant authority, or create a hard blocker. + +Use the narrowest accurate relation. Do not use a hard blocker for a preference, +shared topic, or unverified assumption. Avoid cycles and redundant edges. Before +adding or changing an edge, inspect both issues and verify which one depends on +which. Afterward, read the graph back when the relationship affects readiness. + +Dependency direction is especially easy to reverse when creating a child or +recording discovery. The current CLI help is authoritative for syntax; this page +preserves relationship meaning, not every command form. Agent-authored graph +writes require an explicit executor actor. A ready result is only a scheduling +view: it does not override assignment, task scope, or authorization. The +installed CLI describes hard blockers as affecting readiness; inspect the graph +after a meaningful edit rather than inferring it from the prose alone. diff --git a/assets/oc-beads/resources/INTEGRATION_PATTERNS.md b/assets/oc-beads/resources/INTEGRATION_PATTERNS.md new file mode 100644 index 0000000..ee5c6f0 --- /dev/null +++ b/assets/oc-beads/resources/INTEGRATION_PATTERNS.md @@ -0,0 +1,74 @@ +# Integrating Beads with project workflow + +> Adapted from [Beads marketplace-main `INTEGRATION_PATTERNS.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/INTEGRATION_PATTERNS.md). + +Use Beads to preserve execution state, not to replace every other source of +intent or evidence. A useful division is: + +| Need | Source of truth | +| --- | --- | +| Intended behavior, requirements, and acceptance contract | Project's specification system (for example OpenSpec, when used) | +| Detailed rationale, measurements, source references, and qualification evidence | Project documentation | +| Execution ownership, dependencies, blockers, progress notes, and status | Beads | +| Short checklist for today's already-authorized steps | Current session or optional host-owned todo, if available | + +Link those records when the relationship helps a future reader. Do not duplicate +an entire design or test log in Beads; append a concise checkpoint with the +result, why it matters, and where the detailed evidence lives. + +## Worked linkage: plan, review, then execute + +This is a project-policy example, not a universal Beads requirement. Use the +project's chosen planning and review rules; the pattern below is optional where +no such policy exists. + +For example, an OpenSpec change, in a project that uses it, can own the behavior +and acceptance criteria for a new export option. Put detailed trade-offs and test +evidence in a linked project document. A quick planning draft needs no separate Bead. For substantial +multi-session or delegated planning, create one bounded planning Bead, distinct +from the implementation epic, and reference the OpenSpec change and evidence +document there. + +When review is independently owned and substantive, use one bounded review +Bead linked to the planning work and the exact candidate revision. Record the +reviewer's actual actor/session, verdict, findings, and evidence. Reuse that +same review Bead for correction and recheck cycles; do not create a ticket per +document, retry, or transient nit. Implementation issues track the authorized +execution work. The chosen specification system and docs remain the spec/evidence +sources, not duplicate task trackers; planning or review completion does not +authorize implementation or prove parent acceptance. A ready Bead is not dispatch authority. + +## Milestones and recovery + +Read existing issue notes and relevant project memory before choosing a next +step. At a material milestone, append attempted work, result, rationale, changed +decision, blocker, and next action. Use the explicit executor actor. If the +operation does not materially change the work context, no redundant write is +needed. + +```sh +bd update '' \ + --append-notes="Tried X; observed Y in tests/export.test.ts; chose Z because …; blocker …; next …" \ + --actor='' --sandbox --json +``` + +For delegation, send the worker the exact authorized scope and its actual actor. +Ask for the attempt, outcome, evidence, rationale, blocker, next step, and any +durable record references. The parent reconciles the return against the original +intent and acceptance before continuing or closing. Delegation is discretionary; +do not route work based on a command count. + +OpenCode 2.1 experimental todo support remains opt-in and host-owned. Use it only +when that host feature is actually available and the user/project chooses it; +do not enable it or present it as a universal `TodoWrite` tool. It can hold a +temporary execution checklist, not replace the project's specification system, +project evidence, or Beads. + +## Keep durable and transient information distinct + +Task-specific findings stay with the task. Reusable memory is reserved for +validated knowledge likely to apply elsewhere. Transient shell output and +temporary execution checklists stay local unless they change a decision or must +survive resumption. Existing user and repository instructions remain in force; +an integration does not grant new authority to create issues, mutate other +records, or broaden a task. diff --git a/assets/oc-beads/resources/ISSUE_CREATION.md b/assets/oc-beads/resources/ISSUE_CREATION.md new file mode 100644 index 0000000..d572c4d --- /dev/null +++ b/assets/oc-beads/resources/ISSUE_CREATION.md @@ -0,0 +1,45 @@ +# Creating useful issues + +> Adapted from [Beads marketplace-main `ISSUE_CREATION.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/ISSUE_CREATION.md). + +Create a durable issue when work needs its own assignment, dependencies, +multi-session history, or acceptance record, and only when the existing authority +allows creation. Do not create one for every retry, note, or discovered idea. +Discovery may be reported without being worked; a `discovered-from` edge does +not authorize the new issue. + +## Write for the next executor + +- **Title:** a specific outcome or problem. +- **Description:** why the work exists, relevant context, and boundaries. +- **Design:** the intended approach and important constraints. +- **Acceptance:** observable conditions that define completion. +- **Notes:** current progress and later checkpoints, appended as work proceeds. + +Do not treat a proposed approach as an acceptance test. Avoid secrets, copied +logs, vague “investigate” titles without a question, and unsupported promises. +Leave new work unassigned unless an assignment is intentional. If a human owns +the work, assign that person deliberately; an agent actor is not proof of +permission. + +Keep design and acceptance distinct: design records a candidate approach and +trade-offs that may change; acceptance states a testable outcome that remains +true if the implementation changes. For example, “reuse the current parser if +it handles quoted fields” is design; “quoted commas and quotes round-trip in the +export” is acceptance. Put the former in `--design` and the latter in +`--acceptance`. + +Current syntax comes from `bd create --help`. A write must use the actual +executor actor, for example: + +```sh +bd create "Clarify retry behavior" --type=task --priority=2 \ + --description="Record the user-visible issue and affected path." \ + --design="Trace the current behavior and document the supported correction." \ + --acceptance="The behavior and validation evidence are recorded." \ + --actor='' --sandbox --json +``` + +Before creating linked work, inspect the parent and verify dependency direction. +Keep planning and implementation ownership distinct when the project assigns +them to different people. diff --git a/assets/oc-beads/resources/MOLECULES.md b/assets/oc-beads/resources/MOLECULES.md new file mode 100644 index 0000000..f31b844 --- /dev/null +++ b/assets/oc-beads/resources/MOLECULES.md @@ -0,0 +1,65 @@ +# Molecules, formulas, and wisps + +> Adapted from [Beads marketplace-main `MOLECULES.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/MOLECULES.md). + +The current CLI describes formulas as workflow sources, protos as reusable +templates, molecules as instantiated work, and wisps as ephemeral workflows. +Use these only when their lifecycle solves a real project need; a normal issue +graph is clearer for one-off work. + +## Decide before instantiating + +| Question | Choose | +| --- | --- | +| Is this one-off work with a clear owner and no template need? | A normal issue graph. | +| Must each step and its audit history remain durable? | Persistent molecule with durable audit history from an inspected formula/proto. | +| Is the work short-lived and low-audit, with an agreed way to preserve findings? | Wisp, only with explicit lifecycle authority. | +| Is the workflow repeatable and already validated? | Consider distilling a formula after preserving its evidence and lessons. | + +Inspect the formula's gates, dependencies, variables, and children before +creating work. Confirm that every step fits the authorized scope. Keep generated +children aligned with the intended parent acceptance; closed children do not +prove the parent outcome. + +## Example: approved review wisp + +When a bounded review is authorized as temporary and an existing review proto +fits the scope, create the wisp, inspect its instantiated steps, and retain only +validated findings: + +```sh +bd mol wisp create '' --var 'candidate=' \ + --actor='' --sandbox --json +bd mol show '' +``` + +If the review produced reusable evidence or actionable findings, summarize and +promote the result to a durable digest: + +```sh +bd mol squash '' \ + --summary="Candidate : verified findings and evidence references" \ + --actor='' --sandbox --json +``` + +If the wisp contains only noise with no future value, burn it only when that +deletion is explicitly authorized; this leaves no digest: + +```sh +bd mol burn '' --actor='' --sandbox --json +``` + +These verbs and phase effects are confirmed by installed subcommand help. A +wisp is ephemeral and not synced via Git; do not use it as the only home for +required acceptance evidence. A digest is a summary, not proof that findings +were independently validated or that a parent issue is accepted. + +Distilling a formula is useful only after a workflow has proved repeatable. +Retain validation and lessons in the reusable template, not accidental details +from one run. Bonding workflows can create large graphs; inspect the result and +ownership before proceeding. Do not pour, bond, squash, burn, or distill as +automatic setup or background orchestration. + +Use `bd formula --help` and `bd mol --help`, then each relevant subcommand's +help, for current syntax. These are state changes; keep the actual executor +actor explicit. diff --git a/assets/oc-beads/resources/PATTERNS.md b/assets/oc-beads/resources/PATTERNS.md new file mode 100644 index 0000000..c9c7ea8 --- /dev/null +++ b/assets/oc-beads/resources/PATTERNS.md @@ -0,0 +1,43 @@ +# Common execution patterns + +> Adapted from [Beads marketplace-main `PATTERNS.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/PATTERNS.md). + +## Start with the actual request + +Read the assigned issue, parent decisions, relevant dependencies, and current +notes. Distinguish the requested result from a suggested implementation. A +ready issue or an open side quest does not itself authorize work. + +## Record a side discovery without expanding scope + +When a new concern appears, preserve enough evidence to explain it. If recording +it is authorized, create a concise issue and link its provenance. Continue only +if the current authorization explicitly includes it. Otherwise finish the +assigned work, report the discovery, and stop. + +## Resume a multi-session task + +Read the latest appended checkpoint and relevant reusable memory. Reconfirm the +state from the issue or current evidence instead of assuming the prior next step +still applies. Preserve rejected approaches so the next executor does not repeat +them. + +For research or debugging, keep the hypothesis, the attempt, and the evidence +that changed the conclusion together. A rejected hypothesis is useful when its +failure is explained; an unexplained “tried X” is not. If the changed conclusion +creates separate durable work, record its source only when authorized. Discovery +does not by itself make that work a blocker or assign it to the current executor. + +## Change status and close carefully + +Use the installed CLI's valid transitions. Claim only the item you are +authorized to execute; keep the actual executor actor on claim, updates, and +heartbeats. Before closure, compare the deliverable with acceptance criteria and +record validation results. If a blocker or failed check remains, report it and +leave the state accurate. If someone else owns acceptance, return the evidence +instead of closing on their behalf. + +There is no mandatory one-issue-per-session cadence. Keep each Bead's evidence +clear, while following an explicitly authorized multi-item batch where one was +granted. Use `bd '' --help` for current syntax; do not use old MCP-only +examples or replace notes during ordinary checkpoints. diff --git a/assets/oc-beads/resources/RESUMABILITY.md b/assets/oc-beads/resources/RESUMABILITY.md new file mode 100644 index 0000000..dc83a85 --- /dev/null +++ b/assets/oc-beads/resources/RESUMABILITY.md @@ -0,0 +1,83 @@ +# Resumability and recovery + +> Adapted from [Beads marketplace-main `RESUMABILITY.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/RESUMABILITY.md). + +A future executor needs the state of the work, not a transcript. At meaningful +transitions preserve: + +1. what was attempted and why; +2. the observed result and evidence location; +3. the decision or hypothesis that changed; +4. what remains blocked or uncertain; and +5. one concrete next action within the assigned scope. + +Append this checkpoint to the issue so earlier attempts remain visible. Do not +copy full logs, secrets, or unverified conclusions. An abrupt process stop can +prevent a final write, so checkpoint after important results instead of relying +on end-of-session cleanup. + +## Example: make a thin checkpoint resumable + +### Before checkpoint + +```text +Tried the export change; the test still fails. +``` + +### After checkpoint + +```text +Attempted: split export rows on commas. +Observed evidence: tests/export.test.ts, case quoted-comma still fails; + fixture: tests/fixtures/quoted-comma.csv. +Rationale: commas inside quoted fields are data, so splitting cannot work. +Blocker: confirm whether the existing parser exposes a streaming API. +Next: inspect the parser contract, then add a regression test before changing it. +``` + +The second checkpoint carries the approach, observed evidence and its location, +the decision it supports, the remaining uncertainty, and one next action. Use a +small handoff shape when the example is too detailed: + +### Minimal handoff template + +```text +Task and scope: +Attempt and result (evidence/reference): +Decision and rationale: +Blocker or uncertainty: +Next authorized step: +``` + +Add a tested minimal command or code snippet only when the next executor needs +it to reproduce a hard-to-infer result or response shape. Mark what was actually +run and where. Otherwise link to the relevant test or document and summarize the +result. Do not paste raw logs, giant JSON dumps, credentials, or speculative +snippets; acceptance describes the observable outcome, while notes retain +implementation discoveries and rationale. + +## Resume safely + +For a cold start or after compaction, recover the assigned work rather than +selecting fresh work from `bd ready`: + +```sh +bd list --status=in_progress --assignee='' --json +bd show '' --long --json +``` + +Use your actual assignment actor for the assignee filter. If the assignment +already gives an exact issue ID, show that issue directly; an empty list is not +permission to claim another task. Read the latest appended checkpoint and earlier +notes for attempts, evidence, decisions, blockers, and the next authorized step. +Retrieve only relevant reusable memory, then verify current assignment, status, +dependencies, and time-sensitive evidence before acting. The issue notes explain +work on this item; memory carries validated learning beyond it. Neither should +override current user or repository instructions. + +Treat missing context, failed retrieval, and deliberately withheld context as +different outcomes. A permission or migration hold is not an invitation to run a +manual fallback. If a write timed out, read the issue before retrying; the first +write may have landed. Preserve uncertainty explicitly until it is resolved. + +Use the current CLI syntax from `bd prime` and command help when authorized. diff --git a/assets/oc-beads/resources/STATIC_DATA.md b/assets/oc-beads/resources/STATIC_DATA.md new file mode 100644 index 0000000..da20bbd --- /dev/null +++ b/assets/oc-beads/resources/STATIC_DATA.md @@ -0,0 +1,19 @@ +# Static reference data + +> Adapted from [Beads marketplace-main `STATIC_DATA.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/STATIC_DATA.md). + +Beads is primarily for work with ownership, status, dependencies, and a useful +lifecycle. Avoid turning every glossary term, configuration value, or mostly +static reference entry into an open work issue. Use documentation or an existing +project knowledge system when it provides clearer name-based browsing. + +Validated reusable conventions can belong in Beads memory when they are likely +to help future work and do not already exist. Search first, record scope and +evidence, and correct stale knowledge deliberately. Do not use memory as a dump +of all project facts or as a substitute for authoritative specifications. + +If a team has a real need for structured reference entities with relationships +and ownership, design that use separately. Keep it separated from active work +where possible and verify the installed CLI's search and query capabilities +before choosing it. This advanced pattern does not authorize a new database, +initialization, or schema change. diff --git a/assets/oc-beads/resources/TROUBLESHOOTING.md b/assets/oc-beads/resources/TROUBLESHOOTING.md new file mode 100644 index 0000000..fb65bf7 --- /dev/null +++ b/assets/oc-beads/resources/TROUBLESHOOTING.md @@ -0,0 +1,49 @@ +# Troubleshooting and stop conditions + +> Curated replacement for [Beads marketplace-main `TROUBLESHOOTING.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/TROUBLESHOOTING.md). Legacy repair examples are not carried forward where they imply setup, server changes, or automatic repair. + +## Context is absent, failed, or withheld + +First identify which state actually occurred. If no context was provided, retrieve +current CLI context only when permitted and no hold is active. If retrieval +failed, report the error and preserve the task's last known state. If a migration +or permission guard withheld context, stop; do not run `bd prime`, a different +CLI surface, or another agent as a bypass. + +## A command or flag is unknown + +Use `bd --help` and `bd '' --help` to inspect the installed CLI. Do not +guess a flag from an old example, invoke a similarly named command, or assume an +MCP interface. If a workflow depends on an unavailable command, explain the +limitation and use a documented replacement only when it preserves the needed +semantics. The specialized `bd agent` and `bd slot` commands are absent from the +inspected CLI; see [AGENTS.md](AGENTS.md). + +## Store, migration, or routing problem + +If the CLI reports no store, an outdated schema, an unexpected location, or a +redirect, do not initialize, migrate, reroute, repair, or synchronize it +automatically. Preserve the diagnostic and ask the authorized owner to choose a +supported recovery. The plugin's context guard is not permission to bypass the +same restriction through a shell command. + +## Permission denial or external access prompt + +Respect the decision. Do not switch to another tool, path, or identity to obtain +the same denied result. If a referenced guidance file is unreachable, state that +and continue only if the remaining task can be done safely and accurately. + +## Empty result, failed read, or uncertain write + +An empty successful query differs from an error. Report the actual result, not +the exit code alone. After a timeout or interrupted write, inspect the issue or +memory before retrying; the write may have landed. Do not blindly repeat a +create, claim, remember, or close operation. + +## Lost claim or blocked task + +Read the current assignee, status, and lease before continuing. If another actor +owns it or the claim was reclaimed, stop writes and coordinate a deliberate +handoff. Never force-reassign yourself or fall back to the human Git identity. +Record unresolved acceptance, missing evidence, and the next authorized action +in the handoff. diff --git a/assets/oc-beads/resources/WORKFLOWS.md b/assets/oc-beads/resources/WORKFLOWS.md new file mode 100644 index 0000000..087b9c2 --- /dev/null +++ b/assets/oc-beads/resources/WORKFLOWS.md @@ -0,0 +1,64 @@ +# Workflows and checkpoints + +> Adapted from [Beads marketplace-main `WORKFLOWS.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/WORKFLOWS.md). The append policy below resolves conflicting upstream examples: checkpoints append; replacing notes is deliberate. + +## Begin with the current context + +Use current Beads context if it was supplied. Read the target issue, dependencies, +existing notes, and relevant memory before choosing an action. Confirm the exact +assignment and authority. Do not treat `ready` output as an assignment or +permission to work every listed item. + +For an authorized task, claim it atomically with the actual executor actor. Keep +that actor on all subsequent writes and heartbeats. Direct execution is fine for +small tasks; use delegation where it improves the work, carrying a bounded scope +and the real delegate actor. Do not impose a one-item-per-invocation loop or +delegate by command count. + +## Checkpoint at useful transitions + +Append when a failed approach changes the next step, a finding is validated, a +decision changes, work becomes blocked, a child returns, or a session may stop. +Include the attempt, result, why it matters, evidence, blocker, and next action. +Use `--append-notes`, for example: + +```sh +bd update '' --append-notes="Validated …; evidence …; next …" --actor='' --sandbox --json +``` + +This is an append. Do not use the replacing `--notes` field as the default +checkpoint. If nothing material changed, do not write a redundant note just to +meet a quota. Avoid credentials, raw logs, and claims unsupported by evidence. + +## Capture reusable knowledge selectively + +Task details and short-lived failures belong with the issue. Search existing +memory before recording a validated lesson that will likely help elsewhere. +Include its scope and evidence, use the actual executor actor for memory writes, +and amend stale knowledge intentionally. Do not save every task outcome as +global memory. + +## Validate and hand off + +Check the issue's acceptance conditions, run authorized relevant validation, +and record exact results. If a parent or human owns acceptance, return the +evidence without closing for them. A delegate returns attempts, results, +rationale, validation, blockers, next action, and useful record references; the +parent reconciles before continuing. If the task was an exact assignment, stop +there. Explicitly authorized batches may continue within their stated scope; +new discoveries never expand it. + +When context is held or a required guarantee is missing, stop the affected path +and state the limitation. Do not switch to a manual prime, unregistered tool, +automatic setup, sync, or permission change to complete it. + +## Planning and independently owned review + +Follow project-owned planning and review policy. Track substantive multi-session +or delegated planning in one bounded planning item, separate from implementation; +a quick draft need not get its own ticket. Give substantive independently owned +review one bounded review item linked to planning and the exact candidate. Reuse +the same review item through corrections and rechecks. Record actual reviewer +actor/session, verdict, findings and evidence without retroactive claims. +Planning/review closure needs its own evidence and owner acceptance; readiness +alone never authorizes dispatch, implementation, release or deployment. diff --git a/assets/oc-beads/resources/WORKTREES.md b/assets/oc-beads/resources/WORKTREES.md new file mode 100644 index 0000000..ae08489 --- /dev/null +++ b/assets/oc-beads/resources/WORKTREES.md @@ -0,0 +1,19 @@ +# Worktree routing + +> Adapted from [Beads marketplace-main `WORKTREES.md`](https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/WORKTREES.md). + +A worktree can isolate code changes while retaining a repository's shared Beads +database. The installed CLI documents common-directory discovery for its +worktree command. Do not assume that every unrelated checkout, redirected +workspace, or custom route shares the same store. + +Use a worktree only when isolation helps and its creation is authorized. Before +changing issues, confirm which repository and Beads location the CLI resolved. +Keep the assignment and explicit executor actor the same across the worktree; +Git identity is not a Beads actor. Do not initialize a separate store, change +routes, or copy database state to make a task appear ready. + +Use `bd worktree --help` and relevant subcommand help for current commands and +safety checks. Inspect local changes before removal, preserve work that is not +yours, and remove only a worktree whose cleanup is authorized. This resource +does not create or clean up a worktree automatically. diff --git a/bun.lock b/bun.lock index 4bc8822..13966f7 100644 --- a/bun.lock +++ b/bun.lock @@ -5,13 +5,13 @@ "": { "name": "@snarkipus/opencode-beads", "devDependencies": { - "@opencode/plugin": "2.0.26", + "@opencode/plugin": "2.1.1", "@types/bun": "1.3.4", "@types/node": "^22.0.0", "typescript": "5.9.3", }, "peerDependencies": { - "@opencode/plugin": ">=2.0.26 <3", + "@opencode/plugin": ">=2.1.1 <2.2", }, "optionalPeers": [ "@opencode/plugin", @@ -69,11 +69,11 @@ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], - "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-rc.112", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "^4.0.0-rc.112" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-OTRv1DxTHUmnakgJ6XVM8wVgF1KgZH4UXnOemwSLUwUjXO+RCikzF8oR/rlVmOGq81KtQzj1URM4M4nchlQOuQ=="], + "@effect/opentelemetry": ["@effect/opentelemetry@4.0.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "^4.0.1" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-MZ7M1a1dxMqTqeNwIGSECjO7RrReEgeEwHo8BnbOqP+8IfVpdiJi/iPCmkFVWlAxanMl4qhsq69e0Hkul9t+bA=="], - "@effect/platform-node": ["@effect/platform-node@4.0.0-rc.112", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.112", "mime": "^4.1.0", "undici": "^8.10.0" }, "peerDependencies": { "effect": "^4.0.0-rc.112", "redis": ">=5.0.0 <7.0.0" } }, "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ=="], + "@effect/platform-node": ["@effect/platform-node@4.0.1", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.1", "undici": "^8.11.2" }, "peerDependencies": { "effect": "^4.0.1", "redis": ">=5.0.0 <7.0.0" } }, "sha512-FT0Fu5LO0/XP9zBpj0HqrtJNLluK4StNKqf6TSulUsjp8FPBsDVXGJiDuyoN5J2yVMT7tgvhxSwaDcObWVRIaQ=="], - "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-rc.112", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.3" }, "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ=="], + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.1", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.22.0" }, "peerDependencies": { "effect": "^4.0.1" } }, "sha512-VanaejudEhKJ4mpmqnEzdncVUhN6sGSO9ZDND54mRxAlZxj+4dYAWjDTYAy9o4tJo9aALeP2U8cVcOFzEdSeLg=="], "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], @@ -83,18 +83,6 @@ "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], - "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], - - "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], - - "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="], - - "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="], - - "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="], - - "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], - "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], "@npmcli/arborist": ["@npmcli/arborist@9.4.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" } }, "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA=="], @@ -125,17 +113,17 @@ "@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "dependencies": { "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", "proc-log": "^6.0.0" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="], - "@opencode/ai": ["@opencode/ai@2.0.26", "", { "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", "@opencode/schema": "2.0.26", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-rc.112", "google-auth-library": "10.5.0" } }, "sha512-CB+k3Ed6mtat2Or/6YZiXSPrI8q02NfECEKKO9HNeYLKFG3UrbIxrhnaRLbA9YMQp9mKOA1qWIWXJDnkxdVjMQ=="], + "@opencode/ai": ["@opencode/ai@2.1.1", "", { "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", "@opencode/schema": "2.1.1", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.1", "google-auth-library": "10.5.0" } }, "sha512-sfL8kr53p+P3NeEPOZPalMOq47rR8KROywGzemsZdqMO8MwF+nBQgi7AGolANXKL95n3M8Nnci19ffGbxZ4N5Q=="], - "@opencode/client": ["@opencode/client@2.0.26", "", { "dependencies": { "@opencode/protocol": "2.0.26", "@opencode/schema": "2.0.26" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-0uqhXq6DJYjas3mc4roTyWAmhJpyO2gJaMcenoiA22RXJSOSIwPFho5K4qGmYDJGd5Fvvswh++Nr/wfjaHAYyg=="], + "@opencode/client": ["@opencode/client@2.1.1", "", { "dependencies": { "@opencode/protocol": "2.1.1", "@opencode/schema": "2.1.1" }, "peerDependencies": { "effect": "4.0.1", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-0/JTWitCw2gkr4o+ra+76UeeSpsq7mTFUUtKMPMt4aFu+rYgZw9Lkw4EJgxYK7p3sefiiKBA8KnX0a3LbxIjkw=="], - "@opencode/plugin": ["@opencode/plugin@2.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode/ai": "2.0.26", "@opencode/client": "2.0.26", "@opencode/protocol": "2.0.26", "@opencode/schema": "2.0.26", "@opencode/util": "2.0.26", "effect": "4.0.0-rc.112" }, "peerDependencies": { "@opencode/theme": "2.0.26", "@opentui/core": ">=0.5.17", "@opentui/solid": ">=0.5.17", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-8fu/WokR5hxGjclLQJKL0PAgsMEfh81MHnUBsJuhcnYUiTJ2ZQPLqSYAhzoYEQu/Vj4M65D3Xx7QS0Q9b9Z4fw=="], + "@opencode/plugin": ["@opencode/plugin@2.1.1", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode/ai": "2.1.1", "@opencode/client": "2.1.1", "@opencode/protocol": "2.1.1", "@opencode/schema": "2.1.1", "@opencode/util": "2.1.1", "effect": "4.0.1" }, "peerDependencies": { "@opencode/theme": "2.1.1", "@opentui/core": ">=0.6.6", "@opentui/solid": ">=0.6.6", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-wvbvx/GeFNL7lb2Aj0IlXxPN01J6L1qJ2IbkUE5FaLmnC78aLjibzFP+wuzb28yUAM65dYw7WJRvHv+5GZRL8Q=="], - "@opencode/protocol": ["@opencode/protocol@2.0.26", "", { "dependencies": { "@opencode/schema": "2.0.26", "effect": "4.0.0-rc.112" } }, "sha512-Q+kbu0zTneh0RC4jhiuB19WeP8et9dWXGJVjb5EbUdJ8E1j5Eh0lAyqLXlM2tzTxfMfK3gos9ss6mxMVsvDFIg=="], + "@opencode/protocol": ["@opencode/protocol@2.1.1", "", { "dependencies": { "@opencode/schema": "2.1.1", "effect": "4.0.1" } }, "sha512-rFsaileXP/W7GNpt2C8GcF8WtLYzyoiaq/xMLkHfAk8xnH5jMNaWJL8DRawUM2FvCaI6H6SKeX4gw6eDGhyl6A=="], - "@opencode/schema": ["@opencode/schema@2.0.26", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-DZ09xXCTkTI3Y9VzuQHsm5AMGh67EqkUM13lC0LGKvD12RAYaVmRX5lDv5AnD3OkmUmPPWLYWjicT18CDloy6Q=="], + "@opencode/schema": ["@opencode/schema@2.1.1", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.1" } }, "sha512-Qh8EmvNeE/4UzSbkvvwQ2DHOOFZMk8dteeIsX//+qMhjTXJ+xGoWo5bsoauv6pYnqaQYtEwwpMaqk2N0rO4QaA=="], - "@opencode/util": ["@opencode/util@2.0.26", "", { "dependencies": { "@effect/opentelemetry": "4.0.0-rc.112", "@effect/platform-node": "4.0.0-rc.112", "@effect/platform-node-shared": "4.0.0-rc.112", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/context-async-hooks": "2.8.0", "@opentelemetry/exporter-trace-otlp-http": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0", "@opentelemetry/sdk-trace-node": "2.8.0", "cross-spawn": "7.0.6", "effect": "4.0.0-rc.112", "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "open": "11.0.4", "pacote": "21.5.1" } }, "sha512-RebqfdFEEby7IFA7agajYvCpeW4VoaPkKu/ZMhBXsEYRnpz2NMyoI7zapFvvDJdYl4B0/kPnmt5vUVuqRWm0lQ=="], + "@opencode/util": ["@opencode/util@2.1.1", "", { "dependencies": { "@effect/opentelemetry": "4.0.1", "@effect/platform-node": "4.0.1", "@effect/platform-node-shared": "4.0.1", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/context-async-hooks": "2.8.0", "@opentelemetry/exporter-trace-otlp-http": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0", "@opentelemetry/sdk-trace-node": "2.8.0", "cross-spawn": "7.0.6", "effect": "4.0.1", "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "open": "11.0.4", "pacote": "21.5.1" } }, "sha512-Hi6TDr355iklKhhI+wqhTWfPT4mMdFpjo5yB5PGJfdW+AJQZDdhkhdLEL5khXS3Jgf7aXATYyvBesIEXrC/ktg=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -279,13 +267,11 @@ "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - "effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + "effect": ["effect@4.0.1", "", {}, "sha512-b1VlQG9g8fwxE5QnIZuPoOP/0MsqHJSRKxUijjoM80E5q95FXrX5yWtY5XI8G+GJsBQAVSDbLS458z9a++uVvw=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -295,8 +281,6 @@ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - "fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], @@ -373,8 +357,6 @@ "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], - "mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="], - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], @@ -397,10 +379,6 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "msgpackr": ["msgpackr@2.1.0", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ=="], - - "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], - "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], @@ -409,8 +387,6 @@ "node-gyp": ["node-gyp@12.4.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="], - "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], - "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], "npm-bundled": ["npm-bundled@5.0.0", "", { "dependencies": { "npm-normalize-package-bin": "^5.0.0" } }, "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw=="], @@ -455,8 +431,6 @@ "promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="], - "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], - "read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="], "redis": ["redis@6.3.0", "", { "dependencies": { "@redis/bloom": "6.3.0", "@redis/client": "6.3.0", "@redis/json": "6.3.0", "@redis/search": "6.3.0", "@redis/time-series": "6.3.0" } }, "sha512-XFQbPie1lGpKeUZ8ySYY43yLnQy/iIQtHNvIr/1XdaABpb96rUSdm3Mfl+98FEVbv9ZJIzINMno5s0Nh8E5LEw=="], diff --git a/docs/beads-artifact-policy.md b/docs/beads-artifact-policy.md index 56a9545..0de5f3e 100644 --- a/docs/beads-artifact-policy.md +++ b/docs/beads-artifact-policy.md @@ -1,6 +1,10 @@ # Beads Artifact Policy -Status: revised for 1.0.0 by `opencode-beads-w5y`. The package vendors no upstream Beads artifacts. Earlier revisions were the OpenCode V2 port (`opencode-beads-2zs`), the 0.9.0 contract (`opencode-beads-98l`), and the retired 0.8.0 companion lifecycle decision. +Status: current published stable policy and historical rationale remain below; the unpublished beta/next candidate includes project-owned `assets/oc-beads/`, 30 native commands and 17 explicit installed links (15 corpus resources, CODE_MODE, ADR). The [installed/model continuation](opencode-installed-model-continuation-vuy9.md) records passing selected 6.2 worker evidence and 6.3 reconciliation on the unchanged accepted packed artifact `e952532140b44f812e2660425ec485d3bbd474962dd424ed03d70108593b259f`. Actual installed resource/permission/cleanup observations and genuine Sol/high semantics retain distinct evidence classifications; 73 release/74 marketplace-main dispositions and 19 requirements are mapped. Parent acceptance is complete for all 16 implementation tasks; ordinary typed activation stays withheld and internal qualification remains scratch-only. The package vendors no upstream Beads artifacts. It ships project-owned adaptations rather than an upstream executable or host-specific integration. Older stable exclusion/one-bead descriptions below retain their historical published scope; no source/release/baseline/operator change is authorized. + +Historical policy summary, preserved for release provenance: + +Status: current stable implementation policy, under review in the [full integration decision packet](claude-code-integration-analysis.md). Owner selections for an opt-in beta/next redesign are recorded; task 2.1 supplied the accepted guidance assets and the tasks 2.2/3.3 candidate adds package inclusion and skill registration. All 30 command definitions now register through native precedence; runtime qualification remains unexecuted. Revised for 1.0.0 by `opencode-beads-w5y`. The package vendors no upstream Beads artifacts. The current source package includes the project-owned adaptations under `assets/oc-beads/`; the published stable artifact remains separate. Earlier revisions were the OpenCode V2 port (`opencode-beads-2zs`), the 0.9.0 contract (`opencode-beads-98l`), and the retired 0.8.0 companion lifecycle decision. ## Evidence @@ -8,7 +12,7 @@ Upstream host integrations at Beads `v1.3.1` (commit `c1c4b642ac1c08d8c828007a1c | Host | Integration source | Context lifecycle | Commands | Task agent | | --- | --- | --- | --- | --- | -| Claude Code | `plugins/beads/.claude-plugin/plugin.json` | `SessionStart` and `PreCompact` hooks run full `bd prime` | `skills/beads/commands/*.md` | `plugins/beads/agents/task-agent.md` (`@task-agent`), discovered by convention: an autonomous loop that finds, claims, and completes ready work through MCP-style tool names | +| Claude Code | `plugins/beads/.claude-plugin/plugin.json` | `SessionStart` and `PreCompact` hooks run plain `bd prime` (MCP-sensitive) | `skills/beads/commands/*.md` | `plugins/beads/agents/task-agent.md` (`@task-agent`), discovered by convention: an autonomous loop that finds, claims, and completes ready work through MCP-style tool names | | Codex | `plugins/beads/.codex-plugin/plugin.json` and `.codex-plugin/hooks/hooks.json` | `SessionStart`, `PreCompact`, `PostCompact`, and `UserPromptSubmit` hooks | none | none | | OpenCode (this adapter) | `@snarkipus/opencode-beads` | Session context hook attaches full `bd prime` to each request, behind the schema migration guard | none | Project-owned `beads-task-agent`: one bead per invocation | @@ -23,7 +27,7 @@ Upstream host integrations at Beads `v1.3.1` (commit `c1c4b642ac1c08d8c828007a1c | Upstream artifact | Policy | Reason | | --- | --- | --- | -| `skills/beads/commands/*.md` | Retired in 1.0.0 (`opencode-beads-2zs.4`) | Claude Code command templates, not part of the native OpenCode or Codex install. They largely wrap single `bd` subcommands and duplicate `bd prime` plus the canonical `.agents/skills/beads` skill, which OpenCode V2 discovers natively. V2 commands are executable code rather than templates. | +| `skills/beads/commands/*.md` | Retired in 1.0.0 (`opencode-beads-2zs.4`) | Claude Code command templates, not part of the native OpenCode or Codex install. They largely wrap single `bd` subcommands and duplicate `bd prime` plus the canonical `.agents/skills/beads` skill, which OpenCode V2 discovers natively. V2 plugin command registration uses executable code, but native Markdown and JSON prompt commands are also supported. Retirement was a package policy choice, not a limitation of all V2 commands; see the [full integration analysis](claude-code-integration-analysis.md). | | `agents/task-agent.md` | Retired in 1.0.0 (`opencode-beads-w5y`) | A Claude Code-only artifact whose autonomous multi-task loop this adapter deliberately does not adopt. Before retirement, its body was never executed: the runtime prompt was already a project-owned bounded OpenCode adaptation, and only the upstream description string reached users, misdescribing the one-bead worker. The task agent's prompt and description now live in `src/guidance.ts`. It does not execute upstream's autonomous multi-task loop. | | `skills/beads/SKILL.md`, `skills/beads/adr/`, and `skills/beads/resources/` | Excluded | `bd init` owns per-project initialization. Its automatic Codex project integration creates the canonical shared skill at `.agents/skills/beads`, which OpenCode discovers. This package does not fork, install, update, or remove that skill. | | Claude/Codex manifests and hooks | Excluded | `.claude-plugin` and `.codex-plugin` formats and lifecycle hooks are not OpenCode plugin registration formats. This adapter implements the equivalent lifecycle through OpenCode V2's session context hook and session events. | @@ -51,7 +55,7 @@ The task agent is registered under an explicit name through OpenCode's agent tra ## `bd prime` boundary -The plugin runs the active project's installed `bd prime --full` once per session (`--full` overrides bd's MCP detection, which reads Claude Code's settings), attaches it to each model request for eligible primary agents and `beads-task-agent`, and refreshes it after compaction. This mirrors the behavioral contract of the upstream Claude Code and Codex integrations through OpenCode V2's session context hook and `session.compaction.ended` event rather than host-specific hook files. Before running prime, a schema migration guard confirms that `bd` is 1.3.0 or later and that `.beads/.local_version` is current; otherwise it withholds prime with an agent-facing notice so the plugin never applies an irreversible store migration unattended. Full prime supplies the canonical current workflow, command guidance, and persistent memories. The shared injected layer adds only OpenCode CLI safety and primary-agent delegation. Known regular subagents remain excluded; agent lookup failures fail open for context injection so unknown custom primary agents remain compatible. +The plugin runs the active project's installed `bd prime --full` once per session (`--full` overrides bd's MCP detection, which reads Claude Code's settings), attaches it to each model request for eligible primary agents and `beads-task-agent`, and refreshes it after compaction. This mirrors the behavioral contract of the upstream Claude Code and Codex integrations through OpenCode V2's session context hook and `session.compaction.ended` event rather than host-specific hook files. Before running prime, a schema migration guard confirms that `bd` is 1.3.1 or later and that `.beads/.local_version` is current; otherwise it withholds prime with an agent-facing notice so the plugin never applies an irreversible store migration unattended. Full prime supplies the canonical current workflow, command guidance, and persistent memories. The shared injected layer adds only OpenCode CLI safety and primary-agent delegation. Known regular subagents remain excluded; agent lookup failures fail open for context injection so unknown custom primary agents remain compatible. This is context-preserving atomic delegation: the primary OpenCode thread retains planning context, decisions, and control while `beads-task-agent` processes exactly one Bead per invocation. In the absence of an external orchestrator, that boundary intentionally favors Bead atomicity and context preservation over broad autonomous execution. An orchestrator may repeat the one-Bead cycle, but should preserve the worker boundary. @@ -71,3 +75,39 @@ Any future upstream artifact proposed for inclusion must first show: - that it does not duplicate `bd prime` or the canonical skill; - deterministic validation, package-content tests, and failure behavior; - a documented owner and removal or upgrade path. + +## Selected beta/next source candidate + +The [owner record](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/owner-decisions.md) selects package-owned `oc-beads` skill/resources, `oc-beads:` workflows, N1 continuity tools and scope-bounded continuation on OpenCode `>=2.1.1 <2.2`. This is an opt-in redesign; the current implementation policies above remain historical/current-stable descriptions, not prohibitions against the selected future assets. No upstream shared skill or user file is overwritten, and bd remains executable/data owner. Delivery is complete through npm with native registration/disposal; no manual rich-skill installation. + +Task 2.1 owns the source assets at `assets/oc-beads/`: one `SKILL.md`, the 15 selected marketplace-main resources, and the adapted ADR-0001 rationale. These are project-owned adaptations of the inventory's pinned marketplace-main commit `4a67d0d0288c1b4ee23e17fc896f47616931ba2d`; the [inventory](claude-code-integration-inventory.json) records each selected mapping and disposition. `TROUBLESHOOTING.md` is a curated replacement that preserves the support topic while removing unsafe stale repair advice. The skill links all 16 supporting files explicitly because host auto-listing is limited to ten. Advanced files stay on demand. + +The assets preserve Beads CLI ownership and use `bd prime` and installed command help as dynamic syntax references. Examples of writes carry an explicit executor actor. The agent-bead resource documents that `bd agent` and `bd slot` are not exposed by the inspected installed CLI; ordinary issue claims, assignees, and heartbeat leases are related supported conventions, not a complete substitute for those APIs. Shared `.agents/skills/beads` remains Beads-owned and must not be overwritten. + +The tasks 2.2/3.3 source candidate includes these assets in npm and registers `oc-beads` through a public skill transform with per-rebuild candidate collision checks. No initial list gate permanently suppresses a removed skill customization. Package smoke checks both exports and all 16 installed-base links/resource bytes, invokes actual registration callbacks and exercises all 30 native command bodies with attachments/delivery. It never copies assets into user repositories, settings or upstream caches. One ordinary transform registers the 29 selected stems plus status; initial command collisions are diagnostic-only. Ordinary user configuration/file commands win through later native transforms and removing an override restores the package definition. Same-name other-plugin conflicts follow normal native order. Status reports effective names/descriptions, observable mismatches, missing entries and lookup failures; public owner identity and identical-description overrides are unobservable, and matching descriptions do not prove ownership. See [native registration and diagnostics](opencode-sdk-contract.md#tasks-2233-source-candidate-native-registration-and-diagnostics). No initialization, synchronization or permission changes occur. + +Completed marketplace-main source comparisons succeed whether content changed or not. Ordinary development changes are informational evidence with immutable review links, not drift warning/error annotations, product CI failures, mandatory immediate review/adoption or release gates. A lagging tagged CLI release does not make main changes actionable compatibility failures; changes requiring unreleased features may be deferred until verified inclusion in a supported release. Retrieval/comparison failure is unavailable evidence, never successful no change; it may fail a separate advisory collector but cannot gate product/release checks. Stable tagged CLI compatibility tests remain independent and retain real failure semantics. Reuse the scheduled pattern only under workflow-edit authority. No GitHub issues, issue-write permissions, automatic copying, baseline advancement or releases. Human review selects subsequent Beads execution work. This planning reconciliation does not edit the workflow or package. + +SKILL.md explicitly links all 15 resources plus ADR; host auto-listing of ten files is insufficient. Package fixtures verify installed-base links, and live acceptance honors external-directory ask/deny without concealing unreachable guidance. Ordinary configuration/file command precedence is preserved across rebuilds; competing plugin transforms follow native order, with no universal collision guarantee. Pinned package/config ordering is distinct from the original reviewer's unpinned released-cache suffix-reactivation evidence; neither is live qualification. Before the prerelease compatibility change reaches stable main, separately authorized release work must settle a stable 1.0.x maintenance branch strategy; no branch/release action or present source-work blocker is implied. + +Operative host selection now targets exact 2.1.1; the [contract delta](opencode-2.1.1-contract-delta.md) preserves historical evidence and separates source admission from unexecuted runtime acceptance. This candidate changes source/package registration only; shared skills and operator runtime remain untouched. + +### Task 5.1 accepted source collector — usage and evidence + +Parent accepted the source/script/tests after the [Opus READY review](reviews/opus-source-collector-review-vuy7.md), verified all reviewed file hashes unchanged, and independently passed 166 tests, typecheck, strict global OpenSpec, pinned source/corpus validation and whitespace checks. The parent also independently repeated the live comparison below: five upstream edits still returned informational changed/exit 0 with no drift annotations. This is source acceptance only; scheduled/CI wiring is not implemented or activated. The exact published source handle is recorded in `opencode-beads-vuy.7`. + +Run the read-only [collector](../scripts/check-beads-integration.ts) locally: + +```sh +npm exec --yes --package=bun@1.3.14 -- bun scripts/check-beads-integration.ts +``` + +It reads `marketplace_main` in the existing [inventory](claude-code-integration-inventory.json), resolves the repository's default branch once to an immutable candidate, and retrieves complete recursive Git trees for both commits. Selection includes every blob under `plugins/beads/` and `internal/templates/skills/beads/`, plus each inventory path outside those roots. Baseline membership/blob IDs must match the reviewed inventory. Comparison uses upstream Git blob IDs and modes, never local adaptation bytes. Output sorts paths and includes both commits, source links and a direct comparison link. Unique removed/added pairs with identical blob ID and mode are reported as exact renames; ambiguous or edited moves remain additions/deletions. + +Completed comparisons emit informational JSON (`kind: completed`, `level: info`, `channel: development-source`, `status: changed` or `unchanged`) and exit **0**. Incomplete/truncated trees, missing/malformed required data, HTTP/network failures or an inventory mismatch emit `kind: unavailable` and exit **2**, without a successful comparison status. Requests have a 20-second timeout each; optional existing `GH_TOKEN` or `GITHUB_TOKEN` authenticates read-only GitHub API requests without printing credentials. Truncated trees are unavailable evidence rather than partial comparisons. + +This collector has no workflow/CI wiring and is not a product, runtime compatibility or release gate. Future authorized wiring must isolate its failures from those gates. Stable tagged CLI contract tests and their actual failures remain independent; the collector does not query tags or establish release inclusion. There is no stored candidate state, source copying, automatic adoption, baseline advancement option, version change or issue/PR creation. Human review may accept an adaptation, accept no local change with rationale, or defer adoption until verified inclusion in a supported release. Only an explicit reviewed inventory revision can change the baseline; detection grants no authority to do so. + +Fixture evidence in [upstream-review.test.ts](../tests/upstream-review.test.ts) exercises the actual runner and subprocess entrypoint: unchanged and changed MAIN both exit 0; changed MAIN with the same independent tagged CLI result stays informational with no warning/error annotations or mutation; a repeated candidate emits identical output; truncated retrieval exits 2 as unavailable. Fixtures also cover complete membership, adds/deletes/edits, exact renames, support paths, unrelated paths, malformed/error responses, explicit reviewed inventory input and refusal of a blind advancement argument. They do not qualify a live CLI or host. + +Separate read-only live evidence on 2026-10-21: [reviewed baseline `4a67d0d0288c1b4ee23e17fc896f47616931ba2d`](https://github.com/gastownhall/beads/tree/4a67d0d0288c1b4ee23e17fc896f47616931ba2d) versus [resolved MAIN `882317770349f3c2f61e603b5c1e74030354c955`](https://github.com/gastownhall/beads/tree/882317770349f3c2f61e603b5c1e74030354c955) completed with **exit 0**, informational `changed`, and 74 selected files at each commit. Five edited support paths were reported: `cmd/bd/init.go`, `cmd/bd/init_agent.go`, `cmd/bd/init_embedded_test.go`, `cmd/bd/setup/claude.go`, and `cmd/bd/setup/claude_test.go`. The [exact source comparison](https://github.com/gastownhall/beads/compare/4a67d0d0288c1b4ee23e17fc896f47616931ba2d..882317770349f3c2f61e603b5c1e74030354c955) is source information only; no tagged-release behavior, local adoption or baseline advancement was established. Parent source acceptance is recorded above; no scheduled/CI activation or tagged CLI compatibility outcome is implied. diff --git a/docs/claude-code-integration-analysis.md b/docs/claude-code-integration-analysis.md new file mode 100644 index 0000000..d1c7304 --- /dev/null +++ b/docs/claude-code-integration-analysis.md @@ -0,0 +1,187 @@ +# Full Beads integration experience: analysis and owner decision packet + +Status: historical evidence collected 2026-10-17 and reconciled 2026-10-18 remains below. Current 2026-10-21 [installed/model evidence and 6.3 reconciliation](opencode-installed-model-continuation-vuy9.md) pass the selected bounded 6.2 worker evaluation after accepted source/native/Stop prerequisites. Exact2.1.1/Beads 1.3.1, unchanged accepted package and genuine OpenAI Sol/high substantiate A–D semantics; actual corrected cleanup/17 resource access are reused. All 19normative requirements and every 73 release/74 marketplace-main disposition plus CODE_MODE are mapped without claiming all admin operations or comparative Claude/model quality. Parent acceptance is complete for all 16 implementation tasks; release and activation remain separately gated. Ordinary typed activation is withheld and its internal installed-source bootstrap remains scratch-only. Same-provider/model native replay is observed; cross-route migration, multi-hour reliability and stronger immediate location-reload revocation are not certified. Historical failed probes/reviews remain unchanged. No source/operator/publication authority is implied. + +## Outcome and evidence boundaries + +Matt reports better ongoing task notes and more appropriate `bd remember` use with Claude Code's Beads integration than with OpenCode and OpenAI models. This is an observed user experience, not controlled evidence that the host, provider, model, or prompt caused it. The objective is recovery of attempted approaches, learning, rationale, blockers, and the next action across sessions without repeated user prompting. More writes alone are not success. + +This review enumerates the full shipped plugin subtree, shared skill template, marketplace entry, and the implementation paths that connect them. Static source evidence establishes availability and intended wiring, not that a model loaded a resource, followed a prompt, or successfully invoked a stale command. No Claude/OpenCode inference trial or Beads command was run. Examples of Beads commands below describe future behavior only. + +The [native architecture assessment](opencode-native-parity-assessment.md) completes the behavior-to-mechanism investigation: released V2 call sites, experimental candidates, selected N1 bundle, historical 2.0.26 alternative N0 and explicit qualification gaps. It refines the delivery recommendation below without replacing the historical Claude corpus identity. + +### Selected marketplace-main reference + +Read-only GitHub default-branch/commit/tree retrieval on 2026-10-18 resolved `main` to [`4a67d0d0288c1b4ee23e17fc896f47616931ba2d`](https://github.com/gastownhall/beads/tree/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads), equal to the recorded installed revision. The same declared roots/support paths contain **74 files**: 57 plugin files, 2 shared-template files and 15 support files; 29 commands, 15 resources and one ADR remain. All 57 plugin bytes match the installed cache. The separate `marketplace_main` inventory records exact current blob/hash identities and per-member delivery, reason and task mapping. The original 73 release identities are preserved. + +There is one addition (`plugins/beads/BUILD.bazel`, build-only evidence), no deletion and 16 modifications. Plugin manifest versions differ (1.3.1 release versus 1.3.0 installed/main); versions/dates do not establish ancestry. Five guidance files switch examples from replacement `--notes` to `--append-notes`: skill README and WORKFLOWS, PATTERNS, INTEGRATION_PATTERNS, ISSUE_CREATION. ASYNC_GATES adds cross-repository `--repo` guidance. Unchanged rich skill, agent, commands and ADR still need native adaptation, including stale MCP and TodoWrite examples. See [local reference](claude-code-local-reference.md) for the complete categorized delta. + +Current support source also changes prime disable/routing/project-memory guidance and memory write/commit status, budget refusal and empty-value recall. These are **source differences**, not proof the installed CLI has those semantics. N1 must preserve failed/uncertain/landed-but-uncommitted states and qualify supported released CLI output; no source snapshot implicitly authorizes a CLI upgrade. + +### Source pins and scope + +| Source | Exact reference | Evidence / limit | +| --- | --- | --- | +| This repository | `c5c29f2e0cca98826658cc09b7806eb95afca360`, main at start | Historical first-pass baseline. The earlier native assessment started at `7e710b6e5608bb8aef73fccadbd08e761a77a652`; this reconciliation starts at `3dc2ea327ed490ceb9cf0b95c31af77631613287`; existing package and parent changes are preserved. Prior live results remain historical, not rerun here. | +| Historical Beads stable pin | [v1.3.1 release](https://github.com/gastownhall/beads/releases/tag/v1.3.1), published 2026-09-30; annotated tag resolves to `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c` | Verified with GitHub release/ref/tag APIs; historical inventory source links use this commit. | +| OpenCode V2 source/docs | [`8f7b0120eb6b4aa3da1f82b1a9094a9fb4db2d06`](https://github.com/anomalyco/opencode/tree/8f7b0120eb6b4aa3da1f82b1a9094a9fb4db2d06); `packages/plugin/package.json` says 2.0.32 | Development snapshot, **not the released 2.0.32 source**. The native assessment corrects provenance: released tag is `843a6c514e606b436a7fe713422ace0a93ad3d61`; npm latest was 2.0.32 when the native assessment was recorded. Public, experimental, development-only and unknown-runtime evidence are separated there. No new runtime qualification of 2.0.26. | +| Original OpenCode adapter | [`a508c1205a04516490d9d1078d5a4e788cd6553b`](https://github.com/joshuadavidthomas/opencode-beads/tree/a508c1205a04516490d9d1078d5a4e788cd6553b) | V1 implementation and vendor tree; not evidence of V2 runtime compatibility. | +| OpenSpec | Installed 1.14.2; [official 1.14.3 release](https://github.com/Fission-AI/OpenSpec/releases/tag/v1.14.3), published 2026-10-17 | New release fixes zero-column validation hangs, linked specs, glob bounds and a dependency issue. Installed version is suitable for ordinary local spec-driven files with explicit noninteractive validation and path checks. No upgrade, symlinks, custom schema or setup regeneration needed. | + +[Machine-readable inventory](claude-code-integration-inventory.json): **73 files**, including **56** files under `plugins/beads/`, **2** shared-template files, and **15** explicit supporting sources. Within that inventory: **29 commands**, **15 resources**, **1 ADR**, **2 skill bodies**. Recursive tree API was not truncated. Each entry records Git blob identity, SHA-256, commit link, historical recommendation and selected engineering disposition. Coverage is bounded to these declared roots and support paths; it is not a claim to audit the whole Beads repository. Resource review used systematic heading/trigger enumeration plus the behavior-bearing passages; no resource's CLI examples are certified wholesale. + +## Installation paths and actual reachability + +Subsequent local installation evidence is recorded in [Installed Claude reference integration](claude-code-local-reference.md): Claude recognizes the rich skill, all command entries, agent and hooks. The installed marketplace checkout differs from the pinned release corpus; this does not replace its inventory or establish live model behavior. Future comparisons must align or explicitly distinguish these revisions. The OpenCode packaging objective is a complete owner-selected integration from its npm package, not a hidden separate skill installer. + +| ID / surface | Shipped/runtime connection | Selected adaptation; dependencies and risks | +| --- | --- | --- | +| F01: setup lifecycle | [`setup/claude.go`](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/setup/claude.go) installs `SessionStart` → `bd prime --hook-json`, minimal instruction pointer, project/global/check/remove and legacy-hook cleanup. Comments identify startup/resume/clear/compact. It removes old PreCompact prime hooks. Recognized enabled `beads@…` plugin skips duplicate setup hook registration. | Adapt lifecycle through awaited native V2 context/compaction hooks and observational events (M01/M03/M04); events cannot delay compaction. Keep setup diagnostics and removal understandable. Do not translate Claude hook JSON into OpenCode system text. Detection reduces duplicates but does not prove arbitrary combined installations are duplicate-free. | +| F02: initialization | [`init.go`](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/init.go) creates store/config/version state and integrations; normal non-bare, non-stealth, non-skip-agents path installs Claude, Codex and Cursor integrations. Codex setup writes the shared skill. Init can install Git hooks and stage/commit generated files. | Retain upstream ownership of initialization; document its real side effects. Neither an absent store nor a failed hook authorizes initialization, migration or repair. No initialization occurs in this plan. | +| F03: marketplace | [marketplace entry](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/.claude-plugin/marketplace.json) points to `plugins/beads`; [Claude manifest](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/.claude-plugin/plugin.json) explicitly registers skills/commands and plain `bd prime` on SessionStart and PreCompact. | Adapt the full discoverable experience, not the manifest format. Plain prime is not guaranteed full output: MCP detection can shorten it. Marketplace PreCompact differs from setup's removal of that hook. No evidence here that both hook outputs reach every supported Claude runtime identically. | +| F04: rich skill | [plugin skill](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/SKILL.md) advertises multi-session/dependency/recovery triggers, says to add notes during work, links every resource and ADR. Body/resources load progressively, not all at startup. Manual skill-directory copying is separately documented in its README; that alone does not install marketplace hooks, commands or agent. | Adapt cognitive guidance and resource navigation through package-owned `oc-beads`. CLI help remains dynamic. More resource files alone do not ensure discovery. | +| F05: lean shared skill | [`internal/templates/skills/beads/SKILL.md`](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/internal/templates/skills/beads/SKILL.md) is the local `.agents/skills/beads/SKILL.md` content. It has broad triggers, prime-first advice and basic task workflow but no resource tree, checkpoint examples or explicit remember/recall trigger guidance. | Preserve bd ownership. Enrich experience via the distinct package-owned `oc-beads` surface, not silent overwrite. Its first-step prime instruction and later conditional manual-prime rule can provoke duplicate calls. | +| F06: command catalog | All 29 files are manifest-addressed. Two lack YAML. Several bodies require MCP. [Integration docs](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/docs/integrations/claude-code-plugin.md) explicitly say no MCP server is bundled. | Review every command below; adapt supported workflows instead of blindly loading bodies. Distinguish registration, discovery and successful execution. Exact Claude discovery behavior for malformed/metadata-free templates remains untested. | +| F07: autonomous task agent | [`agents/task-agent.md`](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/agents/task-agent.md) is a convention-discovered Claude agent (also covered by layout test/docs), iterating ready/claim/execute/discover/close. It names MCP tools despite no bundled MCP server. | Adapt native delegation, task selection, validation and knowledge return. Use the selected scope-bounded continuation contract. A bounded worker can be an extension mode, not an unexplained substitute for the base experience. MCP-only text is not directly executable in this CLI-only adapter. | +| F08: configuration/approval/errors | Setup preserves settings and supports stealth/global/remove/check. Plugin docs describe optional standalone MCP approval settings, including broad auto-approval. Skill troubleshooting includes init/server/repair advice. | Replace host-specific settings with documented native configuration and existing permission policy. Do not import blanket approvals or automatic repair. Error vs empty result vs withheld migration context must remain distinct. | +| F09: dynamic context/memory | [`prime.go`](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/prime.go) generates workflow and memories; `--full` defeats MCP detection, not custom PRIME.md. Local, redirected-workspace, then global overrides are possible; memories append unless disabled. Memory read failures can emit a diagnostic; some hook errors are suppressed. | Retain full CLI context and authorized custom workflow. Add targeted retrieval when stale/missing; don't infer successful memory retrieval solely from process exit 0. Respect upstream truncation notices without inventing new token quotas. | +| F10: durable writes/retrieval | [`memory.go`](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/memory.go) provides remember, memories, recall and forget. Bare existing key passed to remember recalls; unknown key-like input is refused. | Adapt explicit triggers: task-specific attempts/results/rationale belong with the issue; reusable validated project learning belongs in memory, with provenance/scope. Search before adding overlapping knowledge; use explicit recall for a known key; amend stale knowledge intentionally. Do not invent facts or copy credentials/log dumps. | +| F11: adjacent host payloads | Codex manifest shares skill tree and points to its own four-event hook file; Copilot manifest and `copilot_manifest.go` are separate host packaging. Both `agents/openai.yaml` files are UI metadata, not Claude task-agent execution. | Exclude host-specific payload installation from the OpenCode package recommendation; retain lifecycle comparison evidence. This does not exclude their useful shared behavior. | +| F12: support/maintenance text | Plugin README, skill README, skill CLAUDE.md and ADR explain installation and maintenance. CLAUDE.md says ADRs are maintainer references, not automatically loaded skill content. | Retain rationale/provenance and adapt relevant user docs. Do not inject maintenance files or all resources as runtime instructions. | + +F01–F12 are resolved: F01/F03/F04/F06/F07/F08/F09/F10 adapt through native lifecycle, discovery, scope and continuity; F02/F05 retain external bd ownership; F11 retains evidence but replaces host payloads; F12 retains rationale and adapts user guidance. Each file has a selected mapping, reason and implementation task in the inventory; support source/tests are not copied into the product. + +## Guidance, memory and conflict analysis + +The rich skill/resources provide substantially more advice about *when* to write and *what meaning to preserve* than the lean shared skill. `WORKFLOWS`, `PATTERNS`, `RESUMABILITY` and `INTEGRATION_PATTERNS` cover milestones, current state, key decisions, tested examples, blockers and next steps. They also conflict: some recommend ephemeral TodoWrite while current prime prohibits it; historical release WORKFLOWS asks permission at every handoff and uses replacement notes; marketplace-main changes the examples to `--append-notes` but retains contradictory "overwrite previous notes, not append" prose and the approval loop, while other passages advocate proactive updates. The skill says initialization is human work yet its error table suggests initialization as the fix. Stale version strings and MCP examples coexist with the ADR's canonical-prime principle. These are reasons to adapt, not evidence that all upstream behavior is desirable. + +Recommended base behavior is to checkpoint meaningful transitions: a failed approach with why it failed; a validated discovery; a changed decision; a blocker; delegation return; stopping/compaction when feasible. Save enough of the previous rationale to avoid repeating dead ends. Append-style notes preserve current position and prior evidence; comments can retain discussion. Replacement is deliberate, never the default checkpoint. Issue descriptions/design/acceptance have distinct roles. Ordinary authorized task updates should not need repeated user prompting, but an explicit hold or missing authority still wins. An abrupt process loss cannot guarantee a final write, so periodic meaningful checkpoints matter more than relying on an end hook. + +Reusable memory needs a separate decision: is the finding supported, project-relevant, likely useful beyond this issue, and absent or outdated in existing memory? If so, preserve its evidence/context and applicability, using the supported memory CLI. A temporary failure is usually a task note, not universal knowledge. On recovery, read the active issue and relevant memories before choosing the next action; prime is a workflow/memory entry point, not a complete reconstruction of all issue notes. No fixed write counts, token targets, or automatic memory dump are proposed. + +[Local guidance](../src/guidance.ts) currently requires delegation for multi-command work/graph analysis and allows only single atomic operations in the primary. The worker prompt says one bead, create discoveries but never work them in that invocation, validate, close/block, return concisely. It does not explicitly require returning failed attempts, learning, or memory keys. A concise return can lose precisely what the primary needs to preserve. Recommended handoff includes task state, attempted approach/outcome, evidence, rationale, blockers, next action and durable record references when relevant. The primary should reconcile the result before closure or continuation. Selected scope-bounded execution removes mandatory command-count delegation; direct work and blocking children remain available within authorization. + +[Context](../src/context.ts) caches by session **and audience**, coalesces loads within that key, retries absence/failure after 30 seconds, and refreshes after compaction or a 30-minute idle gap by default. Every request gets cached text. Continuous activity can retain old memory indefinitely; idle refresh is not an exact resume event. Independent child sessions and different audiences may prime separately. [Host](../src/host.ts) invalidates on `session.compaction.ended`, forgets deleted sessions, and adds summarization guidance for IDs/status/follow-ups/blockers/validation. It does not persist semantic progress itself. Targeted fresh reads at recovery or after relevant changes are a simpler candidate than re-running full prime every model step or adding a new memory service. + +[Guard](../src/guard.ts) checks version/location/marker before automatic prime, but it is not a sandbox for subsequent model shell calls. The migration notice says not to run bd; static skill/worker/failure fallback says run prime when context is missing. Future guidance must distinguish **withheld** from **failed** context so manual recovery cannot bypass a hold. `autoMigrate` and permission rules are existing options, not authority to change this repository now. [Instruction minimization](../src/instructions.ts) strips matching full/Codex managed blocks in requests when prime is attached; it does not prove those blocks are semantically redundant (profiles and updated policy can differ). Retain local authority and test non-duplicate constraints before extending minimization. + +### Local tests: what is and is not established + +`tests/context.test.ts` checks mocked cache, audience, retry, idle and invalidation behavior. `tests/host.test.ts` checks hook registration, flags, guard wiring, compaction guidance and cleanup. `tests/guard.test.ts` verifies the limited safe probe sequence and notices. `tests/instructions.test.ts` checks marker transformation and preservation outside blocks. `tests/guidance.test.ts` asserts prompt strings and a prompt length ceiling; it does not show model comprehension or durable note quality. `tests/agents.test.ts` exercises a copied permission matcher, not a complete host sandbox; source explicitly acknowledges wrapper/alias bypass. Historical live observations apply to their recorded 2.0.26 scenarios only. None of these establish fresh-session recall, appropriate remember selection, or full marketplace experience parity. No product tests were run for this planning-only change. + +## Complete resource and ADR review + +The rich SKILL.md links all 15 resource files. They are reachable on demand if the skill is actually installed and selected; mere packaging does not load their contents. Rows retain release-source provenance; dispositions now apply the accepted groups. Every resource ships in adapted form, including advanced on-demand guidance without automatic enabling. Runtime use is unmeasured; retain means intent, not copied bytes. +| Source | Recommendation | Reason / dependency | +| --- | --- | --- | +| [AGENTS.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/AGENTS.md) | adapt | On-demand agent-state/slot guidance; do not create or enable an orchestration service. | +| [ASYNC_GATES.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/ASYNC_GATES.md) | adapt | On-demand human/CI wait and resolution concepts; no new gate service or automatic approval. | +| [BOUNDARIES.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/BOUNDARIES.md) | adapt | Durable context vs ephemeral execution; reconcile TodoWrite advice with current prime and repository OpenSpec authority. | +| [CHEMISTRY_PATTERNS.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/CHEMISTRY_PATTERNS.md) | adapt | On-demand molecule/wisp selection guidance; execution requires authorized recurring work. | +| [CLI_REFERENCE.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/CLI_REFERENCE.md) | retain | Keep live prime/help pointers; avoid duplicating CLI reference. | +| [DEPENDENCIES.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/DEPENDENCIES.md) | adapt | Preserve dependency direction, provenance and hierarchy concepts; verify examples against current CLI. | +| [INTEGRATION_PATTERNS.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/INTEGRATION_PATTERNS.md) | adapt | Capture meaning at milestones; map planning to OpenSpec, evidence to docs, execution later to Beads. | +| [ISSUE_CREATION.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/ISSUE_CREATION.md) | adapt | Distinguish design from acceptance and capture resumption context; follow existing authorization. | +| [MOLECULES.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/MOLECULES.md) | adapt | On-demand recurring-work guidance with current formula/CLI compatibility checks. | +| [PATTERNS.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/PATTERNS.md) | adapt | Preserve hypotheses, decisions, blocker and closure patterns; replace MCP examples and unnecessary approval loops. | +| [RESUMABILITY.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/RESUMABILITY.md) | adapt | Preserve tested examples, failures, rationale and next action proportionate to task; no raw dumps. | +| [STATIC_DATA.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/STATIC_DATA.md) | adapt | Reference-database pattern is available on demand; use project memory for reusable knowledge rather than open issues as a glossary by default. | +| [TROUBLESHOOTING.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/TROUBLESHOOTING.md) | replace | Curate current CLI/guard failure recovery; stale server/export/import advice must not become automatic repair. | +| [WORKFLOWS.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/WORKFLOWS.md) | adapt | Checkpoint and recovery triggers; use marketplace-main append checkpoints and remove redundant approval loops while retaining applicable authority. | +| [WORKTREES.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/WORKTREES.md) | adapt | On-demand workspace routing and ownership guidance; no automatic worktree creation or store initialization. | + +[ADR-0001](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/adr/0001-bd-prime-as-source-of-truth.md): retain the separation of dynamic CLI syntax from cognitive resources. Its assertion that drift is impossible is not borne out by the shipped surrounding examples. References and compatibility still need review. + +## Complete command review + +All 29 names are reviewed below, with the file itself as evidence. Common dependency for adoption: current CLI semantics and native host discovery/arguments must be verified. All 29 entries are selected under `oc-beads:` through package registration. Replace preserves intent via corrected guidance; advanced/admin entries remain accessible under normal permissions. Per-file selected mappings in the inventory control implementation. +| Source | Recommendation | Reason / dependency | +| --- | --- | --- | +| [audit.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/audit.md) | adapt | Optional diagnostics; avoid making prompt/response logging or a telemetry service a parity dependency. | +| [blocked.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/blocked.md) | retain | Useful blocker view; preserve read-only intent and distinguish blocked status from dependency blocking. | +| [close.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/close.md) | adapt | Replace MCP with CLI; require completion evidence and relevant validation, not merely a successful close call. | +| [comments.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/comments.md) | adapt | Keep discussion/checkpoint history; use noninteractive field updates instead of bd edit advice. | +| [compact.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/compact.md) | adapt | Administrative destructive issue compaction is distinct from conversation compaction; review current CLI and explicit scope. | +| [create.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/create.md) | adapt | CLI structured creation with acceptance/design and discovered-from where applicable; avoid redundant questions when intent is known. | +| [decision.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/decision.md) | adapt | Retain rationale and alternatives; link OpenSpec intended behavior and docs rationale without duplicating their authority. | +| [delete.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/delete.md) | adapt | Selected administrative workflow needs explicit operation authority and verified current semantics; not ordinary closure. | +| [dep.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/dep.md) | adapt | Retain graph operations; correct conflicting prose about dependency direction and verify X depends on Y. | +| [epic.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/epic.md) | adapt | Retain hierarchy/status; do not equate all children closed with validated parent acceptance. | +| [export.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/export.md) | retain | Useful explicit interchange; distinguish issue export from full backup and memory-inclusive export. | +| [import.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/import.md) | adapt | Preserve documented stale/upsert/partial-write cautions and route limits; never implicit setup or restore. | +| [init.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/init.md) | replace | Human-directed setup guide with side effects and ownership; do not invoke nonexistent MCP init or initialize automatically. | +| [label.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/label.md) | retain | Useful metadata operations; respect existing project taxonomy. | +| [list.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/list.md) | adapt | Keep filters with current CLI help; separate retrieval from automatic task selection or mutation. | +| [prime.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/prime.md) | adapt | No frontmatter upstream; explicit guarded refresh using full CLI context; distinguish missing context from migration hold. | +| [quickstart.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/quickstart.md) | replace | Deprecated command; offer current onboarding/resource entry without invoking bd quickstart. | +| [ready.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/ready.md) | adapt | CLI ready view; honor already-authorized task choice and atomic claim rather than always asking again. | +| [rename-prefix.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/rename-prefix.md) | adapt | Project-wide identifier migration; verify syntax and references before any separately approved admin workflow. | +| [reopen.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/reopen.md) | retain | Useful correction/regression operation with reason and current state check. | +| [restore.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/restore.md) | adapt | Historical retrieval useful for recovery; verify current Dolt implementation instead of trusting mixed git/Dolt prose. | +| [search.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/search.md) | adapt | Preserve issue search; remove unsupported MCP efficiency claims and distinguish memory search. | +| [show.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/show.md) | adapt | CLI detail retrieval including notes, acceptance and dependencies; do not mutate when asked to inspect. | +| [stats.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/stats.md) | adapt | CLI overview; no automatic claiming from statistics. | +| [sync.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/sync.md) | adapt | Current federation-loop bd sync differs from removed JSONL sync; preserve explicit authorization and conflict handling, no forced side selection. | +| [template.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/template.md) | adapt | Replace stale template syntax with supported template/formula discovery; no automatic workflow execution. | +| [update.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/update.md) | adapt | CLI fields/atomic claim; distinguish current-state notes from append-only discussion; preserve prior learning. | +| [version.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/version.md) | replace | Query installed CLI/plugin versions; remove hardcoded 0.9.2, MCP assumptions and automatic install advice. | +| [workflow.md](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/workflow.md) | adapt | Native CLI workflow and recovery entry; remove assumed MCP and unbounded continuation as universal default. | + +The original adapter's [loader](https://github.com/joshuadavidthomas/opencode-beads/blob/a508c1205a04516490d9d1078d5a4e788cd6553b/src/vendor.ts) returns null without frontmatter, registers `beads:`, and passes parsed prompt bodies through. Its [README](https://github.com/joshuadavidthomas/opencode-beads/blob/a508c1205a04516490d9d1078d5a4e788cd6553b/README.md) advertises `/bd-*`. Its tree contains 29 vendored command files, but that is not proof of 29 usable commands. In the reviewed Beads corpus `prime.md` and `template.md` lack frontmatter; that loader would skip those bytes. CLI safety text elsewhere does not rewrite command bodies that request MCP tools. Native V2 Markdown supports bodies without frontmatter, so the old parser limitation must not be carried forward as a V2 limitation. + +## Native delivery choices and extension model + +The [V2 command documentation](https://opencode.ai/v2/docs/commands/) explicitly supports Markdown templates and JSON commands; see its [pinned source](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/commands.mdx). The [plugin API](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/build/plugins/index.mdx) separately registers executable commands. The old artifact-policy wording confused these surfaces. Native prompt commands are a viable option; removal of vendoring was a policy choice, not a platform necessity. + +| Evaluated delivery route | Benefits | Ownership/removal/override and risk | +| --- | --- | --- | +| D01: package-owned executable commands | One package/version controls registration and removal; no project-file installer | Must forward prompt attachments/delivery correctly, define naming/collision behavior, and verify user override precedence at the supported host. More adapter code; never directly interpolate user arguments into shell execution. | +| D02: explicit project/global Markdown command install | Native editable prompts, description and argument substitution; familiar file review | Needs an explicit installation/update/removal contract and provenance. Preserve user edits/collisions; remove only owned unchanged files. Project commands override global ones. Avoid shell-output interpolation: V2 evaluates it outside agent tool permission flow. | +| D03: JSON command configuration | Native templates and agent/model selection | User config merging/removal is harder to own safely; no unrequested runtime-config edits. Keep as documented alternative rather than automatic mutation unless selected. | +| D04: package-owned skill registered with real asset path | Resource base can be packaged and versioned; uninstall follows package registration | Npm internals are not automatically discovered. Must provide a valid path and package resources, test resolution after install, and establish user override behavior. | +| D05: supplemental skill in native discovery directories | Explicit resource tree and user-controlled customization | Shared `beads` ID collisions can shadow content. Historical alternative; selected design uses distinct `oc-beads` and does not install shared files; duplicate discovery prompts may confuse models. Upgrades/removal need ownership discipline. | +| D06: lean shared skill plus runtime guidance only | Minimal asset/installer maintenance | Can add checkpoint/recovery triggers but lacks the rich progressive resource experience unless references are actually reachable. Must demonstrate outcome and discoverability rather than assume prime alone suffices. | + +Selected bundle: **N1 with D01+D04**, `oc-beads`, OpenCode `>=2.1.1 <2.2`, opt-in beta/next. N0 and D02/D03/D05/D06 are evaluated historical alternatives, not pending choices or silent fallback. Package registration supplies all selected assets and commands; no installer restoration or dependency change happens in this planning turn. + +The [pinned skill docs](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/skills.mdx) describe description-based advertisement, explicit tool loading, relative resource paths, and permission checks. They document later-source wins by ID, with `.agents` after `.claude` and project `.opencode` later still. A copied rich Claude skill can therefore be shadowed by the lean shared `beads` skill. Supporting file contents are not automatically loaded. Test actual selected content and resource access, not just file existence. + +| Extension ID | Selected disposition relative to the base experience | Tradeoff / owner decision | +| --- | --- | --- | +| E01: bounded one-bead worker | Optional one-bead mode alongside selected scope-bounded continuation | Bounds scope and protects primary context, but may add handoffs and lose discoveries. Exact assignments stop; authorized batches/goals may continue; discoveries do not expand scope. | +| E02: Luna profile | Retain existing opt-in model routing | A provider/model-specific choice is not required for parity. Same behavioral criteria, separately recorded model results; no claimed cost/quality superiority. | +| E03: migration guard | Retain protective behavior with consistent holds/fallback guidance | Marker is conservative, guard covers automatic context only. Do not turn ordinary recoverable errors into blanket permanent denial. | +| E04: permission rules / step cap | Adapt as transparent configurable safeguards | Pattern rules are not a sandbox; caps may interrupt before checkpoint. Respect existing authorization, report incomplete work, and qualify real host behavior. | +| E05: mandatory delegation | Discretionary task-based delegation; remove mandatory command-count routing | Direct atomic operations remain simple; graph analysis benefits from isolation in some tasks. Qualify semantic handoff; no universal mandatory delegation. | +| E06: memory freshness | Adapt targeted retrieval/refresh for relevant state transitions | Balance current knowledge with stable prompt caching. Avoid a new orchestration service, polling telemetry, token/write quotas or broad shell parsing. | + +## Proportionate qualification plan (not executed) + +Use disposable fixtures only after separate implementation authorization and lifting any applicable hold. Keep deterministic checks separate from model-dependent evaluations. + +| Scenario | Deterministic integration evidence | Behavioral outcome to evaluate | +| --- | --- | --- | +| Progress and failed approach | Selected guidance/resource reachable; checkpoint path and error handling covered | Preserve what was tried, failure evidence and rationale; resume without repeating the rejected approach. | +| Reusable discovery vs transient detail | Supported remember/search/recall syntax and persistence fixture | Save supported reusable learning, avoid promoting temporary state; retrieve and apply it later without nagging. | +| Compaction/resume/fresh session | Context invalidation, idle approximation, guarded retry and targeted retrieval paths | Recover active task, attempts, decision, blockers and concrete next action from durable state with old conversation absent. | +| Atomic operation vs graph/delegation | Routing/permissions, child result and failure delivery | Read-only analysis does not claim/mutate; worker returns learning and validation; primary retains intent and reconciles closure. | +| Command workflow | Selected names discoverable, args/attachments/delivery and override/removal tests | Command produces correct scoped workflow through CLI; no invented MCP calls, unsolicited setup or concealed failed write. | +| Error/hold and closure | Withheld context distinguished from empty/failure; validation and permission boundary fixtures | No manual-prime bypass of hold, no false success, no closure with unresolved acceptance; preserve next action if blocked. | + +A small representative set can combine these scenarios. Record host, Beads release, installation path, selected skill/resource, provider/model, reasoning setting, starting state and outcome evidence. Compare host changes on the same model where feasible; compare models under the same host configuration separately. Use targeted repeat runs for uncertain outcomes rather than an exhaustive cross-product. Review actual task/memory records and recovery behavior, not prompt text, write count or model self-report alone. Report failures and attribution limits; no universal architecture or model superiority claim. No new telemetry or evaluation service is required. + +## Selected contract and evidence layers + +The [owner record](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/owner-decisions.md) resolves marketplace-main, N1/minimum host, beta/next, scope-bounded execution, full corpus groups, package ownership/namespace and check-based drift. The [design](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md) resolves technical mappings and the [reconciliation record](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/reconciliation.md) maps findings and requirements to tasks. Earlier recommendations remain historical alternatives, not another owner gate. + +Source qualification is completed evidence, deterministic fixtures are future implementation work, and installed-host/model behavior is future acceptance. Only a demonstrated contract-changing incompatibility returns to the owner. OpenSpec owns touched intended behavior, docs evidence, and Beads later execution; no tracker/store access is needed here. + +## Planning validation + +OpenSpec 1.14.2 strict validation passes for `improve-beads-workflow-parity`; `--all --strict` reports one change passed, zero failed. `--specs --strict` reports no items because the canonical baseline is empty; this is not a claim that baseline behavior was tested. Status reports all four artifact kinds present, with all three capability deltas. Programmatic inventory checks compare the declared roots/support paths to the pinned non-truncated Git tree, verify all 73 Git blob identities and SHA-256 values, check command/resource/ADR source links and all 15 rich-skill resource links, and confirm the local lean skill matches the shared upstream template. Tracked and new-file whitespace checks pass. These checks certify planning structure/source coverage, not runtime or model outcomes. + +### Native revision evidence + +The repository-owned [planning evidence validator](validate-parity-planning.py) preserves the original corpus identity/coverage checks and adds pinned native-source and published-declaration checks, without asserting the intentionally removed freeze marker. See the [native assessment](opencode-native-parity-assessment.md) for exact release distinctions, recommendation and unexecuted material probes. Strict OpenSpec and whitespace results for this revision are recorded in the handoff; source inspection is not implementation or behavioral qualification. + +Operative host selection now targets exact 2.1.1; the [contract delta](opencode-2.1.1-contract-delta.md) preserves historical evidence and separates source admission from unexecuted runtime acceptance. No product or shared-skill changes occur in this pass. diff --git a/docs/claude-code-integration-inventory.json b/docs/claude-code-integration-inventory.json new file mode 100644 index 0000000..a22c46d --- /dev/null +++ b/docs/claude-code-integration-inventory.json @@ -0,0 +1,4384 @@ +{ + "source_repository": "gastownhall/beads", + "tag": "v1.3.1", + "commit": "c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c", + "scope": "All blobs under plugins/beads/ and internal/templates/skills/beads/, marketplace manifest, and explicitly listed supporting implementation/docs. Enumeration is not runtime execution evidence.", + "support_paths": [ + ".claude-plugin/marketplace.json", + "cmd/bd/setup/claude.go", + "cmd/bd/setup/claude_test.go", + "cmd/bd/setup/codex.go", + "cmd/bd/setup/agent_skill.go", + "cmd/bd/setup/opencode.go", + "cmd/bd/setup/plugin_layout_test.go", + "cmd/bd/init.go", + "cmd/bd/init_agent.go", + "cmd/bd/init_embedded_test.go", + "cmd/bd/prime.go", + "cmd/bd/prime_memory_caps_test.go", + "cmd/bd/memory.go", + "cmd/bd/codex_hook.go", + "docs/integrations/claude-code-plugin.md" + ], + "files": [ + { + "path": ".claude-plugin/marketplace.json", + "category": "manifest-or-hooks", + "git_blob": "0a50b06ad979b340d7260fbf17327f2ad6e7161e", + "sha256": "fda944346c5d48c4cd94dd17c4d138e15398d41f4763a05b6e60dc9914870b1a", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/.claude-plugin/marketplace.json", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "F03: preserve discoverability; use native OpenCode delivery, not Claude marketplace installation.", + "owner_disposition": "replace", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "replace", + "group": "native-replacement", + "target": "src/host.ts registration/hooks and src/workflows.ts", + "task_ids": [ + "2.2", + "3.2", + "3.3" + ], + "reason": "Translate discovery/lifecycle intent to native registration, not host-specific manifests." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/codex_hook.go", + "category": "support", + "git_blob": "dcbfb787ec4333298d7050ddeaaac70a0bd31c23", + "sha256": "2adab3df97a83045d95b2f44ab1ad1ddfcbb992287c03004e46746eba6f5fd79", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/codex_hook.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/init.go", + "category": "support", + "git_blob": "45e4b23cf5918771e6ec9a5e3671d2c34056c90e", + "sha256": "3f30258dc36a4c2e65dee6466394a5a80d99fb9a33586d798fd7285484907ed7", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/init.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/init_agent.go", + "category": "support", + "git_blob": "9cc630d716c2d8ac19e9794cd9f8886fbfd0cc33", + "sha256": "46f8e675121900c1b7da9b92c2f0722665cd2708f86c856eb90bafad12282463", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/init_agent.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/init_embedded_test.go", + "category": "support", + "git_blob": "ee27208a081fba81dd1c519edb158938f9c5453a", + "sha256": "a40d00a1f40cf823cd597adbdc14b2d441c54458eefaeb5e305cabb5faf4f5ca", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/init_embedded_test.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/memory.go", + "category": "support", + "git_blob": "ef47463584b6dc27953bbf7b3f263bc0432da92e", + "sha256": "a2a5b0d8eeb913b1cda80a46e28bf2dedf837a0f79fedef417b81faa67d8ef24", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/memory.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/prime.go", + "category": "support", + "git_blob": "76c3db94e4cf2c62545b793e07c603396015f91c", + "sha256": "04995074d087e94edb5eebdf70d98fa93566f5e93442b3b303115d5388a44903", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/prime.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/prime_memory_caps_test.go", + "category": "support", + "git_blob": "20a1b0fcb254ac754c92db197f57f17b1465bf35", + "sha256": "81abb8df720cbf610d8614185e600f9c15e61201ed34eb6f6f689feba2580b5e", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/prime_memory_caps_test.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/setup/agent_skill.go", + "category": "support", + "git_blob": "e423b2e8534adc4f2b95dd4b08a9a0f01a5ff98e", + "sha256": "0caedc17156df34bea312c98c66763925b632b51dc68fdc6c46a09c1bee06353", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/setup/agent_skill.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/setup/claude.go", + "category": "support", + "git_blob": "fb10326d4a8140b02202cbaaf12c7cc0faee609d", + "sha256": "5443c775e60404ecdc8340d96904ef777a14f832373001c8218c3270d9da91d1", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/setup/claude.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/setup/claude_test.go", + "category": "support", + "git_blob": "4d1e178e1ce2dd0f17790d3b698f4ead7e9877ae", + "sha256": "c18abafbaa8c666c773a0293d4ca3619b838da3a2c7e14f0a33259797d5b2b03", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/setup/claude_test.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/setup/codex.go", + "category": "support", + "git_blob": "8ff57f0c35065d6fe2518f4f8e051a5bcf8d9356", + "sha256": "e52fdaa6c8ff511064ec4335aadb5f5fa14ff5af16555665a04dff5f7d494e8e", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/setup/codex.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/setup/opencode.go", + "category": "support", + "git_blob": "a728e07d667f7d86f8b92d6b432fdb6c4383ebd9", + "sha256": "91b214f63018a7c9ed768ce3db6fdef576c5beb4873bebf600b2ac973c7c9aff", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/setup/opencode.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "cmd/bd/setup/plugin_layout_test.go", + "category": "support", + "git_blob": "51b3a2a10aeda9958c04eb4b0b89884cebb6c60c", + "sha256": "3f8eccbaf0f4b5e7ea65a1fa2f527b22bd903fd54dab1cde054651eb58cc4e88", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/setup/plugin_layout_test.go", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "docs/integrations/claude-code-plugin.md", + "category": "support", + "git_blob": "7112f7d404f9b1bc5ee283b7a8a9e0fa6997f48d", + "sha256": "5d43897dc1cc2d50f87de289d23270ca96040073aa2597b08e6919546574cfcc", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/docs/integrations/claude-code-plugin.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "internal/templates/skills/beads/SKILL.md", + "category": "skill", + "git_blob": "a5a3344cffc25b3563697746b6f15337a4f43e66", + "sha256": "1a621606cfa9a14cbacff84bf5c496b4c2c175c2df329e8d19d4e01fc989dedd", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/internal/templates/skills/beads/SKILL.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Shared and marketplace skill differ; adapt discovery and resource links without overwriting the bd-owned shared skill.", + "owner_disposition": "preserve-external", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "preserve-external", + "group": "upstream-owned", + "target": "bd-owned shared beads skill; no writes", + "task_ids": [ + "2.2", + "3.3" + ], + "reason": "Preserve shared skill and metadata; package-owned oc-beads adds the selected rich experience." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "internal/templates/skills/beads/agents/openai.yaml", + "category": "agent-or-metadata", + "git_blob": "09c3b8f61fd5a6cfabe942909a6ad5658d40ab75", + "sha256": "c7ff618a9a6bcce71b25f722f67861b7685651d08fb72d8fff7d947aac403718", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/internal/templates/skills/beads/agents/openai.yaml", + "frontmatter": false, + "recommendation": "retain", + "rationale": "Preserve upstream ownership of shared metadata; plugin does not need to fork or install it.", + "owner_disposition": "preserve-external", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "preserve-external", + "group": "upstream-owned", + "target": "bd-owned shared beads skill; no writes", + "task_ids": [ + "2.2", + "3.3" + ], + "reason": "Preserve shared skill and metadata; package-owned oc-beads adds the selected rich experience." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/.claude-plugin/plugin.json", + "category": "manifest-or-hooks", + "git_blob": "6ca3bc1a19d240bc504d26df531e343db6590ee9", + "sha256": "7da4fac95b98d320d7c0a08ceee8c552726b95dc7b4e6df520e70733d85344f2", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/.claude-plugin/plugin.json", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "F03: translate lifecycle, skill and command intent; plain prime is MCP-sensitive.", + "owner_disposition": "replace", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "replace", + "group": "native-replacement", + "target": "src/host.ts registration/hooks and src/workflows.ts", + "task_ids": [ + "2.2", + "3.2", + "3.3" + ], + "reason": "Translate discovery/lifecycle intent to native registration, not host-specific manifests." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/.codex-plugin/hooks/hooks.json", + "category": "manifest-or-hooks", + "git_blob": "9201db27b183ca6c3b1b896a4e68f0d6332c06a6", + "sha256": "383b2a4bb9a0a6b1c8e821f360bb9c1f5109c34eb7c02e7382dea1adc6aa0481", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/.codex-plugin/hooks/hooks.json", + "frontmatter": false, + "recommendation": "exclude", + "rationale": "F11 recommendation only: Codex event wiring; compare lifecycle, do not install in OpenCode.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/.codex-plugin/plugin.json", + "category": "manifest-or-hooks", + "git_blob": "2428a46e10b8d4d6310368b81d24b49e2e40f6bd", + "sha256": "3edbadf2c311daf804b8e81961eaa3961f9fd1cae0f71fa7b84914fc78abe896", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/.codex-plugin/plugin.json", + "frontmatter": false, + "recommendation": "exclude", + "rationale": "F11 recommendation only: host-specific manifest; retain shared behavior and comparison evidence.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/.copilot-plugin/plugin.json", + "category": "manifest-or-hooks", + "git_blob": "3ebeccd59bad2da84d3270d7e070e3b410a59e5c", + "sha256": "1c49a2e74650b862d21f5723c0937a8fc5fb4c49c7baa271f11a1c539f5d2a17", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/.copilot-plugin/plugin.json", + "frontmatter": false, + "recommendation": "exclude", + "rationale": "F11 recommendation only: separate host packaging, not an OpenCode runtime surface.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/README.md", + "category": "support", + "git_blob": "f897aa8c7524ae554483675faf63275bcbf73232", + "sha256": "2effe26178ea7a51475126732a8ddd999e5a04664881b8d6a0fdd15c27ce9504", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/README.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "F12: preserve installation-path distinctions in product documentation, not runtime injection.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "F12: preserve installation-path distinctions in product documentation, not runtime injection." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/agents/task-agent.md", + "category": "agent-or-metadata", + "git_blob": "7d04bb860553ac03c139478b295550418f72979c", + "sha256": "d5dd212e4f6324cf9f979eb8ed96ee094174c3e0cda8e217e55643244a7f4a79", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/agents/task-agent.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "F07/E01/E05: native CLI delegation, semantic handoff, owner-selected completion scope and continuation policy.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "src/guidance.ts and src/agents.ts; existing agent IDs", + "task_ids": [ + "4.1", + "4.2" + ], + "reason": "Scope-bounded continuation, atomic claims, per-bead evidence and semantic handoff through normal blocking children." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/copilot_manifest.go", + "category": "support", + "git_blob": "d3ca8a206c3e1d01c10580fac559f19ac93ba3f5", + "sha256": "6cb9b61e868d38fc9eadca3ff6e985548a98f1c90f6bebd5bee4ca7302534451", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/copilot_manifest.go", + "frontmatter": false, + "recommendation": "exclude", + "rationale": "F11 recommendation only: Copilot embedding helper, not OpenCode runtime behavior.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/CLAUDE.md", + "category": "support", + "git_blob": "2581bd4501eeaa35d8773732865c650c7831990e", + "sha256": "fdb958aad2188f0304cc76120727a7c3a214f072e94ee886d71bd247742bd09a", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/CLAUDE.md", + "frontmatter": false, + "recommendation": "retain", + "rationale": "F12: maintainer guidance and ADR intent remain evidence, not automatic runtime instructions.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "F12: maintainer guidance and ADR intent remain evidence, not automatic runtime instructions." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/README.md", + "category": "support", + "git_blob": "8bc73c70f2f5b73dd6837166f6aceb9636857863", + "sha256": "cdd32c0448817924a1259fd1192479034c267d9b430a642c535e99f214d07771", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/README.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "F12: preserve useful recovery rationale; distinguish manual skill copy from full marketplace install.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "F12: preserve useful recovery rationale; distinguish manual skill copy from full marketplace install." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/SKILL.md", + "category": "skill", + "git_blob": "5a1968523adfc4f3f51a6257f550079e9dd98b29", + "sha256": "01555fe65d19be401d820d9dec029cd048fb0791d433b4b575374477d6f1d827", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/SKILL.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Shared and marketplace skill differ; adapt discovery and resource links without overwriting the bd-owned shared skill.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/SKILL.md", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Rich progressive guidance with native tools and corrected authority; shared beads remains untouched." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/adr/0001-bd-prime-as-source-of-truth.md", + "category": "adr", + "git_blob": "8ea7996841479c8ef4d20e6cd254365d5fb1cd1a", + "sha256": "29985565ab384b381d7288f2c01e19e6d06d760c560152a1238151731a35ca91", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/adr/0001-bd-prime-as-source-of-truth.md", + "frontmatter": false, + "recommendation": "retain", + "rationale": "Retain canonical dynamic CLI reference principle as rationale, not a guarantee against drift.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "rationale", + "target": "assets/oc-beads/adr/0001-bd-prime-as-source-of-truth.md", + "task_ids": [ + "2.1" + ], + "reason": "Retain canonical dynamic CLI reference principle as rationale, not a guarantee against drift." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/agents/openai.yaml", + "category": "agent-or-metadata", + "git_blob": "09c3b8f61fd5a6cfabe942909a6ad5658d40ab75", + "sha256": "c7ff618a9a6bcce71b25f722f67861b7685651d08fb72d8fff7d947aac403718", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/agents/openai.yaml", + "frontmatter": false, + "recommendation": "exclude", + "rationale": "F11 recommendation only: host UI metadata, not the task-agent prompt or a required native OpenCode asset.", + "owner_disposition": "reference-only", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/audit.md", + "category": "command", + "git_blob": "9c737eed8d9aa9d6249b72aa5fd0846205907d18", + "sha256": "bf941ae72f3dfad0e5a0997c8afa1647d6ce17934692efa30e9fe4da98e39de8", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/audit.md", + "frontmatter": true, + "recommendation": "defer", + "rationale": "Optional diagnostics; avoid making prompt/response logging or a telemetry service a parity dependency.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:audit", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Expose supported audit logging/label workflow only on request; no automatic transcript collection or telemetry." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/blocked.md", + "category": "command", + "git_blob": "83baf87d8fc993cb7c2d9a54db27499cc609530f", + "sha256": "30181c3645b5e233005facde694a3bb001c5935258745ae976abfe7b93eec346", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/blocked.md", + "frontmatter": true, + "recommendation": "retain", + "rationale": "Useful blocker view; preserve read-only intent and distinguish blocked status from dependency blocking.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:blocked", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Useful blocker view; preserve read-only intent and distinguish blocked status from dependency blocking." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/close.md", + "category": "command", + "git_blob": "82bd27a03b69d8df5361427bd962c3eeb7a930c2", + "sha256": "ef43e31c9f86082a6192ca131c175c55ee37dc115f4781413be11f1e85274b58", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/close.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Replace MCP with CLI; require completion evidence and relevant validation, not merely a successful close call.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:close", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Replace MCP with CLI; require completion evidence and relevant validation, not merely a successful close call." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/comments.md", + "category": "command", + "git_blob": "a21018db24707b4e308b7ad6074bfb8449e98b2d", + "sha256": "cad54141ee590be3b8d07878bb3ea34c252466bbb00a9b95d7d521303a8e3b83", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/comments.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Keep discussion/checkpoint history; use noninteractive field updates instead of bd edit advice.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:comments", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Keep discussion/checkpoint history; use noninteractive field updates instead of bd edit advice." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/compact.md", + "category": "command", + "git_blob": "5543e85ed80cb398705c82a801627902abb47869", + "sha256": "5204417505320501e2c5a529da6c670826ef5b62feab708a33b7452dfd6a80c4", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/compact.md", + "frontmatter": true, + "recommendation": "defer", + "rationale": "Administrative destructive issue compaction is distinct from conversation compaction; review current CLI and explicit scope.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:compact", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Explicit administrative issue-compaction workflow, distinct from host conversation compaction; verify current CLI semantics and authority." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/create.md", + "category": "command", + "git_blob": "ee3a912de6c3778cd089885588829b775b41c154", + "sha256": "1e27f3ff4f3ae041f6896b5181f0b0399ff80d52cd415dfd8cf6584a218736d3", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/create.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "CLI structured creation with acceptance/design and discovered-from where applicable; avoid redundant questions when intent is known.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:create", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "CLI structured creation with acceptance/design and discovered-from where applicable; avoid redundant questions when intent is known." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/decision.md", + "category": "command", + "git_blob": "6785eaa5d94543de0dfb5c105cc3ef29970c8b5c", + "sha256": "78c0de4a0979502cb553c2ad49df32cc1e6a7cb3eec25dc41f60290ad4fbbe36", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/decision.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Retain rationale and alternatives; link OpenSpec intended behavior and docs rationale without duplicating their authority.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:decision", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Retain rationale and alternatives; link OpenSpec intended behavior and docs rationale without duplicating their authority." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/delete.md", + "category": "command", + "git_blob": "b8c6eb61953af0ab25cbd40cc1f050482cf92106", + "sha256": "a6a5e943cccc75c27820548ec4a810894be646604b98821822968c7cb55552eb", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/delete.md", + "frontmatter": true, + "recommendation": "defer", + "rationale": "Destructive admin workflow needs explicit selection and verified current semantics; not ordinary closure.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:delete", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Explicit scoped deletion under normal permissions; never substitute for closure or infer deletion authority." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/dep.md", + "category": "command", + "git_blob": "c02818b741c496b194e0c4d7c3cfbe644008aef6", + "sha256": "2d50ea5c65e0ce27bc076e77de5ea32de7086b34c6f49736e71517548cfe4b76", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/dep.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Retain graph operations; correct conflicting prose about dependency direction and verify X depends on Y.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:dep", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Retain graph operations; correct conflicting prose about dependency direction and verify X depends on Y." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/epic.md", + "category": "command", + "git_blob": "4454d26ca06b03c1bf528e03f486a2484f00175a", + "sha256": "174f54e3092e5a4b2f4c010e95418b13f1d910e185349ba0e131b04cf5783b8b", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/epic.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Retain hierarchy/status; do not equate all children closed with validated parent acceptance.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:epic", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Retain hierarchy/status; do not equate all children closed with validated parent acceptance." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/export.md", + "category": "command", + "git_blob": "45612e49d361b2e903bda86c8c590ab0f469361c", + "sha256": "5c8e9c9b4df71c804f489fa5785d607be971b7f62c836ad531f11e8b105ee47a", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/export.md", + "frontmatter": true, + "recommendation": "retain", + "rationale": "Useful explicit interchange; distinguish issue export from full backup and memory-inclusive export.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:export", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Useful explicit interchange; distinguish issue export from full backup and memory-inclusive export." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/import.md", + "category": "command", + "git_blob": "318cf83e2006a57934d97c05468843d7254041b4", + "sha256": "604e48e6d093d9a93f28a3a5ed7dd7946b10e7a4897bec8a88eefc5b6c9c776e", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/import.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Preserve documented stale/upsert/partial-write cautions and route limits; never implicit setup or restore.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:import", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Preserve documented stale/upsert/partial-write cautions and route limits; never implicit setup or restore." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/init.md", + "category": "command", + "git_blob": "805032cb5b9bad44f959963c0b7f8c6172ff1188", + "sha256": "975759ee3d7ac6611fa54ae27c277a2995288a8fd089e6b46b791f73f3322177", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/init.md", + "frontmatter": true, + "recommendation": "replace", + "rationale": "Human-directed setup guide with side effects and ownership; do not invoke nonexistent MCP init or initialize automatically.", + "owner_disposition": "replace", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "replace", + "group": "supported-admin", + "target": "oc-beads:init", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Human-directed upstream setup guidance with side effects; no automatic initialization." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/label.md", + "category": "command", + "git_blob": "579851759a983b9a8329eb6a3245d9a99fbb8fe9", + "sha256": "45f0422cce8764a19158a98e95b6b3c790e7a01b3438d9b5471ddb5f8c544129", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/label.md", + "frontmatter": true, + "recommendation": "retain", + "rationale": "Useful metadata operations; respect existing project taxonomy.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:label", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Useful metadata operations; respect existing project taxonomy." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/list.md", + "category": "command", + "git_blob": "b3b4fa3fdcecb7f3f12152399b0e1567e2a6db45", + "sha256": "990a0e6ef5af6a6ec596df32590750efd27fd231a44a0882d3f900dc71a4380f", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/list.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Keep filters with current CLI help; separate retrieval from automatic task selection or mutation.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:list", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Keep filters with current CLI help; separate retrieval from automatic task selection or mutation." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/prime.md", + "category": "command", + "git_blob": "c464c7225058fd152080515e696dcf5ac41f982e", + "sha256": "4b0559e792c2ea8ead359954896eca8518cd311303637b2e1c542e65b9d960b8", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/prime.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "No frontmatter upstream; explicit guarded refresh using full CLI context; distinguish missing context from migration hold.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:prime", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "No frontmatter upstream; explicit guarded refresh using full CLI context; distinguish missing context from migration hold." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/quickstart.md", + "category": "command", + "git_blob": "6bdbe3d8a67781c64e94d5da3a63e6d8ba6a75ec", + "sha256": "de372b2dac3b1786ee383e8295bca781d6ce890db0daa09efa4d044325b80bbc", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/quickstart.md", + "frontmatter": true, + "recommendation": "replace", + "rationale": "Deprecated command; offer current onboarding/resource entry without invoking bd quickstart.", + "owner_disposition": "replace", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "replace", + "group": "core-daily", + "target": "oc-beads:quickstart", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Replace deprecated bd quickstart with onboarding using current prime/help and installed resources." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/ready.md", + "category": "command", + "git_blob": "e6e50c24818c92c76c31ec64425d9246f248f82e", + "sha256": "48a42cab07b8580b0436bbddf5dd409333ff3b402bb4fee7af39b3aa3b078856", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/ready.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "CLI ready view; honor already-authorized task choice and atomic claim rather than always asking again.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:ready", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "CLI ready view; honor already-authorized task choice and atomic claim rather than always asking again." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/rename-prefix.md", + "category": "command", + "git_blob": "8df7cb233b76276d9d5d7250357dd6e6dbca0145", + "sha256": "2e6a457dfe58a855daecc0581433584073d7b52565a9bdb85131d1b4a3d920e8", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/rename-prefix.md", + "frontmatter": true, + "recommendation": "defer", + "rationale": "Project-wide identifier migration; verify syntax and references before any separately approved admin workflow.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:rename-prefix", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Explicit project identifier migration guidance with reference impact and current syntax checks; no automatic migration." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/reopen.md", + "category": "command", + "git_blob": "7ddb6c07394efbbbfc7326d82dda14801a8e2a57", + "sha256": "ea5535f0004d3137f05b6fc81cff1ecc670d918465b7d878eb6cb01d2427578c", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/reopen.md", + "frontmatter": true, + "recommendation": "retain", + "rationale": "Useful correction/regression operation with reason and current state check.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:reopen", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Useful correction/regression operation with reason and current state check." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/restore.md", + "category": "command", + "git_blob": "61a80990cb67b2279a751fb4957d5a2c76e5afa3", + "sha256": "94928e7b5d8ca1da80571e56685020f31eba6ce8fd38100cba09f00a051e164e", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/restore.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Historical retrieval useful for recovery; verify current Dolt implementation instead of trusting mixed git/Dolt prose.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:restore", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Historical retrieval useful for recovery; verify current Dolt implementation instead of trusting mixed git/Dolt prose." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/search.md", + "category": "command", + "git_blob": "dd611fc47fcc1ea9ea532089274e45f03ede550e", + "sha256": "2295d2cf34f0d7c3a74fab86f73aa312435870c97aa71d8c965486a450310085", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/search.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Preserve issue search; remove unsupported MCP efficiency claims and distinguish memory search.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:search", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Preserve issue search; remove unsupported MCP efficiency claims and distinguish memory search." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/show.md", + "category": "command", + "git_blob": "7eda4208a7d15365cded5156a6687ac552da9e53", + "sha256": "51aa4b5a5bebeb9f1027f5a31a3de307dd8116052471f40272f0b2c980c720af", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/show.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "CLI detail retrieval including notes, acceptance and dependencies; do not mutate when asked to inspect.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:show", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "CLI detail retrieval including notes, acceptance and dependencies; do not mutate when asked to inspect." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/stats.md", + "category": "command", + "git_blob": "b9843127742eca6cc1a5a38fe4ef7c1db6e94f52", + "sha256": "f4da8a61fb7922ece1da727f7cfe2e020def6deed7cb2ce807cc6ab6c6d02aca", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/stats.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "CLI overview; no automatic claiming from statistics.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:stats", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "CLI overview; no automatic claiming from statistics." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/sync.md", + "category": "command", + "git_blob": "9841fe4205557d94203d757b1f0e8d77fa1d1aba", + "sha256": "09d999165b93ea183e5828c94d45498503ed179211db5f68b3c32f372ade9ac7", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/sync.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Current federation-loop bd sync differs from removed JSONL sync; preserve explicit authorization and conflict handling, no forced side selection.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:sync", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Current federation-loop bd sync differs from removed JSONL sync; preserve explicit authorization and conflict handling, no forced side selection." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/template.md", + "category": "command", + "git_blob": "ad724440593df6a12edc569f499712ff0816b8ff", + "sha256": "dd8865a9556ac67c49e83b7db9ad085e3e1ebf2e187fd6c407a0e116dbfb712b", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/template.md", + "frontmatter": false, + "recommendation": "defer", + "rationale": "No frontmatter and long static syntax; verify current template/formula support before selecting an entry.", + "owner_disposition": "replace", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "replace", + "group": "core-daily", + "target": "oc-beads:template", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Replace obsolete static template commands with supported template/formula discovery from installed help; no automatic workflow execution." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/update.md", + "category": "command", + "git_blob": "6b705f7e17209890146878712aa242eab43efe2d", + "sha256": "34896bde7871805fb1941afd81a25ebc2ce1030090b8ce96afc63763bd2bd108", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/update.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "CLI fields/atomic claim; distinguish current-state notes from append-only discussion; preserve prior learning.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:update", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Use atomic claim and append-style progress notes; intentional field replacement remains explicit." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/version.md", + "category": "command", + "git_blob": "751d293a4828f9693dc819ec53eebbf9e0d97140", + "sha256": "f994ab7afb0aef464fe0e77e7aad522d5f2d1558415d4ba63aede43a7411a075", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/version.md", + "frontmatter": true, + "recommendation": "replace", + "rationale": "Query installed CLI/plugin versions; remove hardcoded 0.9.2, MCP assumptions and automatic install advice.", + "owner_disposition": "replace", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "replace", + "group": "core-daily", + "target": "oc-beads:version", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Report installed CLI/package/host versions and capability limits; no hardcoded release or automatic install." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/commands/workflow.md", + "category": "command", + "git_blob": "a91b929e681c26960605f12f6118584c060b984d", + "sha256": "23ce9fbfe8acf49df1b4ddba8f908f4a3ef724c6d9203358c180d019e683978c", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/commands/workflow.md", + "frontmatter": true, + "recommendation": "adapt", + "rationale": "Native CLI workflow and recovery entry; remove assumed MCP and unbounded continuation as universal default.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:workflow", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Native CLI workflow and recovery entry; remove assumed MCP and unbounded continuation as universal default." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/AGENTS.md", + "category": "resource", + "git_blob": "807af0b051008084a7485794289afcabf6bb5a85", + "sha256": "0158c156ffe03858fea3816bc6142cb75133ed54748936a05202b485a02250b5", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/AGENTS.md", + "frontmatter": false, + "recommendation": "defer", + "rationale": "Optional agent-state/slot guidance; do not require new orchestration for basic task recovery.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/AGENTS.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand agent-state/slot guidance; do not require new orchestration for basic task recovery." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/ASYNC_GATES.md", + "category": "resource", + "git_blob": "b504ca223d447c39372a2eb6161c27ae0fa901cc", + "sha256": "89519fa3554a052c44161d601fa8c0d0a56e3cce37f43164d6138399ba4250ef", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/ASYNC_GATES.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "On-demand human/CI wait and resolution concepts; no new gate service or automatic approval.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/ASYNC_GATES.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand human/CI wait and resolution concepts; no new gate service or automatic approval." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/BOUNDARIES.md", + "category": "resource", + "git_blob": "cb2eaef42e95b9d7875032ac3dd40667523ff86c", + "sha256": "9239200595fdc742bc2422c82bd7e71a55dc1af02ff6eea179642a0276f53a7c", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/BOUNDARIES.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Durable context vs ephemeral execution; reconcile TodoWrite advice with current prime and repository OpenSpec authority.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/BOUNDARIES.md", + "task_ids": [ + "2.1" + ], + "reason": "Durable context vs ephemeral execution; reconcile TodoWrite advice with current prime and repository OpenSpec authority." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/CHEMISTRY_PATTERNS.md", + "category": "resource", + "git_blob": "2a5abd9840be738fc90be051c20b5971b8544fce", + "sha256": "c81bf240429099eeafd68c06bad4af45b0360dd5d3c6444f6d5b60d31c1641c5", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/CHEMISTRY_PATTERNS.md", + "frontmatter": false, + "recommendation": "defer", + "rationale": "Optional molecule/wisp selection guidance; useful extension when recurring workflows justify it.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/CHEMISTRY_PATTERNS.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand molecule/wisp selection guidance; useful extension when recurring workflows justify it." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/CLI_REFERENCE.md", + "category": "resource", + "git_blob": "ea24c638738a07cdd914a712275917067f0ce871", + "sha256": "ce9b2ba98f17a54cbfeac3ef7f8854fff93eea8d4114b361588ccfd6f4ad2385", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/CLI_REFERENCE.md", + "frontmatter": false, + "recommendation": "retain", + "rationale": "Keep live prime/help pointers; avoid duplicating CLI reference.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/CLI_REFERENCE.md", + "task_ids": [ + "2.1" + ], + "reason": "Keep live prime/help pointers; avoid duplicating CLI reference." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/DEPENDENCIES.md", + "category": "resource", + "git_blob": "4f18f3884811c1f21b9697a973f4b7674d0f3496", + "sha256": "9c3327611bfbdc47124736dd0cc928bfeff1c135d4ae79d4ea46cba1900df335", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/DEPENDENCIES.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Preserve dependency direction, provenance and hierarchy concepts; verify examples against current CLI.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/DEPENDENCIES.md", + "task_ids": [ + "2.1" + ], + "reason": "Preserve dependency direction, provenance and hierarchy concepts; verify examples against current CLI." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/INTEGRATION_PATTERNS.md", + "category": "resource", + "git_blob": "4be8779adc02cedf5bb79f7a0a183726c919e726", + "sha256": "a66e852d8b734f77bb9e157a37c2ecb14c7a2875cab43c5c96977b3c69321d55", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/INTEGRATION_PATTERNS.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Capture meaning at milestones; map planning to OpenSpec, evidence to docs, execution later to Beads.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/INTEGRATION_PATTERNS.md", + "task_ids": [ + "2.1" + ], + "reason": "Capture meaning at milestones; map planning to OpenSpec, evidence to docs, execution later to Beads. Use marketplace-main append-notes examples; remove redundant approval loops while preserving authority." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/ISSUE_CREATION.md", + "category": "resource", + "git_blob": "1e3ebb198ea812a62e801278c032ee7856690b92", + "sha256": "ff465ed1fb13fbb6c42b42ec15c1bd8fd677c4661237e4fc1675c179f7fca460", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/ISSUE_CREATION.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Distinguish design from acceptance and capture resumption context; follow existing authorization.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/ISSUE_CREATION.md", + "task_ids": [ + "2.1" + ], + "reason": "Distinguish design from acceptance and capture resumption context; follow existing authorization. Use marketplace-main append-notes examples; remove redundant approval loops while preserving authority." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/MOLECULES.md", + "category": "resource", + "git_blob": "b1f380c876a3fc75ae3b50b8ec65e50e44bb9b46", + "sha256": "4dbadf02ef5c4ec585c3aef43fb28e440470b03151a6ff6b49e0c8d51dc9e0a9", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/MOLECULES.md", + "frontmatter": false, + "recommendation": "defer", + "rationale": "Optional recurring-work extension, dependent on selected formula/CLI compatibility.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/MOLECULES.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand recurring-work extension, dependent on selected formula/CLI compatibility." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/PATTERNS.md", + "category": "resource", + "git_blob": "2b110175a82944e1984082b301d70bde22637e76", + "sha256": "b2f235633c664180da162d2b2bccdf62bd7865aeb2b4adde522964b6ca4cef7f", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/PATTERNS.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Preserve hypotheses, decisions, blocker and closure patterns; replace MCP examples and unnecessary approval loops.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/PATTERNS.md", + "task_ids": [ + "2.1" + ], + "reason": "Preserve hypotheses, decisions, blocker and closure patterns; replace MCP examples and unnecessary approval loops. Use marketplace-main append-notes examples; remove redundant approval loops while preserving authority." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/RESUMABILITY.md", + "category": "resource", + "git_blob": "0cf0b257867fbd720cae652ad076e28154ca47d8", + "sha256": "8a7db4e967ace1b4f60dc85e3fb2d02f70749a18056fe90c61b2685bb172d7df", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/RESUMABILITY.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Preserve tested examples, failures, rationale and next action proportionate to task; no raw dumps.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/RESUMABILITY.md", + "task_ids": [ + "2.1" + ], + "reason": "Preserve tested examples, failures, rationale and next action proportionate to task; no raw dumps." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/STATIC_DATA.md", + "category": "resource", + "git_blob": "749dde96056c46c6ab3b90b78c9ec9bcf5ec174c", + "sha256": "610aca9de9cd7ff7bddeb636dd62f7b4b1e3b52694e90ed8c93639e4f17664c6", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/STATIC_DATA.md", + "frontmatter": false, + "recommendation": "defer", + "rationale": "Reference-database pattern is optional; use project memory for reusable knowledge rather than open issues as a glossary by default.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/STATIC_DATA.md", + "task_ids": [ + "2.1" + ], + "reason": "Reference-database pattern is on-demand; use project memory for reusable knowledge rather than open issues as a glossary by default." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/TROUBLESHOOTING.md", + "category": "resource", + "git_blob": "171dd05f1d001cf19941b8944a457be845e468fb", + "sha256": "93dfa0a143a1117de111652cef340ca893e699d91875472d7968160388fa4c77", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/TROUBLESHOOTING.md", + "frontmatter": false, + "recommendation": "replace", + "rationale": "Curate current CLI/guard failure recovery; stale server/export/import advice must not become automatic repair.", + "owner_disposition": "replace", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "replace", + "group": "core-daily", + "target": "assets/oc-beads/resources/TROUBLESHOOTING.md", + "task_ids": [ + "2.1" + ], + "reason": "Curate current CLI/guard failure recovery; stale server/export/import advice must not become automatic repair." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/WORKFLOWS.md", + "category": "resource", + "git_blob": "065cf0f12dd36321b8cfab6e9779523418a5534f", + "sha256": "710a110347e4e17282e1c8469456bf796898ccc2d51a6b5389f25d0c74ba37ea", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/WORKFLOWS.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "Checkpoint and recovery triggers; resolve approval-on-every-handoff and overwriting-notes tension with existing authorization and retained learning.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/WORKFLOWS.md", + "task_ids": [ + "2.1" + ], + "reason": "Checkpoint and recovery triggers; resolve approval-on-every-handoff and historical replacement-notes examples with existing authorization and retained learning. Use marketplace-main append-notes examples; remove redundant approval loops while preserving authority. Current WORKFLOWS line 368 still says overwrite rather than append; correct that conflicting prose, not just examples." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + }, + { + "path": "plugins/beads/skills/beads/resources/WORKTREES.md", + "category": "resource", + "git_blob": "69178e9393c4e55437e748b96a2e72b3f421ad4a", + "sha256": "eb29be5ec2b1d645dbfe583cfc35b06063cfed8670fc76ba276987b486828cff", + "url": "https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads/skills/beads/resources/WORKTREES.md", + "frontmatter": false, + "recommendation": "adapt", + "rationale": "On-demand workspace routing and ownership guidance; no automatic worktree creation or store initialization.", + "owner_disposition": "adapt", + "evidence": "Pinned source inspected; no live runtime qualification in this planning turn.", + "historical_owner_disposition": "pending", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/WORKTREES.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand workspace routing and ownership guidance; no automatic worktree creation or store initialization." + }, + "disposition_basis": "Engineering reconciliation within owner-selected corpus groups; original recommendation and release identity retained." + } + ], + "native_assessment": { + "document": "opencode-native-parity-assessment.md", + "status": "historical-planning", + "checked_on": "2026-10-17", + "release": "2.0.32", + "commit": "843a6c514e606b436a7fe713422ace0a93ad3d61", + "minimum_release": "2.0.26", + "minimum_commit": "9b4ec5714d481559990db0a816d5dec19541a814", + "development_commit": "8f7b0120eb6b4aa3da1f82b1a9094a9fb4db2d06", + "recommended_bundle": "N1 + D01 + D04 (selected)", + "recommended_minimum": "2.0.32 (owner decision pending; no dependency edit)", + "runtime_qualification": "not executed", + "sources": [ + { + "id": "S01", + "path": "packages/plugin/src/promise/session.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/session.ts", + "sha256": "df8e425234da779742770e195ff99302aca59cf3dc8697e7d69985d209c48cdb", + "git_blob": "9794147461d67a5ac7694eb02c309ead5400a0b4" + }, + { + "id": "S02", + "path": "packages/core/src/session/model-request.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/model-request.ts", + "sha256": "776ba86bcc1a82f38c5b2e29235eb77d196c9bab802957fbbef8dc6b7e5a6b42", + "git_blob": "293c93a5e61ce87b42d7e81ced5a06b3b7c0c30c" + }, + { + "id": "S03", + "path": "packages/core/src/plugin/hooks.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/plugin/hooks.ts", + "sha256": "2e475ccada7437c648d81c16403671deeb2de74517e4b05a397c5c2513cbbe48", + "git_blob": "1325837f9d5864b2619b795c2cd1493d291786d1" + }, + { + "id": "S03", + "path": "packages/plugin/src/promise/adapter.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/adapter.ts", + "sha256": "80ba992bede54d6f975cef1025d238b3032a61d862f14815d31982eae60a57e4", + "git_blob": "aacf38893675a69f6d36b8766850d46ea371b388" + }, + { + "id": "S04", + "path": "packages/plugin/src/promise/tool.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/tool.ts", + "sha256": "1b6146b59b90f097fe0f8135b15ac4c51908b2e984b6186548bb8f649cad74b6", + "git_blob": "5eb814e39807fa416dcfaed47e6213922c677113" + }, + { + "id": "S05", + "path": "packages/core/src/tool.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/tool.ts", + "sha256": "53d4b68ad8d7cf4ff4c88bf2c12ab028a0696fd5dfa41fa26951df55d633a2d4", + "git_blob": "a07468a61e18d56be6aec0eb75d499db0ba684c9" + }, + { + "id": "S06", + "path": "packages/core/src/session/compaction.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/compaction.ts", + "sha256": "0ef0aef6759479f76bc0affe8edf8672460bf549da5f501a541ea6250943200b", + "git_blob": "3cb52790295e7057d06573598a2928f3ea27b4cf" + }, + { + "id": "S07", + "path": "packages/schema/src/session-event.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/schema/src/session-event.ts", + "sha256": "5d5adb64afd74130d2376beb69004a51a068b253c42983cabdd214ca56ebd68e", + "git_blob": "0cffae27ad631740c4b7b59c09c01440b4b603a6" + }, + { + "id": "S08", + "path": "packages/core/src/session/execution.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/execution.ts", + "sha256": "f86c35f46c9bae634f955328979c3eade27d5df06e115d677eece7f4e4f1ce3a", + "git_blob": "eeed241ae14e30645d4e0904c69d3ebfe919ef38" + }, + { + "id": "S08", + "path": "packages/core/src/session/projector.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/projector.ts", + "sha256": "b2145973cd8ae49fdbde760cb4277ed61113669447583805fcbd2669ced304b1", + "git_blob": "e007d6f4db86b5f8470076d3f7fb3c66588ab95e" + }, + { + "id": "S09", + "path": "packages/core/src/bus.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/bus.ts", + "sha256": "1bf5e97b3c3618a79487d75adcfddc602d2068c7759d2018eb9ad7d08f4b57f9", + "git_blob": "30bf10a31363954c800f352ad844416aa9e0dfd1" + }, + { + "id": "S09", + "path": "packages/core/src/plugin/host.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/plugin/host.ts", + "sha256": "fac9d0f44b13c2c32c457a4ed5024a6577e4052300b0b44ee8302579965b84d6", + "git_blob": "8d40da4aa8b21f8a254bdbccd2a4158188fb26ae" + }, + { + "id": "S10", + "path": "packages/core/src/session/session.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/session.ts", + "sha256": "53d67aef56a1a6b518fad623d51a15b26f071db25c7e5f1c71a21c36ac3a9d11", + "git_blob": "3cd130086ddd5561501c4b6bc0a348f32b8bc9be" + }, + { + "id": "S10", + "path": "packages/core/src/session.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session.ts", + "sha256": "67979f2c8133b2d350c7779cd2bd7c62b790bc9089ef913e2ca6c41af0882552", + "git_blob": "39f5f36d3a56e017a8b474a52f7d3863ee0effaf" + }, + { + "id": "S11", + "path": "packages/core/src/session/prompt.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/prompt.ts", + "sha256": "4a453f9e57d3cc48fc7d6ea23504d1437c751620d7b97bff795c090f2941fc99", + "git_blob": "80878f354dd682311e518cac132f46b61df77523" + }, + { + "id": "S11", + "path": "packages/core/src/session/inbox.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/inbox.ts", + "sha256": "e849cf92dd802282cba7739f50c7702d4c4e9c31b8299ee9c10353d05d81024a", + "git_blob": "3688575e2e9333055c19b4c029c8de5ddd6df2fe" + }, + { + "id": "S12", + "path": "packages/core/src/tool/plugin/subagent.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/tool/plugin/subagent.ts", + "sha256": "fc6e821262ea17c26e63f2955e6fc1ad17475894701ec79d79494318204ad74b", + "git_blob": "87443f5e8a4b828eede4b632468511693f8ab9ee" + }, + { + "id": "S12", + "path": "packages/core/src/session/subagent-completion.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/subagent-completion.ts", + "sha256": "76fd1bc8cde61470d79aa5144c05d08537b32aa2fd75980b5067270e8d364b03", + "git_blob": "79a00e112aa2f7b6141cef08efd83e3d14fb69d1" + }, + { + "id": "S12", + "path": "packages/core/src/session/subagent-job.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/subagent-job.ts", + "sha256": "fcecd0bc044e643ab749e98aeee08cb40c391bb692fbcf73001b2902f4b62fbd", + "git_blob": "c64cb04732471657e2550b25d60d7a2f7d08a56d" + }, + { + "id": "S13", + "path": "packages/core/src/command.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/command.ts", + "sha256": "51e55a74a561d4e5e03e5435515dac1f367043238789dcbc0c4b364aca977b00", + "git_blob": "8cf1b06bcfbb9a704dd8f2964bfa4c78a081c037" + }, + { + "id": "S13", + "path": "packages/core/src/skill.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/skill.ts", + "sha256": "f857d7ccf3fb770f3dc45c8bc0391c355f753f873ab9f2607a6c63eff03930d6", + "git_blob": "493d1aa2923c111ce84f8c51786c4158bad9edc1" + }, + { + "id": "S13", + "path": "packages/core/src/state.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/state.ts", + "sha256": "ffdaf9268a2cddec8f00870ed78a64031a148142e9c2497fad016e1c07cadea5", + "git_blob": "eafee2696e5645a215c19fdc4db341d283715359" + }, + { + "id": "S14", + "path": "services/www/src/docs/content/build/plugins/index.mdx", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/build/plugins/index.mdx", + "sha256": "873e30bcd87ac702e4be38e8f11ff3c22915083c3c713d63f41668626c2b160a", + "git_blob": "80705a86f947d8ba215591b094c69029793e1808" + }, + { + "id": "S14", + "path": "services/www/src/docs/content/commands.mdx", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/commands.mdx", + "sha256": "ad9e1902d1d09823f3773f6628252ff3ce37fa9307bd118206a56a1114df55df", + "git_blob": "a20d04f6fb301133603743d1bfe18ce6da2f1e89" + }, + { + "id": "S14", + "path": "services/www/src/docs/content/skills.mdx", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/skills.mdx", + "sha256": "9938a918cd4d2078eb7058dcc279ac0cc2c01154e6a167ecc9419f89d81cc032", + "git_blob": "2301c68629cd05a7ccb82e47b7952f8e0610fce5" + }, + { + "id": "S15", + "path": "packages/plugin/src/promise/permission.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/permission.ts", + "sha256": "9209981180f6504ef337b31df333326ccfb33223e2934e2d434973e08656ede2", + "git_blob": "f7eff1a5eb961369e8f0379707d338d2cb82a759" + }, + { + "id": "S15", + "path": "packages/plugin/src/permission.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/permission.ts", + "sha256": "03225ec8a50b9a77e06c74a12070898fa5edcb98fa5cfed4ba8e1518bca572ec", + "git_blob": "73c854f075449e84a06383cb0c19098388cf0195" + }, + { + "id": "S15", + "path": "packages/core/src/permission.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/permission.ts", + "sha256": "d69278896f9a541dac7cf46056c859834d840ecc16ac57a639cbabc1afdda9a5", + "git_blob": "2aecebb0ce347c9acfc7e8dbec0f6c1b7d5951b9" + }, + { + "id": "S16", + "path": "packages/plugin/src/promise/plugin.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/plugin.ts", + "sha256": "3990c2a177ebe917dcd8c94c25c4368d0e293c09e15ef9f8fdc7ce66bd86d081", + "git_blob": "3bb8ddf4effa2616593641d5178cf7be28cb14b9" + }, + { + "id": "S17", + "path": "packages/schema/src/config/experimental.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/schema/src/config/experimental.ts", + "sha256": "fd207aa68daa3550ada1ee41d9d1230be494fb7efb71115fa0f5398222908b9a", + "git_blob": "be9134feeb0d63403bdb0dd7b41149c951a318cc" + }, + { + "id": "S18", + "path": "packages/core/src/tool/plugin/shell.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/tool/plugin/shell.ts", + "sha256": "043dbf4b6c90a4ce2c44d7bbfb2bc62f8bfcc7b0f8f244286e5bc4f2328162ec", + "git_blob": "af454a7d63391473d939436c87fff5c4b73eefb5" + }, + { + "id": "S18", + "path": "services/www/src/docs/content/permissions.mdx", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/permissions.mdx", + "sha256": "68dfdc206ca010d78b0e8695dbc4220bdda8ddacea015b9be306aee23be2f4eb", + "git_blob": "e9c997253a159384884e59586460f542c51bdcf2" + }, + { + "id": "S19", + "path": "services/www/src/docs/content/build/plugins/migrate-v1.mdx", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/build/plugins/migrate-v1.mdx", + "sha256": "e51b691816a29c3fd868b100af7e13e6661fb3828a41c96d4fd1f01c3c5fde77", + "git_blob": "7c1b5e143d67d5a48785babb3b4181242565c4e9" + }, + { + "id": "S19", + "path": "packages/core/src/config/normalize.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/config/normalize.ts", + "sha256": "38812844bbb38621c2a16971dc28bfe04f9362c93406d2054b2db9893f89a8d1", + "git_blob": "af50de40729c5869a2df5dd89ad0fe0fab1bab17" + }, + { + "id": "S20", + "path": "services/www/src/docs/content/compaction.mdx", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/compaction.mdx", + "sha256": "3381a38b1e8b64a767cb0392e3cde3b6ecc3517884d0e82814b3c197587ecb09", + "git_blob": "a22c87e508023b493df005d49931d14a2600b6ee" + }, + { + "id": "S21", + "path": "packages/plugin/src/promise/aisdk.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/aisdk.ts", + "sha256": "120000541b1ef008787bc3a7883f2b0fb9102476bf826502aa3bf1c9fdc02efb", + "git_blob": "9516b306259e62750172c05586fd770e03b05ff0" + }, + { + "id": "S21", + "path": "packages/plugin/src/promise/shell.ts", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/shell.ts", + "sha256": "dc3608b631d2b6600209a465b1d8d7e286a3a812ed57ba307e227ddb4ef16dc6", + "git_blob": "d0e6ae71ed7afef83385782b097a3502f836f52f" + }, + { + "id": "S18", + "path": "packages/core/src/file-access.ts", + "git_blob": "bae484d5d79b52abfae67b1d6e6524f54b06279b", + "sha256": "811ceafb0f9b2f89c169a5d1c1158311cfaca1e9ccb2c5140b486ddf6ba103e9", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/file-access.ts" + }, + { + "id": "S13", + "path": "packages/schema/src/command.ts", + "git_blob": "e1984a0e6af632b14aef77e4fc524ccf522b7335", + "sha256": "6091f667ccf353e2ec7ac93902cd2c58b951236823f9411fb88174f9494932ab", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/schema/src/command.ts" + }, + { + "id": "S13", + "path": "packages/app/src/composer/client-slash-command.ts", + "git_blob": "b149796de2fa6cd49fb3602ae7303c5dd486ea23", + "sha256": "8c43cfe2d81a9f954ab804e6144221af4614af74b2edf08243dc013c3c0b56ee", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/app/src/composer/client-slash-command.ts" + }, + { + "id": "S13", + "path": "packages/app/src/composer/submit.ts", + "git_blob": "be339fdb72338fd8366018a650b71c05f1bb37d7", + "sha256": "0f1c10887b162a84700a539720f8abe4f35b4cfd6fc720ef632e4427bf2e4513", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/app/src/composer/submit.ts" + }, + { + "id": "S13", + "path": "packages/core/src/plugin/supervisor.ts", + "git_blob": "a1bdae8b3acd49b9c8177dd56327e1f62cf63a5f", + "sha256": "7c5b72503909c8dfb20c75315d8f2b4101a48da81f64e2565a330f75afc06666", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/plugin/supervisor.ts" + }, + { + "id": "S13", + "path": "packages/core/src/plugin/internal.ts", + "git_blob": "f6239be4cbd122d97128404402594705f4c981ce", + "sha256": "04d94eb755195c2682cb677bcf6cec2a33726926a096c6c49ebf343cf576bf99", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/plugin/internal.ts" + }, + { + "id": "S13", + "path": "packages/core/src/config/plugin/command.ts", + "git_blob": "a029fb8adeffbed8194be614af4ca793635c540a", + "sha256": "1732401ccab0a8ea3221a1d107fa7e29bf974cd38ba3460cd6a511e3661ba868", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/config/plugin/command.ts" + }, + { + "id": "S18", + "path": "packages/core/src/shell/parse.ts", + "git_blob": "e94f2288eb4e0bdeece051e45374262c16377676", + "sha256": "80179fc5ceff8db1475b45b5be02cd9cd5d01321fe7f7370d64ddfc6bbaa83c9", + "url": "https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/shell/parse.ts" + } + ], + "mechanisms": [ + { + "id": "M01", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M02", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M03", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M04", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M05", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M06", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M07", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M08", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M09", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + }, + { + "id": "M10", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Selected N1 mechanism with limits in native assessment and design; acceptance remains unexecuted." + } + ], + "candidates": [ + { + "id": "X01", + "availability": "released-experimental", + "owner_disposition": "exclude", + "rationale": "No transport rewriting for semantic continuity." + }, + { + "id": "X02", + "availability": "released-experimental", + "owner_disposition": "defer", + "rationale": "Explicit terminal evidence retrieval is not needed for base workflows." + }, + { + "id": "X03", + "availability": "released-experimental", + "owner_disposition": "defer", + "rationale": "Keep normal blocking children; do not change nested-depth config." + }, + { + "id": "X04", + "availability": "released-experimental", + "owner_disposition": "defer", + "rationale": "Respect host parser; no plugin scanner changes." + }, + { + "id": "X05", + "availability": "mixed-v1-deprecated", + "owner_disposition": "exclude", + "rationale": "No deprecated/V1 hook assumptions." + }, + { + "id": "X06", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Support native-provider compaction without replacement or persistence in hook." + }, + { + "id": "X07", + "availability": "released-public", + "owner_disposition": "exclude", + "rationale": "No middleware/shell rewriting; domain hooks suffice." + }, + { + "id": "X08", + "availability": "released-public", + "owner_disposition": "adapt", + "rationale": "Existing read-only diagnostics only; no new service/store/UI." + }, + { + "id": "X09", + "availability": "development-only", + "owner_disposition": "exclude", + "rationale": "Development-only listing is not a released dependency." + } + ], + "package_evidence": [ + { + "package": "@opencode/plugin", + "version": "2.0.26", + "published_at": "2026-10-08T19:45:20.324Z", + "integrity": "sha512-8fu/WokR5hxGjclLQJKL0PAgsMEfh81MHnUBsJuhcnYUiTJ2ZQPLqSYAhzoYEQu/Vj4M65D3Xx7QS0Q9b9Z4fw==", + "tarball": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.26.tgz" + }, + { + "package": "@opencode/plugin", + "version": "2.0.32", + "published_at": "2026-10-16T22:15:43.217Z", + "integrity": "sha512-S83qCwl6VTiRxtpqEmrNr5SHXJDS2FHP9XarTazlSWoccKpefDOdWxwCzF/qkgwQRq09T2t9vVbzv4UuzgFXEw==", + "tarball": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.32.tgz" + } + ], + "declarations_identical_26_32": [ + "session", + "tool", + "skill", + "command", + "agent", + "registration" + ], + "availability_anchors": [ + { + "id": "M01", + "path": "packages/plugin/src/promise/session.ts", + "tokens": [ + "readonly context: SessionContext", + "readonly compaction: SessionCompaction" + ] + }, + { + "id": "M02", + "path": "packages/plugin/src/promise/tool.ts", + "tokens": [ + "\"execute.before\"", + "\"execute.after\"", + "readonly status: \"error\"" + ] + }, + { + "id": "M03", + "path": "packages/core/src/session/compaction.ts", + "tokens": [ + "requests.compaction({", + "if (prepared.event.result)", + "const prepared = yield* prepare(context, context.messages, budget, \"session\")" + ] + }, + { + "id": "M04", + "path": "packages/schema/src/session-event.ts", + "tokens": [ + "session.execution.interrupted", + "session.execution.failed", + "session.step.ended" + ] + }, + { + "id": "M05", + "path": "packages/schema/src/session-event.ts", + "tokens": [ + "session.created", + "session.moved", + "session.deleted", + "session.forked" + ] + }, + { + "id": "M06", + "path": "packages/core/src/session/session.ts", + "tokens": [ + "input.resume !== false", + "execution.wake(sessionID)", + "type: \"synthetic\"" + ] + }, + { + "id": "M07", + "path": "packages/core/src/session/subagent-completion.ts", + "tokens": [ + "sessions.synthetic({", + "metadata: { source: \"subagent\"", + "NO_TEXT" + ] + }, + { + "id": "M08", + "path": "packages/core/src/command.ts", + "tokens": [ + "add: (definition) => editor.set(definition.name, definition)" + ] + }, + { + "id": "M09", + "path": "packages/plugin/src/promise/permission.ts", + "tokens": [ + "readonly assert:", + "readonly hook:" + ] + }, + { + "id": "M10", + "path": "packages/core/src/plugin/hooks.ts", + "tokens": [ + "Scope.addFinalizer(scope, dispose)", + "yield* result" + ] + }, + { + "id": "X01", + "path": "packages/plugin/src/promise/session.ts", + "tokens": [ + "experimental.ws.handshake", + "experimental.ws.send", + "experimental.ws.receive" + ] + }, + { + "id": "X02", + "path": "packages/plugin/src/promise/plugin.ts", + "tokens": [ + "readonly experimental:", + "persistentPty", + "\"read\"" + ] + }, + { + "id": "X03", + "path": "packages/schema/src/config/experimental.ts", + "tokens": [ + "subagent_depth:", + "Defaults to 1." + ] + }, + { + "id": "X04", + "path": "packages/schema/src/config/experimental.ts", + "tokens": [ + "portable_shell_scanner:", + "Defaults to false." + ] + }, + { + "id": "X05", + "path": "services/www/src/docs/content/build/plugins/migrate-v1.mdx", + "tokens": [ + "experimental.compaction.autocontinue", + "experimental.text.complete" + ] + }, + { + "id": "X06", + "path": "packages/core/src/session/compaction.ts", + "tokens": [ + "compactNatively", + "context.model.compaction?.type === \"native\"" + ] + }, + { + "id": "X07", + "path": "packages/plugin/src/promise/shell.ts", + "tokens": [ + "\"create.before\"" + ] + }, + { + "id": "X08", + "path": "packages/plugin/src/promise/plugin.ts", + "tokens": [ + "readonly storage:", + "readonly rpc:", + "readonly reference:" + ] + }, + { + "id": "X09", + "path": "packages/plugin/src/promise/session.ts", + "tokens": [ + "\"context\"" + ], + "absent": [ + "| \"list\"" + ] + } + ], + "selected_minimum": ">=2.0.32 <3" + }, + "marketplace_main": { + "repository": "gastownhall/beads", + "default_branch": "main", + "commit": "4a67d0d0288c1b4ee23e17fc896f47616931ba2d", + "resolved_on": "2026-10-18", + "scope": "All blobs under plugins/beads/ and internal/templates/skills/beads/, marketplace manifest, and explicitly listed supporting implementation/docs. Enumeration is not runtime execution evidence.", + "tree_evidence": "marketplace-main-tree.json", + "commit_evidence": "marketplace-main-commit.json", + "installed_commit": "4a67d0d0288c1b4ee23e17fc896f47616931ba2d", + "installed_plugin_version": "1.3.0", + "installed_match": "All 57 files under plugins/beads byte-match the installed cache; installation/discovery is not behavioral qualification.", + "files": [ + { + "path": ".claude-plugin/marketplace.json", + "category": "manifest-or-hooks", + "git_blob": "aad41ecfb55e1f2141567047e7eb03503c48be89", + "sha256": "408ff701ca7459caf6f4713ff2703d93b24eaf20b4e4baeba7b0a67e4e5498fe", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/.claude-plugin/marketplace.json", + "release_delta": "modified", + "selected_mapping": { + "disposition": "replace", + "group": "native-replacement", + "target": "src/host.ts registration/hooks and src/workflows.ts", + "task_ids": [ + "2.2", + "3.2", + "3.3" + ], + "reason": "Translate discovery/lifecycle intent to native registration, not host-specific manifests." + } + }, + { + "path": "cmd/bd/codex_hook.go", + "category": "support", + "git_blob": "dcbfb787ec4333298d7050ddeaaac70a0bd31c23", + "sha256": "2adab3df97a83045d95b2f44ab1ad1ddfcbb992287c03004e46746eba6f5fd79", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/codex_hook.go", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/init.go", + "category": "support", + "git_blob": "a5b9cd9c8a0a8319b7344450e623ec72d210a70e", + "sha256": "69a31b058e1a41416d3d86644e70631ebac2a503a5027ff7bc84aa7070e307a8", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/init.go", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/init_agent.go", + "category": "support", + "git_blob": "9cc630d716c2d8ac19e9794cd9f8886fbfd0cc33", + "sha256": "46f8e675121900c1b7da9b92c2f0722665cd2708f86c856eb90bafad12282463", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/init_agent.go", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/init_embedded_test.go", + "category": "support", + "git_blob": "dea6ce141cf2fd22a64a7fa50294d3859096bf19", + "sha256": "0a01c9e7088b0b764efa6ee6db6bf1d222ed968cf59a9536ea3173e80bdbf956", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/init_embedded_test.go", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/memory.go", + "category": "support", + "git_blob": "46fce8cd5bdae82fb98458c0303b627c6f1abe38", + "sha256": "95dc71450ec4bf2ae746900d3bc8e864d40878aed8ea2d03ebdacefe6a457253", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/memory.go", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/prime.go", + "category": "support", + "git_blob": "aada0aff8994889eee913e7b1b28198d5591a488", + "sha256": "03e0c9a4219cef2760698b64a062d186bc7ceb8ec815122686c6bb777dfaf4af", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/prime.go", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/prime_memory_caps_test.go", + "category": "support", + "git_blob": "20a1b0fcb254ac754c92db197f57f17b1465bf35", + "sha256": "81abb8df720cbf610d8614185e600f9c15e61201ed34eb6f6f689feba2580b5e", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/prime_memory_caps_test.go", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/setup/agent_skill.go", + "category": "support", + "git_blob": "e423b2e8534adc4f2b95dd4b08a9a0f01a5ff98e", + "sha256": "0caedc17156df34bea312c98c66763925b632b51dc68fdc6c46a09c1bee06353", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/setup/agent_skill.go", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/setup/claude.go", + "category": "support", + "git_blob": "ed9e252660eb5cd0ccda7b7872b3a8111f04dc7d", + "sha256": "aa79481df01669438afaec25e4854ce34bdf6214c98983f000877bdb5565530f", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/setup/claude.go", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/setup/claude_test.go", + "category": "support", + "git_blob": "4d1e178e1ce2dd0f17790d3b698f4ead7e9877ae", + "sha256": "c18abafbaa8c666c773a0293d4ca3619b838da3a2c7e14f0a33259797d5b2b03", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/setup/claude_test.go", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/setup/codex.go", + "category": "support", + "git_blob": "11044b1beefdc27094f797b4a22231a3279bc56d", + "sha256": "aea41b368a17496107caa67e9c4e5177a73dcabae69f441c1f6fa087d2b278b0", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/setup/codex.go", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/setup/opencode.go", + "category": "support", + "git_blob": "a728e07d667f7d86f8b92d6b432fdb6c4383ebd9", + "sha256": "91b214f63018a7c9ed768ce3db6fdef576c5beb4873bebf600b2ac973c7c9aff", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/setup/opencode.go", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "cmd/bd/setup/plugin_layout_test.go", + "category": "support", + "git_blob": "51b3a2a10aeda9958c04eb4b0b89884cebb6c60c", + "sha256": "3f8eccbaf0f4b5e7ea65a1fa2f527b22bd903fd54dab1cde054651eb58cc4e88", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/cmd/bd/setup/plugin_layout_test.go", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "docs/integrations/claude-code-plugin.md", + "category": "support", + "git_blob": "7112f7d404f9b1bc5ee283b7a8a9e0fa6997f48d", + "sha256": "5d43897dc1cc2d50f87de289d23270ca96040073aa2597b08e6919546574cfcc", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/docs/integrations/claude-code-plugin.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "Reference source only; transfer applicable behavior through native host interfaces, not wholesale copying." + } + }, + { + "path": "internal/templates/skills/beads/SKILL.md", + "category": "skill", + "git_blob": "a5a3344cffc25b3563697746b6f15337a4f43e66", + "sha256": "1a621606cfa9a14cbacff84bf5c496b4c2c175c2df329e8d19d4e01fc989dedd", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/internal/templates/skills/beads/SKILL.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "preserve-external", + "group": "upstream-owned", + "target": "bd-owned shared beads skill; no writes", + "task_ids": [ + "2.2", + "3.3" + ], + "reason": "Preserve shared skill and metadata; package-owned oc-beads adds the selected rich experience." + } + }, + { + "path": "internal/templates/skills/beads/agents/openai.yaml", + "category": "agent-or-metadata", + "git_blob": "09c3b8f61fd5a6cfabe942909a6ad5658d40ab75", + "sha256": "c7ff618a9a6bcce71b25f722f67861b7685651d08fb72d8fff7d947aac403718", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/internal/templates/skills/beads/agents/openai.yaml", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "preserve-external", + "group": "upstream-owned", + "target": "bd-owned shared beads skill; no writes", + "task_ids": [ + "2.2", + "3.3" + ], + "reason": "Preserve shared skill and metadata; package-owned oc-beads adds the selected rich experience." + } + }, + { + "path": "plugins/beads/.claude-plugin/plugin.json", + "category": "manifest-or-hooks", + "git_blob": "f861043e2ee89511f20e678931b9515ecfccb2df", + "sha256": "f2ae1cfc1b9952c6e8885dc0db8db92c32a2e0eca9a64b83dd7c190e53e4b195", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/.claude-plugin/plugin.json", + "release_delta": "modified", + "selected_mapping": { + "disposition": "replace", + "group": "native-replacement", + "target": "src/host.ts registration/hooks and src/workflows.ts", + "task_ids": [ + "2.2", + "3.2", + "3.3" + ], + "reason": "Translate discovery/lifecycle intent to native registration, not host-specific manifests." + } + }, + { + "path": "plugins/beads/.codex-plugin/hooks/hooks.json", + "category": "manifest-or-hooks", + "git_blob": "9201db27b183ca6c3b1b896a4e68f0d6332c06a6", + "sha256": "383b2a4bb9a0a6b1c8e821f360bb9c1f5109c34eb7c02e7382dea1adc6aa0481", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/.codex-plugin/hooks/hooks.json", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + } + }, + { + "path": "plugins/beads/.codex-plugin/plugin.json", + "category": "manifest-or-hooks", + "git_blob": "bf76223b389566368ceaee2f8f4d9a3ea948593d", + "sha256": "8be296fa0eed2474cc174dd6bd3ed359e4247d25e5a2f5efdc71eb027cbec9bf", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/.codex-plugin/plugin.json", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + } + }, + { + "path": "plugins/beads/.copilot-plugin/plugin.json", + "category": "manifest-or-hooks", + "git_blob": "90a496a4371fb5012578caface56fbe0eec522e7", + "sha256": "7edb5507f855556510aea4eb0ea38330a0b4674d3b9c5e464139e51f7583874f", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/.copilot-plugin/plugin.json", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + } + }, + { + "path": "plugins/beads/BUILD.bazel", + "category": "build-support", + "git_blob": "feed1f222324204b5223a66e31bca223a98b88b5", + "sha256": "4f05ccbc9c63a52d1466446d879cf4d125bb9d3d903d85967962ba1a235f545a", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/BUILD.bazel", + "release_delta": "added", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + } + }, + { + "path": "plugins/beads/README.md", + "category": "support", + "git_blob": "f897aa8c7524ae554483675faf63275bcbf73232", + "sha256": "2effe26178ea7a51475126732a8ddd999e5a04664881b8d6a0fdd15c27ce9504", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/README.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "F12: preserve installation-path distinctions in product documentation, not runtime injection." + } + }, + { + "path": "plugins/beads/agents/task-agent.md", + "category": "agent-or-metadata", + "git_blob": "7d04bb860553ac03c139478b295550418f72979c", + "sha256": "d5dd212e4f6324cf9f979eb8ed96ee094174c3e0cda8e217e55643244a7f4a79", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/agents/task-agent.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "src/guidance.ts and src/agents.ts; existing agent IDs", + "task_ids": [ + "4.1", + "4.2" + ], + "reason": "Scope-bounded continuation, atomic claims, per-bead evidence and semantic handoff through normal blocking children." + } + }, + { + "path": "plugins/beads/copilot_manifest.go", + "category": "support", + "git_blob": "d3ca8a206c3e1d01c10580fac559f19ac93ba3f5", + "sha256": "6cb9b61e868d38fc9eadca3ff6e985548a98f1c90f6bebd5bee4ca7302534451", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/copilot_manifest.go", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + } + }, + { + "path": "plugins/beads/skills/beads/CLAUDE.md", + "category": "support", + "git_blob": "2581bd4501eeaa35d8773732865c650c7831990e", + "sha256": "fdb958aad2188f0304cc76120727a7c3a214f072e94ee886d71bd247742bd09a", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/CLAUDE.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "F12: maintainer guidance and ADR intent remain evidence, not automatic runtime instructions." + } + }, + { + "path": "plugins/beads/skills/beads/README.md", + "category": "support", + "git_blob": "a7723cfaeb74647a0a62f53dc1cb9e9179358702", + "sha256": "06be6931db34e7cc545e7714a79a3019c6b798c5ba19edc877e2d09da15a2c84", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/README.md", + "release_delta": "modified", + "selected_mapping": { + "disposition": "reference-only", + "group": "support-evidence", + "target": "docs evidence and native adaptation; no upstream source copying", + "task_ids": [ + "2.1", + "3.1", + "5.1" + ], + "reason": "F12: preserve useful recovery rationale; distinguish manual skill copy from full marketplace install." + } + }, + { + "path": "plugins/beads/skills/beads/SKILL.md", + "category": "skill", + "git_blob": "5a1968523adfc4f3f51a6257f550079e9dd98b29", + "sha256": "01555fe65d19be401d820d9dec029cd048fb0791d433b4b575374477d6f1d827", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/SKILL.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/SKILL.md", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Rich progressive guidance with native tools and corrected authority; shared beads remains untouched." + } + }, + { + "path": "plugins/beads/skills/beads/adr/0001-bd-prime-as-source-of-truth.md", + "category": "adr", + "git_blob": "8ea7996841479c8ef4d20e6cd254365d5fb1cd1a", + "sha256": "29985565ab384b381d7288f2c01e19e6d06d760c560152a1238151731a35ca91", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/adr/0001-bd-prime-as-source-of-truth.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "rationale", + "target": "assets/oc-beads/adr/0001-bd-prime-as-source-of-truth.md", + "task_ids": [ + "2.1" + ], + "reason": "Retain canonical dynamic CLI reference principle as rationale, not a guarantee against drift." + } + }, + { + "path": "plugins/beads/skills/beads/agents/openai.yaml", + "category": "agent-or-metadata", + "git_blob": "09c3b8f61fd5a6cfabe942909a6ad5658d40ab75", + "sha256": "c7ff618a9a6bcce71b25f722f67861b7685651d08fb72d8fff7d947aac403718", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/agents/openai.yaml", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "reference-only", + "group": "host-specific", + "target": "source review only; no OpenCode payload", + "task_ids": [ + "5.1" + ], + "reason": "Host metadata/build wiring is evidence, not executable OpenCode behavior; useful shared semantics retained elsewhere." + } + }, + { + "path": "plugins/beads/skills/beads/commands/audit.md", + "category": "command", + "git_blob": "9c737eed8d9aa9d6249b72aa5fd0846205907d18", + "sha256": "bf941ae72f3dfad0e5a0997c8afa1647d6ce17934692efa30e9fe4da98e39de8", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/audit.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:audit", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Expose supported audit logging/label workflow only on request; no automatic transcript collection or telemetry." + } + }, + { + "path": "plugins/beads/skills/beads/commands/blocked.md", + "category": "command", + "git_blob": "83baf87d8fc993cb7c2d9a54db27499cc609530f", + "sha256": "30181c3645b5e233005facde694a3bb001c5935258745ae976abfe7b93eec346", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/blocked.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:blocked", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Useful blocker view; preserve read-only intent and distinguish blocked status from dependency blocking." + } + }, + { + "path": "plugins/beads/skills/beads/commands/close.md", + "category": "command", + "git_blob": "82bd27a03b69d8df5361427bd962c3eeb7a930c2", + "sha256": "ef43e31c9f86082a6192ca131c175c55ee37dc115f4781413be11f1e85274b58", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/close.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:close", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Replace MCP with CLI; require completion evidence and relevant validation, not merely a successful close call." + } + }, + { + "path": "plugins/beads/skills/beads/commands/comments.md", + "category": "command", + "git_blob": "a21018db24707b4e308b7ad6074bfb8449e98b2d", + "sha256": "cad54141ee590be3b8d07878bb3ea34c252466bbb00a9b95d7d521303a8e3b83", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/comments.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:comments", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Keep discussion/checkpoint history; use noninteractive field updates instead of bd edit advice." + } + }, + { + "path": "plugins/beads/skills/beads/commands/compact.md", + "category": "command", + "git_blob": "5543e85ed80cb398705c82a801627902abb47869", + "sha256": "5204417505320501e2c5a529da6c670826ef5b62feab708a33b7452dfd6a80c4", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/compact.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:compact", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Explicit administrative issue-compaction workflow, distinct from host conversation compaction; verify current CLI semantics and authority." + } + }, + { + "path": "plugins/beads/skills/beads/commands/create.md", + "category": "command", + "git_blob": "ee3a912de6c3778cd089885588829b775b41c154", + "sha256": "1e27f3ff4f3ae041f6896b5181f0b0399ff80d52cd415dfd8cf6584a218736d3", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/create.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:create", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "CLI structured creation with acceptance/design and discovered-from where applicable; avoid redundant questions when intent is known." + } + }, + { + "path": "plugins/beads/skills/beads/commands/decision.md", + "category": "command", + "git_blob": "6785eaa5d94543de0dfb5c105cc3ef29970c8b5c", + "sha256": "78c0de4a0979502cb553c2ad49df32cc1e6a7cb3eec25dc41f60290ad4fbbe36", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/decision.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:decision", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Retain rationale and alternatives; link OpenSpec intended behavior and docs rationale without duplicating their authority." + } + }, + { + "path": "plugins/beads/skills/beads/commands/delete.md", + "category": "command", + "git_blob": "b8c6eb61953af0ab25cbd40cc1f050482cf92106", + "sha256": "a6a5e943cccc75c27820548ec4a810894be646604b98821822968c7cb55552eb", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/delete.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:delete", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Explicit scoped deletion under normal permissions; never substitute for closure or infer deletion authority." + } + }, + { + "path": "plugins/beads/skills/beads/commands/dep.md", + "category": "command", + "git_blob": "c02818b741c496b194e0c4d7c3cfbe644008aef6", + "sha256": "2d50ea5c65e0ce27bc076e77de5ea32de7086b34c6f49736e71517548cfe4b76", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/dep.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:dep", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Retain graph operations; correct conflicting prose about dependency direction and verify X depends on Y." + } + }, + { + "path": "plugins/beads/skills/beads/commands/epic.md", + "category": "command", + "git_blob": "4454d26ca06b03c1bf528e03f486a2484f00175a", + "sha256": "174f54e3092e5a4b2f4c010e95418b13f1d910e185349ba0e131b04cf5783b8b", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/epic.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:epic", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Retain hierarchy/status; do not equate all children closed with validated parent acceptance." + } + }, + { + "path": "plugins/beads/skills/beads/commands/export.md", + "category": "command", + "git_blob": "45612e49d361b2e903bda86c8c590ab0f469361c", + "sha256": "5c8e9c9b4df71c804f489fa5785d607be971b7f62c836ad531f11e8b105ee47a", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/export.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:export", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Useful explicit interchange; distinguish issue export from full backup and memory-inclusive export." + } + }, + { + "path": "plugins/beads/skills/beads/commands/import.md", + "category": "command", + "git_blob": "318cf83e2006a57934d97c05468843d7254041b4", + "sha256": "604e48e6d093d9a93f28a3a5ed7dd7946b10e7a4897bec8a88eefc5b6c9c776e", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/import.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:import", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Preserve documented stale/upsert/partial-write cautions and route limits; never implicit setup or restore." + } + }, + { + "path": "plugins/beads/skills/beads/commands/init.md", + "category": "command", + "git_blob": "805032cb5b9bad44f959963c0b7f8c6172ff1188", + "sha256": "975759ee3d7ac6611fa54ae27c277a2995288a8fd089e6b46b791f73f3322177", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/init.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "replace", + "group": "supported-admin", + "target": "oc-beads:init", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Human-directed upstream setup guidance with side effects; no automatic initialization." + } + }, + { + "path": "plugins/beads/skills/beads/commands/label.md", + "category": "command", + "git_blob": "579851759a983b9a8329eb6a3245d9a99fbb8fe9", + "sha256": "45f0422cce8764a19158a98e95b6b3c790e7a01b3438d9b5471ddb5f8c544129", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/label.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:label", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Useful metadata operations; respect existing project taxonomy." + } + }, + { + "path": "plugins/beads/skills/beads/commands/list.md", + "category": "command", + "git_blob": "b3b4fa3fdcecb7f3f12152399b0e1567e2a6db45", + "sha256": "990a0e6ef5af6a6ec596df32590750efd27fd231a44a0882d3f900dc71a4380f", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/list.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:list", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Keep filters with current CLI help; separate retrieval from automatic task selection or mutation." + } + }, + { + "path": "plugins/beads/skills/beads/commands/prime.md", + "category": "command", + "git_blob": "c464c7225058fd152080515e696dcf5ac41f982e", + "sha256": "4b0559e792c2ea8ead359954896eca8518cd311303637b2e1c542e65b9d960b8", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/prime.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:prime", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "No frontmatter upstream; explicit guarded refresh using full CLI context; distinguish missing context from migration hold." + } + }, + { + "path": "plugins/beads/skills/beads/commands/quickstart.md", + "category": "command", + "git_blob": "6bdbe3d8a67781c64e94d5da3a63e6d8ba6a75ec", + "sha256": "de372b2dac3b1786ee383e8295bca781d6ce890db0daa09efa4d044325b80bbc", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/quickstart.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "replace", + "group": "core-daily", + "target": "oc-beads:quickstart", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Replace deprecated bd quickstart with onboarding using current prime/help and installed resources." + } + }, + { + "path": "plugins/beads/skills/beads/commands/ready.md", + "category": "command", + "git_blob": "e6e50c24818c92c76c31ec64425d9246f248f82e", + "sha256": "48a42cab07b8580b0436bbddf5dd409333ff3b402bb4fee7af39b3aa3b078856", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/ready.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:ready", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "CLI ready view; honor already-authorized task choice and atomic claim rather than always asking again." + } + }, + { + "path": "plugins/beads/skills/beads/commands/rename-prefix.md", + "category": "command", + "git_blob": "8df7cb233b76276d9d5d7250357dd6e6dbca0145", + "sha256": "2e6a457dfe58a855daecc0581433584073d7b52565a9bdb85131d1b4a3d920e8", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/rename-prefix.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:rename-prefix", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Explicit project identifier migration guidance with reference impact and current syntax checks; no automatic migration." + } + }, + { + "path": "plugins/beads/skills/beads/commands/reopen.md", + "category": "command", + "git_blob": "7ddb6c07394efbbbfc7326d82dda14801a8e2a57", + "sha256": "ea5535f0004d3137f05b6fc81cff1ecc670d918465b7d878eb6cb01d2427578c", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/reopen.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:reopen", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Useful correction/regression operation with reason and current state check." + } + }, + { + "path": "plugins/beads/skills/beads/commands/restore.md", + "category": "command", + "git_blob": "61a80990cb67b2279a751fb4957d5a2c76e5afa3", + "sha256": "94928e7b5d8ca1da80571e56685020f31eba6ce8fd38100cba09f00a051e164e", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/restore.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:restore", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Historical retrieval useful for recovery; verify current Dolt implementation instead of trusting mixed git/Dolt prose." + } + }, + { + "path": "plugins/beads/skills/beads/commands/search.md", + "category": "command", + "git_blob": "dd611fc47fcc1ea9ea532089274e45f03ede550e", + "sha256": "2295d2cf34f0d7c3a74fab86f73aa312435870c97aa71d8c965486a450310085", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/search.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:search", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Preserve issue search; remove unsupported MCP efficiency claims and distinguish memory search." + } + }, + { + "path": "plugins/beads/skills/beads/commands/show.md", + "category": "command", + "git_blob": "7eda4208a7d15365cded5156a6687ac552da9e53", + "sha256": "51aa4b5a5bebeb9f1027f5a31a3de307dd8116052471f40272f0b2c980c720af", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/show.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:show", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "CLI detail retrieval including notes, acceptance and dependencies; do not mutate when asked to inspect." + } + }, + { + "path": "plugins/beads/skills/beads/commands/stats.md", + "category": "command", + "git_blob": "b9843127742eca6cc1a5a38fe4ef7c1db6e94f52", + "sha256": "f4da8a61fb7922ece1da727f7cfe2e020def6deed7cb2ce807cc6ab6c6d02aca", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/stats.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:stats", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "CLI overview; no automatic claiming from statistics." + } + }, + { + "path": "plugins/beads/skills/beads/commands/sync.md", + "category": "command", + "git_blob": "9841fe4205557d94203d757b1f0e8d77fa1d1aba", + "sha256": "09d999165b93ea183e5828c94d45498503ed179211db5f68b3c32f372ade9ac7", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/sync.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "supported-admin", + "target": "oc-beads:sync", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Current federation-loop bd sync differs from removed JSONL sync; preserve explicit authorization and conflict handling, no forced side selection." + } + }, + { + "path": "plugins/beads/skills/beads/commands/template.md", + "category": "command", + "git_blob": "ad724440593df6a12edc569f499712ff0816b8ff", + "sha256": "dd8865a9556ac67c49e83b7db9ad085e3e1ebf2e187fd6c407a0e116dbfb712b", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/template.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "replace", + "group": "core-daily", + "target": "oc-beads:template", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Replace obsolete static template commands with supported template/formula discovery from installed help; no automatic workflow execution." + } + }, + { + "path": "plugins/beads/skills/beads/commands/update.md", + "category": "command", + "git_blob": "6b705f7e17209890146878712aa242eab43efe2d", + "sha256": "34896bde7871805fb1941afd81a25ebc2ce1030090b8ce96afc63763bd2bd108", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/update.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:update", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Use atomic claim and append-style progress notes; intentional field replacement remains explicit." + } + }, + { + "path": "plugins/beads/skills/beads/commands/version.md", + "category": "command", + "git_blob": "751d293a4828f9693dc819ec53eebbf9e0d97140", + "sha256": "f994ab7afb0aef464fe0e77e7aad522d5f2d1558415d4ba63aede43a7411a075", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/version.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "replace", + "group": "core-daily", + "target": "oc-beads:version", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Report installed CLI/package/host versions and capability limits; no hardcoded release or automatic install." + } + }, + { + "path": "plugins/beads/skills/beads/commands/workflow.md", + "category": "command", + "git_blob": "a91b929e681c26960605f12f6118584c060b984d", + "sha256": "23ce9fbfe8acf49df1b4ddba8f908f4a3ef724c6d9203358c180d019e683978c", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/commands/workflow.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "oc-beads:workflow", + "task_ids": [ + "2.1", + "2.2" + ], + "reason": "Native CLI workflow and recovery entry; remove assumed MCP and unbounded continuation as universal default." + } + }, + { + "path": "plugins/beads/skills/beads/resources/AGENTS.md", + "category": "resource", + "git_blob": "807af0b051008084a7485794289afcabf6bb5a85", + "sha256": "0158c156ffe03858fea3816bc6142cb75133ed54748936a05202b485a02250b5", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/AGENTS.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/AGENTS.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand agent-state/slot guidance; do not require new orchestration for basic task recovery." + } + }, + { + "path": "plugins/beads/skills/beads/resources/ASYNC_GATES.md", + "category": "resource", + "git_blob": "2f99506a68bcf82a3cb1358cdab12bc5203693aa", + "sha256": "76e8d5d07f38226d7d822ad56a48c1f3c2e19556407d56ce97b8b7bd6a320ddd", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/ASYNC_GATES.md", + "release_delta": "modified", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/ASYNC_GATES.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand human/CI wait and resolution concepts; no new gate service or automatic approval." + } + }, + { + "path": "plugins/beads/skills/beads/resources/BOUNDARIES.md", + "category": "resource", + "git_blob": "cb2eaef42e95b9d7875032ac3dd40667523ff86c", + "sha256": "9239200595fdc742bc2422c82bd7e71a55dc1af02ff6eea179642a0276f53a7c", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/BOUNDARIES.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/BOUNDARIES.md", + "task_ids": [ + "2.1" + ], + "reason": "Durable context vs ephemeral execution; reconcile TodoWrite advice with current prime and repository OpenSpec authority." + } + }, + { + "path": "plugins/beads/skills/beads/resources/CHEMISTRY_PATTERNS.md", + "category": "resource", + "git_blob": "2a5abd9840be738fc90be051c20b5971b8544fce", + "sha256": "c81bf240429099eeafd68c06bad4af45b0360dd5d3c6444f6d5b60d31c1641c5", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/CHEMISTRY_PATTERNS.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/CHEMISTRY_PATTERNS.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand molecule/wisp selection guidance; useful extension when recurring workflows justify it." + } + }, + { + "path": "plugins/beads/skills/beads/resources/CLI_REFERENCE.md", + "category": "resource", + "git_blob": "ea24c638738a07cdd914a712275917067f0ce871", + "sha256": "ce9b2ba98f17a54cbfeac3ef7f8854fff93eea8d4114b361588ccfd6f4ad2385", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/CLI_REFERENCE.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/CLI_REFERENCE.md", + "task_ids": [ + "2.1" + ], + "reason": "Keep live prime/help pointers; avoid duplicating CLI reference." + } + }, + { + "path": "plugins/beads/skills/beads/resources/DEPENDENCIES.md", + "category": "resource", + "git_blob": "4f18f3884811c1f21b9697a973f4b7674d0f3496", + "sha256": "9c3327611bfbdc47124736dd0cc928bfeff1c135d4ae79d4ea46cba1900df335", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/DEPENDENCIES.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/DEPENDENCIES.md", + "task_ids": [ + "2.1" + ], + "reason": "Preserve dependency direction, provenance and hierarchy concepts; verify examples against current CLI." + } + }, + { + "path": "plugins/beads/skills/beads/resources/INTEGRATION_PATTERNS.md", + "category": "resource", + "git_blob": "c28cfa900a5d52673c1017dc6eb1aa01d9077de7", + "sha256": "40db2e08e2324bad02ab36679089da99e37d883392422c152b283ee6ac9b6946", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/INTEGRATION_PATTERNS.md", + "release_delta": "modified", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/INTEGRATION_PATTERNS.md", + "task_ids": [ + "2.1" + ], + "reason": "Capture meaning at milestones; map planning to OpenSpec, evidence to docs, execution later to Beads. Use marketplace-main append-notes examples; remove redundant approval loops while preserving authority." + } + }, + { + "path": "plugins/beads/skills/beads/resources/ISSUE_CREATION.md", + "category": "resource", + "git_blob": "cd235495f26dbf4f721ccecf9f67f29f14da343f", + "sha256": "e5d695c9e7d3cb6420e25884a588c1d0903fae0569a0ea079c331ee83fd32d3f", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/ISSUE_CREATION.md", + "release_delta": "modified", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/ISSUE_CREATION.md", + "task_ids": [ + "2.1" + ], + "reason": "Distinguish design from acceptance and capture resumption context; follow existing authorization. Use marketplace-main append-notes examples; remove redundant approval loops while preserving authority." + } + }, + { + "path": "plugins/beads/skills/beads/resources/MOLECULES.md", + "category": "resource", + "git_blob": "b1f380c876a3fc75ae3b50b8ec65e50e44bb9b46", + "sha256": "4dbadf02ef5c4ec585c3aef43fb28e440470b03151a6ff6b49e0c8d51dc9e0a9", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/MOLECULES.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/MOLECULES.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand recurring-work extension, dependent on selected formula/CLI compatibility." + } + }, + { + "path": "plugins/beads/skills/beads/resources/PATTERNS.md", + "category": "resource", + "git_blob": "151896571963d34be38ffa9d67f47706ed3eba53", + "sha256": "43a4fba53096b39ea55d7a00488fe17e0fc74d5867fff2cfe4447f81327e4ac9", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/PATTERNS.md", + "release_delta": "modified", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/PATTERNS.md", + "task_ids": [ + "2.1" + ], + "reason": "Preserve hypotheses, decisions, blocker and closure patterns; replace MCP examples and unnecessary approval loops. Use marketplace-main append-notes examples; remove redundant approval loops while preserving authority." + } + }, + { + "path": "plugins/beads/skills/beads/resources/RESUMABILITY.md", + "category": "resource", + "git_blob": "0cf0b257867fbd720cae652ad076e28154ca47d8", + "sha256": "8a7db4e967ace1b4f60dc85e3fb2d02f70749a18056fe90c61b2685bb172d7df", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/RESUMABILITY.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/RESUMABILITY.md", + "task_ids": [ + "2.1" + ], + "reason": "Preserve tested examples, failures, rationale and next action proportionate to task; no raw dumps." + } + }, + { + "path": "plugins/beads/skills/beads/resources/STATIC_DATA.md", + "category": "resource", + "git_blob": "749dde96056c46c6ab3b90b78c9ec9bcf5ec174c", + "sha256": "610aca9de9cd7ff7bddeb636dd62f7b4b1e3b52694e90ed8c93639e4f17664c6", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/STATIC_DATA.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/STATIC_DATA.md", + "task_ids": [ + "2.1" + ], + "reason": "Reference-database pattern is on-demand; use project memory for reusable knowledge rather than open issues as a glossary by default." + } + }, + { + "path": "plugins/beads/skills/beads/resources/TROUBLESHOOTING.md", + "category": "resource", + "git_blob": "171dd05f1d001cf19941b8944a457be845e468fb", + "sha256": "93dfa0a143a1117de111652cef340ca893e699d91875472d7968160388fa4c77", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/TROUBLESHOOTING.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "replace", + "group": "core-daily", + "target": "assets/oc-beads/resources/TROUBLESHOOTING.md", + "task_ids": [ + "2.1" + ], + "reason": "Curate current CLI/guard failure recovery; stale server/export/import advice must not become automatic repair." + } + }, + { + "path": "plugins/beads/skills/beads/resources/WORKFLOWS.md", + "category": "resource", + "git_blob": "2a6d84097a9d510977c3eb8c58140c072f617f58", + "sha256": "33edff82ae0badba1100e4388d19e8a42058a104d7fade470dbac9868f02e56c", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/WORKFLOWS.md", + "release_delta": "modified", + "selected_mapping": { + "disposition": "adapt", + "group": "core-daily", + "target": "assets/oc-beads/resources/WORKFLOWS.md", + "task_ids": [ + "2.1" + ], + "reason": "Checkpoint and recovery triggers; resolve approval-on-every-handoff and historical replacement-notes examples with existing authorization and retained learning. Use marketplace-main append-notes examples; remove redundant approval loops while preserving authority. Current WORKFLOWS line 368 still says overwrite rather than append; correct that conflicting prose, not just examples." + } + }, + { + "path": "plugins/beads/skills/beads/resources/WORKTREES.md", + "category": "resource", + "git_blob": "69178e9393c4e55437e748b96a2e72b3f421ad4a", + "sha256": "eb29be5ec2b1d645dbfe583cfc35b06063cfed8670fc76ba276987b486828cff", + "url": "https://github.com/gastownhall/beads/blob/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads/skills/beads/resources/WORKTREES.md", + "release_delta": "unchanged", + "selected_mapping": { + "disposition": "adapt", + "group": "advanced-on-demand", + "target": "assets/oc-beads/resources/WORKTREES.md", + "task_ids": [ + "2.1" + ], + "reason": "On-demand workspace routing and ownership guidance; no automatic worktree creation or store initialization." + } + } + ] + }, + "host_baseline_211": { + "version": "2.1.1", + "commit": "ba546ec1598a70db2570e9c6258028d955ba8f58", + "previous_commit": "843a6c514e606b436a7fe713422ace0a93ad3d61", + "admission_range": ">=2.1.1 <2.2", + "exact_live_target": "2.1.1", + "runtime_qualification": "not executed", + "status": "planning-awaiting-review", + "document": "opencode-2.1.1-contract-delta.md", + "cache": "baseline211", + "sources": [ + { + "path": "CHANGELOG.md", + "git_blob": "c2fb8cff2765a3812a239759d4b5aa1d95c0d5d2", + "sha256": "3273cc84e8bda1b36dba4d24a9e34236fcb0c55ff36134b8caef8bbf67242943", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/CHANGELOG.md", + "old_git_blob": "69b05c60d020bdfa14dd7e1277d269b2fa96ffbf", + "delta": "modified" + }, + { + "path": "packages/app/src/composer/client-slash-command.ts", + "git_blob": "b149796de2fa6cd49fb3602ae7303c5dd486ea23", + "sha256": "8c43cfe2d81a9f954ab804e6144221af4614af74b2edf08243dc013c3c0b56ee", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/app/src/composer/client-slash-command.ts", + "old_git_blob": "b149796de2fa6cd49fb3602ae7303c5dd486ea23", + "delta": "unchanged" + }, + { + "path": "packages/app/src/composer/submit.ts", + "git_blob": "be339fdb72338fd8366018a650b71c05f1bb37d7", + "sha256": "0f1c10887b162a84700a539720f8abe4f35b4cfd6fc720ef632e4427bf2e4513", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/app/src/composer/submit.ts", + "old_git_blob": "be339fdb72338fd8366018a650b71c05f1bb37d7", + "delta": "unchanged" + }, + { + "path": "packages/client/src/effect/client.ts", + "git_blob": "c757acc80279aa9653858ad92be71492641519b5", + "sha256": "f7920d3e4c3e3c679fa617710c7944d7f20215b3886c1f84fcdcad4eceae1063", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/client/src/effect/client.ts", + "old_git_blob": "c757acc80279aa9653858ad92be71492641519b5", + "delta": "unchanged" + }, + { + "path": "packages/client/src/promise/client.ts", + "git_blob": "0db239d9cc5844256dff70940932a1c42a29b407", + "sha256": "3282074a27063679230d0ba9b9ac725228c473d8cfc7e7564082a9d18ec6a898", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/client/src/promise/client.ts", + "old_git_blob": "0db239d9cc5844256dff70940932a1c42a29b407", + "delta": "unchanged" + }, + { + "path": "packages/client/src/solid/connection.ts", + "git_blob": "01e6b75b28c1c84d705c74b5f5f31063cc03d6c0", + "sha256": "3b93c8ba83bd7b77bfb6901fa27d062f90e65007a8db0694731c0eab21c19d32", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/client/src/solid/connection.ts", + "old_git_blob": "01e6b75b28c1c84d705c74b5f5f31063cc03d6c0", + "delta": "unchanged" + }, + { + "path": "packages/core/src/bus.ts", + "git_blob": "97f1257a5565e9b8ea61b554e87f62704d824b4c", + "sha256": "2498d2675c99cf74c5e2b3dd6789e09f15bfdac3e7cfabfa0c43a2e9e7ca132c", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/bus.ts", + "old_git_blob": "30bf10a31363954c800f352ad844416aa9e0dfd1", + "delta": "modified" + }, + { + "path": "packages/core/src/command.ts", + "git_blob": "8cf1b06bcfbb9a704dd8f2964bfa4c78a081c037", + "sha256": "51e55a74a561d4e5e03e5435515dac1f367043238789dcbc0c4b364aca977b00", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/command.ts", + "old_git_blob": "8cf1b06bcfbb9a704dd8f2964bfa4c78a081c037", + "delta": "unchanged" + }, + { + "path": "packages/core/src/config/normalize.ts", + "git_blob": "35d702a051f73cafd985235e51b5b4531961a78d", + "sha256": "46bb80421d8b0d95c4ada1e3011942d586591dfb86a2d2c7858d08c7dd204190", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/config/normalize.ts", + "old_git_blob": "af50de40729c5869a2df5dd89ad0fe0fab1bab17", + "delta": "modified" + }, + { + "path": "packages/core/src/config/plugin/command.ts", + "git_blob": "a029fb8adeffbed8194be614af4ca793635c540a", + "sha256": "1732401ccab0a8ea3221a1d107fa7e29bf974cd38ba3460cd6a511e3661ba868", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/config/plugin/command.ts", + "old_git_blob": "a029fb8adeffbed8194be614af4ca793635c540a", + "delta": "unchanged" + }, + { + "path": "packages/core/src/config/plugin/skill-scan.ts", + "git_blob": "c955270c5d554004b73c1661fbd1df4bdd6e286d", + "sha256": "2af4ab4ca137e727d71bda2f480bbf38ba0def3ef6c216d4fcc067d536a01b0e", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/config/plugin/skill-scan.ts", + "old_git_blob": null, + "delta": "added" + }, + { + "path": "packages/core/src/config/plugin/skill.ts", + "git_blob": "be62b9c60c1f6c2a789b1ec874d3e2e10e0c33bd", + "sha256": "d17e3ed5813fba597048c6c7dbb2e4e17caa7cbf21ea04d3835682c9304f680f", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/config/plugin/skill.ts", + "old_git_blob": "1338cab3400b81b42102807d3c80c7e40b07c5b6", + "delta": "modified" + }, + { + "path": "packages/core/src/file-access.ts", + "git_blob": "bae484d5d79b52abfae67b1d6e6524f54b06279b", + "sha256": "811ceafb0f9b2f89c169a5d1c1158311cfaca1e9ccb2c5140b486ddf6ba103e9", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/file-access.ts", + "old_git_blob": "bae484d5d79b52abfae67b1d6e6524f54b06279b", + "delta": "unchanged" + }, + { + "path": "packages/core/src/permission/saved.ts", + "git_blob": "78aa4124c84800a4982a2af06cf431a2325cb577", + "sha256": "ef7418f057b62d4e3e0abb365238eea3ca19d7411b2b25d6b47e6a2cbb01594a", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/permission/saved.ts", + "old_git_blob": "8ba50b16beb100b1d1b811699ef90e83a6fa3cd5", + "delta": "modified" + }, + { + "path": "packages/core/src/permission.ts", + "git_blob": "1dc798807b6c6a2bcc48741190388b02e9515dce", + "sha256": "d34a94cdd526cf74bb954506243e76cf498b07c8b9998fd472da7ea837bcd18f", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/permission.ts", + "old_git_blob": "2aecebb0ce347c9acfc7e8dbec0f6c1b7d5951b9", + "delta": "modified" + }, + { + "path": "packages/core/src/plugin/hooks.ts", + "git_blob": "1325837f9d5864b2619b795c2cd1493d291786d1", + "sha256": "2e475ccada7437c648d81c16403671deeb2de74517e4b05a397c5c2513cbbe48", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin/hooks.ts", + "old_git_blob": "1325837f9d5864b2619b795c2cd1493d291786d1", + "delta": "unchanged" + }, + { + "path": "packages/core/src/plugin/host.ts", + "git_blob": "56f6afde336324ddf88bef4a0f785e2ac1e87781", + "sha256": "aa1e4e95770ad9bf01d4ef8b1284ac654e4d103331c8dbfeca7603b83c2b5efd", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin/host.ts", + "old_git_blob": "8d40da4aa8b21f8a254bdbccd2a4158188fb26ae", + "delta": "modified" + }, + { + "path": "packages/core/src/plugin/internal.ts", + "git_blob": "a6738a1ebf9c5e7bcb0d0d7102ba774950e96083", + "sha256": "7cb539389868b90e6b0d7ac174c17d7329d9074a5dd94acb5d3fc99ed7eab0fc", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin/internal.ts", + "old_git_blob": "f6239be4cbd122d97128404402594705f4c981ce", + "delta": "modified" + }, + { + "path": "packages/core/src/plugin/supervisor.ts", + "git_blob": "a1bdae8b3acd49b9c8177dd56327e1f62cf63a5f", + "sha256": "7c5b72503909c8dfb20c75315d8f2b4101a48da81f64e2565a330f75afc06666", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin/supervisor.ts", + "old_git_blob": "a1bdae8b3acd49b9c8177dd56327e1f62cf63a5f", + "delta": "unchanged" + }, + { + "path": "packages/core/src/session/compaction.ts", + "git_blob": "b2f4599fd8ecefb9116cbcdf9b368981da96bf1b", + "sha256": "f6367aba30c3db4e6084f964645976b1b6105c6e4fe0f6fd11ff481d9dce54c4", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/compaction.ts", + "old_git_blob": "3cb52790295e7057d06573598a2928f3ea27b4cf", + "delta": "modified" + }, + { + "path": "packages/core/src/session/execution.ts", + "git_blob": "eeed241ae14e30645d4e0904c69d3ebfe919ef38", + "sha256": "f86c35f46c9bae634f955328979c3eade27d5df06e115d677eece7f4e4f1ce3a", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/execution.ts", + "old_git_blob": "eeed241ae14e30645d4e0904c69d3ebfe919ef38", + "delta": "unchanged" + }, + { + "path": "packages/core/src/session/history.ts", + "git_blob": "74159a27b2bbab988e520a65691e656c4b335950", + "sha256": "a204b24f425743d9ff01b7e2179a6b448d900672a461faaa421d6f1d43c7adb5", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/history.ts", + "old_git_blob": "aeb7cfc9f81b2d9b27d3e43ff656ed7eb4352a16", + "delta": "modified" + }, + { + "path": "packages/core/src/session/inbox.ts", + "git_blob": "052a15525a76c864805a7fd1eb761558a1b0ce65", + "sha256": "a27d04a9a1a6c3966d5e5c52d45b54f7301ad168a4a8963ed2bd822271640d19", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/inbox.ts", + "old_git_blob": "3688575e2e9333055c19b4c029c8de5ddd6df2fe", + "delta": "modified" + }, + { + "path": "packages/core/src/session/message.ts", + "git_blob": "5ced40fa7729798ccd59603cbcf1c1447bad02c4", + "sha256": "67cffbc5963ea2fc7c8f77b64cea661597a4258ecdf6a6185ef4910cb888e8b9", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/message.ts", + "old_git_blob": "5ced40fa7729798ccd59603cbcf1c1447bad02c4", + "delta": "unchanged" + }, + { + "path": "packages/core/src/session/model-request.ts", + "git_blob": "15c7949ade4a743b40cbaa9f95ceb412a8f5e6b1", + "sha256": "8c9f56b09c4d0f165fabc0d976b8eaabc5dbf19e89f41814b525ecf765cb3cc4", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/model-request.ts", + "old_git_blob": "293c93a5e61ce87b42d7e81ced5a06b3b7c0c30c", + "delta": "modified" + }, + { + "path": "packages/core/src/session/projector.ts", + "git_blob": "63ccbd79c5a03dc6eb81d0add4b87539e458330f", + "sha256": "028c3c19c592d0d4e389f6b595e405651f0082e82fd5cd11abf41054f4e34600", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/projector.ts", + "old_git_blob": "e007d6f4db86b5f8470076d3f7fb3c66588ab95e", + "delta": "modified" + }, + { + "path": "packages/core/src/session/prompt.ts", + "git_blob": "80878f354dd682311e518cac132f46b61df77523", + "sha256": "4a453f9e57d3cc48fc7d6ea23504d1437c751620d7b97bff795c090f2941fc99", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/prompt.ts", + "old_git_blob": "80878f354dd682311e518cac132f46b61df77523", + "delta": "unchanged" + }, + { + "path": "packages/core/src/session/recent-text.ts", + "git_blob": "21cf1710fe4ecff65509ec9a731e386c87800cb9", + "sha256": "3ecc8691774c22a2e20a0899a7a2c37b413b4ae53ee3b7b8dc0f8c49edf37716", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/recent-text.ts", + "old_git_blob": null, + "delta": "added" + }, + { + "path": "packages/core/src/session/runner/to-llm-message.ts", + "git_blob": "bf6cd1413aaadecb6039458192eda263c77a97ea", + "sha256": "acc6b543c5d1629b4c537405135eb49a6345628806a43de156b03a9506debdef", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/runner/to-llm-message.ts", + "old_git_blob": "8d03c824eaafdd0c86244d67b36fa73de6782b75", + "delta": "modified" + }, + { + "path": "packages/core/src/session/session.ts", + "git_blob": "0e44a456b4f2c2452edb677e270812e4392ea652", + "sha256": "2cc1b9c0027640ee8430f9ec66aa08d47e4c987c01df9271246421bb1b0cf68a", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/session.ts", + "old_git_blob": "3cd130086ddd5561501c4b6bc0a348f32b8bc9be", + "delta": "modified" + }, + { + "path": "packages/core/src/session/subagent-completion.ts", + "git_blob": "79a00e112aa2f7b6141cef08efd83e3d14fb69d1", + "sha256": "76fd1bc8cde61470d79aa5144c05d08537b32aa2fd75980b5067270e8d364b03", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/subagent-completion.ts", + "old_git_blob": "79a00e112aa2f7b6141cef08efd83e3d14fb69d1", + "delta": "unchanged" + }, + { + "path": "packages/core/src/session/subagent-job.ts", + "git_blob": "c64cb04732471657e2550b25d60d7a2f7d08a56d", + "sha256": "fcecd0bc044e643ab749e98aeee08cb40c391bb692fbcf73001b2902f4b62fbd", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/subagent-job.ts", + "old_git_blob": "c64cb04732471657e2550b25d60d7a2f7d08a56d", + "delta": "unchanged" + }, + { + "path": "packages/core/src/session.ts", + "git_blob": "46a6eb0b8e3cd533f01e701cd2701c444867ff80", + "sha256": "b4eb5aa67ca511ef901b97ec63b83632f4c6e5764e60f9215ca48bbc12848fac", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session.ts", + "old_git_blob": "39f5f36d3a56e017a8b474a52f7d3863ee0effaf", + "delta": "modified" + }, + { + "path": "packages/core/src/shell/parse.ts", + "git_blob": "e94f2288eb4e0bdeece051e45374262c16377676", + "sha256": "80179fc5ceff8db1475b45b5be02cd9cd5d01321fe7f7370d64ddfc6bbaa83c9", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/shell/parse.ts", + "old_git_blob": "e94f2288eb4e0bdeece051e45374262c16377676", + "delta": "unchanged" + }, + { + "path": "packages/core/src/skill.ts", + "git_blob": "51cf0bc170ad6417c5cd533b4ea4acfee3565b85", + "sha256": "d3fc5e32ec5d0630edb2147c072ebb2a27655a1961f4b0cb1cc2968494aa2234", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/skill.ts", + "old_git_blob": "493d1aa2923c111ce84f8c51786c4158bad9edc1", + "delta": "modified" + }, + { + "path": "packages/core/src/state.ts", + "git_blob": "eafee2696e5645a215c19fdc4db341d283715359", + "sha256": "ffdaf9268a2cddec8f00870ed78a64031a148142e9c2497fad016e1c07cadea5", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/state.ts", + "old_git_blob": "eafee2696e5645a215c19fdc4db341d283715359", + "delta": "unchanged" + }, + { + "path": "packages/core/src/tool/plugin/shell.ts", + "git_blob": "af454a7d63391473d939436c87fff5c4b73eefb5", + "sha256": "043dbf4b6c90a4ce2c44d7bbfb2bc62f8bfcc7b0f8f244286e5bc4f2328162ec", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool/plugin/shell.ts", + "old_git_blob": "af454a7d63391473d939436c87fff5c4b73eefb5", + "delta": "unchanged" + }, + { + "path": "packages/core/src/tool/plugin/subagent.ts", + "git_blob": "87443f5e8a4b828eede4b632468511693f8ab9ee", + "sha256": "fc6e821262ea17c26e63f2955e6fc1ad17475894701ec79d79494318204ad74b", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool/plugin/subagent.ts", + "old_git_blob": "87443f5e8a4b828eede4b632468511693f8ab9ee", + "delta": "unchanged" + }, + { + "path": "packages/core/src/tool.ts", + "git_blob": "e637b8aa9cdb70924b0935f24f16067e6dde986c", + "sha256": "681fcd3128b05bb497bf3992e08a65ed8c1528438c37bee22eb724ecd3fcaa17", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool.ts", + "old_git_blob": "a07468a61e18d56be6aec0eb75d499db0ba684c9", + "delta": "modified" + }, + { + "path": "packages/plugin/src/effect/session.ts", + "git_blob": "91134aa451846c8e204655e1b33342c7738383fe", + "sha256": "7a858bb889e2c4b5484ed075c1857a7254688b6f96697cc6185f3064028cc56c", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/effect/session.ts", + "old_git_blob": "b9f19a6b7249ea901f093809530b8612eab9d178", + "delta": "modified" + }, + { + "path": "packages/plugin/src/permission.ts", + "git_blob": "73c854f075449e84a06383cb0c19098388cf0195", + "sha256": "03225ec8a50b9a77e06c74a12070898fa5edcb98fa5cfed4ba8e1518bca572ec", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/permission.ts", + "old_git_blob": "73c854f075449e84a06383cb0c19098388cf0195", + "delta": "unchanged" + }, + { + "path": "packages/plugin/src/promise/adapter.ts", + "git_blob": "f7088b4cfdc7715dbeaa6032eadc5e9f2eeb374f", + "sha256": "15de1c18718ca2acb574ff9d659e0f2755d4565b5d79999544b41628e8886068", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/adapter.ts", + "old_git_blob": "aacf38893675a69f6d36b8766850d46ea371b388", + "delta": "modified" + }, + { + "path": "packages/plugin/src/promise/aisdk.ts", + "git_blob": "9516b306259e62750172c05586fd770e03b05ff0", + "sha256": "120000541b1ef008787bc3a7883f2b0fb9102476bf826502aa3bf1c9fdc02efb", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/aisdk.ts", + "old_git_blob": "9516b306259e62750172c05586fd770e03b05ff0", + "delta": "unchanged" + }, + { + "path": "packages/plugin/src/promise/permission.ts", + "git_blob": "f7eff1a5eb961369e8f0379707d338d2cb82a759", + "sha256": "9209981180f6504ef337b31df333326ccfb33223e2934e2d434973e08656ede2", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/permission.ts", + "old_git_blob": "f7eff1a5eb961369e8f0379707d338d2cb82a759", + "delta": "unchanged" + }, + { + "path": "packages/plugin/src/promise/plugin.ts", + "git_blob": "3bb8ddf4effa2616593641d5178cf7be28cb14b9", + "sha256": "3990c2a177ebe917dcd8c94c25c4368d0e293c09e15ef9f8fdc7ce66bd86d081", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/plugin.ts", + "old_git_blob": "3bb8ddf4effa2616593641d5178cf7be28cb14b9", + "delta": "unchanged" + }, + { + "path": "packages/plugin/src/promise/session.ts", + "git_blob": "e83a0e1e4cf9881c357cc47d296c7c328827d1ac", + "sha256": "33a0a1d2323850cfbd4c5aa9b887377038b6ca9d8263b3f7d9fe70e59579ee57", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/session.ts", + "old_git_blob": "9794147461d67a5ac7694eb02c309ead5400a0b4", + "delta": "modified" + }, + { + "path": "packages/plugin/src/promise/shell.ts", + "git_blob": "d0e6ae71ed7afef83385782b097a3502f836f52f", + "sha256": "dc3608b631d2b6600209a465b1d8d7e286a3a812ed57ba307e227ddb4ef16dc6", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/shell.ts", + "old_git_blob": "d0e6ae71ed7afef83385782b097a3502f836f52f", + "delta": "unchanged" + }, + { + "path": "packages/plugin/src/promise/tool.ts", + "git_blob": "5eb814e39807fa416dcfaed47e6213922c677113", + "sha256": "1b6146b59b90f097fe0f8135b15ac4c51908b2e984b6186548bb8f649cad74b6", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/tool.ts", + "old_git_blob": "5eb814e39807fa416dcfaed47e6213922c677113", + "delta": "unchanged" + }, + { + "path": "packages/schema/src/command.ts", + "git_blob": "e1984a0e6af632b14aef77e4fc524ccf522b7335", + "sha256": "6091f667ccf353e2ec7ac93902cd2c58b951236823f9411fb88174f9494932ab", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/command.ts", + "old_git_blob": "e1984a0e6af632b14aef77e4fc524ccf522b7335", + "delta": "unchanged" + }, + { + "path": "packages/schema/src/config/experimental.ts", + "git_blob": "515cc77db0741dedad34865007d964024d69292f", + "sha256": "d7bf95cbb14a0f0ae2ceede2d7b8b62cd9708da4d717c9aa0e52116eca22f338", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/config/experimental.ts", + "old_git_blob": "be9134feeb0d63403bdb0dd7b41149c951a318cc", + "delta": "modified" + }, + { + "path": "packages/schema/src/permission-saved.ts", + "git_blob": "29a31d0ca4b5f995ee500a9baa6988f0fcc88917", + "sha256": "8a3496277caf55bcb4c60127b611d9b3172d48225af21a93a15b999c9f44fbc4", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/permission-saved.ts", + "old_git_blob": "29a31d0ca4b5f995ee500a9baa6988f0fcc88917", + "delta": "unchanged" + }, + { + "path": "packages/schema/src/session-event.ts", + "git_blob": "cf26e0543aeb15d04b5fe0262c992e68bd0a7658", + "sha256": "08ad60990a86f6bd21055f1857eef9bcfa5b2f480eb45b8bd92455aeb6290ff7", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/session-event.ts", + "old_git_blob": "0cffae27ad631740c4b7b59c09c01440b4b603a6", + "delta": "modified" + }, + { + "path": "packages/schema/src/session-legacy.ts", + "git_blob": "434e55b2d81725295a1890945bb6ab726143de59", + "sha256": "de9ef8673d3510c953cde52e05b7850a51e752310ff7d5c7137c0c54e364cb46", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/session-legacy.ts", + "old_git_blob": null, + "delta": "added" + }, + { + "path": "packages/schema/src/session-message.ts", + "git_blob": "8c06c0eed092433589cacd13aca127faabb34e35", + "sha256": "dff8db1a0624739684b7697b2e575e5ea942f72b71f4eddab2aa79559cb35408", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/session-message.ts", + "old_git_blob": "cc3d2104a1f9a73ca3a9da6a66ef5f166eec0d89", + "delta": "modified" + }, + { + "path": "packages/schema/src/session.ts", + "git_blob": "9f6cd0d71b0caecfb11ef42b50883fa342b841e0", + "sha256": "3a7d100a8627b62d8530cd99af835cbb756881651382c1461a89eec2746ddcb5", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/session.ts", + "old_git_blob": "b94f0eb2eb6b85886edc78afd73e4b590569fc0e", + "delta": "modified" + }, + { + "path": "packages/tui/src/context/client.tsx", + "git_blob": "cedd8b92118e7686cc83f3b054b7a374964a4a64", + "sha256": "714c7f71f6073d27a1cfd52a421f3940a476eb4749e343925507ab5d8c4a4ea6", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/tui/src/context/client.tsx", + "old_git_blob": "cedd8b92118e7686cc83f3b054b7a374964a4a64", + "delta": "unchanged" + }, + { + "path": "packages/tui/src/mini/stream-v2.transport.ts", + "git_blob": "52117b21d55ccf486f8a69361d0a73f06f8d22a4", + "sha256": "2f9c77680b79c3421212dcd4643411d3668fa046eb8d943ced25bdda504f9dc8", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/tui/src/mini/stream-v2.transport.ts", + "old_git_blob": "896da03839f06251bb0000d94c6f25db5b6cc965", + "delta": "modified" + }, + { + "path": "services/www/src/docs/content/build/plugins/index.mdx", + "git_blob": "d16028d8c9a996d5e271ab7a75200105c1254e91", + "sha256": "1aeb99eace53da31e893b537dd2eecff523013c0e1781064c58d657c7bb34649", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/services/www/src/docs/content/build/plugins/index.mdx", + "old_git_blob": "80705a86f947d8ba215591b094c69029793e1808", + "delta": "modified" + }, + { + "path": "services/www/src/docs/content/build/plugins/migrate-v1.mdx", + "git_blob": "7c1b5e143d67d5a48785babb3b4181242565c4e9", + "sha256": "e51b691816a29c3fd868b100af7e13e6661fb3828a41c96d4fd1f01c3c5fde77", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/services/www/src/docs/content/build/plugins/migrate-v1.mdx", + "old_git_blob": "7c1b5e143d67d5a48785babb3b4181242565c4e9", + "delta": "unchanged" + }, + { + "path": "services/www/src/docs/content/commands.mdx", + "git_blob": "a20d04f6fb301133603743d1bfe18ce6da2f1e89", + "sha256": "ad9e1902d1d09823f3773f6628252ff3ce37fa9307bd118206a56a1114df55df", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/services/www/src/docs/content/commands.mdx", + "old_git_blob": "a20d04f6fb301133603743d1bfe18ce6da2f1e89", + "delta": "unchanged" + }, + { + "path": "services/www/src/docs/content/compaction.mdx", + "git_blob": "aa9c7f6bc101384db4189f3888e35fea38eb5af5", + "sha256": "ac01393c86a17602375fae7048c77497b2c54d50501464f44530da90b216232b", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/services/www/src/docs/content/compaction.mdx", + "old_git_blob": "a22c87e508023b493df005d49931d14a2600b6ee", + "delta": "modified" + }, + { + "path": "services/www/src/docs/content/permissions.mdx", + "git_blob": "e9c997253a159384884e59586460f542c51bdcf2", + "sha256": "68dfdc206ca010d78b0e8695dbc4220bdda8ddacea015b9be306aee23be2f4eb", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/services/www/src/docs/content/permissions.mdx", + "old_git_blob": "e9c997253a159384884e59586460f542c51bdcf2", + "delta": "unchanged" + }, + { + "path": "services/www/src/docs/content/skills.mdx", + "git_blob": "4281110e0f15e6cde1af0868e73add8f441d82ee", + "sha256": "931b5425fffc7339913327fbd5f54416397d664311407eeaaae9e349300da27b", + "url": "https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/services/www/src/docs/content/skills.mdx", + "old_git_blob": "2301c68629cd05a7ccb82e47b7952f8e0610fce5", + "delta": "modified" + } + ], + "package": { + "name": "@opencode/plugin", + "version": "2.1.1", + "tarball": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.1.1.tgz", + "integrity": "sha512-wvbvx/GeFNL7lb2Aj0IlXxPN01J6L1qJ2IbkUE5FaLmnC78aLjibzFP+wuzb28yUAM65dYw7WJRvHv+5GZRL8Q==", + "sha256": "13855b3b7d8eb1110b18d0c1ee34dbd4c0581429c8948a9d294470f5af7ee84d" + }, + "declarations": [ + { + "path": "package/dist/promise/session.d.ts", + "sha256": "786d80d5b61e56b481d017ae2b6f44c3eaa8ffa721dde9e0cbacf765ae5413bf", + "old_sha256": "08f5c8728a15839df02f505e38c4cf1b586ccd2de3885eb4f0e471730680b48b", + "delta": "modified" + }, + { + "path": "package/dist/promise/tool.d.ts", + "sha256": "14ead93a2c03136af9768f9f02447c5fb357d1bce12a4b38a5e9fe31a0e91eff", + "old_sha256": "14ead93a2c03136af9768f9f02447c5fb357d1bce12a4b38a5e9fe31a0e91eff", + "delta": "unchanged" + }, + { + "path": "package/dist/promise/skill.d.ts", + "sha256": "2b300e44c466b6e8fd209309943c73f94795332f3dee4bda13a4ef4ab8c0aa53", + "old_sha256": "2b300e44c466b6e8fd209309943c73f94795332f3dee4bda13a4ef4ab8c0aa53", + "delta": "unchanged" + }, + { + "path": "package/dist/promise/command.d.ts", + "sha256": "7011d6c2bf16fde75786130b5e80269c0e27bec8a8c793ab3ef964b16b3a4205", + "old_sha256": "7011d6c2bf16fde75786130b5e80269c0e27bec8a8c793ab3ef964b16b3a4205", + "delta": "unchanged" + }, + { + "path": "package/dist/promise/agent.d.ts", + "sha256": "462ccafdb0e1809f775366f0a4cf4f4ed195add0d15ecfb8fc1b56b6a49b24a1", + "old_sha256": "462ccafdb0e1809f775366f0a4cf4f4ed195add0d15ecfb8fc1b56b6a49b24a1", + "delta": "unchanged" + }, + { + "path": "package/dist/promise/permission.d.ts", + "sha256": "bc939cc6e47ea0ba50c015c71bf1067556ef9a05580422e20effe0bace5fe190", + "old_sha256": "bc939cc6e47ea0ba50c015c71bf1067556ef9a05580422e20effe0bace5fe190", + "delta": "unchanged" + }, + { + "path": "package/dist/promise/plugin.d.ts", + "sha256": "185cc3ae44209aa3ba4b09c0d0d0624afbfd35e02194bc38a255a7fc61090ab1", + "old_sha256": "185cc3ae44209aa3ba4b09c0d0d0624afbfd35e02194bc38a255a7fc61090ab1", + "delta": "unchanged" + }, + { + "path": "package/dist/effect/session.d.ts", + "sha256": "25835c81c0fc9f48d48d5d6d4c9f70ca6c930488ea84c5868f5617de8bff3392", + "old_sha256": "8367d6e1b154e6bc86cdf57b3ddfc6de2c7c4d46d3cc8ac363dd7692594805f2", + "delta": "modified" + }, + { + "path": "package/dist/effect/tool.d.ts", + "sha256": "84a4655b80d5dce02b55939dd4937d267b469e4aeb1a86d7bbbce88c1671cb2e", + "old_sha256": "84a4655b80d5dce02b55939dd4937d267b469e4aeb1a86d7bbbce88c1671cb2e", + "delta": "unchanged" + }, + { + "path": "package/dist/effect/skill.d.ts", + "sha256": "f9aeb3d79b3758a88367b670357b756ce3cf88c979f50e8c53dcd6e227ec717a", + "old_sha256": "f9aeb3d79b3758a88367b670357b756ce3cf88c979f50e8c53dcd6e227ec717a", + "delta": "unchanged" + }, + { + "path": "package/dist/effect/command.d.ts", + "sha256": "2b19c1f0aaa022725b738745d21b261244251fa3c8bcb9086e2ebfc47d6cc0a2", + "old_sha256": "2b19c1f0aaa022725b738745d21b261244251fa3c8bcb9086e2ebfc47d6cc0a2", + "delta": "unchanged" + }, + { + "path": "package/dist/effect/agent.d.ts", + "sha256": "4924df50a026e0dd2a69ec5b5efbfd7904eedfacd29ede5e2f42ad516090f2da", + "old_sha256": "4924df50a026e0dd2a69ec5b5efbfd7904eedfacd29ede5e2f42ad516090f2da", + "delta": "unchanged" + }, + { + "path": "package/dist/effect/permission.d.ts", + "sha256": "fe27d2a6f0d84dec10cdf93980559d521d5d7912058a8e510ed67f62fd4347ca", + "old_sha256": "fe27d2a6f0d84dec10cdf93980559d521d5d7912058a8e510ed67f62fd4347ca", + "delta": "unchanged" + }, + { + "path": "package/dist/effect/plugin.d.ts", + "sha256": "9cede9ff6900c8b954c26592b491a7a2bc924856f85b90cbc6630dc4df3b191e", + "old_sha256": "9cede9ff6900c8b954c26592b491a7a2bc924856f85b90cbc6630dc4df3b191e", + "delta": "unchanged" + } + ], + "availability_overrides": { + "X09": { + "availability": "released-public", + "disposition": "reference-only", + "reason": "Paginated Effect/Promise list is now released; known-session recovery needs no inventory scan." + } + }, + "task_ids": [ + "1.2", + "1.1", + "2.2", + "3.1", + "3.2", + "3.3", + "4.2", + "6.1", + "6.2", + "6.3" + ] + } +} diff --git a/docs/claude-code-local-reference.md b/docs/claude-code-local-reference.md new file mode 100644 index 0000000..765bf76 --- /dev/null +++ b/docs/claude-code-local-reference.md @@ -0,0 +1,67 @@ +# Installed Claude reference integration + +## Evidence boundary + +The actual `beads@beads-marketplace` plugin is installed and enabled at **local project scope** for the Hermes `/opt/data/opencode-beads` workspace. This records installation/component discovery, not a live model-behavior or functional-parity result. It does not require collaborators to install a Claude plugin to use the OpenCode package. + +## Current OpenCode comparison boundary — 2026-10-21 + +The [installed/model continuation](opencode-installed-model-continuation-vuy9.md) adds genuine OpenAI Sol/high semantic evidence on exact OpenCode 2.1.1 and the unchanged accepted package, including actual failure/partial child, meaningful append/relevant recall, bounded scope and same-provider summary/native recovery. Corrected actual cleanup/17 resource reads are reused; all 19 requirements and 73/74 selected corpus dispositions plus CODE_MODE are mapped. This is a parent-accepted bounded evaluation, not a controlled Claude-versus-OpenCode comparison or a model-quality finding. Ordinary typed execution remains withheld; the internal installed-source qualification seam is scratch-only. The installed Claude identity and all historical source comparisons below are unchanged. All 16 implementation tasks are accepted; release/activation remain separate. + +## Installed identity + +- Claude Code: `2.1.292`. +- Plugin-reported version: `1.3.0` (distinct from the Beads CLI/release version). +- Marketplace: `gastownhall/beads`, name `beads-marketplace`. +- Installed source commit recorded by Claude: `4a67d0d0288c1b4ee23e17fc896f47616931ba2d`. +- Installation path in this runtime: `/opt/data/home/.claude/plugins/cache/beads-marketplace/beads/1.3.0`. +- Local activation: `.claude/settings.local.json`, `enabledPlugins["beads@beads-marketplace"] = true`. +- CLI `plugin list --json` reports `enabled: true`, `scope: local`, and the exact project path. Its separate `projectEnabled: false` field is retained as reported, not treated as a project-scope installation claim. + +Source: [installed revision](https://github.com/gastownhall/beads/tree/4a67d0d0288c1b4ee23e17fc896f47616931ba2d/plugins/beads). The analysis baseline remains [Beads v1.3.1 source](https://github.com/gastownhall/beads/tree/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/plugins/beads). Installation used the marketplace's current checkout, **not** a claim that the pinned release was installed. Behavioral comparisons must record and align the chosen revision, or disclose differences, before attributing outcomes. + +## Reconciled installed/release/current comparison + +On 2026-10-18, read-only GitHub repository/commit/tree APIs resolved default branch `main` to **`4a67d0d0288c1b4ee23e17fc896f47616931ba2d`**, matching the recorded installed revision. All **57** current `plugins/beads/` files byte-match the installed cache. The complete declared current scope has 74 files versus the historical 73; all current blobs are hash-verified against the non-truncated tree. Equality of source identities is established; ancestry is **not** inferred from dates or manifest versions. + +Compared with the historical release `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c`, the full selected scope has one addition, no deletions and 16 edited files: + +| Members | Characterized change and planning consequence | +| --- | --- | +| `plugins/beads/skills/beads/README.md`, resources `WORKFLOWS.md`, `PATTERNS.md`, `INTEGRATION_PATTERNS.md`, `ISSUE_CREATION.md` | Checkpoint examples change `--notes` to `--append-notes`. Preserve previous attempts/learning through append-style checkpoints; do not describe overwriting as the current main recommendation. WORKFLOWS still says "overwrite previous notes, not append" at line 368 and retains approval-on-every-handoff instructions. Its prose conflicts with its updated examples; adapt both to append checkpoints under existing authority. | +| resource `ASYNC_GATES.md` | Adds cross-repository `--repo` guidance for GitHub gates; qualify installed CLI support and normal access rather than enabling a monitor. | +| `.claude-plugin/marketplace.json`, plugin Claude/Codex/Copilot manifests | Version strings 1.3.1 in the release versus 1.3.0 in installed/main; discovery wiring is unchanged. Manifest version does not identify full source behavior. | +| `cmd/bd/prime.go` | Adds disabled-prime behavior before workspace/store access; changes `-C`/redirect/custom-prime and Git routing; distinguishes project facts from operator preferences and recommends append notes. Empty output is not proof of absent memory or a reason to bypass disable/hold. | +| `cmd/bd/memory.go` | Adds opt-in corpus-budget refusal/force semantics, explicit landed-but-uncommitted or indeterminate commit JSON fields, and found-empty versus absent recall. A zero exit is not proof of committed durability. Never silently force, change budgets or commit. | +| `cmd/bd/init.go`, `cmd/bd/init_embedded_test.go` | Home-directory/missing-database safety, explicit routing/initialization boundaries and fixture reorganization/coverage change. Upstream owns setup; this plan neither copies init nor authorizes it. | +| `cmd/bd/setup/claude.go`, `cmd/bd/setup/codex.go` | Adds output-directed installation wrappers; does not change ownership of integrations. | +| added `plugins/beads/BUILD.bazel` | Build/layout metadata only; reference evidence, no OpenCode runtime payload. | + +The remaining **57 selected files** are unchanged from release (including all 29 command bodies, rich skill, agent and ADR); unchanged bytes may still contain obsolete MCP/TodoWrite/setup advice. `--append-notes` already exists in historical CLI source (`cmd/bd/cli_fast_test.go`), so the guidance correction alone does not require a runtime upgrade. Current-source memory/prime changes are not claims about the installed executable. See the separate `marketplace_main` inventory and [design](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md) for the selected acknowledgement and authority contract. + +## Observed component discovery + +`claude plugin details beads@beads-marketplace` reports: + +- **30 skills**: the rich `beads` entry plus all **29 command entries**, including `prime` and `template`. +- **One agent**: `task-agent`. +- **Two hook types**: `SessionStart` and `PreCompact`. +- **Zero MCP servers** and zero LSP servers. + +The installed resource directory contains **15 Markdown resources**. Inspection confirmed that the rich skill and task-agent body byte-match the examined release source. The characterized differences above prevent treating component counts as whole-corpus release equivalence. The static 73-file release inventory remains unchanged and authoritative for its explicitly pinned scope. + +Claude's discovery of `prime` and `template` establishes component recognition in this installed harness. It does not prove successful invocation, argument handling, CLI/MCP resolution, or applicability to the pinned release. The original OpenCode loader's frontmatter limitation is not a Claude discovery limitation. + +## Hook ownership and Codex setup + +`bd setup claude` initially installed a standalone project `SessionStart` hook. After marketplace installation, only that newly generated standalone settings file was removed to avoid duplicate prime execution; marketplace hooks now own Claude startup/compaction wiring. Existing user instructions were preserved. No marketplace payload was modified. + +`bd setup codex` installed the shared `.agents/skills/beads` entry, `.codex/hooks.json`, `.codex/config.toml` enabling hooks, and an `AGENTS.md` section. Setup verification passed. Generated Codex instructions explicitly call for persistent project memory through `bd remember`. This is potential behavior-bearing input to compare, not evidence of actual model compliance. Codex CLI was `0.162.1` when inspected. + +Fresh worker sessions are required to evaluate effective loaded guidance. Do not interpret prior Astra runs as having exercised these newly installed integrations. Shared/generated instruction files and local activation are workspace setup, not implementation of the proposed OpenCode package. + +## Packaging conclusion + +The intended OpenCode product delivers its **complete owner-selected integration through its npm package**: registered command workflows, skill/resource assets, agent policies, lifecycle wiring and selected continuity tools. A separate manual skill/resource installation is not an acceptable hidden prerequisite for advertised capability. The installed `bd` executable and upstream-owned project initialization/data remain external. This refines the proposed packaging goal; the owner has selected N1, the full corpus groups, `oc-beads` and opt-in beta/next; implementation and acceptance remain unexecuted. + +Qualification must inspect the installed artifact and effective registrations, resource reachability, overrides, teardown and a fresh session's actual guidance. Claude component discovery is a useful reference observation, not proof that OpenCode or any model achieves behavioral equivalence. diff --git a/docs/claude-code-parity.md b/docs/claude-code-parity.md index 9961d26..6c2ad86 100644 --- a/docs/claude-code-parity.md +++ b/docs/claude-code-parity.md @@ -1,6 +1,6 @@ # Claude Code Parity -Status: parity with Beads' Claude Code integration established by epic `opencode-beads-unh`. Validated on **OpenCode 2.0.26** with **bd 1.3.1**. +Status: historical **setup-lifecycle** comparison recorded by epic `opencode-beads-unh`, validated on **OpenCode 2.0.26** with **bd 1.3.1**. This did not establish full Claude Code Beads experience parity. The marketplace skill/resources, command catalog, autonomous task agent, and durable knowledge outcomes are now assessed in the [full integration analysis and owner decision packet](claude-code-integration-analysis.md); that broader work is planning only, with owner selections recorded and implementation/behavioral acceptance unexecuted. ## Target diff --git a/docs/code-mode-final-acceptance-vuy9.md b/docs/code-mode-final-acceptance-vuy9.md new file mode 100644 index 0000000..ad08e29 --- /dev/null +++ b/docs/code-mode-final-acceptance-vuy9.md @@ -0,0 +1,22 @@ +# Final beta/next implementation acceptance + +Parent accepts tasks **6.2 and 6.3**, completing all 16 implementation/qualification tasks for `improve-beads-workflow-parity`. This is implementation acceptance, not registry publication or operator activation. + +## Evidence accepted + +- Runtime source `8831169d3c83c333fdf13d4a4efc743eaf495e2b`: 191 source tests, typecheck and package validation; independent corrected-source Opus READY. +- Beads minimum/current **1.3.1**: 63 real disposable CLI checks. Retired 1.3.0 runs remain historical, not compatibility obligations. +- Exact OpenCode **2.1.1**, source `ba546ec1598a70db2570e9c6258028d955ba8f58`: accepted native namespace/permission and explicit Stop/handoff observations. +- Actual installed cleanup/reactivation and all 17 native resource accesses: corrected lifecycle observation verified by parent with 64 evidence assertions. Whole-location reload is not plugin-scope unload; the original stronger failed probe remains intact. +- Genuine `openai/gpt-6.1-sol#high` workflows: parent ran 53 raw-trace assertions and read actual final model outputs for graph projection, exact assignment, authorized continuation, relevant memory, real partial child, fresh/summary/provider-native recovery and permission refusal. Native code-mode actually executed, including concurrent reads and ordered writes. Uncertain create landed once and was separately read back without retry. +- All 19 normative requirements, 73 historical/74 selected-marketplace corpus dispositions, 30 commands and 17 links are explicitly mapped in [the continuation report](opencode-installed-model-continuation-vuy9.md). +- Parent reran final closeout gates: **191 tests passed**, typecheck and packed-package validation passed. The closeout documentation changes produce archive SHA-256 `96deb78254d9a2369a5a16298914ec3037fc0d31dbacaa326ff3f9720eb14457`; runtime source is unchanged from the qualified archive. This is not yet a versioned beta publication. +- Parent docs tests, strict named/all OpenSpec, planning/inventory checks and whitespace passed. Auth access was imported from the current native OpenCode record without refresh; source credential and operator configuration remained unchanged. Owned runtime resources were cleaned. + +The reports retain failed specimen validation as failed work, original location-reload and nested plain-decline observations as historical failures with exact dispositions, and harness errors separately. No comparative model-quality, token-saving, cross-provider checkpoint portability or multi-hour reliability claim is made. + +## Beta release readiness boundary + +The accepted archive was `e952532140b44f812e2660425ec485d3bbd474962dd424ed03d70108593b259f`, with typed tools enabled only through the existing internal qualification seam. **Do not publish the current package unchanged as a code-mode evaluation beta:** ordinary typed activation is still withheld. + +A separately tracked bounded prerelease preparation must provide a public beta-only activation path using the qualified registration, select a prerelease version, update onboarding/changelog, and explicitly publish under npm dist-tag `next` while preserving `latest`. The existing release workflow currently runs `npm publish` without `--tag`; it must not be used blindly for a prerelease. Build/verify one exact archive and verify the registry artifact after separately authorized publication. No stable rollout, operator upgrade or new architecture campaign is required. diff --git a/docs/code-mode-product-investigation.md b/docs/code-mode-product-investigation.md new file mode 100644 index 0000000..869fae3 --- /dev/null +++ b/docs/code-mode-product-investigation.md @@ -0,0 +1,262 @@ +# Code-mode product investigation + +2026-10-21 · `opencode-beads-wgv` · executor `codex:oc-codemode` · parent owns acceptance and closure. + +## Recommended decision + +Keep composition and make one bounded pre-release improvement: publish correctly declared, discriminated results for the five continuity operations, group them using the host's native `beads` namespace, and add **one read-only `query` operation** for ready/list/direct-dependency inspection. Keep the existing 30 commands, shared adapted Beads skill and permissioned native shell. This gives broad work discovery, graph triage and selective recovery a useful structured path without wrapping every CLI verb or building a workflow engine. Implementation requires separate parent dispatch; this investigation changes no product behavior. + +Accept the owner-selected OpenCode 2.1.1 limitation explicitly: a plain decline must prevent that operation from executing, but code mode can report an outer completed/error envelope and continue independently permissioned calls or model steps. Preserve the historical failed stronger guarantee as failed evidence with an accepted limitation. Later operations retain their own permission checks. Explicit Stop/Cancel remains separately unqualified and required; it is not covered by accepting plain-decline continuation. + +## Evidence and boundaries + +Product baseline: `072429f1a7723ade23e756a921924d9d0acacf31`. Host: exact OpenCode 2.1.1 source `ba546ec1598a70db2570e9c6258028d955ba8f58`. Local Beads help identifies 1.3.1, commit `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c`; package minimum remains 1.3.0. Current help is evidence for 1.3.1 syntax, not certification of every flag on the minimum version. + +Read `AGENTS.md`, the Beads skill, `bd --sandbox prime`, and the exact investigation Bead before claiming. Claim used `bd --actor codex:oc-codemode --sandbox update opencode-beads-wgv --claim --json`; readback confirmed `in_progress`, assignee `codex:oc-codemode`, original `created_by: snarkimedes`, and no reported owner. Only this Bead is writable by this worker. Existing dirty documents and concurrent parent OpenSpec edits were left untouched. + +Primary source links below identify immutable host files. Their cached bytes were checked against Git blob IDs in `/opt/data/cache/scratch/opencode-v2-test-gap-assessment/tree.json`. In particular, the cached `minimum/` copies of catalog/instructions match the exact 2.1.1 tree despite that directory's name. Existing installed-host decline evidence in [opencode-host-qualification-vuy2.md](opencode-host-qualification-vuy2.md) is prior evidence, not a host run performed here; its historical disposition may be reconciled by the parent. + +Two isolated probes used installed dependencies and the cached baseline Bun 1.3.14. CLI, permission and session dependencies were fixtures. No host startup, external model/provider request, dependency installation, real fixture database write, operator change, source fix, staging, publication or closure occurred. + +## Complete registration and result path + +1. `src/host.ts` admits the supported host range and owns scoped registrations/cleanup. Ordinary setup **withholds** continuity tools; only the internal `continuityQualified` seam registers `createContinuityTools(...)` through `ctx.tool.transform(editor.add)`. Therefore this defect is not evidence of currently activated operator tools failing. +2. The [Promise tool interface](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/tool.ts) carries `input`, optional `output`, options and Promise execution. The [Promise adapter](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/adapter.ts#L477-L510) spreads the definition into the Effect editor, wraps execution with `Effect.tryPromise`, and supplies execution signal/progress. It does not infer output schemas from returned values or TypeScript interfaces. +3. The [host registry and snapshot](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool.ts#L156-L340) validates registrations, applies transforms and coarse permission visibility, and splits direct tools (`codemode: false`) from code-mode tools (default). These five definitions default to code mode. A whole `shell` deny can remove them because they declare `options.permission: "shell"`; this visibility filter does not replace invocation authorization. The snapshot exposes `execute` plus direct-tool definitions and a separate code-mode inventory. +4. [Code-mode construction](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/codemode/tool.ts#L236-L271) converts each host definition into a callable and uses its declared output schema, falling back to `string | null` when absent. Catalog and execution use the same immutable inventory. Native paths derive from `options.namespace` plus normalized name. +5. Each inner call goes back through host `execute.before`, the actual [tool executor](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool/runtime.ts#L21-L61), and applicable `execute.after`. The plugin checks actual session location, guard, external-directory permission where needed, exact canonical shell resource, abort and location again before spawning. Write actor comes from actual host session/agent identity, never model input; `--sandbox` suppresses auto-push and is not confinement. Beads owns routing/database semantics. +6. The host executor checks/encodes declared output **after** the implementation has run. Effect/Standard schemas validate their shape; raw JSON output schemas only trigger a JSON-value check, not branch validation. `src/continuity.ts` acknowledges writes and invalidates context inside the executor, once, before returning. An outer output-contract failure can therefore follow a real CLI mutation; it cannot justify retrying that mutation. The existing `host.ts` exclusion for outer `execute` and inner `beads_*` events correctly prevents duplicate write acknowledgement. +7. The [code-mode bridge](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/codemode/tool.ts#L89-L150) gives JavaScript declared structured `output` when present, otherwise joined text/null. It returns the program's selected result, warnings/logs, files and per-call trace information in the outer result. Direct tool consumers receive readable `content`; [model replay](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/runner/to-llm-message.ts#L121-L151) uses completed-tool content. Supplying only structured output and an unreadable direct rendering would underserve the direct path. + +### Demonstrated defect and adjacent concrete gaps + +All five definitions (`src/continuity.ts:138–223`) omit `output` and return `{ output: result, content: JSON.stringify(result) }`. The pinned executor dies with `Tool result declared output without an output schema`. Direct-definition tests in `tests/continuity.test.ts` bypass this boundary. + +Re-ran `/opt/data/cache/scratch/opencode-codemode-shape-review/check-output-contract.ts`: exit 0; two harmless fixture reads; as-built definition **Failure** with that exact cause; diagnostic copied definition declaring `{type: "object"}` **Success**. The object-only control is not a production contract: it advertises `{}` and does not describe usable branches or payload fields. + +The new catalog/result-bridge probe also demonstrates the misleading current signature: `tools.beads_show({id: string}): Promise`. A diagnostic discriminated schema produces a discoverable object union and works through the pinned result executor. This is the same root defect in discovery as well as execution, not a second upstream bug. A further diagnostic copied definition returning an undeclared status branch is accepted under a raw JSON output schema: pinned `encodeOutput` checks only JSON-ness in that case, and the code-mode schema bridge treats raw JSON schemas as render-only. This is a concrete enforcement distinction, not a claim that the host promises full raw-schema validation. Our adapter must enforce the outcomes it advertises. + +Other bounded improvements justified by source inspection: + +- `ContinuityResult` is one permissive interface with optional payload fields. It allows impossible combinations and does not tell code which data exists after checking status. Use operation-specific discriminated unions and runtime output declarations. +- Input declarations advertise arbitrary strings while `inputFor` additionally enforces nonempty text, a 64,000-character cap, no NUL, and token syntax for IDs/keys. Reflect these constraints and useful field descriptions in schemas so discovery does not invite predictable rejected calls. +- A post-spawn uncertain write currently lacks `reference`, although its requested issue/key is known. Include that target as a recovery reference without claiming persistence. The host currently cannot retain that reference when the uncertain result omits it. +- If acknowledgement notification throws, line 221 replaces `detail`; retain the original failure/uncertainty detail and append the notification problem. This is a diagnostic-loss path visible in code, not a demonstrated normal production outage. + +No additional upstream executor defect was established here. In particular, do not infer new routing/confinement failures or repair speculative CLI behavior. + +## Contract and discovery shape + +Use the host's existing namespace mechanism: definitions named `show`, `checkpoint`, etc. with `options.namespace: "beads"` and the existing shell permission option. This preserves effective host IDs such as `beads_show` while catalog paths become `tools.beads.show`. Register a short namespace description through `editor.namespace`. Do not retain duplicate flat catalog aliases or pin every large signature inline. + +The pinned [catalog](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/codemode/catalog.ts) uses a default 2,000-unit inline budget based on a four-characters-per-token heuristic, keeps namespace summaries visible and selects signatures within the budget. This is not a tokenizer measurement. The [native search](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/codemode/src/tool-runtime.ts#L89-L107) is synchronous inside code mode: + +```ts +search({ query?: string, namespace?: string, limit?: number, offset?: number }) +// => { items: [{path, description, signature}], remaining, next: {offset} | null } +``` + +Default limit is 10; positive limits and nonnegative offsets are supported. Ranking uses paths, descriptions, namespace descriptions and input-property information; it is tool discovery, not Beads issue/memory search. [Instructions](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/codemode/instructions.ts) support partial catalogs and subsequent catalog changes. No new registry/search service is needed. + +Keep the five input shapes, with proposed namespaced paths: + +```ts +tools.beads.show({id: string}) +tools.beads.checkpoint({id: string, text: string}) +tools.beads.memories({search?: string}) +tools.beads.recall({key: string}) +tools.beads.remember({key: string, text: string}) +``` + +These namespaced paths are **proposed**; current definitions are flat `tools.beads_show`, etc., and ordinary setup withholds them. The existing input shapes were verified from product source, not guessed from a future API. Each call takes one input object and returns a Promise. Always use paths/signatures actually supplied by catalog/search. + +Declare result branches separately, retaining current persistence meanings: + +| Operation | Structured branches and required information | +| --- | --- | +| show | `acknowledged` with operation, reference and issue data; `failed` with operation/detail. Do not reinterpret arbitrary CLI failure as missing. | +| memories | `acknowledged` with operation and string-value map; `failed` with detail. Empty map means successful no matches. | +| recall | `found` with reference/value, including `value: ""`; `missing` with reference; `failed` with detail. Only supported CLI evidence establishes missing. | +| checkpoint | `acknowledged` with target and acknowledged issue; `uncertain` with target/detail after an ambiguous execution. Appending is not idempotent. | +| remember | `acknowledged` with key and CLI acknowledgement; `landed-uncommitted` with key, commit qualification and CLI evidence; `uncertain` with target/detail. Replacing one key is not a transactional handoff. | + +Use literal `operation` and `status` in each schema branch (`anyOf`/equivalent union), with required fields for that branch. Keep strict envelopes and compatible extra CLI fields under `data`; describe stable issue fields for projection, leaving absent fields absent. Do not force a complete upstream issue schema into every catalog signature. A TypeScript union alone does not declare host output. For the smallest delta, declare raw JSON output schemas and enforce their branch invariants in the existing acknowledgement/result construction and focused contract tests; do not assume the host validates raw output against those schemas or introduce a general validation framework. Return concise readable content from the same result, retaining status/reference/detail; code mode consumes the structured branch and need not parse that rendering. + +Permission rejection, guard hold, invalid input, and cancellation before spawn remain thrown host failures/interruptions; do not fabricate an acknowledged or ordinary `denied` persistence result. Promise fulfillment, outer tool completion and trace `completed` each differ from durable success: an `uncertain` result can fulfill normally. Return evidence and inspect state before considering a further write; no automatic retry. + +## Broad workflow coverage and the one added read operation + +Five tools plus native shell are functionally sufficient: [native shell](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool/plugin/shell.ts#L31-L100) exposes `command`, optional `workdir`, `timeout`, `background`, and structured `output`, `exit`, `signal`, `truncated`, optional timeout/status/shell ID. Code can check completion and parse JSON outside model context. However, repeated shell-string construction, quoting, process-status checks and CLI JSON normalization are unnecessary model work for common read discovery. The five tools currently cover continuity well and leave broad work/graph discovery less convenient. + +Propose one additional callable, not a command-per-tool mapping: + +```ts +type QueryInput = + | {view: "ready", assignee?: string, parent?: string, limit?: number} + | {view: "list", status?: string, assignee?: string, parent?: string, limit?: number} + | {view: "dependencies", id: string, direction?: "down" | "up", type?: string, limit?: number}; +// tools.beads.query(input: QueryInput): Promise +// Proposed QueryResult: +// {operation:"query", status:"acknowledged", view, records, complete:boolean} +// | {operation:"query", status:"failed", view, detail:string} +``` + +Use a default limit of 20 and a documented positive cap of 50; fixed argv, no arbitrary command/flags, no claims or automatic traversal. Ready/list records project ID, title, status, priority and available assignee; dependency records additionally retain input root, direction and CLI `dependency_type`. Preserve provenance relations such as `discovered-from` separately from scheduling blockers. Do not recompute readiness from an incomplete graph. + +Map to supported `bd ready --json --limit ...`, `bd list --json --limit ...`, and `bd dep list --direction ... --type ... --json` forms under the same guard/cwd/permission/cancellation boundary. `dep list` returns hydrated related issues with relation type (observed read-only for this investigation's Bead), not an invented `{from,to}` edge API. For dependencies cap the normalized return, mark incomplete when records were omitted, and offer narrower relation selection or normal CLI for the remainder. For list/ready do not claim exhaustive completeness merely because a limited response succeeded; a full cap is conservatively incomplete. Do not invent a cross-backend pagination cursor; current help limits `--offset` to proxied-server routes. Avoid depending on `--brief` until minimum-version support is checked; projection can occur after supported JSON reads. + +| Product workflow | Recommended route and benefit | +| --- | --- | +| Ready work, assigned work, issue search | Query ready/list for concise candidates; exact assignment still wins. Native shell `bd search` and less common filters remain available. Catalog search does not search issue text. | +| Task hydration and dependency graph triage | Show only selected IDs; query direct dependencies/dependents with relation types. Compose bounded adjacency views; use native CLI for tree/whole-graph output. No graph cache or local scheduler. | +| Selective knowledge recovery | Search memories by relevant term, return candidate keys; recall only chosen keys. Do not eagerly hydrate every memory. A known key can be recalled alongside its task. | +| Checkpoint/handoff | Ordered checkpoint and optional justified memory update; return each outcome/reference. Assignment transfer uses a separately authorized atomic CLI update, not implicit tool composition. | +| Wider Beads product | Keep formula, admin, search, lifecycle and advanced CLI operations behind current permissions and current CLI help. Native commands invoke model workflows; callable tools provide structured primitives; the shared skill supplies judgment, authority and supporting resources. | + +Retain all 29 workflow commands plus status in `src/workflows.ts`, their native precedence and the package-owned shared skill. A command executes `session.prompt`; it is not automatically a callable code-mode function. Keep upstream-derived guidance and its 16 explicit resource/ADR links. Add a small project-owned code-mode guidance layer instead of replacing that material with static CLI wrappers or duplicating all command syntax. + +## Representative compositions + +These are design sketches for a separately implemented/qualified namespaced contract, not calls run here. IDs, keys and write content must already be selected within authorized scope. Discovery itself is supported today inside code mode: + +```js +// Current ungrouped catalog: search by term, not namespace "beads". +return search({query: "beads", limit: 6}); +// After proposed grouping: search({namespace: "beads", query: "show", limit: 1}) +// Read returned path/signature before calling it; follow next.offset when needed. +``` + +Known-task recovery with one relevant known memory can overlap independent reads. Do not return raw issue history or all memory values: + +```js +const reads = await Promise.allSettled([ + tools.beads.show({id: "assigned-task"}), + tools.beads.recall({key: "chosen-build-lesson"}) +]); +return reads.map((r, i) => { + if (r.status === "rejected") + return {read: i, outcome: "rejected", detail: String(r.reason)}; + const x = r.value; + if (x.operation === "show" && x.status === "acknowledged") + return {read: i, status: x.status, reference: x.reference, + task: {title: x.data.title, status: x.data.status, + acceptance: x.data.acceptance_criteria, notes: x.data.notes}}; + if (x.operation === "recall" && x.status === "found") + return {read: i, status: x.status, reference: x.reference, value: x.value}; + return {read: i, status: x.status, reference: x.reference, detail: x.detail}; +}); +``` + +Select notes/excerpts according to the recovery question; if notes are long, return an explicitly labelled excerpt and omitted-length indicator or relevant validated structure, then hydrate further as needed. Do not silently discard failed attempts or acceptance blockers. For unknown keys, first use `memories({search: "relevant term"})` and return `Object.keys(result.data)` only on `acknowledged`; let the model choose relevance before recall. This keeps search values out of the prompt even though this CLI operation returns them to the runtime. + +Graph triage separates candidate discovery from judgment. A first `query({view:"ready",parent:"authorized-epic",limit:10})` returns candidate summaries/completeness; readiness supplies no dispatch authority. After choosing a candidate within existing authority, inspect it and its direct blockers concurrently: + +```js +const reads = await Promise.allSettled([ + tools.beads.show({id: "chosen-task"}), + tools.beads.query({view: "dependencies", id: "chosen-task", + direction: "down", type: "blocks", limit: 20}) +]); +return reads.map((r, i) => { + if (r.status === "rejected") return {read: i, outcome: "rejected", detail: String(r.reason)}; + const x = r.value; + if (x.operation === "query" && x.status === "acknowledged") + return {read: i, status: x.status, view: x.view, + complete: x.complete, records: x.records}; + if (x.operation === "show" && x.status === "acknowledged") + return {read: i, status: x.status, reference: x.reference, + acceptance: x.data.acceptance_criteria, notes: x.data.notes}; + return {read: i, status: x.status, detail: x.detail}; +}); +``` + +Do not turn an incomplete/failed dependency read into “unblocked.” Direct queries do not promise a graph snapshot across concurrent writers. Hydrate further only when needed; do not recursively dump the graph into logs. + +Ordered persistence uses explicit status checks. This example assumes both writes were already authorized and the reusable finding already validated and reconciled against its existing key: + +```js +const checkpoint = await tools.beads.checkpoint({ + id: "assigned-task", text: "Measured outcome; evidence path; blocker; next authorized action." +}); +const first = {operation: checkpoint.operation, status: checkpoint.status, + reference: checkpoint.reference, detail: checkpoint.detail}; +if (checkpoint.status !== "acknowledged") + return {checkpoint: first, memory: {status: "not-attempted"}}; +const memory = await tools.beads.remember({ + key: "validated-lesson", text: "Validated reusable finding, scope and evidence." +}); +return {checkpoint: first, memory: {operation: memory.operation, + status: memory.status, reference: memory.reference, + commitIndeterminate: memory.commitIndeterminate, detail: memory.detail}}; +``` + +An exception interrupts this sketch before dependent work; do not catch it and manufacture success. A landed-uncommitted or uncertain memory retains its qualification even after a successful checkpoint. No rollback, exactly-once guarantee or automatic retry is implied. Many handoffs need only a checkpoint and concise return, with no reusable-memory write. + +Atomicity belongs to the individual supported CLI operation. Authorized claiming still uses `bd update --claim --actor --sandbox --json`, followed by separate assignee/status readback. Native `tools.shell({command, workdir, ...})` is the verified shell signature; inspect its exit/timeout/truncation before parsing JSON. Use command-scoped actor attribution on every shell write. The typed write adapter derives its actor from host identity; do not add a model-controlled actor argument to these tools. Parent review does not transfer ownership. + +Batching reduces model round trips; it does not make two writes atomic or idempotent. Never put dependent writes in `Promise.all`. Limit concurrent work to bounded independent reads, await all calls whose completion matters, and inspect every settled result. The host warns that pending calls are interrupted when an execution ends. + +The model still decides relevance, task scope, acceptance sufficiency and whether a finding is reusable. Those decisions cannot be encoded as “if ready, claim/close.” Permissions remain per operation, including later calls following a decline. On rejection, dependent work must stop; independently authorized work may continue under the accepted host limitation. Do not add a plugin decline translator, retry loop or claim that prompt guidance guarantees whole-turn termination. Explicit Stop/Cancel must prevent later dispatch/resumption through the separately measured cancellation path, including child/handoff and late-result behavior. No final checkpoint is promised after Stop, unload or compaction. + +## Context efficiency and measured limits + +The benefit is selective return: raw CLI JSON, unrelated graph nodes, full memory maps and intermediate joins can remain in runtime values instead of the next model request. Native shell JSON parsing can already do this; typed reads improve reliable use and discoverability rather than uniquely enabling it. Small one-off reads may cost more catalog/code than they save. No real token, latency, quality or intervention savings were established. + +Scratch `/opt/data/cache/scratch/opencode-codemode-product-wgv/bridge-probe.ts` uses Git-blob-verified copies of the actual pinned catalog/result bridge (`tool-runtime`, schema rendering, tool, namespace, tool-error) and actual product show plus pinned host output executor. It exercises native search and eight independent fixture reads. A diagnostic union/namespace is a copied definition only. Results in `bridge-results.json`: + +| Measurement | Result | +| --- | --- | +| As-built catalog return | `Promise` | +| Diagnostic catalog/search return | Structured `acknowledged`/`failed` union at `tools.beads.show` | +| Eight verbose fixture results, summed compact JSON bytes | 18,432 | +| Eight projected summaries, compact JSON bytes | 905 | +| Bridge call records | 9: one search plus eight reads | +| Logs | 0 | +| Current primary / task-agent injected guidance bytes | 7,758 / 4,421, excluding prime and task-agent prompt | + +These are synthetic UTF-8 byte counts, not tokens or product savings. The comparison excludes generated program text, catalog/signatures, outer formatting, permission interaction, trace storage and provider replay overhead; the fixture deliberately contains irrelevant long history. Projection runs in the scratch driver using the actual bridge, **not the confined interpreter or an installed host**. A full pinned interpreter comparison was not pursued: three required stdlib files were not available at matching blob IDs in the inspected caches. No dependencies were installed and no mismatched-source run was substituted. + +Budget accounting must include: + +- Catalog namespace summaries, selected signatures, search results, and generated code. Large unions cost context; keep fields and descriptions informative and bounded. Native catalog budget is heuristic, not a guaranteed provider token ceiling. +- Outer `execute` text includes returned value, warnings and **logs**. Returning raw results or logging them defeats projection. Files can also escape as collected result parts; do not use attachments as an accidental raw-dump path. +- Host `toolCalls` retains per-call name/status and nonempty input objects in progress/output metadata. The program itself and write text are model-authored input. Do not claim intermediates or sensitive input are never retained. Ordinary replay uses content rather than dumping every metadata field, but storage/traces and model-visible context are different budgets. +- `src/context.ts` injects full guarded prime plus shared guidance on eligible model requests. Caching avoids subprocess work and stabilizes repeated text; it does not remove that text from the request. Full prime can itself include memory content. Code-mode selection therefore does not eliminate the existing eager prime/memory budget. +- `src/host.ts` keeps bounded durable references and recovery cues, invalidates after writes/events, and contributes compaction references. A summary is not a persistence acknowledgement. Preserve these semantics and native replay metadata. + +Do not change full-prime policy or cache freshness during this pre-release delta. Record current injected bytes separately in the existing acceptance exercise; reducing that policy needs its own evidence and owner decision. + +## Smallest implementation handoff and acceptance additions + +Priority is correctness first, then the single broad read improvement in the same separately authorized candidate. Avoid a new certification campaign or tracking graph. + +| Priority | Named source/test/guidance delta | Acceptance evidence | +| --- | --- | --- | +| P0 | `src/continuity.ts`: real per-operation output schemas/unions; matching input constraints; preserve readable content, uncertain targets and original diagnostic detail. Keep existing guard, permission identity, actor, cancellation and sole acknowledgement boundary. | Deterministic status/payload/schema cases in `tests/continuity.test.ts`; execute real registered definitions through pinned host output validation in the existing host qualification harness. Include one write acknowledgement followed by result delivery: no duplicate write/notification or blind retry. Do not vendor an upstream executor into product tests or add a runtime host dependency merely to duplicate it. | +| P1 | `src/continuity.ts`: one bounded `query` definition using the same fixed-argv invocation boundary; no generic runner API, recursive graph manager or writes. `src/host.ts`: namespace metadata and existing gated registration for six tools; preserve effective IDs, lifecycle and outer-result exclusion. | Query view argv/normalization, relation direction/type, caps/incompleteness, read failure, guard/denial/abort cases in `tests/continuity.test.ts`; registration/disposal/visibility in `tests/host.test.ts`. Extend `scripts/bd-contract.ts` only for the newly used supported read shapes, in its disposable repositories and with explicit authority. Check minimum 1.3.0 flag support before depending on 1.3.1-only options. | +| P1 | `src/guidance.ts`: short discovery, structured-status, projection, independent-read/ordered-write guidance, and accurate accepted plain-decline limitation. `src/workflows.ts`: command common guidance can refer to actually registered tools, keeping shell/help fallback and all 30 commands. Keep shared `assets/oc-beads` skill/resources; reconcile any contradictory unconditional stop language precisely, without rewriting upstream-derived workflows. | Focused `tests/guidance.test.ts`, `tests/workflows.test.ts`, `tests/host.test.ts` assertions that preserve actor/authority, missing versus failed, uncertain writes and no retry. Guidance must not claim typed tools are available before registration or that decline guarantees whole-turn Stop. | + +Reuse `.9` installed/model acceptance (task 6.2) for one representative ready/graph triage, selective recovery and ordered handoff using the actual installed catalog, signatures and host executor. Capture current versus selected return/context bytes with correctness and completeness observations, not an invented savings target. Reuse existing restricted-agent, permission, lifecycle/unload/reactivate cases to cover the added read tool and renamed catalog paths. This probe has not passed those gates. + +Reuse `.6` cancellation/handoff work (task 4.2) for explicit Stop/Cancel during a nested permission wait and in-flight operation, no later dispatch or stopped-parent restart, and recovery from earlier acknowledged records. Reconcile the existing plain-decline case under the accepted limitation: rejected target absent, no plugin retry, later calls independently permissioned, original stronger case remains a historical failure. Keep provider/model/reasoning/install provenance and actual actor separate from author/owner/assignee evidence. Parent maps these additions into existing acceptance and decides when to resume; source readiness is not dispatch permission. + +Defer write wrappers for claim/close/dependencies/admin, arbitrary CLI passthrough tools, bulk transactions/idempotency machinery, persistent graph caches, automatic semantic memory extraction, prime-policy reduction, general benchmarks, upstream patches and new multi-agent automation. Native shell plus the preserved skill/commands covers the wider Beads product without making those additions prerequisites. + +**Decision needed:** parent acceptance of this six-tool, schema-first direction and a separately bounded implementation dispatch. The owner has already accepted plain-decline continuation; do not ask for that decision again. Activation, outstanding installed/model and Stop/Cancel qualification, release and tracker closure remain separately owned. + +## Reproduction commands and disposition + +Executed successfully using command-scoped baseline Bun PATH: + +```sh +PATH=/opt/data/.npm/_npx/60c3515df86f25b1/node_modules/@oven/bun-linux-x64-baseline/bin:$PATH \ + bun /opt/data/cache/scratch/opencode-codemode-shape-review/check-output-contract.ts +PATH=/opt/data/.npm/_npx/60c3515df86f25b1/node_modules/@oven/bun-linux-x64-baseline/bin:$PATH \ + bun /opt/data/cache/scratch/opencode-codemode-product-wgv/bridge-probe.ts +``` + +Additional read-only checks: Git HEAD/status, local source inspection, blob-ID verification against the pinned tree, `bd --sandbox version --json`, current ready/list/dep help and this Bead's direct dependency response. Initial bridge execution failed on a missing scratch `tool-error` module; adding its blob-verified copy resolved it. The added raw-output control initially omitted the required fixture ID and was correctly rejected by input validation; supplying the ID resolved that driver error. The recorded final rerun exited 0. No product tests/typecheck/package checks were needed for this report-only change; the two focused probes supply its runtime evidence. + +Exact repository file added: `docs/code-mode-product-investigation.md`. Exact new scratch entries under `/opt/data/cache/scratch/opencode-codemode-product-wgv/`: `bridge-probe.ts`, `bridge-results.json`, blob-verified source copies `tool-runtime.ts`, `tool-schema.ts`, `tool.ts`, `namespace.ts`, `tool-error.ts`, and `node_modules` symlink to the existing repository dependencies. No existing scratch/source evidence file was changed. + +Evidence/report pointer is appended only to `opencode-beads-wgv`; leave it `in_progress` and assigned to `codex:oc-codemode` for parent review. No acceptance or closure is asserted. diff --git a/docs/code-mode-source-acceptance-vuy10.md b/docs/code-mode-source-acceptance-vuy10.md new file mode 100644 index 0000000..5a69a4a --- /dev/null +++ b/docs/code-mode-source-acceptance-vuy10.md @@ -0,0 +1,17 @@ +# Code-mode source acceptance — tasks 7.1–7.3 + +Parent accepted the corrected source after owner-authorized implementation and closeout. The original plan baseline is `f2d6617619e2c3a35e4aeadd5be0092259b9979c`; the owner subsequently selected Beads CLI minimum **1.3.1**, also the qualification target. Earlier dual-version and not-yet-dispatched wording in the planning package is historical dispatch context, not current execution authority. Only source tasks 7.1–7.3 are accepted here. Remaining tasks 1.1, 4.2, 6.2 and 6.3 stay unchecked. + +- **7.1:** Existing disposable real-CLI contract records 63 passing checks on 1.3.1. The SDK ledger identifies exact binaries, commands and raw captures; 1.3.0 failures remain historical and retired, not passing or blocking supported source acceptance. +- **7.2:** Eleven fixed operations, structured declared outcomes, canonical permission resources, sandbox/actor handling and representative boundary regressions. Existing exact-host scratch executor/catalog observations support result mechanics only, not installed qualification. +- **7.3:** Actual-request identity/capability guidance, packaged CODE_MODE resource and 17 explicit links, 30 retained human commands, conservative freshness and recovery references. Ordinary typed activation remains withheld. + +Independent source review [REVISE](reviews/opus-code-mode-source-vuy10.md) identified one medium recovery-gap overwrite. Sol corrected only `src/host.ts` and `tests/host.test.ts`; all 146 other protected file hashes were preserved. Same-session Opus [recheck READY](reviews/opus-code-mode-source-recheck-vuy10.md) resolved that finding without material correction-caused regressions. Both verdicts are retained. Reviewer model/completion/permission results and exact candidate manifests were parent-verified. + +Parent independently executed the corrected candidate: **191 tests passed, zero failed**, typecheck and packed-package validation passed, whitespace passed. Packed archive SHA-256: `e952532140b44f812e2660425ec485d3bbd474962dd424ed03d70108593b259f`. Optional review observations remain nonblocking; no additional refactoring campaign is implied. + +The source executor was `codex:oc-loop`, session `01a15f23-611b-7f51-886e-f77b574087c8`, Sol/high. Independent reviewer was `claude:aa15a6fe`, session `aa15a6fe-0427-4c80-a763-9d64cb6d12cc`, Opus 5.5/requested medium. Both are stopped; parent owns acceptance and explicit closeout-only assignment handoffs. Beads `.10` and `co7` retain original execution provenance. + +## Remaining gates + +Existing `.2`, `.6` and `.9` own exact OpenCode 2.1.1 native host, explicit Stop/handoff and installed/model/lifecycle qualification. Reuse accepted source evidence and add only their distinct real-host observations. The owner-accepted nested plain-decline continuation remains a failed stronger historical case, not passing Stop/Cancel. The operator OpenCode 2.0.29 is unchanged. This closeout publishes source only; no runtime provisioning, activation, release, archive or new qualification dispatch is authorized by it. diff --git a/docs/integrated-acceptance-vuy8.md b/docs/integrated-acceptance-vuy8.md new file mode 100644 index 0000000..5ea061b --- /dev/null +++ b/docs/integrated-acceptance-vuy8.md @@ -0,0 +1,370 @@ +# Task 6.1 qualification candidate — 2026-10-21 + +**Final parent verification: task 6.1 accepted locally.** Both response-handling +corrections, the deterministic/package checks and the installed-Beads CLI contract +pass. The candidate and acceptance marker remain uncommitted/unpublished; `.8` +stays open for publication/closeout. Live OpenCode qualification is unexecuted. +Earlier incomplete results below are retained as historical phases, not the +current verdict. + +**Hold-retirement disposition (historical phase):** Matt confirmed the Docker Lab-only ancestor hold +was obsolete after the completed Beads 1.3.1 migration and authorized retiring it +and its guidance. Parent removed the exact `/opt/data/cache/MIGRATION-FREEZE` +marker and verified its absence; no command-scoped bypass or replacement marker +was introduced. No separate matching active AGENTS.md/SKILL.md guidance was found. +The freeze failures recorded below are historical evidence, not a current hold. +Removal custody is `/opt/data/cache/scratch/vuy8-retired-freeze-removal.json`. +The guard-classification mismatch remains unresolved, append/memory checks have +not been rerun, and 6.1 remains unchecked. This cleanup does not authorize a guard +source edit, another worker, live host/model work, publication or activation. + +## Identity and consumed evidence + +- Executor `codex:oc-integration`; full Codex session + `01a15c16-2bc1-7841-8289-0cf74b40090b` (environment-provided). Requested route: + `gpt-6.1-sol`, reasoning `high`; no independent model-route attestation made. +- Candidate baseline HEAD `c7d2129584365764b2ea65d50007244cebeb1f6a`, branch + `openspec/improve-beads-workflow-parity`, plus the parent-owned protected local + planning refinements and this worker's two paths below. +- `.8` atomically claimed with + `BEADS_ACTOR=codex:oc-integration bd --sandbox update opencode-beads-vuy.8 --claim --json`. + Readback: `status=in_progress`, `assignee=codex:oc-integration`, + `created_by=unknown`, account/record `owner` absent. Heartbeat's `owner` is the + lease holder, not a stored account-owner observation. +- Consumed `.6`'s explicit `deterministic_contribution_accepted=true` at + `a5d6b2bea779259ba9aa3175ab86fb2f4f2b862b`, evidence + [native assessment](opencode-native-parity-assessment.md), + [Opus READY review](reviews/opus-flow-source-review-vuy6.md), and parent-verified + 160-test/typecheck/package results. Its nested historical `not_started` field + does not replace that acceptance. No `.6` write or repeated independent review. + Source/assets, host/flow/guidance/continuity fixtures, package declarations, + lockfile and package script compare unchanged against that revision (exit 0; + `/opt/data/cache/scratch/vuy8-consumed-source-diff.log`). The additional current + tests include the separately accepted `.7` upstream comparison work. +- Actual CLI: `/usr/local/bin/bd`, + `bd version 1.3.1 (c1c4b642a: HEAD@c1c4b642ac1c)`; + executable SHA-256 `a8f48d771b9e11eccfced4aed72923b59eda746f7a9b425e7ca1af7a51251653`. + Full source pin `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c` from the cached + release tree; downloaded `memory.go`/`admin.go` at that commit match the cache. +- Bun **1.3.14**, existing cached baseline binary. OpenCode **2.1.1**, source + `ba546ec1598a70db2570e9c6258028d955ba8f58`, is only the source/type target; + admission `>=2.1.1 <2.2`. No OpenCode command was run. + +## Commands and actual results + +All logs below are in `/opt/data/cache/scratch/`. The successful Bun route and +both real-contract attempts used this command-scoped environment: + +```sh +TMPDIR=/opt/data/cache/scratch BEADS_ACTOR=codex:oc-integration \ +PATH=/opt/data/.npm/_npx/60c3515df86f25b1/node_modules/@oven/bun-linux-x64-baseline/bin:$PATH \ +bun +``` + +| Arguments / command | Exit | Result / log | +| --- | --- | --- | +| `bun run test:bd-contract` via the requested npm wrapper, before edits | 1 | Did not reach checks: cached wrapper says Bun postinstall was not run; `vuy8-contract-baseline.log` | +| `run test:bd-contract`, original script, cached baseline Bun | 1 | 7 failed checks; `vuy8-contract-baseline-cached-bun.log` | +| `run test:bd-contract`, diagnostic candidate | 1 | 7 failed checks including dependent assertions; `vuy8-contract-diagnostic.log` | +| `test` | 0 | **166 pass, 0 fail**, 1,245 assertions, 12 files; `vuy8-tests.log` | +| `run typecheck` | 0 | `tsc --noEmit`; `vuy8-typecheck.log` | +| `run test:package` | 0 | Packed consumer/import/callback/resource checks; `vuy8-package.log` | +| `openspec validate improve-beads-workflow-parity --strict --no-interactive` | 0 | `vuy8-openspec-change.log` | +| `openspec validate --all --strict --no-interactive` | 0 | 1 passed, 0 failed; `vuy8-openspec-all.log` | +| `python3 docs/validate-parity-planning.py /opt/data/cache/scratch/parity-sources` | 0 | All seven validation groups pass; `vuy8-planning.log` | +| `git diff --check` | 0 | `vuy8-diff-check.log` | + +The first command was exactly +`TMPDIR=/opt/data/cache/scratch BEADS_ACTOR=codex:oc-integration npm exec --yes --package=bun@1.3.14 -- bun run test:bd-contract`. +The cached optimized x64 binary also raised SIGILL on `--version`; the existing +baseline binary returned `1.3.14` and ran the checks. No runtime install, +postinstall repair or upgrade was performed. + +Package identity: `@snarkipus/opencode-beads@1.0.2`, archive +`snarkipus-opencode-beads-1.0.2.tgz`, SHA-256 +`51f0d2ab294e1b23f90bec424ba479fa2162cef621b47e161743eb4d652d8cb1`. +The existing package script deleted its temporary archive/consumer after checking +both exports, all 30 commands, 16 resource links and owned fixture disposal. +This is fixture/package evidence, not installed-host activation. + +## Failures, correction and stop boundary + +1. In the script's disposable repository with no workspace, + `bd --sandbox --actor codex:oc-integration context --json` exits **1** with + `{"error":"cannot resolve repo context: no .beads directory found","schema_version":1}`. + The production guard returns `probe-failed`; the existing contract requires + `uninitialized`. That assertion remains failing. Resolving this distinction + needs parent disposition and potentially production-source authority; no guard + edit, fallback probe or weaker passing assertion was introduced. +2. Disposable `create` and `migrate schema` exit **14** because + `/opt/data/cache/MIGRATION-FREEZE` says: + “Docker Lab-only migration: all other workspaces/stores remain held; do not + migrate or remove without separate approval. docker-lab-3dpc.3”. + The hold is intact. No `BD_MIGRATION_FREEZE_FILE` override, removal, force or + repair was attempted. Fresh disposable init/setup/prime checks did execute; + issue creation, claim/close and schema-refresh outcomes remain unqualified. +3. One stale script assertion called `minimizeBeadsInstructions` without supplied + context and expected deletion by marker alone. The accepted implementation + intentionally retains unique policy. The candidate now checks exact retention + without context and actual replacement with the complete duplicate body + supplied. Both checks pass; this preserves and strengthens the real checks. + +Stopped affected qualification after confirming the guard mismatch and the hold. +No append-retention or memory mutation extension was qualified, and none is +reported as passing. Pinned 1.3.1 source inspection shows remember JSON exposes +`key`, `value`, `action`, without `committed`, `commit_error` or +`commit_indeterminate`; recall renders `found` as `value != ""` and exits 1 for +an empty value. A real found-empty distinction and failed/indeterminate commit +fields were therefore **not observed** on this release. Existing adapter fixtures +cover those shapes separately; no empty store injection, synthetic live result, +unsupported flag or demand for marketplace-main outcomes was added. + +Current help advertises `admin cleanup`, `admin compact`, `admin reset`; +top-level `compact` squashes Dolt history and directs semantic issue compaction +to `admin compact`. Existing packaged guidance asks for current help and explicit +administrative authority. Only help/source were inspected, with no administrative +mutation or backend expansion. CLI routing/database/consistency remain upstream +responsibilities; this qualification introduces no product confinement promise. + +## Candidate custody and remaining acceptance + +Worker changes only: + +- `scripts/bd-contract.ts`: explicit command actor and `--sandbox` for test CLI + calls; actual stdout/stderr failure diagnostics and observed guard kind; correct + supplied-context minimization checks. Existing disposable HOME/root re-exec and + cleanup remain in use. Successful test setup writes use disposable fixture Git + identity, never the human Git identity. +- `docs/integrated-acceptance-vuy8.md`: this evidence and stop report. + +Five protected paths were checked against the supplied SHA-256 manifest before +and after work; all match. Exact readback is +`/opt/data/cache/scratch/vuy8-protected-final.json`. The separately observed +untracked `.claude/settings.local.json` was untouched. No production source, +dependencies, versions, OpenSpec markers or prior review reports changed. + +The worker left `.8` in progress under `codex:oc-integration` for parent review. +Parent subsequently classified it blocked on the two items below, preserving +worker assignment and history. **6.1 is unchecked and incomplete.** Live **1.1, +4.2 and 6.2 remain UNEXECUTED**, including permission correction/decline and the +installed unload/reactivate case. Model behavior, external-directory approvals, +real child delivery and host lifecycle were not evaluated. No installation or +activation, extra agent, staging/commit/push, publication, Dolt remote sync or +closure occurred. Parent owns acceptance and any separately scoped next step. + +## Parent review — incomplete acceptance + +Snarkimedes inspected the complete script diff, the failed CLI-contract log, +the actual freeze marker and production guard source, and verified all five +protected planning/report hashes. The script changes are limited to test actor +attribution/sandboxing, diagnostics and the supplied-context assertion correction; +no production-source change or freeze bypass was made. + +Parent independently ran the existing cached baseline Bun 1.3.14 with its +containing directory on PATH: **166 tests pass, 0 fail**, typecheck passes, and +package smoke passes. Logs are `vuy8-parent-tests.log`, +`vuy8-parent-package.log` under the scratch directory; typecheck returned exit 0 +in the parent tool output. An earlier direct-binary test invocation without Bun +on PATH failed a subprocess lookup; that distinct invocation is preserved in +`vuy8-parent-tests-direct-binary.log`, not represented as a product defect or a +passing run. The worker's requested model/reasoning route was independently +verified in the parent-captured Codex startup banner as gpt-6.1-sol/high. + +A parent-created disposable Git repository with an isolated HOME and no +BEADS_DIR reproduced the exit-1 no-workspace JSON response exactly; the observed +result is retained in `vuy8-parent-context.json`. The guard catches this supported +CLI absence response as `probe-failed`, so the uninitialized-workspace contract +fails. This is our classification mismatch, not broken upstream behavior to +repair. Initial parent probes without a Git boundary instead discovered ancestor +workspace configuration and were not used as this reproduction evidence. + +The ancestor `/opt/data/cache/MIGRATION-FREEZE` explicitly holds other workspaces +and still blocks disposable create/migrate writes. Parent did not rerun the +already-demonstrated blocked mutation path, remove the marker, redirect its check +or weaken the contract assertion. Source inspection of memory output is not +append/memory execution evidence. + +**Disposition:** retain the candidate as partial, leave task 6.1 unchecked and +`.8` blocked. Completing it needs a bounded authorization for our guard's known +no-workspace result handling and authority to run only disposable fixture writes +despite the ancestor hold (or an owner-authorized disposition of that hold). +Do not reopen upstream storage/routing semantics, install a newer CLI, construct +unsupported memory outcomes, or start live host/model acceptance. + +## Resumed phase — authorized guard correction and disposable CLI checks + +**Current result: incomplete; stopped on a new memory-list acknowledgement +mismatch.** Matt authorized the narrow guard correction and disposable rerun in +the same Codex session/actor. This authority supersedes the earlier guard-edit +restriction; the parent review and failed runs above remain historical evidence. +The retired ancestor hold remains absent, with no replacement or bypass. `.8` +was read as blocked/assigned to `codex:oc-integration`, transitioned with +`bd --sandbox --actor codex:oc-integration update opencode-beads-vuy.8 --if-assignee codex:oc-integration --if-status blocked --status in_progress --json`, +then read back in progress with the same assignee, historical `created_by=unknown` +and absent record/account owner. Readbacks are `vuy8-resume-transition.json` and +`vuy8-resumed-readback.json` under the existing scratch directory. + +### Regression and guard correction + +First added a regression using the runner's actual `PrimeProcessError`: exit 1, +empty stderr and stdout +`{"error":"cannot resolve repo context: no .beads directory found","schema_version":1}`. +Focused **RED**: 15 pass, 1 fail, exit 1; the expected uninitialized result was +probe-failed (`vuy8-resume-guard-red.log`). Only afterward, changed +`probeBeadsWorkspace` to recognize that exact error text/schema on exit 1 from +`PrimeProcessError`. It returns absence without marker/store reads or fallback. +Other errors still fail the probe. The negative regression covers an unrelated +exit-1 error, a different exit code, unsupported error schema and interruption; +existing malformed/unsupported context tests remain. Focused **GREEN**: 16 pass, +0 fail, 52 assertions, exit 0 (`vuy8-resume-guard-green.log`). The real CLI +no-workspace guard check now passes too. + +### Actual CLI results and newly demonstrated blocker + +The existing disposable HOME/root re-exec was retained. Every test CLI invocation +carries `--sandbox --actor codex:oc-integration`; no project tracker was used for +fixture operations. Append acknowledges and fresh show retains the exact prior +notes plus newline-separated multiline checkpoint, including literal shell-like +text. Created audit author and claimed assignee match the explicit executor. +Disposable create/claim/discovered-from/close and schema-refresh checks now pass. + +Installed **bd 1.3.1 (c1c4b642a: HEAD@c1c4b642ac1c)** actually returned: + +- Empty remember: exit 1, JSON `error="memory content cannot be empty"`, + `schema_version=1`; no empty memory is created. +- Recall of both the rejected-empty key and an absent key: exit 1, JSON contains + the requested key, `value=""`, `found=false`, `schema_version=1`. +- Remember and same-key update: exit 0, exact multiline value, explicit key, + actions `remembered`/`updated`, and `schema_version=1`. A separate recall finds + the exact stored content with `found=true`. +- **Empty list and no-match search:** exit 0, `{"schema_version":1}`. + **List and matching search:** exit 0, an object containing the exact updated + memory value plus numeric `schema_version:1`. + +The last shape is a genuine **plugin acknowledgement mismatch**, not an upstream +defect: `src/continuity.ts`'s memories branch requires every object value to be a +string. The installed CLI's numeric metadata therefore makes even successful +empty/populated lists fail that branch. The four raw-map contract expectations +remain failing; they are not weakened by deleting metadata only inside the test. +The first extended run (`vuy8-resume-contract.log`) and a diagnostic-only rerun +in another fresh disposable root (`vuy8-resume-contract-evidence.log`) both exit +1. The latter records exact JSON: **36 checks pass, 4 fail**, all four failures +are this list/search shape. Source tracing establishes the adapter consequence; +no installed OpenCode tool invocation or model behavior was tested. + +Stopped on that newly demonstrated blocker. Correcting the acknowledgement and +its fixtures would require `src/continuity.ts`/`tests/continuity.test.ts`, outside +the authorized paths. Neither was edited; no upstream repair, new harness or +backend restriction was introduced. Empty-found and failed/indeterminate commit +fields are still not fabricated: this release refuses empty writes, and its +observed remember JSON has no commit outcome fields. Existing adapter fixtures +for richer outcomes remain unchanged and pass. No admin experiment was run. + +### Consolidated current candidate checks + +All commands used the verified cached baseline Bun on PATH, `TMPDIR` and explicit +test actor exactly as in the earlier environment block; no wrapper repair, +runtime/CLI install or upgrade occurred. Logs below are all in +`/opt/data/cache/scratch/` and preserve earlier runs under their original names. + +| Command | Exit | Current evidence | +| --- | --- | --- | +| `bun test` | 0 | **168 pass, 0 fail**, 1,260 assertions, 12 files; `vuy8-resume-tests.log` | +| `bun run typecheck` | 0 | `vuy8-resume-typecheck.log` | +| `bun run test:package` | 0 | `vuy8-resume-package.log` | +| `bun run test:bd-contract` | 1 | First extended run and diagnostic rerun described above | +| `openspec validate improve-beads-workflow-parity --strict --no-interactive` | 0 | `vuy8-resume-openspec-change.log` | +| `openspec validate --all --strict --no-interactive` | 0 | 1 passed, 0 failed; `vuy8-resume-openspec-all.log` | +| `python3 docs/validate-parity-planning.py /opt/data/cache/scratch/parity-sources` | 0 | Seven groups pass; `vuy8-resume-planning.log` | +| `git diff --check` | 0 | `vuy8-resume-diff-check.log` | + +Current package: `@snarkipus/opencode-beads@1.0.2`, +`snarkipus-opencode-beads-1.0.2.tgz`, SHA-256 +`d7f32c76f1b9abcfe788a1362bbaea325faf51f460fd3fe055f16e64074eb8d7`. +The archive/consumer were cleaned up by the existing smoke script. Its changed +identity reflects the guard source correction; no version or publication changed. + +### Source relationship, custody and parent handoff + +The original `.6` acceptance/review remains immutable at +`a5d6b2bea779259ba9aa3175ab86fb2f4f2b862b`; it did **not** review this new guard +correction. `src/guard.ts` is now the sole production-source difference against +that revision. The current full suite reran the relevant host/guard/flow, +guidance/agent and continuity fixtures, plus package/type checks. Those passing +fixtures do not supersede the real CLI failure or establish live acceptance. +No `.6` ownership, metadata or acceptance record was changed. + +Total worker paths are now exactly `src/guard.ts`, `tests/guard.test.ts`, +`scripts/bd-contract.ts`, and `docs/integrated-acceptance-vuy8.md`. The five +protected paths still match the supplied manifest; custody is +`vuy8-resume-protected-final.json`. The previous report including parent edits +was retained as an exact byte prefix (snapshot `vuy8-resume-prior-evidence.md`, +SHA-256 `371afa8e1f35a9df4d3fc37eb661684528934cb23c180342d2d4ea59d6024f00`). +Current four-path hashes are in `vuy8-resume-candidate-manifest.json`. + +`.8` remains open/in progress under the same executor with a final evidence +pointer for parent disposition. **No task 6.1 acceptance or checkmark is claimed.** +Live **1.1/4.2/6.2 remain UNEXECUTED**, including correction/decline and actual +installed unload/reactivate. No next child, OpenCode command, model evaluation, +activation, extra agent, Git staging/commit/push, remote Dolt sync or closure. + +## Final parent correction and task 6.1 acceptance + +Snarkimedes reviewed the worker guard diff and its RED/GREEN evidence. The exact +supported exit-1/schema-1 no-workspace response is classified as uninitialized; +other errors, unsupported error versions and interruption remain probe failures. +There is no fallback store access. This resolves our adapter classification, not +an upstream defect. + +The resumed real CLI run also exposed numeric `schema_version: 1` in memory +lists. Parent treated this small response-decoding correction as part of the +same task 6.1 integration work, without another worker, issue or upstream repair. +Parent added a regression in `tests/continuity.test.ts` and observed RED (exit 1, +acknowledged expected but failed returned; `vuy8-parent-memory-red.log`) before +editing production code. `src/continuity.ts` now removes only known numeric +schema-1 envelope metadata from a copy of the memory map and validates all +remaining values as strings. Empty and populated versioned lists are accepted; +unsupported numeric versions and unrelated non-string fields remain rejected. +The CLI contract now asserts the actual versioned raw envelope, exact memory +contents and absence of additional entries. Metadata is not merely discarded +inside tests while leaving production decoding broken. + +Final independent parent executions used cached baseline Bun 1.3.14 on PATH, +`TMPDIR=/opt/data/cache/scratch` and actual actor `snarkimedes` for disposable +CLI operations. All exited 0: + +- Guard/continuity focused tests: **31 pass, 0 fail**. +- Full repository suite: **170 pass, 0 fail**, 1,267 assertions, 12 files. +- Typecheck and packed-package smoke: pass. +- Real Beads 1.3.1 disposable CLI contract: **40 checks pass, 0 fail**. +- Strict named/global OpenSpec, planning/source validation and whitespace: pass. + +Execution logs and exits are indexed in +`/opt/data/cache/scratch/vuy8-parent-final-checks.json`; detailed logs use the +`vuy8-parent-{focused-green,tests-final,typecheck-final,package-final,contract-final}.log` +names. Tested production/fixture/contract hashes are retained in +`vuy8-parent-final-source-manifest.json`. The final archive SHA-256 is +`043257ca145a775bf8b10cb73194a2fae2eb0d2d25b379e00f90ca8219e6ddf3`, still package +`@snarkipus/opencode-beads@1.0.2`; no version bump or publication occurred. + +Real checks establish append retention/readback, explicit audit/claim actor, +remember/update/recall, empty-content rejection, missing-key exits, empty and +populated list/search envelopes, setup/prime and disposable schema-marker refresh. +The installed release does not demonstrate found-empty memory or richer commit +outcome fields; those remain fixture-supported shapes, not fabricated live results. + +**Standards:** no remaining blocking finding in the bounded guard/parser/test +changes; direct CLI invocation, attribution, no retry/bypass and user-file +preservation remain intact. **Spec:** task 6.1's supported deterministic/package/CLI +acceptance is met; live 1.1/4.2/6.2 and final reconciliation 6.3 are not satisfied +by these checks. The original `.6` review remains unchanged and does not review +the new guard/parser fixes; relevant current host/guard/flow/continuity fixtures +were rerun by the parent in the full suite. + +Parent checked only task 6.1 and reconciled the validator's accepted task set; +the local ledger is now nine accepted of thirteen. The five protected planning +paths matched before acceptance; the sole subsequent protected-file change is +the parent-owned 6.1 checkbox, with no task wording or gap-resolution changes. +`.8` remains open pending separately authorized publication/closeout; no worker +continuation, next child, live host/model evaluation, install, activation, +commit/push, Dolt synchronization or closure is implied. diff --git a/docs/opencode-2.1.1-contract-delta.md b/docs/opencode-2.1.1-contract-delta.md new file mode 100644 index 0000000..163ba21 --- /dev/null +++ b/docs/opencode-2.1.1-contract-delta.md @@ -0,0 +1,33 @@ +# OpenCode 2.0.32 → 2.1.1 contract delta + +**2026-10-20 — READY for parent/owner review; PAUSED FOR REVIEW.** This is source-backed planning, not host compatibility acceptance or implementation authority. Custody starts at `907232a393443a0f0d59133ffb0e2612f3fd9026`; the accepted 84ab18e plan and dd56cf1 mapping/review remain historical. Matt selected the newer host; all other product selections stand. + +## Identity, scope and admission + +- New exact tag `v2.1.1` resolves directly to commit **`ba546ec1598a70db2570e9c6258028d955ba8f58`**, verified by GitHub ref/tree APIs. Previous release remains **`843a6c514e606b436a7fe713422ace0a93ad3d61`** (2.0.32), not the old development snapshot. +- Published `@opencode/plugin@2.1.1` exists. Downloaded tarball integrity: `sha512-wvbvx/GeFNL7lb2Aj0IlXxPN01J6L1qJ2IbkUE5FaLmnC78aLjibzFP+wuzb28yUAM65dYw7WJRvHv+5GZRL8Q==`. Declarations were extracted as evidence only; nothing was installed. Registry metadata/tarball SHA-256 and source Git/SHA-256 identities are in `host_baseline_211` in the [inventory](claude-code-integration-inventory.json). +- The supplied tag’s [changelog](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/CHANGELOG.md) starts at **v2.1.0**, not curated 2.1.1 notes. Exact released-file comparisons govern this memo. GitHub compare reports diverged history and caps its file list at 300; it is navigation evidence, not an exhaustive endpoint diff. No ancestry is inferred from version/date order. +- Proposed admission: **`>=2.1.1 <2.2`**, exact disposable qualification target **2.1.1**. The explicit breaking minor invalidates blanket V2 compatibility. Future 2.1 patches are admitted candidates subject to source checks and affected acceptance, not claimed tested; 2.0 and 2.2+ are outside this redesign’s declaration. No version has passed live qualification for this redesign. Task 1.2 changes types/declarations only under later authority, before 1.1 probes. Operator 2.0.29 stays untouched. + +Bounded coverage: all 45 previously pinned native files plus 18 directly related schema/history/skill/permission/client sources, including the changelog: **63 files (31 unchanged, 29 modified, 3 added)**. Fourteen published Effect/Promise domain declarations were compared: session changes; tool/skill/command/agent/permission/plugin remain byte-identical. The historical 45-file native inventory, 73-file Claude release inventory, 74-file selected marketplace corpus, and all three independent reviews remain distinct and unchanged in identity. Source cache: `/opt/data/cache/scratch/parity-sources/baseline211/`. + +## Contract decisions + +| Surface and evidence | Decision and task impact | +| --- | --- | +| Provider replay names: [message schema](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/session-message.ts), [event schema](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/session-event.ts), [Promise session](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/session.ts) | **Adapt** relevant types/fixtures in 1.2/3.2/6.1: replay `state`, `providerState`, `providerResultState`, `resultState` become `native`; SessionCompactionResult uses native. Tool execution `state` and UI `metadata` are NOT renamed. Tool call/result native data merge shallowly with result keys winning; do not interpret opaque provider data as Beads memory. | +| Retained summary history: [compaction](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/compaction.ts), [history](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/history.ts), [fork projector](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/projector.ts) | **Retain reference-only M03; adapt** 3.2/4.2/6.2 fixtures. New summaries reference metadata.keepID; legacy flattened text is metadata.recent. History reconstructs the retained tail; forks remap the reference. Preserve host metadata, do not flatten/rewrite it or inject a replacement result. Summary preparation now sees the whole conversation before fallback shrinking; reserve is 15% with a 16k floor where the window permits. Meaningful earlier checkpoints remain necessary; no final-write guarantee. | +| Native replay: [history](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/history.ts), [to-LLM conversion](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/runner/to-llm-message.ts) | **Adapt**, not replace: providerContext becomes native.messages; replay matches provider and model rather than endpoint provenance, falling back to original messages on mismatch. Hook result.native.messages replaces summary replay; never populate it merely to add Beads references. Tool calls require matching provider/model; provider-run results may cross a model change within the same provider, while local results require matching model and nonfailed step. 3.2/6.2 cover recovery boundaries. No compatibility decoder/service of our own; host legacy decoding owns old data. | +| Compaction notifications: event schema above and [legacy decoder](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/schema/src/session-legacy.ts) | **Retire** any session.compacted assumption; use current session.compaction.started/ended/failed. No selected mechanism depended on the transitional event. M04 remains observation, not an awaited stop barrier. Metadata/provider shape adapters are fixture scope in 3.2, not a new event manager. | +| Paginated session listing: Promise session above and [Effect session](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/effect/session.ts) | **Reclassify X09 released-public; do not adopt.** list now exposes opaque cursor pagination and filters; cursor carries filters, limit defaults in core. Known-session/task recovery suffices. Historical development-only classification stays valid for 2.0.32. No session scan or extra ticket. | +| Permission filtering/identity/save: [tool](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool.ts), [permission](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/permission.ts), [saved grants](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/permission/saved.ts) | **Retain** shell filtering, invocation agent, deny precedence, save: [] and external-target checks. Tool change caches inventory by registry/visible names but keeps request executors fresh; permission change drops undefined metadata values. Saved grants now support editing; do not edit permissions from the plugin. 1.1 checks re-evaluation after operator grant edits/revocation; 3.1 retains adapter guard/cancellation/argv checks. Existing shell bd * equivalence and repeated one-call “always” prompts remain. | +| Skills: [scan](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/config/plugin/skill-scan.ts), [watch integration](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/config/plugin/skill.ts), [resource preparation](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/skill.ts) | **Retain explicit package registration/16 links.** Scanning bounds depth at six, skips hidden/ignored directories, stops descending at SKILL.md, watches outside-root resolved files specifically; resource preparation adds ignore globs but still lists only ten. 2.2/3.3 fixtures cover paths/reload without assuming recursive auto-discovery; 6.2 honors external_directory ask/deny. No installer or watch manager. | +| Long-running TUI transport (changelog above) | **Host-owned fix, qualification relevance only.** Release notes report requests surviving the five-minute fetch deadline; inspected client entry surfaces are unchanged, not proof of the lower-level fix or live reliability. 4.2/6.2 observe cancellation/late results on the target host; do not build a transport shim or an exhaustive duration matrix. | + +The direct endpoint comparison found no change in the pinned registration/editor/state ordering, hook dispatcher, external-access/shell authorization, prompt/execution, subagent return/completion/job, supervisor or file-access sources. Context/model preparation changes are native replay and image-budget handling; plugin host adds session.list forwarding, not a new cleanup contract. Session removal uses the underlying store list so child deletion does not inherit public pagination; new archive/unarchive events do not authorize continuation or require a new observer. These observations support **retaining** M01/M02/M05/M07/M09/M10 and collision/stop/unload gates, not claiming them passed. Restrictive-agent discovery, cancellation after approval, late children, namespace dispatch and lifecycle still need exact-host qualification. + +## Delivery and review boundary + +Existing 13 tasks and nine children fit the delta: .1 types before .2 host probes; .4 context/compaction fixtures; .5 resource/lifecycle fixtures; .6 child and fork/live checks; .8 deterministic integration; .9 measured end-to-end acceptance. .3 guidance and .7 drift stay independent. No new epic, service, queue, compatibility manager or task split. .6 remains the sole authoritative deterministic 4.2 evidence owner; .8 consumes only parent-verified evidence and stays blocked otherwise. .9 waits for complete live 1.1/4.2 and 6.1. + +Validation covers immutable old/new source and published artifact integrity, complete corpus dispositions, exact task ownership/17 requirements, dependency/phase acyclicity, scoped lint, fresh tracker readback, whitespace and setup custody. These are static planning checks. Live environment/model access and implementation/dependency authority remain absent. All ten records stay blocked, unassigned and implementation_authorized=false; d9n stays open. Pause for parent/owner review; no host/model run, installation, source implementation, sync, publication or dispatch is authorized. diff --git a/docs/opencode-host-codemode-qualification-vuy2.md b/docs/opencode-host-codemode-qualification-vuy2.md new file mode 100644 index 0000000..b135699 --- /dev/null +++ b/docs/opencode-host-codemode-qualification-vuy2.md @@ -0,0 +1,91 @@ +# OpenCode 2.1.1 native code-mode follow-up — vuy.2 / task 1.1 + +## Parent acceptance + +Parent independently inspected and reran the offline analyzer: all 15 follow-up cases passed, all 150 protected repository paths and 116 historical evidence files were unchanged. Operator before/after records match; independent current socket inspection confirmed neither owned port listening. Task 1.1 is accepted against the selected contract, combining these new observations with retained prior host evidence and the explicitly owner-accepted decline limitation. The stronger historical decline expectation remains failed, not relabeled passed. No installed product, real-model, explicit Stop/handoff or lifecycle acceptance is implied. The original worker handoff below records its pre-acceptance state. + +**Bounded follow-up passed; parent acceptance is pending.** Fifteen isolated released-host cases measured the new namespace, inventory, signature, result-bridge and permission-resource facts. No new unsupported host contract was observed. The historical stronger nested-decline termination case remains **failed, owner-accepted**, as recorded in [the original qualification](opencode-host-qualification-vuy2.md) and [owner decisions](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/owner-decisions.md). This report does not check task 1.1, close `.2`, enable the adapter or qualify later installed/model/Stop behavior. + +## Identity, authority and retained evidence + +- Date: 2026-10-21 UTC. Actor: `codex:oc-host`. Fresh replacement session: `01a15f7e-af76-7d73-9ed3-6d0fe89e3af7`; launch metadata records `gpt-6.1-sol`, high reasoning. No additional worker. This is not a continuation of unavailable session `01a15c62-d106-7e80-af58-5a47b1032cd4`. +- Evidence root **F**: `/opt/data/cache/scratch/vuy2-codemode-followup`. Exact bounded assignment: `F/prompt.md`; current authority: `F/record-current.json`. The original failed resume remains in `F/worker.log`, byte-identical by SHA-256 in `F/identity.json`. `fresh-worker.log` records this replacement's route and actual session. +- Published accepted source / repository HEAD: `8831169d3c83c333fdf13d4a4efc743eaf495e2b`. Entry worktree was clean. Only this new report was added in the repository; all 150 protected paths remain byte-identical to `F/protected.json`. +- Reused exact first-party `@opencode/cli-linux-x64-baseline@2.1.1` from **R**: `/opt/data/cache/scratch/vuy2-oc-host`. Tarball SHA-256 `216d3861d0a167adad5850282439bae13483203f4b110f0510f260118bba2858`; executable SHA-256 `4595933989548a452e920c971d5931898d7d64e73e039f777ed62c84a18916c9`. Published SHA-512 SRI/SHA-1 and both retained registry signatures were independently reverified; actual version output is `opencode v2.1.1`. +- Pinned host source: `ba546ec1598a70db2570e9c6258028d955ba8f58`. All 36 retained source-file SHA-256 and Git-blob identities reverified against the original recorded manifest. This reuses the prior official tag/tree provenance; it is not a reproducible-build attestation or a new upstream lookup. +- Retained analyzer rerun with its single results-file write redirected to `F/prior-analysis.json`: 31 settled historical cases asserted, including the failed stronger decline contract. No old case was rerun. All 116 original evidence files and their path set remain unchanged, checked against `F/prior-evidence-before.json`. +- Beads remains `1.3.1 (c1c4b642a)`; no 1.3.0 qualification. Only `.2` progress notes were written, with explicit `--actor codex:oc-host --sandbox`. `.2` remains `in_progress`, assigned to that actor; `created_by=unknown` remains historical audit data. + +## Isolation and measurement boundary + +The real released executable served only `127.0.0.1:39161`, PID/process group `747580`. The controlled HTTP/SSE response fixture served only `127.0.0.1:45971`, PID/group `747573`. Separate `F/home`, `config`, `data`, `cache`, `state`, temporary files and committed disposable `F/repo` were used. The actual host project ID was `9ffa3d06bf093090747246e6ac95a65d78123a8c`, with project and location both `F/repo`. Startup used an explicit isolated environment and a newly generated transient server credential. Operator auth/config/environment was not reused. + +The provider fixture uses the native OpenAI-compatible HTTP transport. It records request bodies, never Authorization headers, emits the specified tool call, then `CONTROLLED_FIXTURE_STOP`. This measures real host mechanics; it provides no external inference or model-judgment evidence. All registration, catalog instructions, native search/execute, executor/result bridge, permission decisions and native shell execution were supplied by the unmodified release. + +The throwaway plugin registers the eleven `beads` leaves through actual `editor.namespace` and `editor.add`, with `namespace: "beads", permission: "shell", codemode: true`. `schema-declarations.ts` copies only the published candidate's declarative schema constants, `outputFor` and input table into scratch; `definitions.json` contains their evaluated JSON. It neither imports nor executes product adapter, guard, argv, routing, normalization or acknowledgement code. `fixtures.json` supplies explicit accepted example inputs, fixed canonical resource templates and synthetic structured output objects. Each permitted target only appends a scratch observation. The resource strings beginning `bd` are never executed. No real project Beads store is opened by a probe. + +The plugin returns a deliberately different text sentinel alongside each structured `output`. Observed composed results contain the structured objects, not that sentinel. This demonstrates the real bridge's object result behavior; it does not establish validation of every JSON-schema branch or genuine persistence. The candidate's source tests/CLI acceptance remain separate evidence. + +## Observations + +| Case | Actual released-host observation | +| --- | --- | +| `catalog-allow` | Provider direct inventory includes `execute` and native `shell`, with no direct Beads leaf duplicates. Code-mode instructions show `beads (11 tools, 4 shown)` and the namespace description. | +| `namespace-search` | Real `search({namespace:"beads",limit:20})` returns all eleven exact `tools.beads.*` paths, `remaining:0`, `next:null`. Signatures expose object inputs, structured Promise unions, status branches and relevant fields; no `Promise` fallback. | +| `execute-denied-shell-allowed` | Provider request has no `execute`, no Beads leaf tools and no Code Mode instructions/catalog. Direct native `shell` remains visible. | +| `execute-and-shell-denied` | Neither `execute` nor `shell` is in direct inventory; no Beads catalog or direct duplicates. | +| `whole-shell-denied` | Native shell and all eleven Beads leaves disappear; `execute` itself remains available for its independently allowed functionality. | +| `selective-catalog` | `shell: bd update *` deny preserves all eleven searchable leaf signatures, including note and get. Resource-selective denial is enforced at invocation, not inferred from tool visibility. | +| `shell-code-search` | Native `search({query:"shell",limit:20})` returns no code-mode tools. Shell remains direct-only; no `tools.shell` fallback is used. | +| `eleven-resource-and-result-bridge` | One composition invokes every leaf once. Eleven native approvals with `save:[]` precede eleven scratch targets. All eleven structured objects survive the real bridge, including multi-ID get, memory map, empty found recall value, completeness and write actor fields. | +| `selective-note-denied` | One note invocation, `Permission.BlockedError`, zero scratch targets, zero prompts/retries. Outer execute carries an inner error and `metadata.error:true`. | +| `selective-get-allowed` | Same selective agent invokes get once, obtains its independent approval and reaches one scratch target. | +| `namespace-correction` | One note invocation, one reject, zero targets/retries. `Tool.Error` wraps `Permission.CorrectedError`; exact feedback `Use staging; namespace original target is not authorized.` survives the leaf, execute output and next provider request. | +| `namespace-decline` | One note invocation, one reject, zero targets/retries. Leaf rethrows `Permission.DeclinedError` unchanged. Outer execute is completed with inner error, `metadata.error:true` and text `Tool.Error`; two provider requests occur. This reproduces the accepted continuation limitation, not a passing whole-step-termination claim. | +| `decline-then-independent-permission` | One composition catches note's plain decline and attempts get. Note never reaches its target; get asks separately and reaches its target only after its own approval. Two invocations, two permission replies, one scratch target. | +| `namespace-invoking-agent` | Response was held after the request snapshot under `probe_selective`; native API changed session default to `probe_allow` before release. Actual invocation/assertion retains `probe_selective` and blocks note. A native agent-omitted evaluation of the exact same resource under the new default allows it. No forged invocation or child dispatch. | +| `native-shell-with-execute-denied` | Real direct `shell` executes `/usr/bin/pwd` once, after its own shell approval, while `execute` remains absent from provider inventory. Exit 0; stdout is exactly `F/repo`. No Beads command runs. | + +There are 18 typed leaf invocations, 13 scratch targets and five rejections across these cases. Every rejected leaf has one invocation and zero rejected-target executions; no rejected leaf is automatically retried. The independent later get is a different explicitly planned operation. There is also one harmless native shell process. Exact case session IDs, provider-request counts, inputs, permission source/call IDs, errors and result envelopes are retained in `results.json`, `cases.jsonl`, `api.jsonl`, `provider.jsonl` and `plugin.jsonl`. + +Registration definitions are distinct from effective inventory: native transform callbacks logged 44 definition events across rebuilds and two plugin setups; each definition set has the same eleven names. Effective provider inventories and real search outputs establish the eleven callable leaves. The partial inline catalog is not mistaken for a four-tool registration limit. + +### Canonical permission-resource observations + +All eleven representative operation shapes were observed as native `action:"shell"` permission requests. `get` submits both resources together. Each request includes the actual invocation session and agent, tool message/call source and `save:[]`. **A** below abbreviates the observed host-derived `opencode:probe_ask:<12-hex-session-and-agent-hash>` actor, not a literal actor input. Full resources are in the raw captures. Newlines shown as `\n` below are actual newline bytes in the request. + +| Operation | Observed resource shape | +| --- | --- | +| find | `bd ready --limit 3 --json --sandbox` | +| get | `bd show scratch-1 --json --sandbox`, `bd show scratch-2 --json --sandbox` | +| edges | `bd dep list scratch-1 --direction down --type blocks --json --sandbox` | +| memories | `bd memories --json --sandbox -- 'build lesson'` | +| recall | `bd recall build --json --sandbox` | +| claim | `bd update scratch-1 --claim --json --actor A --sandbox` | +| note | `bd update scratch-1 --append-notes 'scratch '\''note'\''\nnext' --json --actor A --sandbox` | +| update | `bd update scratch-1 --status blocked --priority 1 --add-label review --remove-label old --json --actor A --sandbox` | +| close | `bd close scratch-1 --reason 'scratch accepted' --json --actor A --sandbox` | +| create | `bd create --title 'Scratch only' --type task --priority 2 --description 'Fixture only' --acceptance Observed --design Minimal --parent scratch-parent --deps discovered-from:scratch-1 --labels review --json --actor A --sandbox` | +| remember | `bd remember --key build --json --actor A --sandbox -- '-- literal scratch\nlesson'` | + +These are host observations of fixed probe definitions representing accepted canonical shapes. Adapter argv construction and every optional operation branch are not requalified here. Existing saved-grant, grant-edit/revocation, deny-precedence, external-directory/location, native command admission and `save:[]` repeat/always observations remain applicable from the original 31-case evidence; no new observation invalidated them. + +## Reproduction, harness corrections and cleanup + +Run the compact analyzer offline, without a host, provider, credential or product import: + +```sh +python3 /opt/data/cache/scratch/vuy2-codemode-followup/analyze.py +``` + +It asserts actual inventories/search signatures, all eleven observed resources/results, actual actors, denied target counts, feedback/decline envelopes, independent later permissions, native shell result, final empty active/saved lists, protected hashes and unchanged original evidence. It deliberately asserts the stronger decline mismatch instead of relabeling it passed. `results.json` is its retained output. + +The exact scratch scripts are retained: `prepare.py`, `make-fixtures.py`, `schema-declarations.ts`, `probe-plugin.mjs`/`plugin/index.mjs`, `start.py`, `provider.py`, `client.py`, `probes.py`, `run-cases.py`, `run-delta.py`, `resume-probe-control.py`, `run-remaining.py`, `run-shell.py` and `cleanup.py`. Launch commands were `python3 F/provider.py` under the explicit provider environment and `python3 F/start.py`; the latter records the actual `serve --hostname 127.0.0.1 --port 39161` command identity/environment names in `process.json`. Schema evaluation used the pinned available Bun executable at `/opt/data/.npm/_npx/60c3515df86f25b1/node_modules/@oven/bun-linux-x64-baseline/bin/bun`. Per-case code and direct tool inputs are captured in native request bodies; `run-delta.log`, `run-remaining.log` and `run-shell.log` preserve execution outputs. + +Two harness corrections are disclosed. The first runner attempted to read a not-yet-created plugin log before creating a session; it was changed to treat the absent initial log as empty, with no tool measurement/retry. One approval payload used `reply` instead of the pinned API's `decision` key, producing an actual HTTP 400 (`Missing key at ["decision"]`) in `api.jsonl`/`run-delta.log`. The same pending call was completed using the correct key, without resubmitting the tool or duplicating any target. No host/product contract was changed. + +`cleanup.json` confirms both owned process groups terminated, no remaining owned process or listener and neither owned port listening. Before shutdown the native active-session list was empty and saved grants were empty. The server secret and unsanitized private server log were removed; only sanitized logs remain. The provider API-key value was a noncredential fixture placeholder. No genuine external provider credentials were read or used. + +`operator-before.json` and `operator-after.json` match for the resolved executable, known global/project configuration/package hashes and version `opencode v2.0.29`; executable SHA-256 remains `7e23471f4ebe2313d78baacc9ac96fe9e197ce26d808de4b0fcc37efbe473dad`. Original evidence and the failed resume log are preserved. The offline analyzer and whitespace checks passed. The unchanged 191-test source suite was not rerun for this report-only qualification. + +No source/tests/package/assets/OpenSpec/old report/operator integration edits, task checkmarks, staging, repository commits, pushes, Dolt sync, closure, activation, release or `.6`/`.9` dispatch occurred. The SDK contract is protected by this assignment; parent can reconcile accepted evidence separately. **Smallest next gate:** parent independently reruns the offline analyzer and reviews this bounded report together with the retained historical evidence/accepted limitation, then decides task 1.1 acceptance and publication. Explicit Stop/handoff and real installed/model/lifecycle qualification remain with their existing later owners. diff --git a/docs/opencode-host-qualification-vuy2.md b/docs/opencode-host-qualification-vuy2.md new file mode 100644 index 0000000..1907414 --- /dev/null +++ b/docs/opencode-host-qualification-vuy2.md @@ -0,0 +1,117 @@ +# Exact OpenCode 2.1.1 host qualification — vuy.2 / task 1.1 + +**Result: NOT QUALIFIED.** The real released host passed the independent permission, discovery, identity, grant, location and command probes below. Code-mode explicit decline failed to preserve the abort outcome. Task 1.1 remains unaccepted; privileged continuity remains withheld. No source fix, fallback, namespace change, activation or later-child work was performed. + +## Identity and isolation + +- Repository baseline: `072429f1a7723ade23e756a921924d9d0acacf31`, branch `openspec/improve-beads-workflow-parity`. Entry status included an untracked `.claude/settings.local.json`, left untouched. +- Executor: `codex:oc-host`; actual worker session/thread `01a15c62-d106-7e80-af58-5a47b1032cd4`. Requested model/reasoning: `gpt-6.1-sol` / high. No delegated workers. +- Execution date: 2026-10-21 UTC. Evidence root, abbreviated **R** below: `/opt/data/cache/scratch/vuy2-oc-host`. +- Official exact package discovered from pinned `packages/cli/package.json`, `script/build.ts`, `script/publish.ts` and [exact CLI registry metadata](https://registry.npmjs.org/@opencode%2fcli/2.1.1): `@opencode/cli-linux-x64-baseline@2.1.1`. No latest install or global package mutation. +- [Binary metadata](https://registry.npmjs.org/@opencode%2fcli-linux-x64-baseline/2.1.1) identifies the first-party repository and exact tarball. Published SHA-512 SRI and SHA-1 both matched. Both registry signatures verified with the matching public key from `https://registry.npmjs.org/-/npm/v1/keys` using `openssl dgst -sha256 -verify`; results retained in `npm-signature-results.json`. +- Tarball SHA-256: `216d3861d0a167adad5850282439bae13483203f4b110f0510f260118bba2858`. Extracted executable `R/release/package/bin/opencode` SHA-256: `4595933989548a452e920c971d5931898d7d64e73e039f777ed62c84a18916c9`. Actual command output: `opencode v2.1.1`; extracted package name/version also read back. +- Official `v2.1.1` tag resolves to `ba546ec1598a70db2570e9c6258028d955ba8f58`, tree `6b520b02605e7eafa41c03bf9d4e0cab029d5f9c`. Thirty-six bounded source files fetched for setup/API lookup were Git-blob verified against the complete cached pinned tree; see `source-verification.json`, `source-tag.json`, `source-commit.json`. This establishes artifact integrity and official source/release identities, not an independent reproducible-build attestation. +- Separate `R/home`, `config`, `data`, `cache`, `state`, temporary files and a fresh `R/repo` with an initial Git commit. The resolved project ID was `4cc9a0e821dd59eb76d00cd84d22650766c9efd0`, not the shared global project. All startup environment variables were explicitly constructed; no operator environment/auth database was reused. +- Foreground `serve --hostname 127.0.0.1 --port 43355`, PID/process group `634873`, launched from `R/repo`. The controlled local HTTP provider ultimately ran as PID/group `635618` on `127.0.0.1:39209`. No service command, external binding, tunnel or operator restart. +- Provider/model: **controlled local response fixture**, provider ID `vuy2-fixture`, model `deterministic`, using the host's native OpenAI-compatible HTTP/SSE transport. It emits one planned tool call and then `CONTROLLED_FIXTURE_STOP`. No external inference, genuine credential access or model-obedience claim. It records request bodies, never Authorization headers. Host tool registries, permission service, saved-grant storage, code-mode runtime and execution are real. + +## Probe scope and results + +`plugin/index.mjs` is a throwaway Promise plugin registering only `probe_direct` (`codemode: false`), `probe_code` (native code mode), both with `options.permission: "shell"`, and a harmless `oc-beads:ready` command. Its tools pass actual invocation `sessionID`, `agent`, `messageID` and call ID into `permission.assert`, use exact supplied resources and `save: []`, and rethrow rejection unchanged. Before approval the target cannot run. Early targets append a scratch observation only; later location probes additionally run `/usr/bin/pwd` with an explicit scratch cwd. Resource strings beginning `bd` are permission probes, not project-tracker operations. The native `bd` comparison uses a scratch-only shell stub that prints `VUY2_THROWAWAY_BD_STUB_NO_DATABASE` and its cwd. + +The local fixture chooses calls and API replies deterministically. This measures host contracts and propagation; subsequent real-model decisions remain task 6.2. `analyze.py` checks the recorded observations, not a mock implementation. `results.json` indexes all full host session IDs; `cases.jsonl` contains invocation, assertion, evaluation, rejection, target and after-hook records. There were 31 settled probe cases plus one superseded setup attempt with no tool measurement. Counts do not determine acceptance. + +| Selected case | Result and exact evidence | +| --- | --- | +| Whole-shell deny, direct and code-mode discovery | **PASS.** `deny-discovery`: provider request lacks direct `probe_direct` and the `tools.probe_code` catalog entry. `selective-discovery` contains both. Actual inventories are in `provider.jsonl`, not inferred from registration metadata. | +| Selective operation denial in both paths | **PASS.** `selective-deny-direct` / `selective-deny-code`: `bd forbidden target` yields `Permission.BlockedError`; no target event. Code-mode outer completion carries `metadata.error: true` and an inner error record; it is not target success. | +| Actual invoking agent differs from session default | **PASS.** `invoking-agent-differs`, session `ses_ea3960befffeIVHsw6iL9hJ4H3`: provider response held after the real request snapshot for `probe_selective`; native `POST /api/session/:id/agent` switches the session to `probe_allow`. Readback confirms that default; an agent-omitted native permission request for the same resource allows. Releasing the original response invokes the tool as `probe_selective`; its explicit-agent assertion denies `bd forbidden identity-target`. No child agent or forged invocation context. | +| Allow and ask | **PASS.** `allow-direct-http` / `allow-code` reach one target each without a prompt. Ask cases wait for native API approval before a target event. Exact action/resource/source fields are retained in `api.jsonl` and `plugin.jsonl`. | +| `save: []`, always and repeat ask | **PASS.** `ask-always-first` approves `bd update throwaway --append-notes 'probe text' --json --actor codex:oc-host --sandbox` with `always`; saved list remains empty. `ask-always-repeat` asks again. Each reaches one target after its own reply. No Beads write occurs. | +| Correction with feedback, direct | **PASS.** `corrected-direct`: `reject` with `Use staging; original target is not authorized.` yields `Tool.Error` wrapping `Permission.CorrectedError`. Native message has `state.status: error`, `error.type: permission.rejected` and exact feedback. Zero target events and one invocation; the fixture's next step only stops. | +| Correction with feedback, code mode | **PASS.** `corrected-code`: exact `Use staging; code target is not authorized.` survives leaf rejection, inner error and outer code-mode output/`metadata.error: true`. Zero target events and one invocation; no retry. | +| Explicit decline, direct | **PASS.** `declined-direct`: leaf rejects with `Permission.DeclinedError`; native tool message is `aborted`, assistant error says `Step interrupted`, and no next provider request occurs. Zero target events, one invocation. | +| Explicit decline, code mode | **FAIL.** `declined-code`, session `ses_ea396cc6affenEp6WisS1KyHef`, message `msg_15c6933a4001vwxlp9uhAKg3TG`, call `vuy2_declined-code`: leaf rejects with `Permission.DeclinedError`, but outer `execute` has `status: completed`, text `Tool.Error`, inner `status: error` and `metadata.error: true`. It loses the distinct decline/abort outcome and allows a second provider request, unlike direct decline. Target never runs and is not retried. This is a failure of preserved decline propagation, not evidence that the rejected operation succeeded. Affected path stopped after observation. | +| Existing shell `bd *` grant and deny precedence | **PASS.** A fixture-owned grant is created through native session permission request/reply with `save: ["bd *"]` and `always`. `saved-bd-direct`, `saved-bd-code` and `native-bd-existing-grant` proceed without asks. The native command resolves to the scratch stub (first checked by `native-stub-path`), never the installed Beads CLI. `saved-deny-wins` remains blocked by configured `bd forbidden *` deny. | +| Externally edited/revoked grant | **PASS.** Native saved-permission PATCH changes `bd *` to `bd other *`; the next equivalent request asks. Restoring it allows code mode; DELETE makes code mode ask again. Stronger same-session sequence `native-bd-existing-grant` → `same-session-grant-allows` → `same-session-edited-reask` → `same-session-revoked-reask` also asks after edit/revoke, without restarting the host or replacing the session. All fixture grants removed; final saved list empty. | +| Explicit external-directory assertions and actual location | **PASS.** `native-external-pwd` and `typed-external-pwd` request exactly `external_directory: ["R/external/*"]` followed by `shell: ["/usr/bin/pwd"]`, each asking twice. Both actual processes exit zero and print `R/external`. Typed host/session locations both remain `R/repo`; explicit cwd is `R/external`. `typed-local-pwd` / `native-local-pwd` both print `R/repo` and require no external permission. Here `R` abbreviates the full path; logs retain the literal strings. | +| External correction and deny | **PASS.** `typed-external-reject` retains correction feedback and never reaches shell assertion/target. `typed-external-deny` and `native-external-deny` retain the actual blocked external resource and report errors before successful execution. No routing or final-store confinement claim. | +| Native `oc-beads:ready` registration/admission/invocation | **PASS.** `/api/plugin` reports the local throwaway plugin active; `/api/command` lists the exact namespace. `POST /api/session/:id/command` with literal `literal $ARGUMENTS / ready` and `delivery: queue` returns 204; the command body records those literal arguments and the actual session ID. No command rename or production adapter. | + +## Decline mismatch and supported disposition + +Pinned [Promise permission declarations](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/permission.ts) document plain rejection ending the current step and requiring rethrow. The probe does rethrow. The [code-mode result construction](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/codemode/tool.ts#L89) returns a normal tool result with error metadata when runtime execution fails; the observed outer envelope and continuation are consistent with that path. No upstream test service mock was used to establish this result. + +The concrete remaining prerequisite is a parent/owner disposition of the demonstrated exact-host code-mode decline incompatibility, followed by passing measured evidence for the selected guarantee on an explicitly authorized host. No supported 2.1.1 configuration remedy was established. This worker did not patch upstream, add a permission translator, hide code-mode tools as a workaround, choose N0 or activate continuity. Independent passing cases do not complete task 1.1. External-provider/model behavior, child handoff/stop/recovery, installed-package access/unload/reactivation and final product acceptance were **NOT RUN** under this authority. + +## Reproduction and evidence + +All scripts and payloads are scratch-local. To reproduce in a **fresh** dedicated scratch root, replace the literal root in the retained Python/plugin files, retain the verified `release/package` artifact and `plugin/package.json`, and follow this sequence. `start.py` now contains the final supported package-object configuration; the original setup attempt used a string directive, which did not load a package. A separate abandoned in-process language-hook fixture did not intercept native HTTP transport, tried only an unreachable loopback URL, reached no tool, and was interrupted. Neither setup attempt is accepted contract evidence. + +```sh +# From the fresh scratch root; provider binds loopback and writes provider-process.json. +python3 provider.py > provider-process.log 2>&1 & +python3 start.py +python3 - <<'PY' +from probes import * +api('POST', '/api/location/reload', {}) +s = create('probe_allow') +api('POST', f'/api/session/{s}/command', { + 'name': 'oc-beads:ready', 'text': 'literal $ARGUMENTS / ready', 'delivery': 'queue'}) +run('repro-deny-discovery', 'probe_deny') +run('repro-correction', 'probe_ask', { + 'name': 'probe_direct', 'input': {'resource': 'bd show throwaway --json'}}, + [{'decision': 'reject', 'message': 'Use staging'}]) +run('repro-code-decline', 'probe_ask', { + 'name': 'execute', 'input': { + 'code': 'return await tools.probe_code({resource:"bd show throwaway --json"});'}}, + [{'decision': 'reject'}]) +PY +python3 cleanup.py +``` + +`client.py` sends authenticated native API requests without logging the transient credential. Actual request/reply payloads for **all** measured cases, including native grant creation/PATCH/DELETE and the agent switch, remain in sanitized `api.jsonl`; provider bodies are in `provider.jsonl`. `plugin.jsonl`, `cases.jsonl` and message API readbacks expose leaf versus outer outcomes. `analyze.py` verifies filtering, exact resources, target absence/counts, feedback, same-session grant behavior and the decline difference from those retained traces; it passed. `evidence-sha256.json` identifies retained evidence bytes. Runtime commands read Bun `1.3.14` (existing baseline build), OpenSpec `1.14.2` and Beads `1.3.1 (c1c4b642a)`; no runtime repair or dependency edit occurred. + +## Cleanup and handoff + +`cleanup.json` records both owned process groups stopped, no remaining owned process and no listening socket on either port (`43355`, `39209`), checked through `/proc` process/FD/socket data. The scratch server credential and unsanitized server log were deleted; sanitized logs, source/artifact identities and executable remain under R. Final active-session and saved-permission API lists were empty before shutdown. + +Before provisioning and after shutdown, `/usr/local/bin/opencode` resolves to `/opt/hermes/image-toolchain/npm/node_modules/@opencode/cli/bin/opencode.exe`, reports `opencode v2.0.29`, and hashes to `7e23471f4ebe2313d78baacc9ac96fe9e197ce26d808de4b0fcc37efbe473dad`. `operator-before.json` / `operator-after.json` show identical hashes for the executable and known existing global/project OpenCode configuration/package files. No configuration or credential contents were dumped. + +Documentation checks: strict change and all OpenSpec validation, planning validator against `/opt/data/cache/scratch/parity-sources`, and `git diff --check`. Source/package/assets/OpenSpec/reviews remain unchanged. No staging, repository commit, publication, remote synchronization or closure. At worker handoff `.2` remained `in_progress`, assigned to `codex:oc-host`; parent owns acceptance, markers and closure. + +## Parent evidence review and disposition + +Parent inspected the throwaway plugin and the pinned Promise permission and +code-mode implementation, then independently ran the retained evidence analyzer. +Its raw-log assertions passed for 31 settled cases, including zero target events, +one leaf invocation and the same `Permission.DeclinedError` for each decline +path. The direct path made one provider request and aborted; code mode made two +requests and exposed an outer completed envelope with inner error metadata. +This is **not** evidence of unauthorized target execution, a retry, or a successful +persistence acknowledgement. It is a host-owned decline/step-termination mismatch +against the selected preserved-outcome contract and the pinned declaration. + +Parent independently verified the isolated executable hash, current operator +executable/config hashes, absence of both owned listeners and deletion of the +transient fixture credential. The controlled provider tests real host contracts, +not external-provider or model-obedience behavior. + +A bounded first-party lookup found [PR #46056](https://github.com/anomalyco/opencode/pull/46056), +which describes the same nested-decline problem and proposes explicit decline +propagation. Its API state is closed with `merged_at: null`; **this is not a merged +or released fix**, and no successor or supported released remedy is established +by that lookup. No upstream issue/comment, patch, version change or further host +experiment was performed. + +**Disposition:** `.2` blocked for owner/parent disposition of the code-mode +plain-decline path; task 1.1 remains unchecked and activation stays withheld. +Preserve the passing independent observations. Do not invent a local translator, +weaken the selected contract, silently disable code mode or choose another host +baseline. Later .6/.9 work, publication and activation remain separately gated. + +## Superseding owner disposition: accepted limitation, investigation pause + +Matt subsequently accepted the as-built code-mode continuation limitation while retaining composition. The failed stronger plain-decline/whole-step-termination observation above remains failed historical evidence; it is now an accepted host limitation rather than a demand for downstream repair or a supported upstream fix before shipping. Zero rejected-target execution, no plugin automatic retry, correction feedback, independent later-operation permissions and truthful persistence results remain required. Explicit Stop/Cancel is separate and still requires qualification. + +Task 1.1 is not checked or closed by this decision. Remaining qualification/activation is paused for bounded product investigation `opencode-beads-wgv`, including the separate product output-schema defect. No current host experiment, operator change, activation, source fix, publication or tracker closure is authorized. The existing 31-case evidence and cleanup remain intact. diff --git a/docs/opencode-installed-model-continuation-vuy9.md b/docs/opencode-installed-model-continuation-vuy9.md new file mode 100644 index 0000000..6b5a5de --- /dev/null +++ b/docs/opencode-installed-model-continuation-vuy9.md @@ -0,0 +1,112 @@ +# Installed/model continuation — vuy.9, tasks 6.2–6.3 + +**The selected bounded 6.2 evaluation passes on worker evidence; 6.3 evidence reconciliation follows it. Parent acceptance is pending.** `.9` remains `in_progress / codex:oc-accept`; the original **14 accepted / 2 unchecked** task markers are unchanged. This report supplies no ordinary typed activation, release or publication authority. It adds genuine semantic evidence to the previously accepted source/native/Stop evidence and the parent-verified corrected lifecycle/resource observations. + +The original [failed qualification](opencode-installed-model-qualification-vuy9.md), [lifetime investigation](opencode-unload-lifetime-investigation-vuy9.md) and [corrected mechanics report](opencode-installed-model-qualification-vuy9-corrected.md) remain byte-identical. The old location-reload stronger probe remains **failed historical evidence**. It is not retroactively passing: routing replacement can retain an in-flight old graph, whereas this acceptance boundary observes actual plugin cleanup through native watched configuration. + +## Identity and installed boundary + +Execution: 2026-10-21 UTC, sole Sol/high worker `codex:oc-accept`, actual resumed session **`01a15ff1-fc4e-7d53-9b4d-2cb102cd4f76`**. **R** is `/opt/data/cache/scratch/vuy9-model-continuation`. Its complete assignment, authoritative native-auth override and original record are retained in `prompt.md`, `native-auth-resume-prompt.md` and `record-before.json`. The superseded Codex-auth paragraphs were not followed. + +| Surface | Observed identity | +| --- | --- | +| Repository HEAD / runtime source | `d5e2b172c3ab7106b2ea1a55af3725821f4b4d9a` / `8831169d3c83c333fdf13d4a4efc743eaf495e2b` | +| Released host / exact source | OpenCode **2.1.1** / `ba546ec1598a70db2570e9c6258028d955ba8f58` | +| Retained first-party executable | `/opt/data/cache/scratch/vuy2-oc-host/release/package/bin/opencode` | +| Executable / release archive SHA-256 | `4595933989548a452e920c971d5931898d7d64e73e039f777ed62c84a18916c9` / `216d3861d0a167adad5850282439bae13483203f4b110f0510f260118bba2858` | +| Beads | **1.3.1**, commit `c1c4b642ac1c`; executable SHA-256 `a8f48d771b9e11eccfced4aed72923b59eda746f7a9b425e7ca1af7a51251653` | +| Accepted actual packed package | `R/archive/snarkipus-opencode-beads-1.0.2.tgz`, SHA-256 **`e952532140b44f812e2660425ec485d3bbd474962dd424ed03d70108593b259f`** | +| Genuine native route | **`openai/gpt-6.1-sol#high`**, ChatGPT backend `/backend-api/codex`; no provider/model substitution | +| Immutable internal bootstrap SHA-256 | `4520214eb4407eb8fab5dbe48866497be977933b8aa75ffc7300d91e1434cfbe` | + +`prepare.py` reverified retained release bytes, both registry signatures and 36 pinned source files; `additional-source-verification.json` verifies 16 relevant credential/compaction/replay blobs against the exact retained upstream tree. No source runtime was transplanted or patched. The disposable committed project/Beads database lives at `R/repo`, with isolated HOME/XDG config/data/cache/state and an explicitly constructed environment. Foreground native `serve` has the same relevant lifetime semantics as the managed service; no operator service was discovered or restarted. + +The unchanged 31-file accepted archive was packed through the existing `bun pm pack` path and installed through native `file:` package resolution. Its actual installed root is: + +```text +/opt/data/cache/scratch/vuy9-model-continuation/cache/opencode/npm/file:/opt/data/cache/scratch/vuy9-model-continuation/archive/snarkipus-opencode-beads-1.0.2.tgz/1792615248831/node_modules/@snarkipus/opencode-beads +``` + +`artifact.json` retains every content hash and the separate consumer install. Ordinary package setup supplies native command/skill/agent registries; ordinary typed registration remains withheld. For typed semantics only, the immutable `R/bootstrap/index.mjs` imports **this installed package's unchanged `src/host.ts`** and calls `setupBeads(..., {continuityQualified:true})`. Its only other changes transparently log/await the actual native permission assertions, hooks and returned cleanup. No runner, signal, asset, result, registry or provider replacement is supplied. This is an internal qualification dependency, **not an ordinary installed activation switch**. The CLI shim executes the real CLI with unchanged argv and can hold already-completed create output to exercise uncertainty; the real runner supplies the timeout. `bootstrap-identity.json`, `bin/bd`, `package.jsonl` and `bd.jsonl` disclose these differences. + +The archive was made before the later permitted README evidence edit. No new archive was built after documentation reconciliation; the qualified artifact remains the accepted hash above. + +## Native account access and model provenance + +The authority was the existing active OpenAI `credential` record in `/opt/data/.local/share/opencode/opencode-hermes-v2.db`, read using SQLite **`mode=ro` plus `query_only`**. Schema and targeted type/method/expiry/access/account metadata were selected; no other provider or refresh token was selected. The actual method is `chatgpt-headless`, expiry **2026-10-31 20:35:38.990 UTC**, with almost ten days remaining before inference. Native `POST /api/credential` imported current access and necessary metadata into the isolated database. The required refresh string was empty; no refresh capability was imported. Secret API input/output bypassed trace logging. + +`native-auth-before.json`, `credential-import.json`, `scratch-credential-state.json` and `native-auth-after.json` retain nonsecret provenance/digests. The selected source record and imported access/expiry stayed unchanged; **no refresh or rotation occurred**. The source DB was never opened for write or copied; no explicit lock, service stop or checkpoint was requested. Unrelated SQLite maintenance is outside the selected digest comparison. No Codex or legacy OpenCode auth file was read, converted or used. Private host logs/native DB/sidecars and server password were removed after sanitized exports. The exact native database opener creates/restricts its database and sidecars to mode 0600 (`packages/core/src/database/database.ts:79–97`); the harness also restricts scratch native files before inference, with a mode 0600 server password/private log. The earlier run's refresh/discard caveat remains historical; this continuation does not repair or overwrite it. + +`native-model-discovery.json` confirms enabled exact model/tool capability and the `high` variant before inference. `provider-model-provenance.json` and sanitized `native.jsonl` contain actual model request identities, outgoing frames and genuine returned provider frames. All **56 model requests** use the exact route/variant; **55** outgoing `response.create` frames say `gpt-6.1-sol` with `reasoning.effort: high`; the summary request uses actual HTTP and returns 200. There were no retry events, controlled-provider calls or alternative credentials in this continuation. These facts establish route execution, not model superiority. + +## Genuine semantic outcomes and actual records + +`semantic-review.json` gives the worker's evidence-backed judgments. Model descriptions are evaluated against actual native calls, real CLI outputs and persisted records; narration and an outer execute completion are never sufficient persistence evidence. + +| Case | Actual outcome and evidence | +| --- | --- | +| Design A: bounded graph | Two ready issues, declared `complete:false`; actual get preserves a nonexistent-ID failure. Concurrent `Promise.allSettled` edge reads preserve failed/unknown status, including empty edges with `complete:null`. Model returns useful title/blocker/unknown projection and makes no claim/write. `context-A.json`. | +| Design B: exact assignment | Actual typed claim of `repo-9se` acknowledges `opencode:accept:c991a47475e3`. Direct `python3 validate.py invalid` exits **1**, actual **9**, expected **8**, `pass:false`. Original seed notes survive start/measured/handoff appends; rationale, blocker and next scope remain. No specimen repair, closure or other ready task execution in that turn. `context-B.json`, `records-after-exact.json`. | +| Meaningful append versus memory | Direct-shell evidence append uses the same actual actor as typed claim/note. One acknowledged memory `vuy9.validation.invalid-mismatch` captures the reusable lesson to check process exit and semantic result together, rather than the whole transcript. Later model chooses/recalls it for numeric diagnosis and does not recall unrelated CSS. `final-memory-records.json`, fresh recovery context. | +| Failure discovery / uncertain create | One real create produced `repo-itw`, then its output was held and the runner returned **uncertain after 10,000 ms**. The model separately searched the exact title and got the generated ID, proving the real unassigned `discovered-from:repo-9se` record exists; it never repeated create. Search stays `complete:null`, not global uniqueness proof. `bd.jsonl`, `context-B.json`, `final-full-records.json`. | +| Newly authorized continuation | Only after the exact turn ended did a new prompt authorize `repo-onv` plus `repo-712`. Parent claims `repo-onv`, runs the valid specimen directly: exit **0**, actual/expected **16**, `pass:true`, then appends evidence. Unrelated `repo-mi8` remains open/unassigned; followup remains unassigned. `context-authorized-continuation.json`. | +| Actual native partial child | Installed `beads-task-agent` child `ses_e9fca26b1ffeMSPh6fUGoSKXLj` claims `repo-712` as **`opencode:beads-task-agent:d37cf66d75d5`**; typed/shell writes agree. Its actual invalid-specimen run exits1 with9/8/false. Durable note and normal native parent delivery retain attempts/rationale/validation/refs/unresolved next scope. Parent preserves partial/failed validation, leaves both tasks in progress and does not close or repair. Native session/context exports and final records substantiate this, rather than a fixture-supplied failed experiment. | +| Design C: fresh recovery | New actor **`opencode:accept:e5d28a79e8f5`** concurrently finds its own in-progress work (none, complete) and topic memories, projects keys first, then retrieves relevant records/lesson. It distinguishes found/nonempty from missing and does not infer ownership transfer. `context-fresh-recovery.json`. | +| Actual compacted recovery | Real native host summary compaction completes; model then performs fresh durable reads and keeps failure, original assignment and undecided repair scope explicit. No persistence is triggered by compaction. `context-native-compaction.json`, `context-compacted-recovery.json`. | +| Provider-native replacement replay | Public scratch-only `providers.openai.settings.compaction.type=native`, confirmed in actual catalog, produces a real completed encrypted `native.messages` window with empty summary. The next actual request sends the same checkpoint digest, then uses provider continuation IDs and recovers by real reads on the same Sol/high route. `context-provider-native-compaction.json`, `native.jsonl`, `context-provider-native-recovery.json`. No replacement result was manufactured. | +| Remaining design D | Noexecute/noshell agent honestly reports no current lookup capability, with no tool bypass. Selective typed note hits actual canonical `bd update` denial; no target process/text persists, no retry or dependent write; independent get succeeds. `context-noexec.json`, `context-selective-denial.json`, `package.jsonl`, `bd.jsonl`. Prior genuine execute-denied shell fallback is reused. | +| Final command restoration | Actual watched-config override add/remove converges to ordinary ready description, all 30 unique commands and same installed skill. Both membership and description are awaited; `command-restoration.json` resolves the old inconclusive sample with new evidence, without changing it. | + +For all three executed fixture tasks, `created_by=codex:oc-accept` and `owner=scratch@example.invalid`; primary/child assignees are the distinct actual actors above. The discovered followup has `created_by=opencode:accept:c991a47475e3`, the same separate account owner and no assignee. The current fresh actor inherits neither assignment nor permission. `native-sessions.json`, actual permission assertions and `final-full-records.json` preserve these distinctions. All five records remain open/in-progress as appropriate. Diagnostic acceptance means the failed validation was discovered and handed off honestly; **the invalid specimen is still failing** and repair/closure is unresolved work outside this evaluation's authorization. + +## Reused lifecycle, access and replay limits + +The [corrected report](opencode-installed-model-qualification-vuy9-corrected.md) retains the one actual installed watched-config removal/reactivation. Actual ordinary setup cleanup completes at `1792613744.282`; its pending context returns at `.283` with zero Beads envelopes; actual provider request at `.287152` has zero. The held real prime process was aborted by unchanged product cleanup while the harness hold marker remained. Reactivation restores owned registrations once under preserved user overrides; one eligible request receives one envelope. All 17 installed resource links were read through native direct `read`, with17 ask acceptances plus separate ask rejection and deny without bypass. Parent independently verified64 offline checks; those campaigns were not repeated. This continuation only rechecks installed link hashes and settles the previously premature command-description sample. + +Accepted [source/CLI](code-mode-source-acceptance-vuy10.md), [15-case native qualification](opencode-host-codemode-qualification-vuy2.md) and [74-check Stop/handoff](opencode-stop-handoff-qualification-vuy6.md) are consumed without rerunning191 tests,63 CLIcases,15 nativecases or74 Stopchecks. They supply changed-default identity, failed/cancelled/NO_TEXT/late-child/Stop, retained summary/fork/keepID/recent, CLI commit/empty-memory and provider/model mismatch mechanics where this representative model run does not repeat them. Nested plain decline's stronger termination failure remains an owner-accepted host limitation, separate from Stop. + +Actual provider-native replay here is **same OpenAI provider/model/high only**. Exact pinned public docs say encrypted checkpoints are not portable to a different provider/model; the host can rebuild original history on mismatch. No cross-provider/model migration was executed or certified, no extra account/model was invoked, and no fabricated native window was used to claim such a pass. Changed-route mechanics retain their accepted source-evidence classification. This bounded result also does not certify multi-hour transport reliability, comparative model quality or token savings. These limits do not become stronger selected requirements or an activation authorization. + +## Requirement and corpus reconciliation (6.3) + +All **19 normative requirements** have explicit evidence mappings in `requirement-coverage.json`; every row distinguishes genuine new measurements from accepted source/CLI/host/Stop mechanics. There is no unresolved selected semantic/lifecycle/access outcome in this bounded candidate. Owner acceptance, ordinary typed enablement and broader unmeasured runtime claims remain separate gates. + +| Normative requirement | Selected case / reused boundary | Evidence coverage | +| --- | --- | --- | +| Relevant upstream source comparison | reusedaccepted 5.1 | Existing reviewed exact source/inventory/check evidence consumed; no comparison campaign, CLI-upgrade or baseline advancement. | +| Check-based review notification | reusedaccepted 5.1 | Accepted check fixtures/informational main-vs-tag distinction consumed unchanged; no issue dispatch/release inference. | +| Explicit baseline disposition | reusedaccepted 5.1;inventory-coverage.json | Existing owner-selected full inventory dispositions retained; source identity/corpus mapping maintained, no automatic adoption. | +| Meaningful progress checkpoints | B;authorized-continuation | Actual initial/measured/final append checkpoints preserve prior notes, failed experiment/rationale/blocker/nextscope; child partial note; accepted CLI/source append semantics. | +| Relevant reusable knowledge | B;fresh-recovery | Actual reusable validation lesson acknowledged, relevant key selected/recall; unrelated CSS not recalled; missing explicit; landed-uncommitted/empty contracts reused accepted CLI/source. | +| Recovery from durable state | fresh-recovery;compacted-recovery;provider-native-recovery | Fresh and compacted actual durable reads preserve failed attempts/ownership; retained/fork mechanics reused .6. | +| Delegated knowledge and closure evidence | authorized-continuation | Actual native child runs failed diagnostic and returns meaningful partial durable handoff, parent does not close or infer success; failure/cancel/NO_TEXT/Stop interleavings reused .6. | +| Native trigger and semantic responsibility separation | native-compaction;provider-native-compaction;provider-native-recovery | Real summary and encrypted native compactions preserve failure references; no persistence in compaction, no fabricated provider window; actual matching replay observed; changed-provider/model and keepID/recent/fork mechanics reused accepted source/.6, no new migration claim. | +| Honest persistence acknowledgement | B;selective-denial | Real landed create acknowledgment lost -> uncertain -> actual search/get, no retry; denied target never spawns; accepted source/CLI commit/cancel distinctions reused. | +| Typed daily-loop composition | A;B;fresh-recovery;D | Actual catalog signatures, bounded allSettled projection/partial unknowns, ordered claims/writes and current relevant reads; all 11 operation contract/source/native prerequisite reused. | +| Discoverable selected workflows | command-restoration;reused17 resources;reusedfallback | 30 native commands,17 installed links including CODE_MODE; all native resource reads and ask/reject/deny reused; no claim29advanced command semantic trials. | +| Coherent context and authority | A;B;authorized-continuation;fresh-recovery | Model obeys exact versus newly authorized scope, failures/ownership visible; source unique policy/guard mechanisms reused. | +| Owned installation and user customization | reusedcorrectedLifecycle;command-restoration | Actual installed unchanged ordinary package cleanup/reactivation under user overrides reused; final ordinary command description now settled; original failed proxy retained. | +| Scope-preserving execution and visible failure | B;authorized-continuation;D | Readiness not authority, genuine failed validation/childpartial retained; no repairs/closure/unrelated execution; actualStop source/host evidence reused. | +| Explicit native availability and fallback | reusedfallback;noexec | Real2.1.1 catalog; ordinary typed withheld; qualificationbootstrap explicit; prior genuine shell fallback; no-execution admits inability. | +| Restricted-agent typed operation boundary | noexec;selective-denial;reused17 resources | Actual noexecute/noshell capability and selective canonical shell deny; resource ask/reject/deny; actual permission assertion identity. | +| Authorized continuation without scope expansion | B;authorized-continuation | Exact turn stops, new scopedtwo-item batch claims only designated tasks, childownactor; unrelatedready and followupunassigned remainuntouched. | +| Read-only native diagnostics | registry-ordinary-baseline;registry-final;reusedsource/native | Existing status/diagnostic/source/host accepted evidence reused; native registry snapshots do not open/repair project database; no new all-operation modelcampaign. | +| Capability-aware actor and context guidance | B;authorized-continuation;noexec;selective-denial | Actual current/child/fresh request actors, typed/shell agreement, audit/account/assignee distinctions and unavailable leaf capabilities. | + +`inventory-coverage.json` maps **every 73 historical-release and 74 selected-marketplace-main member**, retaining its original disposition, delivery target and task IDs. It includes all 29 workflow names plus status, all 15 adapted corpus resources, project-owned CODE_MODE and ADR: **30 commands /17 links**. The original inventory hash/dispositions remain unchanged. Full resource delivery/access is measured; representative A–D semantic outcomes are measured. Reference/build/admin/CLI-only mappings are not silently converted into claims that all operations or upstream Claude behavior were executed. Relevant source comparison/check/baseline evidence remains the accepted 5.1 evidence, without a new benchmark or drift campaign. + +Current summaries only were reconciled in README, SDK contract, native assessment, Claude local reference, integration analysis and artifact policy. Their detailed historical evidence/reviews remain intact. No source, tests, assets, package/lockfile, validator, OpenSpec/owner-decision or shared operator integration was edited. The task file remains 14checked/2 unchecked. + +## Measurements, retained failures and cleanup + +`measurements.json` separates actual UTF-8 prime (**5,714–6,387 bytes**), appended guidance (**3,234–7,863**), catalog system text (**0 or 16,618**, absent where execute is unavailable), direct schemas (**9,876–12,471 compact JSON bytes**), returned signature catalog text, and returned tool content/results. Returned text ranges **0–19,322 bytes**; whole result JSON **4–40,978 bytes**, including wrapper/metadata duplication. These are individual observed byte categories, not incremental network transmission, token savings or semantic quality measures. All 54 observed eligible qualification context returns contain one Beads envelope; the reused unloaded-generation return remains zero. + +Captured processes/calls: **one native host**, **231 CLI shims +231 real CLI children**, **16 primes**, **7 direct shell calls**, **40 typed leaf calls**, **one actual native child**, **56 genuine model requests**, **zero controlled provider processes/requests** in this root. Counts include initialization/seeding/guard/evidence reads, are not quotas, and exclude uninstrumented `ps` helpers/startup internals; they are not a total OS census. + +Two harness errors are retained separately from product outcomes: initial memory seeding used two unsupported positional arguments; the actual CLI refused it and `seed-correction.json` records supported `--key` correction. Initial cleanup deleted a parent, whose native cascade also deleted its child; the stale cleanup list then got a real404. `cleanup.initial.log` and `cleanup-harness-correction.json` retain this; final cleanup used actual remaining inventory. Neither is relabeled as a product defect or repaired source. The first focused documentation check also failed one retained vendoring-sentence assertion (13 pass/1 fail); the permitted current summary was corrected without changing tests, and the repeated focused check passed 14/0. Its initial log remains `validate-docs.initial.log`. + +`cleanup.json` verifies zero pending/active/saved/remaining sessions/grants, owned server group **781500** stopped, no listener on **53205**, and none of **462 captured shim/CLI PIDs** live. Private native database/sidecars, host log and server secret are removed. Source credential/current access digest and expiry match before/after, no refresh token was selected, no normal or manual OAuth refresh occurred. Known operator executable/config/package files and OpenCode 2.0.29 are unchanged. All 149protected repository paths and all prior evidence path/byte sets (**345 original,72 lifetime,386 corrected files**) match; only the six authorized summaries and this new report differ. No publication, tracker closure, marker or source repair occurred. + +Offline reproduction: `python3 R/analyze.py` validates raw observations without host/account/network access. The final analyzer includes explicit semantic-review consistency; passing asserts evidence consistency rather than independently proving model judgment or owner acceptance. Strict named/all OpenSpec, planning, docs/link/whitespace checks and final protection hashes are retained in `validation.json` and `protection-final.json`; source full-suite tests were not rerun. `case-map.json` keeps new/reused/historical-failed/harness-failed/unmeasured categories distinct. + +**Smallest next gate:** parent independently reviews this bounded 6.2 evidence and 6.3 reconciliation, then decides acceptance and the two remaining markers. Ordinary typed activation, commit/push/sync/closure/archive/release remain separately parent-owned. Scratch final handoff: `R/result.md`. No automatic continuation follows. diff --git a/docs/opencode-installed-model-qualification-vuy9-corrected.md b/docs/opencode-installed-model-qualification-vuy9-corrected.md new file mode 100644 index 0000000..5c3d956 --- /dev/null +++ b/docs/opencode-installed-model-qualification-vuy9-corrected.md @@ -0,0 +1,95 @@ +# Corrected installed acceptance — vuy.9 + +**Actual plugin-scope cleanup/removal/reactivation passed its controlled native observation. Task 6.2 remains incomplete: existing OpenAI access is expired, so the remaining genuine-model evaluation was not run. Task 6.3 was not started.** One final command-restoration snapshot also remains inconclusive, as detailed below. No passing analyzer exit substitutes for these selected gaps. + +The owner clarification in [owner decisions](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/owner-decisions.md) authorizes this corrected probe boundary. The [original location-reload failure](opencode-installed-model-qualification-vuy9.md), [lifetime diagnosis](opencode-unload-lifetime-investigation-vuy9.md) and both prior raw evidence trees remain byte-identical. Their historical failure is not relabeled passing. + +## Identity, artifact and credential gate + +The sole worker remains `codex:oc-accept`, actual session **`01a15ff1-fc4e-7d53-9b4d-2cb102cd4f76`**, Sol/high. HEAD is `d5e2b172c3ab7106b2ea1a55af3725821f4b4d9a`, accepted runtime source `8831169d3c83c333fdf13d4a4efc743eaf495e2b`. Scratch root **R** is `/opt/data/cache/scratch/vuy9-corrected-acceptance`. Authority/preimages are `prompt.md`, `record-before.json` and `protected.json`; the parent's dirty owner-decision clarification is protected. + +The existing pack/install path reproduced archive **`e952532140b44f812e2660425ec485d3bbd474962dd424ed03d70108593b259f`**, `snarkipus-opencode-beads-1.0.2.tgz`. Both consumer and native installations contain the same 31 accepted files. `artifact.json` identifies every file/hash and both install paths. The native root was: + +```text +/opt/data/cache/scratch/vuy9-corrected-acceptance/cache/opencode/npm/file:/opt/data/cache/scratch/vuy9-corrected-acceptance/archive/snarkipus-opencode-beads-1.0.2.tgz/1792613743027/node_modules/@snarkipus/opencode-beads +``` + +The retained released OpenCode **2.1.1** executable SHA-256 remains `4595933989548a452e920c971d5931898d7d64e73e039f777ed62c84a18916c9`; its first-party archive hash, both registry signatures and 36 retained source-file SHA-256/Git-blob identities were reverified at source **`ba546ec1598a70db2570e9c6258028d955ba8f58`**. Beads is **1.3.1**, commit `c1c4b642a`, executable SHA-256 `a8f48d771b9e11eccfced4aed72923b59eda746f7a9b425e7ca1af7a51251653`. This retains the prior release provenance without claiming reproducible builds. + +Before inference or credential import, only the authorized existing OpenAI OAuth type/expiry metadata and nonsecret file provenance were inspected. `auth-gate.json` records expiry **2026-09-18 17:26:59.021 UTC**, checked **2026-10-21 20:13:38.090 UTC**. Access was already expired, independent of the 30-minute reserve selected for a bounded run. **No access/refresh value was selected, copied, imported, printed or submitted; no refresh, login, credential repair/writeback or real-model call was attempted.** Native scratch credential-table rows were zero. Original credential bytes, mode, size, inode, mtime and ctime match after cleanup. + +The previously observed genuine `openai/gpt-6.1-sol/high` route and its passing read-only fallback remain reusable evidence. They do not establish current authentication or the unexecuted semantic cases. No other account credential, provider or model was substituted. The local controlled provider below supplies explicit mechanics-only responses and is never described as genuine model evaluation. + +## Corrected actual lifecycle observation + +The real released foreground server used isolated HOME/XDG/state/cache, a committed disposable project/Beads store and **enabled native file watching**. The lifetime diagnosis already established shared Core semantics with the default managed service; no managed startup comparison or operator service action was performed. + +The initial baseline loaded the unchanged archive normally and captured actual plugin, command, skill and agent registries. A minimal immutable scratch wrapper then imported the **installed `src/plugin.ts` default export**. It calls ordinary `setup`, transparently logs context-hook entry/return, and calls/awaits the actual returned cleanup. `wrapper-identity.json` records its hash and exact difference: generation/timestamp observation only. It supplies no qualification dependency, runner, abort signal, replacement result, asset path or registry. Ordinary typed activation stays withheld; the internal `continuityQualified` bootstrap was not activated because the genuine-model path stopped at the auth gate. + +All transitions used atomic edits of the harness-owned watched config and native activation in the **same location graph**. `/api/location/reload` was never called. Observer generation **`2ec4c7fe-9339-4b69-aad7-f66dfb930ec9`** remained constant through the live cases, with zero `location.shutdown` events before cleanup. Product generation IDs are distinct from that observer identity. + +| Epoch seconds | Actual observation | +| --- | --- | +| `1792613743.9845989` | Real `bd prime --full` output is held by shim PID/group `776323`. | +| `1792613744.281` | Removed product generation `73d72bb3-8d3d-40b2-b7a9-e1e3df66e70d` logs actual cleanup start. | +| `1792613744.282` | Awaited actual package cleanup completes. | +| `1792613744.283` | Its pending context callback returns with **zero Beads envelopes**. | +| `1792613744.287152` | Actual request to the local controlled provider contains **zero Beads envelopes**. | +| `1792613744.667–.668` | Reactivated generation `010c390c-0a8b-497f-bfa2-5307f25dac34` starts/completes ordinary setup. | +| `1792613745.227` | One eligible post-reactivation context returns with **one Beads envelope**; its actual provider request also contains one. | + +The held process was aborted by accepted product cleanup while the harness hold marker still existed; the harness did not release output or kill that process to manufacture suppression. The shim ran the real CLI with unchanged argv and held only its already-completed prime output. No prime/provider result was fabricated or suppressed by the wrapper. `package.jsonl`, `bd.jsonl`, `native.jsonl`, `provider.jsonl` and `lifecycle-result.json` retain the actual chronology. + +Ordinary and transparent baselines each expose 30 unique namespaced commands and the installed skill. Under user command/skill overrides, removal leaves only the user namespaced command/skill and removes the owned task agent. Reactivation restores 30 distinct names and the agent once while preserving those overrides. These are actual native registries, not fixture reconstruction; `registry-ordinary-baseline.json`, `registry-with-user-overrides.json`, `registry-removed.json` and `registry-reactivated-user-overrides.json` retain them. + +**Inconclusive final override-deletion sample:** after the harness removed its test overrides, `registry-restored.json` shows the package skill again but still shows the user command description. The final wait predicate checked skill convergence and command membership but omitted command-description convergence. Offline validation caught that omission. The initial analyzer failure is retained in `analysis.initial.log`; the mixed snapshot is explicitly asserted as unresolved in the corrected analyzer. No new pass for final command restoration, product defect or permanent precedence failure is inferred. Prior accepted override-removal observations remain separate evidence. This does not undo the measured actual cleanup and reactivation under preserved overrides, but final in-place command restoration still needs a settled observation. + +## Installed resource access + +All **17** explicit skill links resolve inside the accepted installed package, including 15 corpus resources, CODE_MODE and ADR rationale. Each was read successfully through the actual native **direct `read` tool**, with `external_directory: ask` accepted once per read and no saved grant. All returned native read results report `truncated:false`. A separate ask rejection returns `executed:false` with the native decline error; a configured external-directory deny returns `executed:false` with `permission.rejected` and no approval prompt. Neither case uses another path or reader to bypass refusal. + +`installed-links.json` records exact paths, archive-content hashes and file bytes; `resources-result.json` maps successful native call/session IDs and denied tool states. These are access mechanics, not evidence of model relevance judgment or comprehension. + +An initial harness attempt incorrectly called `tools.read` inside execute. Its actual search result did not expose that path; the programs returned genuine unknown-tool errors and performed no local reads. Those failed traces remain in `resources.log`/`cases.jsonl`. `direct-read-schema.json` records the real provider-visible direct tool contract used by the corrected calls in `resources-direct.log`. No product change, invented catalogue signature or successful replacement result was introduced. + +## Coverage and selected gaps + +`case-map.json` labels new controlled observations, reused genuine/mechanical evidence, initial harness failures and unresolved cases. The source 191-test, CLI 63-case, native 15-case and Stop 74-check campaigns were consumed without rerun. The owner-accepted plain-decline limitation and the original stronger location-reload failure remain separate historical failed observations. + +The following is an **incomplete acceptance evidence inventory**, not task 6.3 reconciliation or owner acceptance. `requirement-coverage.json` retains all 19 exact normative titles and unresolved selected guarantees; `inventory-coverage.json` enumerates all 29 workflow names plus status and all 17 installed links. The existing reviewed corpus dispositions remain unchanged; no advanced/admin member was dropped or operation executed implicitly. + +| Normative requirement | Evidence and remaining gate | +| --- | --- | +| Meaningful progress checkpoints | Accepted source/CLI mechanics reused; actual-model meaningful append remains auth-blocked. | +| Relevant reusable knowledge | Accepted memory contracts reused; model selection, remember/recall relevance unresolved. | +| Recovery from durable state | Accepted source/Stop retrieval reused; fresh/compacted semantic recovery unresolved. | +| Delegated knowledge and closure evidence | Prior native child outcomes reused; genuine failure discovery/partial child judgment unresolved. | +| Native trigger and semantic responsibility separation | Source/Stop/lifetime boundaries retained; semantic recovery/provider-model interpretation unresolved. | +| Honest persistence acknowledgement | Accepted deterministic/CLI/Stop outcomes retained; selected model uncertain-create readback unresolved. | +| Typed daily-loop composition | Accepted eleven-operation/source/host evidence reused; model workflows A–C, projection/completeness/partial outcomes unexecuted. | +| Discoverable selected workflows | 30 native entries and 17 successful native resource reads; selected workflow semantic outcomes still partial. | +| Coherent context and authority | Source/fallback evidence reused; full selected model behavior unresolved. | +| Owned installation and user customization | Actual cleanup/removal/reactivation and preserved overrides measured; final in-place command restoration sample inconclusive. | +| Scope-preserving execution and visible failure | Genuine read-only fallback reused; exact assignment/continuation/partial failure semantics unresolved. | +| Explicit native availability and fallback | Exact host/source pins and prior genuine fallback retained; no ordinary typed enablement claimed. | +| Restricted-agent typed operation boundary | Accepted host/Stop evidence reused; native resource refusal measured; remaining installed/model capability cases unresolved. | +| Authorized continuation without scope expansion | Source/Stop evidence retained; genuine bounded batch versus exact assignment unresolved. | +| Read-only native diagnostics | Accepted source/native evidence retained; no new model diagnostic acceptance claimed. | +| Capability-aware actor and context guidance | Source and genuine fallback actor retained; actual-model typed/shell primary/child agreement unresolved. | +| Relevant upstream source comparison | Prior accepted deterministic/inventory evidence consumed; no comparison campaign rerun. | +| Check-based review notification | Prior accepted source/check evidence consumed; no new compatibility or release claim. | +| Explicit baseline disposition | Existing reviewed baseline/dispositions preserved; no automatic advancement. | + +In particular, no fixture-supplied narrative qualifies meaningful work, a failed experiment, child reasoning, ownership agreement or compaction recovery. **Design A–C and remaining D/continuation/memory/child/recovery/actor/uncertain-create/replay semantics are unexecuted at the required genuine model boundary.** Task 6.2 cannot pass from the mechanics above; task 6.3 and its six current-summary document edits remain gated. + +## Measurements, cleanup and next gate + +`measurements.json` separates prime, appended guidance, stock host catalogue and returned-result bytes. The suppressed pending request has zero prime/guidance bytes and zero Beads envelopes. A normal post-reactivation context contains **5,714 prime bytes**, **7,621 guidance bytes**, and **13,292 stock catalogue bytes**; that catalogue is not a qualified Beads typed catalogue. Successful resource reads return **1,501–5,631 text bytes** each, with full JSON-result sizes separately retained. No token-saving, transmission or model-quality claim follows. + +There were **76 shim processes and 76 real CLI children**, including initialization and ordinary version/context/prime probes across resource sessions; **25 prime processes**, one host, one local provider, **47 controlled provider requests**, **zero genuine-model requests**, zero typed Beads invocations and zero native children. These are captured counts, not quotas, a benchmark or a total-OS-process census; runner `ps` helpers/startup internals were not instrumented. + +`cleanup.json` confirms empty active sessions, pending permissions and saved grants; owned sessions removed; host group **776177** and provider group **776176** stopped; no listeners on **46727/39321**; and no live process among all **152** recorded shim/CLI PIDs. Transient server-only credentials, private native logs and native DB/sidecars were removed. No real credential material ever entered scratch. Known operator executable/config/package bytes remain unchanged, operator OpenCode remains 2.0.29, and authorized auth bytes/metadata match before/after. All **148 frozen repository paths**, including the owner clarification and both old reports, remain unchanged; both prior evidence trees retain their complete byte/path sets. Only this corrected report is added. README and the five permitted current-summary docs remain unchanged. + +Offline reproduction uses `python3 R/analyze.py`. The analyzer verifies actual cleanup ordering, request contents, resource outcomes, the mixed final snapshot and explicit auth-blocked verdict; success means the evidence assertions agree, **not** that 6.2 passes. Appropriate strict named/all OpenSpec, planning, focused docs/link/whitespace validation is retained in `validation.json`. The unchanged source full suite was not rerun. + +**Smallest next gate:** parent reviews the corrected mechanics and unresolved snapshot, then arranges valid existing operator-managed OpenAI access and explicitly resumes the remaining 6.2 genuine-model evaluation, including a settled final override-restoration read. This worker did not refresh or repair credentials, request secrets, waive a guarantee or authorize itself to continue. `.9` remains `in_progress / codex:oc-accept`; 14 accepted / 2 unchecked markers remain. Parent owns acceptance, task markers and any commit/push/sync/closure/archive/publication/activation/release. Final scratch handoff is `R/result.md`. diff --git a/docs/opencode-installed-model-qualification-vuy9.md b/docs/opencode-installed-model-qualification-vuy9.md new file mode 100644 index 0000000..1767c06 --- /dev/null +++ b/docs/opencode-installed-model-qualification-vuy9.md @@ -0,0 +1,79 @@ +# Installed-package / real-model qualification — vuy.9 + +**Task 6.2 failed a selected lifecycle guarantee and remains incomplete. Task 6.3 was not started.** On the released host, an actual request waiting for package-owned prime output sent a late Beads context to OpenAI after the package had disappeared from the native registries. Passing registry restoration and earlier source/host/Stop evidence do not satisfy that guarantee. No source repair, changed acceptance contract or activation is authorized by this report. + +## Candidate, identity and evidence + +The sole worker was `codex:oc-accept`, actual Codex session `01a15ff1-fc4e-7d53-9b4d-2cb102cd4f76`, gpt-6.1-sol/high. Repository HEAD was `d5e2b172c3ab7106b2ea1a55af3725821f4b4d9a`; accepted runtime source was `8831169d3c83c333fdf13d4a4efc743eaf495e2b`. Evidence root **R** is `/opt/data/cache/scratch/vuy9-installed-model`. `prompt.md`, `record-before.json`, `identity.json` and the parent-frozen `protected.json` retain authority and provenance. + +The reused first-party OpenCode executable is `/opt/data/cache/scratch/vuy2-oc-host/release/package/bin/opencode`, version **2.1.1**, SHA-256 `4595933989548a452e920c971d5931898d7d64e73e039f777ed62c84a18916c9`. Its retained archive SHA-256 is `216d3861d0a167adad5850282439bae13483203f4b110f0510f260118bba2858`. SHA-512 SRI, SHA-1, both registry signatures, and all 36 previously retained source-file SHA-256/Git-blob identities were reverified. Additional inspected provider/lifecycle files were checked against the retained official tree at **`ba546ec1598a70db2570e9c6258028d955ba8f58`**. This reuses release provenance, without asserting reproducible-build equivalence. Beads was **1.3.1**, commit `c1c4b642a`, executable SHA-256 `a8f48d771b9e11eccfced4aed72923b59eda746f7a9b425e7ca1af7a51251653`. + +The existing `bun pm pack --destination R/archive --quiet` path produced `snarkipus-opencode-beads-1.0.2.tgz`, SHA-256 **`e952532140b44f812e2660425ec485d3bbd474962dd424ed03d70108593b259f`**, matching accepted source acceptance. Both the scratch consumer installation and native host installation contain the same 31 files, each byte-identical to the repository preimage. `artifact.json` records the entire content manifest and both exact install paths. The native installed root was: + +```text +/opt/data/cache/scratch/vuy9-installed-model/cache/opencode/npm/file:/opt/data/cache/scratch/vuy9-installed-model/archive/snarkipus-opencode-beads-1.0.2.tgz/1792611344357/node_modules/@snarkipus/opencode-beads +``` + +All qualifying package behavior below used the **ordinary, unchanged packed package** through native `file:` archive installation. Its privileged typed path remained withheld. A minimal immutable bootstrap importing this installed `src/host.ts` with the existing `continuityQualified:true` dependency was prepared but **never activated**: no typed qualification ran before the lifecycle failure. It did not bundle a different source candidate, replace assets or alter the installed package. `bootstrap-identity.json` preserves that unused preparation and its distinction from ordinary installation. + +The [accepted source report](code-mode-source-acceptance-vuy10.md), [native-host report](opencode-host-codemode-qualification-vuy2.md), [original host evidence](opencode-host-qualification-vuy2.md) and [Stop/handoff report](opencode-stop-handoff-qualification-vuy6.md) were consumed. The 191 source tests, 63 CLI cases, 15 native follow-up cases and 74 Stop checks were **not rerun**. Their source/mechanical results do not establish missing model semantics. The owner-accepted plain-decline continuation failure remains historical failed evidence, separate from explicit Stop. + +## Real route and isolation + +Before inference, native `/api/model` discovery established `providerID:"openai"`, model/modelID `gpt-6.1-sol`, tool support and the `high` variant. Its package was `@opencode/ai/providers/openai`, with native ChatGPT/Codex base URL `https://chatgpt.com/backend-api/codex`. Actual outbound WebSocket `response.create` frames used that model and `reasoning.effort:"high"`; actual completed provider frames and native assistant results are retained. There was no OpenRouter, controlled provider, fallback model or supplied model answer. Returned native service tier was `default`; high describes reasoning, not service tier. + +The operator's existing OpenAI OAuth entry alone was copied programmatically into mode-0600 scratch material, after reading the exact native credential/global-path/import contract. Supported native `POST /api/credential` imported it into the disposable database. Native refresh changed the scratch access and refresh tokens and advanced expiry before successful inference. No manual token exchange, login or operator migration occurred. `operator-auth-before.json`, `auth-provenance.json` and `operator-auth-after.json` record nonsecret evidence. **Operator auth bytes, inode, mode, size, mtime and ctime remained unchanged. Refreshed scratch credentials were discarded, never written back over operator auth.** This does not assert that an old operator refresh token remains usable after rotation. + +The host used isolated HOME/XDG/cache/state, committed disposable `R/repo`, its own Beads store and only loopback port **56167**, owned process group **767999**. Harness-created tasks used explicit `--actor codex:oc-accept --sandbox`; their creator, disposable account owner and absent assignee were kept distinct. The model made no issue writes, claims or handoffs. Its actual fallback identity was `opencode:fallback:44e5ef89b5cd`, derived from native session `ses_ea007a134ffer7ErRF24VMZokT` and agent `fallback`. + +An independent scratch observer recorded actual context, tool events, model selection and native transport frames without replacing requests, responses, compaction or tool results. Sanitized `native.jsonl`, `api.jsonl`, `cases.jsonl` and `bd.jsonl` preserve observations. The `bd` logging shim executed unchanged argv against the real CLI. Its only timing intervention held already-completed `prime --full` output until an owned marker was removed, before the normal runner timeout. It supplied no replacement prime text, CLI result or provider response. The unused create-output hold was never exercised. + +## Observed case map + +| Case | Actual result and limit | +| --- | --- | +| Ordinary installed command / design D fallback subset | **PASS.** Native `oc-beads:ready` forwarded the read-only request. With execute denied, the real model loaded the installed skill, read WORKFLOWS, checked current help and invoked direct shell. It reported two of four ready issues with incomplete coverage, no execution authority, separate creator/owner/assignee observations and its actual actor. No issue mutation, delegation or unrelated continuation occurred. | +| Settled native registries | **PASS.** Thirty distinct namespaced commands and the installed skill were effective. User command/skill overrides won. Unload left only the user-owned `oc-beads:ready` and user skill; product agents disappeared. Reactivation restored package entries once while preserving overrides. Removing only the harness-owned overrides restored package descriptions/path. Before/after command/skill/agent/plugin snapshots are `registry-*-settled.json`. | +| Pending-generation output after removal | **FAIL, selected acceptance.** The real prime process was held before unload. The package was absent from the settled plugin registry, product commands/agents were absent and its skill was replaced by the preserved user skill. Releasing the held output then produced a Beads context in the still-running request and an actual outgoing OpenAI request. The held process had remained alive until release. | +| Post-reactivation context | **PASS.** One eligible request received exactly one Beads envelope and one actual model response. This does not erase the preceding failure. | +| Seventeen installed links | **Filesystem PASS; native access partial.** All 17 explicit links resolve inside the unchanged native installation, with bytes/hashes in `installed-links.json`. Native skill loading and WORKFLOWS reading were observed, including one accepted `external_directory: ask` request. Native reading of every other resource, ask rejection and deny remain **unexecuted**. | +| Provider/native replay | **Limited observation.** Genuine same-provider/model requests and native reasoning/replay metadata were observed; encrypted payload fields are sanitized. No compaction, provider/model switch or replacement `native.messages` was manufactured. Their remaining semantic acceptance is unresolved. | + +The decisive settled timeline is retained in `failure.json` and the raw traces: + +| UTC epoch seconds | Observation | +| --- | --- | +| `1792611636.0914652` | Real `bd prime --full` completed; shim PID/group `769512` holds its unmodified output. | +| `1792611636.3046305` | Native registry shows only the user namespaced command; adjacent settled plugin/skill/agent snapshots confirm package removal. | +| `1792611636.338` | After the harness releases output, the in-flight context event contains one Beads envelope. | +| `1792611636.665` | Actual WebSocket `response.create`, OpenAI gpt-6.1-sol/high, includes that envelope. | + +The selected guarantee fails at the outgoing request boundary, independently of the model's simple lifecycle reply. This is installed host/package lifetime evidence; precise upstream-versus-adapter causation was not established. A request continuing across native location reload is not reclassified as explicit Stop failure, and no upstream patch, new supervisor or compensating lifetime framework was attempted. + +Registry snapshots and observer setup/dispose events are retained. The ordinary package's individual disposer invocation counts are not public native observations here; none was invented. In particular, registry removal is not treated as proof that the pending package process had already been aborted. + +The remaining design **A–C** typed composition/projection/partial-failure cases, meaningful append versus selective reusable memory, exact assignment versus authorized continuation, actual-model failure discovery/partial child handoff, fresh/compacted recovery, typed/shell actor agreement, restricted leaf/neither-capability outcomes and uncertain-create readback are **unexecuted selected acceptance gates**. Earlier deterministic/mechanical passes do not complete them. `case-map.json` maps every attempted case and these gaps. Task 6.3's current-summary changes and final 19-requirement/corpus/CODE_MODE coverage reconciliation remain gated; none was marked complete. + +## Measurements and harness corrections + +`measurements.json` separates UTF-8 prime/guidance/catalog text bytes, compact-JSON direct tool schemas, returned result JSON and returned text. Per fallback request, prime was **5,714 bytes**, appended guidance **7,624**, code-mode catalog **0** and direct schemas **11,360**. Lifecycle requests had prime **5,714**, guidance **7,622**, stock host code-mode catalog **13,192–13,272** and direct schemas **12,483**. That stock catalog is not a qualified Beads typed catalog. Returned text bytes were skill **10,679**, WORKFLOWS read **3,965**, shell help **6,125**, and ready output **1,444**; full result JSON sizes are recorded separately. No transmission, token-saving or model-quality inference follows from these sizes. + +Captured processes were **22 shim invocations plus 22 real CLI children**, including initialization, four harness task creations, version/context/prime probes and two model-selected shell commands. There was one additional direct real-CLI final readback, separately disclosed from shim counts. The runner's own `ps` helpers and process-wide startup internals were not instrumented; this is not a total-OS-process census. There were **eight actual model request frames**: four fallback steps and four lifecycle replies across initial and settled runs; **zero typed calls and zero native children**. These are measured counts, not quotas or a benchmark campaign. + +Initial setup failures remain distinct from product findings. An absolute installed-directory reference had no native server entry point; the supported native `file:` archive path resolved the package without editing it. The initial scratch database contained no imported legacy credential row; the supported native credential-create route supplied only the existing scoped OpenAI credential. Early model/registry snapshots were taken before asynchronous settlement. The first lifecycle run similarly sampled premature empty registries, so its unsuffixed snapshots are **inconclusive**, not passing removal proof. The one bounded correction waited for observable effective registries at each stage; it confirmed the late-output failure. `run-fallback.log`, `run-lifecycle.log` and `run-lifecycle-settled.log` retain those runs. No inferred API output or silent successful retry was substituted. + +## Offline verification, cleanup and handoff + +Run without credentials, host, network or inference: + +```sh +python3 /opt/data/cache/scratch/vuy9-installed-model/analyze.py +``` + +The analyzer asserts exact installed bytes/identity, actual provider/model/reasoning frames, read-only fallback, settled registries/overrides, **the observed failure**, single post-reactivation injection, link inventory, unresolved cases, unchanged markers and cleanup. Its passing evidence checks are not a passing acceptance verdict; `analysis.json` says `selectedAcceptance: FAILED / incomplete`. + +`cleanup.json` and `final-native-state.json` establish empty active sessions, pending permissions and saved grants, removal of owned sessions, termination of the owned host group, no listener on 56167 and no live process among all 44 recorded shim/CLI PIDs. The scoped credential was removed through native API; its temporary file, native database/sidecars, private native logs and server secret were removed. Sanitized observations and the disposable Beads records remain. Operator OpenCode is still **2.0.29** with unchanged executable/config/package bytes; all **146 protected repository paths** remain unchanged. Only this new report changes repository content. README and the five permitted current-summary docs remain unchanged because 6.3 did not start. Fourteen accepted and two unchecked markers remain intact; `.9` stays `in_progress / codex:oc-accept`. + +Existing strict named/all OpenSpec validation, planning validation, focused docs/link checks and whitespace verification are recorded in `validation.json`; the unchanged source full suite was not rerun. The focused docs check passed 14 tests/180 assertions. Its initial launch failed before running tests because the script's child shell lacked Bun on PATH; that log is retained separately from the corrected explicit-PATH run. Final scratch handoff is `R/result.md`. + +**Smallest next gate:** parent independently runs the offline analyzer and reviews the failed outgoing-request timeline, then the owner decides the required lifecycle contract and any separately scoped investigation/correction. The late-output guarantee has not been waived. No continuation of qualification, source repair, task acceptance/closure, marker edit, commit, push, sync, archive, publication, release or operator activation is authorized by this handoff. diff --git a/docs/opencode-native-parity-assessment.md b/docs/opencode-native-parity-assessment.md new file mode 100644 index 0000000..a324b14 --- /dev/null +++ b/docs/opencode-native-parity-assessment.md @@ -0,0 +1,255 @@ +# Native OpenCode architecture assessment + +## Current measured acceptance — tasks 6.2–6.3 + +The [installed/model continuation](opencode-installed-model-continuation-vuy9.md) records passing selected 6.2 worker evidence on unchanged accepted archive/released OpenCode 2.1.1/Beads 1.3.1 and genuine `openai/gpt-6.1-sol#high`, followed by 6.3 reconciliation. Actual A–D outcomes cover useful bounded/partial projections, append versus relevant memory, exact versus authorized scope, genuine failed validation/partial native child, actual typed/shell actors, uncertain create readback without retry and fresh/summary/provider-native recovery. Corrected actual watched-config cleanup and 17 native resource access passes are reused; final command-description restoration is now settled. All 19normative requirements and 73/74corpus dispositions plus CODE_MODE have explicit evidence mappings. Parent acceptance is complete for all 16 implementation tasks; see [final acceptance](code-mode-final-acceptance-vuy9.md). + +Previously accepted [source/CLI](code-mode-source-acceptance-vuy10.md), [native permissions](opencode-host-codemode-qualification-vuy2.md) and [Stop/handoff](opencode-stop-handoff-qualification-vuy6.md) were consumed without repeating their campaigns. The original stronger location-reload probe remains failed; actual awaited cleanup governs unload acceptance. Nested plain decline remains a separately accepted host limitation. Ordinary typed activation remains withheld: semantic qualification used an immutable internal bootstrap importing the actual installed source. Only same-provider/model encrypted replay was observed; cross-route migration and multi-hour reliability remain unmeasured. No comparative Claude/model-quality, source repair, operator change or publication follows. Earlier evidence below retains its historical candidate scope. + +## Task 7.1–7.3 working source candidate — 2026-10-21 + +The authorized `.10` worker (`codex:oc-loop`) prepared the eleven-operation native +beads namespace, declared outputs, actual-request actor/capability guidance, +operation-aware references, conservative full-prime freshness and CODE_MODE +resource. See the [CLI/native bridge ledger](opencode-sdk-contract.md#task-7173-source-candidate--2026-10-21) +for exact binaries, commands, captures, candidate hash and limits. Historical +five-tool sufficiency and tools.shell-in-execute statements are superseded; +native shell is direct-only. All30 human command names, native user overrides +and advanced/admin coverage remain; the package now resolves17 explicit links. + +Current deterministic evidence:190 tests pass, typecheck and packed-package smoke +pass; the new scratch copy of the exact2.1.1 output executor/catalog bridge passes +using actual candidate schemas. The owner subsequently selected **Beads >=1.3.1**; +minimum and current qualification target now coincide. Its existing contract run +passes63, including positive blocked-filter evidence. The earlier1.3.0 run remains +historical failed evidence (59 pass/five fixture failures), neither passing nor a +current blocker; no further1.3.0 work is authorized. Package/guard/current guidance +now use1.3.1 and reject older binaries before store access. Host admission remains +>=2.1.1<2.2, with no release/dependency/operator change. This explicitly supersedes +older support and blocker statements in the historical sections below. + +Focused guard/manifest RED→GREEN records26/4 then30/0. A final supported1.3.1 +CLI rerun after the runtime floor change also passes63, including exact positive +blocked query `blocked --parent repo-a5a --label contract-label --json --sandbox`. +Final source checks pass190 tests, typecheck and package smoke (archive +`86f781ba6a17949665448e0603ad696afb7eeb6be95311b68ca6e26c105766d7`). +The same scratch executor/catalog seam was rerun on final continuity SHA-256 +`0bb76279663fd865ba99247a3f1f71b5442c39b4fbcea7eeac875da21d37515c`; +its structured get/note outcomes still pass. New policy-specific captures are +separate from the earlier captures. No retired-target rerun, compatibility +branch, host provisioning or model experiment occurred. + +This is a reviewable uncommitted source candidate, not task acceptance, installed +qualification, activation or publication. `.10` stays in_progress with its worker; +parent owns markers and acceptance. `.2` retains installed native inventory and +permissions, `.6` consumes deterministic results and adds Stop/handoff, and `.9` +retains installed/model workflows and lifecycle. Ordinary continuity activation +remains withheld; operator2.0.29, shared integrations, plans and reports are intact. + +## Historical planning correction — Opus S1–S5 / P1–P3 + +The [original REVISE review](reviews/opus-code-mode-plan-review-wgv.md) remains immutable. The [correction map](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/reconciliation.md) records the new local candidate, not independent acceptance. All typed argv/resources preserve subcommand-first prefixes and --sandbox on reads/writes, with actual derived write actor inserted after operation arguments but before any -- terminator. Pre-spawn input/identity/hold/location refusals stay thrown; permission rejection/cancellation remain unchanged and started-write uncertainty survives. Actors derive from actual request/invocation session and agent; audit attribution grants no permission and assertions separately use context.agent. + +Future task7.1 authority must explicitly cover existing install-bd.sh into disposable directories and existing bd-contract.ts once per1.3.0/1.3.1 for runtime JSON/mutation/claim/create/close/prime-dependence facts. The SDK ledger will be a table citing captured existing-contract output; help/source is discovery, not passing evidence. No installation or contract run occurred in this correction. Task7.2 reruns existing scratch executor/catalog probes once against the actual candidate and records exact pin/commands/results here; there is no maintained product executor seam. Product tests assert declared outputs/branch invariants and bounded table/shared negatives. Task7.3 uses four representative capability states plus the existing cheap suite/typecheck/package gates. .6 consumes deterministic evidence and adds its own Stop/handoff observation; installed catalog/executor/model confirmation remains .2/.9. No graph/ownership change, implementation, activation, publication or closure follows. + + +## Historical planning correction — broader code-mode architecture (2026-10-21) + +The [broader Opus assessment](reviews/opus-code-mode-architecture-wgv.md) and parent pinned-blob verification supersede six-tool sufficiency and the earlier narrow READY. Exact 2.1.1 shell.ts:195 declares native shell codemode:false; tool.ts:236–262 splits direct/code-mode inventories and removes code-mode-only tools when execute is wholly denied. Ordinary shell is a direct model-turn fallback only. Historical direct/code-mode fixture references below describe host hook/permission seams, not proof that the default plugin tools have direct exposure. + +The selected unpublished [active design](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md) section 7 targets eleven typed daily-loop operations with declared outputs, actual host-derived actors visible in context/results, request-capability guidance and targeted freshness. Tasks 7.1–7.3 own this new source delta; historical 3.1 and 6.1 acceptance does not cover it. Keep full prime until measured; do not suppress issue-write refresh until supported prime dependence is established. Planned CODE_MODE adds one resource: 16 resources plus ADR, 17 explicit links; prior 16-link package results remain historical measurements. All 30 human commands/overrides and advanced/admin coverage remain. + +The actual actor is derived from the executing session/agent, not a mandatory beads-task-agent identity or delegation topology; permission checks continue using context.agent. Minimum/current Beads remain >=1.3.0 / installed 1.3.1. Unsupported flags, multi-ID show, claim-conflict shapes and prime-state assumptions remain bounded 7.1 checks. Operator 2.0.29 is untouched. The owner-accepted nested plain-decline continuation remains a failed stronger case accepted as a host limitation, not a passing Stop/Cancel test. Existing .2/.6/.9 independently own host, Stop/handoff and installed/model gates. Planning reconciliation only: implementation_authorized=false; no resumed qualification, activation, publication or closure. Earlier source/live records retain their original scope. + +Historical source assessment from 2026-10-17, reconciled 2026-10-18. Owner selections are complete; current bounded source/fixture evidence is recorded below, separately from unexecuted runtime qualification. This is part of the [full packet](claude-code-integration-analysis.md). The inventory preserves 73 historical release files separately from 74 current marketplace-main files. Claude supplies desired behavior, not the architecture. + +## Operative baseline update — 2026-10-20 + +The owner now selects **2.1.1**, with conservative admission **>=2.1.1 <2.2**. See [the bounded contract delta](opencode-2.1.1-contract-delta.md) for new immutable evidence and adaptations. All S01–S21 source identities and tables below remain the historical 2.0.32 assessment; they are not relabeled 2.1.1. X09 session.list is now released but remains unused in the minimal design. New runtime qualification is unexecuted. + +## Tasks 4.1 / 4.2 deterministic worker contribution — 2026-10-21 + +**Parent-accepted 4.1 source and 4.2 deterministic contribution; live qualification remains unexecuted.** Bead `opencode-beads-vuy.6` owns both tasks and stays open for later live 4.2 after separately authorized 1.1. Baseline HEAD is `db3b65ee86ad02d363baf9cbf24511a15d7bb644`; it contains the accepted `.5` source `5ea915a` and evidence-only successor. Parent records the exact accepted contribution revision in `.6`; neither worker results nor the dependency graph authorize `.8`. Only task 4.1 is checked; 4.2 remains unchecked. [Opus source review](reviews/opus-flow-source-review-vuy6.md) returned READY; parent verified all 10 candidate file hashes unchanged before acceptance. + +Executor: `codex:oc-flow`, full session UUID `01a15bbd-42d2-7d90-867a-1ad927e68fad`. Local session `turn_context` records actual model `gpt-6.1-sol`, reasoning `high`, provider `openai`. The worker atomically claimed only `.6` with command-scoped `BEADS_ACTOR=codex:oc-flow`, read back `assignee=codex:oc-flow`, and retained historical `created_by=unknown` and absent record/account `owner`. The `owner` returned by `bd heartbeat` is the lease holder, not evidence of a stored account-owner field. Checkpoints and heartbeats retain the explicit actor; coordination/review does not itself transfer ownership. + +### 4.1 source contract + +`src/guidance.ts` replaces mandatory one-bead and command-count delegation with authorized-scope guidance. Exact assignments and explicit one-bead mode stop without substitution; a single operation does not authorize a task claim. An already-authorized batch/goal may proceed only within its scope and acceptance gates, with atomic per-bead claim/readback, relevant validation, append checkpoints and closure evidence. Discovery is a report or separately authorized record, never added execution authority. Read-only graph requests authorize no mutation. Denial, cancellation, deliberate stop, permission wait, exhausted scope, decision blockers and incomplete acceptance remain stops. + +The guidance carries actual executor actors through shell claims, writes, updates, heartbeats and delegation, separately from host permission identity. It requires deliberate handoffs, distinct audit-author/account-owner/assignee readback, preserved historical authorship and no human Git/model-name impersonation. Substantive planning and independently owned review have bounded records and their own acceptance; corrections reuse the review item. A quick draft, retry or document does not get a mandatory ticket. Native blocking child return carries scope, actor/session, attempts, outcomes/rationale, candidate, validation, references, blockers and next action; invocation completion alone is not task acceptance. + +`src/agents.ts` keeps both existing IDs, inherited default model, opt-in Luna Max, permissions, default 100-step cap and `0` cap removal. Caps limit an invocation, never its authority or acceptance. No scheduler, scope parser, manager, transport, retry or upstream-repair code was added. The accepted rich assets already express the selected scope and remain untouched. README's former mandatory-one-bead design/task-agent/Luna wording is reconciled for the beta/next candidate while published 1.0.2 remains historical. + +### Deterministic evidence map + +| Contribution | Executed fixture boundary | What remains unproved | +| --- | --- | --- | +| 4.1 exact assignment, authorized continuation, discovery, read-only analysis, denial/incomplete acceptance | `tests/guidance.test.ts` checks canonical prompt contracts; `tests/agents.test.ts` retains profiles/caps/permission rules; `tests/workflows.test.ts` forwards literal caller scope and partial-handoff cues and checks agreement with existing assets | Model compliance, semantic checkpoint quality and real claims/handoffs are not established by text assertions | +| 4.2 failed/cancelled/NO_TEXT/partial child | `tests/host.test.ts` supplies public `execute.after` payloads, checks original input/result/error and identities survive, parent context is invalidated, child cache stays isolated, and only a later requested context gets a recovery cue | No child is actually launched; host job transport, result delivery and model interpretation remain live checks | +| 4.2 deliberate stop racing a late child and success notification | No plugin prompt/synthetic/re-prime is initiated; late payload remains intact; compaction still reports no guaranteed final checkpoint; unload ignores later callbacks | A mock notification is not proof the host itself suppresses background wake or a late native result | +| 4.2 actual parent/child invocation identities, partial handoff and recovery | Registered checkpoint tools append fixture notes with each invocation-derived actor and actual permission agent, despite a different session-default agent. Show preserves attempts, rationale, validation/gaps, references, candidate and separate created_by/owner/assignee data on a fresh setup. Fork starts without parent reference state | This is an in-memory CLI response fixture, with no mutation experiments on the project store; assignment semantics, live durable recovery and host fork remapping are external | +| 4.2 permission wait / late approval | Pending assertion performs no update; reference-only compaction does not write or add permission waits; actual invocation cancellation rejects and later approval cannot spawn the operation | Live allow/ask/deny/filtering, approval UI, saved-grant edits/revocation and wait delivery remain 1.1/4.2 checks | +| 4.2 retained history and provider/model boundary | Representative public compaction payloads preserve absent result, summary metadata.keepID, legacy metadata.recent, supplied native.messages and model identity. The hook adds only system guidance, without replacing result or messages | No provider replay, retained-tail reconstruction, mismatch fallback or fork-remapping algorithm is executed by these fixtures | +| Prior 3.3 / 3.1–3.2 evidence retained | Existing host/continuity tests retain direct + code-mode inner acknowledgement dedup, permission/guard holds, abort/unload, no stale location injection, owned disposal, native command precedence, all 30 registrations, resource paths and honest status limits; [accepted source boundary](opencode-sdk-contract.md#tasks-2233-source-candidate-native-registration-and-diagnostics) | Earlier source acceptance does not qualify live 1.1, 4.2 or end-to-end 6.2 | + +The fixture contract is pinned **2.1.1**, commit `ba546ec1598a70db2570e9c6258028d955ba8f58`, from `baseline211/source` and installed `@opencode/plugin@2.1.1` declarations. [Core tool](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool.ts) lines 103–149 passes session/agent/message/call identity to awaited before/after hooks, with after status `completed` or `error`, not a fabricated cancellation status; lines 264–270 construct actual invocation identity. [Native subagent](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool/plugin/subagent.ts) lines 234–268 blocks on the host job, interrupts/cancels on interruption, returns Tool.Error for failed/cancelled children, and returns a completed result with [NO_TEXT](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/subagent-completion.ts) when text is absent. The after hook is not a universal finally for defects, pre-hook rejection or cancellation. The plugin preserves outcomes and supplies cues; it neither enforces host transport nor adds semantic persistence from arbitrary child text. + +Awaited context/compaction hooks edit the current request. The server event subscription only observes/invalidate state; it is not awaited stop enforcement and never restarts the parent. Natural blocking subagent calls are host-owned. Host history owns retained tails, keepID remapping, legacy decoding and provider/model mismatch; native.messages replaces summary replay. This contribution leaves native replay untouched and performs no compaction persistence, permission wait or replacement result. The prior `.5` suffix-reactivation evidence from the **unpinned** `released` cache remains separately identified in the SDK contract; it is not new pinned or live evidence here. + +The installed Beads CLI continues to own supported database/routing/consistency semantics. These changes do not add confinement promises, routing guards, backend restrictions, upstream repairs, competing-plugin collision guarantees or an activation option. + +### Validation and unexecuted gates + +Focused RED: updated guidance/agent/host assertions failed 6 tests against the old prompt. Minimal GREEN: 58 passed, 0 failed (278 assertions). After adding the narrow deterministic fixtures, the four focused files passed 85 tests, 0 failed (757 assertions); typecheck passed. A fixture initially compared JSON key order and eagerly awaited a rejection before issuing cancellation; both fixture errors were corrected. A partial ToolEditor fixture also required an explicit boundary cast; typecheck then passed. None demonstrated a production host defect, so `src/host.ts` and `src/context.ts` are unchanged. + +The first full cheap suite (`npm exec --yes --package=bun@1.3.14 -- bun test`) reported **159 pass, 1 fail**, 1,137 assertions. The failure is the stale `tests/docs.test.ts` contract demanding old one-bead README wording. `npm exec --yes --package=bun@1.3.14 -- bun run typecheck` passed. `npm exec --yes --package=bun@1.3.14 -- bun run test:package` failed at `scripts/test-package.ts:332–335`, which still requires a sub-1,000-character worker prompt and the old actor-less claim string. Both check files are outside the worker's initial edit scope; narrow correction authority was requested rather than weakening the new guidance or editing them silently. These checks are not reported as passing. + +Parent verification after the worker handoff: the two stale check files were corrected within this source task, replacing old README/one-bead/actor-less claim assertions with the selected scoped-execution contract. Parent reproduced two failing README assertions plus the package-prompt failure before correction. Final independent execution passed **160 tests, 0 failures**, typecheck, packed-package smoke (archive SHA-256 `51f0d2ab294e1b23f90bec424ba479fa2162cef621b47e161743eb4d652d8cb1`), strict global OpenSpec and whitespace checks. The packed prompt's old 1,000-character ceiling was replaced with semantic scope/attribution/closure checks rather than an arbitrary larger quota. These results supersede the earlier failed checks, not their history. Source acceptance and deterministic handoff are now parent-accepted after Opus READY; the exact published revision is recorded in `.6`, and no live outcome is claimed. The review's optional executor-guidance duplication/style observations are not source blockers. Its actor-consistency observation is retained for live 1.1/4.2: verify that shell claims and invocation-derived typed checkpoints use the intended actual executor identity, without silently transferring assignment. + +**LIVE 4.2 remains unexecuted:** no installed host/model launch, install/upgrade, activation, real child/claim scenario or project-store mutation fixture. Live checks must observe blocking child identity/content and failures/NO_TEXT, cancellation and deliberate-stop/late-result races, permission waits, partial handoff/fresh recovery, retained history/fork and provider/model mismatch, and relevant long-running transport behavior on a separately authorized disposable 2.1.1 host after 1.1. Model obedience and meaningful recovery remain behavioral acceptance. No task marker, closure, consumer dispatch, release or publication follows from this worker contribution. + +## Historical selected bundle and alternatives (2.0.32) + +**N1 + D01 + D04 is selected:** narrow typed checkpoint/recovery/memory operations, awaited context/tool hooks, reference-only compaction, observational events, package-owned `oc-beads` skill/resources and `oc-beads:` entries. All corpus members have engineering dispositions under the accepted groups. bd remains executable/database owner; preserve the shared skill and customizations. See [design decisions 2–5](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md) for concrete delivery and authority boundaries. + +The superseded selected minimum was **OpenCode >=2.0.32 <3**, delivered through opt-in beta/next with separately authorized publication. Released permission assertion is the reason for the minimum; no runtime/dependency change occurs here. N0 on 2.0.26 was an evaluated lower-cost alternative, including the Opus review's N0-first advice; Matt selected N1. A failed N1 guarantee is a bounded stop/qualification gate, not an implicit downgrade to N0. + +Scope-bounded continuation is selected: atomic claims and per-bead validation, exact assignment stopping, authorized batch/goal continuation and no discovery-driven scope expansion. Normal blocking child return is the base native mechanism. N2 background completion, nested-depth changes and auxiliary checkpoint/compaction automation are deferred engineering mechanisms; they are unnecessary to implement authorized sequential continuation and carry stop/wake risks. No plugin synthetic restart, command-count delegation mandate or new orchestration service. Optional one-bead mode and opt-in Luna remain. + +Upstream drift compares reviewed marketplace-main commits across full selected roots/support paths, using the existing scheduled check pattern and check output/review links. Unreviewed drift fails a distinct check/job with a drift error; retrieval failure fails with a distinct error diagnostic. No GitHub issue or issue-write permission. Human review advances the baseline; no automatic adaptation, bump or release. + +## Release provenance and availability + +The official [V2 documentation](https://opencode.ai/v2/docs) and [plugin guide](https://opencode.ai/v2/docs/build/plugins/) were checked alongside source and published npm declarations. Rolling docs are discovery evidence; pinned release call sites control availability claims here. + +| Evidence class | Verified identity | Consequence | +| --- | --- | --- | +| Released supported V2 surface | npm `@opencode/cli` and `@opencode/plugin` latest = **2.0.32**; published 2026-10-16 (CLI 22:14:49.966Z, plugin 22:15:43.217Z). Git tag `v2.0.32` = `843a6c514e606b436a7fe713422ace0a93ad3d61`. | All S references below use this tag commit. Published package tarballs were downloaded as evidence, not installed. “Supported” here means public, non-experimental released contract, not proven behavior in the user's runtime. | +| Existing minimum | `v2.0.26` = `9b4ec5714d481559990db0a816d5dec19541a814`; published plugin declarations compared directly. | Session/tool/skill/command/agent/registration declarations are byte-identical to 2.0.32. Permission declaration adds `assert` in 2.0.32; context also adds credential activation (irrelevant). Core compaction/model/subagent implementations differ, so type equality is not runtime equivalence. | +| Released experimental | WebSocket handshake/send/receive hooks; terminal read; `experimental.subagent_depth`; `experimental.portable_shell_scanner`. | Evaluate individually below; no blanket exclusion for instability. | +| Unreleased development | Existing `8f7b0120eb6b4aa3da1f82b1a9094a9fb4db2d06` is current `dev`, not release tag, although package says 2.0.32. Comparison is diverged: 30 development commits vs one release-side commit. `main` lookup returns 422; default branch is `dev`. | Plugin `session.list` is present on development, absent from released SessionDomain. Do not use rolling-doc session listing in the base recovery design. | +| Unknown runtime | No installed host or model/service was exercised. | Source establishes contracts and ordering, not deployed availability, permissions, actual recovery quality or crash durability. | + +GitHub `releases/latest` returned V1 v1.19.3; it does not identify V2 latest. npm registry metadata and Git ref APIs establish the V2 release above. Registry URLs: [plugin](https://registry.npmjs.org/@opencode%2fplugin), [CLI](https://registry.npmjs.org/@opencode%2fcli). Exact source hashes and package integrity checks are reproducible with the inventory validator described below. + +## Native mechanism inventory and call-site findings + +All mechanisms below are released public contracts at 2.0.32 unless explicitly marked experimental/development. Source references identify both declaration and consuming code. A hook being awaited is a trigger guarantee on that code path, not proof that a model will make the right decision. + +| ID / mechanism | Ordering, control and scope | Recommendation and limits | +| --- | --- | --- | +| M01 context / model request | S01/S02/S03: `session.hook("context")` is awaited while assembling each agent-loop request, including tool continuations. Can edit system/messages/tool definitions/options for that request, not persisted history. `model.request` then edits endpoint/headers; HTTP/WS hooks act lower down. Tools invented in context are dropped unless matched to registered definitions. | Use context for guarded prime plus relevant recovery/dirty-state guidance, primary and worker alike with explicit audience. Register tools through the tool domain. Do not use provider traffic rewriting for memory or silently override model settings. | +| M02 tool execution and typed tools | S04/S05: `tool.hook("execute.before")` is awaited before execution, can replace input/tool and reject the call. Promise rejection maps to Tool.Error. After-hook runs after success or typed Tool.Error and before returning result; can edit result/error. It is not a universal finally for defects/cancellation or pre-hook rejection. Both direct and code-mode inner tool paths invoke hooks; observing outer `execute` as well risks duplicates. | Preferred exact acknowledgement comes from the plugin's typed adapter, keyed by session/message/call and operation. For other tools, record only a checkpoint opportunity or concise failure cue, not automatic memory. Preserve original result/error; never hide failures. Narrow tool uses normal permission assertion and abort-aware bounded CLI I/O. No shell-text parser as default. | +| M03 compaction interception | S02/S06: compaction preparation awaits its own hook on both summary and native-provider paths. Summary prompt is appended later; optional `result` bypasses model generation. Auto compaction's started event is emitted inside delivery after preparation; ended/failed are later notifications. Retries can shrink or flatten history. | Use the awaited hook only to add acknowledged durable references and known unsaved/uncertain state. No writes, flush queue, semantic checkpoint wait or new permission wait in this path. Do not enqueue a same-session prompt and wait for it here: compaction occupies that runner. A summary is not a Beads write. Do not replace native compaction with `result` unless deliberately accepting that semantic change. | +| M04 execution/step/permission events | S07/S08/S09: `session.execution.started/succeeded/failed/interrupted` delimit a busy period; step-ended can precede more tools/steps. Interruption carries reason; shutdown differs from deliberate stop. Public `event.subscribe` consumes a server event stream, not the core's awaited internal projection/listener API. The bus applies location routing when a Location service is present; global events and move routing still require explicit session checks. Permission requests/replies are separate from successful execution. | Observe status, invalidate relevant state and expose incomplete checkpoint/blocker diagnostics. No public before-stop interceptor appears in the released SessionHooks contract; no veto or guaranteed checkpoint at stop; no restart on every idle notification. A permission wait is not completed work and must not induce auto-approval. | +| M05 session lifecycle and recovery | S07/S10: created has project/location/parent; moved/forked/deleted are explicit. `get`/`context` can inspect a known session. No released plugin `session.list`, exact “resume” hook, or semantic task-binding API was found. `viewed` is an idle watermark, not recovery authority. | Recover from relevant issue identity and current durable Beads state at the first eligible context request, not merely session-created. Filter server-wide events by actual session location/project; moved/forked sessions require re-resolution. Do not discover tasks by scanning all host transcripts. | +| M06 prompts, synthetic inputs and generation | S10/S11: prompt hook awaits before attachment/skill resolution and durable inbox admission; mutations become canonical input. Default steer vs queue controls admission. Concurrent retries can invoke hooks more than once. S10 synthetic admission bypasses prompt hook, can carry id/metadata, defaults to wake execution unless `resume:false` or revert blocks wake. `session.generate` is transient generation, a separate model request, not persisted task knowledge. | Explicit commands forward attachments/delivery and submit normal prompts. Use synthetic only for selected notifications with provenance, idempotency and explicit continuation policy. `resume:false` does not wake an idle session but is not a promise that active execution cannot consume it. Avoid rewriting user intent or recursive prompt hooks. Auxiliary semantic extraction is an optional future probe, not a background service. | +| M07 native child results | S12: blocking subagent execution waits for the job; completed output is returned in a tool result, failures/cancellation distinguished. Background completion sends a synthetic with child metadata and stable notification ID, then marks notification complete. Returned meaning is last assistant text, possibly NO_TEXT. | Prefer host completion transport over a plugin poller/daemon. Worker guidance must preserve failed attempts, rationale, evidence, blocker/next action and record references; primary reconciles. Do not equate host job completion with issue acceptance or successful checkpoint. No automatic task closure in event listeners. Built-in background delivery wakes by default; deliberate-stop job cancellation exists (S08), but race/restart behavior must be qualified before selecting N2. No synthetic-admission hook was found to let this plugin veto arbitrary host notifications. Until qualified, prefer blocking child return for stop-preserving workflows. | +| M08 registration/discovery | S13/S14: agent, command, skill and tool transforms operate synchronously on domain state, replay in registration order on rebuild and dispose with scope. Command and skill add use map set, so collision can replace an earlier entry. Skill content has real path/resource-base requirements; command executor receives prompt and delivery. | Prefer D01+D04 package ownership, distinct names and preserve-existing collision policy; diagnose collisions instead of overwriting. Command editor exposes only add: inspect `command.list` before registration, then qualify reload/order behavior; do not invent editor.get. Treat later user/plugin transforms as possible overrides and verify effective registry. Agent registration supports optional routing, not a requirement to create new coding agents. | +| M09 permissions, options and diagnostics | S15/S16: permission evaluate hook runs after configured rules; explicit deny bypasses it. Can modify allow/ask to allow/ask/deny. 2.0.32 assertion waits for permission decision; plain rejection must propagate to stop current step. Plugin code is privileged; tool registration alone is not a permission sandbox. Options and `app.version`, plugin list, registry lists and permission list/get expose useful status. | Typed adapter asserts operation-specific authority, preserving existing restrictions and user denials. Do not auto-allow from semantic classification. Read-only diagnostics distinguish unavailable, failed and withheld context without opening stores unnecessarily; no automatic config or credential changes. | +| M10 cleanup/isolation | S03/S09/S16: hooks are location-scoped, registrations have disposal/finalizers, setup may return cleanup. The documented event surface is server-wide; source can location-filter streams, so do not infer session ownership from receipt alone; explicit AbortController required for Promise subscriber cleanup. Hook dispatcher awaits callbacks in order without a generic timeout. Promise hooks other than tool-before use Effect.promise, so unexpected rejection is a defect, not supported policy rejection. | Per-location adapter plus per-session state; abort streams/in-flight work and dispose registrations on unload. Bound plugin-owned I/O, catch/report non-policy failures and avoid leaving queued retries. Do not assume reload or deletion flushes memory. Deduplicate by operation identity, not elapsed-time quotas; don't claim exactly-once CLI mutation across crash. | + +### Experimental and adjacent mechanisms checked + +| ID | Availability / useful possibility | Disposition, qualification and fallback | +| --- | --- | --- | +| X01 WebSocket handshake/send/receive | Released experimental in S01/S02. Awaited per request/frame; HTTP hooks do not cover WS traffic. Can replace frames verbatim; host does not validate semantic rewrites. | Do not adopt for Beads continuity: transport interception offers no semantic checkpoint boundary and adds provider-specific maintenance. If future diagnostics require it, opt in, pin/test WS and HTTP fallback, never log credentials/raw transcripts. Fallback is domain context/tool hooks; no provider mutation needed. | +| X02 persistent terminal read | Released experimental context exposes terminal read (S16), not a new plugin terminal execution lifecycle contract. Could recover visible output from a long-running command. | Deferred from the base; future evidence retrieval only when an identified terminal is relevant and authorized; not a memory feed or daemon. Qualify session/terminal ownership, missing/expired output and truncation; fall back to explicit tool/job results. Do not promise terminal output is durable task state. | +| X03 nested subagent depth | Released experimental config S17, consumed by S12; default depth 1, limit rejects deeper calls. Could support authorized multi-stage investigation without bespoke orchestration. | Deferred engineering mechanism; retain default depth and normal blocking children. A future nested-flow selection would need depth/permission/cancellation and parent-handoff qualification first. Costs more concurrent context and more semantic boundaries. Fallback: primary sequences normal children. No config changed here. | +| X04 portable shell scanner | Released experimental S17/S18; replaces default parser, no tree-sitter fallback; scanner failure is distinct from permission denial. Potentially useful for portability of CLI workflows. | Deferred host qualification candidate, not continuity base or shell-parsing strategy for this plugin. Test relevant platform syntax/error handling separately; owner may retain default scanner. Changing parser does not improve semantic memory or expand authority. | +| X05 policies / V1 experiments | `experimental.policies` is deprecated compatibility input; use supported top-level policy semantics. Migration docs explicitly lack direct equivalents for V1 compaction-autocontinue/text-complete/small-model hooks (S19). | Do not invent V2 hook names or carry V1 guarantees forward. Policy configuration is owner-controlled. Public V2 context/compaction and synthetic delivery cover parts of the behavior, not a universal stop gate. | +| X06 native provider compaction | Released documented option, **not marked experimental** (S06/S20). Provider context may be opaque; endpoint provenance constrains rerouting. | Support as a compatibility path, preserving independent Beads checkpoints and failure reporting. Do not require changing provider/config or replacing opaque state with a custom summary. Probe both native and summary modes only if selected hook behavior depends on it. | +| X07 AI SDK middleware, HTTP, shell hooks | Released domains S02/S21: lower-level request/tool plumbing and shell `create.before` can alter environment/command/timeouts. | Checked, not preferred: semantic task transitions are better served by session/tool contracts. Shell hook has no session identity in its public draft; broad rewriting/parsing is fragile. No middleware-based second agent loop. | +| X08 storage, RPC, reference, worktree, VCS, MCP/integration, UI | Released context domains S16. Storage could retain plugin bookkeeping; RPC/UI could expose richer diagnostics; references can resolve attachments. | No new store/service, telemetry, RPC daemon, auth integration, worktree policy or custom UI required for this outcome. Keep semantic durability in Beads. Read-only plugin-owned status command is enough initially; host-owned durable inbox/child delivery may be used without building a replacement. | +| X09 session listing | Development-only addition at `8f7b012…`, absent in published 2.0.32 plugin SessionDomain. | Exclude from base dependency, not from future consideration. Known-session context plus explicit task recovery works now. Reassess once released, only if cross-session discovery materially improves recovery. | + +## Behavior-to-mechanism mapping + +| Desired behavior | Preferred native path | Semantic owner / verification gap | +| --- | --- | --- | +| Meaningful progress checkpoint | M01 trigger guidance + N1 typed tool; M02 acknowledgement marks task context dirty | Model decides materiality; adapter confirms actual write. No fixed frequency or tool-count write rule. Test duplicate calls, partial failure, fresh recovery. | +| Failed approach | M02 preserves error and suggests task checkpoint at next eligible request; typed checkpoint captures attempt/evidence/next step | Error alone does not prove an approach failed; model distinguishes transient errors and abandoned approaches. Before-hook rejection/cancellation needs separate coverage. | +| Decision and rationale | Registered decision workflow plus typed checkpoint, linked to OpenSpec/docs when relevant | Model/user chooses rationale and alternatives; do not extract decisions automatically from arbitrary transcript text. | +| Reusable discovery / selective remember | Typed explicit memory operation with prior targeted search/recall; on-demand resource examples | Model evaluates evidence, applicability and novelty; no promotion of every successful tool output, private content or hypothesis to memory. | +| Targeted recall / freshness | Recovery context + typed reads; invalidate after acknowledged writes and child handoff/compaction | No complete change feed for external bd writers is established. Re-read active issue/relevant keys at recovery and important transitions; cached absence is not proof of absence. | +| Precompaction durable checkpoint vs summary | M03 carries acknowledged durable IDs/references and known unsaved state without persistence | Cannot ask same runner to perform a new semantic checkpoint inside the hook. Periodic meaningful writes are base protection. An ordinary acknowledged checkpoint before user-requested compaction can protect planned work; automatic overflow/crash remains qualified. | +| Fresh-session recovery | First eligible M01 request + current issue/memory retrieval, using explicit task identity | No reliance on missed creation events or development-only listing. If identity ambiguous, narrow read/search or ask; never auto-claim ready work to reconstruct intent. | +| Worker completion / semantic handoff | M07 host job transport + typed worker checkpoint; context verifies child/primary audience | Host reliably transports status on examined paths, not rationale quality. Parent reconciles record references and acceptance, including NO_TEXT, cancellation and stale records. | +| Stop / failure / permission blocker | M04 observations and M09 permission semantics; earlier checkpoints plus next-request recovery diagnostics | No external veto or crash-final-write guarantee. No synthetic auto-wake after user stop or permission wait. Model decides durable blocker; permission rejection cannot be swallowed as success. | +| Command / skill discoverability | M08 D01+D04, scoped command prompts, installed resource paths, effective registry diagnostics | Model selection remains uncertain; test explicit invocation and normal task-trigger selection separately. All 29 corpus commands retain dispositions even if workflows consolidate. | +| Setup / ownership | Package registrations/disposal, distinct skill ID, preserve-existing collision behavior; upstream initialization remains explicit | Installed package path/override tests needed. No automatic file installer, init, repair, config update or privilege expansion. | + +## Lifecycle and alternatives + +N1 writes synchronously inside the typed operation and returns its evidenced status before ordinary continuation. The compaction hook is reference-only: it carries acknowledged references and known gaps, with no pending-write state, new permission wait or recursive same-session prompt. `session.prompt`/`session.compact` admission is not completion evidence. Meaningful earlier checkpoints protect against compaction; crash-final-write guarantees are unsupported. + +**Concrete permission contract (F2):** S05 `Tool.snapshot` filters on `tool.options?.permission ?? name`; set `options.permission: "shell"` for direct and code-mode inventory filtering. S15 `configured` resolves `agent ?? session.agent`; pass the actual invocation `context.agent`, sessionID and source message/call ID. Assert `action: "shell"` with canonical resource text for the fixed argv operation, preserving selective write denials. S15 saves an "always" reply only when `request.save?.length`; use `save: []`, never a new broad `bd *` grant. Expected “always” acceptance is one-call, so explain repeat prompts. Existing shell `bd *` project grants can authorize equivalent typed operations, subject to configured denies evaluated first. S18 shell parsing gives `bd` a one-token prefix, explaining that existing grant scope. Qualify these outcomes without broadening permissions. Plain rejection must propagate. + +S18 shell preparation resolves cwd, scans target directories, calls `access.authorizeExternal`, then asserts shell resources. The added S18 file-access source shows `external_directory` resources and scope. Plugin assertion alone does not reproduce this path. Bind the narrow adapter to the actual resolved location/workspace, check routing/redirect/symlink/environment effects and guard hold before spawn and after approval. Assert equivalent external-directory restrictions with invocation identity and no broad saved grant where needed; if target or equivalence cannot be established, refuse before store access. Never add a permissive custom action or silently use N0. Qualification covers shell-denied/read-only agents, changed invocation identity, allow/ask/deny, cancelled approval and external targets; no new generic permission manager. + +S13 command names are schema strings and map keys. The released app parser splits the slash name only on whitespace, and submit finds the exact registered name, so `oc-beads:ready` is source-supported through that path. Installed slash discovery/dispatch, collision/rebuild preservation and resource paths remain acceptance checks. Preserve-existing behavior must survive reload; an initial list check alone is insufficient. Report a concrete namespace or lifecycle incompatibility before enabling the affected registration. + +Load registers package-owned surfaces and bounded, location-scoped hooks, then subscribes to notifications with explicit cleanup. First eligible request resolves actual session scope and authority, provides guarded workflow and targeted recovery. During work, the model selects task checkpoints or reusable-memory operations; the typed tool validates and asserts permission, runs supported CLI arguments and returns an honest acknowledgement. Relevant context becomes stale only on known changes, with targeted reads for external uncertainty. Tool hooks provide focused cues and child-result feedback, not autonomous semantic writes. Compaction carries acknowledged references and known gaps only; it neither writes nor requests another same-session turn. Completion/failure events update diagnostics without forcing continuation. Reload/deletion abort observers and clear ephemeral state; later recovery reads durable records. + +Compared with Claude hook shell scripts, typed tool identity and success/error payloads allow precise feedback without parsing arbitrary shell text; mutable context hooks can scope guidance to the actual session/request; native inbox/child results avoid a separate completion poller; package registrations can remove discovery entries with package lifecycle. These are evidenced integration affordances, not proof of better model judgment or durability than Claude. Claude's richer resource guidance remains valuable. + +Historically evaluated CLI-based N0 with native hooks has lower implementation/compatibility cost but weaker acknowledgement and refresh precision. Blanket automatic extraction from tool output is rejected because transport success does not establish reusable meaning and introduces privacy/duplication errors. An end-event-triggered “save everything” loop is rejected because it races shutdown/compaction, can undo deliberate stop and recursively trigger itself. Custom compaction replacement or auxiliary `generate` could extract semantics, but adds model cost/failure/authorization and provider-state consequences; it is not the recommended base. No extractor seam needs implementation now; a later demonstrated need can be evaluated separately. + +## Qualification and stop gates + +Executed evidence is source/API/package inspection and static planning validation only. No inference, server startup, dependency installation, product tests or bd/store operations were executed. Owner selection is not acceptance evidence. + +Future tasks separate stub/disposable fixtures from installed-host/model evaluation. Material checks are typed visibility and allow/ask/deny/save/identity/external-access equivalence; safe multiline argv and uncertain acknowledgements; direct/code-mode handling; package collision/disposal/resource resolution; reference-only summary/native compaction; blocking-child NO_TEXT, cancellation/late-result races and fresh recovery. No exhaustive model matrix or speculative smoke framework. Background/nested automation is deferred, not silently required by scope-bounded continuation. + +All X01–X09 dispositions are recorded in the inventory and design. A concrete contract-changing incompatibility stops the affected path for resolution; normal implementation details do not reopen the owner interview. Source qualification, fixture acceptance and behavioral acceptance remain distinct. + +## Pinned source index and reproducibility + +Each S entry is a released call-site/declaration source, also SHA-256/Git-blob identified in the inventory's `native_assessment.sources`. Run `python3 docs/validate-parity-planning.py /opt/data/cache/scratch/parity-sources` after populating the documented pinned evidence trees. It verifies original corpus identity/coverage, native source identities/links, declared availability anchors, published tarball integrity and declaration comparisons, local links and whitespace. This is a planning evidence checker, not a markdown test framework or runtime qualification. + +- S01: [packages/plugin/src/promise/session.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/session.ts). +- S02: [packages/core/src/session/model-request.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/model-request.ts). +- S03: [packages/core/src/plugin/hooks.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/plugin/hooks.ts), [packages/plugin/src/promise/adapter.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/adapter.ts). +- S04: [packages/plugin/src/promise/tool.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/tool.ts). +- S05: [packages/core/src/tool.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/tool.ts). +- S06: [packages/core/src/session/compaction.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/compaction.ts). +- S07: [packages/schema/src/session-event.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/schema/src/session-event.ts). +- S08: [packages/core/src/session/execution.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/execution.ts), [packages/core/src/session/projector.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/projector.ts). +- S09: [packages/core/src/bus.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/bus.ts), [packages/core/src/plugin/host.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/plugin/host.ts). +- S10: [packages/core/src/session/session.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/session.ts), [packages/core/src/session.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session.ts). +- S11: [packages/core/src/session/prompt.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/prompt.ts), [packages/core/src/session/inbox.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/inbox.ts). +- S12: [packages/core/src/tool/plugin/subagent.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/tool/plugin/subagent.ts), [packages/core/src/session/subagent-completion.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/subagent-completion.ts), [packages/core/src/session/subagent-job.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/session/subagent-job.ts). +- S13: [packages/core/src/command.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/command.ts), [packages/core/src/skill.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/skill.ts), [packages/core/src/state.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/state.ts). +- S14: [services/www/src/docs/content/build/plugins/index.mdx](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/build/plugins/index.mdx), [services/www/src/docs/content/commands.mdx](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/commands.mdx), [services/www/src/docs/content/skills.mdx](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/skills.mdx). +- S15: [packages/plugin/src/promise/permission.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/permission.ts), [packages/plugin/src/permission.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/permission.ts), [packages/core/src/permission.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/permission.ts). +- S16: [packages/plugin/src/promise/plugin.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/plugin.ts). +- S17: [packages/schema/src/config/experimental.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/schema/src/config/experimental.ts). +- S18: [packages/core/src/tool/plugin/shell.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/tool/plugin/shell.ts), [services/www/src/docs/content/permissions.mdx](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/permissions.mdx). +- S19: [services/www/src/docs/content/build/plugins/migrate-v1.mdx](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/build/plugins/migrate-v1.mdx), [packages/core/src/config/normalize.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/config/normalize.ts). +- S20: [services/www/src/docs/content/compaction.mdx](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/services/www/src/docs/content/compaction.mdx). +- S21: [packages/plugin/src/promise/aisdk.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/aisdk.ts), [packages/plugin/src/promise/shell.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/plugin/src/promise/shell.ts). + +Additional checked sources in existing groups: + +- S18: [packages/core/src/file-access.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/file-access.ts). +- S13: [packages/schema/src/command.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/schema/src/command.ts). + +- S13: [packages/app/src/composer/client-slash-command.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/app/src/composer/client-slash-command.ts). + +- S13: [packages/app/src/composer/submit.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/app/src/composer/submit.ts). + +## Final review correction evidence + +The [50919ae independent review](reviews/opus-planning-review-50919ae.md) remains REVISE as issued. B1 is addressed in tasks 1.2 → 1.1: prepare 2.0.32 types first, then minimal throwaway host probes; adapter fixtures are solely 3.1. Live gates 1.1/4.2/6.2 require a separately authorized disposable host pinned initially to 2.0.32 (`>=2.0.32 <3`). The image CLI package is 2.0.29; no runtime was started or upgraded. Missing live environment does not block independent source/fixture work. + +Collision recommendation evidence needs a narrower interpretation: S13 supervisor `ordered` is pre → packages → post; internal `post` includes ConfigCommandPlugin. That plugin refreshes loaded documents, calls command.reload and replays its transform after package commands. Thus source supports later config commands overriding package entries, not the review’s blanket claim that package plugins overwrite them on rebuild. Other transform ordering still needs qualification: command editor has add/Map.set but no conditional get/list; initial listing alone cannot prove preservation. Withhold affected colliding entries where possible, and stop the affected path if public APIs cannot preserve customization. No override permission or generic registry is introduced. Skill editor get/list permits checking each rebuild. + +S13 Skill.prepare sorts and truncates automatic resource listing to ten. Future SKILL.md must explicitly link all 15 resources plus ADR; 2.2 verifies their installed-base paths, and 6.2 checks reachability with external_directory ask/deny (S18), reporting unavailable files without bypass. + +Additional pinned sources for these corrections (same release tree, no new source baseline): + +- S13: [packages/core/src/plugin/supervisor.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/plugin/supervisor.ts). +- S13: [packages/core/src/plugin/internal.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/plugin/internal.ts). +- S13: [packages/core/src/config/plugin/command.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/config/plugin/command.ts). +- S18: [packages/core/src/shell/parse.ts](https://github.com/anomalyco/opencode/blob/843a6c514e606b436a7fe713422ace0a93ad3d61/packages/core/src/shell/parse.ts). diff --git a/docs/opencode-sdk-contract.md b/docs/opencode-sdk-contract.md index 2e5315b..a916669 100644 --- a/docs/opencode-sdk-contract.md +++ b/docs/opencode-sdk-contract.md @@ -1,11 +1,162 @@ # OpenCode V2 Plugin Contract -Reviewed on 2026-10-11 for the narrow OpenCode boundary used by this package (`opencode-beads-2zs`). OpenCode 1.x and its `@opencode-ai/plugin` hook API are no longer supported; 0.10.x and earlier target that line. +## Current measured boundary — tasks 6.2–6.3 + +Source tasks 7.1–7.3 are parent-accepted at `8831169d3c83c333fdf13d4a4efc743eaf495e2b`; [191-test/source and63 CLI evidence](code-mode-source-acceptance-vuy10.md), [15-case native](opencode-host-codemode-qualification-vuy2.md) and [74-check Stop/handoff](opencode-stop-handoff-qualification-vuy6.md) remain accepted and were consumed without rerun. The [installed/model continuation](opencode-installed-model-continuation-vuy9.md) now records passing worker evidence for selected 6.2 and subsequent 6.3 reconciliation: exact released2.1.1, unchanged accepted packed artifact, genuine OpenAI Sol/high A–D semantics, durable uncertainty/readback, actual child actors and fresh/summary/provider-native recovery, with corrected actual cleanup/17 resource passes reused. Parent acceptance is complete for all 16 implementation tasks; see [final acceptance](code-mode-final-acceptance-vuy9.md). + +Ordinary typed activation remains withheld. The typed evaluation uses the existing internal dependency through an immutable scratch bootstrap importing the installed unchanged package. Actual provider-native replacement replay is same-provider/model only; no cross-route migration or multi-hour guarantee is claimed. The nested plain-decline limitation and original location-reload failure remain failed historical evidence. Native watched-config cleanup is the unload acceptance boundary; registry replacement alone does not prove disposal. All 19 requirements and 73/74corpus dispositions plus CODE_MODE are mapped in the new report. Earlier candidate/unexecuted wording below retains historical scope; no release/operator change is authorized. + +## Task 7.1–7.3 source candidate — 2026-10-21 + +Executor `codex:oc-loop`, assigned only `opencode-beads-vuy.10`, based on published plan `f2d6617619e2c3a35e4aeadd5be0092259b9979c`. This section records a working-tree candidate, not parent acceptance, a source commit, installed qualification or activation. Earlier planning and accepted five-operation/170-test evidence below retains its historical scope. The product now declares eleven operations in one native `beads` namespace with code-mode-only registration; ordinary setup still withholds that registration pending the separate native/installed gates. The owner explicitly raised the Beads support floor to >=1.3.1 during this source work; package release version, dependencies, operator installation and host admission remain unchanged. The published planning baseline now has a parent-owned local support-policy delta. + +### Owner support correction and CLI contract ledger + +Owner decision: “1.3.1 should be the minimum beads version not 1.3.0.” +The supported minimum and current qualification target now coincide at **1.3.1**. +Package `beads.minimum`, runtime `MINIMUM_BD_VERSION`, README and positive fixtures +use that floor; 1.3.0 is refused before any store/context lookup, even with +`autoMigrate`. Older captures below remain failed historical evidence, neither +current qualification nor a current blocker. No compatibility branch or further +1.3.0 execution follows. This supersedes the old dual-version/support/blocker +statements in the historical planning sections below. + +Existing `scripts/install-bd.sh` installed official checksum-verified Linux amd64 releases only into `/opt/data/cache/scratch/oc-loop-implementation/bin-1.3.0` and `bin-1.3.1`. Binary identities: + +| Role | Actual version/source reported by binary | Binary SHA-256 | +| --- | --- | --- | +| Historical retired minimum (not supported) | `1.3.0 (f45b249ce: HEAD@f45b249ce6b4)` | `fb387d28410390eb9f0ef81f242a0c789c632f0024f58fe6a8b02d3f06ea20ef` | +| Supported minimum/current target | `1.3.1 (c1c4b642a: HEAD@c1c4b642ac1c)` | `a8f48d771b9e11eccfced4aed72923b59eda746f7a9b425e7ca1af7a51251653` | + +Historical provisioning and initial commands executed once per then-selected version, from the repository (the 1.3.0 commands are records, not instructions to repeat): + +```sh +./scripts/install-bd.sh 1.3.0 /opt/data/cache/scratch/oc-loop-implementation/bin-1.3.0 +./scripts/install-bd.sh 1.3.1 /opt/data/cache/scratch/oc-loop-implementation/bin-1.3.1 +PATH=/opt/data/cache/scratch/oc-loop-implementation/bin-1.3.0:/opt/data/.npm/_npx/60c3515df86f25b1/node_modules/@oven/bun-linux-x64-baseline/bin:$PATH BEADS_ACTOR=codex:oc-loop bun run test:bd-contract +PATH=/opt/data/cache/scratch/oc-loop-implementation/bin-1.3.1:/opt/data/.npm/_npx/60c3515df86f25b1/node_modules/@oven/bun-linux-x64-baseline/bin:$PATH BEADS_ACTOR=codex:oc-loop bun run test:bd-contract +``` + +The existing script creates finite disposable Git repositories and a separate HOME/XDG, clears inherited BEADS_DIR, uses explicit `cwd`, 60-second process bounds, `--sandbox` on every CLI call and the real executor actor on mutations, then removes those repositories. `fixture-owner` is an intentionally assigned fixture assignee, never the command author. No real tracker is a fixture. The script now captures every exact argv, cwd, stdout, exit and failure stderr in `observed` JSON lines. This ledger cites those captures rather than introducing another generator. + +Full captures: `/opt/data/cache/scratch/oc-loop-implementation/contract-1.3.0.log` (SHA-256 `84d76a88b653a545fb3b78f5acef229783011d2a3dabb757ba668e83dc9f2373`) and `contract-1.3.1.log` (SHA-256 `5855393ab67de6d750489974be832cebe2f6a779103a544d06663d6eaa97182d`). **Historical 1.3.0 exited1 (59 checks passed, five fixture assertions failed); supported 1.3.1 exited0 (63 checks passed).** The assertions were corrected before the 1.3.1 run. The owner retired 1.3.0 support; its old failure remains failed and requires no further qualification or fixture work. Supported 1.3.1 has positive blocked-filter evidence and no demonstrated unsupported core behavior. Only the explicit owner-selected floor changed; no upstream repair was made. + +In the table, `I/C/P/O` identify the historical retired-floor issue `repo-g57`, child `repo-an5.1`, parent `repo-an5`, occupied issue `repo-cmo`; current equivalents are `repo-aa0`, `repo-3up.1`, `repo-3up`, `repo-ki3`. `A` is exactly `codex:oc-loop`. Substituting these aliases reconstructs the recorded argv; multiline text and full outputs remain in the captures. All listed successful calls exited 0 unless stated otherwise. Product permission resources render their exact fixed subcommand-first argv; writes insert `--actor A --sandbox` before any `--` terminator, reads insert `--sandbox` there. Product actors derive from actual host session/agent, not this implementation worker's actor. + +| Surface | Runtime argv/evidence in both captures | Observed contract and product decision | +| --- | --- | --- | +| Ready/list/search | ` --limit 2 --json` and filtered variants; ready: `--parent P --label contract-label`; list additionally `--status open`; search: `--label contract-label --status open -- --assignee=x`; assignee calls use `--assignee A --limit 2 --json` (search adds `-- contract`) | Arrays, filtered child C, limit 2 enforced. Ready assignee query returns `[]` because I is in_progress; list/search return I. Product defaults 20/max50 and fetches limit+1 where supported. Ready/list completeness is query-scoped; search stays unknown when not demonstrably truncated, because a short filtered response does not prove absence of CLI truncation. Search parent, ready status and blocked assignee/status are not advertised. No version branching. | +| Blocked | `blocked --parent P --label contract-label --json --sandbox` | Historical 1.3.0 returned `[]`; supported 1.3.1 returned C with ordinary issue fields plus blocker fields. The retired 1.3.0 positive case was unverified because its fixture blocker was not created successfully; supported 1.3.1 has positive filter evidence. Product reports bounded returned rows with completeness unknown (false when capped), never claims empty implies globally unblocked. No unsupported `--limit` flag is sent. | +| Single-ID get | `show I --json --sandbox`; `show contract-not-found --json --sandbox` | Success is a one-record array preserving notes/acceptance/design and separate audit/owner/assignee. Unknown ID exits 1 with error JSON and stderr, never a synthetic missing record. Product get is sequential, at most25 unique IDs, one approval listing all exact resources and fresh checks before every spawn. Partial success preserves per-ID failures; all failed stays failed. | +| Direct edges | `dep list C --direction down --type parent-child --json --sandbox`; `dep list P --direction up --type parent-child --json --sandbox`; unfiltered down call | Typed issue arrays: down yields P, up yields C. Unfiltered output preserves discovered-from and parent-child. Current additionally evidences a blocks edge. Product allows one optional qualified CLI type per call, caps after normalizing, preserves unknown status and returns unknown completeness unless capped; no transitive graph or unblocked inference. | +| Claim | `update I --claim --json --actor A --sandbox` | One-record array with `assignee=A,status=in_progress`; later show confirms author and assignee separately. This is required for acknowledged. Exit0/malformed/wrong actor/state is uncertain, with target and actor. No hidden show or second approval. | +| Claim conflict | `update O --claim --json --actor A --sandbox` | Both exit1, empty stdout. Stderr includes a human diagnostic then JSON `{"error":"1 of 1 issues failed to update","failed":[{"id":O,"error":"updating issue: issue already claimed: already assigned to …"}],"schema_version":1}`. Product not-claimed requires this single-target structured evidence; arbitrary errors remain uncertain. | +| Append note | `update I --append-notes --json --actor A --sandbox` | One-record array retains prior notes plus newline and exact appended text; fresh show confirms retention. Product requires matching target and appended suffix. Notification failure appends detail without erasing the original diagnostic. | +| Selected update | `update C --status blocked --priority 3 --add-label added --remove-label contract-label --json --actor A --sandbox`, then separate deferred/open/in_progress status calls | Returned records evidence status, priority3 and labels `["added"]`; all four nonclosed statuses observed. Product rejects empty updates, closed/ownership/claim changes, conflicting label sets and unknown fields. Requested fields must be evidenced for acknowledgement. | +| Close | `close I --reason contract --json --actor A --sandbox` | Returned one-record array has exact target and status closed. Caller still owns authority and acceptance judgment. | +| Create | `create --title --assignee=x --type task --priority 1 --description "contract-marker description" --acceptance "contract acceptance" --design "contract design" --parent P --deps discovered-from:I --labels contract-label --json --actor A --sandbox` | Generated child ID C; literal title `--assignee=x`, no assignee, actual created_by A, exact selected fields. Other calls establish bug/feature/chore/epic types. Product uses supported `--title`, never a free option-like positional, and never auto-assigns. Uncertain creation preserves its identifying query/title; caller searches/gets before considering another write. | +| Memories/recall/remember | `memories --json --sandbox [-- contract-marker]`; `recall contract-knowledge --json --sandbox`; `remember --key contract-knowledge --json --actor A --sandbox -- ` | Version1 metadata plus key→value map; no matches is a successful empty map. Recall found has exact value; missing exits1 with key/value empty/found=false; empty remember exits1 with error, not an empty stored row. Remember actions remembered/updated preserve exact content. Product retains empty-found versus missing fixtures and landed-uncommitted/commit_error/commit_indeterminate branches, without claiming those commit failures were induced in these real runs. | +| Full prime | `prime --full --sandbox` before and after issue writes | Both bounded comparisons were equal; this does not establish independence across all supported project policies/content. Keep full prime and conservative location invalidation for possible issue/memory writes. Existing guard, migration-marker, default-install and setup contracts also ran in disposable repositories. | + +Historical 1.3.0 fixture failures: adding blocks to a pair already linked discovered-from was correctly refused, causing two downstream positive-blocker assertions to fail; the claim test incorrectly expected `[]` stdout and different text; the ready test incorrectly expected an in_progress issue. The captures preserve all five failures. Corrections use a separate blocker, the observed conflict diagnostic and ready's actual actionable scope. These historical 1.3.0 failures are not relabeled passing. The now-selected 1.3.1 minimum/current run establishes the positive blocked-filter behavior and passes the aggregate; no obsolete-target repair or inference from help is required. Help/source was discovery only. + +### Final verification after the owner floor correction + +The new floor is implemented in `src/guard.ts` and `package.json` only; release +version1.0.2, validated1.3.1, dependencies and lockfile remain unchanged. Focused +RED `minimum131-red.log` records26 pass/four failures (old binary admitted, +old notice, old README and manifest assertions). GREEN `minimum131-green.log` +records30 pass/zero failures, including 1.3.0 refusal after version alone and +1.3.1 readiness. Positive supported fixtures use1.3.1; retired1.3.0 appears only +in rejection fixtures and historical evidence. + +One supported1.3.1 contract rerun was justified by the changed runtime floor, +using the same command above and unchanged extended script, captured separately +as `/opt/data/cache/scratch/oc-loop-implementation/minimum131-contract.log`: +exit0, **63 passed**, SHA-256 +`8b28e3069fbf0bab8051fe28ce47008d7a509a1e7f37020a7d72e1accaa9dfe4`. +The positive blocked query was exactly +`bd blocked --parent repo-a5a --label contract-label --json --sandbox`, exit0; +its returned child `repo-a5a.1` has `status:open`, +`labels:["contract-label"]`, `blocked_by_count:1`, `blocked_by:["repo-6oi"]`. +No1.3.1 core/blocked-filter gap remains from the retired fixture failure. No +1.3.0 command or compatibility repair ran after the owner correction. + +Final branch review also tightened declared issue-field normalization and omits +optional undefined values so output and readable JSON content agree. These are +normalizer corrections in the same module, not a schema validator framework. +Their RED `minimum131-normalization-red.log` is31 pass/one failure; the combined +focused GREEN is62/0. Existing operation table cases now check malformed-output +branches as well as success; no additional version or permission matrix. + +Final cheap checks use new captures `minimum131-final-tests.log` (**190/0**, +1895 assertions), `minimum131-final-typecheck.log` (exit0), and +`minimum131-package.log` (exit0; archive SHA-256 +`86f781ba6a17949665448e0603ad696afb7eeb6be95311b68ca6e26c105766d7`). +That is still20 added tests over170; floor rejection/manifest checks retarget +existing cases. Package smoke checks all30 command callbacks and17 installed +links. Status records registration-plus-execute visibility as observed evidence, +with leaf availability explicitly unknown; it never treats execute alone as +proof every typed leaf exists. User/repository policy and actual permission +identity remain distinct from audit attribution. + +### Actual native output executor/catalog scratch rerun + +A single new scratch adaptation of the existing two probes supplied the initial check: + +```sh +PATH=/opt/data/.npm/_npx/60c3515df86f25b1/node_modules/@oven/bun-linux-x64-baseline/bin:$PATH bun /opt/data/cache/scratch/oc-loop-implementation/native/candidate-probe.ts +``` + +Initial exit0. Pre-floor candidate `src/continuity.ts` SHA-256 `ef9e28be509f35499a183e6a84108d777a3a16236491301a2025ac0b3fac0a8e`. All11 actual definitions supply their own input/output schemas through `definition`, without synthetic schema replacement. Native catalog search returns structured union signatures at `tools.beads.get`/`tools.beads.note`. The actual `packages/core/src/tool/runtime.ts` executor runs inside the copied code-mode result bridge: get acknowledges two separate issue reads, note acknowledges one fixture update and its actual derived actor. These are fixture CLI calls, not real mutations. There is no fallback string/null output schema and no direct-method-only success claim. + +Source pin independently checked against the complete cached 2.1.1 tree (`ba546ec1598a70db2570e9c6258028d955ba8f58`, truncated=false): executed core tool/runtime blob `d1c5e29caf157fce941024570bc1fe9d5727621e`, code-mode tool-runtime blob `9c179bbfe070959704bc6e750e6e2d0045da2b6a`, tool-schema `900556191ca454318de25710df038be96a422035`. The copied tool/namespace/tool-error and Promise adapter inputs also match that tree. Original probes are untouched; all copies and this adaptation live only under the new scratch directory. Results `/opt/data/cache/scratch/oc-loop-implementation/native/results.json`, SHA-256 `eaef8c953bd3df7e9671a0aefe01fe87e7761a34aca7d49b5350fb6847fb217c`; stderr is empty. Product tests import no host executor and add no dependency or maintained runtime seam. This is a native schema/catalog bridge check, **not** interpreter, installed-host, model, permission-matcher or lifecycle qualification; .2/.6/.9 retain their acceptance scope. + +The same scratch adaptation was rerun after the changed guard floor, then after +final normalizer corrections, preserving separate `minimum131-results.json` and +`minimum131-final-results.json` captures. Final exit0 on continuity SHA-256 +`0bb76279663fd865ba99247a3f1f71b5442c39b4fbcea7eeac875da21d37515c`, +guard SHA-256 +`ebfca3c54960d527c7cdca8193c6310714e408240e01b74db249e15bec10ba18`. +Final native result bytes are unchanged (SHA-256 +`eaef8c953bd3df7e9671a0aefe01fe87e7761a34aca7d49b5350fb6847fb217c`), +and final stderr is empty. Exact final dependency hashes and actual worker +session are in `native/minimum131-final-inputs.json`. These reruns check the +changed candidate through the same single scratch seam; they do not create a +maintained test/runtime dependency or qualify the installed host. Earlier +results and original historical probes are preserved. + +### Context, guidance and deterministic verification + +Guarded full prime is cached separately from per-request actor/capability rendering. Actual request session/agent drives the same executorActor function as typed writes; permission assertions independently retain actual invocation context.agent. Status uses observed request identity/capabilities or unknown, discloses unknown leaf availability and effective registry ownership limits, and never opens a store to invent capability. Native shell remains direct-only. Every possible issue/memory write conservatively invalidates relevant location prime; acknowledged outcomes avoid a redundant generic readback cue. Failed/uncertain/partial outcomes retain targets, actor, original diagnostics and recovery gaps. Task and memory references remain distinct; ordinary unrelated tool errors no longer cause a generic prime refresh, while child/important recovery and external changes retain freshness checks. Owned cleanup clears request observations with the existing session/reference state. + +The scope/ownership/stop policy remains in the task-agent prompt; its repeated executor section is removed only from that agent's injected guidance. Detailed project planning/review elaboration moves on demand to existing WORKFLOWS; distinct user/repository policy minimization is unchanged. All30 commands and native override precedence remain, with capability-neutral workflow prompts. CODE_MODE is explicitly linked and packaged: 16 resources plus ADR, 17 links. + +Evidence directory `/opt/data/cache/scratch/oc-loop-implementation`: declaration RED `operations-red.log` (15 pass/1 fail), first GREEN (16/0), final operation suite (32/0), request-context RED `context-red.log` (16/1), integrated GREEN (111/0), final full `bun test` **190 pass/0 fail**, `bun run typecheck` exit0 and `bun run test:package` exit0. This adds20 tests over the accepted170-test baseline, largely operation-specific table cases and representative request states, not an operations×versions×permissions matrix. Package archive SHA-256 `afae8906f44d534a352e8d0fb86469b0ab59b4051e5f5a5e205cb8ef72f658ba`; unpacked callbacks and all17 links passed. Strict OpenSpec change/all, planning validator and whitespace checks passed. Worker task markers, review reports, shared/operator integrations and activation remain untouched; the seven active-plan edits are the separately recorded parent support-policy delta. Historical 1.3.0 aggregate failure remains failed and retired; supported 1.3.1 minimum/current qualification passes. + + + +## Historical planning correction — Opus S1–S5 / P1–P3 + +The [original REVISE review](reviews/opus-code-mode-plan-review-wgv.md) remains immutable. The [correction map](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/reconciliation.md) records the new local candidate, not independent acceptance. All typed argv/resources preserve subcommand-first prefixes and --sandbox on reads/writes, with actual derived write actor inserted after operation arguments but before any -- terminator. Pre-spawn input/identity/hold/location refusals stay thrown; permission rejection/cancellation remain unchanged and started-write uncertainty survives. Actors derive from actual request/invocation session and agent; audit attribution grants no permission and assertions separately use context.agent. + +Future task7.1 authority must explicitly cover existing install-bd.sh into disposable directories and existing bd-contract.ts once per1.3.0/1.3.1 for runtime JSON/mutation/claim/create/close/prime-dependence facts. The SDK ledger will be a table citing captured existing-contract output; help/source is discovery, not passing evidence. No installation or contract run occurred in this correction. Task7.2 reruns existing scratch executor/catalog probes once against the actual candidate and records exact pin/commands/results here; there is no maintained product executor seam. Product tests assert declared outputs/branch invariants and bounded table/shared negatives. Task7.3 uses four representative capability states plus the existing cheap suite/typecheck/package gates. .6 consumes deterministic evidence and adds its own Stop/handoff observation; installed catalog/executor/model confirmation remains .2/.9. No graph/ownership change, implementation, activation, publication or closure follows. + + +## Historical planning correction — broader code-mode architecture (2026-10-21) + +The [broader Opus assessment](reviews/opus-code-mode-architecture-wgv.md) and parent pinned-blob verification supersede six-tool sufficiency and the earlier narrow READY. Exact 2.1.1 shell.ts:195 declares native shell codemode:false; tool.ts:236–262 splits direct/code-mode inventories and removes code-mode-only tools when execute is wholly denied. Ordinary shell is a direct model-turn fallback only. Historical direct/code-mode fixture references below describe host hook/permission seams, not proof that the default plugin tools have direct exposure. + +The selected unpublished [active design](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md) section 7 targets eleven typed daily-loop operations with declared outputs, actual host-derived actors visible in context/results, request-capability guidance and targeted freshness. Tasks 7.1–7.3 own this new source delta; historical 3.1 and 6.1 acceptance does not cover it. Keep full prime until measured; do not suppress issue-write refresh until supported prime dependence is established. Planned CODE_MODE adds one resource: 16 resources plus ADR, 17 explicit links; prior 16-link package results remain historical measurements. All 30 human commands/overrides and advanced/admin coverage remain. + +The actual actor is derived from the executing session/agent, not a mandatory beads-task-agent identity or delegation topology; permission checks continue using context.agent. Minimum/current Beads remain >=1.3.0 / installed 1.3.1. Unsupported flags, multi-ID show, claim-conflict shapes and prime-state assumptions remain bounded 7.1 checks. Operator 2.0.29 is untouched. The owner-accepted nested plain-decline continuation remains a failed stronger case accepted as a host limitation, not a passing Stop/Cancel test. Existing .2/.6/.9 independently own host, Stop/handoff and installed/model gates. Planning reconciliation only: implementation_authorized=false; no resumed qualification, activation, publication or closure. Earlier source/live records retain their original scope. + +Reviewed on 2026-10-11 for the narrow OpenCode boundary used by the published stable package (`opencode-beads-2zs`). OpenCode 1.x and its `@opencode-ai/plugin` hook API are no longer supported; 0.10.x and earlier target that line. The current source branch separately prepares an opt-in beta/next candidate on the 2.1 line. ## Versions and sources -- Declared optional peer range: `@opencode/plugin` `>=2.0.26 <3`. The only import is type-only (`import type { Plugin } from "@opencode/plugin"`), so packages install nothing from OpenCode and use the host's runtime. -- Installed development dependency: `@opencode/plugin` `2.0.26`, matching the validated `@opencode/cli` 2.0.26 host (Bun 1.4.2). +- Historical stable 1.0.2 declarations used optional peer range `@opencode/plugin` `>=2.0.26 <3` and development dependency `2.0.26`. The only import is type-only (`import type { Plugin } from "@opencode/plugin"`), so packages install nothing from OpenCode and use the host's runtime. +- Current beta/next candidate declarations use optional peer range `@opencode/plugin` `>=2.1.1 <2.2` and development typing dependency `@opencode/plugin` `2.1.1` for development typing only. The exact 2.1.1 runtime target has not been qualified. - Authoritative sources: the installed `@opencode/plugin` 2.0.26 declarations, the [plugin guide](https://opencode.ai/v2/docs/build/plugins/), the [V1 migration guide](https://opencode.ai/v2/docs/build/plugins/migrate-v1/), [plugin configuration](https://opencode.ai/v2/docs/plugins/), the [skills reference](https://opencode.ai/v2/docs/skills/), the published OpenAPI document (`https://opencode.ai/v2/openapi.json`), and `@opencode/schema` 2.0.26 for event names. - Qualification: a throwaway V2 plugin (`opencode-beads-2zs.1`) and the actual source plugin were loaded into disposable projects on OpenCode 2.0.26. Results were read with `opencode debug agents`, a JSONL trace from the plugin, and short `opencode run` sessions. Test sessions were deleted afterwards. @@ -31,10 +182,81 @@ OpenCode resolves a package plugin as `/server`, then ``. The packag ## Commands and skills -V2 plugin commands are executable (`execute` calls `ctx.session.prompt`) rather than templates. Plugin-provided skills need a `path`. The vendored `/beads:*` templates were retired instead of ported (`opencode-beads-2zs.4`). OpenCode V2 discovers the canonical `.agents/skills/beads` skill installed by `bd init` natively, and users can load it with `@beads`. +V2 plugin commands are executable (`execute` calls `ctx.session.prompt`). Separately, native [Markdown and JSON commands](https://opencode.ai/v2/docs/commands/) support prompt templates, description metadata and argument substitution. The [full integration analysis](claude-code-integration-analysis.md) pins current source and records the remaining minimum-host qualification; command retirement was not forced by an absence of V2 prompt templates. Plugin-provided skills need a `path`. The vendored `/beads:*` templates were retired instead of ported (`opencode-beads-2zs.4`). OpenCode V2 discovers the canonical `.agents/skills/beads` skill installed by `bd init` natively, and users can load it with `@beads`. -## Compatibility decisions +## Compatibility decisions for the historical stable 1.0.2 baseline The supported line begins at OpenCode 2.0.26, the first version qualified. There is no V1 fallback or dual export: V2 ignores V1 `server()` exports, and supporting both would mean maintaining two hook implementations. Tests fake only the small context slice the plugin uses (`location`, `options`, `event`, `agent.get`, `agent.transform`, `session.hook`) so behavior can be checked without the host. When running `opencode run` from a non-interactive shell during qualification, close stdin (`< /dev/null`); otherwise the CLI waits indefinitely, which looks like a hang. + +## Selected beta/next boundary (not yet qualified) + +The [reconciled design](../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md) selects N1 on OpenCode `>=2.1.1 <2.2`, released source `ba546ec1598a70db2570e9c6258028d955ba8f58`. The package now declares that optional peer range and pins development types to exact `@opencode/plugin` `2.1.1`; the import remains type-only. The host context's existing `ctx.app.version` field is used to report an unsupported-host diagnostic and skip agent/hook registration outside that range. This source guard and the published declarations do not constitute runtime qualification: exact 2.1.1 remains untested, and future 2.1 patches are admitted candidates subject to checks rather than tested versions. The 2.0.26 runtime evidence above remains historical and does not qualify N1. No stable package version, consumer pin, or host runtime was changed. + +Required acceptance covers typed-tool `options.permission: shell` filtering, actual invocation agent in assertions, canonical fixed-operation resources, `save: []` without new broad saved grants, external-directory/location equivalence, holds and cancellation. Command schema/registry accepts string names including the selected `oc-beads:` namespace; installed UI/dispatch still needs qualification. Compaction only carries acknowledged references and known gaps, never a pending-write queue or same-runner checkpoint. Package assets/registrations/disposal and blocking-child stop races need their own fixtures and installed-host checks. N0 is a historical alternative, not an automatic fallback. + +Final review B1 sequencing: task 1.2 prepares the 2.1.1 development types before 1.1’s minimal throwaway host tool/command probes. Current 2.0.26 types lack permission.assert. Adapter-specific fixtures remain in 3.1; fake contexts cannot qualify host filtering or dispatch. Live gates 1.1/4.2/6.2 require a pinned isolated disposable host, initially 2.1.1 within `>=2.1.1 <2.2`, and later authority to provide/install it. The image CLI package currently reads 2.0.29; the older qualified 2.0.26 results above are historical. Neither qualifies N1, and neither runtime is upgraded by this plan. Missing live prerequisites block live acceptance, not independent source/fixture work. + +Expected approval outcomes: `save: []` installs no new saved rule even for “always”; repeated ask prompts are intentional and must be explained. Existing shell `bd *` grants can authorize equivalent typed operations, subject to configured denies; do not broaden scope for convenience. Installed acceptance also checks all 16 explicit resource links under external-directory ask/deny and initial/rebuilt command preservation. The native assessment records config transforms replaying after package transforms, while retaining the bounded public-API collision gate. + +The [2.1.1 delta](opencode-2.1.1-contract-delta.md) governs relevant fixture migration: SessionCompactionResult.native, metadata.keepID/legacy metadata.recent, fork remapping and native.messages replacement semantics. Keep reference-only hooks; no replacement result or replay window. Tool state/UI metadata are not renamed. Current lifecycle events replace any obsolete session.compacted assumption. Released paginated session.list is unnecessary for known-session recovery. Historical 2.0.32 source evidence remains separate; future admitted 2.1 patches are not claimed tested, and 2.2+ requires another explicit contract review. + +## Task 3.1/3.2 accepted source and separate activation boundary + +Parent acceptance: reviewed the complete source/fixture candidate against `f4dddb31eb31cc7b61fb381a5a8740c427be64ba` and the owner-corrected CLI responsibility boundary. Standards and spec review found no remaining material source issue. Independently executed 133 tests (0 failures), typecheck, package smoke, strict change validation and whitespace checks. This accepts tasks 3.1/3.2 source only; it does not qualify native host permissions, model behavior or production activation. The execution record `opencode-beads-vuy.4` holds the resulting publication identity. Historical review findings retain their original verdicts; this is parent acceptance of the corrected implementation, not a new independent-review verdict. + +`src/continuity.ts` defines only checkpoint/show/memories/recall/remember, with direct argv, shell-filtering metadata, actual invocation agent/session/message/call assertions and `save: []`. The normal adapter uses `runBd`; tests override that runner and public host methods, without a separate execution implementation. Actor/cwd/env/force/administration are not model inputs. Matching CLI payloads establish acknowledgement; interrupted or malformed writes remain uncertain with no retry. Memory results retain failed/indeterminate commit fields, and missing-key stdout on a nonzero exit is distinguished from an empty found value. Tests establish plugin-side behavior only. + +**Attribution correction after targeted review:** exact 2.1.1 [core/tool.ts](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/tool.ts) lines 264–270 supplies invocation sessionID and agent, independently of model input. These primitives suffice for audit attribution: `opencode::<12-hex-digest>` uses a bounded agent label and SHA-256 of full sessionID, NUL, and full agent ID. The same invocation session/executor keeps its actor across calls; another session or executor has a different actor. Permission evaluation continues to use actual `context.agent`. Missing invocation identity refuses execution; there is no human Git fallback, model actor parameter, external actor-field requirement or identity service. Attribution is not authentication and never changes assignee: deliberate handoffs still require separately authorized ownership changes and evidence. The earlier actor blocker was overstated. + +**No-open guard correction:** the parent verified the cached `where.go`, `routed.go` and `main.go` Git blobs against the pinned Beads 1.3.1 tree at **c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c**. The earlier unknown-source-identity statement was incorrect. [where.go](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/where.go) lines 87–98 can open storage to recover a missing prefix. The shared automatic guard and typed adapter now use `bd context --json`, not `where`. Installed `context --help` documents configuration-only diagnostics; pinned [main.go](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/main.go) skips its store initialization, [context_cmd.go](https://github.com/gastownhall/beads/blob/c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c/cmd/bd/context_cmd.go) reads configuration, and the proxied variant uses the filesystem context provider. The guard checks `.local_version` under the reported `beads_dir` as before. An unsupported or failed context probe produces an explicit `probe-failed` hold, not silent absence or unsafe fallback. No shadow configuration, environment clearing, alternate database, bootstrap or server was introduced. Help and pinned source were sufficient; no disposable datastore or project-store experiment was needed. + +**Operative owner correction — upstream responsibility:** normal supported Beads routing, including fallback after a local miss, is CLI-owned behavior rather than an observed defect. The owner explicitly withdrew the earlier final-store confinement prerequisite and upstream routing-disable remedy. Checkpoint/show now invoke the installed CLI normally from the actual session/project cwd; no final-store attestation, route-disable flag, backend restriction, local metadata/resolver clone or upstream repair framework is introduced. The plugin does not clear inherited routing environment or claim runtime shell/plugin confinement of internal CLI accesses. Historical review reports and Beads notes remain evidence of prior interpretations, not current authority. + +The adapter's boundary is correct invocation context, exact-operation OpenCode shell permission using actual agent/session/message/call identity, `save: []`, external-directory checks for explicit invocation/location access, executor attribution, cancellation, the existing migration guard and honest outcomes. Session/location and guard are checked again after approval and before spawn. External-directory checks concern the explicit lexical/resolved cwd against host workspace/project roots; internal CLI redirects, backend selection and routing are not additional plugin targets. The guard uses supported `bd context --json` to obtain `beads_dir`; it does not decode or snapshot backend metadata/routes. CLI errors remain visible, ambiguous append/remember outcomes are not retried, and memory acknowledgement does not promise database consistency repair or plugin-issued commits. + +**Source versus activation:** one adapter implementation uses the normal `runBd` default; tests override dependencies only. Ordinary `setupBeads` registers no continuity tools before successful live 1.1. The internal `continuityQualified` dependency lets tests exercise that same registration path, without a permanent user activation option or a qualification claim. Source fixtures cover all five requested operations, explicit actor/permission identities, direct argv, denial, location access, guard/cancellation after approval, acknowledgement distinctions and normal inherited routing/backend choice. They do not emulate upstream routing or qualify live host behavior. No source blocker for intended CLI routing remains; parent acceptance and live qualification/activation are separate. + +Context now records missing/withheld/failed/empty/ready states separately, aborts invalidated loads, and refreshes at relevant execution/compaction/model/agent transitions. Guard failures diagnose `guard_failed` and prohibit manual bypass. Write acknowledgements dirty location caches; generic tool failures and child returns supply focused recovery cues without extracting memory or parsing shell text. Session get returns session info directly, whose public location exposes directory, not workspaceID. When available on the host/test slice it checks that directory before supplying context; move/fork/delete discard local references and caches. No private workspace identity is inferred from the public response. Cleanup aborts owned processes/observers and disposes owned registrations, with no final-write guarantee. Only complete instruction bodies demonstrably present in supplied context are reduced; customized local policy survives managed markers. + +Compaction hooks append reference/gap guidance only: no Beads call, permission wait, queued persistence, recursive prompt, replacement result or `native.messages` injection. Fixtures assert that supplied summary/native results, `metadata.keepID`, legacy `metadata.recent`, fork-remapped references and changed provider/model fields remain untouched. They do not execute or prove the host's replay/fork/mismatch behavior; the pinned 2.1.1 host owns compatibility and replay, including the fact that `native.messages` replaces summary replay. Direct/code-mode acknowledgement is handled only inside typed execution; the outer `execute` hook never repeats it. Live filtering, deny/grant precedence, repeat approval prompts, external access, stop races and replay remain unexecuted qualification/acceptance work. + + +## Tasks 2.2/3.3 source candidate: native registration and diagnostics + +Parent source acceptance: the corrected implementation resolves the original Opus plan findings without claiming a new reviewer READY verdict. Parent inspected ordinary registration/status, per-rebuild customization checks, packed delivery and lifecycle changes against baseline `38206ff428838e0290b6d2bb3db5ddc7b7beb9d4`, and independently executed 150 tests (0 failures), typecheck, package smoke, strict global OpenSpec and whitespace checks. Packed smoke invokes all 30 native bodies and verifies owned disposal. This accepts tasks 2.2/3.3 source/package/fixtures only; live 1.1/6.2 and privileged continuity activation remain unexecuted. Bead `opencode-beads-vuy.5` holds publication identity and limitations. + +`src/workflows.ts` supplies native Promise `CommandDefinition` bodies for exactly the selected inventory's 29 stems plus `oc-beads:status`. One ordinary command transform registers every definition, checks abort before adds and retains its owned disposer. Initial `command.list()` collisions or lookup failures are diagnostic-only: they do not suppress registration. Bodies submit normal `session.prompt` requests with literal user text, `files`, `agents`, `skills` and `delivery` (`queue`/`steer`), without Claude template evaluation, shell interpolation, permission escalation or synthetic continuation. Typed privileged continuity remains withheld pending separate live 1.1; registering prompt commands does not activate it. + +The pinned [Promise command declarations](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/command.ts) expose a synchronous add-only transform. [Core command](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/command.ts) uses map set; [state rebuild](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/state.ts) replays transforms in order. Pinned 2.1.1 `plugin/supervisor.ts:99–103` orders pre → packages → post, and `plugin/internal.ts:254–259` places ConfigCommandPlugin in post. Its single transform replays refreshed configuration/file commands after package transforms on initial load and config rebuild; removing an override restores our definition. Same-name conflicts with other plugin transforms use normal native ordering and may replace an earlier entry. The package neither promises universal collision prevention nor edits user files or host configuration. + +The original [Opus resolution review](reviews/opus-registration-resolution-review-vuy5.md) additionally inspected suffix reactivation in the **unpinned** `released` cache because that registry file was absent from `baseline211`. That detail is not pinned 2.1.1 evidence or live qualification. The report retains its original REVISE verdict; this correction is not a second independent review. Installed reactivation/discovery and permissions remain separate unexecuted acceptance work. + +The registered status body reuses the existing context controller, persistence gaps and config-only guard. It reports actual registration completion, effective public command names/descriptions, missing expected names, description mismatches and lookup failures. A mismatch is observable evidence of a different effective definition, not owner proof. Public owner identity and identical-description overrides are unavailable; matching names/descriptions do not prove ownership. A failed lookup reports unknown effective entries and does not invent missing names or override findings. Context distinguishes missing, withheld, failed, empty and ready; an in-progress load or unknown session location is explicitly uncertain. Unconfirmed location withholds the probe and discards stale session references. Status never runs prime or a store-opening diagnostic, claims a final write, or reports live qualification from fixture results. + +Skill registration always installs the public transform when packaged guidance is readable; the candidate editor's list is checked on every rebuild for a colliding ID/name. There is no initial list collision/unavailability gate. A removed customization therefore allows restoration through native rebuild, while later config transforms keep their precedence. Abort guards and the owned disposer apply to the skill as well. Missing/unreadable packaged guidance is diagnosed without filesystem exception details; accepted asset bodies remain unchanged. + +Package smoke resolves both existing exports, compares the unpacked inventory/resource bytes, resolves all 16 explicit links from the installed base and checks a missing installed file. It invokes the actual registration callbacks, verifies all 30 native definitions, executes their prompt bodies with attachments/delivery, and derives the five owned disposers from agent/skill/command registration plus the two registered hooks. Focused fixtures replay a small ordered list of package/config callbacks to check overrides, restoration and owned removal; they do not clone host internals. + +The host-owned 2.1.1 [skill scan](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/config/plugin/skill-scan.ts) stops at depth six, skips hidden/ignored directories and stops descending when SKILL.md is found. [Resource preparation](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/skill.ts) applies ignore globs and samples only ten files; [config skill watches](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/config/plugin/skill.ts) watch out-of-root resolved files specifically. Fixtures load explicit assets below hidden/ignored node_modules paths deeper than six levels without emulating scanners/watchers. Explicit package registration and all 16 links do not imply recursive discovery or resource watches. No package watcher is added. External-directory ask/deny, model use and installed namespace dispatch remain unexecuted live 1.1/6.2 checks. + +Cleanup is awaitable and idempotent: abort context loads/processes and the event subscription immediately, clear session/location references, then await every owned disposer and observer. Disposal failures are reported; a later setup failure releases earlier registrations. Fixtures include command/skill abort guards, native status cancellation, user config preservation and late-context suppression. They establish source/package obligations, not live unload behavior or a final durable write. Source task markers, acceptance and release/activation state remain parent-owned. + +## Task 1.1 exact released-host observations — 2026-10-21 + +This append records new measurements; preceding source acceptances and historical unexecuted statements retain their original scope. **Task 1.1 is not qualified:** exact 2.1.1 code-mode explicit decline loses the distinct abort outcome. The [qualification record](opencode-host-qualification-vuy2.md) supplies identities, per-case results, reproduction, sanitized traces and cleanup. Actor `codex:oc-host`, actual worker session `01a15c62-d106-7e80-af58-5a47b1032cd4`, repository baseline `072429f1a7723ade23e756a921924d9d0acacf31`. + +The official `@opencode/cli-linux-x64-baseline@2.1.1` tarball passed published SRI/SHA-1 and registry-signature checks; extracted executable SHA-256 `4595933989548a452e920c971d5931898d7d64e73e039f777ed62c84a18916c9` reports `opencode v2.1.1`. Official tag/source pin remains `ba546ec1598a70db2570e9c6258028d955ba8f58`. The real host ran only on loopback with separate HOME/XDG and an initially committed scratch repository under `/opt/data/cache/scratch/vuy2-oc-host`. A controlled local HTTP/SSE provider fixture drove minimal throwaway tools/commands through native host execution; no production adapter, mock host, external-provider/model acceptance or project Beads probe was used. + +**Passed measured contracts:** whole-shell denial removes direct and code-mode probes from actual model inventories; selective denial leaves discovery intact and blocks the forbidden resource. Assertions carry actual invoking identity, including an original restrictive invocation after the live session default was switched to an allowing agent. Allow/ask and correction feedback propagate without rejected-target execution/retry. `save: []` plus “always” leaves the saved list empty and asks again. Fixture-owned shell `bd *` grants authorize equivalent typed operations and a harmless native-shell stub; configured denies still win. Native grant edits/revocation are re-evaluated, including within one unchanged live session. Native shell and typed probes request the same explicit external-directory and shell resources and execute `pwd` in the same approved cwd; external correction/deny prevents target execution. The real command registry admits and invokes exact `oc-beads:ready` with literal arguments and queue delivery. + +**Failed measured contract:** direct explicit decline yields `Permission.DeclinedError`, an aborted native tool/step and no next provider request. In code mode the same rethrown leaf outcome becomes outer `execute` completion with inner error metadata and generic `Tool.Error` text, followed by another provider request. The target does not execute or retry, and `metadata.error: true` must not be mistaken for target success; nevertheless the distinct decline/abort outcome is lost. Evidence: session `ses_ea396cc6affenEp6WisS1KyHef`, message `msg_15c6933a4001vwxlp9uhAKg3TG`, call `vuy2_declined-code`, `cases.jsonl`, `plugin.jsonl`, provider traces and native message readback. This conflicts with the selected preserved-outcome guarantee and the plain-rejection step-ending contract in the pinned Promise permission declaration. The affected path stopped; no upstream/product patch, translator, fallback or activation follows. Parent/owner disposition and subsequently passing authorized host evidence remain required. + +All owned host/provider processes and listeners were stopped; operator 2.0.29 executable/configuration hashes match the pre-provisioning record. `.2` remains open for parent review. No task markers, `continuityQualified`, installed-package/model acceptance, child qualification, publication or source acceptance were changed. + +## Superseding owner decision: retain code mode and accept bounded continuation + +The owner accepted the observed OpenCode 2.1.1 code-mode plain-decline continuation limitation while retaining composition. The failed stronger whole-step-termination case above remains failed historical evidence, now a known accepted host limitation; it is not relabeled as passed. The adapter must still propagate assertion rejection unchanged, preserve correction feedback, never execute the rejected target or automatically retry it, and never acknowledge target success. Later operations retain independent permissions. Explicit Stop/Cancel, scope and honest persistence guarantees are not waived. + +Remaining qualification/activation is paused for parent review of the broader code-mode planning reconciliation in `opencode-beads-wgv`, with new source tasks 7.1–7.3. An isolated pinned-host-executor reproduction of our undeclared output-schema defect is recorded under `/opt/data/cache/scratch/opencode-codemode-shape-review/`; this is fixture evidence, not installed-product/model acceptance, and the defect is NOT covered by the upstream limitation acceptance. Task 1.1 remains unchecked pending parent reconciliation. No product source, activation, public filing, publication or operator change follows from this decision. diff --git a/docs/opencode-stop-handoff-qualification-vuy6.md b/docs/opencode-stop-handoff-qualification-vuy6.md new file mode 100644 index 0000000..cbb670a --- /dev/null +++ b/docs/opencode-stop-handoff-qualification-vuy6.md @@ -0,0 +1,99 @@ +# OpenCode 2.1.1 live Stop/handoff qualification — vuy.6 / task 4.2 + +## Parent acceptance + +Parent accepted task 4.2 after inspecting the actual-source wrapper, CLI shim and offline assertions, independently running all 74 checks, confirming 151 protected source paths unchanged and independently checking that all three owned ports have no listeners. The selected Stop/Cancel, actual-child outcome/identity, partial handoff and durable/retained/fork recovery mechanics are supported by live observations plus explicitly retained deterministic evidence. The shim's intentional post-mutation output hold establishes started-write uncertainty, not rollback. Model judgment/provider replay/lifecycle evaluation remains task 6.2; unmeasured multi-hour behavior is not certified. No successful post-Stop child callback was fabricated as a live pass. The following worker report preserves the pre-acceptance handoff and its exact limits. + +**The bounded native observations passed. Parent acceptance remains pending.** Explicit native Stop suppressed later composed writes during a started mutation and a pending approval. Actual native children preserved failure, cancellation, NO_TEXT and partial-return distinctions. Primary/child shell and typed actors agreed. Durable readback, summary compaction and a real fork were observed. These are controlled-provider mechanics, not installed activation, external inference or model-judgment acceptance. Task 4.2 remains unchecked and `.6` remains in progress. + +## Candidate, worker and artifact + +Execution date: 2026-10-21 UTC. Sole worker `codex:oc-flow`, fresh session `01a15f8f-fcfb-7990-871e-af2661619648`; actual local rollout `turn_context` records `gpt-6.1-sol`, high effort. No prior-session continuity or additional worker is claimed. Full assignment and original task preimage are retained under **R**, `/opt/data/cache/scratch/vuy6-stop-handoff`, in `prompt.md`, `record-before.json` and `record-current.json`. + +| Identity | Exact value | +| --- | --- | +| Published repository HEAD | `2cbd87363baf67a6a271259cdeec02041fc6cef0` | +| Accepted runtime source | `8831169d3c83c333fdf13d4a4efc743eaf495e2b` | +| Historical 4.1 / deterministic 4.2 | `a5d6b2bea779259ba9aa3175ab86fb2f4f2b862b`, retained accepted | +| Native released host | OpenCode `2.1.1`, source `ba546ec1598a70db2570e9c6258028d955ba8f58` | +| Reused first-party package | `@opencode/cli-linux-x64-baseline@2.1.1`, from `/opt/data/cache/scratch/vuy2-oc-host/release/package/bin/opencode` | +| Executable SHA-256 | `4595933989548a452e920c971d5931898d7d64e73e039f777ed62c84a18916c9` | +| Tarball SHA-256 | `216d3861d0a167adad5850282439bae13483203f4b110f0510f260118bba2858` | +| Immutable wrapper source SHA-256 | `5bf30eae3361928c7b3d46664b8f3adf89b5257a32f2201a5ead1bedfa8aca11` | +| Bundled wrapper SHA-256 | `3e347c202d575e0478d7a6f82517f153660eb4bd2e0cc261512d59fa822226cb` | +| Installed disposable-project CLI | Beads `1.3.1 (c1c4b642a: HEAD@c1c4b642ac1c)`; executable SHA-256 `a8f48d771b9e11eccfced4aed72923b59eda746f7a9b425e7ca1af7a51251653` | + +`identity.json` records the identities, 36 retained source-file SHA-256/Git-blob checks and nine additional pinned local source checks for session, child, execution and compaction APIs. Published tarball SRI/SHA-1 and both registry signatures were reverified using retained metadata/keys. This reuses the accepted release provenance; it does not attest a reproducible binary build. No upstream runtime was transplanted or patched. + +The accepted [source acceptance](code-mode-source-acceptance-vuy10.md), original [flow review](reviews/opus-flow-source-review-vuy6.md), code-mode [REVISE](reviews/opus-code-mode-source-vuy10.md) and [READY recheck](reviews/opus-code-mode-source-recheck-vuy10.md) were consumed. Existing deterministic tests and the 191-test suite were not rerun. The accepted [native follow-up](opencode-host-codemode-qualification-vuy2.md) and [original qualification](opencode-host-qualification-vuy2.md), including the failed stronger plain-decline expectation and superseding owner disposition, remain unchanged. + +## Actual host and adapter boundary + +The committed disposable Git project is `R/repo`, native project ID `df9152e52485c2ead34220bc0e80204f0f9abf25`. Its newly initialized `.beads` is the only issue store used for qualification mutations. Separate HOME, XDG config/data/cache/state and an explicitly constructed environment are recorded in `env.json`/`process.json`; the reliable Bun directory is on PATH. No operator environment or external-provider credential was inherited. The local HTTP/SSE provider uses the released OpenAI-compatible transport, a noncredential provider placeholder and a fresh transient native-server password. It records bodies, never Authorization headers. No external inference occurred. + +The real host ultimately ran PID/group `752378`, loopback port `46749`; the provider ran PID/group `752027`, loopback port `33813`. An earlier setup host PID/group `752064`, port `45097`, was stopped before the qualified sequence. `start.py`, `launch.py`, `provider.py`, `client.py` and `env.json` retain the launcher and payload construction. + +`wrapper.ts` imports actual accepted `setupBeads`; Bun bundles its unchanged ten source modules into read-only `plugin/index.mjs`. The only behavioral dependency supplied is existing internal `continuityQualified:true`. The wrapper observes native hooks/events, forwards `permission.assert` unchanged while logging the real request/result, and never fabricates a child result, calls a callback with invented payloads, prompts/restarts a parent or replaces compaction/replay results. Native session, tool, subagent, cancellation and permission services execute the observations. `wrapper-identity.json` identifies the immutable wrapper, package and executable shim. This is adapter-in-real-host evidence through a test-only qualification seam, not normal installed activation. Ordinary production activation remains withheld. + +The scratch PATH shim `bin/bd` delegates to unchanged `/usr/local/bin/bd`, records actual argv, cwd, wrapper/process PID and exit/stdout/stderr, and adds `--sandbox` to automatic read-only guard/prime calls that otherwise omit it. Typed mutation argv already include their own `--actor` and `--sandbox`; the shim does not replace adapter guard, routing, normalization or acknowledgements. Every actual CLI call stays at `R/repo`. No routing/confinement framework or upstream compensation was introduced. + +For the started-write Stop point, the shim runs the real append to completion, records its result, then holds stdout before the adapter can acknowledge it. Native Stop kills that process group through the real runner. This deliberately measures cancellation while a started operation lacks a returned acknowledgement; it does not claim cancellation before the database mutation or rollback. Other calls are not delayed. No operation is automatically retried. + +The bundled scratch wrapper reports `skill-unreachable SKILL.md` because its bundle does not carry the installed package resource tree. That diagnostic is retained; installed skill discovery/resources and unload/reactivation were not qualified here and remain `.9` work. It did not block native adapter tools or context. + +## Scenario map and measured outcomes + +Every task 4.2 scenario is mapped below. “Reused deterministic” means accepted source evidence was inspected and consumed, without rerunning its fixtures. “Later-model-owned” is an outstanding gate, not waived acceptance. Raw case names refer to `R/cases.jsonl`; native messages, calls and permission payloads also appear in `api.jsonl`/`plugin.jsonl` and provider requests in `provider.jsonl`. + +| Task 4.2 scenario | Classification and evidence | +| --- | --- | +| Stop during composed multi-write work | **Live measured PASS**, `stop-started-multiwrite`. The first actual append process ran; native `POST /api/session/:id/interrupt?resume=false` returned `interrupted:true`. Neither the second note nor priority update dispatched. No next provider step for that stopped call. The original tool is aborted, with no normal leaf acknowledgement. | +| Started mutation uncertainty / no final checkpoint | **Live measured PASS**, same case plus `same-session-recovery` and `retained-compacted-recovery`. Recovery context retains `note "repo-mh4" actor=opencode:probe_ask:7d30f5d3596d: uncertain` and the explicit no-final-checkpoint gap. Authorized readback found the append once. Later success/reads/compaction did not erase its conservative in-session uncertainty. No rollback or final-write guarantee is asserted. | +| Stop during native approval; late acceptance | **Live measured PASS**, `stop-pending-approval`. Native pending permission `per_15f948c20001QCUUXcJg1nGU06` carried the exact note resource, source message/call and `save:[]`. Stop returned true. A late `decision:"once"` reply returned 204; pending permissions became empty, and neither the pending note nor later note spawned. A successful reply response is not execution evidence. | +| Primary shell / typed attribution | **Live measured PASS**, `primary-create`, native `primary-shell-claim`, `primary-checkpoint`. Actual session `ses_ea06cc133ffeKSO5jGi4sgYvGW`, agent `probe_ask`, actor `opencode:probe_ask:7d30f5d3596d` agree across context, typed create/note and direct-shell atomic claim. Permission requests separately carry actual invoking agent/message/call. | +| Actual child identity / shell and typed agreement | **Live measured PASS**, `native-child-created`, `child-partial-handoff`. Native parent `ses_ea06a7ad3ffe7LOswjthORHdbB` launched actual child `ses_ea06a79caffedOGAfB7kcC1vVB` via `subagent`, then continued that same child through native `sessionID`. The native parent context exposes actor `opencode:probe_ask:e9cab5cccfa7`; it is a separate primary session from the started-write case. Child agent `flow_child`, actor `opencode:flow_child:169fca4a5c33`; create, shell claim, typed note and remember agree. This actor differs from the primary actor. | +| Actual invocation versus changed session default | **Reused deterministic + accepted host evidence**: accepted continuity actor/permission fixtures and `.2` `namespace-invoking-agent` prove the deliberately changed-default case. Here every real typed assertion was matched against its actual before-hook session/agent/message/call. No new changed-default matrix was run. | +| Failed child | **Live measured PASS**, `child-failure`, child `ses_ea069eb7bffeQILXxl0GCPw5nY`. Controlled local HTTP 400 fails a real child request. Parent receives native error `Subagent failed (sessionID: …): VUY6_CONTROLLED_CHILD_FAILURE`; original outcome is preserved, no success/closure inferred. | +| Cancelled child | **Live measured PASS**, `child-cancelled`, child `ses_ea069e89dffeMvnHwSJP3hX1bz`. Native child interrupt while parent blocks returns true; native parent result remains `Subagent cancelled (sessionID: …)`. Later fixture response does not revive it. | +| NO_TEXT | **Live measured PASS**, `child-no-text`, child `ses_ea069ee88ffephlywkXHOqTmFH`. Empty child assistant text becomes native completed output `Subagent completed without a text response.` Completion is transport status, not acceptance. | +| Deliberate parent Stop racing child / late work | **Live measured PASS for cancellation winning**, `parent-stop-late-child`. Parent `ses_ea069dc46ffePV50ctoqGofSHz` stops while actual child `ses_ea069db57ffejxDYgD681qsDd4` is running. Child request held 12 seconds; both executions settle. Parent subagent state is error with `error.type:"aborted"` and actual child ID, while progress metadata still says running. That metadata is not treated as completed work. Releasing delayed provider text produces no new provider request, target operation or parent restart. | +| Successfully completed child result arriving after Stop | **Reused deterministic evidence**, `tests/host.test.ts` case “a late child return and execution success after deliberate stop do not restart or imply a final checkpoint”, accepted at `a5d6b2b`, retained by corrected source. Live case above observes native cancellation and late provider response, not a successful completed-child callback after Stop. No such live result was fabricated or relabeled. Background delivery/nested automation were not enabled to force that interleaving. | +| Partial handoff / attempts, rationale, validation, references | **Live measured transport/persistence PASS**, `child-partial-handoff`. Real note/memory acknowledgements and native child text preserve a scoped partial payload, task/memory references, unresolved acceptance, unsaved followup and next action. `.6` deterministic partial/failure fixtures remain consumed. Semantic quality/model obedience is **later-model-owned** by `.9`. The payload’s hypothetical failed approach A and phrase “get validated” are fixture-supplied text, not evidence that the child ran that experiment/get; executed CLI facts come from raw traces. | +| Fresh-session recovery and original ownership | **Live measured PASS for retrieval**, `fresh-recovery`, new session `ses_ea069130dffectIPjfiR0BH8RC`. With no inherited transcript it reads both actual tasks and recalls `vuy6-handoff`. Original audit authors/assignees stay distinct from the fresh session; no recovery claim, closure or reassignment. Model interpretation of that state remains **later-model-owned**. | +| Retained summary/history and real fork | **Live measured PASS**, `native-retained-compaction-final`, `fork-compacted-recovery`, `retained-compacted-recovery`. Native summary contains partial status/references; `metadata.keepID` and legacy `recent` are present. Real fork `ses_ea0682c03ffeNxE1pORWP5CwU1` changes compaction/message/keepID identifiers and preserves summary/recent bytes. Projected context exposes summary/recent rather than enumerating all historical keepID referents; no broader storage-consistency attestation is claimed. Compaction itself makes zero CLI calls. | +| Provider-native replay / provider-model mismatch | **Reused deterministic + later-model-owned**, existing accepted host/context fixtures preserve native.messages, keepID/recent and mismatch semantics. This OpenAI-compatible fixture exercised native summary compaction, not encrypted/provider replacement replay or external-provider/model migration. The latter remain `.9` acceptance; no substituted replay window. | +| Transport reliability | **Live measured, bounded**, five real native children, foreground return, error, cancellation and a 12-second held request; exact settlement/outcome evidence above. No poller, transport rewrite or parent restart engine. Multi-hour reliability and external-provider performance remain **unresolved/unmeasured**; this small scenario set does not certify them. | +| Plain decline / independent later permissions | **Reused accepted host limitation**, original `.2` failure plus accepted follow-up. Nested PLAIN DECLINE may allow independently permissioned later work/model steps; failed stronger termination stays failed. It was never used as Stop evidence. | +| Event-only final save, rollback, automatic retry or closure | **Explicitly unsupported guarantees**, preserved by accepted deterministic evidence and live Stop/readback. No event-driven final write/restart, blind retry or automatic closure occurred. Scope/assignment/acceptance are never inferred from readiness. | + +The scenario set used two real disposable tasks, `repo-mh4` and `repo-s31`, plus scoped memory `vuy6-handoff`; five actual child sessions were created. There were 19 typed invocations (including the refused setup call) and 184 actual CLI invocations, including guard/prime probes and initialization. Counts describe observations, not quotas or acceptance criteria. + +Both task creations were unassigned with `created_by` equal to their actual native creator. Shell claims intentionally assigned each to that same executor. `owner` was absent, read separately rather than inferred. Fresh readback retains both original `created_by` and `assignee`, with `status:"in_progress"`. Parent coordination does not transfer ownership. Historical project-tracker `created_by=unknown` is unchanged; `.6` account-owner field is absent and its assignee remains `codex:oc-flow`. + +The explicit post-Stop handoff was a separately requested normal tool sequence after readback. It records that the first append landed once, later writes never dispatched, final semantic work is unsaved and parent acceptance remains unresolved. Stop itself did not prepare that handoff. Raw records retain the earlier notes as append history. + +## Harness failures retained honestly + +- Initial scratch wrapper build had a syntax error; the initial launcher also used a mistaken scratch artifact path. Both failed before any qualifying tool measurement. The final wrapper was then frozen before live use. +- Initial TMPDIR was `R/tmp`, making sibling `R/repo` fall outside Beads’ designated temporary boundary under `/opt`. The real config-only guard returned `probe-failed`, so the first typed create never reached permissions or mutation. The harness corrected isolated TMPDIR to **R**, matching the prior scaffold, and restarted only its owned setup host. The failed call and guard output remain in raw traces. No source/CLI repair, routing override or hidden fallback. +- Raw case name `child-created-no-text` is a harness mislabel: its native output was `CONTROLLED_FIXTURE_STOP`. The analyzer/report use the later `child-no-text` case for genuine NO_TEXT. The original raw record is retained. +- Two manual native compactions failed: first the controlled response used an unsupported heading; then the response was put at an already-consumed provider index. Both native `compaction.failed` outcomes remain recorded, including the host’s template nudge. `run-retained-final.py` supplies the correct structured template at the actual next request index; its separate completed case is the passing observation. No automatic product retry or replacement compaction result was introduced. +- Final live cleanup assertion initially expected an empty active-session array. Native API actually returned `data:{}`. Raw active, saved and per-session pending responses had already been captured; `final-native-state.json` reconstructs their empty state offline after shutdown. No new host was started to manufacture cleanup evidence. + +## Reproduction, protection and remaining gates + +Run the retained analyzer without host, provider, credential, network or product import: + +```sh +python3 /opt/data/cache/scratch/vuy6-stop-handoff/analyze.py +``` + +It passes **74 checks** over actual invocation/process, permission, cancellation, result and recovery traces. It checks the started/pending forbidden targets, native child errors/NO_TEXT, actual actors and original ownership, compaction/fork observations, and cleanup/protection evidence. It also checks current protected repository, wrapper and operator executable/config bytes. `results.json` retains its output; `evidence-sha256.json` identifies stable evidence. `run-stop.py`, `run-child.py`, `run-outcomes.py`, `run-recovery.py`, `run-retained.py` and `run-retained-final.py`, with corresponding logs and `plans.json`, retain exact driver inputs. Only the offline analyzer is intended for immediate parent rerun; live replay needs a new isolated root and fresh credential. + +Before shutdown, native active sessions were empty, all session pending permissions were empty, and saved grants were empty. `cleanup.json`, `protection.json` and `cleanup.log` record termination of owned groups, absence of their listeners and all 369 recorded invocation/initial-host PIDs, and no listeners on ports `45097`, `46749` or `33813`. Only owned groups were terminated. The transient secret and private unsanitized logs were removed; sanitized raw traces remain. + +All **151** protected repository paths match `protected.json`. All **116** original host evidence files and **85** native follow-up files retain their hashes and path sets. The wrapper/source checksums remain unchanged. Operator OpenCode still reports `2.0.29`; executable SHA-256 remains `7e23471f4ebe2313d78baacc9ac96fe9e197ce26d808de4b0fcc37efbe473dad`, with matching before/after config/package hashes. Operator auth was not used by the isolated runtime: its cleanup SHA-256 and inode/mtime/ctime are recorded in `operator-auth-check.json`; both modification/change times predate this worker. A pre-run auth digest was not captured, so no separate before/after auth digest pair is claimed. No credential contents are logged. Beads minimum/current remains 1.3.1. + +Only this new repository report was added. No production code, old report, SDK, tests, assets, package, OpenSpec or operator integration was edited. No staging, repository commit, push, Dolt synchronization, task checkmark, closure, activation, release, external model call or `.9` dispatch occurred. + +**Remaining limits/gates:** no new selected Stop failure or required source repair was demonstrated. A successful late child callback after Stop remains accepted deterministic evidence rather than a new live interleaving; long-duration reliability is unmeasured; provider-native/mismatch and semantic/model/lifecycle acceptance remain with `.9`. None is silently promoted to live passing evidence. The smallest next gate is parent inspection of this case map/raw traces and an independent offline analyzer run, followed by an explicit decision on task 4.2 acceptance and publication. `.6` stays `in_progress / codex:oc-flow`; the worker does not check the task or close it. diff --git a/docs/opencode-unload-lifetime-investigation-vuy9.md b/docs/opencode-unload-lifetime-investigation-vuy9.md new file mode 100644 index 0000000..48b89af --- /dev/null +++ b/docs/opencode-unload-lifetime-investigation-vuy9.md @@ -0,0 +1,72 @@ +# OpenCode 2.1.1 unload/lifetime diagnosis — vuy.9 + +**Conclusion: the observed late context is explained by supported location-generation retention. It does not demonstrate an adapter cleanup defect or an upstream bug.** The earlier probe changed configuration and called `/api/location/reload`. That operation detaches the old location graph and builds a replacement; a running step can continue borrowing the old graph. The replacement registry's absence of the package does not establish that the old package's setup cleanup ran. Default managed-service startup uses the same Core lifetime mechanism. + +The [original qualification failure](opencode-installed-model-qualification-vuy9.md) remains failed and unchanged. This diagnosis neither passes task 6.2 nor waives its selected guarantee. Task 6.3 remains unstarted. The smallest recommended disposition is an **owner-reviewed acceptance-probe correction**, distinguishing actual plugin-scope unload from location routing replacement. No product fix is established or authorized. + +## Scope and source identity + +Same sole Sol/high worker: `codex:oc-accept`, actual session `01a15ff1-fc4e-7d53-9b4d-2cb102cd4f76`. Repository HEAD remains `d5e2b172c3ab7106b2ea1a55af3725821f4b4d9a`; accepted adapter source is `8831169d3c83c333fdf13d4a4efc743eaf495e2b`. Investigation root **I** is `/opt/data/cache/scratch/vuy9-lifetime-investigation`; prior qualification root **Q** is `/opt/data/cache/scratch/vuy9-installed-model`. + +All upstream claims below use **`ba546ec1598a70db2570e9c6258028d955ba8f58`**, the exact 2.1.1 source tree. Relevant cached files were reused; only missing relevant files were fetched at that commit. `I/source-manifest.json` records Git-blob identity and SHA-256 for every inspected copy. `I/finding-to-source.json` distinguishes public contract, source facts, prior measurement and inference, with exact paths/lines. The historical `specs/v2/catalog-config-plugin-lifecycle.md` labels its option sketches historical; they were not treated as current API authority. + +**No live probe was needed.** Core comments, public endpoint semantics, request ownership and the retained observer timeline agree. Source provides no plausible foreground-versus-managed lifetime branch requiring a comparison. No host or controlled provider was started, and no credential file was accessed, checked, refreshed or used. No semantic evaluation, implementation, task/contract edit or publication occurred. + +## What the failed probe actually measured + +The prior driver, `Q/lifecycle-settled.py`, wrote its scratch configuration and called **location reload**, with file watching disabled in its isolated environment. It then queried registries through new HTTP requests. Those reads resolve the **current** location map; the running Session drain retains the graph it already borrowed. Two complete location graphs can therefore coexist, with different effective plugins. + +The retained timeline is measured evidence, not a reconstructed provider response: + +| Epoch seconds | Retained observation | +| --- | --- | +| `1792611635.780` | Observer setup in the pre-reload location. | +| `1792611636.0914652` | Real prime output held by the qualification shim. | +| `1792611636.102` | `location.shutdown` observed. This is logical location shutdown, not a logged package cleanup callback. | +| `1792611636.1173723` | `/api/location/reload` returns 204. | +| `1792611636.158` | Another observer setup, while the old step is still active. | +| `1792611636.3035364` | Current plugin registry omits the product; adjacent command/skill/agent snapshots show the replacement registry state. | +| `1792611636.338` | After held output release, the pending context contains one Beads envelope. | +| `1792611636.665` | The actual outgoing OpenAI request contains that envelope. No new inference was performed for this diagnosis. | +| `1792611638.396` | The retained step ends; the event appears twice because overlapping observers saw it. | +| `1792611638.397` | An observer's actual setup disposer logs `observer.dispose`. | +| `1792611638.401` | Session execution succeeds. | + +`I/retained-timeline.json` extracts the original API/observer events. `Q/failure.json` retains the prime and outgoing-frame observations. **Inference:** the deferred observer disposal fits release of the old borrowed graph after its step. Observer records lack generation IDs, so this pairing is not a directly tagged product-disposer measurement. The original probe did not log the product's setup cleanup timestamp. It cannot establish that prime completed *after that cleanup*. + +## Owning scopes and ordering + +The normal whole-location reload path has this order: + +1. The global Session executor acquires the selected location graph through `Instance.provide`, running the location-scoped runner inside that borrowed layer. A `Reloaded` result releases that invocation and recursively acquires the selected replacement. This borrow owns the graph lifetime; an HTTP registry request has its own shorter borrow. [Session execution:95–107](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/execution.ts#L95-L107), [instance selection:8–18](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/effect/app-node-builder.ts#L8-L18). +2. Reload enumerates loaded locations and invalidates their routing entries. `location-services.ts` explicitly preserves running steps' borrowed graphs until a boundary. Its logical shutdown closes permissions, forms and RPC and emits `location.shutdown`; it does **not** call `Plugin.close` there. Replacement builds are then booted. Returning from reload establishes replacement builds, not completion of every old graph's teardown. [Location map:20–37](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/location-service-map.ts#L20-L37), [invalidation:63–79](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/location-services.ts#L63-L79), [logical shutdown:43–59](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/location-lifecycle.ts#L43-L59). +3. The runner checks `lifecycle.isClosed()` at `advanceToStep`. If reload happens after that check while request context preparation is pending, the current step can finish using its captured services. Once that step returns, the next boundary closes its model transport and returns `Reloaded`. No location-closed recheck is inserted between the awaited context hook and construction of the current provider request. [Runner:69–82](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/runner/llm.ts#L69-L82), [current request:228–243](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/session/runner/llm.ts#L228-L243). +4. After old borrowers release, closing that graph reaches the plugin supervisor's `registry.close` finalizer. Core then closes the plugin root/activation scopes. The Promise adapter's actual setup-disposer invocation is `Effect.acquireRelease(...plugin.setup(context2), cleanup => ...cleanup())`, at **`packages/plugin/src/promise/adapter.ts:619–622`**. This is when the product cleanup is invoked, rather than when another graph's registry changes. [Supervisor:135–138](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin/supervisor.ts#L135-L138), [plugin scope close:238–248](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin.ts#L238-L248), [setup cleanup:619–622](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/adapter.ts#L619-L622). + +Promise hook registrations are attached to the setup scope using the captured runtime at adapter lines 243–251. Session callbacks are wrapped as awaited Promises at lines 591–595. The callback itself does not acquire an additional plugin-generation lease: the Session's graph borrow keeps its owning services alive. Hook disposal removes an entry from the map used by **future triggers**. `PluginHooks.trigger` already iterates an array reference; replacing the map's array does not retract callbacks already selected or cancel a Promise already running. The public `SessionContext` declaration provides no unload AbortSignal. [Hook registration/trigger:69–94](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin/hooks.ts#L69-L94), [Promise session declarations:25–37](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/promise/session.ts#L25-L37). + +**In-place plugin removal is a different ordering within the same graph.** A watched config change publishes `Config.Event.Updated`; the existing supervisor reruns activation. For a healthy removed/replaced plugin suffix, `Plugin.activate` deletes the old active slot, **awaits `Scope.close` of its activation**, loads remaining/replacement definitions, assigns the new public inventory, then publishes `Plugin.Event.Updated`. It does not first detach a location graph and substitute another registry. Normal plugin removal/replacement must also be distinguished from the separate transform-failure recovery path, which can schedule teardown asynchronously. [Normal activation:125–175](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin.ts#L125-L175), [config-change activation:223–244](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/src/plugin/supervisor.ts#L223-L244). + +## Adapter responsibility and the selected guarantee + +The accepted adapter's cleanup immediately sets its abort flag and disposes the context controller before awaiting registration/observer cleanup: `src/host.ts:172–190`. Controller disposal marks it disposed and aborts cached loads; `systemFor` checks disposal/abort again after its awaited load (`src/context.ts:173–175,187–191`). The host context hook checks the plugin abort flag after awaiting `systemFor`, before appending context (`src/host.ts:357–358`). Thus the accepted source already suppresses late injection **when actual setup cleanup has occurred before completion**. This is source evidence, not a new runtime pass or proof covering every race. + +The public Promise-plugin documentation says OpenCode awaits cleanup on actual unload/replacement. The public **location.reload** description separately says running sessions continue with fresh services at the next step boundary. Those are compatible contracts. The old probe substituted absence from a replacement graph's registry for completion of the old setup scope's cleanup. The unconditional interpretation “no old output after location reload returns or the new registry omits the package” exceeds the public/native 2.1.1 reload behavior. The narrower actual-unload guarantee remains a reasonable separate acceptance target and still needs its correctly owned observation. [Plugin cleanup docs:29–38](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/src/README.md#L29-L38), [public reload contract:51–59](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/protocol/src/groups/location.ts#L51-L59). + +There is no demonstrated failure of the adapter's abort checks after its cleanup, no demonstrated upstream violation of the documented reload boundary, and no basis for a downstream drain/supervisor/replay engine. The original stronger selected acceptance result remains **failed**, not retroactively passing under this explanation. + +## Managed service and practical consequence + +The user's default-service correction is confirmed. `server-connection.ts:21–48` selects an explicit endpoint for `--server`, a private server for `--standalone` or disabled service, and otherwise resolves a managed service with `Service.ensure`. Its managed command is the same CLI `serve --service` (`service-config.ts:133–145`). Private standalone starts `serve --stdio --port 0`, with stdin acting as its process ownership lease. + +Foreground and managed modes converge on the same server `start(...)` in `server-process.ts:100–160`, and the same `AppNodeBuilder`/`buildLocationServiceMap` Core graph. Managed mode changes discovery/registration, persistent process ownership, saved server settings/environment and startup cwd; explicit session location therefore matters. It does **not** select a different plugin cleanup or current-step reload rule. This conclusion is source-backed; it is not a claim that managed startup itself was live-qualified. [Connection selection](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/cli/src/services/server-connection.ts#L21-L48), [common server launch](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/cli/src/server-process.ts#L100-L160). + +For real managed-service users, closing or reopening a client does not imply server/plugin teardown. Config reload can make new requests use new registries while a current step finishes with its old context. Native watched plugin changes can instead unload/rebuild plugin scopes in the existing location. Restarting the server is process teardown with separate execution recovery semantics, and deliberate Stop is explicit cancellation. None of those operations is interchangeable with a registry query or hot location reload. No operator action was performed here. + +## Recommended owner disposition and remaining limits + +Recommend recording a **probe/lifecycle-boundary mismatch**, not opening a repair campaign. Owner should decide whether the installed unload criterion means actual plugin-scope cleanup or immediate revocation at whole-location routing replacement. For actual unload, the smallest later scoped gate is a controlled native watched-config plugin removal/reactivation observation in the same location graph, recording the setup disposer and pending context ordering. Keep the existing no-output-after-actual-cleanup checks and preserved failure; do not silently alter tasks/specs. If immediate revocation at location replacement is intended, it is an unsupported stronger contract requiring explicit owner disposition, not an inferred adapter bug. + +No new probe was run: the source/log evidence resolves the cause category, while the unlogged product-disposer timestamp and unexecuted in-place installed probe remain explicit empirical limits. No exact cleanup timestamp, managed-mode runtime comparison or passing actual-unload result is invented. Follow-up authority belongs to the parent/owner; this memo does not resume 6.2/6.3. + +`I/verification.json` records exact-source identity, source-map references, report links/whitespace, unchanged frozen repository/prior-evidence bytes and known noncredential operator executable/config bytes. No new host/provider processes, listeners, sessions, grants or transient server secrets were created. Only this new memo is added; the original failed report and all existing tracked/untracked files are preserved. `.9` remains `in_progress / codex:oc-accept`; markers remain 14 accepted / 2 unchecked. Final bounded handoff: `I/result.md`. diff --git a/docs/reviews/opus-code-mode-architecture-wgv.md b/docs/reviews/opus-code-mode-architecture-wgv.md new file mode 100644 index 0000000..cad8eab --- /dev/null +++ b/docs/reviews/opus-code-mode-architecture-wgv.md @@ -0,0 +1,268 @@ +# Independent broader code-mode architecture assessment + +Review `opencode-beads-ecj`; investigation `opencode-beads-wgv`. Actual reviewer `claude-opus-5-5`, requested medium effort; resumed session `ef963df0-acdb-4479-af76-29a067b3975f`. This supersedes the six-tool architectural recommendation and narrows the prior READY to its valid output-schema/namespace mechanics; original evidence/verdict is preserved. + +Parent verified completed/is_error=false, actual modelUsage, no permission denials and all 58 frozen hashes. Baseline `072429f1a7723ade23e756a921924d9d0acacf31`; host `ba546ec1598a70db2570e9c6258028d955ba8f58`. Frozen packet/manifest and full result under `/opt/data/cache/scratch/opencode-codemode-opus-broader/`. No product implementation or live/model qualification was performed or authorized. + +## Opus assessment (verbatim) + +**Recommendation: yes, substantial reshaping is warranted.** In pinned OpenCode 2.1.1, native `shell` is not callable from code mode. Inside a code-mode program, Beads can only be reached through tools this plugin registers. So the current five continuity tools plus one query (Sol's "six-tool" proposal) would leave claim, close, create and status changes outside composition entirely. That is a legacy seam, not a coherent code-mode product. + +The target I recommend is one `beads` code-mode namespace of about 11 typed operations covering the daily work loop (reads and lifecycle writes), not a wrapper for every CLI verb. It comes with capability-aware context, a single executor-actor source, and the 30 human commands kept as entry points. + +**My earlier narrow READY missed this.** It accepted the claims at `code-mode-product-investigation.md:89` and `:195` that code can call `tools.shell`. That READY should be read as covering the output-schema and namespace mechanics only. + +## 1. Source-backed facts that change the design + +| # | Fact | Evidence | Consequence | +|---|---|---|---| +| F1 | Native `shell` is registered `options: { codemode: false }`, so it is direct-only. | `parity-sources/baseline211/source/packages/core/src/tool/plugin/shell.ts:195` (tag v2.1.1 → `ba546ec`, `baseline211/tag.json`). The other three cached copies agree. | Code mode can't run `bd`, tests or git. Sol `:89` ("five tools plus native shell are functionally sufficient… code can check completion and parse JSON") and `:195` (`tools.shell` as "the verified shell signature") are wrong for code mode. Any Beads operation without a plugin tool can't be composed. | +| F2 | Snapshot split: `codemode !== false` tools appear only inside `execute`. Direct definitions are just the `codemode:false` tools plus `execute`. If `execute` is wholly denied, code-mode tools vanish completely. | `core/src/tool.ts:236-262` | The current tools have no direct path. Sol `:29` ("direct tool consumers receive readable content") and parity doc `:126` ("direct and code-mode inventory") describe a path that doesn't exist under default registration. An agent that denies `execute` loses every typed Beads operation and needs the shell fallback. | +| F3 | Effective ID is `namespace_name`; the catalog path is `namespace.name`; `pinned` is supported; namespace descriptions stay visible in the catalog. | `tool/runtime.ts:278-281`, `codemode/tool.ts:191-208, 321-322` | A namespace with any number of operations keeps `beads_*` IDs, so the after-hook exclusion at `host.ts:349` still works. Catalog cost is the namespace summary plus selected signatures; everything else is reached through search. | +| F4 | The code-mode runtime has no timers, imports or filesystem. Pending calls are interrupted when execution ends. Concurrency comes from `Promise.allSettled`. The outer result is the returned value plus logs; `toolCalls` metadata keeps inner inputs. | `codemode/tool.ts:71-78, 95-150, 333-345` | Code mode is the place for state reads, joins and ordered Beads writes. Repository validation still runs in direct shell turns. Write text persists in trace metadata. | +| F5 | Every typed call runs `session.get` twice and the store guard twice (`bd version`, and possibly `bd context --json`) before the operation itself. | `continuity.ts:188-200`, `guard.ts:39,106` | Hydrating N issues one call at a time costs about 3–5 process spawns per issue. Reads should be batch-shaped: one guard check and one permission assertion per call. | +| F6 | Any typed write runs `invalidateAll()` and sets a "re-read the affected task" recovery cue (`host.ts:279, 288`). Any non-Beads tool error invalidates prime and injects a "A tool failed…" cue (`host.ts:350-355`). | `host.ts` | In code-mode-heavy work, every checkpoint or claim triggers a guard and prime re-run for every session at that location. The cue also tells the model to re-read its own just-acknowledged write. Every failing test command injects a generic cue. That is repeated reasoning and process work with no information gained. | +| F7 | Typed writes use a derived `opencode::` actor (`continuity.ts:34-41`). Guidance and the skill tell the model to use "the actual executor actor… if unavailable, establish it" for shell writes (`guidance.ts:28`, `SKILL.md:45-48`). The model is never told the derived value. | as cited | One executor ends up with two identities: a typed claim as `opencode:build:…`, then shell heartbeats or notes under a model-chosen actor. That breaks the "keep the actor through updates" policy. | +| F8 | Always-on text: full `bd prime --full` (which can include memories) plus 7,758 B of guidance for primary agents and 4,421 B for task agents, on every eligible request. The task agent gets the executor section twice (prompt `guidance.ts:66` plus injected `:84`). The host guidance says "There is no native `bd`… Run `bd` through the `shell` tool" (`:36`). Commands say "no assumed MCP tool or typed tool" (`workflows.ts:99`). | as cited; byte counts from `bridge-results.json` | Once typed tools are active, the always-on guidance contradicts the real capability. The duplication costs bytes on every request. The context hook can see the request's tool set (`event.tools`, as shell's own hook does at `shell.ts:285`), so the capability statement can be made truthful per request. | +| F9 | The five tools have no output schema, so the executor fails, and the catalog shows `Promise`. | shape-review reproduction; `bridge-results.json` | Our defect, still unfixed and withheld. It is a prerequisite for everything below. | + +## 2. Alternatives considered (choosing one) + +| Shape | Verdict | +|---|---| +| **A. Five continuity tools + one query (Sol)** | Rejected as the target. Given F1, claim, close, create and status changes can't be composed. "Start authorized work → read back → checkpoint → close or hand off" has to be split across model turns, which defeats the point of code mode. | +| **B. One wrapper per CLI verb (~29)** | Rejected. Catalog and maintenance cost scale with verbs that are rare, administrative or human-directed. Code mode adds nothing to `init`, `rename-prefix` or `import`. | +| **C. Generic `beads.cli({args})` passthrough** | Rejected for daily use. The output can't be declared (`Promise`). The plugin can't tell writes from reads, so acknowledgement, invalidation and `uncertain` reporting become guesswork. The actor would have to be parsed out of arguments, which invites impersonation. Justified later only as a read-only, allow-listed `--json` escape hatch, and only if evaluation shows models need advanced or admin reads inside programs. Deferrable. | +| **D. Workflow macros (`beads.start`, `beads.handoff`)** | Rejected. They encode scope and acceptance judgment in plugin code. The one exception is folding a pure invariant into an operation: claim readback (D2 below). | +| **E. Typed daily-loop API in one namespace (recommended)** | About 11 operations, each with a discriminated, declared output and a fixed argv. Each exists because code mode gains something specific from it: fields for programmatic decisions, truthful per-operation outcomes, actor injection, or a single acknowledgement site. | + +## 3. Target design + +### Ownership layers + +| Layer | Owns | +|---|---| +| **Beads CLI** | Database, routing, consistency, atomic claim, ID generation, readiness semantics | +| **Host (OpenCode)** | Code-mode runtime, catalog and search, per-call permission, cancellation, compaction, child transport, trace storage | +| **Plugin primitives** | Fixed argv, guard and location checks, permission assertion, derived actor, output normalisation, completeness, acknowledgement and invalidation | +| **Model-authored JavaScript** | Filtering, joins, adjacency checks, projection, ordering of dependent writes, bounded concurrent reads | +| **Model or user judgment** | Relevance, scope, whether to claim, whether acceptance is met, closure authority, what to remember | + +### Proposed operations + +These are **proposed contracts, not available today.** Flags not marked "AGENTS.md-documented" are unverified on 1.3.0/1.3.1. + +**Reads** (no actor, no invalidation): +- `beads.find({view: "ready"|"list"|"search"|"blocked", parent?, assignee?, status?, labels?, text?, limit≤50})` + - Returns `{status:"acknowledged", view, records:[{id,title,status,priority,type,assignee}], complete}` or `failed`. + - Fetches `limit+1` so `complete` is exact for that view's own CLI filters. +- `beads.get({ids: string[] (≤25)})` + - Returns `{status:"acknowledged", issues:{[id]: issue}, failed:[{id,detail}]}`. + - One guard check and one assertion per call. Uses a multi-ID `bd show` if 1.3.0 supports it, otherwise sequential spawns inside the call. + - Per-ID failure is never relabelled as "missing". +- `beads.edges({id, direction:"down"|"up", types?})` + - Direct relations with `dependency_type`; capped after normalisation, so `complete` is exact. +- `beads.memories({search?})` / `beads.recall({key})` + - Current semantics: `found` / `missing` / `failed`, with empty-found distinct from missing. + +**Writes** (derived actor, sole acknowledgement; each call asserts its own canonical shell resource with `save: []`): +- `beads.claim({id})` + - `acknowledged` only when the CLI's returned record shows `assignee === actor`. + - `not-claimed` (with the returned assignee and status) when the CLI returned a record showing someone else. + - `uncertain` (with target) on any ambiguous failure. + - Claim uses `bd update --claim --json` (AGENTS.md-documented). +- `beads.note({id, text})` — append checkpoint, as today. +- `beads.update({id, set:{status?, priority?, addLabels?, removeLabels?}})` + - Notes replacement and description rewrites stay in shell as deliberate edits. +- `beads.close({id, reason})` — `bd close --reason` (AGENTS.md-documented); acknowledged from the returned status. +- `beads.create({title, type, priority, description?, acceptance?, design?, parent?, discoveredFrom?, labels?})` + - `--deps discovered-from:` is AGENTS.md-documented. + - An `uncertain` create says to search before retrying, because blind retry risks a duplicate. +- `beads.remember({key, text})` — as today: `acknowledged` / `landed-uncommitted` / `uncertain`. + +Every write result carries `{operation, status, target, actor}`. No operation takes an actor input. + +**Implementation shape:** keep the existing runner, guard, permission and acknowledgement pipeline from `continuity.ts`. Replace the `inputFor` / `operationArgs` / `acknowledge` ternaries with one table of about 11 entries: input JSON Schema, output JSON Schema, argv builder, normaliser, read/write kind. That is a table, not a framework. The plugin enforces branch invariants itself, because the host only checks JSON-ness for raw schemas. + +**Registration:** code-mode-only (default), namespace `beads` with a short description, none pinned at first. `oc-beads:status` reports whether typed operations are registered and whether `execute` is visible for the session's agent. Agents without `execute` follow the shell fallback. + +**Actor:** the derived host actor becomes the single executor identity for OpenCode sessions. +- It is rendered into injected context per session and agent ("Your Beads executor actor: `opencode:build:3f…`; use it for any shell write"). +- It is returned on every typed write. +- Child sessions get their own derived actor, which matches "the executing worker claims its own work". +- Owner decision D2. + +**Context:** +- Keep prime injection unchanged for now. +- Restructure guidance into: + - a short always-on core: authority, missing/failed/withheld context, honest persistence; + - a capability block chosen from the request's actual tool set ("`beads.*` available in code mode; use `search({namespace:"beads"})`", or "use `bd` via shell"); + - on-demand material: a new `resources/CODE_MODE.md`, and existing delegation, planning and review policy. +- Remove the duplicated task-agent sections. + +**Invalidation:** +- Refresh prime on `remember` (prime can contain memories) and on external events. +- Don't refresh on note, claim, close or update, unless measured prime content turns out to depend on issue state (unverified). +- Emit the recovery cue only for non-acknowledged outcomes and child returns, not for the model's own acknowledged writes or every ordinary tool error. +- References become operation-aware (`task:` for issue operations, `memory:` for memory operations). + +## 4. What stays, what changes, what goes + +| Mechanism | Disposition | +|---|---| +| `prime.ts` runner, abort, timeout | **Keep**. Optional: a stdout byte cap. | +| `guard.ts` migration and store guard | **Keep**; run once per batch read. | +| `continuity.ts` pipeline (location, external-dir, permission, abort, actor, sole acknowledgement) | **Keep the core**. **Replace** the five flat tools with the namespaced operation table and declared outputs (fixes F9). | +| `host.ts` lifecycle, disposal, compaction references, event observer | **Keep**. **Change** `rememberResult` invalidation and cues (F6) and the `execute.after` generic error cue. Status adds typed-registration and `execute` visibility. Production activation still gated on 1.1. | +| `context.ts` cache, audience, prime policy | **Keep the mechanism**. Its render step gets the capability block and actor line, so the cache key must include the agent when the actor is rendered. | +| `guidance.ts` | **Restructure** (F7, F8): remove the "no native `bd`" and "no typed tool" claims; deduplicate; move this repo's planning/review-ticket policy (`:32`) out of always-on product guidance into a resource (D4). | +| `workflows.ts` 30 commands | **Keep all names**: they're the human entry points, user overrides depend on them, and they cost nothing until invoked. **Rewrite the shared text** (`:99`) to be capability-neutral. Daily commands (ready, show, list, blocked, workflow, update, close, create, comments, dep) say "compose with `beads.*` in code mode when present". Admin commands keep shell plus `--help`. Nothing removed; no human writes JS. | +| `oc-beads` skill and resources | **Change**: add `CODE_MODE.md` (status handling, ordered writes, bounded reads, projection, decline/Stop semantics, pointer to catalog search instead of copied signatures). The link count rises 16 → 17, which needs the hard-coded `16` at `workflows.ts:63` updated. Upstream `.agents/skills/beads` untouched. | +| `agents.ts` task agents and permission asks | **Keep**. Prompt deduplicated; mentions the namespace. | +| `instructions.ts` duplicate-block minimiser | **Keep**. | +| Sol's six-tool and namespace proposal | **Replaced** by section 3. Its output-schema, namespace, completeness and no-retry findings carry over. | + +## 5. Representative workflows + +These are **proposed signatures, not qualified behaviour.** Each block is one `execute` program; the gaps between programs are model or user decisions. + +**A. Discovery and graph triage (read-only; no claim):** +```js +const f = await tools.beads.find({view: "ready", parent: "EPIC", limit: 20}); +if (f.status !== "acknowledged") return {failed: f.detail}; +const ids = f.records.map(r => r.id).slice(0, 8); // bounded fan-out +const edges = await Promise.allSettled(ids.map(id => tools.beads.edges({id, direction: "down", types: ["blocks"]}))); +return {complete: f.complete, candidates: f.records.slice(0, 8).map((r, i) => { + const e = edges[i]; + if (e.status === "rejected" || e.value.status !== "acknowledged") return {...r, blockers: "unknown"}; + const open = e.value.records.filter(d => d.status !== "closed").map(d => d.id); + return {...r, openBlockers: open, edgesComplete: e.value.complete}; +})}; +``` +The join and the "all direct blockers closed" check are computation in JavaScript. Choosing a candidate, and whether it is in scope, stays with the model or user. `unknown` is never turned into "unblocked". Ready status gives no dispatch authority. + +**B. Authorized execution:** +- **Program 1:** + ```js + const g = await tools.beads.get({ids: ["T"]}); const t = g.issues?.T; + if (!t || t.status === "closed") return {stop: "not actionable", g}; + const c = await tools.beads.claim({id: "T"}); + return {claim: c.status, actor: c.actor, assignee: c.issue?.assignee, acceptance: t.acceptance_criteria, notesTail: t.notes?.slice(-2000)}; + ``` + Anything other than `acknowledged` makes the model stop. +- **Direct shell turns:** implement and run validation (code mode can't do this). +- **Program 2:** + ```js + const n = await tools.beads.note({id: "T", text: ""}); + if (n.status !== "acknowledged") return {note: n}; + // Only when the model has judged acceptance met AND closure authority exists: + const x = await tools.beads.close({id: "T", reason: ""}); + return {note: n.status, close: x.status, detail: x.detail}; + ``` + A follow-up `create({..., discoveredFrom: "T"})` only when authorized. A rejected write throws and stops the dependent writes; don't catch it and continue. Under the accepted limitation, later independent calls still get their own permission check. + +**C. Selective recovery:** +- **Program 1:** + ```js + const [mine, mem] = await Promise.allSettled([ + tools.beads.find({view: "list", status: "in_progress", assignee: ACTOR, limit: 10}), + tools.beads.memories({search: "build"})]); + // return ids/titles and memory KEYS only + ``` + `ACTOR` is the rendered actor value. +- **Model:** picks the task and the relevant keys. +- **Program 2:** `get` the chosen issue plus `recall` the chosen keys concurrently; return an excerpt of the notes and the recalled values. + +## 6. Operational semantics + +- **Ordering:** dependent writes are sequential `await`s; there is no write batching, and batching never implies transactions, exactly-once execution or idempotency. Reads are batch-shaped (`get` takes IDs). Program fan-out is bounded by guidance. A small per-location spawn semaphore is an optional bounded-I/O addition, not a scheduler. +- **Authorization:** one assertion per operation with its canonical resource. Existing `bd *` grants apply, denies win, and there is no new saved grant. A three-write program can prompt three times under `ask`, and that is correct. +- **Uncertain outcomes:** the plugin never retries. It returns `uncertain` with the target, and the program or model reads back with `get` before considering another write. +- **Identity:** a single derived actor (D2). The permission agent is still the actual invocation agent. +- **Cancellation:** the combined abort signal stays as it is. Stop/Cancel is still unqualified (.6). +- **Not built:** no queue, poller, graph cache, or state engine. + +## 7. Context economy + +| Budget | Change | +|---|---| +| Always-on system | Capability-aware and deduplicated guidance; planning/review policy moved on demand. Prime unchanged. | +| Catalog | One namespace summary plus selected signatures under a heuristic 2,000-unit budget. Large unions get trimmed and reached via search. Measure before pinning anything. | +| Generated code | Model-authored; a small extra cost for one-off reads. For a single trivial read, plain shell may be cheaper. | +| Returned output | Projection in JavaScript. Tools return full typed records, since raw data stays in the runtime. | +| Trace storage | Inner inputs, including write text, are kept in `toolCalls`. Not replayed, but not ephemeral. | +| Process cost | Batch reads and targeted invalidation cut spawns (F5, F6). | + +Possible regressions: +- Models that ignore the catalog and use shell anyway. +- Agents with `execute` denied. +- Larger union signatures crowding the catalog. + +No token savings are claimed. The synthetic 18,432 → 905-byte figure remains fixture bytes only. + +Prime policy: measure prime bytes and the size of its memory section in .9. Any lighter prime is a later owner decision (D6), and depends on a supported CLI mode that I have not verified. + +## 8. Minimum coherent pre-release slice + +1. Operation table with declared outputs and matching input constraints (fixes F9). Namespace registration for the 11 operations. Keep the earlier detail fixes: uncertain results keep their target, and the original detail survives a notification failure. +2. Actor rendered into context and returned by writes. +3. Capability-conditional, deduplicated guidance, plus the `CODE_MODE.md` resource. Rewrite the shared command text. Update the link count. +4. Narrower invalidation and recovery cues; operation-aware references; status reports typed/`execute` visibility. +5. Tests: `continuity` (per-operation argv, normalisation, branches, `limit+1` completeness, claim `not-claimed` / `uncertain`, create-uncertain, no retry), `host` (registration, disposal, IDs, invalidation scope, actor rendering), `guidance` / `workflows` (no false capability claims). `bd-contract`: check every flag used on 1.3.0 and 1.3.1, and drop unsupported filters from the schemas rather than branching on version. + +**Independently deferrable:** +- `link` / `dep add` +- `heartbeat` +- description/notes edits +- read-only passthrough (C) +- prime reduction +- spawn semaphore +- pinning +- comments + +**Spec and tasks** (the parent maps these; I'm not creating a graph): +- `work-continuity` spec: the typed set becomes the daily-loop API. +- `workflow-access` spec: capability-neutral command text. +- Task 3.1 is checked against the five-operation scope, so the expansion needs either a new task or an explicit re-scope (parent/owner). +- 1.1 adds: namespace path, `execute`-denied visibility, write-operation permission resources. +- 6.2 / .9 adds workflows A–C on the installed host, using actual catalog signatures and recorded bytes. +- 4.2 / .6 adds Stop during a multi-write program, with no later dispatch. + +## 9. Owner decisions needed + +| # | Decision | My recommendation | +|---|---|---| +| D1 | Adopt the ~11-operation daily-loop API, including lifecycle writes, instead of the six-tool shape | Yes | +| D2 | Derived host actor is the canonical OpenCode executor identity, shown to the model, never model-supplied | Yes | +| D3 | Code-mode-only registration with shell fallback; no direct duplicates | Yes | +| D4 | Restructure always-on guidance; move repo-specific planning/review policy on demand | Yes | +| D5 | Narrower invalidation and cue policy | Yes | +| D6 | Prime policy unchanged until .9 measurement | Yes | +| D7 | Keep all 30 command names; their prompt text changes, and that is a compatibility-visible change | Yes | + +## 10. Evidence limits + +- Verified by reading source: F1–F8 and the cited lines. +- Not verified: + - 1.3.0 support for any proposed flag, multi-ID `show`, or the claim-conflict output shape; + - whether prime content depends on issue state; + - whether `execute` is enabled for every installed agent and provider; + - blob IDs for `shell.ts` against `tree.json` (I relied on `tag.json` and agreement between the four cached copies); + - any installed, model, interpreter or token behaviour. +- No probes were run. + +## Readiness + +**Ready for an owner design decision.** This is not code or installed-behaviour acceptance. + +**Blockers to implementing the design:** owner decisions D1–D3, and the 1.3.0 flag check for each operation (which may shrink the filter sets). + +**Remaining unknowns, not blockers:** claim-conflict CLI output, whether prime depends on issue state, catalog fit under the budget, and real model uptake. These are resolved in implementation tests and .9. + +## Parent verification and disposition + +The decision-changing F1/F2 findings were independently verified against the exact pinned Git-tree blob IDs after the review: `packages/core/src/tool/plugin/shell.ts:195` declares `codemode:false`, and `packages/core/src/tool.ts:236-262` excludes direct-only tools from code-mode inventory and omits code-mode exposure when execute is wholly denied. Both cached files match the pinned complete tree. The earlier claim that ordinary shell provides an in-code-mode CLI fallback was incorrect; shell remains a direct/model-turn fallback only. Sol report and narrow READY are historical and not broader design approval. + +The recommendation is ready for owner DESIGN decision, not accepted product implementation. The review itself is complete; investigation and remaining implementation/qualification remain open and paused. The parent recommends the coherent daily-loop direction rather than the six-tool patch. Before implementation contracts are finalized, verify supported CLI inputs/outputs, including claim/conflict evidence, multi-ID reads and flags. Do not silently weaken post-approval/pre-spawn guard, location, identity and abort rechecks while optimizing batched reads. Narrowing invalidation also requires confirming whether selected prime output depends on issue state. These are bounded correctness checks, not authority for a new infrastructure or test project. + +No task markers, source, assets, provider/runtime configuration, upstream posts, publication, release or activation changed. Proposed additional resources/commands/tool shapes and context policy require owner selection and active-spec/task reconciliation before implementation. diff --git a/docs/reviews/opus-code-mode-investigation-wgv.md b/docs/reviews/opus-code-mode-investigation-wgv.md new file mode 100644 index 0000000..7690054 --- /dev/null +++ b/docs/reviews/opus-code-mode-investigation-wgv.md @@ -0,0 +1,64 @@ +# Opus code-mode investigation review + +Review: `opencode-beads-ecj`; investigation: `opencode-beads-wgv`. + +Scope: frozen investigation report and proposed implementation direction, NOT implemented source, installed/model qualification, activation or release. + +- Baseline: `072429f1a7723ade23e756a921924d9d0acacf31`. +- Actual model: `claude-opus-5-5`; requested effort: medium. +- Reviewer session: `ef963df0-acdb-4479-af76-29a067b3975f`. +- Report SHA-256: `58af436a57730d4df396b9e38551a1c689efa848af7509c4fe1062f277db1826`. +- Parent verified all 14 frozen hashes and completed working-report identity. +- Result: `is_error=false`, `terminal_reason=completed`, no permission denials; actual modelUsage identifies Opus 5.5. +- Read-only tools: Read/Grep/Glob; no subagents spawned. +- Full execution record: `/opt/data/cache/scratch/opencode-codemode-opus-review/review.jsonl`; manifest alongside. + +## Independent verdict + +**Verdict: READY.** This covers the investigation and the implementation direction it recommends. It does not approve source code, installed behaviour, activation or release. + +## Standards findings +No material findings. + +- **Ownership and scope fit `AGENTS.md`.** The report proposes no upstream patch, no decline translator, no retry loop and no confinement or routing framework (`docs/code-mode-product-investigation.md:46`, `:199`, `:243`). Beads keeps routing and the database (`:27`). Write actors still come from the host's session and agent identity, and the model gets no actor argument (`:195`; matches `src/continuity.ts:34-41`, `:150`, `:201`). +- **Size is proportionate.** It adds one fixed-argv read operation with no generic runner and no writes (`:91-106`, `:236`). Schemas stay as raw JSON with branch checks inside the existing result construction, rather than a validation framework (`:83`). The deferral list is explicit (`:243`). +- **Namespace claim confirmed in pinned source.** `effectiveName` turns namespace + name into `beads_show` (`core/src/tool/runtime.ts:278-281`). `editor.namespace` exists (`core/src/tool.ts:171-178`), and code-mode paths become `beads.show` (`codemode/tool.ts:321-322`). So `host.ts`'s existing exclusion of `beads_*` events can stay as it is (`:28`, `:236`). + +## Spec findings +No blockers. Two low-severity points are worth fixing in the implementation dispatch rather than in another report revision: + +1. **Low: the query filter flags aren't backed by captured evidence.** `:95-97` and `:106` propose `parent` on ready/list, `status`, `assignee`, and `--direction`/`--type` on `dep list`. The packet has no saved help output for them; I found no `--parent`/`--direction` text in any packet file. `:258` says help was inspected, and `:236` makes a 1.3.0 check a condition of implementation. + - **Consequence:** the published `QueryInput` could advertise a filter that the 1.3.0 minimum rejects. + - **Smallest fix:** in the P1 acceptance, require recorded 1.3.0 and 1.3.1 help (or `bd-contract` runs) for every advertised filter. Drop any unsupported filter from the input schema instead of branching on version at runtime. +2. **Low: the `complete` flag for ready/list leaves an ambiguous edge.** `:106` treats a full page as conservatively incomplete. That is honest: it never claims completeness falsely. But a result of exactly `limit` records gets reported as incomplete even when nothing was left out. + - **Smallest fix:** ask the CLI for `limit+1`, return at most `limit`, and set `complete` from whether the extra record arrived. State that "complete" means complete for the CLI view's own default filters, not the whole tracker. + +The remaining spec checks passed: +- **Your accepted plain-decline decision is kept, not reopened.** Stop/Cancel stays separate and still unqualified, and the historical failure stays recorded as a failure (`:9`, `:199`, `:241`). +- **Persistence stays truthful.** Fulfilment and completion are kept distinct from durable success; `uncertain` writes carry their target; there is no automatic retry; a mutation that runs before an outer contract failure is called out (`:28`, `:43`, `:85`). +- **Proposed signatures are labelled as proposed, not available** (`:71`, `:98-101`, `:120`, `:125`). The search signature matches pinned source (`tool-runtime.ts:89-107`). +- **Status checks don't lose uncertainty.** The ordered-write sketch stops on any non-`acknowledged` checkpoint and keeps `commitIndeterminate`. The read sketches keep `rejected` separate from `failed` and `missing` (`:136-147`, `:183-190`). +- **Branch table matches current code.** Write failures produce only `uncertain`, reads produce `failed`, and recall-missing is decided only from the CLI's exit-1 output (`continuity.ts:206-214`). +- **Value of `query` is stated honestly.** It improves discoverability and reliability over structured shell; it doesn't uniquely make selective handling possible. No real savings are claimed (`:89`, `:203`, `:217`). +- **Bridge metrics match `bridge-results.json`:** 18,432 vs 905 bytes, 9 calls, 0 logs, guidance 7,758 / 4,421 bytes, and the as-built signature `Promise`. +- **The scope fits your broad objective.** All commands, the shared skill and shell fallback stay; typed tools cover the common read paths and the continuity primitives (`:108-116`). + +## Optional observations +- **Parent follow-up:** the parent's spec and tasks don't yet mention the six-tool namespaced direction; `candidate.diff` only covers the decline decision and the defect it doesn't accept. That is the parent's reconciliation step after acceptance, as `:245` says, not a defect in the report. +- `:83` could say the readable `content` keeps the `failed`/`uncertain` detail in the direct path. It's implied by "retaining status/reference/detail". +- The dependencies view caps after normalising, so its `complete` flag is exact. Saying so would make it clearer than the ready/list case. + +## Not verified +- Whether 1.3.0 or 1.3.1 supports the query flags, and what `dep list` actually returns. +- The count of 30 commands and 16 resource links (`:116`). +- That the cached source matches its Git blob IDs (I took the researcher's claim). +- Any installed host, model, interpreter, real token use, or cancellation behaviour. +- `host.ts` registration details beyond what the report cites. + +I didn't re-run either probe and relied on the reported results. I also didn't check the manifest hashes. + +## Parent disposition + +Review completed READY for the report and recommended direction. No blocking corrections; retain both low-severity implementation notes: record minimum/current CLI support for every advertised filter, dropping unsupported filters rather than adding runtime version branches; and consider a limit-plus-one lookahead with completeness explicitly scoped to the CLI view defaults. Neither note authorizes implementation or changes the frozen report. + +The owner-accepted upstream plain-decline limitation remains accepted; the output-schema defect is not waived. Investigation remains open pending parent/owner direction and normative implementation reconciliation. No product source, task marker, host/model run, upstream filing, publication, activation or release follows from this review. diff --git a/docs/reviews/opus-code-mode-plan-recheck-wgv.md b/docs/reviews/opus-code-mode-plan-recheck-wgv.md new file mode 100644 index 0000000..b8bd6fa --- /dev/null +++ b/docs/reviews/opus-code-mode-plan-recheck-wgv.md @@ -0,0 +1,50 @@ +# Opus corrected-plan delta recheck + +Review `opencode-beads-ecj`; planning `opencode-beads-wgv`; prospective source `opencode-beads-vuy.10`. + +Actual model `claude-opus-5-5`; requested medium effort; resumed session `ef963df0-acdb-4479-af76-29a067b3975f`. Parent verified completed/is_error=false, actual modelUsage, no permission denials, all20 frozen hashes and exact working-candidate identity. Packet, manifest, correction-only diff and stream are under `/opt/data/cache/scratch/opencode-codemode-opus-plan-recheck/`. Baseline `072429f1a7723ade23e756a921924d9d0acacf31`. + +The original REVISE in opus-code-mode-plan-review-wgv.md remains intact. This is a separate READY on the corrected plan, not implementation or installed/model acceptance. + +## Independent recheck (verbatim) + +**Verdict: READY** for the corrected plan. All eight findings (S1–S5, P1–P3) are resolved, and the corrections introduced no regressions. **Proportionality: yes.** The corrections bound the testing work without weakening any real guarantee: permissions, attribution, guard holds, cancellation and persistence honesty are all kept. The original REVISE stands unchanged as the historical verdict; this recheck is separate from it. + +## Spec findings (correctness) + +| Finding | Status | Evidence | +|---|---|---| +| **S1 — High** (argv order, `--sandbox`) | **Resolved** | The design rule (`design.md` corrected clause, diff :36) now keeps subcommand-first argv. `--sandbox` goes on every read and write; `--actor` goes on writes, after the operation's arguments and before any `--` terminator. Global flags never go before the subcommand or after `--`. The ledger rows (diff :51-61) match the frozen source exactly: `continuity.ts:70-74` plus the splice at `:154` produce `bd show --json --sandbox`, `bd memories --json --sandbox -- [search]`, `bd recall --json --sandbox`, `bd update --append-notes --json --actor --sandbox` and `bd remember --key --json --actor --sandbox -- `. The new rows (claim, update, close, create, find, edges) follow the same pattern. The new normative scenario "Canonical execution preserves permission and sync policy" (`work-continuity/spec.md`, diff :148-152) and the requirement clause (diff :140) state it. Task 7.2 (diff :193) carries it into implementation. | +| **S2 — Moderate** (throw vs. value) | **Resolved** | Diff :64 and the new scenario "Execution is refused before spawn" (diff :154-158): invalid input, missing identity, guard hold and location change stay thrown. There is no `executed:false` branch. Permission rejection and cancellation are unchanged. A started write that is interrupted keeps its recovery uncertainty even without a normal return. This matches accepted `continuity.ts:56-60, 179, 185, 201`. | +| **S3 — Moderate** (actor wording) | **Resolved** | `workflow-access/spec.md` (diff :170) and design (diff :74): the context actor and the typed-write actor both derive from the same actual request/invocation session and agent, never the session-default agent. Attribution grants no permission, and permission checks separately use the actual `context.agent`. Task 7.3 (diff :194) adds "no session-default substitution or mandatory delegation". | +| **S4 — Moderate** (1.3.0 evidence path) | **Resolved** | Task 7.1 (diff :192) and design (diff :87): under a future authorised assignment, `scripts/install-bd.sh` installs 1.3.0 and 1.3.1 into disposable directories, and the existing `scripts/bd-contract.ts` runs once per version. Extensions are limited to the touched shapes. Help and source count only as discovery, never as passing evidence. The ledger is a table in the SDK contract citing captured output. Nothing is authorised now, and the operator environment and the minimum version are untouched. Unsupported optional filters are omitted; a core operation that fails its contract stops for a parent decision. | +| **S5 — Low** (executor seam) | **Resolved** | Diff :89 and task 7.2 state plainly that no maintained executor seam exists. 7.2 reruns the two existing scratch probes once against the real candidate definitions and records the pin, commands and results in the SDK contract. Product tests assert that every definition declares `output` and that normalised fixtures satisfy their declared branch. No vendored runtime, new dependency or host replica. Installed confirmation stays with .2 and .9. | + +## Standards findings (proportionality) + +| Finding | Status | Evidence | +|---|---|---| +| **P1 — Moderate** (capability combinations) | **Resolved** | Task 7.3 (diff :194) and design (diff :93) now name four representative states: typed available; `execute` denied with shell available; neither available; guard hold. Actor isolation stays, and the capability cross-product is explicitly excluded. | +| **P2 — Low** (test multiplication) | **Resolved** | Task 7.2 and diff :91: one table-driven case set per operation. Shared negative tests are retargeted once, against a representative multi-spawn `get` and a representative write, plus only the necessary new partial/completeness/uncertain cases. Production still rechecks before every child spawn. The matrix and the upstream-owned tests are excluded. | +| **P3 — Low** (.6 overlap) | **Resolved** | Task 4.2 (diff :181), `execution-map.md` (diff :104), design (diff :83, :93), and the scoped `.6` record (`scoped-beads.json:227-228, 244`) all say the same thing. .6 consumes the 7.2/7.3 deterministic evidence and adds only its own Stop/handoff observation. Explicit cancellation must still prevent later dispatch. | + +**Regressions from the corrections:** none found. The two optional items the planner chose to resolve are consistent with the necessary contracts: +- `memories` keeps the accepted key→value map (diff :54). +- An uncertain claim returns its target and actor, and the caller runs `get` explicitly; claim no longer hides a second permission prompt (diff :69). + +Task IDs, markers, ownership and dependencies are unchanged in the files I read. + +## Optional notes (non-blocking) + +- **`create` title can be parsed as flags.** This predates the corrections and is outside the recheck scope. The `create` row (diff :60) passes `` as a free positional before the flags. IDs and keys can't start with `-` because of the token check, but a model-supplied title starting with `-` (for example `--assignee=x`) could be read as a flag. When 7.1 qualifies the exact argv for create, use a supported title flag or reject titles with a leading `-`. Search text already follows `--`. +- **"No per-command reapproval" wording.** In 7.1 (diff :192) this should be read as orchestration/tracker approval inside the scoped assignment, not as bypassing OpenCode or host permission prompts. Making that explicit would remove any doubt. + +## Scope + +This verdict covers the corrected plan only. It does not accept any source, installed behaviour or model behaviour, and it does not authorise implementation, CLI installation or contract runs. I didn't run anything. I read the correction diff, the corrected clauses, the frozen `continuity.ts` argv placement and the scoped Beads fields. + +## Parent disposition + +All S1-S5 and P1-P3 corrections are independently resolved; no additional plan revision/review is required for the two nonblocking notes. Carry these into the existing task7.1/7.2 implementation handoff: qualify a supported title flag or otherwise ensure flag-like create titles remain data, never CLI options; scoped orchestration authority never bypasses native host permission prompts. These are reminders within existing operation/argv qualification, not new tasks or runtime fixes. + +The review gate is complete. Source .10 remains blocked/unassigned/implementation_authorized=false; no source execution, CLI installation/contract runs, host/model qualification, publication, activation or release is authorized by this verdict. Planning record remains open for parent-controlled adoption/publication closeout. diff --git a/docs/reviews/opus-code-mode-plan-review-wgv.md b/docs/reviews/opus-code-mode-plan-review-wgv.md new file mode 100644 index 0000000..08e24aa --- /dev/null +++ b/docs/reviews/opus-code-mode-plan-review-wgv.md @@ -0,0 +1,106 @@ +# Opus review of reconciled code-mode plan + +Review `opencode-beads-ecj`; planning `opencode-beads-wgv`; prospective source owner `opencode-beads-vuy.10`. + +Actual model `claude-opus-5-5`, requested medium effort, resumed session `ef963df0-acdb-4479-af76-29a067b3975f`. Review scope: correctness, completeness AND proportionality of Astra-reconciled planning, not implementation or installed qualification. Parent verified completed/is_error=false, actual modelUsage, no permission denials, and all20 frozen hashes. Baseline `072429f1a7723ade23e756a921924d9d0acacf31`. Full execution and candidate manifest: `/opt/data/cache/scratch/opencode-codemode-opus-plan-review/`. + +## Independent verdict (verbatim) + +**Verdict: REVISE** — four bounded wording corrections, then the plan is ready for parent acceptance. **Proportionality: yes, apart from three wording fixes** (P1–P3 below). + +The eleven-operation direction is still the right product. It adds no generic runner, macros, queue or cache, and it doesn't pull in a full upstream data model. + +The corrections fix two problems: +- **A permission and sync regression:** the new argv ledger moves `--actor` in front of the subcommand and drops `--sandbox`. +- **Ambiguities an implementer would have to guess at:** whether pre-spawn refusals throw or return a value, the actor wording in the spec, the 1.3.0 evidence path, and an executor test seam the plan treats as existing when it doesn't. + +## Spec findings (correctness) + +**S1 — High. The argv ledger drops `--sandbox` and puts `--actor` before the subcommand.** +- **Where:** `design.md:121-126` writes every write as `bd --actor <derived> <sub> … --json`; `:112` makes the permission resource the rendering of that argv. Neither the specs nor the design mention `--sandbox` anywhere. +- **What's accepted today:** `continuity.ts:154` adds `--actor` and `--sandbox` after the subcommand. The accepted tests assert resources starting `bd update task-1 --append-notes` and `bd remember --key` (`continuity.test.ts:58, 72`). The skill documents the same subcommand-first form with `--sandbox`. +- **Consequence 1:** a user's selective rule such as `bd close *` (deny or ask) no longer matches `bd --actor … close …`. That is a permission-parity regression against existing user rules. +- **Consequence 2:** without `--sandbox`, Dolt auto-push is no longer suppressed, which breaks the no-automatic-sync policy. +- **Fix:** change the ledger column to "`bd <sub> <positionals> <flags> --actor <derived> --sandbox --json` (writes); reads append `--sandbox --json`; keep the accepted `--` terminator before free text where the current mapping uses it. The canonical resource is this exact subcommand-first rendering." +- **Also:** add one clause to the requirement at `work-continuity/spec.md:115`: "fixed argv keeps the accepted subcommand-first canonical form and `--sandbox`". +- **Test:** covered by the per-operation table test below; nothing extra. + +**S2 — Moderate. It's unclear whether pre-spawn refusals throw or return a value.** +- **Where:** `design.md:128` says "a pre-spawn refusal is `failed` with `executed:false`". In the same paragraph, permission rejection and cancellation propagate unchanged. +- **What's accepted today:** invalid input, missing identity, guard holds and location changes all throw (`continuity.ts:56-60, 179, 185, 201`; tests at `:78` and `:99`). +- **Consequence:** an implementer has to guess which refusals become values. Turning a hold into a returned value changes code-mode control flow: the program keeps running instead of being interrupted. It would also mean rewriting accepted tests. +- **Fix (smaller option):** "Invalid input, missing identity, guard hold and location change remain thrown pre-spawn refusals, as accepted; there is no `executed:false` result branch." If the owner does want the value branch, list exactly which refusals use it and keep permission and abort thrown. + +**S3 — Moderate. The spec's actor wording contradicts how the actor is derived.** +- **Where:** `workflow-access/spec.md:208` says the actor is the same "independently of permission context.agent". +- **The contradiction:** the actor is derived from the actual invocation session plus agent (`continuity.ts:34-41`; `design.md:136`). The vuy2 evidence shows the invoking agent can differ from the session default. +- **Consequence:** taken literally, an implementer could render a session-default actor in context that doesn't match the actor the tool actually writes with. +- **Fix:** "context and typed results expose the same actor derived from the actual request/invocation session and agent; attribution grants no permission, and permission checks separately use the actual `context.agent`." + +**S4 — Moderate. Task 7.1's evidence path can't establish what the minimum version needs.** +- **Where:** `tasks.md:46` says "existing cached source/help and, only under separately authorized source execution, disposable `bd-contract` cases". +- **The gap:** help text can't establish JSON shapes, claim-conflict evidence, the generated ID from create, close state, or whether prime depends on issue state. The risk is either "qualifying" 1.3.0 from help alone, or stalling with no defined rule. +- **Fix:** "JSON, claim, create, close and prime-dependence facts for 1.3.0 and 1.3.1 are established only by running the existing `scripts/bd-contract.ts`. Its binaries are installed into disposable directories via the existing `scripts/install-bd.sh`, under explicit authority. Help-only evidence leaves a contract unestablished: omit the optional filter, or stop for a core operation. The ledger is a table in the SDK contract that cites `bd-contract` output, not a second tool." + +**S5 — Low. The "existing pinned executor/catalog seam" isn't in the product tests.** +- **Where:** `tasks.md:47` says to extend it. The product tests import only `@opencode/plugin` types. The seam exists only as scratch probes (`opencode-codemode-shape-review/check-output-contract.ts`, `opencode-codemode-product-wgv/bridge-probe.ts`), which run against copied host files. +- **Consequence:** an implementer may vendor the host runtime into maintained tests, or skip the one check that catches the demonstrated undeclared-output failure. +- **Fix:** "Rerun the existing scratch executor/catalog probe against the exact .10 candidate's real definitions and record the result in the SDK contract. Product tests assert that every definition declares `output` and that each normalised fixture satisfies its declared branch. Installed catalog and executor confirmation stays with .2 and .9." + +Checks that passed: +- **Capability rendering:** source-supported; `event.tools` is on the context hook (`plugin/src/effect/session.ts:35`). +- **Batch partial failure:** per-ID failures; all failed returns `failed`. +- **Completeness:** true / false / unknown (`null`), and `limit+1` only where the CLI view supports a bounded query. +- **Uncertain outcomes:** uncertain claim and create keep their target; `not-claimed` only on evidence. +- **Prime refresh:** stays conservative until 7.1 settles prime dependence; acknowledged writes no longer trigger a redundant cue. +- **Settled boundaries:** the accepted plain-decline limitation and Stop/Cancel owned by .6. +- **Task 1.1:** already covers the namespace, catalog signatures and execute-denied checks (`tasks.md:12`). + +## Standards findings (proportionality and ownership) + +**P1 — Moderate.** `tasks.md:48` says "for each capability combination". That invites a cross-product of capabilities. Replace with "representative states: typed available; `execute` denied with shell available; neither available; guard hold". + +**P2 — Low.** `tasks.md:47`, "exercise all result branches … rechecks for every child spawn", is a correct implementation requirement but an unbounded test instruction. Add: "Tests: one table-driven case set per operation (argv, normalisation, declared branches). Shared boundary negatives run once against a representative multi-spawn `get` and a representative write. Retarget the 15 existing shared-boundary tests rather than multiplying them across operations." + +**P3 — Low.** `execution-map.md:34` gives .6 "affected identity/handoff/abort rechecks", which overlaps the deterministic abort checks in 7.2/7.3. Add: ".6 consumes the 7.2/7.3 deterministic results; it adds only its Stop/handoff observation." + +Optional, non-blocking: +- The claim readback fallback via "a separately permissioned show" (`design.md:132`) hides a second permission prompt inside one claim. Returning `uncertain` plus the target and letting the program call `get` is simpler. +- Define the `memories` result shape once (`design.md:119` says "records"; the accepted adapter returns a key→value map). +- The `.2 → .10` edge blocks .2's throwaway host probes, which don't need the adapter. Live-host authority gates them anyway. + +The ownership graph is otherwise coherent: one source owner (.10) for 7.1 → 7.2 → 7.3, and the qualification owners (.2, .6, .9) consume its outputs. + +## Verification outline + +| Category | Owner | Content | +|---|---|---| +| Per-operation argv and normalisation | 7.2 | One table entry per operation (about 11): canonical subcommand-first argv with `--sandbox`, the resource string, success normalisation, each declared branch from fixture CLI output. | +| Shared boundary failures | 7.2 | Retarget the existing tests once: permission identity and `save: []`, external directory, hold before and after approval, location change, abort during approval and after spawn, missing identity, no retry, notification error keeps the original detail, sole acknowledgement. Plus new: batch partial failure, complete/unknown, uncertain claim and create. | +| Native executor and catalog seam | 7.2 (scratch), .2 / .9 (installed) | One rerun of the scratch probe against the real definitions, recorded in the SDK contract. Product test only asserts declared outputs. | +| CLI ledger | 7.1 | Existing `bd-contract.ts`, extended only for new shapes (find views with limit, close, update fields, `dep list` direction/type, claim conflict, create ID, prime before/after an issue write). Run once on 1.3.0 and once on 1.3.1. | +| Guidance, context, package | 7.3 | The four capability states, actor isolation across agents and sessions, operation-aware references, conservative invalidation, 17 links installed, disposal. | +| Installed workflows and Stop | .9 / .6 | Workflows A–D on the actual host; one Stop during a composed write; actor agreement between typed and shell writes. Byte and process observations, with no token-savings claim. | +| Regression | 7.3 | Existing suite, typecheck, package smoke. | + +**Do not test downstream:** +- OpenCode's interpreter, JavaScript or Promise semantics. +- Catalog search ranking or renderer internals. +- The permission service's wildcard engine. +- Compaction, replay or scheduling. +- Beads readiness, routing or consistency. +- Plain-decline whole-step termination (owner-accepted). +- Cross-products of operations × versions × modes × permission outcomes. +- A host replica, or a vendored executor in product tests. + +## Scope + +This verdict covers the reconciled plan only. It does not approve implemented source or installed or model behaviour, and it does not authorise implementation. I didn't run anything. I read the plan, the frozen source and tests, `bd-contract.ts`, the scoped Beads, and the pinned host session-type cache. + +## Parent verification and disposition + +REVISE is retained. The broader eleven-operation architecture is judged proportionate; required corrections concern the actual plan/verification scope. Independently confirmed the canonical argv drift against src/continuity.ts:148-154, contradictory actor wording at workflow-access/spec.md:208, and task7.2/7.3 references to an existing product executor seam and every-capability-combination testing. Review finding IDs S1-S5 and P1-P3 are the authoritative correction references; the opening prose count does not enumerate them accurately. + +No product defect was introduced into running code by these planning errors: product source remains unchanged and typed activation withheld. Plan corrections and a bounded recheck are still pending; no source implementation or auto-correction dispatched from this review notification. Preserve all prior reports and accepted source task history. Proposed reuse of existing boundary tests and representative native/installed evidence must not weaken real guard, permission, actor, cancellation or truthful-persistence requirements. + +Minimum/current JSON/mutation/prime contract qualification requires observed supported CLI evidence in isolated repositories, not help alone. Tagged source/help remains useful for planning/discovery; do not create a parallel qualification tool or upstream test campaign. diff --git a/docs/reviews/opus-code-mode-source-recheck-vuy10.md b/docs/reviews/opus-code-mode-source-recheck-vuy10.md new file mode 100644 index 0000000..f85e76c --- /dev/null +++ b/docs/reviews/opus-code-mode-source-recheck-vuy10.md @@ -0,0 +1,45 @@ +# Opus corrected-source recheck — vuy.10 + +Review `opencode-beads-co7`; actual Claude Opus 5.5, requested medium, same reviewer session `aa15a6fe-0427-4c80-a763-9d64cb6d12cc`. Verified completed, `is_error=false`, no permission denials. Corrected 44-path manifest `/opt/data/cache/scratch/opencode-codemode-opus-source-recheck/manifest.json` matched working source after review. Only `src/host.ts` and `tests/host.test.ts` changed from the original reviewed source. Original REVISE remains in `opus-code-mode-source-vuy10.md`. Parent independently passed 191 tests, typecheck, packed-package validation and whitespace checks. + +This evidence-only report is outside the frozen manifest. READY is source review, not live qualification, release or activation. Optional observations remain nonblocking; no remediation is authorized by their inclusion. + +## Reviewer report (verbatim) + +**Verdict: READY.** The original medium finding is resolved, and the correction introduces no material regression. + +## Finding disposition + +The medium finding (later events overwriting an uncertain write's recovery state, `src/host.ts:299-302/365-366/392-394`) is **resolved**. Each part checked against the frozen files: + +- **Bounded retention.** `gaps` now holds a set of distinct unresolved details per session (`snapshot/src/host.ts:139,144-149`). It keeps at most 32 details plus one overflow line that says earlier details are retained. That is a set of strings, not an event log. +- **Reads stay out of the gap set.** Only write outcomes call `recordGap` (`:314`). Read failures and `complete === false` now set only the one-shot recovery cue (`:315`), and those results are also returned to the caller. A capped `find` or failed `get` can no longer displace a write gap. +- **Success doesn't erase uncertainty.** No success path removes an entry. An acknowledged write later to the same target leaves the earlier uncertain entry in place. +- **Interruption appends.** Execution or compaction stopping now adds a generic entry (`:408`) and no longer overwrites the specific ones. +- **Rendering.** Every request now shows the retained gaps together with the one-shot cue (`:368-371`). Only the cue is deleted after use. Compaction (`:380-381`) and status (`:276`) show the same set. An aborted write still reaches `rememberResult`, because the existing notify-before-throw path in `continuity.ts:361-365` is unchanged. +- **Cleanup.** Session deletion and move clear gaps (`:162`), and unload clears them all (`:179`). + +## Standards + +The change is small and matches the surrounding code: JSDoc comments on both helpers, the same Map/Set idiom used for `references`, and no new abstractions. Nothing in argv, normalizers, CLI handling or versions changed. + +## Spec + +The work-continuity scenarios at `spec.md:81-84`, `:108-111` and `:123-126` are now met in source. Compaction and recovery keep the operation, target, actor and uncertainty across later reads, later successes and interruptions. Nothing triggers persistence and nothing retries. Other sessions stay isolated. + +## Correction-caused regressions + +None are material. Two low, nonblocking observations: + +1. **Gap text is now in every request.** + - **Where:** `host.ts:313-314` builds each gap from `JSON.stringify(result.target)` plus `result.detail`. + - **Trigger:** an uncertain `create`. Its target object carries description, acceptance and design (each up to 64k characters), and its detail can include CLI stderr and stdout. + - **Consequence:** these gaps are now rendered on every request for the rest of the session, not just at compaction or status. Before, the cue was shown once. A large uncertain create therefore enlarges every later request. + - **Fix if wanted:** cap the length of each rendered detail, or render create targets as title/type only. +2. **Gaps never clear within a session.** This is the intended conservative trade-off: entries are bounded and removed when the session is moved or deleted. + +## Evidence + +- **What I inspected:** `correction.patch` (both `src/host.ts` and `tests/host.test.ts` hunks), plus the frozen `snapshot/src/host.ts` lines covering the gap helpers, `forgetSession`, unload, `rememberResult`, the context and compaction hooks and the event subscriber. The new test walks the required sequence: started-then-aborted note, landed-uncommitted remember from a second actor, capped find, failed get, later acknowledged note, interrupt, compaction, two recovery requests, no-retry counts, another session, and the moved-session cleanup. +- **What I relied on without re-verifying:** the parent's report that only these two files changed, and their runs. Those were `bun test` (191 pass / 0 fail), typecheck (exit 0), packed-package smoke (exit 0, archive `e9525321…`) and `git diff --check` (exit 0). I ran nothing. +- **Still pending, not source blockers:** the live host, explicit Stop/handoff, installed and model workflows, and lifecycle gates. This verdict covers source only and does not accept or activate anything. diff --git a/docs/reviews/opus-code-mode-source-vuy10.md b/docs/reviews/opus-code-mode-source-vuy10.md new file mode 100644 index 0000000..13dd2ba --- /dev/null +++ b/docs/reviews/opus-code-mode-source-vuy10.md @@ -0,0 +1,80 @@ +# Opus source review — eleven-operation code-mode candidate + +Review: `opencode-beads-co7`. Actual reviewer: `claude:aa15a6fe`, Claude Opus 5.5, requested medium effort. Session `aa15a6fe-0427-4c80-a763-9d64cb6d12cc`. Completed with `is_error=false`, `terminal_reason=completed`, no permission denials or spawned subagents. + +Candidate: local delta over `f2d6617619e2c3a35e4aeadd5be0092259b9979c`; frozen 44-path manifest `/opt/data/cache/scratch/opencode-codemode-opus-source-review/manifest.json`. Parent verified all candidate hashes unchanged after review and independently inspected the cited host recovery overwrites. This report is evidence-only, outside the reviewed manifest. No correction, source acceptance, task markers, publication or activation follows from this verdict. + +## Reviewer report (verbatim) + +**Verdict: REVISE** for this source candidate. There is one medium defect, and the fix is small. Everything else I checked meets the standards and the spec. + +## Standards + +The code follows AGENTS.md: strict ESM, `import type`, `node:` imports, and indexed access handled. Plugin and Beads responsibilities stay separate: no compatibility code for upstream, and no claim of absolute store confinement. + +Nonblocking smells (none needs a change for acceptance): +- **Duplicated write list.** The list of write operations appears twice, at `src/continuity.ts:240` and `src/host.ts:287`. Adding a twelfth operation would mean editing both. Exporting one set would fix it. +- **Thin wrapper.** `OperationDefinition` (`continuity.ts:184-191,239-242`) only wraps four separate per-operation switches (`validate`, `argv`, `outputFor`, `normalize`). It adds a layer without bringing the per-operation logic together. +- **Hard-to-read one-liners.** `continuity.ts:153,157,159,209` are dense. Line 209 is hardest to review. +- **Hand-repeated rules.** The input JSON Schema and `validate()` restate the same rules by hand, e.g. the status enum at `:61`, `:128` and `:134`. That is acceptable given the rule against adding a validator framework, but the two copies can drift apart. +- **Dead check.** The `assignee !== null` check at `:206` can never matter, because `isIssue` (`:78`) already rejects a null assignee. + +## Spec + +**Medium: later events overwrite the recovery state of an uncertain write.** +- **Where:** `src/host.ts:299-302`, consumed at `:365-366`. The same pattern exists before this change at `:392-394`. +- **Trigger:** `gaps` and `recoveryCues` hold one value per session. `rememberResult` now also writes them for read `failed` results and for `complete === false`. The routine case is a ready/list `find` with more than 20 matches, since the default limit is 20. +- **Consequence:** suppose a claim or note comes back `uncertain`, or a started write is aborted. Its gap records the operation, target and actor. A capped `find`, a failed `get`/`recall`, or a later `session.execution.interrupted` event then replaces that gap. Compaction (`:366`) and the next recovery cue no longer carry the write's target and actor. +- **Why it matters:** for an aborted write, the recovery state is the only place this information lives, because the tool throws instead of returning a result. That breaks three spec scenarios: + - "Compaction carries known … uncertain status" (`specs/work-continuity/spec.md:81-84`). + - "Interrupted append … exposes the uncertainty" (`:108-111`). + - "result or recovery state preserves operation, target/query, actual actor" (`:123-126`). + - It also contradicts `CODE_MODE.md:64-66`. +- **Why it's new now:** code mode composes many reads per step, which makes this overwrite routine. The candidate is what widened the overwrite to reads. +- **Smallest fix:** + - Let only write outcomes (`uncertain` / `landed-uncommitted` / interrupted write) set or append to `gaps`. Read partials and failures are already returned to the caller in the result. + - Make the interrupted-event path append to an existing gap instead of replacing it. + - Add one host test: uncertain or aborted write, then a capped `find` and an interrupt event, then compaction still contains the write's target and actor. + +The rest of the spec checks out: +- **Fixed namespace:** exactly eleven operations in namespace `beads`, code-mode-only (`continuity.ts:55,279`). +- **Input contracts:** declared with `additionalProperties:false`, and `validate` rejects unknown keys. +- **Output contracts:** a separate output shape for each outcome, and only evidenced CLI results count as acknowledged (`:192-234`). +- **Fixed argv:** the subcommand comes first in the permission resource; `--sandbox`, plus `--actor` on writes, goes before `--` (`:287-290`). +- **Option-like text:** titles go through `--title`, and free text goes after `--` or as a flag value. +- **Identity:** the actor comes from session and agent (`:46-53`). The same actor is shown to the model (`context.ts:56`) and checked again before each spawn (`:345`). The permission identity separately uses `context.agent` (`:299`). +- **Permissions:** + - Uses the native `permission.assert` with `save: []`. + - Abort rejects the pending permission wait, so a late approval can never start the process (`:302-312`). + - Guard, location and actor are rechecked before every spawn, including each `get` (`:340-346`). +- **Outcomes:** + - `not-claimed` requires the observed structured conflict (`:253-260`). + - Malformed or failed writes report `uncertain`; there are no retries. + - `get` keeps per-ID failures; `complete` is `true`, `false` or `null` per view (`:209`). + - A started write that is aborted still notifies before rethrowing (`:361-365`). +- **Context and guidance:** + - Writes invalidate all cached context. + - Guidance depends on the request's actual capabilities (`guidance.ts:87-91`). + - Shell is direct-only. + - Commands total 29 workflows plus `status`, i.e. 30; the skill requires exactly 17 links. +- **No activation leakage:** tools register only with the test-only `continuityQualified === true` (`host.ts:308`). Status reports them as "withheld pending 1.1". + +**Nonblocking observation:** an update that removes an issue's *last* label may come back `uncertain` even though it landed. `normalize` requires the `labels` array (`continuity.ts:224`), and Beads might leave that field out when it's empty. I haven't confirmed this. The contract run only covered removing a label while another remained (`docs/opencode-sdk-contract.md:49`). One more disposable 1.3.1 contract case would settle it. + +## Evidence / remaining gates + +- **What I read:** + - `candidate.patch`, for the old versions of the host gap and cue lines. + - Frozen `src/{continuity,host,context,workflows,guidance}.ts` and `assets/oc-beads/resources/CODE_MODE.md`. + - Parts of `tests/host.test.ts` and `scripts/bd-contract.ts`. + - The `work-continuity` spec. + - The `opencode-sdk-contract.md` ledger, lines 20-99. +- **What I did not do:** I ran nothing. The rest of the spec and planning docs were only checked through targeted searches. +- **Evidence from the parent and worker, not re-verified by me:** + - `bun test`: 190 pass / 0 fail. + - Typecheck: exit 0. + - Packed package: exit 0, SHA `86f781ba…`. + - Docs tests: 14/14. + - Worker's 1.3.1 contract run: 63 checks passed (`minimum131-contract.log`). +- **Still pending, outside source review:** the live host test, explicit Stop/handoff, installed discovery and model workflows, and unload/reactivation (gates .2/.6/.9). Their pending state is not why I'm asking for revision. +- **Scope of this verdict:** it covers this source only. It does not accept or activate anything. diff --git a/docs/reviews/opus-content-recheck-sol.md b/docs/reviews/opus-content-recheck-sol.md new file mode 100644 index 0000000..a403846 --- /dev/null +++ b/docs/reviews/opus-content-recheck-sol.md @@ -0,0 +1,22 @@ +# Content correction recheck + +Original candidate: `981bfbecad58347d4af2fb4cf3c8625ddd56774c`; original report published at `dd1edc192dd087ac625c4c16728e0b8860855f2b`. This report covers the uncommitted Sol correction delta over dd1edc1, subsequently published together with this report. Reviewer `claude:oc-content`, existing session `77b30a82-e681-4cf8-a138-95199707f49a`, explicit Opus 5.5 medium. No runtime qualification or implementation beyond content is implied. + +# Delta recheck of review opencode-beads-8rl (content corrections on `dd1edc1`): **READY** + +**Reviewer:** claude:oc-content + +All four original findings (M1–M4) are fixed. The corrections introduced no new material defects. I checked the flags the new examples use against the 1.3.1 source. + +| Finding | Status | Evidence | +| --- | --- | --- | +| **M1** Actor examples and shell safety | Resolved | Every `--actor` in the corpus is now the quoted `'<executor-actor>'`. The one exception is the human gate step, which uses `'<actual-human-actor>'`. No `codex:` or `9c2e1` remains. In shell examples every placeholder is quoted (`SKILL.md:41-42,68,87-89`, `MOLECULES.md:31-49`, `WORKFLOWS.md:26`, `DEPENDENCIES.md:41-48`). `SKILL.md:45-48` says to use the actor from your assignment and to ask if none was given. It mentions `opencode:<suffix>` only as an illustration in prose. `ASYNC_GATES.md:42-48` now says the human runs the resolve command and that an agent must never supply a human actor. `SKILL.md:97-98` now explains that the examples include `--sandbox` because of project policy. | +| **M2** Dependency direction and readback | Resolved | `DEPENDENCIES.md:36-51` adds the rule "X needs Y means X depends on Y" and the `bd dep add '<dependent-id>' '<prerequisite-id>'` form. It adds a wrong/right Phase 1/Phase 2 example and readback through `bd blocked` and `bd dep list`. It notes that `dep list` defaults to showing dependencies, which matches `dep.go:254,905` and `ready.go:298`. | +| **M3** Recovery entry point | Resolved | `RESUMABILITY.md:61-76` adds a cold-start/compaction sequence: `bd list --status=in_progress --assignee='<executor-actor>' --json`, then `bd show '<issue-id>' --long --json`. It says to recover assigned work rather than take new work from `bd ready`, and that an empty list does not permit claiming another task. Then read the latest checkpoint and verify state before acting. The flags exist in the source (`list.go:402`, `show.go:300`). `SKILL.md:83-84` also adds the warning that reusing a memory key replaces it in place. | +| **M4** OpenSpec presented as universal | Resolved | `SKILL.md:17-18`, `BOUNDARIES.md:17` and `INTEGRATION_PATTERNS.md:10,37,64` now refer to "the project's specification system (for example OpenSpec, when used)". `INTEGRATION_PATTERNS.md:21-25` labels the planning/review Bead pattern as a project-policy example, not a universal Beads requirement. | + +**Regression tests:** The four new tests (`tests/workflows.test.ts:164-208`) check these properties directly: neutral quoted actors, no unquoted angle brackets in shell blocks, the dependency examples, recovery and memory-key wording, and the conditional specification wording. I did not run them. + +**No remaining correction is required.** The only thing I'd tidy is cosmetic: `ASYNC_GATES.md:50` is one unwrapped long line. + +**Limits:** This was a static, read-only recheck covering M1–M4 only. I didn't run tests or `bd`, and I have no runtime, packaging or model-behavior evidence. CLI checks are against the scratch tree whose CHANGELOG shows 1.3.1, not the installed binary. Tracking, publication and closure stay with the parent. diff --git a/docs/reviews/opus-content-review-981bfbe.md b/docs/reviews/opus-content-review-981bfbe.md new file mode 100644 index 0000000..073b927 --- /dev/null +++ b/docs/reviews/opus-content-review-981bfbe.md @@ -0,0 +1,119 @@ +# Independent guidance content review + +Candidate: `981bfbecad58347d4af2fb4cf3c8625ddd56774c`; review Bead `opencode-beads-8rl`; actor `claude:oc-content`; session `77b30a82-e681-4cf8-a138-95199707f49a`. Actual model `claude-opus-5-5`, medium effort explicitly requested. Completed without permission denials. Findings are not represented as corrected. + +# Content-quality review: `oc-beads` guidance corpus at `981bfbe` + +**Reviewer:** claude:oc-content · **Bead:** opencode-beads-8rl · **Verdict: REVISE** + +The corpus mostly keeps the useful depth of the Claude reference rather than shrinking it to filenames and generic warnings. Four content-only problems stand in the way of READY. Two of them are in the material agents will actually copy, and one is in the project's own core attribution rule. + +## Material issues (smallest fix for each) + +**M1. The actor examples will misattribute OpenCode work, and several aren't shell-safe.** +- **Where:** + - `SKILL.md:40,41,64,84`, `WORKFLOWS.md:26` and `ISSUE_CREATION.md:40` use `--actor=codex:<short-session>` with no quotes. + - `MOLECULES.md:32,42,49` use the literal `--actor=codex:9c2e1`. + - `ASYNC_GATES.md`, `DEPENDENCIES.md` and `INTEGRATION_PATTERNS.md` quote the same actor. So the quoting is inconsistent, and every example uses the `codex:` prefix. +- **What goes wrong:** + - This is an OpenCode skill. An OpenCode executor that copies the central example records itself as `codex:…`. That is exactly the misattribution `AGENTS.md` forbids. + - `codex:9c2e1` is the sample ID from this project's own policy. Copied literally, it attributes work to an actor that looks real. + - Without quotes, bash reads `<short-session>` as a redirection. The command fails on a missing file, or, if a file named `short-session` exists, it creates or truncates a file named `--sandbox`. Either way it doesn't run as intended. +- **Fix:** Use one quoted, host-neutral placeholder everywhere, e.g. `--actor='<executor-actor>'`. Add one sentence to `SKILL.md`: "use the actor named in your assignment (e.g. `opencode:<suffix>`); if none was given, ask rather than invent one or copy an example." Replace `codex:9c2e1` with that placeholder. + +**M2. Dependency direction is explained only for `bd create --deps`. Nothing covers adding edges afterwards or checking the result.** +- **Where:** + - `DEPENDENCIES.md:5-8` defers to `bd dep --help`, and lines 55-56/64 say "read the graph back" without saying how. + - No asset mentions `dep add`, `bd blocked`, `dep tree` or `dep list` (searched the whole corpus). + - Upstream has the material this replaces: `DEPENDENCIES.md:126-131` and 613-631 (`bd dep add <dependent> <prerequisite>`, a wrong/right example and a mnemonic), and `WORKFLOWS.md:147-256` (the "Phase 1 before Phase 2" inversion trap, walking backward from the goal, and checking with `bd blocked`). + - 1.3.1 source confirms the semantics: `dep.go:254,264` is `add [issue-id] [depends-on-id]`, and `ready.go:298` has `blocked`. +- **What goes wrong:** Inverting edges is the most common Beads graph mistake. An agent wiring up an existing epic has no worked mechanics and no way to check, so `bd ready` ends up offering dependents before their prerequisites. The parent's earlier rejection named "worked graph examples"; only the diamond and the create-time example came back. +- **Fix:** About 10–12 lines in `DEPENDENCIES.md`: + - "X needs Y → `bd dep add X Y`", with one wrong/right pair. + - The two-line phase trap. + - A check step: `bd blocked` / `bd dep list <id>` should show each task blocked by its prerequisites, never by its dependents. + - The backward goal walk can stay a 5-line sketch. + +**M3. The resume and recovery path never shows how to find the active work.** +- **Where:** + - `RESUMABILITY.md:59-64` says "Read the active issue and its recent notes", and `PATTERNS.md:18-23` is similar. + - No asset contains `in_progress`, `bd list`, `bd show`, `bd ready` or `bd close`. + - Upstream `SKILL.md:84` ("Recover after compaction: `bd list --status in_progress` → `bd show <id> --long`") and `PATTERNS.md:213-235` keep this even under their own ADR-0001. +- **What goes wrong:** After compaction or a cold start, which is exactly when an agent opens this skill, it has the checkpoint format but no entry point. It may then guess, re-run `bd ready` and claim new work instead of resuming its own. +- **Fix:** Add a 3–4 line recovery sequence to `RESUMABILITY.md`: + - List in-progress work, filtered to your actor or assignee where help supports it. + - `bd show <id>`. + - Read the latest appended checkpoint. + - Re-check state before acting. + + Optionally add one line pairing closure with `--reason` and a `RESOLUTION:` note in `PATTERNS.md`. Three core verb forms are not the broad flag catalog that ADR-0001 excludes. + +**M4. OpenSpec is presented as a universal Beads requirement in a public plugin.** +- **Where:** `SKILL.md:17-18` (stated outright), the `BOUNDARIES.md:17` row, the `INTEGRATION_PATTERNS.md:10` table, and the planning/review Bead model at lines 21-35. +- **What goes wrong:** Users without OpenSpec are told their intended behavior "is" OpenSpec, and may conclude they need it or create planning and review Beads by default. The inventory's INTEGRATION_PATTERNS mapping does say "map planning to OpenSpec", but the settled goal is to avoid presenting that as universal. +- **Fix:** Make it conditional, e.g. "the project's specification system (for example OpenSpec, when used)". Label the planning/review Bead pattern as one project policy example, not a Beads rule. + +## Missing guidance vs intentional adaptation + +**Intentional adaptations I accept** (they are not omissions): +- `TodoWrite` becomes an optional, host-owned, experimental todo. +- MCP tool examples are removed. +- Upstream's "overwrite notes" rule is replaced by append-only checkpoints, explicitly attributed at `WORKFLOWS.md:3`. +- The approve-every-handoff loops are removed. +- Stale v0.15 and Dolt-server repair recipes are dropped. +- The `.beads-wisp/` storage table and the static CLI catalog are dropped. +- The "epic if 5+ tasks" heuristic is dropped. +- Agent/slot: verified that 1.3.1 `cmd/bd` has no `agent` or `slot` command. `AGENTS.md:18-35` explains the limit honestly and offers no false equivalent. + +**Missing important guidance:** only M2 and M3. + +## Accuracy checks against the 1.3.1 source (all passed) + +- `--actor` and `--sandbox` are global flags (`main.go:875,879`). `--sandbox` only disables Dolt auto-push. +- `--append-notes` exists (`flags.go:34`). +- `bd heartbeat` runs as the current actor, and only the owner may heartbeat (`heartbeat.go`). +- Memory commands: `memories`, `recall` and `remember --key` exist; an existing key is updated in place (`memory.go:487`). +- The `--deps` direction text matches `create.go:936`. +- Gates: + - `create` accepts human, timer, `gh:run` and `gh:pr`, not `bead` (`gate.go:1283`). `check` accepts `bead` (`gate.go:1276`). So `ASYNC_GATES.md:21-26` is accurate. + - `resolve --reason` exists. +- Molecules: `bd mol wisp create --var` is valid (wisp is registered under `mol`, `wisp.go:1022`), as are `mol squash --summary`, `mol burn` and `mol show`. + +## Nonblocking improvements + +- **`--sandbox`:** Every example includes it, but `SKILL.md:90` says it applies only where auto-push is prohibited. Say once that the examples assume that policy. +- **Memory keys:** Add "recall before reusing a key; same key replaces in place". This protects other agents' knowledge. +- **2-week test:** Bring back upstream's one-line "Could I resume this after 2 weeks away?" heuristic in `BOUNDARIES.md`. It is a sharper decision aid than the table alone. +- **Human gate example:** In `ASYNC_GATES.md:43-44`, say that the human runs the resolve command; an agent must never type a human's actor. +- **Wisp cleanup:** Add one line: leftover wisps can be found with `bd mol wisp list` and should not pile up (upstream `CHEMISTRY_PATTERNS.md:192`). +- **TROUBLESHOOTING:** + - Name what the human can run, without running it. For example, a missing store means the owner may run `bd init`; `bd doctor` is a diagnostic, but whether it is read-only without a fix flag is unverified. + - Keep the cloud-synced-folder I/O hint. + - As written, "ask the owner" doesn't help the agent tell the owner what to do. +- **Ask first or create directly:** Within authority, keep the heuristic: fuzzy or strategic work → ask; possible duplicate → `bd search` first. +- **Repetition:** The authority, actor and no-automation caveats are restated in almost every file. That's tolerable for files loaded on demand, but `MOLECULES.md:57-66` repeats itself within the same file. + +## Completeness and quality by file group + +- **Main skill:** Lean, with strong policy: scope, atomic claims, `created_by`/`owner`/`assignee`, handoff and stop rules. It separates notes from memory well. The examples defect (M1) is the main problem. +- **Core resources:** + - `RESUMABILITY.md` (before/after example and template), `ISSUE_CREATION.md` (parser design-vs-acceptance example) and `BOUNDARIES.md` (transition example) carry real decision aids. + - `PATTERNS.md` keeps failed approaches and the rejected-hypothesis rationale. + - `DEPENDENCIES.md` keeps the meaning of each relation but lacks the mechanics and the check step (M2). + - `WORKFLOWS.md` has good checkpoint triggers but lost epic planning and unblocking. + - `TROUBLESHOOTING.md` is honest but thin. + - `CLI_REFERENCE.md` is appropriately minimal under the ADR. +- **Advanced resources:** + - `ASYNC_GATES.md` is the strongest file and accurate. + - `MOLECULES.md` and `CHEMISTRY_PATTERNS.md` are adequate, with a decision matrix and squash/burn lifecycle. + - `AGENTS.md` is honest. + - `STATIC_DATA.md` and `WORKTREES.md` are short but enough. +- **Attribution:** Every file has a commit-pinned upstream link, and TROUBLESHOOTING is correctly labelled a replacement. + +## Evidence limits + +- **Static review only:** read-only, no live `bd` help, no host or runtime run, no model-behavior evidence. +- **Source identity:** I checked CLI syntax against `/opt/data/cache/scratch/parity-sources/beads/cmd/bd`, whose CHANGELOG top entry is 1.3.1. I did not confirm it is the exact tagged commit or the installed binary. +- **Asset claims of "installed help confirms…"** were checked against that source, not re-run. +- **No delivery claim:** Nothing here shows the files are discoverable at runtime or improve agent behavior. +- **Tracking:** I made no `bd` calls and wrote no files. Tracking updates on opencode-beads-8rl are left to the parent. diff --git a/docs/reviews/opus-epic-map-review-vuy.md b/docs/reviews/opus-epic-map-review-vuy.md new file mode 100644 index 0000000..b6e2d14 --- /dev/null +++ b/docs/reviews/opus-epic-map-review-vuy.md @@ -0,0 +1,36 @@ +# Epic map cross-check — opencode-beads-vuy + +Planning baseline: `84ab18e76be43f9a733bd799c5a506fed03ce7f6`. Actual model verified: `claude-opus-5-5`; medium effort explicitly requested. Review is read-only and covers the created records before publication, not runtime qualification. + +**READY.** I found no material mapping defects and no corrections are needed. + +## Verdict on each check + +- **13 tasks, each owned exactly once.** The `metadata.task_ids` in the parent readback matches the execution map and `task_owners`: .1→1.2, .2→1.1, .3→2.1, .4→3.1/3.2, .5→2.2/3.3, .6→4.1/4.2, .7→5.1, .8→6.1, .9→6.2/6.3. That's 13 IDs with no duplicates or gaps. +- **17 requirements covered.** The `requirement_coverage` in the check results matches the reconciliation table at `reconciliation.md:39-55` row by row, with the same task IDs. The owners follow correctly from the task owners. +- **Dependency order and cycles.** The `blocks` edges in the readback are exactly .2→.1; .4→.1; .5→.1/.3/.4; .6→.3/.4/.5; .8→.5/.7; .9→.2/.5/.6/.8. These match the map. There's also .8→.6 as `related` (readback line 1011), not a blocking edge. The graph orders cleanly as {.1,.3,.7} → {.2,.4} → .5 → {.6,.8} → .9, so there are no cycles. Every prerequisite in tasks.md is satisfied directly or through another child. +- **The live 2.0.32 host prerequisite blocks only live work.** .3, .7 and .1 have no edges at all. .4 and .5 have no edge to .2. Their phase boundaries say they deliver source and fixtures only, and that the 1.1 activation step and the 6.2 end-to-end acceptance remain open. Nothing claims a closed source child means the feature is live. +- **The .6 partial phase is explicit in the records themselves, not just the map:** + - .6 description, design, acceptance and metadata all say: "owns ALL of 4.2 and stays open until live checks pass", in the order 4.1, then the 4.2 fixtures, then the 4.2 live checks after 1.1. + - The `deterministic_handoff` metadata records `state: not_started`, `live_prerequisite: opencode-beads-vuy.2` and `completion_requires_live: true`. + - The .6 notes require the tested source revision, the fixture commands and results, and an explicit list of pending live checks. The parent verifies these before dispatching .8. Relevant source edits invalidate the evidence. +- **.8 waits on the .6 evidence without waiting for .6 to close.** Its description and acceptance say to consume the passing 4.2 fixture evidence and "Do NOT wait for that child to close or for 1.1/4.2 live checks." It also says to record the tested revision and invalidate the evidence if relevant source changes. +- **.9 has real blocking edges** on .2, .5, .6 and .8. Because .6 only closes after its live checks, .9 correctly waits for all of 4.2. +- **Nothing is marked done early or dispatched automatically.** All 10 records are `blocked`, have `implementation_authorized=false` and carry the `execution-not-authorized` label. The only `assignee` in the readback belongs to d9n (`Snarkimedes`), which is the planning record, not one of the new ones. Every description says "dependency-ready never means authorized". The handoff state is not_started with no evidence, and all 13 OpenSpec task checkboxes are still unchecked. +- **Splitting into nine children is reasonable.** The groupings (continuity, package, flow, acceptance) follow the order the tasks have to run in. I see no harmful fragmentation. + +## Minor nits (none block) + +1. **The handoff state is stored twice.** The `deterministic_handoff` JSON is copied into both the .6 and .8 metadata, so the two copies could drift apart. Treat .6 as the single source of truth and have .8 point to it, or at least update both together. +2. **Graph readiness for .8 doesn't reflect the .6 gate.** Once .5 and .7 close, .8 will look ready even if the .6 evidence hasn't been verified. The blocked status and the notes cover this, but the parent shouldn't move .8 to open just because the graph says it's ready. +3. **One redundant edge.** .9→.5 is already implied through .6 and .8. It's harmless and makes the 2.2 activation dependency explicit. + +## Evidence limitations + +- I read the parent readback for .6, .8 and .9 in full. For the other children I only scanned status, `implementation_authorized`, task IDs and dependencies. I relied on the results file for their phase boundaries. +- I didn't check that every child's `requirement_contributions` matches the coverage table, apart from .6, .8 and .9. +- I didn't open the records snapshot separately from the readback. +- The lint and validation passes are the reported results in the check file; I didn't rerun them. +- No runtime or behaviour qualification is being claimed. + +I made no writes, ran no `bd` commands and launched no agents. You asked for findings in this response only, so I didn't create a plan file or call ExitPlanMode. diff --git a/docs/reviews/opus-flow-source-review-vuy6.md b/docs/reviews/opus-flow-source-review-vuy6.md new file mode 100644 index 0000000..d5ee6b5 --- /dev/null +++ b/docs/reviews/opus-flow-source-review-vuy6.md @@ -0,0 +1,67 @@ +# Opus scope-bounded flow source review + +Baseline: `db3b65ee86ad02d363baf9cbf24511a15d7bb644` plus the unchanged 10-path uncommitted candidate. +Reviewer: `claude:oc-flow-review`, Claude Opus 5.5, requested effort medium. +Session: `c76548d8-0fad-4278-b9b7-1f434284391e`; review record `opencode-beads-8ri`. +Result: completed, is_error=false, no permission denials; actual modelUsage confirms claude-opus-5-5. +Parent verified all reviewed candidate hashes unchanged before retaining this result. +Scope: task 4.1 source and 4.2 deterministic contribution only; no live qualification. + +**Verdict: READY** for the 4.1 source and the 4.2 deterministic contribution. This does not cover live qualification. Task 4.2 should stay unchecked and `.6` should stay open. + +I only read files; I ran no tests. I checked the candidate diff against the 4.1/4.2 text in `tasks.md:25-26`, `AGENTS.md:31-44`, the current `src/guidance.ts`, `src/host.ts:338-379`, and the pinned `baseline211/.../subagent.ts:234-268`. I did not recompute the manifest hashes, so the parent still needs to confirm the candidate is unchanged before accepting. + +### Standards findings +None of these block acceptance. + +- **`src/guidance.ts:84` and `src/guidance.ts:64`:** the task agent gets the executor/ownership section twice, once in its system prompt and again in the injected `beadsGuidance("task-agent")`. The copies are identical, so nothing contradicts. The prompt copy is still needed when prime fails, so I don't think it's worth changing. If you want to trim it, you could drop the section from the injected task-agent guidance only, but that's optional. +- **`tests/host.test.ts:410-411`:** the value import and the type import from `../src/continuity` are on separate lines. That's style only. +- **The parent's assertion fixes are not weakened:** + - `scripts/test-package.ts:331-339` replaces the 1,000-character limit and the old claim string with five specific positive checks. It also adds a negative check against the old "otherwise choose one highest-priority ready bead" fallback. + - `tests/docs.test.ts:189, 232-237` now checks the new README wording. + - Neither file has a placeholder or a check that always passes. I don't see a reason for a new character limit. + +### Spec findings +**4.1 (`src/guidance.ts:13-31, 51-66`): no material gaps.** +- **Exact assignment:** stops without substitution. +- **Single operation:** does not authorize a claim. The prompt also says "Readiness is not dispatch permission". +- **Batch/goal:** continues only within the scope and acceptance gates already granted. +- **Claims:** atomic, using an actor placeholder, with readback and a stop on failure. +- **Discovery:** does not add authority. +- **Read-only analysis:** makes no mutations. +- **Stops:** cover denial, cancellation, and a pending permission wait. Unresolved acceptance stays incomplete. +- **Closure:** when the parent owns closure, the bead is left open. +- **Delegation:** the old command-count rule is gone. Delegation is normal blocking delegation, with no background or nested automation. +- **Identity:** `created_by`, `owner` and `assignee` are read back separately. Handoffs are deliberate, and planning/review items are bounded with no ticket per retry. +- **Other features unchanged:** Luna, the step caps, cap `0` and the agent IDs are untouched (`src/agents.ts:11,48`; `tests/agents.test.ts:33-44`). +- **Instructions don't conflict:** the old one-bead/ready-selection text appears nowhere in `src/`. The README (lines 26-28) correctly says this is guidance, not enforcement. + +**Non-blocking observation:** `guidance.ts:47` says typed writes derive the actor from the session and agent. `guidance.ts:29` tells the agent to use the actor supplied by the invocation or handoff. Once typed continuity is turned on, a worker could claim under one actor and checkpoint under another. Activation is withheld today, so this belongs in the live 1.1/4.2 checks, not as a source fix now. + +**4.2 deterministic: the payloads match the pinned 2.1.1 source.** +- Failed and cancelled children are `status:"error"`, and their messages match `subagent.ts:247-251`. +- NO_TEXT is a completed result. Its `output`/`content`/`metadata` shape matches lines 252-264. +- No fixture invents a `cancelled` status for the after-hook. + +**The tests check what the docs say they check:** +- **No restart:** the plugin never calls `prompt` or `synthetic` and never writes to the store. +- **Child outcomes:** the after-hook only invalidates context and sets a recovery cue for the next request (`host.ts:350-355`). The child's cache stays isolated. +- **Late child result:** after interruption it keeps the "no final checkpoint" note (`host.ts:372`). Callbacks after unload do nothing. +- **Fork:** the fork starts without the parent's references (`host.ts:365`). +- **Permission wait:** a pending permission does no write during compaction. Aborting it rejects, and a later approval does not run the write. +- **Actors:** each checkpoint records the actor of the invocation that made it, and the permission check uses that invocation's agent. + +`src/host.ts` is unchanged, and I'm not asking for any repair of the plugin, transport or upstream Beads. + +**Is the evidence mapping honest? Yes.** The table in `docs/opencode-native-parity-assessment.md:99-109` names what is not proved for every row. That includes: no child is actually launched, mock notifications prove nothing about host suppression, the store is an in-memory fixture, and fork remapping and provider replay are not exercised. The doc keeps the earlier 159-pass/1-fail run and the package failure, then records the parent's 160/0 run as superseding them. It also discloses that there is no final-checkpoint guarantee and labels the earlier unpinned `.5` evidence as such. + +### Live work still not done +Live 4.2 has not been run, and it needs the separately authorized live 1.1 first. That means: +- no installed 2.1.1 host +- no real child or claim +- no permission UI or saved-grant checks +- no retained-history, fork or provider-mismatch checks +- no long-running transport observation +- no verification that the model actually follows the guidance + +Nothing in this candidate supports checking 4.2, closing `.6`, dispatching `.8`, activating anything, or releasing. Before accepting, the parent should record the actual model, session and verdict, and confirm the candidate is unchanged. diff --git a/docs/reviews/opus-integration-closeout-vuy8.md b/docs/reviews/opus-integration-closeout-vuy8.md new file mode 100644 index 0000000..921a4a3 --- /dev/null +++ b/docs/reviews/opus-integration-closeout-vuy8.md @@ -0,0 +1,40 @@ +# Opus closeout review — vuy.8 / task 6.1 + +## Identity and candidate + +- Reviewer: `claude:oc-integration-review`, actual model `claude-opus-5-5`; requested effort Medium. +- Reviewer session: `00503bba-2d4c-4fd9-a704-404d53276abc`. +- Review record: `opencode-beads-9hd`; implementation owner remains `opencode-beads-vuy.8`. +- Baseline: `c7d2129584365764b2ea65d50007244cebeb1f6a`; exact uncommitted candidate is the 12-path manifest at `/opt/data/cache/scratch/vuy8-opus-closeout/manifest.json`, with tracked delta `candidate.diff` and both new evidence documents explicitly included. +- Parent verified all 12 hashes unchanged after review. This report is an evidence-only addition, not a source correction or a new reviewed runtime candidate. +- Raw review stream: `/opt/data/cache/scratch/vuy8-opus-closeout/review.jsonl`; stderr retained beside it. + +## Verdict + +**READY. No required corrections.** + +The reviewer concluded that the frozen candidate supports task 6.1 local acceptance. Publication and closeout remain parent-authorized actions; live qualification remains open. This is the independent source/evidence verdict, separate from the earlier parent acceptance. + +## Standards + +**No material source or test issues.** The reviewer checked: + +- `src/guard.ts:36–51`: only the supported process-error exit/text/schema combination becomes absence, before marker access; other failures remain probe failures. No `where` fallback or store read. Positive/negative tests and the supplied actual CLI result support the distinction. +- `src/continuity.ts:100–108`: known numeric schema-1 metadata is removed from a copied map; actual string memory values are retained. Legacy and versioned empty/populated maps are accepted; unsupported numeric versions/non-string fields remain rejected. Production parsing, not merely test normalization, was corrected. +- `scripts/bd-contract.ts`: explicit sandbox/actor, real append acknowledgement plus readback, audit/claim identity, empty-write rejection, missing recall, remember/update/recall and exact versioned list/search payload checks. No invented commit fields, store injection, permission expansion or retry mechanism. + +## Spec + +**No material gaps or false claims against task 6.1.** Exact tool versions and supplied passing deterministic/package/CLI evidence are recorded. The original `.6` immutable review is not claimed to review the new guard/parser fixes; relevant current fixtures were rerun. Earlier failed phases are retained as history, while the final parent verdict is explicit. The nine-of-thirteen local task ledger and validator agree; tasks 1.1, 4.2, 6.2 and 6.3 remain unchecked. The two approved future live cases preserve task ownership and authority boundaries without new tasks or a host test framework. + +## Nonblocking observations and disposition + +1. The raw CLI memory envelope is not passed through the production acknowledgement path by the contract script itself; that link is covered by the unit regression. Reviewer explicitly stated this does not block 6.1. Retain as an optional refinement, not a new acceptance gate. +2. Supported/replaced administration was checked by help/source rather than destructive contract operations. Reviewer accepted this as consistent with the touched-semantics scope and disclosed limitation. No administrative mutation is added. +3. The old `execution-not-authorized` label is stale closeout bookkeeping. Current `implementation_authorized=false` intentionally means no further worker execution while publication is gated, not that the performed work lacked owner approval. Preserve historical grants and clarify current state in `.8` rather than re-enable execution. + +The raw result also contains an unrelated connector-availability notice. It has no bearing on this review and does not authorize connector setup or other work. + +## Verified review limits + +Parent verified `is_error=false`, `terminal_reason=completed`, actual `modelUsage` for `claude-opus-5-5`, no permission denials and no spawned subagents. Only Read/Grep/Glob were available. The reviewer did not execute tests, CLI commands or validators; it inspected source and supplied parent logs, including the final real-CLI log and execution index. It did not qualify installed OpenCode permissions, cancellation, lifecycle, model behavior or activation. No source remediation or repeated broad review is required by this verdict. diff --git a/docs/reviews/opus-planning-review-50919ae.md b/docs/reviews/opus-planning-review-50919ae.md new file mode 100644 index 0000000..2013c84 --- /dev/null +++ b/docs/reviews/opus-planning-review-50919ae.md @@ -0,0 +1,113 @@ +# Independent review of planning candidate `50919ae` (branch `openspec/improve-beads-workflow-parity`) + +Attribution: supplied independent Claude review of `50919ae0f6b192bb8c2773398685c550fdaad22f`; trace model `claude-opus-5-5`. The parent reports an explicit medium invocation; effort was not introspectable by the reviewer. Original verdict and review substance are retained below, including its source interpretation; the planning agent’s corrections are recorded separately in [reconciliation](../../openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/reconciliation.md). Only the unrelated connector-advertisement footer is omitted. + +## Verdict: **REVISE** + +There is one small blocking finding. It affects task ordering and a missing prerequisite, not the design. Everything else is ready to map. Once tasks 1.1 and 1.2 are re-sequenced and the host prerequisite is stated, I would accept the package for epic mapping without another full review. + +HEAD is `50919ae0f6b192bb8c2773398685c550fdaad22f`. I left the `AGENTS.md` change, `.codex/` and `.claude/settings.local.json` alone and did not count them as part of the candidate. I made no edits, ran no `bd` commands, wrote no plan file, ran no product tests and started no host or model. + +## Blocking finding + +### B1 — Medium-high: task 1.1, the first gate, can't run as ordered, and it needs a host that isn't installed + +**Evidence** +- **The gate order:** + - `tasks.md:7` (1.1) qualifies the 2.0.32 permission surfaces using fixtures in `tests/continuity.test.ts`. It runs "before adapter activation". + - `tasks.md:8` (1.2) changes the dependency to `>=2.0.32 <3` and only runs "After 1.1". + - `design.md:39` says 1.1 "must precede N1 activation". +- **The pinned typings don't have the API 1.1 tests:** + - `package.json` pins `devDependencies["@opencode/plugin"]` to `2.0.26` (the lockfile matches). + - `permission.assert` exists in `npm32/package/dist/promise/permission.d.ts:28` but not in the 2.0.26 typings. + - So 1.1's fixtures can't typecheck until 1.2 lands, and 1.2 is ordered after 1.1. +- **1.1 duplicates 3.1:** the adapter 1.1 would exercise is written in 3.1 (`tasks.md:17`, "After 1.1–1.2"). The two tasks list the same checks: invocation agent, allow/ask/reject, held access and cancellation after approval. +- **Fake-host fixtures can't prove host behavior:** the existing tests fake only a small slice of the host context (`docs/opencode-sdk-contract.md:38`). They can show the plugin sets `options.permission`. They can't show that the host's `Tool.snapshot` filters on it (`core/src/tool.ts:231`), and they can't check whether the host accepts `oc-beads:ready`. +- **The installed runtime is below the selected minimum:** + - The OpenCode on PATH is `@opencode/cli` **2.0.29** (`/opt/hermes/image-toolchain/npm/node_modules/@opencode/cli/package.json`). I read that file and did not run the binary. + - Live-host checks in 1.1, 4.2 ("installed-host checks") and 6.2 all need a host at 2.0.32 or later. + - `owner-decisions.md:25` keeps runtime upgrades separately gated, and no artifact declares this prerequisite. + +**Failure if mapped as-is** +- The epic's first ready bead would be blocked by its own successor. +- The 1.1 and 3.1 beads would duplicate each other. +- Every installed-host gate would hit an undeclared need for runtime-upgrade authority partway through execution. + +**Smallest correction (edit `tasks.md`; mirror it in the `reconciliation.md` requirement table)** +1. Make 1.2's dependency/typing edit the first step, or merge it into the start of 1.1. It still needs dependency-edit authority. +2. Restate 1.1 as host-contract qualification only. Use a minimal throwaway probe tool and command on an OpenCode 2.0.32+ host, and record the results in `docs/opencode-sdk-contract.md`. +3. Move the adapter-specific fixture items into 3.1. +4. Add an explicit prerequisite to 1.1, 4.2 and 6.2. Either provide an isolated, disposable OpenCode 2.0.32+ install that leaves the operator's 2.0.29 runtime untouched, or get explicit authority to upgrade the runtime. If neither exists, stop and report it. + +## Nonblocking recommendations + +1. **Command collisions — the gate is likely to trip; plan the fallback the spec already allows.** + - In the released supervisor, package plugins activate after the config plugins (`core/src/plugin/supervisor.ts:29,37`). The command editor only has `add`, which is a plain `Map.set` (`core/src/command.ts`). + - So a user command with the same name, present at load, can be detected through `ctx.command.list()` and skipped. One added later is overwritten when commands are rebuilt, and the package can't see that happen. + - `design.md:31` and `tasks.md:13` say "stop registration". The spec (`workflow-access/spec.md:57-59`) also allows "documented override behavior". + - Recommendation: scope the stop to the colliding name only. Document that rebuilds shadow later-added same-name overrides, and have `oc-beads:status` report it. + - Skills don't have this problem: the skill editor has `get` and `list`, so the `oc-beads` skill can be checked during every rebuild. + +2. **Skill resources: link all 16 files explicitly from `SKILL.md`.** + - `Skill.prepare` lists only the first 10 sorted files (`core/src/skill.ts:55-62`). With the ADR plus 15 resources, the sorted cutoff drops `PATTERNS`, `RESUMABILITY`, `STATIC_DATA`, `TROUBLESHOOTING`, `WORKFLOWS` and `WORKTREES`. + - The resources sit in the npm cache, outside the project. They are readable by default (`permissions.mdx:142-145`), but a user rule such as `external_directory: ask` or `deny` would prompt or block. + - Add one fixture and an acceptance case to 2.2 and 6.2 that reports an unreachable resource honestly. + +3. **Saved approvals for the typed tool: state the intended outcome in 1.1.** + - With `save: []`, an "always" reply works only once (`core/src/permission.ts:299-305`). Users with an `ask` rule will be prompted on every checkpoint. + - The built-in shell tool saves `bd *` for any `bd` command, because `bd` has no arity entry (`core/src/shell/parse.ts:375-380`). + - Saved rules apply to the whole project and are checked after deny rules (`permission.ts:176`). So an existing shell `bd *` grant will also authorize the typed operations. That matches what the shell does, and is not a broader grant. + - The plan's narrower choice is defensible. 1.1 should list these as expected results, and the guidance should say that "always" on a typed prompt is not saved. + +4. **Drift alerts need to fail the check.** GitHub notifies people about failed scheduled runs, not successful ones. Task 5.1 should say that unreviewed drift fails a separate job or step, the same way the existing review step does (`beads-contract.yml:57-65`). Drift and retrieval failure should produce distinct `::error::` messages. Otherwise "visible in check output" means nobody sees it. + +5. **Drop the 5.1 → 1.1 dependency.** The drift script doesn't depend on N1 qualification. Removing the edge lets 5.1 run in parallel. + +6. **Release branch strategy.** Stable releases are cut from `main` through PRs. Before 1.2 merges (raising the peer range to 2.0.32+), the epic should note where stable 1.0.x fixes will come from. This is a release decision, not a planning defect. + +## Prior findings F1–F5 + +| Finding | Status | What I checked | +| --- | --- | --- | +| F1 | **Resolved** | See detail below. | +| F2 | **Resolved in design** | `options.permission` is part of the released `Tool.Options`; `ToolContext` carries `agent`, `signal`, `messageID` and the call `id`; `configured` resolves `agentID ?? session.agent`; external access can be checked with the public `permission.assert({action: "external_directory"})`, which matches `file-access.ts:125-141`. One nuance remains, recommendation 3. | +| F3 | **Resolved** | Compaction is reference-only (`design.md:49`, `work-continuity/spec.md:76-80`, task 3.2 verifies no writes, waits or recursion). Some scenario wording still says "prepared", which is harmless. | +| F4 | **Resolved** | The alert reuses the existing `contents: read` check, with no issues and no issue-write permission. The upstream-review spec, `design.md:63`, task 5.1 and the artifact policy all agree. See recommendation 4. | +| F5 | **Resolved** | The analysis says "all three capability deltas" (line 181), its command and skill doc links use `843a6c5`, and no freeze-marker or tracker history remains in the tasks. | + +**F1 detail** +- The installed cache matches marketplace-main `4a67d0d` byte for byte, apart from a `.in_use` marker file. +- Release `c1c4b64` → main differs by exactly 16 edited files and 1 added file (`plugins/beads/BUILD.bazel`), with no deletions. That one addition is the whole 73 → 74 difference. +- Five guidance files now use `--append-notes` instead of `--notes`: skill `README.md`, `WORKFLOWS`, `PATTERNS`, `INTEGRATION_PATTERNS` and `ISSUE_CREATION`. +- `WORKFLOWS.md:368` still says "overwrite previous notes, not append". The inventory mapping flags this for the adaptation. +- On drift, the reviewed baseline is `marketplace_main.commit`, and only an explicit human disposition advances it. + +## Coverage and proportionality + +- **Requirements:** all 17 normative requirements (6 work-continuity, 8 workflow-access, 3 upstream-review) match the reconciliation table by title. I checked this by hand, not just through the validator. +- **Tasks:** 13 unique tasks. +- **Corpus:** all 74 marketplace-main members have a disposition, a group, a target and task IDs. The 29 commands map to `oc-beads:<stem>`, plus `oc-beads:status`. The 15 resources and the ADR map to `assets/oc-beads/`. The shared `beads` skill is preserved, and host-specific and support files are reference-only. +- **Native API claims:** these hold against released 2.0.32 source — tool filtering, invocation identity, the assertion schema, colon names in the command schema, absolute skill paths, and hooks awaited with no timeout. +- **Stop, compaction and late-child behavior:** no recursive same-session runner, no queue, no restarts from plugin events, no false persistence claims. Partial or late child results count as incomplete. +- **Implementation seams exist:** + - `src/host.ts` already uses context and compaction hooks plus an abortable event subscription. + - `runBd` in `src/prime.ts` is a bounded runner without abort support yet; 3.1 adds that. + - `src/guard.ts` already probes safely with `bd where --json`. +- **Proportionality:** good. There are no new services, stores, pollers or tracker work. Fixtures (6.1) are kept separate from model qualification (6.2). +- **Verdict on tasks:** apart from B1, coverage is complete and proportionate. + +## Validations I ran + +- `openspec validate improve-beads-workflow-parity --strict --no-interactive`: valid. +- `openspec validate --all --strict --no-interactive`: 1 passed, 0 failed. +- `python3 -I docs/validate-parity-planning.py /opt/data/cache/scratch/parity-sources`: all 6 PASS lines. +- `git diff --check`: clean. +- Read-only `diff`/`cmp` of the installed cache, marketplace-main and the release tree; inventory inspection with `python3 -I`; `grep`/`sed` reads of the pinned 2.0.32 source. + +## What was not tested + +- No OpenCode host was started, and no plugin was loaded at either 2.0.29 or 2.0.32. +- No slash-command dispatch, permission prompts, saved grants, compaction, child-stop races or fresh-session recovery were exercised. +- No `bd` or store access, no npm pack or install, no model sessions. + +All N1, namespace, collision, resource-reachability and behavioral claims above rest on pinned source reading only. They remain future acceptance gates. My model is Opus 5.5; I can't see the reasoning-effort setting, so "medium" is unverified. diff --git a/docs/reviews/opus-planning-review-510c332.md b/docs/reviews/opus-planning-review-510c332.md new file mode 100644 index 0000000..a30284a --- /dev/null +++ b/docs/reviews/opus-planning-review-510c332.md @@ -0,0 +1,103 @@ +# Independent 2.1.1 rebaseline review + +Candidate: `510c3320fd357d5530373f2aefcfaa213d1a26df`. Actual model verified from result: `claude-opus-5-5`; medium effort explicitly requested. First attempt failed before inference on OAuth refresh contention; bounded retry completed with no permission denials. This review does not grant implementation authority. + +# Review: OpenCode 2.1.1 rebaseline, candidate `510c332` + +## Verdict: **READY** + +I found no blocking findings. The delta is bounded and backed by source. It fits the existing 13 tasks and 9 children without new services, tasks or graph changes. The tracker records agree with the spec. No runtime acceptance is claimed anywhere. + +## Blocking findings + +None. + +## Nonblocking observations (wording or record-hygiene fixes the parent can make at publication) + +1. **The new experimental `todowrite` tool is not dispositioned.** The changed sources include it: `packages/core/src/plugin/internal.ts` (+`TodoTool.Plugin`), `config/normalize.ts`, `schema/src/config/experimental.ts` (`todo`, default false), and changelog lines 10/94. The memo does not mention it. This doesn't change any decision, because the existing disposition already covers TodoWrite advice ("reconcile TodoWrite advice with current prime", `docs/claude-code-integration-inventory.json:1483`). Smallest fix: one memo row saying it is opt-in, host-owned, and does not change the 2.1 guidance reconciliation. + +2. **The `session.compacted` row overstates what the evidence shows.** `session.compacted` appears zero times in both the 2.0.32 and the 2.1.1 `session-event.ts`. It only shows up in the 2.0.32 V1 web plugin docs (`released/packages/web/.../plugins.mdx`), and `session-legacy.ts` does not map it. So "retire" is correct, and `src/host.ts:127` already uses `session.compaction.ended`. But the cited legacy decoder doesn't show a 2.1 removal. The real 2.1 lifecycle change is that `compaction.started` no longer carries `recent` (`session-legacy.ts:27`, `compaction.ts` publish). Fix: reword the row. + +3. **Some task cross-references in the memo are slightly off.** + - Row 1 cites 1.2/3.2/**6.1**, but the 6.1 text is unchanged. This is harmless, since 6.1 runs the suites. + - Row 2 calls 6.2 "fixtures", but 6.2 is the live evaluation. + - The transport row cites 6.2, but the 6.2 text doesn't mention transport. + - The skills row cites 3.3, which is unchanged (reload is covered there already). + + Fix: wording only. + +4. **"Unpublished" labels are now stale.** `execution-map.md:1`, the reconciliation 2026-10-20 section, and the record descriptions ("unpublished… No invented publication SHA") all say the delta is unpublished. But `metadata.planning_commit` already says `510c332`. I checked the manifest `/opt/data/cache/scratch/baseline211-planning-manifest.json`: its root is `72906511…`, its file set exactly equals `git diff --name-only 907232a 510c332`, and all 15 blob hashes match `510c332`. So the content identity is sound and only the labels lag. Separately, the epic notes cite a "content digest `d71f1642…`" that I couldn't reproduce from the manifest. Fix: clarify or drop that digest when the labels are refreshed. + +5. **One line in `reconciliation.md:61` still reads as operative.** It says "Selected released OpenCode v2.0.32", under a heading that isn't marked historical. The verdict at line 1 and the bottom section do supersede it. Fix: prefix it with "Historical". The phrase "within V2" at `design.md:82` is generic and harmless. + +6. **`metadata.recent` is a little subtler than the memo says.** `history.ts:144–182` rebuilds `metadata.recent` *in memory* from the `keepID` range for new summaries too, so readers see `recent` in both shapes. The 3.2 fixture should assert that this text is never persisted or flattened back. That fits the existing no-write rule; no task change is needed. + +7. **One evidence gap on skills.** The contents of `FSUtil.ignoreGlobs` and `ignoredDirectories` aren't in the 63-file cache (`packages/util` wasn't fetched). The claim "adds ignore globs" is bounded, and explicit package registration avoids the scan path (`skill-scan.ts` filters children, not the root). Task 2.2's installed-base fixture is the right gate. Nothing to change. + +8. **`.6` has no hard dependency on `.2`, even though its 4.2 live part needs 1.1.** This predates the rebaseline and is intentional: the phase dependency `4.2-live←1.1` and `metadata.deterministic_handoff.live_prerequisite=opencode-beads-vuy.2` encode it, so `.6` can hand off deterministic evidence first. Not a new issue. + +## Source claims I checked against the pinned source + +- **Replay renames:** `state`/`providerState`/`providerResultState`/`resultState` became `native`. + - Tool `state: ToolState` and the `ToolState*` metadata are kept (`session-message.ts` AssistantTool; `session-legacy.ts:54` "A tool part's own `state` is its lifecycle status"). + - `SessionCompactionResult.native` matches (`plugin/src/promise/session.ts:38–44`). +- **`native.messages` replaces the summary:** it is replayed only on the same provider and model, and a mismatched or malformed window is skipped and the original transcript is used (`history.ts:27–45,135–139`; `to-llm-message.ts:294`). A malformed window "from a compaction hook" is skipped, which supports never populating it. +- **Tool replay rules** match `to-llm-message.ts:154–212`: + - call metadata needs the same model (plus no error, or executed and settled); + - hosted (provider-run) results need the same provider; + - local results need the same model and no error. +- **Compaction and forks:** + - `keepID` is written in `compaction.ts` (`metadata: { keepID, ...result.metadata }`). + - Forks remap `keepID` (`projector.ts` fork copy). + - `RESERVE_SHARE 0.15` / `RESERVE_MIN 16_000` match. + - Summary preparation now sees the whole conversation (`prepare(context, context.messages…)`). +- **Permissions:** + - Undefined metadata values are filtered (`permission.ts:198`). + - The code-mode inventory is cached by registry and visible names while executors stay fresh (`tool.ts:236–255`). + - `PermissionSaved.update` is added (`saved.ts`). +- **`session.list`:** + - Plugin host and adapter only add forwarding (`plugin/host.ts:549`, `promise/adapter.ts:596`). + - Child removal uses `store.list`, not the public paginated list (`session.ts:383`). + - `inbox.ts` and `bus.ts` changes are decode-only (legacy upgrade). +- **Counts:** the inventory shows 63 sources (31 unchanged / 29 modified / 3 added) and 14 declarations, with only the two `session.d.ts` files modified. Both match the memo. + +## Contract-delta completeness: **adequate** + +- All the requested surfaces are covered: replay names, `native`, replacement replay, provider/model fallback, `keepID`/`recent`/forks, lifecycle, reference-only compaction (no writes, no recursive waits, no injected result or window), permissions, identity, deny/save/external access, grant edits/revocation, skills/reload, cancellation and late children. +- The claims that declarations and implementation are unchanged are bounded to the endpoint files and are correct where I sampled. +- Released `session.list` is reclassified but not adopted. +- Admission `>=2.1.1 <2.2` is kept separate from exact unexecuted 2.1.1 qualification. Operator 2.0.29 stays untouched, and there's no blanket V2 promise. +- Settled product choices are not reopened. +- The only omission is the experimental `todowrite` tool (observation 1). + +## Tracker-map verdict: **consistent** + +Checked against the fresh readback `opencode-beads-211-opus-readback.json`: + +- **Ownership:** 13 unique task IDs, exactly equal to `tasks.md`. 17/17 requirement titles match the spec headings. +- **Graph:** 14 `blocks` edges, acyclic, identical to the worker snapshot and to `check-results.hard_dependencies`. `.8→.6` is `related` only. +- **Authority:** all ten records are `blocked`, have no assignee, and have `implementation_authorized=false`. +- **Gating:** + - `.6` owns 4.2, with `completion_requires_live: true`. + - `.8` requires parent-verified handoff evidence. + - `.9` is blocked by `.2`, `.5`, `.6` and `.8`. +- **Stale baseline:** none. Each record has one historical "supersedes the former 2.0.32 baseline" mention, and zero operative `>=2.0.32 <3`. +- **Embedded links:** the only pinned planning links point to `84ab18e`, and they are explicitly labelled historical. + +## Checks + +**Executed by me (all passed):** +- `openspec validate improve-beads-workflow-parity --strict --no-interactive` — valid. +- `openspec validate --all --strict --no-interactive` — 1 passed, 0 failed. +- `python3 docs/validate-parity-planning.py /opt/data/cache/scratch/parity-sources` — 7 PASS lines, including the 2.1.1 blob, tarball and declaration integrity. +- `git diff --check` on the worktree and on `907232a..510c332` — clean. +- Read-only git diffs and source greps. +- The read-only Python record, manifest and graph checks above. +- Protected setup: the hashes of `AGENTS.md` and `.codex/*` match `check-results`, and they are unchanged in the commit range. + +**Reported only, not re-run by me:** +- `bd lint` scoped lint. +- The live GitHub tag/tree API and npm registry retrieval (the validator re-verifies only the cached copies). +- The worker's "notes preserved / only eleven records updated" claim. + +**Not executed:** no `bd` commands, typecheck, package tests or installs, host runs, or model runs. No runtime or model evidence exists for 2.1.1. Live gates 1.1, 4.2 and 6.2 remain wholly unexecuted, and they need separately authorized disposable host and model access. diff --git a/docs/reviews/opus-planning-review-e74b507.md b/docs/reviews/opus-planning-review-e74b507.md new file mode 100644 index 0000000..169c8b5 --- /dev/null +++ b/docs/reviews/opus-planning-review-e74b507.md @@ -0,0 +1,146 @@ +# Independent planning review: candidate `e74b507` on `openspec/improve-beads-workflow-parity` + +HEAD is `e74b507fca555b42558ddd6307ce488b30c0e3a6`. The local `AGENTS.md`, `.claude/` and `.codex/` changes are not part of the candidate, and I left them alone. I made no edits, ran no `bd` commands, made no commits and installed nothing. + +## 1. Verdict: **REVISE** (one small, targeted revision; this is not an implementation authorization) + +The architecture analysis is sound and its key API claims match the pinned release source. One finding changes what the owner is actually comparing against. It needs a short evidence correction and a choice of upstream-review baseline before the packet goes to Matt. Everything else is material advice that doesn't block the decision. + +## 2. Findings, in priority order + +### F1 — Blocks owner review: the installed Claude plugin already differs from the pinned baseline on note-taking, which is the behavior that motivated this work + +- **Where:** `docs/claude-code-local-reference.md:28` only says "Other files differ". `docs/claude-code-integration-analysis.md:48,82` presents "WORKFLOWS … tells agents to overwrite notes" as an unresolved adaptation tension. `specs/upstream-review/spec.md:7` and `design.md:86` compare only against a "candidate stable release". +- **Evidence:** + - The installed marketplace checkout is `4a67d0d…`, committed 2026-10-17 on upstream main. It is newer than the v1.3.1 tag `c1c4b64…` (published 2026-09-30). Its `plugin.json` still says `1.3.0`. + - Diffing the installed files against `parity-sources/beads/plugins/beads`: the installed `WORKFLOWS.md`, `PATTERNS.md`, `INTEGRATION_PATTERNS.md`, `ISSUE_CREATION.md` and skill `README.md` all use `bd update … --append-notes`. The pinned release uses `--notes`, which overwrites. For example, `WORKFLOWS.md` lines 69, 79, 345 and 407. + - `--append-notes` already exists in the pinned CLI (`cmd/bd/cli_fast_test.go:469`). + - None of the candidate docs, specs or sources mention `append-notes`. +- **Consequences:** + 1. Matt's observation that Claude takes better notes was formed against guidance the packet never analyzed. Upstream has already resolved the overwrite tension in favor of appending. + 2. Claude marketplace installs follow upstream main, not releases. A drift monitor that only looks at stable releases misses exactly this kind of behavior change for weeks. + 3. The 73-file release inventory is still correct for its own pin, but it is the wrong reference for any "parity with what I see in Claude" comparison. +- **Smallest fix:** + - Add a short, characterized installed-vs-pinned diff to the local-reference doc: list the files, and record that the note write mode changed from replace to append. + - Update the WORKFLOWS/PATTERNS rows to recommend append-style checkpoints. + - Add one gate item: the parity reference is either the release or the marketplace main head. + - Let `upstream-review` optionally track the marketplace default-branch head as a second, informational baseline, or state explicitly that this is a known blind spot. + - No new inventory campaign is needed. + +### F2 — Material, nonblocking: the typed tool's visibility to agents isn't covered, so authorization equivalence is incomplete + +- **Where:** `design.md:33` and `docs/opencode-native-parity-assessment.md:86` only discuss call-time `permission.assert`. +- **Evidence:** + - Released `packages/core/src/tool.ts:231` hides a tool from an agent when `whollyDisabled(tool.options?.permission ?? name, rules)`. So a new `beads_*` tool is visible to any agent that wholly denies `shell`, unless the tool sets `options.permission`. + - `core/src/permission.ts:157-162` (`configured`) resolves `agent ?? session.agent`. The shell tool passes `agent: context.agent` and calls `access.authorizeExternal` before asserting (`core/src/tool/plugin/shell.ts:132-145`). + - The `save` field can turn an "always" reply into a broader saved rule. +- **Consequence:** a read-only or shell-denied agent could still get a write-capable Beads tool. Saved approvals could also become wider than intended. +- **Fix:** add these to the N1 mapping as required qualification items: + - set `options.permission` to the shell action; + - assert `action: "shell"` with canonical `bd …` resource strings and pass `context.agent`; + - narrow or omit `save`; + - check the guard hold before running. + + The plan's principle ("not default-allow") already implies this. It needs to be written down concretely. + +### F3 — Material, nonblocking: the "prepared checkpoint at compaction" state is effectively unreachable under N1's own design + +- **Where:** `design.md:33` (synchronous writes, no queue) versus `design.md:53`, `specs/work-continuity/spec.md:81-85` and `tasks.md:29` ("prepared-write compaction ordering"). +- **Evidence:** the hook dispatcher awaits callbacks one after another with no timeout (`core/src/plugin/hooks.ts:88-95`). The assessment itself says "usually the compaction hook only carries acknowledged references" (`assessment.md:84`). +- **Consequence:** the spec scenario and the test task invite someone to build a pending-write state just so it can be tested. That is the deferred queue the design rejects. +- **Fix:** restate M03 as "compaction adds acknowledged durable references and flags known unsaved state; no writes in the hook unless qualification shows an in-flight overlap". Drop "prepared-write" from 4.2 or make it conditional. + +### F4 — Material, nonblocking: the upstream-review alert sink conflicts with the repo's tracking rule + +- **Where:** `design.md:86` says "deduplicated GitHub review issue". The committed `AGENTS.md:166` says "Do NOT use external issue trackers". +- **Context:** the existing `beads-contract.yml` alerts by failing the check, with `permissions: contents: read`. +- **Consequence:** this adds `issues: write` to a scheduled workflow, which is a new security surface for the zizmor ruleset. It also creates a second tracking surface. +- **Fix:** either state that the GitHub issue is a notification only and a maintainer files the Beads work on disposition, or reuse the existing failing-check pattern with deduplication keyed by candidate commit. This is an engineering choice once the conflict is acknowledged. + +### F5 — Cosmetic + +- `analysis.md:177` says "both proposed capability deltas"; there are three. +- `analysis.md` lines 126 and 139 cite the development pin `8f7b012` for the command and skill docs, even though the assessment corrects provenance to `843a6c5`. +- `tasks.md:2.4` and `analysis.md:173` repeat history about the freeze marker and tracker that isn't needed. +- `design.md` Decision 4 contains the N2 and X-dispositions paragraph, which belongs elsewhere. + +### Checked and confirmed (no findings) + +| Claim | Source checked | +| --- | --- | +| `permission.assert` exists only in 2.0.32 | `npm32` `dist/promise/permission.d.ts:28`; absent from `npm26` and `minimum/` | +| Hooks are awaited in sequence with no timeout | `core/src/plugin/hooks.ts:88-95` | +| Command `add` is a map set, so a collision overwrites; `list` exists | `core/src/command.ts:61,79` | +| Code-mode and direct tool paths both go through `beforeExecute` | `core/src/tool.ts:240-243` | +| Upstream task agent repeats the ready cycle | `agents/task-agent.md:36-38` | +| 29 commands, 15 resources, `SessionStart`/`PreCompact` run plain `bd prime` | pinned `plugin.json` | + +The planning validator passes. No requirement silently fixes one-bead scope or a default command set. + +## 3. N1 versus the alternatives, and proportionality + +- **What the custom tool really buys:** + - safe argument passing for multi-line notes, avoiding shell quoting failures; + - a named tool in the model's tool list, which may be a stronger prompt to checkpoint than prose; + - precise cache invalidation. + + The "typed acknowledgement" over a shell exit code is modest, since the model already sees CLI success or failure. +- **What it costs:** + - raising the minimum host to 2.0.32, which was published two days before this review; + - permission-equivalence engineering (F2); + - a second surface that must track `bd` flags. `--append-notes` shows those flags carry meaning. +- **Is authorization equivalence achievable?** Probably, for a fixed and narrow set of operations, if F2 is done. It is not automatic. The plan correctly makes falling back to N0 the default if equivalence can't be demonstrated. +- **Proportionality:** there is no second datastore, daemon, reviewer fleet or automatic memory capture. The design is proportionate. +- **Framing bias:** the user-reported gap is unexplained, and F1 suggests a cheap guidance-level cause. Most of the plausible improvement comes from the resources and skill, recovery reads and append-style checkpoints. All of that works on N0. The packet presents N0 only as a "compatibility fallback". +- **Recommendation:** offer a staged option, **N0 now with an N1 seam**. The typed tool is adopted only if evaluation shows tool-call salience or quoting failures that guidance can't fix. This is advice for the owner's choice, not a defect. + +## 4. Owner decisions that are genuinely consequential + +These need Matt: + +1. The parity reference: pinned release or marketplace main (new, from F1). +2. The minimum host: 2.0.32 with N1, 2.0.26 with N0, or N0 now with an N1 seam. +3. Invocation-scope policy (E01/E05/N2), and whether batch continuation is the default. +4. Ownership and naming of the supplemental skill and commands, including whether to ever override the shared `beads` skill. +5. Corpus dispositions as groups, especially the deferred resources and admin commands. +6. The upstream alert sink, because of the AGENTS.md conflict (F4). + +These can be engineering decisions: + +- the M01–M10 mechanism mapping; +- X01, X02, X04, X05, X07, X08 and X09 (the plan already recommends against or defers them); +- collision and precedence handling; +- the exact permission mapping in F2, provided it is equivalent or falls back to N0; +- how the compaction hook is simplified (F3). + +X03 (nested delegation depth) and N2 background continuation stay with the owner only if selected. + +## 5. Checks run, limitations and disclosures + +**Checks run (all read-only):** +- `git rev-parse` / `git status`, and the diff stat from base `c5c29f2`. +- Read every candidate artifact, the analysis doc in full, and inventory summaries via `python3 -I`. +- Source call sites in `parity-sources/{released,minimum,npm26,npm32,beads}`, as listed in the findings. +- `diff -rq` of the installed plugin cache against the pinned plugin tree, plus `git log` on the shallow marketplace checkout. +- `python3 -I docs/validate-parity-planning.py …`: all 4 checks PASS. +- `openspec validate improve-beads-workflow-parity --strict`: valid. The OpenSpec CLI printed a notice that it collects anonymous usage stats, so the run may have sent telemetry. +- Read `beads-contract.yml`, `src/host.ts` and `src/agents.ts`. + +**Not done:** +- No product tests, since the candidate is docs-only. +- No runtime or host probes. +- I could not check commit ancestry in the shallow clone; the ordering in F1 rests on commit dates. + +**Installed Claude plugin context in this session:** +- It appeared. The SessionStart `bd prime` hook output was injected, including 4 persistent memories. +- The skill list showed the `beads:beads` skill and all 29 `beads:*` command entries. +- No compaction happened, so `PreCompact` was not observed. +- I had no agent tool, so `task-agent` registration couldn't be checked. + +**Model:** the session reports `claude-opus-5-5` (Opus 5.5). The reasoning-effort setting isn't visible to me, so "medium" is unverified. Nothing was substituted. + +**Tools and authorization:** no tools were soft-denied. Plan mode was active and I wrote no plan file. Exit status 0 from the validators is not proof the review is complete. + +**Unavailable connectors:** the claude.ai Excalidraw, Gmail, Google Calendar and Google Drive connectors need authorization in claude.ai connector settings. They weren't needed here. + +I've stopped here, as requested. The planning bead is not marked complete. \ No newline at end of file diff --git a/docs/reviews/opus-registration-delta-recheck-5ea915a.md b/docs/reviews/opus-registration-delta-recheck-5ea915a.md new file mode 100644 index 0000000..e0e4e5d --- /dev/null +++ b/docs/reviews/opus-registration-delta-recheck-5ea915a.md @@ -0,0 +1,70 @@ +# Opus registration delta recheck + +Candidate: `5ea915ac5b65852da70a1b4bd25457f3259f6b76`. +Reviewer: `claude:oc-registration`, Claude Opus 5.5, requested effort medium. +Session: `95b72364-ff32-423f-a63d-3e007f05768a`; review record `opencode-beads-cny`. +Original REVISE report: [registration resolution review](opus-registration-resolution-review-vuy5.md), retained unchanged. +Scope: original findings and correction-caused regressions only. Read-only source review; no tests, git commands or live qualification by reviewer. +Parent verified exact candidate HEAD and clean worktree before retaining this report. +Result: completed, is_error=false, no permission denials; modelUsage confirms claude-opus-5-5. + +## Verdict: **READY** for the corrected .5 **source** at `5ea915a` + +The branch ref `refs/heads/openspec/improve-beads-workflow-parity` reads `5ea915ac5b65852da70a1b4bd25457f3259f6b76`. The working-tree `src/host.ts` matches the committed diff. I can't run `git status`, so "clean" is the parent's claim, not mine. Every material finding from the original review is fixed in code and tests that I read myself. This verdict covers source only, not live qualification. The original REVISE report stays as it was. + +## How each original finding was handled + +**1. Initial collision withholding → fixed.** +- `src/host.ts:258-263`: the setup-time `command.list()` only produces a diagnostic, including when the lookup fails ("earlier entries follow native precedence and may be replaced"). +- `:264-269`: a single transform adds all definitions with no withholding. +- `COMMAND_REGISTRATION_HOLD` is gone. A search finds it only in the historical review. +- Tests (`tests/host.test.ts`, diff 1150-1186): a 30-command registry despite the initial collision. With callbacks in `[package, config]` order, the user config wins and its body is the one that runs. Removing it restores ours. Cleanup leaves `callbacks == [config]`, removes status, and stops the stale callback from adding anything. Dispatch passes literal text, files, agents, skills and delivery through unchanged. +- Separate test (diff 1188-1193): registration still happens after an initial lookup failure. + +**2. Hardcoded withheld status → fixed.** +- `src/host.ts:232-252`: status now reports: + - the actual `registration` state (`pending`/`registered`/`failed`) + - `lookup` (`available`/`failed`) + - effective names and descriptions, `mismatches` and `missing` + - an `ownership` disclosure covering owner identity, identical-description overrides, and that matching does not prove ownership +- A failed lookup reports "unknown" and never an invented empty list (`:235,243`); the test checks that `mismatches` is undefined in that case. +- The guard is still the config-only probe (`:227-231`). The status test (diff 1347-1401) asserts only `version` and `context --json` are called, and that prime is not called for missing, withheld or failed context. +- Status is reachable as a registered native command, and an in-flight status is cancelled on unload (diff 1403-1422). + +**3. Spec, design and task wording → fixed.** +- spec.md customization and discovery-collision scenarios (diff 247-267), design.md:31 (diff 201-203) and task 2.2 (diff 285) now state the native-precedence boundary. The old "withhold/stop path" sentences are gone. +- Pinned ordering (`supervisor.ts:99-103`, `internal.ts:254-259`) is kept separate from the unpinned released-cache suffix-reactivation evidence. That separation appears in the design, artifact policy and SDK contract. +- 2.2 and 3.3 are marked for source only; the text says live 1.1/6.2 remain unexecuted. + +**4. Abort guard and disposer → fixed.** +- `src/host.ts:265-268` checks abort before any add and pushes the registration into `registrations`. +- Command transform failure is diagnosed, then setup fails and releases earlier registrations (`:270-273`; test diff 1424-1436). + +**Skill gate defect → fixed.** +- `src/host.ts:198-203` no longer has an initial `skill.list()` gate; the per-rebuild `editor.list()` check (`src/workflows.ts:82-88`) is kept. +- Test (diff 1224-1261): `list` is never called. An earlier customization is preserved, then the packaged skill returns after removal, a later config skill wins, and the callback adds nothing after cleanup. + +**Zero-registration tests and package smoke → fixed.** +- `scripts/test-package.ts` (diff 414-481) calls the real callbacks and requires exactly 30 names. +- It runs every body and checks the forwarded fields, the installed guidance path, the status fields and one prompt per command. +- It derives the five owned disposers (agent, skill, command, two hooks) and checks all of them are disposed. +- The 16-link, resource-byte, export and missing-file checks are intact. + +**Stale docs and anchor → fixed.** README, artifact policy and SDK contract are rewritten, and the anchor is now `#tasks-2233-source-candidate-native-registration-and-diagnostics` on both sides. + +## Remaining blockers +None. + +**Non-blocking observation (outside the original findings):** status `skill` reports only loader failures plus effective IDs. A per-rebuild `oc-beads` skill collision only shows up as a warning, and the ownership disclosure is worded for commands only. A one-line skill ownership note would make the status honesty symmetric. This is optional and doesn't affect the verdict. + +## Standards vs spec +- **Standards:** no regressions caused by the correction. Permissions, attribution text, config-only probing, cleanup idempotence and user-file preservation are unchanged. There is no host replica or collision manager. +- **Spec:** reconciled as described under finding 3. + +## Still unexecuted live checks +- Installed 2.1.1 discovery and dispatch of the `oc-beads:*` commands. +- Whether user-config precedence and suffix reactivation behave this way in a real host. Pinned ordering is from source reading; the reactivation evidence comes from the unpinned cache. +- External-directory ask/deny for resource reads, and model use. +- Typed continuity activation (1.1) and the 6.2 acceptance checks. + +All of the above rests on mocks and source reading. I ran no tests and no git commands. diff --git a/docs/reviews/opus-registration-resolution-review-vuy5.md b/docs/reviews/opus-registration-resolution-review-vuy5.md new file mode 100644 index 0000000..4040ba7 --- /dev/null +++ b/docs/reviews/opus-registration-resolution-review-vuy5.md @@ -0,0 +1,71 @@ +# Opus review — vuy.5 registration resolution + +Reviewer: claude:oc-registration; Claude Opus5.5, requested effort medium. +Session: 95b72364-ff32-423f-a63d-3e007f05768a. Review record: opencode-beads-cny. +Baseline:38206ff428838e0290b6d2bb3db5ddc7b7beb9d4 plus unchanged13path uncommitted candidate. +Proposed resolution: /opt/data/cache/scratch/opencode-beads-vuy5-resolution-review.md; tracked diff SHA2569c95159946e8431e26651e3fa3be53c29a449cb58a305e716c21c07792df06fe (untracked workflows.ts separately hashed/read). +Outcome: completed, is_error=false, no permission denials. Initial pre-inference OAuth refresh failure is not a review verdict. No code or tests run by reviewer. Current implementation remains partial; this review does not accept source or live qualification. + +## Verdict: **REVISE** (small plan corrections; the boundary itself is sound) + +The approved intent holds up against host source: use the dedicated namespace, follow native precedence, and disclose that earlier plugins can collide. Two parts of step 2 are not mechanically sound as written, though, and the status contract needs its own step. None of this needs an architectural restart. READY on the corrected plan would not mean the source is complete or live-qualified. The candidate still registers 0 of 30 commands, and that is partial delivery. + +## Mechanically verified host ordering (pinned 2.1.1 unless noted) + +- **How commands are stored.** `core/src/command.ts:28-30,61`: the editor exposes only `add`, which is a map set, so the last write wins. +- **How rebuilds run.** `core/src/state.ts:162,178-181`: a rebuild replays every transform in insertion order (a Set), and `reload()` (`:250`) re-runs the same order. +- **Plugin order.** `plugin/supervisor.ts:99-103` orders plugins as `pre → packages → post`. `plugin/internal.ts:254-259` places `ConfigCommandPlugin` in `post`. `config/plugin/command.ts:44-47,71-72` registers one transform once; config edits only refresh `loaded.documents` and call `ctx.command.reload()`. + - So ordinary user config and file commands always replay after our transform, on the initial load and on every config rebuild. + - When the user removes their entry, our definition comes back naturally. +- **Skills behave the same way.** `skill.ts:106-107` (`add` is a map set) plus `ConfigSkillPlugin` in `post` means config skills also win natively. +- **Not pinned:** the registry's re-activation semantics. `core/src/plugin.ts` is missing from `baseline211`. The non-pinned `released/.../core/src/plugin.ts:108-139` shows a prefix diff that tears down and reloads the changed suffix in order. That keeps post after packages when a package is re-activated. Treat this as strong but unpinned evidence. Plan step 2's "verify actual host ordering" must cite it as such, or find pinned evidence. + +Calling earlier-plugin overwrites "native last-writer semantics" is correct. It is not an upstream defect. + +## Spec/plan findings + +1. **"Initial collision detection may withhold an affected name" doesn't work for user config and causes stale withholding.** + - Our setup runs during the package phase, before `ConfigCommandPlugin`'s post transform is loaded. On a suffix re-activation, that transform is torn down before we load. So the `ctx.command.list()` at `src/host.ts:256` can only see built-in (pre) and earlier-package names, which are exactly the cases the boundary hands to native precedence. + - A name withheld at setup never comes back if the earlier plugin later drops it. That contradicts the plan's own "removal restoring normal host registry". + - **Correction:** register all 30 unconditionally. Keep the setup-time list check only as a diagnostic, e.g. "earlier entry X will be replaced by package precedence". Do not withhold. +2. **The status contract is not in the plan.** `src/host.ts:249` hardcodes `commands: { state: "withheld", reason: COMMAND_REGISTRATION_HOLD }`. Once the hold is removed, that is false status, which task 3.3 makes a stop condition. The spec scenario "status identifies the collision and effective entry" (spec.md:172-175) can be met through public APIs: + - `command.list()` returns `{name, description}`. + - Any owned name whose effective description differs from our definition has been overridden by a higher-precedence entry. + - Disclose the limits: the owner isn't exposed, and an override with an identical description can't be detected. + - Add this to step 2/4 and delete `COMMAND_REGISTRATION_HOLD` (`src/workflows.ts:90-91`). +3. **The spec text needs exact wording replacements.** + - spec.md:66 ("withholds… if preservation cannot be established across rebuilds; documented override behavior does not authorize overwriting user commands"). + - design.md:31 ("stop that affected registration path…"). + - tasks.md:15 ("stop that affected registration path rather than overwrite it"). + - Replacement for each: user config and file commands are preserved by post-ordered native precedence; same-name conflicts with other plugins follow native order and are disclosed and diagnosed. Step 1 covers this; make the target sentences explicit so the reconciliation stays bounded. +4. **Keep the abort guard.** The new command transform should mirror the skill and agent transforms: `if (!abort.signal.aborted)` before any `add`, and push its registration into `registrations` so cleanup disposes it. + +## Candidate source defects affecting the plan + +- **Initial skill gate is redundant and blocks restoration.** `src/host.ts:200-213` checks `skill.list()` once at setup and, on a collision, never installs the transform. The per-rebuild `editor.list()` check (`src/workflows.ts:82-88`) already covers earlier entries. The setup-time gate only stops our skill from returning after the user removes their customization. This is minor, and it is the same pattern as finding 1. Either drop the initial gate or document it. +- **Tests and package smoke still treat zero registrations as success.** + - The package smoke script fails if any command is registered: `scripts/test-package.ts` (diff lines 224-226) checks `commandTransforms !== 0`, and its `disposed !== 4` count must become 5. + - `tests/host.test.ts` asserts `commandTransform).not.toHaveBeenCalled()`. + - The fake transform in the package smoke never invokes its callback, so it can't verify registration. Step 3 covers replacing these; flagging the exact sites. +- **The status command is unreachable while withheld.** `oc-beads:status` is defined but not registered, so the "native status" requirement is currently unmet. +- **Docs to replace in step 4:** + - README "withholds their registration" (diff lines 11 and 43). + - Artifact policy (diff line 64). + - SDK contract section "Unresolved source blocker" (diff line 86); the cross-link anchor `#tasks-2233-source-candidate-delivery-and-concrete-command-hold` will change. +- **No regressions found** in the touched code: + - Command bodies pass literal text, `files`, `agents`, `skills` and `delivery` straight through, with no template or shell evaluation (`src/workflows.ts:107-118`). + - Cleanup is awaitable and idempotent, and setup failures release earlier registrations. + - Status guard uses only the config-only probe. + - Package plumbing is fine: `files`, archive inventory and the 16-link smoke check. + - No permission waiver. + +## Smallest corrective plan + +1. Step 1: add the three exact spec/design/task replacements from finding 3, plus the status-scenario wording from finding 2. +2. Step 2: register all 30 commands without withholding; turn the setup-time list check into a diagnostic only; add the status override detection; add the abort guard; drop `COMMAND_REGISTRATION_HOLD`. +3. Step 3: add a fixture that replays transforms as `[package, config]` in order, covering: user config added later wins, removing it restores ours, and disposal removes only our transform. A small ordered-callback list is enough; this is not a host replica. Also test a status override report. +4. Step 2 evidence: record that registry suffix ordering comes from the unpinned `released` cache. + +## Limitations + +This review used only Read and Grep. Nothing was run, and there were no live host checks. The registry `activate` file is not in the pinned cache. Whether user config precedence holds in a live 2.1.1 install, how commands are discovered and invoked, and how external-directory permissions behave are all still unexecuted (tasks 1.1/6.2). diff --git a/docs/reviews/opus-source-collector-review-vuy7.md b/docs/reviews/opus-source-collector-review-vuy7.md new file mode 100644 index 0000000..4a4f15c --- /dev/null +++ b/docs/reviews/opus-source-collector-review-vuy7.md @@ -0,0 +1,39 @@ +# Opus source collector review + +Baseline: `a5d6b2bea779259ba9aa3175ab86fb2f4f2b862b` plus unchanged 10-path candidate. +Reviewer: `claude:oc-source-check`, Claude Opus 5.5, requested effort medium. +Session: `38ee7b26-db63-400c-bf94-d9f3e28119bc`; review record `opencode-beads-t1v`. +Actual modelUsage confirmed; completed, is_error=false, no permission denials. +Parent verified all reviewed path hashes unchanged before acceptance. +Source only; no workflow/CI activation or tagged-CLI qualification. + +Review done; writing up the verdict now. + +## READY (source only) + +I did a read-only review of `scripts/check-beads-integration.ts`, `tests/upstream-review.test.ts`, the upstream-review spec, task 5.1 and the policy appendix (`docs/beads-artifact-policy.md:91-107`). I ran no tests, network calls, edits or agents. The live run evidence is the parent's, not mine. + +### Standards findings +No material defects. + +- **Complete enumeration and SHA selection:** a tree only counts if its SHA matches the expected one and it reports `truncated === false` (`:65`). Every entry needs a valid path, a 40-hex SHA and a mode/type pair that agree (`:70-79`). The baseline's selected files must match the reviewed inventory exactly, in both count and blob IDs (`:164`). So a partial or empty retrieval can never show up as `unchanged`. If every candidate file disappears, that is reported as deletions (`changed`), not as no change. +- **Root matching:** prefixes end in a slash (`:3`), so `plugins/beads-extra` isn't pulled in. A test covers this (`:93-95`). +- **Renames:** a rename is only reported when the removed file's blob and mode are unique and there is exactly one matching added file (`:98-100`). Anything else stays a delete plus an add. The appendix documents this limit (`:99`). +- **Credentials:** transport errors are swallowed without echoing them (`:138-140`). Error messages carry only the API path and HTTP status. The token never reaches stdout, and the subprocess test checks this (`:226`). +- **Scope:** there's no `.github` wiring, no tag or release queries (test `:147`), no baseline-advance option (`:196`), and no file writes. The script only reads `marketplace_main`. + +### Spec findings +No material gaps against the spec or task 5.1. + +- **Main changes exit 0 as information:** both `changed` and `unchanged` return exit 0 with `level: info` and `channel: development-source` (`:175-183`). This is checked through the real CLI entrypoint with a stubbed fetch (`:220-226`), not just the pure function: exit 0, empty stderr, and no `::error::` or `::warning::`. +- **Unavailable is distinct:** every failure path returns `kind: unavailable`, exit 2, and no `status` or `changes`. The tests check this (`:163-199`), and the entrypoint returns exit 2 for a truncated tree (`:233-238`). +- **CLI compatibility stays independent:** the collector never queries tags, its report has no `compatible` or `taggedRelease` fields (`:227-228`), and the inventory and `package.json` bytes are unchanged afterwards (`:243-244`). Nothing ties it to a hardcoded release. +- **Output:** includes both commits, per-file links at their exact commits, and a compare link (`:157-173`). Output is sorted, and repeated runs are byte-identical (`:132-133`, `:229`). +- **Explicit baseline advancement:** this only happens through a revised inventory passed in by hand (`:150-161`). There's no automatic path. + +### Non-blocking notes (no fix needed) +- The frozen `stableCLIResult` / `stableCLIFailure` objects in the entrypoint test (`:209-239`) prove almost nothing, because nothing could change them. The real independence evidence is the no-tag-query check, the missing report fields and the unchanged files. +- An oddly named path anywhere in the upstream repo (say, one containing a backslash) would make the whole run unavailable. That fails safe, never as a false success, so it isn't material. + +### Smallest material correction +None. diff --git a/docs/reviews/upstream-plugin-test-gap-assessment.md b/docs/reviews/upstream-plugin-test-gap-assessment.md new file mode 100644 index 0000000..079cd14 --- /dev/null +++ b/docs/reviews/upstream-plugin-test-gap-assessment.md @@ -0,0 +1,46 @@ +# Upstream plugin test gap assessment + +## Scope and evidence + +Parent source comparison against exact OpenCode 2.1.1 commit `ba546ec1598a70db2570e9c6258028d955ba8f58`, the selected qualification target. This is not an independent Opus verdict or executed acceptance result. + +The recursive upstream GitHub tree was complete (`truncated: false`). Twelve selected first-party test files were fetched and their Git blob hashes verified against that tree. The survey covered core plugin command, skill, permission-assert, Promise-tool, supervisor, supervisor-reload, session-compact and module tests; SDK host resolution; core plugin lifecycle and failure isolation; and subagent observer lifetime. Relevant assertions were compared with local workflow, continuity, host and packed-package tests and the current OpenSpec tasks. Upstream tests were inspected, not executed; no installed host or model was exercised. + +The preceding Claude Beads comparison found relevant layout/setup and prime tests alongside the CLI rather than a separate test suite in the selected plugin subtree. This follow-up checks OpenCode-owned integration contracts, not test-count parity or a claim of superior thoroughness. + +## Case crosswalk + +- **Command registration and invocation — locally covered; installed dispatch pending.** Upstream checks effective membership, execution, literal arguments, attachments and queue/steer delivery. Local workflow tests and package smoke exercise the actual registration callbacks and bodies through a fixture host. Those fixtures do not qualify actual installed dispatch. +- **Skills and package entrypoints — locally covered at the package boundary; installed access pending.** Upstream checks effective skill registration and root/server resolution. Local smoke resolves both exports, verifies packaged resource bytes and all 16 supporting links, registers guidance and diagnoses missing resources. Task 6.2 already owns installed discovery and external-directory ask/deny. Generic TUI/RPC and conditional-export resolution belong upstream. +- **Cleanup and interruption — plugin responsibilities covered in fixtures; host behavior pending.** Local tests cover owned disposal, idempotent cleanup, partial-setup cleanup, process/approval cancellation and ignored late results. Upstream exercises host interruption reaching Promise executors. Tasks 1.1/4.2 retain the relevant live signal/permission checks. +- **Permission outcomes — generic propagation covered; concrete host variants pending.** Upstream distinguishes blocked, correction-with-feedback and explicit decline. Local `tests/continuity.test.ts` verifies identical rejection propagation and no rejected target operation. `src/continuity.ts` leaves permission assertions outside the CLI-result catch. No implementation defect was demonstrated; actual host outcome delivery remains qualification work. +- **Compaction — intentionally different API usage.** Upstream plugin compaction tests admit a compaction request. This plugin does not invoke that API; it adds reference-only guidance while preserving host compaction payloads. Keep the existing noninterference fixtures and installed recovery checks rather than porting unused-API tests. +- **Supervisor, module loader and background notification infrastructure — host-owned or outside selected scope.** Do not duplicate reload scheduling, dependency redirection, policy admission, failed-generation isolation or moved background-child notification matrices. The selected task flow uses normal blocking child return; task 4.2 already owns relevant failure/stop/handoff checks. + +## Owner-approved refinements + +### Permission correction and decline — task 1.1 / opencode-beads-vuy.2 + +Include correction with feedback and explicit decline alongside the existing allow/ask/deny probe. Verify preservation of the actual host outcome and correction feedback, no rejected target operation, no false success and no automatic retry. Use the existing minimal disposable-host probe, not an adapter replica or a new permission translator. + +**Evidence state: unexecuted.** The generic rejection fixture and source inspection are not actual host qualification. + +### Installed unload/reactivate — task 6.2 / opencode-beads-vuy.9 + +Include one actual installed unload/reactivate round trip. Observe owned entry removal and restoration of the expected commands and skill under native user-override precedence; one eligible request must receive one Beads context injection, with no late result injected from pending old-generation work. Registry rebuilds, imports and disposer fixtures are not substitutes for actual host lifecycle evidence. No reload matrix or supervisor rewrite is selected. + +**Evidence state: unexecuted.** Existing source/package acceptance is preserved, not retroactively relabeled as live success. + +## Authority and disposition + +Matt approved incorporating these two cases into existing acceptance. They do not create tasks, change checkmarks or dependencies, reopen accepted source work, block independent task 6.1, or authorize host provisioning, model experiments, activation, dispatch, publication or release. Existing disposable-host and execution authority gates remain in force. Intended behavior lives in the workflow-access delta; exact cases are in tasks 1.1/6.2 and their existing Beads owners. + +## Representative pinned sources + +- [Core plugin lifecycle, unload and replacement command on reload](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/test/plugin.test.ts) +- [Permission outcomes for Effect and Promise plugins](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/test/plugin/permission-assert.test.ts) +- [Promise-tool interruption and error propagation](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/test/plugin/promise-tool.test.ts) +- [Command registry and invocation contract](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/core/test/plugin/command.test.ts) +- [SDK host entrypoint resolution](https://github.com/anomalyco/opencode/blob/ba546ec1598a70db2570e9c6258028d955ba8f58/packages/plugin/test/host.test.ts) + +Detailed retrieval inventories remain supporting scratch evidence under `/opt/data/cache/scratch/opencode-v2-test-gap-assessment/`; the pinned links and this disposition are the repository-owned record. diff --git a/docs/validate-parity-planning.py b/docs/validate-parity-planning.py new file mode 100644 index 0000000..37ad40b --- /dev/null +++ b/docs/validate-parity-planning.py @@ -0,0 +1,301 @@ +"""Check this planning packet against pinned evidence; no network or store access. + +Usage: python3 docs/validate-parity-planning.py /path/to/parity-sources +Evidence layout: beads/ + beads-tree.json at inventory.commit; released/ + +native-release-tree.json at native_assessment.commit; minimum/ at minimum_commit; +native/ at development_commit; npm26/package and npm32/package extracted from +plugin-2.0.26.tgz / plugin-2.0.32.tgz. Download with gh api repo tarball/tree/ref +and the exact npm tarball URLs in the inventory, all outside the repository. +Current marketplace evidence: marketplace-main-commit.json, marketplace-main-tree.json +and marketplace-main/ at marketplace_main.commit. This validates source evidence and +planning coverage, not runtime contracts or model behavior. +""" +import base64 +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys +from collections import Counter + +repo = Path(__file__).resolve().parents[1] +scratch = Path(sys.argv[1]).resolve() +data = json.loads((repo / 'docs/claude-code-integration-inventory.json').read_text()) +report = (repo / 'docs/claude-code-integration-analysis.md').read_text() +native = data['native_assessment'] +assessment = (repo / 'docs' / native['document']).read_text() + +def verify_blob(path, entry, tree_entry): + content = path.read_bytes() + assert hashlib.sha256(content).hexdigest() == entry['sha256'], path + blob = hashlib.sha1(b'blob ' + str(len(content)).encode() + b'\0' + content).hexdigest() + assert blob == entry['git_blob'] == tree_entry['sha'], path + +beads_tree = json.loads((scratch / 'beads-tree.json').read_text()) +assert not beads_tree['truncated'] +expected = {e['path']: e for e in beads_tree['tree'] if e['type'] == 'blob' and + (e['path'].startswith(('plugins/beads/', 'internal/templates/skills/beads/')) or + e['path'] in data['support_paths'])} +actual = {e['path']: e for e in data['files']} +assert len(actual) == len(data['files']) == 73 +assert actual.keys() == expected.keys() +counts = Counter(e['category'] for e in actual.values()) +assert counts['command'] == 29 and counts['resource'] == 15 and counts['adr'] == 1 +for path, entry in actual.items(): + verify_blob(scratch / 'beads' / path, entry, expected[path]) + assert entry['owner_disposition'] in {'adapt', 'replace', 'preserve-external', 'reference-only', 'exclude', 'defer'} + assert entry['url'] == f"https://github.com/{data['source_repository']}/blob/{data['commit']}/{path}" + if entry['category'] in ('command', 'resource', 'adr'): + assert entry['url'] in report +skill = (scratch / 'beads/plugins/beads/skills/beads/SKILL.md').read_text() +links = set(re.findall(r'\]\((resources/[^)]+)\)', skill)) +resources = {str(Path(e['path']).relative_to('plugins/beads/skills/beads')) + for e in data['files'] if e['category'] == 'resource'} +assert links == resources +assert (repo / '.agents/skills/beads/SKILL.md').read_bytes() == ( + scratch / 'beads/internal/templates/skills/beads/SKILL.md').read_bytes() + +# Preserve historical identity separately; validate all current selected members. +current = data['marketplace_main'] +commit_evidence = json.loads((scratch / current['commit_evidence']).read_text()) +assert commit_evidence['sha'] == current['commit'] +current_tree = json.loads((scratch / current['tree_evidence']).read_text()) +assert not current_tree['truncated'] +assert current_tree['sha'] in {current['commit'], commit_evidence['commit']['tree']['sha']} +current_expected = {e['path']: e for e in current_tree['tree'] if e['type'] == 'blob' and + (e['path'].startswith(('plugins/beads/', 'internal/templates/skills/beads/')) or + e['path'] in data['support_paths'])} +current_actual = {e['path']: e for e in current['files']} +assert len(current_actual) == len(current['files']) == 74 +assert current_actual.keys() == current_expected.keys() +assert Counter(e['category'] for e in current_actual.values())['command'] == 29 +assert Counter(e['category'] for e in current_actual.values())['resource'] == 15 +assert Counter(e['category'] for e in current_actual.values())['adr'] == 1 +assert Counter(e['release_delta'] for e in current_actual.values()) == { + 'added': 1, 'modified': 16, 'unchanged': 57} +for path, entry in current_actual.items(): + verify_blob(scratch / 'marketplace-main' / path, entry, current_expected[path]) + assert entry['url'] == f"https://github.com/{current['repository']}/blob/{current['commit']}/{path}" + prior = actual.get(path) + expected_delta = 'added' if prior is None else 'unchanged' if prior['git_blob'] == entry['git_blob'] else 'modified' + assert entry['release_delta'] == expected_delta +assert current_actual.keys() - actual.keys() == {'plugins/beads/BUILD.bazel'} +assert not actual.keys() - current_actual.keys() +current_skill = (scratch / 'marketplace-main/plugins/beads/skills/beads/SKILL.md').read_text() +assert set(re.findall(r'\]\((resources/[^)]+)\)', current_skill)) == resources +# Ignore only the cache owner's .in_use runtime marker; all payload membership/hashes remain exact. +# Installed cache comparison is optional for other machines; its identity is not inferred from version. +installed = Path('/opt/data/home/.claude/plugins/cache/beads-marketplace/beads/1.3.0') +if installed.is_dir(): + plugin_entries = {p.removeprefix('plugins/beads/'): e for p, e in current_actual.items() + if p.startswith('plugins/beads/')} + assert {str(p.relative_to(installed)) for p in installed.rglob('*') if p.is_file() and p.relative_to(installed).as_posix() != '.in_use'} == plugin_entries.keys() + for path, entry in plugin_entries.items(): + assert hashlib.sha256((installed / path).read_bytes()).hexdigest() == entry['sha256'], path + +change = repo / 'openspec/changes/improve-beads-workflow-parity' +if not change.exists(): + change = repo / 'openspec/changes/archive/2026-10-21-improve-beads-workflow-parity' +assert change.is_dir(), 'Expected active or accepted archived packet' +tasks = (change / 'tasks.md').read_text() +parsed_tasks = re.findall(r'^- \[([ xX])\] (\d+\.\d+) ', tasks, re.M) +task_ids = [task_id for _, task_id in parsed_tasks] +assert len(task_ids) == len(set(task_ids)) and task_ids +# The active execution map must own every task exactly once, including new source deltas. +execution_map = (change / 'execution-map.md').read_text() +owner_rows = re.findall(r'^\| `(opencode-beads-vuy\.\d+)` \| ([0-9., ]+) \|', execution_map, re.M) +owned_ids = [task_id for _, ids in owner_rows for task_id in ids.strip().split(', ')] +assert len(owned_ids) == len(set(owned_ids)) and set(owned_ids) == set(task_ids) +assert len(owner_rows) == len({owner for owner, _ in owner_rows}) + +# Source tasks and 6.1 have parent-accepted deterministic/CLI evidence; live gates remain unexecuted. +accepted_tasks = {task_id for mark, task_id in parsed_tasks if mark.lower() == 'x'} +historical_accepted = {'1.2', '2.1', '2.2', '3.1', '3.2', '3.3', '4.1', '5.1', '6.1'} +source_delta = {'7.1', '7.2', '7.3'} +source_evidence = repo / 'docs/code-mode-source-acceptance-vuy10.md' +assert historical_accepted <= accepted_tasks +host_evidence = repo / 'docs/opencode-host-codemode-qualification-vuy2.md' +flow_evidence = repo / 'docs/opencode-stop-handoff-qualification-vuy6.md' +assert accepted_tasks <= set(task_ids) +if accepted_tasks & {'6.2', '6.3'}: + assert accepted_tasks == set(task_ids) and (repo / 'docs/code-mode-final-acceptance-vuy9.md').is_file(), 'Final acceptance requires all prerequisites and parent evidence' +if '4.2' in accepted_tasks: + assert '1.1' in accepted_tasks and flow_evidence.is_file(), 'Live flow acceptance requires native host acceptance and its parent-reviewed observation record' +if '1.1' in accepted_tasks: + assert host_evidence.is_file(), 'Native host acceptance requires its parent-reviewed observation record' +if accepted_tasks & source_delta: + assert source_delta <= accepted_tasks and source_evidence.is_file(), 'Source delta acceptance requires its parent evidence record' +# Final review B1: stable task IDs with dependency/types preparation first. +assert task_ids.index('1.2') < task_ids.index('1.1') +task_text = dict(re.findall(r'^- \[[ xX]\] (\d+\.\d+) (.+)$', tasks, re.M)) +assert 'After 1.2' in task_text['1.1'] and 'throwaway' in task_text['1.1'] +assert 'tests/continuity.test.ts' not in task_text['1.1'] +assert 'tests/continuity.test.ts' in task_text['3.1'] +assert '1.1' not in task_text['5.1'] +assert 'Live prerequisite for 1.1, 4.2 and 6.2' in tasks +valid_dispositions = {'adapt', 'replace', 'preserve-external', 'reference-only'} +for entry in data['files'] + current['files']: + mapping = entry['selected_mapping'] + assert mapping['disposition'] in valid_dispositions + assert all(mapping[k].strip() for k in ('group', 'target', 'reason')) + assert mapping['task_ids'] and set(mapping['task_ids']) <= set(task_ids) + if entry['category'] == 'command': + assert mapping['target'] == 'oc-beads:' + Path(entry['path']).stem + if entry['category'] == 'resource': + assert mapping['target'] == 'assets/oc-beads/resources/' + Path(entry['path']).name + if entry['path'] in actual and 'owner_disposition' in entry: + assert entry['owner_disposition'] == mapping['disposition'] + assert entry['historical_owner_disposition'] == 'pending' +requirements = {(p.parent.name, title) for p in (change / 'specs').glob('*/spec.md') + for title in re.findall(r'^### Requirement: (.+)$', p.read_text(), re.M)} +record = (change / 'reconciliation.md').read_text() +coverage_rows = re.findall(r'^\| (work-continuity|workflow-access|upstream-review) \| ([^|]+) \| ([0-9., ]+) \|$', record, re.M) +coverage = {(cap, title.strip()): ids.strip().split(', ') for cap, title, ids in coverage_rows} +assert len(coverage) == len(coverage_rows) and coverage.keys() == requirements +assert all(set(ids) <= set(task_ids) and ids for ids in coverage.values()) +assert set(task_ids) - {i for ids in coverage.values() for i in ids} == {'6.3'} + +release_tree = json.loads((scratch / 'native-release-tree.json').read_text()) +assert not release_tree['truncated'] +tree = {e['path']: e for e in release_tree['tree'] if e['type'] == 'blob'} +assert json.loads((scratch / 'native-tag.json').read_text())['object']['sha'] == native['commit'] +assert json.loads((scratch / 'native-min-tag.json').read_text())['object']['sha'] == native['minimum_commit'] +assert (scratch / 'native-dev-head.txt').read_text().strip() == native['development_commit'] +source_paths = set() +for entry in native['sources']: + path = entry['path'] + assert path not in source_paths + source_paths.add(path) + verify_blob(scratch / 'released' / path, entry, tree[path]) + assert entry['url'] == f"https://github.com/anomalyco/opencode/blob/{native['commit']}/{path}" + assert entry['url'] in assessment and entry['id'] in assessment +assessment_source_urls = set(re.findall(r'https://github.com/anomalyco/opencode/blob/[a-f0-9]{40}/[^)\s]+', assessment)) +historical_source_urls = {e['url'] for e in native['sources']} +current_source_urls = {e['url'] for e in data['host_baseline_211']['sources']} +# Preserve complete historical source membership while allowing cited 2.1.1 evidence. +# Both inventories' blobs/hashes are independently verified; unknown source URLs still fail. +assert historical_source_urls <= assessment_source_urls +assert assessment_source_urls <= historical_source_urls | current_source_urls +assert {e['id'] for e in native['sources']} == {f'S{i:02}' for i in range(1, 22)} +mechanisms = native['mechanisms'] + native['candidates'] +assert {e['id'] for e in mechanisms} == {f'M{i:02}' for i in range(1, 11)} | {f'X{i:02}' for i in range(1, 10)} +assert {e['id'] for e in native['availability_anchors']} == {e['id'] for e in mechanisms} +for entry in mechanisms: + assert entry['owner_disposition'] in {'adapt', 'replace', 'preserve-external', 'reference-only', 'exclude', 'defer'} + assert f"| {entry['id']} " in assessment + if entry['id'].startswith('M'): + assert entry['availability'] == 'released-public' + elif entry['id'] in ('X01', 'X02', 'X03', 'X04'): + assert entry['availability'] == 'released-experimental' + elif entry['id'] == 'X09': + assert entry['availability'] == 'development-only' +for entry in native['availability_anchors']: + assert entry['path'] in source_paths + content = (scratch / 'released' / entry['path']).read_text() + assert all(token in content for token in entry['tokens']), entry['id'] + assert all(token not in content for token in entry.get('absent', [])), entry['id'] +assert 'tool.options?.permission ?? name' in (scratch / 'released/packages/core/src/tool.ts').read_text() +permission_source = (scratch / 'released/packages/core/src/permission.ts').read_text() +assert 'agentID ?? session.agent' in permission_source +assert 'existing.request.save?.length' in permission_source +assert 'access.authorizeExternal([target, ...directories], context)' in (scratch / 'released/packages/core/src/tool/plugin/shell.ts').read_text() +assert 'action: "external_directory"' in (scratch / 'released/packages/core/src/file-access.ts').read_text() +assert 'text.search(/\\s/)' in (scratch / 'released/packages/app/src/composer/client-slash-command.ts').read_text() +assert '| "list"' in (scratch / 'native/packages/plugin/src/promise/session.ts').read_text() +assert 'readonly assert:' not in (scratch / 'minimum/packages/plugin/src/promise/permission.ts').read_text() +for entry in native['package_evidence']: + version = entry['version'] + content = (scratch / f'plugin-{version}.tgz').read_bytes() + assert entry['integrity'] == 'sha512-' + base64.b64encode(hashlib.sha512(content).digest()).decode() + directory = scratch / ('npm26' if version == '2.0.26' else 'npm32') / 'package' + assert json.loads((directory / 'package.json').read_text())['version'] == version +for name in native['declarations_identical_26_32']: + rel = f'package/dist/promise/{name}.d.ts' + assert (scratch / 'npm26' / rel).read_bytes() == (scratch / 'npm32' / rel).read_bytes() +assert 'readonly assert:' not in (scratch / 'npm26/package/dist/promise/permission.d.ts').read_text() +assert 'readonly assert:' in (scratch / 'npm32/package/dist/promise/permission.d.ts').read_text() +assert '| "list"' not in (scratch / 'npm32/package/dist/promise/session.d.ts').read_text() +assert native['status'] == 'historical-planning' and native['runtime_qualification'] == 'not executed' +assert native['selected_minimum'] == '>=2.0.32 <3' + +# New baseline evidence is additive; never relabel historical 2.0.32 sources. +new = data['host_baseline_211'] +assert new['commit'] == 'ba546ec1598a70db2570e9c6258028d955ba8f58' +assert new['previous_commit'] == native['commit'] +assert new['admission_range'] == '>=2.1.1 <2.2' and new['exact_live_target'] == '2.1.1' +assert new['runtime_qualification'] == 'not executed' +cache = scratch / new['cache'] +assert json.loads((cache / 'tag.json').read_text())['object'] == { + 'sha': new['commit'], 'type': 'commit', + 'url': f"https://api.github.com/repos/anomalyco/opencode/git/commits/{new['commit']}"} +new_tree = json.loads((cache / 'tree.json').read_text()) +assert not new_tree['truncated'] and new_tree['sha'] == new['commit'] +new_blobs = {e['path']: e for e in new_tree['tree'] if e['type'] == 'blob'} +new_paths = [e['path'] for e in new['sources']] +assert len(new_paths) == len(set(new_paths)) and source_paths <= set(new_paths) +for entry in new['sources']: + path = entry['path'] + verify_blob(cache / 'source' / path, entry, new_blobs[path]) + assert entry['url'] == f"https://github.com/anomalyco/opencode/blob/{new['commit']}/{path}" + prior = tree.get(path) + assert entry['old_git_blob'] == (prior['sha'] if prior else None) + delta = 'added' if prior is None else 'unchanged' if prior['sha'] == entry['git_blob'] else 'modified' + assert entry['delta'] == delta + if prior: + old = (scratch / 'released' / path).read_bytes() + assert hashlib.sha1(b'blob ' + str(len(old)).encode() + b'\0' + old).hexdigest() == prior['sha'] +npm = json.loads((cache / 'npm.json').read_text()) +package = new['package'] +assert npm['name'] == package['name'] and npm['version'] == package['version'] == '2.1.1' +assert npm['dist']['tarball'] == package['tarball'] and npm['dist']['integrity'] == package['integrity'] +tarball = (cache / 'plugin-2.1.1.tgz').read_bytes() +assert hashlib.sha256(tarball).hexdigest() == package['sha256'] +assert 'sha512-' + base64.b64encode(hashlib.sha512(tarball).digest()).decode() == package['integrity'] +import io +import tarfile +with tarfile.open(fileobj=io.BytesIO(tarball)) as archive: + assert json.loads(archive.extractfile('package/package.json').read())['version'] == '2.1.1' + for entry in new['declarations']: + content = (cache / 'npm' / entry['path']).read_bytes() + assert content == archive.extractfile(entry['path']).read() + prior = (scratch / 'npm32' / entry['path']).read_bytes() + assert hashlib.sha256(content).hexdigest() == entry['sha256'] + assert hashlib.sha256(prior).hexdigest() == entry['old_sha256'] + assert entry['delta'] == ('unchanged' if content == prior else 'modified') +for domain in ('effect', 'promise'): + source = (cache / f'source/packages/plugin/src/{domain}/session.ts').read_text() + declaration = (cache / f'npm/package/dist/{domain}/session.d.ts').read_text() + assert '| "list"' in source and 'native?:' in source and 'providerState?:' not in source + assert '"list"' in declaration and 'native?:' in declaration +assert new['availability_overrides']['X09']['availability'] == 'released-public' +assert new['availability_overrides']['X09']['disposition'] == 'reference-only' +assert 'metadata.keepID' in (cache / 'source/packages/core/src/session/history.ts').read_text() +assert 'tool.options?.permission ?? name' in (cache / 'source/packages/core/src/tool.ts').read_text() +assert 'existing.request.save?.length' in (cache / 'source/packages/core/src/permission.ts').read_text() +assert re.search(r'^## (v[^ ]+)', (cache / 'source/CHANGELOG.md').read_text(), re.M)[1] == 'v2.1.0' +assert set(new['task_ids']) <= set(task_ids) +print(f"PASS: 2.1.1 separate pin, {len(new_paths)} endpoint sources, published tarball/declarations, conservative admission; runtime unexecuted") + +files = [p for p in change.rglob('*') if p.is_file()] +files += [repo / 'docs' / name for name in ( + 'claude-code-integration-analysis.md', 'claude-code-integration-inventory.json', + 'opencode-native-parity-assessment.md', 'validate-parity-planning.py', + 'claude-code-local-reference.md', 'beads-artifact-policy.md', + 'claude-code-parity.md', 'opencode-sdk-contract.md', + 'reviews/opus-planning-review-50919ae.md', 'opencode-2.1.1-contract-delta.md')] +for path in files: + assert not path.is_symlink() and path.resolve().is_relative_to(repo) + check = subprocess.run(['git', 'diff', '--no-index', '--check', '/dev/null', str(path)], + capture_output=True, text=True) + assert check.returncode in (0, 1) and not check.stdout and not check.stderr, (path, check.stdout, check.stderr) + if path.suffix == '.md': + for link in re.findall(r'\]\(([^)]+)\)', path.read_text()): + if not link.startswith(('https:', 'http:', '#')): + assert (path.parent / link.split('#')[0]).exists(), (path, link) +print(f'PASS: {len(actual)} Claude files; 29 commands, 15 resources, 1 ADR; Git/SHA identity and complete coverage') +print(f'PASS: {len(native["sources"])} native files / 21 source groups; 10 mechanisms, 9 candidates; links and availability anchors') +print('PASS: npm tarball integrity, 2.0.26/2.0.32 declaration comparison, permission assertion and development-only listing distinctions') +print(f'PASS: {len(current_actual)} marketplace-main files, complete dispositions and installed comparison when available') +print(f'PASS: {len(task_ids)} unique tasks ({len(accepted_tasks)} accepted, {len(task_ids) - len(accepted_tasks)} unchecked); {len(requirements)} normative requirements mapped; full corpus task coverage') +print(f'PASS: {len(files)} planning files whitespace/local links/containment, including untracked artifacts; no tracker access') diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/.openspec.yaml b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/.openspec.yaml new file mode 100644 index 0000000..b699b88 --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-17 diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md new file mode 100644 index 0000000..58bc55d --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/design.md @@ -0,0 +1,164 @@ +# Design + +## Operative code-mode planning reconciliation — 2026-10-21 + +Matt authorized Astra/medium to reconcile the broader Opus recommendations into this active change and Beads. Actual planning executor: `codex:oc-plan`, bounded record `opencode-beads-wgv`. The selected planning direction is the [broader architecture memo](../../../../docs/reviews/opus-code-mode-architecture-wgv.md), including its parent verification; the six-tool recommendation and narrow READY remain historical only. This local unpublished revision is based on published HEAD `072429f1a7723ade23e756a921924d9d0acacf31`, not a new published candidate or independent approval. + +Adopt the eleven-operation daily-loop namespace, host-derived executor actor visible to the model, capability-aware deduplicated guidance, operation-aware references and conservative targeted freshness. Retain all 30 human command names/user overrides, full advanced/admin coverage and full prime initially. Canonical actor means the actual host-derived executor identity, not the literal `beads-task-agent` name or mandatory delegation. Primary agents keep their own actor; delegated workers receive their own. Permission identity remains actual invocation `context.agent`. + +This authorizes planning/spec/Beads reconciliation only. Product implementation, resumed host/model qualification, activation, publication, acceptance and closure remain unauthorized (`implementation_authorized=false`). Nine accepted task markers remain historical acceptance; new tasks 7.1–7.3 own the source delta. Remaining 1.1/4.2/6.2/6.3 remain unchecked. Parent review must confirm the compatibility-visible replacement of the withheld five-tool names by the eleven-operation namespace and changed command prompt wording before source dispatch. Unsupported CLI details are bounded 7.1 qualification gates, not assumptions or a request for Matt to design argv. + +## Operative owner decision: composition retained, planning review pause + +The owner accepts OpenCode 2.1.1's measured code-mode plain-decline continuation limitation, not a permission bypass or successful target execution. Preserve unchanged adapter rejection, correction feedback, zero rejected-target execution, no plugin automatic retry, independent later-operation permissions and honest persistence results. Do not promise that plain decline ends the composed step; explicit Stop/Cancel remains separately qualified. Preserve the historical failed stronger case as an accepted limitation, not passing evidence. No upstream patch, error translator or direct-only restriction is selected. + +Remaining qualification/activation is paused for parent review of the selected broader architecture reconciled below. The missing output schema is our integration defect, not covered by the upstream acceptance. Current fixed tools are the accepted historical implementation baseline, not a prohibition on recommending a better interface. Research/report and parent planning/tracker reconciliation are authorized; product changes and resumed live/model qualification are not. Existing task checkmarks remain unchanged. + +## Context + +See [proposal](proposal.md), [owner decisions](owner-decisions.md), [full corpus analysis](../../../../docs/claude-code-integration-analysis.md) and [native call-site assessment](../../../../docs/opencode-native-parity-assessment.md). Reconciliation baseline is `3dc2ea327ed490ceb9cf0b95c31af77631613287`. Historical release evidence is retained separately from the selected marketplace-main commit. The operative host baseline is now 2.1.1; see the [contract delta](../../../../docs/opencode-2.1.1-contract-delta.md). The following 2.0.32 pin remains historical, not relabeled. Released OpenCode 2.0.32 is `843a6c514e606b436a7fe713422ace0a93ad3d61`; development `8f7b012…` is not that release. + +Published baseline already contains the accepted five-operation adapter (activation withheld), package commands/resources and scope-bounded guidance. The new source delta replaces its missing output schemas and narrow operation surface; historical acceptance does not establish code-mode readiness. + +## Goals / Non-Goals + +**Goals:** complete native workflow access, semantic continuity and authority-preserving execution, with small adapters over the existing runner and host. Separate discovery, execution acknowledgement and behavioral qualification. + +**Non-goals:** a new datastore, generic manager, write queue, transcript harvester, background scheduler, identity service, telemetry stack, backup project or automatic migration. No product work occurs in this planning reconciliation. + +## Decisions + +### 1. Selected scope and authority + +Matt selected marketplace-main, N1 on OpenCode 2.1.1 within the admitted 2.1 patch line (`>=2.1.1 <2.2`), beta/next opt-in delivery, scope-bounded continuation, package ownership with `oc-beads`, all corpus groups, and scheduled check alerts. No further owner interview is needed for technical mappings. N0 and file installers are historical alternatives. Concrete incompatibility that would weaken a selected guarantee is a stop gate, not permission to switch to N0. + +OpenSpec owns intended behavior; docs retain evidence; Beads owns later execution tracking. The planning bead remains open under parent control. Owner selection does not mean implementation acceptance, compatibility qualification or publication authority. + +### 2. Native delivery and complete corpus + +Use D01 command transforms and D04 skill transforms in `src/host.ts`, with small `src/workflows.ts` definitions and npm assets under `assets/oc-beads/`. Register skill `oc-beads` with a real installed path. Ship its SKILL.md, all 15 adapted corpus resources plus the new CODE_MODE resource and ADR rationale, with explicit links to all 17 supporting files (host auto-listing truncates at ten); register every inventory command as `oc-beads:<stem>` (29 entries), plus a read-only `oc-beads:status` diagnostic. Replacements retain discoverable entry names but explain current supported equivalents. All commands submit scoped normal prompts, forwarding user text, attachments and delivery; none execute shell interpolation directly. + +The released command schema uses a string name and the registry is keyed by that name (S13); colon names are source-supported. UI slash discovery/invocation is an installed-host acceptance check, not a completed observation. A parser rejecting the namespace is a concrete stop gate requiring a documented equivalent, not an automatic rename. + +All advanced resources remain available on demand (agent beads, molecules/chemistry, gates, worktrees, static references); availability does not enable automation, nested workers or store setup. Admin commands use ordinary permissioned CLI workflows with current help and explicit requested scope. `quickstart` becomes onboarding; `template` replaces stale template syntax with supported template/formula discovery; `version` reports installed versions; `init` is human-directed setup guidance. Unsupported legacy operations explain the limitation and supported replacement; do not silently drop the entry or invent flags. Keep dynamic syntax in prime/help and semantic guidance in assets. Preserve attribution. + +Register all 29 namespaced workflows plus status through one ordinary command transform. The pinned command editor exposes only add/map set; initial `ctx.command.list()` collisions are diagnostic-only, not a reason to withhold a name permanently. S13 supervisor ordering is pre → packages → post, and ConfigCommandPlugin is post: ordinary configuration/file commands replay after package transforms, including config reload, and removing an override restores the package definition. Same-name conflicts with other plugin transforms follow native order; disclose detectable conflicts without promising universal preservation. This is supported host precedence, not broken upstream behavior. Keep public per-rebuild skill `editor.list()` collision checks, but remove the redundant initial skill gate so a removed customization does not permanently suppress registration. Every owned transform checks abort before add and has an owned disposer. Qualify initial/rebuilt/disposed registries using a small ordered-callback fixture, not a host replica or collision manager. Status compares effective public names/descriptions with definitions, reports observable overrides and lookup failures, and discloses that owner identity and identical-description overrides are unobservable; no hardcoded all-command hold survives. Qualify all packaged resource links and external-directory ask/deny from the installed base, reporting unreachable files without bypass. Do not write user files. Native scoped disposers plus setup cleanup remove only owned registrations and abort observers/processes. Shared `beads` skill, user configuration/files, agent names and npm package name remain unchanged. + +Opus's resolution review verifies package/config ordering from pinned 2.1.1 `plugin/supervisor.ts:99–103`, `plugin/internal.ts:254–259`, `command.ts:61` and `state.ts:162,178–181`. Its registry suffix-reactivation evidence comes from the unpinned `released` cache, not the exact 2.1.1 cache; do not label that detail pinned or live-qualified. Installed-host discovery, reactivation and permissions remain unexecuted 1.1/6.2 acceptance work. See [the original review](../../../../docs/reviews/opus-registration-resolution-review-vuy5.md); its REVISE verdict is historical evidence, not changed by this reconciliation. + +### 3. N1 continuity operations and authorization + +Use one narrow `src/continuity.ts` module wired from host, backed by `runBd` in `src/prime.ts`. The eleven fixed operations and their contracts are specified in section 7 below; the historical five operations are not the target. Do not expose arbitrary argv, shell text, cwd, environment, force, init, sync or admin execution through typed tools. Advanced/admin workflows retain the ordinary direct-turn shell tool, which is not callable inside code mode. Validate operation input and pass argv directly, never construct executable shell strings. + +Set each typed definition's `options.permission` to `shell`: The native inventory split filters shell-denied typed tools and exposes default code-mode tools only through execute; wholly denying execute removes them. At invocation assert `action: "shell"` against canonical resource text for the exact fixed bd operation, `sessionID`, **`agent: context.agent`**, and tool source message/call IDs. Do not substitute the session's default agent. Pass `save: []`, never install a new broad `bd *` grant. S15 makes an "always" response one-call on this path; guidance must explain repeated prompts under ask. Existing project shell `bd *` saved grants may authorize equivalent typed operations, subject to configured denies checked first; this is shell equivalence, not privilege escalation. Qualify these expected outcomes without broadening permissions for convenience. Operation-level allow/ask/deny behavior must match the built-in shell for the supported operation, including selective write denials. Propagate assertion rejection unchanged at the adapter seam; never acknowledge a rejected target as successful. The owner accepts the measured 2.1.1 nested plain-decline continuation limitation: code mode may expose an inner error in an outer completed result and continue independently permissioned work or model steps. Whole-step termination after individual rejection is not promised. Direct shell fallback and code-mode typed paths must each filter/check, and the outer code-mode hook must not duplicate a write acknowledgement. + +`permission.assert` alone is not shell equivalence: S18 shell preparation also resolves cwd and authorizes external directories before asserting command permissions. Bind execution to the actual invocation session/project directory, never process cwd or a model-supplied path. Apply equivalent `external_directory` assertions for explicit invocation/location access, using invocation identity and no broad saved scope. Recheck that session/location, cancellation and the existing migration guard after approval and before spawning. Use supported config-only `bd context --json` for the guard, rather than `where`'s possible store-opening prefix lookup. The installed Beads CLI owns its normal redirects, worktree discovery, inherited environment, backend/routing and consistency semantics. Normal permissioned CLI equivalence does not promise absolute filesystem or final-store confinement; intended routing after a local miss is not a plugin defect or acceptance blocker. Do not require final-store attestation or a routing-disable switch, clear inherited routing, restrict backend modes, clone metadata/resolvers, or build upstream repair/protective frameworks. Actual OpenCode permission/API incompatibilities still stop the affected path. Minimal host assertion probes belong to 1.1; adapter context/guard/argv fixtures belong to 3.1. Both must pass before N1 activation. + +Recheck the guard hold, actual session/location and cancellation immediately before spawning, including after any approval wait. Never let manual-prime advice bypass a hold. Extend existing runner cancellation only as necessary; use finite I/O bounds and abort on stop/unload. A timeout or interrupted write may have landed: disclose uncertainty, re-read on recovery and never blindly retry append/remember. Existing invocation IDs may prevent duplicate handling within the session; no new durable exactly-once system. + +Success records actual CLI results and durable references, not merely exit zero. Current marketplace memory source can return `committed:false`, `commit_error` and `commit_indeterminate` after a landed write; preserve those distinctions without automatically committing. Released CLI behavior remains separately qualified. Memory budget refusal is not permission to force or change config. Empty found memory differs from absent memory. + +### 4. Context, compaction and lifecycle + +M01 awaited context supplies guarded canonical prime and focused recovery cues at the first eligible request and relevant transitions. The model retrieves the current task and relevant memories, interprets failed approaches and chooses meaningful checkpoints. M02 tool hooks preserve results/errors and provide focused cues; adapter acknowledgement applies the operation-aware freshness policy in section 7. External writers require targeted fresh reads on recovery/important transitions; a cache miss is not proof of absence. Preserve distinct local authority when reducing duplicate instructions; retaining a block is preferable to deleting unique policy by marker alone. + +M03 compaction is **reference-only**: add acknowledged task/memory references and known unsaved/uncertain status. No persistence, permission waits, flush queue, semantic extraction, recursive same-session prompt or checkpoint wait inside the occupied runner. An awaited hook is not a license to await work from itself. Keep host summary/native-provider compaction intact. Ordinary meaningful checkpoints before compaction provide durability; summary text is not a Beads write. + +On 2.1.1, provider replay payloads use `native`; tool execution `state` and UI `metadata` retain their meanings. Preserve host-owned `metadata.keepID` and legacy `metadata.recent`, fork remapping and provider/model replay boundaries. Do not populate hook `result.native.messages`: it replaces summary replay. Do not set a replacement `result` merely to add Beads references, or rename all state/metadata fields mechanically. Fixtures cover retained history, fork recovery and provider/model changes without implementing a compatibility manager. + +M04 events use current compaction started/ended/failed lifecycle; retire any assumed `session.compacted` subscription. Events are observations for diagnostics/invalidation/cleanup, never a before-stop barrier or automatic continuation trigger. M05 known-session lookup supplies scope; no dependency on session listing (now released but unnecessary). Scope ephemeral state to actual location/session/audience and discard/re-resolve on move, fork or deletion. M10 disposes registrations, aborts event subscriptions and in-flight processes. Catch non-policy hook errors and report them without indefinite blocking. No final-write or exactly-once crash guarantee. + +### 5. Task flow, delegation and extensions + +Rewrite the canonical beta guidance in `src/guidance.ts` and agent description, retaining existing agent IDs. Atomic claim and per-bead validation remain required. Exact assignments and single operations stop when done; a previously authorized batch/goal may proceed to another actionable bead within that scope. A newly discovered issue is recorded when authorized but does not expand execution scope. Stop on cancellation, denied authority, scope exhaustion or a blocker requiring a decision. Read-only graph analysis never mutates. Direct work and delegation are chosen for the task, not tool-call counts. Every agent-authored write/claim/heartbeat must carry the actual executor's explicit actor, including typed CLI calls and delegated commands; never fall back to human Git identity. Preserve the invoking host agent for permission evaluation separately from the Beads audit actor: a model-supplied actor must not impersonate another executor, and actor names grant no authority. Use stable executor identity from the authorized invocation/handoff; if unavailable, refuse writes rather than invent an identity. Verify created_by/owner/assignee separately, keep ownership handoffs deliberate and historical authorship unchanged. Repository planning/review tracking follows project-owned AGENTS.md and remains separate from this implementation epic. Optional one-bead mode remains supported; Luna remains explicitly opt-in without model-quality claims; existing step caps remain transparent interruption limits. + +Use normal blocking child return (M07) initially, with scope, attempts, evidence, durable references, blockers and next action. Parent reconciles acceptance before closure. NO_TEXT, failed/cancelled child and stale/late result are incomplete outcomes, not success. Do not emit plugin synthetics to restart a stopped parent. Background child completion can wake through host-owned synthetic admission, for which no plugin veto was found; background/nested automation is not selected as a base mechanism. Qualification includes deliberate-stop/late-child races and permission waits. A failed stop guarantee blocks that path; no second poller or job manager. + +Engineering dispositions: X01 transport rewriting excluded; X02 terminal evidence retrieval deferred; X03 nested-depth changes deferred, normal children retained; X04 parser changes deferred, host configuration respected; X05 V1/deprecated hooks excluded; X06 native-provider compaction supported; X07 low-level middleware/shell rewriting excluded; X08 use existing diagnostics only, no new storage/RPC/UI service; X09 released paginated listing remains unused; known-session lookup is sufficient. D02/D03/D05 installers/config mutation and D06 guidance-only delivery are not selected. E01 optional one-bead scope, E02 opt-in Luna, E03 guard, E04 permissions/caps, E05 discretionary delegation and E06 targeted freshness implement the accepted base. + +### 6. Drift and qualification + +Compare both complete source roots (`plugins/beads/`, `internal/templates/skills/beads/`) plus inventory support paths between human-reviewed marketplace-main commits. Resolve default branch to a commit for each comparison; retain both identities and path/diff links. Detect additions/deletions/edits and identifiable renames. A completed comparison exits successfully for both unchanged and changed main content, using informational summary/output only: no drift warning/error annotations, failed product CI or release/adoption gate. Main is development-source evidence, not tagged CLI support. If a change needs an unreleased feature, record explicit deferral until verified inclusion in a supported release; never infer inclusion from dates. Incomplete retrieval/comparison is unavailable evidence, not no change; a distinct collector failure may diagnose it but cannot block the separate product/release gates. Reuse `.github/workflows/beads-contract.yml` scheduled pattern with contents:read only when workflow edits are authorized, keeping the advisory collector independent of stable tagged CLI compatibility tests and their real failures. Repeated main changes remain informational without issue creation or mandatory immediate review. Task 5.1 is independent of N1 qualification. No auto-copy, baseline advancement, version bump or release. + +Prepare dependency/types in 1.2 before minimal host probes in 1.1; 3.1 owns adapter fixtures. Live gates 1.1/4.2/6.2 require an isolated disposable host pinned initially to 2.1.1, within `>=2.1.1 <2.2`, with later authority for availability/installation and model access. The installed image CLI is 2.0.29 and stays untouched. Missing live environment blocks live gates/activation acceptance, not source/asset/fixture work. + +Admission is intentionally narrower than the historical >=2.0.32 <3 proposal because 2.1 breaks public contracts. Future 2.1 patches are admitted candidates subject to source/acceptance checks, not claimed tested; 2.2+ and 2.0 are outside this redesign’s range. Prior exact-host probes are recorded in the SDK contract; product qualification remains incomplete. + +Qualification has three layers: pinned source checks (completed planning evidence), stub/disposable fixture integration (future task work), and installed-host/model behavior (future acceptance). Exercise the complete installed catalog mechanically and a small representative behavioral set: ordinary ready/claim/validate flow, failed approach and discovered follow-up, task notes versus reusable memory, fresh-session recovery, exact assignment versus authorized continuation, restricted agents, hold/failure and partial child handoff. Record host/CLI/model/provider/reasoning/install path and actual durable outcomes. Repeat only uncertain cases; no write/tool-count quota or exhaustive matrix. + +The owner-approved [upstream plugin test gap assessment](../../../../docs/reviews/upstream-plugin-test-gap-assessment.md) refines existing live acceptance, without reopening accepted source work or adding tasks. Task 1.1 includes user correction feedback and explicit decline alongside allow/ask/deny, verifying host outcome propagation and no rejected target execution or automatic retry. Task 6.2 includes one actual installed unload/reactivate round trip: owned removal/restoration under native precedence, a single context injection and suppression of pending old-generation results. Both remain unexecuted and require the existing disposable-host authority. Internal upstream host-service tests, local disposal fixtures and package imports do not establish installed lifecycle behavior. Do not port supervisor/module-loader or background-observer suites, add permission translation, or build another test harness. + +## Risks / Trade-offs + +- [N1 privilege differs from shell] → qualify visibility, invocation identity, resources, saved grants and external access before enabling; stop on demonstrated mismatch. +- [Current source differs from released CLI] → use installed help and disposable CLI contracts; preserve both evidence identities and refuse unsupported syntax, never auto-upgrade. +- [Rich guidance is ignored] → behavioral evaluation of recovered meaning, not prompt-string assertions. +- [Collision or late child races defeat guarantees] → targeted fixtures/live checks; fail the affected path explicitly rather than build speculative machinery. + +## Migration Plan + +The future redesign is opt-in beta/next; stable behavior remains until an explicitly authorized release transition. A later authorized implementation prepares the declared minimum/types within V2 before host qualification, then the package asset list; activation still requires qualification. Before a prerelease compatibility change reaches stable main, release work must settle the stable 1.0.x maintenance branch strategy. This is a release gate, not a source-work blocker or new owner interview; no branch action is authorized here. This assignment changes no dependencies, versions or runtime settings. Exact version/tag/publication remains separate release work. Removal rolls back plugin registrations/assets only, never Beads data or schema. Parent review of compatibility-visible changes and bounded CLI gates is recorded below; no implementation dispatch follows. + +## 7. Selected code-mode daily loop (local unpublished source delta) + +### Native boundary and implementation seams + +The broader Opus memo and parent blob verification establish that pinned 2.1.1 `core/src/tool/plugin/shell.ts:195` sets `codemode:false`; `core/src/tool.ts:236–262` splits inventories. Native shell cannot run inside execute. Default code-mode-only tools disappear when execute is wholly denied. Register one `beads` namespace through the public ToolEditor with a short discovery description and eleven unpinned definitions. Effective IDs are `beads_<name>`; catalog paths are `beads.<name>`. Preserve the host hook's `beads_` exclusion and sole adapter acknowledgement. No direct duplicate registrations or pinning until measured need. If execute is denied, direct shell is an available fallback only when its own permissions and guard permit it; if neither is available, disclose unavailable capability. A hold never authorizes fallback. + +Use the pinned `Tool.Info` input/output `Tool.ValueSchema` contract, already supported through JSON Schema or Effect Schema. Keep a small operation table in continuity.ts: name, input, output, argv builder, normalizer, read/write kind. Declare object/array/union shapes and branch-required fields; enforce invariants in existing validation/normalizers because the host's raw-schema bridge checks JSON-ness, not every discriminant branch. Return structured `output` and readable `content` from the same normalized value. No new schema library/framework. Issue records preserve relevant notes, acceptance/design and separate `created_by`, `owner`, `assignee` when supplied; absence remains unknown, never synthesized identity. Unexpected CLI data becomes an explicit failure/uncertainty, not successful empty data. + +### Operation/schema and canonical argv ledger + +These are target interface contracts, not available or CLI-qualified today. All inputs reject unknown fields, actor/cwd/env/raw argv/force/sync/admin escape hatches. Nonempty IDs/keys and finite limits are validated before permission or spawn. Strings are argv values, never shell interpolation. Priority is integer 0–4; creation types initially use the established bug/feature/task/epic/chore set, subject to minimum/current contract checks. Optional filter/status/type combinations are explicit discriminated input variants, not a permissive bag of flags. The canonical permission resource is the deterministic shell-quoted rendering of the exact subcommand-first argv passed directly to `runBd`. Preserve the accepted operationArgs/execution placement: build the qualified subcommand and its arguments (including --json), then insert --actor <derived> for writes and --sandbox for ALL reads/writes immediately before any -- free-text terminator, or append them if there is no terminator. Never put global flags before the subcommand or after --. This preserves existing selective bd <subcommand> permission prefixes and suppresses automatic Dolt push; no automatic sync or compensating permission rules are introduced. The ledger below is schematic; 7.1 qualifies each exact argv and its identical canonical resource. One operation gets its own shell assertion with actual invocation agent/session/message/call and `save: []`. A get batch asserts all its canonical single-ID resources in that call; no wildcard or broad batch grant. Approved batching never authorizes later different operations. + +| Operation and bounded input | Canonical CLI family (7.1 must qualify exact optional flags/output) | Structured result contract | +| --- | --- | --- | +| `find({view: ready/list/search/blocked, parent?, assignee?, status?, labels?, text?, limit?})`, default 20, max 50 | `bd <view> <qualified args> --json --sandbox [-- <text>]`; search text only for search; view-specific supported filters only, no client postfilter masquerading as CLI completeness | `acknowledged` with view, records (id/title/status/priority/type/assignee), scope, limit and complete; or `failed` with detail. `complete` is true/false/null (unknown), scoped to the declared query, never the entire project. | +| `get({ids})`, 1–25 unique IDs | Sequential `bd show <id> --json --sandbox` by default; do not rely on multi-ID show | `acknowledged` with issues keyed by requested ID, per-ID failures and complete=false if any failed. All failed is `failed`; no failure is silently treated as missing. No claim of snapshot consistency across spawns. | +| `edges({id, direction: down/up, types?})` | `bd dep list <id> <qualified flags> --json --sandbox`, exact argv qualified in 7.1; down means prerequisites of id, up means dependents; not transitive graph traversal | `acknowledged` with typed direct records, direction, scope and complete true/false/null; or `failed`. Preserve dependency_type and unknown status. Normalize before cap (50); no inference that unknown/partial means unblocked. | +| `memories({search?})` | Existing supported `bd memories --json --sandbox -- [search]` mapping | `found` with memories as a key→value map (including an empty map), or `failed`; retain the accepted map semantics, no new record abstraction or task refs. | +| `recall({key})` | Existing supported `bd recall <key> --json --sandbox` mapping | `found` with key/value (empty is found), `missing` only on evidenced absence, or `failed`. | +| `claim({id})` | `bd update <id> --claim --json --actor <derived> --sandbox` | `acknowledged` only on authoritative record showing actual actor as assignee and in_progress; `not-claimed` only on evidenced conflict/non-claim; otherwise `uncertain` after spawn. Return issue when evidenced. No guessed conflict stderr/exit shape. | +| `note({id,text})` | `bd update <id> --append-notes <text> --json --actor <derived> --sandbox` | Existing append acknowledgement with target and result; retain original detail if notification fails. Never replace prior notes or retry ambiguous append. | +| `update({id,set:{status?,priority?,addLabels?,removeLabels?}})` | `bd update <id> <qualified flags> --json --actor <derived> --sandbox` | Nonempty selected-field update; `acknowledged` only when returned fields establish requested change; otherwise `uncertain`. Status excludes closed (use close), claim/assignee changes, force and bulk edits. Exact supported status enum comes from 7.1; reject unknown statuses. | +| `close({id,reason})` | `bd close <id> --reason <reason> --json --actor <derived> --sandbox` | `acknowledged` only with evidenced target closed; otherwise `uncertain` after spawn. Closure authority/acceptance judgment stays with caller, not a plugin macro. | +| `create({title,type,priority,description?,acceptance?,design?,parent?,discoveredFrom?,labels?})` | `bd create <title> <qualified flags> --json --actor <derived> --sandbox`; discoveredFrom maps to supported discovered-from dependency | `acknowledged` with generated ID/issue; otherwise `uncertain` with creation query identity/title even if no ID. No default assignee; `created_by` is not assignee or owner. Search/read back before any manual retry. | +| `remember({key,text})` | Existing supported `bd remember --key <key> --json --actor <derived> --sandbox -- <text>` mapping | `acknowledged`, `landed-uncommitted` with evidence, or `uncertain`; preserve commit_error/commit_indeterminate and never force/commit/retry. | + +Every result declares operation, status and target (ID/key/ID set/query descriptor); every write outcome carries actor when identity was established. Invalid input, unavailable identity, guard holds and location changes remain THROWN pre-spawn refusals, preserving accepted control flow; there is no executed:false result branch. Missing identity prevents spawning rather than fabricating attribution. Permission rejection and cancellation propagate unchanged through the existing host boundary; they are not converted into acknowledged or not-claimed values. An interrupted started write remains known uncertain in recovery state even if cancellation prevents returning a normal result. Exit zero alone is not acknowledgement. Read failures preserve detail; only evidence establishes missing. `landed-uncommitted` is not committed durability. There is one acknowledgement site per invocation; outer execute and notifications cannot duplicate it or erase original error detail. + +Planning read-only evidence: installed `bd version` reports 1.3.1 (`c1c4b642a: HEAD@c1c4b642ac1c`). Fourteen version/help captures are preserved with SHA-256 in `/opt/data/cache/scratch/opencode-codemode-astra-planning/readonly-help-ledger.json`. Current help advertises ready assignee/labels/parent/limit, blocked labels/parent (no limit advertised), show multiple IDs, and `dep list <id> --direction down|up --type <type>`. This supports candidate argv discovery only: JSON result shapes, filter completeness and mutation/conflict behavior remain unqualified. Keep sequential single-ID get; no reliance on current multi-ID advertising. Edges may use this dep-list family only after minimum/current qualification. No installations, CLI mutation experiments or live host startup occurred. Cached release prime source includes persistent memories; this alone does not prove issue-state independence, so issue-write prime invalidation remains conservative. + +Qualification rules: use limit+1 only when the exact view supports a bounded query with the intended filters/order; trim to limit and compute completeness within that query. If a supported complete response can be safely bounded in the existing runner, normalize/trim it; if CLI completeness is not established, return null and disclose why. Do not query an unbounded dataset solely to manufacture complete=true. Unsupported optional filter combinations are rejected/omitted from the public schema for the supported 1.3.1 target, not silently ignored or version-branched. Core operation support failures stop source dispatch for parent review. No further minimum increase follows without owner authorization. Claim uses the qualified returned record; if authoritative verification is missing, return uncertain plus target and actor. The caller may explicitly invoke get under its own permission check before deciding next steps; claim does not hide an additional show/approval prompt. Only qualified CLI conflict evidence can produce not-claimed. Other actors' records are never overwritten to satisfy the result contract. + +### Actors, guidance and freshness + +Derive the rendered context actor and typed-write actor from the same actual request/invocation session AND agent, never the session-default agent. Audit identity grants no permission; permission checks separately use actual context.agent. Reuse the actual invocation-derived `opencode:<readable-agent>:<digest>` identity from the accepted adapter, expose it in context and write results, and require it in direct shell writes/claims/heartbeats. No model actor input, human Git fallback, global identity mutation or literal task-agent identity. A child has its own actual executor actor and must perform its own authorized claim; a parent cannot impersonate it. Existing handoff and historical authorship rules remain. Permission checks still use actual `context.agent`, even if session default changes during an invocation. + +Render guidance per actual request capability after native filtering (`event.tools`), combining known registration state with execute visibility. Do not infer that every leaf is callable merely because execute exists: selective denials remain per invocation. Always-on core retains scope/authority, holds, ownership, actual actor, stop and honest-persistence rules. When typed composition is available, point to native namespace catalog search; when only direct shell is available, say so; when neither is available, state the limitation. Status reports registration and observed request capability, or unknown when no request evidence exists, without store-opening diagnostics or a guessed permission result. Cache guarded prime separately from actor/capability rendering, or include session/agent/capability inputs in rendered-cache identity; never leak an actor across agents. Deduplicate task-agent executor guidance without deleting distinct repository/user policy. + +The new package-owned `resources/CODE_MODE.md` explains ordered dependent writes, bounded read fan-out, projection, complete/unknown and partial failures, uncertain readback, permissions, accepted decline continuation and separate Stop/Cancel. Link it explicitly from SKILL.md: 15 corpus resources + CODE_MODE + ADR = 17 links. Keep all 30 command names; revise shared text to actual request capabilities, with daily commands using typed operations when available and advanced/admin workflows retaining direct shell plus current help. No generated/copied signature catalog in prose. Repo-specific planning/review elaboration moves to the existing on-demand workflow/delegation material; mandatory scope, attribution, ownership and acceptance rules remain always available and AGENTS authority is never weakened. + +Keep full prime initially. Source already shows prime includes persistent memories; `remember` (including possible landed/uncertain write) invalidates relevant location prime. External changes, important recovery transitions and child returns retain fresh-read guidance. Issue writes invalidate relevant task references; suppress prime refresh for these only after 7.1 establishes the selected minimum/current full-prime dependence. Until then preserve conservative relevant-location refresh. Unknown write outcomes require re-read; an acknowledged write does not generate a redundant generic “re-read your own write” cue. An ordinary unrelated tool failure alone is not evidence that Beads state changed. Keep holds, external-change detection and fresh reads on important transitions. Task operations produce task references, memory operations memory references, failed/unknown operations gap state; no `memory:` ref from an issue ID. Compaction remains reference-only. No semaphore, graph cache, poller, queue or new datastore. + +### Representative sequences and acceptance + +A. **Read-only graph triage:** find ready work with qualified parent filter if available; inspect `complete`; get at most eight selected IDs and bounded concurrent direct-edge reads via `Promise.allSettled`. Return a projection of titles, blocker IDs, unknowns and completeness. A failed edge read is unknown, not unblocked. If parent filtering is unavailable, disclose that and use supported bounded inspection; never claim a partial view is complete. Selecting a candidate does not authorize claim. + +B. **Authorized work:** get the exact assigned task and check scope/status/acceptance; await claim; stop unless acknowledged for the rendered actor. Return relevant acceptance/notes. Perform implementation/tests only in separate direct shell turns, using the same rendered actor for shell writes. Later await note with evidence; stop dependent writes on rejection or non-acknowledged outcome. Close only after independently judging acceptance and having closure authority. Authorized create of a discovered follow-up stays unassigned; uncertain create returns the identifying query and triggers find/get readback before considering retry. No write Promise.all, workflow macro or implied transaction. + +C. **Selective recovery:** concurrently find in_progress work for the rendered actor and memories matching the topic; return only candidate IDs/titles and memory keys. The model chooses relevance; next program gets chosen tasks and recalls selected keys, returning qualified excerpts. Unknown/missing/empty are distinct. A session/agent change gets the new actor and fresh context, not an implicit ownership transfer. + +D. **Restricted/failed path:** execute denied removes typed tools; direct shell fallback remains subject to shell and external-directory checks and holds. Shell denied too means no execution, not alternate routing. In a composed write sequence, propagate plain decline; rejected target has no side effect/acknowledgement. Do not catch and continue dependent writes. Host may continue later independently permissioned work under the accepted limitation. Explicit Stop/Cancel must separately prevent later dispatch; .6 owns that observation. Aborted/uncertain writes are read back on an authorized later recovery, never auto-retried. + +7.1 owns minimum/current runtime CLI evidence; 7.2 owns schema/operation fixtures and one scratch executor/catalog rerun; 7.3 owns guidance/context/package and changed-source aggregate checks. Existing .2/task 1.1 independently owns native inventory/assertion qualification, .6/task 4.2 consumes 7.2/7.3 deterministic evidence and adds its own Stop/handoff observation without duplicating identity/abort suites, and .9/tasks 6.2/6.3 own installed workflows A–D, byte/process measurements, lifecycle and final measured documentation. No token savings or model uptake is established by synthetic projection bytes. Keep prime reduction, typed heartbeat/link/comments, arbitrary edits/admin passthrough, pinning and spawn semaphore deferred. + +### Bounded verification correction (S4–S5, P1–P3) + +Under a FUTURE exact source assignment explicitly authorizing task 7.1's bounded path, use existing `scripts/install-bd.sh 1.3.1 <disposable-bin-dir>`, then select that installed binary on the contract process PATH and run existing `scripts/bd-contract.ts` for the sole supported minimum/current 1.3.1 target in its isolated repositories with the assigned executor actor. Capture exact binary identity, commands, output and results. Extend that existing script only for touched find limits/filters, dependency direction/type, selected update fields, JSON/mutation shapes, claim/conflict, generated create ID, close state and prime before/after issue writes. The future scoped assignment covers this path without per-command reapproval; it is NOT authorized during this planning correction. Do not install into the operator environment, increase the owner-selected 1.3.1 minimum further or use the project store. Preserve already-run 1.3.0 evidence as historical; no further 1.3.0 qualification or compatibility repair is required. The SDK ledger is a table citing captured contract output, not a second qualification tool. Help/source aids discovery but cannot establish passing runtime contracts: omit/reject unsupported optional filters, stop unsupported core operations for parent decision, and retain conservative prime refresh until runtime dependence evidence supports narrowing. + +There is no maintained product pinned-host executor/catalog seam. Task 7.2 performs one rerun of the existing scratch executor/catalog probes (`/opt/data/cache/scratch/opencode-codemode-shape-review/check-output-contract.ts` and `/opt/data/cache/scratch/opencode-codemode-product-wgv/bridge-probe.ts`) against the exact .10 candidate's actual definitions. Preserve original scratch evidence and record the new exact host pin, candidate, commands and results durably in the SDK contract. Product tests assert every definition declares output and normalized fixture cases satisfy their declared result branch invariants. Do not vendor upstream runtime, add a dependency or build a host replica. Real installed catalog/executor confirmation stays with .2/.9. + +Use one table-driven case set per operation for exact argv/resource/normalization and evidenced declared branches. Retarget existing shared negative boundary tests ONCE across a representative multi-spawn get and a representative write: identity, save:[], external location, hold before/after approval, location change, abort during approval/after spawn, no retry, notification detail and sole acknowledgement. Add only necessary partial/completeness/uncertain claim/create cases. Production still performs guard/identity/location/abort rechecks before EVERY child spawn and after approval. No operations × versions × modes × permission-outcomes matrix or downstream tests of upstream interpreter, permission wildcard engine, scheduling, replay, readiness, routing or consistency. + +Task 7.3 checks representative capability states: typed available; execute denied with shell available; neither available; guard hold. Preserve actor isolation across sessions/agents and actual request capability correctness. Run the existing cheap full regression suite, typecheck and package smoke. .6 consumes exact 7.2/7.3 deterministic evidence and adds its own Stop/handoff observation, not duplicate deterministic identity/abort suites; explicit cancellation must still prevent later dispatch. .9 retains installed/model acceptance. No graph or task ownership change is required. diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/execution-map.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/execution-map.md new file mode 100644 index 0000000..4a0e51b --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/execution-map.md @@ -0,0 +1,46 @@ +# Execution map + +Current local unpublished planning over published `072429f1a7723ade23e756a921924d9d0acacf31`, branch `openspec/improve-beads-workflow-parity`. This indexes actual Beads records, not a second tracker. [Tasks](tasks.md), [design](design.md), [owner decisions](owner-decisions.md) and [requirement coverage](reconciliation.md) own the current scope. Historical mapping/review at `84ab18e76be43f9a733bd799c5a506fed03ce7f6` / `dd56cf12a141d224dadb247ebd5f7754bd053ecd` remains evidence for its earlier candidate, not approval of this delta. + +## Authority and status + +Planning reconciliation worker `codex:oc-plan` claims only `opencode-beads-wgv`; handoff leaves it in_progress with that assignee for parent review. Planning owner `opencode-beads-d9n` remains in_progress assigned to Snarkimedes. Epic `opencode-beads-vuy` remains blocked/unassigned. Existing .2 is blocked/assigned codex:oc-host; .6 blocked/assigned codex:oc-flow; .9 blocked/unassigned. Their execution ownership is not transferred by planning edits. Closed source/review records stay intact. New `opencode-beads-vuy.10` is blocked/unassigned, created_by codex:oc-plan with no stored owner field; author, account owner and assignee are distinct. + +All affected implementation records retain implementation_authorized=false. Parent review and a separate exact dispatch are required before source execution or resumed host/model qualification. No acceptance, closure, publication, activation or release follows. Readiness alone is not permission. + +## Exclusive task ownership and blocking graph + +Each task appears exactly once in the ownership column. Blocking edges run from dependent to prerequisite. Parent-child and discovered-from edges preserve structure/provenance, not dispatch authority. + +| Actual child ID | Owned OpenSpec tasks | Deliverable | Blocking prerequisites | +| --- | --- | --- | --- | +| `opencode-beads-vuy.1` | 1.2 | accepted types history | none | +| `opencode-beads-vuy.2` | 1.1 | remaining native inventory/permission qualification | `opencode-beads-vuy.1`, `opencode-beads-wgv`, `opencode-beads-vuy.10` | +| `opencode-beads-vuy.3` | 2.1 | accepted corpus assets history | none | +| `opencode-beads-vuy.4` | 3.1, 3.2 | accepted five-operation/context history | `opencode-beads-vuy.1` | +| `opencode-beads-vuy.5` | 2.2, 3.3 | accepted package/lifecycle history | `opencode-beads-vuy.1`, `opencode-beads-vuy.3`, `opencode-beads-vuy.4` | +| `opencode-beads-vuy.6` | 4.1, 4.2 | accepted 4.1/deterministic history; remaining Stop/handoff | `opencode-beads-vuy.3`, `opencode-beads-vuy.4`, `opencode-beads-vuy.5`, `opencode-beads-vuy.10` | +| `opencode-beads-vuy.7` | 5.1 | accepted drift history | none | +| `opencode-beads-vuy.8` | 6.1 | accepted aggregate historical candidate | `opencode-beads-vuy.5`, `opencode-beads-vuy.7` | +| `opencode-beads-vuy.9` | 6.2, 6.3 | installed/model acceptance and measured closeout | `opencode-beads-vuy.2`, `opencode-beads-vuy.6`, `opencode-beads-vuy.8`, `opencode-beads-vuy.5`, `opencode-beads-vuy.10` | +| `opencode-beads-vuy.10` | 7.1, 7.2, 7.3 | cohesive code-mode source delta | `opencode-beads-wgv` | + +Nine original checked identities/status remain accepted; 1.1/4.2/6.2/6.3 stay unchecked and 7.1–7.3 are new unchecked work. Existing open owners are qualification boundaries, so .10 owns the coherent source slice rather than overloading .2, reopening closed .4/.5/.8, creating a second epic or per-tool records. The task total is derived from tasks.md (currently 16), requirement total from specs (currently 19). + +## Phases and independent acceptance + +.10 begins with supported minimum/current runtime CLI evidence through the existing disposable install/contract scripts under future scoped authority (7.1), then typed API/schema fixtures (7.2), then context/guidance/package integration and changed-source aggregate checks (7.3). No .10 dependency on .2 or .6: source preparation must not wait for activation it enables. .10 does depend on wgv planning review; wgv has no blocking dependency on qualification. The existing .2 → wgv dependency remains, with .2 → .10 added for reviewable new shapes. Planning approval alone is not dispatch. + +.6 remains the sole owner of all 4.2. Its historical deterministic contribution at `a5d6b2bea779259ba9aa3175ab86fb2f4f2b862b` remains accepted for that candidate; the new delta is covered by 7.2/7.3 deterministic evidence, which .6 consumes before adding its own Stop/handoff observation; .6 does not duplicate deterministic identity/abort suites. The historical related .8 → .6 evidence link is preserved. Closed .8/6.1 does not accept new source: 7.3 owns changed-source aggregate validation. .6 live subphase additionally waits for successful .2/1.1 and separate disposable-host/model authority; no whole-child .6 → .2 edge serializes its evidence consumption. Explicit Stop/Cancel during multi-write programs and approval waits must prevent later dispatch. The older plain-decline whole-step-termination failure remains failed-but-owner-accepted, never a Stop pass. + +.9 waits for .10, .2, .6 and its retained accepted prerequisites. It runs representative design workflows A–D with actual catalog signatures, all 17 installed links, lifecycle, real actor agreement, scope, honest uncertainty and byte/process measurements. 6.3 follows 6.2; no final acceptance from fixtures, synthetic projection ratios, merged code or tool counts. Planning/review acceptance is independent of implementation completion. + +## Preserved scope and evidence boundaries + +Exact host 2.1.1 source `ba546ec1598a70db2570e9c6258028d955ba8f58`, admission `>=2.1.1 <2.2`; operator 2.0.29 untouched. Beads minimum >=1.3.1 and installed1.3.1; prior 1.3.0 support/qualification is retired by explicit owner decision. The immutable historical/current corpus remains 73/74 members with all dispositions; new CODE_MODE delivery augments it: 16 resources plus ADR, 17 explicit links, all 30 human names and overrides. No inventory history is rewritten. + +Native shell is direct-only; execute-denied agents lose typed code-mode exposure. Actual invocation permission identity is separate from the rendered audit actor. Keep guard/hold, location access, after-approval/pre-spawn rechecks, cancellation, sole acknowledgement and upstream CLI ownership. No upstream patch, routing/confinement framework, runtime install, model experiment or new datastore. + +## Verification custody + +Start custody: `/opt/data/cache/scratch/opencode-codemode-astra-planning/` contains protected-manifest.json, pre-dispatch-status.txt, pre-dispatch.diff and full tracker preimages. Worker readbacks, original task identities and final verification are saved there alongside the final artifact manifest. Original source/review bytes, historical authored notes, closed records and unrelated graph edges remain protected. No new commit SHA is claimed. Parent reviews this local unpublished package, chooses compatibility-visible tool-name/shared-prompt disposition, and separately authorizes the smallest next assignment: .10/7.1 contract discovery before any new argv reliance. diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/owner-decisions.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/owner-decisions.md new file mode 100644 index 0000000..c8ac4ea --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/owner-decisions.md @@ -0,0 +1,75 @@ +# Owner decisions — reconciled planning selections + +## Installed lifecycle probe correction — owner-authorized continuation + +After the pinned lifetime investigation, Matt authorized correcting the probe and proceeding with `.9`. Task 6.2's unload/reactivate criterion means actual plugin activation-scope cleanup/removal within the existing location, not disappearance from a replacement registry after `/api/location/reload`. Whole-location reload permits the running step to retain its borrowed graph until the next boundary. Verify actual disposal ordering and suppression of pending output after that cleanup; preserve the original stronger location-reload failure as historical failed evidence, not a retroactive pass. This corrects the observation boundary, not product source or a promise of immediate revocation at routing replacement. Resume remaining installed/model qualification and then 6.3 only after 6.2 passes. Operator activation, source repair, release, archive, publication and closure are not delegated to the qualification worker. + +Recorded 2026-10-18 UTC from Matt's direct response to the Opus review on published candidate e74b507 (review report published at acf5a68). These are owner planning selections, not implementation acceptance or release authority. Proposal/spec/design/task/inventory reconciliation is now recorded in [reconciliation.md](reconciliation.md); earlier unselected alternatives remain historical. + +## Operative Beads minimum — owner correction during implementation + +Matt explicitly selected: “1.3.1 should be the minimum beads version not 1.3.0.” Beads 1.3.1 is both the supported minimum and current qualification target for this opt-in release. Reconcile package metadata, the runtime minimum guard, user guidance and affected assertions to that floor; reject older binaries normally. Remove further 1.3.0 compatibility work from task 7.1 and the source acceptance gate. Preserve already captured 1.3.0 successes/failures as historical evidence, neither passing nor blocking the new supported target; do not repair its fixtures or add compatibility branches. No operator binary/store change is authorized or needed. All other code-mode, permission, attribution, cancellation and truthful-outcome guarantees remain selected. + +The already-authorized same Sol/high .10 worker continues source tasks 7.1–7.3 under this correction. Parent reconciles this narrow active-spec/tracker policy change; worker updates package/guard/docs/tests and verifies 1.3.1. The previous Opus plan verdicts retain their exact historical candidates; this correction narrows compatibility scope rather than changing the architecture or adding a review campaign. + +## Operative code-mode planning reconciliation — 2026-10-21 + +Matt authorized Astra/medium to reconcile the broader Opus recommendations into this active change and Beads. Actual planning executor: `codex:oc-plan`, bounded record `opencode-beads-wgv`. The selected planning direction is the [broader architecture memo](../../../../docs/reviews/opus-code-mode-architecture-wgv.md), including its parent verification; the six-tool recommendation and narrow READY remain historical only. This local unpublished revision is based on published HEAD `072429f1a7723ade23e756a921924d9d0acacf31`, not a new published candidate or independent approval. + +Adopt the eleven-operation daily-loop namespace, host-derived executor actor visible to the model, capability-aware deduplicated guidance, operation-aware references and conservative targeted freshness. Retain all 30 human command names/user overrides, full advanced/admin coverage and full prime initially. Canonical actor means the actual host-derived executor identity, not the literal `beads-task-agent` name or mandatory delegation. Primary agents keep their own actor; delegated workers receive their own. Permission identity remains actual invocation `context.agent`. + +This authorizes planning/spec/Beads reconciliation only. Product implementation, resumed host/model qualification, activation, publication, acceptance and closure remain unauthorized (`implementation_authorized=false`). Nine accepted task markers remain historical acceptance; new tasks 7.1–7.3 own the source delta. Remaining 1.1/4.2/6.2/6.3 remain unchecked. Parent review must confirm the compatibility-visible replacement of the withheld five-tool names by the eleven-operation namespace and changed command prompt wording before source dispatch. Unsupported CLI details are bounded 7.1 qualification gates, not assumptions or a request for Matt to design argv. + +## Historical code-mode decision — accepted limitation and investigation pause + +Matt explicitly selected: “Let's accept the upstream limitation now, but pause for a quick code-mode investigation to avoid having to ship twice.” Keep code-mode composition. Accept OpenCode 2.1.1's observed loss of whole-step termination after a nested plain permission decline; do not promise that rejecting one operation stops the composed program or subsequent model steps. The adapter still propagates assertion rejection unchanged, never executes the rejected target, preserves correction feedback, does not automatically retry or acknowledge success, and checks permissions independently for every later operation. Explicit Stop/Cancel, scope, attribution and honest persistence guarantees remain selected. No downstream error translator, upstream patch or speculative future API. + +The original failed stronger decline case remains failed historical evidence, now an owner-accepted host limitation; it is not relabeled as passed. Upstream-fix adoption is a normal supported-version/retest decision, not a shipping prerequisite. The separate undeclared structured-output schema defect is our integration responsibility and is NOT accepted by this limitation decision. + +Pause remaining qualification, activation and completion for one bounded product investigation, `opencode-beads-wgv`, before resuming tasks 1.1/4.2/6.2/6.3. Research covers code-mode discovery/signatures, structured outputs, graph workflows, selective results, safe composition and the smallest coherent pre-release source/guidance/acceptance delta. This authorizes research/report, isolated scratch probes and parent decision/spec/tracker reconciliation only; no product implementation, host/model qualification, public filing, commit/push/sync, release or operator changes. Existing accepted source history and task checkmarks remain intact; no new epic or implementation task graph is implied. The current five-tool interface is under investigation, not a constraint requiring a narrow continuity-only product. See the investigation report when available at `docs/code-mode-product-investigation.md`. + +## Operative upstream responsibility boundary — 2026-10-20 + +Matt explicitly corrected the vuy.4 implementation scope: integrate normal supported permissioned Beads CLI behavior. Beads owns database, routing and consistency semantics; routing after a local miss is intended behavior, not an observed defect or a plugin confinement prerequisite. Remove speculative checkpoint/show refusals, backend restrictions, final-store attestation, upstream route-disable remedy requests and local repair/metadata/routing frameworks. Preserve actual invocation/project context, OpenCode permissions for the exact operation and explicit location access, executor attribution, cancellation, honest outcomes and the existing migration guard's config-only context probe. Normal CLI equivalence does not promise absolute filesystem confinement. + +This correction authorizes tasks 3.1/3.2 source continuation and the narrow current planning reconciliation, not other children, live 1.1 qualification, activation, acceptance, closure or publication. It supersedes stronger earlier implementation interpretations without rewriting historical reviews. The parent-added AGENTS.md upstream boundary must remain byte-identical during this work; prior setup custody statements below are historical. Keep the 17 requirement titles, 13 task IDs, nine-child ownership graph and 2.1.1 baseline unchanged; 3.1/3.2 markers stay unchecked until parent acceptance. + +## Native registry resolution — parent source acceptance after incorporated Opus findings + +Matt approved normal namespaced OpenCode precedence, then requested Opus review before implementation resumes. The review returned REVISE on small plan corrections while endorsing that boundary. Matt requested incorporating those findings before final resolution. Commands register all 29 workflows plus status; initial collisions are diagnostic-only, not permanent withholding. Ordinary configuration/file overrides win through native post ordering and their removal restores package entries. Other plugin-transform conflicts follow native order without a universal preservation promise. Skills retain public per-rebuild candidate checks, not a redundant initial gate. Status reports observable effective names/descriptions, real lookup/registration outcomes and the limits of ownership/identical-description detection. Abort/disposal, permissions, attribution and user-owned files remain protected. + +Design, workflow-access scenarios, task 2.2 and operative vuy.5 acceptance include these corrections. After incorporation, Matt authorized the same Sol High worker to implement them. Parent reviewed the corrected source and independently verified 150 tests, typecheck and package smoke; task 2.2/3.3 source is accepted. Actual callback membership/invocation, override/rebuild/removal, status limits and disposal replace zero-registration success fixtures. Pinned package/config ordering remains distinct from unpinned suffix-reactivation evidence; no live qualification is implied. The original Opus report/verdict is preserved in docs/reviews/opus-registration-resolution-review-vuy5.md; parent acceptance is not a new independent READY verdict. Live activation and later children remain separately gated. + +## Upstream test gap resolutions — accepted acceptance refinements + +Matt approved folding the bounded upstream plugin test comparison into existing acceptance. Add correction-with-feedback and explicit-decline outcomes to task 1.1 (`opencode-beads-vuy.2`), with no rejected target execution, false success or automatic retry. Add one actual installed unload/reactivate round trip to task 6.2 (`opencode-beads-vuy.9`), checking owned removal/restoration, native user-override precedence, one context injection and no late old-generation result. See [the source assessment](../../../../docs/reviews/upstream-plugin-test-gap-assessment.md). + +These are acceptance-case refinements, not demonstrated product defects or new tasks. Preserve the 13 task IDs, their checkmarks, exclusive child ownership and dependencies. Both live cases remain unexecuted; this approval authorizes the bounded planning/tracker reconciliation, not host installation, model experiments, source implementation, dispatch, publication or activation. Existing host-owned supervisor, loader, permission-conversion and background-notification suites are not downstream implementation requirements. + +## Accepted direction + +1. **Parity reference: marketplace main.** Compare against the complete Claude Beads marketplace integration on upstream's default branch, resolved to immutable commits for each review/evaluation. A moving branch name alone is not evidence identity. Retain the release-pinned inventory as historical evidence, not the sole current parity target. Characterize the installed-vs-release append-notes change identified by Opus. +2. **Host and architecture: OpenCode 2.0.32 or later with N1.** Select the native continuity adapter direction, not N0-first. This does not declare arbitrary future major releases compatible or authorize a dependency/runtime upgrade. Qualify the chosen minimum and permissions/lifecycle contract; preserve fallback reporting if a concrete guarantee cannot be met. +3. **Release/behavior direction: beta or next, canonical non-mandatory-atomic execution.** This planned plugin version is an opt-in prerelease direction; exact version and distribution tag remain engineering/release decisions requiring normal publication authority. Do not replace stable defaults for existing consumers silently. Atomic claim and per-bead validation remain; invocation scope is not universally one bead. Follow explicit assignment/goal authorization and preserve stop/approval constraints. Optional one-bead orchestration mode may remain, not the mandatory base. No package version/tag has changed. +4. **Upstream source information: non-gating existing check pattern.** Following Matt's explicit concern about regular work on main and lagging tags, completed main comparisons succeed changed or unchanged. Report development-source information/review links without warning/error annotations, product CI failures, mandatory immediate review/adoption or release gates. Stable tagged CLI compatibility remains separate. Retrieval failures are unavailable evidence, not no change; any collector failure is isolated from product/release gates. Changes needing unreleased features may be deferred until verified inclusion in a supported release. Reuse the existing scheduled pattern only with workflow-edit authority; no GitHub issues or issue-write permissions. Baseline advancement remains human-reviewed. This supersedes earlier failed-drift interpretations, not historical review evidence. +5. **Primary objective: overall harness-native DX.** Better note and memory behavior are examples, not the entire problem. Aim for a coherent, tightly integrated OpenCode experience across discovery, command/task flow, contextual guidance, recovery, persistence, delegation, approvals, diagnostics and lifecycle. Claude is the behavioral baseline, not the implementation blueprint. Exploit supported native and selectively adopted experimental capabilities where they improve observed DX. The user suspects Claude-harness optimization contributes to the difference; causality remains unproven. + +## Operative host selection — 2026-10-20 + +Matt directly selected OpenCode **2.1.1**, superseding the earlier 2.0.32 minimum above while retaining all other product choices. Source pin: `ba546ec1598a70db2570e9c6258028d955ba8f58`. Engineering admission policy is `>=2.1.1 <2.2` because 2.1 changes public replay/compaction contracts; exact 2.1.1 is the unexecuted disposable qualification target, not a tested compatibility claim. Future patch candidates require normal qualification; no arbitrary minor compatibility. See [delta memo](../../../../docs/opencode-2.1.1-contract-delta.md). + +Authority covers planning/docs/validator and existing scoped tracker updates only, followed by **PAUSED FOR REVIEW**. No implementation, dependency installation, host upgrade, dispatch or publication. Operator 2.0.29 remains untouched. AGENTS.md/.codex setup is now tracked at `907232a393443a0f0d59133ffb0e2612f3fd9026` and must remain identical; earlier dirty-setup descriptions are historical. + +## Follow-up dispositions + +- **Ownership accepted with namespace below:** package-owned richer guidance and commands; leave the upstream-owned shared `beads` skill untouched and preserve explicit user customizations. Earlier name alternatives are superseded by `oc-beads`. +- **Corpus groups accepted in follow-up:** Matt concurred with the proposed broad grouping: core daily workflows adapted; advanced guidance available on demand without enabling automation; supported administrative workflows explicitly accessible under normal authority; obsolete/host-specific material corrected or replaced, not copied blindly. This rejects a minimal command subset as the assumed target. Exact file mapping is recorded in the inventory against marketplace main, with no silent capability exclusions or implementation authority. +- **Namespace accepted in follow-up: `oc-beads`.** Matt selected this exact namespace slug after the ownership explanation. Use `oc-beads` for the package-owned rich skill and `oc-beads:<command>` for its namespaced command surface (for example `/oc-beads:ready`), subject to native syntax qualification. Preserve the upstream-owned shared `beads` skill and explicit user customizations. This is integration naming, not an npm-package rename or automatic approval to rename existing worker agents. Previous `beads-workflow`, `opencode-beads` skill and `/beads:*` recommendations are superseded for the new owned namespace. Planning artifacts now apply this namespace; implementation and installed-host syntax acceptance remain unexecuted. + +## Review corrections to reconcile + +The original Opus verdict was REVISE. The reconciliation record addresses F1–F5 with source evidence and exact artifact/task mappings: installed/release/main differences, typed-tool visibility/agent/save/external-access, reference-only compaction and check-based drift. This is the planning agent’s reconciliation, not new independent reviewer approval. The [50919ae review](../../../../docs/reviews/opus-planning-review-50919ae.md) is preserved with its REVISE verdict. Final B1/recommendation corrections are mapped in reconciliation; no owner selection is reopened. Dependency preparation now precedes host probes, with separately authorized disposable live-host prerequisites. Runtime and behavioral acceptance remain unexecuted; no new infrastructure or wholesale analysis restart is required. + +## Authority + +The planning bead is `opencode-beads-d9n`. Keep it open. The parent owns publication of the reconciled artifacts for review; implementation, prerelease publication, merging, runtime upgrades and deployment remain unapproved. Current local AGENTS.md/.codex integration setup is separate from the product candidate. diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/proposal.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/proposal.md new file mode 100644 index 0000000..d7fb70e --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/proposal.md @@ -0,0 +1,39 @@ +# Proposal + +## Why + +The historical comparison established setup-lifecycle parity, not the complete Claude marketplace experience. The selected redesign aims for coherent native discovery, task execution, recovery and durable learning; Matt's reported note/memory gap motivates evaluation but does not establish host or model causality. + +## What Changes + +- Require Beads CLI `>=1.3.1`, explicitly replacing the former 1.3.0 floor. Qualify 1.3.1 as both minimum and current target; preserve earlier 1.3.0 observations as historical, with no further compatibility work or operator upgrade. + +- Adopt the complete marketplace-main integration as the behavioral reference, resolved for this evaluation to `4a67d0d0288c1b4ee23e17fc896f47616931ba2d`. Preserve the separate 73-file v1.3.1 inventory and characterize installed/release/current differences. +- Deliver N1: an eleven-operation typed daily-loop namespace, awaited context/tool hooks, reference-only compaction guidance, observational events, package-owned `oc-beads` skill/resources and `oc-beads:<command>` commands. Supply all selected surfaces from npm; bd remains the external executable/database owner. +- Adapt core daily workflows, expose all advanced guidance on demand without enabling automation, make supported admin workflows accessible under normal permissions, and replace obsolete/host-specific/broken content with documented native equivalents. The inventory maps every member. +- **BREAKING, opt-in beta/next only:** target OpenCode `>=2.1.1 <2.2` and replace mandatory one-bead/command-count delegation with scope-bounded continuation. Exact assignments stop; authorized batches/goals may continue with atomic claims and per-bead validation. Stable consumers do not silently migrate. No package/version/tag changes are made here. +- Preserve restricted-agent visibility, invocation identity, narrow saved approvals, location restrictions, holds, cancellation and honest persistence failures. An unqualified N1 guarantee blocks the affected path; N0 is a historical alternative, not a silent degraded default. +- Extend the existing scheduled pattern with advisory marketplace-main source comparison and immutable review links. Completed comparisons succeed despite ordinary main development; they do not gate product CI, adoption or release. Incomplete comparison is unavailable evidence, distinct from no change, without gating product/release checks. Tagged CLI compatibility remains separate. Human-reviewed baseline advancement only; no GitHub issue creation or issue-write permission. +- Apply the [2.1.1 contract delta](../../../../docs/opencode-2.1.1-contract-delta.md): adopt native replay fields and retained-history semantics without replacing host compaction. Admit only the 2.1 patch line (`>=2.1.1 <2.2`); exact 2.1.1 is the unexecuted live target, not a proven compatibility result. Future patches are candidates for normal source/acceptance checks, not automatically tested versions. +- Retain code-mode composition with the owner-accepted OpenCode 2.1.1 nested plain-decline continuation limitation: the rejected operation never executes or receives success acknowledgement, but whole-step termination is not promised. Later operations retain their own permissions; explicit Stop/Cancel qualification remains required. Preserve the failed stronger case as historical evidence. The broader investigation is now reconciled into tasks 7.1–7.3 for source preparation; remaining qualification stays paused for parent review and separate dispatch. This is not authority for an upstream patch or implementation. +- Qualify source contracts, disposable deterministic fixtures and live/model outcomes separately, including fresh-session recovery, discovery and authorized continuation. Architecture and tool-call counts are not behavioral proof. + +- Replace the withheld five-tool surface with `beads.find/get/edges/memories/recall/claim/note/update/close/create/remember`, declared structured results and supported fixed argv. Surface actual host-derived actors to typed and shell workflows; use request capability-aware guidance. Native shell is direct-only, never callable inside execute. Add a planned CODE_MODE resource (16 resources plus ADR, 17 links), retaining all 30 human commands and full prime pending measurement. + +## Capabilities + +### New Capabilities + +- `work-continuity`: meaningful checkpoints, reusable knowledge, recovery and delegated evidence. +- `workflow-access`: complete native discovery/delivery, scope and authority, diagnostics and lifecycle. +- `upstream-review`: path-scoped marketplace-main comparison and human-reviewed baseline maintenance. + +### Modified Capabilities + +None; canonical specs are empty. Existing code remains evidence, not an automatic specification of desired behavior. + +## Impact + +Planning edits only. Later authorized implementation touches guidance/agents, host/context/guard/instructions, the bounded runner, narrow continuity/discovery modules, packaged assets, focused tests, compatibility declarations and documentation. The scheduled workflow is a future implementation seam, read-only in this assignment. No runtime/dependency upgrades or publication are authorized. Scoped existing Beads planning records may be reconciled; implementation remains unauthorized. + +[Owner selections](owner-decisions.md) record the selected planning direction and compatibility-visible changes for parent review. [Design](design.md), [tasks](tasks.md) and [reconciliation record](reconciliation.md) separate those selections from unexecuted acceptance checks. Implementation requires a subsequent request; release publication requires separate authority. diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/reconciliation.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/reconciliation.md new file mode 100644 index 0000000..eccdb96 --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/reconciliation.md @@ -0,0 +1,167 @@ +# Planning reconciliation + +## Bounded Opus correction pass — S1–S5 and P1–P3 + +Matt authorized incorporation of the [plan review findings](../../../../docs/reviews/opus-code-mode-plan-review-wgv.md). Original REVISE and frozen candidate/reports remain historical and immutable; this is a corrected local candidate awaiting parent finding-scoped recheck, not a new review verdict. Executor codex:oc-plan, session 01a15ee8-9655-7f02-b8dd-3b8541d3e42f. Eleven operations, 16 task IDs, nine accepted task texts/marks, seven unchecked IDs, ownership and dependency graph are unchanged. + +| Finding | Correction and artifact | Tracker scope | +| --- | --- | --- | +| S1 HIGH | Design7 ledger and work-continuity guarantee restore subcommand-first exact resources, --sandbox on ALL reads/writes, derived actor on writes after operation arguments but before any -- terminator; no automatic sync or compensating permission rules. Task7.2 owns table verification. | .10; epic/planning current decision | +| S2 MODERATE | Design7 and work-continuity retain thrown invalid-input/identity/hold/location refusals and unchanged permission rejection/cancellation; no executed:false branch. Started-write recovery uncertainty survives absent normal return. | .10 | +| S3 MODERATE | Workflow-access actor scenario, design7 and7.3 derive matching context/tool/shell actor from actual request/invocation session AND agent; audit identity grants no permission, assertions separately use actual context.agent. | .10; .6/.9 consume actor evidence | +| S4 MODERATE | Task7.1/design7 specify future-authorized existing install-bd.sh disposable binaries and existing bd-contract.ts on the owner-selected 1.3.1 minimum/current target for JSON/mutation/claim/create/close/prime facts; earlier dual-version scope is superseded, not retroactively passed; SDK ledger cites captured output. Help is discovery only. No per-command reapproval within that future exact assignment, no install/contract authority now. | .10; d9n/vuy gate | +| S5 LOW | Task7.2/design7 replace nonexistent product executor seam with one rerun of existing scratch probes against actual candidate definitions, durable SDK pin/commands/results; product tests check declared outputs and branch invariants; installed evidence stays .2/.9. | .10, .2, .9 | +| P1 MODERATE | Task7.3/design7 use four representative capability states, retaining actor isolation and request correctness without cross-product testing. | .10 | +| P2 LOW | Task7.2/design7 use per-operation table cases plus shared negatives once across representative multi-spawn get/write, while production rechecks every child spawn. Necessary partial/completeness/uncertain cases and cheap full regression gates remain. | .10 | +| P3 LOW | Task4.2/design7/execution-map make .6 consume7.2/7.3 deterministic evidence and add its own Stop/handoff observation, not duplicate identity/abort suites. Cancellation must still prevent later dispatch. | .6, .10, .9 | + +Tiny optional ambiguities resolved: memories retains the accepted key→value map; uncertain claim returns target/actor for caller-explicit get rather than hiding a second approval. No dependency change for the optional .2 ordering observation. No new tasks, framework, maintained upstream runtime seam or implementation. Correction custody/output uses new correction-prefixed files under `/opt/data/cache/scratch/opencode-codemode-astra-planning/`; original manifests and verification remain intact. All source/qualification stays blocked pending parent acceptance and explicit dispatch. + + +## Current local unpublished revision — 2026-10-21 + +Broader Opus direction supersedes six-tool sufficiency and narrow READY. See design section 7 and owner decisions for selected direction, CLI gates and parent review. Historical sections below retain their dated scope. Nine accepted markers are preserved; tasks 7.1–7.3 add one cohesive source delta, independent of host activation. There are now 16 tasks and 19 requirements; counts are derived by the validator. Existing 1.1/4.2/6.2/6.3 stay unchecked. The historical 73/74-member corpus remains unchanged; planned delivery adds CODE_MODE, yielding 16 resources plus ADR and 17 explicit links. No runtime asset or source is authored here. + +| Broader recommendation | Owning artifact and tasks | +| --- | --- | +| D1 daily-loop schema/argv/results | design section 7; work-continuity Typed daily-loop composition; 7.1, 7.2; installed 6.2 | +| D2 actual host actor, optional delegation | owner decisions; workflow-access capability requirement; 7.2, 7.3; 4.2, 6.2 | +| D3 namespace/code-mode-only; direct shell fallback | design native boundary; restricted-agent requirement; 7.2, 7.3; 1.1 | +| D4 deduplicated guidance/CODE_MODE | design guidance; workflow-access; 7.3; 6.2 | +| D5 targeted freshness/refs | design freshness and CLI ledger; 7.1, 7.3; 4.2, 6.2 | +| D6 full prime pending measurement | owner decisions/design; 7.3, 6.2; no reduction authorized | +| D7 all 30 human names and overrides | proposal/design/workflow-access; 7.3, 6.2 | +| Accepted nested decline versus explicit Stop | existing owner correction preserved; 1.1, 4.2, 6.2 | + +Parent review is needed for compatibility-visible tool-name replacement/command wording before dispatch; CLI specifics remain bounded engineering qualification, not invented contracts. This reconciliation grants no implementation, resumed qualification, publication, activation or closure authority. + +## Historical reconciliation — 2026-10-18 + +## 2026-10-20 owner correction — upstream responsibility + +The current owner decision supersedes the vuy.4 final-store confinement interpretation and its proposed upstream prerequisite. This is scope reconciliation plus authorized 3.1/3.2 source continuation, not an independent review verdict or live qualification. + +| Earlier implementation interpretation | Current contract and correction | +| --- | --- | +| Refuse checkpoint/show until final-store attestation or a routing-disable switch exists | Restore normal fixed append/show operations. Beads owns supported routing, including local-miss fallback; no upstream incident was demonstrated. | +| Snapshot backend/metadata/routes and restrict execution to embedded storage | Remove those gates and repeated snapshots. Preserve inherited CLI context and backend selection; no environment clearing, metadata/resolver clone or upstream repair framework. | +| Treat every internal Beads target as a plugin external-access target | Check actual session/project cwd and normal OpenCode permissions for the exact operation and explicit invocation/location access. This does not promise absolute filesystem confinement. | +| Conflate source completion with activation or persistence guarantees | Retain attribution, migration guard via config-only context, bounded cancellation, honest acknowledgements, context/policy retention and reference-only compaction. Live 1.1 remains unexecuted and production activation withheld. | + +The operative vuy.4 description/design/acceptance/metadata are reconciled to this boundary; historical notes and reviews are preserved. All 17 requirement titles, 13 task IDs and nine child owners remain unchanged. Tasks 3.1/3.2 stay unchecked until parent accepts the source candidate; no other child or publication/closure authority is granted. + +## Native registration review reconciliation — parent-accepted source resolution + +Opus review `opencode-beads-cny`, session `95b72364-ff32-423f-a63d-3e007f05768a`, returned REVISE for small plan corrections, endorsing normal host precedence. The owner requested incorporating the findings before final resolution. Current design, workflow-access scenarios and task 2.2 now require all 30 command registrations, diagnostic-only initial collision checks, native configuration/file override precedence with restoration after removal, abort/disposal and actual status/registration fixtures. The stale initial skill gate is removed from the correction plan while retaining the per-rebuild candidate check. Status reports description-mismatch evidence without claiming owner identity or detecting identical-description overrides. Ordinary user files and all actual permission boundaries remain protected; no upstream fix or collision manager is introduced. + +Following planning incorporation, Matt authorized the same Sol High worker to implement the corrections. The completed candidate registers all 30 commands, restores removed overrides, reports actual status and preserves owned lifecycle. Parent reviewed the source and independently verified 150 tests (0 failures), typecheck, package smoke, strict global OpenSpec and whitespace checks; task 2.2/3.3 source acceptance is reconciled in tasks.md. The original Opus REVISE report remains intact; this is parent-verified remediation, not a new independent READY verdict. Pinned ordering and unpinned suffix-reactivation evidence remain separate. No live outcome, privileged continuity activation or later child authorization is implied. + +## Main-branch development is not a compatibility gate + +Matt explicitly required that the actual source check be insensitive to ordinary main development, including when tagged releases lag. Current proposal, design, upstream-review scenarios, task 5.1, execution map, artifact policy and operative .7 acceptance now require completed changed/unchanged main comparisons to succeed with informational output only. No drift warning/error annotation, mandatory immediate review/adoption, product CI failure or release gate follows from main changing. Collector failures remain distinct unavailable evidence, not successful no change, and must not gate product/release checks. Supported tagged CLI compatibility tests retain their separate real failure semantics. A change needing an unreleased feature may be reviewed/deferred until verified tagged inclusion; dates/version labels alone do not prove inclusion. Earlier failed-drift review descriptions below are historical and superseded. Regression fixtures must prove main advancing with an unchanged tagged release leaves compatibility/release state unchanged. This correction neither advances the reviewed baseline nor authorizes workflow/CI writes or deployment. + +## Owner-approved process correction + +After the 2.1.1 READY review, Matt approved adopting Docker Lab's explicit executor-attribution policy and proportionate tracking of substantive planning/independent review work. Project-owned AGENTS.md now carries that policy outside generated blocks. Existing task IDs 3.1, 4.1 and 6.2 cover typed-write actor propagation, delegation/claim guidance and actual audit/assignment readback; the existing restricted-agent requirement includes an attribution scenario. There are still 13 tasks and 17 requirements; this correction is not implementation or live qualification. + +`opencode-beads-8vg` tracks this bounded policy/plan correction; `opencode-beads-d9n` remains the ongoing planning record. Completed review reports remain historical evidence without invented retroactive reviewer claims. The existing epic/children's `created_by: unknown` is preserved; known Codex planner provenance is documented in notes rather than rewriting audit authorship. Docker Lab clarification is filed separately as `docker-lab-stc5`; only filing was authorized there. The earlier Opus READY verdict applies to 510c332, not this later amendment. + +**Current verdict: READY for review of the 2.1.1 planning delta; PAUSED FOR REVIEW.** The older reconciliation and independent reviews below are historical evidence, not approval of this new baseline. This is the planning agent's assessment, not independent reviewer approval, implementation acceptance or publication authority. Original baseline: `3dc2ea327ed490ceb9cf0b95c31af77631613287`; final correction candidate: `50919ae0f6b192bb8c2773398685c550fdaad22f`, branch `openspec/improve-beads-workflow-parity`. The planning bead `opencode-beads-d9n` remains open; no Beads commands were run. + +## Decisions applied + +| Owner selection | Operative artifacts | +| --- | --- | +| Complete marketplace-main reference | Proposal What Changes; analysis Selected marketplace-main reference; local-reference Reconciled comparison; inventory `marketplace_main`; upstream-review Relevant upstream source comparison | +| N1, OpenCode >=2.1.1 <2.2; exact 2.1.1 target | Design decisions 1/3/4; workflow-access Explicit native availability and fallback and Restricted-agent typed operation boundary; tasks 1.1–1.2, 3.1–3.2 | +| Opt-in beta/next, no stable migration | Proposal breaking-change disclosure; design Migration Plan; workflow-access availability requirement; task 1.2; exact version/tag still separate release authority | +| Scope-bounded continuation | Design decision 5; workflow-access Authorized continuation without scope expansion; tasks 4.1–4.2 and 6.2 | +| Package-owned oc-beads and complete corpus | Design decision 2; inventory every selected_mapping; workflow-access discovery/ownership requirements; tasks 2.1–2.2 and 3.3; shared skill and user files preserved | +| Check-based upstream drift | Design decision 6; all upstream-review requirements; task 5.1; artifact-policy selected beta/next exception | +| Overall harness-native experience | Discovery → commands → task flow → contextual guidance → persistence/recovery → delegation/approval → diagnostics → unload are covered by design decisions 2–6 and tasks 2.1–4.2/6.2 | + +F01–F12, D01–D06, E01–E06 and M01–M10/X01–X09 have explicit dispositions in analysis, design and inventory. All 29 command workflows and 15 resources plus ADR are mapped; advanced content is on demand, not excluded by prior defer recommendations. Host-specific/build/support members remain individually mapped evidence, not silently omitted payload. Historical recommendations and all 73 release blob/hash identities remain unchanged; new selected mappings are engineering reconciliation within the accepted groups. + +## Opus review disposition + +The [original review](../../../../docs/reviews/opus-planning-review-e74b507.md) is preserved unchanged as a review of e74b507. Its REVISE verdict is not rewritten or claimed as approval of this reconciliation. + +| Finding | Resolution and evidence | Acceptance still required | +| --- | --- | --- | +| F1: installed notes differ; stable-only target misses marketplace changes | Local-reference Reconciled comparison characterizes all 16 edits/one addition; analysis distinguishes current 74 from historical 73. GitHub main resolves to installed `4a67d0d…`; five guidance files use append-notes, but WORKFLOWS retains contradictory overwrite prose that the selected adaptation must correct. Upstream-review and task 5.1 now compare marketplace-main. No ancestry inferred from dates. Current memory/prime support differences are also characterized. | Supported CLI append retention and acknowledgement contracts (3.1, 6.1); actual notes/recovery behavior (6.2) | +| F2: typed visibility/identity/save/external access | Design decision 3; workflow-access Restricted-agent typed operation boundary; native Lifecycle and alternatives. Released S05 filters `options.permission`; S15 uses invocation agent and saves only nonempty save lists; S18 checks external directories before shell permission. Fixed argv, shell permission category, context.agent, absent new saved grants, guard and location checks are explicit. | Direct/code-mode restricted agents, selective deny, ask/reject, saved grant, external target and post-approval cancellation fixtures/live checks (1.1, 3.1) | +| F3: unreachable prepared-write compaction state | Design decision 4; work-continuity reference-only compaction scenario; native M03; task 3.2. Writes complete in typed calls; compaction adds only acknowledged references and known gaps. No queue, recursive same-runner prompt, semantic wait or permission escalation. Released S03 awaits callbacks without a generic timeout; S06 has separate summary/native preparation. | Reference-only behavior and honest unsaved status in both compaction paths (3.2, 6.2) | +| F4: GitHub review issues conflict with tracker policy | Upstream-review Check-based review notification; design decision 6; task 5.1. Existing read-only scheduled check pattern is the selected alert sink. No issue-write grant or external tracker. | Drift/error/repeated-output fixtures and later workflow check (5.1); workflow unchanged here | +| F5: cosmetic/provenance/organization | Analysis says three capability deltas; command/skill doc links use released `843a6c5…`; tasks remove obsolete freeze/tracker history; N2/X choices live under design decision 5. | Static validation below | + +The review's N0-first recommendation is retained as historical advice; Matt explicitly selected N1. Its compaction and permission findings informed the selected design. No broad new research, monitoring service or speculative product smoke harness was introduced. + +## Normative requirement coverage + +The validator checks exact requirement titles, unique task IDs and complete mapping. Each row covers all scenarios under that requirement; 6.2 is behavioral evidence, not a replacement for the task-local fixtures. + +| Capability | Requirement | Task IDs | +| --- | --- | --- | +| work-continuity | Meaningful progress checkpoints | 2.1, 3.1, 6.2 | +| work-continuity | Relevant reusable knowledge | 2.1, 3.1, 6.2 | +| work-continuity | Recovery from durable state | 3.2, 4.2, 6.2 | +| work-continuity | Delegated knowledge and closure evidence | 4.1, 4.2, 6.2 | +| work-continuity | Native trigger and semantic responsibility separation | 3.2, 4.2, 6.2 | +| work-continuity | Honest persistence acknowledgement | 3.1, 3.2, 6.1, 6.2, 7.1, 7.2 | +| work-continuity | Typed daily-loop composition | 7.1, 7.2, 6.2 | +| workflow-access | Discoverable selected workflows | 1.1, 2.1, 2.2, 6.2 | +| workflow-access | Coherent context and authority | 2.1, 3.1, 3.2, 6.2, 7.3 | +| workflow-access | Capability-aware actor and context guidance | 7.1, 7.2, 7.3, 4.2, 6.2 | +| workflow-access | Owned installation and user customization | 2.2, 3.3, 6.1, 6.2 | +| workflow-access | Scope-preserving execution and visible failure | 3.1, 4.1, 4.2, 6.2 | +| workflow-access | Explicit native availability and fallback | 1.2, 1.1, 3.3, 6.1 | +| workflow-access | Restricted-agent typed operation boundary | 1.1, 3.1, 6.2 | +| workflow-access | Authorized continuation without scope expansion | 4.1, 4.2, 6.2 | +| workflow-access | Read-only native diagnostics | 2.2, 3.3 | +| upstream-review | Relevant upstream source comparison | 5.1 | +| upstream-review | Check-based review notification | 5.1 | +| upstream-review | Explicit baseline disposition | 5.1 | + +## Source identities and genuine gates + +- Selected marketplace main and installed reference: `4a67d0d0288c1b4ee23e17fc896f47616931ba2d`; 74 selected files, including 57 plugin files, 29 commands, 15 resources and one ADR. Read-only GitHub retrieval succeeded; no source blocker. +- Historical Beads v1.3.1: `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c`; original 73-file evidence remains separately validated. +- Selected released OpenCode v2.0.32: `843a6c514e606b436a7fe713422ace0a93ad3d61`. Historical minimum v2.0.26: `9b4ec5714d481559990db0a816d5dec19541a814`. Development comparison only: `8f7b0120eb6b4aa3da1f82b1a9094a9fb4db2d06`. + +No owner selection or source-retrieval blocker remains. Implementation has not been requested. Permission/external-target equivalence, collision/rebuild preservation, installed namespace dispatch and stop/late-child races are bounded technical acceptance gates, not proven guarantees. If a concrete incompatibility changes the selected contract, stop that path and report the smallest remedy; do not silently drop guarantees. Live/model recovery and note quality remain wholly unexecuted. Exact prerelease publication remains separately authorized work. + +## Original reconciliation validation boundary + +At the original reconciliation (published as 50919ae), executed: `openspec validate improve-beads-workflow-parity --strict --no-interactive` passes; `openspec validate --all --strict --no-interactive` reports 1 passed/0 failed; `python3 docs/validate-parity-planning.py /opt/data/cache/scratch/parity-sources` passes historical/current identities, 41 native files in 21 source groups, published tarball integrity, complete member dispositions, 13 unique unchecked tasks and all 17 requirement mappings. `git diff --check` passes; validator whitespace/link checks include the untracked reconciliation record (17 planning files checked, 16 edited). Current OpenSpec status reports all four artifact kinds done; that is artifact presence, not semantic readiness. SHA-256 before/after comparison proves AGENTS.md and both .codex files byte-identical to turn start. Full change-set inspection found only the 16 authorized planning edits beyond preexisting operator setup, with no staged changes. No product tests, model sessions, database access, implementation, staging, commits, pushes or publication are part of this reconciliation. + +## Final review corrections — candidate 50919ae + +The [supplied independent review](../../../../docs/reviews/opus-planning-review-50919ae.md) retains its **REVISE** verdict. Actual trace model: `claude-opus-5-5`; explicit medium invocation is parent-reported, not introspectable by the reviewer. The following is the planning agent’s correction assessment, not a second independent approval. No second full review was requested or required by that review. + +| Finding / recommendation | Exact correction evidence | Future acceptance boundary | +| --- | --- | --- | +| B1 ordering and absent host | Tasks 1.2 → 1.1 prepares dependency/types first; 1.1 only minimal throwaway host tool/command probes; 3.1 retains adapter fixtures. Design decisions 3/6 and migration, SDK contract final paragraphs, validator B1 assertions and availability mapping above agree. Stable IDs remain unchanged. | Live 1.1/4.2/6.2 need a separately authorized isolated host pinned initially to 2.0.32 within >=2.0.32 <3; image 2.0.29 stays untouched. No environment is qualified. Source/assets/fixtures and 5.1 need not wait for it. | +| R1 collisions | Design decision 2; workflow-access ownership scenario; task 2.2; native Final review correction evidence. S13 supervisor ordered pre/packages/post and ConfigCommandPlugin post contradict the review’s blanket config-overwrite inference. Command editor still lacks conditional lookup. Preserve user entries; withhold affected names where possible, never accept overwrite as fallback. | Initial/rebuilt/disposed registry checks, including later config overrides; stop affected path if public APIs cannot meet preservation. No generic registry. | +| R2 full resource access | Design decision 2; workflow-access discovery scenarios; tasks 2.1/2.2/6.2; artifact policy. Future SKILL.md explicitly links all 15 resources plus ADR, with a narrow unpacked-package link fixture. S13 auto-listing truncates at ten. | Installed-base reachability, external_directory ask/deny and honest unavailable diagnostics in 6.2, with no permission bypass. | +| R3 saved approvals | Design decision 3; workflow-access Saved approval remains narrow; task 1.1; SDK/native evidence. save: [] creates no new saved rule; always can be one-call/repeated prompts. Existing shell bd * grants may authorize equivalent typed operations, subject to configured denies. | Minimal host approval probes in 1.1; adapter authorization/identity fixtures in 3.1, installed restricted behavior in 6.2. No convenience broadening. | +| R4 failed drift alert | Design decision 6; upstream-review check notification/repeated detection; task 5.1; artifact policy/native evidence. Unreviewed drift fails distinct check/job; drift and retrieval/comparison failures have distinct error diagnostics. | Later check fixtures/workflow acceptance only; workflow unchanged, no issues/writes. | +| R5 independent drift work | Task 5.1 no longer depends on 1.1; validator guards that edge removal. | Independently useful read-only source comparison can proceed under later implementation authority. | +| R6 stable maintenance | Design Migration Plan and artifact policy require settling stable 1.0.x maintenance branch strategy before prerelease compatibility changes reach stable main. | Release-gated decision, not present source blocker or new owner interview; no branch/release actions. | + +The historical 73-member and current 74-member inventories/dispositions and all 17 requirement titles remain intact. Inventory changes only add four same-pin source identities for collision ordering and shell grant evidence (45 native files, 21 groups). Validator excludes only the installed cache’s non-payload `.in_use` marker, not any source file; all payload hashes/membership remain exact. The supplied review itself is included in whitespace/local-link validation. + +**Correction verdict: READY for parent verification and separately authorized implementation.** B1 is corrected in the executable plan. No source or owner-selection blocker remains. Actual live acceptance is blocked by the absence of a qualified, authorized disposable >=2.0.32 <3 environment (and any required model access); later authority must provide it. Runtime/behavioral results are entirely unexecuted, all 13 implementation tasks remain unchecked, and concrete incompatible contracts still stop their affected paths. This verdict does not grant installation, implementation, publication or tracker authority. + +### Correction validation results + +Strict change validation passed; strict all-change validation reported 1 passed/0 failed. The planning validator passed all six groups: 73 historical files, 74 current files with complete dispositions, 45 pinned native files/21 groups, npm evidence, 13 unique unchecked tasks, all 17 requirement mappings, and whitespace/local links for 18 planning artifacts including the new untracked review. `git diff --check` passed. `openspec status --change improve-beads-workflow-parity` reports 4/4 artifact kinds complete; that is presence, not runtime acceptance. + +Exact SHA-256 comparison against `/opt/data/cache/scratch/opencode-beads-reconcile-preserved-files.json` passed for AGENTS.md, .codex/config.toml and .codex/hooks.json, with no additional .codex files. Review body comparison confirms all supplied review substance is retained. Full changed-file inspection found 12 planning artifacts (11 edits and the new review), plus only the preexisting AGENTS.md/.codex setup. No staged changes; HEAD remains 50919ae0f6b192bb8c2773398685c550fdaad22f. No implementation, product tests, host/model experiments, installs/upgrades, Beads operations, commits or publication occurred. + +## 2026-10-20 host-baseline delta + +The [contract-delta memo](../../../../docs/opencode-2.1.1-contract-delta.md) supersedes only the operative host baseline: 2.1.1 `ba546ec1598a70db2570e9c6258028d955ba8f58`, proposed admission >=2.1.1 <2.2. Historical 2.0.32 identities, inventory and passed reviews remain intact. Provider replay/compaction fields and retained history need fixture adaptation (1.2/3.2/6.2); released session.list remains unnecessary. Permission, registration, stop and unload contracts retain bounded qualification. No new services, compatibility layer, tasks or owners. + +The 13-task/17-requirement map above remains exact. Existing epic vuy and .1–.9 remain blocked/unassigned/implementation_authorized=false; .6 owns deterministic evidence consumed by gated .8, and .9 still awaits complete live acceptance. Review URLs at 84ab18e/dd56cf1 are explicitly historical; this unpublished delta has a local content digest, not an invented commit. d9n remains open. Setup custody now uses committed 907232a, not the old dirty-file assumption. + +Source retrieval and artifact integrity establish a source-backed planning baseline only. No actual 2.1.1 host compatibility is claimed. Live environment/model authority and all implementation authority remain absent; pause for parent/owner review after static checks and scoped tracker readback. diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/upstream-review/spec.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/upstream-review/spec.md new file mode 100644 index 0000000..6b3cf12 --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/upstream-review/spec.md @@ -0,0 +1,48 @@ +# Upstream review + +## ADDED Requirements + +### Requirement: Relevant upstream source comparison + +The maintenance workflow SHALL compare selected upstream integration source paths between an explicitly human-reviewed marketplace-main commit baseline and the upstream default branch resolved to an immutable candidate commit. It SHALL detect additions, modifications, deletions and renames where identifiable, and report incomplete retrieval or comparison as an error rather than no change. Source comparison SHALL remain separate from CLI compatibility checks. + +#### Scenario: Relevant source changes + +- **WHEN** a candidate commit changes content or membership under the selected source paths +- **THEN** the workflow reports both immutable commit identities, changed paths and reviewable diff references +- **AND** it does not infer behavioral incompatibility from source changes alone + +#### Scenario: Comparison cannot complete + +- **WHEN** source retrieval or comparison is incomplete +- **THEN** the workflow exposes the failure and does not record a successful no-change result + +### Requirement: Check-based review notification + +The workflow SHALL report completed main comparisons as successful informational evidence, not compatibility failures or product/release gates. It SHALL distinguish unavailable comparisons from no change and isolate collector failures from product/release gates. Tagged CLI compatibility SHALL remain separate. It SHALL NOT create GitHub issues, request issue-write permission or require adoption; Beads owns later selected work. + +#### Scenario: Repeated detection + +- **WHEN** the same candidate remains unreviewed across scheduled checks +- **THEN** each completed comparison succeeds and reports the same informational baseline/candidate scope and review links without repeated warning/error annotations or duplicate external tracking records + +#### Scenario: Default branch advances ahead of a tagged release + +- **WHEN** ordinary work changes selected paths on main while the supported tagged CLI release is unchanged +- **THEN** the source comparison succeeds and labels the changes development-source information, not released behavior or proven incompatibility +- **AND** it does not fail product checks, block release, require immediate review/adoption or change the supported CLI version +- **AND** a reviewed change needing an unreleased CLI feature may be deferred until verified inclusion in a supported tagged release + +### Requirement: Explicit baseline disposition + +The reviewed baseline SHALL advance only following an explicit human disposition, which may accept a local adaptation, accept no local change, or defer adoption. Drift detection SHALL NOT automatically copy upstream content, modify runtime behavior, bump package versions or publish releases. + +#### Scenario: No adaptation required + +- **WHEN** a maintainer explicitly reviews changed upstream content and determines the local implementation needs no change +- **THEN** the review records that rationale and may explicitly advance the reviewed source baseline without a product change + +#### Scenario: Detection alone grants no adoption authority + +- **WHEN** a scheduled comparison detects changed upstream sources +- **THEN** packaged content, runtime behavior and the reviewed baseline remain unchanged until their respective authorized actions occur diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/work-continuity/spec.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/work-continuity/spec.md new file mode 100644 index 0000000..ad37085 --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/work-continuity/spec.md @@ -0,0 +1,153 @@ +# Work continuity + +## Purpose + +Preserve meaningful work context so authorized work can resume across compaction, session changes and delegation without reconstructing failed approaches or asking the user to repeat known context. + +These are the selected N1 outcome contracts. Historical source acceptance remains intact; new code-mode source tasks and behavioral acceptance remain unexecuted. See design and tasks for qualified delivery boundaries. + +## ADDED Requirements + +### Requirement: Meaningful progress checkpoints + +During authorized tracked work, the integration SHALL guide preservation of material attempts, outcomes, rationale, blockers and the next action at meaningful work transitions without requiring repeated user reminders. Records SHALL preserve relevant prior learning through append-style checkpoints and distinguish verified results from hypotheses; replacement of existing notes SHALL be intentional, not the default checkpoint path. + +#### Scenario: Failed approach changes the next step + +- **WHEN** an attempted solution fails and work proceeds to another approach +- **THEN** durable task context records the attempted approach, failure evidence, reason for changing course and next action +- **AND** recovery does not require repeating the failed experiment to rediscover its outcome + +#### Scenario: No meaningful new learning + +- **WHEN** an operation produces no material change in work context +- **THEN** the integration does not require a redundant write solely to satisfy a frequency or quantity target + +### Requirement: Relevant reusable knowledge + +The integration SHALL distinguish task-specific progress from supported reusable project knowledge, guide retrieval of relevant existing knowledge before relying on or duplicating it, and preserve scope and evidence when recording or correcting reusable knowledge within the user's authority. + +#### Scenario: Discovery applies beyond the current issue + +- **WHEN** a validated finding is likely to help later work and equivalent knowledge is absent or outdated +- **THEN** the workflow preserves the finding as reusable project knowledge with its applicability and evidence +- **AND** task context retains a reference sufficient to connect the finding to the work + +#### Scenario: Temporary state is not a general rule + +- **WHEN** a transient failure or unsupported hypothesis is useful only to the current task +- **THEN** it remains qualified task context rather than an asserted general project fact + +### Requirement: Recovery from durable state + +On resuming authorized work after compaction or a fresh session, the integration SHALL guide retrieval of relevant task state and reusable knowledge sufficient to identify previous attempts, decisions, blockers and the next action. Missing, stale or unavailable context SHALL be disclosed rather than presented as successful recovery. + +#### Scenario: Conversation history is absent + +- **WHEN** a new session resumes a task with durable checkpoints but no prior conversation +- **THEN** the workflow retrieves and uses the relevant checkpoints and knowledge to explain the current position and next action without asking the user to restate recorded facts + +#### Scenario: Knowledge changed since cached context + +- **WHEN** recovery or a relevant work transition requires knowledge newer than cached context +- **THEN** the workflow obtains current relevant state or reports why it cannot +- **AND** it does not treat cached absence as proof that no knowledge exists + +### Requirement: Delegated knowledge and closure evidence + +For selected delegated workflows, the integration SHALL preserve the task's scope, significant learning, validation, blockers and next action across the worker-to-caller boundary. Completion SHALL be distinguished from partial progress; closure SHALL require relevant acceptance and validation evidence. + +#### Scenario: Worker returns a partial result + +- **WHEN** a worker stops after a failed approach or blocker +- **THEN** the caller receives the attempt, outcome, durable references and next action needed to continue +- **AND** the result is not represented as completed work merely because the invocation ended + +#### Scenario: Closure with unresolved acceptance + +- **WHEN** a task has unresolved acceptance criteria or relevant validation has failed +- **THEN** the workflow preserves the unresolved state and evidence rather than closing it as complete + +### Requirement: Native trigger and semantic responsibility separation + +The selected integration SHALL distinguish awaited interception from observational events and semantic judgment from transport success. It SHALL document the selected native mechanism for each continuity behavior, preserve failed or uncertain persistence status, and avoid indiscriminate conversion of tool results or transcripts into durable memory. + +#### Scenario: Compaction starts with unsaved meaning + +- **WHEN** compaction occurs before a semantic checkpoint has been prepared +- **THEN** summary guidance does not claim that durable task context was saved +- **AND** an event subscriber is not treated as a compaction barrier or a guaranteed final-write mechanism + +#### Scenario: Compaction carries known references and gaps + +- **WHEN** the awaited compaction hook prepares summary or native-provider context +- **THEN** it carries acknowledged durable references and known unsaved or uncertain status without initiating persistence +- **AND** it does not wait for a same-session semantic checkpoint, queued write or permission escalation + +#### Scenario: User stops execution + +- **WHEN** an execution interruption or permission wait is observed +- **THEN** the integration does not automatically restart work or approve the blocked operation to manufacture a final checkpoint +- **AND** recovery uses earlier durable checkpoints and reports known gaps + +#### Scenario: Host retains and replays compacted history + +- **WHEN** 2.1.1 summary/native history is recovered or forked, or its provider/model changes +- **THEN** the integration preserves host-owned native replay and metadata.keepID/legacy metadata.recent semantics without substituting a provider replacement window for a summary +- **AND** tool execution state and UI metadata are not treated as renamed provider replay payloads + +### Requirement: Honest persistence acknowledgement + +N1 operations SHALL acknowledge only evidenced outcomes and distinguish failure, uncertain completion and a landed but uncommitted write. They SHALL NOT blindly retry an ambiguous append, force a rejected write or imply a compaction summary is durable Beads state. + +#### Scenario: Memory write landed but commit failed + +- **WHEN** the CLI reports a stored memory with failed or indeterminate commit status +- **THEN** the integration preserves the memory reference and qualified status without claiming committed durability or automatically committing + +#### Scenario: Interrupted append has unknown outcome + +- **WHEN** cancellation or timeout prevents confirmation of an append +- **THEN** recovery checks current durable state before another write and exposes the uncertainty + +### Requirement: Typed daily-loop composition + +The integration SHALL require Beads 1.3.1 or later and expose the eleven-operation beads daily-loop namespace with declared discriminated outputs and fixed supported argv in the accepted subcommand-first canonical form with --sandbox. It SHALL preserve CLI state/claim ownership, per-operation permissions, executor attribution, partial/uncertain outcomes and sole acknowledgement. It SHALL NOT expose generic argv or workflow macros, infer authority from readiness, or retry ambiguous writes. + +#### Scenario: Bounded discovery is incomplete + +- **WHEN** a bounded query, batch get or direct-edge read is incomplete or partly fails +- **THEN** results identify query scope, per-ID failures and exact or unknown completeness +- **AND** unknown or failed data is not reported as missing, empty or unblocked + +#### Scenario: Claim or create outcome is uncertain + +- **WHEN** a started claim or create lacks authoritative acknowledgement +- **THEN** the result or recovery state preserves operation, target/query, actual actor and uncertainty +- **AND** claim acknowledgement requires the actual executor as assignee in the actionable state; not-claimed requires evidenced non-claim rather than guessed CLI error text +- **AND** an uncertain create requires search/readback before any caller-considered retry + +#### Scenario: Dependent writes compose + +- **WHEN** authorized work sequences claim, checkpoint and optional closure +- **THEN** each operation uses its own canonical permission resources and post-approval/pre-spawn guard, location, actor and cancellation checks +- **AND** dependent writes are ordered and stop on rejection or non-acknowledgement, with no transaction or exactly-once promise +- **AND** closure still requires caller authority and acceptance evidence + +#### Scenario: Canonical execution preserves permission and sync policy + +- **WHEN** a typed read or write is prepared +- **THEN** its resource exactly renders qualified subcommand-first argv with --sandbox on all operations and derived --actor on writes, inserted after operation arguments and before any -- free-text terminator +- **AND** no automatic sync or additional permission rules compensate for changed prefixes + +#### Scenario: Execution is refused before spawn + +- **WHEN** input is invalid, actual identity is unavailable, a guard hold applies or location changes +- **THEN** the accepted pre-spawn refusal remains thrown; permission rejection and cancellation also propagate unchanged +- **AND** no executed:false result branch replaces the refusal; a started interrupted write retains recovery uncertainty even without a returned normal value + +#### Scenario: CLI contract is unsupported + +- **WHEN** a proposed filter, argv or output shape is not established on supported minimum/current Beads +- **THEN** the schema does not advertise that unsupported contract and the affected core operation stops for review if no supported mapping exists +- **AND** no implicit version bump, routing repair or invented conflict response supplies the missing contract diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/workflow-access/spec.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/workflow-access/spec.md new file mode 100644 index 0000000..781586d --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/specs/workflow-access/spec.md @@ -0,0 +1,222 @@ +# Workflow access + +## Purpose + +Make selected Beads workflows discoverable and usable through native OpenCode capabilities while preserving project authority, explicit ownership and honest failure handling. + +Owner selections apply to the complete marketplace-main corpus: core workflows adapted, advanced guidance available on demand, supported administration accessible under normal permissions, and obsolete or host-specific content corrected/replaced. The inventory maps every member to native delivery; acceptance is still unexecuted. + +## ADDED Requirements + +### Requirement: Discoverable selected workflows + +The package-owned `oc-beads` skill, all 15 corpus resources plus CODE_MODE and ADR rationale and all 29 `oc-beads:<command>` entries plus status (including documented replacements) SHALL have a documented native entry point that exposes its purpose and relevant supporting guidance. Advertised command and skill behavior SHALL match the installed implementation and available tools rather than assume absent MCP tools or unreachable resources. + +#### Scenario: User invokes a selected command + +- **WHEN** a user discovers and invokes an installed selected command with arguments +- **THEN** the advertised workflow receives the intended arguments through its documented native entry point +- **AND** uses available execution tools without inventing a Beads MCP tool + +#### Scenario: Task requires deeper guidance + +- **WHEN** a selected workflow directs an agent to a supporting resource +- **THEN** that resource is reachable from the installed guidance's documented base +- **AND** SKILL.md explicitly links all 15 corpus resources plus CODE_MODE and ADR rather than relying on the host’s ten-file auto-listing +- **AND** its presence alone is not reported as evidence that the model used it + +#### Scenario: Installed resource requires external access + +- **WHEN** an installed resource outside the workspace encounters external-directory ask or deny +- **THEN** the integration respects approval/rejection and reports unreachable guidance honestly without bypassing permission + +### Requirement: Coherent context and authority + +The integration SHALL preserve applicable user and repository authority across injected workflow, skills, commands and delegated guidance. It SHALL distinguish missing or failed context from context withheld by an explicit hold, and SHALL NOT recommend bypassing a hold through manual fallback. + +#### Scenario: Migration hold conflicts with prime-first advice + +- **WHEN** workflow context is withheld because store access is not authorized +- **THEN** skill, command and worker recovery guidance preserves that restriction +- **AND** reports the reason and required owner action without opening the store + +#### Scenario: Context is already available + +- **WHEN** current applicable workflow context has already been supplied +- **THEN** discovery guidance does not require another prime solely because the skill was loaded +- **AND** distinct applicable local constraints remain available when duplicate guidance is reduced + +### Requirement: Owned installation and user customization + +The OpenCode npm package SHALL supply and register the complete owner-selected integration, including its selected command, skill/resource, agent and lifecycle surfaces. Advertised capability SHALL NOT depend on a separate undocumented or manual skill/resource installation. The installed Beads CLI and upstream-owned workspace/data initialization remain external prerequisites. + +#### Scenario: Package installed in an initialized workspace + +- **WHEN** a supported OpenCode host loads the package in an initialized Beads workspace with its prerequisites satisfied +- **THEN** the selected packaged workflows and supporting guidance are available through native registration without separately installing the rich Claude plugin or copying skills +- **AND** qualification verifies effective discovery and installed resource paths rather than source-tree presence alone + +Selected integration surfaces SHALL document who installs, updates and removes them and how user overrides are resolved. Updates and removal SHALL preserve unrelated or user-modified artifacts and SHALL NOT silently overwrite the upstream-owned shared skill or runtime configuration. + +#### Scenario: User customizes an installed workflow + +- **WHEN** an integration update or removal encounters user configuration, file commands or a colliding artifact +- **THEN** it preserves user-owned files and ordinary configuration/file command overrides through native post-ordered precedence on initial load and rebuild +- **AND** does not remove unrelated commands, skills or configuration +- **AND** registers all selected namespaced commands without setup-time withholding; same-name conflicts with other plugin transforms follow native order, with detectable conflicts diagnosed rather than a promise of universal collision prevention +- **AND** removing a user override restores the package entry through native rebuild; skill candidate collisions use the public per-rebuild editor check rather than a permanent setup-time gate + +#### Scenario: Installed package is unloaded and reactivated + +- **WHEN** the installed package is unloaded and subsequently reactivated on the qualified disposable host +- **THEN** owned entries disappear on unload and the expected commands and skill return once under native user-override precedence +- **AND** an eligible request receives one Beads context injection, without a late result from work pending in the unloaded generation +- **AND** acceptance records actual host lifecycle results rather than treating fixture disposal or registry rebuilds as installed reactivation evidence + +### Requirement: Scope-preserving execution and visible failure + +Selected workflows SHALL preserve the user's requested execution scope, distinguish inspection from mutation, and report unsuccessful or partial operations accurately. Optional extensions SHALL expose their scope and constraints without silently replacing base workflows or authorizing unrelated actions. + +#### Scenario: Graph analysis is read-only + +- **WHEN** the user requests an issue-graph overview without authorizing task execution +- **THEN** the workflow returns the analysis without claiming, editing or closing issues + +#### Scenario: Single atomic operation + +- **WHEN** the user authorizes one issue operation +- **THEN** the workflow completes or reports failure of that operation without automatically starting unrelated ready work + +#### Scenario: Optional worker limit interrupts work + +- **WHEN** a selected bounded worker or permission constraint stops before completion +- **THEN** the result identifies the incomplete work, relevant context and next action +- **AND** does not claim that the task or durable write succeeded + +### Requirement: Explicit native availability and fallback + +The redesign SHALL use N1 with exact OpenCode 2.1.1 qualification and `>=2.1.1 <2.2` admission through opt-in beta/next delivery. It SHALL NOT silently migrate stable consumers, promise arbitrary minor/major compatibility or substitute N0. Concrete incompatibilities SHALL block the affected path with diagnostics. Publication remains separately authorized. + +#### Scenario: A different host version is considered + +- **WHEN** a future admitted patch or a version outside the declared range is considered +- **THEN** an admitted patch is not described as tested without measured evidence, and an out-of-range host remains unsupported pending explicit contract review + +#### Scenario: Host cannot satisfy N1 + +- **WHEN** the host lacks the selected permission or lifecycle contract +- **THEN** the integration reports the unsupported path and does not enable privileged fallback execution or silently weaken guarantees + +#### Scenario: Experimental capability is considered + +- **WHEN** a released experimental mechanism could improve task flow or recovery +- **THEN** the packet records its semantics, qualification, maintenance and fallback consequences before adoption +- **AND** instability alone is neither approval nor automatic exclusion + +#### Scenario: Plugin reloads or a session changes location + +- **WHEN** registrations are unloaded or a session moves, forks or is deleted +- **THEN** the selected integration disposes or re-resolves affected state and observers without leaking task context across sessions or locations +- **AND** it preserves user overrides and does not claim cleanup guarantees a final durable write + +### Requirement: Restricted-agent typed operation boundary + +Typed tools and direct shell fallback SHALL respect shell visibility and operation-specific permissions using actual invocation identity, canonical resources and narrow saved grants. Guard holds, location, attribution and cancellation SHALL be rechecked after approval and before execution. Beads owns database/routing/consistency semantics; normal CLI equivalence does not promise filesystem confinement. + +#### Scenario: Agent wholly denies shell + +- **WHEN** a restricted agent's effective rules wholly deny shell +- **THEN** typed tools are absent from its code-mode inventory, not merely denied after selection; default typed tools have no direct registration and native shell is never callable inside execute + +#### Scenario: Invoking agent differs from session default + +- **WHEN** a typed operation is called by an agent with more restrictive rules than the session's default agent +- **THEN** permission evaluation uses the invoking agent and does not broaden saved approvals to unrelated bd operations + +#### Scenario: Saved approval remains narrow + +- **WHEN** a typed operation requests approval with `save: []` +- **THEN** even an “always” response installs no new saved grant and guidance explains possible repeat prompts +- **AND** existing shell `bd *` grants may authorize equivalent typed operations, while configured denies retain precedence + +#### Scenario: User corrects or declines a typed operation + +- **WHEN** the host permission assertion returns user correction with feedback or explicit decline +- **THEN** the adapter propagates the assertion rejection unchanged and preserves correction feedback without executing the rejected target operation +- **AND** it does not acknowledge target success or automatically retry the operation +- **AND** code-mode composition remains available despite the owner-accepted OpenCode 2.1.1 limitation that a plain decline may become an inner error in an outer completed result and permit subsequent model steps or independently permissioned work +- **AND** rejection of one operation does not promise whole-step termination; every later operation retains its own permission checks, and explicit Stop/Cancel remains a separate cancellation guarantee + +#### Scenario: External target or held workspace + +- **WHEN** the explicit invocation/location requires external-directory access or the existing guard withholds store access +- **THEN** the operation preserves that restriction and refuses before store-opening execution unless invocation context and the required OpenCode authority are established +- **AND** Beads retains ownership of its internal routing; no final-store attestation or additional internal-route confinement is promised +- **AND** normal intended routing is not a speculative blocker or a prerequisite for upstream routing-disable support or local repair logic +- **AND** unresolved OpenCode permission equivalence prevents the affected execution + +#### Scenario: Agent claims and hands off tracked work + +- **WHEN** a selected workflow writes, claims or renews tracked work, or delegates execution +- **THEN** it supplies the actual executor's explicit actor consistently and verifies audit author, account owner and assignee separately +- **AND** coordination or review does not silently transfer a claim, actor naming does not grant authority, and historical authorship is not rewritten + +### Requirement: Authorized continuation without scope expansion + +Canonical execution SHALL allow continuation within an authorized batch or goal, with atomic claims and per-bead validation. Exact assignments SHALL stop without substitution; discovery SHALL NOT expand authority. Cancellation, exhausted scope or a decision blocker SHALL stop continuation, and plugin events SHALL NOT restart it. Denied authority SHALL block the rejected operation; the typed-operation scenarios define the accepted code-mode continuation exception. + +#### Scenario: Exact assignment completes + +- **WHEN** the assigned bead is complete or cannot be acted on +- **THEN** the workflow reports its evidence or blocker and returns without selecting another bead + +#### Scenario: Authorized batch has more work + +- **WHEN** one bead meets its acceptance and another actionable bead remains within the authorized batch +- **THEN** the workflow may continue after recording evidence and claiming that bead atomically +- **AND** newly discovered out-of-scope work remains a reported follow-up + +#### Scenario: Child result arrives after stop + +- **WHEN** a child completion races a deliberate stop +- **THEN** the integration does not initiate renewed work or closure from the notification and preserves partial status for later authorized recovery + +### Requirement: Read-only native diagnostics + +The package SHALL expose effective discovery, compatibility, ownership conflicts and context/persistence status through a native status entry without opening a withheld store, changing configuration or exposing credentials. Diagnostics SHALL distinguish unavailable, failed, withheld and uncertain outcomes. + +#### Scenario: Discovery collision + +- **WHEN** a user customization prevents an owned entry from taking effect +- **THEN** status reports effective names/descriptions and detectable overrides while preserving native user-config precedence +- **AND** a description mismatch may identify an override but SHALL NOT be presented as proof of ownership; public owner identity and identical-description overrides remain unobservable and are disclosed +- **AND** command registration status reflects actual registration and lookup outcomes rather than an obsolete blanket hold + +### Requirement: Capability-aware actor and context guidance + +The integration SHALL render actual executor identity and request capabilities, deduplicate guidance while retaining authority, and retain full prime pending measurement. It SHALL preserve all 30 human command names, overrides and selected advanced/admin workflows, with explicit links to 16 resources plus ADR. Durable references and freshness SHALL be operation-aware. + +#### Scenario: Execute is denied + +- **WHEN** execute is wholly denied for the actual request +- **THEN** code-mode-only Beads tools are unavailable and guidance offers direct shell only if separately available and not held +- **AND** when both capabilities are unavailable the limitation is reported without bypass or invented tool access + +#### Scenario: Typed and shell writes share an executor + +- **WHEN** a primary or delegated agent alternates typed and direct-shell writes +- **THEN** context and typed results expose the same actor derived from the actual request/invocation session and agent; attribution grants no permission, and permission checks separately use actual context.agent +- **AND** child executors keep their own identities, no model-controlled actor argument exists, and beads-task-agent remains an optional execution profile + +#### Scenario: A write changes cached state + +- **WHEN** a memory write may have landed, external state changes, or recovery needs current knowledge +- **THEN** relevant location context is refreshed or its uncertainty reported +- **AND** issue-write prime refresh is retained until supported prime content is verified independent of issue state +- **AND** acknowledged writes avoid redundant generic recovery cues, while uncertain results preserve targeted readback needs + +#### Scenario: Actor or capabilities change + +- **WHEN** a request changes agent, session or effective capabilities +- **THEN** rendered actor/capability guidance is recomputed without leaking cached identity or promising unobserved permissions +- **AND** deduplication preserves distinct local authority and the required scope, stop, ownership and honest-outcome rules diff --git a/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/tasks.md b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/tasks.md new file mode 100644 index 0000000..36ef3fc --- /dev/null +++ b/openspec/changes/archive/2026-10-21-improve-beads-workflow-parity/tasks.md @@ -0,0 +1,48 @@ +# Tasks + +The selected planning direction and parent compatibility review are recorded in [owner-decisions](owner-decisions.md). Tasks 1.2 and 2.1 have parent-accepted source/test evidence from bounded Luna Max work; tasks 3.1/3.2 and 2.2/3.3 have parent-accepted source/package/fixture evidence from Sol High under opencode-beads-vuy.4 and opencode-beads-vuy.5 with the corrected upstream and native-precedence boundaries. Unchecked tasks remain unaccepted and require their own scoped implementation authority. See the source acceptance record in [the SDK contract](../../../../docs/opencode-sdk-contract.md). Live host/model qualification remains unexecuted; use disposable fixtures only with the applicable authority. Parent owns Beads execution records. Dependencies below order the work without a second tracker. + +## Current planning boundary and accepted host limitation + +The owner retains code-mode composition and accepts the measured 2.1.1 plain-decline continuation limitation described in [owner decisions](owner-decisions.md). The failed stronger whole-step-termination case remains historical failed evidence, not relabeled as passing. Broader Opus architecture is the selected planning direction, reconciled in design and tasks 7.1–7.3. Remaining qualification, activation and closeout are paused for parent review; no product implementation or resumed live/model evaluation is authorized. Preserve all original task identities/status and exclusive ownership. Accepted task 3.1 covers the historical five operations only; 6.1 covers its historical candidate, not this delta. + +## 1. Qualify the selected boundary before enabling it + +- [x] 1.2 First, with explicit dependency-edit authority, prepare the smallest compatibility declaration and development typing change in `package.json`/lockfile for `>=2.1.1 <2.2`, using pinned 2.1.1 plugin types (`permission.assert` is absent in current 2.0.26 types). Update README/SDK contract with opt-in beta/next scope and conservative 2.1 patch admission; exact 2.1.1 remains an unexecuted target, no arbitrary minor compatibility. Adapt only relevant type fixtures for native replay/compaction fields, leaving tool execution state/UI metadata intact; verify typecheck and unsupported-host diagnostic. No runtime upgrade or release version/tag selection. Dependency preparation does not depend on 1.1; absent edit authority blocks this task only. +- [x] 1.1 After 1.2, qualify host contracts on the disposable host prerequisite below using minimal throwaway tool/command probes, not the product adapter or its fixtures. Check released S05/S13/S15/S18 direct/code-mode shell filtering, invoking agent identity, selective denial, allow/ask/reject, user correction with feedback and explicit decline, re-evaluation after externally edited/revoked saved grants, external-directory assertions and `oc-beads:ready` admission. For correction/decline, propagate the assertion rejection unchanged at the adapter seam, preserve correction feedback, execute no rejected target operation, and do not report target success or automatically retry. Retain the observed failed whole-step-termination case as an owner-accepted OpenCode 2.1.1 code-mode limitation, not a passing test: nested plain decline may permit subsequent independently permissioned work or model steps; explicit Stop/Cancel remains separately qualified. With `save: []`, expect no new saved grant and one-call “always” acceptance with repeat prompts; existing shell `bd *` grants can authorize equivalent typed operations, but denies still win. Record exact host/probe results in `docs/opencode-sdk-contract.md`. Additionally qualify native namespace/catalog signatures, execute-denied removal of all code-mode-only tools, direct-only shell fallback, and per-operation canonical permission resources using throwaway probes. Do not exercise adapter routing, guard, argv or acknowledgement logic here; historical fixtures belong to 3.1 and the new delta to 7.1–7.2. Concrete host mismatch stops the affected activation path, never silently selects N0 or renames commands. + +**Live prerequisite for 1.1, 4.2 and 6.2:** a pinned, isolated disposable OpenCode host `>=2.1.1 <2.2`, initially exact 2.1.1 (source `ba546ec1598a70db2570e9c6258028d955ba8f58`), with authorized test credentials/model access where needed. Availability or installation requires later explicit authority. The operator image CLI is 2.0.29 and remains untouched. No live environment has been qualified; its absence blocks these live checks and activation acceptance, not independent source/asset/fixture work. No installation or host experiment is authorized by this plan. + +## 2. Deliver the full native discovery package + +- [x] 2.1 Independently of live host availability, author `assets/oc-beads/SKILL.md`, all 15 `resources/*.md` and ADR rationale from the inventory mappings. Link all 16 files explicitly from SKILL.md because host auto-listing exposes only ten. Adapt daily triggers, append checkpoints, selective memory, recovery, advanced on-demand/admin guidance and known obsolete examples; keep current syntax in prime/help. Extend `tests/workflows.test.ts` to verify every mapped resource/link and corrected entry, and document ownership in `docs/beads-artifact-policy.md`. Stop if a current unsupported operation has no honest replacement; do not hide the member or copy broken syntax. +- [x] 2.2 After 1.2 and 2.1, add narrow definitions in `src/workflows.ts` and registration in `src/host.ts`: `oc-beads`, all 29 `oc-beads:<stem>` workflows and `oc-beads:status`. Extend `tests/workflows.test.ts`/`tests/host.test.ts` for arguments, attachments/delivery, effective registry collisions, user overrides, rebuild and disposal. Add assets to `package.json` files and extend existing `scripts/test-package.ts` to inspect unpacked resource paths and both exports. Add a narrow fixture resolving all 16 SKILL.md links from the unpacked installed base, including honest missing/unreachable-resource diagnostics and 2.1 bounded scan/ignore/watch behavior without relying on automatic recursive discovery; live external access acceptance is 6.2. Verify `bun run test:package`; no manual skill copy or user-file writes. Register all 30 namespaced commands without setup-time withholding; initial collisions are diagnostic-only and competing plugin transforms follow native precedence. Verify ordinary configuration/file overrides win on initial load and rebuild, removal restores our entry, and disposal removes only our registration. Use a small ordered-callback fixture, not a host replica. Keep the per-rebuild skill editor collision check but remove the redundant initial skill gate. Guard command adds on abort and retain the owned disposer. Status reports actual registration/lookup outcomes and effective name/description mismatches, disclosing unobservable owner identity and identical-description overrides; remove the obsolete blanket hold. Package smoke must invoke the real registration callbacks and assert actual command membership, not zero transforms. Record pinned ordering separately from unpinned suffix-reactivation evidence. Activation acceptance also requires 1.1. + +## 3. Implement continuity and lifecycle within the qualified boundary + +- [x] 3.1 After 1.2, implement fixed checkpoint/show/memories/recall/remember operations in `src/continuity.ts`, wired through `src/host.ts` and existing `runBd` in `src/prime.ts`. Add only necessary abort support to the runner. Test in `tests/continuity.test.ts` and `tests/prime.test.ts`: safe multiline argv, guard holds, actual session/project cwd, normal external-directory checks for explicit invocation/location access, and inherited Beads routing without local overrides, canonical permission resources, actual invocation agent, denied/held access before spawn, cancellation after approval, timeout/uncertain write without blind retry, landed-but-uncommitted memory, empty-found versus missing memory, and direct/code-mode acknowledgement deduplication. Document task notes versus reusable memory in the packaged guidance. Derive an explicit executor actor from actual invocation sessionID/agent for typed writes, separately from host permission identity; fixture-check no human Git fallback and refusal when executor attribution is unavailable, without adding an identity service. Stop on failed acknowledgement or unsupported CLI contract; never auto-force or commit. Beads owns normal database/routing/consistency semantics: no final-store attestation, routing-disable prerequisite, backend restriction or upstream repair framework. Keep the supported config-only context probe for the migration guard. Adapter fixtures remain here; N1 activation requires successful 1.1 host qualification. +- [x] 3.2 After 3.1, adapt `src/context.ts`, `src/host.ts`, `src/guidance.ts` and narrowly `src/guard.ts`/`src/instructions.ts` for relevant recovery/dirty refresh and distinct withheld/failed/empty context. Preserve unique local policy instead of marker-only deletion. Extend existing context/host/guard/instruction tests with fresh recovery cues, external-change refresh, move/fork/delete isolation, unload abort, and reference-only summary/native compaction. Use 2.1 native replay fields and current compaction lifecycle; preserve metadata.keepID, legacy metadata.recent and fork-remapped references. Cover provider/model mismatch recovery and that native.messages replaces summary replay, without injecting a replacement window or custom compaction result. Verify compaction performs no writes, permission waits or recursive prompt. Document diagnostics and known gaps; no final-write claim. +- [x] 3.3 After 2.2 and 3.2, verify package lifecycle across reload/removal in the existing host/package test seams: owned command/skill/tool/agent/hook disposal, observer/process abort, no user customization deletion, no stale location state, and status that distinguishes missing/withheld/failed/uncertain context without store-opening diagnostics. Update README installation/removal instructions. Stop acceptance on leaked registrations, pending retries or false status. + +## 4. Make task flow scope-bounded + +- [x] 4.1 After 2.1 and 3.1–3.2, revise `src/guidance.ts` and `src/agents.ts` descriptions/options without renaming agents: remove mandatory one-bead and command-count delegation, retain explicit one-bead mode, atomic claim, per-bead evidence/validation, optional Luna/caps and normal blocking child return. Update `tests/guidance.test.ts`/`tests/agents.test.ts` and workflow fixtures for exact-assignment stopping, authorized batch/goal continuation, discovered work without scope expansion, read-only graph work, denied authority and incomplete closure. Document selected defaults and explicit executor actors through claim/update/heartbeat/delegation, intentional assignment/handoffs and separate created_by/owner/assignee readback. Guidance must distinguish substantive planning/review work from implementation, without ticketing every retry or document. Prompt assertions alone cannot complete behavioral acceptance. +- [x] 4.2 After 4.1 and 3.3, qualify child/parent identity and delivery in focused host fixtures and then installed-host checks after 1.1 under the live prerequisite above: failed/cancelled/NO_TEXT/late child, deliberate stop racing completion, permission wait, partial handoff and next-session recovery on the new baseline, including retained-history/fork behavior; long-running transport reliability is observed here without a product transport rewrite. Preserve attempts/rationale/validation/references and never restart the parent from plugin events. Record results in the native assessment. After 7.2–7.3, consume their exact deterministic evidence without duplicating identity/abort suites; live 4.2 adds its own Stop/handoff observation and tests explicit Stop/Cancel during a multi-write program and approval wait, preventing later dispatch, and shell/typed actor consistency for primary and child sessions. Plain-decline continuation is failed-but-accepted history, not a Stop pass. Stop any path that resumes against cancellation; do not enable background/nested automation or build a poller to repair it. + +## 5. Add reviewed upstream drift checks + +- [x] 5.1 Independently of N1 qualification, add a path-scoped read-only comparison in `scripts/check-beads-integration.ts` (or a small equivalent script) and focused `tests/upstream-review.test.ts`; only in later authorized implementation wire it into `.github/workflows/beads-contract.yml` beside the existing stable CLI contract. Use inventory roots/support paths and human-reviewed marketplace-main baseline. Fixtures cover unchanged/added/deleted/edited/identifiable-renamed paths, incomplete tree/retrieval failure, repeated candidate output and explicit baseline advancement. Completed main comparisons succeed unchanged or changed: informational development-source output only, no drift warning/error annotations, product CI failure, release/adoption gate or mandatory immediate review. Add regression fixtures where main advances but the tagged CLI release does not; the comparison must exit zero and leave CLI compatibility/release state unchanged. Incomplete retrieval/comparison is distinctly unavailable evidence, never no change; a collector error must not gate product/release checks. Preserve independent stable tagged CLI compatibility failures. A reviewed change needing an unreleased feature may be deferred until verified inclusion in a supported release. Check output contains both commits and review links; no GitHub issue, issue-write permission, auto-copy/bump/release. Document human disposition in artifact policy. + +## 6. Integrated acceptance, kept separate from source qualification + +- [x] 6.1 After the deterministic work in 2.2, 3.3, 4.2 and 5.1, run `bun run typecheck`, `bun test`, `bun run test:package` and authorized `bun run test:bd-contract` only in disposable repositories. Extend existing `scripts/bd-contract.ts` only for touched supported CLI semantics (append retention, memory result distinctions, supported/replaced admin commands). Record exact CLI/host versions and results. Stop on unresolved failures; never use the project tracker or install/upgrade a CLI implicitly. +- [x] 6.2 After accepted 6.1 history, new source tasks 7.1–7.3 and the live gates in 1.1/4.2, using the disposable host prerequisite above, run a small installed-package/model evaluation covering work and failure discovery, append notes versus reusable memory, relevant recall, compaction and fresh-session recovery, exact assignment and authorized continuation, restricted-agent visibility/denial and partial child handoff, including 2.1 retained-history and provider/model replay boundaries. Run design workflows A–D using actual catalog signatures; measure prime/guidance/catalog/returned bytes and process counts separately, without token-savings claims. Verify typed/execute/shell availability, projection/completeness/partial failure, actual actor agreement, uncertain create readback and no retry. Verify all 17 explicitly linked resources from the installed skill base, including `external_directory: ask` approval/rejection and `deny`; report unreachable guidance honestly without bypassing permissions. Include one actual installed unload/reactivate round trip: owned entries disappear on unload, expected commands and skill return once on reactivation under native user-override precedence, one eligible request receives one Beads context injection, and pending work from the unloaded generation cannot inject a late result. Use the real host lifecycle, not fixture rebuilds as a proxy; no supervisor rewrite or reload matrix. Record host/CLI/provider/model/reasoning/install path, actual executor actor and separate audit-author/account-owner/assignee observations for authorized writes/claims/handoffs, actual records and observed recovery; compare host/model changes separately where feasible. No tool/write quotas or exhaustive matrix. Failed semantic outcomes remain failed acceptance even if every deterministic check passed. +- [x] 6.3 After 6.2, reconcile measured capability and limitations into analysis/native/local-reference/SDK/artifact-policy docs and README. Verify every normative requirement and inventory member has passing evidence or an explicit unresolved acceptance gate; run strict change/all validation and planning validator. Do not mark implementation complete with unresolved selected guarantees. Publication, archive and tracker closure remain separately authorized parent work. + +## 7. Code-mode source delta (new, not dispatched) + +One cohesive source owner holds 7.1–7.3; order is 7.1 then 7.2 then 7.3. Source preparation does not depend on host activation. The existing .2/.6/.9 owners retain independent host, Stop/handoff and installed-model acceptance. No accepted historical marker is reopened. + +- [x] 7.1 Before implementing new argv, establish runtime CLI contracts for Beads 1.3.1, now both the supported minimum and current qualification target. A future exact source assignment explicitly authorizing this bounded path covers existing `scripts/install-bd.sh <version> <disposable-bin-dir>` for 1.3.1 and one supported-version run of existing `scripts/bd-contract.ts` with the selected binary on PATH, isolated repositories and the actual executor actor; no per-command reapproval within that assignment. No installation or contract run is authorized NOW. Extend only touched cases for supported flags, JSON/mutation shapes, limits/filter completeness, single-ID get, dependency direction/type, selected update fields, claim/conflict evidence, generated creation ID, close state, memory distinctions and full prime before/after issue writes. Help/source supports discovery, never passing runtime evidence. The SDK ledger is a table citing captured existing-contract output with exact versions/commands/results, not a second tool. Require minimum >=1.3.1 and leave the operator untouched; omit/reject unsupported optional filters on 1.3.1, stop unsupported core operations for parent decision, and retain conservative refresh if prime dependence is unresolved. No guessed multi-ID show, unverified conflict branch or fabricated missing result. +- [x] 7.2 After 7.1, replace the withheld five-operation definitions in `src/continuity.ts` with the eleven-entry table, declared host JSON/Effect input/output contracts, qualified fixed argv and explicit normalizers through existing `runBd`/guard. Preserve subcommand-first canonical resources, --sandbox on every read/write, derived --actor on writes, inserted after operation arguments but BEFORE any -- free-text terminator; no automatic sync. Wire native beads namespace in `src/host.ts`; no direct duplicates, generic argv, macros or validation framework. In existing continuity/host tests, use per-operation table cases for argv/resource/normalization and evidenced declared branches; assert every definition declares output and normalized cases satisfy declared branch invariants. Retarget existing shared-negative tests ONCE across representative multi-spawn get and representative write, preserving invalid-input/identity/hold/location thrown refusals, unchanged permission rejection/cancellation, save:[], actor/target preservation, notification detail, no retry and sole acknowledgement. Add necessary batch partial/completeness/uncertain claim/create cases; no operations × versions × modes × permission-outcomes matrix. Production retains post-approval/pre-spawn guard/location/actor/abort rechecks for EVERY child spawn. Rerun existing scratch executor/catalog probes once against actual .10 candidate definitions and durably record exact pin/candidate/commands/results in SDK contract; there is no maintained product executor seam. No vendored runtime, new dependency or host replica; real installed confirmation belongs to .2/.9. Keep activation withheld; stop on unsupported core contracts, false success or permission/cancellation regression. +- [x] 7.3 After 7.2, reconcile `src/context.ts`, `src/host.ts`, `src/guidance.ts`, `src/agents.ts`, `src/workflows.ts` and package-owned `assets/oc-beads` for actual request/invocation session-and-agent actor rendering, request capabilities, deduplication, operation-aware refs, conditional location freshness and honest registration/execute diagnostics. Permission checks separately use actual context.agent; no session-default substitution or mandatory delegation. Add CODE_MODE resource and explicit SKILL link (16 resources plus ADR /17 links), preserving all30 names/user overrides/full advanced-admin coverage. Move repo-specific planning/review elaboration on demand while retaining authority/ownership/stop rules. Keep full prime and issue-write refresh without 7.1 runtime evidence. Extend existing context/host/guidance/agents/workflows/package checks for representative states: typed available; execute denied with shell available; neither available; guard hold. Preserve actor isolation, acknowledgement/uncertainty, external/child freshness, installed link and disposal without a capability cross product. Run existing cheap full regression suite (`bun test`), typecheck and package smoke; consume 7.1 CLI output without repeating its matrix. Record exact candidate/results for .2/.6/.9; .6 consumes deterministic evidence and adds its Stop/handoff observation without duplicate identity/abort suites. Update SDK/README product guidance only in later authorized implementation. Historical6.1 remains accepted for its prior candidate. Stop on lost authority/stale state/contradictory guidance/leaked registrations; no live host/model experiment, activation or publication follows. diff --git a/openspec/specs/upstream-review/spec.md b/openspec/specs/upstream-review/spec.md new file mode 100644 index 0000000..5b7a3cb --- /dev/null +++ b/openspec/specs/upstream-review/spec.md @@ -0,0 +1,51 @@ +# upstream-review Specification + +## Purpose +Track relevant upstream integration changes without conflating development-source drift with tagged CLI compatibility or automatically adopting new content. + +## Requirements + +### Requirement: Relevant upstream source comparison + +The maintenance workflow SHALL compare selected upstream integration source paths between an explicitly human-reviewed marketplace-main commit baseline and the upstream default branch resolved to an immutable candidate commit. It SHALL detect additions, modifications, deletions and renames where identifiable, and report incomplete retrieval or comparison as an error rather than no change. Source comparison SHALL remain separate from CLI compatibility checks. + +#### Scenario: Relevant source changes + +- **WHEN** a candidate commit changes content or membership under the selected source paths +- **THEN** the workflow reports both immutable commit identities, changed paths and reviewable diff references +- **AND** it does not infer behavioral incompatibility from source changes alone + +#### Scenario: Comparison cannot complete + +- **WHEN** source retrieval or comparison is incomplete +- **THEN** the workflow exposes the failure and does not record a successful no-change result + +### Requirement: Check-based review notification + +The workflow SHALL report completed main comparisons as successful informational evidence, not compatibility failures or product/release gates. It SHALL distinguish unavailable comparisons from no change and isolate collector failures from product/release gates. Tagged CLI compatibility SHALL remain separate. It SHALL NOT create GitHub issues, request issue-write permission or require adoption; Beads owns later selected work. + +#### Scenario: Repeated detection + +- **WHEN** the same candidate remains unreviewed across scheduled checks +- **THEN** each completed comparison succeeds and reports the same informational baseline/candidate scope and review links without repeated warning/error annotations or duplicate external tracking records + +#### Scenario: Default branch advances ahead of a tagged release + +- **WHEN** ordinary work changes selected paths on main while the supported tagged CLI release is unchanged +- **THEN** the source comparison succeeds and labels the changes development-source information, not released behavior or proven incompatibility +- **AND** it does not fail product checks, block release, require immediate review/adoption or change the supported CLI version +- **AND** a reviewed change needing an unreleased CLI feature may be deferred until verified inclusion in a supported tagged release + +### Requirement: Explicit baseline disposition + +The reviewed baseline SHALL advance only following an explicit human disposition, which may accept a local adaptation, accept no local change, or defer adoption. Drift detection SHALL NOT automatically copy upstream content, modify runtime behavior, bump package versions or publish releases. + +#### Scenario: No adaptation required + +- **WHEN** a maintainer explicitly reviews changed upstream content and determines the local implementation needs no change +- **THEN** the review records that rationale and may explicitly advance the reviewed source baseline without a product change + +#### Scenario: Detection alone grants no adoption authority + +- **WHEN** a scheduled comparison detects changed upstream sources +- **THEN** packaged content, runtime behavior and the reviewed baseline remain unchanged until their respective authorized actions occur diff --git a/openspec/specs/work-continuity/spec.md b/openspec/specs/work-continuity/spec.md new file mode 100644 index 0000000..74a9a12 --- /dev/null +++ b/openspec/specs/work-continuity/spec.md @@ -0,0 +1,152 @@ +# work-continuity Specification + +## Purpose +Preserve meaningful work context so authorized work can resume across compaction, session changes and delegation without reconstructing failed approaches or asking the user to repeat known context. + +These are the selected N1 outcome contracts. Historical source acceptance remains intact; new code-mode source tasks and behavioral acceptance remain unexecuted. See design and tasks for qualified delivery boundaries. + +## Requirements + +### Requirement: Meaningful progress checkpoints + +During authorized tracked work, the integration SHALL guide preservation of material attempts, outcomes, rationale, blockers and the next action at meaningful work transitions without requiring repeated user reminders. Records SHALL preserve relevant prior learning through append-style checkpoints and distinguish verified results from hypotheses; replacement of existing notes SHALL be intentional, not the default checkpoint path. + +#### Scenario: Failed approach changes the next step + +- **WHEN** an attempted solution fails and work proceeds to another approach +- **THEN** durable task context records the attempted approach, failure evidence, reason for changing course and next action +- **AND** recovery does not require repeating the failed experiment to rediscover its outcome + +#### Scenario: No meaningful new learning + +- **WHEN** an operation produces no material change in work context +- **THEN** the integration does not require a redundant write solely to satisfy a frequency or quantity target + +### Requirement: Relevant reusable knowledge + +The integration SHALL distinguish task-specific progress from supported reusable project knowledge, guide retrieval of relevant existing knowledge before relying on or duplicating it, and preserve scope and evidence when recording or correcting reusable knowledge within the user's authority. + +#### Scenario: Discovery applies beyond the current issue + +- **WHEN** a validated finding is likely to help later work and equivalent knowledge is absent or outdated +- **THEN** the workflow preserves the finding as reusable project knowledge with its applicability and evidence +- **AND** task context retains a reference sufficient to connect the finding to the work + +#### Scenario: Temporary state is not a general rule + +- **WHEN** a transient failure or unsupported hypothesis is useful only to the current task +- **THEN** it remains qualified task context rather than an asserted general project fact + +### Requirement: Recovery from durable state + +On resuming authorized work after compaction or a fresh session, the integration SHALL guide retrieval of relevant task state and reusable knowledge sufficient to identify previous attempts, decisions, blockers and the next action. Missing, stale or unavailable context SHALL be disclosed rather than presented as successful recovery. + +#### Scenario: Conversation history is absent + +- **WHEN** a new session resumes a task with durable checkpoints but no prior conversation +- **THEN** the workflow retrieves and uses the relevant checkpoints and knowledge to explain the current position and next action without asking the user to restate recorded facts + +#### Scenario: Knowledge changed since cached context + +- **WHEN** recovery or a relevant work transition requires knowledge newer than cached context +- **THEN** the workflow obtains current relevant state or reports why it cannot +- **AND** it does not treat cached absence as proof that no knowledge exists + +### Requirement: Delegated knowledge and closure evidence + +For selected delegated workflows, the integration SHALL preserve the task's scope, significant learning, validation, blockers and next action across the worker-to-caller boundary. Completion SHALL be distinguished from partial progress; closure SHALL require relevant acceptance and validation evidence. + +#### Scenario: Worker returns a partial result + +- **WHEN** a worker stops after a failed approach or blocker +- **THEN** the caller receives the attempt, outcome, durable references and next action needed to continue +- **AND** the result is not represented as completed work merely because the invocation ended + +#### Scenario: Closure with unresolved acceptance + +- **WHEN** a task has unresolved acceptance criteria or relevant validation has failed +- **THEN** the workflow preserves the unresolved state and evidence rather than closing it as complete + +### Requirement: Native trigger and semantic responsibility separation + +The selected integration SHALL distinguish awaited interception from observational events and semantic judgment from transport success. It SHALL document the selected native mechanism for each continuity behavior, preserve failed or uncertain persistence status, and avoid indiscriminate conversion of tool results or transcripts into durable memory. + +#### Scenario: Compaction starts with unsaved meaning + +- **WHEN** compaction occurs before a semantic checkpoint has been prepared +- **THEN** summary guidance does not claim that durable task context was saved +- **AND** an event subscriber is not treated as a compaction barrier or a guaranteed final-write mechanism + +#### Scenario: Compaction carries known references and gaps + +- **WHEN** the awaited compaction hook prepares summary or native-provider context +- **THEN** it carries acknowledged durable references and known unsaved or uncertain status without initiating persistence +- **AND** it does not wait for a same-session semantic checkpoint, queued write or permission escalation + +#### Scenario: User stops execution + +- **WHEN** an execution interruption or permission wait is observed +- **THEN** the integration does not automatically restart work or approve the blocked operation to manufacture a final checkpoint +- **AND** recovery uses earlier durable checkpoints and reports known gaps + +#### Scenario: Host retains and replays compacted history + +- **WHEN** 2.1.1 summary/native history is recovered or forked, or its provider/model changes +- **THEN** the integration preserves host-owned native replay and metadata.keepID/legacy metadata.recent semantics without substituting a provider replacement window for a summary +- **AND** tool execution state and UI metadata are not treated as renamed provider replay payloads + +### Requirement: Honest persistence acknowledgement + +N1 operations SHALL acknowledge only evidenced outcomes and distinguish failure, uncertain completion and a landed but uncommitted write. They SHALL NOT blindly retry an ambiguous append, force a rejected write or imply a compaction summary is durable Beads state. + +#### Scenario: Memory write landed but commit failed + +- **WHEN** the CLI reports a stored memory with failed or indeterminate commit status +- **THEN** the integration preserves the memory reference and qualified status without claiming committed durability or automatically committing + +#### Scenario: Interrupted append has unknown outcome + +- **WHEN** cancellation or timeout prevents confirmation of an append +- **THEN** recovery checks current durable state before another write and exposes the uncertainty + +### Requirement: Typed daily-loop composition + +The integration SHALL require Beads 1.3.1 or later and expose the eleven-operation beads daily-loop namespace with declared discriminated outputs and fixed supported argv in the accepted subcommand-first canonical form with --sandbox. It SHALL preserve CLI state/claim ownership, per-operation permissions, executor attribution, partial/uncertain outcomes and sole acknowledgement. It SHALL NOT expose generic argv or workflow macros, infer authority from readiness, or retry ambiguous writes. + +#### Scenario: Bounded discovery is incomplete + +- **WHEN** a bounded query, batch get or direct-edge read is incomplete or partly fails +- **THEN** results identify query scope, per-ID failures and exact or unknown completeness +- **AND** unknown or failed data is not reported as missing, empty or unblocked + +#### Scenario: Claim or create outcome is uncertain + +- **WHEN** a started claim or create lacks authoritative acknowledgement +- **THEN** the result or recovery state preserves operation, target/query, actual actor and uncertainty +- **AND** claim acknowledgement requires the actual executor as assignee in the actionable state; not-claimed requires evidenced non-claim rather than guessed CLI error text +- **AND** an uncertain create requires search/readback before any caller-considered retry + +#### Scenario: Dependent writes compose + +- **WHEN** authorized work sequences claim, checkpoint and optional closure +- **THEN** each operation uses its own canonical permission resources and post-approval/pre-spawn guard, location, actor and cancellation checks +- **AND** dependent writes are ordered and stop on rejection or non-acknowledgement, with no transaction or exactly-once promise +- **AND** closure still requires caller authority and acceptance evidence + +#### Scenario: Canonical execution preserves permission and sync policy + +- **WHEN** a typed read or write is prepared +- **THEN** its resource exactly renders qualified subcommand-first argv with --sandbox on all operations and derived --actor on writes, inserted after operation arguments and before any -- free-text terminator +- **AND** no automatic sync or additional permission rules compensate for changed prefixes + +#### Scenario: Execution is refused before spawn + +- **WHEN** input is invalid, actual identity is unavailable, a guard hold applies or location changes +- **THEN** the accepted pre-spawn refusal remains thrown; permission rejection and cancellation also propagate unchanged +- **AND** no executed:false result branch replaces the refusal; a started interrupted write retains recovery uncertainty even without a returned normal value + +#### Scenario: CLI contract is unsupported + +- **WHEN** a proposed filter, argv or output shape is not established on supported minimum/current Beads +- **THEN** the schema does not advertise that unsupported contract and the affected core operation stops for review if no supported mapping exists +- **AND** no implicit version bump, routing repair or invented conflict response supplies the missing contract diff --git a/openspec/specs/workflow-access/spec.md b/openspec/specs/workflow-access/spec.md new file mode 100644 index 0000000..ca93fd8 --- /dev/null +++ b/openspec/specs/workflow-access/spec.md @@ -0,0 +1,221 @@ +# workflow-access Specification + +## Purpose +Make selected Beads workflows discoverable and usable through native OpenCode capabilities while preserving project authority, explicit ownership and honest failure handling. + +Owner selections apply to the complete marketplace-main corpus: core workflows adapted, advanced guidance available on demand, supported administration accessible under normal permissions, and obsolete or host-specific content corrected/replaced. The inventory maps every member to native delivery; acceptance is still unexecuted. + +## Requirements + +### Requirement: Discoverable selected workflows + +The package-owned `oc-beads` skill, all 15 corpus resources plus CODE_MODE and ADR rationale and all 29 `oc-beads:<command>` entries plus status (including documented replacements) SHALL have a documented native entry point that exposes its purpose and relevant supporting guidance. Advertised command and skill behavior SHALL match the installed implementation and available tools rather than assume absent MCP tools or unreachable resources. + +#### Scenario: User invokes a selected command + +- **WHEN** a user discovers and invokes an installed selected command with arguments +- **THEN** the advertised workflow receives the intended arguments through its documented native entry point +- **AND** uses available execution tools without inventing a Beads MCP tool + +#### Scenario: Task requires deeper guidance + +- **WHEN** a selected workflow directs an agent to a supporting resource +- **THEN** that resource is reachable from the installed guidance's documented base +- **AND** SKILL.md explicitly links all 15 corpus resources plus CODE_MODE and ADR rather than relying on the host’s ten-file auto-listing +- **AND** its presence alone is not reported as evidence that the model used it + +#### Scenario: Installed resource requires external access + +- **WHEN** an installed resource outside the workspace encounters external-directory ask or deny +- **THEN** the integration respects approval/rejection and reports unreachable guidance honestly without bypassing permission + +### Requirement: Coherent context and authority + +The integration SHALL preserve applicable user and repository authority across injected workflow, skills, commands and delegated guidance. It SHALL distinguish missing or failed context from context withheld by an explicit hold, and SHALL NOT recommend bypassing a hold through manual fallback. + +#### Scenario: Migration hold conflicts with prime-first advice + +- **WHEN** workflow context is withheld because store access is not authorized +- **THEN** skill, command and worker recovery guidance preserves that restriction +- **AND** reports the reason and required owner action without opening the store + +#### Scenario: Context is already available + +- **WHEN** current applicable workflow context has already been supplied +- **THEN** discovery guidance does not require another prime solely because the skill was loaded +- **AND** distinct applicable local constraints remain available when duplicate guidance is reduced + +### Requirement: Owned installation and user customization + +The OpenCode npm package SHALL supply and register the complete owner-selected integration, including its selected command, skill/resource, agent and lifecycle surfaces. Advertised capability SHALL NOT depend on a separate undocumented or manual skill/resource installation. The installed Beads CLI and upstream-owned workspace/data initialization remain external prerequisites. + +#### Scenario: Package installed in an initialized workspace + +- **WHEN** a supported OpenCode host loads the package in an initialized Beads workspace with its prerequisites satisfied +- **THEN** the selected packaged workflows and supporting guidance are available through native registration without separately installing the rich Claude plugin or copying skills +- **AND** qualification verifies effective discovery and installed resource paths rather than source-tree presence alone + +Selected integration surfaces SHALL document who installs, updates and removes them and how user overrides are resolved. Updates and removal SHALL preserve unrelated or user-modified artifacts and SHALL NOT silently overwrite the upstream-owned shared skill or runtime configuration. + +#### Scenario: User customizes an installed workflow + +- **WHEN** an integration update or removal encounters user configuration, file commands or a colliding artifact +- **THEN** it preserves user-owned files and ordinary configuration/file command overrides through native post-ordered precedence on initial load and rebuild +- **AND** does not remove unrelated commands, skills or configuration +- **AND** registers all selected namespaced commands without setup-time withholding; same-name conflicts with other plugin transforms follow native order, with detectable conflicts diagnosed rather than a promise of universal collision prevention +- **AND** removing a user override restores the package entry through native rebuild; skill candidate collisions use the public per-rebuild editor check rather than a permanent setup-time gate + +#### Scenario: Installed package is unloaded and reactivated + +- **WHEN** the installed package is unloaded and subsequently reactivated on the qualified disposable host +- **THEN** owned entries disappear on unload and the expected commands and skill return once under native user-override precedence +- **AND** an eligible request receives one Beads context injection, without a late result from work pending in the unloaded generation +- **AND** acceptance records actual host lifecycle results rather than treating fixture disposal or registry rebuilds as installed reactivation evidence + +### Requirement: Scope-preserving execution and visible failure + +Selected workflows SHALL preserve the user's requested execution scope, distinguish inspection from mutation, and report unsuccessful or partial operations accurately. Optional extensions SHALL expose their scope and constraints without silently replacing base workflows or authorizing unrelated actions. + +#### Scenario: Graph analysis is read-only + +- **WHEN** the user requests an issue-graph overview without authorizing task execution +- **THEN** the workflow returns the analysis without claiming, editing or closing issues + +#### Scenario: Single atomic operation + +- **WHEN** the user authorizes one issue operation +- **THEN** the workflow completes or reports failure of that operation without automatically starting unrelated ready work + +#### Scenario: Optional worker limit interrupts work + +- **WHEN** a selected bounded worker or permission constraint stops before completion +- **THEN** the result identifies the incomplete work, relevant context and next action +- **AND** does not claim that the task or durable write succeeded + +### Requirement: Explicit native availability and fallback + +The redesign SHALL use N1 with exact OpenCode 2.1.1 qualification and `>=2.1.1 <2.2` admission through opt-in beta/next delivery. It SHALL NOT silently migrate stable consumers, promise arbitrary minor/major compatibility or substitute N0. Concrete incompatibilities SHALL block the affected path with diagnostics. Publication remains separately authorized. + +#### Scenario: A different host version is considered + +- **WHEN** a future admitted patch or a version outside the declared range is considered +- **THEN** an admitted patch is not described as tested without measured evidence, and an out-of-range host remains unsupported pending explicit contract review + +#### Scenario: Host cannot satisfy N1 + +- **WHEN** the host lacks the selected permission or lifecycle contract +- **THEN** the integration reports the unsupported path and does not enable privileged fallback execution or silently weaken guarantees + +#### Scenario: Experimental capability is considered + +- **WHEN** a released experimental mechanism could improve task flow or recovery +- **THEN** the packet records its semantics, qualification, maintenance and fallback consequences before adoption +- **AND** instability alone is neither approval nor automatic exclusion + +#### Scenario: Plugin reloads or a session changes location + +- **WHEN** registrations are unloaded or a session moves, forks or is deleted +- **THEN** the selected integration disposes or re-resolves affected state and observers without leaking task context across sessions or locations +- **AND** it preserves user overrides and does not claim cleanup guarantees a final durable write + +### Requirement: Restricted-agent typed operation boundary + +Typed tools and direct shell fallback SHALL respect shell visibility and operation-specific permissions using actual invocation identity, canonical resources and narrow saved grants. Guard holds, location, attribution and cancellation SHALL be rechecked after approval and before execution. Beads owns database/routing/consistency semantics; normal CLI equivalence does not promise filesystem confinement. + +#### Scenario: Agent wholly denies shell + +- **WHEN** a restricted agent's effective rules wholly deny shell +- **THEN** typed tools are absent from its code-mode inventory, not merely denied after selection; default typed tools have no direct registration and native shell is never callable inside execute + +#### Scenario: Invoking agent differs from session default + +- **WHEN** a typed operation is called by an agent with more restrictive rules than the session's default agent +- **THEN** permission evaluation uses the invoking agent and does not broaden saved approvals to unrelated bd operations + +#### Scenario: Saved approval remains narrow + +- **WHEN** a typed operation requests approval with `save: []` +- **THEN** even an “always” response installs no new saved grant and guidance explains possible repeat prompts +- **AND** existing shell `bd *` grants may authorize equivalent typed operations, while configured denies retain precedence + +#### Scenario: User corrects or declines a typed operation + +- **WHEN** the host permission assertion returns user correction with feedback or explicit decline +- **THEN** the adapter propagates the assertion rejection unchanged and preserves correction feedback without executing the rejected target operation +- **AND** it does not acknowledge target success or automatically retry the operation +- **AND** code-mode composition remains available despite the owner-accepted OpenCode 2.1.1 limitation that a plain decline may become an inner error in an outer completed result and permit subsequent model steps or independently permissioned work +- **AND** rejection of one operation does not promise whole-step termination; every later operation retains its own permission checks, and explicit Stop/Cancel remains a separate cancellation guarantee + +#### Scenario: External target or held workspace + +- **WHEN** the explicit invocation/location requires external-directory access or the existing guard withholds store access +- **THEN** the operation preserves that restriction and refuses before store-opening execution unless invocation context and the required OpenCode authority are established +- **AND** Beads retains ownership of its internal routing; no final-store attestation or additional internal-route confinement is promised +- **AND** normal intended routing is not a speculative blocker or a prerequisite for upstream routing-disable support or local repair logic +- **AND** unresolved OpenCode permission equivalence prevents the affected execution + +#### Scenario: Agent claims and hands off tracked work + +- **WHEN** a selected workflow writes, claims or renews tracked work, or delegates execution +- **THEN** it supplies the actual executor's explicit actor consistently and verifies audit author, account owner and assignee separately +- **AND** coordination or review does not silently transfer a claim, actor naming does not grant authority, and historical authorship is not rewritten + +### Requirement: Authorized continuation without scope expansion + +Canonical execution SHALL allow continuation within an authorized batch or goal, with atomic claims and per-bead validation. Exact assignments SHALL stop without substitution; discovery SHALL NOT expand authority. Cancellation, exhausted scope or a decision blocker SHALL stop continuation, and plugin events SHALL NOT restart it. Denied authority SHALL block the rejected operation; the typed-operation scenarios define the accepted code-mode continuation exception. + +#### Scenario: Exact assignment completes + +- **WHEN** the assigned bead is complete or cannot be acted on +- **THEN** the workflow reports its evidence or blocker and returns without selecting another bead + +#### Scenario: Authorized batch has more work + +- **WHEN** one bead meets its acceptance and another actionable bead remains within the authorized batch +- **THEN** the workflow may continue after recording evidence and claiming that bead atomically +- **AND** newly discovered out-of-scope work remains a reported follow-up + +#### Scenario: Child result arrives after stop + +- **WHEN** a child completion races a deliberate stop +- **THEN** the integration does not initiate renewed work or closure from the notification and preserves partial status for later authorized recovery + +### Requirement: Read-only native diagnostics + +The package SHALL expose effective discovery, compatibility, ownership conflicts and context/persistence status through a native status entry without opening a withheld store, changing configuration or exposing credentials. Diagnostics SHALL distinguish unavailable, failed, withheld and uncertain outcomes. + +#### Scenario: Discovery collision + +- **WHEN** a user customization prevents an owned entry from taking effect +- **THEN** status reports effective names/descriptions and detectable overrides while preserving native user-config precedence +- **AND** a description mismatch may identify an override but SHALL NOT be presented as proof of ownership; public owner identity and identical-description overrides remain unobservable and are disclosed +- **AND** command registration status reflects actual registration and lookup outcomes rather than an obsolete blanket hold + +### Requirement: Capability-aware actor and context guidance + +The integration SHALL render actual executor identity and request capabilities, deduplicate guidance while retaining authority, and retain full prime pending measurement. It SHALL preserve all 30 human command names, overrides and selected advanced/admin workflows, with explicit links to 16 resources plus ADR. Durable references and freshness SHALL be operation-aware. + +#### Scenario: Execute is denied + +- **WHEN** execute is wholly denied for the actual request +- **THEN** code-mode-only Beads tools are unavailable and guidance offers direct shell only if separately available and not held +- **AND** when both capabilities are unavailable the limitation is reported without bypass or invented tool access + +#### Scenario: Typed and shell writes share an executor + +- **WHEN** a primary or delegated agent alternates typed and direct-shell writes +- **THEN** context and typed results expose the same actor derived from the actual request/invocation session and agent; attribution grants no permission, and permission checks separately use actual context.agent +- **AND** child executors keep their own identities, no model-controlled actor argument exists, and beads-task-agent remains an optional execution profile + +#### Scenario: A write changes cached state + +- **WHEN** a memory write may have landed, external state changes, or recovery needs current knowledge +- **THEN** relevant location context is refreshed or its uncertainty reported +- **AND** issue-write prime refresh is retained until supported prime content is verified independent of issue state +- **AND** acknowledged writes avoid redundant generic recovery cues, while uncertain results preserve targeted readback needs + +#### Scenario: Actor or capabilities change + +- **WHEN** a request changes agent, session or effective capabilities +- **THEN** rendered actor/capability guidance is recomputed without leaking cached identity or promising unobserved permissions +- **AND** deduplication preserves distinct local authority and the required scope, stop, ownership and honest-outcome rules diff --git a/package.json b/package.json index e63b9ec..8756690 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ }, "files": [ "src", + "assets/oc-beads", "README.md", "LICENSE" ], @@ -55,11 +56,11 @@ "typecheck": "tsc --noEmit" }, "beads": { - "minimum": "1.3.0", + "minimum": "1.3.1", "validated": "1.3.1" }, "peerDependencies": { - "@opencode/plugin": ">=2.0.26 <3" + "@opencode/plugin": ">=2.1.1 <2.2" }, "peerDependenciesMeta": { "@opencode/plugin": { @@ -67,7 +68,7 @@ } }, "devDependencies": { - "@opencode/plugin": "2.0.26", + "@opencode/plugin": "2.1.1", "@types/bun": "1.3.4", "@types/node": "^22.0.0", "typescript": "5.9.3" diff --git a/scripts/bd-contract.ts b/scripts/bd-contract.ts index ebfa02e..512ff4d 100644 --- a/scripts/bd-contract.ts +++ b/scripts/bd-contract.ts @@ -16,7 +16,7 @@ import { parseVersion, } from "../src/guard"; import { minimizeBeadsInstructions } from "../src/instructions"; -import { runBd } from "../src/prime"; +import { PrimeProcessError, runBd } from "../src/prime"; const failures: string[] = []; function check(condition: unknown, message: string, failureHint?: string): void { @@ -32,7 +32,13 @@ async function attempt<T>(label: string, run: () => Promise<T>): Promise<T | und try { return await run(); } catch (error) { - check(false, `${label}: ${error instanceof Error ? error.message : String(error)}`); + check( + false, + `${label}: ${error instanceof Error ? error.message : String(error)}`, + error instanceof PrimeProcessError + ? [error.stderr.trim(), error.stdout.trim()].filter(Boolean).join("\n ") + : undefined + ); return undefined; } } @@ -77,7 +83,7 @@ if (isolatedRoot === undefined) { const root = await fs.mkdtemp(path.join(os.tmpdir(), "bd-contract-")); const home = path.join(root, "home"); await fs.mkdir(home); - const env: Record<string, string | undefined> = { ...process.env, HOME: home, BD_CONTRACT_ROOT: root }; + const env: Record<string, string | undefined> = { ...process.env, HOME: home, XDG_CONFIG_HOME: path.join(home, ".config"), XDG_DATA_HOME: path.join(home, ".local/share"), BD_CONTRACT_ROOT: root }; delete env.BEADS_DIR; const child = Bun.spawn([process.execPath, import.meta.path], { env, stdio: ["ignore", "inherit", "inherit"] }); const exitCode = await child.exited; @@ -87,7 +93,21 @@ if (isolatedRoot === undefined) { const root = isolatedRoot; try { - const bd = (cwd: string, args: readonly string[]) => runBd(cwd, args, { timeoutMs: 60_000 }); + const actor = process.env.BEADS_ACTOR ?? "bd-contract"; + const bd = async (cwd: string, args: readonly string[]): Promise<string> => { + const argv = [...args]; + const write = ["init", "create", "update", "close", "remember", "setup", "migrate", "dep"].includes(argv[0] ?? "") && !(argv[0] === "dep" && argv[1] === "list"); + const terminator = argv.indexOf("--"); + argv.splice(terminator < 0 ? argv.length : terminator, 0, ...(write ? ["--actor", actor] : []), "--sandbox"); + try { + const stdout = await runBd(cwd, argv, { timeoutMs: 60_000 }); + console.log(`observed ${JSON.stringify({ cwd, argv, exitCode: 0, stdout })}`); + return stdout; + } catch (error) { + console.log(`observed ${JSON.stringify({ cwd, argv, exitCode: error instanceof PrimeProcessError ? error.exitCode : null, stdout: error instanceof PrimeProcessError ? error.stdout : undefined, stderr: error instanceof PrimeProcessError ? error.stderr : undefined, error: String(error) })}`); + throw error; + } + }; const guardRuntime = createGuardRuntime(bd); const repo = path.join(root, "repo"); const bare = path.join(root, "bare"); @@ -107,10 +127,14 @@ try { `bd ${installed?.join(".")} meets the supported minimum ${MINIMUM_BD_VERSION}` ); + const bareGuard = await checkBeadsStore(bare, guardRuntime); check( - (await checkBeadsStore(bare, guardRuntime)).kind === "uninitialized", - "guard reports a repository without a workspace as uninitialized" + bareGuard.kind === "uninitialized", + `guard reports a repository without a workspace as uninitialized (actual: ${bareGuard.kind})` ); + if (bareGuard.kind !== "uninitialized") { + await attempt("bd context --json (uninitialized diagnostic)", () => bd(bare, ["context", "--json"])); + } await attempt("bd init", () => bd(repo, ["init", "--skip-agents", "-q"])); const where = json(await attempt("bd where --json", () => bd(repo, ["where", "--json"]))) as @@ -132,9 +156,12 @@ try { "guard reports a current store as ready" ); + const initialFullPrime = await attempt("prime before issue writes", () => bd(repo, ["prime", "--full"])); + // Commands named by the task-agent prompt and guidance. + const priorNotes = "Prior attempt failed; retain this evidence.\nNext action: verify append."; const created = json( - await attempt("bd create", () => bd(repo, ["create", "contract probe", "-t", "task", "-p", "2", "--json"])) + await attempt("bd create", () => bd(repo, ["create", "contract probe", "-t", "task", "-p", "2", "--notes", priorNotes, "--json"])) ); const id = issueId(created); check(id, "bd create --json returns an issue id"); @@ -145,6 +172,79 @@ try { json(await attempt("bd update --claim", () => bd(repo, ["update", id, "--claim", "--json"]))) !== undefined, "bd update <id> --claim --json succeeds" ); + const checkpoint = "Validated append contract.\nLiteral '$ARGUMENTS' $(no-shell) `text`."; + const appended = json(await attempt("bd update --append-notes", () => + bd(repo, ["update", id, "--append-notes", checkpoint, "--json"]) + )) as Array<{ id?: unknown; notes?: unknown }> | undefined; + const expectedNotes = `${priorNotes}\n${checkpoint}`; + check(Array.isArray(appended) && appended[0]?.id === id && appended[0]?.notes === expectedNotes, + "append acknowledgement retains prior notes and exact multiline checkpoint"); + const recovered = json(await attempt("bd show appended notes", () => + bd(repo, ["show", id, "--json"]) + )) as Array<{ id?: unknown; notes?: unknown; assignee?: unknown; status?: unknown; created_by?: unknown }> | undefined; + check(Array.isArray(recovered) && recovered[0]?.id === id && recovered[0]?.notes === expectedNotes, + "a fresh show retains both prior notes and the appended checkpoint"); + check(Array.isArray(recovered) && recovered[0]?.assignee === actor && recovered[0]?.status === "in_progress" && recovered[0]?.created_by === actor, + `created_by and claimed assignee use the explicit test executor (${actor})`); + // Daily-loop contracts: exact supported argv, returned records and bounded query shapes. + const rows = (value: unknown): Array<Record<string, unknown>> => Array.isArray(value) ? value as Array<Record<string, unknown>> : []; + const parent = issueId(json(await attempt("create parent", () => bd(repo, ["create", "--title", "contract parent", "--type", "epic", "--priority", "2", "--json"])))); + const child = json(await attempt("create option-like title and selected fields", () => bd(repo, ["create", "--title", "--assignee=x", "--type", "task", "--priority", "1", "--description", "contract-marker description", "--acceptance", "contract acceptance", "--design", "contract design", "--parent", parent!, "--deps", `discovered-from:${id}`, "--labels", "contract-label", "--json"]))); + const childId = issueId(child); + const childRecord = (Array.isArray(child) ? child[0] : child) as Record<string, unknown> | undefined; + check(childId && childRecord?.title === "--assignee=x" && !childRecord.assignee && childRecord.created_by === actor && childRecord.acceptance_criteria === "contract acceptance" && childRecord.design === "contract design", "create preserves flag-like title as data, generated ID, selected fields and no auto-assignee"); + for (const type of ["bug", "feature", "chore"]) { + const value = json(await attempt(`create ${type}`, () => bd(repo, ["create", "--title", `contract ${type}`, "--type", type, "--priority", "2", "--json"]))); + check(issueId(value), `create supports ${type}`); + } + if (childId && parent) { + for (const [view, flags] of [ + ["ready", ["--parent", parent, "--label", "contract-label"]], + ["list", ["--parent", parent, "--label", "contract-label", "--status", "open"]], + ["search", ["--label", "contract-label", "--status", "open", "--", "--assignee=x"]], + ] as const) { + const args = [view, "--limit", "2", "--json", ...flags]; + const value = json(await attempt(`find ${view} filtered`, () => bd(repo, args))); + check(rows(value).length === 1 && rows(value)[0]?.id === childId, `${view} supports declared filters, terminator and bounded array result`); + } + for (const view of ["ready", "list", "search"]) { + const text = view === "search" ? ["--", "contract"] : []; + const value = json(await attempt(`${view} limit`, () => bd(repo, [view, "--limit", "2", "--json", ...text]))); + check(rows(value).length === 2, `${view} enforces limit for limit+1 projection`); + } + for (const direction of ["down", "up"]) { + const value = json(await attempt(`dep list ${direction}`, () => bd(repo, ["dep", "list", direction === "down" ? childId : parent, "--direction", direction, "--type", "parent-child", "--json"]))); + check(rows(value).some(r => r.id === (direction === "down" ? parent : childId) && r.dependency_type === "parent-child"), `dep list ${direction} preserves typed direct edge direction`); + } + const blocker = issueId(json(await attempt("create independent blocker", () => bd(repo, ["create", "--title", "contract blocker", "--json"])))); + await attempt("add blocker", () => bd(repo, ["dep", "add", childId, blocker!, "--type", "blocks", "--json"])); + const blocked = json(await attempt("blocked filters", () => bd(repo, ["blocked", "--parent", parent, "--label", "contract-label", "--json"]))); + check(rows(blocked).some(r => r.id === childId), "blocked returns scoped array (no limit supported; completeness unknown)"); + const allEdges = json(await attempt("dep list all", () => bd(repo, ["dep", "list", childId, "--direction", "down", "--json"]))); + check(rows(allEdges).some(r => r.id === blocker && r.dependency_type === "blocks"), "dep list without type retains dependency_type"); + const changed = rows(json(await attempt("selected update fields", () => bd(repo, ["update", childId, "--status", "blocked", "--priority", "3", "--add-label", "added", "--remove-label", "contract-label", "--json"]))))[0]; + check(changed?.status === "blocked" && changed.priority === 3 && Array.isArray(changed.labels) && changed.labels.includes("added") && !changed.labels.includes("contract-label"), "selected update returns requested status, priority and labels"); + for (const status of ["deferred", "open", "in_progress"]) { + const changed = rows(json(await attempt(`update status ${status}`, () => bd(repo, ["update", childId, "--status", status, "--json"]))))[0]; + check(changed?.status === status, `update status ${status} is evidenced`); + } + } + // Intentional fixture assignment is distinct from the actual executor/audit author. + const occupied = issueId(json(await attempt("create occupied fixture", () => bd(repo, ["create", "--title", "occupied fixture", "--assignee", "fixture-owner", "--json"])))); + if (occupied) { + try { await bd(repo, ["update", occupied, "--claim", "--json"]); check(false, "claim conflict refuses another assignee"); } + catch (error) { + check(error instanceof PrimeProcessError && error.exitCode === 1 && error.stdout.trim() === "" && error.stderr.includes("issue already claimed: already assigned to") && error.stderr.includes("fixture-owner"), "claim conflict has exit 1, empty stdout and explicit already-assigned diagnostic"); + } + } + try { await bd(repo, ["show", "contract-not-found", "--json"]); check(false, "missing show refuses unknown ID"); } + catch (error) { check(error instanceof PrimeProcessError && error.exitCode !== 0, "unknown single-ID show remains a failure, never synthetic missing"); } + for (const view of ["ready", "list", "search"]) { + const value = json(await attempt(`${view} assignee`, () => bd(repo, [view, "--assignee", actor, "--limit", "2", "--json", ...(view === "search" ? ["--", "contract"] : [])]))); + check(view === "ready" ? rows(value).length === 0 : rows(value).some(r => r.id === id), `${view} filters by actual assignee (ready excludes in_progress)`); + } + const issuePrime = await attempt("prime after issue updates", () => bd(repo, ["prime", "--full"])); + console.log(`observed prime issue comparison: ${JSON.stringify({ equal: initialFullPrime === issuePrime, conclusion: "bounded observation only; retain conservative issue-write invalidation" })}`); const discovered = issueId( json( await attempt("bd create --deps discovered-from", () => @@ -153,11 +253,67 @@ try { ) ); check(discovered, "bd create --deps discovered-from:<id> succeeds"); - check( - (await attempt("bd close --reason", () => bd(repo, ["close", id, "--reason", "contract", "--json"]))) !== undefined, - "bd close <id> --reason --json succeeds" - ); + const closed = rows(json(await attempt("bd close --reason", () => bd(repo, ["close", id, "--reason", "contract", "--json"]))))[0]; + check(closed?.id === id && closed.status === "closed", "close acknowledges exact target in closed state"); + } + + // Real supported memory results, without injecting empty rows or manufacturing commit outcomes. + const initialMemories = json(await attempt("bd memories (empty)", () => bd(repo, ["memories", "--json"]))); + check(initialMemories !== null && typeof initialMemories === "object" && !Array.isArray(initialMemories) && + "schema_version" in initialMemories && initialMemories.schema_version === 1 && Object.keys(initialMemories).length === 1, + "a fresh store returns versioned metadata with no memory entries", `Observed JSON: ${JSON.stringify(initialMemories)}`); + const emptyKey = "contract-empty"; + try { + await bd(repo, ["remember", "--key", emptyKey, "--json", "--", ""]); + check(false, "bd remember rejects empty content"); + } catch (error) { + const response = json(error instanceof PrimeProcessError ? error.stdout : undefined) as { error?: unknown } | undefined; + check(error instanceof PrimeProcessError && error.exitCode === 1 && response?.error === "memory content cannot be empty", + "bd remember rejects empty content with exit 1 and its JSON diagnostic"); + if (error instanceof PrimeProcessError) console.log(`observed empty remember: ${error.stdout.trim()}`); + } + for (const key of [emptyKey, "contract-missing"]) { + try { + await bd(repo, ["recall", key, "--json"]); + check(false, `bd recall ${key} exits 1 for a missing memory`); + } catch (error) { + const response = json(error instanceof PrimeProcessError ? error.stdout : undefined) as + { key?: unknown; value?: unknown; found?: unknown } | undefined; + check(error instanceof PrimeProcessError && error.exitCode === 1 && response?.key === key && response.value === "" && response.found === false, + `bd recall ${key} reports missing with empty value, found=false and exit 1`); + if (error instanceof PrimeProcessError) console.log(`observed missing recall: ${error.stdout.trim()}`); + } } + const memoryKey = "contract-knowledge"; + const memory = "Reusable contract-marker knowledge.\nLiteral '$ARGUMENTS' $(no-shell) `text`."; + const remembered = json(await attempt("bd remember", () => + bd(repo, ["remember", "--key", memoryKey, "--json", "--", memory]) + )) as { key?: unknown; value?: unknown; action?: unknown } | undefined; + check(remembered?.key === memoryKey && remembered.value === memory && remembered.action === "remembered", + "bd remember returns the explicit key, exact content and remembered action"); + console.log(`observed remember: ${JSON.stringify(remembered)}`); + const recalled = json(await attempt("bd recall (found)", () => bd(repo, ["recall", memoryKey, "--json"]))) as + { key?: unknown; value?: unknown; found?: unknown } | undefined; + check(recalled?.key === memoryKey && recalled.value === memory && recalled.found === true, + "bd recall finds the exact stored multiline memory"); + const updatedMemory = `${memory}\nUpdated contract-marker evidence.`; + const updated = json(await attempt("bd remember (update)", () => + bd(repo, ["remember", "--key", memoryKey, "--json", "--", updatedMemory]) + )) as { key?: unknown; value?: unknown; action?: unknown } | undefined; + check(updated?.key === memoryKey && updated.value === updatedMemory && updated.action === "updated", + "remembering the same key updates it in place with the updated action"); + console.log(`observed updated remember: ${JSON.stringify(updated)}`); + for (const args of [["memories", "--json"], ["memories", "--json", "--", "contract-marker"]]) { + const memories = json(await attempt(`bd ${args.join(" ")}`, () => bd(repo, args))) as Record<string, unknown> | undefined; + check(memories && !Array.isArray(memories) && memories.schema_version === 1 && Object.keys(memories).length === 2 && memories[memoryKey] === updatedMemory, + `${args.join(" ")} returns versioned metadata and only the updated memory, with no empty or duplicate record`, `Observed JSON: ${JSON.stringify(memories)}`); + } + const noMatches = json(await attempt("bd memories (no match)", () => + bd(repo, ["memories", "--json", "--", "absent-contract-marker"]) + )); + check(noMatches !== null && typeof noMatches === "object" && !Array.isArray(noMatches) && + "schema_version" in noMatches && noMatches.schema_version === 1 && Object.keys(noMatches).length === 1, + "a successful memory search with no matches returns versioned metadata and no memory entries", `Observed JSON: ${JSON.stringify(noMatches)}`); // Install shape: the plugin assumes plain `bd init` and never `bd setup opencode` (Claude Code parity). const setupRepo = path.join(root, "setup"); @@ -206,7 +362,12 @@ try { agentsFile.includes("<!-- END BEADS INTEGRATION -->"), "bd setup opencode writes a full-profile block with the expected markers" ); - const minimized = minimizeBeadsInstructions(agentsFile); + const preserved = minimizeBeadsInstructions(agentsFile); + check( + preserved.replaced === 0 && preserved.text === agentsFile, + "the plugin preserves managed instructions when their body is not supplied in context" + ); + const minimized = minimizeBeadsInstructions(agentsFile, agentsFile); check( minimized.replaced > 0 && !minimized.text.includes("profile:full"), `the plugin replaces bd's duplicate AGENTS.md blocks (${minimized.replaced} block(s), ${agentsFile.length} -> ${minimized.text.length} chars)` diff --git a/scripts/check-beads-integration.ts b/scripts/check-beads-integration.ts new file mode 100644 index 0000000..32c7c2a --- /dev/null +++ b/scripts/check-beads-integration.ts @@ -0,0 +1,204 @@ +import { readFile } from "node:fs/promises"; + +const roots = ["internal/templates/skills/beads/", "plugins/beads/"]; +const shaPattern = /^[0-9a-f]{40}$/; +type SourceFetch = (url: string, init?: RequestInit) => Promise<Response>; +type RecordValue = Record<string, unknown>; +type SourceFile = { sha: string; mode: string }; +type Change = { + kind: "added" | "deleted" | "edited" | "renamed"; + path: string; + previousPath?: string; + before?: SourceFile; + after?: SourceFile; + beforeUrl?: string; + afterUrl?: string; +}; + +/** Reject absent or malformed API/inventory records rather than supplying empty defaults. */ +function record(value: unknown): RecordValue { + if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Missing or malformed record"); + return value as RecordValue; +} + +function sha(value: unknown): string { + if (typeof value !== "string" || !shaPattern.test(value)) throw new Error("Missing or malformed immutable Git SHA"); + return value; +} + +function sourcePath(value: unknown): string { + if (typeof value !== "string" || !value || value.split("/").some((p) => !p || p === "." || p === "..") || /[\x00-\x1f\\]/.test(value)) { + throw new Error("Missing or malformed source path"); + } + return value; +} + +function underRoot(path: string): boolean { + return roots.some((root) => path.startsWith(root)); +} + +/** Read only marketplace-main provenance; release inventory and local adaptation bytes are unrelated. */ +function selection(inventory: unknown) { + const main = record(record(inventory).marketplace_main); + if (typeof main.repository !== "string" || !/^[\w.-]+\/[\w.-]+$/.test(main.repository)) { + throw new Error("Missing or malformed marketplace-main repository"); + } + const commit = sha(main.commit); + if (!Array.isArray(main.files) || main.files.length === 0) throw new Error("Missing marketplace-main file inventory"); + const expected = new Map<string, string>(); + for (const value of main.files) { + const file = record(value); + const path = sourcePath(file.path); + if (expected.has(path)) throw new Error("Duplicate inventory path"); + expected.set(path, sha(file.git_blob)); + } + for (const root of roots) { + if (![...expected.keys()].some((path) => path.startsWith(root))) throw new Error("Inventory is missing a selected root"); + } + const supportPaths = [...expected.keys()].filter((path) => !underRoot(path)).sort(); + return { repository: main.repository, commit, expected, supportPaths }; +} + +/** Require an explicitly complete recursive tree at the exact resolved tree identity. */ +function selectedTree(value: unknown, treeSha: string, supportPaths: string[]): Map<string, SourceFile> { + const tree = record(value); + if (tree.sha !== treeSha || tree.truncated !== false || !Array.isArray(tree.tree)) { + throw new Error("Tree identity, completeness or entries unavailable (possibly truncated)"); + } + const seen = new Set<string>(); + const selected = new Map<string, SourceFile>(); + const modes: Record<string, string> = { "040000": "tree", "100644": "blob", "100755": "blob", "120000": "blob", "160000": "commit" }; + for (const value of tree.tree) { + const entry = record(value); + const path = sourcePath(entry.path); + const id = sha(entry.sha); + if (seen.has(path)) throw new Error("Duplicate tree path"); + seen.add(path); + if (typeof entry.mode !== "string" || !modes[entry.mode] || modes[entry.mode] !== entry.type) { + throw new Error("Missing or malformed tree entry type/mode"); + } + if (underRoot(path) || supportPaths.includes(path)) { + if (entry.type === "tree") continue; + if (entry.type !== "blob") throw new Error("Selected source is not a blob"); + selected.set(path, { sha: id, mode: entry.mode }); + } + } + return selected; +} + +/** Compare upstream membership and Git blob/mode facts; uniquely paired exact moves are renames. */ +function compare(before: Map<string, SourceFile>, after: Map<string, SourceFile>): Change[] { + const removed = [...before.keys()].filter((path) => !after.has(path)).sort(); + const added = [...after.keys()].filter((path) => !before.has(path)).sort(); + const same = (a: SourceFile, b: SourceFile) => a.sha === b.sha && a.mode === b.mode; + const changes: Change[] = []; + const renamed = new Set<string>(); + for (const path of removed) { + const old = before.get(path)!; + const matches = added.filter((next) => same(old, after.get(next)!)); + const uniqueOld = removed.filter((other) => same(old, before.get(other)!)).length === 1; + const next = matches.length === 1 && uniqueOld ? matches[0] : undefined; + if (next !== undefined) { + renamed.add(next); + changes.push({ kind: "renamed", path: next, previousPath: path, before: old, after: after.get(next)! }); + } else { + changes.push({ kind: "deleted", path, before: old }); + } + } + for (const path of added) { + if (!renamed.has(path)) changes.push({ kind: "added", path, after: after.get(path)! }); + } + for (const [path, old] of before) { + const next = after.get(path); + if (next && !same(old, next)) changes.push({ kind: "edited", path, before: old, after: next }); + } + return changes.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); +} + +/** Collect informational MAIN source evidence. Exit 2 means collection unavailable, never incompatibility. */ +export async function runSourceCheck( + inventory: unknown, + fetchSource: SourceFetch = (url, init) => fetch(url, init) +): Promise<{ exitCode: 0 | 2; stdout: string }> { + const context: Record<string, unknown> = { channel: "development-source" }; + try { + const scope = selection(inventory); + const web = `https://github.com/${scope.repository}`; + const api = `https://api.github.com/repos/${scope.repository}`; + context.repository = scope.repository; + context.baselineCommit = scope.commit; + context.baselineUrl = `${web}/tree/${scope.commit}`; + const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN; + const headers: Record<string, string> = { Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" }; + if (token) headers.Authorization = `Bearer ${token}`; + const get = async (path: string): Promise<unknown> => { + let response: Response; + try { + response = await fetchSource(`${api}${path}`, { headers, signal: AbortSignal.timeout(20_000), redirect: "error" }); + } catch { + // Do not print transport errors, which may contain credentials or arbitrary remote text. + throw new Error(`GitHub retrieval unavailable: ${path}`); + } + if (!response.ok) throw new Error(`GitHub retrieval unavailable: ${path} (HTTP ${response.status})`); + try { + return await response.json(); + } catch { + throw new Error(`GitHub response is not valid JSON: ${path}`); + } + }; + const repo = record(await get("")); + if (typeof repo.default_branch !== "string" || !repo.default_branch.trim()) throw new Error("Missing default branch"); + const branch = repo.default_branch; + const candidate = record(await get(`/commits/${encodeURIComponent(branch)}`)); + const candidateCommit = sha(candidate.sha); + const candidateTree = sha(record(record(candidate.commit).tree).sha); + context.defaultBranch = branch; + context.candidateCommit = candidateCommit; + context.candidateUrl = `${web}/tree/${candidateCommit}`; + context.reviewUrl = `${web}/compare/${scope.commit}..${candidateCommit}`; + const baseline = candidateCommit === scope.commit ? candidate : record(await get(`/commits/${scope.commit}`)); + if (baseline.sha !== scope.commit) throw new Error("Baseline commit identity mismatch"); + const baselineTree = sha(record(record(baseline.commit).tree).sha); + const before = selectedTree(await get(`/git/trees/${baselineTree}?recursive=1`), baselineTree, scope.supportPaths); + // The reviewed inventory proves the baseline retrieval contains the entire selected membership. + if (before.size !== scope.expected.size || [...scope.expected].some(([path, blob]) => before.get(path)?.sha !== blob)) { + throw new Error("Baseline selected tree does not match reviewed inventory"); + } + const after = baselineTree === candidateTree ? before + : selectedTree(await get(`/git/trees/${candidateTree}?recursive=1`), candidateTree, scope.supportPaths); + const sourceUrl = (commit: string, path: string) => `${web}/blob/${commit}/${path.split("/").map(encodeURIComponent).join("/")}`; + const changes = compare(before, after).map((change) => ({ + ...change, + ...(change.before ? { beforeUrl: sourceUrl(scope.commit, change.previousPath ?? change.path) } : {}), + ...(change.after ? { afterUrl: sourceUrl(candidateCommit, change.path) } : {}), + })); + return { + exitCode: 0, + stdout: JSON.stringify({ + kind: "completed", level: "info", ...context, + status: changes.length ? "changed" : "unchanged", + summary: "Completed MAIN comparison: development-source information only; tagged CLI support remains independent.", + scope: { roots, supportPaths: scope.supportPaths }, + selectedFiles: { baseline: before.size, candidate: after.size }, changes, + }, null, 2), + }; + } catch (error) { + return { exitCode: 2, stdout: JSON.stringify({ + kind: "unavailable", ...context, + error: error instanceof Error ? error.message : "Source comparison unavailable", + }, null, 2) }; + } +} + +if (import.meta.main) { + let result: { exitCode: 0 | 2; stdout: string }; + try { + if (process.argv.length !== 2) throw new Error("Usage: bun scripts/check-beads-integration.ts (no baseline advancement option)"); + const inventory = JSON.parse(await readFile(new URL("../docs/claude-code-integration-inventory.json", import.meta.url), "utf8")); + result = await runSourceCheck(inventory); + } catch { + result = { exitCode: 2, stdout: JSON.stringify({ kind: "unavailable", channel: "development-source", error: "Unable to load inventory or invalid arguments; use bun scripts/check-beads-integration.ts without arguments" }, null, 2) }; + } + console.log(result.stdout); + process.exitCode = result.exitCode; +} diff --git a/scripts/release.ts b/scripts/release.ts index 63405bd..425933f 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -126,7 +126,7 @@ export async function validateReleaseMetadata( async function expectedArchiveInventory(projectDirectory: string): Promise<string[]> { const expected = ["LICENSE", "README.md", "package.json"]; - for (const directory of ["src"]) { + for (const directory of ["src", "assets/oc-beads"]) { expected.push( ...(await filesBelow(path.join(projectDirectory, directory))).map( (file) => `${directory}/${file}` diff --git a/scripts/test-package.ts b/scripts/test-package.ts index f0e39c4..27ca689 100644 --- a/scripts/test-package.ts +++ b/scripts/test-package.ts @@ -1,4 +1,5 @@ import { createHash } from "node:crypto"; +import type { CommandDefinition, CommandInvocation } from "@opencode/plugin/promise/command"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; @@ -110,14 +111,14 @@ try { } if ( JSON.stringify(manifest.files) !== - JSON.stringify(["src", "README.md", "LICENSE"]) + JSON.stringify(["src", "assets/oc-beads", "README.md", "LICENSE"]) ) { throw new Error("Packed package has an unexpected files contract"); } if (manifest.bin !== undefined) { throw new Error("Packed package unexpectedly registers a companion CLI"); } - const opencodeRange = ">=2.0.26 <3"; + const opencodeRange = ">=2.1.1 <2.2"; if ( manifest.dependencies !== undefined || JSON.stringify(manifest.peerDependencies) !== @@ -135,7 +136,7 @@ try { } const expectedPackedFiles = ["LICENSE", "README.md", "package.json"]; - for (const directory of ["src"]) { + for (const directory of ["src", "assets/oc-beads"]) { expectedPackedFiles.push( ...(await filesBelow(path.join(projectDir, directory))).map((file) => `${directory}/${file}`) ); @@ -166,6 +167,34 @@ try { throw new Error("Packed package does not default-export the OpenCode V2 plugin definition"); } + // Inspect the unpacked installed base, not the repository's assets. node_modules is + // ignored by host discovery, so this also exercises explicit package registration. + const workflows = await import(pathToFileURL(path.join(packageDir, "src/workflows.ts")).href); + const installedSkill = await workflows.loadBeadsSkill(); + const installedSkillPath = path.join(packageDir, "assets/oc-beads/SKILL.md"); + if (installedSkill.path !== installedSkillPath) throw new Error("Skill path escaped the installed package base"); + const links = [...installedSkill.content.matchAll(/\]\(((?:resources|adr)\/[^)]+)\)/g)] + .map((match) => (match as RegExpMatchArray)[1]!); + if (new Set(links).size !== 17) throw new Error("Packed skill must explicitly link all 17 supporting resources"); + for (const link of links) { + const resource = path.resolve(path.dirname(installedSkillPath), link); + const relative = path.relative(packageDir, resource); + const installed = await fs.readFile(resource); + const source = await fs.readFile(path.join(projectDir, relative)); + if (!installed.equals(source)) throw new Error(`Packed resource differs: ${relative}`); + } + // Missing/unreachable guidance is a diagnostic, not silently advertised capability. + const missingResource = path.join(path.dirname(installedSkillPath), links[15]!); + const savedResource = await fs.readFile(missingResource); + try { + await fs.unlink(missingResource); + let failure = ""; + try { await workflows.loadBeadsSkill(); } catch (error) { failure = String(error); } + if (!failure.includes(`resource-unreachable ${links[15]}`)) throw new Error("Missing packed guidance was not diagnosed"); + } finally { + await fs.writeFile(missingResource, savedResource); + } + if (await fs.exists(path.join(packageDir, "vendor"))) { throw new Error("Packed package unexpectedly contains the retired vendor directory"); } @@ -189,7 +218,23 @@ try { async function setupPacked(options: Record<string, unknown>) { const registeredHooks: string[] = []; const agents = new Map<string, PackedAgent>(); + const skills = new Map<string, { id: string; name: string; content: string; path: string }>(); + const userSkill = { id: "beads", name: "beads", content: "USER SHARED SKILL", path: "/user/SKILL.md" }; + skills.set("beads", userSkill); + const commands = new Map<string, CommandDefinition>([["oc-beads:ready", { + name: "oc-beads:ready", description: "EARLIER PLUGIN", execute: async () => {}, + }]]); + const owned = new Set<string>(); + const disposed = new Set<string>(); + const own = (name: string) => { + if (owned.has(name)) throw new Error(`Duplicate owned registration: ${name}`); + owned.add(name); + return { dispose: async () => { disposed.add(name); } }; + }; + const prompts: Array<Record<string, unknown>> = []; + let invoking = false; const cleanup = await (plugin!.setup as (ctx: unknown) => Promise<unknown>)({ + app: { name: "OpenCode", version: "2.1.1", channel: "stable" }, location: { directory: consumerDir }, options, agent: { @@ -202,13 +247,32 @@ try { agents.set(id, agent); }, }); - return { dispose: async () => {} }; + return own("agent"); + }, + }, + command: { + list: async () => ({ data: [...commands.values()].map(({ name, description }) => ({ name, description })) }), + transform: async (callback: (editor: { add(definition: CommandDefinition): void }) => void) => { + callback({ add: (definition) => commands.set(definition.name, definition) }); + return own("command"); + }, + }, + skill: { + list: async () => ({ data: [...skills.values()] }), + transform: async (callback: (editor: unknown) => void) => { + callback({ list: () => [...skills.values()], add: (skill: { id: string; name: string; content: string; path: string }) => skills.set(skill.id, skill) }); + return own("skill"); }, }, session: { + prompt: async (input: Record<string, unknown>) => { + if (!invoking) throw new Error("Setup must not submit a prompt"); + prompts.push(input); + return {}; + }, hook: async (name: string) => { registeredHooks.push(name); - return { dispose: async () => {} }; + return own(`hook:${name}`); }, }, event: { @@ -216,7 +280,42 @@ try { }, }); if (typeof cleanup !== "function") throw new Error("Packed plugin setup returned no cleanup"); - cleanup(); + const richSkill = skills.get("oc-beads"); + if (richSkill?.path !== installedSkillPath || richSkill.content !== installedSkill.content) throw new Error("Installed skill registration differs from packed asset"); + if (skills.get("beads") !== userSkill) throw new Error("Shared user skill was changed"); + const expectedCommands = [...Object.keys(workflows.WORKFLOWS).map((stem) => `oc-beads:${stem}`), "oc-beads:status"].sort(); + if (commands.size !== 30 || [...commands.keys()].sort().join("\0") !== expectedCommands.join("\0")) { + throw new Error("Packed callback did not register all 30 native command definitions"); + } + invoking = true; + const invocation = { + sessionID: "packed-session", delivery: "steer", + prompt: { text: "literal '$ARGUMENTS' $(no-shell) `text`", files: [{ uri: "file:///tmp/a%20b" }], agents: [{ name: "build" }], skills: [{ id: "oc-beads" }] }, + } as unknown as CommandInvocation; + for (const definition of commands.values()) { + if (typeof definition.execute !== "function" || !definition.description) throw new Error(`Missing native body: ${definition.name}`); + await definition.execute(invocation); + const submitted = prompts.at(-1); + if (!submitted || typeof submitted.text !== "string" || !submitted.text.includes(invocation.prompt.text) + || submitted.sessionID !== invocation.sessionID || submitted.delivery !== invocation.delivery + || submitted.files !== invocation.prompt.files || submitted.agents !== invocation.prompt.agents || submitted.skills !== invocation.prompt.skills) { + throw new Error(`Packed command did not preserve native invocation: ${definition.name}`); + } + if (definition.name === "oc-beads:status") { + const status = JSON.parse(submitted.text.split("\n")[1]!); + if (status.commands.registration !== "registered" || status.commands.lookup !== "available" + || !status.commands.ownership.includes("identical-description") || status.liveQualification !== "not executed") { + throw new Error("Packed status did not report observed registration and qualification limits"); + } + } else if (!submitted.text.includes(JSON.stringify(installedSkillPath))) throw new Error("Packed command uses the wrong JSON-quoted installed guidance base"); + } + if (prompts.length !== commands.size) throw new Error("Packed native commands did not each submit one prompt"); + await cleanup(); + const expectedOwned = ["agent", "skill", "command", ...registeredHooks.map((name) => `hook:${name}`)].sort(); + if ([...owned].sort().join("\0") !== expectedOwned.join("\0") + || [...disposed].sort().join("\0") !== expectedOwned.join("\0")) { + throw new Error("Owned native registrations were not all disposed"); + } return { registeredHooks, agents }; } @@ -230,10 +329,14 @@ try { throw new Error("Packed plugin did not register the task agent as a subagent"); } if ( - taskAgentPrompt.length >= 1_000 || taskAgentPrompt.includes("Agent Delegation") || !taskAgentPrompt.includes("Full `bd prime` workflow context is injected") || - !taskAgentPrompt.includes("bd update <id> --claim --json") + !taskAgentPrompt.includes("bd update <id> --claim --json --actor='<executor-actor>' --sandbox") || + !taskAgentPrompt.includes("An exact assignment or explicit one-bead mode ends with that bead") || + !taskAgentPrompt.includes("already-authorized batch or goal") || + !taskAgentPrompt.includes("Discovery does not add execution authority") || + !taskAgentPrompt.includes("If the parent owns acceptance or closure, return evidence and leave the bead open") || + taskAgentPrompt.includes("otherwise choose one highest-priority ready bead") ) { throw new Error("Packed task-agent prompt lacks the bounded workflow fallback"); } diff --git a/src/agents.ts b/src/agents.ts index 2645161..0905ca3 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -8,7 +8,7 @@ export const BEADS_TASK_AGENTS: ReadonlySet<string> = new Set([ BEADS_TASK_AGENT_LUNA, ]); -/** Default hard cap on worker steps; a backstop for "one bead, then return", not a target. */ +/** Default interruption cap on worker steps; never a completion target or authority to continue. */ export const DEFAULT_TASK_AGENT_STEPS = 100; /** @@ -45,7 +45,7 @@ export interface BeadsAgentDefinition { export interface BeadsAgentOptions { /** Register the Luna Max profile alongside the default task agent. */ luna?: boolean; - /** Hard cap on worker steps; 0 removes the cap. */ + /** Interruption cap on worker steps; 0 removes the cap, never the assignment's scope limits. */ steps?: number; } diff --git a/src/context.ts b/src/context.ts index 301cb7e..b76b011 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,7 +1,8 @@ +import { executorActor } from "./continuity"; import { BEADS_TASK_AGENTS } from "./agents"; import { guardNotice, type GuardResult } from "./guard"; import { PrimeTimeoutError } from "./prime"; -import { beadsGuidance, primeFailureNotice } from "./guidance"; +import { beadsGuidance, primeFailureNotice, type RequestCapabilities } from "./guidance"; /** Retry window for withheld or failed context, so model steps do not respawn `bd` each time. */ export const DEFAULT_CONTEXT_RETRY_MS = 30_000; @@ -15,11 +16,12 @@ export const DEFAULT_IDLE_REFRESH_MS = 30 * 60_000; export type AgentMode = "primary" | "subagent" | "all"; export type ContextAudience = "primary" | "task-agent"; -export type ContextDiagnostic = "agent_lookup_failed" | "prime_failed" | "prime_timeout"; +export type ContextDiagnostic = "agent_lookup_failed" | "guard_failed" | "prime_failed" | "prime_timeout"; +export type ContextStatus = "loading" | "missing" | "withheld" | "failed" | "empty" | "ready"; export interface ContextRuntime { - guard(directory: string): Promise<GuardResult>; - prime(directory: string): Promise<string>; + guard(directory: string, signal?: AbortSignal): Promise<GuardResult>; + prime(directory: string, signal?: AbortSignal): Promise<string>; /** Resolve an agent's mode; undefined when the agent is unknown. */ agentMode(agentID: string): Promise<AgentMode | undefined>; diagnose?(code: ContextDiagnostic, sessionID: string): void; @@ -34,26 +36,33 @@ export interface ContextControllerOptions { export interface ContextController { /** System text for one model request, or undefined when nothing should be added. */ - systemFor(sessionID: string, agentID: string): Promise<string | undefined>; + systemFor(sessionID: string, agentID: string, capabilities?: RequestCapabilities): Promise<string | undefined>; /** Refresh prime on the next request, e.g. after compaction changed the session. */ invalidate(sessionID: string): void; /** Drop all state for a deleted session. */ forget(sessionID: string): void; + /** Context state is distinct from an absent text part (including intentional empty prime). */ + statusFor(sessionID: string, audience: ContextAudience): ContextStatus | undefined; + /** A write can affect every cached session in this location. */ + invalidateAll(): void; + /** Abort owned loads and prevent late context injection after unload. */ + dispose(): void; } -type CacheEntry = { value: Promise<string | undefined>; expiresAt?: number; lastUsed: number }; +type CacheEntry = { value: Promise<string | undefined>; expiresAt?: number; lastUsed: number; abort: AbortController; status?: ContextStatus }; /** Wrap prime output in the envelope and shared guidance for one audience. */ -export function renderBeadsContext(prime: string, audience: ContextAudience): string { - return `<beads-context audience="${audience}">\n${prime}\n</beads-context>\n\n${beadsGuidance(audience)}`; +export function renderBeadsContext(prime: string, audience: ContextAudience, request?: { sessionID: string; agent: string; capabilities?: RequestCapabilities }): string { + const identity = request ? `\nYour Beads executor actor: ${executorActor(request)}. Use this exact actor for every direct shell write, claim and heartbeat (command-scoped --actor or BEADS_ACTOR); children use their own actual request actor. Attribution grants no permission.\n` : ""; + return `<beads-context audience="${audience}">\n${prime}\n</beads-context>\n${identity}\n${beadsGuidance(audience, request?.capabilities)}`; } /** * Supply Beads context through the per-request session context hook. * * OpenCode V2 attaches system parts to every model request without storing them, so context - * survives compaction on its own. The rendered text is cached per session to keep it stable - * across requests; compaction invalidates it so persistent memories can refresh. + * survives compaction on its own. Guarded prime is cached separately from per-request actor/capability rendering; + * compaction invalidates it so persistent memories can refresh. */ export function createContextController( runtime: ContextRuntime, @@ -65,6 +74,7 @@ export function createContextController( const now = options.now ?? Date.now; const cache = new Map<string, CacheEntry>(); const modes = new Map<string, Promise<AgentMode | undefined>>(); + let disposed = false; async function audienceFor( sessionID: string, @@ -97,34 +107,53 @@ export function createContextController( let guard: GuardResult; try { - guard = await runtime.guard(directory); + guard = await runtime.guard(directory, entry.abort.signal); } catch { - return retryLater(undefined); + if (entry.abort.signal.aborted) return undefined; + entry.status = "failed"; + runtime.diagnose?.("guard_failed", sessionID); + return retryLater("<beads-notice>Beads workflow context failed during the store safety check. Store authority is unknown; do not bypass this failure with manual prime.</beads-notice>"); + } + if (entry.abort.signal.aborted) return undefined; + if (guard.kind !== "ready") { + entry.status = guard.kind === "probe-failed" ? "failed" : guard.kind === "migration-pending" || guard.kind === "unsupported" ? "withheld" : "missing"; + return retryLater(guardNotice(guard)); } - if (guard.kind !== "ready") return retryLater(guardNotice(guard)); let output: string; try { - output = (await runtime.prime(directory)).trim(); + output = (await runtime.prime(directory, entry.abort.signal)).trim(); } catch (error) { + if (entry.abort.signal.aborted) return undefined; + entry.status = "failed"; const timedOut = error instanceof PrimeTimeoutError; runtime.diagnose?.(timedOut ? "prime_timeout" : "prime_failed", sessionID); return retryLater(primeFailureNotice(timedOut ? "timed out" : "failed")); } - if (!output) return retryLater(undefined); - return renderBeadsContext(output, audience); + if (entry.abort.signal.aborted) return undefined; + if (!output) { + entry.status = "empty"; + return retryLater(undefined); + } + entry.status = "ready"; + return output; } function dropSession(sessionID: string): void { + modes.clear(); for (const key of cache.keys()) { - if (key.startsWith(`${sessionID}\0`)) cache.delete(key); + if (key.startsWith(`${sessionID}\0`)) { + cache.get(key)?.abort.abort(); + cache.delete(key); + } } } return { - async systemFor(sessionID, agentID) { + async systemFor(sessionID, agentID, capabilities) { + if (disposed) return undefined; const audience = await audienceFor(sessionID, agentID); - if (!audience) return undefined; + if (!audience || disposed) return undefined; const key = `${sessionID}\0${audience}`; const current = now(); @@ -133,16 +162,32 @@ export function createContextController( const resumed = cached && idleRefreshMs > 0 && current - cached.lastUsed > idleRefreshMs; if (cached && fresh && !resumed) { cached.lastUsed = current; - return cached.value; + const output = await cached.value; + if (disposed || cached.abort.signal.aborted) return undefined; + return cached.status === "ready" ? renderBeadsContext(output!, audience, { sessionID, agent: agentID, capabilities }) : output; } - const entry: CacheEntry = { value: Promise.resolve(undefined), lastUsed: current }; + cached?.abort.abort(); + const entry: CacheEntry = { value: Promise.resolve(undefined), lastUsed: current, abort: new AbortController(), status: "loading" }; entry.value = load(sessionID, audience, entry); cache.set(key, entry); - return entry.value; + const output = await entry.value; + if (disposed || entry.abort.signal.aborted) return undefined; + return entry.status === "ready" ? renderBeadsContext(output!, audience, { sessionID, agent: agentID, capabilities }) : output; }, invalidate: dropSession, forget: dropSession, + statusFor: (sessionID, audience) => cache.get(`${sessionID}\0${audience}`)?.status, + invalidateAll() { + for (const entry of cache.values()) entry.abort.abort(); + cache.clear(); + }, + dispose() { + disposed = true; + for (const entry of cache.values()) entry.abort.abort(); + cache.clear(); + modes.clear(); + }, }; } diff --git a/src/continuity.ts b/src/continuity.ts new file mode 100644 index 0000000..12a6f0a --- /dev/null +++ b/src/continuity.ts @@ -0,0 +1,380 @@ +import type { Plugin } from "@opencode/plugin"; +import type { Info, ToolContext } from "@opencode/plugin/promise/tool"; +import { createHash } from "node:crypto"; +import { realpath } from "node:fs/promises"; +import { isAbsolute, parse, relative, sep } from "node:path"; +import { checkBeadsStore, createGuardRuntime, type GuardRuntime } from "./guard"; +import { PrimeProcessError, runBd } from "./prime"; + +export type ContinuityHost = Pick<Plugin.Context, "location"> & { + permission: Pick<Plugin.Context["permission"], "assert">; + session: Pick<Plugin.Context["session"], "get">; +}; + +export interface ContinuityResult { + operation: Operation; + target: string | string[] | Record<string, unknown>; + actor?: string; + status: "acknowledged" | "not-claimed" | "landed-uncommitted" | "uncertain" | "failed" | "found" | "missing"; + reference?: string; + value?: string; + data?: Record<string, unknown>; + issue?: Record<string, unknown>; + issues?: Record<string, Record<string, unknown>>; + failed?: Array<{ id: string; detail: string }>; + records?: Array<Record<string, unknown>>; + memories?: Record<string, string>; + view?: string; + direction?: "down" | "up"; + scope?: Record<string, unknown>; + limit?: number; + complete?: boolean | null; + detail?: string; + commitIndeterminate?: boolean; +} + +export interface ContinuityDependencies { + /** Override the normal bounded runner in tests. */ + bd?: typeof runBd; + readText?: GuardRuntime["readText"]; + realpath?: (path: string) => Promise<string>; + acknowledged?: (sessionID: string, result: ContinuityResult) => void; + signal?: AbortSignal; +} + +/** Audit attribution from host invocation identity; not authentication or an assignment handoff. */ +export function executorActor(context: { sessionID: string; agent: string }): string { + if ([context.sessionID, context.agent].some((value) => typeof value !== "string" || !value.trim() || value.includes("\0"))) { + throw new Error("Invocation executor identity unavailable; write withheld"); + } + const label = context.agent.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 24) || "agent"; + const suffix = createHash("sha256").update(context.sessionID).update("\0").update(context.agent).digest("hex").slice(0, 12); + return `opencode:${label}:${suffix}`; +} + +export type Operation = "find" | "get" | "edges" | "memories" | "recall" | "claim" | "note" | "update" | "close" | "create" | "remember"; +type Data = Record<string, unknown>; +type JS = import("effect").JsonSchema.JsonSchema; +const text: JS = { type: "string", minLength: 1, maxLength: 64_000 }; +const token: JS = { ...text, pattern: "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$" }; +const strings: JS = { type: "array", items: token, minItems: 1, maxItems: 25, uniqueItems: true }; +const status: JS = { enum: ["open", "in_progress", "blocked", "deferred"] }; +const priority: JS = { type: "integer", minimum: 0, maximum: 4 }; +const obj = (properties: Record<string, JS>, required = Object.keys(properties)): JS => ({ type: "object", properties, required, additionalProperties: false }); +const issue: JS = { type: "object", properties: { id: token, title: text, status: { type: "string" }, priority, issue_type: text, assignee: { type: "string" }, created_by: { type: "string" }, owner: { type: "string" }, notes: { type: "string" }, acceptance_criteria: { type: "string" }, design: { type: "string" }, labels: { type: "array", items: { type: "string" } } }, required: ["id"], additionalProperties: true }; +const summary: JS = obj({ id: token, title: text, status: text, priority, type: text, assignee: { type: ["string", "null"] } }, ["id", "title", "status", "priority", "type"]); +const complete: JS = { type: ["boolean", "null"] }; +const limit: JS = { type: "integer", minimum: 1, maximum: 50, default: 20 }; +const query = { parent: token, assignee: text, labels: strings, limit }; +const setSchema = obj({ status, priority, addLabels: strings, removeLabels: strings }, []); +const createSchema = obj({ title: text, type: { enum: ["bug", "feature", "task", "epic", "chore"] }, priority, description: text, acceptance: text, design: text, parent: token, discoveredFrom: token, labels: strings }, ["title", "type", "priority"]); + +function record(value: unknown): value is Data { + return typeof value === "object" && value !== null && !Array.isArray(value); +} +/** Validate the declared issue fields; preserve other CLI fields without inventing values. */ +function isIssue(value: unknown): value is Data & { id: string } { + if (!record(value) || typeof value.id !== "string" || value.id.length > 64_000 || !/^[a-zA-Z0-9][a-zA-Z0-9._:-]*$/.test(value.id)) return false; + for (const key of ["title", "status", "issue_type", "assignee", "created_by", "owner", "notes", "acceptance_criteria", "design"]) { + if (value[key] !== undefined && typeof value[key] !== "string") return false; + } + for (const key of ["title", "issue_type"]) { + if (typeof value[key] === "string" && (value[key].length === 0 || value[key].length > 64_000)) return false; + } + if (value.priority !== undefined && (!Number.isInteger(value.priority) || Number(value.priority) < 0 || Number(value.priority) > 4)) return false; + if (value.labels !== undefined && (!Array.isArray(value.labels) || !value.labels.every(label => typeof label === "string"))) return false; + return true; +} +function stringField(data: Data, key: string, isToken = false): string { + const value = data[key]; + if (typeof value !== "string" || !value.trim() || value.includes("\0") || value.length > 64_000 || + (isToken && !/^[a-zA-Z0-9][a-zA-Z0-9._:-]*$/.test(value))) throw new Error(`Invalid ${key}`); + return value; +} +function fields(data: unknown, allowed: readonly string[]): Data { + if (!record(data)) throw new Error("Expected operation input"); + for (const key of Object.keys(data)) if (!allowed.includes(key)) throw new Error(`Unsupported input: ${key}`); + // Optional undefined values are omitted in JSON; keep target/content JSON-consistent too. + return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined)); +} +function list(data: Data, key: string): string[] { + const value = data[key]; + if (!Array.isArray(value) || !value.length || value.length > 25 || new Set(value).size !== value.length) throw new Error(`Invalid ${key}`); + return value.map(v => stringField({ [key]: v }, key, true)); +} +function validate(operation: Operation, input: unknown): Data { + const allowed: Record<Operation, string[]> = { + find: ["view", "parent", "assignee", "status", "labels", "text", "limit"], get: ["ids"], edges: ["id", "direction", "types"], memories: ["search"], recall: ["key"], claim: ["id"], note: ["id", "text"], update: ["id", "set"], close: ["id", "reason"], create: ["title", "type", "priority", "description", "acceptance", "design", "parent", "discoveredFrom", "labels"], remember: ["key", "text"], + }; + const d = fields(input, allowed[operation]); + if (["edges", "claim", "note", "update", "close"].includes(operation)) stringField(d, "id", true); + if (["recall", "remember"].includes(operation)) stringField(d, "key", true); + if (["remember", "note"].includes(operation)) stringField(d, "text"); + if (operation === "close") stringField(d, "reason"); + if (operation === "get") list(d, "ids"); + if (operation === "memories" && d.search !== undefined) stringField(d, "search"); + if (operation === "edges") { + if (!["down", "up"].includes(String(d.direction))) throw new Error("Invalid direction"); + // One qualified CLI --type per operation; arbitrary dependency types remain CLI-owned. + if (d.types !== undefined && (list(d, "types").length !== 1)) throw new Error("Expected one dependency type"); + } + if (operation === "find") { + if (!["ready", "list", "search", "blocked"].includes(String(d.view))) throw new Error("Invalid view"); + if (d.limit !== undefined && (!Number.isInteger(d.limit) || Number(d.limit) < 1 || Number(d.limit) > 50)) throw new Error("Invalid limit"); + if (d.view === "search") stringField(d, "text"); + else if (d.text !== undefined) throw new Error("text requires search"); + if (d.parent !== undefined) { stringField(d, "parent", true); if (d.view === "search") throw new Error("parent unsupported for search"); } + if (d.assignee !== undefined) { stringField(d, "assignee"); if (d.view === "blocked") throw new Error("assignee unsupported for blocked"); } + if (d.status !== undefined && (!["list", "search"].includes(String(d.view)) || !["open", "in_progress", "blocked", "deferred", "closed"].includes(String(d.status)))) throw new Error("Invalid status filter"); + if (d.labels !== undefined) list(d, "labels"); + } + if (operation === "update") { + const set = fields(d.set, ["status", "priority", "addLabels", "removeLabels"]); + if (!Object.values(set).some(v => v !== undefined)) throw new Error("Empty update"); + if (set.status !== undefined && !["open", "in_progress", "blocked", "deferred"].includes(String(set.status))) throw new Error("Invalid update status; use close separately"); + if (set.priority !== undefined && (!Number.isInteger(set.priority) || Number(set.priority) < 0 || Number(set.priority) > 4)) throw new Error("Invalid priority"); + for (const k of ["addLabels", "removeLabels"]) if (set[k] !== undefined) list(set, k); + if ((set.addLabels as string[] | undefined)?.some(v => (set.removeLabels as string[] | undefined)?.includes(v))) throw new Error("Conflicting label update"); + } + if (operation === "create") { + stringField(d, "title"); + if (!["bug", "feature", "task", "epic", "chore"].includes(String(d.type))) throw new Error("Invalid issue type"); + if (!Number.isInteger(d.priority) || Number(d.priority) < 0 || Number(d.priority) > 4) throw new Error("Invalid priority"); + for (const k of ["description", "acceptance", "design", "parent", "discoveredFrom"]) if (d[k] !== undefined) stringField(d, k, ["parent", "discoveredFrom"].includes(k)); + if (d.labels !== undefined) list(d, "labels"); + } + return d; +} + +function argv(operation: Operation, d: Data): string[][] { + const flags = (value: Data, mapping: Record<string, string>): string[] => Object.entries(mapping).flatMap(([key, flag]) => value[key] === undefined ? [] : [flag, String(value[key])]); + switch (operation) { + case "get": return (d.ids as string[]).map(id => ["show", id, "--json"]); + case "find": return [[String(d.view), ...flags(d, { parent: "--parent", assignee: "--assignee", status: "--status" }), ...(d.labels ? ["--label", (d.labels as string[]).join(",")] : []), ...(d.view === "blocked" ? [] : ["--limit", String(Number(d.limit ?? 20) + 1)]), "--json", ...(d.view === "search" ? ["--", String(d.text)] : [])]]; + case "edges": return [["dep", "list", String(d.id), "--direction", String(d.direction), ...(d.types ? ["--type", String((d.types as string[])[0])] : []), "--json"]]; + case "claim": return [["update", String(d.id), "--claim", "--json"]]; + case "note": return [["update", String(d.id), "--append-notes", String(d.text), "--json"]]; + case "update": { const set = d.set as Data; return [["update", String(d.id), ...flags(set, { status: "--status", priority: "--priority" }), ...["addLabels", "removeLabels"].flatMap(k => (set[k] as string[] | undefined)?.flatMap(v => [k === "addLabels" ? "--add-label" : "--remove-label", v]) ?? []), "--json"]]; } + case "close": return [["close", String(d.id), "--reason", String(d.reason), "--json"]]; + case "create": return [["create", "--title", String(d.title), "--type", String(d.type), "--priority", String(d.priority), ...flags(d, { description: "--description", acceptance: "--acceptance", design: "--design", parent: "--parent" }), ...(d.discoveredFrom ? ["--deps", `discovered-from:${d.discoveredFrom}`] : []), ...(d.labels ? ["--labels", (d.labels as string[]).join(",")] : []), "--json"]]; + case "memories": return [["memories", "--json", "--", ...(d.search === undefined ? [] : [String(d.search)])]]; + case "recall": return [["recall", String(d.key), "--json"]]; + case "remember": return [["remember", "--key", String(d.key), "--json", "--", String(d.text)]]; + } +} + +function outputFor(operation: Operation, write: boolean): JS { + const base: Record<string, JS> = { operation: { const: operation }, target: operation === "get" ? strings : operation === "find" || operation === "create" || operation === "memories" ? { type: "object", additionalProperties: true } : token, ...(write ? { actor: text } : {}), detail: { type: "string" }, reference: token }; + const requiredBase = ["operation", "target", ...(write ? ["actor"] : [])]; + const branch = (value: string, extra: Record<string, JS> = {}, required: string[] = []): JS => obj({ ...base, status: { const: value }, ...extra }, [...requiredBase, "status", ...required]); + const readFailure = branch("failed", {}, ["detail"]); + const uncertainty = branch("uncertain", {}, ["detail"]); + switch (operation) { + case "find": return { anyOf: [branch("acknowledged", { view: { enum: ["ready", "list", "search", "blocked"] }, records: { type: "array", items: summary }, scope: { type: "object", additionalProperties: true }, limit, complete }, ["view", "records", "scope", "limit", "complete"]), readFailure] }; + case "get": return { anyOf: [branch("acknowledged", { issues: { type: "object", additionalProperties: issue }, failed: { type: "array", items: obj({ id: token, detail: text }) }, complete: { type: "boolean" } }, ["issues", "failed", "complete"]), readFailure] }; + case "edges": return { anyOf: [branch("acknowledged", { records: { type: "array", items: { ...issue, required: ["id", "dependency_type"], properties: { dependency_type: text }, additionalProperties: true } }, direction: { enum: ["down", "up"] }, scope: { type: "object", additionalProperties: true }, complete }, ["records", "direction", "scope", "complete"]), readFailure] }; + case "memories": return { anyOf: [branch("found", { memories: { type: "object", additionalProperties: { type: "string" } } }, ["memories"]), readFailure] }; + case "recall": return { anyOf: [branch("found", { value: { type: "string" } }, ["reference", "value"]), branch("missing", {}, ["reference"]), readFailure] }; + case "remember": { const data = { data: { type: "object", additionalProperties: true } as JS, commitIndeterminate: { type: "boolean" } as JS }; return { anyOf: [branch("acknowledged", data, ["reference", "data", "commitIndeterminate"]), branch("landed-uncommitted", data, ["reference", "data", "commitIndeterminate"]), uncertainty] }; } + case "claim": return { anyOf: [branch("acknowledged", { issue }, ["reference", "issue"]), branch("not-claimed", {}, ["detail"]), uncertainty] }; + default: return { anyOf: [branch("acknowledged", { issue }, ["reference", "issue"]), uncertainty] }; + } +} + +interface OperationDefinition { + name: Operation; + write: boolean; + input: JS; + output: JS; + argv(data: Data): string[][]; + normalize(data: Data, stdout: string, actor: string): Partial<ContinuityResult>; +} +function normalize(operation: Operation, d: Data, stdout: string, actor: string): Partial<ContinuityResult> { + const value: unknown = JSON.parse(stdout); + const fail = () => { throw new Error("Unsupported CLI acknowledgement; inspect current durable state before another write"); }; + if (operation === "memories" && record(value)) { + const memories = { ...value }; + if (Object.hasOwn(memories, "schema_version") && memories.schema_version !== 1) return fail(); + if (memories.schema_version === 1) delete memories.schema_version; + if (Object.values(memories).every(v => typeof v === "string")) return { status: "found", memories: memories as Record<string, string> }; + } + if (operation === "recall" && record(value) && value.key === d.key && typeof value.value === "string" && typeof value.found === "boolean" && (value.found || value.value === "")) return value.found ? { status: "found", reference: String(d.key), value: value.value } : { status: "missing", reference: String(d.key) }; + if (operation === "remember" && record(value) && value.key === d.key && value.value === d.text && ["remembered", "updated"].includes(String(value.action))) return { status: value.committed === false || value.commit_error !== undefined || value.commit_indeterminate === true ? "landed-uncommitted" : "acknowledged", reference: String(d.key), data: value, commitIndeterminate: value.commit_indeterminate === true }; + if (operation === "find" || operation === "edges") { + if (!Array.isArray(value)) return fail(); + for (const row of value) { + if (!isIssue(row) || (operation === "edges" ? typeof row.dependency_type !== "string" : typeof row.title !== "string" || typeof row.status !== "string" || !Number.isInteger(row.priority) || typeof row.issue_type !== "string" || (row.assignee !== undefined && row.assignee !== null && typeof row.assignee !== "string"))) return fail(); + } + const cap = operation === "edges" ? 50 : Number(d.limit ?? 20); + return operation === "edges" ? { status: "acknowledged", records: value.slice(0, cap), direction: d.direction as "down" | "up", scope: { id: d.id, direction: d.direction, ...(d.types ? { types: d.types } : {}) }, complete: value.length > cap ? false : null } : { status: "acknowledged", view: String(d.view), records: value.slice(0, cap).map(row => ({ id: row.id, title: row.title, status: row.status, priority: row.priority, type: row.issue_type, ...(Object.hasOwn(row, "assignee") ? { assignee: row.assignee } : {}) })), scope: { ...d, limit: cap }, limit: cap, complete: ["blocked", "search"].includes(String(d.view)) ? (value.length > cap ? false : null) : value.length <= cap }; + } + const row = operation === "create" && record(value) ? value : Array.isArray(value) && value.length === 1 && record(value[0]) ? value[0] : undefined; + if (!isIssue(row)) return fail(); + if (operation === "get") return row.id === d.id ? { issue: row } : fail(); + if (operation !== "create" && row.id !== d.id) return fail(); + let accepted = false; + switch (operation) { + case "claim": accepted = row.assignee === actor && row.status === "in_progress"; break; + case "note": accepted = typeof row.notes === "string" && row.notes.endsWith(String(d.text)); break; + case "close": accepted = row.status === "closed"; break; + case "create": accepted = row.title === d.title && row.issue_type === d.type && row.priority === d.priority && !row.assignee; break; + case "update": + accepted = Object.entries(d.set as Data).every(([key, value]) => { + if (key === "addLabels" || key === "removeLabels") { + if (!Array.isArray(row.labels)) return false; + const labels = row.labels; + return (value as string[]).every(label => key === "addLabels" ? labels.includes(label) : !labels.includes(label)); + } + return row[key] === value; + }); + break; + } + if (!accepted) return fail(); + return { status: "acknowledged", reference: row.id, issue: row }; +} +const inputs: Record<Operation, JS> = { + find: { anyOf: [obj({ view: { const: "ready" }, ...query }, ["view"]), obj({ view: { const: "list" }, ...query, status: { enum: ["open", "in_progress", "blocked", "deferred", "closed"] } }, ["view"]), obj({ view: { const: "search" }, assignee: text, labels: strings, status: { enum: ["open", "in_progress", "blocked", "deferred", "closed"] }, text, limit }, ["view", "text"]), obj({ view: { const: "blocked" }, parent: token, labels: strings, limit }, ["view"])] }, + get: obj({ ids: strings }), edges: obj({ id: token, direction: { enum: ["down", "up"] }, types: { type: "array", items: token, minItems: 1, maxItems: 1 } }, ["id", "direction"]), memories: obj({ search: text }, []), recall: obj({ key: token }), claim: obj({ id: token }), note: obj({ id: token, text }), update: obj({ id: token, set: { ...setSchema, minProperties: 1 } }), close: obj({ id: token, reason: text }), create: createSchema, remember: obj({ key: token, text }), +}; +const OPERATIONS: OperationDefinition[] = (Object.keys(inputs) as Operation[]).map(name => { + const write = ["claim", "note", "update", "close", "create", "remember"].includes(name); + return { name, write, input: inputs[name], output: outputFor(name, write), argv: d => argv(name, d), normalize: (d, stdout, actor) => normalize(name, d, stdout, actor) }; +}); + +/** Canonical shell resource only; execution always uses the separate argv array. */ +function shellResource(args: readonly string[]): string { + return ["bd", ...args].map(arg => /^[a-zA-Z0-9_./:=-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", "'\\''")}'`).join(" "); +} +function failureDetail(error: unknown): string { + if (error instanceof PrimeProcessError) return [error.message, error.stderr.trim(), error.stdout.trim()].filter(Boolean).join(": "); + return error instanceof Error ? error.message : String(error); +} +/** Only the observed single-target JSON diagnostic establishes a non-claim. */ +function claimConflict(error: unknown, id: unknown): boolean { + if (!(error instanceof PrimeProcessError) || error.exitCode !== 1 || error.stdout.trim()) return false; + try { + const line = error.stderr.trim().split("\n").at(-1)!; + const data: unknown = JSON.parse(line); + return record(data) && data.schema_version === 1 && data.error === "1 of 1 issues failed to update" && Array.isArray(data.failed) && data.failed.length === 1 && record(data.failed[0]) && data.failed[0].id === id && typeof data.failed[0].error === "string" && data.failed[0].error.startsWith("updating issue: issue already claimed: already assigned to "); + } catch { return false; } +} + +function contains(root: string, target: string): boolean { + const path = relative(root, target); + return path === "" || (!isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`)); +} + +/** + * Fixed N1 definitions using the same runner in production and tests. Ordinary setup must not + * register these before 1.1 qualification. Beads owns its normal routing and storage semantics. + */ +export function createContinuityTools(host: ContinuityHost, deps: ContinuityDependencies = {}): Info[] { + const bd = deps.bd ?? runBd; + const canonical = deps.realpath ?? realpath; + + return OPERATIONS.map((definition): Info => { + const operation = definition.name; + return { + name: operation, + options: { namespace: "beads", permission: "shell", codemode: true }, + description: `Beads ${operation}: fixed permissioned operation. No new saved grant; ask may prompt again.`, + input: definition.input, + output: definition.output, + async execute(input, context) { + const data = validate(operation, input); + const write = definition.write; + const actor = executorActor(context); + const commands = definition.argv(data).map(args => { + const terminator = args.indexOf("--"); + args.splice(terminator < 0 ? args.length : terminator, 0, ...(write ? ["--actor", actor] : []), "--sandbox"); + return args; + }); + const target = operation === "get" ? data.ids as string[] : operation === "find" || operation === "create" || operation === "memories" ? { ...data } : String(data.id ?? data.key); + const base = { operation, target, ...(write ? { actor } : {}) }; + const signal = deps.signal ? AbortSignal.any([context.signal, deps.signal]) : context.signal; + signal.throwIfAborted(); + const options = { signal }; + const runtime = createGuardRuntime((cwd, argv) => bd(cwd, argv, options)); + if (deps.readText) runtime.readText = deps.readText; + const invocation = { sessionID: context.sessionID, agent: context.agent, + source: { type: "tool" as const, messageID: context.messageID, id: context.id }, save: [] }; + + async function assertPermission(action: string, resources: string[]): Promise<void> { + signal.throwIfAborted(); + // The public API owns its prompt and exposes no cancellation endpoint. Stop this + // invocation on unload/abort; a late approval must never reach process execution. + await new Promise<void>((resolve, reject) => { + const onAbort = () => reject(signal.reason); + signal.addEventListener("abort", onAbort, { once: true }); + void host.permission.assert({ ...invocation, action, resources }).then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort)); + if (signal.aborted) onAbort(); + }); + } + + const directory = host.location.directory; + async function invocationDirectory(): Promise<string> { + signal.throwIfAborted(); + const response = await host.session.get({ sessionID: context.sessionID }); + if (response.location.directory !== directory) throw new Error("Session location changed; operation withheld"); + return canonical(directory); + } + + async function assertGuard(): Promise<void> { + const guard = await checkBeadsStore(directory, runtime); + if (guard.kind !== "ready") throw new Error(`Beads store held: ${guard.kind}`); + } + + const resolvedDirectory = await invocationDirectory(); + const project = host.location.project.directory; + // Match shell access for the explicit cwd. Internal Beads redirects, backend selection + // and issue routing remain CLI-owned; they are not extra plugin permission targets. + const external = [...new Set([directory, resolvedDirectory].filter((path) => + !contains(directory, path) && !(project !== parse(project).root && contains(project, path))) + .map((path) => `${path.replaceAll("\\", "/").replace(/\/$/, "")}/*`))]; + if (external.length) await assertPermission("external_directory", external); + await assertGuard(); + await assertPermission("shell", commands.map(shellResource)); + const issues: Record<string, Record<string, unknown>> = {}; + const failed: Array<{ id: string; detail: string }> = []; + let result: ContinuityResult = { ...base, status: "failed", detail: "No CLI result" }; + for (const args of commands) { + // Approval never freezes authority. Recheck before EVERY child process, including get. + signal.throwIfAborted(); + await assertGuard(); + if (await invocationDirectory() !== resolvedDirectory) throw new Error("Invocation directory changed during approval"); + if (executorActor(context) !== actor) throw new Error("Executor changed before spawn"); + signal.throwIfAborted(); + const item = operation === "get" ? { id: args[1] } : data; + try { + const normalized = definition.normalize(item, await bd(directory, args, options), actor); + if (operation === "get") issues[String(item.id)] = normalized.issue!; + else result = { ...base, ...normalized } as ContinuityResult; + } catch (error) { + result = { ...base, status: write ? claimConflict(error, data.id) && operation === "claim" ? "not-claimed" : "uncertain" : "failed", detail: failureDetail(error) }; + if (operation === "get") failed.push({ id: String(item.id), detail: result.detail! }); + if (operation === "recall" && error instanceof PrimeProcessError && error.exitCode === 1) { + try { + const missing = definition.normalize(data, error.stdout, actor); + if (missing.status === "missing") result = { ...base, ...missing } as ContinuityResult; + } catch { /* Keep original diagnostic; absence is not established. */ } + } + if (signal.aborted) { + // A started write may have landed even when cancellation prevents a normal return. + if (write) notify(result); + signal.throwIfAborted(); + } + } + } + if (operation === "get") result = Object.keys(issues).length ? { ...base, status: "acknowledged", issues, failed, complete: failed.length === 0 } : { ...base, status: "failed", detail: failed.map(f => `${f.id}: ${f.detail}`).join("; ") }; + function notify(value: ContinuityResult): void { + try { deps.acknowledged?.(context.sessionID, value); } + catch { value.detail = [value.detail, "Context refresh notification failed; the reported persistence outcome still applies."].filter(Boolean).join("; "); } + } + // Sole write acknowledgement; outer execute must not duplicate it. + notify(result); + signal.throwIfAborted(); + return { output: result, content: JSON.stringify(result) }; + }, + }; + }); +} diff --git a/src/guard.ts b/src/guard.ts index 774edd3..9b0b147 100644 --- a/src/guard.ts +++ b/src/guard.ts @@ -1,8 +1,9 @@ import { readFile } from "node:fs/promises"; import { join } from "node:path"; +import { PrimeProcessError } from "./prime"; /** Oldest Beads release this adapter supports. */ -export const MINIMUM_BD_VERSION = "1.3.0"; +export const MINIMUM_BD_VERSION = "1.3.1"; export type Version = readonly [number, number, number]; @@ -11,6 +12,7 @@ export type GuardResult = | { kind: "ready" } | { kind: "uninitialized" } | { kind: "unavailable" } + | { kind: "probe-failed" } | { kind: "unsupported"; installed: string } | { kind: "migration-pending"; installed: string; store: string | undefined }; @@ -26,6 +28,35 @@ export interface GuardOptions { autoMigrate?: boolean; } +export interface BeadsWorkspace { + path: string; +} + +/** Config-only context avoids `where`'s opportunistic embedded-store prefix lookup. */ +export async function probeBeadsWorkspace(directory: string, runtime: GuardRuntime): Promise<BeadsWorkspace | undefined> { + let output: string; + try { + output = await runtime.bd(directory, ["context", "--json"]); + } catch (error) { + if (!(error instanceof PrimeProcessError) || error.exitCode !== 1) throw error; + // bd 1.3.1 reports absence through this versioned error rather than a successful context. + const response: unknown = JSON.parse(error.stdout); + if (typeof response === "object" && response !== null && !Array.isArray(response)) { + const data = response as Record<string, unknown>; + if (data.schema_version === 1 && data.error === "cannot resolve repo context: no .beads directory found") { + return undefined; + } + } + throw error; + } + const location: unknown = JSON.parse(output); + if (typeof location !== "object" || location === null) throw new Error("Invalid bd context response"); + const data = location as Record<string, unknown>; + if (typeof data.beads_dir !== "string") throw new Error("Unsupported bd context response"); + if (!data.beads_dir) return undefined; + return { path: data.beads_dir }; +} + /** Parse the leading `major.minor.patch` triple; pre-release suffixes are ignored. */ export function parseVersion(text: string): Version | undefined { const match = text.match(/(\d+)\.(\d+)\.(\d+)/); @@ -59,8 +90,9 @@ async function readTextIfPresent(path: string): Promise<string | undefined> { * Decide whether automatic `bd prime` is safe without opening the store. * * Beads 1.3+ migrates an embedded store in place, irreversibly, on the first command that opens - * it, and `--readonly` does not prevent that. `bd version` and `bd where --json` do not open the - * store. `.beads/.local_version` records the newest binary that opened the store through an + * it, and `--readonly` does not prevent that. `bd version` and `bd context --json` skip store + * initialization; `where` can open storage to recover a missing prefix and is not used here. + * `.beads/.local_version` records the newest binary that opened the store through an * ordinary command (but not `bd prime`), so an older or missing value means a migration may be * pending. `bd migrate schema` is idempotent and updates it, clearing a stale marker. */ @@ -82,17 +114,14 @@ export async function checkBeadsStore( return { kind: "unsupported", installed: formatVersion(installed) }; } - let location: unknown; + let location: BeadsWorkspace | undefined; try { - location = JSON.parse(await runtime.bd(directory, ["where", "--json"])); + location = await probeBeadsWorkspace(directory, runtime); } catch { - // `bd where` exits non-zero without a workspace; treat every failure as no usable store. - return { kind: "uninitialized" }; + // Never substitute `where` or silently treat an unsupported/failed safety probe as absence. + return { kind: "probe-failed" }; } - const beadsPath = - typeof location === "object" && location !== null && "path" in location - ? (location as { path: unknown }).path - : undefined; + const beadsPath = location?.path; if (typeof beadsPath !== "string" || !beadsPath) return { kind: "uninitialized" }; if (options.autoMigrate) return { kind: "ready" }; @@ -122,6 +151,10 @@ export function createGuardRuntime(bd: GuardRuntime["bd"]): GuardRuntime { /** Explain withheld context to the agent so it neither migrates the store nor guesses. */ export function guardNotice(result: GuardResult): string | undefined { switch (result.kind) { + case "probe-failed": + return `<beads-notice> +Beads workflow context was withheld: the config-only \`bd context --json\` safety probe failed or returned an unsupported result. Store authority is unknown. Do not substitute \`bd where\` or manual prime; report the CLI contract failure to the user. +</beads-notice>`; case "unsupported": return `<beads-notice> Beads workflow context was not loaded: the installed bd ${result.installed} is older than ${MINIMUM_BD_VERSION}, the minimum this OpenCode integration supports. Tell the user to upgrade bd before relying on Beads in this session. diff --git a/src/guidance.ts b/src/guidance.ts index 906938c..fd24d7a 100644 --- a/src/guidance.ts +++ b/src/guidance.ts @@ -1,35 +1,67 @@ /** * Project-owned guidance for OpenCode: the shared context layer and the bounded task-agent prompt. * - * Upstream Beads ships its task agent only for Claude Code, as an autonomous multi-task loop. This - * adapter deliberately diverges (one bead per invocation), so the prompt and description live here. + * Scope and acceptance come from the authorized assignment, not a command count or ready graph. + * The prompt remains project-owned; native child transport and Beads storage remain external. */ -/** Shown when OpenCode lists subagents; describes the one-bead worker contract. */ +/** Shown when OpenCode lists subagents; describes the scope-bounded worker contract. */ export const BEADS_TASK_AGENT_DESCRIPTION = - "Bounded Beads worker: analyzes the issue graph read-only, or claims, completes, and closes or blocks exactly one bead per invocation."; + "Bounded Beads worker: read-only issue-graph analysis or execution within an authorized task, batch or goal, with atomic claims, validation and evidence-bearing handoff."; + +const BEADS_SCOPE_GUIDANCE = `## Authorized scope + +For status or graph analysis, never claim, update, close, or modify a bead. Return the analysis and stop. + +An exact assignment or explicit one-bead mode ends with that bead: report completion or a blocker and return without substitution. A single authorized operation ends with that operation; it does not authorize claiming ownership or selecting ready work. Only an already-authorized batch or goal allows continuation to another actionable bead within its stated scope. Readiness is not dispatch permission. If execution scope is missing or ambiguous, obtain it before claiming work. + +For each bead authorized for task execution, first read the exact issue (typed get when available; direct shell equivalent \`bd show <id> --json --sandbox\`); confirm it is open, actionable, within scope and appropriate; then claim atomically (typed claim when available; direct shell equivalent \`bd update <id> --claim --json --actor='<executor-actor>' --sandbox\`) and read back assignment. If the claim fails or readback does not confirm your assignment, stop. If an assigned bead is closed, blocked, unsuitable, missing, or otherwise not actionable, report and return without mutation or substitution. + +Implement within scope and run relevant authorized validation. Append per-bead checkpoints with \`--append-notes\`, preserving attempts, measured outcomes, rationale, validation, references, blockers and the next authorized action. Close only with acceptance evidence and closure authority; unresolved acceptance or failed validation remains incomplete. If the parent owns acceptance or closure, return evidence and leave the bead open. Continue within an authorized batch only after recording the current bead's outcome and confirming that continuation meets its acceptance and authority gates. + +Discovery does not add execution authority. Report follow-ups; create them only when authorized, with \`discovered-from:<current-id>\` and a parent only when structurally appropriate. Leave new work unassigned unless assignment is intentional. Never claim or implement discoveries merely because you recorded them. + +Stop on permission denial, cancellation, deliberate stop, exhausted scope or a blocker requiring a decision. A permission wait is pending authority, not completion or permission to bypass it. Keep partial progress and unresolved acceptance visible; never restart work from a completion notification.`; + +const BEADS_EXECUTOR_GUIDANCE = `## Executor and work ownership + +Use the actual executor actor from the authorized invocation or handoff on every agent-authored write, claim, update and heartbeat, with command-scoped \`BEADS_ACTOR\` or \`--actor\`. Replace '<executor-actor>' with that identity; if unavailable, refuse writes until the host supplies it. Never fall back to human Git identity, a shared user-global actor, a model name alone or another executor's identity. Keep full session IDs in evidence and readable unambiguous actor names in commands. Actor attribution grants no permission and is separate from the actual invoking host agent used for permission evaluation. + +Read back \`created_by\` (audit author), \`owner\` (record/account ownership) and \`assignee\` (executor) separately. Preserve historical authorship, including unknown values; put provenance corrections in notes. A deliberate handoff records the change and authorized ownership update; parent coordination or review alone does not transfer the worker's claim. Delegated instructions carry the actual executor actor; the executing worker claims its own authorized work and retains that actor through updates and heartbeats.`; const BEADS_HOST_GUIDANCE = `## CLI Safety -There is no native \`bd\` or Beads MCP tool in OpenCode. Run \`bd\` through the \`shell\` tool. +Use actual request capabilities below. Native shell is direct-only and cannot be called inside execute. Treat the injected \`bd prime\` output as the canonical workflow reference. Use \`bd --help\` to discover commands, \`bd <command> --help\` for current syntax, and \`--json\` when structured output improves reliability. Distinguish command failures from output parsing failures. Run relevant validation before closing an issue. Do not commit, push, or run Dolt synchronization automatically; do so only when current instructions or repository policy explicitly require it.`; +const BEADS_RECOVERY_GUIDANCE = `## Work continuity + +At the first eligible request after a fresh session, compaction, interruption, child handoff or important change, retrieve the assigned task and relevant reusable knowledge under the current permissions and guard. Re-read current state when external writers may have changed it; cached absence is not proof of absence. Missing, withheld, failed and intentionally empty workflow context are different outcomes; never bypass a hold with manual prime. + +Append task-specific attempts, measured outcomes, rationale, blockers and the next authorized action at meaningful transitions. Keep hypotheses qualified. Reusable project memory is for validated findings with scope and evidence: search or recall relevant existing knowledge before adding or correcting it. No redundant write quota applies. + +Only actual CLI results acknowledge persistence. A timeout or interrupted append/remember is uncertain: inspect durable state before another write, with no blind retry. A landed memory with failed or indeterminate commit retains its reference and qualified status; do not force it or automatically commit. Empty found memory differs from a missing key. Every authorized write uses the actual executor's explicit Beads actor, separate from the host permission agent; never use human Git identity or a model-supplied impersonation. + +The installed Beads CLI owns database, routing and consistency semantics; the adapter invokes normal supported operations from the actual project context under OpenCode permissions. Typed writes derive a stable readable actor from the actual invocation session and agent. That attribution is not authentication and never transfers assignment: deliberate handoffs still require their own authorized claim/update and evidence. Where an authorized permission check uses save: [], an always reply grants this call only and ask may prompt again; existing shell grants remain subject to denies.`; + const BEADS_DELEGATION_GUIDANCE = `## Delegation -Delegate multi-command Beads work and issue-graph analysis to the \`beads-task-agent\` subagent through the \`subagent\` tool; perform only single atomic operations directly.`; +Choose direct work or delegation for the task's needs, not the number of commands. When authorized delegation helps, use the \`beads-task-agent\` subagent through the \`subagent\` tool with normal blocking child return. Carry the exact scope, actual executor actor, acceptance/closure owner, expected evidence and stop conditions. Explicit parent/child boundaries still apply; delegation does not grant a larger goal. Background or nested automation is not enabled by this guidance. + +Reconcile the child's scope, actor/session, attempts, outcomes, rationale, validation, durable references, blockers, incomplete acceptance and next authorized action. A tool return alone is not completion evidence: NO_TEXT, failed/cancelled children and stale/late results remain incomplete until reconciled. A result after deliberate stop does not authorize resumption or closure. Use earlier acknowledged records and disclose gaps on later authorized recovery. Never synthesize a parent restart or approve a permission wait to obtain a final checkpoint.`; -/** Project-owned system prompt for the bounded one-bead task agent. */ +/** Project-owned system prompt for the scope-bounded task agent. */ export const BEADS_TASK_AGENT_PROMPT = `## Beads Task Agent -Full \`bd prime\` workflow context is injected with each request. Use \`bd\` via the \`shell\` tool; rerun prime if context is missing or stale. +Full \`bd prime\` workflow context is injected with each request. Use the request capability block and native beads namespace catalog when available; repository validation runs in the direct \`shell\` tool. Refresh missing or stale context only when authorized and no hold applies. -For status or graph analysis, never claim, update, close, or modify a bead. Return the analysis and stop. +${BEADS_SCOPE_GUIDANCE} -For completion, process exactly one bead. Use a caller-supplied ID; otherwise choose one highest-priority ready bead. First run \`bd show <id> --json\`; confirm it is open, actionable, and appropriate; then claim with \`bd update <id> --claim --json\`. If an assigned bead is closed, blocked, unsuitable, missing, or otherwise not actionable, report and return without mutation or substitution. +${BEADS_EXECUTOR_GUIDANCE} -Implement and validate it. Create discoveries with \`discovered-from:<current-id>\` and a parent only when structurally obvious. Never claim or implement work created this invocation; it may be eligible later. Close or block the current bead, report discoveries, and return without seeking more work. Return concise results, not raw JSON.`; +Return concise results, not raw JSON: include task/scope, actual actor and full session ID, attempts/outcomes/rationale, exact validation and candidate revision, durable references, blockers, unresolved acceptance and next authorized action. Distinguish completion from partial progress, NO_TEXT, failure or cancellation. The caller reconciles acceptance; a step cap or invocation ending does not establish completion. Do not launch background or nested automation or restart a stopped parent.`; /** Shown instead of context when `bd prime` fails, as upstream's Codex hook reports refresh failures. */ export function primeFailureNotice(reason: "failed" | "timed out"): string { @@ -40,14 +72,32 @@ Beads workflow context could not be loaded: \`bd prime\` ${reason}. Beads may st /** Added to compaction requests so the summary keeps the Beads work in flight. */ export const BEADS_COMPACTION_GUIDANCE = `<beads-compaction> -This session tracks work in Beads (bd). In the summary, preserve every bead ID mentioned, which bead is claimed or in progress and its current status, discovered follow-up beads and how they were linked, blockers, and validation still pending. Fresh \`bd prime\` context is attached again after compaction, so do not restate the Beads workflow itself. +This session tracks work in Beads (bd). Carry acknowledged durable task/memory references, current scope, attempts, blockers, pending validation and known unsaved or uncertain gaps into the summary or native-provider context. A summary is not a Beads write. Do not persist, wait for permission or a queued write, or request a recursive same-session checkpoint during compaction. Earlier meaningful checkpoints support recovery; no final-write guarantee applies. Fresh guarded context is requested after compaction. Preserve host-owned native replay, metadata.keepID and legacy metadata.recent; do not inject result.native.messages or a replacement result to add references. </beads-compaction>`; +/** Observed after native request filtering; selective operation permission remains per call. */ +export interface RequestCapabilities { + typed: "registered-execute-visible" | "unavailable" | "unknown"; + execute: boolean | null; + shell: boolean | null; +} +export const UNKNOWN_CAPABILITIES: RequestCapabilities = { typed: "unknown", execute: null, shell: null }; + +/** Capability statements describe observations, never a permission grant. */ +export function capabilityGuidance(capabilities: RequestCapabilities): string { + if (capabilities.typed === "registered-execute-visible") return "Beads namespace is registered and execute is visible. Discover current signatures with native search({namespace: \"beads\"}); compose only operations found in the native catalog. Some or all leaves may be filtered out. Each leaf still checks its own permissions; visibility grants no authority. Native shell is direct-only; run repository work and validation in direct shell turns. Do not call tools.shell inside execute."; + if (capabilities.shell === true) return "Typed Beads composition is unavailable for this request. Use permissioned bd through the direct shell tool only when authorized and no hold applies. Shell cannot run inside execute."; + if (capabilities.execute !== null && capabilities.shell === false) return "Neither typed Beads composition nor direct shell access is available for this request. Report unavailable capability and stop the affected work; do not route around it."; + return "Beads request capability is unknown. Check actual native catalog/tools before choosing typed composition or direct shell; do not infer permission or bypass a hold."; +} + /** Build the single shared guidance layer for one injected audience. */ export function beadsGuidance( - audience: "primary" | "task-agent" + audience: "primary" | "task-agent", + capabilities: RequestCapabilities = UNKNOWN_CAPABILITIES ): string { - const sections = [BEADS_HOST_GUIDANCE]; - if (audience === "primary") sections.push(BEADS_DELEGATION_GUIDANCE); + const sections = [BEADS_HOST_GUIDANCE, capabilityGuidance(capabilities), BEADS_RECOVERY_GUIDANCE]; + if (audience === "primary") sections.push(BEADS_EXECUTOR_GUIDANCE); + if (audience === "primary") sections.push(BEADS_SCOPE_GUIDANCE, BEADS_DELEGATION_GUIDANCE); return `<beads-guidance>\n${sections.join("\n\n")}\n</beads-guidance>`; } diff --git a/src/host.ts b/src/host.ts index 0db5040..387206c 100644 --- a/src/host.ts +++ b/src/host.ts @@ -1,6 +1,7 @@ import type { Plugin } from "@opencode/plugin"; -import { applyBeadsAgents, beadsAgents } from "./agents"; -import { BEADS_COMPACTION_GUIDANCE } from "./guidance"; +import { applyBeadsSkill, createBeadsCommands, loadBeadsSkill, type SkillLoadDependencies } from "./workflows"; +import { applyBeadsAgents, beadsAgents, BEADS_TASK_AGENTS } from "./agents"; +import { BEADS_COMPACTION_GUIDANCE, UNKNOWN_CAPABILITIES, type RequestCapabilities } from "./guidance"; import { createContextController, type AgentMode, @@ -10,11 +11,16 @@ import { import { checkBeadsStore, createGuardRuntime } from "./guard"; import { minimizeSystemParts } from "./instructions"; import { runBd, runBdPrime } from "./prime"; +import { createContinuityTools, type ContinuityDependencies, type ContinuityResult } from "./continuity"; /** The slice of the OpenCode V2 plugin context this adapter uses. */ -export type HostContext = Pick<Plugin.Context, "location" | "options" | "event"> & { +export type HostContext = Pick<Plugin.Context, "app" | "location" | "options" | "event"> & { agent: Pick<Plugin.Context["agent"], "get" | "transform">; - session: Pick<Plugin.Context["session"], "hook">; + session: Pick<Plugin.Context["session"], "hook"> & Partial<Pick<Plugin.Context["session"], "get" | "prompt">>; + tool?: Pick<Plugin.Context["tool"], "hook" | "transform">; + permission?: Pick<Plugin.Context["permission"], "assert">; + command?: Pick<Plugin.Context["command"], "list" | "transform">; + skill?: Pick<Plugin.Context["skill"], "list" | "transform">; }; export interface HostDependencies { @@ -28,9 +34,25 @@ export interface HostDependencies { diagnosticIntervalMs?: number; now?: () => number; retryMs?: number; + /** Internal qualification seam for tests; production remains false pending successful 1.1. */ + continuityQualified?: boolean; + /** Overrides the same adapter dependencies used after qualification. */ + continuity?: ContinuityDependencies; + /** Override package reads only; no user-facing activation or installation option. */ + workflowAssets?: SkillLoadDependencies; } const DEFAULT_DIAGNOSTIC_INTERVAL_MS = 60_000; +const SUPPORTED_OPENCODE_RANGE = ">=2.1.1 <2.2"; + +function isSupportedOpenCodeVersion(version: string): boolean { + const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z.-]+)?$/.exec(version); + if (!match) return false; + const major = Number(match[1]); + const minor = Number(match[2]); + const patch = Number(match[3]); + return major === 2 && minor === 1 && patch >= 1; +} function eventSessionID(event: unknown): string | undefined { if (typeof event !== "object" || event === null) return undefined; @@ -44,7 +66,7 @@ function eventSessionID(event: unknown): string | undefined { export async function setupBeads( ctx: HostContext, deps: HostDependencies = {} -): Promise<() => void> { +): Promise<() => Promise<void>> { const directory = ctx.location.directory; const autoMigrate = ctx.options?.autoMigrate === true; // `--full` defeats bd's MCP detection, which reads Claude Code's settings and would otherwise @@ -55,13 +77,27 @@ export async function setupBeads( const warn = deps.warn ?? ((message: string) => console.warn(message)); const diagnosticIntervalMs = deps.diagnosticIntervalMs ?? DEFAULT_DIAGNOSTIC_INTERVAL_MS; const diagnosticTimes = new Map<string, number>(); + const abort = new AbortController(); + const registrations: Array<{ dispose(): Promise<void> }> = []; + + const hostVersion = typeof ctx.app?.version === "string" ? ctx.app.version : "unknown"; + if (!isSupportedOpenCodeVersion(hostVersion)) { + try { + warn( + `[opencode-beads] unsupported-host OpenCode ${hostVersion}; supported beta/next range is ${SUPPORTED_OPENCODE_RANGE}. No Beads agents or hooks were registered.` + ); + } catch { + // Host-version diagnostics never affect plugin loading. + } + return async () => {}; + } - const guardRuntime = createGuardRuntime(deps.bd ?? ((cwd, args) => runBd(cwd, args))); const runtime: ContextRuntime = { - guard: deps.runtime?.guard ?? ((cwd) => checkBeadsStore(cwd, guardRuntime, { autoMigrate })), + guard: deps.runtime?.guard ?? ((cwd, signal) => checkBeadsStore(cwd, + createGuardRuntime(deps.bd ?? ((dir, args) => runBd(dir, args, { signal }))), { autoMigrate })), prime: deps.runtime?.prime ?? - ((cwd) => (deps.runPrime ?? ((dir, flags) => runBdPrime(dir, {}, flags)))(cwd, primeFlags)), + ((cwd, signal) => deps.runPrime ? deps.runPrime(cwd, primeFlags) : runBdPrime(cwd, { signal }, primeFlags)), async agentMode(agentID): Promise<AgentMode | undefined> { const response = await ctx.agent.get({ agentID }); return response.data.mode; @@ -85,7 +121,6 @@ export async function setupBeads( luna: ctx.options?.luna === true, steps: typeof stepsOption === "number" && Number.isInteger(stepsOption) && stepsOption >= 0 ? stepsOption : undefined, }); - await ctx.agent.transform((editor) => applyBeadsAgents(editor, agents)); const idleMinutes = ctx.options?.refreshAfterIdleMinutes; const controller = createContextController(runtime, directory, { @@ -98,45 +133,292 @@ export async function setupBeads( }); // Sessions that have received Beads context; their compaction summaries should keep Beads state. - const beadsSessions = new Set<string>(); - - await ctx.session.hook("context", async (event) => { - const text = await controller.systemFor(event.sessionID, event.agent); - if (!text) return; - if (text.startsWith("<beads-context")) { - beadsSessions.add(event.sessionID); - // With prime attached, bd's full AGENTS.md sections only repeat it (Claude Code gets a pointer). - minimizeSystemParts(event.system); + const beadsSessions = new Map<string, Set<string>>(); + const recoveryCues = new Map<string, string>(); + const references = new Map<string, Set<string>>(); + const gaps = new Map<string, Set<string>>(); + const requestCapabilities = new Map<string, { agent: string; capabilities: RequestCapabilities }>(); + let typedRegistration = deps.continuityQualified === true ? "unavailable: host tool/permission/session interface missing" : "withheld pending 1.1"; + + /** Retain distinct unresolved outcomes without a read, success or lifecycle cue erasing them. */ + function recordGap(sessionID: string, detail: string): void { + const known = gaps.get(sessionID) ?? new Set<string>(); + if (known.size < 32) known.add(detail); + else if (!known.has(detail)) known.add("Additional persistence gaps exceed the retained detail limit; earlier unresolved details remain. Inspect tool outcomes before further writes."); + gaps.set(sessionID, known); + } + + /** Render bounded persistence state independently of the next-request recovery cue. */ + function gapFor(sessionID: string): string | undefined { + return [...(gaps.get(sessionID) ?? [])].join("\n") || undefined; + } + + /** Drop every location-bound reference together before another request can reuse it. */ + function forgetSession(sessionID: string): void { + controller.forget(sessionID); + beadsSessions.delete(sessionID); + references.delete(sessionID); + recoveryCues.delete(sessionID); + gaps.delete(sessionID); + requestCapabilities.delete(sessionID); + for (const key of diagnosticTimes.keys()) { + if (key.endsWith(`:${sessionID}`)) diagnosticTimes.delete(key); } - event.system.push({ type: "text", text }); - }); + } + + let observation: Promise<void> | undefined; + let cleanupPromise: Promise<void> | undefined; + /** Abort immediately; callers can await all scoped registration/observer disposal. */ + function cleanup(): Promise<void> { + if (cleanupPromise) return cleanupPromise; + abort.abort(); + controller.dispose(); + beadsSessions.clear(); + recoveryCues.clear(); + references.clear(); + gaps.clear(); + requestCapabilities.clear(); + diagnosticTimes.clear(); + const pending = registrations.splice(0).reverse().map((registration) => { + try { return registration.dispose(); } catch (error) { return Promise.reject(error); } + }); + cleanupPromise = Promise.allSettled([...pending, ...(observation ? [observation] : [])]).then((results) => { + const failures = results.filter((result): result is PromiseRejectedResult => result.status === "rejected"); + if (failures.length) { + try { warn("[opencode-beads] cleanup-failed: owned registration disposal failed"); } catch { /* Preserve the actual failure. */ } + throw new AggregateError(failures.map((result) => result.reason), "Beads cleanup failed"); + } + }); + return cleanupPromise; + } + + try { + registrations.push(await ctx.agent.transform((editor) => { + if (!abort.signal.aborted) applyBeadsAgents(editor, agents); + })); - await ctx.session.hook("compaction", (event) => { - if (beadsSessions.has(event.sessionID)) { - event.system.push({ type: "text", text: BEADS_COMPACTION_GUIDANCE }); + // Diagnostics share the host's bounded reporting state; no status service or store reads. + function workflowDiagnostic(message: string): void { + if (abort.signal.aborted || diagnosticTimes.has(message)) return; + diagnosticTimes.set(message, now()); + try { warn(`[opencode-beads] ${message}`); } catch { /* Diagnostics never break loading. */ } } - }); - const abort = new AbortController(); - void (async () => { - try { - for await (const event of ctx.event.subscribe({ signal: abort.signal })) { - const type = (event as { type?: unknown }).type; - const sessionID = eventSessionID(event); - if (!sessionID) continue; - if (type === "session.compaction.ended") controller.invalidate(sessionID); - else if (type === "session.deleted") { - controller.forget(sessionID); - beadsSessions.delete(sessionID); - for (const key of diagnosticTimes.keys()) { - if (key.endsWith(`:${sessionID}`)) diagnosticTimes.delete(key); - } + let skillFailure: string | undefined; + if (ctx.skill) { + let skill; + try { skill = await loadBeadsSkill(deps.workflowAssets); } + catch (error) { + skillFailure = error instanceof Error ? error.message : "skill-unreachable"; + workflowDiagnostic(skillFailure); + } + if (skill) { + const packagedSkill = skill; + registrations.push(await ctx.skill.transform((editor) => { + if (!abort.signal.aborted) applyBeadsSkill(editor, packagedSkill, workflowDiagnostic); + })); + } + } + + // Native package transforms precede config/file command transforms. Competing + // plugins use the same ordinary ordering; initial lookup is diagnostic only. + if (ctx.command && ctx.session.prompt) { + let commandRegistration = "pending"; + const definitions = createBeadsCommands({ prompt: ctx.session.prompt }, async (sessionID) => { + let context: string = "uncertain"; + let locationKnown = false; + if (ctx.session.get) { + try { + const info = await ctx.session.get({ sessionID: sessionID as Parameters<NonNullable<typeof ctx.session.get>>[0]["sessionID"] }); + if (info.location.directory !== directory) { + forgetSession(sessionID); + context = "withheld: session location changed"; + } else { + locationKnown = true; + const observedAgent = requestCapabilities.get(sessionID)?.agent; + const audience = observedAgent && BEADS_TASK_AGENTS.has(observedAgent) ? "task-agent" : "primary"; + const cached = controller.statusFor(sessionID, audience); + context = cached === "loading" ? "uncertain: context refresh in progress" : cached ?? "missing: not loaded"; + } + } catch { forgetSession(sessionID); context = "uncertain: session lookup failed"; } + } + let guard: unknown = { kind: "withheld", reason: "session location unconfirmed" }; + if (locationKnown && !abort.signal.aborted) { + try { guard = await runtime.guard(directory, abort.signal); } + catch { guard = { kind: "probe-failed" }; } } + let effectiveCommands: Array<{ name: string; description?: string }> | undefined; + let effectiveSkills: string[] | string; + try { effectiveCommands = (await ctx.command!.list()).data.map(({ name, description }) => ({ name, description })); } + catch { /* Absence of lookup evidence is unknown, not an empty registry. */ } + try { effectiveSkills = ctx.skill ? (await ctx.skill.list()).data.map((entry) => String(entry.id)) : "unavailable"; } + catch { effectiveSkills = "unavailable: registry lookup failed"; } + return JSON.stringify({ + host: hostVersion, admission: SUPPORTED_OPENCODE_RANGE, liveQualification: "not executed", + commands: { + registration: commandRegistration, + lookup: effectiveCommands ? "available" : "failed", + effective: effectiveCommands ?? "unknown: registry lookup failed", + mismatches: effectiveCommands && definitions.flatMap((expected) => { + const effective = effectiveCommands.find((entry) => entry.name === expected.name); + return effective && effective.description !== expected.description + ? [{ name: expected.name, expectedDescription: expected.description, effectiveDescription: effective.description }] + : []; + }), + missing: effectiveCommands && definitions.filter((expected) => !effectiveCommands.some((entry) => entry.name === expected.name)).map((entry) => entry.name), + ownership: "unavailable: public owner identity is not exposed; matching names/descriptions do not prove ownership; identical-description overrides cannot be detected", + }, + skill: { failure: skillFailure, effective: effectiveSkills }, + typedContinuity: deps.continuityQualified === true ? "internal qualification fixture" : "withheld pending 1.1", + typedRegistration, + requestCapability: requestCapabilities.get(sessionID) ?? "unknown: no actual request observation", + leafAvailability: "unknown until native catalog discovery and per-operation permission checks; execute visibility alone does not prove leaf availability", + context, guard, persistence: gapFor(sessionID) ?? "uncertain: no final-write guarantee", + }); + }, deps.workflowAssets?.skillPath, abort.signal); + let existing: string[]; + try { existing = (await ctx.command.list()).data.map((entry) => entry.name); } + catch { existing = []; workflowDiagnostic("command-registry-unavailable: effective ownership unknown"); } + const names = definitions.map((entry) => entry.name); + const collisions = existing.filter((name) => names.includes(name)); + if (collisions.length) workflowDiagnostic(`command-collision: ${collisions.join(", ")}; earlier entries follow native precedence and may be replaced by this package`); + try { + registrations.push(await ctx.command.transform((editor) => { + if (abort.signal.aborted) return; + for (const definition of definitions) editor.add(definition); + })); + commandRegistration = "registered"; + } catch (error) { + commandRegistration = "failed"; + workflowDiagnostic("command-registration-failed: native transform failed"); + throw error; } - } catch { - // The stream ends on cleanup; lost events only delay a cache refresh. } - })(); - return () => abort.abort(); + function rememberResult(sessionID: string, result: ContinuityResult): void { + if (abort.signal.aborted) return; + const write = ["claim", "note", "update", "close", "create", "remember"].includes(result.operation); + // Full prime can depend on issue state. 7.1 does not establish safe narrowing. + if (write && result.status !== "not-claimed") controller.invalidateAll(); + const values = references.get(sessionID) ?? new Set<string>(); + const memory = ["remember", "recall", "memories"].includes(result.operation); + if (["acknowledged", "found", "landed-uncommitted"].includes(result.status)) { + if (result.reference) values.add(`${memory ? "memory" : "task"}:${result.reference}`); + if (result.operation === "get") for (const id of Object.keys(result.issues ?? {})) values.add(`task:${id}`); + if (result.operation === "find") for (const row of result.records ?? []) values.add(`task:${row.id}`); + } + while (values.size > 32) values.delete(values.values().next().value!); + if (values.size) references.set(sessionID, values); + if (["uncertain", "failed", "landed-uncommitted"].includes(result.status) || result.complete === false) { + const detail = `${result.operation} ${JSON.stringify(result.target)}${result.actor ? ` actor=${result.actor}` : ""}: ${result.status}; ${result.detail ?? (result.complete === false ? "partial result; inspect failed IDs/completeness" : "commit not confirmed")}`; + if (write) recordGap(sessionID, detail); + else recoveryCues.set(sessionID, `Inspect current ${memory ? "memory" : "task"} state before relying on this outcome: ${detail}. Do not retry an uncertain write blindly.`); + } + } + + // 1.1 remains unexecuted: ordinary setup has no product option that enables this path. + // Tests supply the qualification dependency and the same public host/runner interfaces. + if (deps.continuityQualified === true && ctx.tool && ctx.permission && ctx.session.get) { + registrations.push(await ctx.tool.transform((editor) => { + if (abort.signal.aborted) return; + editor.namespace({ name: "beads", description: "Permissioned Beads daily-loop reads and writes. Search this namespace for current signatures; inspect outcome and completeness before dependent work." }); + for (const tool of createContinuityTools({ location: ctx.location, permission: ctx.permission!, session: { get: ctx.session.get! } }, { + ...deps.continuity, signal: abort.signal, + acknowledged(sessionID, result) { + rememberResult(sessionID, result); + deps.continuity?.acknowledged?.(sessionID, result); + }, + })) editor.add(tool); + typedRegistration = "registered: beads namespace, code-mode-only"; + })); + } + + registrations.push(await ctx.session.hook("context", async (event) => { + if (abort.signal.aborted) return; + if (ctx.session.get) { + try { + const info = await ctx.session.get({ sessionID: event.sessionID }); + if (info.location.directory !== directory) { + forgetSession(event.sessionID); + return; + } + } catch { + forgetSession(event.sessionID); + if (!abort.signal.aborted) event.system.push({ type: "text", text: "<beads-notice>Beads context withheld: current session location could not be established. Do not bypass this hold with manual store access.</beads-notice>" }); + return; + } + } + const knownTools = event.tools && typeof event.tools === "object"; + const capabilities: RequestCapabilities = knownTools ? { + execute: Object.hasOwn(event.tools, "execute"), shell: Object.hasOwn(event.tools, "shell"), + typed: typedRegistration.startsWith("registered") && Object.hasOwn(event.tools, "execute") ? "registered-execute-visible" : "unavailable", + } : { ...UNKNOWN_CAPABILITIES }; + requestCapabilities.set(event.sessionID, { agent: event.agent, capabilities }); + const text = await controller.systemFor(event.sessionID, event.agent, capabilities); + if (!text || abort.signal.aborted) return; + if (text.startsWith("<beads-context")) { + const eligible = beadsSessions.get(event.sessionID) ?? new Set<string>(); + eligible.add(event.agent); + beadsSessions.set(event.sessionID, eligible); + // Reduce only complete bodies already supplied here; marker ownership alone is insufficient. + minimizeSystemParts(event.system, text); + } + event.system.push({ type: "text", text }); + const cue = recoveryCues.get(event.sessionID); + const gap = gapFor(event.sessionID); + if (cue || gap) { + const recovery = [cue, gap && `Known persistence gaps: inspect durable state before another write; do not blindly retry.\n${gap}`].filter(Boolean).join("\n"); + event.system.push({ type: "text", text: `<beads-recovery>${recovery}</beads-recovery>` }); + recoveryCues.delete(event.sessionID); + } + })); + + registrations.push(await ctx.session.hook("compaction", (event) => { + if (!abort.signal.aborted && beadsSessions.get(event.sessionID)?.has(event.agent)) { + event.system.push({ type: "text", text: BEADS_COMPACTION_GUIDANCE }); + const known = references.get(event.sessionID); + const gap = gapFor(event.sessionID); + if (known?.size || gap) event.system.push({ type: "text", text: `<beads-references>${JSON.stringify({ references: [...(known ?? [])], gap: gap ?? "No final-write guarantee; only earlier acknowledgements are known." })}</beads-references>` }); + } + })); + + if (ctx.tool) registrations.push(await ctx.tool.hook("execute.after", (event) => { + // Typed inner calls acknowledge in their executor. The outer code-mode result adds nothing. + if (abort.signal.aborted || event.tool === "execute" || event.tool.startsWith("beads_") || !beadsSessions.has(event.sessionID)) return; + if (event.tool === "subagent") { + controller.invalidate(event.sessionID); + recoveryCues.set(event.sessionID, "A child returned. Reconcile its scope, attempts, validation, durable references and incomplete work before proceeding within the existing authorization."); + } + })); + + observation = (async () => { + try { + for await (const event of ctx.event.subscribe({ signal: abort.signal })) { + if (abort.signal.aborted) break; + const type = (event as { type?: unknown }).type; + const sessionID = eventSessionID(event); + if (!sessionID) continue; + if (type === "session.deleted" || type === "session.moved" || type === "session.forked") { + forgetSession(sessionID); + } else if (beadsSessions.has(sessionID) && ["session.compaction.started", "session.compaction.ended", "session.compaction.failed", + "session.execution.started", "session.execution.interrupted", "session.execution.failed", "session.model.selected", "session.agent.selected"].includes(String(type))) { + controller.invalidate(sessionID); + recoveryCues.set(sessionID, "Recovery requires current task state and relevant memory: re-read attempts, decisions, blockers and the next authorized action. Cached absence is not proof of absence; preserve any guard hold."); + if (type === "session.execution.interrupted" || type === "session.execution.failed" || type === "session.compaction.failed") { + recordGap(sessionID, "Execution or compaction stopped; no final checkpoint is guaranteed. Recover from earlier acknowledged records."); + } + } + } + } catch { + // The stream ends on cleanup; lost events only delay a cache refresh. + } + })(); + + return cleanup; + } catch (error) { + // Setup failures must not leave earlier registrations alive. Keep the setup error + // as the primary failure; cleanup reports its own failure independently. + await cleanup().catch(() => {}); + throw error; + } } diff --git a/src/instructions.ts b/src/instructions.ts index 2de9da5..fc9122d 100644 --- a/src/instructions.ts +++ b/src/instructions.ts @@ -3,8 +3,8 @@ * * `bd setup claude` writes only a short pointer into CLAUDE.md because its hooks run `bd prime`. * OpenCode projects instead get bd's full-profile AGENTS.md section (and often a Codex section), - * which repeats what injected prime already provides on every request. When this plugin attaches - * prime, those bd-managed blocks are replaced in the outgoing system text only; files are untouched. + * which can repeat injected prime. Only demonstrably duplicated complete bodies are replaced in + * outgoing system text; customized blocks and files are untouched. */ /** Replaces bd-managed blocks whose content duplicates injected prime. */ @@ -14,17 +14,20 @@ export const MINIMAL_BEADS_POINTER = // bd's full-profile integration block and its Codex setup block. Minimal-profile blocks are already // equivalent to Claude Code's pointer and are left alone. const DUPLICATE_BLOCKS: readonly RegExp[] = [ - /<!-- BEGIN BEADS INTEGRATION\b[^>]*\bprofile:full\b[^>]*-->[\s\S]*?<!-- END BEADS INTEGRATION -->/g, - /<!-- BEGIN BEADS CODEX SETUP\b[^>]*-->[\s\S]*?<!-- END BEADS CODEX SETUP -->/g, + /<!-- BEGIN BEADS INTEGRATION\b[^>]*\bprofile:full\b[^>]*-->([\s\S]*?)<!-- END BEADS INTEGRATION -->/g, + /<!-- BEGIN BEADS CODEX SETUP\b[^>]*-->([\s\S]*?)<!-- END BEADS CODEX SETUP -->/g, ]; /** Return `text` with duplicate bd blocks collapsed into one pointer, and how many were replaced. */ -export function minimizeBeadsInstructions(text: string): { text: string; replaced: number } { +export function minimizeBeadsInstructions(text: string, suppliedContext = ""): { text: string; replaced: number } { let replaced = 0; let pointerPlaced = false; let result = text; for (const pattern of DUPLICATE_BLOCKS) { - result = result.replace(pattern, () => { + result = result.replace(pattern, (block: string, body: string) => { + // Markers prove ownership, not duplication. Keep customized policy unless the complete + // body is already present in the context actually attached to this request. + if (!body.trim() || !suppliedContext.includes(body.trim())) return block; replaced++; if (pointerPlaced) return ""; pointerPlaced = true; @@ -35,11 +38,11 @@ export function minimizeBeadsInstructions(text: string): { text: string; replace } /** Apply {@link minimizeBeadsInstructions} to each text system part in place. */ -export function minimizeSystemParts(parts: Array<{ type: string; text?: string }>): number { +export function minimizeSystemParts(parts: Array<{ type: string; text?: string }>, suppliedContext = ""): number { let replaced = 0; for (const part of parts) { if (part.type !== "text" || typeof part.text !== "string") continue; - const minimized = minimizeBeadsInstructions(part.text); + const minimized = minimizeBeadsInstructions(part.text, suppliedContext); if (minimized.replaced > 0) { part.text = minimized.text; replaced += minimized.replaced; diff --git a/src/prime.ts b/src/prime.ts index 04ed67a..2291cf7 100644 --- a/src/prime.ts +++ b/src/prime.ts @@ -11,7 +11,7 @@ export class PrimeTimeoutError extends Error { } export class PrimeProcessError extends Error { - constructor(readonly exitCode: number, readonly stderr: string, command = "bd prime") { + constructor(readonly exitCode: number, readonly stderr: string, command = "bd prime", readonly stdout = "") { super(`${command} exited with code ${exitCode}`); this.name = "PrimeProcessError"; } @@ -27,6 +27,7 @@ export interface PrimeProcess { } export interface PrimeExecutionOptions { + signal?: AbortSignal; timeoutMs?: number; drainTimeoutMs?: number; spawn?: (directory: string, args: readonly string[]) => PrimeProcess; @@ -108,12 +109,14 @@ async function runBdAttempt( const timeoutMs = options.timeoutMs ?? DEFAULT_PRIME_TIMEOUT_MS; const drainTimeoutMs = options.drainTimeoutMs ?? DEFAULT_PRIME_DRAIN_TIMEOUT_MS; const command = ["bd", ...args].join(" "); + options.signal?.throwIfAborted(); const child = (options.spawn ?? spawnBd)(directory, args); const stdout = new Response(child.stdout).text(); const stderr = new Response(child.stderr).text(); let timedOut = false; - let rejectTimeout: (error: PrimeTimeoutError) => void = () => {}; + let aborted = false; + let rejectTimeout: (error: unknown) => void = () => {}; const timeout = new Promise<never>((_resolve, reject) => { rejectTimeout = reject; }); @@ -122,10 +125,17 @@ async function runBdAttempt( terminateProcessTree(child); rejectTimeout(new PrimeTimeoutError(timeoutMs, command)); }, timeoutMs); + const onAbort = () => { + aborted = true; + terminateProcessTree(child); + rejectTimeout(options.signal?.reason ?? new DOMException("Aborted", "AbortError")); + }; + options.signal?.addEventListener("abort", onAbort, { once: true }); + if (options.signal?.aborted) onAbort(); const completion = Promise.all([child.exited, stdout, stderr]).then( ([exitCode, output, errorOutput]) => { - if (exitCode !== 0) throw new PrimeProcessError(exitCode, errorOutput, command); + if (exitCode !== 0) throw new PrimeProcessError(exitCode, errorOutput, command, output); return output; } ); @@ -134,7 +144,8 @@ async function runBdAttempt( return await Promise.race([completion, timeout]); } finally { cancelTimeout(); - if (timedOut) { + options.signal?.removeEventListener("abort", onAbort); + if (timedOut || aborted) { await drainAfterKill( completion.catch(() => undefined), drainTimeoutMs, diff --git a/src/workflows.ts b/src/workflows.ts new file mode 100644 index 0000000..cc2adef --- /dev/null +++ b/src/workflows.ts @@ -0,0 +1,131 @@ +import { readFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import type { Plugin } from "@opencode/plugin"; +import type { CommandDefinition } from "@opencode/plugin/promise/command"; + +type SkillEditor = Parameters<Parameters<Plugin.Context["skill"]["transform"]>[0]>[0]; +export type BeadsSkill = Parameters<SkillEditor["add"]>[0]; + +/** Package-relative paths survive npm installation, spaces and ignored/deep install roots. */ +export const BEADS_SKILL_PATH = fileURLToPath(new URL("../assets/oc-beads/SKILL.md", import.meta.url)); + +/** Exact membership is checked against the selected immutable command inventory. */ +export const WORKFLOWS = { + audit: ["Inspect or explicitly record scoped audit evidence; never collect transcripts automatically.", "INTEGRATION_PATTERNS"], + blocked: ["Inspect blocked work; distinguish explicit blocked status from dependency blocking.", "DEPENDENCIES"], + close: ["Close only the requested work with acceptance and validation evidence and closure authority.", "WORKFLOWS"], + comments: ["Read discussion or append the requested comment/checkpoint without replacing prior notes.", "RESUMABILITY"], + compact: ["Explain or perform explicitly authorized administrative issue compaction using current CLI help; this is separate from conversation compaction.", "TROUBLESHOOTING"], + create: ["Create the requested issue with useful description, acceptance/design and discovered-from provenance when applicable; leave it unassigned unless assignment is intentional.", "ISSUE_CREATION"], + decision: ["Record the requested decision, rationale and alternatives; reference intended behavior and detailed rationale in their project-owned homes.", "INTEGRATION_PATTERNS"], + delete: ["Inspect the requested deletion and its effects; delete only with explicit scoped authority, never as a substitute for closure.", "TROUBLESHOOTING"], + dep: ["Inspect or change only the requested dependencies. Verify that X depends on Y in the intended direction.", "DEPENDENCIES"], + epic: ["Inspect or manage the requested hierarchy; closed children alone do not establish parent acceptance.", "WORKFLOWS"], + export: ["Export only the requested scope and destination with current CLI help; distinguish issue interchange from a full backup or memory export.", "CLI_REFERENCE"], + import: ["Inspect current import help and requested input before an authorized import; disclose stale/upsert and partial-write risks and routing limits. Never initialize or restore implicitly.", "TROUBLESHOOTING"], + init: ["Provide human-directed onboarding and current bd init help, explaining side effects. Do not initialize a store automatically.", "INTEGRATION_PATTERNS"], + label: ["Inspect or update only requested labels while preserving the project's taxonomy.", "CLI_REFERENCE"], + list: ["Retrieve issues with the requested supported filters. Retrieval does not authorize claims or task execution.", "CLI_REFERENCE"], + prime: ["Use supplied current context when available; request a guarded full-context refresh only when authorized and no hold is active.", "RESUMABILITY"], + quickstart: ["Provide onboarding using current prime/help and packaged guidance; the deprecated bd quickstart command is not assumed available.", "BOUNDARIES"], + ready: ["Inspect actionable work. Honor an exact assignment; claim and execute only work already within the authorized scope.", "WORKFLOWS"], + "rename-prefix": ["Explain the requested project identifier migration and reference impact using current CLI help; require explicit migration authority.", "TROUBLESHOOTING"], + reopen: ["Inspect current state, then reopen only the requested issue with the supplied regression/correction reason.", "WORKFLOWS"], + restore: ["Inspect supported historical retrieval/restoration in the installed Dolt-based CLI; do not assume legacy Git restore syntax or restore without authority.", "RESUMABILITY"], + search: ["Search issues using supported filters; distinguish issue search from reusable memory search.", "CLI_REFERENCE"], + show: ["Read requested issue details, prior notes, acceptance and dependencies without mutating them.", "RESUMABILITY"], + stats: ["Report the requested issue overview without automatically claiming work.", "CLI_REFERENCE"], + sync: ["Inspect current bd sync help: current federation synchronization differs from removed JSONL sync. Synchronize only with explicit authority; disclose conflicts without forcing a side.", "WORKTREES"], + template: ["Discover supported templates/formulas through installed help; replace obsolete static template syntax and do not execute a formula automatically.", "MOLECULES"], + update: ["Apply only requested field changes; use an atomic claim for authorized execution and append-style progress notes. Replacement of notes is intentional, never routine.", "RESUMABILITY"], + version: ["Report actual installed CLI, package and host versions and qualification limits; do not hardcode a latest version or install/upgrade anything.", "CLI_REFERENCE"], + workflow: ["Guide native Beads work and recovery: inspect scope, read prior state, claim atomically, checkpoint meaningful learning, validate and return evidence. Continue only within an authorized batch/goal.", "WORKFLOWS"], +} as const; + +export interface SkillLoadDependencies { + skillPath?: string; + readText?: (path: string) => Promise<string>; +} + +/** Read only the explicit packaged guidance links; no scan, installer or watcher is needed. */ +export async function loadBeadsSkill(deps: SkillLoadDependencies = {}): Promise<BeadsSkill> { + const skillPath = deps.skillPath ?? BEADS_SKILL_PATH; + const readText = deps.readText ?? ((path: string) => readFile(path, "utf8")); + let markdown: string; + try { + markdown = await readText(skillPath); + } catch { + throw new Error("skill-unreachable SKILL.md"); + } + const content = markdown.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "").trim(); + const links = new Set([...content.matchAll(/\]\(((?:resources|adr)\/[^)]+)\)/g)].map((match) => match[1]!)); + if (links.size !== 17) throw new Error("resource-links-invalid: expected 17 explicit supporting links"); + for (const link of links) { + try { + if (!(await readText(resolve(dirname(skillPath), link))).trim()) throw new Error("empty"); + } catch { + throw new Error(`resource-unreachable ${link}`); + } + } + // The accepted asset's semantic trigger; no general frontmatter parser or host scanner clone. + return { + id: "oc-beads" as BeadsSkill["id"], + name: "oc-beads" as BeadsSkill["name"], + description: "Use Beads for durable work tracking, dependencies, blockers, checkpoints, recovery and reusable project knowledge.", + path: skillPath as BeadsSkill["path"], + content, + }; +} + +/** Inspect the candidate editor on every rebuild, including earlier changing user transforms. */ +export function applyBeadsSkill(editor: SkillEditor, skill: BeadsSkill, diagnose: (message: string) => void): void { + if (editor.list().some((entry) => String(entry.id) === "oc-beads" || String(entry.name) === "oc-beads")) { + diagnose("skill-collision oc-beads: existing customization preserved"); + return; + } + editor.add(skill); +} + +/** Build native prompt commands, never shell/template interpolation or synthetic continuation. */ +export function createBeadsCommands( + session: Pick<Plugin.Context["session"], "prompt">, + status: (sessionID: string) => Promise<string>, + skillPath = BEADS_SKILL_PATH, + signal?: AbortSignal, +): CommandDefinition[] { + const common = [ + "Follow repository/user authority and the exact requested scope. Do not bypass a hold with manual prime or other store access.", + "Use actual request capabilities: discover the native beads namespace and compose supported daily operations in code mode when present. Native shell is direct-only; advanced/admin workflows use permissioned direct shell and current CLI help. Do not call tools.shell inside execute. Advanced guidance does not enable automation.", + "Every agent-authored write, claim and heartbeat needs the host-rendered actual executor's command-scoped BEADS_ACTOR or --actor. Preserve authorship; read created_by, owner and assignee separately.", + "Distinguish task checkpoints from reusable memory. Report failed, partial and uncertain outcomes; never blindly retry an ambiguous write.", + `Load skill oc-beads (installed file ${JSON.stringify(skillPath)}). If guidance is missing or permission denies access, report it honestly without bypass.`, + ].join("\n"); + const commands: CommandDefinition[] = Object.entries(WORKFLOWS).map(([stem, [purpose, resource]]) => ({ + name: `oc-beads:${stem}`, + description: purpose, + async execute(input) { + if (signal?.aborted) throw new Error("Beads workflow unloaded"); + await session.prompt({ + ...input.prompt, + sessionID: input.sessionID, + delivery: input.delivery, + text: `${purpose}\n${common}\nSupporting guidance: ${JSON.stringify(resolve(dirname(skillPath), "resources", `${resource}.md`))}\n\nUser request:\n${input.prompt.text}`, + }); + }, + })); + commands.push({ + name: "oc-beads:status", + description: "Inspect native discovery, conflicts, compatibility and context/persistence status without opening a store.", + async execute(input) { + if (signal?.aborted) throw new Error("Beads workflow unloaded"); + const report = await status(input.sessionID); + if (signal?.aborted) throw new Error("Beads workflow unloaded"); + await session.prompt({ + ...input.prompt, sessionID: input.sessionID, delivery: input.delivery, + text: `Report these observed Beads diagnostics. This is read-only; do not run store-opening commands, change configuration or infer live qualification.\n${report}\n\nUser request:\n${input.prompt.text}`, + }); + }, + }); + return commands; +} diff --git a/tests/agents.test.ts b/tests/agents.test.ts index a201d92..7841e11 100644 --- a/tests/agents.test.ts +++ b/tests/agents.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { TASK_AGENT_PERMISSIONS } from "../src/agents"; +import { beadsAgents, DEFAULT_TASK_AGENT_STEPS, TASK_AGENT_PERMISSIONS } from "../src/agents"; type Rule = { action: string; resource: string; effect: string }; @@ -30,6 +30,18 @@ const rules: Rule[] = [{ action: "*", resource: "*", effect: "allow" }, ...TASK_ const decide = (command: string) => evaluate("shell", command, rules); describe("task-agent permission decisions", () => { + test("keeps IDs, inherited model, optional Luna and interruption caps independent of scope", () => { + const defaults = beadsAgents(); + expect(Object.keys(defaults)).toEqual(["beads-task-agent"]); + expect(defaults["beads-task-agent"]?.model).toBeUndefined(); + expect(defaults["beads-task-agent"]?.steps).toBe(DEFAULT_TASK_AGENT_STEPS); + const capped = beadsAgents({ luna: true, steps: 12 }); + expect(Object.keys(capped)).toEqual(["beads-task-agent", "beads-task-agent-luna"]); + expect(capped["beads-task-agent-luna"]?.system).toBe(capped["beads-task-agent"]?.system); + expect(capped["beads-task-agent-luna"]?.system).toContain("already-authorized batch or goal"); + expect(capped["beads-task-agent-luna"]?.steps).toBe(12); + expect(beadsAgents({ steps: 0 })["beads-task-agent"]?.steps).toBeUndefined(); + }); test.each([ "git commit", "git commit -m done", diff --git a/tests/context.test.ts b/tests/context.test.ts index 767d538..8abd84c 100644 --- a/tests/context.test.ts +++ b/tests/context.test.ts @@ -22,7 +22,7 @@ function fixture( async (_directory: string): Promise<GuardResult> => typeof options.guard === "function" ? options.guard() : (options.guard ?? { kind: "ready" }) ); - const prime = mock(async (_directory: string) => (options.prime ? options.prime() : "PRIME")); + const prime = mock(async (_directory: string, _signal?: AbortSignal) => (options.prime ? options.prime() : "PRIME")); const agentMode = mock(async (agentID: string) => options.modes?.[agentID]); const diagnose = mock((_code: string, _sessionID: string) => {}); const runtime: ContextRuntime = { guard, prime, agentMode, diagnose }; @@ -44,13 +44,60 @@ function fixture( } describe("context controller", () => { + test("renders actual request actors and capabilities without caching one agent identity", async () => { + const f = fixture(); + const typed = { typed: "registered-execute-visible", execute: true, shell: true } as const; + const a = await f.controller.systemFor("s1", "build", typed); + const b = await f.controller.systemFor("s1", "review", { typed: "unavailable", execute: false, shell: true }); + expect(a).toContain("opencode:build:"); + expect(a).toContain("namespace"); + expect(b).toContain("opencode:review:"); + expect(b).not.toContain("opencode:build:"); + expect(b).toContain("direct shell"); + expect(await f.controller.systemFor("s1", "review", { typed: "unavailable", execute: false, shell: false })).toContain("Neither"); + expect(f.prime).toHaveBeenCalledTimes(1); + const held = fixture({ guard: { kind: "migration-pending", installed: "1.3.1", store: "1.2.0" } }); + expect(await held.controller.systemFor("s1", "build", typed)).not.toContain("direct shell"); + }); + test("distinguishes missing, withheld, failed and intentionally empty context", async () => { + const missing = fixture({ guard: { kind: "uninitialized" } }); + await missing.controller.systemFor("s1", "build"); + expect(missing.controller.statusFor("s1", "primary")).toBe("missing"); + const held = fixture({ guard: { kind: "migration-pending", installed: "1.3.1", store: "1.2.0" } }); + await held.controller.systemFor("s1", "build"); + expect(held.controller.statusFor("s1", "primary")).toBe("withheld"); + const failed = fixture({ prime: async () => { throw new Error("failure"); } }); + await failed.controller.systemFor("s1", "build"); + expect(failed.controller.statusFor("s1", "primary")).toBe("failed"); + const empty = fixture({ prime: async () => "" }); + await empty.controller.systemFor("s1", "build"); + expect(empty.controller.statusFor("s1", "primary")).toBe("empty"); + }); + + test("aborts stale loads on invalidation and unload without leaking a late result", async () => { + let release: (value: string) => void = () => {}; + let signal: AbortSignal | undefined; + const f = fixture(); + f.prime.mockImplementation(async (_directory, abortSignal?: AbortSignal) => { + signal = abortSignal; + return new Promise<string>((resolve) => { release = resolve; }); + }); + const pending = f.controller.systemFor("s1", "build"); + for (let i = 0; i < 5; i++) await Promise.resolve(); + f.controller.invalidate("s1"); + expect(signal?.aborted).toBeTrue(); + release("STALE"); + expect(await pending).toBeUndefined(); + f.controller.dispose(); + expect(await f.controller.systemFor("s1", "build")).toBeUndefined(); + }); test("renders prime for primary agents and caches it per session", async () => { const f = fixture({ modes: { build: "primary" } }); const first = await f.controller.systemFor("s1", "build"); - expect(first).toBe(renderBeadsContext("PRIME", "primary")); + expect(first).toBe(renderBeadsContext("PRIME", "primary", { sessionID: "s1", agent: "build" })); expect(await f.controller.systemFor("s1", "build")).toBe(first); expect(f.prime).toHaveBeenCalledTimes(1); - expect(f.prime).toHaveBeenCalledWith("/project"); + expect(f.prime).toHaveBeenCalledWith("/project", expect.any(AbortSignal)); await f.controller.systemFor("s2", "build"); expect(f.prime).toHaveBeenCalledTimes(2); }); @@ -58,10 +105,10 @@ describe("context controller", () => { test("gives task agents the task-agent audience without an agent lookup", async () => { const f = fixture(); expect(await f.controller.systemFor("s1", "beads-task-agent")).toBe( - renderBeadsContext("PRIME", "task-agent") + renderBeadsContext("PRIME", "task-agent", { sessionID: "s1", agent: "beads-task-agent" }) ); expect(await f.controller.systemFor("s2", "beads-task-agent-luna")).toBe( - renderBeadsContext("PRIME", "task-agent") + renderBeadsContext("PRIME", "task-agent", { sessionID: "s2", agent: "beads-task-agent-luna" }) ); expect(f.agentMode).not.toHaveBeenCalled(); }); @@ -69,8 +116,8 @@ describe("context controller", () => { test("excludes regular subagents and fails open for unknown agents", async () => { const f = fixture({ modes: { explore: "subagent", custom: "all" } }); expect(await f.controller.systemFor("s1", "explore")).toBeUndefined(); - expect(await f.controller.systemFor("s1", "custom")).toBe(renderBeadsContext("PRIME", "primary")); - expect(await f.controller.systemFor("s2", "mystery")).toBe(renderBeadsContext("PRIME", "primary")); + expect(await f.controller.systemFor("s1", "custom")).toBe(renderBeadsContext("PRIME", "primary", { sessionID: "s1", agent: "custom" })); + expect(await f.controller.systemFor("s2", "mystery")).toBe(renderBeadsContext("PRIME", "primary", { sessionID: "s2", agent: "mystery" })); }); test("fails open and diagnoses when the agent lookup throws, then retries the lookup", async () => { @@ -78,7 +125,7 @@ describe("context controller", () => { f.agentMode.mockImplementationOnce(async () => { throw new Error("lookup failed"); }); - expect(await f.controller.systemFor("s1", "build")).toBe(renderBeadsContext("PRIME", "primary")); + expect(await f.controller.systemFor("s1", "build")).toBe(renderBeadsContext("PRIME", "primary", { sessionID: "s1", agent: "build" })); expect(f.diagnose).toHaveBeenCalledWith("agent_lookup_failed", "s1"); await f.controller.systemFor("s2", "build"); expect(f.agentMode).toHaveBeenCalledTimes(2); @@ -109,7 +156,7 @@ describe("context controller", () => { guard = { kind: "ready" }; f.advance(1_001); expect(await f.controller.systemFor("s1", "beads-task-agent")).toBe( - renderBeadsContext("PRIME", "task-agent") + renderBeadsContext("PRIME", "task-agent", { sessionID: "s1", agent: "beads-task-agent" }) ); expect(f.prime).toHaveBeenCalledTimes(1); }); @@ -124,13 +171,15 @@ describe("context controller", () => { expect(f.guard).toHaveBeenCalledTimes(2); }); - test("treats guard exceptions as retryable absence", async () => { + test("reports guard exceptions as retryable failure without manual bypass advice", async () => { const f = fixture({ guard: async () => { throw new Error("boom"); }, }); - expect(await f.controller.systemFor("s1", "beads-task-agent")).toBeUndefined(); + expect(await f.controller.systemFor("s1", "beads-task-agent")).toContain("do not bypass"); + expect(f.controller.statusFor("s1", "task-agent")).toBe("failed"); + expect(f.diagnose).toHaveBeenCalledWith("guard_failed", "s1"); expect(f.prime).not.toHaveBeenCalled(); }); @@ -158,7 +207,7 @@ describe("context controller", () => { expect(await f.controller.systemFor("s1", "beads-task-agent")).toBeUndefined(); f.advance(1_001); expect(await f.controller.systemFor("s1", "beads-task-agent")).toBe( - renderBeadsContext("PRIME", "task-agent") + renderBeadsContext("PRIME", "task-agent", { sessionID: "s1", agent: "beads-task-agent" }) ); }); @@ -211,3 +260,17 @@ describe("context controller", () => { expect(await f.controller.systemFor("s1", "beads-task-agent")).toContain("SECOND"); }); }); + +test("pending context is distinguishable from missing context for read-only diagnostics", async () => { + let release: (value: string) => void = () => {}; + const f = fixture({ prime: async () => new Promise<string>((resolve) => { release = resolve; }) }); + expect(f.controller.statusFor("s1", "primary")).toBeUndefined(); + const pending = f.controller.systemFor("s1", "build"); + for (let index = 0; index < 5; index++) await Promise.resolve(); + expect(f.controller.statusFor("s1", "primary")).toBe("loading"); + release("PRIME"); + await pending; + expect(f.controller.statusFor("s1", "primary")).toBe("ready"); + f.controller.dispose(); + expect(f.controller.statusFor("s1", "primary")).toBeUndefined(); +}); diff --git a/tests/continuity.test.ts b/tests/continuity.test.ts new file mode 100644 index 0000000..c2ce6a9 --- /dev/null +++ b/tests/continuity.test.ts @@ -0,0 +1,349 @@ +import { describe, expect, mock, test } from "bun:test"; +import type { ToolContext } from "@opencode/plugin/promise/tool"; +import { createContinuityTools, executorActor, type ContinuityDependencies, type ContinuityHost } from "../src/continuity"; +import { PrimeProcessError, PrimeTimeoutError, type PrimeExecutionOptions } from "../src/prime"; + +const invocation = (signal = new AbortController().signal) => ({ + sessionID: "session-1", agent: "restricted-worker", messageID: "message-1", id: "call-1", signal, + progress: async () => {}, +}) as unknown as ToolContext; + +function fixture() { + let output = JSON.stringify({key: 'lesson', value: 'first\nsecond', action: 'remembered'}); + let directory = "/project"; + let workspace = "/project/.beads"; + const permission = mock(async (_input: unknown) => {}); + const acknowledged = mock((_sessionID: string, _result: unknown) => {}); + const bd = mock(async (_cwd: string, args: readonly string[], _options?: PrimeExecutionOptions) => { + if (args[0] === "version") return "bd version 1.3.1"; + if (args[0] === "context") return JSON.stringify({ beads_dir: workspace, backend: "dolt", dolt_mode: "embedded", database: "beads" }); + return output; + }); + const host = { + location: { directory: "/project", project: { directory: "/project" } }, + permission: { assert: permission }, + session: { get: async () => ({ location: { directory } }) }, + } as unknown as ContinuityHost; + const dependencies: ContinuityDependencies = { + bd, acknowledged, + realpath: async (path) => path, + readText: async () => "1.3.1", + }; + const tools = createContinuityTools(host, dependencies); + const execute = (name: string, input: unknown, context = invocation()) => { + const tool = tools.find((tool) => tool.name === name); + if (!tool) throw new Error(`missing tool ${name}`); + return tool.execute(input, context).then(result => { + const schema = tool.output as { anyOf: Array<{ properties: Record<string, { const?: unknown }>; required: string[] }> }; + const branch = schema.anyOf.find(b => b.properties.status?.const === result.output.status); + expect(branch).toBeDefined(); + for (const required of branch!.required) expect(Object.hasOwn(result.output as object, required)).toBeTrue(); + expect(Object.keys(result.output as object).every(k => k in branch!.properties)).toBeTrue(); + expect(result.output.operation).toBe(name); + if (["claim", "note", "update", "close", "create", "remember"].includes(name)) expect(result.output.actor).toBe(executorActor(context)); + expect(JSON.parse(String(result.content))).toEqual(result.output); + return result; + }); + }; + return { tools, execute, bd, permission, acknowledged, dependencies, + output: (value: string) => { output = value; }, + move: (value: string) => { directory = value; }, + workspace: (value: string) => { workspace = value; }, + }; +} + +describe("continuity adapter (plugin fixtures, not host qualification)", () => { + test("declares eleven native namespace operations and structured output branches", () => { + const f = fixture(); + expect(f.tools.map(t => t.name)).toEqual(["find", "get", "edges", "memories", "recall", "claim", "note", "update", "close", "create", "remember"]); + for (const tool of f.tools) { + expect(tool.options).toEqual({ namespace: "beads", permission: "shell", codemode: true }); + expect(tool.output).toHaveProperty("anyOf"); + } + }); + const actor = "opencode:restricted-worker:960defe988fd"; + const row = { id: "task-1", title: "Task", status: "open", priority: 2, issue_type: "task" }; + const cases: Array<{ name: string; input: unknown; cli: unknown; args: string[]; result: Record<string, unknown> }> = [ + { name: "find", input: { view: "search", text: "--assignee=x", status: "open", assignee: actor, labels: ["a"], limit: 1 }, cli: [row, { ...row, id: "task-2" }], args: ["search", "--assignee", actor, "--status", "open", "--label", "a", "--limit", "2", "--json", "--sandbox", "--", "--assignee=x"], result: { status: "acknowledged", complete: false, limit: 1 } }, + { name: "get", input: { ids: ["task-1"] }, cli: [row], args: ["show", "task-1", "--json", "--sandbox"], result: { status: "acknowledged", issues: { "task-1": row }, failed: [], complete: true } }, + { name: "edges", input: { id: "task-1", direction: "up", types: ["blocks"] }, cli: [{ ...row, dependency_type: "blocks" }], args: ["dep", "list", "task-1", "--direction", "up", "--type", "blocks", "--json", "--sandbox"], result: { status: "acknowledged", complete: null, direction: "up" } }, + { name: "memories", input: { search: "--force" }, cli: { schema_version: 1, lesson: "text" }, args: ["memories", "--json", "--sandbox", "--", "--force"], result: { status: "found", memories: { lesson: "text" } } }, + { name: "recall", input: { key: "lesson" }, cli: { key: "lesson", found: true, value: "" }, args: ["recall", "lesson", "--json", "--sandbox"], result: { status: "found", value: "" } }, + { name: "claim", input: { id: "task-1" }, cli: [{ ...row, assignee: actor, status: "in_progress" }], args: ["update", "task-1", "--claim", "--json", "--actor", actor, "--sandbox"], result: { status: "acknowledged", reference: "task-1" } }, + { name: "note", input: { id: "task-1", text: "new\n'quoted'" }, cli: [{ ...row, notes: "prior\nnew\n'quoted'" }], args: ["update", "task-1", "--append-notes", "new\n'quoted'", "--json", "--actor", actor, "--sandbox"], result: { status: "acknowledged", reference: "task-1" } }, + { name: "update", input: { id: "task-1", set: { status: "blocked", priority: 1, addLabels: ["a"], removeLabels: ["b"] } }, cli: [{ ...row, status: "blocked", priority: 1, labels: ["a"] }], args: ["update", "task-1", "--status", "blocked", "--priority", "1", "--add-label", "a", "--remove-label", "b", "--json", "--actor", actor, "--sandbox"], result: { status: "acknowledged", reference: "task-1" } }, + { name: "close", input: { id: "task-1", reason: "Accepted" }, cli: [{ ...row, status: "closed" }], args: ["close", "task-1", "--reason", "Accepted", "--json", "--actor", actor, "--sandbox"], result: { status: "acknowledged", reference: "task-1" } }, + { name: "create", input: { title: "--assignee=x", type: "task", priority: 2, description: "d", acceptance: "a", design: "r", parent: "epic-1", discoveredFrom: "task-1", labels: ["a"] }, cli: { ...row, id: "generated-1", title: "--assignee=x" }, args: ["create", "--title", "--assignee=x", "--type", "task", "--priority", "2", "--description", "d", "--acceptance", "a", "--design", "r", "--parent", "epic-1", "--deps", "discovered-from:task-1", "--labels", "a", "--json", "--actor", actor, "--sandbox"], result: { status: "acknowledged", reference: "generated-1" } }, + { name: "remember", input: { key: "lesson", text: "text" }, cli: { key: "lesson", value: "text", action: "updated" }, args: ["remember", "--key", "lesson", "--json", "--actor", actor, "--sandbox", "--", "text"], result: { status: "acknowledged", reference: "lesson" } }, + ]; + for (const c of cases) test(`${c.name}: qualified argv, resource and declared success branch`, async () => { + const f = fixture(); f.output(JSON.stringify(c.cli)); + expect((await f.execute(c.name, c.input)).output).toMatchObject(c.result); + expect(f.bd.mock.calls.at(-1)?.[1]).toEqual(c.args); + const quote = (a: string) => /^[a-zA-Z0-9_./:=-]+$/.test(a) ? a : "'" + a.replaceAll("'", "'\\''") + "'"; + expect(f.permission).toHaveBeenCalledWith(expect.objectContaining({ action: "shell", resources: [["bd", ...c.args].map(quote).join(" ")], save: [] })); + f.output(JSON.stringify(c.name === "memories" ? { schema_version: 2 } : {})); + expect((await f.execute(c.name, c.input)).output.status).toBe(["find", "get", "edges", "memories", "recall"].includes(c.name) ? "failed" : "uncertain"); + }); + test("batch get preserves per-ID failures; rechecks before the next child spawn", async () => { + const f = fixture(); + f.bd.mockImplementation(async (_cwd, args) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads"}'; + if (args[1] === "task-2") throw new PrimeProcessError(1, "not found", "bd show"); + return JSON.stringify([{ ...row, id: args[1] }]); + }); + expect((await f.execute("get", { ids: ["task-1", "task-2"] })).output).toMatchObject({ status: "acknowledged", complete: false, failed: [{ id: "task-2", detail: expect.stringContaining("not found") }] }); + expect(f.permission.mock.calls).toHaveLength(1); + expect((f.permission.mock.calls[0]![0] as { resources: string[] }).resources).toEqual(["bd show task-1 --json --sandbox", "bd show task-2 --json --sandbox"]); + expect((await f.execute("get", { ids: ["task-2"] })).output).toMatchObject({ status: "failed", target: ["task-2"] }); + f.bd.mockImplementation(async (_cwd, args) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads"}'; + f.move("/moved"); return JSON.stringify([row]); + }); + const before = f.bd.mock.calls.filter(([,a]) => a[0] === "show").length; + await expect(f.execute("get", { ids: ["task-1", "task-2"] })).rejects.toThrow("location"); + expect(f.bd.mock.calls.filter(([,a]) => a[0] === "show")).toHaveLength(before + 1); + }); + test("find and edges distinguish bounded, unknown and failed data", async () => { + const f = fixture(); f.output(JSON.stringify([row])); + expect((await f.execute("find", { view: "list", limit: 2 })).output).toMatchObject({ complete: true }); + expect((await f.execute("find", { view: "blocked" })).output).toMatchObject({ complete: null }); + f.output('[{"id":"task-1"}]'); + expect((await f.execute("edges", { id: "task-1", direction: "down" })).output).toMatchObject({ status: "failed" }); + f.output("{}"); + expect((await f.execute("find", { view: "ready" })).output).toMatchObject({ status: "failed" }); + f.output(JSON.stringify([{ ...row, title: "" }])); + expect((await f.execute("get", { ids: ["task-1"] })).output.status).toBe("failed"); + f.output('{"schema_version":1}'); + expect((await f.execute("memories", { search: undefined })).output.target).toEqual({}); + }); + test("claim uses observed non-claim evidence; ambiguous claim/create keep target and actor without retry", async () => { + const f = fixture(); + f.output(JSON.stringify([{ ...row, assignee: "another-worker", status: "in_progress" }])); + expect((await f.execute("claim", { id: "task-1" })).output).toMatchObject({ status: "uncertain", target: "task-1", actor }); + f.output("{}"); + expect((await f.execute("create", { title: "new task", type: "task", priority: 2 })).output).toMatchObject({ status: "uncertain", target: { title: "new task" }, actor }); + f.bd.mockImplementation(async (_cwd, args) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads"}'; + throw new PrimeProcessError(1, 'Error updating task-1: issue already claimed\n' + JSON.stringify({ error: "1 of 1 issues failed to update", failed: [{ id: "task-1", error: 'updating issue: issue already claimed: already assigned to "fixture-owner" — coordinate with the holder' }], schema_version: 1 }), "bd update"); + }); + expect((await f.execute("claim", { id: "task-1" })).output).toMatchObject({ status: "not-claimed", target: "task-1", actor }); + expect(f.bd.mock.calls.filter(([,a]) => ["update", "create"].includes(a[0] ?? ""))).toHaveLength(3); + }); + test("started abort preserves recovery uncertainty and notification failure retains the original diagnostic", async () => { + const f = fixture(); const abort = new AbortController(); + f.dependencies.acknowledged = () => { throw new Error("notification failed"); }; + f.output("{}"); + expect((await f.execute("note", { id: "task-1", text: "evidence" })).output.detail).toContain("Unsupported CLI acknowledgement"); + expect((await f.execute("note", { id: "task-1", text: "evidence" })).output.detail).toContain("notification failed"); + const g = fixture(); + g.bd.mockImplementation(async (_cwd, args) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads"}'; + abort.abort(); throw abort.signal.reason; + }); + await expect(g.execute("note", { id: "task-1", text: "pending" }, invocation(abort.signal))).rejects.toHaveProperty("name", "AbortError"); + expect(g.acknowledged).toHaveBeenCalledWith("session-1", expect.objectContaining({ status: "uncertain", target: "task-1", actor })); + }); + test("rejects unsupported variants and ownership changes before permission", async () => { + const f = fixture(); + for (const [name, input] of [["find", { view: "search", text: "q", parent: "epic" }], ["find", { view: "ready", status: "open" }], ["update", { id: "task-1", set: { status: "closed" } }], ["create", { title: "a", type: "task", priority: 2, assignee: actor }], ["get", { ids: ["task-1", "task-1"] }]] as const) await expect(f.execute(name, input)).rejects.toThrow(); + expect(f.permission).not.toHaveBeenCalled(); + }); + test("derives stable audit attribution from actual session and executor, independently of Git identity", () => { + expect(executorActor(invocation())).toBe("opencode:restricted-worker:960defe988fd"); + expect(executorActor({ ...invocation(), id: "another-call" } as unknown as ToolContext)).toBe(executorActor(invocation())); + expect(executorActor({ ...invocation(), agent: "another-worker" } as unknown as ToolContext)).not.toBe(executorActor(invocation())); + expect(executorActor({ ...invocation(), sessionID: "another-session" } as unknown as ToolContext)).not.toBe(executorActor(invocation())); + expect(() => executorActor({ ...invocation(), agent: "" } as ToolContext)).toThrow("executor"); + }); + test("executes fixed checkpoint/show operations in actual project context with explicit write attribution", async () => { + const f = fixture(); + f.output(JSON.stringify([{ id: "task-1", notes: "previous\nfirst\nsecond" }])); + expect((await f.execute("note", { id: "task-1", text: "first\nsecond" })).output).toMatchObject({ status: "acknowledged", reference: "task-1" }); + expect(f.bd.mock.calls.at(-1)).toEqual(["/project", ["update", "task-1", "--append-notes", "first\nsecond", "--json", "--actor", "opencode:restricted-worker:960defe988fd", "--sandbox"], expect.anything()]); + expect(f.permission).toHaveBeenCalledWith(expect.objectContaining({ action: "shell", agent: "restricted-worker", sessionID: "session-1", save: [], source: { type: "tool", messageID: "message-1", id: "call-1" }, resources: [expect.stringMatching(/^bd update task-1 --append-notes /)] })); + expect((await f.execute("get", { ids: ["task-1"] })).output).toMatchObject({ status: "acknowledged", issues: { "task-1": { id: "task-1" } } }); + expect(f.bd.mock.calls.at(-1)).toEqual(["/project", ["show", "task-1", "--json", "--sandbox"], expect.anything()]); + expect(f.bd.mock.calls.filter(([, args]) => args[0] === "update")).toHaveLength(1); + expect(f.acknowledged).toHaveBeenCalledTimes(2); + }); + test("uses fixed multiline argv, actual invocation permission identity and explicit executor actor", async () => { + const f = fixture(); + expect(f.tools.map((tool) => tool.options?.permission)).toEqual(Array(11).fill("shell")); + const result = await f.execute("remember", { key: "lesson", text: "first\nsecond" }); + expect(f.bd.mock.calls.at(-1)).toEqual(["/project", ["remember", "--key", "lesson", "--json", "--actor", "opencode:restricted-worker:960defe988fd", "--sandbox", "--", "first\nsecond"], expect.anything()]); + expect(f.permission).toHaveBeenCalledWith(expect.objectContaining({ + action: "shell", agent: "restricted-worker", sessionID: "session-1", save: [], + source: { type: "tool", messageID: "message-1", id: "call-1" }, + resources: [expect.stringMatching(/^bd remember --key lesson /)], + })); + expect(result.output).toMatchObject({ status: "acknowledged", reference: "lesson" }); + expect(f.acknowledged).toHaveBeenCalledTimes(1); + }); + + test("rejects denied access, missing executor attribution and model supplied actor before mutation", async () => { + const f = fixture(); + await expect(f.execute("remember", { key: "lesson", text: "hello" }, { ...invocation(), agent: "" } as ToolContext)).rejects.toThrow("executor"); + await expect(f.execute("remember", { key: "lesson", text: "hello", actor: "human" })).rejects.toThrow("actor"); + const denial = new Error("permission rejected"); + f.permission.mockImplementation(async () => { throw denial; }); + await expect(f.execute("note", { id: "task-1", text: "hello" })).rejects.toBe(denial); + await expect(f.execute("get", { ids: ["task-1"] })).rejects.toBe(denial); + expect(f.bd.mock.calls.every(([, args]) => ["version", "context"].includes(args[0] ?? ""))).toBeTrue(); + }); + + test("rechecks cancellation and actual session location after approval", async () => { + const f = fixture(); + const abort = new AbortController(); + f.permission.mockImplementation(async () => { abort.abort(); }); + await expect(f.execute("note", { id: "task-1", text: "hello" }, invocation(abort.signal))).rejects.toHaveProperty("name", "AbortError"); + f.permission.mockImplementation(async () => { f.move("/elsewhere"); }); + await expect(f.execute("get", { ids: ["task-1"] })).rejects.toThrow("location"); + expect(f.bd.mock.calls.some(([, args]) => args[0] === "recall")).toBeFalse(); + }); + + test("preserves migration holds before and after shell approval", async () => { + const f = fixture(); + f.dependencies.readText = async () => "1.2.0"; + await expect(f.execute("note", { id: "task-1", text: "progress" })).rejects.toThrow("held"); + const afterApproval = fixture(); + let marker = "1.3.1"; + afterApproval.dependencies.readText = async () => marker; + afterApproval.permission.mockImplementation(async () => { marker = "1.2.0"; }); + await expect(afterApproval.execute("get", { ids: ["task-1"] })).rejects.toThrow("held"); + for (const candidate of [f, afterApproval]) { + expect(candidate.bd.mock.calls.some(([, args]) => ["update", "show"].includes(args[0] ?? ""))).toBeFalse(); + } + }); + + test("checks external access for the explicit resolved invocation directory, preserving denial and save:[]", async () => { + const f = fixture(); + const host = { + location: { directory: "/project", project: { directory: "/project" } }, + permission: { assert: f.permission }, + session: { get: async () => ({ location: { directory: "/project" } }) }, + } as unknown as ContinuityHost; + const tools = createContinuityTools(host, { ...f.dependencies, realpath: async (path) => path === "/project" ? "/external/work" : path }); + const show = tools.find((tool) => tool.name === "get")!; + f.output('[{"id":"task-1"}]'); + await show.execute({ ids: ["task-1"] }, invocation()); + expect(f.permission).toHaveBeenCalledWith(expect.objectContaining({ action: "external_directory", resources: ["/external/work/*"], save: [], agent: "restricted-worker" })); + f.permission.mockImplementation(async (input) => { + if ((input as { action: string }).action === "external_directory") throw new Error("external denied"); + }); + await expect(show.execute({ ids: ["task-1"] }, invocation())).rejects.toThrow("external denied"); + expect(f.bd.mock.calls.filter(([, args]) => args[0] === "show")).toHaveLength(1); + }); + + test("leaves inherited Beads routing and backend choice to the installed CLI", async () => { + const f = fixture(); + const previous = process.env.BEADS_DIR; + f.bd.mockImplementation(async (_cwd, args) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/shared/.beads","backend":"dolt","dolt_mode":"server","database":"team"}'; + return '[{"id":"task-1"}]'; + }); + try { + f.permission.mockImplementation(async () => { process.env.BEADS_DIR = "/shared/.beads"; }); + expect((await f.execute("get", { ids: ["task-1"] })).output).toMatchObject({ status: "acknowledged", issues: { "task-1": { id: "task-1" } } }); + expect(process.env.BEADS_DIR).toBe("/shared/.beads"); + expect(f.bd.mock.calls.at(-1)).toEqual(["/project", ["show", "task-1", "--json", "--sandbox"], expect.anything()]); + expect(f.permission.mock.calls.every(([input]) => (input as { action: string }).action === "shell")).toBeTrue(); + } finally { + if (previous === undefined) delete process.env.BEADS_DIR; + else process.env.BEADS_DIR = previous; + } + }); + + test("unload aborts a pending approval and a later approval cannot spawn", async () => { + const f = fixture(); + const abort = new AbortController(); + let approve: () => void = () => {}; + f.permission.mockImplementation(() => new Promise<void>((resolve) => { approve = resolve; })); + const pending = f.execute("recall", { key: "lesson" }, invocation(abort.signal)); + for (let i = 0; i < 20 && f.permission.mock.calls.length === 0; i++) await new Promise((resolve) => setTimeout(resolve, 0)); + abort.abort(); + await expect(pending).rejects.toHaveProperty("name", "AbortError"); + approve(); + await Promise.resolve(); + expect(f.bd.mock.calls.some(([, args]) => args[0] === "recall")).toBeFalse(); + }); + + test("keeps quoted multiline content as a single argv value and exposes no force or arbitrary execution fields", async () => { + const f = fixture(); + const text = "first\n'quoted' $(echo never) `never` --force"; + f.output(JSON.stringify({ key: "lesson", value: text, action: "remembered" })); + await f.execute("remember", { key: "lesson", text }); + const argv = f.bd.mock.calls.at(-1)?.[1]; + expect(argv).toEqual(["remember", "--key", "lesson", "--json", "--actor", "opencode:restricted-worker:960defe988fd", "--sandbox", "--", text]); + await expect(f.execute("remember", { key: "lesson", text: "hello", force: true })).rejects.toThrow("force"); + await expect(f.execute("recall", { key: "lesson", cwd: "/elsewhere" })).rejects.toThrow("cwd"); + f.output("{}"); + expect((await f.execute("memories", { search: "--force" })).output).toMatchObject({ status: "found", memories: {} }); + expect(f.bd.mock.calls.at(-1)?.[1]).toEqual(["memories", "--json", "--sandbox", "--", "--force"]); + }); + + test("treats exit zero without a matching acknowledgement as uncertain and never retries", async () => { + const f = fixture(); + f.output("{}"); + expect((await f.execute("note", { id: "task-1", text: "hello" })).output).toMatchObject({ status: "uncertain" }); + expect(f.bd.mock.calls.filter(([, args]) => args[0] === "update")).toHaveLength(1); + }); + + test("distinguishes landed but uncommitted memory and interrupted writes without retry", async () => { + const f = fixture(); + f.output(JSON.stringify({ key: "lesson", value: "validated finding", action: "remembered", committed: false, commit_error: "dirty keys", commit_indeterminate: true })); + expect((await f.execute("remember", { key: "lesson", text: "validated finding" })).output).toMatchObject({ status: "landed-uncommitted", reference: "lesson", commitIndeterminate: true }); + f.bd.mockImplementation(async (_cwd, args) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads","backend":"dolt","dolt_mode":"embedded","database":"beads"}'; + throw new PrimeTimeoutError(1); + }); + expect((await f.execute("note", { id: "task-1", text: "hello" })).output).toMatchObject({ status: "uncertain" }); + expect(f.bd.mock.calls.filter(([, args]) => args[0] === "update")).toHaveLength(1); + }); + + test("accepts versioned memory lists without treating schema metadata as memory", async () => { + for (const entries of [{}, { lesson: "first\nsecond" }]) { + const f = fixture(); + f.output(JSON.stringify({ ...entries, schema_version: 1 })); + const result = await f.execute("memories", {}); + expect(result.output).toMatchObject({ status: "found" }); + expect(result.output.memories).toEqual(entries); + } + }); + + test("rejects unsupported memory metadata and non-string memory values", async () => { + for (const payload of [ + { schema_version: 2, lesson: "text" }, + { schema_version: 1, lesson: 42 }, + { schema_version: 1, lesson: "text", other_metadata: 1 }, + ]) { + const f = fixture(); + f.output(JSON.stringify(payload)); + expect((await f.execute("memories", {})).output).toMatchObject({ status: "failed" }); + } + }); + + test("distinguishes empty found memory, missing-key exit and CLI failure", async () => { + const f = fixture(); + f.output('{"key":"lesson","value":"","found":true}'); + expect((await f.execute("recall", { key: "lesson" })).output).toMatchObject({ status: "found", value: "" }); + f.bd.mockImplementation(async (_cwd, args) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads","backend":"dolt","dolt_mode":"embedded","database":"beads"}'; + throw new PrimeProcessError(1, "", "bd recall", '{"key":"lesson","value":"","found":false}'); + }); + expect((await f.execute("recall", { key: "lesson" })).output).toMatchObject({ status: "missing" }); + f.bd.mockImplementation(async (_cwd, args) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads"}'; + throw new PrimeProcessError(1, "database unavailable", "bd recall"); + }); + expect((await f.execute("recall", { key: "lesson" })).output).toMatchObject({ status: "failed", detail: expect.stringContaining("database unavailable") }); + }); +}); diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 5ab19b3..3e9a7f5 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -13,6 +13,9 @@ const packageManifest = JSON.parse(await fs.readFile("package.json", "utf8")) as version: string; contributors?: Array<{ name?: string; url?: string }>; beads: { minimum: string; validated: string }; + peerDependencies: Record<string, string>; + peerDependenciesMeta: Record<string, { optional?: boolean }>; + devDependencies: Record<string, string>; }; const packageIdentity = `${packageManifest.name}@${packageManifest.version}`; @@ -67,7 +70,7 @@ describe("documentation contracts", () => { "bd init", "creates the canonical shared Beads skill", "`.agents/skills/beads`", - "This plugin has no separate skill lifecycle", + "The beta/next source candidate additionally owns its packaged `oc-beads` skill", "does not write startup files", "Update the pin explicitly when upgrading", "**Do not run `bd setup opencode`.**", @@ -123,23 +126,32 @@ describe("documentation contracts", () => { } }); - test("keeps the compatibility record on the supported OpenCode V2 baseline", () => { + test("keeps the compatibility record on the selected beta/next OpenCode baseline", () => { expect(sdkContract).toContain("# OpenCode V2 Plugin Contract"); - expect(sdkContract).toContain("`@opencode/plugin` `>=2.0.26 <3`"); + expect(packageManifest.peerDependencies["@opencode/plugin"]).toBe(">=2.1.1 <2.2"); + expect(packageManifest.peerDependenciesMeta["@opencode/plugin"]?.optional).toBeTrue(); + expect(packageManifest.devDependencies["@opencode/plugin"]).toBe("2.1.1"); + expect(sdkContract).toContain("`@opencode/plugin` `>=2.1.1 <2.2`"); + expect(sdkContract).toContain("`@opencode/plugin` `2.1.1` for development typing only"); expect(sdkContract).toContain("`<name>/server`, then `<name>`"); expect(sdkContract).toContain("`session.compaction.ended`"); + expect(sdkContract).toContain("The exact 2.1.1 runtime target has not been qualified"); expect(sdkContract).not.toContain("1.18."); - expect(readme).toContain("| OpenCode | 2.0.26 through 2.x | 2.0.26 |"); - expect(readme).toContain("| `bd` CLI | 1.3.0 through 1.x | 1.3.1 |"); + expect(readme).toContain( + "| OpenCode (beta/next candidate) | `>=2.1.1 <2.2` | None (exact 2.1.1 target not qualified) |" + ); + expect(readme).toContain("| OpenCode (stable 1.0.2 historical) | 2.0.26 | 2.0.26 |"); + expect(readme).toContain("| `bd` CLI | 1.3.1 through 1.x | 1.3.1 |"); expect(readme).not.toContain("@opencode-ai/"); }); test("keeps the Beads compatibility range in one place", () => { const { minimum, validated } = packageManifest.beads; + expect(minimum).toBe("1.3.1"); expect(minimum).toBe(MINIMUM_BD_VERSION); expect(readme).toContain(`| \`bd\` CLI | ${minimum} through 1.x | ${validated} |`); expect(readme).toContain(`reviewed against Beads v${validated}`); - expect(readme).toContain("`bd` 1.3.0 or later"); + expect(readme).toContain("`bd` 1.3.1 or later"); expect(readme).toContain("beads-contract"); expect(artifactPolicy).toContain("scripts/bd-contract.ts"); expect(artifactPolicy).toContain("REASSESS:"); @@ -147,7 +159,7 @@ describe("documentation contracts", () => { test("records Claude Code parity with live evidence and explicit divergences", () => { expect(readme).toContain("[Claude Code parity matrix](docs/claude-code-parity.md)"); - expect(parity).toContain("Validated on **OpenCode 2.0.26** with **bd 1.3.1**"); + expect(parity).toContain("validated on **OpenCode 2.0.26** with **bd 1.3.1**"); for (const source of ["`startup`", "`resume`", "`clear`", "`compact`"]) { expect(parity).toContain(`\`SessionStart\` (${source})`); } @@ -175,7 +187,7 @@ describe("documentation contracts", () => { test("documents upstream-aligned full-prime context boundaries", () => { expect(readme).toContain("runs `bd prime --full`"); expect(readme).not.toContain("runs `bd prime --memories-only`"); - expect(readme).toContain("bounded standalone quick reference"); + expect(readme).toContain("standalone guidance for authorized scope"); expect(sdkContract).toContain('Bun.spawn(["bd", "prime"]'); expect(artifactPolicy).toContain("Claude Code plugin manifests both run full `bd prime`"); expect(artifactPolicy).toContain( @@ -218,10 +230,12 @@ describe("documentation contracts", () => { expect(artifactPolicy).toContain("local repository commit `e51b8fe`"); }); - test("documents context-preserving atomic delegation and runtime provenance", () => { - expect(readme).toContain("context-preserving atomic delegation"); + test("documents scope-bounded task execution and runtime provenance", () => { + expect(readme).toContain("scope-bounded task execution"); expect(readme).not.toContain("Autonomous issue completion"); - expect(readme).toContain("exactly one Bead per invocation"); + expect(readme).toContain("An exact assignment or explicit one-bead mode stops with that item"); + expect(readme).toContain("already-authorized batch or goal may continue within scope"); + expect(readme).toContain("Discovery does not expand that authority"); expect(readme).toContain("primary OpenCode thread retains planning context, decisions, and control"); expect(readme).toContain("unknown custom primary agents"); expect(readme).toContain("Known regular subagents remain excluded"); diff --git a/tests/guard.test.ts b/tests/guard.test.ts index 87fb9cf..b7b2654 100644 --- a/tests/guard.test.ts +++ b/tests/guard.test.ts @@ -9,16 +9,17 @@ import { parseVersion, type GuardRuntime, } from "../src/guard"; +import { PrimeProcessError } from "../src/prime"; -const WHERE = JSON.stringify({ path: "/project/.beads", database_path: "/project/.beads/embeddeddolt" }); +const CONTEXT = JSON.stringify({ beads_dir: "/project/.beads" }); function runtime(options: { version?: string | Error; - where?: string | Error; + context?: string | Error; marker?: string | Error; }): GuardRuntime & { bd: ReturnType<typeof mock>; readText: ReturnType<typeof mock> } { const bd = mock(async (_directory: string, args: readonly string[]) => { - const value = args[0] === "version" ? options.version : options.where; + const value = args[0] === "version" ? options.version : options.context; if (value instanceof Error) throw value; return value ?? ""; }); @@ -40,22 +41,22 @@ describe("parseVersion", () => { describe("checkBeadsStore", () => { test("is ready when the store marker matches the installed binary", async () => { - const fake = runtime({ version: "bd version 1.3.1 (abc)", where: WHERE, marker: "1.3.1\n" }); + const fake = runtime({ version: "bd version 1.3.1 (abc)", context: CONTEXT, marker: "1.3.1\n" }); expect(await checkBeadsStore("/project", fake)).toEqual({ kind: "ready" }); expect(fake.bd).toHaveBeenCalledWith("/project", ["version"]); - expect(fake.bd).toHaveBeenCalledWith("/project", ["where", "--json"]); + expect(fake.bd).toHaveBeenCalledWith("/project", ["context", "--json"]); expect(fake.readText).toHaveBeenCalledWith(path.join("/project/.beads", ".local_version")); }); test("never invokes a command that opens the store", async () => { - const fake = runtime({ version: "bd version 1.3.1", where: WHERE, marker: "1.2.2" }); + const fake = runtime({ version: "bd version 1.3.1", context: CONTEXT, marker: "1.2.2" }); await checkBeadsStore("/project", fake); const commands = fake.bd.mock.calls.map((call) => (call[1] as string[])[0]); - expect(commands).toEqual(["version", "where"]); + expect(commands).toEqual(["version", "context"]); }); test("reports a pending migration for an older store marker", async () => { - const fake = runtime({ version: "bd version 1.3.1", where: WHERE, marker: "1.2.2" }); + const fake = runtime({ version: "bd version 1.3.1", context: CONTEXT, marker: "1.2.2" }); expect(await checkBeadsStore("/project", fake)).toEqual({ kind: "migration-pending", installed: "1.3.1", @@ -65,42 +66,79 @@ describe("checkBeadsStore", () => { test("treats a missing or unreadable marker as possibly pending", async () => { expect( - await checkBeadsStore("/p", runtime({ version: "bd version 1.3.1", where: WHERE })) + await checkBeadsStore("/p", runtime({ version: "bd version 1.3.1", context: CONTEXT })) ).toEqual({ kind: "migration-pending", installed: "1.3.1", store: undefined }); expect( await checkBeadsStore( "/p", - runtime({ version: "bd version 1.3.1", where: WHERE, marker: new Error("EACCES") }) + runtime({ version: "bd version 1.3.1", context: CONTEXT, marker: new Error("EACCES") }) ) ).toEqual({ kind: "migration-pending", installed: "1.3.1", store: undefined }); }); test("lets autoMigrate opt out of the marker check", async () => { - const fake = runtime({ version: "bd version 1.3.1", where: WHERE, marker: "1.2.2" }); + const fake = runtime({ version: "bd version 1.3.1", context: CONTEXT, marker: "1.2.2" }); expect(await checkBeadsStore("/project", fake, { autoMigrate: true })).toEqual({ kind: "ready" }); expect(fake.readText).not.toHaveBeenCalled(); }); - test("rejects binaries older than the supported minimum before locating the store", async () => { - const fake = runtime({ version: "bd version 1.2.2 (6c124203e)", where: WHERE, marker: "1.2.2" }); + test("rejects 1.3.0 before locating or opening the store, even with autoMigrate", async () => { + const fake = runtime({ version: "bd version 1.3.0 (f45b249ce)", context: CONTEXT, marker: "1.3.0" }); expect(await checkBeadsStore("/project", fake, { autoMigrate: true })).toEqual({ kind: "unsupported", - installed: "1.2.2", + installed: "1.3.0", }); - expect(fake.bd).toHaveBeenCalledTimes(1); + expect(fake.bd.mock.calls.map(call => call[1])).toEqual([["version"]]); + expect(fake.readText).not.toHaveBeenCalled(); }); test("treats a missing workspace as uninitialized", async () => { - const noWorkspace = JSON.stringify({ error: "no_beads_directory", schema_version: 1 }); + const noWorkspace = JSON.stringify({ beads_dir: "" }); expect( - await checkBeadsStore("/p", runtime({ version: "bd version 1.3.1", where: noWorkspace })) + await checkBeadsStore("/p", runtime({ version: "bd version 1.3.1", context: noWorkspace })) ).toEqual({ kind: "uninitialized" }); expect( - await checkBeadsStore("/p", runtime({ version: "bd version 1.3.1", where: new Error("exit 1") })) - ).toEqual({ kind: "uninitialized" }); + await checkBeadsStore("/p", runtime({ version: "bd version 1.3.1", context: new Error("exit 1") })) + ).toEqual({ kind: "probe-failed" }); expect( - await checkBeadsStore("/p", runtime({ version: "bd version 1.3.1", where: "not json" })) - ).toEqual({ kind: "uninitialized" }); + await checkBeadsStore("/p", runtime({ version: "bd version 1.3.1", context: "not json" })) + ).toEqual({ kind: "probe-failed" }); + }); + + test("recognizes the supported exit-1 no-workspace context response without reading a store", async () => { + const fake = runtime({ + version: "bd version 1.3.1", + context: new PrimeProcessError(1, "", "bd context --json", JSON.stringify({ + error: "cannot resolve repo context: no .beads directory found", schema_version: 1, + })), + }); + expect(await checkBeadsStore("/project", fake)).toEqual({ kind: "uninitialized" }); + expect(fake.bd.mock.calls.map((call) => call[1])).toEqual([["version"], ["context", "--json"]]); + expect(fake.readText).not.toHaveBeenCalled(); + }); + + test("keeps unrelated context errors, unsupported errors and interruption as probe failures", async () => { + const absent = { error: "cannot resolve repo context: no .beads directory found", schema_version: 1 }; + for (const error of [ + new PrimeProcessError(1, "", "bd context --json", JSON.stringify({ ...absent, error: "unsafe location" })), + new PrimeProcessError(2, "", "bd context --json", JSON.stringify(absent)), + new PrimeProcessError(1, "", "bd context --json", JSON.stringify({ ...absent, schema_version: 2 })), + new DOMException(absent.error, "AbortError"), + ]) { + const fake = runtime({ version: "bd version 1.3.1", context: error }); + expect(await checkBeadsStore("/project", fake)).toEqual({ kind: "probe-failed" }); + expect(fake.bd.mock.calls.map((call) => call[1])).toEqual([["version"], ["context", "--json"]]); + expect(fake.readText).not.toHaveBeenCalled(); + } + }); + + test("reports an unsupported no-open probe without falling back to where or opening a marker", async () => { + const fake = runtime({ version: "1.3.1", context: '{"path":"/legacy/where-result"}' }); + const result = await checkBeadsStore("/project", fake); + expect(result).toEqual({ kind: "probe-failed" }); + expect(fake.bd.mock.calls.map((call) => (call[1] as string[])[0])).toEqual(["version", "context"]); + expect(fake.readText).not.toHaveBeenCalled(); + expect(guardNotice(result)).toContain("Do not substitute"); }); test("treats a missing or unparsable bd as unavailable", async () => { @@ -138,9 +176,9 @@ describe("guardNotice", () => { }); test("names the minimum version for unsupported binaries", () => { - const notice = guardNotice({ kind: "unsupported", installed: "1.2.2" }); - expect(notice).toContain("bd 1.2.2"); - expect(notice).toContain("1.3.0"); + const notice = guardNotice({ kind: "unsupported", installed: "1.3.0" }); + expect(notice).toContain("bd 1.3.0"); + expect(notice).toContain("1.3.1, the minimum"); }); test("adds nothing for ready, uninitialized, or unavailable states", () => { diff --git a/tests/guidance.test.ts b/tests/guidance.test.ts index a3a10a4..01ba014 100644 --- a/tests/guidance.test.ts +++ b/tests/guidance.test.ts @@ -1,3 +1,4 @@ +import { readFileSync } from "node:fs"; import { describe, expect, test } from "bun:test"; import { beadsAgents } from "../src/agents"; import { @@ -9,41 +10,59 @@ import { const unsupportedPattern = /beads mcp|mcp server|via mcp|\bmcp\b|claude code|\/plugin update beads/i; describe("project-owned task-agent contract", () => { - test("describes the bounded one-bead worker, not upstream's autonomous loop", () => { - expect(BEADS_TASK_AGENT_DESCRIPTION).toContain("exactly one bead per invocation"); + test("describes read-only analysis and execution within an authorized scope", () => { + expect(BEADS_TASK_AGENT_DESCRIPTION).toContain("authorized task, batch or goal"); + expect(BEADS_TASK_AGENT_DESCRIPTION).toContain("read-only"); expect(BEADS_TASK_AGENT_DESCRIPTION).not.toMatch(/autonomous|finds and completes ready tasks/i); expect(BEADS_TASK_AGENT_DESCRIPTION).not.toMatch(unsupportedPattern); expect(beadsAgents()["beads-task-agent"]?.description).toBe(BEADS_TASK_AGENT_DESCRIPTION); }); - test("keeps the bounded workflow in a compact CLI-only prompt", () => { + test("keeps exact assignments bounded while allowing authorized continuation", () => { const prompt = BEADS_TASK_AGENT_PROMPT; expect(prompt).not.toMatch(unsupportedPattern); expect(prompt).toContain("Full `bd prime` workflow context is injected"); expect(prompt).toContain( "For status or graph analysis, never claim, update, close, or modify a bead. Return the analysis and stop." ); - expect(prompt).toContain("For completion, process exactly one bead."); - expect(prompt).toContain("Use a caller-supplied ID; otherwise choose one highest-priority ready bead."); - expect(prompt).toContain("confirm it is open, actionable, and appropriate; then claim"); - const showIndex = prompt.indexOf("`bd show <id> --json`"); - const claimIndex = prompt.indexOf("`bd update <id> --claim --json`"); + expect(prompt).toContain("An exact assignment or explicit one-bead mode ends with that bead"); + expect(prompt).toContain("already-authorized batch or goal"); + expect(prompt).toContain("Readiness is not dispatch permission"); + const showIndex = prompt.indexOf("`bd show <id> --json --sandbox`"); + const claimIndex = prompt.indexOf("`bd update <id> --claim --json --actor='<executor-actor>' --sandbox`"); expect(showIndex).toBeGreaterThanOrEqual(0); expect(claimIndex).toBeGreaterThan(showIndex); expect(prompt).toContain( "If an assigned bead is closed, blocked, unsuitable, missing, or otherwise not actionable, report and return without mutation or substitution." ); - expect(prompt).toContain("Create discoveries with `discovered-from:<current-id>`"); - expect(prompt).toContain("a parent only when structurally obvious"); - expect(prompt).toContain( - "Never claim or implement work created this invocation; it may be eligible later." - ); - expect(prompt).toContain( - "Close or block the current bead, report discoveries, and return without seeking more work." - ); + expect(prompt).toContain("claim fails or readback does not confirm your assignment, stop"); + expect(prompt).toContain("Discovery does not add execution authority"); + expect(prompt).toContain("discovered-from:<current-id>"); + expect(prompt).toContain("Append per-bead checkpoints"); + expect(prompt).toContain("unresolved acceptance or failed validation remains incomplete"); + expect(prompt).toContain("If the parent owns acceptance or closure, return evidence and leave the bead open"); + expect(prompt).toContain("permission denial, cancellation, deliberate stop"); expect(prompt).toContain("concise results, not raw JSON"); expect(prompt).not.toContain("Agent Delegation"); - expect(prompt.length).toBeLessThan(1_000); + expect(prompt).not.toContain("otherwise choose one highest-priority ready bead"); + }); + + test("carries executor attribution and separates planning/review acceptance", () => { + for (const text of [BEADS_TASK_AGENT_PROMPT, beadsGuidance("primary"), BEADS_TASK_AGENT_PROMPT + beadsGuidance("task-agent")]) { + expect(text).toContain("command-scoped"); + expect(text).toContain("heartbeat"); + expect(text).toContain("created_by"); + expect(text).toContain("owner"); + expect(text).toContain("assignee"); + expect(text).toContain("human Git identity"); + expect(text).toContain("deliberate handoff"); + + } + const planning = readFileSync(new URL("../assets/oc-beads/resources/WORKFLOWS.md", import.meta.url), "utf8"); + expect(planning).toContain("same review item"); + expect(planning).toContain("owner acceptance"); + expect(beadsGuidance("primary")).not.toContain("same review item"); + expect(beadsGuidance("task-agent")).not.toContain("## Executor and work ownership"); }); test("shares one guidance layer and adds delegation only for primary agents", () => { @@ -51,16 +70,20 @@ describe("project-owned task-agent contract", () => { const taskAgent = beadsGuidance("task-agent"); for (const guidance of [primary, taskAgent]) { expect(guidance).toMatch(/^<beads-guidance>\n[\s\S]*\n<\/beads-guidance>$/); - expect(guidance).toContain("Run `bd` through the `shell` tool"); + expect(guidance).toContain("Native shell is direct-only"); expect(guidance).toContain("Do not commit, push, or run Dolt synchronization automatically"); } expect(primary).toContain("through the `subagent` tool"); + expect(primary).toContain("normal blocking child return"); + expect(primary).toContain("NO_TEXT"); + expect(primary).toContain("actual executor actor"); + expect(primary).not.toMatch(/Delegate multi-command|only single atomic operations/); expect(taskAgent).not.toContain("## Delegation"); }); test("names OpenCode V2's `shell` tool, not V1's `bash`", () => { for (const text of [BEADS_TASK_AGENT_PROMPT, beadsGuidance("primary"), beadsGuidance("task-agent")]) { - expect(text).toContain("`shell` tool"); + expect(text).toContain("shell"); expect(text).not.toContain("`bash`"); } }); diff --git a/tests/host.test.ts b/tests/host.test.ts index 6dd4638..2be65f6 100644 --- a/tests/host.test.ts +++ b/tests/host.test.ts @@ -5,10 +5,16 @@ import path from "node:path"; import * as pluginModule from "../src/plugin"; import { renderBeadsContext } from "../src/context"; import { setupBeads, type HostContext } from "../src/host"; +import type { CommandDefinition, CommandInvocation } from "@opencode/plugin/promise/command"; +import type { ContinuityHost } from "../src/continuity"; +import { Error as ToolError, type Info, type ToolContext, type ToolDomain } from "@opencode/plugin/promise/tool"; +import type { SessionCompaction } from "@opencode/plugin/promise/session"; +import { executorActor } from "../src/continuity"; import { DEFAULT_TASK_AGENT_STEPS, TASK_AGENT_PERMISSIONS } from "../src/agents"; import { BEADS_COMPACTION_GUIDANCE, primeFailureNotice } from "../src/guidance"; -type ContextEvent = { sessionID: string; agent: string; system: Array<{ type: "text"; text: string }> }; +type ContextEvent = { tools?: Record<string, unknown>; sessionID: string; agent: string; system: Array<{ type: "text"; text: string }> }; +type ToolAfterEvent = Extract<Parameters<Parameters<ToolDomain["hook"]>[1]>[0], { status: string }>; /** A controllable server event stream. */ function eventStream() { @@ -43,7 +49,11 @@ async function settle(): Promise<void> { for (let index = 0; index < 5; index++) await new Promise((resolve) => setTimeout(resolve, 0)); } -function fakeContext(options: { directory?: string; pluginOptions?: Record<string, unknown> } = {}) { +function fakeContext(options: { + directory?: string; + pluginOptions?: Record<string, unknown>; + appVersion?: string; +} = {}) { const hooks = new Map<string, (event: ContextEvent) => Promise<void> | void>(); const events = eventStream(); const agentGet = mock(async ({ agentID }: { agentID: string }) => ({ @@ -63,6 +73,7 @@ function fakeContext(options: { directory?: string; pluginOptions?: Record<strin return { dispose: async () => {} }; }); const ctx = { + app: { name: "OpenCode", version: options.appVersion ?? "2.1.1", channel: "stable" }, location: { directory: options.directory ?? "/project" }, options: options.pluginOptions ?? {}, event: { subscribe: events.subscribe }, @@ -75,8 +86,8 @@ function fakeContext(options: { directory?: string; pluginOptions?: Record<strin }, } as unknown as HostContext; - async function request(sessionID: string, agent: string): Promise<string[]> { - const event: ContextEvent = { sessionID, agent, system: [{ type: "text", text: "HOST" }] }; + async function request(sessionID: string, agent: string, tools?: Record<string, unknown>): Promise<string[]> { + const event: ContextEvent = { sessionID, agent, ...(tools ? { tools } : {}), system: [{ type: "text", text: "HOST" }] }; const hook = hooks.get("context"); if (!hook) throw new Error("context hook not registered"); await hook(event); @@ -86,6 +97,30 @@ function fakeContext(options: { directory?: string; pluginOptions?: Record<strin return { ctx, hooks, events, agentGet, agents, request }; } +/** Capture public hook/registration callbacks, without simulating host child transport. */ +function toolFixture(f: ReturnType<typeof fakeContext>) { + const tools: Info[] = []; + const namespaces: unknown[] = []; + let after: (event: ToolAfterEvent) => Promise<void> | void = () => {}; + const hook: ToolDomain["hook"] = async (name, callback) => { + if (name === "execute.after") after = callback as typeof after; + return { dispose: async () => {} }; + }; + const transform: ToolDomain["transform"] = async (callback) => { + callback({ namespace: (value: unknown) => { namespaces.push(value); }, add: (tool: Info) => { tools.push(tool); } } as unknown as Parameters<typeof callback>[0]); + return { dispose: async () => {} }; + }; + Object.assign(f.ctx, { tool: { hook, transform } }); + return { tools, namespaces, after: (event: ToolAfterEvent) => after(event) }; +} + +/** Cast only host-branded IDs at the fixture boundary. */ +function invocation(sessionID: string, agent: string, signal = new AbortController().signal): ToolContext { + return { sessionID, agent, messageID: `message-${sessionID}`, id: `call-${sessionID}`, signal, + progress: async () => {}, + } as unknown as ToolContext; +} + describe("OpenCode V2 entry", () => { test("default-exports a V2 plugin definition and nothing else", () => { expect(Object.keys(pluginModule)).toEqual(["default"]); @@ -95,6 +130,256 @@ describe("OpenCode V2 entry", () => { }); describe("setupBeads", () => { + test("refreshes recovery state for external changes, isolates fork/move/audience and never restarts on interruption", async () => { + const f = fakeContext(); + let directory = "/project"; + let output = "FIRST"; + Object.assign(f.ctx.session, { get: async () => ({ location: { directory } }) }); + const prime = mock(async () => output); + const cleanup = await setupBeads(f.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime } }); + await f.request("s1", "build"); + await f.request("s10", "build"); + output = "EXTERNAL CHANGE"; + f.events.push({ type: "session.execution.started", data: { sessionID: "s1" } }); + await settle(); + const recovered = await f.request("s1", "build"); + expect(recovered[0]).toContain("EXTERNAL CHANGE"); + expect(recovered[1]).toContain("Cached absence is not proof of absence"); + expect((await f.request("s10", "build"))[0]).toContain("FIRST"); + expect((await f.request("fork", "build"))[0]).toContain("EXTERNAL CHANGE"); + const hidden: ContextEvent = { sessionID: "s1", agent: "explore", system: [] }; + await f.hooks.get("compaction")!(hidden); + expect(hidden.system).toEqual([]); + f.events.push({ type: "session.execution.interrupted", data: { sessionID: "s1" } }); + await settle(); + const count = prime.mock.calls.length; + await settle(); + expect(prime).toHaveBeenCalledTimes(count); + directory = "/elsewhere"; + f.events.push({ type: "session.moved", data: { sessionID: "s1" } }); + await settle(); + expect(await f.request("s1", "build")).toEqual([]); + expect(prime).toHaveBeenCalledTimes(count); + cleanup(); + }); + + test("normal adapter writes invalidate location caches once and compaction only carries acknowledged references/gaps", async () => { + const f = fakeContext(); + const tools: Info[] = []; + let after: (event: any) => unknown = () => {}; + const disposed = mock(async () => {}); + Object.assign(f.ctx, { tool: { hook: async (_name: string, callback: typeof after) => { after = callback; return { dispose: disposed }; } } }); + const host = { + location: { directory: "/project", project: { directory: "/project" } }, + permission: { assert: mock(async () => {}) }, + session: { get: async () => ({ location: { directory: "/project" } }) }, + tool: { transform: async (callback: (editor: { namespace(input: unknown): void; add(tool: Info): void }) => void) => { callback({ namespace: () => {}, add: (tool) => tools.push(tool) }); return { dispose: disposed }; } }, + } as unknown as ContinuityHost & { tool: { transform: any } }; + let output = "BEFORE"; + const prime = mock(async () => output); + const acknowledged = mock(() => {}); + const bd = mock(async (_cwd: string, args: readonly string[]) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads","backend":"dolt","dolt_mode":"embedded","database":"beads"}'; + return '{"key":"lesson","value":"validated finding","action":"remembered","committed":false,"commit_error":"not committed"}'; + }); + Object.assign(f.ctx, { location: host.location, permission: host.permission, tool: { ...f.ctx.tool, ...host.tool } }); + Object.assign(f.ctx.session, host.session); + const cleanup = await setupBeads(f.ctx, { + runtime: { guard: async () => ({ kind: "ready" }), prime }, + continuityQualified: true, continuity: { + bd, acknowledged, realpath: async (path) => path, + readText: async () => "1.3.1", + }, + }); + await f.request("s1", "build"); + await f.request("s2", "build"); + const tool = tools.find((tool) => tool.name === "remember")!; + const context = { sessionID: "s1", agent: "worker", messageID: "m1", id: "c1", signal: new AbortController().signal, progress: async () => {} } as unknown as ToolContext; + const result = await tool.execute({ key: "lesson", text: "validated finding" }, context); + await after({ ...context, tool: "beads_remember", status: "completed", result }); + await after({ ...context, tool: "execute", status: "completed", result }); + expect(acknowledged).toHaveBeenCalledTimes(1); + expect(bd.mock.calls.filter(([, args]) => args[0] === "remember")).toHaveLength(1); + output = "AFTER"; + expect((await f.request("s2", "build"))[0]).toContain("AFTER"); + const count = bd.mock.calls.length; + const event: ContextEvent = { sessionID: "s1", agent: "build", system: [] }; + await f.hooks.get("compaction")!(event); + expect(event.system[1]?.text).toContain("memory:lesson"); + expect(event.system[1]?.text).toContain("landed-uncommitted"); + expect(bd).toHaveBeenCalledTimes(count); + cleanup(); + expect(disposed).toHaveBeenCalledTimes(2); + }); + + test("retains unresolved write identities across partial reads, interruption and recovery", async () => { + const f = fakeContext(); const t = toolFixture(f); + const stopped = new AbortController(); + const writer = invocation("s1", "build", stopped.signal); + const otherWriter = invocation("s1", "worker"); + const bd = mock(async (_cwd: string, args: readonly string[]) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads"}'; + if (args[0] === "update" && args[3] === "started note") { + stopped.abort(new Error("fixture write interrupted")); + throw stopped.signal.reason; + } + if (args[0] === "update") return JSON.stringify([{ id: args[1], notes: args[3] }]); + if (args[0] === "remember") return JSON.stringify({ key: "lesson-uncommitted", value: "finding", action: "remembered", committed: false, commit_error: "fixture commit failed" }); + if (args[0] === "list") return JSON.stringify(["read-1", "read-2"].map(id => ({ id, title: id, status: "open", priority: 2, issue_type: "task" }))); + throw new Error("fixture read failed"); + }); + Object.assign(f.ctx, { location: { directory: "/project", project: { directory: "/project" } }, permission: { assert: async () => {} } }); + Object.assign(f.ctx.session, { get: async () => ({ location: { directory: "/project" } }) }); + const cleanup = await setupBeads(f.ctx, { + continuityQualified: true, runtime: { guard: async () => ({ kind: "ready" }), prime: async () => "PRIME" }, + continuity: { bd, realpath: async p => p, readText: async () => "1.3.1" }, + }); + try { + await f.request("s1", "build"); + await expect(t.tools.find(tool => tool.name === "note")!.execute({ id: "task-first", text: "started note" }, writer)).rejects.toThrow("fixture write interrupted"); + expect((await t.tools.find(tool => tool.name === "remember")!.execute({ key: "lesson-uncommitted", text: "finding" }, otherWriter)).output.status).toBe("landed-uncommitted"); + const read = invocation("s1", "build"); + expect((await t.tools.find(tool => tool.name === "find")!.execute({ view: "list", limit: 1 }, read)).output).toMatchObject({ status: "acknowledged", complete: false }); + expect((await t.tools.find(tool => tool.name === "get")!.execute({ ids: ["unavailable-task"] }, read)).output.status).toBe("failed"); + // Even a later acknowledged write to the same target does not resolve the earlier append. + expect((await t.tools.find(tool => tool.name === "note")!.execute({ id: "task-first", text: "later note" }, read)).output.status).toBe("acknowledged"); + f.events.push({ type: "session.execution.interrupted", data: { sessionID: "s1" } }); + await settle(); + const summary: ContextEvent = { sessionID: "s1", agent: "build", system: [] }; + const beforeCompaction = bd.mock.calls.length; + await f.hooks.get("compaction")!(summary); + expect(bd).toHaveBeenCalledTimes(beforeCompaction); + const gap = JSON.parse(summary.system[1]!.text.replace(/^<beads-references>|<\/beads-references>$/g, "")).gap; + const expected = [ + `note "task-first" actor=${executorActor(writer)}: uncertain; fixture write interrupted`, + `remember "lesson-uncommitted" actor=${executorActor(otherWriter)}: landed-uncommitted`, + ]; + const recovery = (await f.request("s1", "build")).join("\n"); + const later = (await f.request("s1", "build")).join("\n"); + for (const detail of expected) { + expect(gap).toContain(detail); + expect(recovery).toContain(detail); + expect(later).toContain(detail); + } + expect(gap).toContain("no final checkpoint is guaranteed"); + expect(bd.mock.calls.filter(([, args]) => args[0] === "update")).toHaveLength(2); + expect(bd.mock.calls.filter(([, args]) => args[0] === "remember")).toHaveLength(1); + expect((await f.request("other-session", "build")).join("\n")).not.toContain("task-first"); + f.events.push({ type: "session.moved", data: { sessionID: "s1" } }); + await settle(); + expect((await f.request("s1", "build")).join("\n")).not.toContain("lesson-uncommitted"); + } finally { + await cleanup(); + } + }); + + test("renders actual filtered request capability and actor, preserving guard holds", async () => { + const f = fakeContext(); const t = toolFixture(f); + let guard: any = { kind: "ready" }; + const prime = mock(async () => "FULL PRIME"); + Object.assign(f.ctx, { permission: { assert: async () => {} } }); + Object.assign(f.ctx.session, { get: async () => ({ location: { directory: "/project" }, agent: "session-default" }) }); + const cleanup = await setupBeads(f.ctx, { continuityQualified: true, runtime: { guard: async () => guard, prime } }); + expect(t.namespaces).toEqual([{ name: "beads", description: expect.any(String) }]); + expect(t.tools).toHaveLength(11); + expect(t.tools.every(tool => tool.options?.namespace === "beads" && tool.options.codemode === true)).toBeTrue(); + const typed = (await f.request("actual", "build", { execute: {}, shell: {} }))[0]!; + expect(typed).toContain(executorActor({ sessionID: "actual", agent: "build" })); + expect(typed).toContain("Beads namespace is registered and execute is visible"); + const shell = (await f.request("actual", "review", { shell: {} }))[0]!; + expect(shell).toContain(executorActor({ sessionID: "actual", agent: "review" })); + expect(shell).not.toContain(executorActor({ sessionID: "actual", agent: "build" })); + expect(shell).toContain("direct shell tool"); + expect((await f.request("actual", "review", {}))[0]).toContain("Neither"); + expect(prime).toHaveBeenCalledTimes(1); + guard = { kind: "migration-pending", installed: "1.3.1", store: "1.2.0" }; + f.events.push({ type: "session.compaction.ended", data: { sessionID: "actual" } }); await settle(); + const held = (await f.request("actual", "build", { execute: {}, shell: {} }))[0]!; + expect(held).not.toContain("Beads namespace is registered"); + expect(held).not.toContain("direct shell tool"); + await cleanup(); + }); + test("acknowledged task writes retain task references without a redundant cue; unrelated errors do not refresh prime", async () => { + const f = fakeContext(); const t = toolFixture(f); + const prime = mock(async () => "PRIME"); + Object.assign(f.ctx, { location: { directory: "/project", project: { directory: "/project" } }, permission: { assert: async () => {} } }); + Object.assign(f.ctx.session, { get: async () => ({ location: { directory: "/project" } }) }); + const cleanup = await setupBeads(f.ctx, { continuityQualified: true, runtime: { guard: async () => ({ kind: "ready" }), prime }, continuity: { realpath: async p => p, readText: async () => "1.3.1", bd: async (_cwd, args) => args[0] === "version" ? "1.3.1" : args[0] === "context" ? '{"beads_dir":"/project/.beads"}' : '[{"id":"task-1","notes":"evidence"}]' } }); + await f.request("s1", "build"); + await t.after({ ...invocation("s1", "build"), tool: "shell", input: {}, status: "error", error: new ToolError({ message: "test failed" }) }); + expect(await f.request("s1", "build")).toHaveLength(1); + expect(prime).toHaveBeenCalledTimes(1); + await t.tools.find(tool => tool.name === "note")!.execute({ id: "task-1", text: "evidence" }, invocation("s1", "build")); + expect(await f.request("s1", "build")).toHaveLength(1); + expect(prime).toHaveBeenCalledTimes(2); + const event: ContextEvent = { sessionID: "s1", agent: "build", system: [] }; + await f.hooks.get("compaction")!(event); + expect(event.system[1]?.text).toContain("task:task-1"); + expect(event.system[1]?.text).not.toContain("memory:task-1"); + await cleanup(); + }); + + test("ordinary setup withholds privileged continuity registration", async () => { + const f = fakeContext(); + const transform = mock(async () => ({ dispose: async () => {} })); + Object.assign(f.ctx, { tool: { transform, hook: mock(async () => ({ dispose: async () => {} })) } }); + const cleanup = await setupBeads(f.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime: async () => "PRIME" } }); + expect(transform).not.toHaveBeenCalled(); + cleanup(); + }); + + test("compaction preserves native replay, keepID, legacy recent and model boundaries without I/O", async () => { + const f = fakeContext(); + const prime = mock(async () => "PRIME"); + const cleanup = await setupBeads(f.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime } }); + await f.request("s1", "build"); + const hook = f.hooks.get("compaction")!; + // Representative public payloads, not a provider/history implementation or cross-product. + const cases = [ + { model: { providerID: "provider-a", id: "model-a" } }, + { model: { providerID: "provider-a", id: "model-a" }, result: { summary: "host summary", metadata: { keepID: "fork-remapped-id" } } }, + { model: { providerID: "provider-b", id: "model-b" }, result: { summary: "host fallback", metadata: { recent: "legacy retained tail" }, native: { messages: [{ opaque: true }] } } }, + ]; + for (const fields of cases) { + const event = { sessionID: "s1", agent: "build", system: [], messages: [], options: {}, tools: {}, ...fields } as unknown as SessionCompaction; + const before = JSON.stringify(event); + await hook(event); + const { system, ...preserved } = event; + expect(system).toEqual([{ type: "text", text: BEADS_COMPACTION_GUIDANCE }]); + expect({ ...preserved, system: [] }).toEqual({ ...JSON.parse(before), system: [] }); + expect(event.result).toBe(fields.result); + } + expect(prime).toHaveBeenCalledTimes(1); + cleanup(); + }); + test("reports and skips setup for an unsupported OpenCode host", async () => { + for (const appVersion of ["2.0.29", "2.1.0", "2.1.1-beta.1", "2.2.0", "unknown"]) { + const f = fakeContext({ appVersion }); + const warn = mock(() => {}); + + const cleanup = await setupBeads(f.ctx, { warn }); + + expect(warn).toHaveBeenCalledWith(expect.stringContaining(`OpenCode ${appVersion}`)); + expect(warn).toHaveBeenCalledWith(expect.stringContaining(">=2.1.1 <2.2")); + expect(f.agents.size).toBe(0); + expect(f.hooks.size).toBe(0); + cleanup(); + } + }); + + test("admits candidate patch hosts within the selected 2.1 line", async () => { + const f = fakeContext({ appVersion: "2.1.8" }); + const cleanup = await setupBeads(f.ctx, { + runtime: { guard: async () => ({ kind: "ready" as const }), prime: async () => "PRIME" }, + }); + + expect([...f.hooks.keys()]).toEqual(["context", "compaction"]); + expect([...f.agents.keys()]).toEqual(["beads-task-agent"]); + cleanup(); + }); + test("registers the bounded task agent as a subagent and keeps Luna opt-in", async () => { const ready = { runtime: { guard: async () => ({ kind: "ready" as const }), prime: async () => "PRIME" } }; const vanilla = fakeContext(); @@ -104,8 +389,8 @@ describe("setupBeads", () => { expect(agent?.mode).toBe("subagent"); expect(agent?.name).toBe("beads-task-agent"); expect(agent?.model).toBeUndefined(); - expect(String(agent?.system)).toContain("process exactly one bead"); - expect(String(agent?.system)).toContain("bd update <id> --claim --json"); + expect(String(agent?.system)).toContain("An exact assignment or explicit one-bead mode ends with that bead"); + expect(String(agent?.system)).toContain("bd update <id> --claim --json --actor='<executor-actor>' --sandbox"); expect(typeof agent?.description).toBe("string"); expect(agent?.permissions).toEqual([...TASK_AGENT_PERMISSIONS]); expect(agent?.steps).toBe(DEFAULT_TASK_AGENT_STEPS); @@ -127,18 +412,18 @@ describe("setupBeads", () => { const cleanup = await setupBeads(f.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime } }); expect([...f.hooks.keys()]).toEqual(["context", "compaction"]); - expect(await f.request("s1", "build")).toEqual([renderBeadsContext("PRIME", "primary")]); - expect(await f.request("s1", "build")).toEqual([renderBeadsContext("PRIME", "primary")]); + expect(await f.request("s1", "build")).toEqual([renderBeadsContext("PRIME", "primary", { sessionID: "s1", agent: "build" })]); + expect(await f.request("s1", "build")).toEqual([renderBeadsContext("PRIME", "primary", { sessionID: "s1", agent: "build" })]); expect(await f.request("s1", "explore")).toEqual([]); expect(prime).toHaveBeenCalledTimes(1); - expect(prime).toHaveBeenCalledWith("/project"); + expect(prime).toHaveBeenCalledWith("/project", expect.any(AbortSignal)); expect(f.agentGet).toHaveBeenCalledWith({ agentID: "build" }); cleanup(); }); test("minimizes bd's full AGENTS.md block only when prime is attached", async () => { const block = - "<!-- BEGIN BEADS INTEGRATION v:1 profile:full hash:1 -->\nfull reference\n<!-- END BEADS INTEGRATION -->"; + "<!-- BEGIN BEADS INTEGRATION v:1 profile:full hash:1 -->\nPRIME\n<!-- END BEADS INTEGRATION -->"; let guard: { kind: "ready" } | { kind: "migration-pending"; installed: string; store: string } = { kind: "ready", }; @@ -153,8 +438,8 @@ describe("setupBeads", () => { const withPrime: ContextEvent = { sessionID: "s1", agent: "build", system: [{ type: "text", text: block }] }; await hook(withPrime); expect(withPrime.system[0]?.text).toContain("provided by the opencode-beads plugin"); - expect(withPrime.system[0]?.text).not.toContain("full reference"); - expect(withPrime.system[1]?.text).toBe(renderBeadsContext("PRIME", "primary")); + expect(withPrime.system[0]?.text).not.toContain("BEGIN BEADS"); + expect(withPrime.system[1]?.text).toBe(renderBeadsContext("PRIME", "primary", { sessionID: "s1", agent: "build" })); const subagent: ContextEvent = { sessionID: "s1", agent: "explore", system: [{ type: "text", text: block }] }; await hook(subagent); @@ -184,7 +469,7 @@ describe("setupBeads", () => { await hook(event); expect(event.system).toEqual([ { type: "text", text: instructions }, - { type: "text", text: renderBeadsContext("PRIME", "primary") }, + { type: "text", text: renderBeadsContext("PRIME", "primary", { sessionID: "s1", agent: "build" }) }, ]); cleanup(); }); @@ -297,7 +582,7 @@ describe("setupBeads", () => { try { await writeFile(path.join(directory, ".local_version"), "1.2.2\n"); const bd = mock(async (_cwd: string, args: readonly string[]) => - args[0] === "version" ? "bd version 1.3.1 (abc)" : JSON.stringify({ path: directory }) + args[0] === "version" ? "bd version 1.3.1 (abc)" : JSON.stringify({ beads_dir: directory }) ); const prime = mock(async (_directory: string) => "PRIME"); @@ -311,8 +596,8 @@ describe("setupBeads", () => { const optedIn = fakeContext({ directory, pluginOptions: { autoMigrate: true } }); const cleanupOptedIn = await setupBeads(optedIn.ctx, { bd, runtime: { prime } }); - expect(await optedIn.request("s1", "build")).toEqual([renderBeadsContext("PRIME", "primary")]); - expect(prime).toHaveBeenCalledWith(directory); + expect(await optedIn.request("s1", "build")).toEqual([renderBeadsContext("PRIME", "primary", { sessionID: "s1", agent: "build" })]); + expect(prime).toHaveBeenCalledWith(directory, expect.any(AbortSignal)); cleanupOptedIn(); } finally { await rm(directory, { recursive: true, force: true }); @@ -350,3 +635,510 @@ describe("setupBeads", () => { cleanup(); }); }); + +/** Replay only the public add callbacks in native package/config order. */ +function commandFixture(f: ReturnType<typeof fakeContext>) { + type Editor = { add(definition: CommandDefinition): void }; + const callbacks: Array<(editor: Editor) => void> = []; + const initial: CommandDefinition = { name: "oc-beads:ready", description: "EARLIER PLUGIN", execute: async () => {} }; + let lookupFailed = false; + let omitted: string | undefined; + const commands = () => { + const result = new Map([[initial.name, initial]]); + for (const callback of callbacks) callback({ add: (definition) => result.set(definition.name, definition) }); + return result; + }; + const transform = mock(async (callback: (editor: Editor) => void) => { + callbacks.push(callback); + commands(); + return { dispose: async () => { callbacks.splice(callbacks.indexOf(callback), 1); } }; + }); + const prompt = mock(async (_input: Record<string, unknown>) => ({})); + Object.assign(f.ctx, { command: { + transform, + list: async () => { + if (lookupFailed) throw new Error("lookup failed"); + return { data: [...commands().values()].filter((entry) => entry.name !== omitted).map(({ name, description }) => ({ name, description })) }; + }, + } }); + Object.assign(f.ctx.session, { prompt, get: async () => ({ agent: "build", location: { directory: "/project" } }) }); + const invocation = { sessionID: "s1", delivery: "queue", prompt: { + text: "literal '$ARGUMENTS' $(touch nope) `text`", files: [{ uri: "file:///tmp/a%20b" }], + agents: [{ name: "build" }], skills: [{ id: "oc-beads" }], + } } as unknown as CommandInvocation; + return { callbacks, commands, transform, prompt, invocation, + failLookup: (failed: boolean) => { lookupFailed = failed; }, + omit: (name?: string) => { omitted = name; }, + }; +} + +describe("child handoff plugin boundary (not live host qualification)", () => { + test.each(["failed", "cancelled", "NO_TEXT", "partial"] as const)("preserves %s child outcome and adds only a next-request recovery cue", async (outcome) => { + const f = fakeContext(); + const t = toolFixture(f); + const prompt = mock(async () => ({})); + const synthetic = mock(async () => ({})); + Object.assign(f.ctx.session, { prompt, synthetic, get: async () => ({ agent: "human-default", location: { directory: "/project" } }) }); + const prime = mock(async () => "CURRENT"); + const cleanup = await setupBeads(f.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime } }); + await f.request("parent", "build"); + await f.request("child", "beads-task-agent"); + const parent = invocation("parent", "build"); + const child = invocation("child", "beads-task-agent"); + const handoff = `Task fixture-1 only; actor ${executorActor(child)}; session child; tried A; failed validation; because B; reference task:fixture-1; candidate fixture-rev; blocker acceptance; next authorized action C; partial.`; + const input = { agent: "beads-task-agent", description: "bounded fixture", prompt: handoff }; + const base = { tool: "subagent", sessionID: parent.sessionID, agent: parent.agent, messageID: parent.messageID, id: parent.id, input }; + // Pinned subagent.ts:245–268: failure/cancellation are Tool.Error; NO_TEXT is + // a completed tool result. There is no execute.after status="cancelled". + const event: ToolAfterEvent = outcome === "failed" || outcome === "cancelled" + ? { ...base, status: "error", error: new ToolError({ message: outcome === "failed" ? "Subagent failed (sessionID: child): fixture failure" : "Subagent cancelled (sessionID: child)" }) } + : { ...base, status: "completed", result: { + output: { sessionID: "child", status: "completed", output: outcome === "NO_TEXT" ? "Subagent completed without a text response." : handoff }, + content: `<subagent sessionID="child" state="completed">\n${outcome === "NO_TEXT" ? "Subagent completed without a text response." : handoff}\n</subagent>`, + metadata: { sessionID: "child", status: "completed" }, + } }; + const before = JSON.stringify(event); + await t.after(event); + expect(JSON.stringify(event)).toBe(before); + expect(event.input).toBe(input); + expect(prime).toHaveBeenCalledTimes(2); + expect(prompt).not.toHaveBeenCalled(); + expect(synthetic).not.toHaveBeenCalled(); + const recovered = await f.request("parent", "build"); + expect(prime).toHaveBeenCalledTimes(3); + expect(recovered[1]).toContain("Reconcile its scope, attempts, validation, durable references and incomplete work"); + // Child context is isolated; a parent-side result does not change its actor/cache. + expect(await f.request("child", "beads-task-agent")).toHaveLength(1); + expect(prime).toHaveBeenCalledTimes(3); + await cleanup(); + }); + + test("a late child return and execution success after deliberate stop do not restart or imply a final checkpoint", async () => { + const f = fakeContext(); + const t = toolFixture(f); + const prompt = mock(async () => ({})); + const synthetic = mock(async () => ({})); + Object.assign(f.ctx.session, { prompt, synthetic }); + const prime = mock(async () => "CURRENT"); + const cleanup = await setupBeads(f.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime } }); + await f.request("parent", "build"); + f.events.push({ type: "session.execution.interrupted", data: { sessionID: "parent", reason: "user" } }); + await settle(); + const context = invocation("parent", "build"); + const event: ToolAfterEvent = { tool: "subagent", sessionID: context.sessionID, agent: context.agent, messageID: context.messageID, id: context.id, + input: { agent: "beads-task-agent", prompt: "exact fixture-1" }, status: "completed", + result: { output: { sessionID: "child", status: "completed", output: "late partial evidence" }, metadata: { sessionID: "child", status: "completed" } }, + }; + const before = JSON.stringify(event); + await t.after(event); + f.events.push({ type: "session.execution.succeeded", data: { sessionID: "parent" } }); + await settle(); + expect(JSON.stringify(event)).toBe(before); + expect(prime).toHaveBeenCalledTimes(1); + expect(prompt).not.toHaveBeenCalled(); + expect(synthetic).not.toHaveBeenCalled(); + const summary: ContextEvent = { sessionID: "parent", agent: "build", system: [] }; + await f.hooks.get("compaction")!(summary); + expect(summary.system[1]?.text).toContain("no final checkpoint"); + expect(prime).toHaveBeenCalledTimes(1); + // Only a caller-initiated request asks for fresh recovery; the test does not run a model. + expect((await f.request("parent", "build"))[1]).toContain("incomplete work"); + await cleanup(); + const calls = prime.mock.calls.length; + await t.after(event); + expect(await f.request("parent", "build")).toEqual([]); + expect(prime).toHaveBeenCalledTimes(calls); + }); + + test("preserves parent/child checkpoint actors and partial handoff on fresh-session/fork recovery", async () => { + const f = fakeContext(); + const t = toolFixture(f); + const permission = mock(async (_input: unknown) => {}); + Object.assign(f.ctx, { permission: { assert: permission }, location: { directory: "/project", project: { directory: "/project" } } }); + Object.assign(f.ctx.session, { get: async () => ({ agent: "human-default", location: { directory: "/project" } }) }); + const child = invocation("child-full-session", "beads-task-agent"); + const parent = invocation("parent-full-session", "build"); + const notes = [`Earlier attempt failed; rationale retained.`]; + const handoff = `Scope fixture-1; executor ${executorActor(child)}; full session ${child.sessionID}; tried alternative; result partial; validation unit pass/live unexecuted; because source only; reference task:fixture-1; candidate fixture-rev; blocker live gate; next authorized action parent review.`; + const bd = mock(async (_cwd: string, args: readonly string[]) => { + if (args[0] === "version") return "1.3.1"; + if (args[0] === "context") return '{"beads_dir":"/project/.beads"}'; + if (args[0] === "update") notes.push(String(args[args.indexOf("--append-notes") + 1])); + return JSON.stringify([{ id: "fixture-1", created_by: "unknown", owner: "account-owner", assignee: executorActor(child), notes: notes.join("\n") }]); + }); + const prime = mock(async () => "CURRENT"); + const cleanup = await setupBeads(f.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime }, continuityQualified: true, + continuity: { bd, realpath: async (value) => value, readText: async () => "1.3.1" }, + }); + await f.request(String(child.sessionID), String(child.agent)); + await f.request(String(parent.sessionID), String(parent.agent)); + const checkpoint = t.tools.find((tool) => tool.name === "note")!; + await checkpoint.execute({ id: "fixture-1", text: handoff }, child); + await checkpoint.execute({ id: "fixture-1", text: "Parent review only; no ownership transfer or closure." }, parent); + const writes = bd.mock.calls.filter(([, args]) => args[0] === "update"); + expect(writes.map(([, args]) => args[args.indexOf("--actor") + 1])).toEqual([executorActor(child), executorActor(parent)]); + expect(writes[0]?.[1]).toContain(handoff); + expect(permission.mock.calls.map(([input]) => (input as { agent: string }).agent)).toEqual([child.agent, parent.agent]); + const compact = async (sessionID: string, agent: string) => { + const event: ContextEvent = { sessionID, agent, system: [] }; + await f.hooks.get("compaction")!(event); + return event.system; + }; + expect((await compact(String(parent.sessionID), String(parent.agent)))[1]?.text).toContain("task:fixture-1"); + f.events.push({ type: "session.forked", data: { sessionID: "fork", parentID: parent.sessionID, boundary: { type: "through", messageID: parent.messageID } } }); + await settle(); + await f.request("fork", "build"); + expect(await compact("fork", "build")).toHaveLength(1); + expect((await compact(String(parent.sessionID), String(parent.agent)))[1]?.text).toContain("task:fixture-1"); + await cleanup(); + const fresh = fakeContext(); + const recoveredTools = toolFixture(fresh); + Object.assign(fresh.ctx, { location: f.ctx.location, permission: f.ctx.permission }); + Object.assign(fresh.ctx.session, { get: f.ctx.session.get }); + const freshCleanup = await setupBeads(fresh.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime }, continuityQualified: true, + continuity: { bd, realpath: async (value) => value, readText: async () => "1.3.1" }, + }); + const recovery = await fresh.request("fresh", "build"); + expect(recovery[0]).toContain("retrieve the assigned task and relevant reusable knowledge"); + const result = await recoveredTools.tools.find((tool) => tool.name === "get")!.execute({ ids: ["fixture-1"] }, invocation("fresh", "build")); + expect(JSON.stringify(result.output)).toContain(handoff); + expect(JSON.stringify(result.output)).toContain("Earlier attempt failed"); + expect(JSON.stringify(result.output)).toContain('"created_by":"unknown"'); + expect(JSON.stringify(result.output)).toContain('"owner":"account-owner"'); + expect(JSON.stringify(result.output)).toContain(`"assignee":"${executorActor(child)}"`); + expect(bd.mock.calls.filter(([, args]) => args[0] === "update")).toHaveLength(2); + await freshCleanup(); + }); + + test("a permission wait neither writes nor checkpoints during compaction; stop rejects a later approval", async () => { + const f = fakeContext(); + const t = toolFixture(f); + let release: () => void = () => {}; + const permission = mock(async (_input: unknown) => new Promise<void>((resolve) => { release = resolve; })); + const prompt = mock(async () => ({})); + const synthetic = mock(async () => ({})); + Object.assign(f.ctx, { permission: { assert: permission }, location: { directory: "/project", project: { directory: "/project" } } }); + Object.assign(f.ctx.session, { prompt, synthetic, get: async () => ({ agent: "human-default", location: { directory: "/project" } }) }); + const bd = mock(async (_cwd: string, args: readonly string[]) => args[0] === "version" ? "1.3.1" : '{"beads_dir":"/project/.beads"}'); + const cleanup = await setupBeads(f.ctx, { runtime: { guard: async () => ({ kind: "ready" }), prime: async () => "CURRENT" }, continuityQualified: true, + continuity: { bd, realpath: async (value) => value, readText: async () => "1.3.1" }, + }); + await f.request("child", "beads-task-agent"); + const abort = new AbortController(); + const context = invocation("child", "beads-task-agent", abort.signal); + const pending = t.tools.find((tool) => tool.name === "note")!.execute({ id: "fixture-1", text: "unsaved partial result" }, context); + const outcome = pending.then(() => undefined, (error: unknown) => error); + await settle(); + expect(permission).toHaveBeenCalledWith(expect.objectContaining({ agent: context.agent, sessionID: context.sessionID, save: [], source: { type: "tool", messageID: context.messageID, id: context.id } })); + const count = bd.mock.calls.length; + const summary: ContextEvent = { sessionID: "child", agent: "beads-task-agent", system: [] }; + await f.hooks.get("compaction")!(summary); + expect(summary.system).toEqual([{ type: "text", text: BEADS_COMPACTION_GUIDANCE }]); + expect(bd).toHaveBeenCalledTimes(count); + expect(permission).toHaveBeenCalledTimes(1); + abort.abort(); + expect(await outcome).toHaveProperty("name", "AbortError"); + release(); + await settle(); + expect(bd).toHaveBeenCalledTimes(count); + expect(bd.mock.calls.every(([, args]) => ["version", "context"].includes(args[0] ?? ""))).toBe(true); + expect(prompt).not.toHaveBeenCalled(); + expect(synthetic).not.toHaveBeenCalled(); + await cleanup(); + }); +}); + +describe("owned workflow registration", () => { + test("registers all commands despite initial collisions; config wins on rebuild, removal restores and disposal preserves config", async () => { + const f = fakeContext(); + const c = commandFixture(f); + const warns: string[] = []; + const cleanup = await setupBeads(f.ctx, { warn: (message) => warns.push(message) }); + expect(c.transform).toHaveBeenCalledTimes(1); + expect(c.commands().size).toBe(30); + expect(warns.join("\n")).toContain("command-collision"); + expect(warns.join("\n")).toContain("native precedence"); + const packageCallback = c.callbacks[0]!; + const ready = c.commands().get("oc-beads:ready")!; + await ready.execute(c.invocation); + const sent = c.prompt.mock.calls.at(-1)![0]; + expect(sent.text).toContain(c.invocation.prompt.text); + expect(sent.files).toBe(c.invocation.prompt.files); + expect(sent.agents).toBe(c.invocation.prompt.agents); + expect(sent.skills).toBe(c.invocation.prompt.skills); + expect(sent.delivery).toBe(c.invocation.delivery); + const user = { name: ready.name, description: "USER CONFIG", execute: mock(async () => {}) }; + const config = (editor: { add(definition: CommandDefinition): void }) => editor.add(user); + c.callbacks.push(config); + expect(c.commands().get(ready.name)).toBe(user); + await c.commands().get(ready.name)!.execute(c.invocation); + expect(user.execute).toHaveBeenCalledTimes(1); + c.callbacks.splice(c.callbacks.indexOf(config), 1); + expect(c.commands().get(ready.name)).toBe(ready); + c.callbacks.push(config); + await cleanup(); + expect(c.callbacks).toEqual([config]); + expect(c.commands().get(ready.name)).toBe(user); + expect(c.commands().has("oc-beads:status")).toBe(false); + const add = mock((_definition: CommandDefinition) => {}); + packageCallback({ add }); + expect(add).not.toHaveBeenCalled(); + await expect(ready.execute(c.invocation)).rejects.toThrow("unloaded"); + expect(f.events.aborted()).toBe(true); + }); + + test("registers after an initial lookup failure and reports observable mismatches, missing names and ownership blind spots", async () => { + const f = fakeContext(); + const c = commandFixture(f); + c.failLookup(true); + const cleanup = await setupBeads(f.ctx, { warn: () => {}, runtime: { guard: async () => ({ kind: "ready" }) } }); + expect(c.transform).toHaveBeenCalledTimes(1); + c.failLookup(false); + const ready = c.commands().get("oc-beads:ready")!; + const status = c.commands().get("oc-beads:status")!; + const report = async () => { + await status.execute(c.invocation); + const text = String(c.prompt.mock.calls.at(-1)![0].text); + return JSON.parse(text.split("\n")[1]!); + }; + let observed = await report(); + expect(observed.commands.registration).toBe("registered"); + expect(observed.commands.lookup).toBe("available"); + expect(observed.commands.ownership).toContain("unavailable"); + expect(observed.commands.ownership).toContain("identical-description"); + expect(observed.commands.ownership).toContain("do not prove ownership"); + c.callbacks.push((editor) => editor.add({ ...ready, description: "USER DESCRIPTION" })); + observed = await report(); + expect(observed.commands.mismatches).toContainEqual({ name: ready.name, expectedDescription: ready.description, effectiveDescription: "USER DESCRIPTION" }); + expect(observed.commands.effective).toContainEqual({ name: ready.name, description: "USER DESCRIPTION" }); + c.callbacks.push((editor) => editor.add({ ...ready, execute: async () => {} })); + expect((await report()).commands.mismatches).toEqual([]); + c.omit("oc-beads:show"); + expect((await report()).commands.missing).toEqual(["oc-beads:show"]); + c.failLookup(true); + observed = await report(); + expect(observed.commands.lookup).toBe("failed"); + expect(observed.commands.effective).toBe("unknown: registry lookup failed"); + expect(observed.commands.mismatches).toBeUndefined(); + await cleanup(); + }); + + test("skill checks each candidate without an initial list gate and restores a disappearing override", async () => { + const f = fakeContext(); + type Skill = { id: string; name: string; path: string; content: string }; + const custom = { id: "oc-beads", name: "oc-beads", path: "/user/SKILL.md", content: "USER" }; + let before: Skill | undefined = custom; + let after: Skill | undefined; + const skills = new Map<string, Skill>(); + const callbacks: Array<(editor: unknown) => void> = []; + const rebuild = () => { + skills.clear(); + if (before) skills.set(before.id, before); + const editor = { list: () => [...skills.values()], add: (skill: Skill) => skills.set(skill.id, skill) }; + for (const callback of callbacks) callback(editor); + if (after) skills.set(after.id, after); + }; + const list = mock(async () => { throw new Error("setup must not gate on list"); }); + Object.assign(f.ctx, { skill: { list, transform: async (callback: (editor: unknown) => void) => { + callbacks.push(callback); rebuild(); + return { dispose: async () => { callbacks.splice(callbacks.indexOf(callback), 1); rebuild(); } }; + } } }); + const cleanup = await setupBeads(f.ctx, { warn: () => {} }); + expect(list).not.toHaveBeenCalled(); + expect(callbacks).toHaveLength(1); + expect(skills.get("oc-beads")).toBe(custom); + before = undefined; rebuild(); + expect(skills.get("oc-beads")?.content).toContain("Beads work in OpenCode"); + after = custom; rebuild(); + expect(skills.get("oc-beads")).toBe(custom); + after = undefined; rebuild(); + expect(skills.get("oc-beads")?.content).toContain("Beads work in OpenCode"); + const callback = callbacks[0]!; + after = custom; + await cleanup(); + expect(skills.get("oc-beads")).toBe(custom); + const add = mock((_skill: Skill) => {}); + callback({ list: () => [], add }); + expect(add).not.toHaveBeenCalled(); + }); +}); + +describe("owned lifecycle", () => { + test("awaits idempotent cleanup of every owned registration and aborts pending context", async () => { + const f = fakeContext(); + const disposed: string[] = []; + const completed: string[] = []; + const own = (name: string) => ({ dispose: async () => { + disposed.push(name); + await new Promise((resolve) => setTimeout(resolve, 1)); + completed.push(name); + } }); + const originalAgent = f.ctx.agent.transform; + const transform: HostContext["agent"]["transform"] = async (callback) => { await originalAgent(callback); return own("agent"); }; + Object.assign(f.ctx.agent, { transform }); + const originalHook = f.ctx.session.hook; + const hook: HostContext["session"]["hook"] = async (name, callback) => { await originalHook(name, callback); return own(`hook:${name}`); }; + Object.assign(f.ctx.session, { hook }); + Object.assign(f.ctx, { + skill: { list: async () => ({ data: [] }), transform: async (callback: (editor: unknown) => void) => { callback({ list: () => [], add: () => {} }); return own("skill"); } }, + tool: { transform: async () => own("tool"), hook: async () => own("tool-hook") }, + permission: { assert: async () => {} }, + }); + Object.assign(f.ctx.session, { get: async () => ({ location: { directory: "/project" } }) }); + let signal: AbortSignal | undefined; + let release: (value: string) => void = () => {}; + const cleanup = await setupBeads(f.ctx, { continuityQualified: true, runtime: { + guard: async () => ({ kind: "ready" }), + prime: async (_cwd, abortSignal) => { signal = abortSignal; return new Promise<string>((resolve) => { release = resolve; }); }, + } }); + const pending = f.request("s1", "build"); + await settle(); + await cleanup(); + expect(signal?.aborted).toBe(true); + expect(f.events.aborted()).toBe(true); + expect(completed.sort()).toEqual(["agent", "skill", "tool", "hook:context", "hook:compaction", "tool-hook"].sort()); + release("LATE OLD LOCATION"); + expect(await pending).toEqual([]); + await cleanup(); + expect(disposed).toHaveLength(6); + }); + + test("releases earlier owned registrations if a later setup seam fails", async () => { + const f = fakeContext(); + const disposed = mock(async () => {}); + const originalAgent = f.ctx.agent.transform; + const transform: HostContext["agent"]["transform"] = async (callback) => { await originalAgent(callback); return { dispose: disposed }; }; + Object.assign(f.ctx.agent, { transform }); + Object.assign(f.ctx.session, { hook: async () => { throw new Error("host hook failed"); } }); + await expect(setupBeads(f.ctx)).rejects.toThrow("host hook failed"); + expect(disposed).toHaveBeenCalledTimes(1); + }); +}); + +test("missing packaged guidance is withheld with honest diagnostics", async () => { + const transform = mock(async () => ({ dispose: async () => {} })); + const warnings: string[] = []; + const missing = fakeContext(); + Object.assign(missing.ctx, { skill: { list: async () => ({ data: [] }), transform } }); + const missingCleanup = await setupBeads(missing.ctx, { + warn: (message) => warnings.push(message), + workflowAssets: { readText: async () => { throw new Error("sensitive exception content"); } }, + }); + expect(transform).not.toHaveBeenCalled(); + expect(warnings.some((warning) => warning.includes("skill-unreachable SKILL.md"))).toBe(true); + expect(warnings.join("\n")).not.toContain("sensitive exception content"); + await missingCleanup(); +}); + +test("cleanup attempts every owned disposer and reports a failed disposal", async () => { + const f = fakeContext(); + const warn = mock((_message: string) => {}); + const disposed = mock(async () => {}); + const transform: HostContext["agent"]["transform"] = async () => ({ dispose: async () => { throw new Error("dispose failed"); } }); + Object.assign(f.ctx.agent, { transform }); + const originalHook = f.ctx.session.hook; + const hook: HostContext["session"]["hook"] = async (name, callback) => { await originalHook(name, callback); return { dispose: disposed }; }; + Object.assign(f.ctx.session, { hook }); + const cleanup = await setupBeads(f.ctx, { warn }); + await expect(cleanup()).rejects.toThrow("Beads cleanup failed"); + expect(disposed).toHaveBeenCalledTimes(2); + expect(warn.mock.calls.flat().join("\n")).toContain("cleanup-failed"); + expect(f.events.aborted()).toBe(true); +}); + +test("registered status distinguishes missing, held, failed, loading and uncertain context using only config probes", async () => { + const f = fakeContext({ pluginOptions: { autoMigrate: true } }); + const c = commandFixture(f); + let time = 0; + let version = "1.3.1"; + let probe: "missing" | "failed" | "ready" = "missing"; + const bd = mock(async (_directory: string, args: readonly string[]) => { + if (args.join(" ") === "version") return `bd ${version}`; + if (args.join(" ") === "context --json") { + if (probe === "failed") throw new Error("probe failed"); + return JSON.stringify({ beads_dir: probe === "ready" ? "/unused-fixture/.beads" : "" }); + } + throw new Error("unexpected store-opening probe"); + }); + let release: (text: string) => void = () => {}; + const prime = mock(async () => new Promise<string>((resolve) => { release = resolve; })); + const cleanup = await setupBeads(f.ctx, { bd, runtime: { prime }, now: () => time, retryMs: 1, warn: () => {} }); + const status = c.commands().get("oc-beads:status")!; + const report = async () => { + await status.execute(c.invocation); + return JSON.parse(String(c.prompt.mock.calls.at(-1)![0].text).split("\n")[1]!); + }; + const initial = await report(); + expect(initial.context).toBe("missing: not loaded"); + expect(initial.requestCapability).toBe("unknown: no actual request observation"); + await f.request("s1", "build"); + expect((await report()).context).toBe("missing"); + version = "1.2.0"; time += 2; + await f.request("s1", "build"); + expect((await report()).context).toBe("withheld"); + version = "1.3.1"; probe = "failed"; time += 2; + await f.request("s1", "build"); + const failed = await report(); + expect(failed.context).toBe("failed"); + expect(failed.guard.kind).toBe("probe-failed"); + expect(prime).not.toHaveBeenCalled(); + probe = "ready"; time += 2; + const pending = f.request("s1", "build"); + await settle(); + expect((await report()).context).toBe("uncertain: context refresh in progress"); + release("PRIME"); + await pending; + const observed = await report(); + expect(observed.context).toBe("ready"); + expect(observed.requestCapability.agent).toBe("build"); + expect(observed.requestCapability.capabilities.typed).toBe("unknown"); + expect(observed.leafAvailability).toContain("unknown"); + expect(prime).toHaveBeenCalledTimes(1); + f.events.push({ type: "session.execution.interrupted", data: { sessionID: "s1" } }); + await settle(); + expect((await report()).persistence).toContain("no final checkpoint"); + Object.assign(f.ctx.session, { get: async () => { throw new Error("unknown location"); } }); + const count = bd.mock.calls.length; + const unknown = await report(); + expect(unknown.context).toBe("uncertain: session lookup failed"); + expect(unknown.guard.kind).toBe("withheld"); + expect(unknown.persistence).toContain("uncertain: no final-write guarantee"); + expect(bd).toHaveBeenCalledTimes(count); + expect(new Set(bd.mock.calls.map(([, args]) => args.join(" ")))).toEqual(new Set(["version", "context --json"])); + await cleanup(); +}); + +test("unload aborts an in-flight native status without submitting a late prompt", async () => { + const f = fakeContext(); + const c = commandFixture(f); + let signal: AbortSignal | undefined; + let release: () => void = () => {}; + const cleanup = await setupBeads(f.ctx, { warn: () => {}, runtime: { + guard: async (_directory, abortSignal) => { + signal = abortSignal; + await new Promise<void>((resolve) => { release = resolve; }); + return { kind: "ready" }; + }, + } }); + const pending = c.commands().get("oc-beads:status")!.execute(c.invocation); + await settle(); + await cleanup(); + expect(signal?.aborted).toBe(true); + release(); + await expect(pending).rejects.toThrow("unloaded"); + expect(c.prompt).not.toHaveBeenCalled(); +}); + +test("a failed native command registration diagnoses failure and releases earlier owned registrations", async () => { + const f = fakeContext(); + commandFixture(f); + const disposed = mock(async () => {}); + const original = f.ctx.agent.transform; + const transform: HostContext["agent"]["transform"] = async (callback) => { await original(callback); return { dispose: disposed }; }; + Object.assign(f.ctx.agent, { transform }); + Object.assign(f.ctx.command!, { transform: async () => { throw new Error("native registration failed"); } }); + const warnings: string[] = []; + await expect(setupBeads(f.ctx, { warn: (message) => warnings.push(message) })).rejects.toThrow("native registration failed"); + expect(warnings.join("\n")).toContain("command-registration-failed"); + expect(disposed).toHaveBeenCalledTimes(1); +}); diff --git a/tests/instructions.test.ts b/tests/instructions.test.ts index 1262da3..9c0ea04 100644 --- a/tests/instructions.test.ts +++ b/tests/instructions.test.ts @@ -27,9 +27,13 @@ const MINIMAL_BLOCK = [ const USER_AFTER = "## Release process\n\nUser-owned section."; describe("minimizeBeadsInstructions", () => { + test("preserves unique local policy inside managed markers", () => { + const customized = FULL_BLOCK.replace("Long command reference duplicated by bd prime.", "Never push without Matt's approval."); + expect(minimizeBeadsInstructions(customized, "canonical prime")).toEqual({ text: customized, replaced: 0 }); + }); test("collapses bd's full-profile and Codex blocks into one pointer", () => { const text = [USER_BEFORE, FULL_BLOCK, CODEX_BLOCK, USER_AFTER].join("\n\n"); - const result = minimizeBeadsInstructions(text); + const result = minimizeBeadsInstructions(text, text); expect(result.replaced).toBe(2); expect(result.text).toBe([USER_BEFORE, MINIMAL_BEADS_POINTER, "", USER_AFTER].join("\n\n")); expect(result.text.match(/opencode-beads plugin/g)).toHaveLength(1); @@ -55,7 +59,7 @@ describe("minimizeSystemParts", () => { { type: "text", text: `Instructions from: /repo/AGENTS.md\n${FULL_BLOCK}` }, { type: "image" }, ]; - expect(minimizeSystemParts(parts)).toBe(1); + expect(minimizeSystemParts(parts, FULL_BLOCK)).toBe(1); expect(parts[0]?.text).toBe("You are an AI agent running in OpenCode."); expect(parts[1]?.text).toBe(`Instructions from: /repo/AGENTS.md\n${MINIMAL_BEADS_POINTER}`); }); diff --git a/tests/prime.test.ts b/tests/prime.test.ts index 5b28773..0c44f82 100644 --- a/tests/prime.test.ts +++ b/tests/prime.test.ts @@ -3,6 +3,7 @@ import { PrimeProcessError, PrimeTimeoutError, runBdPrime, + runBd, type PrimeProcess, } from "../src/prime"; @@ -21,6 +22,31 @@ function completedProcess(exitCode: number, output = "context", error = "failure } describe("runBdPrime", () => { + test("refuses a cancelled invocation before spawn", async () => { + const abort = new AbortController(); + abort.abort(); + const spawn = mock(() => completedProcess(0)); + await expect(runBd("/project", ["show", "task-1"], { signal: abort.signal, spawn })).rejects.toHaveProperty("name", "AbortError"); + expect(spawn).not.toHaveBeenCalled(); + }); + + test("aborts the process tree and bounds draining without retrying an interrupted write", async () => { + const abort = new AbortController(); + const killTree = mock(() => {}); + const spawn = mock(() => ({ + ...completedProcess(0), + stdout: new ReadableStream<Uint8Array>(), + exited: new Promise<number>(() => {}), + killTree, + })); + const pending = runBd("/project", ["update", "task-1", "--append-notes", "one\ntwo"], { + signal: abort.signal, spawn, drainTimeoutMs: 1, + }); + abort.abort(); + await expect(pending).rejects.toHaveProperty("name", "AbortError"); + expect(killTree).toHaveBeenCalledWith("SIGKILL"); + expect(spawn).toHaveBeenCalledTimes(1); + }); test("returns stdout and cancels the timeout after success", async () => { let cancelled = false; const spawn = mock((_directory: string, _args: readonly string[]) => diff --git a/tests/upstream-review.test.ts b/tests/upstream-review.test.ts new file mode 100644 index 0000000..3a7956b --- /dev/null +++ b/tests/upstream-review.test.ts @@ -0,0 +1,246 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import * as fs from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { runSourceCheck } from "../scripts/check-beads-integration"; + +const baseline = "4a67d0d0288c1b4ee23e17fc896f47616931ba2d"; +const candidate = "b".repeat(40); +const baseTree = "c".repeat(40); +const nextTree = "d".repeat(40); +const repository = "gastownhall/beads"; +const api = `https://api.github.com/repos/${repository}`; +const script = resolve(import.meta.dir, "../scripts/check-beads-integration.ts"); +const inventoryPath = resolve(import.meta.dir, "../docs/claude-code-integration-inventory.json"); +const temporaryDirectories: string[] = []; + +afterEach(async () => { + await Promise.all(temporaryDirectories.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); +}); + +function blob(path: string, id = "1", mode = "100644") { + return { path, type: "blob", mode, sha: id.repeat(40) }; +} + +const entries = [ + blob("plugins/beads/edit.md"), + blob("plugins/beads/delete.md", "2"), + blob("plugins/beads/old.md", "3"), + blob("internal/templates/skills/beads/SKILL.md", "4"), + blob("cmd/bd/prime.go", "5"), + blob(".claude-plugin/marketplace.json", "6"), +]; +const inventory = { + marketplace_main: { + repository, + commit: baseline, + default_branch: "main", + files: entries.map(({ path, sha }) => ({ path, git_blob: sha })), + }, +}; + +/** GitHub-shaped responses; no tag or local adaptation bytes enter the comparison. */ +function responses(next = entries): Record<string, unknown> { + return { + [api]: { default_branch: "main" }, + [`${api}/commits/main`]: { sha: candidate, commit: { tree: { sha: nextTree } } }, + [`${api}/commits/${baseline}`]: { sha: baseline, commit: { tree: { sha: baseTree } } }, + [`${api}/git/trees/${baseTree}?recursive=1`]: { sha: baseTree, truncated: false, tree: entries }, + [`${api}/git/trees/${nextTree}?recursive=1`]: { sha: nextTree, truncated: false, tree: next }, + }; +} + +function fixtureFetch(data: Record<string, unknown>, calls: string[] = []) { + return async (url: string) => { + calls.push(url); + if (!(url in data)) throw new Error("Unexpected fixture request"); + return new Response(JSON.stringify(data[url]), { status: 200 }); + }; +} + +async function run(data = responses(), sourceInventory: unknown = inventory) { + const result = await runSourceCheck(sourceInventory, fixtureFetch(data)); + return { ...result, report: JSON.parse(result.stdout) }; +} + +/** Run the actual CLI with a temporary preload replacing only HTTP retrieval. */ +async function entrypoint(data: Record<string, unknown>, args: string[] = []) { + const directory = await fs.mkdtemp(join(tmpdir(), "oc-upstream-entrypoint-")); + temporaryDirectories.push(directory); + const preload = join(directory, "fetch.ts"); + await fs.writeFile(preload, ` +const data = ${JSON.stringify(data)}; +globalThis.fetch = async (url) => { + if (!(String(url) in data)) throw new Error("Unexpected fixture request"); + return new Response(JSON.stringify(data[String(url)]), { status: 200 }); +}; +`); + const proc = Bun.spawn([process.execPath, "--preload", preload, script, ...args], { + cwd: directory, + stdout: "pipe", + stderr: "pipe", + env: { ...process.env, GH_TOKEN: "fixture-secret-never-print" }, + }); + const [exitCode, stdout, stderr] = await Promise.all([ + proc.exited, new Response(proc.stdout).text(), new Response(proc.stderr).text(), + ]); + expect(await fs.readdir(directory)).toEqual(["fetch.ts"]); + return { exitCode, stdout, stderr, report: JSON.parse(stdout) }; +} + +describe("advisory marketplace MAIN source collector", () => { + test("unchanged selection succeeds despite unrelated paths and prefix noise", async () => { + const result = await run(responses([...entries, + blob("README.md", "7"), blob("plugins/beads-extra/noise.md", "8"), + blob("internal/templates/skills/beads-other/noise.md", "9"), + ])); + expect(result.exitCode).toBe(0); + expect(result.report.kind).toBe("completed"); + expect(result.report.status).toBe("unchanged"); + expect(result.report.level).toBe("info"); + expect(result.report.changes).toEqual([]); + expect(result.report.selectedFiles).toEqual({ baseline: 6, candidate: 6 }); + }); + + test("complete membership detects add/delete/edit/exact rename and support edits, sorted and repeatable", async () => { + const next = [ + blob("plugins/beads/new.md", "3"), + blob("plugins/beads/edit.md", "7"), + blob("plugins/beads/added.md", "8"), + blob("internal/templates/skills/beads/SKILL.md", "4", "100755"), + blob("cmd/bd/prime.go", "9"), + entries[5]!, + ]; + const data = responses(next); + const result = await run(data); + expect(result.exitCode).toBe(0); + expect(result.report.status).toBe("changed"); + expect(result.report.changes.map((c: { kind: string; path: string; previousPath?: string }) => + [c.kind, c.path, c.previousPath ?? null])).toEqual([ + ["edited", "cmd/bd/prime.go", null], + ["edited", "internal/templates/skills/beads/SKILL.md", null], + ["added", "plugins/beads/added.md", null], + ["deleted", "plugins/beads/delete.md", null], + ["edited", "plugins/beads/edit.md", null], + ["renamed", "plugins/beads/new.md", "plugins/beads/old.md"], + ]); + expect(result.report.baselineCommit).toBe(baseline); + expect(result.report.candidateCommit).toBe(candidate); + expect(result.report.reviewUrl).toContain(`${baseline}..${candidate}`); + expect(result.report.changes.every((c: { beforeUrl?: string; afterUrl?: string }) => + c.beforeUrl?.includes(baseline) || c.afterUrl?.includes(candidate))).toBeTrue(); + expect((await run(responses([...next].reverse()))).stdout).toBe(result.stdout); + expect((await run(data)).stdout).toBe(result.stdout); + expect(inventory.marketplace_main.commit).toBe(baseline); + }); + + test("resolves the actual default branch and immutable candidate only once", async () => { + const data = responses(); + data[api] = { default_branch: "development" }; + data[`${api}/commits/development`] = data[`${api}/commits/main`]; + delete data[`${api}/commits/main`]; + const calls: string[] = []; + const result = await runSourceCheck(inventory, fixtureFetch(data, calls)); + expect(result.exitCode).toBe(0); + expect(calls.filter((url) => url.endsWith("/commits/development"))).toHaveLength(1); + expect(calls).toHaveLength(5); + expect(calls.some((url) => /tags|releases|compare/.test(url))).toBeFalse(); + }); + + test("reads an explicitly revised reviewed inventory without advancing the original baseline", async () => { + const reviewedInventory = structuredClone(inventory); + reviewedInventory.marketplace_main.commit = candidate; + const calls: string[] = []; + const result = await runSourceCheck(reviewedInventory, fixtureFetch(responses(), calls)); + expect(result.exitCode).toBe(0); + expect(JSON.parse(result.stdout).baselineCommit).toBe(candidate); + expect(JSON.parse(result.stdout).status).toBe("unchanged"); + expect(calls).toHaveLength(3); + expect(inventory.marketplace_main.commit).toBe(baseline); + expect(reviewedInventory.marketplace_main.commit).toBe(candidate); + }); + + test("incomplete, malformed or failed retrieval is unavailable, never unchanged", async () => { + const treeUrl = `${api}/git/trees/${nextTree}?recursive=1`; + for (const invalid of [ + { sha: nextTree, truncated: true, tree: entries }, + { sha: nextTree, tree: entries }, + { sha: nextTree, truncated: false }, + { sha: baseTree, truncated: false, tree: entries }, + { sha: nextTree, truncated: false, tree: [{ path: "plugins/beads/x", type: "blob" }] }, + { sha: nextTree, truncated: false, tree: [blob("plugins/beads/x", "1", "invalid")] }, + { sha: nextTree, truncated: false, tree: [...entries, entries[0]] }, + ]) { + const data = responses(); + data[treeUrl] = invalid; + const result = await run(data); + expect(result.exitCode).toBe(2); + expect(result.report.kind).toBe("unavailable"); + expect(result.report.status).toBeUndefined(); + expect(result.report.changes).toBeUndefined(); + } + for (const fetchSource of [ + async () => new Response('{"message":"denied"}', { status: 403 }), + async () => new Response("not JSON"), + async () => { throw new Error("network unavailable"); }, + fixtureFetch({ [api]: {} }), + ]) { + const result = await runSourceCheck(inventory, fetchSource); + expect(result.exitCode).toBe(2); + expect(JSON.parse(result.stdout).kind).toBe("unavailable"); + } + const missingBaseline = responses(); + missingBaseline[`${api}/git/trees/${baseTree}?recursive=1`] = { sha: baseTree, truncated: false, tree: [] }; + expect((await run(missingBaseline)).exitCode).toBe(2); + const missingCommit = responses(); + missingCommit[`${api}/commits/main`] = { sha: candidate }; + expect((await run(missingCommit)).exitCode).toBe(2); + expect((await run(responses(), { marketplace_main: { commit: "main" } })).exitCode).toBe(2); + }); + + test("actual entrypoint: changed MAIN with the same tagged CLI result stays informational/0 and read-only", async () => { + const actualInventory = await fs.readFile(inventoryPath, "utf8"); + const packagePath = resolve(import.meta.dir, "../package.json"); + const actualPackage = await fs.readFile(packagePath, "utf8"); + const main = JSON.parse(actualInventory).marketplace_main; + const selected = main.files.map((f: { path: string; git_blob: string }) => ({ + path: f.path, sha: f.git_blob, type: "blob", mode: "100644", + })); + const stableCLIResult = Object.freeze({ taggedRelease: "v1.3.1", compatible: true, exitCode: 0 }); + const stableCLIFailure = Object.freeze({ taggedRelease: "v1.3.1", compatible: false, exitCode: 1 }); + const before = JSON.stringify({ stableCLIResult, stableCLIFailure }); + const data = responses(selected); + data[`${api}/git/trees/${baseTree}?recursive=1`] = { sha: baseTree, truncated: false, tree: selected }; + const unchanged = await entrypoint(data); + expect(unchanged.exitCode).toBe(0); + expect(unchanged.report.status).toBe("unchanged"); + data[`${api}/git/trees/${nextTree}?recursive=1`] = { + sha: nextTree, truncated: false, tree: [...selected, blob("plugins/beads/new-main-work.md", "a")], + }; + const changed = await entrypoint(data); + expect(changed.exitCode).toBe(0); + expect(changed.report.status).toBe("changed"); + expect(changed.report.channel).toBe("development-source"); + expect(changed.report.level).toBe("info"); + expect(changed.stderr).toBe(""); + expect(changed.stdout).not.toMatch(/::(?:error|warning)::|fixture-secret-never-print/); + expect(changed.report).not.toHaveProperty("compatible"); + expect(changed.report).not.toHaveProperty("taggedRelease"); + expect((await entrypoint(data)).stdout).toBe(changed.stdout); + expect(JSON.stringify({ stableCLIResult, stableCLIFailure })).toBe(before); + expect(stableCLIFailure.exitCode).toBe(1); + expect(await fs.readFile(inventoryPath, "utf8")).toBe(actualInventory); + data[`${api}/git/trees/${nextTree}?recursive=1`] = { sha: nextTree, truncated: true, tree: selected }; + const unavailable = await entrypoint(data); + expect(unavailable.exitCode).toBe(2); + expect(unavailable.report.kind).toBe("unavailable"); + expect(unavailable.report.status).toBeUndefined(); + expect(unavailable.stderr).toBe(""); + expect(JSON.stringify({ stableCLIResult, stableCLIFailure })).toBe(before); + const blindAdvancement = await entrypoint({}, ["--advance-baseline", candidate]); + expect(blindAdvancement.exitCode).toBe(2); + expect(blindAdvancement.report.kind).toBe("unavailable"); + expect(await fs.readFile(inventoryPath, "utf8")).toBe(actualInventory); + expect(await fs.readFile(packagePath, "utf8")).toBe(actualPackage); + }); +}); diff --git a/tests/workflows.test.ts b/tests/workflows.test.ts new file mode 100644 index 0000000..9afc6ed --- /dev/null +++ b/tests/workflows.test.ts @@ -0,0 +1,333 @@ +import { describe, expect, mock, test } from "bun:test" +import { existsSync, readFileSync } from "node:fs" +import { relative, resolve, sep } from "node:path" +import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { fileURLToPath } from "node:url"; +import { + createBeadsCommands, + loadBeadsSkill, + WORKFLOWS, +} from "../src/workflows"; +import type { Plugin } from "@opencode/plugin"; +import type { CommandInvocation } from "@opencode/plugin/promise/command"; + +type InventoryEntry = { + category: string + path: string + selected_mapping: { + disposition: string + target: string + task_ids: string[] + } +} + +type Inventory = { + marketplace_main: { + files: InventoryEntry[] + } +} + +const repositoryRoot = process.cwd() +const assetRoot = resolve(repositoryRoot, "assets/oc-beads") +const skillPath = resolve(assetRoot, "SKILL.md") + +function readInventory(): Inventory { + return JSON.parse( + readFileSync(resolve(repositoryRoot, "docs/claude-code-integration-inventory.json"), "utf8"), + ) as Inventory +} + +function selectedSupportingFiles(): InventoryEntry[] { + return readInventory().marketplace_main.files.filter( + (entry) => + (entry.category === "resource" || entry.category === "adr") && + entry.selected_mapping.task_ids.includes("2.1"), + ) +} + +function readResource(name: string): string { + return readFileSync(resolve(assetRoot, `resources/${name}.md`), "utf8") +} + +describe("oc-beads guidance assets", () => { + test("covers and explicitly links every selected marketplace resource and ADR", () => { + const supportingFiles = selectedSupportingFiles() + const resources = supportingFiles.filter((entry) => entry.category === "resource") + const adrs = supportingFiles.filter((entry) => entry.category === "adr") + const skill = readFileSync(skillPath, "utf8") + + expect(resources).toHaveLength(15) + expect(adrs).toHaveLength(1) + + const missing = supportingFiles.filter((entry) => !existsSync(resolve(repositoryRoot, entry.selected_mapping.target))) + expect(missing.map((entry) => entry.selected_mapping.target)).toEqual([]) + + const unlinked = supportingFiles.filter((entry) => { + const relativePath = relative(assetRoot, resolve(repositoryRoot, entry.selected_mapping.target)).split(sep).join("/") + return !skill.includes(`](${relativePath})`) + }) + expect(unlinked.map((entry) => entry.selected_mapping.target)).toEqual([]) + }) + + test("teaches append checkpoints, selective memory, and bounded actor-owned work", () => { + const skill = readFileSync(skillPath, "utf8") + const workflows = readFileSync(resolve(assetRoot, "resources/WORKFLOWS.md"), "utf8") + const guidance = `${skill}\n${workflows}` + + expect(guidance).toContain("--append-notes") + expect(guidance).toContain("--actor") + expect(guidance).toContain("--sandbox") + expect(guidance).toContain("created_by") + expect(guidance).toContain("assignee") + expect(guidance).toContain("reusable knowledge") + expect(guidance).toContain("authorized batch") + expect(guidance).not.toMatch(/bd update[^\n]*\s--notes\s/) + }) + + test("keeps current CLI access honest without claiming host prerequisites or automatic setup", () => { + const skill = readFileSync(skillPath, "utf8") + + expect(skill).toContain("permissioned CLI") + expect(skill).toContain("Do not initialize or reroute a store, synchronize it, change permissions, or enable automation as a side effect.") + expect(skill).not.toMatch(/requires? OpenCode\s+v?\d/i) + expect(skill).not.toMatch(/OpenCode\s*>=\s*2\./i) + expect(skill).not.toMatch(/\bmcp__\w+/i) + expect(skill).not.toMatch(/automatically run `bd init`/i) + }) + + test("states the unsupported agent and slot CLI limitation with a supported boundary", () => { + const agents = readFileSync(resolve(assetRoot, "resources/AGENTS.md"), "utf8") + const troubleshooting = readFileSync(resolve(assetRoot, "resources/TROUBLESHOOTING.md"), "utf8") + + expect(agents).toMatch(/`bd agent --help` and `bd slot --help` both\s+return unknown-command errors\./) + expect(agents).toContain("not a one-to-one implementation") + expect(troubleshooting).toMatch(/do not initialize, migrate, reroute, repair, or synchronize it\s+automatically/) + }) + + test("gives dependency choices, direction, and a parallel merge example", () => { + const dependencies = readResource("DEPENDENCIES") + + expect(dependencies).toMatch(/\|\s*Question\s*\|\s*Relationship\s*\|/) + expect(dependencies).toMatch(/--deps=.*depends-on:/) + expect(dependencies).toContain("--parent='<epic-id>'") + expect(dependencies).toContain("not an ordering edge") + expect(dependencies).toMatch(/hard\s+block only when a real prerequisite exists/i) + expect(dependencies).toMatch(/diamond/i) + expect(dependencies).toMatch(/`discovered-from` does not make that\s+cascade required work/i) + }) + + test("shows a useful before-and-after checkpoint and a compact handoff shape", () => { + const resumability = readResource("RESUMABILITY") + + expect(resumability).toContain("### Before checkpoint") + expect(resumability).toContain("### After checkpoint") + expect(resumability).toMatch(/attempted[\s\S]*evidence[\s\S]*rationale[\s\S]*blocker[\s\S]*next/i) + expect(resumability).toContain("Minimal handoff template") + expect(resumability).toMatch(/tested.{0,50}(snippet|example)|(?:snippet|example).{0,50}tested/i) + expect(resumability).toMatch(/raw (?:log|JSON|dump)/i) + }) + + test("helps choose transient versus durable work and preserves planning authority", () => { + const boundaries = readResource("BOUNDARIES") + const integration = readResource("INTEGRATION_PATTERNS") + + expect(boundaries).toContain("| Situation | Record | Why |") + expect(boundaries).toMatch(/experimental[\s\S]{0,100}opt-in[\s\S]{0,100}host-owned/i) + expect(boundaries).toMatch(/transition/i) + expect(integration).toContain("OpenSpec") + expect(integration).toContain("one bounded planning Bead") + expect(integration).toMatch(/review is independently owned/i) + expect(integration).toMatch(/same review Bead[\s\S]*retry|same review Bead[\s\S]*recheck/i) + expect(integration).toMatch(/do not create a ticket per\s+document, retry/i) + expect(integration).toContain("--append-notes") + expect(integration).toContain("--sandbox") + }) + + test("distinguishes human and timer gates and documents deliberate inspection and resolution", () => { + const gates = readResource("ASYNC_GATES") + + expect(gates).toMatch(/human.{0,100}timeout.{0,100}(?:stored|metadata).{0,100}(?:not enforced|does not enforce)/is) + expect(gates).toMatch(/timeout has elapsed when an authorized executor runs/i) + expect(gates).toContain("bd gate check --type=timer") + expect(gates).toMatch(/success[\s\S]*failure[\s\S]*escalat/i) + expect(gates).toContain("bd gate create") + expect(gates).toContain("bd gate show") + expect(gates).toContain("bd gate resolve") + expect(gates).toMatch(/do not (?:add|run|schedule).{0,40}(?:poll|watch|approval)/i) + }) + + test("explains molecule lifetime and a bounded research or review wisp lifecycle", () => { + const chemistry = readResource("CHEMISTRY_PATTERNS") + const molecules = readResource("MOLECULES") + const guidance = `${chemistry}\n${molecules}` + + expect(guidance).toMatch(/persistent molecule.{0,80}audit history/i) + expect(guidance).toMatch(/wisp.{0,100}ephemeral/i) + expect(guidance).toMatch(/research|review/i) + expect(guidance).toContain("bd mol wisp create") + expect(guidance).toContain("bd mol squash") + expect(guidance).toContain("bd mol burn") + expect(guidance).toMatch(/promot[\s\S]{0,100}findings[\s\S]{0,100}(?:noise|no future value)/i) + }) + + test("keeps copyable placeholders quoted and executor actors neutral", () => { + const files = [skillPath, ...selectedSupportingFiles().map((entry) => resolve(repositoryRoot, entry.selected_mapping.target))] + const guidance = files.map((file) => readFileSync(file, "utf8")).join("\n") + const actors = [...guidance.matchAll(/--actor=([^\s`]+)/g)].map((match) => match[1]) + + expect(actors.length).toBeGreaterThan(0) + expect(actors.filter((actor) => actor !== "'<executor-actor>'" && actor !== "'<actual-human-actor>'")).toEqual([]) + expect(guidance).not.toMatch(/codex:/) + // Strip quoted arguments: remaining angle brackets in shell blocks would be redirects. + const shellBlocks = [...guidance.matchAll(/```(?:sh|bash)\n([\s\S]*?)```/g)].map((match) => match[1] ?? "") + const unquotedShell = shellBlocks.join("\n").replace(/'[^']*'|"[^"]*"/g, "") + expect(unquotedShell).not.toMatch(/<[^>\n]+>/) + expect(readFileSync(skillPath, "utf8")).toMatch(/host-rendered actor[\s\S]{0,100}request session and agent/i) + expect(readResource("ASYNC_GATES")).toMatch(/(?:human[^.]*runs|run by[^.]*human)/i) + }) + + test("shows existing-issue dependency direction and graph readback", () => { + const dependencies = readResource("DEPENDENCIES") + + expect(dependencies).toMatch(/bd dep add '<dependent-id>' '<prerequisite-id>'[^\n]*--actor='<executor-actor>'[^\n]*--sandbox/) + expect(dependencies).toMatch(/wrong[\s\S]*right/i) + expect(dependencies).toMatch(/phase 1[\s\S]*phase 2/i) + expect(dependencies).toContain("bd blocked --json") + expect(dependencies).toContain("bd dep list '<dependent-id>'") + }) + + test("recovers assigned active work before acting and protects existing memory keys", () => { + const recovery = readResource("RESUMABILITY") + const skill = readFileSync(skillPath, "utf8") + + expect(recovery).toMatch(/bd list --status=in_progress --assignee='<executor-actor>'[^\n]*--json/) + expect(recovery).toContain("bd show '<issue-id>'") + expect(recovery).toMatch(/checkpoint[\s\S]*verify[\s\S]*before acting/i) + expect(recovery).toMatch(/(?:not|never|rather than)[^.]*`bd ready`/i) + expect(skill).toMatch(/recall[^.]*before[^.]*key/i) + expect(skill).toMatch(/same key[^.]*replace[^.]*in place/i) + }) + + test("makes the project specification system and planning pattern conditional", () => { + for (const guidance of [readFileSync(skillPath, "utf8"), readResource("BOUNDARIES"), readResource("INTEGRATION_PATTERNS")]) { + expect(guidance).toMatch(/project.{0,20}specification system/i) + expect(guidance).toMatch(/OpenSpec, when used/) + } + expect(readResource("INTEGRATION_PATTERNS")).toMatch(/project.policy example[^.]*not[^.]*Beads requirement/is) + }) +}) + +describe("packaged native workflows", () => { + test("forwards exact scope and partial-handoff authority cues without selecting or claiming work", async () => { + const prompt = mock(async (_input: unknown) => ({} as Awaited<ReturnType<Plugin.Context["session"]["prompt"]>>)); + const commands = createBeadsCommands({ prompt }, async () => "STATUS"); + // These are literal caller instructions, not a mock model or a scope interpreter. + const scenarios = [ + ["ready", "Execute exact fixture-1 only; stop without substitution; closure belongs to parent."], + ["ready", "Authorized batch fixture-1 and fixture-2 only; claim each atomically; validate and append evidence before continuing."], + ["create", "Record discovered-from:fixture-1 follow-up when authorized; this does not authorize its execution or assignment."], + ["list", "Read-only graph overview; do not claim or write."], + ["close", "Permission denied and acceptance incomplete; preserve partial status, do not close. Actor worker:fixture; full session fixture-session; tried A; result failed; because B; validation pending; reference task:fixture-1; next parent review."], + ] as const; + for (const [stem, text] of scenarios) { + const input = { sessionID: "parent", delivery: "queue", prompt: { text } } as unknown as CommandInvocation; + await commands.find((command) => command.name === `oc-beads:${stem}`)!.execute(input); + const sent = prompt.mock.calls.at(-1)![0] as Record<string, unknown>; + expect(sent.sessionID).toBe("parent"); + expect(sent.delivery).toBe("queue"); + expect(sent.text).toContain(text); + expect(sent.text).toContain(WORKFLOWS[stem][0]); + } + expect(prompt).toHaveBeenCalledTimes(scenarios.length); + // The established rich assets agree with the new canonical scope guidance. + const skill = readFileSync(skillPath, "utf8"); + const workflows = readResource("WORKFLOWS"); + expect(skill).toContain("An assignment to one specific item is an exact assignment"); + expect(workflows).toContain("Explicitly authorized batches may continue within their stated scope"); + expect(workflows).toContain("If a parent or human owns acceptance"); + expect(workflows).toContain("new discoveries never expand it"); + }); + + test("defines exactly the selected inventory's 29 command stems plus status", async () => { + const inventory = await Bun.file("docs/claude-code-integration-inventory.json").json(); + const selected = inventory.marketplace_main.files + .filter((file: { path: string }) => file.path.startsWith("plugins/beads/skills/beads/commands/")) + .map((file: { selected_mapping: { target: string } }) => file.selected_mapping.target).sort(); + expect(Object.keys(WORKFLOWS).map((stem) => `oc-beads:${stem}`).sort()).toEqual(selected); + const commands = createBeadsCommands({ prompt: mock(async () => ({} as Awaited<ReturnType<Plugin.Context["session"]["prompt"]>>)) }, async () => "STATUS"); + expect(commands.map((command) => command.name).sort()).toEqual([...selected, "oc-beads:status"].sort()); + }); + + test("forwards literal arguments, attachments, prompt fields and native delivery without template execution", async () => { + const prompt = mock(async (_input: unknown) => ({} as Awaited<ReturnType<Plugin.Context["session"]["prompt"]>>)); + const input = { + sessionID: "s1", delivery: "queue", + prompt: { text: "a 'quoted' $ARGUMENTS $(touch nope) `literal`", files: [{ uri: "file:///tmp/a%20b", name: "a b" }], agents: [{ name: "build" }], skills: [{ id: "oc-beads" }] }, + } as unknown as CommandInvocation; + for (const command of createBeadsCommands({ prompt }, async () => "STATUS: withheld")) { + await command.execute(input); + const sent = prompt.mock.calls.at(-1)![0] as Record<string, unknown>; + expect(sent.sessionID).toBe(input.sessionID); + expect(sent.delivery).toBe(input.delivery); + expect(sent.files).toBe(input.prompt.files); + expect(sent.agents).toBe(input.prompt.agents); + expect(sent.skills).toBe(input.prompt.skills); + expect(sent.text).toContain(input.prompt.text); + if (command.name !== "oc-beads:status") { + expect(sent.text).toContain("Do not bypass a hold"); + expect(sent.text).toContain("oc-beads"); + expect(sent.text).not.toContain("mcp__"); + } else expect(sent.text).toContain("STATUS: withheld"); + } + }); + + test("loads the rich skill at its installed path with all 17 explicit links, beyond a sampled ten", async () => { + const skill = await loadBeadsSkill(); + expect(String(skill.id)).toBe("oc-beads"); + expect(String(skill.name)).toBe("oc-beads"); + expect(String(skill.path)).toBe(fileURLToPath(new URL("../assets/oc-beads/SKILL.md", import.meta.url))); + expect(skill.content).toContain("### Advanced"); + const links = [...skill.content.matchAll(/\]\(((?:resources|adr)\/[^)]+)\)/g)].map((match) => match[1]!); + expect(new Set(links).size).toBe(17); + for (const link of links) expect(await readFile(new URL(link, `file://${skill.path}`), "utf8")).not.toBeEmpty(); + }); + + test("reports missing or unreachable packaged guidance rather than silently advertising it", async () => { + const realRead = (file: string) => readFile(file, "utf8"); + for (const code of ["ENOENT", "EACCES"]) { + await expect(loadBeadsSkill({ readText: async (file) => { + if (file.endsWith("WORKTREES.md")) throw Object.assign(new Error("sensitive detail"), { code }); + return realRead(file); + } })).rejects.toThrow("resource-unreachable resources/WORKTREES.md"); + } + }); + + test("package manifest includes owned assets without a version or dependency change", async () => { + const manifest = await Bun.file("package.json").json(); + expect(manifest.files).toContain("assets/oc-beads"); + expect(manifest.version).toBe("1.0.2"); + }); +}); + + +test("explicit installed skill base survives ignored/hidden roots beyond the host scan depth and reload", async () => { + const root = await mkdtemp(resolve(tmpdir(), "oc-beads-path-")); + const installed = resolve(root, ".hidden/node_modules/a/b/c/d/e/f/g/oc-beads"); + try { + await mkdir(installed, { recursive: true }); + await cp(assetRoot, installed, { recursive: true }); + const path = resolve(installed, "SKILL.md"); + const first = await loadBeadsSkill({ skillPath: path }); + expect(String(first.path)).toBe(path); + expect(first.content).toContain("resources/WORKTREES.md"); + await writeFile(path, `${await readFile(path, "utf8")}\nReloaded package fixture.\n`); + const reloaded = await loadBeadsSkill({ skillPath: path }); + expect(reloaded.content).toContain("Reloaded package fixture."); + // Explicit registration supplies SKILL.md; it never asks a recursive scanner or + // resource watcher to discover the other 17 files. Reads still use host permissions. + expect(reloaded.content.match(/\]\((?:resources|adr)\//g)).toHaveLength(17); + } finally { + await rm(root, { recursive: true, force: true }); + } +});