Skip to content

v1.10.6 ·

The guidance that never arrived: three mechanisms that reported success and delivered nothing

The problem

Every AIDLC phase skill ends with a ## Guidance section, and for seventeen days that section was a lie. Where an agent should have found the project's security rules, it found the literal characters {{guidance:secure-defaults}} — a fill-in-the-blank marker nothing ever filled. The implementation skill went further and asserted the opposite a few lines above, listing "guidance layer rules have been applied (security defaults, accessibility, API conventions)" as a completion criterion. No rule had been applied. None had ever been delivered.

Two things were broken, and each one alone was enough. The code to substitute those markers existed — two functions, written in the framework's first commit, both unit-tested and both passing. Neither had a single caller anywhere in the production path. A repo-wide grep found only the definitions and their own tests. Separately, nothing ever copied the guidance files out of the package and into a project, so even a correctly wired substitution would have had nothing to substitute. The framework shipped three rule files, referenced them from seven skills, and delivered them zero times.

What made this survive is that every signal pointed the right way. The unit tests were green, because the functions worked perfectly when called directly. The platform adapters printed a tidy list of guidance file paths into CLAUDE.md, which looked like the feature working. And grepping the emitted per-platform skill files for a leftover {{ found nothing at all — a false clean, because those files are 431-byte stubs that say "read the canonical copy", and the canonical copy was where the markers actually sat. The defect had been recorded once already, in a review note against an earlier instance, and left alone.

The same release carries two more fixes, and they are the same shape of failure: a mechanism that reports success while delivering nothing.

Cost-capture hooks bake absolute paths to the node binary and the installed CLI into their commands. A new machine, a renamed account, or a routine runtime upgrade invalidates them, and the hook then fails silently — nothing runs, nothing reaches the capture log, and cost reporting stays empty forever. One real project's hooks still named a home directory and a node version that had both been gone for four days, and no command said anything was wrong. The check that would have caught it already existed; it just had no caller outside a status command a human has to remember to run.

And when a project had no cost data at all, the reports printed $0.00. Two separate paths turned absence into a confident zero: a sum over an empty ledger, and a hard-coded zero for a phase that had elapsed time but no ledger entry. A reader sees $0.00 and concludes the work was free. The distinction matters more than it sounds — a ledger entry that genuinely prices to zero is data worth reporting, while no entry at all is a measurement that never happened, and the two had been rendered identically.

How it could be solved

The guidance fix had two plausible shapes, and the choice between them was not obvious from the code — both halves of the feature were half-built, so the half-built parts disagreed about the intent.

One option was to inline: wire up the existing substitution so each layer's full text is pasted into the skill body where its marker sits. That is what the guidance resolver already did — it inlined unconditionally, with no size check of any kind. It has a real advantage, in that an agent reading the skill cannot miss rules that are sitting in front of it. It was rejected on arithmetic. The three shipped layers are roughly 3.5, 3.8, and 5.1 kilobytes, and the implementation skill references all three; inlining them would have grown a 4.5-kilobyte skill past 16, nearly quadrupling it, and repeated most of that across seven skills. Worse, it makes every rule edit invisible until the skills are regenerated, so a project that tunes its own security rules would keep getting the old ones with no indication why.

The other option was to install the files into the project and point at them by path. This won partly on cost and partly on evidence: the framework was already doing it. The adapters emit a list of guidance file paths into the platform config, and that code works — the one layer this repo had registered showed up correctly. The per-platform skill files are themselves nothing but pointers to a canonical copy, so "read this file" was already the established mechanism, trusted for the skills themselves. The sibling context resolver had also already been written with exactly this two-tier behaviour: inline anything small, point at anything large.

So the fix adopted that rule for both kinds of placeholder rather than inventing a third behaviour: at or under two kilobytes the content is inlined, above it the skill gets a one-line instruction naming a repo-relative path. In practice every shipped guidance layer is referenced and every context document is inlined, which is the outcome either option would have wanted, arrived at by one rule instead of two special cases.

Two smaller decisions went the same way — toward removing a silent failure rather than papering over it. The pointer could have been emitted as an HTML comment, which is what the existing code did for large context documents. That was rejected: the emitted body is read by an agent as instructions, and a comment is not an instruction. It is now visible prose. The path could have been absolute, which is simpler to compute; that was rejected because this same release fixes a bug whose entire cause was absolute paths baked into generated files, and repeating it in a file that gets committed and read on other machines would have been perverse.

The stale-hook detection was deliberately left as report-only rather than self-repairing. The repair rewrites project configuration files, which belongs behind an explicit install command, not inside a routine health check. That migration also shipped with a bug of its own on the first attempt — it drove its platform list from a registry that only populates after an explicit registration call, so every case quietly found nothing, which is the third instance of this release's theme. Its own tests caught it on the first run.

For the zero-versus-nothing fix, the cheap option was to leave the number and add a footnote. Rejected: the fix distinguishes a real measured zero, which stays a zero, from the absence of any counted unit, which is now null and prints as "no data". One of those is a fact about the work, and the other is a fact about the instrumentation.

How AIDLC solves it

The guidance-layer feature worked end to end for the first time since the framework's initial commit. Before this, every phase skill's ## Guidance section was placeholder text an agent could not act on:

## Guidance

{{context:style-guide}}
{{guidance:secure-defaults}}

Two resolvers had existed since da7a31e (2026-08-09) with passing unit tests and no production caller, and nothing ever copied the bundled layers into a project — so the security, accessibility, and API guidance the framework claimed to inject was never delivered once in 17 days of use.

Now, in this repo:

## Guidance

# Style Guide
… (inlined, 304 B, frontmatter stripped)

Read `.aidlc/guidance/secure-defaults.md` (Security best practices and secure coding defaults) and apply it.

Commits

CommitShips
912764bthe fix — installer, resolver wiring, doctor migration — shipped code
f912302AC coverage plus a path-traversal guard found during testing — shipped code

Shipped surface: 7 source files and 2 test files. New: packages/cli/src/compile/guidance-install.ts and packages/cli/src/doctor/migrations/install-guidance.ts. Modified: packages/cli/src/compile/loaders.ts, packages/cli/src/core/types.ts, packages/cli/src/commands/init.ts, packages/cli/src/commands/update.ts, packages/cli/src/doctor/migrations/index.ts.

User-visible effect once published

  • aidlc init creates .aidlc/guidance/ and installs the three registered bundled layers, registering them in index.yaml.
  • aidlc update and aidlc doctor install missing layers into projects created before this release — no manual command to remember.
  • No emitted skill contains {{guidance:...}} or {{context:...}} any more. Targets at or under 2048 bytes are inlined; larger ones become a visible, repo-relative read instruction.
  • Layers a project has edited are never overwritten, and existing index.yaml registrations — including the retrospective-fed lessons layer and its last_consolidated field — are preserved.

Behaviour change callers may notice

  • aidlc update rewrites the generated skill files in .aidlc/skills/ in every existing project. Expected: they are generated artifacts and rewriting them is update's function. In this repo it touched 13 files. Not treated as breaking (requirements D-3), but users will see a diff they did not author.
  • A guidance layer over 2048 bytes is now referenced rather than inlined. Any workflow that grepped a skill body for guidance text will now find a path.
  • resolvePlaceholders() takes a required third argument (projectRoot). Exported but not part of the CLI's public surface; no documented consumer exists.