@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.
- package/gate-3.sh +48 -7
- package/gate-8.sh +32 -3
- package/gate-9.sh +14 -2
- package/gates/common-process.ts +178 -0
- package/gates/common.ts +208 -0
- package/gates/gate-3.ts +144 -0
- package/gates/gate-7.ts +147 -0
- package/gates/gate-8.ts +92 -0
- package/gates/gate-9.ts +269 -0
- package/gates/pbt-detect.ts +163 -0
- package/gates/python-health.ts +266 -0
- package/gates/test-layers.ts +312 -0
- package/hooks/gate-3.sh +48 -7
- package/hooks/gate-8.sh +32 -3
- package/hooks/gate-9.sh +14 -2
- package/hooks/pre-commit +51 -2
- package/lib/__tests__/check-normalize-exit.test.js +24 -0
- package/lib/__tests__/shared-utils.test.js +21 -0
- package/lib/__tests__/uninstall.test.js +18 -0
- package/lib/check.js +27 -4
- package/lib/gate-runner.js +6 -1
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/shared-utils.js +6 -1
- package/lib/uninstall.js +21 -1
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +2 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/scripts/sync-package-content.js +6 -1
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- 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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 };
|
package/lib/gate-runner.js
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/lib/shared-utils.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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.
|
|
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.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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.
|
package/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** b45021b6
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.33.
|
|
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.
|