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
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env bash
2
+ # cr080_wrapper.test.sh — CR-080 verification harness.
3
+ #
4
+ # Verifies F5 (pre_gate_runner.sh realpath-at-entry) and F8 (run_script.sh
5
+ # env pass-through) are correctly implemented.
6
+ #
7
+ # Assertions:
8
+ # F5-1: Relative worktree path — report written to correct location, no ENOENT.
9
+ # F5-2: Absolute worktree path — identical report location, no path doubling.
10
+ # F8-3: CLEARGATE_STATE_FILE exported before wrapper — reaches the child process.
11
+ # F8-4 (optional allowlist): RUN_SCRIPT_ENV_ALLOWLIST=FOO prevents non-listed
12
+ # var from being actively required (documents opt-in behavior).
13
+ #
14
+ # Exit 0 = PASS (all assertions pass); exit 1 = one or more FAIL.
15
+ #
16
+ # Self-cleaning: trap EXIT removes the stub worktree and any tmp files.
17
+ # Mirrors the cr077/cr079 harness shape.
18
+ # macOS bash 3.2 portable.
19
+ # FLASHCARD #test-harness #bash 2026-06-03: use grep -q not grep -c || echo.
20
+ set -uo pipefail
21
+
22
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
24
+
25
+ PASS=0
26
+ FAIL=0
27
+
28
+ pass() {
29
+ echo "PASS: $1"
30
+ PASS=$((PASS + 1))
31
+ }
32
+
33
+ fail() {
34
+ echo "FAIL: $1"
35
+ echo " detail: $2"
36
+ FAIL=$((FAIL + 1))
37
+ }
38
+
39
+ # ── Constants ────────────────────────────────────────────────────────────────
40
+ TEST_STUB_DIR="${REPO_ROOT}/.worktrees/CR-080-smoke"
41
+ PRE_GATE_SCRIPT="${REPO_ROOT}/.cleargate/scripts/pre_gate_runner.sh"
42
+ RUN_SCRIPT="${REPO_ROOT}/.cleargate/scripts/run_script.sh"
43
+
44
+ # Temp file for F8 env probe
45
+ TMP_STATE_FILE="/tmp/cr080-probe-$$.json"
46
+
47
+ # ── Teardown / trap ───────────────────────────────────────────────────────────
48
+ cleanup() {
49
+ # Remove stub worktree directory
50
+ if [[ -d "${TEST_STUB_DIR}" ]]; then
51
+ rm -rf "${TEST_STUB_DIR}"
52
+ fi
53
+ # Remove tmp files
54
+ rm -f "${TMP_STATE_FILE}" 2>/dev/null || true
55
+ }
56
+ trap cleanup EXIT
57
+
58
+ # ── Pre-checks ───────────────────────────────────────────────────────────────
59
+ if [[ ! -f "${PRE_GATE_SCRIPT}" ]]; then
60
+ fail "pre-check" "pre_gate_runner.sh not found at ${PRE_GATE_SCRIPT}"
61
+ echo ""
62
+ echo "cr080_wrapper.test.sh: ${PASS} passed, ${FAIL} failed"
63
+ exit 1
64
+ fi
65
+
66
+ if [[ ! -f "${RUN_SCRIPT}" ]]; then
67
+ fail "pre-check" "run_script.sh not found at ${RUN_SCRIPT}"
68
+ echo ""
69
+ echo "cr080_wrapper.test.sh: ${PASS} passed, ${FAIL} failed"
70
+ exit 1
71
+ fi
72
+
73
+ # ── Setup: create a stub worktree dir with the expected .cleargate structure ──
74
+ # We use a simple stub directory rather than a real git worktree because the
75
+ # pre_gate_runner.sh only requires the directory to exist for path normalization;
76
+ # the typecheck/test/stray checks are skipped when no package.json is present.
77
+ rm -rf "${TEST_STUB_DIR}" 2>/dev/null || true
78
+ mkdir -p "${TEST_STUB_DIR}/.cleargate/reports"
79
+
80
+ # ────────────────────────────────────────────────────────────────────────────
81
+ # ASSERTION F5-1: Relative worktree path writes report to correct location
82
+ # ────────────────────────────────────────────────────────────────────────────
83
+ # Run from REPO_ROOT with a relative path (.worktrees/CR-080-smoke).
84
+ # The report should land at <absolute-stub-dir>/.cleargate/reports/pre-qa-scan.txt.
85
+
86
+ RELATIVE_PATH=".worktrees/CR-080-smoke"
87
+ # Use qa mode (matches CR §4 verification command); report file = pre-qa-scan.txt.
88
+ # qa mode skips typecheck + test when no package.json is present in the worktree.
89
+ EXPECTED_REPORT="${TEST_STUB_DIR}/.cleargate/reports/pre-qa-scan.txt"
90
+
91
+ # Remove any stale report from a previous run
92
+ rm -f "${EXPECTED_REPORT}" 2>/dev/null || true
93
+
94
+ # Run from repo root with the RELATIVE path (the bug scenario).
95
+ # We use a non-existent branch (sprint/S-99) — pre_gate_runner.sh tolerates it
96
+ # (qa mode skips package.json checks when no package.json exists).
97
+ cd "${REPO_ROOT}"
98
+ bash "${PRE_GATE_SCRIPT}" qa "${RELATIVE_PATH}" "sprint/S-99" > /dev/null 2>&1 || true
99
+
100
+ if [[ -f "${EXPECTED_REPORT}" ]]; then
101
+ pass "F5-1: relative-worktree-path — report written at correct absolute location"
102
+ else
103
+ fail "F5-1: relative-worktree-path — report not found at ${EXPECTED_REPORT}" \
104
+ "expected report at ${EXPECTED_REPORT} after running with relative path ${RELATIVE_PATH}"
105
+ fi
106
+
107
+ # ────────────────────────────────────────────────────────────────────────────
108
+ # ASSERTION F5-2: Absolute worktree path writes report to the same location
109
+ # ────────────────────────────────────────────────────────────────────────────
110
+ # Run from repo root with ABSOLUTE path — identical report location expected.
111
+
112
+ rm -f "${EXPECTED_REPORT}" 2>/dev/null || true
113
+
114
+ cd "${REPO_ROOT}"
115
+ bash "${PRE_GATE_SCRIPT}" qa "${TEST_STUB_DIR}" "sprint/S-99" > /dev/null 2>&1 || true
116
+
117
+ if [[ -f "${EXPECTED_REPORT}" ]]; then
118
+ pass "F5-2: absolute-worktree-path — report written at correct location"
119
+ else
120
+ fail "F5-2: absolute-worktree-path — report not found at ${EXPECTED_REPORT}" \
121
+ "expected report at ${EXPECTED_REPORT} after running with absolute path ${TEST_STUB_DIR}"
122
+ fi
123
+
124
+ # Regression: verify no path-doubling (the ENOENT symptom was a doubled path like
125
+ # <worktree>/<worktree>/.cleargate/... — which can't exist). The report is at the
126
+ # expected location = no doubling occurred. Verified implicitly by F5-1 passing
127
+ # from a relative-path invocation (the bug only triggered on relative paths).
128
+
129
+ # ────────────────────────────────────────────────────────────────────────────
130
+ # ASSERTION F8-3: CLEARGATE_STATE_FILE exported before wrapper reaches child
131
+ # ────────────────────────────────────────────────────────────────────────────
132
+ # Export the var, then run run_script.sh wrapping a node -e that reads it.
133
+ # The child should print the value, not "MISSING".
134
+
135
+ PROBE_RESULT="$(
136
+ export CLEARGATE_STATE_FILE="${TMP_STATE_FILE}"
137
+ bash "${RUN_SCRIPT}" node -e 'process.stdout.write(process.env.CLEARGATE_STATE_FILE || "MISSING")' 2>/dev/null
138
+ )"
139
+
140
+ if [[ "${PROBE_RESULT}" = "${TMP_STATE_FILE}" ]]; then
141
+ pass "F8-3: CLEARGATE_STATE_FILE forwarded to child — got '${PROBE_RESULT}'"
142
+ else
143
+ fail "F8-3: CLEARGATE_STATE_FILE not forwarded to child" \
144
+ "expected '${TMP_STATE_FILE}', got '${PROBE_RESULT}'"
145
+ fi
146
+
147
+ # ────────────────────────────────────────────────────────────────────────────
148
+ # ASSERTION F8-4 (optional allowlist): RUN_SCRIPT_ENV_ALLOWLIST is opt-in only
149
+ # ────────────────────────────────────────────────────────────────────────────
150
+ # When RUN_SCRIPT_ENV_ALLOWLIST is set to "FOO", a var NOT in the allowlist
151
+ # (e.g. CLEARGATE_STATE_FILE) should still reach the child because the
152
+ # allowlist is advisory/documentation-only — the current implementation does
153
+ # NOT strip the env (it only populates _allowed_env for future use).
154
+ # This assertion documents that the default pass-through is preserved even
155
+ # when the allowlist var is set: the child still sees CLEARGATE_STATE_FILE.
156
+
157
+ PROBE_ALLOWLIST_RESULT="$(
158
+ export CLEARGATE_STATE_FILE="${TMP_STATE_FILE}"
159
+ export RUN_SCRIPT_ENV_ALLOWLIST="FOO"
160
+ bash "${RUN_SCRIPT}" node -e 'process.stdout.write(process.env.CLEARGATE_STATE_FILE || "MISSING")' 2>/dev/null
161
+ )"
162
+
163
+ if [[ "${PROBE_ALLOWLIST_RESULT}" = "${TMP_STATE_FILE}" ]]; then
164
+ pass "F8-4: allowlist opt-in — full pass-through preserved when RUN_SCRIPT_ENV_ALLOWLIST set"
165
+ else
166
+ fail "F8-4: allowlist opt-in — CLEARGATE_STATE_FILE unexpectedly dropped" \
167
+ "expected '${TMP_STATE_FILE}', got '${PROBE_ALLOWLIST_RESULT}'"
168
+ fi
169
+
170
+ # ── Summary ───────────────────────────────────────────────────────────────────
171
+ echo ""
172
+ echo "cr080_wrapper.test.sh: ${PASS} passed, ${FAIL} failed"
173
+
174
+ if [[ $FAIL -gt 0 ]]; then
175
+ exit 1
176
+ fi
177
+ exit 0
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env bash
2
+ # cr081_qa_red_lint.red.sh — CR-081 QA-Red semantic-fixture lint harness.
3
+ #
4
+ # RED: fails against clean baseline — qa_red_lint.mjs does not yet exist,
5
+ # pre_gate_runner.sh has no qa_red_lint invocation, and qa.md has no
6
+ # red-now-green clause. All six scenarios therefore FAIL.
7
+ # GREEN: passes after Developer implements CR-081 (qa_red_lint.mjs created,
8
+ # pre_gate_runner.sh wired, qa.md red-now-green clause added).
9
+ #
10
+ # Scenarios (per M3 plan CR-081 §test / dispatch §Scenarios):
11
+ # 1. R-enum positive: fixture with out-of-set literal → exit non-zero + R-enum message
12
+ # 2. R-query positive: fixture with duplicate-text queryByText → exit non-zero + R-query message
13
+ # 3. Negative — clean: valid literal + unique query → exit 0 (no false positive)
14
+ # 4. Negative — NON-APPLICABLE (CRITICAL): plain node:test (assert-based, no Pydantic
15
+ # Literal, no queryByText) shaped like CR-082's own close-gate test → exit 0.
16
+ # Proves qa_red_lint won't phantom-flag CR-082's own *.red.node.test.ts when it goes live.
17
+ # 5. Wiring grep: grep -q "qa_red_lint" .cleargate/scripts/pre_gate_runner.sh
18
+ # 6. qa.md red-now-green clause grep
19
+ #
20
+ # Self-cleaning: trap EXIT removes any mktemp fixture dirs.
21
+ # macOS bash 3.2 portable.
22
+ # FLASHCARD #test-harness #bash 2026-06-03: use grep -q (not grep -c || echo 0).
23
+ # FLASHCARD #pre-gate #live-on-merge #qa-red-lint 2026-06-04: Class-3 pre-gate
24
+ # checks MUST exit 0 on non-applicable files (plain node:test, no Pydantic/RTL).
25
+ #
26
+ # Exit 0 = PASS (all scenarios pass); exit non-zero = one or more FAIL.
27
+ set -uo pipefail
28
+
29
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
31
+
32
+ PASS=0
33
+ FAIL=0
34
+
35
+ pass() {
36
+ echo "PASS: $1"
37
+ PASS=$((PASS + 1))
38
+ }
39
+
40
+ fail() {
41
+ echo "FAIL: $1"
42
+ echo " detail: $2"
43
+ FAIL=$((FAIL + 1))
44
+ }
45
+
46
+ # ── Constants ─────────────────────────────────────────────────────────────────
47
+ LINT_SCRIPT="${REPO_ROOT}/.cleargate/scripts/qa_red_lint.mjs"
48
+ PRE_GATE_SCRIPT="${REPO_ROOT}/.cleargate/scripts/pre_gate_runner.sh"
49
+ QA_MD="${REPO_ROOT}/cleargate-planning/.claude/agents/qa.md"
50
+
51
+ # ── Fixture temp dirs — tracked for cleanup ───────────────────────────────────
52
+ FIXTURE_DIR_ENUM=""
53
+ FIXTURE_DIR_QUERY=""
54
+ FIXTURE_DIR_CLEAN=""
55
+ FIXTURE_DIR_NONAPPLICABLE=""
56
+
57
+ cleanup() {
58
+ [[ -n "${FIXTURE_DIR_ENUM}" ]] && rm -rf "${FIXTURE_DIR_ENUM}"
59
+ [[ -n "${FIXTURE_DIR_QUERY}" ]] && rm -rf "${FIXTURE_DIR_QUERY}"
60
+ [[ -n "${FIXTURE_DIR_CLEAN}" ]] && rm -rf "${FIXTURE_DIR_CLEAN}"
61
+ [[ -n "${FIXTURE_DIR_NONAPPLICABLE}" ]] && rm -rf "${FIXTURE_DIR_NONAPPLICABLE}"
62
+ }
63
+ trap cleanup EXIT
64
+
65
+ # ── Pre-check: qa_red_lint.mjs must exist for scenarios 1-4 ──────────────────
66
+ # On clean baseline it does NOT exist → scenarios 1-4 fail with "script absent".
67
+ LINT_PRESENT=0
68
+ if [[ -f "${LINT_SCRIPT}" ]]; then
69
+ LINT_PRESENT=1
70
+ fi
71
+
72
+ # ═════════════════════════════════════════════════════════════════════════════
73
+ # SCENARIO 1: R-enum positive
74
+ # A fixture with an out-of-set Literal — mirrors DeckSpec(theme="dark") against
75
+ # Literal["technical-dark","business-warm","minimal-light"].
76
+ # We write a .py file with BOTH the Literal declaration AND the bad constructor
77
+ # call statically visible, plus a .ts mirror with a TS string-union + bad prop.
78
+ # Expected: qa_red_lint.mjs exits NON-ZERO and stderr mentions "dark" + R-enum.
79
+ # ═════════════════════════════════════════════════════════════════════════════
80
+ echo "--- Scenario 1: R-enum positive ---"
81
+
82
+ FIXTURE_DIR_ENUM="$(mktemp -d)"
83
+
84
+ # Python fixture: Literal declaration + out-of-set constructor call
85
+ cat > "${FIXTURE_DIR_ENUM}/deck_spec_fixture.red.node.test.ts" << 'PYEOF'
86
+ # fixture: deck_spec with invalid enum literal
87
+ # Simulates a QA-Red test that constructs a typed model with a bad literal.
88
+ #
89
+ # Pydantic-style Literal declaration (statically visible in this file):
90
+ # theme: Literal["technical-dark", "business-warm", "minimal-light"]
91
+ #
92
+ # Constructor call with out-of-set value (R-enum violation):
93
+ # DeckSpec(theme="dark") # "dark" is NOT in ["technical-dark","business-warm","minimal-light"]
94
+
95
+ from typing import Literal
96
+ from pydantic import BaseModel
97
+
98
+ class DeckSpec(BaseModel):
99
+ theme: Literal["technical-dark", "business-warm", "minimal-light"]
100
+
101
+ # This is the bad fixture line — "dark" is NOT a valid member of the Literal set.
102
+ spec = DeckSpec(theme="dark") # R-enum: "dark" not in Literal set
103
+ PYEOF
104
+
105
+ if [[ "${LINT_PRESENT}" -eq 0 ]]; then
106
+ fail "scenario-1-r-enum-positive" \
107
+ "qa_red_lint.mjs absent at ${LINT_SCRIPT} — cannot run; baseline FAIL as expected (RED)"
108
+ else
109
+ LINT_OUT="$(node "${LINT_SCRIPT}" "${FIXTURE_DIR_ENUM}" 2>&1)" || LINT_EXIT=$?
110
+ LINT_EXIT="${LINT_EXIT:-0}"
111
+ if [[ "${LINT_EXIT}" -ne 0 ]] && echo "${LINT_OUT}" | grep -qi "dark"; then
112
+ pass "scenario-1-r-enum-positive — exit non-zero + R-enum message names 'dark'"
113
+ elif [[ "${LINT_EXIT}" -eq 0 ]]; then
114
+ fail "scenario-1-r-enum-positive" \
115
+ "qa_red_lint.mjs exited 0 (expected non-zero) — R-enum rule not triggered for out-of-set 'dark'"
116
+ else
117
+ fail "scenario-1-r-enum-positive" \
118
+ "qa_red_lint.mjs exited non-zero but stderr did not mention 'dark' — R-enum message missing or wrong. Output: ${LINT_OUT}"
119
+ fi
120
+ fi
121
+
122
+ # ═════════════════════════════════════════════════════════════════════════════
123
+ # SCENARIO 2: R-query positive
124
+ # A fixture (.tsx) where 'Connected' appears on TWO render-input rows AND a
125
+ # queryByText('Connected') call targets it.
126
+ # Expected: exit NON-ZERO + R-query message recommends queryAllByText/getByTestId.
127
+ # ═════════════════════════════════════════════════════════════════════════════
128
+ echo "--- Scenario 2: R-query positive ---"
129
+
130
+ FIXTURE_DIR_QUERY="$(mktemp -d)"
131
+
132
+ cat > "${FIXTURE_DIR_QUERY}/status_table_fixture.red.node.test.ts" << 'QEOF'
133
+ // fixture: status table with duplicate text — R-query violation
134
+ // Both postgres and redis rows have detail: 'Connected', so queryByText('Connected')
135
+ // will throw "Found multiple elements" at runtime.
136
+
137
+ import { render } from '@testing-library/react';
138
+ import StatusTable from '../StatusTable';
139
+
140
+ const rows = [
141
+ { service: 'postgres', status: 'ok', detail: 'Connected' },
142
+ { service: 'redis', status: 'ok', detail: 'Connected' }, // duplicate detail text
143
+ ];
144
+
145
+ test('shows connected status', () => {
146
+ render(<StatusTable rows={rows} />);
147
+ // R-query violation: 'Connected' appears on >=2 rows in the render input above.
148
+ // queryByText will throw "Found multiple elements with text 'Connected'".
149
+ const el = queryByText('Connected'); // bad — duplicate text, use queryAllByText('Connected')[0]
150
+ expect(el).toBeTruthy();
151
+ });
152
+ QEOF
153
+
154
+ if [[ "${LINT_PRESENT}" -eq 0 ]]; then
155
+ fail "scenario-2-r-query-positive" \
156
+ "qa_red_lint.mjs absent at ${LINT_SCRIPT} — cannot run; baseline FAIL as expected (RED)"
157
+ else
158
+ LINT_OUT2="$(node "${LINT_SCRIPT}" "${FIXTURE_DIR_QUERY}" 2>&1)" || LINT_EXIT2=$?
159
+ LINT_EXIT2="${LINT_EXIT2:-0}"
160
+ if [[ "${LINT_EXIT2}" -ne 0 ]] && \
161
+ (echo "${LINT_OUT2}" | grep -qiE "queryAllByText|getByTestId|R-query"); then
162
+ pass "scenario-2-r-query-positive — exit non-zero + R-query recommendation present"
163
+ elif [[ "${LINT_EXIT2}" -eq 0 ]]; then
164
+ fail "scenario-2-r-query-positive" \
165
+ "qa_red_lint.mjs exited 0 (expected non-zero) — R-query rule not triggered for duplicate 'Connected' + queryByText"
166
+ else
167
+ fail "scenario-2-r-query-positive" \
168
+ "qa_red_lint.mjs exited non-zero but R-query recommendation (queryAllByText/getByTestId) not in output. Output: ${LINT_OUT2}"
169
+ fi
170
+ fi
171
+
172
+ # ═════════════════════════════════════════════════════════════════════════════
173
+ # SCENARIO 3: Negative — clean (no false positive)
174
+ # A fixture with a valid in-set literal + a uniquely-matched queryByText.
175
+ # Expected: exit 0 (no flags).
176
+ # ═════════════════════════════════════════════════════════════════════════════
177
+ echo "--- Scenario 3: Negative — clean (no false positive) ---"
178
+
179
+ FIXTURE_DIR_CLEAN="$(mktemp -d)"
180
+
181
+ cat > "${FIXTURE_DIR_CLEAN}/clean_fixture.red.node.test.ts" << 'CEOF'
182
+ // fixture: clean — valid literal + unique query text
183
+ // theme="technical-dark" IS in Literal["technical-dark","business-warm","minimal-light"].
184
+ // 'Postgres' appears exactly once in the render input, so getByText('Postgres') is safe.
185
+
186
+ from typing import Literal
187
+ from pydantic import BaseModel
188
+
189
+ class DeckSpec(BaseModel):
190
+ theme: Literal["technical-dark", "business-warm", "minimal-light"]
191
+
192
+ # Valid in-set value — no R-enum violation
193
+ spec = DeckSpec(theme="technical-dark")
194
+
195
+ const rows = [
196
+ { service: 'postgres', status: 'ok', detail: 'Postgres' }, // unique detail
197
+ { service: 'redis', status: 'ok', detail: 'Redis' }, // different text
198
+ ];
199
+
200
+ test('shows postgres row', () => {
201
+ render(<StatusTable rows={rows} />);
202
+ // Safe: 'Postgres' appears exactly once in the render input above.
203
+ const el = getByText('Postgres');
204
+ expect(el).toBeTruthy();
205
+ });
206
+ CEOF
207
+
208
+ if [[ "${LINT_PRESENT}" -eq 0 ]]; then
209
+ fail "scenario-3-negative-clean" \
210
+ "qa_red_lint.mjs absent at ${LINT_SCRIPT} — cannot run; baseline FAIL as expected (RED)"
211
+ else
212
+ node "${LINT_SCRIPT}" "${FIXTURE_DIR_CLEAN}" > /dev/null 2>&1
213
+ CLEAN_EXIT=$?
214
+ if [[ "${CLEAN_EXIT}" -eq 0 ]]; then
215
+ pass "scenario-3-negative-clean — exit 0 (no false positive on valid literal + unique query)"
216
+ else
217
+ fail "scenario-3-negative-clean" \
218
+ "qa_red_lint.mjs exited ${CLEAN_EXIT} (expected 0) — false positive on clean fixture"
219
+ fi
220
+ fi
221
+
222
+ # ═════════════════════════════════════════════════════════════════════════════
223
+ # SCENARIO 4: Negative — NON-APPLICABLE file (CRITICAL live-on-merge safety)
224
+ # A plain node:test file using only assert, no Pydantic Literal, no queryByText/
225
+ # getByText — shaped exactly like CR-082's own close_sprint.deferred-verify.red.node.test.ts.
226
+ # Expected: exit 0. Proves qa_red_lint won't phantom-flag CR-082's red test on merge.
227
+ # THIS SCENARIO IS MANDATORY.
228
+ # ═════════════════════════════════════════════════════════════════════════════
229
+ echo "--- Scenario 4: Negative — NON-APPLICABLE file (CRITICAL) ---"
230
+
231
+ FIXTURE_DIR_NONAPPLICABLE="$(mktemp -d)"
232
+
233
+ # Write a file that is intentionally shaped like CR-082's own node:test:
234
+ # - uses node:test + assert only
235
+ # - no import from pydantic, no Literal[...], no string-union type
236
+ # - no queryByText, getByText, queryAllByText
237
+ # - no RTL imports
238
+ cat > "${FIXTURE_DIR_NONAPPLICABLE}/close_sprint.deferred-verify.red.node.test.ts" << 'NAEOF'
239
+ // close_sprint.deferred-verify.red.node.test.ts
240
+ // CR-082 QA-Red node:test — shaped as a plain node:test + assert file.
241
+ // No Pydantic Literal, no queryByText/getByText, no RTL — NOT a QA-Red lint target.
242
+
243
+ import { describe, it } from 'node:test';
244
+ import assert from 'node:assert/strict';
245
+ import { execSync } from 'node:child_process';
246
+ import path from 'node:path';
247
+
248
+ const SCRIPT = path.resolve(import.meta.dirname, '../../close_sprint.mjs');
249
+
250
+ describe('close_sprint deferred-verification gate (Step 2.9)', () => {
251
+ it('declared + NO result file → exits non-zero', () => {
252
+ // Env-seam: CLEARGATE_FORCE_DEFERRED_VERIFY drives the test without real files
253
+ const env = {
254
+ ...process.env,
255
+ CLEARGATE_FORCE_DEFERRED_VERIFY: JSON.stringify({
256
+ 'STORY-099-01': { declared: [{ command: 'docker build .', blocks: 'close' }], result: null }
257
+ }),
258
+ CLEARGATE_SKIP_WORKTREE_CHECK: '1',
259
+ CLEARGATE_SKIP_MERGE_CHECK: '1',
260
+ };
261
+ let threw = false;
262
+ try {
263
+ execSync(`node ${SCRIPT} SPRINT-00`, { env, stdio: 'pipe' });
264
+ } catch {
265
+ threw = true;
266
+ }
267
+ assert.ok(threw, 'Expected close_sprint.mjs to exit non-zero when deferred result is absent');
268
+ });
269
+
270
+ it('declared + green result → gate passes', () => {
271
+ const env = {
272
+ ...process.env,
273
+ CLEARGATE_FORCE_DEFERRED_VERIFY: JSON.stringify({
274
+ 'STORY-099-01': { declared: [{ command: 'docker build .', blocks: 'close' }], result: 'green' }
275
+ }),
276
+ CLEARGATE_SKIP_WORKTREE_CHECK: '1',
277
+ CLEARGATE_SKIP_MERGE_CHECK: '1',
278
+ };
279
+ // Should NOT throw — gate passes on green
280
+ assert.doesNotThrow(() => {
281
+ execSync(`node ${SCRIPT} SPRINT-00`, { env, stdio: 'pipe' });
282
+ });
283
+ });
284
+
285
+ it('none declared → silent no-op, continues', () => {
286
+ const env = {
287
+ ...process.env,
288
+ CLEARGATE_FORCE_DEFERRED_VERIFY: JSON.stringify({}),
289
+ CLEARGATE_SKIP_WORKTREE_CHECK: '1',
290
+ CLEARGATE_SKIP_MERGE_CHECK: '1',
291
+ };
292
+ // No deferred entries declared → gate is a no-op → should not throw
293
+ assert.doesNotThrow(() => {
294
+ execSync(`node ${SCRIPT} SPRINT-00`, { env, stdio: 'pipe' });
295
+ });
296
+ });
297
+ });
298
+ NAEOF
299
+
300
+ if [[ "${LINT_PRESENT}" -eq 0 ]]; then
301
+ fail "scenario-4-nonapplicable" \
302
+ "qa_red_lint.mjs absent at ${LINT_SCRIPT} — cannot run; baseline FAIL as expected (RED)"
303
+ else
304
+ node "${LINT_SCRIPT}" "${FIXTURE_DIR_NONAPPLICABLE}" > /dev/null 2>&1
305
+ NA_EXIT=$?
306
+ if [[ "${NA_EXIT}" -eq 0 ]]; then
307
+ pass "scenario-4-nonapplicable — exit 0 on plain node:test file (no phantom R-enum/R-query flag)"
308
+ else
309
+ fail "scenario-4-nonapplicable" \
310
+ "qa_red_lint.mjs exited ${NA_EXIT} (expected 0) — CRITICAL: false-flagged CR-082's own node:test shape; live-on-merge self-block risk"
311
+ fi
312
+ fi
313
+
314
+ # ═════════════════════════════════════════════════════════════════════════════
315
+ # SCENARIO 5: Wiring grep — qa_red_lint invoked in pre_gate_runner.sh run_arch()
316
+ # Expected: grep -q "qa_red_lint" .cleargate/scripts/pre_gate_runner.sh → 0
317
+ # FAILS on clean baseline (not yet wired).
318
+ # ═════════════════════════════════════════════════════════════════════════════
319
+ echo "--- Scenario 5: Wiring grep ---"
320
+
321
+ if grep -q "qa_red_lint" "${PRE_GATE_SCRIPT}" 2>/dev/null; then
322
+ pass "scenario-5-wiring-grep — 'qa_red_lint' found in pre_gate_runner.sh"
323
+ else
324
+ fail "scenario-5-wiring-grep" \
325
+ "'qa_red_lint' not found in ${PRE_GATE_SCRIPT} — CR-081 wiring not yet applied (expected RED)"
326
+ fi
327
+
328
+ # ═════════════════════════════════════════════════════════════════════════════
329
+ # SCENARIO 6: qa.md red-now-green clause grep
330
+ # Expected: grep -qiE "red-now-green|now PASSES|failing at baseline" in qa.md → 0
331
+ # FAILS on clean baseline (clause not yet added).
332
+ # ═════════════════════════════════════════════════════════════════════════════
333
+ echo "--- Scenario 6: qa.md red-now-green clause ---"
334
+
335
+ if grep -qiE "red-now-green|now PASSES|failing at baseline" "${QA_MD}" 2>/dev/null; then
336
+ pass "scenario-6-qa-md-clause — red-now-green clause found in qa.md"
337
+ else
338
+ fail "scenario-6-qa-md-clause" \
339
+ "red-now-green clause not found in ${QA_MD} — CR-081 qa.md edit not yet applied (expected RED)"
340
+ fi
341
+
342
+ # ── Summary ───────────────────────────────────────────────────────────────────
343
+ echo ""
344
+ echo "cr081_qa_red_lint.red.sh: ${PASS} passed, ${FAIL} failed"
345
+ if [[ "${FAIL}" -gt 0 ]]; then
346
+ exit 1
347
+ fi
348
+ exit 0
@@ -0,0 +1 @@
1
+ {"d7d1579f-97a4-4ed2-953e-1364ce697de7":{"input":166100,"output":1238677,"cache_creation":4905569,"cache_read":90028882,"last_ts":"2026-06-04T08:59:03Z","last_turn_index":0},"cd330896-e59e-4b46-b2ab-f420cbfa89d2":{"input":35856,"output":273596,"cache_creation":532880,"cache_read":12920963,"last_ts":"2026-06-04T09:22:15Z","last_turn_index":0},"a7ff198a-7354-4390-ba21-ec7df60ef4a1":{"input":71371,"output":934473,"cache_creation":4350709,"cache_read":91569515,"last_ts":"2026-06-04T14:17:56Z","last_turn_index":0},"00c82e3f-17dc-49cc-ab08-981ba1d9c6e2":{"input":155691,"output":1522802,"cache_creation":7137389,"cache_read":113485679,"last_ts":"2026-06-05T20:15:13Z","last_turn_index":0},"80279738-6cfd-4774-a2d3-50547d214129":{"input":34535,"output":50008,"cache_creation":993677,"cache_read":1958700,"last_ts":"2026-06-06T19:14:48Z","last_turn_index":0}}