Merge pull request #1835 from open-gsd/feat/1820-specless-predicate-rail
feat(#1820): spec-optional predicate rail — author probe predicates into must_haves when SPEC omits them
This commit is contained in:
5
.changeset/fierce-bears-gather.md
Normal file
5
.changeset/fierce-bears-gather.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 1835
|
||||
---
|
||||
`plan-phase` now authors edge and prohibition predicates into PLAN.md `must_haves` when a phase SPEC omits `## Edge Coverage` / `## Prohibitions`, so goal-backward verification still has predicates to check on a spec-less phase (ADR-857 Phase 6). Gated by the new default-on `workflow.specless_probe_fallback` toggle — disable it to skip the fallback (the skip is recorded visibly in the plan). Spec-less prohibitions are authored descriptor-less and disposed flagged/unverified (honest verifier #1154), never a silent pass.
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -96,6 +96,7 @@ build/
|
||||
/gsd-core/bin/lib/plan-drift-guard.cjs
|
||||
/gsd-core/bin/lib/edge-probe.cjs
|
||||
/gsd-core/bin/lib/probe-core.cjs
|
||||
/gsd-core/bin/lib/spec-section.cjs
|
||||
/gsd-core/bin/lib/prohibition-enforcement.cjs
|
||||
/gsd-core/bin/lib/config-types.cjs
|
||||
/gsd-core/bin/lib/cli-exit.cjs
|
||||
|
||||
@@ -325,6 +325,9 @@ The ownership seam between the prohibition probe and security/compliance tooling
|
||||
### Prohibition Probe Module
|
||||
Second adapter of the Probe Core Module (ADR-550 Decision 7): the spec-phase prohibition-completeness probe wired into spec-phase Step 5.6, surfacing the unwritten *must-NOT* constraints (values/safety/ethics) the spec never forbids. Unlike the Edge Probe, recall is **prose-orchestrated, not a compiled engine** (ADR-550 D7b) — a two-stage pass per requirement: Stage 1 an adversarial recall question, Stage 2 a one-pass precision classifier (drop routine engineering, keep genuine prohibitions). The code surface is schema/projection only: `projectProhibitions()` (deterministic SPEC↔`must_haves.prohibitions` projection backing the `DEFECT.GENERATIVE-FIX` parity assertion), the `{test, judgment}` `PROHIBITION_VALIDATORS`, `validateProhibitionResolution`, and `dispositionForProhibition()` (the fail-closed default — an unwired `test`-tier item resolves to `unverified`/flagged, never green). **Deterministic test-tier locate (#1278, ADR-550 D3 addendum):** a resolved `test`-tier prohibition MAY carry an optional flat-scalar `check` descriptor — `check_kind` (`node-test` | `lint-rule`), `check_target`, and `check_rule` (lint-rule only) — that `projectProhibitions` emits into `must_haves.prohibitions` when well-formed, and `descriptorFromProjection()` (the read-back seam in the #1259 enforcement producer, `src/prohibition-enforcement.cts`) reconstructs into a `{kind, target, rule?}` `CheckDescriptor`, so verify-phase locates the wired check with zero LLM/author authoring. **Flat scalars, never a nested `check:{}` object** — so the round-trip rides the *unchanged* shared `parseMustHavesBlock` (the #644 no-parser-rewrite precedent); an absent/partial descriptor falls through to the producer's existing fail-closed locate, and `failFirst` stays caller-attested (machine-proof is #1279). No `proposeProhibitions()` — recall is LLM prose. `plan-phase` lifts every resolved prohibition from the SPEC `## Prohibitions (must-NOT)` section into the `must_haves.prohibitions` sibling block (never `truths`). Exports (locked surface): `projectProhibitions`, `PROHIBITION_VALIDATORS` (the `{test, judgment}` validators bundle injected into `probe-core`'s generic engine), `validateProhibitionResolution`, and `dispositionForProhibition` (the fail-closed disposition) — the prohibition adapter surface, shipped from `probe-core` alongside the generic engine. Source of truth: `gsd-core/bin/lib/probe-core.cjs` (the prohibition exports live in `src/probe-core.cts`, gitignored per ADR-457) + `gsd-core/references/prohibition-probe.md`. Tests: `tests/prohibition-probe.*.test.cjs`. See ADR-550, Probe Core Module, Edge Probe Module, Verification Tier, Bespoke vs Canon Prohibition.
|
||||
|
||||
### Spec-Section Helper Module
|
||||
The SINGLE source of truth for "did the phase SPEC supply section X (with at least one resolved row)?" — the SPEC-section detection seam consumed by `plan-phase` Step 7.95 (the spec-less probe fallback) to decide, per section, whether to run the fallback. Replaces the ad-hoc `awk` that previously lived in the workflow body, which hard-coded the section header strings at the call site and hand-rolled markdown-table row counting — a brittleness that produced two bugs: an exact `^## Prohibitions$` anchor that missed the canonical `## Prohibitions (must-NOT)` heading, and a single-table row-counting assumption. **Suffix-tolerant header invariant:** `SECTION_HEADERS` regexes match a heading AND any parenthetical/whitespace suffix — `prohibitions` matches both `## Prohibitions` and `## Prohibitions (must-NOT)`; `edges` matches `## Edge Coverage` (and any future suffix); if spec-phase renames a heading, update HERE and the `templates/spec.md` heading together (the contract is pinned by `tests/spec-section.test.cjs`). **Supply rule:** `supplied = present AND dataRows > 0` — a present-but-empty section is NOT supplied (it triggers the fallback). **Multi-table robustness:** a blank or prose line resets the per-table state, so a section with multiple tables (or prose between them) counts every table's data rows without miscounting a second table's header row; the `|…|` line before a `|---|` separator is the table header row and is never counted. **Fail-safe:** a missing/unreadable SPEC file resolves to `present:false` / `supplied:false` (so the fallback fires) rather than throwing. Exports (locked surface): the `SpecSectionKey` type (`edges | prohibitions`), `SECTION_HEADERS` (the canonical header matchers), the `SectionStatus` shape (`{ key, present, dataRows, supplied }`), `countSectionDataRows` (pure `specText → { present, dataRows }`), and `specSectionStatus` (disk-reading wrapper). CLI: `node spec-section.cjs <specFile> <edges|prohibitions>` prints `SectionStatus` JSON — exit 0 on success (an absent file is a valid "not supplied" answer), exit 2 only on a usage error (missing args / bad key). Pure and dependency-free. Source of truth: `gsd-core/bin/lib/spec-section.cjs` (generated from `src/spec-section.cts`, gitignored per ADR-457). Tests: `tests/spec-section.test.cjs`. See Edge Probe Module, Prohibition Probe Module, and `references/specless-probe-fallback.md`.
|
||||
|
||||
### MVP Mode
|
||||
Phase-level planning mode that frames work as a vertical slice (UI → API → DB) of one user-visible capability instead of horizontal layers. Resolved at workflow init via the precedence chain: `--mvp` CLI flag → ROADMAP.md `**Mode:** mvp` field → `workflow.mvp_mode` config → false. All-or-nothing per phase (PRD #2826 Q1). Surfaced as `MVP_MODE=true|false` to the planner, executor, verifier, and discovery surfaces (progress, stats, graphify). Canonical parser: `roadmap.cjs` `**Mode:**` field; canonical resolution chain documented in `workflows/plan-phase.md`. Concept index: `references/mvp-concepts.md`.
|
||||
|
||||
|
||||
@@ -264,6 +264,7 @@
|
||||
"sketch-theme-system.md",
|
||||
"sketch-tooling.md",
|
||||
"sketch-variant-patterns.md",
|
||||
"specless-probe-fallback.md",
|
||||
"spidr-splitting.md",
|
||||
"tdd.md",
|
||||
"thinking-models-debug.md",
|
||||
@@ -413,6 +414,7 @@
|
||||
"semver-compare.cjs",
|
||||
"shell-command-projection.cjs",
|
||||
"smart-entry.cjs",
|
||||
"spec-section.cjs",
|
||||
"stale-bake-guard.cjs",
|
||||
"state-command-router.cjs",
|
||||
"state-document.cjs",
|
||||
|
||||
@@ -381,6 +381,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
| `planner-load-graph-context.md` | Planner's load_graph_context step: knowledge-graph freshness + dependency-context query via the gsd_run launcher (extracted from gsd-planner.md). |
|
||||
| `skeleton-template.md` | SKELETON.md template emitted for new-project Walking Skeleton (Phase 1 + `--mvp`). |
|
||||
| `user-story-template.md` | User story format for MVP planning — "As a / I want to / So that" structured fields. |
|
||||
| `specless-probe-fallback.md` | Spec-less probe fallback protocol — gate (toggle + per-section absence via the shared `spec-section` helper), the deterministic edge probe (mirrors spec-phase 5.5), the in-planner prohibition recall, and the `must_haves` authoring lift; consumed by plan-phase step 7.95 when a phase SPEC omits `## Edge Coverage` / `## Prohibitions` (ADR-857 Phase 6). |
|
||||
| `spidr-splitting.md` | SPIDR splitting decomposition rules for handling large user stories in MVP mode. |
|
||||
|
||||
> **Subdirectory:** `gsd-core/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not among the top-level references.
|
||||
@@ -502,6 +503,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
|
||||
| `semver-compare.cjs` | Shared semver comparison policy helpers (`compareSemverCore`, stable-triplet validation, normalized tuple parsing) consumed by update-check hooks, statusline dev-install detection, and changeset extract range logic (#10) |
|
||||
| `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON/shell helpers |
|
||||
| `shell-command-projection.cjs` | Runtime-aware shell command projection for managed hook serialization: decides PowerShell call-operator usage by runtime/platform and normalizes Windows script path tokens |
|
||||
| `spec-section.cjs` | SPEC section-status helper (compiled from `src/spec-section.cts`, gitignored) — the single source of truth for the canonical SPEC headings (suffix-tolerant) and markdown-table row counting; `specSectionStatus`/`countSectionDataRows` decide per-section "supplied" for plan-phase's spec-less probe fallback, replacing ad-hoc awk (contract pinned by `tests/spec-section.test.cjs`) |
|
||||
| `state-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools state` |
|
||||
| `state.cjs` | STATE.md parsing, updating, progression, metrics |
|
||||
| `state-document.cjs` | Pure STATE.md field extraction, replacement, status normalization, and progress calculation transforms |
|
||||
|
||||
60
docs/adr/1820-spec-optional-predicate-rail.md
Normal file
60
docs/adr/1820-spec-optional-predicate-rail.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# ADR-1820: Spec-Optional Predicate Rail — the Spec-Section Detection Module, the fallback toggle, and the SPEC↔probe precedence contract
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-07-04
|
||||
- **Issue:** [#1820](https://github.com/open-gsd/gsd-core/issues/1820) — `approved-feature` (ADR-857 Phase 6, predicate-rail exception 857:138)
|
||||
- **Relationship to prior ADRs:** This does NOT restate ADR-857 (predicate generation is core substrate, not an off-by-default Capability), ADR-550 (the spec-phase probe contract + the `must_haves.prohibitions` projection), or ADR-1606 (the verify-time enforcement seam). It documents the *new architectural surface* #1820 introduces on top of them: a new Module seam, a new config toggle, and the SPEC-supplied-vs-probe-derived precedence/authoring contract. 857:138 pre-approved the *policy* of wiring the probe family onto the core rail (settled 2026-06-12); it does not itself document this module, toggle, or gate, which were designed afterward.
|
||||
|
||||
## Context
|
||||
|
||||
The edge-probe and prohibition-probe families surface a phase's unwritten edges and must-NOTs. Before #1820 they ran **only** in `spec-phase` (Step 5.5 / 5.6), so their value reached the verifier **only when a phase authored a SPEC.md**. But the verifier reads `must_haves` from **PLAN.md** frontmatter (`verify-phase.md`), not from SPEC.md — SPEC.md is only an optional source `plan-phase` lifts from. A phase planned without a SPEC therefore shipped with an empty predicate set and the probes' reach was lost. Per the load-bearing premise *verifier reach = spec reach*, that is a silent coverage hole precisely where a spec is thinnest.
|
||||
|
||||
The fix wires the probe generators into `plan-phase`'s `must_haves` authoring as a **spec-optional fallback**: when a section is absent (no SPEC, or a SPEC that omits/empties that section), the fallback runs the probe and authors the predicates directly into PLAN.md. Deciding *whether a section was supplied* requires detecting the SPEC's `## Edge Coverage` / `## Prohibitions (must-NOT)` sections and counting their resolved rows. That detection first lived as ad-hoc `awk` inline in the workflow body, which hard-coded the header strings at the call site and hand-rolled markdown-table row counting — brittleness that produced two real bugs (an exact `^## Prohibitions$` anchor that missed the canonical `## Prohibitions (must-NOT)` heading, and a single-table row-counting assumption). Centralizing the detection made the knowledge testable and shared — and created a new Module seam, which is what makes this an ADR-worthy decision (contributor-standards: *"an ADR is required when a decision introduces a Module seam that other code will depend on"*).
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. A new `spec-section` Module seam owns "did the SPEC supply section X?"
|
||||
|
||||
`src/spec-section.cts` (compiled to the gitignored `gsd-core/bin/lib/spec-section.cjs` per ADR-457) is the **single source of truth** for SPEC-section detection. Pure and dependency-free. Locked exported surface:
|
||||
|
||||
- `SpecSectionKey` — the logical sections the fallback cares about (`edges | prohibitions`).
|
||||
- `SECTION_HEADERS` — the canonical, **suffix-tolerant** header matchers (`prohibitions` matches both `## Prohibitions` and `## Prohibitions (must-NOT)`; `edges` matches `## Edge Coverage` and any future parenthetical suffix).
|
||||
- `SectionStatus` — `{ key, present, dataRows, supplied }`.
|
||||
- `countSectionDataRows(specText, key)` — pure `specText → { present, dataRows }` (multi-table-robust: a blank/prose line resets per-table state, the `|…|` line before a `|---|` separator is the header row and is never counted).
|
||||
- `specSectionStatus(specFile, key)` — the disk-reading wrapper.
|
||||
|
||||
**Invariants this seam pins:**
|
||||
- **Supply rule:** `supplied = present AND dataRows > 0`. A present-but-empty section is *not* supplied and triggers the fallback.
|
||||
- **Suffix-tolerant header contract:** if `spec-phase` renames a heading, update `SECTION_HEADERS` **and** the `templates/spec.md` heading together. The contract is pinned by `tests/spec-section.test.cjs` (both historical bugs are regression-tested).
|
||||
- **Fail-safe:** a missing/unreadable SPEC file resolves to `present:false` / `supplied:false` (fallback fires) rather than throwing.
|
||||
|
||||
Ownership boundary: this seam answers *detection only*. It does not run probes, author `must_haves`, or read config — those stay in the workflow body and the existing probe/projection modules. A future third spec-section consumer reuses this seam instead of re-parsing markdown (the extraction Gall's-Law rationale below).
|
||||
|
||||
### 2. `workflow.specless_probe_fallback` — a default-ON, disableable policy toggle
|
||||
|
||||
The fallback is governed by a new config key `workflow.specless_probe_fallback` (registered in `config-schema.manifest.json` `validKeys`), **default-on**, with a **visible "disabled" marker** when off.
|
||||
|
||||
- **Why a toggle exists (Chesterton's Fence):** the prohibition-recall half is an LLM pass with a real per-invocation cost; the toggle is the cost-gate that lets a project opt the fallback *invocation path* out without touching the verifier↔predicate contract.
|
||||
- **Why default-on:** the probe family is core verification substrate (ADR-857); shipping the fallback default-off would recreate the skippable-gate failure one level up. Default-on keeps the substrate reaching the verifier by default; the visible skip keeps a disabled state honest, never silent.
|
||||
- **Boundary with ADR-857:66 (ruled, not re-opened):** 857:66 says the probe *generator* is "default-on and non-removable." This toggle governs the **spec-less fallback invocation path**, not the generator or the verifier↔predicate contract (which stay non-toggleable core substrate). The maintainer ruled on #1820 that *857:66 governs spec-phase, not the spec-less fallback* — so a default-on **disableable** toggle over the fallback path is in-bounds. This ADR records that ruling; it does not amend 857:66.
|
||||
|
||||
### 3. The SPEC-supplied ↔ probe-derived precedence & authoring contract
|
||||
|
||||
- **Section-level precedence — a SPEC-supplied section is never re-run.** The fallback fires **per section**: only for a section that is absent or empty (`supplied === false`). A section the human authored in the SPEC is lifted as-is and never overwritten by the probe. This is section-level, not file-level: a partial SPEC (edges present, prohibitions omitted) runs the fallback for prohibitions only.
|
||||
- **One serializer, no second producer (Hyrum).** Fallback-authored prohibitions go into the `must_haves.prohibitions` sibling block via the **same `projectProhibitions` serializer** SPEC-lifted prohibitions use — no parallel authoring path, so the verifier↔predicate round-trip contract is unchanged.
|
||||
- **Fallback predicates are descriptor-less → flagged, never green, never auto-dismissed.** A probe-recalled prohibition authored without a wired `check_*` descriptor disposes `flagged`/`unverified` at verify time (ADR-550 D4/D5 fail-closed default; honest-verifier abstention, #1154) — it surfaces for human resolution, it does not silently pass. Unresolved edges are surfaced as explicit assumptions, never silently dropped. **No-silent-drop equality** holds between the SPEC-lifted and fallback paths.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** the probes' reach no longer depends on a phase authoring a SPEC; SPEC-section detection is testable and shared instead of re-parsed inline; the toggle gives projects a cost-gate without a capability surface; the precedence contract makes "who wins, SPEC or probe?" a documented decision rather than call-site behavior.
|
||||
- **Cost / Hyrum:** the toggle default-on is an observable behavior change (a no-SPEC phase now emits predicates it previously didn't) — but the honest-verifier's fail-closed abstention keeps it from being a *regression* (descriptor-less fallback predicates flag/abstain, never flip a previously-green phase red). `SECTION_HEADERS` becomes a depended-on surface: renames must move it and `templates/spec.md` together (pinned by test).
|
||||
- **Deferred (Gall's Law).** The build surfaced a duplicated `_gsd_lib()` runtime-dir resolution idiom shared by the probe shim steps; further centralizing it is deferred, not done. This ADR names the `spec-section` seam as the precedent so the **next** probe-fallback consumer extends an existing seam rather than re-inlining detection — the failure mode a documented seam prevents.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- **ADR-857** — *Verification substrate vs. plug-in tier (the predicate boundary)*: predicate generation is core, non-toggleable substrate; Phase 6 (Migrate) wires the probe family onto the core rail (857:138). This ADR is that migration's plan-side landing.
|
||||
- **ADR-550** — spec-phase probe contract; the `must_haves.prohibitions` projection (`projectProhibitions`) and the fail-closed disposition this rail reuses unchanged.
|
||||
- **ADR-1606** — the verify-time enforcement seam that consumes the authored predicates.
|
||||
- **ADR-457** — `src/*.cts` → gitignored `bin/lib/*.cjs` build seam.
|
||||
- **CONTEXT.md** — *Spec-Section Helper Module* (the greppable module predicate).
|
||||
- **Code / prompts:** `src/spec-section.cts`, `gsd-core/workflows/plan-phase.md` (Step 7.95 + the `<downstream_consumer>` Otherwise branches), `gsd-core/references/specless-probe-fallback.md` (the protocol), `gsd-core/references/planning-config.md` (the toggle), `gsd-core/bin/shared/config-schema.manifest.json`. Tests: `tests/spec-section.test.cjs`. Issue: #1820.
|
||||
@@ -76,6 +76,7 @@ export default tseslint.config(
|
||||
'gsd-core/bin/lib/external-job.cjs',
|
||||
'gsd-core/bin/lib/edge-probe.cjs',
|
||||
'gsd-core/bin/lib/probe-core.cjs',
|
||||
'gsd-core/bin/lib/spec-section.cjs',
|
||||
'gsd-core/bin/lib/prohibition-enforcement.cjs',
|
||||
'gsd-core/bin/lib/code-review-flags.cjs',
|
||||
'gsd-core/bin/lib/context-utilization.cjs',
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"workflow.plan_bounce_script",
|
||||
"workflow.plan_bounce_passes",
|
||||
"workflow.plan_chunked",
|
||||
"workflow.specless_probe_fallback",
|
||||
"workflow.plan_review_convergence",
|
||||
"code_quality.fallow.enabled",
|
||||
"code_quality.fallow.scope",
|
||||
|
||||
@@ -269,6 +269,7 @@ Set via `workflow.*` namespace in config.json (e.g., `"workflow": { "research":
|
||||
| `workflow.mvp_mode` | boolean | `false` | `true`, `false` | Persist the MVP-mode flag in config so every phase defaults to MVP framing without requiring `--mvp` on the CLI. Resolved via the chain: `--mvp` CLI flag → ROADMAP.md `**Mode:** mvp` field → this config value → `false`. When `true`, the planner, executor, verifier, and discovery surfaces (progress, stats, graphify) all treat the phase as an MVP vertical slice (UI → API → DB) of one user-visible capability. |
|
||||
| `workflow.context_guard_mode` | string | `"warn"` | `"auto"`, `"warn"`, `"off"` | Context exhaustion guard mode for `execute-phase`. Before each wave, the orchestrator self-assesses context pressure using degradation signals from `context-budget.md`. `"warn"` (default): emit a warning and recommend `/gsd:pause-work` when POOR tier is detected. `"auto"`: automatically invoke `/gsd:pause-work` before the next wave when POOR tier is detected. `"off"`: disable the guard. The guard is heuristic — no programmatic context-% API exists. |
|
||||
| `workflow.plan_chunked` | boolean | `false` | `true`, `false` | Enable chunked planning mode. When `true`, the plan-phase orchestrator splits the single long-lived planner Task into a short outline Task followed by N short per-plan Tasks (~3–5 min each). Each plan is committed individually for crash resilience. Particularly useful on Windows where long-lived Tasks may hang on stdio. Also activated by the `--chunked` flag. |
|
||||
| `workflow.specless_probe_fallback` | boolean | `true` | `true`, `false` | Gate the SPEC-less probe fallback in `plan-phase`. When `true` (default), a phase that did not supply a `## Edge Coverage` / `## Prohibitions` SPEC section (header absent or present-but-empty) runs the existing probe protocol — the deterministic `edge-probe.cjs` for edges and an in-planner LLM recall pass for prohibitions — and authors the resulting predicates into PLAN.md `must_haves` (section-level precedence: a SPEC-supplied section is never re-run or overwritten). When `false`, the fallback is skipped but the skip is recorded: plan-phase emits a visible "probe fallback disabled" marker, never a silent skip. |
|
||||
| `workflow.code_review_command` | string\|null | `null` | Any shell command | External code-review command integrated into `/gsd:ship`. The diff is piped to the command via stdin; the command must output JSON with a `verdict` field (`"APPROVED"` or `"REVISE"`). Non-zero exit or `"REVISE"` verdict blocks the ship workflow. When unset, the built-in review flow runs. Example: `my-review-tool --review`. |
|
||||
| `workflow.inline_plan_threshold` | number | `2` | `0`–`10` | Plans with ≤N tasks execute inline instead of spawning a subagent |
|
||||
| `workflow.code_review` | boolean | `true` | `true`, `false` | Enable built-in code review step in the ship workflow |
|
||||
|
||||
172
gsd-core/references/specless-probe-fallback.md
Normal file
172
gsd-core/references/specless-probe-fallback.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Spec-less Probe Fallback — protocol
|
||||
|
||||
Lazy-loaded by `workflows/plan-phase.md` step 7.95 (the gate) and the `<specless_probe_fallback>`
|
||||
planner block. When a phase SPEC did NOT supply `## Edge Coverage` / `## Prohibitions`, plan-phase
|
||||
runs the same probe protocol the SPEC path uses and authors the predicates into PLAN.md `must_haves`
|
||||
(ADR-857 Phase 6 — the *else branch* of the `<downstream_consumer>` SPEC-conditional lift). This is
|
||||
core workflow-body substrate — NOT the `PLAN_PRE_HOOKS_JSON contribution into planner` capability rail
|
||||
(D-03). Section absence is detected by the shared `spec-section` helper in the gate; this file holds the
|
||||
*run-the-probe* half so the capped plan-phase.md stays lean (#717/#1074 budget).
|
||||
|
||||
## 0. Gate — toggle + per-section absence (run first, in the orchestrator)
|
||||
|
||||
Reads the default-ON toggle and computes `EDGE_ABSENT` / `PROHIB_ABSENT` via the shared `spec-section`
|
||||
helper; records a VISIBLE skip when disabled or when the phase has no requirement IDs (never a silent
|
||||
skip, never a hard-fail). Sets `SPECLESS_FALLBACK`, `EDGE_ABSENT`, `PROHIB_ABSENT`, and
|
||||
`SPECLESS_FALLBACK_DISABLED` for §A and the planner prompt.
|
||||
|
||||
```bash
|
||||
# Toggle defaults ON (D-04 / RAIL-05): any value other than literal "false" enables.
|
||||
SPECLESS_CFG=$(gsd_run query config-get workflow.specless_probe_fallback 2>/dev/null || echo "true")
|
||||
SPECLESS_FALLBACK=true; [[ "$SPECLESS_CFG" == "false" ]] && SPECLESS_FALLBACK=false
|
||||
|
||||
# Per-section absence (D-05 / RAIL-03): "not supplied" = header absent OR present-but-empty. The
|
||||
# shared, tested `spec-section` helper (src/spec-section.cts -> bin/lib/spec-section.cjs) is the SINGLE
|
||||
# source of truth for the canonical SPEC headings (suffix-tolerant) and table-row counting, replacing
|
||||
# ad-hoc awk (contract pinned by tests/spec-section.test.cjs). Resolve via the edge-probe install-dir
|
||||
# idiom; build only in a source checkout, else fail loud (never silently mis-detect).
|
||||
_GSD_RT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
||||
_gsd_lib() { for _d in "$_GSD_RT/gsd-core/bin/lib" "$_GSD_RT/bin/lib" "$_GSD_RT/.claude/bin/lib" "$HOME/.claude/gsd-core/bin/lib" "$HOME/.claude/bin/lib"; do [ -f "$_d/$1" ] && { echo "$_d/$1"; return; }; done; }
|
||||
SPEC_SECTION_JS=$(_gsd_lib spec-section.cjs)
|
||||
if [ -z "$SPEC_SECTION_JS" ] && [ -f "$_GSD_RT/tsconfig.build.json" ] && [ -f "$_GSD_RT/src/spec-section.cts" ]; then
|
||||
npm --prefix "$_GSD_RT" run build:lib 2>/dev/null || true; SPEC_SECTION_JS=$(_gsd_lib spec-section.cjs)
|
||||
fi
|
||||
[ -n "$SPEC_SECTION_JS" ] || { echo "ERROR: spec-section.cjs not found - reinstall GSD or run build:lib." >&2; exit 1; }
|
||||
# supplied => header present AND >=1 row; missing $SPEC_FILE => supplied:false => fallback fires.
|
||||
EDGE_ABSENT=1; node "$SPEC_SECTION_JS" "$SPEC_FILE" edges 2>/dev/null | grep -q '"supplied":true' && EDGE_ABSENT=0
|
||||
PROHIB_ABSENT=1; node "$SPEC_SECTION_JS" "$SPEC_FILE" prohibitions 2>/dev/null | grep -q '"supplied":true' && PROHIB_ABSENT=0
|
||||
|
||||
# Disabled path - record the skip VISIBLY, never silently (RAIL-05 / PROH-4); the note rides into the
|
||||
# planner prompt (Step 8) so the plan records that no probe predicates were generated.
|
||||
SPECLESS_FALLBACK_DISABLED=""
|
||||
if [[ "$SPECLESS_FALLBACK" != "true" ]]; then
|
||||
echo "WARNING: probe fallback disabled (workflow.specless_probe_fallback=false); skip recorded, not silent." >&2
|
||||
SPECLESS_FALLBACK_DISABLED="probe fallback disabled (workflow.specless_probe_fallback=false): no probe-derived predicates generated for SPEC-absent sections this run."
|
||||
fi
|
||||
|
||||
# Nothing-to-probe guard: the fallback derives predicates from requirement TEXT, so zero requirement
|
||||
# IDs => nothing to probe => skip VISIBLY (like the disabled path), NOT a hard-fail. Prevents a
|
||||
# no-SPEC + no-requirements phase from aborting under the default-ON fallback. The orchestrator
|
||||
# substitutes {phase_req_ids}; empty/whitespace/TBD => no requirements. (A still-literal token is
|
||||
# non-empty, so an unsubstituted run correctly hits the reference's fail-loud guard instead.)
|
||||
SPECLESS_REQ_IDS="{phase_req_ids}"
|
||||
if [[ "$SPECLESS_FALLBACK" == "true" ]] && { [ -z "${SPECLESS_REQ_IDS// /}" ] || [ "${SPECLESS_REQ_IDS}" = "TBD" ]; }; then
|
||||
echo "info: spec-less probe fallback: phase has no requirement IDs - nothing to probe; skipping (visible skip)." >&2
|
||||
SPECLESS_FALLBACK=false
|
||||
SPECLESS_FALLBACK_DISABLED="spec-less probe fallback skipped: phase has no requirement IDs to probe (visible skip)."
|
||||
fi
|
||||
```
|
||||
|
||||
## A. Edge probe (deterministic) — run when `SPECLESS_FALLBACK=true` AND `EDGE_ABSENT=1`
|
||||
|
||||
Mirrors spec-phase Step 5.5 verbatim; the ONLY divergence (D-02) is sourcing `$REQS_JSON` from the
|
||||
phase requirement IDs (`{phase_req_ids}`) instead of a SPEC interview. Leave `$COVERAGE` empty when
|
||||
`EDGE_ABSENT=0` — a SPEC-supplied section is never re-run (section-level precedence).
|
||||
|
||||
```bash
|
||||
# Resolve the compiled edge-probe.cjs against the GSD install dir via RUNTIME_DIR (#448) — NOT the
|
||||
# consuming project's git root — falling back to git toplevel / $HOME/.claude (spec-phase.md:198 idiom).
|
||||
_GSD_RT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
||||
EDGE_PROBE_JS=$(for _c in \
|
||||
"$_GSD_RT/gsd-core/bin/lib/edge-probe.cjs" "$_GSD_RT/bin/lib/edge-probe.cjs" \
|
||||
"$_GSD_RT/.claude/bin/lib/edge-probe.cjs" "$HOME/.claude/gsd-core/bin/lib/edge-probe.cjs" \
|
||||
"$HOME/.claude/bin/lib/edge-probe.cjs"; do [ -f "$_c" ] && { echo "$_c"; break; }; done)
|
||||
# Build ONLY inside a verified GSD source checkout; --prefix pins npm so we never trigger the
|
||||
# consuming project's build:lib. Never silent-skip (RR-04) — fail loud if unresolvable.
|
||||
if [ -z "$EDGE_PROBE_JS" ]; then
|
||||
if [ -f "$_GSD_RT/tsconfig.build.json" ] && [ -f "$_GSD_RT/src/edge-probe.cts" ]; then
|
||||
npm --prefix "$_GSD_RT" run build:lib 2>/dev/null || true
|
||||
EDGE_PROBE_JS=$(for _c in \
|
||||
"$_GSD_RT/gsd-core/bin/lib/edge-probe.cjs" "$_GSD_RT/bin/lib/edge-probe.cjs" \
|
||||
"$_GSD_RT/.claude/bin/lib/edge-probe.cjs" "$HOME/.claude/gsd-core/bin/lib/edge-probe.cjs" \
|
||||
"$HOME/.claude/bin/lib/edge-probe.cjs"; do [ -f "$_c" ] && { echo "$_c"; break; }; done)
|
||||
fi
|
||||
[ -n "$EDGE_PROBE_JS" ] || { echo "ERROR: edge-probe.cjs not found — reinstall GSD or run \`npm run build:lib\`." >&2; exit 1; }
|
||||
fi
|
||||
|
||||
# THE ONE DIVERGENCE (D-02): source requirements from THIS phase. Populate the heredoc from
|
||||
# {phase_req_ids}, pulling each requirement's text from REQUIREMENTS.md: {"id","text","shapes"?}.
|
||||
# mktemp suffix trick is BSD/GNU portable (#1520).
|
||||
REQS_JSON=$(mktemp "${TMPDIR:-/tmp}/edge-probe-reqs-XXXXXX") && mv "$REQS_JSON" "${REQS_JSON}.json" && REQS_JSON="${REQS_JSON}.json" || exit 1
|
||||
cat > "$REQS_JSON" <<'JSON'
|
||||
[
|
||||
{ "id": "R1", "text": "<replace: requirement text from REQUIREMENTS.md for each {phase_req_ids} id>" }
|
||||
]
|
||||
JSON
|
||||
# Guard — fail loud on empty/invalid array OR a still-present `<replace:>` placeholder (forgotten
|
||||
# substitution would yield a bogus report). Never a silent no-op.
|
||||
if ! node -e 'const a=require(process.argv[1]);if(!Array.isArray(a)||a.length===0)process.exit(1);if(a.some(r=>typeof r.text!=="string"||!r.text.trim()||r.text.includes("<replace:")))process.exit(1)' "$REQS_JSON" 2>/dev/null; then
|
||||
echo "ERROR: edge-probe requirements JSON is empty/invalid or still holds the <replace: …> placeholder — populate \$REQS_JSON from {phase_req_ids} before running." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Invoke + CAPTURE, exit-checked (engine FAILS CLOSED exit 2 on bad shape; a bare COVERAGE=$(node …)
|
||||
# would swallow it and fall through to prose re-derivation = fail-OPEN).
|
||||
if ! COVERAGE=$(node "$EDGE_PROBE_JS" "$REQS_JSON"); then
|
||||
rm -f "$REQS_JSON"
|
||||
echo "ERROR: edge-probe engine failed (invalid shapes or bad input) — fix the requirement(s); never proceed with empty coverage." >&2
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$REQS_JSON"
|
||||
# Exit-0-but-garbage guard: report must parse as JSON with { items[], coverage{} }.
|
||||
if ! printf '%s' "$COVERAGE" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let r;try{r=JSON.parse(s)}catch{process.exit(1)}if(!r||!Array.isArray(r.items)||typeof r.coverage!=="object"||r.coverage===null)process.exit(1)})'; then
|
||||
echo "ERROR: edge-probe produced an unparseable/malformed coverage report — refusing to proceed." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Zero-applicable guard: surface a likely classification miss loudly (spec-phase 5.5:277 shape).
|
||||
APPLICABLE=$(printf '%s' "$COVERAGE" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let n=0;try{n=JSON.parse(s).coverage.applicable}catch{n=0}process.stdout.write(String(n))})')
|
||||
if [ "$APPLICABLE" = "0" ]; then
|
||||
echo "WARNING: edge-probe proposed ZERO applicable edges across all phase requirements — likely a classification miss, not a genuinely edge-free phase. Do NOT silently write an empty fallback Edge Coverage." >&2
|
||||
fi
|
||||
```
|
||||
|
||||
**Edge `--auto` resolution rules (reuse spec-phase 5.5 verbatim, D-06):** auto-`covered` where a
|
||||
defensible acceptance criterion can be written (→ a plain `must_haves.truths` string); else
|
||||
auto-`backstop` → author it as a **structured flat-scalar marker** `{ statement: <the check>,
|
||||
verification: backstop }` in `must_haves.truths`, NOT a prose note (the verifier branches
|
||||
deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110
|
||||
fragility; flat scalar `verification:` key, never a nested object, ADR-550 #1278). A `backstop` truth
|
||||
the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason
|
||||
`insufficient_spec`), never a silent pass (#1154; `references/honest-verifier.md`). **Never
|
||||
auto-dismiss** (a wrong dismissal is the exact silent failure this eliminates). An `unclassified` row
|
||||
stays **`unresolved`** (#1110) — never auto-`backstop`ped — and is surfaced to the planner as a flagged
|
||||
assumption. Pass `$COVERAGE` (+ the gate's `$SPECLESS_FALLBACK_DISABLED` note) into the gsd-planner
|
||||
prompt (Step 8). When `EDGE_ABSENT=0`, `$COVERAGE` is empty and this does not run.
|
||||
|
||||
## B. Prohibition recall (LLM prose pass) — run when `PROHIB_ABSENT=1`
|
||||
|
||||
There is NO compiled prohibition engine and NO `node` invocation (ADR-550 D7b) — the gsd-planner runs
|
||||
this in-prompt. Full two-stage protocol, canon-referral rule, and status×verification schema live in
|
||||
`~/.claude/gsd-core/references/prohibition-probe.md` (do not inline it). Summary:
|
||||
|
||||
- **Stage 1 — Recall (adversarial).** Per requirement: *"What could this feature silently become that
|
||||
the author would NOT want, but the spec does not forbid?"* Over-produce (~10 raw must-NOT candidates).
|
||||
- **Stage 2 — Precision.** DROP routine-engineering (normal correctness/hygiene — owned by the edge
|
||||
probe or code review); KEEP values / safety / ethics (~2–3 survive).
|
||||
- **Canon-referral drop (ADR-550 D6).** A kept candidate that is canon security/compliance (OWASP /
|
||||
prototype-pollution / path-traversal / injection / GDPR / generic fairness) is NOT minted — emit a
|
||||
one-line breadcrumb and DROP it.
|
||||
|
||||
**Fallback `--auto` divergence (D-06 / RAIL-04 / PROH-1):** author each kept prohibition as
|
||||
**flagged-unverified with NO wired-check descriptor**. NEVER write `check_kind` / `check_target` /
|
||||
`check_rule` / `check_violation_fixture` / `check_clean_fixture` — there is no human to wire/verify a
|
||||
check, and a descriptor-less item is what keeps it fail-closed (it disposes
|
||||
`{status:'unverified', flagged:true}` downstream via the reused `dispositionForProhibition`). **Never
|
||||
auto-dismiss**; never fabricate a check path. Surface any `unresolved` prohibition as a flagged
|
||||
assumption — never a silent drop.
|
||||
|
||||
## C. Authoring (the `<downstream_consumer>` else-branch)
|
||||
|
||||
Author the fallback report into `must_haves` with the SAME lift the SPEC path uses — only the source
|
||||
changes (the fallback report, not the SPEC):
|
||||
|
||||
- **Edges →** every `covered` edge's acceptance criterion → `must_haves.truths` as a plain string;
|
||||
every `backstop` edge → `must_haves.truths` as a structured `{ statement, verification: backstop }`
|
||||
marker (NOT prose; #1110/#1278), which abstains → `human_needed` at verify time when unconfirmed
|
||||
(#1154); every `unresolved`/`unclassified` row → an explicit flagged assumption (never a silent drop).
|
||||
- **Prohibitions →** every kept prohibition → the `must_haves.prohibitions:` sibling block (NOT
|
||||
`truths`, ADR-550 D3) via the single `projectProhibitions` serializer (Hyrum — no second
|
||||
serializer), authored **descriptor-less** (no `check_*` scalar) so each disposes flagged-unverified.
|
||||
- **Section-level precedence:** a SPEC-supplied section is never re-run or overwritten — exactly one
|
||||
producer per section.
|
||||
- **No-silent-drop equality:** for each section, (# probe-surfaced items) == (# authored into
|
||||
`must_haves` + # surfaced as flagged assumptions).
|
||||
@@ -693,6 +693,19 @@ echo "✓ API surface regenerated: ${API_SURFACE_PATH}" # injected into step 8
|
||||
|
||||
Continue to step 8.
|
||||
|
||||
## 7.95. Spec-less Probe Fallback (gate)
|
||||
|
||||
When the SPEC did not supply `## Edge Coverage` / `## Prohibitions`, plan-phase runs the probe protocol
|
||||
and authors the predicates into PLAN.md `must_haves` (ADR-857 Phase 6 — the *else branch* of the
|
||||
`<downstream_consumer>` lift below). Core workflow-body substrate, not a capability rail (D-03). Runs
|
||||
after `$SPEC_FILE` (Step 7), before the gsd-planner spawn (Step 8).
|
||||
|
||||
**Read and run** the gate + edge probe in `~/.claude/gsd-core/references/specless-probe-fallback.md`
|
||||
(§0 default-ON toggle + per-section absence via the `spec-section` helper, visibly skipping when
|
||||
disabled or no requirement IDs; §A deterministic edge probe → `$COVERAGE` when `EDGE_ABSENT`; §B
|
||||
prohibition recall in the planner). Pass `$COVERAGE` and `$SPECLESS_FALLBACK_DISABLED` into Step 8.
|
||||
|
||||
|
||||
## 8. Spawn gsd-planner Agent
|
||||
|
||||
Display banner:
|
||||
@@ -769,6 +782,22 @@ ${MVP_MODE === 'true' ? `
|
||||
**MVP Mode is ENABLED.** Read `~/.claude/gsd-core/references/planner-mvp-mode.md` now and follow its vertical-slice planning rules. Each plan must deliver a complete vertical slice — thin end-to-end functionality rather than horizontal layers.
|
||||
</mvp_mode_active>
|
||||
` : ''}
|
||||
|
||||
<specless_probe_fallback>
|
||||
**Spec-less probe fallback** (only when step 7.95 set `EDGE_ABSENT` and/or `PROHIB_ABSENT`). The SPEC
|
||||
omitted that section — author its predicates into `must_haves` via the `<downstream_consumer>`
|
||||
else-branch below, per §A/§B/§C of `~/.claude/gsd-core/references/specless-probe-fallback.md`
|
||||
(descriptor-less prohibitions, never auto-dismiss, no silent drops).
|
||||
|
||||
Edge coverage report (`$COVERAGE`, present when `EDGE_ABSENT`):
|
||||
|
||||
```json
|
||||
{COVERAGE}
|
||||
```
|
||||
${SPECLESS_FALLBACK_DISABLED ? `
|
||||
**⚠ ${SPECLESS_FALLBACK_DISABLED}** — record this in the plan (a visible, recorded choice); do not generate probe predicates this run.
|
||||
` : ''}
|
||||
|
||||
</planning_context>
|
||||
|
||||
<downstream_consumer>
|
||||
@@ -777,8 +806,8 @@ Output consumed by /gsd:execute-phase. Plans need:
|
||||
- Tasks in XML format with read_first and acceptance_criteria fields (MANDATORY on every task)
|
||||
- Verification criteria
|
||||
- must_haves for goal-backward verification
|
||||
- If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths` as a plain string, and every `backstop` edge **as a structured flat-scalar marker** — an object item `{ statement: <the check>, verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them.
|
||||
- If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) carrying `statement` + `status` + `verification`; unresolved prohibitions are explicit assumptions — surface them in the plan, do not silently drop them. A prohibition is a must-NOT (negative) check that belongs in its own `must_haves.prohibitions` block. Never place a must-NOT under `must_haves.truths` — that block keeps positive-observable semantics only.
|
||||
- If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths` as a plain string, and every `backstop` edge **as a structured flat-scalar marker** — an object item `{ statement: <the check>, verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them. **Otherwise** (`EDGE_ABSENT`): apply the SAME lift to the fallback report `{COVERAGE}` (per §C of `references/specless-probe-fallback.md`); a SPEC-supplied section is never re-run.
|
||||
- If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) with `statement`+`status`+`verification`, via the single `projectProhibitions` serializer (Hyrum — no second serializer); unresolved -> flagged assumptions, don't drop; never put a must-NOT under `truths`. **Otherwise** (`PROHIB_ABSENT`), author the recalled prohibitions into the SAME block via the SAME `projectProhibitions` contract but **descriptor-less** (no `check_*`) so each disposes flagged-unverified; never auto-dismiss. Section-level precedence + no-silent-drop equality apply (§C).
|
||||
- **"Artifacts this phase produces" section (MANDATORY)** — list every symbol this phase creates: decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths. The plan-review-convergence source-grounding pass reads this section to exclude newly-created symbols from drift verification; omitting it causes new symbols to be flagged for acknowledgement.
|
||||
</downstream_consumer>
|
||||
|
||||
|
||||
124
src/spec-section.cts
Normal file
124
src/spec-section.cts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* SPEC section-status helper — the SINGLE source of truth for "did the phase SPEC supply
|
||||
* section X (with at least one resolved row)?".
|
||||
*
|
||||
* Consumed by `plan-phase` step 7.95 (the spec-less probe fallback) to decide, per section,
|
||||
* whether to run the fallback. Previously this lived as ad-hoc `awk` in the workflow body, which
|
||||
* (1) hard-coded the section header strings at the call site and (2) hand-rolled markdown-table
|
||||
* row counting — a brittleness that produced two bugs: an exact `^## Prohibitions$` anchor that
|
||||
* missed the canonical `## Prohibitions (must-NOT)` heading, and a single-table row-counting
|
||||
* assumption. Centralising the header matchers + the counting here makes the knowledge testable
|
||||
* and shared (any future SPEC-section consumer reuses it instead of re-parsing markdown).
|
||||
*
|
||||
* Authored as strict TypeScript (`src/spec-section.cts`) and compiled by
|
||||
* `tsc -p tsconfig.build.json` to the gitignored runtime artifact
|
||||
* `gsd-core/bin/lib/spec-section.cjs`. Do NOT hand-write the `.cjs`; it is emitted. Tests
|
||||
* `require()` the built artifact; `pretest` runs `build:lib`.
|
||||
*
|
||||
* Pure and dependency-free.
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
|
||||
/** The logical SPEC sections the spec-less probe fallback cares about. */
|
||||
export type SpecSectionKey = 'edges' | 'prohibitions';
|
||||
|
||||
/**
|
||||
* Canonical SPEC section header matchers — the SINGLE source of truth for the headings
|
||||
* `templates/spec.md` renders. Suffix-tolerant by design: `prohibitions` matches BOTH
|
||||
* `## Prohibitions` and `## Prohibitions (must-NOT)`; `edges` matches `## Edge Coverage`
|
||||
* (and any future parenthetical suffix). If spec-phase renames a heading, update HERE and the
|
||||
* spec.md template together — the contract is pinned by `tests/spec-section.test.cjs`.
|
||||
*/
|
||||
export const SECTION_HEADERS: Record<SpecSectionKey, RegExp> = {
|
||||
edges: /^##[ \t]+Edge Coverage([ \t]|\(|$)/,
|
||||
prohibitions: /^##[ \t]+Prohibitions([ \t]|\(|$)/,
|
||||
};
|
||||
|
||||
export interface SectionStatus {
|
||||
key: SpecSectionKey;
|
||||
/** A matching `## …` header line was found. */
|
||||
present: boolean;
|
||||
/** Markdown table DATA rows under the header (excludes the table header row and the `|---|` separator). */
|
||||
dataRows: number;
|
||||
/** `present` AND `dataRows > 0`. A present-but-empty section is NOT supplied (it triggers the fallback). */
|
||||
supplied: boolean;
|
||||
}
|
||||
|
||||
const ANY_H2 = /^##[ \t]/;
|
||||
const TABLE_LINE = /^\|/;
|
||||
// Separator row of a markdown table: |---|, |:--|, | :---: |, etc.
|
||||
const SEPARATOR = /^\|[ \t]*:?-{2,}/;
|
||||
|
||||
/**
|
||||
* Count markdown table DATA rows inside the named section. A data row is a `|…|` line that
|
||||
* follows the table's `|---|` separator; the `|…|` line BEFORE the separator is the table header
|
||||
* row and is not counted. Blank or prose lines reset the per-table state, so a section containing
|
||||
* multiple tables (or prose between tables) counts every table's data rows without miscounting a
|
||||
* second table's header — more robust than the single-table awk it replaces.
|
||||
*/
|
||||
export function countSectionDataRows(
|
||||
specText: string,
|
||||
key: SpecSectionKey,
|
||||
): { present: boolean; dataRows: number } {
|
||||
const header = SECTION_HEADERS[key];
|
||||
let inSection = false;
|
||||
let present = false;
|
||||
let afterSeparator = false;
|
||||
let dataRows = 0;
|
||||
|
||||
for (const line of specText.split(/\r?\n/)) {
|
||||
if (ANY_H2.test(line)) {
|
||||
inSection = header.test(line);
|
||||
if (inSection) present = true;
|
||||
afterSeparator = false;
|
||||
continue;
|
||||
}
|
||||
if (!inSection) continue;
|
||||
|
||||
if (TABLE_LINE.test(line)) {
|
||||
if (SEPARATOR.test(line)) {
|
||||
afterSeparator = true; // the next |…| lines are data rows
|
||||
} else if (afterSeparator) {
|
||||
dataRows++;
|
||||
}
|
||||
// a |…| line before any separator is the table header row -> not counted
|
||||
} else {
|
||||
// blank or prose line ends the current table; a later table re-arms on its own separator
|
||||
afterSeparator = false;
|
||||
}
|
||||
}
|
||||
|
||||
return { present, dataRows };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a SPEC file's section status from disk. A missing/unreadable file is reported as
|
||||
* not present (so the fallback fires) rather than throwing.
|
||||
*/
|
||||
export function specSectionStatus(specFile: string, key: SpecSectionKey): SectionStatus {
|
||||
let text: string;
|
||||
try {
|
||||
text = fs.readFileSync(specFile, 'utf8');
|
||||
} catch {
|
||||
return { key, present: false, dataRows: 0, supplied: false };
|
||||
}
|
||||
const { present, dataRows } = countSectionDataRows(text, key);
|
||||
return { key, present, dataRows, supplied: present && dataRows > 0 };
|
||||
}
|
||||
|
||||
const VALID_KEYS: readonly SpecSectionKey[] = ['edges', 'prohibitions'];
|
||||
|
||||
// CLI: `node spec-section.cjs <specFile> <edges|prohibitions>` -> prints SectionStatus JSON.
|
||||
// Exit 0 on success (even when the file is absent — that is a valid "not supplied" answer); exit
|
||||
// 2 only on a usage error (missing args / bad key). `require.main === module` so it runs only when
|
||||
// the compiled `.cjs` is executed directly, never when imported by tests.
|
||||
if (require.main === module) {
|
||||
const specFile = process.argv[2];
|
||||
const key = process.argv[3] as SpecSectionKey | undefined;
|
||||
if (!specFile || !key || !VALID_KEYS.includes(key)) {
|
||||
process.stderr.write('usage: spec-section.cjs <specFile> <edges|prohibitions>\n');
|
||||
process.exit(2);
|
||||
}
|
||||
process.stdout.write(JSON.stringify(specSectionStatus(specFile, key)) + '\n');
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f2dd7a6993dc12f1",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
|
||||
@@ -108,7 +108,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "49fbbfdac6b8ced4",
|
||||
"gsd-core/references/planning-config.md": "b0e55e9d585f90c1",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -127,6 +127,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5a308e4beeabd013",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -267,7 +268,7 @@
|
||||
"gsd-core/workflows/onboard.md": "69d871aa53a1a859",
|
||||
"gsd-core/workflows/pause-work.md": "564de32981a24337",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "dd6a4b3a8b05ab6e",
|
||||
"gsd-core/workflows/plan-phase.md": "cb56e635fef4d3f3",
|
||||
"gsd-core/workflows/plan-phase.md": "f3310f285ab426c6",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "ab0b22244c3389aa",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261",
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -179,7 +179,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "7fe958e09e74b6c8",
|
||||
"gsd-core/references/planning-config.md": "eb168188abd00101",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -198,6 +198,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -338,7 +339,7 @@
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
|
||||
"gsd-core/workflows/plan-phase.md": "8b1209958c31ca2f",
|
||||
"gsd-core/workflows/plan-phase.md": "b693bf6fd9ec6b08",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5",
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -107,7 +107,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "35aa39fef2641311",
|
||||
"gsd-core/references/planning-config.md": "99c46b7d318adf1a",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -126,6 +126,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -266,7 +267,7 @@
|
||||
"gsd-core/workflows/onboard.md": "3c50ed1f1fd07619",
|
||||
"gsd-core/workflows/pause-work.md": "5716362557f44ce4",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e",
|
||||
"gsd-core/workflows/plan-phase.md": "6978a15b5232b310",
|
||||
"gsd-core/workflows/plan-phase.md": "823254227fc8e369",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "c6818560fbd9f8e1",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -111,7 +111,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "15a5fc63268379d4",
|
||||
"gsd-core/references/planning-config.md": "a0dc2dddb953b22c",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -130,6 +130,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "be4f9fe09213210b",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -270,7 +271,7 @@
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "3530607514b0ac00",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "bafdc6945cd2bd87",
|
||||
"gsd-core/workflows/plan-phase.md": "46db1a845f25a579",
|
||||
"gsd-core/workflows/plan-phase.md": "a08c5c99792f3086",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "4b0a2cb0f4f28179",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "090c31e22b1508fe",
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -179,7 +179,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "7fe958e09e74b6c8",
|
||||
"gsd-core/references/planning-config.md": "eb168188abd00101",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -198,6 +198,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -338,7 +339,7 @@
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
|
||||
"gsd-core/workflows/plan-phase.md": "3e4debac4579684f",
|
||||
"gsd-core/workflows/plan-phase.md": "7e7e9c1c99b83834",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5",
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -143,7 +143,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "7889bfa28e82156b",
|
||||
"gsd-core/references/planner-revision.md": "2ebf1a714d1ec4bf",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "8593e6610784a339",
|
||||
"gsd-core/references/planning-config.md": "40d4abb1ed0f3723",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -162,6 +162,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -302,7 +303,7 @@
|
||||
"gsd-core/workflows/onboard.md": "83c40ba7055b8b24",
|
||||
"gsd-core/workflows/pause-work.md": "a217770ecafcb2e0",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "73d46f77c50a0690",
|
||||
"gsd-core/workflows/plan-phase.md": "89ee3233c1aee273",
|
||||
"gsd-core/workflows/plan-phase.md": "34c4d4c7ad7fdf36",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "d838b87563feedf6",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "f10975692cbd036e",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "f5edc589cab52a7b",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f2dd7a6993dc12f1",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
|
||||
@@ -109,7 +109,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "091d7541826f8cb0",
|
||||
"gsd-core/references/planning-config.md": "fd81dd276828eab4",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -128,6 +128,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "99905f064311ee46",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -268,7 +269,7 @@
|
||||
"gsd-core/workflows/onboard.md": "a62602c6f3fd538a",
|
||||
"gsd-core/workflows/pause-work.md": "9ce66367be6c40db",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "5cf589802d08bdf3",
|
||||
"gsd-core/workflows/plan-phase.md": "da462f812d1d0e61",
|
||||
"gsd-core/workflows/plan-phase.md": "909457cfb58ce634",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "bb052483744f0a6d",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261",
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "2493822b0ae27980",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -179,7 +179,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "a7b41174b754b215",
|
||||
"gsd-core/references/planning-config.md": "8af05de88771e9f4",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -198,6 +198,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -338,7 +339,7 @@
|
||||
"gsd-core/workflows/onboard.md": "20c28136423d40ac",
|
||||
"gsd-core/workflows/pause-work.md": "5716362557f44ce4",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e",
|
||||
"gsd-core/workflows/plan-phase.md": "c90c001a92fc8a47",
|
||||
"gsd-core/workflows/plan-phase.md": "d10fdacf2b9d850e",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "3bed01c3c906ac52",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "3d032fe88d2dc09b",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -108,7 +108,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "b5a719401e701e12",
|
||||
"gsd-core/references/planning-config.md": "1358a14bded944f5",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -127,6 +127,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "fba5dfb6ed07bbc2",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -267,7 +268,7 @@
|
||||
"gsd-core/workflows/onboard.md": "3c50ed1f1fd07619",
|
||||
"gsd-core/workflows/pause-work.md": "ae2d5789a95f70fe",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "c86cdc1964256b98",
|
||||
"gsd-core/workflows/plan-phase.md": "e995429805b69fdc",
|
||||
"gsd-core/workflows/plan-phase.md": "e30dcfb9615f19da",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "9607e6d03e93c1c2",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "62f8e4f3b475fe5f",
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -179,7 +179,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "af0a9694b3c39557",
|
||||
"gsd-core/references/planning-config.md": "aad463ddda23dbaf",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -198,6 +198,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -338,7 +339,7 @@
|
||||
"gsd-core/workflows/onboard.md": "78b288089305dc46",
|
||||
"gsd-core/workflows/pause-work.md": "a6e5336c409fdc8b",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e",
|
||||
"gsd-core/workflows/plan-phase.md": "03e59f06885e200f",
|
||||
"gsd-core/workflows/plan-phase.md": "6f5eacc08e5d9d2a",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "f10975692cbd036e",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -144,7 +144,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "7fe958e09e74b6c8",
|
||||
"gsd-core/references/planning-config.md": "eb168188abd00101",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -163,6 +163,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -303,7 +304,7 @@
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
|
||||
"gsd-core/workflows/plan-phase.md": "2f16f3cc05fc59f1",
|
||||
"gsd-core/workflows/plan-phase.md": "bfd41e5f867bd751",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5",
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -179,7 +179,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "af0a9694b3c39557",
|
||||
"gsd-core/references/planning-config.md": "aad463ddda23dbaf",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -198,6 +198,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "6905b93872b87d1f",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -338,7 +339,7 @@
|
||||
"gsd-core/workflows/onboard.md": "102556e1715c01b9",
|
||||
"gsd-core/workflows/pause-work.md": "70c72beca55c080a",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "0a9dacd422cd9533",
|
||||
"gsd-core/workflows/plan-phase.md": "3c49d9f4061b2388",
|
||||
"gsd-core/workflows/plan-phase.md": "c771c9c99f4d777d",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "3a09141de7f3dedb",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "6e2a3fff91bacb3e",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -108,7 +108,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "e8087cf6aae8d56f",
|
||||
"gsd-core/references/planning-config.md": "0037314fe38ca55c",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -127,6 +127,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "074038f98f03d222",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -267,7 +268,7 @@
|
||||
"gsd-core/workflows/onboard.md": "3c50ed1f1fd07619",
|
||||
"gsd-core/workflows/pause-work.md": "be33f84dc1d4822f",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "d98e98486123eb97",
|
||||
"gsd-core/workflows/plan-phase.md": "1cac08591d361397",
|
||||
"gsd-core/workflows/plan-phase.md": "8ef2986df30b616e",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "c22ff5ea46de665a",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "d050d8d551ed1756",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "2ed2d11d5be017e2",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -108,7 +108,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "33c5b0685be54e96",
|
||||
"gsd-core/references/planning-config.md": "e47cdb2337e10dac",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -127,6 +127,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "99be8a3315c75183",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -267,7 +268,7 @@
|
||||
"gsd-core/workflows/onboard.md": "61f111302af0f404",
|
||||
"gsd-core/workflows/pause-work.md": "c20d267e28ce92f0",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "26db7b9329b7ddc8",
|
||||
"gsd-core/workflows/plan-phase.md": "fc7d25c0649e2fdf",
|
||||
"gsd-core/workflows/plan-phase.md": "dbdcfdb7aeb4a4f0",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "778b73a8db6f7c32",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "619946c879f33b9d",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "ba7396b5e12d9b70",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -108,7 +108,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "062db9fe16ec76f9",
|
||||
"gsd-core/references/planning-config.md": "aafbfc62a3bb84b1",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -127,6 +127,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5ec1ddd3c4134750",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -267,7 +268,7 @@
|
||||
"gsd-core/workflows/onboard.md": "20c28136423d40ac",
|
||||
"gsd-core/workflows/pause-work.md": "93fcc1c845da6396",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "7880866ee1caf923",
|
||||
"gsd-core/workflows/plan-phase.md": "3e143f39514f8192",
|
||||
"gsd-core/workflows/plan-phase.md": "c7c5cac6409937ac",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "80b1ba493a9a967f",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "3fed4740a91d0443",
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -179,7 +179,7 @@
|
||||
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "7fe958e09e74b6c8",
|
||||
"gsd-core/references/planning-config.md": "eb168188abd00101",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
@@ -198,6 +198,7 @@
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
@@ -338,7 +339,7 @@
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
|
||||
"gsd-core/workflows/plan-phase.md": "a886ba9bdf4d0baf",
|
||||
"gsd-core/workflows/plan-phase.md": "24a8ceaa8b032641",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5",
|
||||
|
||||
146
tests/spec-section.test.cjs
Normal file
146
tests/spec-section.test.cjs
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* SPEC section-status helper tests.
|
||||
*
|
||||
* Asserts the LOCKED behaviour of `spec-section` against the BUILT artifact
|
||||
* (`gsd-core/bin/lib/spec-section.cjs`), which `npm run build:lib` (run by pretest) emits from
|
||||
* `src/spec-section.cts`.
|
||||
*
|
||||
* This is the contract that pins the spec-less probe fallback's per-section absence detection
|
||||
* (plan-phase step 7.95). It exists because the prior ad-hoc awk produced two regressions:
|
||||
* - an exact `^## Prohibitions$` anchor that missed the canonical `## Prohibitions (must-NOT)`
|
||||
* heading -> a supplied section read as absent -> human SPEC prohibitions overridden;
|
||||
* - single-table row counting that could miscount multi-table sections.
|
||||
* Both are regression-tested below.
|
||||
*/
|
||||
'use strict';
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
|
||||
const BUILT_SCRIPT = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'spec-section.cjs');
|
||||
const ss = require(BUILT_SCRIPT);
|
||||
|
||||
function writeTmp(name, content) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'spec-section-'));
|
||||
const p = path.join(dir, name);
|
||||
fs.writeFileSync(p, content);
|
||||
return p;
|
||||
}
|
||||
|
||||
const PROHIB_SUFFIX_SPEC = [
|
||||
'## Prohibitions (must-NOT)',
|
||||
'',
|
||||
'**Coverage:** 2/2 applicable prohibitions resolved · 0 unresolved',
|
||||
'',
|
||||
'| Prohibition | Requirement | Status | Verification |',
|
||||
'|-------------|-------------|--------|--------------|',
|
||||
'| MUST NOT do X | R1 | resolved | test |',
|
||||
'| MUST NOT do Y | R2 | resolved | judgment |',
|
||||
'',
|
||||
'## Ambiguity Report',
|
||||
'| Dim | Score |',
|
||||
'|-----|-------|',
|
||||
'| Goal | 0.9 |',
|
||||
].join('\n');
|
||||
|
||||
describe('spec-section: countSectionDataRows / specSectionStatus', () => {
|
||||
test('suffix-tolerant header: `## Prohibitions (must-NOT)` is detected for key=prohibitions (the header-bug regression)', () => {
|
||||
const status = ss.countSectionDataRows(PROHIB_SUFFIX_SPEC, 'prohibitions');
|
||||
assert.equal(status.present, true);
|
||||
assert.equal(status.dataRows, 2);
|
||||
});
|
||||
|
||||
test('does not bleed into the next `##` section (Ambiguity Report rows are not counted as prohibitions)', () => {
|
||||
const status = ss.countSectionDataRows(PROHIB_SUFFIX_SPEC, 'prohibitions');
|
||||
assert.equal(status.dataRows, 2); // not 3 — the Ambiguity Report data row is outside the section
|
||||
});
|
||||
|
||||
test('`## Edge Coverage` is detected and data rows counted', () => {
|
||||
const spec = [
|
||||
'## Edge Coverage',
|
||||
'',
|
||||
'| Category | Requirement | Status | Resolution |',
|
||||
'|----------|-------------|--------|------------|',
|
||||
'| empty | R1 | covered | x |',
|
||||
'| ordering | R1 | dismissed | y |',
|
||||
'| adjacency | R2 | backstop | z |',
|
||||
].join('\n');
|
||||
const status = ss.countSectionDataRows(spec, 'edges');
|
||||
assert.equal(status.present, true);
|
||||
assert.equal(status.dataRows, 3);
|
||||
});
|
||||
|
||||
test('present-but-empty section (header + table header + separator, no data rows) is NOT supplied', () => {
|
||||
const spec = [
|
||||
'## Prohibitions (must-NOT)',
|
||||
'',
|
||||
'**Coverage:** 0/0 applicable',
|
||||
'',
|
||||
'| Prohibition | Requirement | Status | Verification |',
|
||||
'|-------------|-------------|--------|--------------|',
|
||||
].join('\n');
|
||||
const p = writeTmp('empty.md', spec);
|
||||
const status = ss.specSectionStatus(p, 'prohibitions');
|
||||
assert.equal(status.present, true);
|
||||
assert.equal(status.dataRows, 0);
|
||||
assert.equal(status.supplied, false);
|
||||
});
|
||||
|
||||
test('absent section -> not present, not supplied', () => {
|
||||
const p = writeTmp('noproh.md', '## Edge Coverage\n\n| a | b |\n|---|---|\n| 1 | 2 |\n');
|
||||
const status = ss.specSectionStatus(p, 'prohibitions');
|
||||
assert.equal(status.present, false);
|
||||
assert.equal(status.supplied, false);
|
||||
});
|
||||
|
||||
test('missing file -> not present, not supplied (no throw)', () => {
|
||||
const status = ss.specSectionStatus('/no/such/file-xyz.md', 'edges');
|
||||
assert.deepEqual(status, { key: 'edges', present: false, dataRows: 0, supplied: false });
|
||||
});
|
||||
|
||||
test('multi-table section counts every table’s data rows (the awk single-table miscount regression)', () => {
|
||||
const spec = [
|
||||
'## Edge Coverage',
|
||||
'',
|
||||
'| Category | Status |',
|
||||
'|----------|--------|',
|
||||
'| empty | covered |',
|
||||
'',
|
||||
'Some prose between two tables.',
|
||||
'',
|
||||
'| Category | Status |',
|
||||
'|----------|--------|',
|
||||
'| ordering | covered |',
|
||||
'| adjacency | backstop |',
|
||||
].join('\n');
|
||||
const status = ss.countSectionDataRows(spec, 'edges');
|
||||
assert.equal(status.dataRows, 3); // 1 + 2 across both tables
|
||||
});
|
||||
|
||||
test('supplied = present AND dataRows > 0', () => {
|
||||
const p = writeTmp('full.md', PROHIB_SUFFIX_SPEC);
|
||||
assert.equal(ss.specSectionStatus(p, 'prohibitions').supplied, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('spec-section: CLI', () => {
|
||||
test('prints JSON status and exits 0 for a valid key', () => {
|
||||
const p = writeTmp('cli.md', PROHIB_SUFFIX_SPEC);
|
||||
const r = spawnSync(process.execPath, [BUILT_SCRIPT, p, 'prohibitions'], { encoding: 'utf8' });
|
||||
assert.equal(r.status, 0);
|
||||
const out = JSON.parse(r.stdout);
|
||||
assert.equal(out.supplied, true);
|
||||
assert.equal(out.dataRows, 2);
|
||||
});
|
||||
|
||||
test('exits 2 on a bad key', () => {
|
||||
const p = writeTmp('cli2.md', PROHIB_SUFFIX_SPEC);
|
||||
const r = spawnSync(process.execPath, [BUILT_SCRIPT, p, 'bogus'], { encoding: 'utf8' });
|
||||
assert.equal(r.status, 2);
|
||||
});
|
||||
});
|
||||
@@ -53,7 +53,7 @@
|
||||
"onboard.md": 8590,
|
||||
"pause-work.md": 14441,
|
||||
"plan-milestone-gaps.md": 11809,
|
||||
"plan-phase.md": 90455,
|
||||
"plan-phase.md": 92251,
|
||||
"plan-review-convergence.md": 23512,
|
||||
"plant-seed.md": 11785,
|
||||
"pr-branch.md": 15963,
|
||||
|
||||
Reference in New Issue
Block a user