cleargate 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MANIFEST.json +30 -16
- package/dist/cli.cjs +486 -51
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +481 -47
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +24 -0
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +24 -0
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +74 -0
- package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +162 -0
- package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +10 -7
- package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +9 -8
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +36 -13
- package/dist/templates/cleargate-planning/.claude/settings.json +9 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +55 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +7 -7
- package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +137 -40
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +93 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +8 -4
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +9 -1
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +74 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +65 -1
- package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +31 -8
- package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +93 -8
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +19 -4
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +58 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +32 -2
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +3 -1
- package/dist/templates/cleargate-planning/CLAUDE.md +1 -1
- package/dist/templates/cleargate-planning/MANIFEST.json +30 -16
- package/package.json +1 -1
- package/templates/cleargate-planning/.claude/agents/architect.md +24 -0
- package/templates/cleargate-planning/.claude/agents/developer.md +24 -0
- package/templates/cleargate-planning/.claude/agents/reporter.md +74 -0
- package/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +162 -0
- package/templates/cleargate-planning/.claude/hooks/session-start.sh +10 -7
- package/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +9 -8
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +36 -13
- package/templates/cleargate-planning/.claude/settings.json +9 -0
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +55 -0
- package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +7 -7
- package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +137 -40
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +93 -0
- package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +8 -4
- package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +9 -1
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +74 -0
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +65 -1
- package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +31 -8
- package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +93 -8
- package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +19 -4
- package/templates/cleargate-planning/.cleargate/templates/hotfix.md +58 -0
- package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +32 -2
- package/templates/cleargate-planning/.cleargate/templates/story.md +3 -1
- package/templates/cleargate-planning/CLAUDE.md +1 -1
- package/templates/cleargate-planning/MANIFEST.json +30 -16
|
@@ -115,6 +115,30 @@ After SPRINT-10 ships, `max = 20`. A story drafted before SPRINT-10 might cite `
|
|
|
115
115
|
|
|
116
116
|
**Rule:** Never let a Developer emit a protocol section number that conflicts with an existing one. Audit first, emit second.
|
|
117
117
|
|
|
118
|
+
## Lane Classification
|
|
119
|
+
|
|
120
|
+
Before emitting a `lane` recommendation per story during Sprint Design Review, run the seven-check rubric. A story is eligible for `lane: fast` **only if all seven checks pass**. Any single false flips it to `standard`.
|
|
121
|
+
|
|
122
|
+
1. **Size cap.** Implementation diff projected at ≤2 files AND ≤50 LOC net (additions + deletions). Tests count toward the cap; generated files do not.
|
|
123
|
+
2. **No forbidden surfaces.** Story does not modify any of the following file-path prefixes:
|
|
124
|
+
| Prefix | Category |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `mcp/src/db/`, `**/migrations/` | Database schema / migration |
|
|
127
|
+
| `mcp/src/auth/`, `mcp/src/admin-api/auth-*` | Auth / identity flow |
|
|
128
|
+
| `cleargate.config.json`, `mcp/src/config.ts` | Runtime config schema |
|
|
129
|
+
| `mcp/src/adapters/` | MCP adapter API surface |
|
|
130
|
+
| `cleargate-planning/MANIFEST.json` | Scaffold manifest |
|
|
131
|
+
| token handling, invite verification, gate enforcement | Security-relevant code |
|
|
132
|
+
3. **No new dependency.** Story does not add a package to any `package.json`. Removals and version pins within an existing major are allowed.
|
|
133
|
+
4. **Single acceptance scenario or doc-only.** Story Gherkin has exactly one `Scenario:` block (or zero, for pure doc/comment changes). Stories with `Scenario Outline:` or multiple scenarios are not fast-eligible.
|
|
134
|
+
5. **Existing tests cover the runtime change.** Either (a) story description names an existing test file the change exercises, or (b) story is doc-only / comment-only / non-runtime config. The pre-gate scanner verifies (a) by checking that at least one referenced test file exists and includes the affected module name as a string match.
|
|
135
|
+
6. **`expected_bounce_exposure: low`.** A story can only be fast if its decomposition signal is already `low`. `med` or `high` is auto-`standard`.
|
|
136
|
+
7. **No epic-spanning subsystem touches.** Story's affected files all live under one of the epic's declared scope directories. A story that touches files outside its parent epic's declared scope is auto-`standard`.
|
|
137
|
+
|
|
138
|
+
**Sprint Design Review tail step:** After running the rubric on each story, emit `lane: standard|fast` per story in the §1 story table. For every non-`standard` lane, emit a one-line rationale (≤80 chars). Architect MUST write a `## §2.4 Lane Audit` subsection in the Sprint Plan listing every fast-lane story with a ≤80-char rationale. Empty by default — rows added only for non-`standard` lanes.
|
|
139
|
+
|
|
140
|
+
Full rubric, demotion mechanics, and forbidden-surface table are in protocol §24 "Lane Routing". These rules apply under `execution_mode: v2`.
|
|
141
|
+
|
|
118
142
|
## Guardrails
|
|
119
143
|
- **No production code.** You write one markdown plan file. Nothing else.
|
|
120
144
|
- **No speculation.** Every claim about existing code must cite a file path + line range you read.
|
|
@@ -71,6 +71,30 @@ These rules apply under `execution_mode: v2`. Under v1 they are informational.
|
|
|
71
71
|
|
|
72
72
|
3. **Never run `git worktree add` inside `mcp/`.** The `mcp/` directory is a nested independent git repository. Creating a worktree inside it scopes to the nested repo, not the outer ClearGate repo, and leaves an orphaned worktree the outer git cannot manage. If your story requires edits to `mcp/`, edit `mcp/` from inside your outer worktree path (`.worktrees/STORY-NNN-NN/mcp/...`). See protocol §15.3 for full rationale.
|
|
73
73
|
|
|
74
|
+
## Lane-Aware Execution
|
|
75
|
+
|
|
76
|
+
These rules apply under `execution_mode: v2`. Under v1 they are informational.
|
|
77
|
+
|
|
78
|
+
**On spawn:** read `.cleargate/sprint-runs/<sprint-id>/state.json` for the current sprint (locate the active sprint via `.cleargate/sprint-runs/.active`). Look up the story's `lane` field under `state.json.stories[<story-id>].lane`. Default to `"standard"` if the field is absent, the story key is missing, or `state.json` does not exist.
|
|
79
|
+
|
|
80
|
+
**lane=fast behavior:**
|
|
81
|
+
|
|
82
|
+
- Skip writing the architect-plan-citation block. No plan exists for fast-lane stories; the orchestrator dispatched without one.
|
|
83
|
+
- The pre-gate scanner (`pre_gate_runner.sh`) is **never skipped** on lane=fast — that routing contract belongs to `pre_gate_runner.sh` (STORY-022-04). The Developer's commit MUST still pass typecheck + tests, and the single-commit rule is fully preserved.
|
|
84
|
+
- All other guardrails (no `--no-verify`, no scope bleed, no mocked DB) remain in force regardless of lane.
|
|
85
|
+
|
|
86
|
+
**lane=standard behavior (or lane absent / state.json missing):**
|
|
87
|
+
|
|
88
|
+
- Follow the existing four-agent contract verbatim: read the Architect's plan, cite it in your blueprint section, implement against it.
|
|
89
|
+
|
|
90
|
+
**Demotion handler:**
|
|
91
|
+
|
|
92
|
+
If `state.json` lane flips from `"fast"` to `"standard"` mid-sprint (`lane_demoted_at` populated by `pre_gate_runner.sh` after a fast-lane scanner failure), the orchestrator re-dispatches the story with the architect plan attached. The Developer treats the new dispatch as a fresh spawn and follows the lane=standard contract — there is no state machine or continuation logic on the Developer side.
|
|
93
|
+
|
|
94
|
+
**First-line marker contract preserved:**
|
|
95
|
+
|
|
96
|
+
The Developer's first response line still emits `STORY=NNN-NN` (or `CR=NNN`, `BUG=NNN`, `EPIC=NNN`, `PROPOSAL=NNN` / `PROP=NNN`) per BUG-010's detector contract. Lane is **NOT** part of the first-line marker.
|
|
97
|
+
|
|
74
98
|
## Circuit Breaker
|
|
75
99
|
|
|
76
100
|
These rules apply under `execution_mode: v2`. Under v1 they are informational.
|
|
@@ -97,6 +97,80 @@ If SPRINT-09 Reporter regresses post-swap of this reporter.md, rollback path:
|
|
|
97
97
|
`.cleargate/sprint-runs/S-09/fixtures/sprint-08-shaped/` was used to validate this
|
|
98
98
|
spec before atomic swap.
|
|
99
99
|
|
|
100
|
+
## Sprint Report v2.1 — Lane + Hotfix Metrics
|
|
101
|
+
|
|
102
|
+
When `state.json` has `schema_version >= 2` AND at least one story shipped with `lane: fast`,
|
|
103
|
+
the Reporter MUST populate the following additional rows and sections. When the activation
|
|
104
|
+
conditions are not met (v1 state, or all stories `lane: standard`), these rows and sections
|
|
105
|
+
may be omitted or left with placeholder values.
|
|
106
|
+
|
|
107
|
+
### §3 Execution Metrics — Six New Rows
|
|
108
|
+
|
|
109
|
+
The Reporter computes and writes these six rows in §3 (after the existing rows):
|
|
110
|
+
|
|
111
|
+
| Row label | Computation | Source |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `Fast-Track Ratio` | `count(stories where lane=fast at sprint close) / total stories × 100` | `state.json` `.stories[*].lane` |
|
|
114
|
+
| `Fast-Track Demotion Rate` | `count(stories with LD event) / count(stories where lane=fast was ever assigned) × 100` | `state.json` `.stories[*].lane_demoted_at` + sprint markdown §4 LD rows |
|
|
115
|
+
| `Hotfix Count (sprint window)` | Count of rows in `wiki/topics/hotfix-ledger.md` where `merged_at` is between sprint `started_at` and `closed_at` | `wiki/topics/hotfix-ledger.md` filtered by sprint window |
|
|
116
|
+
| `Hotfix-to-Story Ratio` | `Hotfix Count / total in-sprint stories` | Derived from above |
|
|
117
|
+
| `Hotfix Cap Breaches` | Count of rolling-7-day windows during the sprint window that had ≥ 3 hotfixes | `wiki/topics/hotfix-ledger.md` `merged_at` column |
|
|
118
|
+
| `LD events` | Count of LD event rows in sprint markdown §4 events list | Sprint plan file `## §4 Events Log` or equivalent |
|
|
119
|
+
|
|
120
|
+
**Sources detail:**
|
|
121
|
+
|
|
122
|
+
- `state.json` lane fields per `.cleargate/scripts/state.schema.json` StoryEntry: `lane`, `lane_assigned_by`, `lane_demoted_at`, `lane_demotion_reason`.
|
|
123
|
+
- Sprint markdown §4 LD events written by `pre_gate_runner.sh` `append_ld_event` (STORY-022-04). Each LD row records the story, timestamp, and demotion reason.
|
|
124
|
+
- `wiki/topics/hotfix-ledger.md` — filter rows by `merged_at` between sprint `started_at` and `closed_at`. If the ledger is absent, record `Hotfix Count = 0` and a note explaining the fallback.
|
|
125
|
+
- For historical sprints with `schema_version: 1` (no lane fields), default all lane metrics to `0` or `N/A` and note the fallback in §5 Tooling.
|
|
126
|
+
|
|
127
|
+
### §5 Process — Lane Audit table
|
|
128
|
+
|
|
129
|
+
One row per story that was ever assigned `lane: fast` during the sprint (whether it shipped fast
|
|
130
|
+
or was auto-demoted). The Reporter computes the first four columns from `git log` + `state.json`;
|
|
131
|
+
the last two columns are left blank for human fill-in at sprint close.
|
|
132
|
+
|
|
133
|
+
Template row format (per `sprint_report.md` lines 167-172):
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
| Story | Files touched | LOC | Demoted? | In retrospect, was fast correct? (y/n) | Notes |
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- **Story**: story ID (e.g. `STORY-022-08`).
|
|
140
|
+
- **Files touched**: count via `git diff --name-only <base>..<story-sha>`.
|
|
141
|
+
- **LOC**: `git diff --stat <base>..<story-sha>` insertions+deletions total.
|
|
142
|
+
- **Demoted?**: `y` if `lane_demoted_at` is non-null in `state.json`; `n` otherwise.
|
|
143
|
+
- **In retrospect, was fast correct?**: blank — human fills at close.
|
|
144
|
+
- **Notes**: blank — human fills at close.
|
|
145
|
+
|
|
146
|
+
### §5 Process — Hotfix Audit table
|
|
147
|
+
|
|
148
|
+
One row per hotfix merged within the sprint window. Read from `wiki/topics/hotfix-ledger.md`
|
|
149
|
+
filtered by `merged_at` between sprint `started_at` and `closed_at`. Last two columns blank.
|
|
150
|
+
|
|
151
|
+
Template row format (per `sprint_report.md` lines 174-179):
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
| Hotfix ID | Originating signal | Files touched | LOC | Resolved-by SHA | Could this have been a sprint story? (y/n) | If y — why was it missed at planning? |
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
If zero hotfixes in window, write a single row: `| (none) | — | — | — | — | — | — |`
|
|
158
|
+
|
|
159
|
+
### §5 Process — Hotfix Trend narrative
|
|
160
|
+
|
|
161
|
+
A one-paragraph narrative summarising the rolling 4-sprint hotfix count and a
|
|
162
|
+
monotonic-increase flag. The Reporter reads the last 4 sprint `REPORT.md` files
|
|
163
|
+
(at `.cleargate/sprint-runs/<id>/REPORT.md`) OR walks `wiki/topics/hotfix-ledger.md`
|
|
164
|
+
by `sprint_id` field to gather per-sprint counts.
|
|
165
|
+
|
|
166
|
+
Monotonic-increase flag: if the count increased (or stayed ≥ 1) for 3+ consecutive sprints,
|
|
167
|
+
flag it as `trend: INCREASING` and recommend a retrospective action in §5 Tooling.
|
|
168
|
+
|
|
169
|
+
For historical v1-schema sprints with no lane data, record `0 hotfixes (v1 — no ledger data)`.
|
|
170
|
+
|
|
171
|
+
Template location: `sprint_report.md` lines 181-188. Leave the placeholder text intact for
|
|
172
|
+
sprints with no hotfixes in the window.
|
|
173
|
+
|
|
100
174
|
## Guardrails
|
|
101
175
|
- **Numbers before narrative.** Every claim in §1 must be backed by a ledger row, commit, or flashcard -- cite them.
|
|
102
176
|
- **Do not fabricate cost.** If you cannot find current model rates, state the rate date and mark cost `~$X (rates as of <date>)`.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pre-edit-gate.sh — CR-008 Phase B: planning-first PreToolUse gate
|
|
3
|
+
#
|
|
4
|
+
# Registered as a PreToolUse hook for Edit|Write tool calls.
|
|
5
|
+
# In warn mode (default), logs would-block decisions but always exits 0.
|
|
6
|
+
# In enforce mode, exits 1 to block the tool call when planning is missing.
|
|
7
|
+
#
|
|
8
|
+
# Mode controlled by CLEARGATE_PLANNING_GATE_MODE (warn|enforce|off). Default: warn.
|
|
9
|
+
# Bypass: CLEARGATE_PLANNING_BYPASS=1 → skip check, log bypass=true, exit 0.
|
|
10
|
+
|
|
11
|
+
set -u
|
|
12
|
+
|
|
13
|
+
REPO_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
14
|
+
MODE="${CLEARGATE_PLANNING_GATE_MODE:-warn}"
|
|
15
|
+
LOG_FILE="${REPO_ROOT}/.cleargate/hook-log/pre-edit-gate-warn.log"
|
|
16
|
+
|
|
17
|
+
# ── 0. Off mode — do nothing ──────────────────────────────────────────────────
|
|
18
|
+
if [ "${MODE}" = "off" ]; then
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# ── 1. Read file path from stdin (PreToolUse JSON payload) ────────────────────
|
|
23
|
+
# Claude Code sends JSON on stdin: {"tool_name":"Edit","tool_input":{"file_path":"..."}}
|
|
24
|
+
INPUT=$(cat)
|
|
25
|
+
FILE=$(printf '%s' "${INPUT}" | node -e "
|
|
26
|
+
try {
|
|
27
|
+
var d = require('fs').readFileSync('/dev/stdin','utf8');
|
|
28
|
+
var o = JSON.parse(d);
|
|
29
|
+
var fp = (o.tool_input && o.tool_input.file_path) ? o.tool_input.file_path : '';
|
|
30
|
+
process.stdout.write(fp);
|
|
31
|
+
} catch(e) {
|
|
32
|
+
process.stdout.write('');
|
|
33
|
+
}
|
|
34
|
+
" 2>/dev/null || true)
|
|
35
|
+
|
|
36
|
+
TOOL_NAME=$(printf '%s' "${INPUT}" | node -e "
|
|
37
|
+
try {
|
|
38
|
+
var d = require('fs').readFileSync('/dev/stdin','utf8');
|
|
39
|
+
var o = JSON.parse(d);
|
|
40
|
+
process.stdout.write(o.tool_name || '');
|
|
41
|
+
} catch(e) {
|
|
42
|
+
process.stdout.write('');
|
|
43
|
+
}
|
|
44
|
+
" 2>/dev/null || true)
|
|
45
|
+
|
|
46
|
+
# ── Guard: if we couldn't parse the file path, fail-open ──────────────────────
|
|
47
|
+
if [ -z "${FILE}" ]; then
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# ── 2. Bypass env var ─────────────────────────────────────────────────────────
|
|
52
|
+
if [ "${CLEARGATE_PLANNING_BYPASS:-0}" = "1" ]; then
|
|
53
|
+
ISO_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
54
|
+
mkdir -p "$(dirname "${LOG_FILE}")"
|
|
55
|
+
printf '[%s] mode=%s bypass=true file=%s\n' "${ISO_TS}" "${MODE}" "${FILE}" >> "${LOG_FILE}"
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# ── 3. Whitelist: always allow these paths ────────────────────────────────────
|
|
60
|
+
# Normalise: strip leading REPO_ROOT prefix for matching
|
|
61
|
+
REL_FILE="${FILE}"
|
|
62
|
+
if [[ "${FILE}" == "${REPO_ROOT}/"* ]]; then
|
|
63
|
+
REL_FILE="${FILE#${REPO_ROOT}/}"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
is_whitelisted() {
|
|
67
|
+
local f="$1"
|
|
68
|
+
# Exact or prefix matches for whitelisted directories/files
|
|
69
|
+
case "${f}" in
|
|
70
|
+
.cleargate/*|.claude/*|cleargate-planning/*) return 0 ;;
|
|
71
|
+
CLAUDE.md|MANIFEST.json|README.md|.gitignore|.gitkeep) return 0 ;;
|
|
72
|
+
package.json|package-lock.json) return 0 ;;
|
|
73
|
+
.env|.env.*|.npmrc|.editorconfig) return 0 ;;
|
|
74
|
+
# Also allow absolute paths that fall under the repo's cleargate dirs
|
|
75
|
+
esac
|
|
76
|
+
# Check absolute path variants
|
|
77
|
+
case "${FILE}" in
|
|
78
|
+
"${REPO_ROOT}/.cleargate/"*|"${REPO_ROOT}/.claude/"*|"${REPO_ROOT}/cleargate-planning/"*) return 0 ;;
|
|
79
|
+
esac
|
|
80
|
+
return 1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if is_whitelisted "${REL_FILE}"; then
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# ── 3.5 Sprint-active sentinel bypass ────────────────────────────────────────
|
|
88
|
+
# If a sprint is actively running, all in-sprint edits bypass the gate.
|
|
89
|
+
if [ -f "${REPO_ROOT}/.cleargate/sprint-runs/.active" ]; then
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ── 4. Resolve cleargate CLI (three-branch resolver — CR-009) ────────────────
|
|
94
|
+
if [ -f "${REPO_ROOT}/cleargate-cli/dist/cli.js" ]; then
|
|
95
|
+
CG=(node "${REPO_ROOT}/cleargate-cli/dist/cli.js")
|
|
96
|
+
elif command -v cleargate >/dev/null 2>&1; then
|
|
97
|
+
CG=(cleargate)
|
|
98
|
+
else
|
|
99
|
+
# Read pinned version from stamp-and-gate.sh
|
|
100
|
+
HOOK_PIN=""
|
|
101
|
+
HOOK_SH="${REPO_ROOT}/.claude/hooks/stamp-and-gate.sh"
|
|
102
|
+
if [ -f "${HOOK_SH}" ]; then
|
|
103
|
+
HOOK_PIN=$(grep -oP '(?<=# cleargate-pin: )[\S]+' "${HOOK_SH}" 2>/dev/null || \
|
|
104
|
+
grep -oE 'cleargate@[^"]+' "${HOOK_SH}" 2>/dev/null | head -1 | sed 's/.*@//' || true)
|
|
105
|
+
fi
|
|
106
|
+
if [ -z "${HOOK_PIN}" ]; then
|
|
107
|
+
HOOK_PIN="__CLEARGATE_VERSION__"
|
|
108
|
+
fi
|
|
109
|
+
CG=(npx -y "cleargate@${HOOK_PIN}")
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# ── 5. Read user prompt snippet for log context (optional; best-effort) ───────
|
|
113
|
+
PROMPT_SNIPPET=$(printf '%s' "${INPUT}" | node -e "
|
|
114
|
+
try {
|
|
115
|
+
var d = require('fs').readFileSync('/dev/stdin','utf8');
|
|
116
|
+
var o = JSON.parse(d);
|
|
117
|
+
var p = (o.user_prompt || '').slice(0, 200);
|
|
118
|
+
process.stdout.write(p);
|
|
119
|
+
} catch(e) {
|
|
120
|
+
process.stdout.write('');
|
|
121
|
+
}
|
|
122
|
+
" 2>/dev/null || true)
|
|
123
|
+
|
|
124
|
+
# ── 6. Ask doctor --can-edit ──────────────────────────────────────────────────
|
|
125
|
+
# Capture both stdout AND exit code without || true swallowing the exit code.
|
|
126
|
+
_DOCTOR_TMPFILE=$(mktemp)
|
|
127
|
+
"${CG[@]}" doctor --can-edit "${FILE}" --cwd "${REPO_ROOT}" > "${_DOCTOR_TMPFILE}" 2>/dev/null
|
|
128
|
+
DOCTOR_EXIT=$?
|
|
129
|
+
DOCTOR_OUT=$(cat "${_DOCTOR_TMPFILE}")
|
|
130
|
+
rm -f "${_DOCTOR_TMPFILE}"
|
|
131
|
+
|
|
132
|
+
# Parse reason from doctor output
|
|
133
|
+
REASON=$(printf '%s' "${DOCTOR_OUT}" | grep -oP '(?<=blocked: )\S+' 2>/dev/null || \
|
|
134
|
+
printf '%s' "${DOCTOR_OUT}" | sed -n 's/blocked: //p' 2>/dev/null || \
|
|
135
|
+
echo "unknown")
|
|
136
|
+
|
|
137
|
+
# Count approved stories in pending-sync (for log entry)
|
|
138
|
+
PENDING_DIR="${REPO_ROOT}/.cleargate/delivery/pending-sync"
|
|
139
|
+
PENDING_MATCH_COUNT=0
|
|
140
|
+
if [ -d "${PENDING_DIR}" ]; then
|
|
141
|
+
PENDING_MATCH_COUNT=$(grep -rl 'approved: true' "${PENDING_DIR}" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# ── 7. Gate decision ──────────────────────────────────────────────────────────
|
|
145
|
+
if [ "${DOCTOR_EXIT}" -ne 0 ]; then
|
|
146
|
+
# Would block
|
|
147
|
+
ISO_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
148
|
+
mkdir -p "$(dirname "${LOG_FILE}")"
|
|
149
|
+
printf '[%s] mode=%s would_block file=%s reason=%s tool=%s pending_match_count=%d prompt_snippet=%s\n' \
|
|
150
|
+
"${ISO_TS}" "${MODE}" "${FILE}" "${REASON}" "${TOOL_NAME}" "${PENDING_MATCH_COUNT}" "${PROMPT_SNIPPET}" \
|
|
151
|
+
>> "${LOG_FILE}"
|
|
152
|
+
|
|
153
|
+
if [ "${MODE}" = "enforce" ]; then
|
|
154
|
+
printf 'ClearGate: planning-first gate — no approved story covers %s (%s). Draft a work item first.\n' \
|
|
155
|
+
"${FILE}" "${REASON}" >&2
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# warn mode: log written above, exit 0 (do not block)
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
exit 0
|
|
@@ -2,17 +2,20 @@
|
|
|
2
2
|
set -u
|
|
3
3
|
REPO_ROOT="${CLAUDE_PROJECT_DIR}"
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
# cleargate-pin: __CLEARGATE_VERSION__
|
|
6
|
+
# Resolve cleargate CLI (three-branch resolver — CR-009):
|
|
7
|
+
# 1. meta-repo dogfood dist (fastest; only present in ClearGate's own repo)
|
|
8
|
+
# 2. on-PATH binary (global install or shim)
|
|
9
|
+
# 3. pinned npx invocation (always works wherever Node is present)
|
|
10
|
+
if [ -f "${REPO_ROOT}/cleargate-cli/dist/cli.js" ]; then
|
|
10
11
|
CG=(node "${REPO_ROOT}/cleargate-cli/dist/cli.js")
|
|
12
|
+
elif command -v cleargate >/dev/null 2>&1; then
|
|
13
|
+
CG=(cleargate)
|
|
11
14
|
else
|
|
12
|
-
|
|
15
|
+
CG=(npx -y "cleargate@__CLEARGATE_VERSION__")
|
|
13
16
|
fi
|
|
14
17
|
|
|
15
|
-
"${CG[@]}" doctor --session-start
|
|
18
|
+
"${CG[@]}" doctor --session-start || true
|
|
16
19
|
|
|
17
20
|
# ── §14.9 SessionStart sync nudge (STORY-010-08) ─────────────────────────────
|
|
18
21
|
# Daily-throttled: probe remote for updates at most once per 24h.
|
|
@@ -4,16 +4,17 @@ REPO_ROOT="${CLAUDE_PROJECT_DIR}"
|
|
|
4
4
|
LOG="${REPO_ROOT}/.cleargate/hook-log/gate-check.log"
|
|
5
5
|
mkdir -p "$(dirname "$LOG")"
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
# cleargate-pin: __CLEARGATE_VERSION__
|
|
8
|
+
# Resolve cleargate CLI (three-branch resolver — CR-009):
|
|
9
|
+
# 1. meta-repo dogfood dist (fastest; only present in ClearGate's own repo)
|
|
10
|
+
# 2. on-PATH binary (global install or shim)
|
|
11
|
+
# 3. pinned npx invocation (always works wherever Node is present)
|
|
12
|
+
if [ -f "${REPO_ROOT}/cleargate-cli/dist/cli.js" ]; then
|
|
13
13
|
CG=(node "${REPO_ROOT}/cleargate-cli/dist/cli.js")
|
|
14
|
+
elif command -v cleargate >/dev/null 2>&1; then
|
|
15
|
+
CG=(cleargate)
|
|
14
16
|
else
|
|
15
|
-
|
|
16
|
-
exit 0
|
|
17
|
+
CG=(npx -y "cleargate@__CLEARGATE_VERSION__")
|
|
17
18
|
fi
|
|
18
19
|
|
|
19
20
|
FILE=$(jq -r '.tool_input.file_path' 2>/dev/null || echo "")
|
|
@@ -15,12 +15,21 @@
|
|
|
15
15
|
# misrouted SPRINT-04 firings to SPRINT-03 because ledger appends
|
|
16
16
|
# themselves bumped SPRINT-03's mtime. See REPORT.md SPRINT-04.
|
|
17
17
|
#
|
|
18
|
-
# Work-item ID detection (GENERALIZED 2026-04-19 STORY-008-04):
|
|
18
|
+
# Work-item ID detection (GENERALIZED 2026-04-19 STORY-008-04, SCOPED 2026-04-26 BUG-010):
|
|
19
19
|
# Primary : first user message in the transcript — that's the orchestrator's
|
|
20
20
|
# dispatch prompt, which by agent convention starts with
|
|
21
21
|
# `STORY=NNN-NN`, `PROPOSAL-NNN`, `EPIC-NNN`, `CR-NNN`, or `BUG-NNN`.
|
|
22
|
-
#
|
|
23
|
-
#
|
|
22
|
+
# Scoping : look ONLY at lines whose FIRST CHARACTER matches the dispatch-marker
|
|
23
|
+
# pattern (line-anchored). The SessionStart hook emits a "blocked items"
|
|
24
|
+
# reminder that lists work-item IDs in prose/bullets — those lines start
|
|
25
|
+
# with "- " or whitespace, never with the marker prefix. Scanning the full
|
|
26
|
+
# content caused BUG-010: all SPRINT-14 rows tagged BUG-002 (first item in
|
|
27
|
+
# the reminder list). Fix: join content with \n, split, filter line-starts.
|
|
28
|
+
# Pattern : ^(STORY|PROPOSAL|PROP|EPIC|CR|BUG)[-=][0-9]...
|
|
29
|
+
# PROP-NNN is normalised to PROPOSAL-NNN after match (BUG-009, 2026-04-26)
|
|
30
|
+
# Tie-break: when multiple dispatch-marker lines appear in the first user message,
|
|
31
|
+
# the FIRST line wins (deterministic; orchestrator puts the marker first).
|
|
32
|
+
# Fallback : grep first line-start match anywhere in the transcript.
|
|
24
33
|
# story_id : populated only when the match is a STORY-* (backward compat).
|
|
25
34
|
# work_item_id: always populated when detection succeeds; equals story_id for STORY items.
|
|
26
35
|
# (Removed): grep-first-anywhere as PRIMARY — it picked up SPRINT-05 mentions
|
|
@@ -107,28 +116,42 @@ ACTIVE_SENTINEL="${REPO_ROOT}/.cleargate/sprint-runs/.active"
|
|
|
107
116
|
fi
|
|
108
117
|
|
|
109
118
|
# --- detect work_item_id (PRIMARY: first user message; FALLBACK: anywhere-grep) ---
|
|
110
|
-
# Orchestrator convention (.claude/agents/developer.md): the
|
|
111
|
-
# dispatch prompt is `STORY=NNN-NN
|
|
112
|
-
#
|
|
113
|
-
#
|
|
119
|
+
# Orchestrator convention (.claude/agents/developer.md): the FIRST LINE of the
|
|
120
|
+
# dispatch prompt is `STORY=NNN-NN` (or CR=NNN, BUG=NNN, EPIC=NNN, PROPOSAL=NNN,
|
|
121
|
+
# PROP=NNN). The SessionStart hook injects a "blocked items" reminder whose lines
|
|
122
|
+
# start with "- " or whitespace — never with the marker prefix. We therefore
|
|
123
|
+
# ONLY match lines whose first character is the start of the marker (line-anchored).
|
|
124
|
+
#
|
|
125
|
+
# BUG-010 (2026-04-26): joined with " " (space) before — scan() found BUG-002 from
|
|
126
|
+
# the reminder text. Fix: join with "\n" so line-anchored regex works.
|
|
114
127
|
#
|
|
115
128
|
# Pattern covers: STORY-NNN-NN, PROPOSAL-NNN, EPIC-NNN, CR-NNN, BUG-NNN
|
|
116
129
|
# (and = separator variants like STORY=008-04)
|
|
130
|
+
# Tie-break: first line matching the anchor wins (orchestrator puts marker first).
|
|
117
131
|
WORK_ITEM_RAW="$(jq -rs '
|
|
118
132
|
[.[] | select(.type == "user")] | .[0].message.content
|
|
119
|
-
| if type == "array" then map(.text? // "") | join("
|
|
133
|
+
| if type == "array" then map(.text? // "") | join("\n") else (. // "") end
|
|
120
134
|
| tostring
|
|
121
|
-
|
|
|
135
|
+
| split("\n")
|
|
136
|
+
| map(select(test("^(STORY|PROPOSAL|PROP|EPIC|CR|BUG)[-=][0-9]")))
|
|
137
|
+
| .[0] // ""
|
|
138
|
+
| capture("^(?<kind>STORY|PROPOSAL|PROP|EPIC|CR|BUG)[-=](?<id>[0-9]+(-[0-9]+)?)")
|
|
139
|
+
| (.kind + "-" + .id)
|
|
122
140
|
' "${TRANSCRIPT_PATH}" 2>/dev/null | head -1)"
|
|
123
141
|
|
|
124
142
|
if [[ -n "${WORK_ITEM_RAW}" && "${WORK_ITEM_RAW}" != "null" && "${WORK_ITEM_RAW}" != "-" ]]; then
|
|
125
143
|
# Normalize: replace any = separator with - in the result
|
|
126
|
-
|
|
144
|
+
# Then normalize PROP-NNN → PROPOSAL-NNN (canonical form matches wiki/proposals/PROPOSAL-NNN.md filenames)
|
|
145
|
+
# BUG-009 (2026-04-26): PROP↔PROPOSAL normalization — PRESERVED BY BUG-010.
|
|
146
|
+
WORK_ITEM_ID="$(printf '%s' "${WORK_ITEM_RAW}" | sed 's/=/-/g' | sed 's/^PROP-/PROPOSAL-/')"
|
|
127
147
|
else
|
|
128
|
-
# Fallback: grep anywhere in the transcript
|
|
129
|
-
|
|
148
|
+
# Fallback: grep for line-start dispatch markers anywhere in the transcript.
|
|
149
|
+
# Line-anchored (^) so that "- BUG-002:" reminder bullet lines are not matched —
|
|
150
|
+
# only lines whose first character starts the marker prefix.
|
|
151
|
+
# BUG-009 normalization preserved: sed 's/=/-/g' | sed 's/^PROP-/PROPOSAL-/'
|
|
152
|
+
WORK_ITEM_ID="$(grep -oE '^(STORY|PROPOSAL|PROP|EPIC|CR|BUG)[-=][0-9][0-9]*(-[0-9]+)?' "${TRANSCRIPT_PATH}" 2>/dev/null \
|
|
130
153
|
| head -1 \
|
|
131
|
-
| sed 's/=/-/g')"
|
|
154
|
+
| sed 's/=/-/g' | sed 's/^PROP-/PROPOSAL-/')"
|
|
132
155
|
if [[ -n "${WORK_ITEM_ID}" ]]; then
|
|
133
156
|
printf '[%s] work_item_id fallback grep: %s\n' "$(date -u +%FT%TZ)" "${WORK_ITEM_ID}" >> "${HOOK_LOG}"
|
|
134
157
|
fi
|
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/pending-task-sentinel.sh"
|
|
20
20
|
}
|
|
21
21
|
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"matcher": "Edit|Write",
|
|
25
|
+
"hooks": [
|
|
26
|
+
{
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/pre-edit-gate.sh"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
22
31
|
}
|
|
23
32
|
],
|
|
24
33
|
"PostToolUse": [
|
|
@@ -108,6 +108,8 @@ Under `execution_mode: "v1"` this rule is **advisory only** — the orchestrator
|
|
|
108
108
|
|
|
109
109
|
**v2 story-file assertion:** Additionally for v2 sprints, `cleargate sprint init` asserts every story in §1 Consolidated Deliverables has a `pending-sync/STORY-*.md` file before writing `state.json`; missing files block init with an enumerated stderr list. Under v1 the assertion runs but only warns (does not block). The assertion is also available standalone: `node .cleargate/scripts/assert_story_files.mjs <sprint-file-path>`.
|
|
110
110
|
|
|
111
|
+
As of cleargate@0.6.x, sprint-init asserts all six work-item id shapes (STORY/CR/BUG/EPIC/PROPOSAL/HOTFIX). v2 mode hard-blocks on missing OR unapproved OR stub-empty items; v1 warns-only (backwards compat).
|
|
112
|
+
|
|
111
113
|
### Gate 3 — Push Gate
|
|
112
114
|
|
|
113
115
|
- **Never call `cleargate_push_item` on a file where `approved: false`.**
|
|
@@ -913,3 +915,56 @@ wiki:
|
|
|
913
915
|
```
|
|
914
916
|
|
|
915
917
|
Exceeding the ceiling fails `cleargate wiki lint` (enforcement mode). Under `--suggest`, the usage percentage is reported but the check does not fail. Reference: EPIC-015.
|
|
918
|
+
|
|
919
|
+
---
|
|
920
|
+
|
|
921
|
+
## 22. Advisory Readiness Gates on Push (v2) — CR-010
|
|
922
|
+
|
|
923
|
+
### §22.1 Two-tier push gate semantics
|
|
924
|
+
|
|
925
|
+
Push-time gate enforcement uses two distinct tiers:
|
|
926
|
+
|
|
927
|
+
**Tier 1 — `approved: true` (hard reject, unchanged):**
|
|
928
|
+
`cleargate_push_item` throws `PushNotApprovedError` when `payload.approved !== true`. This is the human go/no-go gate. No advisory mode or env knob overrides it.
|
|
929
|
+
|
|
930
|
+
**Tier 2 — `cached_gate_result` (advisory by default):**
|
|
931
|
+
When `cached_gate_result.pass === false`, the push proceeds in default advisory mode. The pushed item's body receives a single advisory prefix line placed immediately after the H1 heading (or as the first line if no H1 exists):
|
|
932
|
+
|
|
933
|
+
```
|
|
934
|
+
[advisory: gate_failed — <comma-separated criterion ids>]
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
Body content beyond the advisory prefix is byte-identical to the input. The push result includes `gate_status: 'open'` and `failing_criteria: [...]` as response metadata (not persisted to the DB schema).
|
|
938
|
+
|
|
939
|
+
### §22.2 Strict-mode opt-in and audit log
|
|
940
|
+
|
|
941
|
+
Set `STRICT_PUSH_GATES=true` on the MCP server to restore pre-CR-010 hard-reject behavior (`PushGateFailedError`, no DB write). Default: `false` (advisory mode).
|
|
942
|
+
|
|
943
|
+
Advisory pushes (gate_status='open') are recorded in `audit_log` with `result='ok'` — the push succeeded. The `failing_criteria` are surfaced in the push response shape, not in a new audit column. No schema migration is required.
|
|
944
|
+
**Rationale:** PM-tool answer-collection requires items to land before readiness answers arrive; advisory mode enables this. See CR-010 §0 for full evidence.
|
|
945
|
+
|
|
946
|
+
---
|
|
947
|
+
|
|
948
|
+
## 23. Doctor Exit-Code Semantics
|
|
949
|
+
|
|
950
|
+
`cleargate doctor` exits with one of three codes (all modes: default, `--session-start`, `--can-edit`, `--check-scaffold`, `--pricing`). Hooks branch on the integer, not on stdout.
|
|
951
|
+
- `0` — clean. No blockers, no config errors. Stdout MAY include informational lines.
|
|
952
|
+
- `1` — blocked items or advisory issues (gate failures, stamp errors, drifted SHAs, missing ledger rows). Stdout lists each blocker.
|
|
953
|
+
- `2` — ClearGate misconfigured or partially installed (missing `.cleargate/`, missing `MANIFEST.json`, missing `auth.json`, hook resolver failure). Stdout emits a remediation hint. See `cleargate doctor --help`.
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## 24. Lane Routing
|
|
958
|
+
|
|
959
|
+
A story is eligible for `lane: fast` only if all seven checks pass (any false → `standard`):
|
|
960
|
+
1. **Size cap.** ≤2 files AND ≤50 LOC net (tests count; generated files do not).
|
|
961
|
+
2. **No forbidden surfaces.** Story does not modify: `mcp/src/db/` / `**/migrations/` (schema); `mcp/src/auth/` / `mcp/src/admin-api/auth-*` (auth); `cleargate.config.json` / `mcp/src/config.ts` (runtime config); `mcp/src/adapters/` (adapter API); `cleargate-planning/MANIFEST.json` (scaffold manifest); security-relevant code (token handling, invite verification, gate enforcement).
|
|
962
|
+
3. **No new dependency.** No new package added to any `package.json`.
|
|
963
|
+
4. **Single acceptance scenario or doc-only.** Exactly one `Scenario:` block (or zero for doc-only). `Scenario Outline:` or multiple scenarios → `standard`.
|
|
964
|
+
5. **Existing tests cover the runtime change.** Named test file exists and includes the affected module, OR story is doc/comment/non-runtime config only.
|
|
965
|
+
6. **`expected_bounce_exposure: low`.** `med` or `high` is auto-`standard`.
|
|
966
|
+
7. **No epic-spanning subsystem touches.** All affected files live under the parent epic's declared scope directories.
|
|
967
|
+
|
|
968
|
+
**Demotion mechanics.** Demotion is one-way (`fast → standard`). Trigger: pre-gate scanner failure OR post-merge test failure on a fast-lane story. On demotion: set `lane = "standard"`, write `lane_demoted_at` (ISO-8601), `lane_demotion_reason`, reset `qa_bounces = 0` and `arch_bounces = 0` (see STORY-022-02 schema). Architect plan is invoked and QA spawned per standard contract.
|
|
969
|
+
|
|
970
|
+
Event-type `LD` (Lane Demotion) is recorded in sprint markdown §4 alongside existing `UR` and `CR` events; Reporter aggregates into §3 Execution Metrics > Fast-Track Demotion Rate.
|
|
@@ -52,7 +52,7 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
52
52
|
- id: touched-files-populated
|
|
53
53
|
check: "section(3) has ≥1 listed-item"
|
|
54
54
|
- id: no-tbds
|
|
55
|
-
check: "body does not contain 'TBD'"
|
|
55
|
+
check: "body does not contain marker 'TBD'"
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
```yaml
|
|
@@ -63,7 +63,7 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
63
63
|
- id: proposal-approved
|
|
64
64
|
check: "frontmatter(context_source).approved == true"
|
|
65
65
|
- id: no-tbds
|
|
66
|
-
check: "body does not contain 'TBD'"
|
|
66
|
+
check: "body does not contain marker 'TBD'"
|
|
67
67
|
- id: scope-in-populated
|
|
68
68
|
check: "section(2) has ≥1 listed-item"
|
|
69
69
|
- id: affected-files-declared
|
|
@@ -84,7 +84,7 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
84
84
|
- id: gherkin-error-path
|
|
85
85
|
check: "body contains 'Error'"
|
|
86
86
|
- id: no-tbds
|
|
87
|
-
check: "body does not contain 'TBD'"
|
|
87
|
+
check: "body does not contain marker 'TBD'"
|
|
88
88
|
- id: interrogation-resolved
|
|
89
89
|
check: "body does not contain 'Unresolved'"
|
|
90
90
|
```
|
|
@@ -97,7 +97,7 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
97
97
|
- id: parent-epic-ref-set
|
|
98
98
|
check: "frontmatter(.).parent_epic_ref != null"
|
|
99
99
|
- id: no-tbds
|
|
100
|
-
check: "body does not contain 'TBD'"
|
|
100
|
+
check: "body does not contain marker 'TBD'"
|
|
101
101
|
- id: implementation-files-declared
|
|
102
102
|
check: "section(3) has ≥1 listed-item"
|
|
103
103
|
- id: dod-declared
|
|
@@ -112,9 +112,9 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
112
112
|
severity: enforcing
|
|
113
113
|
criteria:
|
|
114
114
|
- id: blast-radius-populated
|
|
115
|
-
check: "section(
|
|
115
|
+
check: "section(2) has ≥1 listed-item"
|
|
116
116
|
- id: no-tbds
|
|
117
|
-
check: "body does not contain 'TBD'"
|
|
117
|
+
check: "body does not contain marker 'TBD'"
|
|
118
118
|
- id: sandbox-paths-declared
|
|
119
119
|
check: "section(2) has ≥1 listed-item"
|
|
120
120
|
```
|
|
@@ -129,5 +129,5 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
129
129
|
- id: severity-set
|
|
130
130
|
check: "frontmatter(.).severity != null"
|
|
131
131
|
- id: no-tbds
|
|
132
|
-
check: "body does not contain 'TBD'"
|
|
132
|
+
check: "body does not contain marker 'TBD'"
|
|
133
133
|
```
|