Skip to content

feat(backends): add a beads backend behind the Store seam - #52

Open
LeonidShamis wants to merge 5 commits into
kunchenguid:mainfrom
LeonidShamis:feat/beads-backend
Open

feat(backends): add a beads backend behind the Store seam#52
LeonidShamis wants to merge 5 commits into
kunchenguid:mainfrom
LeonidShamis:feat/beads-backend

Conversation

@LeonidShamis

Copy link
Copy Markdown

What Changed

  • Added src/backends/beads.ts, a second Store implementation that drives the bd CLI via --json subprocesses with a per-invocation list cache, stores fields beads lacks in tasks_axi metadata, and rewrites a canonical-markdown mirror after every mutation; --backend beads and a [beads] config table (dir, bin) select and configure it through resolveConfig/resolveTasksContext.
  • Extracted backend-independent Task validation from src/backends/markdown.ts into the shared src/backends/normalize.ts, gated public-followup commands on a new backend capability, and made the generic single-id mv fallback reject moving a task that still blocks active dependents before it creates-then-removes.
  • Added a real-bd conformance suite (test/backends/beads.test.ts + test/beads-helpers.ts) that self-skips when bd is absent, and a Linux CI step that installs a pinned @beads/bd and sets REQUIRE_BD=1 so the suite cannot silently skip there; README and AGENTS.md document the new backend.

Risk Assessment

✅ Low: All seven accepted round-1 fixes are verified implemented and correct — the hold/--defer fix and the bd metadata replace-vs-merge semantics were confirmed empirically against real bd 1.2.2, the locking/rollback/CI-guard changes close their races as claimed, and only two info-level robustness notes remain.

Testing

Ran the 27-test beads conformance suite against a real bd 1.2.2 under REQUIRE_BD=1 (all pass), captured an end-to-end CLI transcript demonstrating the beads backend as a user experiences it — including raw bd evidence that hold --until no longer demotes an in-flight task — behaviorally verified the mv-fallback pre-check, word-bounded LOCKED classifier, fail-closed mutation locking, and REQUIRE_BD fail-loud guard, confirmed the create/prune rollback orderings landed as decided, and ran 185 targeted regression tests on the refactored markdown/config/command surfaces; everything passed and the worktree was left clean.

Evidence: CLI transcript: beads backend end-to-end (real bd 1.2.2)
## tasks-axi beads-backend end-to-end demo (real bd 1.2.2)

$ bd init --non-interactive --skip-hooks --skip-agents --prefix demo
(bd workspace created in /tmp/tasks-axi-beads-demo-gHKS)

\### add an in-flight task
$ node_modules/.bin/tsx bin/tasks-axi.ts add ship-login SHIP login flow revamp --repo web --priority 1 --start --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
ok: added ship-login (repo web) -> In flight
task:
  id: ship-login
  title: SHIP login flow revamp
  state: in_flight
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: task
  repo: web
  priority: 1
  created: 2026-08-29
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi done ship-login --pr <url> --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` when it ships
  - Run `tasks-axi block ship-login --by <other> --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to record a dependency

\### add a queued task blocked by it
$ node_modules/.bin/tsx bin/tasks-axi.ts add fix-token fix auth token refresh --blocked-by ship-login --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
ok: added fix-token -> Queued
task:
  id: fix-token
  title: fix auth token refresh
  state: queued
  blocked: yes
  blocked_by: ship-login
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: task
  repo: "-"
  priority: "-"
  created: 2026-08-29
  closed: "-"
  deps: "blocked-by:ship-login"
  links: none
  body: ""
help[2]:
  - Run `tasks-axi start fix-token --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to move it to in flight
  - Run `tasks-axi block fix-token --by <other> --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to record a dependency

