fix(scaffold): copy templates/ in Claude scaffold mode so record helpers resolve (#109)#119
fix(scaffold): copy templates/ in Claude scaffold mode so record helpers resolve (#109)#119Kyarha wants to merge 8 commits into
Conversation
…ers resolve Claude scaffold mode copied `skills/` and `hooks/` into `.claude/` but not `templates/` — only the Codex path copied templates. jig's record helpers resolve their template as `CLAUDE_PLUGIN_ROOT`, else `parents[2]/templates/`, which for a copied helper at `.claude/skills/jig-<name>/<helper>.py` lands on `<project>/.claude`. With no plugin root to fall back on, a scaffolded project could not seed a record home or open an ADR. `copy_machinery(host="claude")` now copies `templates/` into `.claude/templates/`, mirroring `_copy_codex_templates`. The helpers' existing fallback resolves with no change to any helper's template *resolution*. This is the maintainer's pick — option (a) of the three posted to him on #109 and parked in refinement-todo. Recorded as ADR-0038; implemented by spec 095. Closes bug 012's `## Remaining risk` (via an ADR-0010 amendment, since the record is closed) and resolves the refinement-todo entry. Review found the family is four helpers, not two: `workflow.py` (slice-template) and `memory.py` (people.md) read the tree through the same shape, so an allowlist of the two templates in evidence would have left two consumers broken. See the slice deviation log. Tests: red witnessed against a real `--with-machinery` install with CLAUDE_PLUGIN_ROOT unset (both helpers), green after the copy. Full suite on CI's matrix: 3.12 3508 -> 3517 (+9, all new); 3.9 unchanged at 3361 OK (test_scaffold_mode.py is skipped whole below 3.11 by its own tomllib load_tests gate — noted in the PR). Co-Authored-By: Claude Opus 4.8 <[email protected]>
…te order Post-implementation review (compliance + craft + arch + frame-critique, then a second round of each, plus reconciliation) found real defects. What changed: - workflow.py `_render_stub_slice` read its template at the default encoding inside `except OSError`. This slice makes that file reachable in scaffold mode for the first time, so under LANG=C a UnicodeDecodeError (a ValueError) would escape the fallback and CRASH where the old behaviour degraded cleanly. Fixed — a regression this slice would otherwise have introduced. - The template-reading family is FIVE helpers, not the two the spec assumed: `migrate.py`, `memory.py` and `workflow.py` resolve templates the same way. Found only by review. That is the argument for copying the whole tree rather than an allowlist of the two templates in evidence, which would have shipped looking correct and left consumers broken. - ADR-0038 asserted two claims as fact that were false: that `copy-machinery` retrofits existing projects (it cannot — a scaffolded project can't repair itself from inside), and that Codex's copy was an unexplained asymmetry. Both probed and corrected. - An AC4 test pinned its edge case against two files that never take the rewrite branch — the round-trip had zero coverage. Split and made real. - `status: Accepted` had been hand-stamped onto ADR-0038, bypassing the frame-critique gate that gates exactly this ADR. Reverted to Proposed and accepted through `adr.py accept` on a passing round-4 verdict; the order is frame-critique -> accept -> index -> resolve-todo. - The copy-machinery contract has three surfaces (SKILL.md, the hardcoded Codex render, migrate.py's --help + report text). All three now name templates. Reviews recorded under docs/specs/095-scaffold-template-copy/reviews/ and docs/decisions/reviews/. Slice DONE; spec 095 closed. Deviation log §1-§9 carries the full accounting; three out-of-scope follow-ups inboxed. Suite: 3.12 3508 -> 3518 OK (+10 new); 3.9 3361 OK unchanged. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Resolve docs/specs/README.md status-board conflict: keep main's three current 094-capture-hygiene rows (DONE, from #116) and append the 095-01 row. The branch carried a stale `094-01 — tbd` placeholder that predated 094's real slices; main's rows win, plus 095. Co-Authored-By: Claude Opus 4.8 <[email protected]>
#118 landed the 095 reservation stub on main (spec.md placeholder + slice-01-tbd.md). Resolve against this branch's real work: - spec.md: keep the branch's full drafted spec over the stub. - slice-01-tbd.md: removed — superseded by slice-01-claude-scaffold-templates.md. Branch now contains current main; PR is conflict-free. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
Second-opinion review pass + conflict resolution (via Review: No blockers. The whole-tree copy is well-justified (the family is five helpers, not the two in the bug report), all three packaged copies ( Resolved the merge conflict against current
Branch now fully contains Still a draft awaiting your validation of option (a) — not un-drafting or merging. |
…skipping The file-wide `load_tests` hook returned an empty suite below Python 3.11, so CI's 3.9 job silently dropped all ~90 tests in test_scaffold_mode.py — including this spec's new 095 template-copy tests — over just two `tomllib`-dependent Codex assertions. Remove the gate and add `_load_agent_toml`: it uses `tomllib` on 3.11+ and, below that (jig ships zero dependencies, so no `tomli` fallback), parses the exact TOML shape jig's own `render_codex_agent_toml` emits. Writer and reader are both jig's, kept in step — not a general TOML parser. The two Codex tests now pass on 3.9 rather than skip. Full file: 91 tests, all pass on 3.9 (Python 3.9.6). Per maintainer: skipping tests on 3.9 is not acceptable — they must pass there. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Retrofit for pre-095 scaffolded projects — already works; one discoverability gap for your callChecked this end-to-end against the ADR-0038 open question. Retrofit already works with no new mechanism, as a direct consequence of this PR: Probe (scaffold → delete Caveats (inherent, not fixable):
The one gap — discoverability: Your call — two options:
My lean: option 1 (retrofit already works; the nudge is a nice-to-have follow-up, not a blocker). But it's your design call on whether report should proactively surface it. |
|
Let's add the nudge |
Retrofit already works — copy-machinery carries templates/ since 095-01 — but migrate report never told an affected project to run it: the copy-machinery suggestion was suppressed once any jig machinery was present, so a project scaffolded before 095-01 (skills copied, templates/ absent) got no nudge and would only discover the gap when a record helper failed. Add the discoverability half (ADR-0038 retrofit open question, resolved by the maintainer on #119): - Inventory records `jig_templates_present` (any file under .claude/templates/). - render_operations routes a project with jig machinery but no templates tree to `copy-machinery`, stating the backfill runs from a jig install (a scaffolded project can't repair itself from its own copied files). TDD: TemplatesBackfillOperationsTests (4 cases, red→green). Existing suppression test now seeds a complete install (skills + templates) since templates are part of a complete install. Full migrate suite 162 OK on 3.9. Host mirrors rebuilt (drift guard green). ADR-0038 + glossary updated. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Added the nudge (1e731e2)
Verified end-to-end: scaffold → delete |
Resolves the refinement-todo entry "how should record helpers reach their templates in Claude scaffold mode?" — the question parked by bug 012 and asked of you on #109.
Maintainer picked option (a) — @ramboz to validate before merge. (Relayed verbally via M-R; recorded in ADR-0038 with the provenance stated, so if the record misstates your call the fix is to correct the record.)
Links #109. Depends on #118 (the 095 number reservation) — merge that first or this branch carries it.
What / why
jig's helpers resolve their template as
CLAUDE_PLUGIN_ROOT, elseparents[2]/templates/. That reaches a template in three of four install modes. In Claude scaffold mode the helper lives at.claude/skills/jig-<name>/, soparents[2]is<project>/.claude— which had notemplates/, and no plugin root to fall back on. So a scaffolded project could not record a decision or open an ADR at all.copy_machinery(host="claude")now copiestemplates/into.claude/templates/, mirroring_copy_codex_templates. No helper's template resolution changed — the point of option (a) is that the existing fallback starts resolving. (Four helpers' comments/messages did change: they asserted "scaffold mode has no templates" as a premise.)The #109 probe, in a real
--with-machineryinstall withCLAUDE_PLUGIN_ROOTunset — the command that had never once succeeded in this mode:Test evidence (red → green)
Red witnessed against a real install before any implementation — both helpers:
Test-level: 3 failures + 2 errors in the new
ClaudeScaffoldTemplatesTests/PluginOnlyTemplatesTests→ green after_copy_claude_templates, no test edited to make it pass. The tests run the copied helpers as subprocesses withCLAUDE_PLUGIN_ROOTgenuinely unset.Suite: 3.12 (CI's upper bound) 3508 → 3518 OK (+10, all new). 3.9 (CI's floor) 3361 OK, unchanged — see the question below.
Three things worth your attention
1. The family is five helpers, not two — and that decided the design. The spec assumed
decisions.py+adr.py. Review foundworkflow.py(slice-template),memory.py(people.md) andmigrate.py(seed-decisions) resolve templates the same way.memory.py's own error message already said the failure was "expected for a scaffold-mode target" — this gap was documented in a helper nobody had connected to it. An allowlist of the two templates in evidence would have shipped, looked right, and left two consumers broken. That is why the copy is whole-tree. Of 25 files, 4 are live in a scaffolded project; the other 21 arescaffold-initseed templates, inert there. ADR-0038 carries the table and the "this is jig's source, not your content — don't edit it" warning.2. A regression this slice would have introduced, caught by review.
workflow.py's_render_stub_sliceread its template at the default encoding insideexcept OSError. Making that file reachable in scaffold mode meant that underLANG=CaUnicodeDecodeError(aValueError) would escape the handler and crash, where the pre-change behaviour degraded cleanly to an inline fallback. Fixed here. The rest of the family has the same shape pre-existing in plugin mode too — inboxed rather than folded in.3. I bypassed one of your gates, and the gate caught me. ADR-0038 was first written with
status: Acceptedhand-stamped, reasoning that your pick is the acceptance act. The authority argument is fine; the mechanism wasn't —adr.py acceptgates that flip on a passing frame-critique for aframe_review: trueADR, and the ADR was failing its critique at the time.resolve-todothen accepted the resolution because it only reads the field. Reverted; accepted properly throughadr.py accepton a passing round-4 verdict. Recorded as deviation §7b/§7c because the pressure is structural:resolve-todorequires Accepted, which is exactly what tempts a hand-write.Question for you (not decided here)
run_tests.pysilently skipstest_scaffold_mode.pywhole on Python 3.9 — the documented floor and the default macOS python3. The file's ownload_testshook returns an empty suite below 3.11 (atomllibgate for the Codex-packaging tests), so all 90 tests in it vanish there, including these new ones. That's why the 3.9 count doesn't move above. It's pre-existing and CI's 3.12 job covers it, but ~88 of those tests have nothing to do withtomllib, and the skip is silent. Narrowing the gate to the tomllib-dependent classes looked like a bigger call than this slice should make. Want a separate issue?Also still open, deliberately: retrofitting projects scaffolded before this. A
copy-machineryre-run picks the tree up, but only from a jig install — a scaffolded project cannot repair itself from inside (probed; its own copiedmigrate.pyresolves the plugin root to<project>/.claude). Whether that re-run is the sanctioned backfill, and whethermigrate reportshould flag the gap, is posted on #109 rather than decided here.Process
Followed jig's own lifecycle: spec 095 (reserved via #118) → one slice → TDD → post-impl review (compliance + craft + arch, flagged) + frame-critique on the ADR → reconciliation. Every pass ran twice; the frame critique took four rounds. Round 1 verdicts were not clean — they found a false claim in my ADR, a vacuous test, and an arithmetic argument backwards by ~30×. Verdicts recorded under
docs/specs/095-scaffold-template-copy/reviews/anddocs/decisions/reviews/; the full accounting is the slice's deviation log §1–§9.Bug 012's
## Remaining riskis closed via a dated## Amendmentsentry (ADR-0010: closed records preserve their prose). Three out-of-scope follow-ups went todocs/inbox.md.docs/conventions.mduntouched.🤖 Generated with Claude Code