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.
Files changed (54) hide show
  1. package/dist/MANIFEST.json +30 -16
  2. package/dist/cli.cjs +486 -51
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.js +481 -47
  5. package/dist/cli.js.map +1 -1
  6. package/dist/templates/cleargate-planning/.claude/agents/architect.md +24 -0
  7. package/dist/templates/cleargate-planning/.claude/agents/developer.md +24 -0
  8. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +74 -0
  9. package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +162 -0
  10. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +10 -7
  11. package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +9 -8
  12. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +36 -13
  13. package/dist/templates/cleargate-planning/.claude/settings.json +9 -0
  14. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +55 -0
  15. package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +7 -7
  16. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +137 -40
  17. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +93 -0
  18. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +8 -4
  19. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +9 -1
  20. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +74 -0
  21. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +65 -1
  22. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +31 -8
  23. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +93 -8
  24. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +19 -4
  25. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +58 -0
  26. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +32 -2
  27. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +3 -1
  28. package/dist/templates/cleargate-planning/CLAUDE.md +1 -1
  29. package/dist/templates/cleargate-planning/MANIFEST.json +30 -16
  30. package/package.json +1 -1
  31. package/templates/cleargate-planning/.claude/agents/architect.md +24 -0
  32. package/templates/cleargate-planning/.claude/agents/developer.md +24 -0
  33. package/templates/cleargate-planning/.claude/agents/reporter.md +74 -0
  34. package/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +162 -0
  35. package/templates/cleargate-planning/.claude/hooks/session-start.sh +10 -7
  36. package/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +9 -8
  37. package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +36 -13
  38. package/templates/cleargate-planning/.claude/settings.json +9 -0
  39. package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +55 -0
  40. package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +7 -7
  41. package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +137 -40
  42. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +93 -0
  43. package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +8 -4
  44. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +9 -1
  45. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +74 -0
  46. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +65 -1
  47. package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +31 -8
  48. package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +93 -8
  49. package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +19 -4
  50. package/templates/cleargate-planning/.cleargate/templates/hotfix.md +58 -0
  51. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +32 -2
  52. package/templates/cleargate-planning/.cleargate/templates/story.md +3 -1
  53. package/templates/cleargate-planning/CLAUDE.md +1 -1
  54. package/templates/cleargate-planning/MANIFEST.json +30 -16
@@ -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
- # Resolve cleargate CLI: prefer on-PATH binary, fall back to meta-repo-local
6
- # dist. If neither is present, this hook is a no-op (BUG-006).
7
- if command -v cleargate >/dev/null 2>&1; then
8
- CG=(cleargate)
9
- elif [ -f "${REPO_ROOT}/cleargate-cli/dist/cli.js" ]; then
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
- exit 0
15
+ CG=(npx -y "cleargate@__CLEARGATE_VERSION__")
13
16
  fi
14
17
 
15
- "${CG[@]}" doctor --session-start 2>/dev/null || true
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
- # Resolve cleargate CLI: prefer on-PATH binary (`npm i -g cleargate` / `npx`),
8
- # fall back to a meta-repo-local dist (dogfood case). If neither is present,
9
- # log a remediation message and exit 0 (BUG-006).
10
- if command -v cleargate >/dev/null 2>&1; then
11
- CG=(cleargate)
12
- elif [ -f "${REPO_ROOT}/cleargate-cli/dist/cli.js" ]; then
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
- echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] cleargate CLI not found — install with 'npm i -g cleargate' or run via 'npx cleargate'. Hook skipped." >>"$LOG"
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
- # Pattern : (STORY|PROPOSAL|EPIC|CR|BUG)[-=]?[0-9]+(-[0-9]+)?
23
- # Fallback : grep first match anywhere in the transcript.
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 first line of the
111
- # dispatch prompt is `STORY=NNN-NN`. The first user message in the transcript
112
- # is that prompt. Look there first to avoid picking up work-item mentions inside
113
- # the subagent's later reads of plans, sprint files, etc.
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(" ") else (. // "") end
133
+ | if type == "array" then map(.text? // "") | join("\n") else (. // "") end
120
134
  | tostring
121
- | scan("(STORY|PROPOSAL|EPIC|CR|BUG)[-=]?([0-9]+(-[0-9]+)?)") | .[0:2] | join("-")
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
- WORK_ITEM_ID="$(printf '%s' "${WORK_ITEM_RAW}" | sed 's/=/-/g')"
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
- WORK_ITEM_ID="$(grep -oE '(STORY|PROPOSAL|EPIC|CR|BUG)[-=]?[0-9]+(-[0-9]+)?' "${TRANSCRIPT_PATH}" 2>/dev/null \
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(1) has ≥1 listed-item"
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
  ```