cleargate 0.14.0 → 0.15.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/CHANGELOG.md +16 -0
- package/dist/MANIFEST.json +71 -15
- package/dist/admin-api/index.cjs +0 -1
- package/dist/admin-api/index.js +1 -2
- package/dist/auth/factory.cjs +0 -1
- package/dist/auth/factory.js +2 -3
- package/dist/auth/require-token.cjs +0 -1
- package/dist/auth/require-token.js +1 -2
- package/dist/auth/token-store.cjs +0 -1
- package/dist/auth/token-store.js +1 -2
- package/dist/{bootstrap-root-QKSA5V75.js → bootstrap-root-2H5HVTCC.js} +1 -2
- package/dist/{chunk-PDE37WFQ.js → chunk-A7MSQUU7.js} +2 -3
- package/dist/{chunk-BTSZOEWC.js → chunk-P6KEDAK2.js} +0 -1
- package/dist/{chunk-E3X7IE5E.js → chunk-PY6FHGV5.js} +1 -2
- package/dist/{chunk-5DI2Z3C2.js → chunk-Y53ZZYYU.js} +1 -2
- package/dist/cli.cjs +1564 -1414
- package/dist/cli.js +1514 -1364
- package/dist/lib/ledger.cjs +0 -1
- package/dist/lib/ledger.js +1 -2
- package/dist/lib/lifecycle-reconcile.cjs +0 -1
- package/dist/lib/lifecycle-reconcile.js +2 -3
- package/dist/{whoami-EANGN46Z.js → whoami-JKQQPABQ.js} +3 -4
- package/package.json +4 -3
- package/templates/cleargate-planning/.claude/agents/architect.md +4 -2
- package/templates/cleargate-planning/.claude/agents/developer.md +4 -11
- package/templates/cleargate-planning/.claude/agents/qa.md +14 -6
- package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +2 -2
- package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +19 -1
- package/templates/cleargate-planning/.cleargate/config.example.yml +16 -0
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.deferred-verify.red.node.test.ts +245 -0
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +227 -0
- package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +5 -4
- package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +75 -2
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +48 -0
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +57 -1
- package/templates/cleargate-planning/.cleargate/scripts/provision_worktree_config.sh +155 -0
- package/templates/cleargate-planning/.cleargate/scripts/qa_red_lint.mjs +380 -0
- package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +34 -1
- package/templates/cleargate-planning/.cleargate/scripts/test/cr077_eviction.red.sh +113 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/cr078_init.test.sh +309 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/cr079_provision.red.sh +262 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/cr080_wrapper.test.sh +177 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/cr081_qa_red_lint.red.sh +348 -0
- package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/.session-totals.json +1 -0
- package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/token-ledger.jsonl +27 -0
- package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +17 -0
- package/templates/cleargate-planning/.cleargate/templates/story.md +1 -0
- package/templates/cleargate-planning/MANIFEST.json +71 -15
- package/dist/admin-api/index.cjs.map +0 -1
- package/dist/admin-api/index.js.map +0 -1
- package/dist/auth/factory.cjs.map +0 -1
- package/dist/auth/factory.js.map +0 -1
- package/dist/auth/require-token.cjs.map +0 -1
- package/dist/auth/require-token.js.map +0 -1
- package/dist/auth/token-store.cjs.map +0 -1
- package/dist/auth/token-store.js.map +0 -1
- package/dist/bootstrap-root-QKSA5V75.js.map +0 -1
- package/dist/chunk-5DI2Z3C2.js.map +0 -1
- package/dist/chunk-BTSZOEWC.js.map +0 -1
- package/dist/chunk-E3X7IE5E.js.map +0 -1
- package/dist/chunk-PDE37WFQ.js.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/lib/ledger.cjs.map +0 -1
- package/dist/lib/ledger.js.map +0 -1
- package/dist/lib/lifecycle-reconcile.cjs.map +0 -1
- package/dist/lib/lifecycle-reconcile.js.map +0 -1
- package/dist/templates/cleargate-planning/.claude/agents/architect-reader.md +0 -61
- package/dist/templates/cleargate-planning/.claude/agents/architect-synth.md +0 -124
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +0 -230
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-contradict.md +0 -108
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +0 -194
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +0 -261
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-query.md +0 -143
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +0 -185
- package/dist/templates/cleargate-planning/.claude/agents/devops.md +0 -257
- package/dist/templates/cleargate-planning/.claude/agents/qa.md +0 -171
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +0 -274
- package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +0 -209
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +0 -33
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +0 -58
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +0 -19
- package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +0 -162
- package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-autonomy.sh +0 -58
- package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-task.sh +0 -148
- package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +0 -75
- package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +0 -43
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +0 -590
- package/dist/templates/cleargate-planning/.claude/settings.json +0 -68
- package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +0 -102
- package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +0 -742
- package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +0 -7
- package/dist/templates/cleargate-planning/.cleargate/config.example.yml +0 -67
- package/dist/templates/cleargate-planning/.cleargate/config.yml +0 -18
- package/dist/templates/cleargate-planning/.cleargate/delivery/archive/.gitkeep +0 -0
- package/dist/templates/cleargate-planning/.cleargate/delivery/pending-sync/.gitkeep +0 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-enforcement.md +0 -551
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +0 -878
- package/dist/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +0 -160
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +0 -213
- package/dist/templates/cleargate-planning/.cleargate/knowledge/sprint-closeout-checklist.md +0 -71
- package/dist/templates/cleargate-planning/.cleargate/scripts/_migrate-schema-v3.mjs +0 -120
- package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +0 -265
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +0 -1012
- package/dist/templates/cleargate-planning/.cleargate/scripts/collision_surface.sh +0 -114
- package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +0 -62
- package/dist/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +0 -219
- package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +0 -320
- package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +0 -15
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +0 -38
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +0 -240
- package/dist/templates/cleargate-planning/.cleargate/scripts/launch_wave.mjs +0 -341
- package/dist/templates/cleargate-planning/.cleargate/scripts/lib/report-filename.mjs +0 -54
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +0 -206
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +0 -371
- package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +0 -280
- package/dist/templates/cleargate-planning/.cleargate/scripts/prep_doc_refresh.mjs +0 -378
- package/dist/templates/cleargate-planning/.cleargate/scripts/prep_qa_context.mjs +0 -888
- package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +0 -209
- package/dist/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +0 -71
- package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +0 -127
- package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +0 -717
- package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +0 -27
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +0 -261
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +0 -210
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +0 -190
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +0 -482
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +0 -327
- package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +0 -261
- package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +0 -246
- package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +0 -111
- package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +0 -184
- package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +0 -172
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +0 -126
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +0 -130
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +0 -137
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +0 -166
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +0 -111
- package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +0 -122
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +0 -50
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +0 -224
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +0 -213
- package/dist/templates/cleargate-planning/CLAUDE.md +0 -66
- package/dist/templates/cleargate-planning/MANIFEST.json +0 -503
- package/dist/templates/synthesis/active-sprint.md +0 -30
- package/dist/templates/synthesis/open-gates.md +0 -38
- package/dist/templates/synthesis/product-state.md +0 -31
- package/dist/templates/synthesis/roadmap.md +0 -63
- package/dist/whoami-EANGN46Z.js.map +0 -1
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# file_surface_diff.sh — Parse §3.1 of the active story file and compare
|
|
3
|
-
# declared file paths against `git diff --cached --name-only`.
|
|
4
|
-
#
|
|
5
|
-
# Usage: file_surface_diff.sh [--story-file <path>] [--whitelist <path>] [--v1]
|
|
6
|
-
#
|
|
7
|
-
# Environment:
|
|
8
|
-
# SKIP_SURFACE_GATE=1 — bypass the gate (exit 0 always)
|
|
9
|
-
# CLEARGATE_REPO_ROOT — override repo root (default: CWD git toplevel)
|
|
10
|
-
#
|
|
11
|
-
# Exit codes:
|
|
12
|
-
# 0 — all staged files are on-surface or whitelisted, or v1 mode
|
|
13
|
-
# 1 — off-surface files detected (v2 mode only)
|
|
14
|
-
|
|
15
|
-
set -euo pipefail
|
|
16
|
-
|
|
17
|
-
# ---- Configuration ---------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
REPO_ROOT="${CLEARGATE_REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
20
|
-
WHITELIST_DEFAULT="${REPO_ROOT}/.cleargate/scripts/surface-whitelist.txt"
|
|
21
|
-
ACTIVE_SENTINEL="${REPO_ROOT}/.cleargate/sprint-runs/.active"
|
|
22
|
-
STATE_JSON_GLOB="${REPO_ROOT}/.cleargate/sprint-runs"
|
|
23
|
-
|
|
24
|
-
# Parse args
|
|
25
|
-
STORY_FILE=""
|
|
26
|
-
WHITELIST_FILE="${WHITELIST_DEFAULT}"
|
|
27
|
-
FORCE_V1=0
|
|
28
|
-
|
|
29
|
-
while [[ $# -gt 0 ]]; do
|
|
30
|
-
case "$1" in
|
|
31
|
-
--story-file) STORY_FILE="$2"; shift 2 ;;
|
|
32
|
-
--whitelist) WHITELIST_FILE="$2"; shift 2 ;;
|
|
33
|
-
--v1) FORCE_V1=1; shift ;;
|
|
34
|
-
*) shift ;;
|
|
35
|
-
esac
|
|
36
|
-
done
|
|
37
|
-
|
|
38
|
-
# ---- Bypass check ----------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
if [[ "${SKIP_SURFACE_GATE:-0}" == "1" ]]; then
|
|
41
|
-
echo "[surface-gate] SKIP_SURFACE_GATE=1 — bypassing gate" >&2
|
|
42
|
-
exit 0
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# ---- Execution mode --------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
detect_execution_mode() {
|
|
48
|
-
local state_file="${STATE_JSON_GLOB}"
|
|
49
|
-
# Determine active sprint
|
|
50
|
-
local sprint_id=""
|
|
51
|
-
if [[ -f "${ACTIVE_SENTINEL}" ]]; then
|
|
52
|
-
sprint_id="$(tr -d '[:space:]' < "${ACTIVE_SENTINEL}")"
|
|
53
|
-
fi
|
|
54
|
-
if [[ -z "${sprint_id}" ]]; then
|
|
55
|
-
echo "v1"
|
|
56
|
-
return
|
|
57
|
-
fi
|
|
58
|
-
local state_json="${REPO_ROOT}/.cleargate/sprint-runs/${sprint_id}/state.json"
|
|
59
|
-
if [[ -f "${state_json}" ]]; then
|
|
60
|
-
local mode
|
|
61
|
-
mode="$(grep -oE '"execution_mode"\s*:\s*"v[12]"' "${state_json}" 2>/dev/null | grep -oE 'v[12]' | head -1 || true)"
|
|
62
|
-
if [[ -n "${mode}" ]]; then
|
|
63
|
-
echo "${mode}"
|
|
64
|
-
return
|
|
65
|
-
fi
|
|
66
|
-
fi
|
|
67
|
-
# Fallback: check sprint file frontmatter
|
|
68
|
-
local sprint_file
|
|
69
|
-
sprint_file="$(ls "${REPO_ROOT}/.cleargate/delivery/pending-sync/SPRINT-${sprint_id}_"*.md 2>/dev/null | head -1 || true)"
|
|
70
|
-
if [[ -z "${sprint_file}" ]]; then
|
|
71
|
-
sprint_file="$(ls "${REPO_ROOT}/.cleargate/delivery/archive/SPRINT-${sprint_id}_"*.md 2>/dev/null | head -1 || true)"
|
|
72
|
-
fi
|
|
73
|
-
if [[ -n "${sprint_file}" ]]; then
|
|
74
|
-
local mode
|
|
75
|
-
mode="$(grep -E '^execution_mode:' "${sprint_file}" 2>/dev/null | grep -oE 'v[12]' | head -1 || true)"
|
|
76
|
-
if [[ -n "${mode}" ]]; then
|
|
77
|
-
echo "${mode}"
|
|
78
|
-
return
|
|
79
|
-
fi
|
|
80
|
-
fi
|
|
81
|
-
echo "v1"
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if [[ "${FORCE_V1}" == "1" ]]; then
|
|
85
|
-
EXECUTION_MODE="v1"
|
|
86
|
-
else
|
|
87
|
-
EXECUTION_MODE="$(detect_execution_mode)"
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# ---- Resolve active story file ---------------------------------------------
|
|
91
|
-
|
|
92
|
-
resolve_story_file() {
|
|
93
|
-
local sprint_id=""
|
|
94
|
-
if [[ -f "${ACTIVE_SENTINEL}" ]]; then
|
|
95
|
-
sprint_id="$(tr -d '[:space:]' < "${ACTIVE_SENTINEL}")"
|
|
96
|
-
fi
|
|
97
|
-
if [[ -z "${sprint_id}" ]]; then
|
|
98
|
-
echo ""
|
|
99
|
-
return
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
local state_json="${REPO_ROOT}/.cleargate/sprint-runs/${sprint_id}/state.json"
|
|
103
|
-
if [[ ! -f "${state_json}" ]]; then
|
|
104
|
-
echo ""
|
|
105
|
-
return
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
# Find first non-terminal story (In Progress or Ready)
|
|
109
|
-
local story_id=""
|
|
110
|
-
story_id="$(python3 -c "
|
|
111
|
-
import json, sys
|
|
112
|
-
data = json.load(open('${state_json}'))
|
|
113
|
-
stories = data.get('stories', {})
|
|
114
|
-
for sid, st in stories.items():
|
|
115
|
-
if st.get('state','') in ('In Progress','Ready','In Review'):
|
|
116
|
-
print(sid)
|
|
117
|
-
break
|
|
118
|
-
" 2>/dev/null || true)"
|
|
119
|
-
|
|
120
|
-
if [[ -z "${story_id}" ]]; then
|
|
121
|
-
# Fallback: most recently updated
|
|
122
|
-
story_id="$(python3 -c "
|
|
123
|
-
import json, sys
|
|
124
|
-
data = json.load(open('${state_json}'))
|
|
125
|
-
stories = data.get('stories', {})
|
|
126
|
-
latest = max(stories.items(), key=lambda kv: kv[1].get('updated_at',''), default=(None,None))
|
|
127
|
-
if latest[0]:
|
|
128
|
-
print(latest[0])
|
|
129
|
-
" 2>/dev/null || true)"
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
if [[ -z "${story_id}" ]]; then
|
|
133
|
-
echo ""
|
|
134
|
-
return
|
|
135
|
-
fi
|
|
136
|
-
|
|
137
|
-
# Convert e.g. STORY-014-01 -> find file
|
|
138
|
-
local story_num="${story_id#STORY-}"
|
|
139
|
-
local story_file
|
|
140
|
-
story_file="$(ls "${REPO_ROOT}/.cleargate/delivery/pending-sync/STORY-${story_num}_"*.md 2>/dev/null | head -1 || true)"
|
|
141
|
-
if [[ -z "${story_file}" ]]; then
|
|
142
|
-
story_file="$(ls "${REPO_ROOT}/.cleargate/delivery/archive/STORY-${story_num}_"*.md 2>/dev/null | head -1 || true)"
|
|
143
|
-
fi
|
|
144
|
-
echo "${story_file}"
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if [[ -z "${STORY_FILE}" ]]; then
|
|
148
|
-
STORY_FILE="$(resolve_story_file)"
|
|
149
|
-
fi
|
|
150
|
-
|
|
151
|
-
if [[ -z "${STORY_FILE}" || ! -f "${STORY_FILE}" ]]; then
|
|
152
|
-
echo "[surface-gate] WARNING: No active story file found — skipping surface check" >&2
|
|
153
|
-
exit 0
|
|
154
|
-
fi
|
|
155
|
-
|
|
156
|
-
# ---- Parse §3.1 file surface table -----------------------------------------
|
|
157
|
-
|
|
158
|
-
parse_surface_paths() {
|
|
159
|
-
local story_file="$1"
|
|
160
|
-
# Extract rows between "### 3.1" and the next "### " header.
|
|
161
|
-
# Table rows: | Item | Value |
|
|
162
|
-
# Only rows where Value cell looks like a path (contains . or /)
|
|
163
|
-
# Strip backticks. Split on ", " for multiple paths in one cell.
|
|
164
|
-
awk '
|
|
165
|
-
/^### 3\.1/ { in_section=1; next }
|
|
166
|
-
in_section && /^### / { in_section=0; next }
|
|
167
|
-
in_section && /^\|/ {
|
|
168
|
-
# Remove leading/trailing pipe, split into fields
|
|
169
|
-
line=$0
|
|
170
|
-
gsub(/^\||\|$/, "", line)
|
|
171
|
-
n=split(line, cols, "|")
|
|
172
|
-
if (n < 2) next
|
|
173
|
-
val=cols[2]
|
|
174
|
-
# Trim whitespace
|
|
175
|
-
gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
|
|
176
|
-
# Only process if value looks like a path (contains . or /)
|
|
177
|
-
if (val !~ /[.\/]/) next
|
|
178
|
-
# Strip backticks
|
|
179
|
-
gsub(/`/, "", val)
|
|
180
|
-
# Handle multiple paths separated by ", "
|
|
181
|
-
npaths=split(val, paths, ", ")
|
|
182
|
-
for (i=1; i<=npaths; i++) {
|
|
183
|
-
p=paths[i]
|
|
184
|
-
gsub(/^[[:space:]]+|[[:space:]]+$/, "", p)
|
|
185
|
-
if (p != "" && (p ~ /[.\/]/)) print p
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
' "${story_file}"
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
# Collect declared paths into array (portable bash 3.2 compat — no mapfile)
|
|
192
|
-
declared_paths=()
|
|
193
|
-
while IFS= read -r p; do
|
|
194
|
-
declared_paths+=("$p")
|
|
195
|
-
done < <(parse_surface_paths "${STORY_FILE}")
|
|
196
|
-
|
|
197
|
-
if [[ ${#declared_paths[@]} -eq 0 ]]; then
|
|
198
|
-
echo "[surface-gate] WARNING: No file paths found in §3.1 of ${STORY_FILE} — skipping surface check" >&2
|
|
199
|
-
exit 0
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
|
-
# ---- Get staged files -------------------------------------------------------
|
|
203
|
-
|
|
204
|
-
staged_files=()
|
|
205
|
-
while IFS= read -r f; do
|
|
206
|
-
staged_files+=("$f")
|
|
207
|
-
done < <(git -C "${REPO_ROOT}" diff --cached --name-only 2>/dev/null || true)
|
|
208
|
-
|
|
209
|
-
if [[ ${#staged_files[@]} -eq 0 ]]; then
|
|
210
|
-
# Nothing staged — nothing to check
|
|
211
|
-
exit 0
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
# ---- Load whitelist ---------------------------------------------------------
|
|
215
|
-
|
|
216
|
-
whitelist_patterns=()
|
|
217
|
-
if [[ -f "${WHITELIST_FILE}" ]]; then
|
|
218
|
-
while IFS= read -r line; do
|
|
219
|
-
# Skip comments and blank lines
|
|
220
|
-
[[ -z "${line}" || "${line}" =~ ^# ]] && continue
|
|
221
|
-
whitelist_patterns+=("$line")
|
|
222
|
-
done < "${WHITELIST_FILE}"
|
|
223
|
-
fi
|
|
224
|
-
|
|
225
|
-
# ---- Helper: match file against whitelist -----------------------------------
|
|
226
|
-
|
|
227
|
-
is_whitelisted() {
|
|
228
|
-
local file="$1"
|
|
229
|
-
local pattern
|
|
230
|
-
for pattern in "${whitelist_patterns[@]+"${whitelist_patterns[@]}"}"; do
|
|
231
|
-
# Use bash glob matching — convert ** to * for simple matching
|
|
232
|
-
# Try direct fnmatch with case
|
|
233
|
-
if [[ "${file}" == ${pattern} ]]; then
|
|
234
|
-
return 0
|
|
235
|
-
fi
|
|
236
|
-
# Try matching basename
|
|
237
|
-
local basename="${file##*/}"
|
|
238
|
-
local pat_base="${pattern##*/}"
|
|
239
|
-
if [[ "${basename}" == ${pat_base} && "${pat_base}" == "${basename}" ]]; then
|
|
240
|
-
: # need full path match
|
|
241
|
-
fi
|
|
242
|
-
# Try: if pattern has **, match any path segment
|
|
243
|
-
local simple_pat="${pattern//\*\*\//*/}"
|
|
244
|
-
if [[ "${file}" == ${simple_pat} ]]; then
|
|
245
|
-
return 0
|
|
246
|
-
fi
|
|
247
|
-
# Also: check if file path contains the pattern as suffix
|
|
248
|
-
if [[ "${file}" == *"${pattern}" ]]; then
|
|
249
|
-
return 0
|
|
250
|
-
fi
|
|
251
|
-
done
|
|
252
|
-
return 1
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
# ---- Helper: normalize path for comparison ----------------------------------
|
|
256
|
-
|
|
257
|
-
normalize_path() {
|
|
258
|
-
local p="$1"
|
|
259
|
-
# Strip leading ./
|
|
260
|
-
p="${p#./}"
|
|
261
|
-
# If absolute path under REPO_ROOT, make it relative
|
|
262
|
-
if [[ "${p}" == "${REPO_ROOT}/"* ]]; then
|
|
263
|
-
p="${p#${REPO_ROOT}/}"
|
|
264
|
-
fi
|
|
265
|
-
echo "${p}"
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
# ---- Compare staged vs declared ---------------------------------------------
|
|
269
|
-
|
|
270
|
-
off_surface=()
|
|
271
|
-
for staged in "${staged_files[@]}"; do
|
|
272
|
-
staged_norm="$(normalize_path "${staged}")"
|
|
273
|
-
|
|
274
|
-
# Check whitelist first
|
|
275
|
-
if is_whitelisted "${staged_norm}"; then
|
|
276
|
-
continue
|
|
277
|
-
fi
|
|
278
|
-
# Also check absolute path against whitelist
|
|
279
|
-
if is_whitelisted "${REPO_ROOT}/${staged_norm}"; then
|
|
280
|
-
continue
|
|
281
|
-
fi
|
|
282
|
-
|
|
283
|
-
# Check against declared surface
|
|
284
|
-
found=0
|
|
285
|
-
for declared in "${declared_paths[@]+"${declared_paths[@]}"}"; do
|
|
286
|
-
declared_norm="$(normalize_path "${declared}")"
|
|
287
|
-
if [[ "${staged_norm}" == "${declared_norm}" ]]; then
|
|
288
|
-
found=1
|
|
289
|
-
break
|
|
290
|
-
fi
|
|
291
|
-
done
|
|
292
|
-
|
|
293
|
-
if [[ "${found}" == "0" ]]; then
|
|
294
|
-
off_surface+=("${staged_norm}")
|
|
295
|
-
fi
|
|
296
|
-
done
|
|
297
|
-
|
|
298
|
-
# ---- Report -----------------------------------------------------------------
|
|
299
|
-
|
|
300
|
-
if [[ ${#off_surface[@]} -eq 0 ]]; then
|
|
301
|
-
exit 0
|
|
302
|
-
fi
|
|
303
|
-
|
|
304
|
-
# Off-surface files detected
|
|
305
|
-
if [[ "${EXECUTION_MODE}" == "v1" ]]; then
|
|
306
|
-
echo "[surface-gate] WARNING (v1 advisory): staged files outside declared §3.1 surface:" >&2
|
|
307
|
-
for f in "${off_surface[@]}"; do
|
|
308
|
-
echo " off-surface: ${f}" >&2
|
|
309
|
-
done
|
|
310
|
-
echo "[surface-gate] v1 mode — not blocking commit. Switch to v2 to enforce." >&2
|
|
311
|
-
exit 0
|
|
312
|
-
else
|
|
313
|
-
echo "[surface-gate] BLOCKED: staged files outside declared §3.1 surface:" >&2
|
|
314
|
-
for f in "${off_surface[@]}"; do
|
|
315
|
-
echo " off-surface: ${f}" >&2
|
|
316
|
-
done
|
|
317
|
-
echo "[surface-gate] Commit blocked. Declare these files in §3.1 or open a CR:scope-change." >&2
|
|
318
|
-
echo "[surface-gate] Set SKIP_SURFACE_GATE=1 to bypass (v2 mode — use sparingly)." >&2
|
|
319
|
-
exit 1
|
|
320
|
-
fi
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": 1,
|
|
3
|
-
"qa": {
|
|
4
|
-
"typecheck": "cd cleargate-cli && npm run typecheck",
|
|
5
|
-
"debug_patterns": ["console.log", "console.debug", "debugger"],
|
|
6
|
-
"todo_patterns": ["TODO", "FIXME", "XXX"],
|
|
7
|
-
"test": "cd cleargate-cli && npm test"
|
|
8
|
-
},
|
|
9
|
-
"arch": {
|
|
10
|
-
"typecheck": "cd cleargate-cli && npm run typecheck",
|
|
11
|
-
"new_deps_check": true,
|
|
12
|
-
"stray_env_files": [".env", ".env.local", ".env.production"],
|
|
13
|
-
"file_count_report": true
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# init_gate_config.sh — Idempotent seeder for gate-checks.json
|
|
3
|
-
# Usage: init_gate_config.sh [--config-path <path>]
|
|
4
|
-
# If gate-checks.json already exists, exits 0 without overwriting.
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
-
CONFIG_PATH="${SCRIPT_DIR}/gate-checks.json"
|
|
9
|
-
|
|
10
|
-
# Allow override via argument
|
|
11
|
-
if [[ "${1:-}" == "--config-path" && -n "${2:-}" ]]; then
|
|
12
|
-
CONFIG_PATH="$2"
|
|
13
|
-
fi
|
|
14
|
-
|
|
15
|
-
if [[ -f "$CONFIG_PATH" ]]; then
|
|
16
|
-
echo "gate-checks.json already exists at ${CONFIG_PATH} — no-op." >&2
|
|
17
|
-
exit 0
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
cat > "$CONFIG_PATH" << 'EOF'
|
|
21
|
-
{
|
|
22
|
-
"schema_version": 1,
|
|
23
|
-
"qa": {
|
|
24
|
-
"typecheck": "npm run typecheck",
|
|
25
|
-
"debug_patterns": ["console.log", "console.debug", "debugger"],
|
|
26
|
-
"todo_patterns": ["TODO", "FIXME", "XXX"],
|
|
27
|
-
"test": "npm test"
|
|
28
|
-
},
|
|
29
|
-
"arch": {
|
|
30
|
-
"typecheck": "npm run typecheck",
|
|
31
|
-
"new_deps_check": true,
|
|
32
|
-
"stray_env_files": [".env", ".env.local", ".env.production"],
|
|
33
|
-
"file_count_report": true
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
EOF
|
|
37
|
-
|
|
38
|
-
echo "gate-checks.json created at ${CONFIG_PATH}" >&2
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* init_sprint.mjs — Initialize a sprint state.json
|
|
4
|
-
*
|
|
5
|
-
* Usage: node init_sprint.mjs <sprint-id> --stories ID1,ID2,... [--force] [--preserve-bounces]
|
|
6
|
-
*
|
|
7
|
-
* --preserve-bounces requires --force; reads existing state.json and carries
|
|
8
|
-
* forward qa_bounces / arch_bounces / state / lane / worktree per matching
|
|
9
|
-
* story-id. Items not in the new --stories list are dropped. Useful when
|
|
10
|
-
* mid-sprint dogfood / rerun must not lose kickback history.
|
|
11
|
-
*
|
|
12
|
-
* Creates .cleargate/sprint-runs/<sprint-id>/state.json with initial state
|
|
13
|
-
* "Ready to Bounce" for each story. Refuses if state.json already exists
|
|
14
|
-
* unless --force is passed.
|
|
15
|
-
*
|
|
16
|
-
* STORY-070-01: execution_mode is retired. All gates are always enforced.
|
|
17
|
-
* Break-glass: set CLEARGATE_ADVISORY=1 to downgrade gate failures to warnings.
|
|
18
|
-
* The story-file assertion always runs in blocking mode (v2-equivalent behavior).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import fs from 'node:fs';
|
|
22
|
-
import path from 'node:path';
|
|
23
|
-
import { fileURLToPath } from 'node:url';
|
|
24
|
-
import { spawnSync } from 'node:child_process';
|
|
25
|
-
import { SCHEMA_VERSION, VALID_STATES, TERMINAL_STATES } from './constants.mjs';
|
|
26
|
-
|
|
27
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
-
// Resolve repo root: .cleargate/scripts/ -> ../../ (two levels up)
|
|
29
|
-
// CLEARGATE_REPO_ROOT env var overrides for testing
|
|
30
|
-
const REPO_ROOT = process.env.CLEARGATE_REPO_ROOT
|
|
31
|
-
? path.resolve(process.env.CLEARGATE_REPO_ROOT)
|
|
32
|
-
: path.resolve(__dirname, '..', '..');
|
|
33
|
-
|
|
34
|
-
function usage() {
|
|
35
|
-
process.stderr.write(
|
|
36
|
-
'Usage: node init_sprint.mjs <sprint-id> --stories ID1,ID2,... [--force]\n'
|
|
37
|
-
);
|
|
38
|
-
process.exit(2);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Locate the sprint file in pending-sync/ or archive/ for the given sprint ID.
|
|
43
|
-
* Returns the absolute path if found, null otherwise.
|
|
44
|
-
*/
|
|
45
|
-
function findSprintFile(repoRoot, sprintId) {
|
|
46
|
-
const searchDirs = [
|
|
47
|
-
path.join(repoRoot, '.cleargate', 'delivery', 'pending-sync'),
|
|
48
|
-
path.join(repoRoot, '.cleargate', 'delivery', 'archive'),
|
|
49
|
-
];
|
|
50
|
-
for (const dir of searchDirs) {
|
|
51
|
-
let entries;
|
|
52
|
-
try {
|
|
53
|
-
entries = fs.readdirSync(dir);
|
|
54
|
-
} catch {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const prefix = `${sprintId}_`;
|
|
58
|
-
const match = entries.find(
|
|
59
|
-
(e) => (e === `${sprintId}.md` || e.startsWith(prefix)) && e.endsWith('.md')
|
|
60
|
-
);
|
|
61
|
-
if (match) return path.join(dir, match);
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Run assert_story_files.mjs for the given sprint file.
|
|
68
|
-
* Returns { exitCode, stderr }.
|
|
69
|
-
*/
|
|
70
|
-
function runAssertStoryFiles(repoRoot, sprintFilePath) {
|
|
71
|
-
// Use __dirname to locate the sibling script (not CLEARGATE_REPO_ROOT, which is a test-isolation
|
|
72
|
-
// override that points to a tmpdir without scripts).
|
|
73
|
-
const assertScript = path.join(__dirname, 'assert_story_files.mjs');
|
|
74
|
-
const env = { ...process.env, CLEARGATE_REPO_ROOT: repoRoot };
|
|
75
|
-
const result = spawnSync(process.execPath, [assertScript, sprintFilePath], {
|
|
76
|
-
encoding: 'utf8',
|
|
77
|
-
env,
|
|
78
|
-
});
|
|
79
|
-
return {
|
|
80
|
-
exitCode: result.status ?? 1,
|
|
81
|
-
stderr: result.stderr || '',
|
|
82
|
-
stdout: result.stdout || '',
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function main() {
|
|
87
|
-
const args = process.argv.slice(2);
|
|
88
|
-
|
|
89
|
-
const sprintId = args[0];
|
|
90
|
-
if (!sprintId || sprintId.startsWith('--')) usage();
|
|
91
|
-
|
|
92
|
-
const storiesIdx = args.indexOf('--stories');
|
|
93
|
-
if (storiesIdx === -1 || !args[storiesIdx + 1]) usage();
|
|
94
|
-
|
|
95
|
-
const storyIds = args[storiesIdx + 1].split(',').map((s) => s.trim()).filter(Boolean);
|
|
96
|
-
if (storyIds.length === 0) {
|
|
97
|
-
process.stderr.write('Error: --stories requires at least one story ID\n');
|
|
98
|
-
process.exit(2);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const force = args.includes('--force');
|
|
102
|
-
const preserveBounces = args.includes('--preserve-bounces');
|
|
103
|
-
|
|
104
|
-
const sprintDir = path.join(REPO_ROOT, '.cleargate', 'sprint-runs', sprintId);
|
|
105
|
-
const stateFile = path.join(sprintDir, 'state.json');
|
|
106
|
-
|
|
107
|
-
if (fs.existsSync(stateFile) && !force) {
|
|
108
|
-
process.stderr.write(
|
|
109
|
-
`state.json already exists at ${stateFile}; pass --force to overwrite\n`
|
|
110
|
-
);
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// --- CR-049-followup: Preserve bounce counters when --force re-inits an in-flight sprint ---
|
|
115
|
-
// Default --force overwrites everything (initial design). With --preserve-bounces,
|
|
116
|
-
// qa_bounces / arch_bounces / state are read from the existing state.json and merged
|
|
117
|
-
// back per-story. Only Ready-to-Bounce default fields get reset.
|
|
118
|
-
let preserved = {};
|
|
119
|
-
if (force && preserveBounces && fs.existsSync(stateFile)) {
|
|
120
|
-
try {
|
|
121
|
-
const existing = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
122
|
-
preserved = existing.stories || {};
|
|
123
|
-
} catch (err) {
|
|
124
|
-
process.stderr.write(`WARN: --preserve-bounces could not read existing state.json: ${err.message}\n`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// --- Gate-2 story-file assertion (always enforced — STORY-070-01) ---
|
|
129
|
-
const sprintFilePath = findSprintFile(REPO_ROOT, sprintId);
|
|
130
|
-
if (sprintFilePath) {
|
|
131
|
-
const { exitCode, stderr } = runAssertStoryFiles(REPO_ROOT, sprintFilePath);
|
|
132
|
-
if (exitCode !== 0) {
|
|
133
|
-
// Always enforce (v2-equivalent behavior; CLEARGATE_ADVISORY=1 for break-glass)
|
|
134
|
-
process.stderr.write(stderr);
|
|
135
|
-
// Count categories from structured stderr lines
|
|
136
|
-
const missingCount = (stderr.match(/^MISSING \((\d+)\):/m) ?? [])[1] ?? '0';
|
|
137
|
-
const unapprovedCount = (stderr.match(/^UNAPPROVED \((\d+)\):/m) ?? [])[1] ?? '0';
|
|
138
|
-
const emptyCount = (stderr.match(/^STUB-EMPTY \((\d+)\):/m) ?? [])[1] ?? '0';
|
|
139
|
-
const msg = `ERROR: sprint init blocked — ${missingCount} items missing, ${unapprovedCount} unapproved, ${emptyCount} stub-empty. Fix the above, then re-run init.\n`;
|
|
140
|
-
if (process.env.CLEARGATE_ADVISORY === '1') {
|
|
141
|
-
process.stderr.write(`[advisory] ${msg}`);
|
|
142
|
-
} else {
|
|
143
|
-
process.stderr.write(msg);
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const now = new Date().toISOString();
|
|
150
|
-
const stories = {};
|
|
151
|
-
for (const id of storyIds) {
|
|
152
|
-
const carry = preserved[id] || {};
|
|
153
|
-
stories[id] = {
|
|
154
|
-
state: carry.state ?? 'Ready to Bounce',
|
|
155
|
-
qa_bounces: carry.qa_bounces ?? 0,
|
|
156
|
-
arch_bounces: carry.arch_bounces ?? 0,
|
|
157
|
-
worktree: carry.worktree ?? null,
|
|
158
|
-
updated_at: now,
|
|
159
|
-
notes: carry.notes ?? '',
|
|
160
|
-
lane: carry.lane ?? 'standard',
|
|
161
|
-
lane_assigned_by: carry.lane_assigned_by ?? 'migration-default',
|
|
162
|
-
lane_demoted_at: carry.lane_demoted_at ?? null,
|
|
163
|
-
lane_demotion_reason: carry.lane_demotion_reason ?? null,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const state = {
|
|
168
|
-
schema_version: SCHEMA_VERSION,
|
|
169
|
-
sprint_id: sprintId,
|
|
170
|
-
sprint_status: 'Active',
|
|
171
|
-
stories,
|
|
172
|
-
last_action: `Sprint ${sprintId} initialised`,
|
|
173
|
-
updated_at: now,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
fs.mkdirSync(sprintDir, { recursive: true });
|
|
177
|
-
|
|
178
|
-
const tmpFile = `${stateFile}.tmp.${process.pid}`;
|
|
179
|
-
fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
180
|
-
fs.renameSync(tmpFile, stateFile);
|
|
181
|
-
|
|
182
|
-
// --- CR-045: Write sprint-context.md from template ---
|
|
183
|
-
// Reads .cleargate/templates/sprint_context.md, substitutes frontmatter fields,
|
|
184
|
-
// optionally splices the sprint goal from the sprint plan §0, and writes atomically.
|
|
185
|
-
// Skip if file already exists and --force was not passed (idempotency-safe re-init).
|
|
186
|
-
const ctxTemplate = path.join(REPO_ROOT, '.cleargate', 'templates', 'sprint_context.md');
|
|
187
|
-
const ctxOut = path.join(sprintDir, 'sprint-context.md');
|
|
188
|
-
|
|
189
|
-
if (!fs.existsSync(ctxOut) || force) {
|
|
190
|
-
let ctxContent;
|
|
191
|
-
try {
|
|
192
|
-
ctxContent = fs.readFileSync(ctxTemplate, 'utf8');
|
|
193
|
-
} catch {
|
|
194
|
-
// Template absent — log warning and continue (non-fatal)
|
|
195
|
-
process.stderr.write(
|
|
196
|
-
`WARN: sprint-context.md template not found at ${ctxTemplate}; skipping sprint-context.md write.\n`
|
|
197
|
-
);
|
|
198
|
-
ctxContent = null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (ctxContent !== null) {
|
|
202
|
-
// Substitute frontmatter placeholders
|
|
203
|
-
ctxContent = ctxContent.replace(/sprint_id:\s*["']?S-NN["']?/, `sprint_id: "${sprintId}"`);
|
|
204
|
-
ctxContent = ctxContent.replace(/created_at:\s*["']?YYYY-MM-DDTHH:MM:SSZ["']?/, `created_at: "${now}"`);
|
|
205
|
-
ctxContent = ctxContent.replace(/last_updated:\s*["']?YYYY-MM-DDTHH:MM:SSZ["']?/, `last_updated: "${now}"`);
|
|
206
|
-
|
|
207
|
-
// Optionally extract sprint goal from sprint plan §0.
|
|
208
|
-
// Regex matches `- **Sprint Goal:** <text>` within first 200 lines (after H1).
|
|
209
|
-
// Falls back to placeholder if absent — non-fatal.
|
|
210
|
-
if (sprintFilePath) {
|
|
211
|
-
try {
|
|
212
|
-
const planContent = fs.readFileSync(sprintFilePath, 'utf8');
|
|
213
|
-
const planLines = planContent.split('\n').slice(0, 200);
|
|
214
|
-
const goalLine = planLines.find((l) => /^- \*\*Sprint Goal:\*\* (.+)$/.test(l.trim()));
|
|
215
|
-
if (goalLine) {
|
|
216
|
-
const goalMatch = goalLine.trim().match(/^- \*\*Sprint Goal:\*\* (.+)$/);
|
|
217
|
-
if (goalMatch) {
|
|
218
|
-
const goalText = goalMatch[1].trim();
|
|
219
|
-
ctxContent = ctxContent.replace(
|
|
220
|
-
'_(populated by orchestrator from sprint plan §0 at kickoff)_',
|
|
221
|
-
goalText
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} catch {
|
|
226
|
-
// Goal extraction failed — leave placeholder; non-fatal
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Write atomically via tmpFile + renameSync (mirrors state.json pattern)
|
|
231
|
-
const ctxTmp = `${ctxOut}.tmp.${process.pid}`;
|
|
232
|
-
fs.writeFileSync(ctxTmp, ctxContent, 'utf8');
|
|
233
|
-
fs.renameSync(ctxTmp, ctxOut);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
process.stdout.write(`Initialized state.json for sprint ${sprintId} with ${storyIds.length} stories\n`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
main();
|