diff --git a/.seeds/issues.jsonl b/.seeds/issues.jsonl index 4d02cbf..0d2f3a5 100644 --- a/.seeds/issues.jsonl +++ b/.seeds/issues.jsonl @@ -327,3 +327,9 @@ {"id":"mulch-6145","title":"T4: trim redundant git init/commits from beforeEach","status":"open","type":"task","priority":2,"createdAt":"2026-05-28T19:15:24.041Z","updatedAt":"2026-05-28T19:16:03.243Z","description":"GOAL: Stop paying for real `git init` + git config + commits in beforeEach for tests that do not actually assert git behavior. Real git subprocesses (3-5 per test) dominate several files.\n\nWorking dir: cd /Users/jayminwest/Projects/os-eco/mulch\nINDEPENDENT: no dependency on other Ts.\n\nHOTSPOTS (run real execFileSync('git', ...) in setup):\n- test/commands/sync.test.ts (9.15s/19 tests; initGitRepo + gitCommitAll per test)\n- test/commands/config.test.ts (worktree blocks)\n- test/commands/record.test.ts, test/commands/prime.test.ts\n- test/utils/active-work.test.ts, test/utils/worktree.test.ts\n\nMETHOD:\n- Introduce a small helper (e.g. test/helpers/git.ts withGitRepo(dir)) using `git init -q`, single `git config` for user.name/email, and only commit when the test needs commits.\n- Move git setup OUT of blanket beforeEach and INTO the specific describe/it blocks that assert git-dependent behavior. Tests that only touch .mulch/ files should not init a repo at all.\n- Where a git repo is genuinely required for every test in a file (e.g. sync), keep it but minimize subprocess count (skip extra commits/config not asserted).\n\nVERIFICATION (must exit 0; test count unchanged):\n bun run lint && bun run typecheck\n bun test test/commands/sync.test.ts test/commands/config.test.ts test/commands/record.test.ts test/commands/prime.test.ts test/utils/active-work.test.ts test/utils/worktree.test.ts\n bun run test:ci && bun run report:timing # record reductions\n\nACCEPTANCE:\n- No test loses coverage of git-dependent behavior.\n- Per-test git subprocess count reduced; sync/record/prime/active-work file times drop.\n\nPITFALLS:\n- getMulchDir()/worktree resolution depends on a real .git — keep git for tests that exercise worktree/sync semantics.\n- Do not share a single mutable repo across tests that commit; isolation matters. Per-test temp repos stay, just leaner.","blocks":["mulch-bee4"]} {"id":"mulch-f7bf","title":"T5: adaptive parallel shard runner (core-count agnostic)","status":"open","type":"task","priority":1,"createdAt":"2026-05-28T19:15:54.049Z","updatedAt":"2026-05-28T19:16:03.298Z","description":"GOAL: `bun test` runs all files sequentially in ONE process, so wall-clock = sum of file times. Add a runner that splits test files into N balanced shards and runs them as concurrent `bun test ` processes. Shard count MUST be derived from the runtime environment — never hardcoded to any developer machine spec.\n\nWorking dir: cd /Users/jayminwest/Projects/os-eco/mulch\nINDEPENDENT build (blocks T6).\n\nFILES TO ADD:\n- ADD scripts/test-parallel.ts (run via Bun):\n - Determine shard count: `MULCH_TEST_SHARDS` env > `--shards N` arg > Math.max(1, os.availableParallelism?.() ?? os.cpus().length). Cap to number of test files.\n - Discover test files (glob test/**/*.test.ts).\n - Weight files by historical timing parsed from junit.xml when present (reuse parseJUnit from scripts/report-test-timing.ts), else by file size; greedily bin-pack into shards for balance.\n - Spawn one `bun test ` per shard concurrently (Bun.spawn), stream/prefix output, await all, exit non-zero if ANY shard fails.\n - SEQUENTIAL FALLBACK: if resolved shards <= 1, exec a single plain `bun test` (identical behavior to today).\n- ADD package.json script: \"test:parallel\": \"bun run scripts/test-parallel.ts\".\n- ADD scripts/test-parallel.test.ts: unit-test the pure pieces (shard-count resolution precedence, bin-packing balance, sequential fallback) — pure functions, no real spawning.\n\nDESIGN CONSTRAINTS:\n- Core-count agnostic + cloud-safe: works on a 1-vCPU box (falls back to sequential), a 2-4 vCPU cloud dev box, and large CI runners with the SAME code. No machine-specific constants.\n- Tests already isolate via mkdtemp unique prefixes, so cross-shard temp-dir collisions are not a concern.\n- Deterministic file->shard assignment given the same inputs (stable sort) for reproducibility.\n\nVERIFICATION (must exit 0):\n bun run lint && bun run typecheck\n bun test scripts/test-parallel.test.ts\n MULCH_TEST_SHARDS=1 bun run test:parallel # sequential fallback, full suite green\n MULCH_TEST_SHARDS=4 bun run test:parallel # parallel, same pass/fail outcome + lower wall-clock\n\nACCEPTANCE:\n- Same overall pass/fail result as `bun test` at any shard count.\n- Wall-clock scales down with shard count; sequential fallback exactly mirrors plain `bun test`.\n\nPITFALLS:\n- Aggregate exit code: fail if ANY shard fails (do not mask with tail/pipe).\n- os.availableParallelism may be undefined on older runtimes — guard with cpus().length.\n- Coverage merge across shards is handled in T6, but design spawn args so per-shard `--coverage --coverage-reporter=lcov` with distinct outfiles is possible.","blocks":["mulch-d61d","mulch-bee4"]} {"id":"mulch-d61d","title":"T6: wire CI to sharded runner + coverage merge","status":"open","type":"task","priority":2,"createdAt":"2026-05-28T19:15:54.152Z","updatedAt":"2026-05-28T19:16:03.352Z","description":"GOAL: Use the T5 shard runner in CI with an EXPLICIT, deterministic shard matrix (not derived from runner core count, so behavior is reproducible across runner sizes), merge per-shard coverage, and keep the timing report.\n\nWorking dir: cd /Users/jayminwest/Projects/os-eco/mulch\nDEPENDS ON: T5 (runner must exist).\n\nFILES TO EDIT:\n- .github/workflows/ci.yml:\n - Replace the single test:ci step with a small matrix (e.g. shard: [1,2,3,4], total 4) OR a single job invoking `MULCH_TEST_SHARDS=4 bun run test:parallel` — choose whichever keeps coverage merge simplest; document the choice in the job.\n - Each shard runs `bun test --coverage --coverage-reporter=lcov --coverage-reporter=...` writing a shard-unique lcov path, plus --reporter=junit to a shard-unique junit file.\n - Add a merge step: combine shard lcov files into coverage/lcov.info (concatenate/merge) and shard junit files into junit.xml so existing check:coverage + report:timing + report:quality keep working unchanged.\n - Keep uploading coverage/lcov.info and junit.xml artifacts (VAL-CROSS-003) and appending report:timing to GITHUB_STEP_SUMMARY.\n\nVERIFICATION (must exit 0):\n bun run lint && bun run typecheck\n # Locally emulate: run the runner with coverage, merge, then:\n bun run check:coverage\n bun run report:timing\n ls -la coverage/lcov.info junit.xml\n\nACCEPTANCE:\n- CI wall-clock for tests drops vs the previous single sequential run.\n- Merged coverage feeds check:coverage with no budget regression; merged junit feeds report:timing.\n- Matrix is explicit/deterministic — independent of the physical runner's core count.\n\nPITFALLS:\n- lcov merge: same source files appear in multiple shards' reports only if executed there; merge must union records, not double-count lines (use a real lcov merge approach, not naive concat if it corrupts totals — verify check:coverage numbers are sane).\n- junit merge must keep total tests = sum across shards for report:timing accuracy.\n- Do not regress check:all ordering or artifact-upload steps.","blockedBy":["mulch-f7bf"],"blocks":["mulch-bee4"]} +{"id":"mulch-355b","title":"nightwatch patrol: 2026-06-01","status":"open","type":"task","priority":3,"createdAt":"2026-06-01T10:07:44.079Z","updatedAt":"2026-06-01T10:08:16.494Z","labels":["patrol","nightwatch"],"plan_id":"pl-045b","blockedBy":["mulch-29ce","mulch-5e01","mulch-c7cb","mulch-1343","mulch-1b27"]} +{"id":"mulch-29ce","title":"Split test/commands/record.test.ts under the file-size budget","status":"open","type":"task","priority":2,"plan_step_index":0,"description":"\nStep 1 of plan pl-045b.\n\nParent seed: mulch-355b — nightwatch patrol: 2026-06-01\nPlan template: refactor\nPlan approach: Land each fix as an independent, surgical PR. Step 1 (ratchet) is the only urgent one because check:size is currently red — it splits test/commands/record.test.ts into per-feature files mirroring its top-level describe blocks (batch mode,…\n\nRun `sd plan show pl-045b` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-01T10:08:16.494Z","updatedAt":"2026-06-01T10:08:16.494Z","labels":["nightwatch","tests","ratchet"],"requires_plan":true,"blocks":["mulch-1b27","mulch-355b"]} +{"id":"mulch-5e01","title":"De-duplicate parseStrictPositiveInt across command files","status":"open","type":"task","priority":3,"plan_step_index":1,"description":"\nStep 2 of plan pl-045b.\n\nParent seed: mulch-355b — nightwatch patrol: 2026-06-01\nPlan template: refactor\nPlan approach: Land each fix as an independent, surgical PR. Step 1 (ratchet) is the only urgent one because check:size is currently red — it splits test/commands/record.test.ts into per-feature files mirroring its top-level describe blocks (batch mode,…\n\nRun `sd plan show pl-045b` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-01T10:08:16.494Z","updatedAt":"2026-06-01T10:08:16.494Z","labels":["nightwatch","refactor"],"requires_plan":true,"blocks":["mulch-1b27","mulch-355b"]} +{"id":"mulch-c7cb","title":"Delete unused src/utils/index.ts barrel","status":"open","type":"task","priority":3,"plan_step_index":2,"description":"\nStep 3 of plan pl-045b.\n\nParent seed: mulch-355b — nightwatch patrol: 2026-06-01\nPlan template: refactor\nPlan approach: Land each fix as an independent, surgical PR. Step 1 (ratchet) is the only urgent one because check:size is currently red — it splits test/commands/record.test.ts into per-feature files mirroring its top-level describe blocks (batch mode,…\n\nRun `sd plan show pl-045b` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-01T10:08:16.494Z","updatedAt":"2026-06-01T10:08:16.494Z","labels":["nightwatch","dead-code"],"requires_plan":true,"blocks":["mulch-1b27","mulch-355b"]} +{"id":"mulch-1343","title":"Unexport formatEvidence in src/utils/format-helpers.ts","status":"open","type":"task","priority":3,"plan_step_index":3,"description":"\nStep 4 of plan pl-045b.\n\nParent seed: mulch-355b — nightwatch patrol: 2026-06-01\nPlan template: refactor\nPlan approach: Land each fix as an independent, surgical PR. Step 1 (ratchet) is the only urgent one because check:size is currently red — it splits test/commands/record.test.ts into per-feature files mirroring its top-level describe blocks (batch mode,…\n\nRun `sd plan show pl-045b` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-01T10:08:16.494Z","updatedAt":"2026-06-01T10:08:16.494Z","labels":["nightwatch","dead-code"],"requires_plan":true,"blocks":["mulch-1b27","mulch-355b"]} +{"id":"mulch-1b27","title":"Release: run /release per .claude/commands/release.md","status":"open","type":"task","priority":3,"plan_step_index":4,"description":"\nStep 5 of plan pl-045b.\n\nParent seed: mulch-355b — nightwatch patrol: 2026-06-01\nPlan template: refactor\nPlan approach: Land each fix as an independent, surgical PR. Step 1 (ratchet) is the only urgent one because check:size is currently red — it splits test/commands/record.test.ts into per-feature files mirroring its top-level describe blocks (batch mode,…\n\nRun `sd plan show pl-045b` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n","createdAt":"2026-06-01T10:08:16.494Z","updatedAt":"2026-06-01T10:08:16.494Z","labels":["nightwatch","release"],"plan_id":"pl-045b","blockedBy":["mulch-29ce","mulch-5e01","mulch-c7cb","mulch-1343"],"blocks":["mulch-355b"]} diff --git a/.seeds/plans.jsonl b/.seeds/plans.jsonl index 257c676..841c833 100644 --- a/.seeds/plans.jsonl +++ b/.seeds/plans.jsonl @@ -9,3 +9,4 @@ {"id":"pl-7a81","seed":"mulch-e7f6","template":"refactor","status":"approved","revision":1,"sections":{"context":"Nightwatch patrol on 2026-05-28 found four small quality issues: (1) outcome.duration is parsed with bare Number.parseFloat in three CLI commands, so junk input silently writes NaN to .mulch/expertise/*.jsonl — violating the project's strict-numeric-flag-parsing convention (mx-5b9578, already followed by rank.ts); (2) fatal-error console.error sites have drifted — some use chalk.red('Error: ...'), but the common 'No .mulch/ directory found.' message is plain text in delete/ready/archive/query/prime/edit/restore/search, plus a few generic catch-block 'Error: ${err.message}' lines that are also plain — readability across call sites suffers; (3) the Claude setup recipe parses settings.json with bare JSON.parse and no path context, so a corrupted file throws an unhelpful 'Unexpected token …' error inconsistent with the file:line context convention used by readExpertiseFile (mx-7c199c); (4) the deprecated 'mulch update' command prints a non-JSON warning to stderr even when --json is set, breaking the JSON-output contract every other command honors. Quality gates (bun test, bun run lint, bun run typecheck) are green at HEAD — these are not regressions, just drift that has accumulated.","behavior_invariant":"All existing tests continue to pass. Public CLI surface, command names, flags, and JSON schema shapes are unchanged. Successful command paths produce byte-identical stdout/stderr to before for every input that was valid before. The only observable changes are: (a) invalid --outcome-duration / --duration inputs now exit non-zero with a clear error instead of silently writing NaN; (b) error lines that were plain text become chalk.red-wrapped to match peer commands (no message wording changes); (c) corrupted Claude settings.json yields an error mentioning the file path instead of raw JSON parser output; (d) 'mulch update --json' emits the standard JSON error envelope. No new commands, no new flags, no dependency changes, no public API signature changes.","approach":"Targeted, isolated fixes. Each step touches a small set of files and lands as a single PR. Independent steps (parsing, error-coloring, JSON.parse hardening, update.ts JSON output) can ship in any order; the release step is last and blocked by all four.","steps":[{"title":"Validate --outcome-duration / --duration with strict regex parser","description":"Files: src/commands/record.ts (line ~806), src/commands/edit.ts (line ~128), src/commands/outcome.ts (line ~124). All three currently call `Number.parseFloat(options.outcomeDuration as string)` (or options.duration) with no validation, so `--outcome-duration foo` writes `\"duration\": null` or NaN into the JSONL Outcome object. This violates the strict-numeric-flag-parsing convention (mulch record mx-5b9578) which rank.ts already follows. Fix: introduce a shared helper (e.g. parseStrictNonNegativeNumber in src/utils/format-helpers.ts or a new src/utils/numeric-flags.ts) mirroring the regex+Number() pair in src/commands/rank.ts:31-37, and call it from all three sites. On parse failure, print `Error: --outcome-duration must be a non-negative number (got \"\")` (chalk.red in non-JSON, outputJsonError in JSON mode), set process.exitCode = 1, and return without writing the record. Add unit tests under test/commands/record.test.ts (and edit/outcome equivalents) that pass a bogus value and assert exit code 1 + no JSONL mutation. Do NOT change the flag name or add new flags.","labels":["nightwatch"],"blocks":[]},{"title":"Normalize fatal-error coloring across commands","description":"Plain-text `console.error(\"Error: ...\")` and `console.error(\\`Error: ${...}\\`)` calls have drifted from the chalk.red(`Error: ...`) idiom used by most commands. Specifically: src/commands/delete.ts:303,309; src/commands/ready.ts:177,183; src/commands/archive.ts:201,207; src/commands/query.ts:91,203,209; src/commands/prime.ts:150,162,183,255,283,514,675,681; src/commands/onboard.ts:369; src/commands/edit.ts:202,208; src/commands/restore.ts:154,160; src/commands/search.ts:316,322; src/commands/learn.ts:174; src/commands/move.ts:314,317; src/commands/rank.ts:166,173. Wrap each with chalk.red so non-JSON output is visually consistent with peer commands (delete.ts:60, archive.ts uses it earlier, etc.). Do not change message wording or add new error paths. Verify by running `bun test` and a quick `grep -rn 'console.error(\"Error:\\|console.error(\\`Error:' src/` — should return no matches.","labels":["nightwatch"],"blocks":[]},{"title":"Add file-path context to Claude settings.json parse errors in setup.ts","description":"src/commands/setup.ts at lines 199, 234, and 266 call `JSON.parse(raw) as ClaudeSettings` on the contents of `~/.claude/settings.json` (resolved via claudeSettingsPath(cwd)) with no try/catch, so a corrupted/edited settings file throws a bare `SyntaxError: Unexpected token ...` with no path or hint. The project's convention is to wrap JSONL parse errors with file:line context (mulch record mx-7c199c, see src/utils/expertise.ts:67-76). Fix: extract a small helper inside setup.ts (e.g. `parseClaudeSettings(path: string, raw: string): ClaudeSettings`) that wraps JSON.parse in try/catch and rethrows `Error(`Malformed Claude settings at ${path}: ${err.message}. Edit or remove the file to recover.`)`. Use it from install(), check(), and remove(). No behavior change on the happy path. Add a test in test/commands/setup.test.ts that writes a corrupt settings.json, invokes the claude recipe's check(), and asserts the error message mentions the file path.","labels":["nightwatch"],"blocks":[]},{"title":"Honor --json in the deprecated 'mulch update' command","description":"src/commands/update.ts unconditionally calls `printWarning(...)` and exits with code 1, even when the global --json flag is set. Every other command branches on `program.opts().json === true` and emits a structured envelope via outputJson/outputJsonError. Fix: in registerUpdateCommand's action, capture `const jsonMode = program.opts().json === true;` and call `outputJsonError(\"update\", \"'mulch update' is deprecated. Use 'mulch upgrade' instead.\")` when jsonMode, otherwise the existing printWarning. Keep `process.exitCode = 1` either way. Add a one-line test verifying the JSON envelope shape under --json. No new flags, no rename, the command stays hidden.","labels":["nightwatch"],"blocks":[]},{"title":"Release: run /release per .claude/commands/release.md","description":"Final patrol step. After steps 1-4 land, run /release as documented in .claude/commands/release.md to cut and publish the patch release containing these fixes. Bumps version in package.json and src/cli.ts in lockstep (mulch record mx-5ab2bc), updates CHANGELOG.md (mx-900a53), and runs the usual release ritual.","labels":["nightwatch"],"blocks":[]}],"acceptance":["bun test passes (all 1460+ existing tests plus the new tests added in steps 1, 3, and 4).","bun run lint and bun run typecheck both exit 0.","`grep -rn 'console.error(\"Error:\\|console.error(\\`Error:' src/` returns no matches after step 2.","Running `ml record cli --type pattern --name x --description y --outcome-status success --outcome-duration foo` exits non-zero with a 'must be a non-negative number' error and does NOT append a record to .mulch/expertise/cli.jsonl.","Writing invalid JSON to ~/.claude/settings.json and running `ml setup claude --check` produces an error message that contains the settings.json path, not a bare 'Unexpected token'.","`ml update --json` writes a JSON object with {\"success\": false, \"command\": \"update\", \"error\": ...} to stderr and exits non-zero.","No new CLI commands, flags, or dependencies are introduced. Public API and JSON schema shapes are unchanged."]},"children":["mulch-5b9c","mulch-2a17","mulch-1b36","mulch-a8cd","mulch-201a"],"createdAt":"2026-05-28T10:06:37.862Z","updatedAt":"2026-05-28T10:06:37.862Z","name":"nightwatch patrol fixes 2026-05-28"} {"id":"pl-4fb9","seed":"mulch-3204","template":"feature","status":"done","revision":1,"sections":{"context":"Mulch is the structured-expertise CLI tool in the os-eco ecosystem. The os-eco L5 uplift mission targets Level 5 (≥80% pass rate, matching warren's ceiling per docs/l5-uplift/library/warren-ceiling.md) for every sub-repo by porting templates/l5-toolkit/ (the warren-derived toolkit, finalized 2026-05-27) and authoring repo-specific artifacts. Mulch has NOT started uplift yet — this plan ports the toolkit from scratch and lifts mulch to ~50/63 ≈ ~80% → Level 5 ✓ per the per-repo expected math in docs/l5-uplift/library/warren-ceiling.md. The 6 children of this plan are self-contained: an agent reading `sd show ` has the full target dir, files-to-add/edit, source-of-truth refs, verification commands, rubric criteria closed, commit guidance, and mulch-specific pitfalls without needing the parent mission context.","approach":"Port the os-eco L5 toolkit (templates/l5-toolkit/) into mulch/ in six focused commits, in dependency order. Each child seed targets one feature from the mission's seed-plan-source/mulch.json. Mulch hasn't started, so the first child (mulch-repo-scaffolding) lays down all the baseline configs + devcontainer + env + gitignore + biome extension; subsequent children layer ratchet scripts + budgets, coverage + reporters, AGENTS.md + validator + RUNBOOK + skill, pino logger + governance hooks (pre-commit + dependabot cooldown + labels.yml), and finally the check:all aggregator + CI workflow extension. Each child commits one feature to mulch main; do NOT push (user pushes manually). After the final child seals, the user runs /readiness-report from mulch/ to confirm Level 5 ≥80%.","steps":[{"title":"mulch-repo-scaffolding: port baseline configs + devcontainer + env + gitignore + biome/knip/jscpd/bunfig"},{"title":"mulch-ratchet-scripts: port check-file-sizes + check-debt-markers + budgets"},{"title":"mulch-test-coverage-and-reporters: wire coverage + test:ci + report-test-timing + report-quality-metrics"},{"title":"mulch-agents-md-validator-runbook-skills: author AGENTS.md + validator + RUNBOOK + mulch-expertise skill"},{"title":"mulch-pino-logger-and-governance: add pino + redact + pre-commit hook + dependabot cooldown + labels.yml"},{"title":"mulch-check-all-and-ci-finalize: wire check:all aggregator + extend ci.yml + final verification"}],"acceptance":["VAL-MULCH-002 (naming_consistency), VAL-MULCH-003 (cyclomatic_complexity), VAL-MULCH-005 (duplicate_code_detection), VAL-MULCH-007 (unused_dependencies_detection), VAL-MULCH-012 (devcontainer), VAL-MULCH-017 (env_template), VAL-MULCH-018 (gitignore_comprehensive) closed by step 1","VAL-MULCH-004 (large_file_detection), VAL-MULCH-006 (tech_debt_tracking) closed by step 2","VAL-MULCH-008 (test_performance_tracking), VAL-MULCH-009 (test_coverage_thresholds), VAL-MULCH-015 (code_quality_metrics) closed by step 3","VAL-MULCH-010 (agents_md), VAL-MULCH-011 (agents_md_validation), VAL-MULCH-016 (runbooks_documented), VAL-MULCH-020 (skills) closed by step 4","VAL-MULCH-001 (pre_commit_hooks), VAL-MULCH-013 (structured_logging), VAL-MULCH-014 (log_scrubbing), VAL-MULCH-019 (min_release_age), VAL-MULCH-021 (issue_labeling_system) closed by step 5","VAL-MULCH-FINAL: post-mission /readiness-report from mulch/ shows Level 5 / 5 with pass rate ≥80%; each VAL-MULCH-001..021 criterion at 1/1","From cd /Users/jayminwest/Projects/os-eco/mulch: bun install && bun run lint && bun run typecheck && bun test && bun run check:all all exit 0 after step 6"]},"children":["mulch-409a","mulch-3fda","mulch-8cd7","mulch-d919","mulch-3fd5","mulch-6048"],"createdAt":"2026-05-28T03:22:52.773Z","updatedAt":"2026-05-28T18:57:32.181Z","name":"L5 uplift: mulch"} {"id":"pl-099d","seed":"mulch-5acb","template":"feature","status":"done","revision":1,"sections":{"context":"Mulch is one of five sub-repos targeted by the os-eco L5 readiness uplift mission. Prior /readiness-report audits placed mulch below Level 5 because it lacks the ratchet scripts, governance artifacts, validators, structured logger, and CI wiring that warren (the exemplar at Level 5) ships. The orchestrator has already finalised templates/l5-toolkit/ at the os-eco root; this work ports that toolkit into mulch so the next /readiness-report audit can certify Level 5 (≥80% pass rate) and close every VAL-MULCH-* assertion in docs/l5-uplift/validation-contract.md. Mulch's test directory layout (test/ rather than src/) and existing debug/output channels make this slightly different from the canopy and plot ports already shipped — those deltas are documented in docs/l5-uplift/library/porting-playbook.md.","approach":"Port the L5 toolkit (templates/l5-toolkit/) into mulch in six dependent stages, matching warren's ceiling and closing the VAL-MULCH-* assertions enumerated in docs/l5-uplift/validation-contract.md. Each stage lands in one commit on mulch's working branch (no push) and is gated by bun install / lint / typecheck / test exit 0. Special handling for mulch's test/ directory layout (no bunfig change needed for discovery) and the requirement to route the existing debug/output channels through pino in stage 5 instead of adding a parallel logger. Final stage wires check:all + extends ci.yml and asks the user to run /readiness-report.","steps":[{"title":"mulch-repo-scaffolding"},{"title":"mulch-ratchet-scripts"},{"title":"mulch-test-coverage-and-reporters"},{"title":"mulch-agents-md-validator-runbook-skills"},{"title":"mulch-pino-logger-and-governance"},{"title":"mulch-check-all-and-ci-finalize"}],"acceptance":["All six child seeds exist as dependent open seeds linked to parent mulch-5acb.","Each child seed has a self-contained description (>3000 chars) covering goal, working dir, references to templates/l5-toolkit/ + docs/l5-uplift/library/, files to add/edit, exact verification commands, acceptance rubric criteria, commit guidance, and pitfalls.","After all six children complete, bun install && bun run lint && bun run typecheck && bun test exit 0 from /Users/jayminwest/Projects/os-eco/mulch.","bun run check:all exits 0 after the final child lands.","The agent-readiness auditor (/readiness-report) confirms mulch reaches Level 5 (≥80% pass rate) closing each VAL-MULCH-001..021 plus VAL-MULCH-FINAL."]},"children":["mulch-3744","mulch-5c6f","mulch-f8d1","mulch-cbf4","mulch-9258","mulch-a11b"],"createdAt":"2026-05-28T13:14:50.739Z","updatedAt":"2026-05-28T17:38:48.524Z","name":"L5 uplift: mulch"} +{"id":"pl-045b","seed":"mulch-355b","template":"refactor","status":"approved","revision":1,"sections":{"context":"Code-patrol scan on 2026-06-01 surfaced four small quality issues that don't change public API: (1) `bun run check:size` is currently failing — test/commands/record.test.ts is 2939 lines, over the frozen 2750-line budget, so the ratchet gate is red; (2) `parseStrictPositiveInt` (and its `POSITIVE_INT_RE`) is hand-rolled in four command files (compact.ts, prime.ts, rank.ts, ready.ts) even though src/utils/numeric-flags.ts already exports a canonical version — pure duplication that future bugfixes would need to apply four times; (3) src/utils/index.ts is a barrel file that no source or test imports — knip flags it as unused, and grep across src/ and test/ confirms no consumer; (4) `formatEvidence` in src/utils/format-helpers.ts is exported but only referenced inside the same file, so the export is dead surface. All four are internal cleanups, no API surface change, no dependency change.","behavior_invariant":"After the refactor: (a) all bun-test cases still pass; (b) `bun run lint`, `bun run typecheck`, `bun run check:size`, `bun run check:debt`, `bun run check:agents`, and `bun run check:coverage` all exit 0; (c) the `ml` CLI behaves identically — same parsing of --limit / --budget / --max-records / --min-group-size flags (same rejection of \"10abc\", \"3.7\", \"\", negatives, etc.), same record/prime/rank/ready output, same evidence formatting in prime/status output; (d) the public API exports from src/index.ts and src/api.ts are unchanged.","approach":"Land each fix as an independent, surgical PR. Step 1 (ratchet) is the only urgent one because check:size is currently red — it splits test/commands/record.test.ts into per-feature files mirroring its top-level describe blocks (batch mode, validation hints, per-domain rules, disabled-types, dir_anchors, etc.) without changing any test bodies. Steps 2–4 are independent dead-code / duplication cleanups that can land in any order. The final step runs /release per .claude/commands/release.md once every other step is merged.","steps":[{"title":"Split test/commands/record.test.ts under the file-size budget","type":"task","priority":2,"blocks":[5],"labels":["nightwatch","tests","ratchet"],"plan_template":"refactor"},{"title":"De-duplicate parseStrictPositiveInt across command files","type":"task","priority":3,"blocks":[5],"labels":["nightwatch","refactor"],"plan_template":"refactor"},{"title":"Delete unused src/utils/index.ts barrel","type":"task","priority":3,"blocks":[5],"labels":["nightwatch","dead-code"],"plan_template":"refactor"},{"title":"Unexport formatEvidence in src/utils/format-helpers.ts","type":"task","priority":3,"blocks":[5],"labels":["nightwatch","dead-code"],"plan_template":"refactor"},{"title":"Release: run /release per .claude/commands/release.md","type":"task","priority":3,"blocks":[],"labels":["nightwatch","release"]}],"acceptance":["`bun run check:size` exits 0 (test/commands/record.test.ts no longer exceeds its budget; no other file is over budget either).","`bun test` reports the same number of passing assertions/tests as before the split (1494 tests / 3849 expect calls — adjust only if a step intentionally adds coverage).","`bun run lint` and `bun run typecheck` exit 0.","`bun run check:debt`, `bun run check:agents`, and `bun run check:coverage` exit 0.","`grep -rn \"POSITIVE_INT_RE\\|function parseStrictPositiveInt\" src/commands/` returns no matches; each former call site imports parseStrictPositiveInt from src/utils/numeric-flags.ts and CLI behavior on `ml rank --limit 10abc`, `ml ready --limit 3.7`, `ml prime --budget 0`, `ml compact --min-group-size foo` is unchanged (still rejected with the same error path).","`grep -rn \"from.*utils/index\\|from.*\\\"\\.\\./utils\\\"\\|from.*\\\"\\.\\./\\.\\./utils\\\"\" src/ test/` returns no matches and src/utils/index.ts is removed (or kept only if a consumer is found and documented).","`grep -rn \"formatEvidence\" src/ test/` shows only intra-file references in src/utils/format-helpers.ts (no export, no external import).","Public API surface in src/index.ts / src/api.ts is byte-identical except for any exports the steps explicitly remove (none are planned)."]},"children":["mulch-29ce","mulch-5e01","mulch-c7cb","mulch-1343","mulch-1b27"],"createdAt":"2026-06-01T10:08:16.494Z","updatedAt":"2026-06-01T10:08:16.494Z","name":"Nightwatch patrol 2026-06-01: ratchet + dead-code cleanup"}