@boyingliu01/xp-gate 0.33.0 → 0.33.2

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 (48) hide show
  1. package/gate-3.sh +48 -7
  2. package/gate-8.sh +32 -3
  3. package/gate-9.sh +14 -2
  4. package/gates/common-process.ts +178 -0
  5. package/gates/common.ts +208 -0
  6. package/gates/gate-3.ts +144 -0
  7. package/gates/gate-7.ts +147 -0
  8. package/gates/gate-8.ts +92 -0
  9. package/gates/gate-9.ts +269 -0
  10. package/gates/pbt-detect.ts +163 -0
  11. package/gates/python-health.ts +266 -0
  12. package/gates/test-layers.ts +312 -0
  13. package/hooks/gate-3.sh +48 -7
  14. package/hooks/gate-8.sh +32 -3
  15. package/hooks/gate-9.sh +14 -2
  16. package/hooks/pre-commit +51 -2
  17. package/lib/__tests__/check-normalize-exit.test.js +24 -0
  18. package/lib/__tests__/shared-utils.test.js +21 -0
  19. package/lib/__tests__/uninstall.test.js +18 -0
  20. package/lib/check.js +27 -4
  21. package/lib/gate-runner.js +6 -1
  22. package/lib/mattpocock-license-verdict.json +1 -1
  23. package/lib/shared-utils.js +6 -1
  24. package/lib/uninstall.js +21 -1
  25. package/mock-policy/AGENTS.md +2 -2
  26. package/mutation/AGENTS.md +2 -2
  27. package/package.json +2 -1
  28. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  29. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  30. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  31. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  32. package/plugins/dsh/package.json +1 -1
  33. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  34. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  35. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  36. package/plugins/opencode/package.json +1 -1
  37. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  38. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  39. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  40. package/plugins/qoder/plugin.json +1 -1
  41. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  42. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  43. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  44. package/principles/AGENTS.md +2 -2
  45. package/scripts/sync-package-content.js +6 -1
  46. package/skills/delphi-review/AGENTS.md +2 -2
  47. package/skills/sprint-flow/AGENTS.md +2 -2
  48. package/skills/test-specification-alignment/AGENTS.md +2 -2
package/hooks/gate-8.sh CHANGED
@@ -59,10 +59,24 @@ fi
59
59
  # revisions and reports "0 commits scanned" on a pre-commit run -- so a staged
60
60
  # secret was never seen and Gate 8 always passed. `git --staged` scans
61
61
  # `git diff --cached`, which is exactly the pre-commit payload (#449).
62
+ #
63
+ # #616: `--all` is a different question, and the index is the wrong place to ask
64
+ # it. Under `xp-gate check . --all` the user asked "is the whole tree clean", the
65
+ # index is usually empty, `git --staged` legitimately found nothing, and that
66
+ # nothing was counted as PASSED -- a secret sitting in a tracked file was
67
+ # invisible to the one command meant to find it. The runner already exports
68
+ # GATE_SCOPE (#589); this fragment just has to read it. `dir --no-git` scans the
69
+ # filesystem so tracked-but-unmodified files are covered.
62
70
  GITLEAKS_REPORT="${TMPDIR:-/tmp}/gitleaks-report.json"
63
71
  rm -f "$GITLEAKS_REPORT"
64
- GITLEAKS_OUTPUT=$($GITLEAKS_CMD git --staged --redact --no-banner $GITLEAKS_CONFIG --report-format=json --report-path="$GITLEAKS_REPORT" 2>&1)
65
- GITLEAKS_EXIT=$?
72
+ if [ "${GATE_SCOPE:-diff}" = "all" ]; then
73
+ echo " Scope: --all (scanning the working tree, not the index)"
74
+ GITLEAKS_OUTPUT=$($GITLEAKS_CMD dir --no-git --redact --no-banner $GITLEAKS_CONFIG --report-format=json --report-path="$GITLEAKS_REPORT" 2>&1)
75
+ GITLEAKS_EXIT=$?
76
+ else
77
+ GITLEAKS_OUTPUT=$($GITLEAKS_CMD git --staged --redact --no-banner $GITLEAKS_CONFIG --report-format=json --report-path="$GITLEAKS_REPORT" 2>&1)
78
+ GITLEAKS_EXIT=$?
79
+ fi
66
80
 
67
81
  if [ "$GITLEAKS_EXIT" -eq 0 ]; then
68
82
  # Decide SKIP from the REPORT ARTIFACT, not from log wording. Parsing stdout
