Skip to content

fix: fail the build on unresolvable partial references - #26

Merged
steve-calvert-glean merged 3 commits into
mainfrom
fix/strict-partial-resolution
Aug 6, 2026
Merged

fix: fail the build on unresolvable partial references#26
steve-calvert-glean merged 3 commits into
mainfrom
fix/strict-partial-resolution

Conversation

@steve-calvert-glean

Copy link
Copy Markdown
Contributor

Partials could leave a skill file broken in three different ways. This closes all three.

The gap

  1. A missing partial silently deleted content. mustache renders an unknown partial as an empty string, so a one-character typo, or renaming a file under partials/, quietly removed that section from every skill including it. Build stayed green. This was asserted as intended behaviour by a test and documented in the README.
  2. Unrelated braces were eaten. Any other {{...}} text was rendered against the empty view and vanished, so a skill could not document Handlebars, Jinja, or Go templates. This fired even with no source.partials configured at all.
  3. Tags survived literally. Substitution only runs on .md/.mdc/.markdown/.txt, so a tag authored in a .yaml reference file or a .py script passed straight through into output with nothing flagging it.

The fix

Strict resolution. mustache accepts a function for its partials argument, so its own parser decides what is genuinely a partial tag; an unknown name throws and is rethrown naming the emitted file, the nearest match, and the available partials. Malformed references and unknown references inside a partial body fail too. With no partials configured, the error names the config key.

Partial-only substitution. Every {{ that is not a partial tag is swapped for a private-use sentinel before mustache parses, and restored afterwards. mustache still owns the semantics that are genuinely subtle (standalone-line handling and partial indentation), so partial output is byte-identical to before; everything else is preserved verbatim, including input that previously made mustache.parse throw. {{> name}} escapes a literal tag. A file that never mentions {{> is now returned byte-identical without touching mustache at all.

Build and validate guards. build fails on any tag left in emitted output in a file type substitution skips. validate runs the same scan from disk in validateFromDefinition, so it covers all five targets: error for skipped file types, warning for md-family files, which can only carry a tag via the documented escape.

Migration

Anyone relying on a missing partial rendering as nothing will now get a build error. That is the point: shipping a gutted skill file is worse than a red build. Needs the breaking label so the release notes carry it.

Testing

npm run check is green: format, lint, typecheck, 123 tests, build, docs. Two tests inverted to assert the new behaviour, eight added covering unknown / unconfigured / nested-unknown / malformed references, verbatim brace passthrough, the escape, the build guard, and the validate guard. Each partial-rendering case was diffed against the old renderer to confirm byte-identical output.

🤖 Generated with Claude Code

steve-calvert-glean and others added 2 commits August 5, 2026 15:14
A {{> name}} tag naming a partial that does not exist rendered as an empty
string, so a typo or a renamed partial silently deleted a section from every
skill that included it. Unrelated {{...}} text in the same file was rendered
away as well, and a tag in a file type substitution skips (.yaml, .py) shipped
through into output verbatim.

- unknown, malformed, and nested-unknown references now fail the build, naming
  the emitted file, the nearest match, and the available partials
- only partial tags are substituted now: mustache gets a function for its
  partials argument, and every other {{ is swapped for a sentinel before it
  parses, so other brace text survives byte for byte, including text mustache
  cannot parse as a template at all
- \{{> name}} emits a literal tag, for files that document partial syntax
- build fails, and validate reports, when a tag survives in a file type
  substitution does not run on

BREAKING CHANGE: an unresolvable partial reference is now a build error rather
than rendering as an empty string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@steve-calvert-glean steve-calvert-glean added bug Something isn't working breaking Considered a breaking change labels Aug 5, 2026
A run of // lines above a top-level function, type, or const was doing the
job of a doc comment without being one: not picked up by editors, TSDoc, or
generated docs. Converted all 11 remaining sites in src/ to JSDoc, including
the one added earlier on this branch.

Inline // comments inside function bodies are left alone — they annotate
statements, which JSDoc does not cover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@steve-calvert-glean
steve-calvert-glean merged commit ad28573 into main Aug 6, 2026
3 checks passed
@steve-calvert-glean
steve-calvert-glean deleted the fix/strict-partial-resolution branch August 6, 2026 00:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Considered a breaking change bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant