@boyingliu01/xp-gate 0.30.3 → 0.30.4

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 (29) hide show
  1. package/gate-10.sh +80 -13
  2. package/hooks/gate-10.sh +80 -13
  3. package/hooks/pre-push +53 -3
  4. package/lib/mattpocock-license-verdict.json +1 -1
  5. package/mock-policy/AGENTS.md +2 -2
  6. package/mutation/AGENTS.md +2 -2
  7. package/mutation/__tests__/gate-m-survivors.test.ts +133 -0
  8. package/mutation/gate-m.ts +114 -11
  9. package/package.json +1 -1
  10. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  11. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  12. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  13. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  14. package/plugins/dsh/package.json +1 -1
  15. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  16. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  17. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  18. package/plugins/opencode/package.json +1 -1
  19. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  20. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  21. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  22. package/plugins/qoder/plugin.json +1 -1
  23. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  24. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  25. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  26. package/principles/AGENTS.md +2 -2
  27. package/skills/delphi-review/AGENTS.md +2 -2
  28. package/skills/sprint-flow/AGENTS.md +2 -2
  29. package/skills/test-specification-alignment/AGENTS.md +2 -2
package/gate-10.sh CHANGED
@@ -91,11 +91,27 @@ else
91
91
  [ -n "$_semgrep_file" ] || continue
92
92
  SEMGREP_FILE_COUNT=$((SEMGREP_FILE_COUNT + 1))
93
93
  SEMGREP_ARGS+=("--include=${_semgrep_file##*/}")
