mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
feat: add gates ensuring discuss-phase decisions are translated to plans and verified (closes #2492) (#2611)
* feat(#2492): add gates ensuring discuss-phase decisions are translated and verified Two gates close the loop between CONTEXT.md `<decisions>` and downstream work, fixing #2492: - Plan-phase **translation gate** (BLOCKING). After requirements coverage, refuses to mark a phase planned when a trackable decision is not cited (by id `D-NN` or by 6+-word phrase) in any plan's `must_haves`, `truths`, or body. Failure message names each missed decision with id, category, text, and remediation paths. - Verify-phase **validation gate** (NON-BLOCKING). Searches plans, SUMMARY.md, files modified, and recent commit subjects for each trackable decision. Misses are written to VERIFICATION.md as a warning section but do not change verification status. Asymmetry is deliberate — fuzzy-match miss should not fail an otherwise green phase. Shared helper `parseDecisions()` lives in `sdk/src/query/decisions.ts` so #2493 can consume the same parser. Decisions opt out of both gates via `### Claude's Discretion` heading or `[informational]` / `[folded]` / `[deferred]` tags. Both gates skip silently when `workflow.context_coverage_gate=false` (default `true`). Closes #2492 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#2492): make plan-phase decision gate actually block (review F1, F8, F9, F10, F15) - F1: replace `${context_path}` with `${CONTEXT_PATH}` in the plan-phase gate snippet so the BLOCKING gate receives a non-empty path. The variable was defined in Step 4 (`CONTEXT_PATH=$(_gsd_field "$INIT" ...)`) and the gate snippet referenced the lowercase form, leaving the gate to run with an empty path argument and silently skip. - F15: wrap the SDK call with `jq -e '.data.passed == true' || exit 1` so failure halts the workflow instead of being printed and ignored. The verify-phase counterpart deliberately keeps no exit-1 (non-blocking by design) and now carries an inline note documenting the asymmetry. - F10: tag the JSON example fence as `json` and the options-list fence as `text` (MD040). - F8/F9: anchor the heading-presence test regexes to `^## 13[a-z]?\\.` so prose substrings like "Requirements Coverage Gate" mentioned in body text cannot satisfy the assertion. Added two new regression tests (variable-name match, exit-1 guard) so a future revert is caught. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#2492): tighten decision-coverage gates against false positives and config drift (review F3,F4,F5,F6,F7,F16,F18,F19) - F3: forward `workstream` arg through both gate handlers so workstream-scoped `workflow.context_coverage_gate=false` actually skips. Added negative test that creates a workstream config disabling the gate while the root config has it enabled and asserts the workstream call is skipped. - F4: restrict the plan-phase haystack to designated sections — front-matter `must_haves` / `truths` / `objective` plus body sections under headings matching `must_haves|truths|tasks|objective`. HTML comments and fenced code blocks are stripped before extraction so a commented-out citation or a literal example never counts as coverage. Verify-phase keeps the broader artifact-wide haystack by design (non-blocking). - F5: reject decisions with fewer than 6 normalized words from soft-matching (previously only rejected when the resulting phrase was under 12 chars AFTER slicing — too lenient). Short decisions now require an explicit `D-NN` citation, with regression tests for the boundary. - F6: walk every `*-SUMMARY.md` independently and use `matchAll` with the `/g` flag so multiple `files_modified:` blocks across multiple summaries are all aggregated. Previously only the first block in the concatenated string was parsed, silently dropping later plans' files. - F7: validate every `files_modified` path stays inside `projectDir` after resolution (rejects absolute paths, `../` traversal). Cap each file read at 256 KB. Skipped paths emit a stderr warning naming the entry. - F16: validate `workflow.context_coverage_gate` is boolean in `loadGateConfig`; warn loudly on numeric or other-shaped values and default to ON. Mirrors the schema-vs-loadConfig validation gap from #2609. - F18: bump verify-phase `git log -n` cap from 50 to 200 so longer-running phases are not undercounted. Documented as a precision-vs-recall tradeoff appropriate for a non-blocking gate. - F19: tighten `QueryResult` / `QueryHandler` to be parameterized (`<T = unknown>`). Drops the `as unknown as Record<string, unknown>` casts in the gate handlers and surfaces shape mismatches at compile time for callers that pass a typed `data` value. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#2492): harden decisions parser and verify-phase glob (review F11,F12,F13,F14,F17,F20) - F11: strip fenced code blocks from CONTEXT.md before searching for `<decisions>` so an example block inside ``` ``` is not mis-parsed. - F12: accept tab-indented continuation lines (previously required a leading space) so decisions split with `\t` continue cleanly. - F13: parse EVERY `<decisions>` block in the file via `matchAll`, not just the first. CONTEXT.md may legitimately carry more than one block. - F14: `decisions.parse` handler now resolves a relative path against `projectDir` — symmetric with the gate handlers — and still accepts absolute paths. - F17: replace `ls "${PHASE_DIR}"/*-CONTEXT.md | head -1` in verify-phase.md with a glob loop (ShellCheck SC2012 fix). Also avoids spawning an extra subprocess and survives filenames with whitespace. - F20: extend the unicode quote-stripping in the discretion-heading match to cover U+2018/2019/201A/201B and the U+201C-F double-quote variants plus backtick, so any rendering of "Claude's Discretion" collapses to the same key. Each fix has a regression test in `decisions.test.ts`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -411,7 +411,9 @@ plan-phase
|
||||
├── Research gate (blocks if RESEARCH.md has unresolved open questions)
|
||||
├── Phase Researcher → RESEARCH.md
|
||||
├── Planner (with reachability check) → PLAN.md files
|
||||
└── Plan Checker → Verify loop (max 3x)
|
||||
├── Plan Checker → Verify loop (max 3x)
|
||||
├── Requirements coverage gate (REQ-IDs → plans)
|
||||
└── Decision coverage gate (CONTEXT.md `<decisions>` → plans, BLOCKING — #2492)
|
||||
│
|
||||
▼
|
||||
state planned-phase → STATE.md (Planned/Ready to execute)
|
||||
@@ -422,6 +424,7 @@ execute-phase (context reduction: truncated prompts, cache-friendly ordering)
|
||||
├── Executor per plan → code + atomic commits
|
||||
├── SUMMARY.md per plan
|
||||
└── Verifier → VERIFICATION.md
|
||||
└── Decision coverage gate (CONTEXT.md decisions → shipped artifacts, NON-BLOCKING — #2492)
|
||||
│
|
||||
▼
|
||||
verify-work → UAT.md (user acceptance testing)
|
||||
|
||||
@@ -454,6 +454,60 @@ These keys live under `workflow.*` — that is where the workflows and installer
|
||||
|
||||
---
|
||||
|
||||
## Decision Coverage Gates (`workflow.context_coverage_gate`)
|
||||
|
||||
When `discuss-phase` writes implementation decisions into CONTEXT.md
|
||||
`<decisions>`, two gates ensure those decisions survive the trip into
|
||||
plans and shipped code (issue #2492).
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `workflow.context_coverage_gate` | boolean | `true` | Toggle for both decision-coverage gates. When `false`, both the plan-phase translation gate and the verify-phase validation gate skip silently. |
|
||||
|
||||
### What the gates do
|
||||
|
||||
**Plan-phase translation gate (BLOCKING).** Runs immediately after the
|
||||
existing requirements coverage gate, before plans are committed. For each
|
||||
trackable decision in `<decisions>`, it checks that the decision id
|
||||
(`D-NN`) or its text appears in at least one plan's `must_haves`,
|
||||
`truths`, or body. A miss surfaces the missing decision by id and refuses
|
||||
to mark the phase planned.
|
||||
|
||||
**Verify-phase validation gate (NON-BLOCKING).** Runs alongside the other
|
||||
verify steps. Searches every shipped artifact (PLAN.md, SUMMARY.md, files
|
||||
modified, recent commit subjects) for each trackable decision. Misses are
|
||||
written to VERIFICATION.md as a warning section but do **not** flip the
|
||||
overall verification status. The asymmetry is deliberate — by verify time
|
||||
the work is done, and a fuzzy substring miss should not fail an otherwise
|
||||
green phase.
|
||||
|
||||
### How to write decisions the gates accept
|
||||
|
||||
The discuss-phase template already produces `D-NN`-numbered decisions.
|
||||
The gate is happiest when:
|
||||
|
||||
1. Every plan that implements a decision **cites the id** somewhere —
|
||||
`must_haves.truths: ["D-12: bit offsets exposed"]` or a `D-12:` mention
|
||||
in the plan body. Strict id match is the cheapest, deterministic path.
|
||||
2. Soft phrase matching is a fallback for paraphrases — if a 6+-word slice
|
||||
of the decision text appears verbatim in a plan/summary, it counts.
|
||||
|
||||
### Opt-outs
|
||||
|
||||
A decision is **not** subject to the gates when any of the following
|
||||
apply:
|
||||
|
||||
- It lives under the `### Claude's Discretion` heading inside `<decisions>`.
|
||||
- It is tagged `[informational]`, `[folded]`, or `[deferred]` in its
|
||||
bullet (e.g., `- **D-08 [informational]:** Naming style for internal
|
||||
helpers`).
|
||||
|
||||
Use these escape hatches when a decision genuinely doesn't need plan
|
||||
coverage — implementation discretion, future ideas captured for the
|
||||
record, or items already deferred to a later phase.
|
||||
|
||||
---
|
||||
|
||||
## Review Settings
|
||||
|
||||
Configure per-CLI model selection for `/gsd-review`. When set, overrides the CLI's default model for that reviewer.
|
||||
|
||||
@@ -179,6 +179,47 @@ By default, `/gsd-discuss-phase` asks open-ended questions about your implementa
|
||||
|
||||
See [docs/workflow-discuss-mode.md](workflow-discuss-mode.md) for the full discuss-mode reference.
|
||||
|
||||
### Decision Coverage Gates
|
||||
|
||||
The discuss-phase captures implementation decisions in CONTEXT.md under a
|
||||
`<decisions>` block as numbered bullets (`- **D-01:** …`). Two gates — added
|
||||
for issue #2492 — ensure those decisions survive into plans and shipped
|
||||
code.
|
||||
|
||||
**Plan-phase translation gate (blocking).** After planning, GSD refuses to
|
||||
mark the phase planned until every trackable decision appears in at least
|
||||
one plan's `must_haves`, `truths`, or body. The gate names each missed
|
||||
decision by id (`D-07: …`) so you know exactly what to add, move, or
|
||||
reclassify.
|
||||
|
||||
**Verify-phase validation gate (non-blocking).** During verification, GSD
|
||||
searches plans, SUMMARY.md, modified files, and recent commit messages for
|
||||
each trackable decision. Misses are logged to VERIFICATION.md as a warning
|
||||
section; verification status is unchanged. The asymmetry is deliberate —
|
||||
the blocking gate is cheap at plan time but hostile at verify time.
|
||||
|
||||
**Writing decisions the gate can match.** Two match modes:
|
||||
|
||||
1. **Strict id match (recommended).** Cite the decision id anywhere in a
|
||||
plan that implements it — `must_haves.truths: ["D-12: bit offsets
|
||||
exposed"]`, a bullet in the plan body, a frontmatter comment. This is
|
||||
deterministic and unambiguous.
|
||||
2. **Soft phrase match (fallback).** If a 6+-word slice of the decision
|
||||
text appears verbatim in any plan or shipped artifact, it counts. This
|
||||
forgives paraphrasing but is less reliable.
|
||||
|
||||
**Opting a decision out.** If a decision genuinely should not be tracked —
|
||||
an implementation-discretion note, an informational capture, a decision
|
||||
already deferred — mark it one of these ways:
|
||||
|
||||
- Move it under the `### Claude's Discretion` heading inside `<decisions>`.
|
||||
- Tag it in its bullet: `- **D-08 [informational]:** …`,
|
||||
`- **D-09 [folded]:** …`, `- **D-10 [deferred]:** …`.
|
||||
|
||||
**Disabling the gates.** Set
|
||||
`workflow.context_coverage_gate: false` in `.planning/config.json` (or via
|
||||
`/gsd-settings`) to skip both gates silently. Default is `true`.
|
||||
|
||||
---
|
||||
|
||||
## UI Design Contract
|
||||
|
||||
Reference in New Issue
Block a user