diff --git a/.github/workflows/beads-contract.yml b/.github/workflows/beads-contract.yml index 9fad77a..cf30812 100644 --- a/.github/workflows/beads-contract.yml +++ b/.github/workflows/beads-contract.yml @@ -2,6 +2,7 @@ name: beads-contract # Nothing is vendored from upstream Beads. This checks the latest stable bd release against the # plugin's contract daily, and fails when a newer release has not been reviewed yet. +# Independent MAIN source comparison is informational; unavailable evidence fails only its job. on: schedule: - cron: '0 9 * * *' @@ -64,3 +65,43 @@ jobs: echo "::error::Beads $VERSION passes the contract but is newer than the validated $validated. Review its upgrade notes against docs/beads-artifact-policy.md, then update beads.validated in package.json and the README compatibility table." exit 1 fi + + source-comparison: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.3.14 + + - name: Compare upstream MAIN sources + env: + GH_TOKEN: ${{ github.token }} + run: | + status=0 + bun scripts/check-beads-integration.ts > "$RUNNER_TEMP/beads-source.json" || status=$? + printf '%s\n' "$status" > "$RUNNER_TEMP/beads-source.exit" + exit "$status" + + - name: Summarize upstream MAIN comparison + if: ${{ always() }} + run: | + { + printf '## Upstream MAIN source comparison\n\n' + printf 'Development-source information only; tagged CLI compatibility is independent.\n\n' + if [[ -f "$RUNNER_TEMP/beads-source.exit" ]]; then + printf 'Collector exit status: %s\n\n' "$(cat "$RUNNER_TEMP/beads-source.exit")" + else + printf 'Comparison unavailable: collector did not finish.\n\n' + fi + if [[ -s "$RUNNER_TEMP/beads-source.json" ]]; then + printf '````json\n' + cat "$RUNNER_TEMP/beads-source.json" + printf '\n````\n' + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/README.md b/README.md index 81c7fd5..ba677f4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Version **2.0.0** is the stable line. | Component | Supported | Evidence | | --- | --- | --- | | OpenCode | `>=2.2.6 <2.3` | Exact 2.2.6 native-host qualification plus one genuine model workflow | -| `bd` CLI | 1.3.1 through 1.x | 1.3.1 contract baseline | +| `bd` CLI | 1.3.1 through 1.x | 1.3.4 contract baseline | A compatible range is admission, not proof that every future patch was tested. The plugin refuses to register on hosts outside the range. Older hosts: see [Compatibility](docs/user-guide.md#compatibility). @@ -89,7 +89,7 @@ The agent claims the issue atomically and appends checkpoints under the actual s ## Authorship and license -This is an independent project maintained by Matt Jackson. 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. It vendors nothing from upstream Beads; each Beads release is checked against the `bd` behavior the plugin relies on. +This is an independent project maintained by Matt Jackson. 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. It vendors nothing from upstream Beads; the daily/manual maintenance workflow checks tagged CLI compatibility and independently compares reviewed upstream integration sources with current MAIN. Completed source changes are informational; adoption remains a human decision. opencode-beads is licensed under the MIT license. See [`LICENSE`](LICENSE). diff --git a/docs/beads-1.3.4-review-2026-11-09.md b/docs/beads-1.3.4-review-2026-11-09.md new file mode 100644 index 0000000..c7887ec --- /dev/null +++ b/docs/beads-1.3.4-review-2026-11-09.md @@ -0,0 +1,23 @@ +# Beads 1.3.4 review — 2026-11-09 + +Reviewed from plugin 2.0.0 source `bb496c1bc0b05a4131856c90106f05748e396d57`. Disposition: validate exactly Beads 1.3.4, retaining minimum 1.3.1. No adapter runtime, packaged guidance, API or dependency change is needed for the reviewed deltas. This is CLI compatibility evidence, not a guarantee about every upstream storage topology or an operator upgrade. + +## Reviewed deltas and disposition + +- [1.3.2 release/upgrade notes](https://github.com/gastownhall/beads/releases/tag/v1.3.2): retention, backup, concurrent blocked-state recheck, least-privilege connections and export fixes belong to the installed CLI. The migration0059 stored-blockedness warning can affect ready results in older adopted stores; the upstream notes prescribe `bd recompute-blocked` for affected workspaces. The adapter must report CLI results honestly, not recompute or repair them itself. No operator action was performed here. +- [1.3.3 release](https://github.com/gastownhall/beads/releases/tag/v1.3.3) and [tagged changelog, including rc.1 upgrade notes and known issues](https://github.com/gastownhall/beads/blob/v1.3.3/CHANGELOG.md): `.beads/.env` allowlisting, user-owned mail delegation and remote URL refusal are upstream boundaries. The plugin invokes the installed CLI with project context and explicit executor attribution; it does not parse these settings or implement mail/remote transport. `src/prime.ts` retains stdout and stderr on failures, unlike the cited Gas City stderr-only failure. No new environment or routing layer is warranted. +- [Exact-id deletion change](https://github.com/gastownhall/beads/blob/v1.3.4/cmd/bd/delete.go) and rules-compaction confirmation changes: the eleven typed operations in `src/continuity.ts` do not implement deletion or rules compaction. `src/workflows.ts` delivers scoped command guidance rather than parsing either changed JSON shape. Existing target/effect and authority guidance remains applicable; no prompt change is required. +- Database timestamp preservation, proxy startup, doctor cleanup, GitHub sync, history compaction and security dependency fixes remain CLI-owned. The adapter neither identifies proxy children from their config filename nor reimplements these internals. The changelog's remaining shared-endpoint credential, stale-local-copy sync and server reset-data issues remain upstream limitations; validation does not claim their repair, confinement or universal store safety. No downstream mitigation, credential/config change or store experiment was undertaken. +- [1.3.4 release](https://github.com/gastownhall/beads/releases/tag/v1.3.4) and [tagged environment loader/tests](https://github.com/gastownhall/beads/blob/v1.3.4/cmd/bd/envfile_test.go): the exact `GT_ROOT` key is admitted again, shell environment still wins, and other `GT_*` keys remain refused. The [loader](https://github.com/gastownhall/beads/blob/v1.3.4/cmd/bd/main.go) delegates admission upstream. This fixes the 1.3.3 rig notice/formula-discovery regression; no local workaround is needed. +- **Migration0062 warning retained:** 1.3.4 does not include the [events restoration fix](https://github.com/gastownhall/beads/issues/7039). Its [upgrade notes](https://github.com/gastownhall/beads/blob/v1.3.4/CHANGELOG.md) say not to run `bd compact` or `bd flatten` on clones that adopted that migration by pull/bootstrap or were cloned afterward: these can erase history needed for recovery. The plugin's compact command explicitly distinguishes semantic `bd admin compact` from Dolt-history squashing. No local migration guard, backfill, history compaction or live-store test is justified by this review. + +The [tagged comparison](https://github.com/gastownhall/beads/compare/v1.3.1...v1.3.4), including the loader, exact-id deletion and migration timestamp changes, was inspected against the prime/guard/typed-operation/workflow seams and artifact policy. No new migration file or embedded engine change is claimed by these patch releases; existing guard behavior and its 1.3.1 refusal fixtures remain unchanged. + +## Evidence and separate baselines + +[Scheduled run 42591441654](https://github.com/snarkipus/opencode-beads/actions/runs/42591441654) installed exact bd 1.3.4 (`7ef2fd05e: HEAD@7ef2fd05eb0c`), passed Check bd contract, and failed only Require review of new Beads releases because validated metadata was still 1.3.1. The retained receipt is `/opt/data/research/opencode-beads-1.3.4-status/latest-scheduled.log`. This exercises the existing disposable contract, including actual guard, prime, init/setup, typed operations and actor/outcome assertions. Native OpenCode qualification's 1.3.4 fixture is separate evidence and is not the release-note review. + +The artifact policy's Beads 1.3.1 comparison is historical integration evidence and stays intact. CLI validation does not advance `docs/claude-code-integration-inventory.json`, adopt upstream source, raise the supported minimum, change package 2.0.0, or publish a release. No real store was migrated or used as a fixture. + + +Local candidate verification on 2026-11-09 ran the unchanged `bun run test:bd-contract` once with exact bd 1.3.4 (`7ef2fd05e: HEAD@7ef2fd05eb0c`) and actor `codex:d1075a97`: **contract satisfied**. All stores used by that command were disposable under the environment scratch directory; the real tracker was not a fixture. The full receipt is `/opt/data/research/opencode-beads-upstream-maintenance-implementation/bd-contract.log`. No live Actions source-comparison execution is established by this CLI receipt. diff --git a/docs/beads-artifact-policy.md b/docs/beads-artifact-policy.md index 04f5874..2dfe3ae 100644 --- a/docs/beads-artifact-policy.md +++ b/docs/beads-artifact-policy.md @@ -2,6 +2,9 @@ Status: current policy for the published 2.x line. The package vendors no upstream Beads artifacts. It ships project-owned adaptations under `assets/oc-beads/` (one skill, 15 corpus resources, CODE_MODE and the adapted ADR, 17 explicit links), 30 native commands and the typed `beads` operations, rather than an upstream executable or host-specific integration. The installed `bd` CLI remains the execution and data owner, and `bd init` owns the shared `.agents/skills/beads`. The sections below are dated rationale and evidence: earlier stable exclusion and one-bead descriptions, and "candidate", "unpublished" or "withheld" wording, describe the state when they were written. Current behavior is defined by the canonical specs; see the [documentation index](README.md). +Current maintenance: the daily/manual `beads-contract` workflow defines independent tagged-CLI and advisory MAIN source-comparison jobs, with collector JSON and exit status in the Actions summary. Completed source comparisons are informational successes; unavailable evidence fails only the independent source job, never a product/release prerequisite. Adoption and reviewed source-baseline changes require explicit human disposition under the [canonical upstream-review spec](../openspec/specs/upstream-review/spec.md). The [1.3.4 CLI review](beads-1.3.4-review-2026-11-09.md) leaves the historical 1.3.1 integration comparison and marketplace-main inventory intact. The dated source-only acceptance below remains historical evidence, not proof of workflow execution. + + 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. diff --git a/docs/user-guide.md b/docs/user-guide.md index 684c844..008e286 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -148,10 +148,10 @@ Remove the `plugins` entry to unload the plugin. Cleanup aborts owned context/pr | OpenCode (historical beta.5) | `>=2.1.1 <2.2` | 2.1.1 (bounded accepted evidence) | | OpenCode (2.0.0) | `>=2.2.6 <2.3` | 2.2.6 (accepted native and genuine-model qualification) | | OpenCode (stable 1.0.2 historical) | 2.0.26 | 2.0.26 | -| `bd` CLI | 1.3.1 through 1.x | 1.3.1 | +| `bd` CLI | 1.3.1 through 1.x | 1.3.4 | | Bun (OpenCode host runtime) | >=1.3.14 | 1.4.2 | -Version 2.0.0 type-checks against `@opencode/plugin` 2.2.6 and declares optional peer range `>=2.2.6 <2.3`. Admission does not qualify future patches. Native qualification used Beads 1.3.4 as its fixture CLI; the supported-minimum contract evidence remains 1.3.1. See the [2.2.6 qualification](opencode-22-qualification-5rj2.md) and the [OpenCode integration reference](opencode-sdk-contract.md). The `bd` baseline was reviewed against Beads v1.3.1 (commit `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c`). +Version 2.0.0 type-checks against `@opencode/plugin` 2.2.6 and declares optional peer range `>=2.2.6 <2.3`. Admission does not qualify future patches. Native qualification used Beads 1.3.4 as its fixture CLI; the supported-minimum contract evidence remains 1.3.1. See the [2.2.6 qualification](opencode-22-qualification-5rj2.md) and the [OpenCode integration reference](opencode-sdk-contract.md). The current CLI baseline was reviewed against Beads v1.3.4; see the [2026-11-09 release review](beads-1.3.4-review-2026-11-09.md). The historical integration comparison remains Beads v1.3.1 (commit `c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c`), separately from CLI validation and the reviewed marketplace-main inventory. ## Tracking upstream Beads @@ -159,6 +159,8 @@ The package vendors nothing from upstream. It depends on the installed `bd` CLI [`scripts/bd-contract.ts`](../scripts/bd-contract.ts) checks a real `bd` against everything the plugin relies on. CI runs it against the validated release from `package.json` (`beads.validated`). The scheduled `beads-contract` workflow runs it daily against the latest stable Beads release and fails if that release breaks the contract or is newer than the validated one. When a new release appears, review its upgrade notes, then update `beads.validated` and the compatibility table. The [artifact policy](beads-artifact-policy.md) records why upstream artifacts are not vendored. +The daily/manual `beads-contract` workflow also has an independent source-comparison job using `scripts/check-beads-integration.ts`. Its step summary contains the collector JSON and exit status: immutable reviewed baseline/candidate commits, selected paths and source/diff links. Completed changed and unchanged comparisons succeed informationally; unavailable retrieval/comparison fails that job and is never reported as unchanged. Neither job depends on the other, and these maintenance checks are not product/release prerequisites. No GitHub issues, automatic adoption or baseline advancement occur. A maintainer must explicitly disposition changes before updating the reviewed inventory. Schedules use the default branch; an authorized manual run can select a published branch with `--ref` when the workflow exists on default. + ## Troubleshooting - **No Beads context:** Run `bd prime`. Install `bd` if missing or run `bd init` if the project has no Beads workspace. diff --git a/package.json b/package.json index a7aa2b5..f066b48 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "beads": { "minimum": "1.3.1", - "validated": "1.3.1" + "validated": "1.3.4" }, "peerDependencies": { "@opencode/plugin": ">=2.2.6 <2.3" diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 459e1c8..1598201 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -277,7 +277,7 @@ describe("documentation contracts", () => { expect(userGuide).toContain("| OpenCode (historical beta.5) | `>=2.1.1 <2.2` | 2.1.1 (bounded accepted evidence) |"); expect(userGuide).toContain(`| OpenCode (${packageManifest.version}) | \`>=2.2.6 <2.3\` | 2.2.6 (accepted native and genuine-model qualification) |`); expect(userGuide).toContain("| OpenCode (stable 1.0.2 historical) | 2.0.26 | 2.0.26 |"); - expect(userGuide).toContain("| `bd` CLI | 1.3.1 through 1.x | 1.3.1 |"); + expect(userGuide).toContain("| `bd` CLI | 1.3.1 through 1.x | 1.3.4 |"); expect(readme).toContain("| OpenCode | `>=2.2.6 <2.3` |"); expect(readme).not.toContain("The candidate has not passed live host qualification"); expect(readme).toContain("Upgrade OpenCode before changing the plugin pin"); @@ -344,7 +344,7 @@ describe("documentation contracts", () => { test("records the retired vendoring decision and the historical baselines", () => { expect(userGuide).toContain("The package vendors nothing from upstream."); - expect(userGuide).toContain("The `bd` baseline was reviewed against Beads v1.3.1"); + expect(userGuide).toContain("The historical integration comparison remains Beads v1.3.1"); expect(userGuide).toContain("c1c4b642ac1c08d8c828007a1c2f96e47e43ef7c"); expect(artifactPolicy).toContain("The package vendors no upstream Beads artifacts."); expect(artifactPolicy).toContain("| `agents/task-agent.md` | Retired in 1.0.0 (`opencode-beads-w5y`) |"); diff --git a/tests/release.test.ts b/tests/release.test.ts index 9bc6c65..f5fd567 100644 --- a/tests/release.test.ts +++ b/tests/release.test.ts @@ -249,6 +249,26 @@ describe("release artifact identity", () => { expect(workflow).not.toMatch(/npm publish\s*(?:\n|$)/); }); + test("keeps source comparison independent and reports even unavailable collection", async () => { + const workflow = await fs.readFile(join(projectDirectory, ".github/workflows/beads-contract.yml"), "utf8"); + expect(workflow).toContain("schedule:"); + expect(workflow).toContain("workflow_dispatch:"); + expect(workflow.match(/^ [a-z-]+:$/gm)?.filter(line => line === " contract:" || line === " source-comparison:")).toHaveLength(2); + expect(workflow).not.toMatch(/\bneeds:|continue-on-error:|(?:issues|checks):\s*write/); + const source = workflow.split(" source-comparison:")[1]!; + expect(source).toContain("bun scripts/check-beads-integration.ts"); + expect(source).toContain('exit "$status"'); + expect(source).toContain("if: ${{ always() }}"); + expect(source).toContain('>> "$GITHUB_STEP_SUMMARY"'); + expect(source).not.toMatch(/install-bd|test:bd-contract|::(?:error|warning)::/); + expect(workflow).toContain("Require review of new Beads releases"); + for (const name of ["ci.yml", "release.yml"]) { + const product = await fs.readFile(join(projectDirectory, ".github/workflows", name), "utf8"); + expect(product).not.toContain("source-comparison"); + expect(product).not.toContain("beads-contract.yml"); + } + }); + test("hardens workflow dependencies, credentials, caching, and update cooldowns", async () => { const workflowNames = ["ci.yml", "release.yml", "beads-contract.yml"]; const workflows = await Promise.all( diff --git a/tests/upstream-review.test.ts b/tests/upstream-review.test.ts index 3a7956b..5d93166 100644 --- a/tests/upstream-review.test.ts +++ b/tests/upstream-review.test.ts @@ -64,7 +64,7 @@ async function run(data = responses(), sourceInventory: unknown = inventory) { } /** Run the actual CLI with a temporary preload replacing only HTTP retrieval. */ -async function entrypoint(data: Record, args: string[] = []) { +async function entrypoint(data: Record, args: string[] = [], workflowText?: string) { const directory = await fs.mkdtemp(join(tmpdir(), "oc-upstream-entrypoint-")); temporaryDirectories.push(directory); const preload = join(directory, "fetch.ts"); @@ -75,17 +75,41 @@ globalThis.fetch = async (url) => { return new Response(JSON.stringify(data[String(url)]), { status: 200 }); }; `); - const proc = Bun.spawn([process.execPath, "--preload", preload, script, ...args], { + const workflow = (workflowText ?? await fs.readFile(resolve(import.meta.dir, "../.github/workflows/beads-contract.yml"), "utf8")) + .replace(/\r\n/g, "\n"); + const runBlocks = [...workflow.split(" source-comparison:")[1]!.matchAll(/ run: \|\n((?: .*\n|\n)+)/g)] + .map(match => match[1]!.replace(/^ /gm, "")); + expect(runBlocks).toHaveLength(2); + const [collect, summarize] = runBlocks as [string, string]; + // Exercise the literal workflow shell; only add the existing HTTP preload to its real collector invocation. + const command = collect.replace("bun scripts/check-beads-integration.ts", '"$BUN_EXECUTABLE" --preload "$FETCH_PRELOAD" "$COLLECTOR"'); + const summaryPath = join(directory, "summary.md"); + const env = { ...process.env, GH_TOKEN: "fixture-secret-never-print", RUNNER_TEMP: directory, + GITHUB_STEP_SUMMARY: summaryPath, BUN_EXECUTABLE: process.execPath, FETCH_PRELOAD: preload, COLLECTOR: script }; + const proc = Bun.spawn(args.length ? [process.execPath, "--preload", preload, script, ...args] : ["bash", "-e", "-c", command], { cwd: directory, stdout: "pipe", stderr: "pipe", - env: { ...process.env, GH_TOKEN: "fixture-secret-never-print" }, + env, }); - const [exitCode, stdout, stderr] = await Promise.all([ + const [exitCode, output, 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) }; + let stdout = output; + let summary = ""; + if (!args.length) { + stdout = await fs.readFile(join(directory, "beads-source.json"), "utf8"); + expect(await fs.readFile(join(directory, "beads-source.exit"), "utf8")).toBe(`${exitCode}\n`); + const reporting = Bun.spawn(["bash", "-e", "-c", summarize], { cwd: directory, env, stdout: "pipe", stderr: "pipe" }); + expect(await reporting.exited).toBe(0); + summary = await fs.readFile(summaryPath, "utf8"); + expect(summary).toContain(`Collector exit status: ${exitCode}`); + expect(summary).toContain(stdout); + expect(summary).not.toMatch(/::(?:error|warning)::|fixture-secret-never-print/); + } + expect((await fs.readdir(directory)).sort()).toEqual(args.length ? ["fetch.ts"] + : ["beads-source.exit", "beads-source.json", "fetch.ts", "summary.md"]); + return { exitCode, stdout, stderr, summary, report: JSON.parse(stdout) }; } describe("advisory marketplace MAIN source collector", () => { @@ -198,6 +222,18 @@ describe("advisory marketplace MAIN source collector", () => { expect((await run(responses(), { marketplace_main: { commit: "main" } })).exitCode).toBe(2); }); + test("actual workflow shell accepts CRLF line endings", async () => { + const workflow = await fs.readFile(resolve(import.meta.dir, "../.github/workflows/beads-contract.yml"), "utf8"); + const selected = JSON.parse(await fs.readFile(inventoryPath, "utf8")).marketplace_main.files + .map((file: { path: string; git_blob: string }) => ({ path: file.path, sha: file.git_blob, type: "blob", mode: "100644" })); + const data = responses(selected); + data[`${api}/git/trees/${baseTree}?recursive=1`] = { sha: baseTree, truncated: false, tree: selected }; + const result = await entrypoint(data, [], workflow.replace(/\r?\n/g, "\r\n")); + expect(result.exitCode).toBe(0); + expect(result.report.kind).toBe("completed"); + expect(result.report.status).toBe("unchanged"); + }); + 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");