* test(#3172): failing-first suite for the stated failing-direction probe Pins the <fails_when> pairing walk, placeholder denylist, MISSING sentinel exemption, degraded-read contract, CLI arm and the plan-authoring contract text. RED by construction: the module exports it requires do not exist yet. Executed on the remote runner. * feat(#3172): require a stated failing direction for every automated acceptance command Every runnable <automated> command now carries a <fails_when> sibling naming what output constitutes failure. A command with no expressible failure mode is not an acceptance test: it reads as rigour and is not falsifiable. - verify-command-grounding gains a failing-direction probe sharing the existing <automated> grammar, MISSING sentinel and walk guard rather than copying them - gsd-tools check verify-failure-directions <N> backs it; plan-phase dispatches it and hands the JSON to gsd-plan-checker check 8f - Dimension 8 detail extracted to references to stay under the agent size cap Verified on the remote runner. * fix(#3172): close four review findings in the failing-direction probe - MISSING_SENTINEL_RE matched an env-var assignment prefix (MISSING=1 cmd), so a real command was exempted from the new blocking gate. Tightened the SHARED constant rather than adding a second copy. - Both token regexes scanned to EOF on unclosed openers (O(n^2), 1562ms at 40k). Bodies are now non-crossing; 1ms, byte-identical on well-formed input. The pre-existing AUTOMATED_BLOCK_RE carried the same defect and is fixed here too. - probePhaseFailingDirections reported status 'ok' when one plan was unreadable, conflating 'could not look' with 'nothing to report'. - Extracted the phase-resolution block both check arms had copied verbatim. Also corrects a docs/AGENTS.md dimension list stale since #2401. Verified on the remote runner. * fix(#3172): project the planner rule onto the spawn contract, settle emitted bookkeeping The remote runner refuted the planner-side edit. agents/gsd-planner.md is frozen under a 49152-LF-char cap asserted by four suites and sat at 49,146 — six chars of headroom — so the +537 of authoring rule blew it. #3297/#3645 already settled where such a rule goes: the planner spawn contract in plan-phase.md, beside <tracked_source_paths>. The agent file is reverted to origin/next verbatim. - plan-phase.md gains <failing_direction_contract>; tests row 30 now asserts the contract there and row 30b guards the freeze in both directions - plan-phase.md growth acknowledged by APPENDING to the 3409 fragment, per the precedent that two ack sources may never name the same path - install-tree fixtures regenerated for the three new reference files Verified on the remote runner. * chore(#3172): backfill PR number into the changeset fragment pr:0 -> pr:3825 now that the PR exists. --------- Co-authored-by: sim <sim@local>
This commit is contained in:
5
.changeset/kind-ravens-dart.md
Normal file
5
.changeset/kind-ravens-dart.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
type: Changed
|
||||||
|
pr: 3825
|
||||||
|
---
|
||||||
|
**Plans must now say what output constitutes failure** — every runnable `<automated>` acceptance command needs a `<fails_when>` sibling naming an observable failure signal, and `/gsd-plan-phase` blocks a plan that omits one. A command with no expressible failure mode is not an acceptance test. Breaking for phases planned before this release: re-check reports one blocker per unstated command until statements are added or the phase is re-planned. (#3172)
|
||||||
File diff suppressed because one or more lines are too long
@@ -497,61 +497,16 @@ issue:
|
|||||||
|
|
||||||
## Dimension 8: Nyquist Compliance
|
## Dimension 8: Nyquist Compliance
|
||||||
|
|
||||||
Skip if: `workflow.nyquist_validation` is explicitly set to `false` in config.json (absent key = enabled), phase has no RESEARCH.md, or RESEARCH.md has no "Validation Architecture" section. Output: "Dimension 8: SKIPPED (nyquist_validation disabled or not applicable)"
|
**Question:** Is every task's completion decided by an automated check that can actually fail?
|
||||||
|
|
||||||
### Check 8e — VALIDATION.md Existence (Gate)
|
Checks 8a-8e (presence, latency, sampling continuity, Wave 0 completeness, VALIDATION.md gate),
|
||||||
|
their skip condition and the Dimension 8 output table: @gsd-core/references/nyquist-compliance.md
|
||||||
|
|
||||||
Before running checks 8a-8d, verify VALIDATION.md exists:
|
### Check 8f - Stated Failing Direction (#3172)
|
||||||
|
|
||||||
```bash
|
Each runnable `<automated>` command needs a `<fails_when>` sibling naming what output constitutes
|
||||||
ls "${PHASE_DIR}"/*-VALIDATION.md 2>/dev/null
|
failure. Consume the supplied `{FAILING_DIRECTIONS}` probe, never re-derive it:
|
||||||
```
|
@gsd-core/references/failing-direction.md
|
||||||
|
|
||||||
**If missing:** **BLOCKING FAIL** — "VALIDATION.md not found for phase {N}. Re-run `/gsd:plan-phase {N} --research` to regenerate."
|
|
||||||
Skip checks 8a-8d entirely. Report Dimension 8 as FAIL with this single issue.
|
|
||||||
|
|
||||||
**If exists:** Proceed to checks 8a-8d.
|
|
||||||
|
|
||||||
### Check 8a — Automated Verify Presence
|
|
||||||
|
|
||||||
For each `<task>` in each plan:
|
|
||||||
- `<verify>` must contain `<automated>` command, OR a Wave 0 dependency that creates the test first
|
|
||||||
- If `<automated>` is absent with no Wave 0 dependency → **BLOCKING FAIL**
|
|
||||||
- If `<automated>` says "MISSING", a Wave 0 task must reference the same test file path → **BLOCKING FAIL** if link broken
|
|
||||||
|
|
||||||
### Check 8b — Feedback Latency Assessment
|
|
||||||
|
|
||||||
For each `<automated>` command:
|
|
||||||
- Full E2E suite (playwright, cypress, selenium) → **WARNING** — suggest faster unit/smoke test
|
|
||||||
- Watch mode flags (`--watchAll`) → **BLOCKING FAIL**
|
|
||||||
- Delays > 30 seconds → **WARNING**
|
|
||||||
|
|
||||||
### Check 8c — Sampling Continuity
|
|
||||||
|
|
||||||
Map tasks to waves. Per wave, any consecutive window of 3 implementation tasks must have ≥2 with `<automated>` verify. 3 consecutive without → **BLOCKING FAIL**.
|
|
||||||
|
|
||||||
### Check 8d — Wave 0 Completeness
|
|
||||||
|
|
||||||
For each `<automated>MISSING</automated>` reference:
|
|
||||||
- Wave 0 task must exist with matching `<files>` path
|
|
||||||
- Wave 0 plan must execute before dependent task
|
|
||||||
- Missing match → **BLOCKING FAIL**
|
|
||||||
|
|
||||||
### Dimension 8 Output
|
|
||||||
|
|
||||||
```
|
|
||||||
## Dimension 8: Nyquist Compliance
|
|
||||||
|
|
||||||
| Task | Plan | Wave | Automated Command | Status |
|
|
||||||
|------|------|------|-------------------|--------|
|
|
||||||
| {task} | {plan} | {wave} | `{command}` | ✅ / ❌ |
|
|
||||||
|
|
||||||
Sampling: Wave {N}: {X}/{Y} verified → ✅ / ❌
|
|
||||||
Wave 0: {test file} → ✅ present / ❌ MISSING
|
|
||||||
Overall: ✅ PASS / ❌ FAIL
|
|
||||||
```
|
|
||||||
|
|
||||||
If FAIL: return to planner with specific fixes. Same revision loop as other dimensions (max 3 loops).
|
|
||||||
|
|
||||||
## Dimension 9: Cross-Plan Data Contracts
|
## Dimension 9: Cross-Plan Data Contracts
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ GSD uses a multi-agent architecture where thin orchestrators (workflow files) sp
|
|||||||
- Reads PROJECT.md, REQUIREMENTS.md, CONTEXT.md, RESEARCH.md
|
- Reads PROJECT.md, REQUIREMENTS.md, CONTEXT.md, RESEARCH.md
|
||||||
- Creates 2-3 atomic task plans sized for single context windows
|
- Creates 2-3 atomic task plans sized for single context windows
|
||||||
- Uses XML structure with `<task>` elements
|
- Uses XML structure with `<task>` elements
|
||||||
|
- Emits a `<fails_when>` sibling for every runnable `<automated>` verify command, naming what output constitutes failure (#3172)
|
||||||
- Includes `read_first` and `acceptance_criteria` sections
|
- Includes `read_first` and `acceptance_criteria` sections
|
||||||
- Groups plans into dependency waves
|
- Groups plans into dependency waves
|
||||||
- Performs reachability check to validate plan steps reference accessible files and APIs (v1.32)
|
- Performs reachability check to validate plan steps reference accessible files and APIs (v1.32)
|
||||||
@@ -255,14 +256,14 @@ GSD uses a multi-agent architecture where thin orchestrators (workflow files) sp
|
|||||||
| 7 | Context compliance (when CONTEXT.md exists) |
|
| 7 | Context compliance (when CONTEXT.md exists) |
|
||||||
| 7b | Scope reduction detection |
|
| 7b | Scope reduction detection |
|
||||||
| 7c | Architectural tier compliance (when RESEARCH.md defines a responsibility map) |
|
| 7c | Architectural tier compliance (when RESEARCH.md defines a responsibility map) |
|
||||||
| 8 | Nyquist compliance (when enabled) |
|
| 8 | Nyquist compliance (when enabled) — checks 8a-8e cover automated-verify presence, feedback latency, sampling continuity and Wave 0 completeness; check 8f blocks a runnable `<automated>` command with no stated `<fails_when>` failing direction (#3172) |
|
||||||
| 9 | Cross-plan data contracts |
|
| 9 | Cross-plan data contracts |
|
||||||
| 10 | CLAUDE.md compliance |
|
| 10 | CLAUDE.md compliance |
|
||||||
| 11 | Research resolution |
|
| 11 | Research resolution |
|
||||||
| 12 | Pattern compliance |
|
| 12 | Pattern compliance |
|
||||||
|
|
||||||
Two further dimensions carry no number: **Verify Command Format Sanity** and
|
Three further dimensions carry no number: **Verify Command Format Sanity**,
|
||||||
**Numeric/Factual Claim Authority**.
|
**Verify Command Path Resolvability**, and **Numeric/Factual Claim Authority**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1269,6 +1269,55 @@ gsd-tools check verify-command-paths 3 --raw # probe phase 3's verify command
|
|||||||
|
|
||||||
See [Resolve verify-command path findings](how-to/resolve-verify-command-path-findings.md).
|
See [Resolve verify-command path findings](how-to/resolve-verify-command-path-findings.md).
|
||||||
|
|
||||||
|
### `gsd-tools check verify-failure-directions`
|
||||||
|
|
||||||
|
Deterministic presence probe over a phase's stated failing directions (#3172). Run automatically
|
||||||
|
by `/gsd-plan-phase` before the plan-check pass and handed to `gsd-plan-checker`; runnable by
|
||||||
|
hand to see what the checker saw.
|
||||||
|
|
||||||
|
Every runnable `<automated>` command must carry a `<fails_when>` sibling naming what output
|
||||||
|
constitutes failure. A command with no expressible failure mode is not an acceptance test.
|
||||||
|
|
||||||
|
| Argument | Required | Description |
|
||||||
|
|----------|----------|-------------|
|
||||||
|
| `N` | **Yes** | Phase number whose `-PLAN.md` files are probed |
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--raw` | Emit the JSON payload with no surrounding prose |
|
||||||
|
|
||||||
|
**Prerequisites:** none — an unresolvable phase degrades to a JSON payload with `readError` set
|
||||||
|
rather than failing.
|
||||||
|
**Produces:** JSON on stdout. Nothing is written to disk.
|
||||||
|
|
||||||
|
**It never executes command text**, and it never authors a statement for the planner — a
|
||||||
|
prescribed failure signal would be copied verbatim and carry no information.
|
||||||
|
|
||||||
|
**Pairing.** Within one `<task>`, each `<fails_when>` binds to the nearest **preceding**
|
||||||
|
`<automated>`; the first statement after a command is the binding one. N runnable commands need
|
||||||
|
N statements. A redundant second statement for the same command is ignored.
|
||||||
|
|
||||||
|
Each row of `commands` carries `command`, `statement`, `plan`, `task`, `status`, and `severity`.
|
||||||
|
|
||||||
|
| `status` | `severity` | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `ok` | `none` | A non-empty, non-placeholder statement is bound to this command |
|
||||||
|
| `missing` | `blocker` | The command has no `<fails_when>` at all |
|
||||||
|
| `empty` | `blocker` | A `<fails_when>` is present but blank |
|
||||||
|
| `placeholder` | `blocker` | The whole statement is `TBD`, `TODO`, `N/A`, `NA`, `none`, `unknown`, `TBA`, `?`, or `-` (case-insensitive, whole value only) |
|
||||||
|
| `orphan` | `warning` | A `<fails_when>` that follows no command — it satisfies nothing |
|
||||||
|
| `sentinel` | `none` | A Nyquist `MISSING — Wave 0 …` placeholder; not runnable, so exempt |
|
||||||
|
|
||||||
|
The top-level `status` is `blocked` when any row is a blocker, `unresolvable` when the probe
|
||||||
|
could not look, and `ok` otherwise. A non-empty `readError` means the probe **could not look** —
|
||||||
|
distinct from finding nothing.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gsd-tools check verify-failure-directions 3 --raw # probe phase 3's failing directions
|
||||||
|
```
|
||||||
|
|
||||||
|
See [State a failing direction](how-to/state-a-failing-direction.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Workstream Management
|
## Workstream Management
|
||||||
|
|||||||
@@ -3574,3 +3574,35 @@ See [Resolve verify-command path findings](how-to/resolve-verify-command-path-fi
|
|||||||
**Known limits:** an empty `phases: []` cannot be told apart from "no `ROADMAP.md`" or "roadmap unreadable" — the `1.0` schema carries no diagnostic channel, and `planning inspect` is the surface that does. A roadmap phase marked `Deferred` is reported as `pending`, because the roadmap vocabulary has four values and this contract has three; inventing a fourth wire value would break every existing reader. `phases[]` is not milestone-scoped, so a long-running project lists every phase it has ever had.
|
**Known limits:** an empty `phases: []` cannot be told apart from "no `ROADMAP.md`" or "roadmap unreadable" — the `1.0` schema carries no diagnostic channel, and `planning inspect` is the surface that does. A roadmap phase marked `Deferred` is reported as `pending`, because the roadmap vocabulary has four values and this contract has three; inventing a fourth wire value would break every existing reader. `phases[]` is not milestone-scoped, so a long-running project lists every phase it has ever had.
|
||||||
|
|
||||||
**Reference:** [Consume the state contract](how-to/consume-the-state-contract.md) · [Consume the planning snapshot](how-to/consume-the-planning-snapshot.md)
|
**Reference:** [Consume the state contract](how-to/consume-the-state-contract.md) · [Consume the planning snapshot](how-to/consume-the-planning-snapshot.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 167. Stated Failing Direction
|
||||||
|
|
||||||
|
**Command:** `/gsd-plan-phase` (automatic), `gsd-tools check verify-failure-directions <N>` (#3172)
|
||||||
|
|
||||||
|
**Behavior:** A plan's `<automated>` block is the thing that decides whether work is done, and nothing checked that the command inside it could fail. In the motivating case six plans shipped 21 commands that could not run at all — `cargo test -p <pkg> --lib` against a package with no library target. They read as rigour and were not falsifiable, so three separate executors each rediscovered the defect and improvised a substitute at execution time. Every runnable `<automated>` command now needs a `<fails_when>` sibling naming what output constitutes failure:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<verify>
|
||||||
|
<automated>npm --prefix apps/api test -- auth.spec.ts</automated>
|
||||||
|
<fails_when>non-zero exit, or "0 passed" in the summary line</fails_when>
|
||||||
|
</verify>
|
||||||
|
```
|
||||||
|
|
||||||
|
`gsd-planner` emits it; `gsd-tools check verify-failure-directions <N>` verifies it deterministically; `/gsd-plan-phase` runs the probe before the plan-check pass and hands the JSON to `gsd-plan-checker`, whose check 8f blocks on `severity`.
|
||||||
|
|
||||||
|
**Why this shape and not the two obvious alternatives.** *Validating command shape* — teaching the checker Cargo's `--lib`/`--bin` target resolution, then pytest's node-ids, then the next one — always trails the newest toolchain. *Executing each command at plan time* is the strongest signal but means running planner-invented commands, with whatever side effects they carry, during planning. Requiring a stated failing direction needs no toolchain knowledge at all, and it is the only one of the three that catches the dangerous case: the motivating command exited non-zero, so it failed loudly, but the same class of error with a command that exits 0 on a no-op passes green and silently. Naming the failure signal is what makes that visible.
|
||||||
|
|
||||||
|
**Presence, not quality — deliberately split.** The probe is deterministic and owns the blockers: a statement is missing, blank, or a whole-value placeholder (`TBD`, `TODO`, `N/A`, `NA`, `none`, `unknown`, `TBA`, `?`, `-`). Whether the statement names the *right* signal is prose judgment, so `gsd-plan-checker` raises a vacuous statement (*"the command fails"*) as a WARNING only. Every BLOCKER stays reproducible; judgment stays advisory.
|
||||||
|
|
||||||
|
**It reports, it never prescribes.** The payload names the command with no stated failure mode and stops there. A prescribed statement would be copied verbatim and carry zero information — reproducing the original defect one level up.
|
||||||
|
|
||||||
|
**Not findings:** the Nyquist `MISSING — Wave 0 …` sentinel (not runnable, so it has no failure mode to state), an empty `<automated>` body (check 8a owns command presence), and a `<verify>` with no `<automated>` at all.
|
||||||
|
|
||||||
|
**Known limits:**
|
||||||
|
- Presence only. A statement that is present and specific can still name the wrong signal; that is caught, if at all, by judgment rather than by the probe.
|
||||||
|
- **Breaking for plans authored before this shipped.** A phase planned earlier has no `<fails_when>` anywhere and blocks on re-check until statements are added or the phase is re-planned.
|
||||||
|
- The adjacent **vacuous pass** — a command that runs successfully and asserts nothing, such as a test-name filter matching zero tests and exiting 0 — is a distinct problem and is explicitly out of scope.
|
||||||
|
|
||||||
|
See [State a failing direction](how-to/state-a-failing-direction.md) and [`gsd-tools check verify-failure-directions`](COMMANDS.md#gsd-tools-check-verify-failure-directions).
|
||||||
|
|||||||
@@ -235,6 +235,7 @@
|
|||||||
"execute-phase-response-language.md",
|
"execute-phase-response-language.md",
|
||||||
"execute-phase-wave-guard.md",
|
"execute-phase-wave-guard.md",
|
||||||
"executor-examples.md",
|
"executor-examples.md",
|
||||||
|
"failing-direction.md",
|
||||||
"gate-prompts.md",
|
"gate-prompts.md",
|
||||||
"gates.md",
|
"gates.md",
|
||||||
"git-integration.md",
|
"git-integration.md",
|
||||||
@@ -247,10 +248,12 @@
|
|||||||
"model-profile-resolution.md",
|
"model-profile-resolution.md",
|
||||||
"model-profiles.md",
|
"model-profiles.md",
|
||||||
"mvp-concepts.md",
|
"mvp-concepts.md",
|
||||||
|
"nyquist-compliance.md",
|
||||||
"offer-next.md",
|
"offer-next.md",
|
||||||
"phase-argument-parsing.md",
|
"phase-argument-parsing.md",
|
||||||
"planner-antipatterns.md",
|
"planner-antipatterns.md",
|
||||||
"planner-chunked.md",
|
"planner-chunked.md",
|
||||||
|
"planner-failing-direction.md",
|
||||||
"planner-gap-closure.md",
|
"planner-gap-closure.md",
|
||||||
"planner-graphify-auto-update.md",
|
"planner-graphify-auto-update.md",
|
||||||
"planner-guidance.md",
|
"planner-guidance.md",
|
||||||
|
|||||||
@@ -332,6 +332,8 @@ Full roster at `gsd-core/references/*.md`. References are shared knowledge docum
|
|||||||
| `research-philosophy.md` | Shared research philosophy (training-as-hypothesis, honest reporting, investigation-not-confirmation) injected into researcher agents. |
|
| `research-philosophy.md` | Shared research philosophy (training-as-hypothesis, honest reporting, investigation-not-confirmation) injected into researcher agents. |
|
||||||
| `research-verification-protocol.md` | Shared research verification protocol (4 pitfalls + pre-submission checklist) injected into researcher agents. |
|
| `research-verification-protocol.md` | Shared research verification protocol (4 pitfalls + pre-submission checklist) injected into researcher agents. |
|
||||||
| `verify-command-path-resolvability.md` | Verify Command Path Resolvability dimension (#2401) loaded by `gsd-plan-checker`: how to consume the `{VERIFY_PATHS}` probe result (never re-run or hand-reason the filesystem), the severity/reason table, and report-never-prescribe rules. |
|
| `verify-command-path-resolvability.md` | Verify Command Path Resolvability dimension (#2401) loaded by `gsd-plan-checker`: how to consume the `{VERIFY_PATHS}` probe result (never re-run or hand-reason the filesystem), the severity/reason table, and report-never-prescribe rules. |
|
||||||
|
| `nyquist-compliance.md` | Dimension 8 checks 8a-8e (#3172) loaded by `gsd-plan-checker`: the VALIDATION.md gate, automated-verify presence, feedback-latency assessment, sampling continuity, Wave 0 completeness, and the Dimension 8 output table — extracted from the agent to stay under its LARGE size cap. |
|
||||||
|
| `failing-direction.md` | Check 8f, Stated Failing Direction (#3172), loaded by `gsd-plan-checker`: how to consume the `{FAILING_DIRECTIONS}` probe result, the status/severity table, the sentinel exemption, and the split between deterministic blockers and advisory vacuous-statement warnings. |
|
||||||
|
|
||||||
### Workflow References
|
### Workflow References
|
||||||
|
|
||||||
@@ -425,6 +427,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
|||||||
| `planner-interface-context.md` | Interface context rules for executors — how to extract key interfaces/types/exports from existing code and document new interfaces that downstream plans will consume. |
|
| `planner-interface-context.md` | Interface context rules for executors — how to extract key interfaces/types/exports from existing code and document new interfaces that downstream plans will consume. |
|
||||||
| `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). |
|
| `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). |
|
||||||
| `planner-verify-command-grounding.md` | Verify Command Grounding rules (#2401): inherit `prior_verify_commands` verbatim when the story repeats, prefer `npm --prefix <dir> run <script>` over `cd <dir> && npm run <script>`, and ground every authored path. |
|
| `planner-verify-command-grounding.md` | Verify Command Grounding rules (#2401): inherit `prior_verify_commands` verbatim when the story repeats, prefer `npm --prefix <dir> run <script>` over `cd <dir> && npm run <script>`, and ground every authored path. |
|
||||||
|
| `planner-failing-direction.md` | Stated Failing Direction rules (#3172): every runnable `<automated>` carries a `<fails_when>` sibling naming an observable failure signal, the pairing and placeholder rules, the `MISSING` sentinel exemption, and the authoring test ("if this command were silently doing nothing, what would tell me?"). |
|
||||||
| `skeleton-template.md` | SKELETON.md template emitted for new-project Walking Skeleton (Phase 1 + `--mvp`). |
|
| `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. |
|
| `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). |
|
| `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). |
|
||||||
@@ -657,7 +660,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
|
|||||||
| `validate.cjs` | Pure phase variant normalization helpers (`phaseVariants`, `buildRoadmapPhaseVariants`, `buildNotStartedPhaseVariants`) used by `verify.cjs` for W006/W007 checks; no I/O, no async |
|
| `validate.cjs` | Pure phase variant normalization helpers (`phaseVariants`, `buildRoadmapPhaseVariants`, `buildNotStartedPhaseVariants`) used by `verify.cjs` for W006/W007 checks; no I/O, no async |
|
||||||
| `verification-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools verification` |
|
| `verification-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools verification` |
|
||||||
| `verification.cjs` | Verification-status routing — consolidates pass/gaps_found/human_needed status from phase verifier-emitted VERIFICATION.md frontmatter (#651) |
|
| `verification.cjs` | Verification-status routing — consolidates pass/gaps_found/human_needed status from phase verifier-emitted VERIFICATION.md frontmatter (#651) |
|
||||||
| `verify-command-grounding.cjs` | Verify-command path-resolvability probe (#2401) — pure `extractAutomatedCommands` (`<automated>` blocks + owning task, ReDoS-safe), `resolveVerifyCommandTarget` (grounds a leading `cd <literal>` chain or `npm --prefix <literal>` against the project root; three-state `ok`/`broken`/`unresolvable` plus `not_applicable`/`pending_creation`), `probePhaseVerifyCommands` (per-phase report backing `gsd-tools check verify-command-paths`), and `harvestPriorVerifyCommands` (nearest prior phase's commands, surfaced to the planner ungated by `context_window`). Never executes command text — PLAN.md is model-authored — and never prescribes a replacement path. Compiled from `src/verify-command-grounding.cts` |
|
| `verify-command-grounding.cjs` | Verify-command path-resolvability probe (#2401) — pure `extractAutomatedCommands` (`<automated>` blocks + owning task, ReDoS-safe), `resolveVerifyCommandTarget` (grounds a leading `cd <literal>` chain or `npm --prefix <literal>` against the project root; three-state `ok`/`broken`/`unresolvable` plus `not_applicable`/`pending_creation`), `probePhaseVerifyCommands` (per-phase report backing `gsd-tools check verify-command-paths`), `harvestPriorVerifyCommands` (nearest prior phase's commands, surfaced to the planner ungated by `context_window`), and the failing-direction probe (#3172) — `extractFailingDirections` (pairs each `<fails_when>` to the nearest preceding `<automated>` in one document-order pass, first-wins), `resolveFailingDirection` (closed 6-atom status `ok`/`missing`/`empty`/`placeholder`/`sentinel`/`orphan`), and `probePhaseFailingDirections` (per-phase report backing `gsd-tools check verify-failure-directions`). Never executes command text — PLAN.md is model-authored — and never prescribes a replacement path or a failure statement. Compiled from `src/verify-command-grounding.cts` |
|
||||||
| `verify-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools verify` |
|
| `verify-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools verify` |
|
||||||
| `verify.cjs` | Plan structure, phase completeness, reference, commit validation |
|
| `verify.cjs` | Plan structure, phase completeness, reference, commit validation |
|
||||||
| `workflow-fragments.cjs` | In-file `<!-- gsd:section id= when= -->` marker parser/composer for GSD workflow markdown (ADR-1671, #2930) — `parseWorkflowSections` (fence/HTML-comment-aware document partition into explicit/gap sections, fail-closed on malformed/unclosed/nested/duplicate markers or an unknown `when=`), `toFragments` (maps sections to `context-composer.cjs` `verbatim` fragments — non-lossy by construction), and `renderFragments`/`composeWorkflow` (compose-within-budget then join, run BEFORE per-runtime converters so a marker attribute never reaches a path-rewrite regex). `WHEN_VOCABULARY` is a frozen 4-atom applicability set (`always`, `flag:--wave`, `state:gap-closure-phase`, `state:has-prior-phases`); widening it is an ADR amendment, not an organic edit. Compiled from `src/workflow-fragments.cts` |
|
| `workflow-fragments.cjs` | In-file `<!-- gsd:section id= when= -->` marker parser/composer for GSD workflow markdown (ADR-1671, #2930) — `parseWorkflowSections` (fence/HTML-comment-aware document partition into explicit/gap sections, fail-closed on malformed/unclosed/nested/duplicate markers or an unknown `when=`), `toFragments` (maps sections to `context-composer.cjs` `verbatim` fragments — non-lossy by construction), and `renderFragments`/`composeWorkflow` (compose-within-budget then join, run BEFORE per-runtime converters so a marker attribute never reaches a path-rewrite regex). `WHEN_VOCABULARY` is a frozen 4-atom applicability set (`always`, `flag:--wave`, `state:gap-closure-phase`, `state:has-prior-phases`); widening it is an ADR amendment, not an organic edit. Compiled from `src/workflow-fragments.cts` |
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Language versions: [English](README.md) · [Português (pt-BR)](pt-BR/README.md)
|
|||||||
- [Resolve prohibition findings](how-to/resolve-prohibition-findings.md) — turn the spec phase's surfaced must-NOT constraints into resolved, dismissed, or deferred spec decisions
|
- [Resolve prohibition findings](how-to/resolve-prohibition-findings.md) — turn the spec phase's surfaced must-NOT constraints into resolved, dismissed, or deferred spec decisions
|
||||||
- [Resolve an unreachable-workflow finding](how-to/resolve-unreachable-workflow-findings.md) — wire or fully sweep a shipped workflow that no command, agent, or skill references
|
- [Resolve an unreachable-workflow finding](how-to/resolve-unreachable-workflow-findings.md) — wire or fully sweep a shipped workflow that no command, agent, or skill references
|
||||||
- [Resolve verify-command path findings](how-to/resolve-verify-command-path-findings.md) — fix an `<automated>` verify command whose target directory does not resolve from the executor's cwd
|
- [Resolve verify-command path findings](how-to/resolve-verify-command-path-findings.md) — fix an `<automated>` verify command whose target directory does not resolve from the executor's cwd
|
||||||
|
- [State a failing direction](how-to/state-a-failing-direction.md) — say what output constitutes failure for an `<automated>` verify command, and migrate a phase planned before the rule
|
||||||
- [Resolve a contract-drift finding](how-to/resolve-contract-drift-findings.md) — bring an agent's completion contract, read-tag gate, or deleted-file test reference back into agreement with the registry
|
- [Resolve a contract-drift finding](how-to/resolve-contract-drift-findings.md) — bring an agent's completion contract, read-tag gate, or deleted-file test reference back into agreement with the registry
|
||||||
- [Resolve unreachable-guard findings](how-to/resolve-unreachable-guard-findings.md) — fix shell guards whose fallback arm cannot run, and tell "nothing to report" apart from "could not look"
|
- [Resolve unreachable-guard findings](how-to/resolve-unreachable-guard-findings.md) — fix shell guards whose fallback arm cannot run, and tell "nothing to report" apart from "could not look"
|
||||||
- [Resolve an ESLint glob-coverage finding](how-to/resolve-eslint-coverage-findings.md) — bring a source file that matches no lint rule under coverage, or record a reasoned exemption
|
- [Resolve an ESLint glob-coverage finding](how-to/resolve-eslint-coverage-findings.md) — bring a source file that matches no lint rule under coverage, or record a reasoned exemption
|
||||||
|
|||||||
94
docs/how-to/state-a-failing-direction.md
Normal file
94
docs/how-to/state-a-failing-direction.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# State a failing direction
|
||||||
|
|
||||||
|
`/gsd-plan-phase` blocks when a plan's `<automated>` acceptance command does not say what output
|
||||||
|
constitutes failure. This page is what to do about it — how to write the statement, how to read
|
||||||
|
the probe's verdicts, and how to migrate a phase planned before the rule existed.
|
||||||
|
|
||||||
|
## The rule
|
||||||
|
|
||||||
|
Every runnable `<automated>` command needs a `<fails_when>` sibling naming an observable failure
|
||||||
|
signal:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<verify>
|
||||||
|
<automated>npm --prefix apps/api test -- auth.spec.ts</automated>
|
||||||
|
<fails_when>non-zero exit, or "0 passed" in the summary line</fails_when>
|
||||||
|
</verify>
|
||||||
|
```
|
||||||
|
|
||||||
|
Within one `<task>`, each `<fails_when>` binds to the nearest **preceding** `<automated>`, and
|
||||||
|
the first statement after a command is the binding one. Two commands need two statements.
|
||||||
|
|
||||||
|
## Write the statement
|
||||||
|
|
||||||
|
Ask one question: **if this command were silently doing nothing, what in its output would tell
|
||||||
|
me?** The answer is the statement. If there is no answer, you do not have an acceptance command
|
||||||
|
— fix the command rather than inventing a statement for it.
|
||||||
|
|
||||||
|
| Good — names a signal | Rejected — restates "failure" |
|
||||||
|
|---|---|
|
||||||
|
| `non-zero exit` | `the command fails` |
|
||||||
|
| `"0 passed" appears in the summary` | `it doesn't work` |
|
||||||
|
| `the coverage line is absent from stdout` | `an error occurs` |
|
||||||
|
| `stderr contains "ECONNREFUSED"` | `TBD` |
|
||||||
|
| `exit code > 0, or fewer than 12 tests report` | `N/A` |
|
||||||
|
|
||||||
|
Short is fine — `non-zero exit` is complete. There is no minimum length and no required keyword.
|
||||||
|
Any characters are safe: `exit code > 0` and `stderr contains "FAIL" && exit != 0` are ordinary
|
||||||
|
prose here.
|
||||||
|
|
||||||
|
## Run the probe yourself
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gsd-tools check verify-failure-directions 3 --raw
|
||||||
|
```
|
||||||
|
|
||||||
|
Same JSON the plan-checker acts on. Each row carries `command`, `statement`, `plan`, `task`,
|
||||||
|
`status`, and `severity`.
|
||||||
|
|
||||||
|
## Read the verdicts
|
||||||
|
|
||||||
|
| `status` | `severity` | What happened | What to do |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `ok` | `none` | A real statement is bound to this command | nothing |
|
||||||
|
| `missing` | `blocker` | The command has no `<fails_when>` | add one after the command |
|
||||||
|
| `empty` | `blocker` | The element is present but blank | fill it in, or delete the command |
|
||||||
|
| `placeholder` | `blocker` | The whole value is `TBD`, `TODO`, `N/A`, `NA`, `none`, `unknown`, `TBA`, `?`, or `-` | write the real signal; a statement you cannot write is a command you should not ship |
|
||||||
|
| `orphan` | `warning` | A `<fails_when>` that follows no command | move it after the command it describes |
|
||||||
|
| `sentinel` | `none` | A Nyquist `MISSING — Wave 0 …` placeholder — not runnable, so exempt | nothing; check 8a/8d own it |
|
||||||
|
|
||||||
|
The placeholder match is **whole-value and case-insensitive**. `TBD in the harness output` is real
|
||||||
|
prose and passes; a bare `TBD` does not.
|
||||||
|
|
||||||
|
## Tell "nothing to report" from "could not look"
|
||||||
|
|
||||||
|
The top-level `status` is `blocked` when any row is a blocker and `ok` when none are. A third
|
||||||
|
state matters: `unresolvable` with a populated `readError` means the probe **could not read the
|
||||||
|
plans** — an unreadable file, or a phase directory that does not resolve. An empty `commands`
|
||||||
|
list with a non-empty `readError` is not a clean bill of health, and neither the probe nor the
|
||||||
|
plan-checker will report it as one.
|
||||||
|
|
||||||
|
| Top-level `status` | `readError` | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `ok` | `null` | Every runnable command has a stated failing direction |
|
||||||
|
| `blocked` | `null` | At least one blocker — fix the rows above |
|
||||||
|
| `unresolvable` | populated | Could not look. Check the phase number and the plans' readability |
|
||||||
|
|
||||||
|
## Migrate a phase planned before this rule
|
||||||
|
|
||||||
|
A phase planned earlier has no `<fails_when>` anywhere, so re-checking it reports one `missing`
|
||||||
|
blocker per runnable command. Two ways forward:
|
||||||
|
|
||||||
|
1. **Add the statements by hand.** Run the probe with `--raw`, and for each `missing` row open
|
||||||
|
the named `plan` and `task` and add a `<fails_when>` after that command. This preserves the
|
||||||
|
plan as reviewed.
|
||||||
|
2. **Re-plan the phase** with `/gsd-plan-phase <N>`, which emits statements from the start. Do
|
||||||
|
this when the plan needs revising anyway; it discards any hand edits.
|
||||||
|
|
||||||
|
Sentinel commands need no migration — they were never runnable.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [`gsd-tools check verify-failure-directions`](../COMMANDS.md#gsd-tools-check-verify-failure-directions) — the command reference
|
||||||
|
- [Resolve verify-command path findings](resolve-verify-command-path-findings.md) — the sibling probe, for a command whose *target* does not resolve
|
||||||
|
- [Stated Failing Direction](../FEATURES.md#167-stated-failing-direction) — why the check is shaped this way
|
||||||
78
gsd-core/references/failing-direction.md
Normal file
78
gsd-core/references/failing-direction.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Check 8f — Stated Failing Direction (#3172)
|
||||||
|
|
||||||
|
> Reference file for gsd-plan-checker agent. Loaded on-demand via `@` reference.
|
||||||
|
|
||||||
|
**Question:** For each runnable `<automated>` command, does the plan say what output constitutes
|
||||||
|
failure?
|
||||||
|
|
||||||
|
Checks 8a–8d ask whether an acceptance command is *present*, and Verify Command Path
|
||||||
|
Resolvability asks whether its target *resolves*. This one asks whether the command is
|
||||||
|
**falsifiable at all**. A command with no expressible failure mode is not an acceptance test —
|
||||||
|
it reads as rigour and delivers none.
|
||||||
|
|
||||||
|
#3172: a planner emitted 21 `<automated>` commands that could not run. Cargo exited non-zero, so
|
||||||
|
that instance failed loudly — luck, not design. The same class of error with a command that
|
||||||
|
exits 0 on a no-op passes green and silently. Requiring a stated failing direction is the only
|
||||||
|
shape that catches the silent case, because it forces the plan to name the failure signal rather
|
||||||
|
than assume the command has one.
|
||||||
|
|
||||||
|
## The contract
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<verify>
|
||||||
|
<automated>npm --prefix apps/api test -- auth.spec.ts</automated>
|
||||||
|
<fails_when>non-zero exit, or "0 passed" in the summary line</fails_when>
|
||||||
|
</verify>
|
||||||
|
```
|
||||||
|
|
||||||
|
Within one `<task>`, each `<fails_when>` binds to the nearest **preceding** `<automated>`; a
|
||||||
|
command's binding statement is the **first** one that follows it. N runnable commands need N
|
||||||
|
statements.
|
||||||
|
|
||||||
|
## Do not hand-reason this
|
||||||
|
|
||||||
|
`gsd-core/workflows/plan-phase.md` runs the deterministic probe **before** spawning this checker
|
||||||
|
and interpolates the result into the verification prompt as `{FAILING_DIRECTIONS}`, inside a
|
||||||
|
`<failing_direction_probe>` block. This check reads that already-supplied JSON — it never invokes
|
||||||
|
`gsd_run check verify-failure-directions` itself. If `{FAILING_DIRECTIONS}` is absent from the
|
||||||
|
prompt, treat this check as silent (nothing to check) rather than trying to run the probe.
|
||||||
|
|
||||||
|
The probe never executes command text (PLAN.md is untrusted, LLM-authored). It is a **presence**
|
||||||
|
recognizer: it proves a statement exists and is not a placeholder. It does not judge whether the
|
||||||
|
statement names the *right* signal — that judgment is yours, below.
|
||||||
|
|
||||||
|
## Process — act on `severity` only
|
||||||
|
|
||||||
|
| `severity` | `status` | Action |
|
||||||
|
|---|---|---|
|
||||||
|
| `blocker` | `missing` | **BLOCKER** — quote the `command` verbatim: it has no stated failing direction |
|
||||||
|
| `blocker` | `empty` | **BLOCKER** — a `<fails_when>` is present but blank |
|
||||||
|
| `blocker` | `placeholder` | **BLOCKER** — quote the placeholder text; `TBD` is not a failure signal |
|
||||||
|
| `warning` | `orphan` | **WARNING** — a `<fails_when>` that follows no command; it satisfies nothing |
|
||||||
|
| `none` | `ok` / `sentinel` | silent |
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- **Report, never prescribe.** State which command has no stated failure mode. Do **not** author
|
||||||
|
the statement for the planner. A prescribed statement is copied verbatim and carries zero
|
||||||
|
information — that reproduces #3172 one level up, exactly as the #2401 probe refuses to
|
||||||
|
prescribe a replacement path.
|
||||||
|
- `status: sentinel` is a `MISSING — Wave 0 must create …` placeholder command. It is not
|
||||||
|
runnable, so it has no failure mode to state. **Not a finding.** Say nothing; checks 8a/8d
|
||||||
|
own it.
|
||||||
|
- A non-empty `readError` means the probe **could not look**. Report that as a WARNING in its own
|
||||||
|
words; it is not a clean bill of health.
|
||||||
|
- **Your added judgment, on `ok` rows only:** a statement that is present, non-empty and
|
||||||
|
non-placeholder can still be vacuous — *"the command fails"*, *"it doesn't work"*, *"an error
|
||||||
|
occurs"* restate the word "failure" without naming an observable signal. Raise those as a
|
||||||
|
**WARNING**, naming what a usable statement looks like (an exit code, a string in the output, a
|
||||||
|
missing line). Do **not** escalate a vacuous statement to BLOCKER: the deterministic layer owns
|
||||||
|
the blockers so that a BLOCKER is always reproducible, and prose judgment stays advisory.
|
||||||
|
- **Length is not a signal.** `<fails_when>non-zero exit</fails_when>` is a complete failing
|
||||||
|
direction. There is no minimum length, word count, or required keyword.
|
||||||
|
|
||||||
|
## Not in scope
|
||||||
|
|
||||||
|
A command that runs successfully and asserts nothing (a test-name filter matching zero tests and
|
||||||
|
exiting 0) is the adjacent **vacuous pass** problem. It is explicitly out of scope for this
|
||||||
|
check — see the issue's "Out of scope". Do not report it here.
|
||||||
74
gsd-core/references/nyquist-compliance.md
Normal file
74
gsd-core/references/nyquist-compliance.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Dimension 8 — Nyquist Compliance (checks 8a–8e)
|
||||||
|
|
||||||
|
> Reference file for gsd-plan-checker agent. Loaded on-demand via `@` reference.
|
||||||
|
> Extracted from `agents/gsd-plan-checker.md` in #3172 to stay under the agent's
|
||||||
|
> LARGE size cap. Checks 8a-8e are moved unchanged; the only edit is to the
|
||||||
|
> Dimension 8 Output table, which gains a `Failing Direction` column and a
|
||||||
|
> stated/runnable tally so check 8f's result is reported alongside them.
|
||||||
|
> Check 8f itself lives in its own reference:
|
||||||
|
> @gsd-core/references/failing-direction.md
|
||||||
|
|
||||||
|
**Skip if:** `workflow.nyquist_validation` is explicitly set to `false` in config.json
|
||||||
|
(absent key = enabled), phase has no RESEARCH.md, or RESEARCH.md has no "Validation
|
||||||
|
Architecture" section. Output: "Dimension 8: SKIPPED (nyquist_validation disabled or not
|
||||||
|
applicable)"
|
||||||
|
|
||||||
|
## Check 8e — VALIDATION.md Existence (Gate)
|
||||||
|
|
||||||
|
Before running checks 8a-8d, verify VALIDATION.md exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls "${PHASE_DIR}"/*-VALIDATION.md 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
**If missing:** **BLOCKING FAIL** — "VALIDATION.md not found for phase {N}. Re-run
|
||||||
|
`/gsd:plan-phase {N} --research` to regenerate."
|
||||||
|
Skip checks 8a-8d entirely. Report Dimension 8 as FAIL with this single issue.
|
||||||
|
|
||||||
|
**If exists:** Proceed to checks 8a-8d.
|
||||||
|
|
||||||
|
## Check 8a — Automated Verify Presence
|
||||||
|
|
||||||
|
For each `<task>` in each plan:
|
||||||
|
- `<verify>` must contain `<automated>` command, OR a Wave 0 dependency that creates the test
|
||||||
|
first
|
||||||
|
- If `<automated>` is absent with no Wave 0 dependency → **BLOCKING FAIL**
|
||||||
|
- If `<automated>` says "MISSING", a Wave 0 task must reference the same test file path →
|
||||||
|
**BLOCKING FAIL** if link broken
|
||||||
|
|
||||||
|
## Check 8b — Feedback Latency Assessment
|
||||||
|
|
||||||
|
For each `<automated>` command:
|
||||||
|
- Full E2E suite (playwright, cypress, selenium) → **WARNING** — suggest faster unit/smoke test
|
||||||
|
- Watch mode flags (`--watchAll`) → **BLOCKING FAIL**
|
||||||
|
- Delays > 30 seconds → **WARNING**
|
||||||
|
|
||||||
|
## Check 8c — Sampling Continuity
|
||||||
|
|
||||||
|
Map tasks to waves. Per wave, any consecutive window of 3 implementation tasks must have ≥2
|
||||||
|
with `<automated>` verify. 3 consecutive without → **BLOCKING FAIL**.
|
||||||
|
|
||||||
|
## Check 8d — Wave 0 Completeness
|
||||||
|
|
||||||
|
For each `<automated>MISSING</automated>` reference:
|
||||||
|
- Wave 0 task must exist with matching `<files>` path
|
||||||
|
- Wave 0 plan must execute before dependent task
|
||||||
|
- Missing match → **BLOCKING FAIL**
|
||||||
|
|
||||||
|
## Dimension 8 Output
|
||||||
|
|
||||||
|
```
|
||||||
|
## Dimension 8: Nyquist Compliance
|
||||||
|
|
||||||
|
| Task | Plan | Wave | Automated Command | Failing Direction | Status |
|
||||||
|
|------|------|------|-------------------|-------------------|--------|
|
||||||
|
| {task} | {plan} | {wave} | `{command}` | `{fails_when}` / ❌ | ✅ / ❌ |
|
||||||
|
|
||||||
|
Sampling: Wave {N}: {X}/{Y} verified → ✅ / ❌
|
||||||
|
Wave 0: {test file} → ✅ present / ❌ MISSING
|
||||||
|
Failing directions: {stated}/{runnable} → ✅ / ❌
|
||||||
|
Overall: ✅ PASS / ❌ FAIL
|
||||||
|
```
|
||||||
|
|
||||||
|
If FAIL: return to planner with specific fixes. Same revision loop as other dimensions
|
||||||
|
(max 3 loops).
|
||||||
53
gsd-core/references/planner-failing-direction.md
Normal file
53
gsd-core/references/planner-failing-direction.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Stated Failing Direction (#3172)
|
||||||
|
|
||||||
|
> Reference file for the gsd-planner agent. Loaded on-demand via `@` reference from the
|
||||||
|
> `<failing_direction_contract>` block of the planner spawn prompt in
|
||||||
|
> `gsd-core/workflows/plan-phase.md` — NOT from `agents/gsd-planner.md`, which is frozen
|
||||||
|
> under a 49152-LF-char cap, so planner-side rules are projected onto its spawn contract
|
||||||
|
> (the #3297 / #3645 precedent).
|
||||||
|
|
||||||
|
**Every runnable `<automated>` command needs a `<fails_when>` sibling naming what output
|
||||||
|
constitutes failure.** A command with no expressible failure mode is not an acceptance test.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<verify>
|
||||||
|
<automated>npm --prefix apps/api test -- auth.spec.ts</automated>
|
||||||
|
<fails_when>non-zero exit, or "0 passed" in the summary line</fails_when>
|
||||||
|
</verify>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why.** #3172: six plans shipped 21 `<automated>` commands that could not run at all — a
|
||||||
|
`--lib` target against a binary-only package. They sat inside the very blocks that decide whether
|
||||||
|
work is done, so the acceptance criteria for those plans were improvised at execution time by
|
||||||
|
three separate executors instead of reviewed at planning time. Cargo happened to exit non-zero,
|
||||||
|
so it failed loudly. The identical mistake with a command that exits 0 on a no-op — a test-name
|
||||||
|
filter matching nothing — passes green and silently. Naming the failure signal is what makes the
|
||||||
|
difference visible while you are still authoring the plan.
|
||||||
|
|
||||||
|
**The authoring test, applied to yourself:** *if this command were silently doing nothing, what
|
||||||
|
in its output would tell me?* If you cannot answer, you do not yet have an acceptance command —
|
||||||
|
you have a command. Fix the command, do not invent a statement for it.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- **One statement per runnable command**, placed immediately after it. Within a task, each
|
||||||
|
`<fails_when>` binds to the nearest preceding `<automated>`, and the first statement after a
|
||||||
|
command is the binding one. Two commands need two statements.
|
||||||
|
- **Name an observable signal**, not the word "failure". `non-zero exit`, `"0 passed" in the
|
||||||
|
summary`, `the coverage line is absent`, `stderr contains "ECONNREFUSED"` are signals. *"the
|
||||||
|
command fails"*, *"it doesn't work"*, *"an error occurs"* are restatements and will be flagged.
|
||||||
|
- **Short is fine.** `non-zero exit` is complete. There is no minimum length and no required
|
||||||
|
keyword.
|
||||||
|
- **`TBD`, `TODO`, `N/A`, `none`, `unknown`, `?`, `-` are rejected outright** as whole values.
|
||||||
|
A statement you cannot write is a command you should not ship.
|
||||||
|
- **Any characters are safe.** `exit code > 0`, `stderr contains "FAIL" && exit != 0` are ordinary
|
||||||
|
prose here — that is exactly why this is an element and not an attribute.
|
||||||
|
- **The `MISSING — Wave 0 must create …` sentinel is exempt.** It is not a runnable command, so
|
||||||
|
it has no failure mode to state. Do not attach a `<fails_when>` to one.
|
||||||
|
|
||||||
|
## Where the failing direction comes from
|
||||||
|
|
||||||
|
Prefer the signal the tool actually emits over one you imagine. When
|
||||||
|
`prior_verify_commands` supplies a command a prior phase already proved, the failure signal that
|
||||||
|
command produces is the one to state — you have seen its output. When you author a new command,
|
||||||
|
name the signal from the tool's documented output shape, not from a guess about it.
|
||||||
@@ -766,6 +766,31 @@ inherited paths: fix a mirror path, never inherit. Submodule files: check
|
|||||||
from within the submodule.
|
from within the submodule.
|
||||||
</tracked_source_paths>
|
</tracked_source_paths>
|
||||||
|
|
||||||
|
<failing_direction_contract>
|
||||||
|
**Stated failing direction (#3172):** Every runnable `<automated>` verify command
|
||||||
|
you write MUST be followed by a `<fails_when>` sibling naming what output
|
||||||
|
constitutes failure — an exit code, a string in the output, a missing line. A
|
||||||
|
command with no expressible failure mode is not an acceptance test.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<verify>
|
||||||
|
<automated>npm --prefix apps/api test -- auth.spec.ts</automated>
|
||||||
|
<fails_when>non-zero exit, or "0 passed" in the summary line</fails_when>
|
||||||
|
</verify>
|
||||||
|
```
|
||||||
|
|
||||||
|
One statement per runnable command, placed immediately after it: within a task
|
||||||
|
each `<fails_when>` binds to the nearest preceding `<automated>`, and the first
|
||||||
|
statement after a command is the binding one. Name an OBSERVABLE signal, never
|
||||||
|
the word "failure" — `non-zero exit` is complete, `the command fails` is a
|
||||||
|
restatement. `TBD`/`TODO`/`N/A`/`none`/`unknown`/`?`/`-` are rejected outright as
|
||||||
|
whole values. The `MISSING — Wave 0 …` sentinel is exempt: it is not runnable, so
|
||||||
|
it has no failure mode to state. Ask yourself: if this command were silently
|
||||||
|
doing nothing, what in its output would tell me? If you cannot answer, fix the
|
||||||
|
command — do not invent a statement for it.
|
||||||
|
Rules + worked examples: @gsd-core/references/planner-failing-direction.md
|
||||||
|
</failing_direction_contract>
|
||||||
|
|
||||||
**Project instructions:** Read ./CLAUDE.md or ./.claude/CLAUDE.md if either exists — follow project-specific guidelines
|
**Project instructions:** Read ./CLAUDE.md or ./.claude/CLAUDE.md if either exists — follow project-specific guidelines
|
||||||
**Project skills:** Check .claude/skills/ or .agents/skills/ directory (if either exists) — read SKILL.md files, plans should account for project skill rules
|
**Project skills:** Check .claude/skills/ or .agents/skills/ directory (if either exists) — read SKILL.md files, plans should account for project skill rules
|
||||||
|
|
||||||
@@ -991,13 +1016,17 @@ Display banner:
|
|||||||
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Verify-command path probe (#2401).** Before spawning, run the deterministic resolvability
|
**Verify-command probes (#2401, #3172).** Before spawning, run both deterministic probes and
|
||||||
probe and hand its JSON to the checker. It never executes command text and never prescribes a
|
hand their JSON to the checker. The first resolves each `<automated>` command's target; the
|
||||||
replacement path — it reports which `<automated>` targets resolve, which do not, and which it
|
second reports which runnable commands carry a `<fails_when>` statement naming their failure
|
||||||
refused to guess at. Handing it over is what stops the checker hand-reasoning the filesystem.
|
signal. Neither executes command text, and neither prescribes a replacement — the first reports
|
||||||
|
which `<automated>` targets resolve, which do not, and which it refused to guess at; the second
|
||||||
|
reports which commands state a failure signal and never authors one. Handing both over is what
|
||||||
|
stops the checker hand-reasoning the filesystem or the plans.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
VERIFY_PATHS=$(gsd_run check verify-command-paths "${PHASE}" --raw)
|
VERIFY_PATHS=$(gsd_run check verify-command-paths "${PHASE}" --raw)
|
||||||
|
FAILING_DIRECTIONS=$(gsd_run check verify-failure-directions "${PHASE}" --raw)
|
||||||
```
|
```
|
||||||
|
|
||||||
Checker prompt:
|
Checker prompt:
|
||||||
@@ -1032,6 +1061,18 @@ replacement path.
|
|||||||
```
|
```
|
||||||
</verify_command_path_probe>
|
</verify_command_path_probe>
|
||||||
|
|
||||||
|
<failing_direction_probe>
|
||||||
|
**Deterministic failing-direction probe (#3172)** — already run; do NOT re-derive these verdicts
|
||||||
|
by re-reading the plans yourself. Act on `severity` per check 8f: `blocker` → BLOCKER,
|
||||||
|
`warning` → WARNING, `none` → silent. `status: sentinel` is a Wave-0 `MISSING` placeholder and is
|
||||||
|
not a finding. A non-empty `readError` means the probe could not look — a WARNING, not a pass.
|
||||||
|
Quote the command that has no stated failure mode; never author the statement for the planner.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{FAILING_DIRECTIONS}
|
||||||
|
```
|
||||||
|
</failing_direction_probe>
|
||||||
|
|
||||||
<review_incorporation_verification>
|
<review_incorporation_verification>
|
||||||
**If Mode is reviews:** Read REVIEWS.md and verify each current actionable review finding is visible in executable PLAN.md content or explicitly deferred/rejected in the relevant PLAN.md. A finding remains actionable if it requires a concrete plan task, `<action>`, `<acceptance_criteria>`, `<verify>`, `must_haves`, threat-model item, stale-path correction, or execution contract change before /gsd:execute-phase runs.
|
**If Mode is reviews:** Read REVIEWS.md and verify each current actionable review finding is visible in executable PLAN.md content or explicitly deferred/rejected in the relevant PLAN.md. A finding remains actionable if it requires a concrete plan task, `<action>`, `<acceptance_criteria>`, `<verify>`, `must_haves`, threat-model item, stale-path correction, or execution contract change before /gsd:execute-phase runs.
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import planningScopeMod = require('./planning-scope.cjs');
|
|||||||
const { SCOPE } = planningScopeMod;
|
const { SCOPE } = planningScopeMod;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
import verifyCommandGroundingMod = require('./verify-command-grounding.cjs');
|
import verifyCommandGroundingMod = require('./verify-command-grounding.cjs');
|
||||||
const { probePhaseVerifyCommands } = verifyCommandGroundingMod;
|
const { probePhaseVerifyCommands, probePhaseFailingDirections } = verifyCommandGroundingMod;
|
||||||
|
|
||||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -935,6 +935,30 @@ function cmdTddReviewCheckpoint(projectDir: string, args: string[], raw: boolean
|
|||||||
output(result, raw, undefined);
|
output(result, raw, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a phase argument to an absolute phase directory, or '' when it
|
||||||
|
* cannot be resolved. Shared by every `check` arm that probes a phase's
|
||||||
|
* PLAN.md files, so the two never drift (DEFECT.GENERATIVE-FIX-DIVERGENCE).
|
||||||
|
* Never throws — the callers emit a degraded JSON payload instead, because
|
||||||
|
* a consumer must be able to tell "nothing to report" from "could not look".
|
||||||
|
*/
|
||||||
|
function resolvePhaseDirOrEmpty(projectDir: string, phase: string): string {
|
||||||
|
try {
|
||||||
|
const result = findPhaseInternal(projectDir, phase);
|
||||||
|
if (result && typeof result === 'object') {
|
||||||
|
// findPhaseInternal returns { directory: '<relative-posix-path>', ... }
|
||||||
|
// directory is relative to cwd — resolve it to absolute.
|
||||||
|
const relDir = typeof result['directory'] === 'string' ? result['directory'] : '';
|
||||||
|
if (relDir) {
|
||||||
|
return path.resolve(projectDir, relDir);
|
||||||
|
}
|
||||||
|
} else if (typeof result === 'string') {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch { /* phase dir lookup failure → caller emits degraded payload */ }
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// ─── verify-command-paths (#2401) ──────────────────────────────────────────────
|
// ─── verify-command-paths (#2401) ──────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -968,20 +992,7 @@ function cmdVerifyCommandPaths(projectDir: string, args: string[], raw: boolean)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let phaseDir = '';
|
const phaseDir = resolvePhaseDirOrEmpty(projectDir, phase);
|
||||||
try {
|
|
||||||
const result = findPhaseInternal(projectDir, phase);
|
|
||||||
if (result && typeof result === 'object') {
|
|
||||||
// findPhaseInternal returns { directory: '<relative-posix-path>', ... }
|
|
||||||
// directory is relative to cwd — resolve it to absolute.
|
|
||||||
const relDir = typeof result['directory'] === 'string' ? result['directory'] : '';
|
|
||||||
if (relDir) {
|
|
||||||
phaseDir = path.resolve(projectDir, relDir);
|
|
||||||
}
|
|
||||||
} else if (typeof result === 'string') {
|
|
||||||
phaseDir = result;
|
|
||||||
}
|
|
||||||
} catch { /* phase dir lookup failure → degraded payload below */ }
|
|
||||||
|
|
||||||
if (!phaseDir) {
|
if (!phaseDir) {
|
||||||
output(
|
output(
|
||||||
@@ -1001,6 +1012,59 @@ function cmdVerifyCommandPaths(projectDir: string, args: string[], raw: boolean)
|
|||||||
output(probed, raw, undefined);
|
output(probed, raw, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── verify-failure-directions (#3172) ─────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verify-failure-directions: probes every `<automated>` verify command
|
||||||
|
* declared in a phase's `-PLAN.md` files for a stated `<fails_when>` failing
|
||||||
|
* direction — see verify-command-grounding.cjs for the recognizer contract.
|
||||||
|
*
|
||||||
|
* Args: check verify-failure-directions <phase>
|
||||||
|
* Invocable as: gsd_run check verify-failure-directions <phase>
|
||||||
|
*
|
||||||
|
* When the phase cannot be resolved to a directory, this emits a non-throwing
|
||||||
|
* degraded JSON payload (status/commands/counts all zeroed, `readError`
|
||||||
|
* populated) rather than calling `error()` — the plan-checker parses this
|
||||||
|
* result and must be able to distinguish "nothing to report" from "could not
|
||||||
|
* look", which a non-zero exit / thrown error would collapse.
|
||||||
|
*/
|
||||||
|
function cmdVerifyFailureDirections(projectDir: string, args: string[], raw: boolean): void {
|
||||||
|
// args[0] = 'check', args[1] = 'verify-failure-directions', args[2] = phase
|
||||||
|
const phase = args[2] || '';
|
||||||
|
if (!phase) {
|
||||||
|
output(
|
||||||
|
{
|
||||||
|
status: 'unresolvable',
|
||||||
|
commands: [],
|
||||||
|
counts: { blocker: 0, warning: 0, total: 0 },
|
||||||
|
readError: 'verify-failure-directions requires a phase argument: check verify-failure-directions <phase>',
|
||||||
|
},
|
||||||
|
raw,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const phaseDir = resolvePhaseDirOrEmpty(projectDir, phase);
|
||||||
|
|
||||||
|
if (!phaseDir) {
|
||||||
|
output(
|
||||||
|
{
|
||||||
|
status: 'unresolvable',
|
||||||
|
commands: [],
|
||||||
|
counts: { blocker: 0, warning: 0, total: 0 },
|
||||||
|
readError: `could not resolve phase directory for phase ${phase}`,
|
||||||
|
},
|
||||||
|
raw,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = probePhaseFailingDirections({ phaseDir });
|
||||||
|
output(result, raw, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── gap-analysis-plan-post ───────────────────────────────────────────────────
|
// ─── gap-analysis-plan-post ───────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1594,6 +1658,12 @@ function routeCheckCommand({ args, cwd, raw }: RouteCheckCommandOptions): void {
|
|||||||
cmdVerifyCommandPaths(cwd, args, raw);
|
cmdVerifyCommandPaths(cwd, args, raw);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (subcommand === 'verify-failure-directions') {
|
||||||
|
// Presence probe for a stated <fails_when> per <automated> command
|
||||||
|
// (#3172) — never executes anything; see verify-command-grounding.cjs.
|
||||||
|
cmdVerifyFailureDirections(cwd, args, raw);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (subcommand === 'api-coverage-verify-pre') {
|
if (subcommand === 'api-coverage-verify-pre') {
|
||||||
// ai-integration capability blocking gate at verify:pre (#1562). Dot-to-
|
// ai-integration capability blocking gate at verify:pre (#1562). Dot-to-
|
||||||
// hyphen normalization means query "api-coverage.verify-pre" routes here.
|
// hyphen normalization means query "api-coverage.verify-pre" routes here.
|
||||||
@@ -1640,7 +1710,7 @@ function routeCheckCommand({ args, cwd, raw }: RouteCheckCommandOptions): void {
|
|||||||
routeProhibitionEnforcement(args, raw);
|
routeProhibitionEnforcement(args, raw);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
error('Unknown check subcommand. Available: api-coverage-verify-pre, auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-command-paths, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
error('Unknown check subcommand. Available: api-coverage-verify-pre, auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-command-paths, verify-failure-directions, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
@@ -1651,6 +1721,7 @@ export = {
|
|||||||
computeUiSafetyGate,
|
computeUiSafetyGate,
|
||||||
cmdGapAnalysisPlanPost,
|
cmdGapAnalysisPlanPost,
|
||||||
cmdVerifyCommandPaths,
|
cmdVerifyCommandPaths,
|
||||||
|
cmdVerifyFailureDirections,
|
||||||
cmdTddReviewCheckpoint,
|
cmdTddReviewCheckpoint,
|
||||||
cmdCheckPredicate,
|
cmdCheckPredicate,
|
||||||
buildPredicateDeps,
|
buildPredicateDeps,
|
||||||
|
|||||||
@@ -123,15 +123,27 @@ interface HarvestOptions {
|
|||||||
// ─── Extraction ───────────────────────────────────────────────────────────────
|
// ─── Extraction ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const TASK_NAME_RE = /<name>([\s\S]*?)<\/name>/;
|
const TASK_NAME_RE = /<name>([\s\S]*?)<\/name>/;
|
||||||
const AUTOMATED_BLOCK_RE = /<automated[^>]{0,200}>([\s\S]*?)<\/automated>/g;
|
/**
|
||||||
|
* The body cannot cross a subsequent `<automated>` opener (negative lookahead
|
||||||
|
* `(?!<automated\b)` gates each consumed char), mirroring the stop-at-next-open
|
||||||
|
* shape `extractTaggedBlocks`/`stripTaggedBlocks` already use. This is what
|
||||||
|
* makes a scan over an unclosed opener LINEAR instead of quadratic: the lazy
|
||||||
|
* `[\s\S]*?` body used to scan all the way to EOF hunting a closing tag that
|
||||||
|
* never comes (measured: k=40000 unclosed openers, ~1.5s). `MAX_BLOCK_WALK`
|
||||||
|
* below bounds matched-block COUNT, not scan cost — it does nothing for a scan
|
||||||
|
* that matches nothing, which is exactly the case this shape fixes.
|
||||||
|
*/
|
||||||
|
const AUTOMATED_BLOCK_RE = /<automated[^>]{0,200}>((?:(?!<automated\b)[\s\S])*?)<\/automated>/g;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bounds walking pathological input (e.g. hundreds of unclosed `<automated>`
|
* Bounds the NUMBER of matched `<automated>` blocks walked (e.g. hundreds of
|
||||||
* openers). `extractTaggedBlocks`/`stripTaggedBlocks` (task-block grammar
|
* legitimately closed blocks in one document), not the cost of scanning
|
||||||
* owner, `./markdown-sectionizer.cjs`) already use a ReDoS-safe
|
* pathological unclosed input — that cost is now bounded by the non-crossing
|
||||||
* stop-at-next-open pattern with no separate iteration cap of their own — a
|
* body in `AUTOMATED_BLOCK_RE`/`VERIFY_TOKEN_RE` themselves (a failed scan is
|
||||||
* document full of unclosed `<task>` openers never matches, so their walk is
|
* linear, so no separate iteration cap is needed to keep it cheap).
|
||||||
* a single linear scan regardless. This guard only bounds the `<automated>`
|
* `extractTaggedBlocks`/`stripTaggedBlocks` (task-block grammar owner,
|
||||||
|
* `./markdown-sectionizer.cjs`) already use the same non-crossing shape with
|
||||||
|
* no iteration cap of their own. This guard only bounds the `<automated>`
|
||||||
* scan this module still runs directly, matching the pre-existing behavior.
|
* scan this module still runs directly, matching the pre-existing behavior.
|
||||||
*/
|
*/
|
||||||
const MAX_BLOCK_WALK = 20000;
|
const MAX_BLOCK_WALK = 20000;
|
||||||
@@ -202,6 +214,253 @@ function extractAutomatedCommands(planText: unknown): AutomatedCommand[] {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Failing direction (#3172) ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
type FailingDirectionStatus = 'ok' | 'missing' | 'empty' | 'placeholder' | 'sentinel' | 'orphan';
|
||||||
|
type FailingDirectionSeverity = 'blocker' | 'warning' | 'none';
|
||||||
|
|
||||||
|
interface ResolvedFailingDirection {
|
||||||
|
command: string;
|
||||||
|
statement: string | null;
|
||||||
|
status: FailingDirectionStatus;
|
||||||
|
severity: FailingDirectionSeverity;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FailingDirectionEntry extends ResolvedFailingDirection {
|
||||||
|
plan: string;
|
||||||
|
task: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FailingDirectionCounts {
|
||||||
|
blocker: number;
|
||||||
|
warning: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProbePhaseFailingResult {
|
||||||
|
status: 'ok' | 'blocked' | 'unresolvable';
|
||||||
|
commands: FailingDirectionEntry[];
|
||||||
|
counts: FailingDirectionCounts;
|
||||||
|
readError: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProbePhaseFailingOptions {
|
||||||
|
phaseDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ordered alternation with a backreference so `<automated>` and `<fails_when>`
|
||||||
|
* are recovered in a SINGLE document-order pass — the pairing walk needs
|
||||||
|
* their relative positions, which two independent scans would discard.
|
||||||
|
*
|
||||||
|
* The body cannot cross a subsequent `<automated>` or `<fails_when>` opener
|
||||||
|
* (negative lookahead `(?!<(?:automated|fails_when)\b)`), the same
|
||||||
|
* non-crossing shape as `AUTOMATED_BLOCK_RE` above. This is what keeps a scan
|
||||||
|
* over an unclosed opener LINEAR (verified: k=10000/20000/40000 unclosed
|
||||||
|
* openers all complete in ~0ms); `MAX_BLOCK_WALK` bounds matched-block COUNT,
|
||||||
|
* not scan cost.
|
||||||
|
*/
|
||||||
|
const VERIFY_TOKEN_RE = /<(automated|fails_when)[^>]{0,200}>((?:(?!<(?:automated|fails_when)\b)[\s\S])*?)<\/\1>/g;
|
||||||
|
const PLACEHOLDER_STATEMENTS = new Set(['tbd', 'todo', 'n/a', 'na', 'none', 'unknown', 'tba', '?', '-']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Judge one `<automated>` command's stated failing direction. Pure, total,
|
||||||
|
* and never throws for any input shape — order of checks is load-bearing
|
||||||
|
* (#3172 review): the sentinel exemption (step 3) must run BEFORE the
|
||||||
|
* statement-presence check (step 4), because the Nyquist Wave-0 sentinel is
|
||||||
|
* not a runnable command and so has no failure mode to state, even when a
|
||||||
|
* statement happens to follow it in the plan text.
|
||||||
|
*/
|
||||||
|
function resolveFailingDirection(command: unknown, statement: unknown): ResolvedFailingDirection {
|
||||||
|
const cmd = typeof command === 'string' ? command.trim() : '';
|
||||||
|
|
||||||
|
if (cmd === '') {
|
||||||
|
return {
|
||||||
|
command: '',
|
||||||
|
statement: typeof statement === 'string' ? statement : null,
|
||||||
|
status: 'orphan',
|
||||||
|
severity: 'warning',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MISSING_SENTINEL_RE.test(cmd)) {
|
||||||
|
return {
|
||||||
|
command: cmd,
|
||||||
|
statement: typeof statement === 'string' ? statement.trim() : null,
|
||||||
|
status: 'sentinel',
|
||||||
|
severity: 'none',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof statement !== 'string') {
|
||||||
|
return { command: cmd, statement: null, status: 'missing', severity: 'blocker' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = statement.trim();
|
||||||
|
if (trimmed === '') {
|
||||||
|
return { command: cmd, statement, status: 'empty', severity: 'blocker' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whole-value match only — never a substring test. "TBD in the harness
|
||||||
|
// output" is real prose and must pass (#3172 review Finding).
|
||||||
|
if (PLACEHOLDER_STATEMENTS.has(trimmed.toLowerCase())) {
|
||||||
|
return { command: cmd, statement: trimmed, status: 'placeholder', severity: 'blocker' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { command: cmd, statement: trimmed, status: 'ok', severity: 'none' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan one text unit's `<automated>`/`<fails_when>` tokens in document order,
|
||||||
|
* binding each `<fails_when>` to the nearest PRECEDING `<automated>` (first
|
||||||
|
* statement wins; a redundant trailing statement adds no row) and emitting an
|
||||||
|
* `orphan` row for a `<fails_when>` with no pending command. Shares `guard`
|
||||||
|
* across every text unit in one `extractFailingDirections` call, mirroring
|
||||||
|
* `extractAutomatedFromText`'s MAX_BLOCK_WALK bound and zero-length-match
|
||||||
|
* advance. Returns `false` when the bound was hit; `true` otherwise.
|
||||||
|
*/
|
||||||
|
function extractFailingFromText(
|
||||||
|
text: string,
|
||||||
|
task: string,
|
||||||
|
out: FailingDirectionEntry[],
|
||||||
|
guard: { n: number },
|
||||||
|
): boolean {
|
||||||
|
VERIFY_TOKEN_RE.lastIndex = 0;
|
||||||
|
let pending: FailingDirectionEntry | null = null;
|
||||||
|
let bound = false;
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
while ((match = VERIFY_TOKEN_RE.exec(text)) !== null) {
|
||||||
|
const kind = match[1];
|
||||||
|
const body = match[2] ?? '';
|
||||||
|
|
||||||
|
if (kind === 'automated') {
|
||||||
|
const trimmedCommand = body.trim();
|
||||||
|
if (trimmedCommand !== '') {
|
||||||
|
const entry: FailingDirectionEntry = { ...resolveFailingDirection(trimmedCommand, undefined), plan: '', task };
|
||||||
|
out.push(entry);
|
||||||
|
pending = entry;
|
||||||
|
bound = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// kind === 'fails_when'
|
||||||
|
if (pending === null) {
|
||||||
|
const entry: FailingDirectionEntry = { ...resolveFailingDirection('', body), plan: '', task };
|
||||||
|
out.push(entry);
|
||||||
|
} else if (!bound) {
|
||||||
|
const resolved = resolveFailingDirection(pending.command, body);
|
||||||
|
pending.statement = resolved.statement;
|
||||||
|
pending.status = resolved.status;
|
||||||
|
pending.severity = resolved.severity;
|
||||||
|
bound = true;
|
||||||
|
}
|
||||||
|
// already bound → redundant trailing statement, ignored (first-wins).
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.index === VERIFY_TOKEN_RE.lastIndex) VERIFY_TOKEN_RE.lastIndex += 1;
|
||||||
|
guard.n += 1;
|
||||||
|
if (guard.n > MAX_BLOCK_WALK) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract every `<automated>`/`<fails_when>` pairing verdict from PLAN.md
|
||||||
|
* text. Never throws; a non-string or blank plan yields `[]`. `plan` is left
|
||||||
|
* `''`, mirroring `extractAutomatedCommands` — callers set it from the
|
||||||
|
* filename they read.
|
||||||
|
*
|
||||||
|
* Walks the SAME text units, in the SAME order, as `extractAutomatedCommands`
|
||||||
|
* (every `<task>` body first in document order, then the task-stripped
|
||||||
|
* remainder as a final unit with task `''`) so pairing never crosses a task
|
||||||
|
* boundary, and shares ONE `{n:0}` guard across every unit so
|
||||||
|
* `MAX_BLOCK_WALK` bounds the whole document.
|
||||||
|
*/
|
||||||
|
function extractFailingDirections(planText: unknown): FailingDirectionEntry[] {
|
||||||
|
if (typeof planText !== 'string' || planText.length === 0) return [];
|
||||||
|
|
||||||
|
const out: FailingDirectionEntry[] = [];
|
||||||
|
const guard = { n: 0 };
|
||||||
|
|
||||||
|
const taskBodies = extractTaggedBlocks(planText, 'task', true);
|
||||||
|
for (const body of taskBodies) {
|
||||||
|
const nameMatch = TASK_NAME_RE.exec(body);
|
||||||
|
const task = nameMatch ? nameMatch[1].trim() : '';
|
||||||
|
if (!extractFailingFromText(body, task, out, guard)) return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainder = stripTaggedBlocks(planText, 'task', true);
|
||||||
|
extractFailingFromText(remainder, '', out, guard);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe every `<automated>` command's stated failing direction across a
|
||||||
|
* phase directory's `-PLAN.md` files. Never throws — a read failure degrades
|
||||||
|
* to `readError` with the offending file skipped, and an empty result with a
|
||||||
|
* populated `readError` means "could not look", which must never render as
|
||||||
|
* "nothing to report".
|
||||||
|
*/
|
||||||
|
function probePhaseFailingDirections(options: ProbePhaseFailingOptions): ProbePhaseFailingResult {
|
||||||
|
const { phaseDir } = options;
|
||||||
|
|
||||||
|
let entries: string[];
|
||||||
|
try {
|
||||||
|
entries = fs.readdirSync(phaseDir);
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
status: 'unresolvable',
|
||||||
|
commands: [],
|
||||||
|
counts: { blocker: 0, warning: 0, total: 0 },
|
||||||
|
readError: toMessage(err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const planFiles = entries.filter(f => PLAN_FILE_RE.test(f)).sort();
|
||||||
|
const commands: FailingDirectionEntry[] = [];
|
||||||
|
const readErrors: string[] = [];
|
||||||
|
|
||||||
|
for (const file of planFiles) {
|
||||||
|
let text: string;
|
||||||
|
try {
|
||||||
|
text = fs.readFileSync(path.join(phaseDir, file), 'utf-8');
|
||||||
|
} catch (err) {
|
||||||
|
readErrors.push(toMessage(err));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of extractFailingDirections(text)) {
|
||||||
|
commands.push({ ...entry, plan: file });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const counts: FailingDirectionCounts = { blocker: 0, warning: 0, total: commands.length };
|
||||||
|
for (const c of commands) {
|
||||||
|
if (c.severity === 'blocker') counts.blocker += 1;
|
||||||
|
else if (c.severity === 'warning') counts.warning += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readError = readErrors.length > 0 ? readErrors.join('; ') : null;
|
||||||
|
|
||||||
|
// A populated readError can never yield 'ok' — "could not look" must never
|
||||||
|
// render as "nothing to report" (#3172 review self-finding: with the old
|
||||||
|
// `commands.length === 0 && readError !== null` precedence, a phase where
|
||||||
|
// one plan read fine with zero blockers and a SECOND plan failed to read
|
||||||
|
// fell through to 'ok' because commands.length > 0). 'blocked' keeps
|
||||||
|
// precedence over 'unresolvable' because a proven blocker is the stronger
|
||||||
|
// signal.
|
||||||
|
let status: 'ok' | 'blocked' | 'unresolvable';
|
||||||
|
if (counts.blocker > 0) {
|
||||||
|
status = 'blocked';
|
||||||
|
} else if (readError !== null) {
|
||||||
|
status = 'unresolvable';
|
||||||
|
} else {
|
||||||
|
status = 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status, commands, counts, readError };
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Resolution ───────────────────────────────────────────────────────────────
|
// ─── Resolution ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -234,7 +493,17 @@ const PREFIX_FLAG_STRIP_RE = /(?:^|\s)--prefix(?:=|\s+)(?:"[^"]*"|'[^']*'|\S+)/g
|
|||||||
const NEEDS_NPM_RE = /^(npm|npx|pnpm|yarn|bun)\b/;
|
const NEEDS_NPM_RE = /^(npm|npx|pnpm|yarn|bun)\b/;
|
||||||
const NEEDS_MAKE_RE = /^make\b/;
|
const NEEDS_MAKE_RE = /^make\b/;
|
||||||
const NPM_RUN_SCRIPT_RE = /\bnpm\s+run\s+([\w:@./-]+)/;
|
const NPM_RUN_SCRIPT_RE = /\bnpm\s+run\s+([\w:@./-]+)/;
|
||||||
const MISSING_SENTINEL_RE = /^MISSING\b/;
|
/**
|
||||||
|
* The `\s|$` anchor (not `\b`) is load-bearing: `\b` also matches an env-var
|
||||||
|
* assignment prefix such as `MISSING=1 cmd` (`=` is a non-word char, so `\b`
|
||||||
|
* sits happily before it) — and that IS a real runnable command, not the
|
||||||
|
* Nyquist Wave-0 sentinel. #3172 wires this constant into a BLOCKING gate
|
||||||
|
* (`resolveFailingDirection`), so a benign #2401 false-positive on the `\b`
|
||||||
|
* form becomes a real gate bypass: `MISSING=true npm test` would be reported
|
||||||
|
* `status:'sentinel', severity:'none'` instead of `missing`/`blocker`
|
||||||
|
* (#3172 review).
|
||||||
|
*/
|
||||||
|
const MISSING_SENTINEL_RE = /^MISSING(?:\s|$)/;
|
||||||
const MAX_MANIFEST_BYTES = 512 * 1024;
|
const MAX_MANIFEST_BYTES = 512 * 1024;
|
||||||
|
|
||||||
function emptyResult(base: string): ResolvedVerifyCommand {
|
function emptyResult(base: string): ResolvedVerifyCommand {
|
||||||
@@ -751,4 +1020,7 @@ export = {
|
|||||||
resolveVerifyCommandTarget,
|
resolveVerifyCommandTarget,
|
||||||
probePhaseVerifyCommands,
|
probePhaseVerifyCommands,
|
||||||
harvestPriorVerifyCommands,
|
harvestPriorVerifyCommands,
|
||||||
|
resolveFailingDirection,
|
||||||
|
extractFailingDirections,
|
||||||
|
probePhaseFailingDirections,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"paths": {
|
||||||
|
"plan-phase.md": "#3172: plan-phase.md now dispatches the deterministic `gsd_run check verify-failure-directions` probe beside the #2401 path probe and interpolates its result into the plan-checker prompt as {FAILING_DIRECTIONS} inside a new <failing_direction_probe> block, and the planner spawn prompt gains a <failing_direction_contract> block requiring a `<fails_when>` sibling for every runnable `<automated>` command. That contract block is the larger half of the growth and lives here rather than in the planner agent file ON PURPOSE: the planner is frozen under a 49152-LF-char cap, so a planner-side authoring rule is projected onto its spawn contract — the #3297 / #3645 precedent. 90877 -> 93073 bytes (+2196). Deliberate runtime-loaded workflow text for the new gate, not converter drift. Filed as its own fragment per #3078: the 3409 fragment that previously carried plan-phase.md's growth reason was spent and has been swept from next, so nothing else claims this key."
|
||||||
|
}
|
||||||
|
}
|
||||||
657
tests/failing-direction.test.cjs
Normal file
657
tests/failing-direction.test.cjs
Normal file
@@ -0,0 +1,657 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stated failing direction (#3172).
|
||||||
|
*
|
||||||
|
* Module: gsd-core/bin/lib/verify-command-grounding.cjs
|
||||||
|
* Exported (added by #3172): extractFailingDirections, resolveFailingDirection,
|
||||||
|
* probePhaseFailingDirections
|
||||||
|
*
|
||||||
|
* #3172: `gsd-planner` emitted 21 `<automated>` acceptance commands that could
|
||||||
|
* not run at all. A command with no expressible failure mode is not an
|
||||||
|
* acceptance test — it reads as rigour and is not falsifiable. The approved fix
|
||||||
|
* (maintainer verdict on the issue, "shape 3") is to require the plan to STATE
|
||||||
|
* what output constitutes failure, in a `<fails_when>` sibling of `<automated>`.
|
||||||
|
*
|
||||||
|
* This probe answers "does each runnable `<automated>` command have a stated
|
||||||
|
* failing direction?" It is a PRESENCE recognizer, not a judge of the
|
||||||
|
* statement's quality: whether the statement names the RIGHT failure signal
|
||||||
|
* stays plan-checker (LLM) judgment. It never executes command text — PLAN.md is
|
||||||
|
* model-authored untrusted input — and it never PRESCRIBES a statement, because
|
||||||
|
* a prescribed statement would be copied verbatim and carry zero information
|
||||||
|
* (Goodhart), reproducing #3172 one level up.
|
||||||
|
*
|
||||||
|
* Pairing rule: within one `<task>` body, each `<fails_when>` binds to the
|
||||||
|
* nearest PRECEDING `<automated>`; a command's binding statement is the FIRST
|
||||||
|
* one that follows it before the next command. A statement with no preceding
|
||||||
|
* command is an `orphan` WARNING, never a blocker.
|
||||||
|
*
|
||||||
|
* Row numbers below map to
|
||||||
|
* `.gsd/phase/feat-3172-stated-failing-direction/50-test-matrix.md`.
|
||||||
|
*
|
||||||
|
* IO-failure rows monkeypatch the `fs` method and restore in `finally` — never
|
||||||
|
* `chmod 0o000`, which root bypasses in Docker/CI so the test would pass with
|
||||||
|
* zero coverage.
|
||||||
|
*
|
||||||
|
* The final `describe('property-based invariants', …)` block carries the
|
||||||
|
* fast-check properties (P1-P4 in the matrix): closed-enum totality, the
|
||||||
|
* pairing walk at generated command counts, CRLF invariance, and the sentinel
|
||||||
|
* exemption's guarantee that a blocker is never reported for a sentinel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { describe, test, before, after } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const os = require('node:os');
|
||||||
|
const path = require('node:path');
|
||||||
|
const fc = require('./helpers/fast-check-setup.cjs');
|
||||||
|
const { cleanup, runGsdTools } = require('./helpers.cjs');
|
||||||
|
|
||||||
|
const {
|
||||||
|
extractAutomatedCommands,
|
||||||
|
probePhaseVerifyCommands,
|
||||||
|
extractFailingDirections,
|
||||||
|
resolveFailingDirection,
|
||||||
|
probePhaseFailingDirections,
|
||||||
|
} = require('../gsd-core/bin/lib/verify-command-grounding.cjs');
|
||||||
|
|
||||||
|
const KNOWN_STATUSES = new Set(['ok', 'missing', 'empty', 'placeholder', 'sentinel', 'orphan']);
|
||||||
|
const KNOWN_SEVERITIES = new Set(['blocker', 'warning', 'none']);
|
||||||
|
|
||||||
|
/** Every placeholder atom the probe refuses, per 40-design.md. */
|
||||||
|
const PLACEHOLDER_ATOMS = ['tbd', 'todo', 'n/a', 'na', 'none', 'unknown', 'tba', '?', '-'];
|
||||||
|
|
||||||
|
const SENTINEL = 'MISSING — Wave 0 must create tests/auth.test.ts first';
|
||||||
|
|
||||||
|
/** Root for every fixture built by this file; removed in `after`. */
|
||||||
|
let ROOT = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a PLAN.md whose single task contains `parts` verbatim, in order.
|
||||||
|
* A part is `{ automated }` or `{ failsWhen }`, so a row can place statements
|
||||||
|
* before, between, or after commands and assert the pairing walk directly.
|
||||||
|
*/
|
||||||
|
function taskWith(parts, { eol = '\n', name = 'task-0' } = {}) {
|
||||||
|
const body = parts.map((p) =>
|
||||||
|
'automated' in p
|
||||||
|
? ` <verify><automated>${p.automated}</automated></verify>`
|
||||||
|
: ` <fails_when>${p.failsWhen}</fails_when>`,
|
||||||
|
);
|
||||||
|
return [
|
||||||
|
'# Plan',
|
||||||
|
'',
|
||||||
|
'<task type="auto">',
|
||||||
|
` <name>${name}</name>`,
|
||||||
|
' <action>do the thing</action>',
|
||||||
|
...body,
|
||||||
|
' <done>committed</done>',
|
||||||
|
'</task>',
|
||||||
|
].join(eol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `parts` per task, so a row can prove pairing never crosses a task boundary. */
|
||||||
|
function planWithTasks(taskParts, { eol = '\n' } = {}) {
|
||||||
|
return taskParts
|
||||||
|
.map((parts, i) => taskWith(parts, { eol, name: `task-${i}` }).split(eol).slice(2).join(eol))
|
||||||
|
.reduce((acc, t) => `${acc}${eol}${t}`, ['# Plan', ''].join(eol));
|
||||||
|
}
|
||||||
|
|
||||||
|
function statuses(entries) {
|
||||||
|
return entries.map((e) => e.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixture matching the real `.planning/phases/<dir>/` layout findPhaseInternal
|
||||||
|
* resolves against.
|
||||||
|
*/
|
||||||
|
function writePhase(root, phaseName, plans) {
|
||||||
|
const phaseDir = path.join(root, '.planning', 'phases', phaseName);
|
||||||
|
fs.mkdirSync(phaseDir, { recursive: true });
|
||||||
|
for (const [file, body] of Object.entries(plans)) {
|
||||||
|
fs.writeFileSync(path.join(phaseDir, file), body, 'utf8');
|
||||||
|
}
|
||||||
|
return phaseDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3172-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
if (ROOT) cleanup(ROOT);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('extractFailingDirections — pairing', () => {
|
||||||
|
test('row 1 — pairs a single statement to its preceding command', () => {
|
||||||
|
const out = extractFailingDirections(
|
||||||
|
taskWith([{ automated: 'npm test' }, { failsWhen: 'non-zero exit' }]),
|
||||||
|
);
|
||||||
|
assert.equal(out.length, 1);
|
||||||
|
assert.equal(out[0].command, 'npm test');
|
||||||
|
assert.equal(out[0].statement, 'non-zero exit');
|
||||||
|
assert.equal(out[0].status, 'ok');
|
||||||
|
assert.equal(out[0].severity, 'none');
|
||||||
|
assert.equal(out[0].task, 'task-0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 2 — blocks a command with no failing direction', () => {
|
||||||
|
const out = extractFailingDirections(taskWith([{ automated: 'npm test' }]));
|
||||||
|
assert.equal(out.length, 1);
|
||||||
|
assert.equal(out[0].status, 'missing');
|
||||||
|
assert.equal(out[0].severity, 'blocker');
|
||||||
|
assert.equal(out[0].statement, null);
|
||||||
|
// The command is quoted verbatim so the checker can report it without
|
||||||
|
// re-deriving it (the "report, never prescribe" rule).
|
||||||
|
assert.equal(out[0].command, 'npm test');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 9 — attributes a statement to the command it follows, not the next one', () => {
|
||||||
|
const out = extractFailingDirections(
|
||||||
|
taskWith([
|
||||||
|
{ automated: 'npm test' },
|
||||||
|
{ failsWhen: 'non-zero exit' },
|
||||||
|
{ automated: 'npm run lint' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert.deepEqual(statuses(out), ['ok', 'missing']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 10 — does not credit a later statement to an earlier command', () => {
|
||||||
|
const out = extractFailingDirections(
|
||||||
|
taskWith([
|
||||||
|
{ automated: 'npm test' },
|
||||||
|
{ automated: 'npm run lint' },
|
||||||
|
{ failsWhen: 'any eslint error' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert.deepEqual(statuses(out), ['missing', 'ok']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 11 — pairs N statements to N commands in order', () => {
|
||||||
|
const out = extractFailingDirections(
|
||||||
|
taskWith([
|
||||||
|
{ automated: 'a' },
|
||||||
|
{ failsWhen: 'a fails on non-zero exit' },
|
||||||
|
{ automated: 'b' },
|
||||||
|
{ failsWhen: 'b fails when stderr is non-empty' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert.deepEqual(statuses(out), ['ok', 'ok']);
|
||||||
|
assert.deepEqual(
|
||||||
|
out.map((e) => e.statement),
|
||||||
|
['a fails on non-zero exit', 'b fails when stderr is non-empty'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 12 — statement count at limit-1, limit and limit+1', () => {
|
||||||
|
const cmds = ['a', 'b', 'c'];
|
||||||
|
const build = (n) => {
|
||||||
|
const parts = [];
|
||||||
|
cmds.forEach((c, i) => {
|
||||||
|
parts.push({ automated: c });
|
||||||
|
if (i < n) parts.push({ failsWhen: `${c} fails on non-zero exit` });
|
||||||
|
});
|
||||||
|
// limit+1: an extra statement trailing the last command.
|
||||||
|
if (n > cmds.length) parts.push({ failsWhen: 'redundant but harmless' });
|
||||||
|
return parts;
|
||||||
|
};
|
||||||
|
|
||||||
|
// limit-1 — two statements for three commands.
|
||||||
|
assert.deepEqual(statuses(extractFailingDirections(taskWith(build(2)))), [
|
||||||
|
'ok',
|
||||||
|
'ok',
|
||||||
|
'missing',
|
||||||
|
]);
|
||||||
|
// limit — one each.
|
||||||
|
assert.deepEqual(statuses(extractFailingDirections(taskWith(build(3)))), ['ok', 'ok', 'ok']);
|
||||||
|
// limit+1 — the extra statement HAS a preceding command, so it is redundant,
|
||||||
|
// not an orphan, and adds no row.
|
||||||
|
const over = extractFailingDirections(taskWith(build(4)));
|
||||||
|
assert.deepEqual(statuses(over), ['ok', 'ok', 'ok']);
|
||||||
|
assert.equal(over.length, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 12b — the first statement after a command is the binding one', () => {
|
||||||
|
const out = extractFailingDirections(
|
||||||
|
taskWith([
|
||||||
|
{ automated: 'npm test' },
|
||||||
|
{ failsWhen: 'TBD' },
|
||||||
|
{ failsWhen: 'non-zero exit, or "0 passed" in the summary' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert.equal(out.length, 1);
|
||||||
|
assert.equal(out[0].status, 'placeholder');
|
||||||
|
assert.equal(out[0].severity, 'blocker');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 13 — warns on a statement bound to no command', () => {
|
||||||
|
const out = extractFailingDirections(
|
||||||
|
taskWith([{ failsWhen: 'non-zero exit' }, { automated: 'npm test' }]),
|
||||||
|
);
|
||||||
|
assert.equal(out.length, 2);
|
||||||
|
const orphan = out.find((e) => e.status === 'orphan');
|
||||||
|
assert.ok(orphan, 'expected an orphan row');
|
||||||
|
assert.equal(orphan.severity, 'warning');
|
||||||
|
assert.equal(orphan.command, '');
|
||||||
|
// The command that follows it is still unstated — the orphan does not
|
||||||
|
// satisfy it.
|
||||||
|
assert.equal(out.find((e) => e.command === 'npm test').status, 'missing');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 16 — pairs commands that sit outside any task block', () => {
|
||||||
|
const md = [
|
||||||
|
'# Plan',
|
||||||
|
'',
|
||||||
|
'<verify><automated>npm run build</automated></verify>',
|
||||||
|
'<fails_when>a non-zero tsc exit</fails_when>',
|
||||||
|
'<verify><automated>npm run lint</automated></verify>',
|
||||||
|
].join('\n');
|
||||||
|
const out = extractFailingDirections(md);
|
||||||
|
assert.deepEqual(statuses(out), ['ok', 'missing']);
|
||||||
|
assert.equal(out[0].task, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 17 — pairing does not leak across task boundaries', () => {
|
||||||
|
const md = planWithTasks([
|
||||||
|
[{ automated: 'a' }, { failsWhen: 'a fails on non-zero exit' }],
|
||||||
|
[{ automated: 'b' }],
|
||||||
|
]);
|
||||||
|
const out = extractFailingDirections(md);
|
||||||
|
assert.deepEqual(statuses(out), ['ok', 'missing']);
|
||||||
|
assert.equal(out[1].task, 'task-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 20 — tolerates attributes on the automated opening tag', () => {
|
||||||
|
const md = [
|
||||||
|
'<task><name>t</name>',
|
||||||
|
'<verify><automated tier="fast">npm test</automated></verify>',
|
||||||
|
'<fails_when>non-zero exit</fails_when>',
|
||||||
|
'</task>',
|
||||||
|
].join('\n');
|
||||||
|
const out = extractFailingDirections(md);
|
||||||
|
assert.equal(out.length, 1);
|
||||||
|
assert.equal(out[0].status, 'ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 21 — non-string plan text yields an empty list without throwing', () => {
|
||||||
|
for (const bad of [0, '', [], null, undefined, true, {}]) {
|
||||||
|
assert.deepEqual(extractFailingDirections(bad), [], `input ${JSON.stringify(bad)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 22 — bounded walk on pathological unclosed tags', () => {
|
||||||
|
// The non-crossing body (`(?!<(?:automated|fails_when)\b)`) is what keeps
|
||||||
|
// this LINEAR rather than quadratic; the repeat count was reduced from
|
||||||
|
// 30000 to 5000 (#3172 review Fix 2) — the quadratic blowup this probed
|
||||||
|
// for is fixed, so 30000 only burns suite time now.
|
||||||
|
const md = '<automated>'.repeat(5000) + '<fails_when>'.repeat(5000);
|
||||||
|
const out = extractFailingDirections(md);
|
||||||
|
assert.ok(Array.isArray(out));
|
||||||
|
});
|
||||||
|
|
||||||
|
// #3172 review Fix 2: the non-crossing body must not let an unclosed
|
||||||
|
// opener swallow a later well-formed block.
|
||||||
|
test('row 36 — an unclosed automated opener does not swallow a later block', () => {
|
||||||
|
// Both the unclosed opener and the well-formed pair sit in the SAME task
|
||||||
|
// body, so a single VERIFY_TOKEN_RE scan must not let the unclosed
|
||||||
|
// opener's lazy body cross the later `<automated>` and consume it.
|
||||||
|
const md = [
|
||||||
|
'<task><name>t</name>',
|
||||||
|
'<verify><automated>',
|
||||||
|
'<verify><automated>npm test</automated></verify>',
|
||||||
|
'<fails_when>non-zero exit</fails_when>',
|
||||||
|
'</task>',
|
||||||
|
].join('\n');
|
||||||
|
const out = extractFailingDirections(md);
|
||||||
|
const recovered = out.find((e) => e.command === 'npm test');
|
||||||
|
assert.ok(recovered, 'expected the well-formed command to be recovered');
|
||||||
|
assert.equal(recovered.status, 'ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 14 — an empty automated body is not a failing-direction finding', () => {
|
||||||
|
const out = extractFailingDirections(
|
||||||
|
taskWith([{ automated: '' }, { automated: ' ' }, { automated: 'npm test' }]),
|
||||||
|
);
|
||||||
|
// The extractor drops empty bodies (Dimension 8a owns presence), so only
|
||||||
|
// the real command is judged here.
|
||||||
|
assert.equal(out.length, 1);
|
||||||
|
assert.equal(out[0].command, 'npm test');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 15 — a verify block with no automated command reports nothing', () => {
|
||||||
|
const md = taskWith([]).replace('<done>committed</done>', '<verify>eyeball it</verify>');
|
||||||
|
assert.deepEqual(extractFailingDirections(md), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 18 — CRLF plan text produces identical verdicts', () => {
|
||||||
|
const parts = [
|
||||||
|
{ automated: 'a' },
|
||||||
|
{ failsWhen: 'a fails on non-zero exit' },
|
||||||
|
{ automated: 'b' },
|
||||||
|
];
|
||||||
|
const lf = extractFailingDirections(taskWith(parts, { eol: '\n' }));
|
||||||
|
const crlf = extractFailingDirections(taskWith(parts, { eol: '\r\n' }));
|
||||||
|
assert.deepEqual(
|
||||||
|
crlf.map((e) => ({ c: e.command, s: e.statement, st: e.status })),
|
||||||
|
lf.map((e) => ({ c: e.command, s: e.statement, st: e.status })),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolveFailingDirection — statement verdicts', () => {
|
||||||
|
test('row 3 — treats a whitespace-only statement as empty', () => {
|
||||||
|
for (const blank of [' ', '\t', '\n', ' \r\n ']) {
|
||||||
|
const r = resolveFailingDirection('npm test', blank);
|
||||||
|
assert.equal(r.status, 'empty', `blank ${JSON.stringify(blank)}`);
|
||||||
|
assert.equal(r.severity, 'blocker');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 4 — rejects every placeholder atom, case-insensitively', () => {
|
||||||
|
for (const atom of PLACEHOLDER_ATOMS) {
|
||||||
|
for (const cased of [atom, atom.toUpperCase(), ` ${atom} `]) {
|
||||||
|
const r = resolveFailingDirection('npm test', cased);
|
||||||
|
assert.equal(r.status, 'placeholder', `atom ${JSON.stringify(cased)}`);
|
||||||
|
assert.equal(r.severity, 'blocker');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 5 — accepts prose that merely starts with a placeholder word', () => {
|
||||||
|
for (const prose of [
|
||||||
|
'TBD in the harness output is printed instead of a count',
|
||||||
|
'none of the assertions print, so the summary line is absent',
|
||||||
|
'- a missing summary line',
|
||||||
|
]) {
|
||||||
|
const r = resolveFailingDirection('npm test', prose);
|
||||||
|
assert.equal(r.status, 'ok', `prose ${JSON.stringify(prose)}`);
|
||||||
|
assert.equal(r.severity, 'none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 19 — accepts a statement containing angle brackets and quotes', () => {
|
||||||
|
for (const stmt of [
|
||||||
|
'exit code > 0',
|
||||||
|
'stderr contains "FAIL" && exit != 0',
|
||||||
|
"the line 'ok 0' appears",
|
||||||
|
'a non-zero exit,\nor an empty summary',
|
||||||
|
]) {
|
||||||
|
const r = resolveFailingDirection('npm test', stmt);
|
||||||
|
assert.equal(r.status, 'ok', `statement ${JSON.stringify(stmt)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 6 — exempts the MISSING Wave-0 sentinel', () => {
|
||||||
|
const r = resolveFailingDirection(SENTINEL, null);
|
||||||
|
assert.equal(r.status, 'sentinel');
|
||||||
|
assert.equal(r.severity, 'none');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 7 — keeps a sentinel silent even when a statement follows it', () => {
|
||||||
|
const r = resolveFailingDirection(SENTINEL, 'non-zero exit');
|
||||||
|
assert.equal(r.status, 'sentinel');
|
||||||
|
assert.equal(r.severity, 'none');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 8 — a sentinel does not consume the following statement', () => {
|
||||||
|
const out = extractFailingDirections(
|
||||||
|
taskWith([
|
||||||
|
{ automated: SENTINEL },
|
||||||
|
{ automated: 'npm test' },
|
||||||
|
{ failsWhen: 'non-zero exit' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert.deepEqual(statuses(out), ['sentinel', 'ok']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns a closed-enum shape for non-string input', () => {
|
||||||
|
for (const bad of [null, undefined, 0, [], {}, true]) {
|
||||||
|
const r = resolveFailingDirection(bad, bad);
|
||||||
|
assert.ok(KNOWN_STATUSES.has(r.status), `status for ${JSON.stringify(bad)}`);
|
||||||
|
assert.ok(KNOWN_SEVERITIES.has(r.severity), `severity for ${JSON.stringify(bad)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// #3172 review finding: a blocking gate must not be bypassed by a command
|
||||||
|
// that merely starts with the letters MISSING — the pre-fix `\b` anchor
|
||||||
|
// also matched an env-var assignment prefix (`=` is a non-word char).
|
||||||
|
test('row 34 — an env-var assignment prefixed MISSING= is judged, not exempted', () => {
|
||||||
|
const r = resolveFailingDirection('MISSING=true npm test && echo done', undefined);
|
||||||
|
assert.equal(r.status, 'missing');
|
||||||
|
assert.equal(r.severity, 'blocker');
|
||||||
|
for (const cmd of ['MISSING_FOO=1 npm test', 'MISSINGLY npm test']) {
|
||||||
|
const r2 = resolveFailingDirection(cmd, undefined);
|
||||||
|
assert.equal(r2.status, 'missing', `command ${JSON.stringify(cmd)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Negative-space partner of row 34 — the tightening must not break the
|
||||||
|
// exemption it exists to preserve.
|
||||||
|
test('row 35 — the real Wave-0 sentinel forms stay exempt', () => {
|
||||||
|
for (const cmd of ['MISSING', 'MISSING — Wave 0 must create tests/x.test.ts']) {
|
||||||
|
const r = resolveFailingDirection(cmd, undefined);
|
||||||
|
assert.equal(r.status, 'sentinel', `command ${JSON.stringify(cmd)}`);
|
||||||
|
assert.equal(r.severity, 'none', `command ${JSON.stringify(cmd)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('probePhaseFailingDirections', () => {
|
||||||
|
test('row 25 — aggregates counts across every plan in the phase', () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(ROOT, 'agg-'));
|
||||||
|
const phaseDir = writePhase(root, 'phase-1', {
|
||||||
|
'01-01-PLAN.md': taskWith([{ automated: 'a' }, { failsWhen: 'a fails on non-zero exit' }]),
|
||||||
|
'01-02-PLAN.md': taskWith([{ automated: 'b' }]),
|
||||||
|
});
|
||||||
|
const out = probePhaseFailingDirections({ phaseDir });
|
||||||
|
assert.equal(out.readError, null);
|
||||||
|
assert.equal(out.counts.total, 2);
|
||||||
|
assert.equal(out.counts.blocker, 1);
|
||||||
|
assert.equal(out.status, 'blocked');
|
||||||
|
const plans = out.commands.map((c) => c.plan).sort();
|
||||||
|
assert.deepEqual(plans, ['01-01-PLAN.md', '01-02-PLAN.md']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a phase whose every command is stated reports ok', () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(ROOT, 'clean-'));
|
||||||
|
const phaseDir = writePhase(root, 'phase-2', {
|
||||||
|
'02-01-PLAN.md': taskWith([{ automated: 'a' }, { failsWhen: 'a fails on non-zero exit' }]),
|
||||||
|
});
|
||||||
|
const out = probePhaseFailingDirections({ phaseDir });
|
||||||
|
assert.equal(out.status, 'ok');
|
||||||
|
assert.equal(out.counts.blocker, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 23 — an unreadable plan reports readError rather than a clean pass', () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(ROOT, 'ioerr-'));
|
||||||
|
const phaseDir = writePhase(root, 'phase-3', {
|
||||||
|
'03-01-PLAN.md': taskWith([{ automated: 'a' }]),
|
||||||
|
});
|
||||||
|
const realRead = fs.readFileSync;
|
||||||
|
try {
|
||||||
|
fs.readFileSync = (p, ...rest) => {
|
||||||
|
if (String(p).endsWith('03-01-PLAN.md')) {
|
||||||
|
const err = new Error('EACCES: simulated read failure');
|
||||||
|
err.code = 'EACCES';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return realRead(p, ...rest);
|
||||||
|
};
|
||||||
|
const out = probePhaseFailingDirections({ phaseDir });
|
||||||
|
assert.ok(out.readError, 'expected a populated readError');
|
||||||
|
assert.deepEqual(out.commands, []);
|
||||||
|
// "could not look" must never render as "nothing to report".
|
||||||
|
assert.notEqual(out.status, 'ok');
|
||||||
|
} finally {
|
||||||
|
fs.readFileSync = realRead;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 24 — an unresolvable phase emits a degraded payload', () => {
|
||||||
|
const out = probePhaseFailingDirections({ phaseDir: path.join(ROOT, 'does-not-exist') });
|
||||||
|
assert.ok(out.readError, 'expected a populated readError');
|
||||||
|
assert.deepEqual(out.commands, []);
|
||||||
|
assert.deepEqual(out.counts, { blocker: 0, warning: 0, total: 0 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('the #2401 path probe is not disturbed (Hyrum consequences)', () => {
|
||||||
|
test('row 28 — adding fails_when does not change extractAutomatedCommands output', () => {
|
||||||
|
const withStatements = taskWith([
|
||||||
|
{ automated: 'npm test' },
|
||||||
|
{ failsWhen: 'non-zero exit' },
|
||||||
|
{ automated: 'npm run lint' },
|
||||||
|
{ failsWhen: 'any eslint error' },
|
||||||
|
]);
|
||||||
|
const withoutStatements = taskWith([{ automated: 'npm test' }, { automated: 'npm run lint' }]);
|
||||||
|
assert.deepEqual(
|
||||||
|
extractAutomatedCommands(withStatements).map((c) => ({ t: c.task, c: c.command })),
|
||||||
|
extractAutomatedCommands(withoutStatements).map((c) => ({ t: c.task, c: c.command })),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 29 — a missing failing direction does not alter the path probe verdict', () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(ROOT, 'undisturbed-'));
|
||||||
|
const stated = writePhase(root, 'phase-a', {
|
||||||
|
'01-PLAN.md': taskWith([{ automated: 'npm test' }, { failsWhen: 'non-zero exit' }]),
|
||||||
|
});
|
||||||
|
const unstated = writePhase(root, 'phase-b', {
|
||||||
|
'01-PLAN.md': taskWith([{ automated: 'npm test' }]),
|
||||||
|
});
|
||||||
|
const a = probePhaseVerifyCommands({ phaseDir: stated, projectRoot: root });
|
||||||
|
const b = probePhaseVerifyCommands({ phaseDir: unstated, projectRoot: root });
|
||||||
|
assert.deepEqual(a.counts, b.counts);
|
||||||
|
assert.equal(a.status, b.status);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('gsd-tools check verify-failure-directions', () => {
|
||||||
|
test('row 26 — the check arm emits the probe JSON', () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(ROOT, 'cli-'));
|
||||||
|
writePhase(root, '01-test-phase', {
|
||||||
|
'01-01-PLAN.md': taskWith([{ automated: 'npm test' }]),
|
||||||
|
});
|
||||||
|
const result = runGsdTools(['check', 'verify-failure-directions', '1', '--raw'], root);
|
||||||
|
assert.ok(result.success, `check verify-failure-directions should succeed. stderr: ${result.error}`);
|
||||||
|
const parsed = JSON.parse(result.output);
|
||||||
|
assert.equal(parsed.counts.blocker, 1);
|
||||||
|
assert.equal(parsed.commands[0].status, 'missing');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 27 — the check arm degrades when the phase argument is absent', () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(ROOT, 'cli-noarg-'));
|
||||||
|
fs.mkdirSync(path.join(root, '.planning', 'phases'), { recursive: true });
|
||||||
|
const result = runGsdTools(['check', 'verify-failure-directions', '--raw'], root);
|
||||||
|
const parsed = JSON.parse(result.output);
|
||||||
|
assert.ok(parsed.readError, 'expected a populated readError');
|
||||||
|
assert.match(parsed.readError, /phase/i);
|
||||||
|
assert.deepEqual(parsed.commands, []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('the plan-authoring contract text (#3172)', () => {
|
||||||
|
const repoRoot = path.join(__dirname, '..');
|
||||||
|
const read = (rel) => fs.readFileSync(path.join(repoRoot, rel), 'utf8');
|
||||||
|
|
||||||
|
test('row 30 — the planner spawn contract requires a failing direction', () => {
|
||||||
|
// #3172's planner-side rule is projected onto the spawn contract, not
|
||||||
|
// written into the agent file — see row 30b. Assert the block exists and
|
||||||
|
// governs what it must.
|
||||||
|
const block = read('gsd-core/workflows/plan-phase.md')
|
||||||
|
.split('<failing_direction_contract>')[1]
|
||||||
|
?.split('</failing_direction_contract>')[0];
|
||||||
|
assert.ok(block, 'plan-phase.md must carry the <failing_direction_contract> block in the planner spawn prompt (#3172)');
|
||||||
|
assert.match(block, /<fails_when>/);
|
||||||
|
assert.match(block, /planner-failing-direction\.md/);
|
||||||
|
// The rule is worthless if it does not name the sentinel exemption and
|
||||||
|
// the placeholder rejection — those are the two edges a planner gets wrong.
|
||||||
|
assert.match(block, /MISSING/);
|
||||||
|
assert.match(block, /TBD/);
|
||||||
|
// The reference the contract points at must exist and name the element.
|
||||||
|
assert.match(read('gsd-core/references/planner-failing-direction.md'), /<fails_when>/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 30b — the frozen planner agent file is untouched by #3172', () => {
|
||||||
|
// agents/gsd-planner.md is pinned under a 49152-LF-char cap by four
|
||||||
|
// suites; #3297/#3645 established that a planner-side rule goes in the
|
||||||
|
// spawn contract instead. Guard the freeze in both directions: the cap
|
||||||
|
// itself, and the absence of this issue's rule from the agent file.
|
||||||
|
const src = read('agents/gsd-planner.md');
|
||||||
|
const lf = src.replace(/\r\n/g, '\n').length;
|
||||||
|
assert.ok(lf < 49152, `gsd-planner.md is ${lf} LF chars — must stay < 49152 (#3172 keeps the planner frozen; the rule lives in plan-phase.md's spawn contract)`);
|
||||||
|
assert.ok(!src.includes('fails_when'),
|
||||||
|
'the failing-direction rule belongs in the spawn contract, not the frozen agent file (#3172)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 31 — the extracted Nyquist detail survives the move', () => {
|
||||||
|
const checker = read('agents/gsd-plan-checker.md');
|
||||||
|
const nyquist = read('gsd-core/references/nyquist-compliance.md');
|
||||||
|
// The new check lives in the agent as a pointer; its detail and the
|
||||||
|
// extracted 8a-8e detail live in references.
|
||||||
|
assert.match(checker, /nyquist-compliance\.md/);
|
||||||
|
assert.match(checker, /failing-direction\.md/);
|
||||||
|
for (const check of ['8a', '8b', '8c', '8d', '8e']) {
|
||||||
|
assert.match(nyquist, new RegExp(`Check ${check}`), `Check ${check} detail`);
|
||||||
|
}
|
||||||
|
assert.match(read('gsd-core/references/failing-direction.md'), /blocker/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row 32 — plan-phase dispatches the failing-direction probe', () => {
|
||||||
|
const workflow = read('gsd-core/workflows/plan-phase.md');
|
||||||
|
assert.match(workflow, /check verify-failure-directions/);
|
||||||
|
assert.match(workflow, /failing_direction_probe/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('property-based invariants', () => {
|
||||||
|
test('P1 — resolveFailingDirection is total over arbitrary strings', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.string(), fc.string(), (command, statement) => {
|
||||||
|
const r = resolveFailingDirection(command, statement);
|
||||||
|
return KNOWN_STATUSES.has(r.status) && KNOWN_SEVERITIES.has(r.severity);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P2 — the pairing walk is exact at generated command counts', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.integer({ min: 1, max: 12 }), fc.boolean(), (k, stated) => {
|
||||||
|
const parts = [];
|
||||||
|
for (let i = 0; i < k; i += 1) {
|
||||||
|
parts.push({ automated: `cmd-${i}` });
|
||||||
|
if (stated) parts.push({ failsWhen: `cmd-${i} fails on a non-zero exit` });
|
||||||
|
}
|
||||||
|
const out = extractFailingDirections(taskWith(parts));
|
||||||
|
if (out.length !== k) return false;
|
||||||
|
return out.every((e) => e.status === (stated ? 'ok' : 'missing'));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P3 — CRLF invariance over generated plans', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.integer({ min: 1, max: 8 }), (k) => {
|
||||||
|
const parts = [];
|
||||||
|
for (let i = 0; i < k; i += 1) {
|
||||||
|
parts.push({ automated: `cmd-${i}` });
|
||||||
|
parts.push({ failsWhen: `cmd-${i} fails on a non-zero exit` });
|
||||||
|
}
|
||||||
|
const lf = extractFailingDirections(taskWith(parts, { eol: '\n' }));
|
||||||
|
const crlf = extractFailingDirections(taskWith(parts, { eol: '\r\n' }));
|
||||||
|
return (
|
||||||
|
lf.length === crlf.length &&
|
||||||
|
lf.every((e, i) => e.status === crlf[i].status && e.statement === crlf[i].statement)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P4 — a sentinel command never yields a blocker', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.string(), fc.option(fc.string(), { nil: null }), (suffix, statement) => {
|
||||||
|
const r = resolveFailingDirection(`MISSING ${suffix}`, statement);
|
||||||
|
return r.severity !== 'blocker';
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
3
tests/fixtures/install-tree/antigravity.json
vendored
3
tests/fixtures/install-tree/antigravity.json
vendored
@@ -96,6 +96,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -110,10 +111,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/augment.json
vendored
3
tests/fixtures/install-tree/augment.json
vendored
@@ -167,6 +167,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -181,10 +182,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
@@ -167,6 +167,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -181,10 +182,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/claude.json
vendored
3
tests/fixtures/install-tree/claude.json
vendored
@@ -96,6 +96,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -110,10 +111,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/cline.json
vendored
3
tests/fixtures/install-tree/cline.json
vendored
@@ -98,6 +98,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -112,10 +113,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/codebuddy.json
vendored
3
tests/fixtures/install-tree/codebuddy.json
vendored
@@ -167,6 +167,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -181,10 +182,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/codex.json
vendored
3
tests/fixtures/install-tree/codex.json
vendored
@@ -132,6 +132,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -146,10 +147,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/copilot.json
vendored
3
tests/fixtures/install-tree/copilot.json
vendored
@@ -97,6 +97,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -111,10 +112,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/cursor.json
vendored
3
tests/fixtures/install-tree/cursor.json
vendored
@@ -96,6 +96,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -110,10 +111,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/hermes.json
vendored
3
tests/fixtures/install-tree/hermes.json
vendored
@@ -96,6 +96,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -110,10 +111,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/kilo.json
vendored
3
tests/fixtures/install-tree/kilo.json
vendored
@@ -167,6 +167,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -181,10 +182,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/kimi-code.json
vendored
3
tests/fixtures/install-tree/kimi-code.json
vendored
@@ -97,6 +97,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -111,10 +112,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/kimi.json
vendored
3
tests/fixtures/install-tree/kimi.json
vendored
@@ -133,6 +133,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -147,10 +148,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/opencode.json
vendored
3
tests/fixtures/install-tree/opencode.json
vendored
@@ -167,6 +167,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -181,10 +182,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/pi.json
vendored
3
tests/fixtures/install-tree/pi.json
vendored
@@ -63,6 +63,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -77,10 +78,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/qwen.json
vendored
3
tests/fixtures/install-tree/qwen.json
vendored
@@ -96,6 +96,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -110,10 +111,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/trae.json
vendored
3
tests/fixtures/install-tree/trae.json
vendored
@@ -96,6 +96,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -110,10 +111,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/windsurf.json
vendored
3
tests/fixtures/install-tree/windsurf.json
vendored
@@ -96,6 +96,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -110,10 +111,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
3
tests/fixtures/install-tree/zcode.json
vendored
3
tests/fixtures/install-tree/zcode.json
vendored
@@ -167,6 +167,7 @@
|
|||||||
"gsd-core/references/execute-phase-response-language.md",
|
"gsd-core/references/execute-phase-response-language.md",
|
||||||
"gsd-core/references/execute-phase-wave-guard.md",
|
"gsd-core/references/execute-phase-wave-guard.md",
|
||||||
"gsd-core/references/executor-examples.md",
|
"gsd-core/references/executor-examples.md",
|
||||||
|
"gsd-core/references/failing-direction.md",
|
||||||
"gsd-core/references/few-shot-examples/plan-checker.md",
|
"gsd-core/references/few-shot-examples/plan-checker.md",
|
||||||
"gsd-core/references/few-shot-examples/verifier.md",
|
"gsd-core/references/few-shot-examples/verifier.md",
|
||||||
"gsd-core/references/gate-prompts.md",
|
"gsd-core/references/gate-prompts.md",
|
||||||
@@ -181,10 +182,12 @@
|
|||||||
"gsd-core/references/model-profile-resolution.md",
|
"gsd-core/references/model-profile-resolution.md",
|
||||||
"gsd-core/references/model-profiles.md",
|
"gsd-core/references/model-profiles.md",
|
||||||
"gsd-core/references/mvp-concepts.md",
|
"gsd-core/references/mvp-concepts.md",
|
||||||
|
"gsd-core/references/nyquist-compliance.md",
|
||||||
"gsd-core/references/offer-next.md",
|
"gsd-core/references/offer-next.md",
|
||||||
"gsd-core/references/phase-argument-parsing.md",
|
"gsd-core/references/phase-argument-parsing.md",
|
||||||
"gsd-core/references/planner-antipatterns.md",
|
"gsd-core/references/planner-antipatterns.md",
|
||||||
"gsd-core/references/planner-chunked.md",
|
"gsd-core/references/planner-chunked.md",
|
||||||
|
"gsd-core/references/planner-failing-direction.md",
|
||||||
"gsd-core/references/planner-gap-closure.md",
|
"gsd-core/references/planner-gap-closure.md",
|
||||||
"gsd-core/references/planner-graphify-auto-update.md",
|
"gsd-core/references/planner-graphify-auto-update.md",
|
||||||
"gsd-core/references/planner-guidance.md",
|
"gsd-core/references/planner-guidance.md",
|
||||||
|
|||||||
Reference in New Issue
Block a user