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
|
@@ -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
|
```
|