Repository navigation
fix(partials): keep substituted files faithful and never emit a live tag - #46
Merged
Merged
Conversation
Partial substitution decoded a file as UTF-8 and re-encoded it, so a file
with a partial tag and any invalid byte (a Latin-1 "é", or a UTF-16
byte-order mark) came out with U+FFFD in place of those bytes, on a green
build. The same file without a tag kept its bytes. A file that uses
partials, and every partial, now has to survive a UTF-8 round trip, or the
build fails and names the file.
Extension matching was case-sensitive, so NOTES.MD or REF.TXT was treated
as a file type substitution skips, and the build failed with an error
saying substitution only runs on .md and .txt files. The extension is now
compared in lower case, as the README already implied.
Rendering could assemble a tag that no single piece contained: a partial
ending in "{" before "{> name}}", a partial ending in "{{" before
"> name}}", or "{{{> name}}}". The build guard skipped substituted file
types on the reasoning that only the documented escape could leave a tag
there, so these shipped as live tags. Escaped tags now render through
their own placeholder, and substitute() fails on any tag left in its
output. That makes the reasoning true, so validate no longer warns on
every documented \{{> name}} escape, which was permanent noise on the one
sanctioned way to write partial syntax.
A partial's byte-order mark was inlined as U+FEFF mid-file, and a CRLF
partial left mixed line endings in an LF file. Partials now drop a leading
byte-order mark and take the line ending of the file that includes them.
The README documents these rules and the backslash edge case.
…he root Each target's validator reports the plugin directories it owns, and validate scans those for surviving partial tags. A plugin configured with path "." lives at the output root, so with outDir "." its directory is the repository itself. Validate then walked node_modules, authored sources and unrelated files, and failed on tags in pluginpack's own dependency cache. For a plugin at the output root, validate now scans only its component directories and static files. The managed manifest still covers the rest of the files the build wrote. Plugins in their own directory are scanned as before.
The previous change stopped validate from walking the whole repository for a plugin emitted at the output root, but it went too far in one direction and not far enough in the other. It dropped the target's native files from the scan. Without a managed manifest, a partial tag in the plugin manifest or MCP config of a root plugin passed validation, although the app reads those files and the previous release reported the tag. Each target now names the files its app reads from a plugin root (manifest, MCP config, hooks file, and for Agent Plugins each client's extension directory), and validate always scans them. It still scanned every recognized component directory when a managed manifest was present, so an unrelated repository script under scripts/ failed validation for a plugin that only ships skills. Validate now tells each target whether the output has managed state. With it, the managed file list covers the plugin's output and the component directories are not scanned. Without it, they remain the fallback.
File values may be Uint8Array rather than Buffer, so decode through Buffer.from instead of calling Buffer methods on the value.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Partial substitution could corrupt files and ship live tags on a green build:
.mdfile with a partial tag and one non-UTF-8 byte had that byte rewritten to U+FFFD. Without a tag, the byte survived. NUL-containing text files behaved the same way.{placed before{> auth}}produced a live{{> auth}}in the output. The comments claimed the escape was the only way a tag could reach output.NOTES.MDwith a tag failed the build, because extension matching was case-sensitive.\{{>escape produced avalidatewarning.path: "."),validatewalked the whole repository, includingnode_modules/and user sources, and failed on correct output.Change
validateno longer scans substituted file types for tags. Other file types are still checked.validatescans the target's native manifest, MCP and hooks files, plus the managed files when.pluginpackstate exists. Without state, it falls back to the component directories.Breaking changes
{{{> name}}}and other assembled tags now fail the build.Testing
npm run checkpasses. On unfixed source, the new tests fail for the intended reasons. Root-plugin validation is tested for cursor, claude, copilot, codex and agent-plugins, with and without managed state.