@boyingliu01/xp-gate 0.34.1 → 0.35.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.
Files changed (31) hide show
  1. package/gate-3.sh +46 -2
  2. package/gate-4.sh +20 -3
  3. package/hooks/gate-3.sh +46 -2
  4. package/hooks/gate-4.sh +20 -3
  5. package/hooks/lib/ccn-ratchet.sh +109 -0
  6. package/lib/__tests__/doctor-timeout-partial.test.js +79 -0
  7. package/lib/doctor.js +60 -3
  8. package/lib/mattpocock-license-verdict.json +1 -1
  9. package/mock-policy/AGENTS.md +2 -2
  10. package/mutation/AGENTS.md +2 -2
  11. package/package.json +1 -1
  12. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  13. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  14. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  15. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  16. package/plugins/dsh/package.json +1 -1
  17. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  18. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  19. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  20. package/plugins/opencode/package.json +1 -1
  21. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  22. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  23. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  24. package/plugins/qoder/plugin.json +1 -1
  25. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  26. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  27. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  28. package/principles/AGENTS.md +2 -2
  29. package/skills/delphi-review/AGENTS.md +2 -2
  30. package/skills/sprint-flow/AGENTS.md +2 -2
  31. package/skills/test-specification-alignment/AGENTS.md +2 -2
package/gate-3.sh CHANGED
@@ -461,6 +461,52 @@ else
461
461
  GATE_3_DETAIL="mode=ratchet verifier=MISSING"
462
462
  exit 1
463
463
  fi