\### add a docs task
$ node_modules/.bin/tsx bin/tasks-axi.ts add docs-rollout DOCS-ONLY rollout notes --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
ok: added docs-rollout -> Queued
task:
  id: docs-rollout
  title: DOCS-ONLY rollout notes
  state: queued
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: task
  repo: "-"
  priority: "-"
  created: 2026-08-29
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi start docs-rollout --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to move it to in flight
  - Run `tasks-axi block docs-rollout --by <other> --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to record a dependency

\### list everything
$ node_modules/.bin/tsx bin/tasks-axi.ts list --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
count: 3
tasks[3]{id,state,kind,repo,title}:
  ship-login,in_flight,task,web,SHIP login flow revamp
  fix-token,queued,task,"-",fix auth token refresh
  docs-rollout,queued,task,"-",DOCS-ONLY rollout notes
help[2]:
  - Run `tasks-axi show <id> --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` for full notes on a task
  - Run `tasks-axi ready --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to see unblocked queued work

\### hold the IN-FLIGHT task with --until (review fix: must stay In flight)
$ node_modules/.bin/tsx bin/tasks-axi.ts hold ship-login --reason captain review pending --until 2026-09-15 --kind captain --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
ok: hold ship-login -> held (captain, until 2026-09-15)
task:
  id: ship-login
  title: SHIP login flow revamp
  state: in_flight
  blocked: no
  blocked_by: none
  held: yes
  hold_reason: captain review pending
  hold_kind: captain
  hold_until: 2026-09-15
  kind: task
  repo: web
  priority: 1
  created: 2026-08-29
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi unhold ship-login --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to resume dispatch
  - Run `tasks-axi ready --include-held --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to review paused work

\### raw bd status after hold --until (must remain in_progress, not deferred)
$ bd show ship-login --json | grep -E 'status|id'
    "id": "ship-login",
    "status": "in_progress",

\### ready view with held group
$ node_modules/.bin/tsx bin/tasks-axi.ts ready --include-held --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
count: 1
ready[1]{id,state,kind,repo,title}:
  docs-rollout,queued,task,"-",DOCS-ONLY rollout notes
ready_public_followups: 0 delivery-ready obligations
help[1]:
  - Run `tasks-axi start <id> --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to dispatch one of these

\### complete the held task with a PR (--json machine signal)
$ node_modules/.bin/tsx bin/tasks-axi.ts done ship-login --pr https://github.com/acme/web/pull/42 --json --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
{
  "ok": true,
  "action": "done",
  "pruned": 0,
  "task": {
    "id": "ship-login",
    "title": "SHIP login flow revamp https://github.com/acme/web/pull/42",
    "state": "done",
    "kind": null,
    "repo": "web",
    "priority": 1,
    "created": "2026-08-29",
    "closed": "2026-08-29",
    "deps": [],
    "hold": {
      "reason": "captain review pending",
      "kind": "captain",
      "until": "2026-09-15"
    },
    "links": [
      {
        "kind": "pr",
        "url": "https://github.com/acme/web/pull/42"
      }
    ],
    "body": null,
    "blocked": false,
    "blocked_by": [],
    "held": false
  }
}

\### duplicate id is a structured CONFLICT error
$ node_modules/.bin/tsx bin/tasks-axi.ts add ship-login again --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
ok: add ship-login already exists -> Done
already: true
task:
  id: ship-login
  title: "SHIP login flow revamp https://github.com/acme/web/pull/42"
  state: done
  blocked: no
  blocked_by: none
  held: no
  hold_reason: captain review pending
  hold_kind: captain
  hold_until: 2026-09-15
  kind: task
  repo: web
  priority: 1
  created: 2026-08-29
  closed: 2026-08-29
  deps: none
  links: "pr:https://github.com/acme/web/pull/42"
  body: ""