94
- _semgrep_dir="./${_semgrep_file%/*}"
95
- # A repo-root file has no directory component ("./x.ts" strips to "./").
96
- [ "$_semgrep_dir" = "./" ] && _semgrep_dir="."
94
+ # A repo-root file has no slash at all, so "%/*" returns it unchanged and
95
+ # the old comment's assumption (`"./x.ts" strips to "./"`) never held:
96
+ # the target became the FILE path dressed as a directory ("./x.ts").
97
+ # Strip only when a slash is present; otherwise the target is the repo
98
+ # root, spelled ".". Scanning "." widens the scan to the whole tree, which
99
+ # is the safe side (see the note above) and is what a root-level staged
100
+ # file requires in order to be analysed at all.
101
+ case "$_semgrep_file" in
102
+ */*) _semgrep_dir="./${_semgrep_file%/*}" ;;
103
+ *) _semgrep_dir="." ;;
104
+ esac
105
+ # #558: dedup must match WHOLE lines. The previous `case "$SEEN" in
106
+ # *"$_semgrep_dir"*)` was an unanchored substring test: once
107
+ # ./fastdl/tests was recorded, the parent ./fastdl matched it and was
108
+ # never added as a target, so a staged fastdl/a.py contributed an
109
+ # --include= with no directory to scan it in. Nothing errored and nothing
110
+ # was reported -- the file was simply not analysed, which for a security
111
+ # gate is the worst outcome: a silent net loss that looks like a pass.
112
+ # Sibling prefixes (src/api vs src/app) collapsed the same way.
97
113
  case "$SEMGREP_DIRS_SEEN" in
98
- *"$_semgrep_dir"*) : ;;
114
+ *$'\n'"$_semgrep_dir"$'\n'*) : ;;
99
115
  *) SEMGREP_ARGS+=("$_semgrep_dir"); SEMGREP_DIRS_SEEN="$SEMGREP_DIRS_SEEN$_semgrep_dir"$'\n' ;;
100
116
  esac
101
117
  done <<EOF
@@ -131,9 +147,41 @@ EOF
131
147
  # code is demoted to "did the tool itself run". --error stays OFF: it would
132
148
  # add nothing the JSON does not already provide, and keeping it off leaves
133
149
  # failure exit codes unambiguous for the #475 diagnostics path.
134
- SEMGREP_STDOUT_FILE=$(mktemp)
135
- SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check "${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
136
- SEMGREP_EXIT=$?
150
+ # #559: bound the run. Without a timeout, a wedged semgrep blocks the
151
+ # invocation for as long as its process lives; the fail-closed verdict logic
152
+ # below is correct but unreachable, so the hook hangs instead of judging.
153
+ # A gate that can wedge an interactive `git commit` gets bypassed with
154
+ # --no-verify, which is worse than any verdict it could have produced.
155
+ # `-k` after the primary signal: pysemgrep spinning in a syscall ignores
156
+ # SIGTERM, and the shipped 0.29 code used the same `-k 10` shape.
157
+ SEMGREP_TIMEOUT_S="${XP_GATE_SEMGREP_TIMEOUT:-180}"
158
+ SEMGREP_TIMED_OUT=false
159
+ if command -v timeout >/dev/null 2>&1; then
160
+ SEMGREP_STDOUT_FILE=$(mktemp)
161
+ SEMGREP_OUTPUT=$(timeout -k 10 "$SEMGREP_TIMEOUT_S" \
162
+ "$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check \
163
+ "${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
164
+ SEMGREP_EXIT=$?
165
+ # 124 = timeout's own "I gave up" code. 137 = 128+SIGKILL, which is what
166
+ # `timeout -k` returns when the child ignored SIGTERM and had to be
167
+ # killed -- measured with a semgrep stand-in that traps TERM. Both mean
168
+ # "bounded, no verdict", so both must be reported as a timeout; checking
169
+ # only 124 told the user their semgrep was unparseable when in fact the
170
+ # scan had been cut off.
171
+ case "$SEMGREP_EXIT" in
172
+ 124|137) SEMGREP_TIMED_OUT=true ;;
173
+ esac
174
+ else
175
+ # No `timeout` on this platform (stock macOS). Say so: an unbounded run
176
+ # nobody mentioned is exactly how #559 stayed invisible for a whole
177
+ # release. The scan still runs -- refusing to scan would be a worse
178
+ # trade -- but the unbounded window is now stated.
179
+ echo " ⚠️ 'timeout' command not available — semgrep runs UNBOUNDED (install coreutils or set up gtimeout)"
180
+ SEMGREP_STDOUT_FILE=$(mktemp)
181
+ SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check \
182
+ "${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
183
+ SEMGREP_EXIT=$?
184
+ fi
137
185
 
138
186
  # One node pass over the JSON: first line = verdict counts, remaining lines =
139
187
  # finding details. The severity vocabulary is the UNION of the two scales
@@ -175,7 +223,18 @@ console.log("");});
175
223
  if [ "$V_PARSE" != "OK" ] || [ -z "$V_RESULTS" ] || [ -z "$V_ERRORS" ] || [ -z "$V_BLOCKING" ] || [ -z "$V_WARNINGS" ]; then
176
224
  # Unparseable output: nothing the exit code claims can be trusted (#518).
177
225
  # Fail closed — SKIP with diagnostics, never a PASS.
178
- echo " ⚠️ semgrep output unparseable or verdict fields missing (exit code ${SEMGREP_EXIT}) — cannot determine verdict"
226
+ #
227
+ # #559: name a timeout as a timeout. The generic "unparseable" message
228
+ # would be true but useless here -- it points at the JSON parser when the
229
+ # real problem is that the scan was cut off. Same verdict (SKIP), but a
230
+ # diagnosis the reader can act on.
231
+ if [ "$SEMGREP_TIMED_OUT" = true ]; then
232
+ echo " ⏱️ semgrep exceeded ${SEMGREP_TIMEOUT_S}s and was killed — no verdict is possible"
233
+ echo " This is NOT a finding and NOT a clean scan. Raise XP_GATE_SEMGREP_TIMEOUT"
234
+ echo " if the repo legitimately takes longer; investigate if semgrep itself is wedged."
235
+ else
236
+ echo " ⚠️ semgrep output unparseable or verdict fields missing (exit code ${SEMGREP_EXIT}) — cannot determine verdict"
237
+ fi
179
238
  if [ -n "$SEMGREP_OUTPUT" ]; then
180
239
  echo " ── what semgrep reported on stderr ──"
181
240
  printf '%s\n' "$SEMGREP_OUTPUT" | tail -10 | sed 's/^/ /'
@@ -184,11 +243,19 @@ console.log("");});
184
243
  echo " ── what semgrep wrote to stdout (tail) ──"
185
244
  printf '%s\n' "$SEMGREP_STDOUT_TAIL" | sed 's/^/ /'
186
245
  fi
187
- echo " ⏭️ SKIPPED - SAST (semgrep output unparseable)"
188
- GATE_10_STATUS="SKIP"
189
- # Consumed by the sourcing parent (pre-commit report JSON), not here.
190
- # shellcheck disable=SC2034
191
- GATE_10_REASON="output_unparseable"
246
+ if [ "$SEMGREP_TIMED_OUT" = true ]; then
247
+ echo " ⏭️ SKIPPED - SAST (semgrep timed out after ${SEMGREP_TIMEOUT_S}s)"
248
+ GATE_10_STATUS="SKIP"
249
+ # Consumed by the sourcing parent (pre-commit report JSON), not here.
250
+ # shellcheck disable=SC2034
251
+ GATE_10_REASON="semgrep_timeout"
252
+ else
253
+ echo " ⏭️ SKIPPED - SAST (semgrep output unparseable)"
254
+ GATE_10_STATUS="SKIP"
255
+ # Consumed by the sourcing parent (pre-commit report JSON), not here.
256
+ # shellcheck disable=SC2034
257
+ GATE_10_REASON="output_unparseable"
258
+ fi
192
259
  elif [ "$V_RESULTS" -eq 0 ] && { [ "$V_ERRORS" -gt 0 ] || [ "$SEMGREP_EXIT" -ne 0 ]; }; then
193
260
  # Nothing was scanned to a verdict: semgrep either failed outright
194
261
  # (nonzero exit) or completed but reported scan errors for every input
package/hooks/gate-10.sh CHANGED
@@ -91,11 +91,27 @@ else
91
91
  [ -n "$_semgrep_file" ] || continue
92
92
  SEMGREP_FILE_COUNT=$((SEMGREP_FILE_COUNT + 1))
93
93
  SEMGREP_ARGS+=("--include=${_semgrep_file##*/}")