@@ -183,7 +197,22 @@ fi
183
197
  # if-condition (errexit-exempt); a non-zero hook exit lands in the else branch.
184
198
  if command -v detect-secrets-hook >/dev/null 2>&1 && [ -f ".secrets.baseline" ]; then
185
199
  DS_TMP="$(dirname "$(mktemp -u)")/ds-output.$$"
186
- if git diff --cached --name-only --diff-filter=ACM -z \
200
+ # #616: same scope split as the gitleaks call above. Under `--all` the index is
201
+ # usually empty, so piping `git diff --cached -z` into xargs fed the hook zero
202
+ # files and it reported success without reading anything.
203
+ if [ "${GATE_SCOPE:-diff}" = "all" ]; then
204
+ if git ls-files -z | xargs -0 detect-secrets-hook --baseline .secrets.baseline > "$DS_TMP" 2>&1; then
205
+ echo " ✅ detect-secrets PASSED - no new secrets beyond baseline (scope: --all)."
206
+ else
207
+ cat "$DS_TMP"
208
+ echo ""
209
+ echo " ❌ BLOCKED - detect-secrets found secrets not in .secrets.baseline."
210
+ echo " If these are false positives, regenerate the baseline (cross-platform):"
211
+ echo " detect-secrets scan --update .secrets.baseline && git add .secrets.baseline"
212
+ rm -f "$DS_TMP"
213
+ exit 1
214
+ fi
215
+ elif git diff --cached --name-only --diff-filter=ACM -z \
187
216
  | xargs -0 detect-secrets-hook --baseline .secrets.baseline > "$DS_TMP" 2>&1; then
188
217
  echo " ✅ detect-secrets PASSED - no new secrets beyond baseline."
189
218
  else
package/hooks/gate-9.sh CHANGED
@@ -32,8 +32,20 @@ if [ -z "$SEMGREP_CMD" ]; then
32
32
  echo " Gate 9: SAST Security (WARN, semgrep not installed)"
33
33
  GATE_9_STATUS="WARN"
34
34
  else
35
- # Get staged files filtered to Semgrep-supported languages
36
- SEMGREP_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.(ts|tsx|js|jsx|py|go|java|c|cpp|cs|rb|php|scala|swift)$' || true)
35
+ # Get staged files filtered to Semgrep-supported languages.
36
+ #
37
+ # #616: under `--all` the index is usually empty, so the staged diff yielded no
38
+ # files and the gate reported SKIPPED -- "no supported language files changed"
39
+ # -- for a run whose whole purpose was to scan the tree. Same fail-open shape
40
+ # #589 fixed in Gate 3, one layer down: the CLI says the tree was checked and
41
+ # the fragment checks a diff that does not exist. The runner exports GATE_SCOPE;
42
+ # read it and enumerate tracked files instead.
43
+ if [ "${GATE_SCOPE:-diff}" = "all" ]; then
44
+ echo " Scope: --all (scanning tracked files, not the index)"
45
+ SEMGREP_FILES=$(git ls-files 2>/dev/null | grep -E '\.(ts|tsx|js|jsx|py|go|java|c|cpp|cs|rb|php|scala|swift)$' || true)
46
+ else
47
+ SEMGREP_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.(ts|tsx|js|jsx|py|go|java|c|cpp|cs|rb|php|scala|swift)$' || true)
48
+ fi
37
49
 
38
50
  if [ -z "$SEMGREP_FILES" ]; then
39
51
  echo " ⏭️ SKIPPED - SAST (no supported language files changed)"
package/hooks/pre-commit CHANGED
@@ -3495,6 +3495,29 @@ else
3495
3495
  GATE_5_STATUS="PASS"
3496
3496
  fi
3497
3497
  fi