help[1]:
  - Run `tasks-axi reopen ship-login --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to move it back to queued

\### list after completion
$ node_modules/.bin/tsx bin/tasks-axi.ts list --backend beads --file /tmp/tasks-axi-beads-demo-gHKS/backlog.md
count: 3
tasks[3]{id,state,kind,repo,title}:
  fix-token,queued,task,"-",fix auth token refresh
  docs-rollout,queued,task,"-",DOCS-ONLY rollout notes
  ship-login,done,task,web,"SHIP login flow revamp https://github.com/acme/web/pull/42"
help[2]:
  - Run `tasks-axi show <id> --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` for full notes on a task
  - Run `tasks-axi ready --backend=beads --file=/tmp/tasks-axi-beads-demo-gHKS/backlog.md` to see unblocked queued work

\### canonical markdown mirror (/tmp/tasks-axi-beads-demo-gHKS/backlog.md)
$ cat backlog.md
# Backlog

<!-- Read-only mirror generated by tasks-axi from the beads database (.beads). Do not hand-edit; mutate through tasks-axi or bd, then `tasks-axi render` to refresh. -->

## In flight

## Queued
- [ ] fix-token - fix auth token refresh blocked-by: ship-login (since 2026-08-29)
- [ ] docs-rollout - DOCS-ONLY rollout notes (since 2026-08-29)

## Done
- [x] ship-login - SHIP login flow revamp https://github.com/acme/web/pull/42 (repo: web) (priority: 1) (merged 2026-08-29) (hold: captain review pending) (hold-kind: captain) (hold-until: 2026-09-15)

\### persisted beads database state
$ bd list --all (in the demo workspace)
✓ ship-login ● P1 task SHIP login flow revamp https://github.com/acme/web/pull/42
○ docs-rollout ● P2 DOCS-ONLY rollout notes
○ fix-token ● P2 fix auth token refresh

--------------------------------------------------------------------------------
Total: 3 issues (2 open, 0 in progress)

Status: ○ open  ◐ in_progress  ● blocked  ✓ closed  ❄ deferred
Evidence: Canonical markdown mirror written by the beads backend
# Backlog

<!-- Read-only mirror generated by tasks-axi from the beads database (.beads). Do not hand-edit; mutate through tasks-axi or bd, then `tasks-axi render` to refresh. -->

## In flight

## Queued
- [ ] fix-token - fix auth token refresh blocked-by: ship-login (since 2026-08-29)
- [ ] docs-rollout - DOCS-ONLY rollout notes (since 2026-08-29)

## Done
- [x] ship-login - SHIP login flow revamp https://github.com/acme/web/pull/42 (repo: web) (priority: 1) (merged 2026-08-29) (hold: captain review pending) (hold-kind: captain) (hold-until: 2026-09-15)
Evidence: Review-fix behavioral checks: mv fallback, LOCKED classifier, lock fail-closed

bd stderr "Error: tst-1 is blocked by tst-2" -> AxiError code=UNKNOWN bd stderr "Error: dependency of type blocks already exists" -> AxiError code=UNKNOWN bd stderr "Error: database is locked" -> AxiError code=LOCKED bd stderr "Error: cannot write: another process holds the beads db" -> AxiError code=LOCKED $ tasks-axi mv blocker-b1 --to /tmp/beads-hard-target.md error: "Task &#34;blocker-b1&#34; is still blocking active tasks: dependent-d2" (VALIDATION_ERROR, exit 2; target file never created) $ tasks-axi add lk-one (with backlog.md.lock held) -> error: backlog is locked by another tasks-axi process (LOCKED); succeeds after release

## review-fix behavioral checks (beads backend, real bd)

$ tasks-axi add blocker-b1 the blocker
ok: added blocker-b1 -> Queued
task:
  id: blocker-b1
  title: the blocker
  state: queued
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: task
  repo: "-"
  priority: "-"
  created: 2026-08-29
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi start blocker-b1 --backend=beads --file=/tmp/tasks-axi-beads-hard-IWmc/backlog.md` to move it to in flight
  - Run `tasks-axi block blocker-b1 --by <other> --backend=beads --file=/tmp/tasks-axi-beads-hard-IWmc/backlog.md` to record a dependency
(exit=0)

$ tasks-axi add dependent-d2 the dependent --blocked-by blocker-b1
ok: added dependent-d2 -> Queued
task:
  id: dependent-d2
  title: the dependent
  state: queued
  blocked: yes
  blocked_by: blocker-b1
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: task
  repo: "-"
  priority: "-"
  created: 2026-08-29
  closed: "-"
  deps: "blocked-by:blocker-b1"
  links: none
  body: ""