94
- _semgrep_dir="./${_semgrep_file%/*}"
95
- # A repo-root file has no directory component ("./x.ts" strips to "./").
96
- [ "$_semgrep_dir" = "./" ] && _semgrep_dir="."
94
+ # A repo-root file has no slash at all, so "%/*" returns it unchanged and
95
+ # the old comment's assumption (`"./x.ts" strips to "./"`) never held:
96
+ # the target became the FILE path dressed as a directory ("./x.ts").
97
+ # Strip only when a slash is present; otherwise the target is the repo
98
+ # root, spelled ".". Scanning "." widens the scan to the whole tree, which
99
+ # is the safe side (see the note above) and is what a root-level staged
100
+ # file requires in order to be analysed at all.
101
+ case "$_semgrep_file" in
102
+ */*) _semgrep_dir="./${_semgrep_file%/*}" ;;
103
+ *) _semgrep_dir="." ;;
104
+ esac
105
+ # #558: dedup must match WHOLE lines. The previous `case "$SEEN" in
106
+ # *"$_semgrep_dir"*)` was an unanchored substring test: once
107
+ # ./fastdl/tests was recorded, the parent ./fastdl matched it and was
108
+ # never added as a target, so a staged fastdl/a.py contributed an
109
+ # --include= with no directory to scan it in. Nothing errored and nothing
110
+ # was reported -- the file was simply not analysed, which for a security
111
+ # gate is the worst outcome: a silent net loss that looks like a pass.
112
+ # Sibling prefixes (src/api vs src/app) collapsed the same way.
97
113
  case "$SEMGREP_DIRS_SEEN" in
98
- *"$_semgrep_dir"*) : ;;
114
+ *$'\n'"$_semgrep_dir"$'\n'*) : ;;
99
115
  *) SEMGREP_ARGS+=("$_semgrep_dir"); SEMGREP_DIRS_SEEN="$SEMGREP_DIRS_SEEN$_semgrep_dir"$'\n' ;;
100
116
  esac
101
117
  done <<EOF
@@ -131,9 +147,41 @@ EOF
131
147
  # code is demoted to "did the tool itself run". --error stays OFF: it would
132
148
  # add nothing the JSON does not already provide, and keeping it off leaves
133
149
  # failure exit codes unambiguous for the #475 diagnostics path.
134
- SEMGREP_STDOUT_FILE=$(mktemp)
135
- SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check "${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
136
- SEMGREP_EXIT=$?
150
+ # #559: bound the run. Without a timeout, a wedged semgrep blocks the
151
+ # invocation for as long as its process lives; the fail-closed verdict logic
152
+ # below is correct but unreachable, so the hook hangs instead of judging.
153
+ # A gate that can wedge an interactive `git commit` gets bypassed with
154
+ # --no-verify, which is worse than any verdict it could have produced.
155
+ # `-k` after the primary signal: pysemgrep spinning in a syscall ignores
156
+ # SIGTERM, and the shipped 0.29 code used the same `-k 10` shape.
157
+ SEMGREP_TIMEOUT_S="${XP_GATE_SEMGREP_TIMEOUT:-180}"
158
+ SEMGREP_TIMED_OUT=false
159
+ if command -v timeout >/dev/null 2>&1; then
160
+ SEMGREP_STDOUT_FILE=$(mktemp)
161
+ SEMGREP_OUTPUT=$(timeout -k 10 "$SEMGREP_TIMEOUT_S" \
162
+ "$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check \
163
+ "${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
164
+ SEMGREP_EXIT=$?
165
+ # 124 = timeout's own "I gave up" code. 137 = 128+SIGKILL, which is what
166
+ # `timeout -k` returns when the child ignored SIGTERM and had to be
167
+ # killed -- measured with a semgrep stand-in that traps TERM. Both mean
168
+ # "bounded, no verdict", so both must be reported as a timeout; checking
169
+ # only 124 told the user their semgrep was unparseable when in fact the
170
+ # scan had been cut off.
171
+ case "$SEMGREP_EXIT" in
172
+ 124|137) SEMGREP_TIMED_OUT=true ;;
173
+ esac
174
+ else
175
+ # No `timeout` on this platform (stock macOS). Say so: an unbounded run
176
+ # nobody mentioned is exactly how #559 stayed invisible for a whole
177
+ # release. The scan still runs -- refusing to scan would be a worse
178
+ # trade -- but the unbounded window is now stated.
179
+ echo " ⚠️ 'timeout' command not available — semgrep runs UNBOUNDED (install coreutils or set up gtimeout)"
180
+ SEMGREP_STDOUT_FILE=$(mktemp)
181
+ SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check \
182
+ "${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
183
+ SEMGREP_EXIT=$?
184
+ fi
137
185
 
138
186
  # One node pass over the JSON: first line = verdict counts, remaining lines =
139
187
  # finding details. The severity vocabulary is the UNION of the two scales
@@ -175,7 +223,18 @@ console.log("");});
175
223
  if [ "$V_PARSE" != "OK" ] || [ -z "$V_RESULTS" ] || [ -z "$V_ERRORS" ] || [ -z "$V_BLOCKING" ] || [ -z "$V_WARNINGS" ]; then
176
224
  # Unparseable output: nothing the exit code claims can be trusted (#518).
177
225
  # Fail closed — SKIP with diagnostics, never a PASS.
178
- echo " ⚠️ semgrep output unparseable or verdict fields missing (exit code ${SEMGREP_EXIT}) — cannot determine verdict"
226
+ #
227
+ # #559: name a timeout as a timeout. The generic "unparseable" message
228
+ # would be true but useless here -- it points at the JSON parser when the
229
+ # real problem is that the scan was cut off. Same verdict (SKIP), but a
230
+ # diagnosis the reader can act on.
231
+ if [ "$SEMGREP_TIMED_OUT" = true ]; then
232
+ echo " ⏱️ semgrep exceeded ${SEMGREP_TIMEOUT_S}s and was killed — no verdict is possible"
233
+ echo " This is NOT a finding and NOT a clean scan. Raise XP_GATE_SEMGREP_TIMEOUT"
234
+ echo " if the repo legitimately takes longer; investigate if semgrep itself is wedged."
235
+ else
236
+ echo " ⚠️ semgrep output unparseable or verdict fields missing (exit code ${SEMGREP_EXIT}) — cannot determine verdict"
237
+ fi
179
238
  if [ -n "$SEMGREP_OUTPUT" ]; then
180
239
  echo " ── what semgrep reported on stderr ──"
181
240
  printf '%s\n' "$SEMGREP_OUTPUT" | tail -10 | sed 's/^/ /'
@@ -184,11 +243,19 @@ console.log("");});
184
243
  echo " ── what semgrep wrote to stdout (tail) ──"
185
244
  printf '%s\n' "$SEMGREP_STDOUT_TAIL" | sed 's/^/ /'
186
245
  fi
187
- echo " ⏭️ SKIPPED - SAST (semgrep output unparseable)"
188
- GATE_10_STATUS="SKIP"
189
- # Consumed by the sourcing parent (pre-commit report JSON), not here.
190
- # shellcheck disable=SC2034
191
- GATE_10_REASON="output_unparseable"
246
+ if [ "$SEMGREP_TIMED_OUT" = true ]; then
247
+ echo " ⏭️ SKIPPED - SAST (semgrep timed out after ${SEMGREP_TIMEOUT_S}s)"
248
+ GATE_10_STATUS="SKIP"
249
+ # Consumed by the sourcing parent (pre-commit report JSON), not here.
250
+ # shellcheck disable=SC2034
251
+ GATE_10_REASON="semgrep_timeout"
252
+ else
253
+ echo " ⏭️ SKIPPED - SAST (semgrep output unparseable)"
254
+ GATE_10_STATUS="SKIP"
255
+ # Consumed by the sourcing parent (pre-commit report JSON), not here.
256
+ # shellcheck disable=SC2034
257
+ GATE_10_REASON="output_unparseable"
258
+ fi
192
259
  elif [ "$V_RESULTS" -eq 0 ] && { [ "$V_ERRORS" -gt 0 ] || [ "$SEMGREP_EXIT" -ne 0 ]; }; then
193
260
  # Nothing was scanned to a verdict: semgrep either failed outright
194
261
  # (nonzero exit) or completed but reported scan errors for every input
package/hooks/pre-push CHANGED
@@ -47,11 +47,28 @@ write_pre_push_report() {
47
47
  commit_hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
48
48
  verdict=$([ "$exit_code" -eq 0 ] && echo "PASS" || echo "BLOCKED")
49
49
  mkdir -p "$reports_dir"
50
+ # #544: the survivor detail is a JSON document, so it cannot be interpolated
51
+ # into this heredoc — a single quote in a path would break the Python source.
52
+ # Hand it to the child through the environment and let Python parse it, then
53
+ # store it serialised back into the `details` string domain (every value there
54
+ # is a string; the journal has broken before over mixed types).
55
+ export XP_GATE_M_SURVIVORS_JSON="${M_SURVIVORS_JSON:-}"
50
56
  python3 - <<PY 2>/dev/null || true
51
57
  import json
58
+ import os
52
59
  from pathlib import Path
53
60
  pushed_files = '''${PUSHED_FILES:-}'''.splitlines()
54
61
  passed = 1 if '$verdict' == 'PASS' else 0
62
+ # Absent stays absent: no key at all when nothing was measured, so a reader can
63
+ # tell "not measured" from "measured: zero survivors" (#544).
64
+ try:
65
+ _survivors_raw = os.environ.get('XP_GATE_M_SURVIVORS_JSON', '').strip()
66
+ survivors_detail = json.loads(_survivors_raw) if _survivors_raw else None
67
+ except (ValueError, TypeError):
68
+ survivors_detail = None
69
+ _m_details = {'skip_reason': '${GATE_M_SKIP_REASON:-}', 'duration_ms': '${M_DURATION_MS:-0}', 'mutation_score': '${M_SCORE:-}'}
70
+ if survivors_detail is not None:
71
+ _m_details['survivors'] = json.dumps(survivors_detail, sort_keys=True)
55
72
  report = {
56
73
  'timestamp': '$timestamp',
57
74
  'trigger': 'pre-push',
@@ -67,15 +84,20 @@ report = {
67
84
  'gates': [
68
85
  {'id': 'MP', 'name': 'Gate-Script Mirror Parity', 'status': '${GATE_MP_STATUS:-SKIP}', 'details': {'duration_ms': '${MP_DURATION_MS:-0}'}},
69
86
  {'id': '10', 'name': 'Build Integrity', 'status': '${GATE_10_STATUS:-SKIP}', 'details': {}},
70
- {'id': 'M', 'name': 'Mutation Testing', 'status': '${GATE_M_STATUS:-SKIP}', 'details': {'skip_reason': '${GATE_M_SKIP_REASON:-}', 'duration_ms': '${M_DURATION_MS:-0}', 'mutation_score': '${M_SCORE:-}'}},
71
- {'id': 'M-Python', 'name': 'Mutation Testing (Python)', 'status': '${GATE_M_PYTHON_STATUS:-SKIP}', 'details': {}},
87
+ {'id': 'M', 'name': 'Mutation Testing', 'status': '${GATE_M_STATUS:-SKIP}', 'details': _m_details},
88
+ # #557 follow-up: this line read GATE_M_PYTHON_STATUS while the code assigns
89
+ # GATE_M_PY_STATUS, so the field always fell back to its SKIP default -- the
90
+ # "never write the real state" bug #557 set out to fix, still present for
91
+ # this one gate. The skip reason now travels with it, so a retro reader can
92
+ # tell "no Python here" from "timed out" from "mutmut missing".
93
+ {'id': 'M-Python', 'name': 'Mutation Testing (Python)', 'status': '${GATE_M_PY_STATUS:-SKIP}', 'details': {'skip_reason': '${GATE_M_PY_SKIP_REASON:-}'}},
72
94
  {'id': 'M-Go', 'name': 'Mutation Testing (Go)', 'status': '${GATE_M_GO_STATUS:-SKIP}', 'details': {}},
73
95
  {'id': 'M-Java', 'name': 'Mutation Testing (Java)', 'status': '${GATE_M_JAVA_STATUS:-SKIP}', 'details': {}},
74
96
  {'id': 'M-Kotlin', 'name': 'Mutation Testing (Kotlin)', 'status': '${GATE_M_KOTLIN_STATUS:-SKIP}', 'details': {}},
75
97
  {'id': 'M2', 'name': 'Mock Density (WARNING only)', 'status': '${GATE_M2_STATUS:-SKIP}', 'details': {}},
76
98
  {'id': 'ML', 'name': 'Mock Layering', 'status': '${GATE_M3_STATUS:-SKIP}', 'details': {}},
77
99
  {'id': 'UI', 'name': 'UI Sprint Gates', 'status': '${GATE_UI_STATUS:-SKIP}', 'details': {}},
78
- {'id': 'MW', 'name': 'Code Walkthrough', 'status': '${GATE_DELPHI_STATUS:-SKIP}', 'details': {'remote': '$REMOTE', 'url': '$URL'}},
100
+ {'id': 'MW', 'name': 'Code Walkthrough', 'status': '${GATE_DELPHI_STATUS:-SKIP}', 'details': {'remote': '$REMOTE', 'url': '$URL', 'skip_reason': '${GATE_DELPHI_SKIP_REASON:-}'}},
79
101
  {'id': 'S', 'name': 'Sprint Flow Enforcement', 'status': '${GATE_S_STATUS:-SKIP}', 'details': {}}
80
102
  ],
81
103
  'warnings': [],
@@ -453,6 +475,15 @@ else
453
475
  # It is printed by gate-m.ts; take the last percentage it reported.
454
476
  M_SCORE=$(sed -n 's/.*[Mm]utation score[: ]*\([0-9][0-9]*\(\.[0-9][0-9]*\)\?\)%.*/\1/p' "$MUTATION_OUTPUT" 2>/dev/null | tail -1)