3498
+
3499
+ # #665: a subset run is exempt from the GLOBAL coverage floor by design (#473),
3500
+ # but the exemption was never published, so the persisted report showed
3501
+ # `actual.coverage = 29` beside `thresholds.lines = 82` with `status = PASS` and
3502
+ # `reason = ""`. The verdict was correct; the RECORD was unreadable, and a record
3503
+ # is all `xp-gate audit` and the tech-debt trackers (#636, #638) ever see. Two
3504
+ # opposite situations -- "coverage really was 29% and the gate passed it" and
3505
+ # "a subset run, global floor not applicable" -- rendered identically.
3506
+ #
3507
+ # Derived from PARTIAL_TEST_RUN at ONE place instead of being set by hand at each
3508
+ # of the six branches that mark a partial run: a reason written at one call site
3509
+ # and forgotten at another is the drift that produced the empty reason here.
3510
+ if [ "${PARTIAL_TEST_RUN:-false}" = "true" ]; then
3511
+ GATE_5_COVERAGE_APPLIES_TO="subset"
3512
+ # Never overwrite a reason the branch itself recorded (docs_only_change,
3513
+ # no_code_files_changed, ...) -- those explain a different verdict.
3514
+ if [ -z "${GATE_5_DETAIL:-}" ] && [ "$GATE_5_STATUS" = "PASS" ]; then
3515
+ GATE_5_DETAIL="partial_test_run_coverage_not_applicable"
3516
+ fi
3517
+ else
3518
+ GATE_5_COVERAGE_APPLIES_TO="project"
3519
+ fi
3520
+
3498
3521
  record_gate_audit "gate-5" "tests-coverage" "$GATE_5_STATUS" "0" "$GATE_5_START" "${GATE_5_DETAIL:-}"
3499
3522
 
3500
3523
  # ============================================================================
@@ -3619,7 +3642,26 @@ else
3619
3642
  if [ -f "arch-go.yaml" ] || [ -f "arch-go.yml" ]; then
3620
3643
  if require_tool "arch-go" "Gate 6" "go install github.com/arch-go/arch-go@latest"; then
3621
3644
  echo " Running arch-go for Go..."
3645
+ # #630: #602 replaced the vacuous Java arm with a status-based verdict,
3646
+ # but the Go arm kept the identical fail-open shape: it piped arch-go
3647
+ # output into `head` and then printed "Go architecture validation
3648
+ # completed" unconditionally. A pipeline's status is `head`'s -- 0 for
3649
+ # ANY non-empty output -- so every arch-go result, contract violations
3650
+ # included, printed "completed" and the commit landed. A gate that
3651
+ # cannot fail is not a gate. Capture the checker's own status and
3652
+ # block on it, as the Python arm above and run_java_build_integrity do.
3622
3653
  arch-go check 2>&1 | head -20
3654
+ # The pipeline's own status is head's, so the checker's verdict has
3655
+ # to come from PIPESTATUS[0] (#630). This is the same reading #602
3656
+ # installed for Java via run_java_build_integrity.
3657
+ _go_arch_rc="${PIPESTATUS[0]}"
3658
+ if [ "$_go_arch_rc" -ne 0 ]; then
3659
+ echo ""
3660
+ echo "❌ BLOCKED - Go architecture contract violations (arch-go, exit $_go_arch_rc)"
3661
+ echo "Fix the layering violation(s) above before committing."
3662
+ GATE_6_REASON_BY_LANG["$CURRENT_LANG"]="architecture_tests_failed"
3663
+ exit 1
3664
+ fi
3623
3665
  echo " ✅ Go architecture validation completed."
3624
3666
  else
3625
3667
  echo " ❌ BLOCKED - arch-go not installed"
@@ -4583,7 +4625,7 @@ generate_quality_report() {
4583
4625
  "reason": "${GATE_5_DETAIL:-}",
4584
4626
  "thresholds": { "lines": ${COVERAGE_THRESHOLD}, "functions": null, "branches": null, "statements": null },
4585
4627
  "threshold_source": "${COVERAGE_THRESHOLD_SOURCE}",
4586
- "actual": { "coverage": "${COV_PCT}" },
4628
+ "actual": { "coverage": "${COV_PCT}", "applies_to": "${GATE_5_COVERAGE_APPLIES_TO:-project}" },
4587
4629
  "failedTests": $FAILED_TESTS_JSON
4588
4630
  },