help[2]:
  - Run `tasks-axi start dependent-d2 --backend=beads --file=/tmp/tasks-axi-beads-hard-IWmc/backlog.md` to move it to in flight
  - Run `tasks-axi block dependent-d2 --by <other> --backend=beads --file=/tmp/tasks-axi-beads-hard-IWmc/backlog.md` to record a dependency
(exit=0)

\### mv fallback refuses to strand a blocker (no duplicate in target)

$ tasks-axi mv blocker-b1 --to /tmp/beads-hard-target.md
error: "Task \"blocker-b1\" is still blocking active tasks: dependent-d2"
code: VALIDATION_ERROR
help[1]: "Unblock them first, e.g. `tasks-axi unblock dependent-d2 --by blocker-b1`"
(exit=2)
target file after refused mv:
(target backlog not created / task absent)

\### dependency-cycle bd error mentioning 'blocked' is NOT mislabeled LOCKED/busy

$ tasks-axi block blocker-b1 --by dependent-d2
error: "beads backend: `bd dep` failed — Error: line 1: adding dependency would create a cycle"
code: UNKNOWN
(exit=1)
## LOCKED classifier behavioral check (stub bd via the public bin seam)
bd stderr "Error: tst-1 is blocked by tst-2"
  -> AxiError code=UNKNOWN
bd stderr "Error: dependency of type blocks already exists"
  -> AxiError code=UNKNOWN
bd stderr "Error: database is locked"
  -> AxiError code=LOCKED
bd stderr "Error: cannot write: another process holds the beads db"
  -> AxiError code=LOCKED

## beads mutation serialization: fails closed when another process holds the lock
$ tasks-axi add lk-one "first task"   (with a fresh /tmp/tasks-axi-beads-lock-ZNSu/backlog.md.lock held by another process)
error: backlog is locked by another tasks-axi process
code: LOCKED
help[1]: Wait a moment and retry
(exit=1)