455
477
 
478
+ # #544: per-file survivor detail, so retro can answer "which module has
479
+ # the most surviving mutants". gate-m.ts emits one
480
+ # GATE_M_SURVIVORS_JSON= line; it is ABSENT when no report was produced,
481
+ # and absent must stay absent. An empty value means "not measured"; a
482
+ # written zero would mean "measured, no survivors". Only the former is
483
+ # ever true when the line is missing -- conflating the two is the whole
484
+ # of #544.
485
+ M_SURVIVORS_JSON=$(printf '%s\n' "$MUTATION_OUTPUT" | sed -n 's/^GATE_M_SURVIVORS_JSON=//p' | tail -1)
486
+
456
487
  case $MUTATION_EXIT in
457
488
  0)
458
489
  echo "✅ Gate M: PASS"
@@ -1066,6 +1097,15 @@ else
1066
1097
  # ============================================================================
1067
1098
  RESULT_FILE=".code-walkthrough-result.json"
1068
1099
  MW_SKIPPED=0
1100
+ # Journal status (#557 follow-up). The journal already read GATE_DELPHI_STATUS
1101
+ # but nothing ever assigned it, so Gate MW reported its SKIP default in every
1102
+ # report -- including pushes where the walkthrough was in fact verified. The
1103
+ # closing banner told the truth while the machine-readable journal did not, which
1104
+ # is the half-fix #557 was about.
1105
+ GATE_DELPHI_STATUS="SKIP"
1106
+ # Consumed by the journal heredoc (sourced/inherited boundary).
1107
+ # shellcheck disable=SC2034
1108
+ GATE_DELPHI_SKIP_REASON="not_run"
1069
1109
 
