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,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
|
-
}
|