$ tasks-axi add lk-one "first task"   (lock released)
ok: added lk-one -> Queued
task:
  id: lk-one
  title: first task

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 2 infos
  • 🚨 src/backends/beads.ts:745 - Holding an in-flight task with --until silently demotes it to queued. update() pushes --defer &lt;date&gt; without re-asserting -s, and bd 1.2.2 flips an in_progress issue's status to deferred when --defer is set (verified in a scratch workspace); stateFromStatus maps deferred back to queued, so after tasks-axi hold &lt;id&gt; --reason r --until &lt;date&gt; on an in-flight task, the store and mirror report the task as queued while the markdown backend keeps it in flight — wrong state with exit 0, and the hold confirmation still prints the stale in_flight record. Fix (verified against bd 1.2.2): push -s BD_STATUS[task.state] in the same bd update whenever --defer is pushed; -s in_progress --defer &lt;date&gt; together keeps in_progress. Add an in-flight hold case to the conformance suite.
  • ⚠️ src/backends/beads.ts:611 - bd create --id X --force silently overwrites an existing issue (verified with bd 1.2.2: exit 0, title and foreign metadata replaced). The only duplicate guard is an unlocked check-then-act against a cached bd list snapshot, so two concurrent tasks-axi creates (or a tasks-axi create racing a bd-native create) with the same id destroy the loser without any error — unlike the markdown backend, whose mutations run under withLock and fail closed. --force is required for the id-prefix bypass, so consider serializing beads mutations under withLock(mirrorPath) to close the tasks-axi-vs-tasks-axi race, and documenting the residual bd-native race.
  • ⚠️ src/commands/state.ts:730 - The non-markdown single-id mv fallback (newly reachable via the beads backend) creates the task in the target markdown file and only then removes it from the source; BeadsStore.remove rejects a task that still blocks active dependents, so mv A --to other.md where A blocks an active B leaves A present in BOTH backlogs, and a retry after unblocking hits CONFLICT ('already exists in the destination backlog'), requiring manual cleanup. Pre-check active dependents (mirror requireNoActiveDependents) at the command layer before creating in the target so the failure happens before any write.
  • ⚠️ src/backends/beads.ts:895 - prune appends the full archive block for all surplus tasks before the per-task bd update --metadata archived loop and has no restore on failure. If a mid-loop bd invocation fails (e.g. db busy), the archive already contains entries for tasks that are still active, and a retry re-appends those tasks' lines, producing duplicate archive entries — the markdown backend's prune captures an archive restore point and rolls the append back on persist failure. Either flag first and append only for successfully-flagged tasks, or append per-task after its flag succeeds.
  • ⚠️ src/backends/beads.ts:624 - create() is three or more bd invocations (create, optional status update, one dep-add per dep) with no rollback: if the -s update or a dep add fails after bd create succeeded, the task exists half-created in bd, create() throws, and a retry reports CONFLICT 'already exists', stranding the user. bd create supports --deps &#39;type:id&#39; (verified in bd 1.2.2 help), which would fold the dep step into the create call and shrink the window; reasons could still ride in the initial --metadata payload.
  • ⚠️ src/backends/beads.ts:320 - The LOCKED classifier /lock|locked|busy|another process/i substring-matches 'blocked' and 'blocks' — routine words in a dependency tracker's error output (the bd edge type used by this backend is literally 'blocks'). A bd error like a dependency-cycle message mentioning 'blocks' gets mislabeled 'the beads database is busy' with a 'retry once the concurrent bd operation finishes' hint, steering agents to retry a deterministic failure. Use word-bounded / db-specific patterns, e.g. /database is locked|\block\b|\bbusy\b|another process/i (\block\b does not match 'blocked'/'blocks').
  • ℹ️ src/backends/beads.ts:385 - taskOrder runs issues.find(...) twice per comparison inside the sort comparator, making loadAll O(n² log n) over the full issue list on every read. Precompute a Map<string, created_at> from issues before sorting.
  • ℹ️ test/beads-helpers.ts:11 - The Linux 'real bd coverage' the CI commit adds is unenforced: BD_AVAILABLE silently skips the entire conformance suite when bd version fails, so a broken @beads/bd install or a PATH regression on the runner turns the suite into a silent skip while CI stays green. An opt-in guard (e.g. a REQUIRE_BD env var set in the Linux CI job that fails the suite when bd is absent) would make the guarantee fail loud.

🔧 Fix: harden beads backend holds, locking, rollback, and CI guard
2 infos still open:

  • ℹ️ src/backends/beads.ts:899 - addDep/removeDep write the dep edge and its reason metadata in two bd calls with no retry convergence for the second: if bd update --metadata fails after bd dep add succeeded (src/backends/beads.ts:899-904), the reason is lost and a retry returns the idempotent 'already' result (false) without backfilling it; symmetrically, removeDep (beads.ts:919-927) can leave a stale dep_reasons entry that silently resurrects if the same edge is later re-added without a reason. Writing the metadata before the edge mutation would make both operations retry-convergent (a reason for a nonexistent edge is ignored on read, so the intermediate state is invisible). Impact is limited to the optional reason annotation and requires a mid-operation bd failure.
  • ℹ️ src/backends/lock.ts:23 - The shared lock helper's 30s staleness threshold (src/backends/lock.ts:23) was tuned for millisecond markdown mutations; a beads mutation legitimately holding the mirror lock through several Dolt-backed bd invocations (60s timeout each, create chains 3+) can exceed 30s, so a contending process's LOCKED error shows the 'lock looks stale ... remove <path>.lock' hint while the holder is still alive. The hint's guard ('If no tasks-axi process is running') keeps it safe when followed; if desired, BeadsStore.mutate could pass a larger staleMs via withLock's existing options parameter.
✅ **Test** - passed

