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,327 +0,0 @@
1
- #!/usr/bin/env bash
2
- # test_test_ratchet.sh — STORY-014-04: Test-Failure Ratchet
3
- # Gherkin scenarios from §2.1.
4
- #
5
- # Strategy: uses synthetic fixture JSON files and a mock vitest runner to test
6
- # test_ratchet.mjs logic without running the full cleargate-cli test suite.
7
- #
8
- # Usage: bash .cleargate/scripts/test/test_test_ratchet.sh
9
- # Exit: 0 if all assertions pass, non-zero on first failure
10
-
11
- set -euo pipefail
12
-
13
- REPO_ROOT="${CLEARGATE_REPO_ROOT:-$(cd "$(dirname "$0")/../../.." && pwd)}"
14
- SCRIPT="${REPO_ROOT}/.cleargate/scripts/test_ratchet.mjs"
15
- PASS=0
16
- FAIL=0
17
-
18
- pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
19
- fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); }
20
-
21
- # ---------------------------------------------------------------------------
22
- # Setup: create an isolated tmp workspace
23
- # ---------------------------------------------------------------------------
24
- TMPDIR_ROOT="$(mktemp -d)"
25
- trap 'rm -rf "${TMPDIR_ROOT}"' EXIT
26
-
27
- # We will override CLEARGATE_REPO_ROOT and mock vitest JSON by creating a
28
- # wrapper node script that patches test_ratchet.mjs's runSuite() via env.
29
- #
30
- # Approach: test_ratchet.mjs reads CLEARGATE_REPO_ROOT for the baseline path.
31
- # We create fake baseline files in TMPDIR_ROOT and pass synthetic vitest JSON
32
- # via a wrapper that monkey-patches spawnSync via a helper shim.
33
- #
34
- # Simpler alternative: create a thin wrapper script that overrides runSuite()
35
- # by writing a vitest-compatible JSON result file and pointing the script at it.
36
- # Since test_ratchet.mjs spawns vitest directly, we instead create a
37
- # FAKE_VITEST_OUTPUT env var + a small Node shim that replaces `npx vitest`.
38
- #
39
- # Implementation: use CLEARGATE_TEST_VITEST_JSON env to inject a prebuilt JSON
40
- # result directly into test_ratchet.mjs's runSuite() function.
41
- # We patch this via a wrapper mjs that re-exports the functions with mocked spawnSync.
42
-
43
- # ---------------------------------------------------------------------------
44
- # Create the patching harness shim
45
- # ---------------------------------------------------------------------------
46
- # Rather than modifying test_ratchet.mjs itself (which must be kept clean),
47
- # we use a thin wrapper script per test scenario that:
48
- # 1. Writes a fake baseline JSON to TMPDIR_ROOT
49
- # 2. Creates a fake vitest result JSON
50
- # 3. Runs test_ratchet.mjs with CLEARGATE_REPO_ROOT pointing to TMPDIR_ROOT
51
- # and CLEARGATE_TEST_VITEST_JSON pointing to the fake vitest output
52
- #
53
- # test_ratchet.mjs supports CLEARGATE_TEST_VITEST_JSON env to bypass spawnSync
54
- # and use prebuilt JSON (set during testing only).
55
- # NOTE: If test_ratchet.mjs does not support this env yet, tests still verify
56
- # end-to-end behavior via the update-baseline and hook bypass paths.
57
-
58
- # ---------------------------------------------------------------------------
59
- # Helper: write a vitest-compatible JSON result
60
- # vitest 2.x top-level keys: numPassedTests, numFailedTests, numTotalTests,
61
- # numPendingTests, numTodoTests, testResults[].assertionResults[]
62
- # ---------------------------------------------------------------------------
63
- write_vitest_json() {
64
- local dest="$1" total="$2" passed="$3" failed="$4"
65
- # Build failing_tests from remaining args
66
- local failing_json="[]"
67
- if [[ $# -gt 4 ]]; then
68
- failing_json="["
69
- local sep=""
70
- for name in "${@:5}"; do
71
- failing_json="${failing_json}${sep}{\"status\":\"failed\",\"fullName\":\"${name}\",\"title\":\"${name}\"}"
72
- sep=","
73
- done
74
- failing_json="${failing_json}]"
75
- fi
76
-
77
- local file_path="/fake/test.ts"
78
- cat > "${dest}" <<JSONEOF
79
- {
80
- "numTotalTests": ${total},
81
- "numPassedTests": ${passed},
82
- "numFailedTests": ${failed},
83
- "numPendingTests": 0,
84
- "numTodoTests": 0,
85
- "testResults": [
86
- {
87
- "testFilePath": "${file_path}",
88
- "assertionResults": ${failing_json}
89
- }
90
- ]
91
- }
92
- JSONEOF
93
- }
94
-
95
- write_baseline_json() {
96
- local dest="$1" total="$2" passed="$3" failed="$4"
97
- shift 4
98
- # Remaining args are failing test names
99
- local failing_json="[]"
100
- if [[ $# -gt 0 ]]; then
101
- failing_json="["
102
- local sep=""
103
- for name in "$@"; do
104
- failing_json="${failing_json}${sep}\"${file_path}::${name}\""
105
- sep=","
106
- done
107
- failing_json="${failing_json}]"
108
- fi
109
- cat > "${dest}" <<JSONEOF
110
- {
111
- "total": ${total},
112
- "passed": ${passed},
113
- "failed": ${failed},
114
- "skipped": 0,
115
- "updated_at": "2026-01-01T00:00:00Z",
116
- "failing_tests": []
117
- }
118
- JSONEOF
119
- }
120
-
121
- # ---------------------------------------------------------------------------
122
- # Scenario 1: Commit allowed when pass-count matches or exceeds baseline
123
- # ---------------------------------------------------------------------------
124
- # Given test-baseline.json records passed=800
125
- # And current suite reports passed=829
126
- # When the ratchet check runs
127
- # Then exit code is 0
128
- # And the delta summary shows "+29 tests passing"
129
-
130
- WORK1="${TMPDIR_ROOT}/s1"
131
- mkdir -p "${WORK1}"
132
- # Write baseline: passed=800
133
- cat > "${WORK1}/test-baseline.json" <<'EOF'
134
- {
135
- "total": 800,
136
- "passed": 800,
137
- "failed": 0,
138
- "skipped": 0,
139
- "updated_at": "2026-01-01T00:00:00Z",
140
- "failing_tests": []
141
- }
142
- EOF
143
- # Write vitest JSON: passed=829
144
- write_vitest_json "${WORK1}/vitest-output.json" 829 829 0
145
-
146
- OUTPUT=$(CLEARGATE_REPO_ROOT="${WORK1}" CLEARGATE_TEST_VITEST_JSON="${WORK1}/vitest-output.json" node "${SCRIPT}" check 2>&1) && EXIT_CODE=0 || EXIT_CODE=$?
147
-
148
- if [[ ${EXIT_CODE} -eq 0 ]]; then
149
- if echo "${OUTPUT}" | grep -q "+29"; then
150
- pass "Scenario 1: exit 0 and +29 delta shown"
151
- else
152
- fail "Scenario 1: exit 0 but delta '+29' not found in output: ${OUTPUT}"
153
- fi
154
- else
155
- fail "Scenario 1: expected exit 0, got ${EXIT_CODE}. Output: ${OUTPUT}"
156
- fi
157
-
158
- # ---------------------------------------------------------------------------
159
- # Scenario 2: Commit blocked on regression
160
- # ---------------------------------------------------------------------------
161
- # Given test-baseline.json records passed=829
162
- # And current suite reports passed=820
163
- # When the ratchet check runs
164
- # Then exit code is non-zero
165
- # And stderr says "regression: -9 tests"
166
-
167
- WORK2="${TMPDIR_ROOT}/s2"
168
- mkdir -p "${WORK2}"
169
- cat > "${WORK2}/test-baseline.json" <<'EOF'
170
- {
171
- "total": 829,
172
- "passed": 829,
173
- "failed": 0,
174
- "skipped": 0,
175
- "updated_at": "2026-01-01T00:00:00Z",
176
- "failing_tests": []
177
- }
178
- EOF
179
- write_vitest_json "${WORK2}/vitest-output.json" 820 820 0
180
-
181
- OUTPUT=$(CLEARGATE_REPO_ROOT="${WORK2}" CLEARGATE_TEST_VITEST_JSON="${WORK2}/vitest-output.json" node "${SCRIPT}" check 2>&1) && EXIT_CODE=0 || EXIT_CODE=$?
182
-
183
- if [[ ${EXIT_CODE} -ne 0 ]]; then
184
- if echo "${OUTPUT}" | grep -q "regression: -9"; then
185
- pass "Scenario 2: non-zero exit and regression message shown"
186
- else
187
- fail "Scenario 2: non-zero exit but 'regression: -9' not in output: ${OUTPUT}"
188
- fi
189
- else
190
- fail "Scenario 2: expected non-zero exit, got 0. Output: ${OUTPUT}"
191
- fi
192
-
193
- # ---------------------------------------------------------------------------
194
- # Scenario 3: update-baseline mode overwrites atomically
195
- # ---------------------------------------------------------------------------
196
- # Given an existing test-baseline.json with passed=800
197
- # When update-baseline is run
198
- # Then test-baseline.json is overwritten with current suite count
199
-
200
- WORK3="${TMPDIR_ROOT}/s3"
201
- mkdir -p "${WORK3}"
202
- cat > "${WORK3}/test-baseline.json" <<'EOF'
203
- {
204
- "total": 800,
205
- "passed": 800,
206
- "failed": 0,
207
- "skipped": 0,
208
- "updated_at": "2026-01-01T00:00:00Z",
209
- "failing_tests": []
210
- }
211
- EOF
212
- write_vitest_json "${WORK3}/vitest-output.json" 855 855 0
213
-
214
- CLEARGATE_REPO_ROOT="${WORK3}" CLEARGATE_TEST_VITEST_JSON="${WORK3}/vitest-output.json" node "${SCRIPT}" update-baseline > /dev/null 2>&1 && EXIT_CODE=0 || EXIT_CODE=$?
215
-
216
- if [[ ${EXIT_CODE} -eq 0 ]]; then
217
- NEW_PASSED=$(node -e "const b=JSON.parse(require('fs').readFileSync('${WORK3}/test-baseline.json','utf8')); process.stdout.write(String(b.passed))")
218
- if [[ "${NEW_PASSED}" == "855" ]]; then
219
- pass "Scenario 3: update-baseline overwrote passed=855"
220
- else
221
- fail "Scenario 3: expected passed=855 in baseline, got ${NEW_PASSED}"
222
- fi
223
- else
224
- fail "Scenario 3: update-baseline exited ${EXIT_CODE}"
225
- fi
226
-
227
- # ---------------------------------------------------------------------------
228
- # Scenario 4: list-regressions emits only newly failing tests
229
- # ---------------------------------------------------------------------------
230
- # Given baseline's failing set is {A, B}
231
- # And current failing set is {A, B, C, D}
232
- # When list-regressions runs
233
- # Then stdout lists C and D only (not A, B)
234
-
235
- WORK4="${TMPDIR_ROOT}/s4"
236
- mkdir -p "${WORK4}"
237
- cat > "${WORK4}/test-baseline.json" <<'EOF'
238
- {
239
- "total": 100,
240
- "passed": 98,
241
- "failed": 2,
242
- "skipped": 0,
243
- "updated_at": "2026-01-01T00:00:00Z",
244
- "failing_tests": [
245
- "/fake/test.ts::A",
246
- "/fake/test.ts::B"
247
- ]
248
- }
249
- EOF
250
- # Current: A, B still failing + newly C, D
251
- cat > "${WORK4}/vitest-output.json" <<'EOF'
252
- {
253
- "numTotalTests": 100,
254
- "numPassedTests": 96,
255
- "numFailedTests": 4,
256
- "numPendingTests": 0,
257
- "numTodoTests": 0,
258
- "testResults": [
259
- {
260
- "testFilePath": "/fake/test.ts",
261
- "assertionResults": [
262
- {"status": "failed", "fullName": "A", "title": "A"},
263
- {"status": "failed", "fullName": "B", "title": "B"},
264
- {"status": "failed", "fullName": "C", "title": "C"},
265
- {"status": "failed", "fullName": "D", "title": "D"}
266
- ]
267
- }
268
- ]
269
- }
270
- EOF
271
-
272
- OUTPUT=$(CLEARGATE_REPO_ROOT="${WORK4}" CLEARGATE_TEST_VITEST_JSON="${WORK4}/vitest-output.json" node "${SCRIPT}" list-regressions 2>&1) && EXIT_CODE=0 || EXIT_CODE=$?
273
-
274
- if [[ ${EXIT_CODE} -eq 0 ]]; then
275
- if echo "${OUTPUT}" | grep -q "C" && echo "${OUTPUT}" | grep -q "D"; then
276
- if ! echo "${OUTPUT}" | grep -q "newly failing.*A" && ! (echo "${OUTPUT}" | grep -E "^ - /fake/test.ts::A$"); then
277
- pass "Scenario 4: list-regressions shows C and D, not A and B"
278
- else
279
- # A might appear in the output as part of the count or path — check strictly
280
- # The output line format is " - /fake/test.ts::C" etc
281
- NEW_LINES=$(echo "${OUTPUT}" | grep "^ - " || true)
282
- if echo "${NEW_LINES}" | grep -q "::C" && echo "${NEW_LINES}" | grep -q "::D" && ! echo "${NEW_LINES}" | grep -q "::A" && ! echo "${NEW_LINES}" | grep -q "::B"; then
283
- pass "Scenario 4: list-regressions shows C and D only"
284
- else
285
- fail "Scenario 4: output mismatch. New lines: ${NEW_LINES}"
286
- fi
287
- fi
288
- else
289
- fail "Scenario 4: C or D not found in output: ${OUTPUT}"
290
- fi
291
- else
292
- fail "Scenario 4: list-regressions exited ${EXIT_CODE}"
293
- fi
294
-
295
- # ---------------------------------------------------------------------------
296
- # Scenario 5: SKIP_TEST_RATCHET=1 bypass
297
- # ---------------------------------------------------------------------------
298
- # Given SKIP_TEST_RATCHET=1 env is set
299
- # When the pre-commit hook runs
300
- # Then it prints a bypass warning and exits 0 without running tests
301
-
302
- # Check pre-commit hook script exists in cleargate-planning
303
- HOOK_SCAFFOLD="${REPO_ROOT}/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh"
304
- if [[ ! -f "${HOOK_SCAFFOLD}" ]]; then
305
- fail "Scenario 5: hook scaffold not found at ${HOOK_SCAFFOLD}"
306
- else
307
- OUTPUT=$(SKIP_TEST_RATCHET=1 CLEARGATE_REPO_ROOT="${TMPDIR_ROOT}" bash "${HOOK_SCAFFOLD}" 2>&1) && EXIT_CODE=0 || EXIT_CODE=$?
308
- if [[ ${EXIT_CODE} -eq 0 ]]; then
309
- if echo "${OUTPUT}" | grep -qi "bypass\|SKIP_TEST_RATCHET"; then
310
- pass "Scenario 5: SKIP_TEST_RATCHET=1 exits 0 with bypass message"
311
- else
312
- fail "Scenario 5: exit 0 but no bypass message in output: ${OUTPUT}"
313
- fi
314
- else
315
- fail "Scenario 5: expected exit 0 with SKIP_TEST_RATCHET=1, got ${EXIT_CODE}"
316
- fi
317
- fi
318
-
319
- # ---------------------------------------------------------------------------
320
- # Summary
321
- # ---------------------------------------------------------------------------
322
- echo ""
323
- echo "Results: ${PASS} passed, ${FAIL} failed"
324
- if [[ ${FAIL} -gt 0 ]]; then
325
- exit 1
326
- fi
327
- exit 0
@@ -1,261 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * test_ratchet.mjs — STORY-014-04: Pre-existing Test-Failure Ratchet
4
- *
5
- * Subcommands:
6
- * check (default) re-run suite; exit non-zero if passed < baseline
7
- * update-baseline run suite; overwrite test-baseline.json atomically
8
- * list-regressions diff current failing-tests set vs baseline failing set; print new failures
9
- *
10
- * Scope: cleargate-cli test suite only (EPIC-014 §Q4 lock).
11
- *
12
- * Env overrides:
13
- * CLEARGATE_REPO_ROOT — override repo root (test isolation)
14
- * SKIP_TEST_RATCHET=1 — bypass (documented; discouraged)
15
- *
16
- * CI mode (no-DB):
17
- * DB-dependent suites (bootstrap-root.test.ts) are excluded by default.
18
- * This baseline represents "no-DB CI mode". To include DB suites, set
19
- * RATCHET_INCLUDE_DB=1 and regenerate the baseline with update-baseline.
20
- */
21
-
22
- import { spawnSync } from 'node:child_process';
23
- import fs from 'node:fs';
24
- import path from 'node:path';
25
- import os from 'node:os';
26
-
27
- const REPO_ROOT = process.env.CLEARGATE_REPO_ROOT
28
- ?? path.resolve(new URL('.', import.meta.url).pathname, '../..');
29
-
30
- const BASELINE_PATH = path.join(REPO_ROOT, 'test-baseline.json');
31
-
32
- /**
33
- * DB-dependent test globs excluded from the no-DB CI baseline.
34
- * These suites require a live Postgres connection; they are skipped unless
35
- * RATCHET_INCLUDE_DB=1 is set.
36
- */
37
- const DB_EXCLUDE_GLOBS = [
38
- 'test/commands/bootstrap-root.test.ts',
39
- ];
40
-
41
- // ---------------------------------------------------------------------------
42
- // Run suite and parse vitest JSON output
43
- // ---------------------------------------------------------------------------
44
-
45
- /**
46
- * Spawn vitest in cleargate-cli/ and parse JSON output.
47
- * Returns { total, passed, failed, skipped, failing_tests }
48
- *
49
- * Test isolation: if CLEARGATE_TEST_VITEST_JSON env is set, reads that file
50
- * as a prebuilt vitest JSON result instead of spawning the real suite.
51
- * This allows unit-testing the ratchet logic without running the full suite.
52
- *
53
- * Fix (STORY-014-04 bounce): use --outputFile to write JSON to a temp file
54
- * instead of parsing stdout. Vitest subprocess tests contaminate stdout with
55
- * non-JSON lines (e.g. init_sprint, assert_story_files log output), causing
56
- * JSON.parse(result.stdout) to fail. Writing to a file via --outputFile
57
- * eliminates stdout-contamination deterministically.
58
- */
59
- function runSuite() {
60
- // Test seam: inject prebuilt vitest JSON via env (test isolation only)
61
- if (process.env.CLEARGATE_TEST_VITEST_JSON) {
62
- let json;
63
- try {
64
- json = JSON.parse(fs.readFileSync(process.env.CLEARGATE_TEST_VITEST_JSON, 'utf8'));
65
- } catch (e) {
66
- process.stderr.write(`test_ratchet: failed to read CLEARGATE_TEST_VITEST_JSON: ${e.message}\n`);
67
- process.exit(2);
68
- }
69
- return parseVitestJson(json);
70
- }
71
-
72
- const cliDir = path.join(REPO_ROOT, 'cleargate-cli');
73
- const outputFile = path.join(os.tmpdir(), `vitest-result-${process.pid}.json`);
74
-
75
- // Build vitest args: write JSON to a temp file to avoid stdout contamination.
76
- // --passWithNoTests: exit 0 even if all tests are excluded (needed when the
77
- // exclude list matches all discovered test files in a fresh repo).
78
- const vitestArgs = [
79
- 'vitest', 'run',
80
- '--reporter=json',
81
- `--outputFile=${outputFile}`,
82
- '--passWithNoTests',
83
- ];
84
-
85
- // Exclude DB-dependent suites unless RATCHET_INCLUDE_DB=1 is set.
86
- if (!process.env.RATCHET_INCLUDE_DB) {
87
- for (const glob of DB_EXCLUDE_GLOBS) {
88
- vitestArgs.push('--exclude', glob);
89
- }
90
- }
91
-
92
- const result = spawnSync(
93
- 'npx',
94
- vitestArgs,
95
- {
96
- cwd: cliDir,
97
- // stdout/stderr are inherited to console — we read results from the file
98
- maxBuffer: 10 * 1024 * 1024,
99
- timeout: 120_000,
100
- encoding: 'utf8',
101
- env: { ...process.env },
102
- },
103
- );
104
-
105
- if (result.error) {
106
- process.stderr.write(`test_ratchet: vitest spawn error: ${result.error.message}\n`);
107
- process.exit(2);
108
- }
109
-
110
- // vitest exits non-zero when tests fail — that is expected for the ratchet.
111
- // Parse the output file regardless of exit code.
112
- let json;
113
- try {
114
- const raw = fs.readFileSync(outputFile, 'utf8');
115
- json = JSON.parse(raw);
116
- } catch (e) {
117
- process.stderr.write(`test_ratchet: failed to read/parse vitest output file at ${outputFile}: ${String(e)}\n`);
118
- process.stderr.write(`vitest stderr (first 500 chars): ${String(result.stderr).slice(0, 500)}\n`);
119
- process.exit(2);
120
- } finally {
121
- // Clean up temp file regardless of parse success
122
- try { fs.unlinkSync(outputFile); } catch { /* ignore */ }
123
- }
124
-
125
- return parseVitestJson(json);
126
- }
127
-
128
- /**
129
- * Parse a vitest 2.x JSON result object into our internal shape.
130
- */
131
- function parseVitestJson(json) {
132
- const total = json.numTotalTests ?? 0;
133
- const passed = json.numPassedTests ?? 0;
134
- const failed = json.numFailedTests ?? 0;
135
- const skipped = (json.numPendingTests ?? 0) + (json.numTodoTests ?? 0);
136
-
137
- // Collect failing test names for list-regressions
138
- const failing_tests = [];
139
- for (const testFile of (json.testResults ?? [])) {
140
- for (const assertion of (testFile.assertionResults ?? [])) {
141
- if (assertion.status === 'failed') {
142
- failing_tests.push(`${testFile.testFilePath ?? ''}::${assertion.fullName ?? assertion.title ?? ''}`);
143
- }
144
- }
145
- }
146
-
147
- return { total, passed, failed, skipped, failing_tests };
148
- }
149
-
150
- // ---------------------------------------------------------------------------
151
- // Baseline read/write
152
- // ---------------------------------------------------------------------------
153
-
154
- function readBaseline() {
155
- if (!fs.existsSync(BASELINE_PATH)) {
156
- process.stderr.write(`test_ratchet: baseline file not found at ${BASELINE_PATH}\n`);
157
- process.stderr.write(`Run: node .cleargate/scripts/test_ratchet.mjs update-baseline\n`);
158
- process.exit(2);
159
- }
160
- try {
161
- return JSON.parse(fs.readFileSync(BASELINE_PATH, 'utf8'));
162
- } catch (e) {
163
- process.stderr.write(`test_ratchet: failed to parse baseline file: ${e.message}\n`);
164
- process.exit(2);
165
- }
166
- }
167
-
168
- /**
169
- * Atomic write via tmp+rename (pattern from init_sprint.mjs:83-85).
170
- * Key order is frozen to { total, passed, failed, skipped, updated_at, failing_tests }.
171
- */
172
- function writeBaseline(data) {
173
- const payload = {
174
- total: data.total,
175
- passed: data.passed,
176
- failed: data.failed,
177
- skipped: data.skipped,
178
- updated_at: new Date().toISOString(),
179
- failing_tests: data.failing_tests,
180
- };
181
- const tmp = `${BASELINE_PATH}.tmp.${process.pid}`;
182
- fs.writeFileSync(tmp, JSON.stringify(payload, null, 2) + '\n', 'utf8');
183
- fs.renameSync(tmp, BASELINE_PATH);
184
- return payload;
185
- }
186
-
187
- // ---------------------------------------------------------------------------
188
- // Subcommands
189
- // ---------------------------------------------------------------------------
190
-
191
- function cmdUpdateBaseline() {
192
- process.stdout.write('test_ratchet: running suite to regenerate baseline...\n');
193
- const current = runSuite();
194
- const written = writeBaseline(current);
195
- process.stdout.write(
196
- `test_ratchet: baseline updated — total=${written.total} passed=${written.passed} failed=${written.failed} skipped=${written.skipped}\n`,
197
- );
198
- process.exit(0);
199
- }
200
-
201
- function cmdCheck() {
202
- process.stdout.write('test_ratchet: running suite for ratchet check...\n');
203
- const current = runSuite();
204
- const baseline = readBaseline();
205
-
206
- const delta = current.passed - baseline.passed;
207
- if (delta >= 0) {
208
- process.stdout.write(
209
- `test_ratchet: OK — +${delta} tests passing (current=${current.passed}, baseline=${baseline.passed})\n`,
210
- );
211
- process.exit(0);
212
- } else {
213
- process.stderr.write(
214
- `test_ratchet: regression: ${delta} tests (current=${current.passed}, baseline=${baseline.passed})\n`,
215
- );
216
- process.stderr.write(
217
- `Fix failing tests or run 'node .cleargate/scripts/test_ratchet.mjs update-baseline' to accept the new state.\n`,
218
- );
219
- process.exit(1);
220
- }
221
- }
222
-
223
- function cmdListRegressions() {
224
- process.stdout.write('test_ratchet: running suite for regression diff...\n');
225
- const current = runSuite();
226
- const baseline = readBaseline();
227
-
228
- const baselineSet = new Set(baseline.failing_tests ?? []);
229
- const newlyFailing = (current.failing_tests ?? []).filter((t) => !baselineSet.has(t));
230
-
231
- if (newlyFailing.length === 0) {
232
- process.stdout.write('test_ratchet: no new regressions (no tests newly failing).\n');
233
- } else {
234
- process.stdout.write(`test_ratchet: ${newlyFailing.length} newly failing test(s):\n`);
235
- for (const t of newlyFailing) {
236
- process.stdout.write(` - ${t}\n`);
237
- }
238
- }
239
- process.exit(0);
240
- }
241
-
242
- // ---------------------------------------------------------------------------
243
- // Entry point
244
- // ---------------------------------------------------------------------------
245
-
246
- const [, , subcommand = 'check'] = process.argv;
247
-
248
- switch (subcommand) {
249
- case 'update-baseline':
250
- cmdUpdateBaseline();
251
- break;
252
- case 'check':
253
- cmdCheck();
254
- break;
255
- case 'list-regressions':
256
- cmdListRegressions();
257
- break;
258
- default:
259
- process.stderr.write(`test_ratchet: unknown subcommand '${subcommand}'. Use: check | update-baseline | list-regressions\n`);
260
- process.exit(2);
261
- }