464
+ RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
465
+ [ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
466
+ # #617: grade exactly the population the baseline was written from.
467
+ #
468
+ # The comment below has always said so, but only as a comment: a repo whose
469
+ # baseline came from a narrower scan (a Java-only ccn-report, say) had its
470
+ # vendored minified JS graded anyway, and --csv-verified then disagreed with
471
+ # lizard on single-line code and fail-closed with "could not be
472
+ # span-verified". That BLOCK has no repair: the file has no baseline entry
473
+ # to ratchet, so there is nothing the user could add to clear it.
474
+ #
475
+ # The population filter runs BEFORE verification on purpose. Domain
476
+ # restriction is decidable from the path alone, so asking the expensive
477
+ # span re-derivation about a file that will not be graded anyway only
478
+ # manufactures failures for code the ratchet never claims to cover.
479
+ if [ -f "$RATCHET_LIB" ]; then
480
+ # shellcheck source=/dev/null
481
+ . "$RATCHET_LIB" 2>/dev/null || true
482
+ if declare -F ccn_file_in_population >/dev/null 2>&1; then
483
+ _cc_pop_kept=""
484
+ _cc_pop_skipped=""
485
+ while IFS= read -r _cc_row; do
486
+ [ -n "$_cc_row" ] || continue
487
+ _cc_row_path=$(printf '%s' "$_cc_row" | awk -F, '{print $1}')
488
+ if ccn_file_in_population ".ccn-baseline.json" "$_cc_row_path"; then
489
+ _cc_pop_kept="${_cc_pop_kept}${_cc_row}
490
+ "
491
+ else
492
+ _cc_pop_skipped="${_cc_pop_skipped}${_cc_row_path}
493
+ "
494
+ fi
495
+ done <<CC_POP_EOF
496
+ $CC_CSV
497
+ CC_POP_EOF
498
+ CC_CSV=$(printf '%s' "$_cc_pop_kept")
499
+ if [ -n "$_cc_pop_skipped" ]; then
500
+ _cc_pop_skip_n=$(printf '%s' "$_cc_pop_skipped" | grep -c . || true)
501
+ echo "ℹ️ Gate 3: ${_cc_pop_skip_n:-0} file(s) outside the baseline population — not graded, not ratcheted:"
502
+ printf '%s' "$_cc_pop_skipped" | sed 's/^/ /' | sed -n '1,5p'
503
+ if [ "${_cc_pop_skip_n:-0}" -gt 5 ]; then
504
+ echo " ... and $((_cc_pop_skip_n - 5)) more"
505
+ fi
506
+ echo " (declare or widen the population in .ccn-baseline.json to grade them)"
507
+ fi
508
+ fi
509
+ fi
464
510
  if [ -f "$CC_REPORT" ] && command -v node >/dev/null 2>&1; then
465
511
  _cc_verify_failed=0
466
512
  CC_CSV_VERIFIED=$(printf '%s\n' "$CC_CSV" | node "$CC_REPORT" --csv-verified batch --root "$(git rev-parse --show-toplevel 2>/dev/null)") || _cc_verify_failed=1
@@ -471,8 +517,6 @@ else
471
517
  fi
472
518
  CC_CSV="$CC_CSV_VERIFIED"
473
519
  fi
474
- RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
475
- [ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
476
520
  # Fail closed when the ratchet library is missing. Sourcing a nonexistent
477
521
  # file only prints "No such file or directory" and leaves
478
522
  # evaluate_ccn_ratchet undefined; the verdict then came back empty, every
package/gate-4.sh CHANGED
@@ -172,9 +172,26 @@ else
172
172
  rm -f "$PRINCIPLES_STDERR"
173
173
 
174
174
  if [ "$PRINCIPLES_EXIT" -ge 2 ]; then
175
- echo "⚠️ Warning: Principles checker execution failed"
176
- echo "⏭️ SKIPPED - Principles check (execution issue)"
177
- GATE_4_STATUS="SKIP"
175
+ # #661: exit>=2 means the checker did not RUN (crash, module not found,
176
+ # bad invocation). This used to record SKIP, and pre-commit treats SKIP
177
+ # as non-blocking, so a tool failure was absorbed into the green light --
178
+ # the reproduction printed a module-not-found stack trace and then
179
+ # "✅ PASSED - Principles checker (no errors found)".
180
+ #
181
+ # The distinction the gate must keep:
182
+ # - tool NOT INSTALLED -> SKIP is honest; nothing is being claimed.
183
+ # - tool PRESENT, CRASH -> WARN. Not a pass (nothing was checked) and
184
+ # not a silent skip (the user must know their code went unaudited).
185
+ #
186
+ # WARN rather than FAIL on purpose: the commit itself is not necessarily
187
+ # bad, the TOOL is broken, and blocking every commit until node/tsx is
188
+ # repaired would blame the change for an environment fault. What must
189
+ # never happen is the green light.
190
+ echo "⚠️ Principles checker did not run (exit ${PRINCIPLES_EXIT}) — this code is NOT verified."
191
+ echo " Gate 4 cannot claim a pass for a check that never executed."
192
+ echo " Fix the checker invocation above (or run 'npx xp-gate check principles .')."
193
+ echo "⏭️ WARN - Principles check (checker execution error)"
194
+ GATE_4_STATUS="WARN"
178
195
  # Consumed by the sourcing parent, not here (see #548).
179
196
  # shellcheck disable=SC2034
180
197
  GATE_4_REASON="checker_execution_error"
package/hooks/gate-3.sh CHANGED
@@ -461,6 +461,52 @@ else
461
461
  GATE_3_DETAIL="mode=ratchet verifier=MISSING"
462
462
  exit 1
463
463
  fi
464
+ RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
465
+ [ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
466
+ # #617: grade exactly the population the baseline was written from.
467
+ #
468
+ # The comment below has always said so, but only as a comment: a repo whose
469
+ # baseline came from a narrower scan (a Java-only ccn-report, say) had its
470
+ # vendored minified JS graded anyway, and --csv-verified then disagreed with
471
+ # lizard on single-line code and fail-closed with "could not be
472
+ # span-verified". That BLOCK has no repair: the file has no baseline entry
473
+ # to ratchet, so there is nothing the user could add to clear it.
474
+ #
475
+ # The population filter runs BEFORE verification on purpose. Domain
476
+ # restriction is decidable from the path alone, so asking the expensive
477
+ # span re-derivation about a file that will not be graded anyway only
478
+ # manufactures failures for code the ratchet never claims to cover.
479
+ if [ -f "$RATCHET_LIB" ]; then
480
+ # shellcheck source=/dev/null
481
+ . "$RATCHET_LIB" 2>/dev/null || true
482
+ if declare -F ccn_file_in_population >/dev/null 2>&1; then
483
+ _cc_pop_kept=""
484
+ _cc_pop_skipped=""
485
+ while IFS= read -r _cc_row; do
486
+ [ -n "$_cc_row" ] || continue
487
+ _cc_row_path=$(printf '%s' "$_cc_row" | awk -F, '{print $1}')
488
+ if ccn_file_in_population ".ccn-baseline.json" "$_cc_row_path"; then
489
+ _cc_pop_kept="${_cc_pop_kept}${_cc_row}
490
+ "
491
+ else
492
+ _cc_pop_skipped="${_cc_pop_skipped}${_cc_row_path}
493
+ "
494
+ fi
495
+ done <<CC_POP_EOF
496
+ $CC_CSV
497
+ CC_POP_EOF
498
+ CC_CSV=$(printf '%s' "$_cc_pop_kept")
499
+ if [ -n "$_cc_pop_skipped" ]; then
500
+ _cc_pop_skip_n=$(printf '%s' "$_cc_pop_skipped" | grep -c . || true)
501
+ echo "ℹ️ Gate 3: ${_cc_pop_skip_n:-0} file(s) outside the baseline population — not graded, not ratcheted:"
502
+ printf '%s' "$_cc_pop_skipped" | sed 's/^/ /' | sed -n '1,5p'
503
+ if [ "${_cc_pop_skip_n:-0}" -gt 5 ]; then
504
+ echo " ... and $((_cc_pop_skip_n - 5)) more"
505
+ fi
506
+ echo " (declare or widen the population in .ccn-baseline.json to grade them)"
507
+ fi
508
+ fi
509
+ fi
464
510
  if [ -f "$CC_REPORT" ] && command -v node >/dev/null 2>&1; then
465
511
  _cc_verify_failed=0
466
512
  CC_CSV_VERIFIED=$(printf '%s\n' "$CC_CSV" | node "$CC_REPORT" --csv-verified batch --root "$(git rev-parse --show-toplevel 2>/dev/null)") || _cc_verify_failed=1
@@ -471,8 +517,6 @@ else
471
517
  fi
472
518
  CC_CSV="$CC_CSV_VERIFIED"
473
519
  fi
474
- RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
475
- [ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
476
520
  # Fail closed when the ratchet library is missing. Sourcing a nonexistent
477
521
  # file only prints "No such file or directory" and leaves
478
522
  # evaluate_ccn_ratchet undefined; the verdict then came back empty, every
package/hooks/gate-4.sh CHANGED
@@ -172,9 +172,26 @@ else
172
172
  rm -f "$PRINCIPLES_STDERR"
173
173
 
174
174
  if [ "$PRINCIPLES_EXIT" -ge 2 ]; then
175
- echo "⚠️ Warning: Principles checker execution failed"
176
- echo "⏭️ SKIPPED - Principles check (execution issue)"
177
- GATE_4_STATUS="SKIP"
175
+ # #661: exit>=2 means the checker did not RUN (crash, module not found,
176
+ # bad invocation). This used to record SKIP, and pre-commit treats SKIP
177
+ # as non-blocking, so a tool failure was absorbed into the green light --
178
+ # the reproduction printed a module-not-found stack trace and then
179
+ # "✅ PASSED - Principles checker (no errors found)".
180
+ #
181
+ # The distinction the gate must keep:
182
+ # - tool NOT INSTALLED -> SKIP is honest; nothing is being claimed.
183
+ # - tool PRESENT, CRASH -> WARN. Not a pass (nothing was checked) and
184
+ # not a silent skip (the user must know their code went unaudited).
185
+ #
186
+ # WARN rather than FAIL on purpose: the commit itself is not necessarily
187
+ # bad, the TOOL is broken, and blocking every commit until node/tsx is
188
+ # repaired would blame the change for an environment fault. What must
189
+ # never happen is the green light.
190
+ echo "⚠️ Principles checker did not run (exit ${PRINCIPLES_EXIT}) — this code is NOT verified."
191
+ echo " Gate 4 cannot claim a pass for a check that never executed."
192
+ echo " Fix the checker invocation above (or run 'npx xp-gate check principles .')."
193
+ echo "⏭️ WARN - Principles check (checker execution error)"
194
+ GATE_4_STATUS="WARN"
178
195
  # Consumed by the sourcing parent, not here (see #548).
179
196
  # shellcheck disable=SC2034
180
197
  GATE_4_REASON="checker_execution_error"
@@ -11,6 +11,23 @@
11
11
  # improves we say so and suggest refreshing the baseline, because a stale
12
12
  # ceiling invites nobody to finish the job.
13
13
  #
14
+ # ── Population (#617) ───────────────────────────────────────────────────────
15
+ # A ratchet is only meaningful over the population it was recorded from. Gate 3
16
+ # grades every extension it recognises; a repo whose baseline came from a
17
+ # narrower scan then reports pre-existing code as "new", which is a false BLOCK
18
+ # no repair can clear — the file has no baseline entry to fix.
19
+ #
20
+ # The baseline may therefore DECLARE its population:
21
+ #
22
+ # { "population": { "roots": ["src/main/java"], "extensions": [".java"] },
23
+ # "entries": { "src/main/java/A.java::f": 4 } }
24
+ #
25
+ # A baseline without that key is LEGACY and is treated as unrestricted, which is
26
+ # exactly the historical behaviour: every baseline already in the field has the
27
+ # flat `{ "path::fn": ccn }` shape, and refusing to load it would break every
28
+ # install on upgrade. Absent metadata must never be read as an empty population
29
+ # -- that would silently grade nothing at all, the opposite failure.
30
+ #
14
31
  # Key = "<relative/path>::<functionName>". Rename or move = new key = treated
15
32
  # as a new function. That is deliberate: fuzzy matching would hide debt moves.
16
33
  # `node scripts/ccn-report.cjs --write-baseline` regenerates the file after a
@@ -41,6 +58,98 @@
41
58
  # every row is read as a single field).
42
59
  CSV_FS=$'\x1f'
43
60
 
61
+ # ---------------------------------------------------------------------------
62
+ # #617: baseline population.
63
+ #
64
+ # Prints the declared population as `<root>|<ext>|<ext>...` on one line, or
65
+ # NOTHING when the baseline carries no `population` key (legacy => unrestricted).
66
+ # Printing nothing is deliberate: "no declaration" and "empty population" are
67
+ # opposite meanings, and a caller that cannot tell them apart would either grade
68
+ # everything (wrong for a Java-only repo) or nothing (catastrophic -- the ratchet
69
+ # would report a clean run while comparing zero functions).
70
+ # ---------------------------------------------------------------------------
71
+ ccn_baseline_population() {
72
+ local _bl="${1:-.ccn-baseline.json}"
73
+ [ -f "$_bl" ] || return 0
74
+ command -v node >/dev/null 2>&1 || return 0
75
+ node -e '
76
+ const fs = require("fs");
77
+ let data;
78
+ try { data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); }
79
+ catch { process.exit(0); }
80
+ // Legacy shape: a flat map of "path::fn" -> ccn. No declaration.
81
+ if (!data || typeof data !== "object" || !data.population) process.exit(0);
82
+ const pop = data.population || {};
83
+ const roots = Array.isArray(pop.roots) ? pop.roots : [];
84
+ const exts = Array.isArray(pop.extensions) ? pop.extensions : [];
85
+ // A declared-but-empty population is not a legitimate declaration: it would
86
+ // excuse every file. Treat it as no declaration so the gate keeps grading.
87
+ if (roots.length === 0 && exts.length === 0) process.exit(0);
88
+ // One line: "<roots csv>|<exts csv>". Callers split on the single pipe.
89
+ process.stdout.write(roots.join(",") + "|" + exts.join(","));
90
+ ' "$_bl" 2>/dev/null
91
+ return 0
92
+ }
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # #617: is this file inside the baseline's declared population?
96
+ #
97
+ # Returns 0 when the file may be graded, 1 when it is outside the population.
98
+ # A legacy baseline (no declaration) permits everything, preserving the
99
+ # behaviour every existing install already relies on.
100
+ #
101
+ # Both axes are ANDed only when both are declared; a baseline declaring just
102
+ # roots (or just extensions) narrows on that axis alone.
103
+ # ---------------------------------------------------------------------------
104
+ ccn_file_in_population() {
105
+ local _bl="${1:-.ccn-baseline.json}"
106
+ local _file="${2:-}"
107
+ [ -n "$_file" ] || return 1
108
+
109
+ local _pop
110
+ _pop=$(ccn_baseline_population "$_bl")
111
+ # No declaration => unrestricted (legacy baseline).
112
+ [ -n "$_pop" ] || return 0
113
+
114
+ local _roots="${_pop%%|*}"
115
+ local _exts="${_pop#*|}"
116
+ # Normalise separators so a Windows path and a POSIX path compare the same way.
117
+ # sed, not `tr '\' '/'`: a lone backslash is a portability trap in tr's SET
118
+ # argument (GNU tr warns "unescaped backslash at end of string" and MSYS tr
119
+ # behaviour differs), while the sed regex below is unambiguous everywhere.
120
+ _norm=$(printf '%s' "$_file" | sed 's|\\|/|g')
121
+
122
+ if [ -n "$_roots" ]; then
123
+ local _hit=0 _r
124
+ local _oldifs="$IFS"
125
+ IFS=','
126
+ for _r in $_roots; do
127
+ IFS="$_oldifs"
128
+ case "$_norm" in
129
+ "$_r"|"$_r"/*) _hit=1; break ;;
130
+ esac
131
+ done
132
+ IFS="$_oldifs"
133
+ [ "$_hit" -eq 1 ] || return 1
134
+ fi
135
+
136
+ if [ -n "$_exts" ]; then
137
+ local _match=0 _e
138
+ local _oldifs2="$IFS"
139
+ IFS=','
140
+ for _e in $_exts; do
141
+ IFS="$_oldifs2"
142
+ case "$_norm" in
143
+ *"$_e") _match=1; break ;;
144
+ esac
145
+ done
146
+ IFS="$_oldifs2"
147
+ [ "$_match" -eq 1 ] || return 1
148
+ fi
149
+
150
+ return 0
151
+ }
152
+
44
153
  csv_to_unit_separated() {
45
154
  awk '
46
155
  function emit(line, cur, i, ch, q, nf) {
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @test REQ-629
3
+ * @intent `xp-gate doctor` is non-deterministic on Windows. Against an identical,
4
+ * freshly-installed, fully healthy installation it printed
5
+ * "All checks passed" on one run and "Timed out after 10s — some checks
6
+ * incomplete" on the next two. The timeout is a deliberate safety net
7
+ * (#348, "43s on Windows"); the defect is what happens when it fires.
8
+ *
9
+ * `Promise.race` discarded the entire `diagnoseAsync()` result and
10
+ * substituted a single placeholder check, so everything that had already
11
+ * finished was thrown away. Consequence: doctor can report LESS
12
+ * information the LONGER it runs — the opposite of what a diagnostic
13
+ * should do — and a WARN from the timeout made the skills section vanish
14
+ * entirely, so tooling that scrapes `Skill:.*Up to date` read 0/12.
15
+ *
16
+ * The fix: reach a verdict at the deadline (never hang — #348 stays fixed)
17
+ * but keep whatever finished. A diagnostic that says "these 9 checks
18
+ * passed, 2 did not report in time" is strictly more useful than one that
19
+ * says "timed out".
20
+ *
21
+ * The tests below pin the merge semantics directly rather than waiting on
22
+ * a real 10s timer: the behaviour that matters is "partial results are
23
+ * preserved and reported as partial", not the clock.
24
+ * @covers AC-670-43, AC-670-44, AC-670-45
25
+ */
26
+ 'use strict';
27
+
28
+ // describe/it/expect are vitest globals in this repo's config; requiring them from
29
+ // 'vitest' fails because this file is CommonJS. (Hooks are not needed here.)
30
+
31
+ /** A completed check, shaped like diagnoseAsync()'s output. */
32
+ function check(name, status) {
33
+ return { name, status, detail: `${name} detail` };
34
+ }
35
+
36
+ describe('doctor keeps completed checks when the global timeout fires (#629)', () => {
37
+ it('AC-670-43: completed checks are merged with the timeout notice, not replaced', () => {
38
+ const { mergeTimedOutDiagnosis } = require('../doctor');
39
+ expect(typeof mergeTimedOutDiagnosis).toBe('function');
40
+
41
+ const completed = {
42
+ checks: [check('Hooks', 'PASS'), check('Adapters', 'PASS'), check('Skills', 'WARN')],
43
+ issues: 1,
44
+ };
45
+ const merged = mergeTimedOutDiagnosis(completed, ['Gate env', 'OpenCode TUI']);
46
+
47
+ // Nothing that finished may be dropped -- that is the whole defect.
48
+ for (const name of ['Hooks', 'Adapters', 'Skills']) {
49
+ expect(merged.checks.map((c) => c.name)).toContain(name);
50
+ }
51
+ // The deadline itself is still reported, and still not a pass.
52
+ const notice = merged.checks.find((c) => c.name === 'Diagnosis');
53
+ expect(notice).toBeDefined();
54
+ expect(notice.status).toBe('WARN');
55
+ // Named individually, so the reader knows which surface is unknown.
56
+ expect(notice.detail).toContain('Gate env');
57
+ expect(notice.detail).toContain('OpenCode TUI');
58
+ expect(merged.timedOut).toBe(true);
59
+ // A partial run is never clean.
60
+ expect(merged.issues).toBeGreaterThanOrEqual(1);
61
+ });
62
+
63
+ it('AC-670-44: an empty partial result still yields the honest timeout notice', () => {
64
+ const { mergeTimedOutDiagnosis } = require('../doctor');
65
+ const merged = mergeTimedOutDiagnosis({ checks: [], issues: 0 }, ['Gate env']);
66
+ expect(merged.checks).toHaveLength(1);
67
+ expect(merged.checks[0].name).toBe('Diagnosis');
68
+ expect(merged.timedOut).toBe(true);
69
+ });
70
+
71
+ it('AC-670-45: nothing that already reported is ever lost, and a real issue count survives', () => {
72
+ const { mergeTimedOutDiagnosis } = require('../doctor');
73
+ const completed = { checks: [check('Hooks', 'FAIL'), check('Templates', 'PASS')], issues: 4 };
74
+ const merged = mergeTimedOutDiagnosis(completed, []);
75
+ // Pre-existing issues are not reset by the timeout.
76
+ expect(merged.issues).toBeGreaterThanOrEqual(4);
77
+ expect(merged.checks.map((c) => c.name)).toEqual(expect.arrayContaining(['Hooks', 'Templates']));
78
+ });
79
+ });
package/lib/doctor.js CHANGED
@@ -80,6 +80,38 @@ const EXEC_TIMEOUT_MS = 1500;
80
80
  */
81
81
  const GLOBAL_DIAGNOSIS_TIMEOUT_MS = 10000;
82
82
 
83
+ /**
84
+ * Merge a timed-out partial diagnosis into one honest verdict (#629).
85
+ *
86
+ * `doctor`'s global timeout (#348) exists so the command never hangs, but the
87
+ * original handler threw away the entire `diagnoseAsync()` result and substituted
88
+ * a single placeholder check. Against an identical healthy install that made the
89
+ * SAME command print "All checks passed" or "Timed out after 10s" depending on
90
+ * machine load, and a timed-out run reported 0 installed skills — doctor giving
91
+ * LESS information the LONGER it runs is the opposite of a diagnostic.
92
+ *
93
+ * Keeps every completed check, adds ONE Diagnosis notice naming what did not
94
+ * report, and never lets a partial run read as clean (#495).
95
+ *
96
+ * @param {{checks?: Array, issues?: number}} partial checks finished before the deadline
97
+ * @param {string[]} pending names of surfaces that did not report in time
98
+ */
99
+ function mergeTimedOutDiagnosis(partial, pending) {
100
+ const completed = (partial && Array.isArray(partial.checks)) ? partial.checks : [];
101
+ const inherited = (partial && Number.isFinite(partial.issues)) ? partial.issues : 0;
102
+ const pendingNames = Array.isArray(pending) ? pending.filter(Boolean) : [];
103
+ const detail = pendingNames.length > 0
104
+ ? `Timed out after ${GLOBAL_DIAGNOSIS_TIMEOUT_MS / 1000}s — did not report in time: ${pendingNames.join(', ')}`
105
+ : `Timed out after ${GLOBAL_DIAGNOSIS_TIMEOUT_MS / 1000}s — some checks incomplete`;
106
+ return {
107
+ checks: [...completed, { name: 'Diagnosis', status: 'WARN', detail }],
108
+ // A partial run is never clean, and a real issue count is never reset.
109
+ issues: Math.max(inherited, 1),
110
+ timedOut: true,
111
+ completedCheckCount: completed.length,
112
+ };
113
+ }
114
+
83
115
  /**
84
116
  * Fixed attribution text for the WorkBuddy-desktop sandbox restriction (#525).
85
117
  *
@@ -1605,9 +1637,17 @@ async function checkAdapters(checks, mode, gitDirPromise) {
1605
1637
  * D) Env + CLI tools — fully independent, run IN PARALLEL
1606
1638
  * E) TUI + Skills — independent, run IN PARALLEL
1607
1639
  */
1608
- async function diagnoseAsync(refreshEnv = false) {
1640
+ async function diagnoseAsync(refreshEnv = false, progress = null) {
1641
+ // #629: `progress` is a live view of the checks completed so far. The global
1642
+ // timeout used to discard everything diagnoseAsync had already produced; with a
1643
+ // shared sink, a deadline can report "these finished, those did not" instead of
1644
+ // replacing the whole diagnosis with a placeholder.
1609
1645
  const checks = [];
1610
1646
  let issues = 0;
1647
+ if (progress) {
1648
+ progress.checks = checks;
1649
+ progress.getIssues = () => issues;
1650
+ }
1611
1651
 
1612
1652
  // Spawn capability probe FIRST (sync, no config dependency) — #525: it must
1613
1653
  // report even when the config early-exits below, and its WARN (environmental,
@@ -2869,10 +2909,26 @@ async function doctor(args) {
2869
2909
 
2870
2910
  // Global timeout wrapper — ensures doctor completes within GLOBAL_DIAGNOSIS_TIMEOUT_MS
2871
2911
  // even if network/subprocess checks stack up (fix #348: 43s on Windows).
2872
- const diagnosisPromise = diagnoseAsync(refreshEnv);
2912
+ // #629: give diagnoseAsync a live sink so a deadline can keep what finished.
2913
+ // Without it, `Promise.race` discarded the entire diagnosis -- so the SAME
2914
+ // healthy install reported different results run to run, and a timed-out run
2915
+ // lost the skills section entirely (scrapers read 0/12 installed skills).
2916
+ const progress = { checks: [], getIssues: () => 0 };
2917
+ const diagnosisPromise = diagnoseAsync(refreshEnv, progress);
2873
2918
  let diagnosisTimer = null;
2874
2919
  const timeoutPromise = new Promise((resolve) => {
2875
- diagnosisTimer = setTimeout(() => resolve({ checks: [{ name: 'Diagnosis', status: 'WARN', detail: `Timed out after ${GLOBAL_DIAGNOSIS_TIMEOUT_MS / 1000}s — some checks incomplete` }], issues: 0, timedOut: true }), GLOBAL_DIAGNOSIS_TIMEOUT_MS);
2920
+ diagnosisTimer = setTimeout(() => {
2921
+ const completed = Array.isArray(progress.checks) ? progress.checks : [];
2922
+ const reported = new Set(completed.map((c) => c && c.name));
2923
+ // Name the surfaces that did NOT report, so the reader knows exactly which
2924
+ // conclusions are missing rather than just "something timed out".
2925
+ const pending = ['Hooks', 'Adapters', 'Skills', 'Gate env', 'OpenCode TUI']
2926
+ .filter((name) => !reported.has(name));
2927
+ resolve(mergeTimedOutDiagnosis(
2928
+ { checks: completed, issues: progress.getIssues() },
2929
+ pending,
2930
+ ));
2931
+ }, GLOBAL_DIAGNOSIS_TIMEOUT_MS);
2876
2932
  });
2877
2933
  const { checks, issues: diagnosedIssues, timedOut } = await Promise.race([
2878
2934
  diagnosisPromise.then(r => ({ ...r, timedOut: false })),
@@ -3349,6 +3405,7 @@ module.exports = {
3349
3405
  readTuiJson,
3350
3406
  formatDoctorJson,
3351
3407
  diagnoseHookDrift,
3408
+ mergeTimedOutDiagnosis,
3352
3409
  classifyHooksDir,
3353
3410
  syncGlobalHooksFromRepo,
3354
3411
  diagnoseModuleDrift,
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-30T14:53:53.917Z",
5
+ "checked_at": "2026-10-30T15:31:30.899Z",
6
6
  "source": "network"
7
7
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/xp-gate",
3
- "version": "0.34.1",
3
+ "version": "0.35.0",
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.34.1",
3
+ "version": "0.35.0",
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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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.34.1",
3
+ "version": "0.35.0",
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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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.34.1",
3
+ "version": "0.35.0",
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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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.34.1",
3
+ "version": "0.35.0",
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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.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-30
4
- **Commit:** e583bcfb
4
+ **Commit:** c8a73b2a
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.