* 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:
@@ -355,6 +355,32 @@ The `CONTEXT.md` predicate fact-store — every backtick-wrapped `CLASS.subkey=v
|
||||
|
||||
The committed index intentionally carries **no `line` field** for any predicate (ADR-1671 open question 4, resolved by #2928) — committed-but-uncompared metadata goes silently stale, the same defect class the drift-guard exists to catch, with the alarm removed. The live `gsd-tools query context-predicates` parse still returns `line`/`section` for callers that want to cite a source location. See [ADR-1671](adr/1671-dynamic-context-management-platform.md) and [CLI Tools Reference](CLI-TOOLS.md#query-context-predicates).
|
||||
|
||||
### Workflow Fragmentization and Emission (`src/workflow-fragments.cts`, ADR-1671)
|
||||
|
||||
Workflow markdown under `gsd-core/workflows/*.md` can mark one or more sections with an
|
||||
in-file `<!-- gsd:section id="<id>" when="<when>" -->` / `<!-- /gsd:section -->` pair. A
|
||||
compiled parser/composer seam (generated to `gsd-core/bin/lib/workflow-fragments.cjs` per
|
||||
ADR-457) partitions a marked document into fragments and recomposes them through the shared
|
||||
`context-composer.cjs` budget seam (ADR-1671, #2929) before any per-runtime converter sees the
|
||||
text — so a marker attribute can never be corrupted by a `.claude/` → `.windsurf/`-style
|
||||
path-rewrite regex. `bin/install.js`'s `copyWithPathReplacement` calls `composeWorkflow` on
|
||||
every workflow file at emit time; an unmarked file (88 of the 89 shipped workflows today)
|
||||
parses to a single implicit fragment and round-trips byte-identical, so this is a no-op for
|
||||
every workflow that hasn't opted in yet.
|
||||
|
||||
Every fragment in this phase carries the `verbatim` strategy, so composition is structurally
|
||||
non-lossy — nothing is trimmed regardless of budget. Fence and HTML-comment interleaving
|
||||
reuses the same LOCAL, single-pass, mutually-suppressing scan discipline as
|
||||
`context-predicates.cts` (see above), so a marker-shaped line inside a fenced code block or an
|
||||
unrelated comment is never misread as structural. Markers are **stripped at emit** — the
|
||||
installed artifact carries no build metadata and is smaller than the source by exactly the
|
||||
stripped marker bytes.
|
||||
|
||||
See [Reference: Workflow fragments](reference/workflow-fragments.md) for the full marker
|
||||
grammar, the frozen `when=` vocabulary, and fail-closed authoring rules, and
|
||||
[ADR-1671](adr/1671-dynamic-context-management-platform.md) (open questions 1 and 2) for why
|
||||
in-file markers were chosen over separate fragment files or a sidecar manifest.
|
||||
|
||||
### CLI Tools (`gsd-core/bin/`)
|
||||
|
||||
Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `gsd-core/bin/lib/` (see [`docs/INVENTORY.md`](INVENTORY.md#cli-modules) for the authoritative roster):
|
||||
|
||||
@@ -466,6 +466,7 @@
|
||||
"verification.cjs",
|
||||
"verify-command-router.cjs",
|
||||
"verify.cjs",
|
||||
"workflow-fragments.cjs",
|
||||
"workstream-inventory-builder.cjs",
|
||||
"workstream-inventory.cjs",
|
||||
"workstream-name-policy.cjs",
|
||||
|
||||
@@ -549,6 +549,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
|
||||
| `verification.cjs` | Verification-status routing — consolidates pass/gaps_found/human_needed status from phase verifier-emitted VERIFICATION.md frontmatter (#651) |
|
||||
| `verify-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools verify` |
|
||||
| `verify.cjs` | Plan structure, phase completeness, reference, commit validation |
|
||||
| `workflow-fragments.cjs` | In-file `<!-- gsd:section id= when= -->` marker parser/composer for GSD workflow markdown (ADR-1671, #2930) — `parseWorkflowSections` (fence/HTML-comment-aware document partition into explicit/gap sections, fail-closed on malformed/unclosed/nested/duplicate markers or an unknown `when=`), `toFragments` (maps sections to `context-composer.cjs` `verbatim` fragments — non-lossy by construction), and `renderFragments`/`composeWorkflow` (compose-within-budget then join, run BEFORE per-runtime converters so a marker attribute never reaches a path-rewrite regex). `WHEN_VOCABULARY` is a frozen 4-atom applicability set (`always`, `flag:--wave`, `state:gap-closure-phase`, `state:has-prior-phases`); widening it is an ADR amendment, not an organic edit. Compiled from `src/workflow-fragments.cts` |
|
||||
| `workstream-inventory-builder.cjs` | Pure workstream inventory projection builder |
|
||||
| `workstream-inventory.cjs` | Shared workstream inventory projection: state fields, phase/plan/summary counts, roadmap phase count, and active marker — thin orchestrator that delegates pure projection to `workstream-inventory-builder.cjs` |
|
||||
| `workstream-name-policy.cjs` | Canonical workstream name validation (`isValidActiveWorkstreamName`, `hasInvalidPathSegment`, `validateWorkstreamName`) and slug normalization (`toWorkstreamSlug`) |
|
||||
|
||||
@@ -61,9 +61,11 @@ Language versions: [English](README.md) · [Português (pt-BR)](pt-BR/README.md)
|
||||
- [PLAN.md schema](reference/plan-md.md) — field-by-field reference for `.planning/phases/<N>/PLAN.md`
|
||||
- [Planning artifacts](reference/planning-artifacts.md) — all `.planning/` files and their roles
|
||||
- [Review and verification capabilities](reference/review-verification-capabilities.md) — code review, security, and Nyquist capability ownership and hook contracts
|
||||
- [Gate predicates](reference/gate-predicates.md) — canonical specification of the phase-gate predicate vocabulary
|
||||
- [Capability matrix](reference/capability-matrix.md) — generated catalogue of every capability's role, tier, extension points, hook kinds, and `engines.gsd`
|
||||
- [Capability manifest](reference/capability-manifest.md) — the full `capability.json` schema and validation rules
|
||||
- [`gsd capability` command](reference/gsd-capability-command.md) — install / update / remove / list reference for third-party capabilities
|
||||
- [Workflow fragments](reference/workflow-fragments.md) — in-file `<!-- gsd:section -->` marker grammar for fragmentizing workflow markdown at emission time
|
||||
- [Reviewer Lane Registry](registries/reviewer-registry.md) — generated catalogue of third-party reviewer lanes, with their flags, transport, and install commands
|
||||
|
||||
---
|
||||
|
||||
@@ -66,6 +66,7 @@ Pure Agent Skills (A alone) and pure MCP (D alone) were rejected as the foundati
|
||||
- **Composer contract:** an ordered list of fragments, each carrying a *shrink strategy*; the closed set is `verbatim`, `head-shrink`, `proportional-truncate` (with a per-fragment floor), and `drop`. `flexReserve`-style floors for load-bearing fragments (`META.RULE` citation rules, contribution gates, closing-keyword rules) generalize the existing per-plan 1024-byte floor. A byte-stable canonical prefix (`<isolate>`) is kept identical across runtimes to preserve KV-cache warmth and keep launcher-parity tests green.
|
||||
|
||||
**Amended by #2929 (Phase 2).** This ADR originally specified the contract as "priority + binary-search cutoff to a per-runtime budget". Implementing Phase 2 established that a cutoff alone **cannot express the function this platform generalizes**: `prompt-budget.applyBudget` is not a cutoff but a fixed five-step ladder in which each section carries its own shrink strategy, and only three of its eight sections are ever droppable — `PROJECT.md` is head-shrunk to N lines and plans are proportionally tail-truncated with a per-plan floor, while instructions and roadmap are never trimmed at all. A cutoff composer sorts by priority and discards the tail; it has no way to say "shrink this one", "truncate that one but never below its floor", or "these three are the only droppables, in this order". Building to the literal wording and routing `prompt-budget` through it would have silently changed review-prompt output. Shrink strategies are therefore the core abstraction, and **binary-search cutoff becomes one strategy among them** — the right one for per-runtime emission in Phases 3-4, not for this ladder. Ordering is declaration order rather than a numeric priority field. This is an elaboration of the decision's intent, not a reversal of it.
|
||||
- **Applicability grammar (added by #2930, Phase 3).** The fragment unit's `when=` attribute is deliberately a CLOSED grammar: exactly one atom from a frozen vocabulary — `always`, `flag:--wave`, `state:gap-closure-phase`, `state:has-prior-phases` — with no boolean operators, negation, or nesting, and an unknown `when=` value throws rather than being ignored. This is a Greenspun's-Tenth-Rule guard: left open-ended, `when=` acquires `&&`/`!`/precedence/runtime-capability predicates and becomes an ad-hoc, informally-specified predicate language grown one condition at a time. Widening the vocabulary requires a coordinated ADR amendment, not an organic edit. `when=` is parsed and validated in Phase 3 but not yet acted on; applicability selection is Phase 5.
|
||||
- **Budget unit:** bytes for emission caps (matches `lfByteCount`, deterministic, offline-safe); a token estimate for run-time selection.
|
||||
- **Determinism + drift-guard:** every generated artifact follows the universal `--check`/`--write` idiom and is committed; any constant shared between two surfaces gets a `DEFECT.GENERATIVE-FIX` parity assertion. Caps are asserted on **emitted per-runtime bytes** via real spawn-install tests (engine-direct tests are false-green for install behavior).
|
||||
- **Boundary coverage:** the composer's budget logic is tested at `cap-1 / cap / cap+1` per `RULESET.TESTS.boundary-coverage`.
|
||||
@@ -132,6 +133,10 @@ Prototype scope notes: the parser is intentionally self-contained for the exampl
|
||||
|
||||
**Resolved by other work — not carried as open.** A fourth question was proposed in review (#1671, 2026-06-25): *what populates the eval-gate assertion set, and is it graded exogenously?* Since that review, the answer has landed as first-class predicate classes rather than remaining a design gap: `PROBE.principle` (`verifier-reach-equals-spec-reach`), `PROBE.family` (edge-probe + prohibition-probe + ui-consideration-probe), `PROBE.protocol` (recall → precision), and `PROHIB.judgment-tier` (exogenous grading) — see ADR-550 D4/D7 and ADR-1606. The `PROHIB.*` predicates live in the same `CONTEXT.md` store this ADR formalizes, which is the single-store property that review asked for.
|
||||
|
||||
**Resolved by #2930 (Phase 3) — fragment unit: in-file `<!-- gsd:section id= when= -->` markers.** Question 1 asked separate files vs in-file section markers. Confirmed with the maintainer: separate files are eliminated by this phase's own acceptance criterion — "emitted output byte-identical-or-smaller" — because splitting a workflow into files changes the emitted tree's *shape*, which is neither identical nor smaller, it is different; it also multiplies INVENTORY rows and `@`-ref contract surface for no Phase-3 benefit. A sidecar fragment manifest keyed on heading anchors was also rejected: zero source growth, but it creates a second surface that drifts from the workflow — the exact multi-surface edit pain the epic exists to remove (`DEFECT.GENERATIVE-FIX`), and directly against the epic's "one fragment, not 4 surfaces" thesis. The shipped answer is in-file markers, stripped at emit so the installed artifact carries no build metadata and shrinks; markers are self-anchoring (no line-number keying — Open question 4 already rejected that for the predicate index, and the same reasoning applies here), and the existing `<!-- gsd:loop-host … -->` block at `plan-phase.md:1` is in-repo precedent for the form. Production landed under `src/workflow-fragments.cts` → `gsd-core/bin/lib/workflow-fragments.cjs` (ADR-457 build-at-publish), piloted on `execute-phase.md`. **The pilot was retargeted from `plan-phase.md` mid-phase, and the reason is itself the most important finding here.** The branches the epic names as motivating (`--prd`, `--ingest`, `--mvp`, `--reviews`) all live in `plan-phase.md` — but `plan-phase.md` sits only 36 B under an independent, pre-existing size gate (`tests/phase6-capstone-conformance.test.cjs`'s `PRE_PHASE6`, an ADR-857 Phase-6 completion property that this ADR's own Blast-radius analysis did not enumerate against, catching only the XL cap). It cannot absorb even the smallest marker overhead, so **it could not be fragmentized at all under this phase's grammar**, independent of any shape limitation. The pilot instead proves the mechanism on state- and flag-gated `<step>` blocks in `execute-phase.md` (`partial-wave`/`flag:--wave`, `gap-closure-artifacts`/`state:gap-closure-phase`, `regression-gate`/`state:has-prior-phases`), which has 728 B of real headroom under its own `PRE_PHASE6` gate. This is direct evidence for the epic's premise that fragmentization pays off, but it also means **Phase 4 (moving size caps from source bytes to emitted bytes) may need to land before `plan-phase.md` itself can be fragmentized.** Separately, and independent of the size-gate finding: the marker grammar addresses SECTION-shaped branches only — a whole-line, non-nesting comment pair around a contiguous block — and `--mvp`'s content in `plan-phase.md` is INTERLEAVED rather than sectioned (`MVP_MODE` resolution shares a bash block with `--tdd`/`--no-tracer`/`--no-reversibility-gates` at `plan-phase.md:125-158`, and is inline `${MVP_MODE === 'true' ? ... }` template interpolation at `:794-803`), so `--mvp` would remain unmarkable by this grammar even if the size gate allowed it. Phase 6 must either accept that gap or introduce a finer-grained (sub-line) mechanism for interleaved branches.
|
||||
|
||||
**Resolved by #2930 (Phase 3) — build-time emission is the primary surface; per-workflow cutover, no double-write.** Question 2 asked build-time emission vs run-time assembly as the primary surface during migration, and whether that requires a double-write period. Because markers are stripped at emit, an unmarked workflow parses to exactly one implicit fragment and composes back byte-identical by construction — that structural guarantee is what makes a per-workflow cutover safe file-by-file, with no double-write period and no flag day: a workflow can gain markers on its own schedule without touching any other workflow's emission path. Phase 5's run-time selection is planned to consume a build-derived manifest, not markers read at run time, keeping the run-time surface decoupled from the authoring surface.
|
||||
|
||||
## Related
|
||||
|
||||
- ADR-0002 — Command Contract Validation Module (the stub `<execution_context>` @-ref contract this platform's emission must keep satisfying).
|
||||
|
||||
175
docs/reference/workflow-fragments.md
Normal file
175
docs/reference/workflow-fragments.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# Workflow fragments (reference)
|
||||
|
||||
> **Diátaxis quadrant:** Reference. This is the canonical specification of the
|
||||
> in-file `<!-- gsd:section -->` marker grammar used to fragmentize GSD workflow
|
||||
> markdown for per-runtime emission. For the surrounding seam (why it exists and
|
||||
> how it composes with the shared budget composer), see
|
||||
> [Architecture: Workflow Fragmentization and Emission](../ARCHITECTURE.md#workflow-fragmentization-and-emission-srcworkflow-fragmentscts-adr-1671)
|
||||
> and [ADR-1671](../adr/1671-dynamic-context-management-platform.md) (open
|
||||
> questions 1 and 2).
|
||||
|
||||
Workflow authors can mark one or more sections of a `gsd-core/workflows/*.md` file
|
||||
so that `bin/install.js`'s emission path can compose them per runtime. Today this
|
||||
is an authoring model with no run-time effect yet — see
|
||||
[Not acted on yet](#not-acted-on-yet) below.
|
||||
|
||||
## Marker syntax
|
||||
|
||||
An open marker is a line whose only content (after trimming leading/trailing
|
||||
whitespace) is:
|
||||
|
||||
```html
|
||||
<!-- gsd:section id="<id>" when="<when>" -->
|
||||
```
|
||||
|
||||
A close marker is a line whose only content is:
|
||||
|
||||
```html
|
||||
<!-- /gsd:section -->
|
||||
```
|
||||
|
||||
- Attribute order is free and inner spacing around `=` and between attributes
|
||||
is flexible.
|
||||
- `id` must match `/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/` and must be unique
|
||||
within one file.
|
||||
- `when` must be exactly one entry of the frozen vocabulary below — no
|
||||
operators, no negation, no nesting.
|
||||
- Both `id` and `when` are **required** on every open marker; a marker missing
|
||||
either attribute fails closed (see [Fails closed](#fails-closed)).
|
||||
|
||||
Text between an open marker and its matching close marker is that section's
|
||||
body, byte-for-byte (including its own line terminators). Text outside any
|
||||
marker pair becomes an implicit "gap" fragment — the file's ordinary,
|
||||
unmarked content — so a workflow with no markers at all parses to exactly one
|
||||
gap fragment and composes back byte-identical to its source.
|
||||
|
||||
## The frozen `when=` vocabulary
|
||||
|
||||
`when=` takes exactly one of:
|
||||
|
||||
| Value | Meaning |
|
||||
|---|---|
|
||||
| `always` | Section is always applicable. |
|
||||
| `flag:--wave` | Applicable when the workflow runs with `--wave`. |
|
||||
| `state:gap-closure-phase` | Applicable when the phase number is a gap-closure phase (has a decimal, e.g. `4.1`). |
|
||||
| `state:has-prior-phases` | Applicable when prior phases (and their `VERIFICATION.md` files) exist. |
|
||||
|
||||
This list is **closed by design** (Greenspun's Tenth Rule): left open-ended,
|
||||
`when=` would acquire boolean operators, negation, precedence, and
|
||||
runtime/capability predicates one edit at a time, becoming an ad-hoc,
|
||||
informally-specified applicability language. Widening the vocabulary is a
|
||||
coordinated ADR amendment to ADR-1671, never an organic edit to the parser.
|
||||
|
||||
## Fails closed
|
||||
|
||||
An authoring mistake throws at parse time, naming the source file and 1-based
|
||||
line number, rather than being silently dropped or swallowed to end-of-file:
|
||||
|
||||
- Missing `id=` or `when=` attribute (`MISSING_ID`, `MISSING_WHEN`).
|
||||
- `when=` value not in the frozen vocabulary above, including any boolean
|
||||
operator or negation form (`UNKNOWN_WHEN`).
|
||||
- `id=` value that does not match the id grammar (`MALFORMED_ID`).
|
||||
- Malformed attribute syntax on an open marker — the attribute text is not a
|
||||
run of well-formed `key="value"` tokens (e.g. an unterminated quote or a
|
||||
duplicate attribute key) (`MALFORMED_ATTRIBUTES`).
|
||||
- An unrecognized attribute on an open marker (`UNRECOGNIZED_ATTRIBUTE`).
|
||||
- A close marker carrying attributes (`CLOSE_WITH_ATTRIBUTES`).
|
||||
- An unmatched close marker, i.e. close with no open (`UNMATCHED_CLOSE`).
|
||||
- A nested marker, i.e. open marker while already inside an open section
|
||||
(`NESTED_SECTION`).
|
||||
- A duplicate `id=` within one file (`DUPLICATE_ID`).
|
||||
- An open marker with no matching close before end of file
|
||||
(`UNCLOSED_SECTION`).
|
||||
|
||||
An unrecognized `when=` is treated as an authoring instruction that must never
|
||||
be silently ignored, not as a value to fail open on — this is deliberately
|
||||
asymmetric with the marker *formatting* tolerance above (free attribute order,
|
||||
flexible spacing), which is liberal by design.
|
||||
|
||||
## Markers are stripped at emit
|
||||
|
||||
Composition runs `parseWorkflowSections` → map sections to fragments → the
|
||||
shared `context-composer.cjs` budget seam (every fragment uses the `verbatim`
|
||||
strategy, so nothing is trimmed) → re-join fragment bodies in document order.
|
||||
The marker lines themselves are never part of any fragment body, so the
|
||||
composed output — and therefore every installed runtime artifact — contains
|
||||
no `gsd:section` markers at all. An unmarked file composes to itself exactly;
|
||||
a marked file composes to itself minus the marker line bytes.
|
||||
|
||||
Composition runs **before** the per-runtime converters (the `.claude/` →
|
||||
`.windsurf/`-style path and reference rewrites), so a marker's `id`/`when`
|
||||
attribute text is never exposed to a rewrite regex.
|
||||
|
||||
## Fenced and commented lookalikes are literal
|
||||
|
||||
A `<!-- gsd:section ... -->`-shaped line inside a fenced code block (three or
|
||||
more backticks or tildes, CommonMark-style) is **not** a marker — it is
|
||||
literal fence content, because workflows document their own marker syntax in
|
||||
fenced examples (as in this page and in the workflow files themselves). The
|
||||
same applies to a `gsd:section` mention inside an unrelated HTML comment, or
|
||||
in prose/backtick text that never opens a real one-line comment. Fence and
|
||||
comment detection run as a single interleaved left-to-right scan, mirroring
|
||||
the discipline used by the `CONTEXT.md` predicate parser
|
||||
(`src/context-predicates.cts`): while a fence is open, only a matching closer
|
||||
can end it; while a comment is open, only `-->` can end it; an unclosed fence
|
||||
running to end of file is not an error — everything after it is simply
|
||||
literal.
|
||||
|
||||
The pre-existing `<!-- gsd:loop-host ... -->` marker family (consumed by
|
||||
`scripts/gen-loop-host-contract.cjs`) is a different, already-established
|
||||
marker and is never treated as a `gsd:section` marker.
|
||||
|
||||
## Not acted on yet
|
||||
|
||||
`when=` is parsed and validated today, but applicability selection — actually
|
||||
choosing which sections apply to a given invocation — is not implemented in
|
||||
this phase. Every fragment composes into the output regardless of its `when=`
|
||||
value; only the marker lines are stripped. Run-time selection is planned for
|
||||
a later phase of ADR-1671's epic.
|
||||
|
||||
## Piloted on one workflow so far
|
||||
|
||||
Only `gsd-core/workflows/execute-phase.md` carries markers today. The marker
|
||||
grammar and composer seam are general-purpose across any workflow file, but
|
||||
rollout to other LARGE/XL workflows is intentionally sequenced as later work,
|
||||
not part of this phase.
|
||||
|
||||
The pilot marks three `<step>` blocks: `partial-wave` (`flag:--wave`),
|
||||
`gap-closure-artifacts` (`state:gap-closure-phase`), and `regression-gate`
|
||||
(`state:has-prior-phases`).
|
||||
|
||||
**The pilot was retargeted from `plan-phase.md` mid-phase.** Issue #2930's
|
||||
own motivating mutually-exclusive branches (`--prd`, `--ingest`, `--mvp`,
|
||||
`--reviews`) all live in `plan-phase.md`, not `execute-phase.md`. But
|
||||
`plan-phase.md` sits only 36 B under an independent, pre-existing size gate
|
||||
(`tests/phase6-capstone-conformance.test.cjs`'s `PRE_PHASE6`, an ADR-857
|
||||
Phase-6 completion property) and cannot absorb any marker overhead at all —
|
||||
so it could not be fragmentized under this phase's grammar regardless of
|
||||
branch shape. This is direct evidence for the epic's premise that
|
||||
fragmentization pays off, and it also means Phase 4 (moving size caps from
|
||||
source bytes to emitted bytes) may need to land before `plan-phase.md`
|
||||
itself can be fragmentized. Separately, and independent of the size-gate
|
||||
finding, `--mvp` would remain unmarkable by this grammar even if the size
|
||||
gate allowed it: its content in `plan-phase.md` is INTERLEAVED with other
|
||||
flags rather than living in its own contiguous section (`MVP_MODE`
|
||||
resolution shares a single bash block with `--tdd`, `--no-tracer`, and
|
||||
`--no-reversibility-gates` handling at `plan-phase.md:125-158`, and
|
||||
elsewhere it is inline `${MVP_MODE === 'true' ? ... }` template
|
||||
interpolation embedded inside the planner prompt at `plan-phase.md:794-803`)
|
||||
— the marker grammar is closed, non-nesting, and whole-line (see
|
||||
[Marker syntax](#marker-syntax) above), with no way to wrap part of a line
|
||||
or split a shared conditional block without either corrupting the
|
||||
conditional or bundling unrelated flags into one section. See
|
||||
[ADR-1671](../adr/1671-dynamic-context-management-platform.md) open
|
||||
question 1's resolution for the full record, and Phase 6 (LARGE/XL rollout)
|
||||
for how both limits get addressed.
|
||||
|
||||
## Related
|
||||
|
||||
- [ADR-1671](../adr/1671-dynamic-context-management-platform.md) — the
|
||||
platform decision record, including open questions 1 (fragment unit) and 2
|
||||
(build-time vs. run-time emission), both resolved by this phase.
|
||||
- [Architecture: Workflow Fragmentization and Emission](../ARCHITECTURE.md#workflow-fragmentization-and-emission-srcworkflow-fragmentscts-adr-1671).
|
||||
- `src/workflow-fragments.cts` — the compiled parser/composer source.
|
||||
- `src/context-composer.cts` — the shared budget-composition seam consumed by
|
||||
`composeWorkflow`.
|
||||
Reference in New Issue
Block a user