4589
4631
  "gate6_arch_boyscout": {
@@ -4681,7 +4723,14 @@ ENDJSON
4681
4723
  printf " %-45s %s\n" "Gate 2: Duplicate Code" "${GATE_2_STATUS:-SKIP}"
4682
4724
  printf " %-45s %s (warnings: ${CC_WARNINGS:-0})\n" "Gate 3: Complexity" "${GATE_3_STATUS:-SKIP}"
4683
4725
  printf " %-45s %s (warnings: ${WARNING_COUNT:-0})\n" "Gate 4: Principles" "${GATE_4_STATUS:-SKIP}"
4684
- printf " %-45s %s (coverage: ${COV_PCT}%%)\n" "Gate 5: Tests + Coverage" "${GATE_5_STATUS:-SKIP}"
4726
+ # #665: a subset run's percentage describes a SUBSET, not the project, and the
4727
+ # global floor was not applied. Printing the bare number next to a PASS reads as
4728
+ # "29% passed an 82% floor". Say which population the number came from.
4729
+ if [ "${GATE_5_COVERAGE_APPLIES_TO:-project}" = "subset" ]; then
4730
+ printf " %-45s %s (coverage: ${COV_PCT}%%, subset run -- global floor not checked)\n" "Gate 5: Tests + Coverage" "${GATE_5_STATUS:-SKIP}"
4731
+ else
4732
+ printf " %-45s %s (coverage: ${COV_PCT}%%)\n" "Gate 5: Tests + Coverage" "${GATE_5_STATUS:-SKIP}"
4733
+ fi
4685
4734
  printf " %-45s %s\n" "Gate 6: Architecture + Boy Scout" "${GATE_6_STATUS:-SKIP}"
4686
4735
  printf " %-45s %s\n" "Gate 7: IaC Security" "${GATE_7_STATUS:-SKIP}"
4687
4736
  printf " %-45s %s\n" "Gate 8: Secret Scanning" "${GATE_8_STATUS:-SKIP}"
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @test check-normalize-exit
3
+ * @intent Prove the CLI never hands a non-numeric verdict to process.exit (the #657 crash) and that ONLY the GATE_NOT_RUN sentinel is coerced to 0
4
+ * @covers AC-DSH-019
5
+ */
6
+ const { normalizeGateExit } = require('../check.js');
7
+
8
+ describe('normalizeGateExit (#657)', () => {
9
+ it('coerces the GATE_NOT_RUN string sentinel to 0 (honest skip, not a crash)', () => {
10
+ expect(normalizeGateExit('NOT_RUN')).toBe(0);
11
+ });
12
+
13
+ it('passes real numeric verdicts through unchanged', () => {
14
+ expect(normalizeGateExit(0)).toBe(0);
15
+ expect(normalizeGateExit(1)).toBe(1);
16
+ expect(normalizeGateExit(2)).toBe(2);
17
+ });
18
+
19
+ it('never returns a non-number for any input', () => {
20
+ for (const v of ['NOT_RUN', 0, 1, undefined, null]) {
21
+ expect(typeof normalizeGateExit(v)).toBe('number');
22
+ }
23
+ });
24
+ });
@@ -109,4 +109,25 @@ describe('shared-utils', () => {
109
109
  });
110
110
  });
111
111
 
112
+ describe('copyHooks ships hook-module-resolution.sh (#655/#631)', () => {
113
+ it('@covers gate-script deployment: copyHooks installs hook-module-resolution.sh so pre-commit does not FATAL', () => {
114
+ // pre-commit loads hook-module-resolution.sh before any tier decision and
115
+ // fails closed. It was absent from copyHooks, so every fresh `init` /
116
+ // `setup-global` produced hooks that FATAL'd on every commit.
117
+ const pkgRoot = path.join(tmpRoot, 'node_modules', '@boyingliu01', 'xp-gate');
118
+ const hooksSrc = path.join(pkgRoot, 'hooks');
119
+ fs.mkdirSync(hooksSrc, { recursive: true });
120
+ for (const h of ['pre-commit', 'pre-push', 'post-commit', 'post-merge', 'hook-module-resolution.sh']) {
121
+ fs.writeFileSync(path.join(hooksSrc, h), `# ${h}`);
122
+ }
123
+ const destDir = path.join(tmpRoot, 'dest-hooks');
124
+ fs.mkdirSync(destDir, { recursive: true });
125
+
126
+ const { copyHooks } = require('../shared-utils');
127
+ copyHooks(pkgRoot, destDir);
128
+
129
+ expect(fs.existsSync(path.join(destDir, 'hook-module-resolution.sh'))).toBe(true);
130
+ });
131
+ });
132
+
112
133
  });
@@ -309,6 +309,24 @@ describe('uninstall', () => {
309
309
  expect(cfg.uninstalled).toBeDefined();
310
310
  });
311
311
 