✅ No issues found.

  • REQUIRE_BD=1 pnpm vitest run test/backends/beads.test.ts — full beads conformance suite against real bd 1.2.2 (27/27 pass, including the hold --until in-flight regression)
  • End-to-end CLI demo in a scratch bd init workspace: tasks-axi add/list/hold --until/ready --include-held/done --pr --json with --backend beads, plus mirror backlog.md and bd list --all persisted state
  • bd show ship-login --json after hold --until on an in-flight task — status stays in_progress, not deferred
  • tasks-axi mv blocker-b1 --to &lt;target&gt; on the beads backend with an active dependent — refused with VALIDATION_ERROR and no target file created
  • LOCKED classifier behavioral check via the public bin seam with a stub bd: "blocked"/"blocks" stderr → UNKNOWN, "database is locked"/"another process" → LOCKED
  • Mutation lock fail-closed: mutation with a fresh backlog.md.lock held → LOCKED error, succeeds after release
  • REQUIRE_BD fail-loud: PATH shadowed with a broken bd + REQUIRE_BD=1 → suite fails with the actionable install error (exit 1) instead of skipping; inspected .github/workflows/ci.yml Linux job wiring
  • Code inspection: create() does status-before-deps with documented partial-create recovery; prune() flags each task in bd before appending its archive block
  • pnpm vitest run test/backends/markdown.test.ts test/commands/state.test.ts test/commands/public-followup.test.ts test/config.test.ts — 185/185 pass on the surfaces this change refactored
  • Markdown-backend parity check for duplicate add (already: true contract matches beads)
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 info
  • ℹ️ package.json - prettier is a devDependency but has no config file, no format script, and no CI check, and 7 files untouched by this change (src/backends/lock.ts, src/toon.ts, test/commands/home.test.ts, test/commands/setup.test.ts, test/config.test.ts, test/derive.test.ts, test/release-ci-exclusions.test.ts) fail prettier defaults. This change's own six new/modified files were formatted in this pass; the pre-existing drift was left alone as out of scope. A follow-up could either add a prettier config + check (and format the stragglers) or drop the dependency, so the intended formatter is unambiguous.
✅ **Push** - passed

✅ No issues found.

Adds BeadsStore, a Store implementation backed by the beads issue
tracker (https://github.com/gastownhall/beads) through the bd CLI:

- bd --json subprocesses with a per-invocation list cache; one
  `bd list --json --all` serves every read
- state maps queued/in_flight/done onto open/in_progress/closed;
  bd-native blocked/deferred statuses read back as queued because
  blocked/held stay derived above the seam
- kind, repo, priority, holds, canonical dates, dependency reasons,
  and meta live in a tasks_axi object in bd issue metadata; a hold
  with --until also sets bd's defer date
- links stay folded into the title prose exactly like the markdown
  backend, so deriveLinks works identically on both
- after every mutation the backend rewrites the configured markdown
  path as a read-only canonical mirror, byte-compatible with the
  markdown grammar, so direct backlog.md readers keep working
- prune archives surplus Done tasks to done-archive.md and hides them
  via a metadata flag while beads retains the full record
- public-followups are unsupported and now capability-gated in the
  command layer with a structured UNSUPPORTED error

Shared Task normalization moves from the markdown backend into
src/backends/normalize.ts so both backends validate identically.
Config grows a [beads] table (dir, bin); context resolution becomes a
backend map. Real-bd conformance tests skip when bd is absent.
Installs a pinned @beads/bd on the Linux CI leg so the BeadsStore suite
exercises the real CLI instead of self-skipping, and hardens the test
helper's bd init with --non-interactive --skip-hooks --skip-agents and an
explicit prefix so a CI environment cannot stall or write git hooks.
All three failing checks on PR kunchenguid#52 (CI, Guard generated files, Require
no-mistakes) concluded action_required with zero jobs executed: this is
GitHub's fork-PR approval gate for a first-time outside contributor, not
a code failure. Every CI step passes locally on Linux with bd@1.2.2
(build, lint, REQUIRE_BD=1 test: 456 passed / 1 skipped, skill check),
and the guard script's condition holds (no generated files touched).
Record the trap in AGENTS.md so future sessions verify locally and hand
the run approval to a maintainer instead of hunting for a code fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant