Files
suaveplan/openspec/changes/diff-stringify/tasks.md
2026-05-27 23:29:53 +01:00

5.8 KiB

Tasks — Diff stringifyPatch human-readable patch formatter

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened.

0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

Audit Note (2026-05-02)

Minimal progress — DEFERRED, do not archive.

  • No packages/data/diff/src/utils/stringify-patch/ directory exists.
  • grep -rln "stringifyPatch\|stringify-patch" packages/data/diff/src/ returns zero hits.
  • bunx turbo lint typecheck test --filter=@suaveplan/diff is GREEN — but the new stringifyPatch surface is not implemented.
  • All 40 tasks remain to be done: error code DIFF.INVALID_PATCH_OP, stringify-patch.ts feature module, StringifyPatchOptions, OTel span event, subpath export, sibling .md, conformance + property tests.

1. Spec authoring

  • 1.1 Read ../suaveplan/packages/universal/diff/src/utils/utils.ts (legacy stringify at L237-L240)
  • 1.2 Read RFC 6902 §4 for op grammar reference
  • 1.3 Read openspec/specs/diff/spec.md and src/operations/ to confirm the canonical JsonPatch type
  • 1.4 Read openspec/AGENTS.md Sections 13 and 14
  • 1.5 Draft proposal.md from openspec/templates/proposal.md
  • 1.6 Fill in ## Source mapping table — every requirement cites a line range OR appears in the "no source line" table with justification
  • 1.7 Draft specs/diff/spec.md from openspec/templates/spec-delta.md
  • 1.8 Every requirement in the spec delta carries a **Verified by:** line naming concrete it(...) titles

2. Implementation

  • 2.1 Register error code DIFF.INVALID_PATCH_OP in src/errors/diff-error.ts
  • 2.2 Create src/utils/stringify-patch/ feature module with stringify-patch.ts, stringify-patch.types.ts, stringify-patch.schemas.ts, stringify-patch.md, index.ts
  • 2.3 Define StringifyPatchOptions in stringify-patch.types.ts
  • 2.4 Implement stringifyPatch(patch, options?) rendering one line per op:
    • add /path -> <value> — value via valueFormatter
    • remove /path
    • replace /path -> <value>
    • move /from -> /path
    • copy /from -> /path
    • test /path -> <value>
  • 2.5 Apply options.indent prefix per line and options.uppercaseOps casing transform
  • 2.6 Throw DiffError(DIFF.INVALID_PATCH_OP) on unknown op (defensive)
  • 2.7 Wire OTel span event diff.stringify-patch with opCount attribute
  • 2.8 Add subpath export ./utils/stringify-patch to package.json::exports
  • 2.9 Re-export stringifyPatch from the root barrel src/index.ts

3. Tests

  • 3.1 Unit tests at src/utils/stringify-patch/stringify-patch.test.ts — every operation type rendered; multi-op join with \n; empty patch returns empty string; default formatter uses JSON.stringify
  • 3.2 Spec-conformance tests at src/utils/stringify-patch/stringify-patch.spec-conformance.test.ts — valueFormatter invoked once per value-bearing op; uppercaseOps flips case; indent prefix prepended per line; DIFF.INVALID_PATCH_OP thrown on unknown op; OTel span event emitted with correct opCount; subpath import resolves
  • 3.3 Property test (fast-check): for any well-formed JsonPatch, stringifyPatch(patch).split('\n').length === patch.length
  • 3.4 Every it(...) carries { timeout: 5000 }

4. Documentation

  • 4.1 src/utils/stringify-patch/stringify-patch.md — sibling doc (≥200 words, basic + advanced examples including valueFormatter redaction usage, full API reference)
  • 4.2 README links ./utils/stringify-patch/stringify-patch.md under ## Modules
  • 4.3 Update CLAUDE.md @suaveplan/diff entry to mention stringifyPatch

5. Pre-archive verification (the 5-gate triple)

  • 5.1 bun run check — Biome 0 errors / 0 warnings / 0 info across the whole monorepo
  • 5.2 bunx turbo lint typecheck test --filter=@suaveplan/diff — green
  • 5.3 bunx turbo test:coverage --filter=@suaveplan/diff — 100/100/100/100
  • 5.4 bun scripts/openspec-conformance.ts @suaveplan/diff — exit 0, no error findings
  • 5.5 bunx turbo build --filter=@suaveplan/diff — green
  • 5.6 bunx turbo test --filter=...@suaveplan/diff — every dependent (undo, event-sourcing, crdt) passes

6. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 6.1 Generics threaded; expectTypeOf confirms stringifyPatch returns string (not string | undefined)
  • 6.2 Zod schema wired at StringifyPatchOptions boundary
  • 6.3 DIFF.INVALID_PATCH_OP thrown by at least one production path
  • 6.4 Strict-mode behaviors tested (uppercaseOps: false vs true; default vs custom valueFormatter)
  • 6.5 OTel span event emitted from production paths and asserted with a real in-memory TracerProvider
  • 6.6 Lifecycle dedup — N/A (stateless function)
  • 6.7 No retroactive tick — every box above reflects work done in this change

7. Archive

  • 7.1 Fold spec delta into openspec/specs/diff/spec.md
  • 7.2 Move openspec/changes/diff-stringify/ → openspec/archive/<yyyy-mm-dd>-diff-stringify/
  • 7.3 Refresh MANIFEST.md if dep set changed (no expected change)