Skip to content

fix(scaffold): copy templates/ in Claude scaffold mode so record helpers resolve (#109)#119

Draft
Kyarha wants to merge 8 commits into
mainfrom
claude/scaffold-template-resolution-1410b8
Draft

fix(scaffold): copy templates/ in Claude scaffold mode so record helpers resolve (#109)#119
Kyarha wants to merge 8 commits into
mainfrom
claude/scaffold-template-resolution-1410b8

Conversation

@Kyarha

@Kyarha Kyarha commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

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, else parents[2]/templates/. That reaches a template in three of four install modes. In Claude scaffold mode the helper lives at .claude/skills/jig-<name>/, so parents[2] is <project>/.claude — which had no templates/, 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 copies templates/ 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-machinery install with CLAUDE_PLUGIN_ROOT unset — the command that had never once succeeded in this mode:

$ env -u CLAUDE_PLUGIN_ROOT python3 .claude/skills/jig-memory-sync/decisions.py \
    add-lightweight --title probe --decision probe --project-dir .
seeded docs/decisions/lightweight-decisions.md from jig's template (this project had none)
recorded lightweight decision in docs/decisions/lightweight-decisions.md: probe

Test evidence (red → green)

Red witnessed against a real install before any implementation — both helpers:

error: lightweight-decisions template not found: <project>/.claude/templates/docs/decisions/lightweight-decisions.md.template
template not found: <project>/.claude/templates/docs/decisions/adr-0000-template.md

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 with CLAUDE_PLUGIN_ROOT genuinely 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 found workflow.py (slice-template), memory.py (people.md) and migrate.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 are scaffold-init seed 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_slice read its template at the default encoding inside except OSError. Making that file reachable in scaffold mode meant that under LANG=C a UnicodeDecodeError (a ValueError) 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: Accepted hand-stamped, reasoning that your pick is the acceptance act. The authority argument is fine; the mechanism wasn't — adr.py accept gates that flip on a passing frame-critique for a frame_review: true ADR, and the ADR was failing its critique at the time. resolve-todo then accepted the resolution because it only reads the field. Reverted; accepted properly through adr.py accept on a passing round-4 verdict. Recorded as deviation §7b/§7c because the pressure is structural: resolve-todo requires Accepted, which is exactly what tempts a hand-write.

Question for you (not decided here)

run_tests.py silently skips test_scaffold_mode.py whole on Python 3.9 — the documented floor and the default macOS python3. The file's own load_tests hook returns an empty suite below 3.11 (a tomllib gate 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 with tomllib, 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-machinery re-run picks the tree up, but only from a jig install — a scaffolded project cannot repair itself from inside (probed; its own copied migrate.py resolves the plugin root to <project>/.claude). Whether that re-run is the sanctioned backfill, and whether migrate report should 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/ and docs/decisions/reviews/; the full accounting is the slice's deviation log §1–§9.

Bug 012's ## Remaining risk is closed via a dated ## Amendments entry (ADR-0010: closed records preserve their prose). Three out-of-scope follow-ups went to docs/inbox.md. docs/conventions.md untouched.

🤖 Generated with Claude Code

Kyarha and others added 6 commits July 16, 2026 22:16
…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]>
@Kyarha

Kyarha commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Second-opinion review pass + conflict resolution (via /pr-review).

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 (skills/, hosts/claude/, hosts/codex/) are byte-identical, the workflow.py UnicodeDecodeError guard is a real regression catch, and the tests exercise the copied helpers end-to-end with CLAUDE_PLUGIN_ROOT unset. New 095 tests + touched test_decisions.py green on 3.9. Scope discipline on the four inboxed follow-ups is right — nothing to fold in here.

Resolved the merge conflict against current main (#118 had landed the 095 reservation stub):

  • docs/specs/README.md — kept main's current 094 rows, appended the 095 DONE row.
  • spec.md — kept the full drafted spec over docs(specs): reserve 095-scaffold-template-copy #118's placeholder.
  • slice-01-tbd.md — removed (superseded by slice-01-claude-scaffold-templates.md).

Branch now fully contains main; PR is MERGEABLE. (Two merge commits rather than one — a first merge used a stale origin/main ref; harmless under squash-merge.)

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]>
@Kyarha

Kyarha commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Retrofit for pre-095 scaffolded projects — already works; one discoverability gap for your call

Checked this end-to-end against the ADR-0038 open question. Retrofit already works with no new mechanism, as a direct consequence of this PR: copy_machinery now carries templates/, so migrate copy-machinery backfills it.

Probe (scaffold → delete .claude/templates/ to simulate a pre-095 project → retrofit from a jig install):

$ python3 skills/migrate/migrate.py copy-machinery <project>
copied machinery into <project>/.claude
# .claude/templates/ restored: 25 files

Caveats (inherent, not fixable):

  • Must run from a jig install (plugin root available) — a scaffolded project can't repair itself from its own copied files, since templates/ is exactly what it's missing to copy from. This is the same "needs a plugin root" remedy-class the ADR already documents.

The one gap — discoverability: migrate report on a templates-missing project does not flag the gap or route the user to copy-machinery. So the fix works if you know to run it, but the tool won't point you there.

Your call — two options:

  1. Ship as-is. Retrofit is supported via migrate copy-machinery; document it as the sanctioned backfill and close the ADR open question. No code change.
  2. Add the nudge. Small change: migrate report detects jig machinery present + .claude/templates/ absent and recommends copy-machinery (from a jig install). ~detection + one report line + a test + doc updates. Can go in this PR or a follow-up.

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.

@ramboz

ramboz commented Jul 17, 2026

Copy link
Copy Markdown
Owner

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]>
@Kyarha

Kyarha commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Added the nudge (1e731e2)

migrate report now flags a project that has jig machinery but no .claude/templates/ (the pre-095 shape) and routes it to copy-machinery:

2. migrate.py copy-machinery <project-dir> — this project has jig machinery
   but no .claude/templates/, so it was scaffolded before that tree shipped
   (ADR-0038). The record helpers (decisions.py, adr.py, workflow.py,
   memory.py) resolve their template beside the machinery, so in this state
   recording a decision or opening an ADR fails. Re-run copy-machinery from a
   jig install … to backfill the tree. Idempotent; never overwrites your
   project's own files.
  • Detection: inventory records jig_templates_present (any file under .claude/templates/); render_operations fires the backfill line only when machinery is present and templates are absent — mutually exclusive with the existing full-copy suggestion.
  • Caveat kept honest: the nudge itself states the backfill runs from a jig install — a scaffolded project can't repair itself from its own copied files.
  • Tests: TemplatesBackfillOperationsTests (4 cases, TDD red→green); the old suppression test now seeds a complete install (skills + templates). Full migrate suite 162 OK on 3.9. Host mirrors rebuilt (drift guard green).
  • Records: ADR-0038's retrofit open question marked resolved; glossary updated.

Verified end-to-end: scaffold → delete .claude/templates/ → report shows the nudge → copy-machinery → nudge gone. PR is MERGEABLE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants