cleargate 0.14.0 → 0.15.1

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 (150) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/MANIFEST.json +72 -16
  3. package/dist/admin-api/index.cjs +0 -1
  4. package/dist/admin-api/index.js +1 -2
  5. package/dist/auth/factory.cjs +0 -1
  6. package/dist/auth/factory.js +2 -3
  7. package/dist/auth/require-token.cjs +0 -1
  8. package/dist/auth/require-token.js +1 -2
  9. package/dist/auth/token-store.cjs +0 -1
  10. package/dist/auth/token-store.js +1 -2
  11. package/dist/{bootstrap-root-QKSA5V75.js → bootstrap-root-2H5HVTCC.js} +1 -2
  12. package/dist/{chunk-PDE37WFQ.js → chunk-A7MSQUU7.js} +2 -3
  13. package/dist/{chunk-BTSZOEWC.js → chunk-P6KEDAK2.js} +0 -1
  14. package/dist/{chunk-E3X7IE5E.js → chunk-PY6FHGV5.js} +1 -2
  15. package/dist/{chunk-5DI2Z3C2.js → chunk-Y53ZZYYU.js} +1 -2
  16. package/dist/cli.cjs +1564 -1414
  17. package/dist/cli.js +1514 -1364
  18. package/dist/lib/ledger.cjs +0 -1
  19. package/dist/lib/ledger.js +1 -2
  20. package/dist/lib/lifecycle-reconcile.cjs +0 -1
  21. package/dist/lib/lifecycle-reconcile.js +2 -3
  22. package/dist/{whoami-EANGN46Z.js → whoami-JKQQPABQ.js} +3 -4
  23. package/package.json +4 -3
  24. package/templates/cleargate-planning/.claude/agents/architect-synth.md +2 -0
  25. package/templates/cleargate-planning/.claude/agents/architect.md +4 -2
  26. package/templates/cleargate-planning/.claude/agents/developer.md +4 -11
  27. package/templates/cleargate-planning/.claude/agents/qa.md +14 -6
  28. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +2 -2
  29. package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +19 -1
  30. package/templates/cleargate-planning/.cleargate/config.example.yml +16 -0
  31. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.deferred-verify.red.node.test.ts +245 -0
  32. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +227 -0
  33. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +5 -4
  34. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +75 -2
  35. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +48 -0
  36. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +57 -1
  37. package/templates/cleargate-planning/.cleargate/scripts/provision_worktree_config.sh +155 -0
  38. package/templates/cleargate-planning/.cleargate/scripts/qa_red_lint.mjs +380 -0
  39. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +34 -1
  40. package/templates/cleargate-planning/.cleargate/scripts/test/cr077_eviction.red.sh +113 -0
  41. package/templates/cleargate-planning/.cleargate/scripts/test/cr078_init.test.sh +309 -0
  42. package/templates/cleargate-planning/.cleargate/scripts/test/cr079_provision.red.sh +262 -0
  43. package/templates/cleargate-planning/.cleargate/scripts/test/cr080_wrapper.test.sh +177 -0
  44. package/templates/cleargate-planning/.cleargate/scripts/test/cr081_qa_red_lint.red.sh +348 -0
  45. package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/.session-totals.json +1 -0
  46. package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/token-ledger.jsonl +222 -0
  47. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +17 -0
  48. package/templates/cleargate-planning/.cleargate/templates/story.md +1 -0
  49. package/templates/cleargate-planning/MANIFEST.json +72 -16
  50. package/dist/admin-api/index.cjs.map +0 -1
  51. package/dist/admin-api/index.js.map +0 -1
  52. package/dist/auth/factory.cjs.map +0 -1
  53. package/dist/auth/factory.js.map +0 -1
  54. package/dist/auth/require-token.cjs.map +0 -1
  55. package/dist/auth/require-token.js.map +0 -1
  56. package/dist/auth/token-store.cjs.map +0 -1
  57. package/dist/auth/token-store.js.map +0 -1
  58. package/dist/bootstrap-root-QKSA5V75.js.map +0 -1
  59. package/dist/chunk-5DI2Z3C2.js.map +0 -1
  60. package/dist/chunk-BTSZOEWC.js.map +0 -1
  61. package/dist/chunk-E3X7IE5E.js.map +0 -1
  62. package/dist/chunk-PDE37WFQ.js.map +0 -1
  63. package/dist/cli.cjs.map +0 -1
  64. package/dist/cli.js.map +0 -1
  65. package/dist/lib/ledger.cjs.map +0 -1
  66. package/dist/lib/ledger.js.map +0 -1
  67. package/dist/lib/lifecycle-reconcile.cjs.map +0 -1
  68. package/dist/lib/lifecycle-reconcile.js.map +0 -1
  69. package/dist/templates/cleargate-planning/.claude/agents/architect-reader.md +0 -61
  70. package/dist/templates/cleargate-planning/.claude/agents/architect-synth.md +0 -124
  71. package/dist/templates/cleargate-planning/.claude/agents/architect.md +0 -230
  72. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-contradict.md +0 -108
  73. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +0 -194
  74. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +0 -261
  75. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-query.md +0 -143
  76. package/dist/templates/cleargate-planning/.claude/agents/developer.md +0 -185
  77. package/dist/templates/cleargate-planning/.claude/agents/devops.md +0 -257
  78. package/dist/templates/cleargate-planning/.claude/agents/qa.md +0 -171
  79. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +0 -274
  80. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +0 -209
  81. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +0 -33
  82. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +0 -58
  83. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +0 -19
  84. package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +0 -162
  85. package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-autonomy.sh +0 -58
  86. package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-task.sh +0 -148
  87. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +0 -75
  88. package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +0 -43
  89. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +0 -590
  90. package/dist/templates/cleargate-planning/.claude/settings.json +0 -68
  91. package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +0 -102
  92. package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +0 -742
  93. package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +0 -7
  94. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +0 -67
  95. package/dist/templates/cleargate-planning/.cleargate/config.yml +0 -18
  96. package/dist/templates/cleargate-planning/.cleargate/delivery/archive/.gitkeep +0 -0
  97. package/dist/templates/cleargate-planning/.cleargate/delivery/pending-sync/.gitkeep +0 -0
  98. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-enforcement.md +0 -551
  99. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +0 -878
  100. package/dist/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +0 -160
  101. package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +0 -213
  102. package/dist/templates/cleargate-planning/.cleargate/knowledge/sprint-closeout-checklist.md +0 -71
  103. package/dist/templates/cleargate-planning/.cleargate/scripts/_migrate-schema-v3.mjs +0 -120
  104. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +0 -265
  105. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +0 -1012
  106. package/dist/templates/cleargate-planning/.cleargate/scripts/collision_surface.sh +0 -114
  107. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +0 -62
  108. package/dist/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +0 -219
  109. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +0 -320
  110. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +0 -15
  111. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +0 -38
  112. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +0 -240
  113. package/dist/templates/cleargate-planning/.cleargate/scripts/launch_wave.mjs +0 -341
  114. package/dist/templates/cleargate-planning/.cleargate/scripts/lib/report-filename.mjs +0 -54
  115. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +0 -206
  116. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +0 -371
  117. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +0 -280
  118. package/dist/templates/cleargate-planning/.cleargate/scripts/prep_doc_refresh.mjs +0 -378
  119. package/dist/templates/cleargate-planning/.cleargate/scripts/prep_qa_context.mjs +0 -888
  120. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +0 -209
  121. package/dist/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +0 -71
  122. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +0 -127
  123. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +0 -717
  124. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +0 -27
  125. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +0 -261
  126. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +0 -210
  127. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +0 -190
  128. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +0 -482
  129. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +0 -327
  130. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +0 -261
  131. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +0 -246
  132. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +0 -111
  133. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +0 -184
  134. package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +0 -172
  135. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +0 -126
  136. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +0 -130
  137. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +0 -137
  138. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +0 -166
  139. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +0 -111
  140. package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +0 -122
  141. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +0 -50
  142. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +0 -224
  143. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +0 -213
  144. package/dist/templates/cleargate-planning/CLAUDE.md +0 -66
  145. package/dist/templates/cleargate-planning/MANIFEST.json +0 -503
  146. package/dist/templates/synthesis/active-sprint.md +0 -30
  147. package/dist/templates/synthesis/open-gates.md +0 -38
  148. package/dist/templates/synthesis/product-state.md +0 -31
  149. package/dist/templates/synthesis/roadmap.md +0 -63
  150. 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();