Skip to content

PLAN — Ledger mutation CLI (ID-35.4)

Task: ID-35. This Subtask: {35.4} PLAN — decompose implementation into Subtasks. Predecessors: RESEARCH, PRODUCT, TECH.

Six implementation Subtasks {35.5}–{35.10}, sibling-only deps. Built in one worktree (single worker), committed incrementally.


Subtask records (to splice into ID-35 subtasks[])

Section titled “Subtask records (to splice into ID-35 subtasks[])”

{35.5} — Vendor the 4 pure primitives into lib/ledger/ + provenance README

Section titled “{35.5} — Vendor the 4 pure primitives into lib/ledger/ + provenance README”
  • deps: [] · PRODUCT inv: 2, 3, 16(partial)
  • ALLOWED files: lib/ledger/{atomic-write,detect-schema,patch-apply,record-mutate}.ts, lib/ledger/README.md
  • details: Copy the four primitives from task-view v0.2.0 (packages/server/). Rewire @task-view/schemas/{task-list,roadmap,backlog}@/lib/validation/* (TECH §2); atomic-write.ts needs no rewire (schema-free). Add a JSDoc provenance header to each (VENDORED from task-view @ v0.2.0-task-view) + a README.md with the re-vendor procedure. tsc clean over lib/ledger/.
  • testStrategy: bun tsc --noEmit clean over lib/ledger; a smoke import of each module resolves; the four files diff zero against task-view source through the drift normalise().

{35.6} — CLI scaffold: argv parser + dispatch + JSON envelope + show

Section titled “{35.6} — CLI scaffold: argv parser + dispatch + JSON envelope + show”
  • deps: [5] · PRODUCT inv: 1, 4, 12
  • ALLOWED files: scripts/ledger-cli.ts, __tests__/scripts/ledger-cli.test.ts
  • details: Entry point: parse <subcommand> [args] [--flags]; resolve --ledger-dir (default docs/reference); dispatch table; the CliResult envelope (TECH §3) to stdout/stderr with exit 0/1; --pretty. Implement show <ledger> <id> (read-only, detectSchema + find). Usage on unknown subcommand / missing args.
  • testStrategy: show task 35 prints the record + exit 0; unknown subcommand → usage + exit 1; envelope is valid JSON on the correct stream.

{35.7} — Field-edit subcommands (flip-task, flip-subtask, append-journal, add-subtask, update-backlog)

Section titled “{35.7} — Field-edit subcommands (flip-task, flip-subtask, append-journal, add-subtask, update-backlog)”
  • deps: [6] · PRODUCT inv: 6, 7, 8, 13
  • ALLOWED files: scripts/ledger-cli.ts, __tests__/scripts/ledger-cli.test.ts
  • details: Shared write path: detectSchema → structuredClone → applyPatches([{fieldPath, newValue}]) → atomicWriteFile. append-journal reads current details, appends a timestamped <info added on …> block (ID-34 journal shape). add-subtask appends to subtasks[] (sibling superRefine enforced on re-parse). After write, run parseTaskListWithWarnings and print warnings[] to stderr (inv 13). All on temp-copied fixtures in tests.
  • testStrategy: each subcommand mutates the right field + re-parses; bad status / cross-Task dep / schema violation → exit 1, file unchanged; over-budget mutation warns + exit 0.

{35.8} — Record CREATE/DELETE subcommands (open-task, create-backlog, delete-backlog)

Section titled “{35.8} — Record CREATE/DELETE subcommands (open-task, create-backlog, delete-backlog)”
  • deps: [6] · PRODUCT inv: 9, 10
  • ALLOWED files: scripts/ledger-cli.ts, __tests__/scripts/ledger-cli.test.ts
  • details: open-task/create-backloginsertRecord (duplicate-id → exit 1); delete-backlogremoveRecord (record-not-found → exit 1). Serialise + atomicWriteFile on ok. Mirror-stale reminder (inv 14).
  • testStrategy: insert fresh id ok; re-insert dup → exit 1; delete existing removes exactly it; delete absent → not-found exit 1, file unchanged.

{35.9} — Promote (cross-ledger glue) + dry-run + mirror reminder + —regen-mirrors

Section titled “{35.9} — Promote (cross-ledger glue) + dry-run + mirror reminder + —regen-mirrors”
  • deps: [7, 8] · PRODUCT inv: 5, 11, 14, 15
  • ALLOWED files: scripts/ledger-cli.ts, __tests__/scripts/ledger-cli.test.ts
  • details: Implement promote per TECH §4 (validate→stage-both→commit-last, ADD first) reusing insertRecord/removeRecord/stageAtomicWrite/commitStagedWrite, citing ledger-transaction.ts as algorithm source. Wire --dry-run (in-memory mutation + print, no write) across all mutating subcommands. Mirror-stale reminder + --regen-mirrors spawn of scripts/regen-mirrors.sh.
  • testStrategy: promote success removes backlog id + inserts Task across two temp fixtures; schema-invalid taskJson leaves BOTH unchanged + exit 1; --dry-run leaves mtime unchanged.

{35.10} — Vendor-drift guard extension + integration round-trip dogfood

Section titled “{35.10} — Vendor-drift guard extension + integration round-trip dogfood”
  • deps: [5, 9] · PRODUCT inv: 16, + PRODUCT §4 self-bootstrap
  • ALLOWED files: .github/workflows/task-view-vendor-drift.yml, __tests__/scripts/ledger-cli-integration.test.ts
  • details: Extend the workflow per TECH §6 (add lib/ledger/*.ts to paths: + a task-view→KH primitive-diff loop reusing normalise() + TASK_VIEW_TAG, non-blocking). Integration test: full round-trip (open-task → flip-subtask → append-journal → show) on a temp-copied ledger fixture, proving the primitives wire end-to-end (PRODUCT §4 dogfood).
  • testStrategy: workflow YAML lints; integration round-trip green; bun run test over the CLI suite green.

  • {35.5} first (everything imports the vendored primitives).
  • {35.6} next (scaffold + envelope).
  • {35.7} and {35.8} parallel after {35.6} (both extend scripts/ledger-cli.ts — single worker serialises them to avoid self-conflict; logically independent).
  • {35.9} after both ({dry-run, promote} build on the field + record paths).
  • {35.10} last (drift guard + integration).

Follow-up subtasks (post-S267, parent-captured)

Section titled “Follow-up subtasks (post-S267, parent-captured)”

{35.11} — Scoped-write mode (re-emit only mutated records) — follow-up, non-blocking

Section titled “{35.11} — Scoped-write mode (re-emit only mutated records) — follow-up, non-blocking”
  • deps: [6] · status: pending
  • Why: The CLI currently serialises the WHOLE document via JSON.stringify(parsedData, null, 2), and Zod .parse() returns objects in schema-declared key order — so every write normalises key order across ALL records (OQ-35-B). This is correct and desirable once the CLI is the SOLE writer of a schema-consistent ledger, but too broad for the shared task-list.json during the parallel-cmux phase (it rewrites records owned by other terminals → cherry-pick conflicts). S267 worked around it with a key-order-preserving scoped splice (commit d6c4f4af).
  • Scope: add a --scoped (or default-on) write mode that re-emits only the mutated record(s) into the original file text, preserving untouched-record bytes (textual splice or a key-order-preserving serialiser). Pair with the one-time key-order normalisation pass that lands at the CLI-becomes-sole-writer transition (post-cmux, alongside the ID-34 {34.8} sweep).
  • testStrategy: a mutation to one record leaves all other records byte-identical (git diff touches only the mutated record’s lines).
  • Single-file contention — {35.7}/{35.8}/{35.9} all edit scripts/ledger-cli.ts; single-worker serial build avoids conflict.
  • Promote reimplementation fidelity — mitigated by reusing the vendored stage/commit primitives + inline citation of the source algorithm + a both-unchanged-on-failure test.
  • Drift-guard direction — schemas diff KH→task-view (release assets); primitives diff task-view→KH (tag clone). Both non-blocking warnings; documented in the workflow.