PLAN — Ledger mutation CLI (ID-35.4)
PLAN — Ledger mutation CLI (ID-35.4)
Section titled “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.tsneeds no rewire (schema-free). Add a JSDoc provenance header to each (VENDORED from task-view @ v0.2.0-task-view) + aREADME.mdwith the re-vendor procedure.tscclean overlib/ledger/. - testStrategy:
bun tsc --noEmitclean over lib/ledger; a smoke import of each module resolves; the four files diff zero against task-view source through the driftnormalise().
{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(defaultdocs/reference); dispatch table; theCliResultenvelope (TECH §3) to stdout/stderr with exit 0/1;--pretty. Implementshow <ledger> <id>(read-only, detectSchema + find). Usage on unknown subcommand / missing args. - testStrategy:
show task 35prints 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 tosubtasks[](sibling superRefine enforced on re-parse). After write, runparseTaskListWithWarningsand 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-backlog→insertRecord(duplicate-id → exit 1);delete-backlog→removeRecord(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-mirrorsspawn ofscripts/regen-mirrors.sh. - testStrategy: promote success removes backlog id + inserts Task across two temp fixtures;
schema-invalid taskJson leaves BOTH unchanged + exit 1;
--dry-runleaves 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/*.tstopaths:+ a task-view→KH primitive-diff loop reusingnormalise()+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 testover the CLI suite green.
Sequencing & parallelism
Section titled “Sequencing & parallelism”- {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 sharedtask-list.jsonduring 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 difftouches 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.