1070
1110
  # Skipped on main/master by design, exactly like Gate ML and Gate UI above.
1071
1111
  # Work is already walkthrough-reviewed on the feature branch before it lands, so
@@ -1075,6 +1115,9 @@ MW_SKIPPED=0
1075
1115
  # documented this skip; the code did not implement it (drift fixed here).
1076
1116
  if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
1077
1117
  MW_SKIPPED=1
1118
+ # Consumed by the journal heredoc (sourced/inherited boundary).
1119
+ # shellcheck disable=SC2034
1120
+ GATE_DELPHI_SKIP_REASON="main_branch_push"
1078
1121
  echo ""
1079
1122
  echo " ℹ️ Gate MW skipped (pushing to ${CURRENT_BRANCH} — pre-reviewed via branch walkthrough)"
1080
1123
  echo ""
@@ -1144,6 +1187,13 @@ if ! VALIDATION_ERROR=$(node "$MW_VALIDATOR" "$RESULT_FILE" "$EXPECTED_COMMIT" "
1144
1187
  echo ""
1145
1188
  exit 1
1146
1189
  fi
1190
+
1191
+ # The validator passed: record that the walkthrough was actually verified.
1192
+ # Consumed by the journal heredoc (sourced/inherited boundary).
1193
+ # shellcheck disable=SC2034
1194
+ GATE_DELPHI_STATUS="PASS"
1195
+ # shellcheck disable=SC2034
1196
+ GATE_DELPHI_SKIP_REASON=""
1147
1197
  fi
1148
1198
 
1149
1199
  # ============================================================================
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-20T19:22:03.640Z",
5
+ "checked_at": "2026-10-20T23:31:25.547Z",
6
6
  "source": "network"
7
7
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
@@ -1,9 +1,9 @@
1
1
  # SRC/MUTATION KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **Gate M** (incremental mutation testing) + **Gate M2** helpers (test-layer detection used by `src/mock-policy/`). Pre-push quality gate. TypeScript-only; uses Stryker.
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @test #544 - Gate M survivor detail for retro
3
+ * @intent Verify Gate M reports per-file mutant outcome counts, not just a
4
+ * score. #544: the pre-push journal carried
5
+ * {skip_reason, duration_ms, mutation_score} and nothing else, so
6
+ * `xp-gate retro` could not answer "which module has the most surviving
7
+ * mutants". The data was never missing at the source: Stryker's JSON
8
+ * reporter emits files[].nrOfSurvivedMutants, and stryker-types.ts
9
+ * already declares it. The counts were dropped on the way through, then
10
+ * buildMutationScores hardcoded {mutants: 0, killed: 0, survived: 0}.
11
+ * Three zeros written next to a real score read as "measured: no
12
+ * survivors" -- the same class of false success this project keeps
13
+ * fixing, so unknown must stay absent, never zero.
14
+ * @covers REQ-MUT-001 AC-001
15
+ */
16
+
17
+ import { describe, it, expect } from 'vitest';
18
+ import {
19
+ summarizeSurvivors,
20
+ type StrykerReportLike,
21
+ type SurvivorSummary,
22
+ } from '../gate-m';
23
+
24
+ /** A report shaped like Stryker's JSON output. */
25
+ function report(files: Record<string, [number, number, number]>): StrykerReportLike {
26
+ const entries = Object.entries(files);
27
+ const totalMutants = entries.reduce((a, [, v]) => a + v[0], 0);
28
+ const totalKilled = entries.reduce((a, [, v]) => a + v[1], 0);
29
+ const totalSurvived = entries.reduce((a, [, v]) => a + v[2], 0);
30
+ return {
31
+ mutationScore: totalMutants > 0 ? parseFloat(((totalKilled / totalMutants) * 100).toFixed(1)) : 100,
32
+ nrOfMutants: totalMutants,
33
+ nrOfKilledMutants: totalKilled,
34
+ nrOfSurvivedMutants: totalSurvived,
35
+ files: Object.fromEntries(
36
+ entries.map(([k, [m, kd, sv]]) => [k, {
37
+ mutationScore: m > 0 ? parseFloat(((kd / m) * 100).toFixed(1)) : 100,
38
+ nrOfMutants: m,
39
+ nrOfKilledMutants: kd,
40
+ nrOfSurvivedMutants: sv,
41
+ }])
42
+ ),
43
+ };
44
+ }
45
+
46
+ /** Narrow to a non-null summary, failing the test with a clear message. */
47
+ function expectSummary(s: SurvivorSummary | null): SurvivorSummary {
48
+ expect(s).not.toBeNull();
49
+ if (s === null) throw new Error('expected a survivor summary, got null');
50
+ return s;
51
+ }
52
+
53
+ describe('AC-544: Gate M survivor detail', () => {
54
+ it('reports per-file survived counts from the Stryker report', () => {
55
+ const summary = expectSummary(summarizeSurvivors(report({
56
+ 'src/mutation/gate-m.ts': [20, 8, 12],
57
+ 'src/principles/analyzer.ts': [10, 3, 7],
58
+ })));
59
+
60
+ expect(summary.survivedByFile).toEqual({
61
+ 'src/mutation/gate-m.ts': 12,
62
+ 'src/principles/analyzer.ts': 7,
63
+ });
64
+ });
65
+
66
+ it('totals killed, survived and mutants', () => {
67
+ const summary = expectSummary(summarizeSurvivors(report({
68
+ 'a.ts': [20, 8, 12],
69
+ 'b.ts': [10, 3, 7],
70
+ })));
71
+
72
+ expect(summary.mutants).toBe(30);
73
+ expect(summary.killed).toBe(11);
74
+ expect(summary.survived).toBe(19);
75
+ });
76
+
77
+ it('orders survivedByFile worst-first so retro can take the top N', () => {
78
+ const summary = expectSummary(summarizeSurvivors(report({
79
+ 'small.ts': [10, 9, 1],
80
+ 'worst.ts': [10, 0, 10],
81
+ 'middle.ts': [10, 5, 5],
82
+ })));
83
+
84
+ expect(Object.keys(summary.survivedByFile)).toEqual(['worst.ts', 'middle.ts', 'small.ts']);
85
+ });
86
+
87
+ it('caps the per-file map so one huge repo cannot bloat the journal', () => {
88
+ const many: Record<string, [number, number, number]> = {};
89
+ // Every file must have at least one survivor, otherwise this asserts on a
90
+ // smaller population than intended and the count below drifts.
91
+ for (let i = 0; i < 50; i++) {
92
+ const survived = (i % 9) + 1; // 1..9, never 0
93
+ many[`f${i}.ts`] = [10, 10 - survived, survived];
94
+ }
95
+
96
+ const summary = expectSummary(summarizeSurvivors(report(many), { limit: 20 }));
97
+
98
+ expect(Object.keys(summary.survivedByFile)).toHaveLength(20);
99
+ // Truncation must be visible, not silent: a capped list that looks complete
100
+ // is how "top 20" gets mistaken for "all files".
101
+ expect(summary.truncated).toBe(true);
102
+ expect(summary.filesWithSurvivors).toBe(50);
103
+ });
104
+
105
+ it('does not claim truncation when everything fits', () => {
106
+ const summary = expectSummary(summarizeSurvivors(report({ 'a.ts': [10, 5, 5] }), { limit: 20 }));
107
+ expect(summary.truncated).toBe(false);
108
+ });
109
+
110
+ it('omits files with zero survivors', () => {
111
+ const summary = expectSummary(summarizeSurvivors(report({
112
+ 'clean.ts': [10, 10, 0],
113
+ 'dirty.ts': [10, 4, 6],
114
+ })));
115
+
116
+ expect(summary.survivedByFile).toEqual({ 'dirty.ts': 6 });
117
+ });
118
+
119
+ it('degrades to null instead of inventing zeros when no report is available', () => {
120
+ // A missing/timed-out report must not be reported as "0 survivors" -- that
121
+ // is the #544 failure repeated. null means unknown.
122
+ expect(summarizeSurvivors(null)).toBeNull();
123
+ });
124
+
125
+ it('degrades to null when the report has no per-file section', () => {
126
+ expect(summarizeSurvivors({
127
+ mutationScore: 80,
128
+ nrOfMutants: 10,
129
+ nrOfKilledMutants: 8,
130
+ nrOfSurvivedMutants: 2,
131
+ })).toBeNull();
132
+ });
133
+ });
@@ -522,6 +522,86 @@ function buildMutationScores(fileScores: Record<string, number>): Record<string,
522
522
  return scores;
523
523
  }
