* feat(#2930): fragmentize plan-phase.md workflow into per-runtime-composed sections Adds src/workflow-fragments.cts (in-file <!-- gsd:section --> marker parser/composer, ADR-1671 epic #1671 Phase 3), wires it into bin/install.js's copyWithPathReplacement emission path, and pilots the marker grammar on gsd-core/workflows/plan-phase.md. Bookkeeping ripple for the new src/*.cts module: .gitignore, eslint.config.mjs, docs/INVENTORY.md + docs/INVENTORY-MANIFEST.json, and a CONTEXT.md glossary entry. Amends ADR-1671 with open questions 1 and 2 resolutions and records the closed when= applicability grammar. Adds docs/reference/workflow-fragments.md and an ARCHITECTURE.md section documenting the marker authoring model. * fix(#2930): put allow-test-rule issue ref on the same line as the marker lint-allow-test-rule-refs.cjs requires the #NNN issue reference on the same source line as `allow-test-rule:`; it was one line below and read as an unreferenced novel exemption. * docs(#2930): link the orphaned gate-predicates reference from the docs index Found while adding the workflow-fragments reference doc: docs/reference/gate-predicates.md shipped without an entry in docs/README.md, so it was unreachable from the docs index. Fixed inline rather than deferred. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#2930): scope composition to workflows, add typed failure reasons Review findings from two orthogonal passes: - Scope composeWorkflow to gsd-core/workflows/ only. It previously ran on every .md the installer copied, so a future agent/command/reference doc documenting the marker syntax with an unfenced example would have been mis-parsed and silently stripped — a lossy drop the phase forbids. - Add a frozen REASON enum; failures attach a typed .reason and tests assert on it instead of matching free-form message text (CONTRIBUTING.md:635-694). - Derive the property generator's when= values from WHEN_VOCABULARY instead of duplicating them (DEFECT.GENERATIVE-FIX). - Add adversarial parser fixtures: Unicode headings, NUL, U+FFFD, BOM, fence-within-fence, tilde and indented fences, lone-CR marker line. - Document why --mvp is structurally unmarkable: its content is interleaved, not sectioned, so the whole-line grammar cannot reach it. Also fixes two stale tests on this branch, each reproduced on the unmodified tree before correction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#2930): retarget the pilot from plan-phase to execute-phase The full remote matrix went red on both Linux lanes. Root cause was ours: tests/phase6-capstone-conformance.test.cjs holds a PRE_PHASE6 ceiling of 94519 bytes for plan-phase.md, asserting an ADR-857 Phase-6 completion property. That is a third size gate beyond the tier caps and the differential ratchet, and it left plan-phase.md just 36 bytes of headroom rather than the 3821 computed from the XL cap. The 330 marker bytes overran it by 294. Raising the ceiling is not an option: it is a red line certifying another ADR's completion. plan-phase.md is reverted to byte-identical origin/next and the pilot moves to execute-phase.md, which has 728 bytes of headroom under its own ceiling and lands at 93147 with 3 marker pairs. The vocabulary narrows to the atoms actually used: always, flag:--wave, state:gap-closure-phase, state:has-prior-phases. Recorded in the ADR: every branch the epic names lives in plan-phase.md, which cannot be fragmentized until caps move from source to emitted bytes. That is direct evidence for the epic's premise and may reorder phases 3-4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(#2930): backfill changeset PR number (#2972) * fix(#2930): make the emission install tests portable on Windows The windows-latest lane went red on three tests in the new install suite; Linux was green. Both causes were in the test harness, not the module. Root normalization: the opencode converter always embeds the install root forward-slashed, but the tests stripped it with the native-separator string from mkdtemp. On Windows that never matched, so the root leaked through unstripped — and because the real and stub install roots have different prefix lengths, that length difference landed directly in the byte-delta assertion (344 observed vs 275 expected). Normalize both text and root to one separator form before stripping. @-ref resolution: the helper stripped only the @~/ and @$HOME/ forms, so a Windows absolute ref (@C:/Users/...) fell through and was joined onto the root, producing ...\@C:\Users\... Strip the @ first, then detect absoluteness from the token's own shape (POSIX, drive-letter, or UNC) with no platform branching, so every OS takes the same path. Neither assertion was weakened; the exact-equality byte check is the point of the test and still holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(#2930): document every REASON member and guard the doc/enum parity Code review found the reference doc's 'Fails closed' list covering 10 of the 11 frozen REASON members — MALFORMED_ATTRIBUTES (parseAttrs rejects malformed key="value" syntax) had no bullet, and it is distinct from UNRECOGNIZED_ATTRIBUTE, which is valid syntax with an unknown key. Two parallel surfaces sharing one constant with nothing asserting they agree is the DEFECT.GENERATIVE-FIX class, so the same commit adds the parity assertion: the test derives the enum side from the built module and the doc side by parsing the reference page, keyed on the reason IDENTIFIER rather than prose so a reworded bullet does not break it, and reports set differences in both directions by name. Proven non-vacuous: removing the MALFORMED_ATTRIBUTES bullet turns the suite red naming that exact member; restoring it returns 44/44. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,9 @@ const {
|
||||
} = require('../gsd-core/bin/lib/worktree-base-ref.cjs');
|
||||
const { resolveInstallPlan } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
|
||||
const { createImperativeAdapter } = require('../gsd-core/bin/lib/adapter-imperative.cjs');
|
||||
// #2930 (epic #1671 Phase 3): strips `<!-- gsd:section -->` markers from
|
||||
// workflow .md content at emit time, before any per-runtime rewrite runs.
|
||||
const { composeWorkflow } = require('../gsd-core/bin/lib/workflow-fragments.cjs');
|
||||
const runtimeArtifactConversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
// Canonical set of hook files shipped to users. Imported here so writeManifest()
|
||||
// records exactly the same set that build-hooks.js copies to hooks/dist/, making
|
||||
@@ -7786,6 +7789,32 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
||||
// Replace ~/.claude/ and $HOME/.claude/ and ./.claude/ with runtime-appropriate paths
|
||||
// Skip generic replacement for Copilot/Antigravity — their converters handle all paths
|
||||
let content = fs.readFileSync(srcPath, 'utf8');
|
||||
|
||||
// #2930 (epic #1671 Phase 3): strip `<!-- gsd:section -->` markers
|
||||
// BEFORE any per-runtime rewrite so a `.claude/` -> `.windsurf/` regex
|
||||
// (or any other converter below) never reaches inside a marker
|
||||
// attribute and corrupts it. composeWorkflow is a no-op (byte-identical
|
||||
// return) for the 88+ workflows and every non-workflow .md that carries
|
||||
// no markers, and for a malformed marker it throws loudly naming
|
||||
// srcPath — never emit a half-composed workflow.
|
||||
//
|
||||
// Scoped to gsd-core/workflows/ ONLY (two independent reviewers,
|
||||
// chore/2930): copyWithPathReplacement is the emit path for every .md
|
||||
// under gsd-core/, skills/, and commands/ (see the three call sites),
|
||||
// not just workflows. A doc that merely DOCUMENTS the marker syntax
|
||||
// with an unfenced example (docs/reference/workflow-fragments.md is
|
||||
// the live instance of this class, though not under the install tree
|
||||
// today) would otherwise get silently mis-parsed as a real marker and
|
||||
// that line lossily dropped — a file class issue #2930 never scoped
|
||||
// to. Path is normalized UNCONDITIONALLY (backslash paths arrive on
|
||||
// Linux too — CONTEXT.md path-separator rule) and checked as a
|
||||
// path-segment match so the recursive descent (srcPath may be several
|
||||
// directory levels below gsd-core/workflows/) is still caught.
|
||||
const normalizedSrcPath = srcPath.replace(/\\/g, '/');
|
||||
if (/(?:^|\/)gsd-core\/workflows\//.test(normalizedSrcPath)) {
|
||||
content = composeWorkflow(content, { sourcePath: srcPath });
|
||||
}
|
||||
|
||||
if (!dispatch.mdSkipGenericRewrite) {
|
||||
const globalClaudeRegex = /~\/\.claude\//g;
|
||||
const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
|
||||
|
||||
Reference in New Issue
Block a user