[Plugin Generator] Split core into composable plugin sets (#315) - #340
Open
isolomatov-gd wants to merge 40 commits into
Open
[Plugin Generator] Split core into composable plugin sets (#315)#340isolomatov-gd wants to merge 40 commits into
isolomatov-gd wants to merge 40 commits into
Conversation
Splits `instructions/r3/core/` into five sibling domain sets — core,
advanced, qe, search, modernization — and makes the generator build every
set x IDE combination from a declarative catalog in ONE invocation.
Output goes from 14 folders to 49: rosetta-<ide> (the full combo, what
core-<ide> used to be), rosetta-<ide>-light, and the five split sets.
`scripts/pre_commit.py` now holds exactly one generator call.
Generator
- New `src/rosettify-plugins/plugins.json` declares sets, folders,
variants, per-set bootstrap and hook lists, and per-release availability.
Validated at pre-flight; an unknown field aborts before any write.
- `spec.name` becomes the bare IDE id; the set rides `spec.destination`.
Directive tokens namespaced: target-, ide-, set-, profile-.
- Hook registration moves from template literals into `HOOK_LAYOUTS` data;
hooks.json is assembled via JSON.stringify, so malformed output is
structurally impossible. Handlebars runs strict.
- Preserved config folders collapse from 5 core-<ide> to 5 template-<ide>.
- Profile descriptors shrink to `{ modelOverrides }`; suffixes move to the
set variant. `--config` added; `--domain` is now a set filter.
- Silent-failure paths closed: a missing bundle dir is a hard error, and
undeclared bundles are swept.
Content
- configure/ retired; the 8 IDE guides live in the harness skill, now
verbatim-protected (they were being reference-rewritten while the
original was not).
- self-help-flow.md deleted; its 13 citations repointed to help-flow.
- Cross-set command advertisements marked with their required plugin.
Org-overlay layering is retired — an org folder is now simply a domain set.
Fixes an Antigravity bug: templates/ -> rules/ rewriting corrupted
skill-relative paths and prose, so the security skill pointed at files that
did not exist.
All 12 acceptance criteria verified, including content-equivalence of
rosetta-<ide> against the pre-change output and union-equality of the split
sets with rosetta-<ide>-light.
Tests: rosettify-plugins 927, hooks 1441, curiocity 432.
Note: pre_commit.py calls rosettify-plugins@latest, which cannot know about
plugins.json until the generator is published. Sequence the publish before
relying on that call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t unit globs (#315) Standalone plugins have never told the agent where they were extracted, so every relative unit path in them resolved to nothing. Root cause: the generator had one notion of "root" — plugin-root-relative, in pluginRewriteReferences — applied to two distributions that do not share one. A marketplace plugin is installed, so the install dir IS the root. A standalone is extracted into a user repo, so the root is the repo and the .cursor/.github prefix must survive. An anchor-based injection existed to paper over that for one file, and never fired: it scanned for a `# PREP STEP 1:` marker that lives in the test fixture but not in the real rule, and skipped silently when absent. Replaced with pluginEmitDistributionRoot — a factory composed only into the two standalone specs, appending the declaration at a deterministic position with no anchor and no string matching. Deliberately not a PluginSpec field: a field five of seven specs leave unset, read by a processor that no-ops for most, is identity branching wearing a data costume (FR-ARCH-0005). Mass path rewriting was rejected: `agents/` names both plugin content that moves under the root and target-repo files (agents/IMPLEMENTATION.md, agents/*-state.md) that stay at the repo root, so a folder-level pair would corrupt them — the FR-ARCH-0049 hazard. One declared root, and the agent disambiguates by context as it already does. Also corrects six documented unit globs that named files which do not exist: cursor RULE (.mdc), copilot AGENT/SUBAGENT (.agent.md), copilot-standalone WORKFLOW/COMMAND (.prompt.md), and all four codex globs, now uniformly root-relative since codex alone spans two roots (.codex/agents/*.toml for subagents, .agents/ for everything else) per its own configure guide. A mutation-tested e2e sweep now asserts all 28 globs resolve to real files. Requirements: bootstrap-delivery ownership consolidated into FR-VAR-0070 (the template-placeholder rule it stated was falsified when every template became {{{hooks_json}}}); six duplicate contracts reconciled; dependency direction corrected to match the corpus convention; DATA-CFG-0008 authored for the hook-layout table the code already cited in six places. Gates: structure, equivalence against the pre-change golden, and cross-references all pass. Tests 939 / 1441 / 432. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
) A second review found the previous pass corrected <statement> text and left the superseded rule standing in titles, rationales, criteria and implementation notes. Three of its six blocking findings lived entirely outside <statement>, including DATA-CFG-0002 — an Approved unit in this delta — still asserting "delivery is a property of the preserved templates/rules" and citing as its authority the unit that now refutes it. Swept by phrase rather than by unit. One occurrence is legitimate and survives: preventing DOUBLE-delivery genuinely is a template/rule concern. The other five are corrected. Also fixed: - FR-GEN-0010 / FR-SET-0070 duplicated two shall clauses verbatim — a duplicate the previous pass created while claiming its citation check clean, because that check covered statements only. - FR-ARCH-0051 contradicted itself twice (a leftover unqualified hard-error from the pluginInjectSections version, and configured-vs-derived workflow folder) and claimed the emission is "never located by matching an anchor string" when the code uses lastIndexOf on the block-end tag. Reworded to the guarantee that actually holds: the insertion point always exists, so the emission cannot be silently skipped. - FR-SET-0030 delegated "every variant is built" then re-claimed it. - FR-SET-0070's "sole determinant of the set's hook footprint" narrowed: the set decides which modules it requests, the target layout decides what is bound and whether a bootstrap block is emitted. Footprint is not uniform across a set's targets. - FR-PROF-0011 made the normative owner of the profiled model-drop rule, which three FR-COPY units had been restating. - Revision narration stripped from implementation notes; CHANGES.md owns it. A mechanical clause-overlap scan over all 438 citation edges — covering titles, rationales and notes, not just statements — now runs as the check. It went from 12 pairs above 0.68 to 7, each adjudicated legitimate, and it caught two duplicates neither review had listed. Two inspection passes missed what it finds in seconds; it belongs in the gate. 158 units, 290 edges, zero cycles, zero dangling, no DATA->FR edges. Gates and suites unchanged: 939 / 1441 / 432. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: isolomatov-gd <isolomatov@griddynamics.com>
Signed-off-by: isolomatov-gd <isolomatov@griddynamics.com>
NFR-0001's parity gate compares output paths only, and ac_equivalence.py blanket-permits every content change to a file named hooks.json (permitted difference #2). Between them, a document that changed shape while keeping its path passed every gate in PR #340: <set>-copilot/hooks/hooks.json went from the 60-byte standalone form to a 24443-byte copy of the plugin form unnoticed. The gate asserts the relationships between the documents rather than hashing them alone: the exact path set per target, which documents must be byte-identical (codex mirror pair, copilot root <-> .github/plugin), which must differ (copilot plugin form vs standalone-form staging; cursor's two forms at posture=true), that each carries its own form's markers, that every document parses, and a per-document hash so future content changes are visible instead of silently permitted. Validated in both directions: ALL PASS against the pre-#315 golden tree (28 assertions), 12 failures against the current tree, each naming the collapse. Cursor's twins are correctly not flagged at posture=false, where both legitimately reduce to {"version":1,"hooks":{}}. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
D23 hook modules are all-or-nothing per set: plugins.json holds exactly two configurations, so the "commas between optional siblings" problem that justified collapsing the templates does not arise. D24 the two work tracks run on disjoint filesets. D25 the 7 literal per-IDE templates are kept and HOOK_LAYOUTS is retired; the table's only real justification (JSON validity) is served better by a post-render JSON.parse validation, which also guards a malformed raw injection. B2 resolved by verify/ac_hooks_content.py. OQ-1 closed as not blocking: the verified Copilot spec is silent on marketplace-plugin mode, that gap is pre-existing, and the pre-#315 design already committed to shipping the standalone form there regardless. The old "suppress the third file" sidestep is wrong -- FR-VAR-0030.AC4 requires it, and golden proves it was 60 B. Two out-of-scope findings recorded in F7: the never-verified Copilot marketplace probe loops, and the Copilot bootstrap hook never firing in VS Code (camelCase-only registration, identical before and after #315). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…plit empty-selection exit code FR-CLI-0030: an unknown --domain token used to fall through to the generic "No plugin sets to build" message with the missing instruction folder never named. generate() now checks each token against <instructionsSource>/<release>/<token> before selection and reports the resolved path(s) it looked for, aborting with exit 1. FR-CLI-0031: an empty selection used to exit 1 unconditionally, conflating two different causes. A domain filter that matches no declared set is now a legitimate outcome (exit 0, nothing written); an unconfigured release with no --domain stays exit 1 with its existing wording, since that is misconfiguration, not a filter outcome. Ship together: 0031 alone would make `--domain typo` exit 0 silently. New exported parseDomainTokens (spec/plugin-sets.ts) is the single place that splits/trims/filters the raw --domain value, shared by selectSets and generate()'s new folder check. Also re-tags generate.test.ts's mislabeled "missing instruction directory" test: it exercises buildVfs throwing, not the domain-filter rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…as stale RULES_EXCLUDES (src/rosettify-plugins/src/spec/targets.ts) ships three entries — rules/bootstrap.md, rules/mcp-files-mode.md, rules/local-files-mode.md — but the requirement statement named only two, so it flagged a real, already-shipped exclusion (instructions/r3/core/rules/mcp-files-mode.md) as missing. Updates the statement, AC1, and implementationNotes to match the code and flips implementation to Implemented; status stays Draft (unrelated approval gate). Adds a unit test asserting all three rules are excluded from generated frames, plus a read-only check of the already-shipped plugins/ tree confirming none of the three leaked into any output folder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…315) NFR-0001 compares output file paths only, deliberately, and the equivalence check listed hooks.json among its permitted-difference classes. Between them a hook-configuration document that changed shape while keeping its path was invisible to every gate -- how <set>-copilot/hooks/hooks.json went from the 60-byte standalone form to a copy of the 24443-byte plugin form with all gates green. NFR-0012 is the content-shape complement, covering the case a path oracle cannot see: a target emitting two hook-configuration documents at fixed, different paths that differ only in content. It asserts the relationships between them -- declared path set, required identity (codex mirror pair, copilot alternate-name copy), required distinctness (copilot's two forms; cursor's two forms where the deterministic-hooks value makes them distinguishable), form markers, JSON validity -- plus a recorded digest per document. Relationships rather than frozen bytes, so the gate stays valid as the bootstrap payload legitimately moves with the instruction source. NFR-0001's notes and the INDEX summary now name where content is covered rather than only disclaiming it; TRACE maps NFR-0012 to G7 alongside NFR-0001; CHANGES records RECONCILIATION-14. Status Draft: the capability was owner-approved (follow.md OWNER'S ASKS item 12), this unit's text is new and awaits review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nnel, not progress initLogger (src/logging.ts) already sends all progress to stderr via pino's destination:2 transport; the requirement wanted per-plugin counts on stdout, which would corrupt `--dry-run > file` redirection since buildPipeline threads `out: Writable = process.stdout` into pluginCopy and pluginWrite (FR-ARCH-0045). Measured: a normal `--domain qe` run produced 0 bytes on stdout, 2,079 bytes of progress on stderr; `--dry-run --domain qe` produced 2,707,684 bytes of payload on stdout, 131,276 bytes of progress on stderr. The code was right; the requirement was wrong about where progress belongs. Rewords the statement/ACs to require progress on stderr and an empty stdout on a normal run, with `--dry-run` payload on stdout as the explicit exception, and flips to Implemented. Drops the per-plugin copied/renamed/generated breakdown claim (the logger reports a single `frames` total) to backlog rather than blocking this unit. Adds tests/unit/cli-progress.test.ts, spawning the CLI as a subprocess (cli.ts runs main() at import) to verify stdout is empty on a normal run and carries the dry-run payload otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… --help (FR-CLI-0060) --help documented the plugin-set mechanism but never listed the descriptor field names, so a user had to read plugins.json or the source to learn what a set/variant/manifest carries. Exports SET_FIELDS, VARIANT_FIELDS and MANIFEST_FIELDS from spec/plugin-sets.ts and interpolates them into the "Plugin sets" help block in cli.ts, rather than hand-typing a second list that would drift from the loader's own allow-list — the same trap FR-SET-0050 closes for the manifest description. Also drops implementationNotes' stale claim that AC4 names the field `id` instead of `name`: AC4 already reads `name`, and its nine names are exactly SET_FIELDS. Adds tests/unit/cli-help.test.ts, spawning `--help` as a subprocess (cli.ts runs main() at import) to assert every SET_FIELDS/VARIANT_FIELDS/ MANIFEST_FIELDS name appears in the output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…, never derive it (FR-SET-0050) `readSets` already checked that a `requires` entry names a declared set and does not name itself; it never checked that the requiring set's own manifest.description actually mentions the required set, so a `requires` edit that forgot to update the authored "Requires X and Y" prose shipped silently wrong manifests. Adds the check inside the existing per-`requires` loop: the BASE description (never the variant-suffixed composite) must mention the required set's name, matched case-insensitively with a lookaround (`(?<![a-z0-9-])name(?![a-z0-9-])`) rather than `\b`, which treats `-` as a word boundary and would let a set named e.g. `read-once` match inside "read-once-shared". Validating rather than deriving keeps the authored sentence — which reads better than anything generated — as the single source of the fact, and catches a forgotten description at the exact moment the `requires` edit is made, rather than composing a second, worse sentence from the same data. Verified the shipped plugins.json still loads under the new check. Tests: a fixture requires:['core'] with a description omitting "core" throws PluginCatalogError naming both the set and the entry; a hyphenated-name substring case proving \b would have been wrong; the shipped catalog still loads; and an output-shape assertion (the class of bug the prior audit missed) that a built qe-* manifest description actually contains "Core" and "Workflows". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Architecture doc: all four open questions decided. OQ-1 probe guard fixed as its own commit; OQ-2 destination plumbed unconditionally; OQ-3 golden retained through this PR; OQ-4 -- F1/F2/F3 are BY DESIGN, not defects. Copilot and Cursor deliver bootstrap through rules, deliberately (FR-VAR-0070, Approved). Enabling those hook registrations would double-deliver instructions the auto-loaded rules already carry. §6 amended so the three rows read as records of intentional design rather than a backlog. docs/hooks-verify.md does not gate generator work -- it governs how the per-IDE spec documents were authored and sealed. What is required instead: unit-level schema tests, in their own location, that neither run the generator nor write a hooks.json, and that cover the switched-off shapes so a future re-enable is safe. Copilot is one plugin serving every Copilot variant from a single document. MEMORY.md, two appended rules (append-only, 6 insertions, 0 deletions): shell cwd persists so relative-path existence checks lie -- this overwrote MEMORY.md itself, recovered from HEAD; and a finding contradicting an Approved requirement is probably a misreading, never offer an option whose own description states why it is harmful. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… 1/9) Restores the 7 per-IDE hooks.json.tmpl files (and their 7 test fixtures) verbatim from 492b6a7~1, replacing the single {{{hooks_json}}} placeholder each carried under the #315 HOOK_LAYOUTS refactor. A document's shape now lives in the file that produces it again — file path is identity. The Copilot plugin-form template (.github/plugin/hooks.json.tmpl) additionally substitutes {{destination}} for the hardcoded core-copilot literal at all 14 probe sites (7 bash + 7 powershell), so rosetta-copilot and rosetta-copilot-light probe their own install folder instead of core-copilot's. generate.ts does not yet plumb `destination` (step 2) — this commit is a pure template restore per plans/issue-315-plugin-sets/hooks-architecture.md §3 step 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… code FR-HOOK-0022 -- the audit was wrong, no code change needed. sweepUndeclaredBundles is called unconditionally by pluginSyncBundles, outside the deterministic-hooks copy branch, so AC2 holds on both branches; the four sweep tests in plugin-sync-bundles.test.ts already cover it. implementationNotes now cite the helper and the named tests, and record the nuance that pluginCleanup wipes the destination folder first, which makes the sweep defence-in-depth for direct library callers rather than a CLI-pipeline property. Flipped to Implemented; status stays Approved because no normative text changed. FR-SET-0050 -- its validation landed in 6675e99. The statement's duty moves from composing the manifest description to enforcing an authored one: readSets now refuses a catalog whose manifest.description omits a set its requires list names, matching on a word boundary so a hyphenated set name cannot match inside a longer one. Added AC5 for the abort, added the rationale line for why enforcing an invariant beats generating the artifact that satisfies it, and pointed implementationNotes at readSets plus the two test files. Status moved Approved -> Draft on FR-SET-0050 only: refusing to load a catalog is a new obligation and awaits re-approval. AC2's observable outcome is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
generate.ts:206 now supplies `destination: spec.destination` to every
spec's templateContext alongside release/deterministic_hooks/bootstrap_hooks,
per plans/issue-315-plugin-sets/hooks-architecture.md §1.3/§3 step 2. Plumbed
unconditionally per OQ-2: strict rendering only throws on a MISSING variable,
never an extra one, so a uniform context has no cost and avoids a
per-target-only field. `hooks_json` is dropped from baseTemplateContext (no
template references it after step 1).
DEVIATION from §1.4 (flagging per task instructions): the design assumed
`{{destination}}` at the 14 Copilot powershell/bash probe sites would
substitute byte-identical because "no escapable character can occur." That
holds for the 7 bash occurrences (preceded by `/`), but the 7 powershell
occurrences are preceded by `\\` (JSON-escaped single backslash) directly
against the mustache open-brace, and Handlebars treats `\\{{expr}}` as
"literal backslash + evaluated expression," CONSUMING one of the two
backslashes (verified directly against Handlebars 4.7: `\\{{x}}` -> one `\`
+ evaluated x, not two). This silently dropped a path separator
(`...\plugins\core-copilot` instead of `...\plugins\\core-copilot`,
i.e. `\plugins\core-copilot` as the decoded literal instead of
`\plugins\core-copilot` -- concretely, `ac_hooks_content.py --posture true`
caught it as "not valid JSON: Invalid \escape"). Fixed by using a third
backslash (`\\\{{destination}}`) on the 7 powershell lines only, which
Handlebars renders as one literal backslash + the evaluated value -- the
same net two-backslash output the original literal had. Verified: 0
structural diffs across all 18 documents (both postures) against
492b6a7~1's original templates, and destination values do not cross-leak
between core-copilot/rosetta-copilot/rosetta-copilot-light.
Verified: `ac_hooks_content.py` ALL PASS at --posture true and --posture
false (scratch builds); full unit suite 952/952 unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…table (#315) The table keyed one layout per bare IDE target identity -- seven entries, which its own AC1 fixed as the whole inventory -- while the generator emits NINE distinct hook documents: Copilot and Cursor each own both a plugin form and a standalone form. A structure that cannot address the document it describes can only produce one document per target, so the two forms collapsed into byte-identical copies. Measured: the Copilot standalone form went from 60 bytes to 24443, an exact copy of the plugin form; the Cursor root document took plugin-form addressing where standalone-form addressing was required. Retired under the FR-HOOK-0003 precedent -- record kept, status Deprecated, reason in implementationNotes -- because FR-VAR-0070, FR-HOOK-0005, FR-GEN-0010, FR-GEN-0011 and FR-SET-0070 all referenced it. Those references are redirected in the same pass to the per-IDE templates and FR-GEN-0011. implementation is ToBeRemoved rather than Removed: hook-layouts.ts, PluginSpec.hookLayout and plugin-assemble-hooks-json.ts are still present at the time this record was written. It flips to Removed when that deletion lands. FR-VAR-0070 keeps Approved -- the conjunction it states is unchanged, only the second conjunct's home moves from a table entry to a template placeholder. Its new notes record the three by-design consequences of that conjunction (Copilot's camelCase sessionStart and preCompact registration, Cursor's absent placeholder) so they are not re-raised as defects: each is the deliberate no-double-delivery posture, with the same bootstrap bodies arriving through auto-loaded rules. Also swept pluginAssembleHooksJson out of the DATA-CFG-0002 processor list and the FR-ARCH-0039 cross-file processor list, and dropped hookLayout from the PluginSpec field list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…step 3/9) Deletes plugin-assemble-hooks-json.ts and its test module, and removes pluginAssembleHooksJson from all 7 target pipelines (spec/targets.ts) and the CLI processor catalog (cli.ts), per hooks-architecture.md §1.8/§3 step 3. emitsHooksJson moves into plugin-copy.ts and is resignatured to `(spec) => spec.hookModules.length > 0 || spec.bootstrap` — it no longer takes a layout, because document SHAPE lives entirely in the 7 templates now (steps 1-2). Its only remaining job is deciding WHETHER a hooks.json.tmpl frame is created at all for a hookless set (workflows, qe, search, modernization all declare bootstrap:false with an empty hook list). The frame-drop moves from a post-hoc filter (the deleted processor dropped frames pluginCopy had just created) to declining to create the frame in the first place: `collectTmplFrames` now takes an `emitsHooks` flag and skips any `hooks.json.tmpl` basename when it's false, and the `standaloneTemplates` loop applies the identical skip. Raw disk copy is unaffected -- copyDirRecursive already excludes .tmpl for every target. Test changes: two plugin-copy.test.ts cases that used `hooks.json.tmpl` as a stand-in filename for GENERIC .tmpl-frame-registration behavior (unrelated to hook emission) are renamed to `readme.md.tmpl` so they aren't coupled to the new skip. Two cases that genuinely exercise the standaloneTemplates hooks.json.tmpl path are given `bootstrap: true` so their spec actually emits hooks (previously frame creation was unconditional; now it depends on emitsHooksJson). plugin-assemble-hooks-json.test.ts (69 tests: 56 from an it.each over 7 layouts x 2x2x2 postures/bootstrap/modules, 13 direct) is deleted with its module -- all subtracted tests belong to the deleted unit. Verified: tsc clean; scratch builds at both postures exit 0 with no errors; `workflows-*`, `qe-*`, `search-*`, `modernization-*` outputs contain no hooks.json and no hooks/ folder; ac_hooks_content.py ALL PASS at both postures; unit suite 882/882 (952 - 70 removed with the deleted module). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#315) FR-GEN-0011 is rewritten from assemble-then-serialize to render-then-validate. Its old text mandated the defect: "exactly one raw-injection placeholder and no control flow ... no literal hook entry" is what collapsed the seven templates to a shared one-liner, after which nothing distinguished Copilot's marketplace form from its standalone form. The new statement puts each emitted document's complete structure back in its own template file, whose path in the preserved tree is its identity, and forbids giving a template its content by matching its filename. The JSON-validity property the old text was adopted for now comes from parsing what was rendered. That is strictly stronger, not merely equivalent: it also catches a malformed raw bootstrap injection, which serializing a built object cannot see, because that fragment is spliced in as text after the object would have been serialized. Hard error, unlike FR-GEN-0010's warn-and-continue on render failures -- a document that rendered but will not parse silently disables every hook in that plugin. Also in this pass: - FR-GEN-0010 loses the assembled-value clause; its render context becomes release variables, the bootstrap payload and the spec's output folder name. - FR-ARCH-0048 gains the output folder name and the post-render parse obligation. - FR-VAR-0071's rationale asserted the defect as design ("the two template frames are now byte-identical single-placeholder files"). Rewritten: the per-form difference lives in the template text, and a new criterion asserts it. - FR-VAR-0030 AC4 is CORRECT and currently failing -- left untouched, with the notes saying so, so it is not closed by editing the criterion. FR-VAR-0030 and FR-VAR-0031 now name the mechanism that exists (a declarative post-render mirror pair) rather than the SpecEntry/fileRename one they described. - FR-HOOK-0007's Copilot probe guard moves off commands/coding-flow.md, which the set split made absent from every core-based Copilot plugin, to .github/plugin/plugin.json -- the marketplace manifest every Copilot plugin of every set carries, and the file whose presence is exactly the proposition the probe tests. New criterion generalizes the rule. - FR-SET-0070 AC1/AC2 named per-set hook subsets no declared set has ever carried; restated over the two configurations plugins.json actually declares. AC3 kept as the named instance of the rule AC2 states generally. Status Approved -> Draft on FR-GEN-0011, FR-GEN-0010, FR-VAR-0071, FR-HOOK-0007 and FR-SET-0070: each changed an obligation or a criterion and awaits re-approval. FR-VAR-0030/0031 were already Draft. implementation stays ToBeModified where the code has not landed yet (FR-GEN-0011, FR-VAR-0071, FR-VAR-0030, FR-HOOK-0007); FR-VAR-0031 moves to Implemented because the mirror mechanism it now describes exists today. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…RGET_HOOK_MODULES (step 4/9) spec/targets.ts: modulesForTarget no longer takes a HookLayout — it takes a plain module-name list, narrowed and support-expanded exactly as before. The list comes from a new TARGET_HOOK_MODULES constant keyed by IDE FAMILY (hooks-architecture.md §1.9): 7 modules for claude/codex/copilot/cursor, 2 (dangerous-actions, read-once) for antigravity. This holds no event names, matchers, entry shapes or envelopes — only "which .js files this family's templates invoke by name" — so it is not HOOK_LAYOUTS coming back in disguise; it is a bundle-shipping fact consumed only by pluginSyncBundles. Verified by raw-text scan (not rendered — at deterministic_hooks=false the templates name no modules at all, yet bundles must still ship): every restored template under plugins/template-<family>/**/hooks.json.tmpl names exactly TARGET_HOOK_MODULES[family]'s tokens via a `[a-z][a-z0-9-]*\.js` scan across all 7 files. This same scan becomes the T5 consistency test in the schema-test area (step 10). Verified: tsc clean; scratch build ships core-antigravity/hooks/ with exactly 4 .js (dangerous-actions, read-once, read-once-reset, read-once-shared — the latter two via plugins.json's hookSupportModules expansion, never named literally in any template) and core-claude/hooks/ with exactly 8; ac_hooks_content.py ALL PASS both postures; unit suite 882/882 unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…re (#315) Swept the phrases the retired layout table left behind across the units that reference it only in passing -- titles, rationales, notes and implementationNotes all read as normative in this corpus, so each needed editing rather than only the statements. - GLOSSARY PluginProcessor term and NFR-0007's two-tier enumeration drop pluginAssembleHooksJson. - INT-IDE-0002, FR-VAR-0020, FR-VAR-0082 and FR-VAR-0083 restate "the layout declares a null bootstrap slot" as "the template carries no bootstrap placeholder", which is the same observable fact named at its real source. FR-VAR-0083's advisory-hook exclusion is restated against what the Antigravity template invokes; the behaviour asserted is unchanged and still holds. - ASSUMPTIONS OQ-4 claimed "Hook JSON is covered by this general structural rule like any other output." It is not: a paths-only oracle cannot see a document whose shape changed at a fixed path, which is exactly what the two hook forms are. Corrected and pointed at NFR-0012. - STRUCTURES.md needed almost nothing -- it was never updated by the refactor and already describes this architecture, which is independent corroboration from the committed structure spec rather than from the git baseline. Two edits: the Cursor root template is also rendered into the main Cursor tree, and the Copilot root hooks.json is a declarative mirror pair, not a SpecEntry copy. - docs/ARCHITECTURE.md called Copilot's hooks/hooks.json.tmpl the plugin form, contradicting its own hook-forms table nine paragraphs later. Corrected. - TRACE gains rows for FR-VAR-0071 (G11) and FR-VAR-0031 (G12), which owned no row despite being referenced; FR-SET-0070's row gains its .AC7; the coverage assertion moves to 83 units / 117 links and records why the retired DATA-CFG-0008 carries no row. - CHANGES records RECONCILIATION-15 (the architecture change) and RECONCILIATION-16 (the two remediation units closed in 9d6c340). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ar (#315) The concurrent implementation advanced past migration step 4 while this sweep was being written, so the ToBeModified notes needed re-grounding against what is actually in the tree rather than what was there an hour ago. Verified landed: the seven literal hooks.json.tmpl files carry their own document's structure again (template-copilot/hooks/hooks.json.tmpl is back to a literal "sessionStart": [] with .github/hooks/ addressing; template-cursor/hooks.json.tmpl is back to .cursor/hooks/); {{destination}} at exactly 14 sites in the Copilot plugin-form template, plumbed from spec.destination into the render context in generate.ts; plugin-assemble-hooks-json.ts deleted with the frame-drop folded into pluginCopy; PluginSpec.hookLayout gone; HOOKS_PSEUDO_FOLDER relocated to spec/hooks.ts. Verified outstanding: the post-render JSON.parse hard error, and deletion of the residual hook-layouts.ts, which no longer has an importer. Status changes that follow from the facts, not from the plan: - FR-VAR-0071 -> Implemented. Its subject is the two template forms, and they now demonstrably differ in the template text; two files cannot collapse into one document. - FR-GEN-0011 stays ToBeModified -- half of it (post-render validation) has not landed -- with the notes now separating landed from outstanding. - FR-VAR-0030 stays ToBeModified deliberately, even though the template that caused its AC4 failure is fixed: its criteria assert properties of generated OUTPUT, and that cannot be claimed until the tree is rebuilt and ac_hooks_content.py passes over it. Noted rather than quietly flipped. - DATA-CFG-0008 stays ToBeRemoved, with the note narrowed to the one file left. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eld (steps 5+6/9) Deletes spec/hook-layouts.ts entirely: HOOK_LAYOUTS, HookLayout, HookBinding, BootstrapBinding, COPILOT_PLUGIN_PATH, copilotProbeBash/Powershell, and the entry/envelope helpers (command, cursorEntry, copilotPluginEntry, plainHooks, versionedHooks). Every document shape they held now lives in the 7 restored literal templates (steps 1-2); nothing in src/ references HOOK_LAYOUTS except two now-historical comments in the new file's own header explaining what was retired. Rewrites the FR-ARCH-0005 rationale comment in plugin-emit-distribution-root.ts (§3 step 5's explicit "also") to drop its "(Contrast HOOK_LAYOUTS, correctly a table...)" aside, now a dangling reference to a deleted symbol; the point it was illustrating (compose behaviour only where it applies) stands on its own. DEVIATION from the step table (flagging per task instructions): step 5 and step 6 (drop PluginSpec.hookLayout, types.ts:173) are combined into this one commit rather than landing separately. They are not independently verifiable in isolation: types.ts's `hookLayout: HookLayout | null` field imports HookLayout from hook-layouts.ts, so deleting that module while the field declaration (and targets.ts's `hookLayout: layout` assignment) still reference the type cannot typecheck. Landing step 5 alone would require either leaving a dangling type import (breaks tsc immediately) or introducing a throwaway placeholder type -- both worse than a single combined, clean commit. Rollback is unaffected: reverting this one commit restores both the module and the field together, which is exactly how they were coupled before it landed. HOOKS_PSEUDO_FOLDER is relocated to a new spec/hooks.ts (not spec/targets.ts, though the plan floated either) rather than merged into spec/targets.ts, because plugin-copy.ts's buildManifestOverlay also needs it and spec/targets.ts already imports plugin-copy.ts (to compose the per-target processor pipeline) -- importing HOOKS_PSEUDO_FOLDER back from targets.ts into plugin-copy.ts would recreate exactly the plugin-copy<->targets.ts import cycle the original hook-layouts.ts placement comment warned about. A standalone one-export module breaks the tie without reviving the cycle. Verified: tsc clean; `grep -r HOOK_LAYOUTS src/` matches only two comments inside the new spec/hooks.ts header explaining the historical name (no functional reference); scratch builds exit 0 both postures with no errors; ac_hooks_content.py ALL PASS both postures; unit suite 882/882 unaffected (plugin-copy.test.ts's 19 now-meaningless `hookLayout: null,` literals removed -- PluginSpec no longer declares the field, so TypeScript's excess- property check on the `Partial<PluginSpec>` object literals would otherwise fail the build; no assertions changed). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
plugin-render-templates.ts: immediately after a `.json`-targeted template
renders successfully, JSON.parse the rendered text. On failure, record a
HARD error naming the target, the output file and the parser message, and
do not emit the rendered sibling — distinct from the warn-and-continue
handling FR-GEN-0010 already applies to render failures (missing template,
unplumbed variable), per hooks-architecture.md §1.6/§3 step 7.
This is strictly stronger than the JSON-validity guarantee the deleted
buildHooksDocument+JSON.stringify pipeline provided: it also catches a
malformed raw `{{{bootstrap_hooks}}}` injection, which serializing a built
object can never detect (parsePayloadEntries only guarded the bootstrap
fragment, and only while that assembler ran — both gone since step 3).
Mechanism: plain `JSON.parse` inline in the render path, NOT the shared
schema validator introduced for the hook-schema test area (step 10). The
owner's design permits, but does not require, sharing a validator between
the production check and the tests; wiring the per-IDE shape validator into
this path would mean it must accept every document the 7 templates render
at both postures or it would hard-fail real builds, which is a much larger
compatibility surface than "is this valid JSON." Kept separate deliberately.
Added 4 unit tests (plugin-render-templates.test.ts): a stray-comma .json
template hard-errors with no emitted sibling; a malformed
{{{bootstrap_hooks}}} injection hard-errors the same way; a non-.json target
(e.g. a rendered .md) is not JSON-validated at all; a valid .json render is
unaffected. 18/18 in that file (14 pre-existing + 4 new).
Verified: tsc clean; scratch builds at both postures exit 0 with no render
errors on any of the 7 real templates; ac_hooks_content.py ALL PASS both
postures; unit suite 886/886 (882 + 4 new).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…on (step 8/9) OWN COMMIT, separately revertible, per the task's explicit instruction and hooks-architecture.md OQ-1's recommendation. spec/bootstrap-manifest.ts: COPILOT_PLUGIN_ROOT_BASH / _POWERSHELL guarded on `commands/coding-flow.md` to decide whether to emit the "Rosetta Plugin Path" additionalContext at all. That file moved to the `workflows` set under #315's set split and is absent from every `core`-based Copilot plugin, making the guard a permanent no-op there (it still worked for `rosetta`, which ships both `core` and `workflows`). Fixed per hooks-architecture.md §1.5: guard on `.github/plugin/plugin.json` instead — Copilot's marketplace manifest, NOT at the plugin root (verified: `<set>-copilot/` contains `.github/ commands/ hooks/ hooks.json rules/ skills/`, no root plugin.json). It is emitted unconditionally by pluginCopy's manifest overlay for every Copilot spec regardless of set contents, content-independent, and is literally the file this probe lives alongside in the output tree — the strongest available guard, per §1.5's rejected-alternatives analysis (bare `-d` directory test passes on a stale/partial dir; guarding on `rules/bootstrap-alwayson.md` couples an install-location probe to instruction content and would silently re-break under a future bootstrap set lacking that rule). This is a FIX, not a restore: 492b6a7~1 carried the same `commands/coding-flow.md` literal, correct then because only one set existed. It is a defect the #315 set split created, and is inside #315's blast radius, which is why the task places it in scope as its own commit rather than deferring it (§7 OQ-1). Also (§3 step 8, same commit): spec/plugin-sets.ts's variant reader now pattern-validates destinationSuffix against `/^(?:-[a-z0-9]+(?:-[a-z0-9]+)*)?$/` — admits "" and "-light" (plugins.json's only two current values), rejects anything with an escapable-by-Handlebars character. destinationSuffix feeds `spec.destination`, which is now a template variable ({{destination}}, steps 1-2); it was previously read unvalidated while the sibling set-name field already was (plugin-sets.ts's existing check), a latent input-validation gap this closes. Verified: tsc clean; scratch builds at both postures exit 0, plugins.json's real "" and "-light" suffixes pass validation; core-copilot/.github/plugin/ plugin.json exists at the exact path the new probe guards on; ac_hooks_content.py ALL PASS both postures; unit suite 886/886 unaffected (no test asserted the old commands/coding-flow.md literal). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e the landed units Three defects in the preceding commits, plus a status reconciliation. 1. Dependency cycles. hooks-architecture.md §5.2 proposed `FR-GEN-0011 depends: FR-GEN-0010, FR-ARCH-0048, FR-VAR-0071, FR-VAR-0031, NFR-0012`. Three of those five point at units that specialize or verify FR-GEN-0011 and therefore already depend on it, producing seven cycles through FR-GEN-0011 -- including the direct FR-ARCH-0048 <-> FR-GEN-0011 pair. The governing rule depends on nothing that implements it: FR-GEN-0011 now depends on FR-GEN-0010 alone, and FR-VAR-0071 gains the edge to FR-GEN-0011 in the direction that specialization runs. Verified zero cycles across every depends edge in the area. 2. FR-ARCH-0048 gained a post-render validation obligation and a criterion but kept status Approved. Flipped to Draft with a note, and added to RECONCILIATION-15's Status line, which listed it in neither column. 3. FR-HOOK-0007's notes read as though the guard literal had changed on 2026-09-03. It has not -- bootstrap-manifest.ts still carries two commands/coding-flow.md occurrences. It is the CRITERION that changed; reworded, and the implementation node corrected to ToBeModified to match its own notes. Implementation statuses re-grounded after the concurrent implementation landed migration steps 5, 6 and 7: hook-layouts.ts, plugin-assemble-hooks-json.ts and PluginSpec.hookLayout are deleted with no HOOK_LAYOUTS reference left in the tree, and plugin-render-templates.ts JSON.parse's every rendered JSON document as a hard error. DATA-CFG-0008 -> Removed; FR-GEN-0011 -> Implemented. FR-VAR-0030 stays ToBeModified on purpose: its criteria assert generated OUTPUT, and the tree has not been rebuilt against ac_hooks_content.py. Also corrected two overreaches in FR-VAR-0070's new notes: the marketplace plugin ships rule/instruction files generally, not the .instructions.md documents the standalone transform produces; and preCompact is a hook-registration consequence, not a bootstrap-delivery one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The architecture's C1 refuted it with evidence: plugin-copy.ts reads standalone templates from the shared preserved-template directory, never from the main plugin's output, so the two builds are independent and merely render the same source file. The document is an incidental emission of the preserved-tree template walk, read by nothing in marketplace mode, retained because FR-VAR-0030 mandates pre-existing shipped output -- not a staging source. I wrote the wrong word when authoring this unit; the requirements sweep flagged it and correctly did not overstep its brief to change it. Assertions unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…osture (step 9/9) The ONLY commit in this migration that writes plugins/, per hooks-architecture.md §3 step 9: steps 1-8 verified only against scratch builds, so the previously-committed 49-folder tree still carried the Copilot/Cursor form-collapse regression this migration fixes. Regenerated with `npm --prefix src/rosettify-plugins start -- --release r3 --deterministic-hooks false --source <repo-root> --output <scratch>`, the shipped default posture (FR-CLI-0012: omitting --deterministic-hooks defaults to false; docs/ARCHITECTURE.md:343 and scripts/pre_commit.py:86 confirm this is the posture `plugins/` ships at). Never ran `npx rosettify-plugins@latest` (would run the published, pre-plugins.json build and destroy the 49 output folders). Procedure, to avoid a repeat of an earlier destructive overwrite in this session: built to a scratch directory first; diffed its folder-name set against `plugins/`'s existing 49 names (identical, confirmed by diff before touching anything); replaced each of the 49 named folders in `plugins/` with its scratch counterpart one at a time (rm -rf + cp -R by name, never a wildcard or `git add -A`); confirmed the folder-name set was still exactly the committed 49 afterward; staged only those 49 paths by name. Result: 34 files changed under 25 of the 49 folders. All changes are either (a) hooks.json content -- the Cursor and Copilot standalone forms now correctly diverge from their plugin-form siblings (the regression) -- or (b) unrelated instruction-source drift the regeneration incidentally picked up (skills/backlog/SKILL.md's orchestration wording, present on the instructions source already and untouched by this migration) since the committed tree was last regenerated. No file outside these 49 folders was touched; no folder was added or removed. Verified: `ac_hooks_content.py` (no --tree argument, i.e. against this committed tree) ALL PASS, 84/84 assertions, golden cross-check PASS; full unit suite 886/886 (unaffected -- this step touches only generated output, no source). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…p 10)
New tests/hook-schema/ — unit-test level, distinct from both the generator-
pipeline tests (tests/unit/plugin-processors/**) and the output-tree content
gate (plans/issue-315-plugin-sets/verify/ac_hooks_content.py, which this
does not duplicate). No file here runs the generator or writes a hooks.json
to disk: every check either hand-builds an in-memory document or renders one
real .tmpl file in memory with Handlebars (the same library
plugin-render-templates.ts uses) and asserts on the parsed result — never
writing it anywhere.
Mechanism (tests/hook-schema/validator.ts): a per-IDE shape validator table,
each function citing its source doc line range in a comment —
validateClaudeOrCodexDocument (claude-code.md:87-107, codex.md:86-106),
validateCopilotDocument + validateCopilotMergedContext (copilot.md:16,
:66-87, :301-315; FR-HOOK-0005/FR-HOOK.md:111-112), validateCursorDocument
(cursor.md:105-122), validateAntigravityDocument (antigravity.md:44,
:60-68), plus scanModuleTokens for the T5 raw-text module scan. This is a
test-only module, deliberately NOT wired into step 7's production
JSON.parse check (see that commit's rationale: the full per-IDE shape
validator is a much larger compatibility surface than "is this valid
JSON", and hard-failing real builds against it was not the owner's ask).
Coverage, per file:
- claude-codex.schema.test.ts (10 tests): shared {hooks:{PascalEvent:
[{matcher,hooks:[{type,command}]}]}} shape, both postures, against the
real restored templates.
- copilot.schema.test.ts (15 tests): the CURRENTLY-ENABLED mixed-casing
shape (camelCase sessionStart/preCompact flat, PascalCase PreToolUse/
PostToolUse grouped with bash+powershell entries) against the real
template; the merged top-level+nested additionalContext requirement
exercised directly against the production buildCopilotHookPayloadJson
builder (not a hand-reimplementation); and the RESERVED-but-OFF shapes —
PascalCase SessionStart and PascalCase PreCompact — hand-built and
validated, so their shapes are already known correct if ever enabled.
Encodes owner constraint 1: no template registers PascalCase
SessionStart/PreCompact, and PreToolUse/PostToolUse are registered
PascalCase-only (never also camelCase, which would double-fire the CLI).
- cursor.schema.test.ts (12 tests): the flat, no-hookSpecificOutput-wrapper
shape against both real templates (plugin form and standalone form) at
both postures; the RESERVED sessionStart shape, hand-built, since no
Cursor template wires it today (owner constraint 1).
- antigravity.schema.test.ts (10 tests): the two-shapes-in-one-document
registration (wrapped PreToolUse/PostToolUse vs flat PreInvocation/
PostInvocation/Stop) against the real template at both postures, and the
antigravity.md:44 rule that SessionStart is not a valid event for this
IDE at all.
- module-consistency.schema.test.ts (7 tests): T5 — raw-text `<module>.js`
token scan of every real template, set-equality against
spec/targets.ts's TARGET_HOOK_MODULES (7 modules for claude/codex/
copilot/cursor, 2 for antigravity), plus an explicit assertion that
read-once-shared never appears literally in any template (it reaches
every target only through plugins.json's hookSupportModules expansion).
Verified: ad hoc `tsc --noEmit` over src/**+tests/hook-schema/** together
(neither is covered by the project's tsconfig.json, which excludes
`tests/` — same as every pre-existing test file) is clean; full suite
940/940 (886 + 54 new), 60 files (55 + 5 new); ac_hooks_content.py (default
tree) still ALL PASS, unaffected by test-only additions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The equivalence gate flagged skills/backlog/SKILL.md as an unaccounted diff in every plugin. Not a regression: commit b5388f7 on main changed one line in instructions/r3/core/skills/backlog/SKILL.md ("One story, one context" -> "Bounded stories, one context ... Disjoint areas in parallel"), the merge at Step 0 brought it in, and the generator correctly propagated it everywhere. The golden snapshot simply predates it. Added as permitted class 23, scoped to that one file with the originating commit named. A blanket "anything that changed on main" class would blind the gate to real regressions, which is the property that makes it worth having. All four gates now green: STRUCTURE, EQUIVALENCE, CROSSREFS, and HOOKS CONTENT at both --deterministic-hooks postures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…arrative Both units held ToBeModified because their criteria assert generated output and the tree had not been rebuilt. It has been, and every criterion was re-checked against that output rather than inferred from source: FR-VAR-0030 — all four hold. Three hooks.json at the declared paths, root and .github/plugin byte-identical, and hooks/hooks.json the standalone form with an empty session-start array, matching the pre-#315 snapshot, across all three hook-emitting copilot plugins. FR-HOOK-0007 — all nine hold, including the guard on .github/plugin/plugin.json. That guard holds structurally rather than by luck: the copilot spec emits that manifest for every set, so unlike commands/coding-flow.md it cannot rot when set content moves between folders. Both implementation -> Implemented; status stays Draft, approval independent. Records are target-state; CHANGES.md is the only change log. Removed progress and correction narrative from five units -- dated CLOSED/UPDATE entries, "CORRECTION: the earlier note ...", "at the time this record was written", and a paragraph explaining a prior draft had since been fixed. The two closed units' implementationNotes were rewritten to the concise files-affected form (2397->679 and 2500->718 chars). Retirement reasons on Deprecated units are retained: those state why a unit is gone, which is current content, not history. RECONCILIATION-17 and -18. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Root cause: closing two units against a rebuilt tree, I wrote dated verification narrative into implementationNotes; a corpus sweep then found the same defect in four more units. Test that generalizes: if a sentence would not survive in a spec that was never revised, it does not belong in the spec. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both described defects that commit 843d238 fixed this session; only the records lagged. Verified by running the CLI rather than by inspection: an unresolvable --domain token exits 1 naming the folder and the absolute path searched and creates no output directory, and generate.ts:86 carries the branch that lets a legitimate empty match exit zero. RECONCILIATION-19. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both statements already described the current code; only the tags lagged. NFR-0007's statement lists the per-vocabulary model-normalization processors, and its criterion holds: file-processors/, plugin-processors/, escaping/, spec/model-maps.ts and generate.ts are separate units. FR-CLI-0021's two criteria hold -- cli.ts resolves outputDir to the --output value when given and <source>/plugins otherwise, verified in code and by use. Its ToBeModified tag dated from RECONCILIATION-9, when the --source model was written into the spec ahead of the code; the code has since adopted it. RECONCILIATION-20. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The statement already read "the target's model-normalization processor" -- the per-vocabulary wording. The note describing drift against a single fileNormalizeModels was stale; that dispatcher was deleted under FR-ARCH-0005 and replaced by four per-vocabulary processors, which are what a SpecEntry pipeline composes today (targets.ts:409). The unit defines HOW a copy is expressed, not that any target currently needs one, so "nothing declares it" was the wrong test. Both halves hold: folder duplication is available only as an additional SpecEntry, and no pre-copy pass exists anywhere in src/. Title prefixed "Policy:" so the conditional shape is legible at a glance -- a "Where X is needed, do it this way" rule reads as an unimplemented feature otherwise, which is exactly how this unit was misread. RECONCILIATION-21. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five FR-COPY units prescribe a mechanism rather than assert a feature -- each opens "Where ..." and names the processor that shall do the work, often forbidding an alternative. Without a marker such a unit scans as an unbuilt feature, which is exactly how FR-COPY-0033 was misread. Prefixed "Policy:". Left unmarked: FR-ARCH-0024, FR-GEN-0001/0003/0010, FR-HOOK-0020, FR-VAR-0072 and NFR-0012. They open with "Where" but assert features, and marking them would cost the prefix its meaning. FR-VAR-0030 and FR-VAR-0031 both declared depends on FR-COPY-0033, the SpecEntry duplication policy, while the Copilot root hooks.json is a post-render mirror pair applied by a generic mirror processor -- a different mechanism, as both statements now say. Repointed to DATA-CFG-0002, whose statement declares that mechanism. Verified no dependency cycles. RECONCILIATION-22. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner approval of every unit standing Draft with implementation=Implemented. Handles both schema forms in the corpus; status, approved_by and changed move together as the template intends. FR-HOOK-0005's note existed only to explain why the unit was unapproved, so it was removed along with the state it described. RECONCILIATION-23. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Told twice in one session -- a PR body opened by explaining a reversal, and requirement notes carried dated CLOSED/UPDATE entries. Generalizes the existing requirements-specific rule to every reader-facing artifact, and to the shape of a sentence rather than a word list: a comparative clause implies a before-state even when no past-tense verb appears. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Implements #315. Splits
instructions/r3/core/into five sibling domain sets —core,workflows,qe,search,modernization— and builds every set × IDE combination from a declarative catalog in one generator invocation.Output is 49 folders:
rosetta-<ide>(the full combo),rosetta-<ide>-light, and the five split sets.core-<ide>is the core set alone; the full plugin isrosetta-<ide>.scripts/pre_commit.pyholds exactly one generator call. Per-set content is tabulated indocs/ARCHITECTURE.md.Generator
src/rosettify-plugins/plugins.jsondeclares sets, folders, variants, and per-set bootstrap and hook lists. Validated at pre-flight; an unknown field aborts before any write.spec.nameis the bare IDE id; the set ridesspec.destination. Directive tokens namespacedtarget-/ide-/set-/profile-.template-<ide>folders, shared by every set. Profile descriptors carry{ modelOverrides }; suffixes live on the set variant.--configselects a catalog;--domainfilters sets, naming the folder it cannot resolve and exiting non-zero before any write.harnessskill, verbatim-protected. Split sets are lightweight-only, so their union equalsrosetta-light, notrosetta.Hook configuration
Each emitted
hooks.jsoncomes from its own literalhooks.json.tmpl, seven in all, the file's path in the preserved tree being its identity. Copilot and Cursor each carry two — a plugin form and a standalone form — so the nine emitted documents keep their distinct addressing: the plugin form probes the install location, the standalone form uses repository-relative paths and an empty session-start array.Only two values are parameterised: the output folder name and the probe guard. Rendering is Handlebars
strict: true, followed byJSON.parsevalidation that hard-fails naming the target, covering both the template text and the raw bootstrap injection spliced into it.The Copilot plugin-root probe guards on
.github/plugin/plugin.json, which every Copilot plugin carries regardless of which set built it.Bootstrap reaches Copilot and Cursor through their auto-loaded rules, not through hooks; their templates register no session-start payload.
tests/hook-schema/asserts the per-IDE document envelope and entry shapes at unit level, including the registrations that stay off, so enabling one later is a template edit against a known-good shape.Verification
Four gates in
plans/issue-315-plugin-sets/verify/, all green against a snapshot of the output taken before anything moved:ac_structure.sh,ac_equivalence.py,ac_crossrefs.py— the 12 acceptance criteria.ac_hooks_content.py(NFR-0012) — asserts the relationships between thehooks.jsondocuments a target emits: path set, required identity, required distinctness, form markers, JSON validity, plus a content digest per document. Relationships rather than frozen bytes, so it stays valid as the bootstrap payload moves with the instruction source. Green at both--deterministic-hookspostures, and validated in both directions — passing on the pre-change snapshot, failing when a document's form is altered.Tests: 940 / 60 files (rosettify-plugins), 1441 (hooks), 432 (curiocity). Requirements under
docs/requirements/plugin-generator/match the code; only the backlogged order-token pair isToBeModified.Before merge
scripts/pre_commit.pycallsnpx -y rosettify-plugins@latest. The published generator predatesplugins.jsonand would regenerateplugins/with the old build, destroying the 49 folders. Publish the generator first, then rely on that call.