524
524
 
525
+ /**
526
+ * The subset of a runner report that survivor summarisation needs. Declared
527
+ * structurally rather than as a concrete Stryker type because every runner
528
+ * (Stryker, mutmut, gremlins, go-mutant) normalises to `MutationRunResult`,
529
+ * which carries the same field names -- the summary must work for all of them,
530
+ * not just the TS one.
531
+ */
532
+ export interface StrykerReportLike {
533
+ mutationScore: number;
534
+ nrOfMutants: number;
535
+ nrOfKilledMutants: number;
536
+ nrOfSurvivedMutants: number;
537
+ files?: Record<string, {
538
+ mutationScore: number;
539
+ nrOfMutants: number;
540
+ nrOfKilledMutants: number;
541
+ nrOfSurvivedMutants: number;
542
+ }>;
543
+ }
544
+
545
+ /** Per-file mutant outcome detail for the pre-push journal (#544). */
546
+ export interface SurvivorSummary {
547
+ /** Files with at least one surviving mutant, worst first, capped by `limit`. */
548
+ survivedByFile: Record<string, number>;
549
+ /**
550
+ * How many files actually had survivors. Present so a capped map can be told
551
+ * apart from a complete one -- a truncated list that looks exhaustive is how
552
+ * "top 20" gets read as "all files".
553
+ */
554
+ filesWithSurvivors: number;
555
+ /** True when `survivedByFile` dropped entries to stay under `limit`. */
556
+ truncated: boolean;
557
+ mutants: number;
558
+ killed: number;
559
+ survived: number;
560
+ }
561
+
562
+ /**
563
+ * Condense a Stryker report into survivor detail (#544).
564
+ *
565
+ * Returns `null` -- never a summary of zeros -- when the report is missing or
566
+ * carries no per-file section. A timed-out or failed run has no measurements,
567
+ * and writing `survived: 0` next to a real-looking score would report
568
+ * "measured: no survivors" for a run that measured nothing. That is the exact
569
+ * shape of false success this project keeps finding, so unknown stays `null`.
570
+ */
571
+ export function summarizeSurvivors(
572
+ report: StrykerReportLike | null | undefined,
573
+ options: { limit?: number } = {},
574
+ ): SurvivorSummary | null {
575
+ if (!report || !report.files) return null;
576
+
577
+ const limit = options.limit ?? 20;
578
+ const survivors: Array<[string, number]> = [];
579
+ let mutants = 0;
580
+ let killed = 0;
581
+ let survived = 0;
582
+
583
+ for (const [file, entry] of Object.entries(report.files)) {
584
+ const fileSurvived = entry.nrOfSurvivedMutants ?? 0;
585
+ mutants += entry.nrOfMutants ?? 0;
586
+ killed += entry.nrOfKilledMutants ?? 0;
587
+ survived += fileSurvived;
588
+ if (fileSurvived > 0) survivors.push([file, fileSurvived]);
589
+ }
590
+
591
+ // Worst first, then by path so equal counts order deterministically.
592
+ survivors.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
593
+ const kept = survivors.slice(0, limit);
594
+
595
+ return {
596
+ survivedByFile: Object.fromEntries(kept),
597
+ filesWithSurvivors: survivors.length,
598
+ truncated: survivors.length > kept.length,
599
+ mutants,
600
+ killed,
601
+ survived,
602
+ };
603
+ }
604
+
525
605
  export async function runGateM(options: GateMOptions): Promise<GateMResult> {
526
606
  const warnings: string[] = [];
527
607
  const errors: string[] = [];
@@ -595,6 +675,22 @@ async function collectTestIntentWarnings(sourceFiles: string[], warnings: string
595
675
  }
596
676
  }