312
+ it('AC-02: uninstall in global mode unsets even when git returns a forward-slash path (#656)', async () => {
313
+ // Windows: git returns "C:/Users/.../hooks" while expectedPath uses
314
+ // backslashes. A raw === skipped the unset, leaving a dangling hooksPath.
315
+ setupGlobalInstall();
316
+ const expectedHooksPath = globalHooksDir();
317
+ mockExecGlobalHooksPath(expectedHooksPath.replace(/\\/g, '/'));
318
+ const { uninstall } = require('../uninstall');
319
+
320
+ const result = await uninstall([]);
321
+
322
+ expect(result).toBe(0);
323
+ expect(execSpy).toHaveBeenCalledWith(
324
+ expect.stringContaining('git config --global --unset core.hooksPath'),
325
+ expect.any(Object)
326
+ );
327
+ expect(warnSpy).not.toHaveBeenCalledWith(expect.stringContaining('does not match'));
328
+ });
329
+
312
330
  it('AC-02: uninstall in global mode skips unset when hooksPath does not match', async () => {
313
331
  setupGlobalInstall();
314
332
  mockExecGlobalHooksPath('/some/other/path');
package/lib/check.js CHANGED
@@ -43,12 +43,31 @@ function parseCheckArgs(args) {
43
43
  };
44
44
  }
45
45
 
46
+ /**
47
+ * Coerce a runGate() verdict into a valid process exit code (#657). The only
48
+ * non-numeric verdict is GATE_NOT_RUN (a string sentinel meaning "did not run");
49
+ * it must become 0, because a gate that honestly reported nothing to analyse is
50
+ * not a failure — and passing the string to process.exit() crashes Node.
51
+ * @param {number|string} verdict
52
+ * @returns {number}
53
+ */
54
+ function normalizeGateExit(verdict) {
55
+ return typeof verdict === 'number' ? verdict : 0;
56
+ }
57
+
46
58
  async function check(args) {
47
59
  const opts = parseCheckArgs(args);
48
60
 
49
61
  if (opts.list) return listGates();
50
62
  if (opts.changed) return runChangedBrief();
51
- if (opts.gateNumber && !opts.all && !opts.gateIds) return runGate(opts.gateNumber, opts.pathArg);
63
+ // #657: runGate() can return the GATE_NOT_RUN sentinel (a string) when a gate
64
+ // has no module/fallback. Returning it straight to the caller let it reach
65
+ // process.exit(), which throws ERR_INVALID_ARG_TYPE and dumps a stack trace.
66
+ // Map it to exit 0 here (the gate honestly reported "did not run"); a real
67
+ // failure code passes through unchanged.
68
+ if (opts.gateNumber && !opts.all && !opts.gateIds) {
69
+ return normalizeGateExit(await runGate(opts.gateNumber, opts.pathArg));
70
+ }
52
71
  if (opts.all) return checkAllGates(opts.pathArg);
53
72
  if (opts.gateIds && opts.gateIds.length > 0) return runGatesByIds(opts.gateIds, opts.pathArg);
54
73
 
@@ -179,6 +198,7 @@ async function checkAllGates(target) {
179
198
 
180
199
  async function runGatesByIds(gateIds, target) {
181
200
  let failures = 0;
201
+ let skipped = 0;
182
202
  let ran = 0;
183
203
  const resolvedGates = [];
184
204
  const unknown = [];
@@ -201,8 +221,10 @@ async function runGatesByIds(gateIds, target) {
201
221
  }
202
222
 
203
223
  for (const resolvedId of resolvedGates) {
224
+ // #618/#657: a NOT_RUN sentinel is a skip, not a pass and not a failure.
204
225
  const code = await runGate(resolvedId, target);
205
- if (code !== 0) failures += 1;
226
+ if (code === GATE_NOT_RUN) skipped += 1;
227
+ else if (code !== 0) failures += 1;
206
228
  ran += 1;
207
229
  console.log('');
208
230
  }
@@ -213,7 +235,8 @@ async function runGatesByIds(gateIds, target) {
213
235
  return 1;
214
236
  }
215
237
 
216
- console.log(`[xp-gate check] ${ran - failures}/${ran} gate(s) passed`);
238
+ const tally = { passed: ran - failures - skipped, failed: failures, warned: 0, skipped, total: ran };
239
+ console.log(`[xp-gate check] ${formatGateTally(tally)}`);
217
240
  return failures === 0 ? 0 : 1;
218
241
  }
219
242
 
@@ -356,4 +379,4 @@ function listGates() {
356
379
  return 0;
357
380
  }
358
381
 
359
- module.exports = { check, checkAllGates, formatGateTally, runChangedBrief, fileExtension, printBriefSummary };
382
+ module.exports = { check, checkAllGates, formatGateTally, runChangedBrief, fileExtension, printBriefSummary, normalizeGateExit };
@@ -252,7 +252,12 @@ function runGateAdapter(scriptPath, cwd = process.cwd(), opts = {}) {
252
252
  execFileSync(
253
253
  bashCommand,
254
254
  ['--noprofile', '--norc', '-c', wrap, 'xp-gate', commonScript, nowMsScript, scriptPath, process.execPath, listFile],
255
- { stdio: 'inherit', cwd }
255
+ // #660: the pre-commit hook exports GATE_DIR before sourcing a fragment, but
256
+ // the CLI path never did, so gate-3.sh looked for $GATE_DIR/lib/ccn-ratchet.sh
257
+ // (empty), fell back to <repo>/githooks/lib, and falsely BLOCKED on any
258
+ // project without vendored gates. The fragment lives beside its lib/, so
259
+ // point GATE_DIR at its own directory.
260
+ { stdio: 'inherit', cwd, env: { ...process.env, GATE_DIR: scriptDir } }
256
261
  );
257
262
  } finally {
258
263
  removeFileList(listFile);
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-30T12:14:24.278Z",
5
+ "checked_at": "2026-10-30T13:23:44.380Z",
6
6
  "source": "network"
7
7
  }
@@ -37,7 +37,12 @@ function copyHooks(srcDir, destDir) {
37
37
  // post-commit carries the audit-attribution flush and --no-verify detection.
38
38
  // It was absent here, so neither ever ran for installed users (the source
39
39
  // existed but no installer copied it).
40
- ['pre-commit', 'pre-push', 'post-commit', 'post-merge'].forEach(hook => {
40
+ //
41
+ // hook-module-resolution.sh (#655/#631) is loaded by pre-commit BEFORE any tier
42
+ // decision and fails closed — omitting it made every commit FATAL on a fresh
43
+ // install. It is generated, but it must still ship beside the hooks.
44
+ const hookFiles = ['pre-commit', 'pre-push', 'post-commit', 'post-merge', 'hook-module-resolution.sh'];
45
+ hookFiles.forEach(hook => {
41
46
  const src = path.join(srcDir, 'hooks', hook);
42
47
  const dest = path.join(destDir, hook);
43
48
  if (fs.existsSync(src)) {
package/lib/uninstall.js CHANGED
@@ -333,7 +333,11 @@ function unsetGitConfigIfMatch(item) {
333
333
  console.log(` ${item.label} — not set`);
334
334
  return true;
335
335
  }
336
- if (currentPath === item.expectedPath) {
336
+ // #656: git returns a forward-slash path ("C:/Users/.../hooks"), while
337
+ // item.expectedPath is built with path.join (backslashes on Windows). A raw
338
+ // === compares these as different and the unset is silently skipped, leaving
339
+ // a dangling core.hooksPath. Normalize both sides to an absolute, real path.
340
+ if (sameHooksPath(currentPath, item.expectedPath)) {
337
341
  unsetHooksPath();
338
342
  console.log(` Unset ${item.label}`);
339
343
  return true;
@@ -342,6 +346,22 @@ function unsetGitConfigIfMatch(item) {
342
346
  return false;
343
347
  }
344
348
 
349
+ /**
350
+ * True when two hook-directory paths refer to the same location (#656), tolerant
351
+ * of forward/back slashes, trailing separators, and case differences on Windows.
352
+ * @param {string} a
353
+ * @param {string} b
354
+ * @returns {boolean}
355
+ */
356
+ function sameHooksPath(a, b) {
357
+ const norm = (p) => {
358
+ let r = path.resolve(p);
359
+ try { r = fs.realpathSync.native(r); } catch { /* path need not exist */ }
360
+ return process.platform === 'win32' ? r.toLowerCase() : r;
361
+ };
362
+ return norm(a) === norm(b);
363
+ }
364
+
345
365
  /**
346
366
  * Execute a single plan item (file/dir/gitconfig).
347
367
  * @returns {boolean} true on success, false on skip/failure
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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.33.0",
3
+ "version": "0.33.2",
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"
@@ -10,6 +10,7 @@
10
10
  "hooks/",
11
11
  "adapters/",
12
12
  "lib/",
13
+ "gates/",
13
14
  "skills/",
14
15
  "plugins/",
15
16
  "dsh.bundle.patch.yml",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.33.0",
3
+ "version": "0.33.2",
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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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.33.0",
3
+ "version": "0.33.2",
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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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.33.0",
3
+ "version": "0.33.2",
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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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.33.0",
3
+ "version": "0.33.2",
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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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:** 3c5e9aaf
4
+ **Commit:** b45021b6
5
5
  **Branch:** main
6
- **Version:** 0.33.0.0
6
+ **Version:** 0.33.2.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.