How to debug template emission
Purpose #
When the wrong file content lands in the emitted tree, find the cause: staging dir, interpolation, file modes, or template source.
Big Picture #
The emission pipeline is:
- Adapter emit methods return
EmittedFile[]from in-memory templates loaded viafiles-resolver. @aidokit/clicollects the plan.@aidokit/core writeFilesStagedwrites to<projectRoot>/.aido-staging/.- Atomic move into the project root.
Any of these layers can be the bug source.
How It Works #
1. Confirm what was actually emitted #
aidokit init --dry-run --verbose --json prints the full plan without writing. Pipe to jq to inspect:
node packages/cli/dist/bin/aidokit.js init --dry-run --verbose --json \
--adapter claude-code --stack node-ts --yes \
| jq '.result.filePlan[] | {path, mode, contentPreview: (.content | tostring | .[0:80])}'
Confirm with maintainer — exact JSON shape of the file plan in --json --dry-run mode. If the schema differs, log the in-memory plan via a temporary console.log in packages/cli/src/init/compute-file-plan.ts.
2. Confirm which template source was read #
files-resolver locates dist/files/ (built) or falls back to src/files/ (source-tree). If dist/files/ is stale, you'll get old content.
pnpm --filter @aidokit/adapter-claude-code build # regenerates dist/files/
If the bug persists after a clean build, the template under src/files/ is wrong.
3. Confirm interpolation #
interpolate(template, vars) in @aidokit/core is the only substitution mechanism. If a {{varName}} lands literally in the output, the variable is missing from the var bag passed by the caller.
- Inspect the caller (usually
packages/adapter-claude-code/src/emit/agent-rules.tsor similar). - Confirm the variable name matches between template and call site (the helper throws on unknown keys per .docs/ARCHITECTURE.md §11.2).
If the template needs a conditional or loop, it should NOT use interpolation — it should be programmatically generated by the emit method (CLAUDE.md §8).
4. Confirm file modes #
Executable scripts must be mode 0o755. Check the emitted file:
ls -l /tmp/scratch/.claude/scripts/
If a script is missing the executable bit, the EmittedFile.mode field was omitted in the relevant emit method (typically emitWatchdog in the adapter). Fix by setting mode: 0o755 on the returned object.
Per .docs/docs/specs/adapter-contract.md §7.11, prefer mode on the EmittedFile over a chmod in postInstall.
5. Confirm the staging → atomic-move boundary #
If the project root is left in a half-written state, the atomic-move step failed. The staging dir is preserved on failure with a warning telling you where it is. Inspect:
ls -la /tmp/scratch/.aido-staging/
Compare to what made it into the project root. If staging has the right content but the move didn't happen, dig into packages/core/src/write-files-staged.ts.
6. Confirm capability declarations match #
If your new path isn't declared in manifest.capabilityDeclarations.writesPaths, AD-STD-CAP-01 will fail. Add the glob to the manifest.
7. Confirm determinism #
Run the emit method twice; compare:
node packages/cli/dist/bin/aidokit.js init --dry-run --json ... > /tmp/a.json
node packages/cli/dist/bin/aidokit.js init --dry-run --json ... > /tmp/b.json
diff /tmp/a.json /tmp/b.json
Any difference → non-deterministic field somewhere (timestamp, random id, set iteration order). Fix; otherwise dogfood compare will fail.
Example: missing {{projectName}} substitution #
Symptom: emitted CLAUDE.md contains the literal {{projectName}} instead of my-app.
Trace:
packages/adapter-claude-code/src/emit/agent-rules.tsreadssrc/files/agent-rules.md.- It calls
interpolate(template, vars). varsis built fromctx.projectName— but if the calling site spelled itprojectname(lowercase), the helper threw "Unknown variable: projectname" before substitution.- Either the template or the var bag is wrong; fix the spelling.
Common pitfalls #
- Re-running
aidokit initover the same scratch dir withoutgit clean. Old files linger. Re-init from an empty dir for clean tests. - Inspecting
dist/files/and concluding the template is right.dist/files/is built fromsrc/files/. Always inspectsrc/files/. - Adding a non-deterministic field for "easier debugging". Removes the dogfood gate's value.