597
677
 
678
+ /**
679
+ * Read the mutation report the runner left on disk and summarise it (#544).
680
+ * Returns null when there is no readable report -- the caller then prints
681
+ * nothing, and the journal field stays absent. Every failure here is silent by
682
+ * design: #544 explicitly requires that missing detail must not turn Gate M
683
+ * itself into a failure.
684
+ */
685
+ async function readSurvivorSummary(): Promise<SurvivorSummary | null> {
686
+ try {
687
+ const raw = await fs.readFile('.stryker-report.json', 'utf-8');
688
+ return summarizeSurvivors(JSON.parse(raw) as StrykerReportLike);
689
+ } catch {
690
+ return null;
691
+ }
692
+ }
693
+
598
694
  export async function main(args: string[]): Promise<number> {
599
695
  const options = parseArgs(args);
600
696
 
@@ -611,19 +707,14 @@ export async function main(args: string[]): Promise<number> {
611
707
 
612
708
  const result = await runGateM(options);
613
709
 
614
- if (result.warnings.length > 0) {
615
- console.log('\nWarnings:');
616
- for (const warning of result.warnings) {
617
- console.log(` ⚠ ${warning}`);
618
- }
710
+ // #544: survivor detail on a machine-readable line. Printed only when a report
711
+ // exists -- absent means "not measured", never "measured: zero survivors".
712
+ const survivors = await readSurvivorSummary();
713
+ if (survivors) {
714
+ console.log(`GATE_M_SURVIVORS_JSON=${JSON.stringify(survivors)}`);
619
715
  }
620
716
 
621
- if (result.errors.length > 0) {
622
- console.log('\nErrors:');
623
- for (const error of result.errors) {
624
- console.error(` ✗ ${error}`);
625
- }
626
- }
717
+ logDiagnostics(result.warnings, result.errors);
627
718
 
628
719
  const statusIcon = result.status === 'pass' ? '✓' :
629
720
  result.status === 'skip' ? '⊘' :
@@ -634,6 +725,18 @@ export async function main(args: string[]): Promise<number> {
634
725
  return result.exitCode;
635
726
  }
636
727
 
728
+ /** Print Gate M's warnings and errors. Errors go to stderr. */
729
+ function logDiagnostics(warnings: string[], errors: string[]): void {
730
+ if (warnings.length > 0) {
731
+ console.log('\nWarnings:');
732
+ for (const warning of warnings) console.log(` ⚠ ${warning}`);
733
+ }
734
+ if (errors.length > 0) {
735
+ console.log('\nErrors:');
736
+ for (const error of errors) console.error(` ✗ ${error}`);
737
+ }
738
+ }
739
+
637
740
  const cliArgs = process.argv.slice(2);
638
741
  if (typeof require !== 'undefined' && require.main === module) {
639
742
  main(cliArgs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/xp-gate",
3
- "version": "0.30.3",
3
+ "version": "0.30.4",
4
4
  "description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
5
5
  "bin": {
6
6
  "xp-gate": "bin/xp-gate.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.30.3",
3
+ "version": "0.30.4",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/dsh-plugin-xp-gate",
3
- "version": "0.30.3",
3
+ "version": "0.30.4",
4
4
  "description": "XP-Gate deterministic quality gates + AI workflow skills as a DeepSeek Harness (DSH) native plugin: gate-check / gate-principles / gate-arch tools + 12 SKILL.md skills",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.30.3",
3
+ "version": "0.30.4",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.30.3",
3
+ "version": "0.30.4",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,9 +1,9 @@
1
1
  # PRINCIPLES CHECKER MODULE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-20
4
- **Commit:** 03f6d10a
4
+ **Commit:** 54318594
5
5
  **Branch:** main
6
- **Version:** 0.30.3.0
6
+ **Version:** 0.30.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.