@boyingliu01/xp-gate 0.18.5 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapter-common.sh +27 -0
- package/adapters/gate-3.sh +0 -0
- package/adapters/gate-4.sh +0 -0
- package/adapters/gate-7.sh +0 -0
- package/adapters/gate-8.sh +0 -0
- package/adapters/gate-9.sh +0 -0
- package/adapters/powershell.sh +114 -35
- package/adapters/typescript.sh +8 -8
- package/build-integrity/__tests__/import-resolver.test.ts +22 -0
- package/build-integrity/__tests__/orchestrator.test.ts +1 -2
- package/build-integrity/gate-10.ts +7 -0
- package/gate-12-file-hygiene.sh +230 -0
- package/hooks/adapter-common.sh +27 -0
- package/hooks/gate-10.sh +105 -0
- package/hooks/gate-12-file-hygiene.sh +0 -0
- package/hooks/lib/validate-code-walkthrough.cjs +125 -0
- package/hooks/pre-commit +130 -33
- package/hooks/pre-push +15 -91
- package/lib/__tests__/check-version.test.js +67 -46
- package/lib/__tests__/doctor.test.js +7 -6
- package/lib/__tests__/gate-runner.test.js +267 -0
- package/lib/__tests__/init.test.js +32 -0
- package/lib/__tests__/npm-package.test.js +13 -0
- package/lib/__tests__/phase-transition.test.js +604 -12
- package/lib/__tests__/sprint-discovery.test.ts +0 -1
- package/lib/__tests__/sprint-status-rework.test.js +100 -1
- package/lib/__tests__/update-hooks.test.js +143 -10
- package/lib/__tests__/upgrade-exec.test.js +73 -45
- package/lib/__tests__/upgrade-skill-sync.test.js +1 -1
- package/lib/__tests__/upgrade.test.js +12 -11
- package/lib/doctor.js +2 -1
- package/lib/gate-runner.js +68 -23
- package/lib/init.js +34 -5
- package/lib/install-skill.js +1 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/phase-transition.js +203 -23
- package/lib/shared-paths.js +2 -0
- package/lib/sprint-status.js +15 -4
- package/lib/test-alignment.ts +3 -13
- package/lib/update-hooks.js +129 -15
- package/mock-policy/AGENTS.md +4 -4
- package/mock-policy/__tests__/gate-m3.test.ts +0 -5
- package/mock-policy/__tests__/scope-scanner.test.ts +8 -2
- package/mutation/AGENTS.md +3 -3
- package/mutation/__tests__/mutmut-runner.test.ts +443 -2
- package/mutation/__tests__/update-baseline.test.ts +0 -2
- package/mutation/runners/mutmut-runner.ts +67 -17
- package/package.json +9 -3
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/claude-code/skills/delphi-review/.delphi-config.json.example +24 -27
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +12 -11
- package/plugins/claude-code/skills/delphi-review/INSTALL.md +15 -17
- package/plugins/claude-code/skills/delphi-review/SKILL.md +30 -29
- package/plugins/claude-code/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +66 -54
- package/plugins/claude-code/skills/delphi-review/references/round-templates.md +7 -7
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +37 -35
- package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/README.md +25 -0
- package/plugins/dsh/cordis.patch.yml +7 -0
- package/plugins/dsh/package.json +70 -0
- package/plugins/dsh/scripts/prepack.cjs +82 -0
- package/plugins/dsh/skills/admin-template-guidelines/SKILL.md +113 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-1.md +91 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-2.md +155 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-3.md +70 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-4.md +154 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-5.md +126 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-6.md +180 -0
- package/plugins/dsh/skills/batch-grill-me/SKILL.md +15 -0
- package/plugins/dsh/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/dsh/skills/delphi-review/.delphi-config.json.example +70 -0
- package/plugins/dsh/skills/delphi-review/AGENTS.md +67 -0
- package/plugins/dsh/skills/delphi-review/INSTALL.md +150 -0
- package/plugins/dsh/skills/delphi-review/SKILL.md +559 -0
- package/plugins/dsh/skills/delphi-review/hooks.md +19 -0
- package/plugins/dsh/skills/delphi-review/opencode.json.delphi.example +52 -0
- package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +470 -0
- package/plugins/dsh/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
- package/plugins/dsh/skills/delphi-review/references/requirements.md +402 -0
- package/plugins/dsh/skills/delphi-review/references/round-templates.md +134 -0
- package/plugins/dsh/skills/delphi-review/scripts/verify-consensus.sh +43 -0
- package/plugins/dsh/skills/delphi-review/tools.md +29 -0
- package/plugins/dsh/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/plugins/dsh/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/plugins/dsh/skills/domain-modeling/SKILL.md +74 -0
- package/plugins/dsh/skills/grill-with-docs/SKILL.md +7 -0
- package/plugins/dsh/skills/grilling/SKILL.md +12 -0
- package/plugins/dsh/skills/improve-codebase-architecture/SKILL.md +156 -0
- package/plugins/dsh/skills/ralph-loop/SKILL.md +198 -0
- package/plugins/dsh/skills/ralph-loop/references/components.md +113 -0
- package/plugins/dsh/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
- package/plugins/dsh/skills/ralph-loop/references/workflow-details.md +99 -0
- package/plugins/dsh/skills/ralph-loop/templates/progress-log.md +36 -0
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +103 -0
- package/plugins/dsh/skills/sprint-flow/SKILL.md +411 -0
- package/plugins/dsh/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
- package/plugins/dsh/skills/sprint-flow/hooks.md +18 -0
- package/plugins/dsh/skills/sprint-flow/references/components/memory.md +87 -0
- package/plugins/dsh/skills/sprint-flow/references/components/middleware.md +75 -0
- package/plugins/dsh/skills/sprint-flow/references/components/skill-invocations.md +105 -0
- package/plugins/dsh/skills/sprint-flow/references/components/system-prompt.md +27 -0
- package/plugins/dsh/skills/sprint-flow/references/components/tool-descriptions.md +95 -0
- package/plugins/dsh/skills/sprint-flow/references/force-levels.md +204 -0
- package/plugins/dsh/skills/sprint-flow/references/orchestration-rules.md +236 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-1-prep.md +294 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +273 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-3-build.md +348 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-4-verify.md +225 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-5-ship.md +222 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-6-close.md +266 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-overview.md +486 -0
- package/plugins/dsh/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
- package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
- package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-output-template.md +110 -0
- package/plugins/dsh/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
- package/plugins/dsh/skills/sprint-flow/templates/pain-document-template.md +115 -0
- package/plugins/dsh/skills/sprint-flow/templates/sprint-progress-template.md +140 -0
- package/plugins/dsh/skills/sprint-flow/templates/sprint-summary-template.md +121 -0
- package/plugins/dsh/skills/sprint-flow/tools.md +29 -0
- package/plugins/dsh/skills/test-driven-development/SKILL.md +371 -0
- package/plugins/dsh/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +56 -0
- package/plugins/dsh/skills/test-specification-alignment/SKILL.md +205 -0
- package/plugins/dsh/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
- package/plugins/dsh/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
- package/plugins/dsh/skills/test-specification-alignment/references/specification-format.md +348 -0
- package/plugins/dsh/skills/to-issues/SKILL.md +277 -0
- package/plugins/dsh/src/command.test.ts +102 -0
- package/plugins/dsh/src/command.ts +75 -0
- package/plugins/dsh/src/contract.test.ts +62 -0
- package/plugins/dsh/src/gate-runner.test.ts +100 -0
- package/plugins/dsh/src/gate-runner.ts +58 -0
- package/plugins/dsh/src/index.test.ts +97 -0
- package/plugins/dsh/src/index.ts +94 -0
- package/plugins/dsh/src/skills.test.ts +58 -0
- package/plugins/dsh/tsconfig.build.json +13 -0
- package/plugins/dsh/tsconfig.json +19 -0
- package/plugins/dsh/vitest.config.ts +8 -0
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/scripts/prepack.cjs +1 -0
- package/plugins/opencode/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/opencode/skills/delphi-review/.delphi-config.json.example +24 -27
- package/plugins/opencode/skills/delphi-review/AGENTS.md +12 -11
- package/plugins/opencode/skills/delphi-review/INSTALL.md +15 -17
- package/plugins/opencode/skills/delphi-review/SKILL.md +30 -29
- package/plugins/opencode/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/opencode/skills/delphi-review/references/requirements.md +66 -54
- package/plugins/opencode/skills/delphi-review/references/round-templates.md +7 -7
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/SKILL.md +4 -4
- package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/plugins/opencode/skills/sprint-flow/references/force-levels.md +37 -35
- package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/SKILL.md +47 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +3 -11
- package/plugins/qoder/skills/delphi-review/AGENTS.md +14 -16
- package/plugins/qoder/skills/delphi-review/INSTALL.md +12 -12
- package/plugins/qoder/skills/delphi-review/SKILL.md +58 -25
- package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +1 -1
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/SKILL.md +2 -2
- package/plugins/qoder/skills/sprint-flow/references/force-levels.md +38 -35
- package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +48 -6
- package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +5 -6
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/scripts/__tests__/sync-package-content.test.js +30 -0
- package/scripts/delphi-external-review.cjs +194 -63
- package/scripts/sync-package-content.js +9 -2
- package/skills/clipboard-vision/SKILL.md +47 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/skills/delphi-review/.delphi-config.json.example +24 -27
- package/skills/delphi-review/AGENTS.md +12 -11
- package/skills/delphi-review/INSTALL.md +15 -17
- package/skills/delphi-review/SKILL.md +30 -29
- package/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/skills/delphi-review/references/requirements.md +66 -54
- package/skills/delphi-review/references/round-templates.md +7 -7
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/sprint-flow/SKILL.md +4 -4
- package/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/skills/sprint-flow/references/force-levels.md +37 -35
- package/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/skills/sprint-flow/references/phase-overview.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +3 -3
- package/sprint-gate.sh +291 -0
package/lib/gate-runner.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const path = require('path');
|
|
1
|
+
const { execSync, execFileSync, spawnSync } = require('node:child_process');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
|
-
* Run a
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Run a gate adapter fragment standalone.
|
|
8
|
+
*
|
|
9
|
+
* The gate fragments in githooks/adapters/gate-*.sh are designed to be *sourced*
|
|
10
|
+
* into the pre-commit hook, which pre-defines the context they rely on:
|
|
11
|
+
* - PROJECT_LANG (from detect_project_lang)
|
|
12
|
+
* - CHANGED_FILES (from git)
|
|
13
|
+
* - gate_start_ms() / record_gate_audit() / now_ms() audit & timing helpers
|
|
14
|
+
*
|
|
15
|
+
* When `xp-gate check` invokes a gate standalone, that context is missing and
|
|
16
|
+
* the fragments fail with "command not found". This wrapper reconstructs the
|
|
17
|
+
* same preamble the pre-commit hook sets up, then *sources* the fragment so it
|
|
18
|
+
* behaves exactly as it does inside the commit hook.
|
|
11
19
|
*/
|
|
12
|
-
function
|
|
20
|
+
function runGateAdapter(scriptPath, cwd = process.cwd()) {
|
|
13
21
|
if (process.platform === 'win32') {
|
|
14
|
-
// Check if bash is available on Windows (Git Bash, MSYS2, WSL)
|
|
15
22
|
try {
|
|
16
23
|
execSync('bash --version', { stdio: 'pipe', timeout: 5000 });
|
|
17
24
|
} catch {
|
|
@@ -21,8 +28,44 @@ function runBashScript(scriptPath) {
|
|
|
21
28
|
return;
|
|
22
29
|
}
|
|
23
30
|
}
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
const scriptDir = path.dirname(scriptPath);
|
|
32
|
+
const commonScript = [
|
|
33
|
+
path.join(scriptDir, 'adapter-common.sh'),
|
|
34
|
+
path.join(scriptDir, '..', 'adapter-common.sh'),
|
|
35
|
+
path.join(cwd, 'githooks', 'adapter-common.sh'),
|
|
36
|
+
].find(candidate => fs.existsSync(candidate)) || '';
|
|
37
|
+
const nowMsScript = [
|
|
38
|
+
path.join(scriptDir, 'lib', 'now-ms.sh'),
|
|
39
|
+
path.join(scriptDir, '..', 'lib', 'now-ms.sh'),
|
|
40
|
+
path.resolve(__dirname, '..', 'hooks', 'lib', 'now-ms.sh'),
|
|
41
|
+
].find(candidate => fs.existsSync(candidate)) || '';
|
|
42
|
+
const changedFilesParameter = ['$', '{CHANGED_FILES}'].join('');
|
|
43
|
+
|
|
44
|
+
// Build the shell preamble the pre-commit hook would otherwise inject before
|
|
45
|
+
// sourcing the gate fragment, then source that fragment. Deliberately NOT
|
|
46
|
+
// set -u (the pre-commit hook does not use it), so fragments that reference
|
|
47
|
+
// a not-yet-defined variable fail the same way inside the commit hook.
|
|
48
|
+
const wrap = [
|
|
49
|
+
'if [ -n "$1" ] && [ -f "$1" ]; then source "$1"; fi',
|
|
50
|
+
'if [ -n "$2" ] && [ -f "$2" ]; then source "$2"; fi',
|
|
51
|
+
'if ! command -v now_ms >/dev/null 2>&1; then now_ms() { "$4" -e "console.log(Date.now())"; }; fi',
|
|
52
|
+
'gate_start_ms() { now_ms; }',
|
|
53
|
+
'record_gate_audit() { :; }',
|
|
54
|
+
'PROJECT_LANG="$(detect_project_lang 2>/dev/null || echo unknown)"',
|
|
55
|
+
'CHANGED_FILES="$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)"',
|
|
56
|
+
`if [ -z "${changedFilesParameter}" ] && [ -n "$(git rev-parse HEAD 2>/dev/null)" ]; then`,
|
|
57
|
+
' CHANGED_FILES="$(git diff HEAD --name-only --diff-filter=ACM 2>/dev/null || true)"',
|
|
58
|
+
'fi',
|
|
59
|
+
'source "$3"',
|
|
60
|
+
].join('\n');
|
|
61
|
+
|
|
62
|
+
// execFileSync avoids an extra /bin/sh layer, so bash receives the script as a
|
|
63
|
+
// single argv arg (no shell-metacharacter injection through the path).
|
|
64
|
+
execFileSync(
|
|
65
|
+
'bash',
|
|
66
|
+
['--noprofile', '--norc', '-c', wrap, 'xp-gate', commonScript, nowMsScript, scriptPath, process.execPath],
|
|
67
|
+
{ stdio: 'inherit', cwd }
|
|
68
|
+
);
|
|
26
69
|
}
|
|
27
70
|
|
|
28
71
|
// Gate metadata registry — maps gate IDs to names, descriptions, and how to run them.
|
|
@@ -35,12 +78,14 @@ function runBashScript(scriptPath) {
|
|
|
35
78
|
* Falls back to bash script if TypeScript module not found.
|
|
36
79
|
*/
|
|
37
80
|
function runTsGate(gateModule, targetPath) {
|
|
81
|
+
const targetCwd = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
38
82
|
const entry = findTsGateEntry(gateModule);
|
|
39
83
|
if (!entry) {
|
|
40
84
|
// Fallback to bash script
|
|
41
|
-
const
|
|
85
|
+
const gateNum = gateModule.replace('gate-', '');
|
|
86
|
+
const script = resolveGateScript(gateNum, targetCwd);
|
|
42
87
|
if (script) {
|
|
43
|
-
|
|
88
|
+
runGateAdapter(script, targetCwd);
|
|
44
89
|
} else {
|
|
45
90
|
console.log(`Gate ${gateModule}: TypeScript module not found and no bash fallback available.`);
|
|
46
91
|
}
|
|
@@ -53,7 +98,7 @@ function runTsGate(gateModule, targetPath) {
|
|
|
53
98
|
const result = spawnSync('npx', args, {
|
|
54
99
|
stdio: 'inherit',
|
|
55
100
|
shell: process.platform === 'win32',
|
|
56
|
-
cwd:
|
|
101
|
+
cwd: targetCwd,
|
|
57
102
|
timeout: 120000,
|
|
58
103
|
});
|
|
59
104
|
|
|
@@ -76,6 +121,7 @@ function findTsGateEntry(gateModule) {
|
|
|
76
121
|
}
|
|
77
122
|
return null;
|
|
78
123
|
}
|
|
124
|
+
// allow: SIZE_OK - this module's bulk is the declarative gate registry below.
|
|
79
125
|
const GATE_REGISTRY = {
|
|
80
126
|
'0': {
|
|
81
127
|
name: 'Version Consistency',
|
|
@@ -130,7 +176,7 @@ const GATE_REGISTRY = {
|
|
|
130
176
|
name: 'Architecture + Boy Scout Rule',
|
|
131
177
|
description: 'Architecture layer boundary validation and warning baseline enforcement',
|
|
132
178
|
aliases: ['architecture', 'arch', '6'],
|
|
133
|
-
run: (
|
|
179
|
+
run: () => {
|
|
134
180
|
const { arch } = require('./arch.js');
|
|
135
181
|
return arch([]);
|
|
136
182
|
},
|
|
@@ -242,17 +288,16 @@ function findConfig(basePath, fileName) {
|
|
|
242
288
|
return null;
|
|
243
289
|
}
|
|
244
290
|
|
|
245
|
-
function resolveGateScript(gateNum) {
|
|
291
|
+
function resolveGateScript(gateNum, cwd = process.cwd()) {
|
|
246
292
|
const candidates = [
|
|
247
|
-
path.join(
|
|
248
|
-
path.join(
|
|
293
|
+
path.join(cwd, 'githooks', 'gates', `gate-${gateNum}-*.sh`),
|
|
294
|
+
path.join(cwd, 'githooks', `gate-${gateNum}.sh`),
|
|
249
295
|
];
|
|
250
296
|
|
|
251
297
|
// Try glob patterns
|
|
252
298
|
for (const pattern of candidates) {
|
|
253
299
|
if (pattern.includes('*')) {
|
|
254
300
|
const dir = path.dirname(pattern);
|
|
255
|
-
const prefix = path.basename(pattern).replace('*', '');
|
|
256
301
|
if (fs.existsSync(dir)) {
|
|
257
302
|
const match = fs.readdirSync(dir).find(f => f.startsWith(`gate-${gateNum}-`) && f.endsWith('.sh'));
|
|
258
303
|
if (match) return path.join(dir, match);
|
|
@@ -263,7 +308,7 @@ function resolveGateScript(gateNum) {
|
|
|
263
308
|
}
|
|
264
309
|
|
|
265
310
|
// Check global install
|
|
266
|
-
const globalDir = path.join(
|
|
311
|
+
const globalDir = path.join(os.homedir(), '.config', 'xp-gate', 'adapters');
|
|
267
312
|
const globalScript = path.join(globalDir, `gate-${gateNum}.sh`);
|
|
268
313
|
if (fs.existsSync(globalScript)) return globalScript;
|
|
269
314
|
|
|
@@ -289,7 +334,7 @@ async function runGate(gateId, targetPath) {
|
|
|
289
334
|
const gate = GATE_REGISTRY[String(gateId)];
|
|
290
335
|
if (!gate) {
|
|
291
336
|
console.error(`Unknown gate: ${gateId}`);
|
|
292
|
-
console.error(
|
|
337
|
+
console.error(`Available gates: ${Object.keys(GATE_REGISTRY).join(', ')}`);
|
|
293
338
|
return 1;
|
|
294
339
|
}
|
|
295
340
|
|
|
@@ -319,4 +364,4 @@ async function runGate(gateId, targetPath) {
|
|
|
319
364
|
return 1;
|
|
320
365
|
}
|
|
321
366
|
|
|
322
|
-
module.exports = { GATE_REGISTRY, getGateInfo, getAllGates, runGate, resolveAlias, getAliases };
|
|
367
|
+
module.exports = { GATE_REGISTRY, getGateInfo, getAllGates, runGate, resolveAlias, getAliases, runGateAdapter, findTsGateEntry, resolveGateScript };
|
package/lib/init.js
CHANGED
|
@@ -24,6 +24,27 @@ function copyRecursive(src, dest) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Third-party packages imported by the copied quality-gate modules. When the
|
|
28
|
+
// modules run via `npx tsx` from <CONFIG_DIR>/modules/<module>, these must be
|
|
29
|
+
// resolvable from an ancestor directory, so they are installed once into
|
|
30
|
+
// <CONFIG_DIR>/node_modules. Keep in sync with each module's imports.
|
|
31
|
+
const MODULE_RUNTIME_DEPS = { 'js-yaml': '^5.2.2', 'zod': '^4.4.3' };
|
|
32
|
+
|
|
33
|
+
function installModuleRuntimeDeps(configDir) {
|
|
34
|
+
const { execSync } = require('child_process');
|
|
35
|
+
const pkgPath = path.join(configDir, 'package.json');
|
|
36
|
+
try {
|
|
37
|
+
const existing = fs.existsSync(pkgPath) ? JSON.parse(fs.readFileSync(pkgPath, 'utf8')) : {};
|
|
38
|
+
const mergedDeps = { ...(existing.dependencies || {}), ...MODULE_RUNTIME_DEPS };
|
|
39
|
+
fs.writeFileSync(pkgPath, JSON.stringify({ ...existing, dependencies: mergedDeps }, null, 2) + '\n');
|
|
40
|
+
execSync('npm install --omit=dev --no-audit --no-fund', { cwd: configDir, stdio: ['ignore', 'inherit', 'inherit'] });
|
|
41
|
+
console.log(` module runtime deps -> ${path.join(configDir, 'node_modules')}`);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn(` Warning: could not install module runtime deps into ${configDir} (${e.message})`);
|
|
44
|
+
console.warn(' Gate ML (mock-policy) may SKIP until deps are available.');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
27
48
|
function logDeps(depCheck) {
|
|
28
49
|
if (!depCheck.ok) {
|
|
29
50
|
console.warn('Warning: Missing dependencies');
|
|
@@ -471,9 +492,9 @@ async function installLocal(args) {
|
|
|
471
492
|
copyAdapters(srcDir, path.join(projectRoot, 'githooks'));
|
|
472
493
|
console.log(` adapter-common.sh + adapters -> ${projectRoot}/githooks/`);
|
|
473
494
|
|
|
474
|
-
// Install principles/, mutation/, mock-policy/ to .xp-gate/modules/
|
|
495
|
+
// Install principles/, mutation/, mock-policy/, build-integrity/ to .xp-gate/modules/
|
|
475
496
|
const modulesDir = path.join(projectRoot, '.xp-gate', 'modules');
|
|
476
|
-
['principles', 'mutation', 'mock-policy'].forEach(module => {
|
|
497
|
+
['principles', 'mutation', 'mock-policy', 'build-integrity'].forEach(module => {
|
|
477
498
|
const moduleSrc = path.join(srcDir, module);
|
|
478
499
|
const moduleDest = path.join(modulesDir, module);
|
|
479
500
|
if (fs.existsSync(moduleSrc)) {
|
|
@@ -482,6 +503,7 @@ async function installLocal(args) {
|
|
|
482
503
|
console.log(` ${module}/ -> .xp-gate/modules/${module}/`);
|
|
483
504
|
}
|
|
484
505
|
});
|
|
506
|
+
installModuleRuntimeDeps(path.join(projectRoot, '.xp-gate'));
|
|
485
507
|
|
|
486
508
|
fs.mkdirSync(TEMPLATE_DIR, { recursive: true });
|
|
487
509
|
copyHooks(srcDir, TEMPLATE_DIR);
|
|
@@ -551,10 +573,10 @@ async function setupGlobal(args) {
|
|
|
551
573
|
copyAdapters(srcDir, GLOBAL_ADAPTERS_DIR);
|
|
552
574
|
console.log(` adapter-common.sh + adapters -> ${GLOBAL_ADAPTERS_DIR}`);
|
|
553
575
|
|
|
554
|
-
// Install principles/, mutation/, mock-policy/ to global modules dir
|
|
576
|
+
// Install principles/, mutation/, mock-policy/, build-integrity/ to global modules dir
|
|
555
577
|
console.log('[setup-global] Installing quality gate modules...');
|
|
556
578
|
const globalModulesDir = path.join(CONFIG_DIR, 'modules');
|
|
557
|
-
['principles', 'mutation', 'mock-policy'].forEach(module => {
|
|
579
|
+
['principles', 'mutation', 'mock-policy', 'build-integrity'].forEach(module => {
|
|
558
580
|
const moduleSrc = path.join(srcDir, module);
|
|
559
581
|
const moduleDest = path.join(globalModulesDir, module);
|
|
560
582
|
if (fs.existsSync(moduleSrc)) {
|
|
@@ -564,6 +586,13 @@ async function setupGlobal(args) {
|
|
|
564
586
|
}
|
|
565
587
|
});
|
|
566
588
|
|
|
589
|
+
// Copied modules are pure TS sourced into <CONFIG_DIR>/modules/. Any of them
|
|
590
|
+
// that import third-party packages (currently only mock-policy: js-yaml, zod)
|
|
591
|
+
// resolve them via Node's ancestor lookup up from the module location, so we
|
|
592
|
+
// install those deps into <CONFIG_DIR>/node_modules once. Best-effort: a
|
|
593
|
+
// missing register/network must not break global setup.
|
|
594
|
+
installModuleRuntimeDeps(CONFIG_DIR);
|
|
595
|
+
|
|
567
596
|
console.log('[setup-global] Configuring git...');
|
|
568
597
|
const { execSync } = require('child_process');
|
|
569
598
|
try {
|
|
@@ -742,4 +771,4 @@ function injectKarpathyPrinciples(projectRoot) {
|
|
|
742
771
|
}
|
|
743
772
|
}
|
|
744
773
|
|
|
745
|
-
module.exports = { init, promptBootstrap };
|
|
774
|
+
module.exports = { init, promptBootstrap, installModuleRuntimeDeps };
|
package/lib/install-skill.js
CHANGED
|
@@ -32,6 +32,7 @@ function getCliVersion() {
|
|
|
32
32
|
const SKILLS_REGISTRY = {
|
|
33
33
|
'admin-template-guidelines': { repo: 'boyingliu01/xp-gate', path: 'skills/admin-template-guidelines' },
|
|
34
34
|
'batch-grill-me': { repo: 'boyingliu01/xp-gate', path: 'skills/batch-grill-me' },
|
|
35
|
+
'clipboard-vision': { repo: 'boyingliu01/xp-gate', path: 'skills/clipboard-vision' },
|
|
35
36
|
'delphi-review': { repo: 'boyingliu01/xp-gate', path: 'skills/delphi-review' },
|
|
36
37
|
'domain-modeling': { repo: 'boyingliu01/xp-gate', path: 'skills/domain-modeling' },
|
|
37
38
|
'grill-with-docs': { repo: 'boyingliu01/xp-gate', path: 'skills/grill-with-docs' },
|
package/lib/phase-transition.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
const path = require('path');
|
|
26
26
|
const fs = require('fs');
|
|
27
27
|
const crypto = require('crypto');
|
|
28
|
-
const { execSync } = require('child_process');
|
|
28
|
+
const { execFileSync, execSync } = require('child_process');
|
|
29
29
|
const { SprintStateManager } = require('./sprint-state-manager');
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -45,7 +45,10 @@ const PHASE_NAMES = {
|
|
|
45
45
|
const EVIDENCE_FILES = {
|
|
46
46
|
2: {
|
|
47
47
|
path: '.sprint-state/phase-outputs/requirements-reviewed.json',
|
|
48
|
-
requiredFields: [
|
|
48
|
+
requiredFields: [
|
|
49
|
+
'verdict', 'requirements_statement', 'timestamp', 'consensus_ratio',
|
|
50
|
+
'expert_verdicts', 'head_commit', 'requirements_hash',
|
|
51
|
+
],
|
|
49
52
|
blockingCheck: (data) => data.verdict === 'APPROVED',
|
|
50
53
|
blockingMessage: 'Requirements review verdict is not APPROVED',
|
|
51
54
|
},
|
|
@@ -57,22 +60,79 @@ const EVIDENCE_FILES = {
|
|
|
57
60
|
},
|
|
58
61
|
};
|
|
59
62
|
|
|
63
|
+
const GIT_REPOSITORY_ENV_VARS = [
|
|
64
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS',
|
|
65
|
+
'GIT_CONFIG_COUNT', 'GIT_OBJECT_DIRECTORY', 'GIT_DIR', 'GIT_WORK_TREE',
|
|
66
|
+
'GIT_IMPLICIT_WORK_TREE', 'GIT_GRAFT_FILE', 'GIT_INDEX_FILE',
|
|
67
|
+
'GIT_NO_REPLACE_OBJECTS', 'GIT_REPLACE_REF_BASE', 'GIT_PREFIX',
|
|
68
|
+
'GIT_SHALLOW_FILE', 'GIT_COMMON_DIR',
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
function createRepositoryGitEnv() {
|
|
72
|
+
const env = { ...process.env };
|
|
73
|
+
for (const name of GIT_REPOSITORY_ENV_VARS) {
|
|
74
|
+
delete env[name];
|
|
75
|
+
}
|
|
76
|
+
return env;
|
|
77
|
+
}
|
|
78
|
+
|
|
60
79
|
/**
|
|
61
80
|
* Get current HEAD commit hash from a project directory.
|
|
62
|
-
* Returns
|
|
81
|
+
* Returns null if HEAD cannot be resolved.
|
|
63
82
|
* @param {string} projectDir
|
|
64
|
-
* @returns {string}
|
|
83
|
+
* @returns {string | null}
|
|
65
84
|
*/
|
|
66
85
|
function getCurrentHeadCommit(projectDir) {
|
|
67
86
|
try {
|
|
68
87
|
return execSync('git rev-parse HEAD', {
|
|
69
88
|
cwd: projectDir,
|
|
89
|
+
env: createRepositoryGitEnv(),
|
|
70
90
|
encoding: 'utf8',
|
|
71
91
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
72
92
|
}).trim();
|
|
73
93
|
} catch {
|
|
74
|
-
return
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function validateRequirementsExpertEvidence(data) {
|
|
99
|
+
const validationErrors = [];
|
|
100
|
+
if (!Array.isArray(data.expert_verdicts) || data.expert_verdicts.length !== 3) {
|
|
101
|
+
validationErrors.push('expert_verdicts must contain exactly 3 expert results');
|
|
102
|
+
return validationErrors;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const expectedRoles = new Set(['architecture', 'technical', 'feasibility']);
|
|
106
|
+
const roles = data.expert_verdicts.map(expert => expert?.role);
|
|
107
|
+
if (roles.some(role => !expectedRoles.has(role)) || new Set(roles).size !== expectedRoles.size) {
|
|
108
|
+
validationErrors.push('expert_verdicts roles must be exactly architecture, technical, and feasibility');
|
|
109
|
+
}
|
|
110
|
+
if (data.expert_verdicts.some(expert => expert?.verdict !== 'APPROVED')) {
|
|
111
|
+
validationErrors.push('every expert verdict must be APPROVED');
|
|
112
|
+
}
|
|
113
|
+
if (data.expert_verdicts.some(expert => expert?.result_type !== 'delphi_expert_result')) {
|
|
114
|
+
validationErrors.push('every expert result_type must be delphi_expert_result');
|
|
75
115
|
}
|
|
116
|
+
if (data.expert_verdicts.some(expert =>
|
|
117
|
+
(expert?.error !== undefined && expert.error !== false && expert.error !== null)
|
|
118
|
+
|| (expert?.fallback !== undefined && expert.fallback !== false && expert.fallback !== null)
|
|
119
|
+
)) {
|
|
120
|
+
validationErrors.push('expert results must not contain an error or fallback marker');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const requestedModels = data.expert_verdicts.map(expert =>
|
|
124
|
+
typeof expert?.requested_model === 'string' ? expert.requested_model.trim() : ''
|
|
125
|
+
);
|
|
126
|
+
if (requestedModels.some(model => model.length === 0)) {
|
|
127
|
+
validationErrors.push('every requested_model must be a non-empty trimmed string');
|
|
128
|
+
} else if (new Set(requestedModels).size !== requestedModels.length) {
|
|
129
|
+
validationErrors.push('requested_model values must be distinct after trimming');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!Number.isFinite(data.consensus_ratio) || data.consensus_ratio < 0.9 || data.consensus_ratio > 1) {
|
|
133
|
+
validationErrors.push('consensus_ratio must be a finite number from 0.90 through 1');
|
|
134
|
+
}
|
|
135
|
+
return validationErrors;
|
|
76
136
|
}
|
|
77
137
|
|
|
78
138
|
/**
|
|
@@ -85,6 +145,66 @@ function computeFileHash(filePath) {
|
|
|
85
145
|
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
86
146
|
}
|
|
87
147
|
|
|
148
|
+
function parseEvidenceDate(timestamp) {
|
|
149
|
+
if (typeof timestamp !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(timestamp)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const parsed = new Date(timestamp);
|
|
153
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== timestamp.slice(0, 10)) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return timestamp.slice(0, 10);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function readRequirementsContext(data, projectDir) {
|
|
160
|
+
const contextFile = data.context_file_used;
|
|
161
|
+
if (contextFile === null || contextFile === undefined || contextFile === '') {
|
|
162
|
+
return { content: '' };
|
|
163
|
+
}
|
|
164
|
+
if (typeof contextFile !== 'string' || path.isAbsolute(contextFile)) {
|
|
165
|
+
return { error: 'context_file_used must be a safe project-relative regular file path' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const projectRoot = fs.realpathSync(projectDir);
|
|
169
|
+
const candidatePath = path.resolve(projectRoot, contextFile);
|
|
170
|
+
const relativeCandidate = path.relative(projectRoot, candidatePath);
|
|
171
|
+
if (relativeCandidate.startsWith('..') || path.isAbsolute(relativeCandidate) || !fs.existsSync(candidatePath)) {
|
|
172
|
+
return { error: 'context_file_used must be a safe project-relative regular file path' };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const realContextPath = fs.realpathSync(candidatePath);
|
|
176
|
+
const relativeRealPath = path.relative(projectRoot, realContextPath);
|
|
177
|
+
if (relativeRealPath.startsWith('..') || path.isAbsolute(relativeRealPath) || !fs.statSync(realContextPath).isFile()) {
|
|
178
|
+
return { error: 'context_file_used must be a safe project-relative regular file path' };
|
|
179
|
+
}
|
|
180
|
+
return { content: fs.readFileSync(realContextPath, 'utf8') };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function computeRequirementsHash(data, projectDir) {
|
|
184
|
+
if (typeof data.requirements_statement !== 'string') {
|
|
185
|
+
return { error: 'requirements_statement must be a string' };
|
|
186
|
+
}
|
|
187
|
+
const evidenceDate = parseEvidenceDate(data.timestamp);
|
|
188
|
+
if (evidenceDate === null) {
|
|
189
|
+
return { error: 'timestamp must be a valid UTC ISO-8601 timestamp' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let context;
|
|
193
|
+
try {
|
|
194
|
+
context = readRequirementsContext(data, projectDir);
|
|
195
|
+
} catch {
|
|
196
|
+
return { error: 'context_file_used must be a safe project-relative regular file path' };
|
|
197
|
+
}
|
|
198
|
+
if (context.error) {
|
|
199
|
+
return context;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
hash: crypto.createHash('sha256')
|
|
203
|
+
.update(`${data.requirements_statement}${context.content}${evidenceDate}`, 'utf8')
|
|
204
|
+
.digest('hex'),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
88
208
|
/**
|
|
89
209
|
* Validate phase evidence files for a given phase transition.
|
|
90
210
|
*
|
|
@@ -149,6 +269,15 @@ function validateEvidence(phase, projectDir) {
|
|
|
149
269
|
return { ok: false, errors, warnings };
|
|
150
270
|
}
|
|
151
271
|
|
|
272
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
273
|
+
const msg = `${fileName} must contain a JSON object`;
|
|
274
|
+
if (isLegacySprint) {
|
|
275
|
+
warnings.push(`WARNING: ${msg}. Upgrade sprint with evidence_schema_version >= 2 to enforce.`);
|
|
276
|
+
return { ok: true, errors: [], warnings };
|
|
277
|
+
}
|
|
278
|
+
return { ok: false, errors: [msg], warnings };
|
|
279
|
+
}
|
|
280
|
+
|
|
152
281
|
// Check required fields
|
|
153
282
|
for (const field of evidenceConfig.requiredFields) {
|
|
154
283
|
if (!(field in data)) {
|
|
@@ -168,7 +297,7 @@ function validateEvidence(phase, projectDir) {
|
|
|
168
297
|
|
|
169
298
|
// Run blocking check
|
|
170
299
|
if (!evidenceConfig.blockingCheck(data)) {
|
|
171
|
-
const msg = `${fileName}: ${evidenceConfig.blockingMessage}
|
|
300
|
+
const msg = `${fileName}: ${evidenceConfig.blockingMessage}`;
|
|
172
301
|
if (isLegacySprint) {
|
|
173
302
|
warnings.push(`WARNING: ${msg}. Upgrade sprint with evidence_schema_version >= 2 to enforce.`);
|
|
174
303
|
return { ok: true, errors: [], warnings };
|
|
@@ -178,10 +307,16 @@ function validateEvidence(phase, projectDir) {
|
|
|
178
307
|
}
|
|
179
308
|
|
|
180
309
|
// Phase-specific anti-staleness checks
|
|
181
|
-
if (phase === 4) {
|
|
182
|
-
// head_commit check
|
|
310
|
+
if (phase === 2 || phase === 4) {
|
|
183
311
|
const currentHead = getCurrentHeadCommit(projectDir);
|
|
184
|
-
if (
|
|
312
|
+
if (currentHead === null) {
|
|
313
|
+
const msg = `${fileName}: unable to resolve current Git HEAD; evidence identity cannot be verified`;
|
|
314
|
+
if (isLegacySprint) {
|
|
315
|
+
warnings.push(`WARNING: ${msg}. Upgrade sprint with evidence_schema_version >= 2 to enforce.`);
|
|
316
|
+
} else {
|
|
317
|
+
errors.push(msg);
|
|
318
|
+
}
|
|
319
|
+
} else if (data.head_commit !== currentHead) {
|
|
185
320
|
const msg = `${fileName}: head_commit mismatch — report has "${data.head_commit}", current HEAD is "${currentHead}"`;
|
|
186
321
|
if (isLegacySprint) {
|
|
187
322
|
warnings.push(`WARNING: ${msg}. Upgrade sprint with evidence_schema_version >= 2 to enforce.`);
|
|
@@ -189,7 +324,9 @@ function validateEvidence(phase, projectDir) {
|
|
|
189
324
|
errors.push(msg);
|
|
190
325
|
}
|
|
191
326
|
}
|
|
327
|
+
}
|
|
192
328
|
|
|
329
|
+
if (phase === 4) {
|
|
193
330
|
// spec_hash check (only if specification.yaml exists)
|
|
194
331
|
const specPath = path.join(projectDir, 'specification.yaml');
|
|
195
332
|
if (fs.existsSync(specPath)) {
|
|
@@ -205,10 +342,42 @@ function validateEvidence(phase, projectDir) {
|
|
|
205
342
|
}
|
|
206
343
|
}
|
|
207
344
|
|
|
208
|
-
// Phase 2: requirements_hash existence check (full verification in v0.18.0)
|
|
209
345
|
if (phase === 2) {
|
|
210
|
-
if (typeof data.requirements_hash !== 'string' || data.requirements_hash
|
|
211
|
-
const msg = `${fileName}: requirements_hash must be a
|
|
346
|
+
if (typeof data.requirements_hash !== 'string' || !/^[a-fA-F0-9]{64}$/.test(data.requirements_hash)) {
|
|
347
|
+
const msg = `${fileName}: requirements_hash must be a 64-character hexadecimal SHA-256 digest`;
|
|
348
|
+
if (isLegacySprint) {
|
|
349
|
+
warnings.push(`WARNING: ${msg}. Upgrade sprint with evidence_schema_version >= 2 to enforce.`);
|
|
350
|
+
} else {
|
|
351
|
+
errors.push(msg);
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
const computedHash = computeRequirementsHash(data, projectDir);
|
|
355
|
+
const hashError = computedHash.error || (
|
|
356
|
+
data.requirements_hash.toLowerCase() !== computedHash.hash
|
|
357
|
+
? 'requirements_hash mismatch; regenerate the requirements review from current inputs'
|
|
358
|
+
: null
|
|
359
|
+
);
|
|
360
|
+
if (hashError) {
|
|
361
|
+
const msg = `${fileName}: ${hashError}`;
|
|
362
|
+
if (isLegacySprint) {
|
|
363
|
+
warnings.push(`WARNING: ${msg}. Upgrade sprint with evidence_schema_version >= 2 to enforce.`);
|
|
364
|
+
} else {
|
|
365
|
+
errors.push(msg);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const validationError of validateRequirementsExpertEvidence(data)) {
|
|
370
|
+
const msg = `${fileName}: ${validationError}`;
|
|
371
|
+
if (isLegacySprint) {
|
|
372
|
+
warnings.push(`WARNING: ${msg}. Upgrade sprint with evidence_schema_version >= 2 to enforce.`);
|
|
373
|
+
} else {
|
|
374
|
+
errors.push(msg);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const currentHead = getCurrentHeadCommit(projectDir);
|
|
379
|
+
if (data.head_commit !== currentHead) {
|
|
380
|
+
const msg = `${fileName}: head_commit mismatch — review has "${data.head_commit}", current HEAD is "${currentHead}"; regenerate the requirements review for the current commit`;
|
|
212
381
|
if (isLegacySprint) {
|
|
213
382
|
warnings.push(`WARNING: ${msg}. Upgrade sprint with evidence_schema_version >= 2 to enforce.`);
|
|
214
383
|
} else {
|
|
@@ -240,7 +409,7 @@ function logEvidenceSkip(projectDir, phase, reason) {
|
|
|
240
409
|
event: 'evidence_skipped',
|
|
241
410
|
phase,
|
|
242
411
|
reason,
|
|
243
|
-
commit_hash: getCurrentHeadCommit(projectDir),
|
|
412
|
+
commit_hash: getCurrentHeadCommit(projectDir) ?? 'unknown',
|
|
244
413
|
};
|
|
245
414
|
fs.appendFileSync(path.join(auditDir, 'audit.jsonl'), JSON.stringify(entry) + '\n', 'utf8');
|
|
246
415
|
}
|
|
@@ -539,16 +708,25 @@ function checkWalkthrough(projectDir) {
|
|
|
539
708
|
return { ok: false, errors, warnings };
|
|
540
709
|
}
|
|
541
710
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
711
|
+
if (typeof data.commit !== 'string' || !/^[0-9a-f]{7,64}$/i.test(data.commit)) {
|
|
712
|
+
errors.push('Code walkthrough commit must be a hexadecimal Git commit ID');
|
|
713
|
+
return { ok: false, errors, warnings };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
try {
|
|
717
|
+
const currentCommit = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
718
|
+
cwd: projectDir,
|
|
719
|
+
env: createRepositoryGitEnv(),
|
|
720
|
+
encoding: 'utf8',
|
|
721
|
+
stdio: 'pipe',
|
|
722
|
+
}).trim();
|
|
723
|
+
if (data.commit !== currentCommit) {
|
|
724
|
+
errors.push(`Code walkthrough commit ${data.commit} does not match HEAD ${currentCommit}`);
|
|
550
725
|
return { ok: false, errors, warnings };
|
|
551
726
|
}
|
|
727
|
+
} catch {
|
|
728
|
+
errors.push('Unable to resolve the current Git commit for code walkthrough validation');
|
|
729
|
+
return { ok: false, errors, warnings };
|
|
552
730
|
}
|
|
553
731
|
|
|
554
732
|
// Check expiration
|
|
@@ -585,8 +763,10 @@ function checkBypassAudit(projectDir) {
|
|
|
585
763
|
if (entry.type === 'precommit_bypass' && entry.commit) {
|
|
586
764
|
// Verify the commit is on the current branch
|
|
587
765
|
try {
|
|
588
|
-
|
|
589
|
-
cwd: projectDir,
|
|
766
|
+
execFileSync('git', ['merge-base', '--is-ancestor', entry.commit, 'HEAD'], {
|
|
767
|
+
cwd: projectDir,
|
|
768
|
+
env: createRepositoryGitEnv(),
|
|
769
|
+
stdio: 'pipe',
|
|
590
770
|
});
|
|
591
771
|
bypassedCommits.push(entry.commit);
|
|
592
772
|
} catch {
|
package/lib/shared-paths.js
CHANGED
|
@@ -20,6 +20,7 @@ const CONFIG_DIR = path.join(HOME_DIR, '.config', 'xp-gate');
|
|
|
20
20
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'xp-gate.json');
|
|
21
21
|
const GLOBAL_HOOKS_DIR = path.join(CONFIG_DIR, 'hooks');
|
|
22
22
|
const GLOBAL_ADAPTERS_DIR = path.join(CONFIG_DIR, 'adapters');
|
|
23
|
+
const GLOBAL_MODULES_DIR = path.join(CONFIG_DIR, 'modules');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Detect which AI agent platform is currently in use.
|
|
@@ -61,6 +62,7 @@ module.exports = {
|
|
|
61
62
|
TEMPLATE_DIR,
|
|
62
63
|
GLOBAL_HOOKS_DIR,
|
|
63
64
|
GLOBAL_ADAPTERS_DIR,
|
|
65
|
+
GLOBAL_MODULES_DIR,
|
|
64
66
|
detectPlatform,
|
|
65
67
|
getTemplateDir,
|
|
66
68
|
};
|
package/lib/sprint-status.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
-
const {
|
|
12
|
+
const { execFileSync } = require('child_process');
|
|
13
13
|
const { discoverActiveSprints } = require('./sprint-discovery');
|
|
14
14
|
const { SprintStateManager } = require('./sprint-state-manager');
|
|
15
15
|
|
|
@@ -229,9 +229,20 @@ const FIX_RE = /^(fix(\(.+\))?:)|\b(fix|bugfix|hotfix|patch|修复)\b/i;
|
|
|
229
229
|
*/
|
|
230
230
|
function getFixCommitCount(repoDir, sinceDate) {
|
|
231
231
|
try {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
const gitEnv = { ...process.env };
|
|
233
|
+
for (const name of [
|
|
234
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS',
|
|
235
|
+
'GIT_CONFIG_COUNT', 'GIT_OBJECT_DIRECTORY', 'GIT_DIR', 'GIT_WORK_TREE',
|
|
236
|
+
'GIT_IMPLICIT_WORK_TREE', 'GIT_GRAFT_FILE', 'GIT_INDEX_FILE',
|
|
237
|
+
'GIT_NO_REPLACE_OBJECTS', 'GIT_REPLACE_REF_BASE', 'GIT_PREFIX',
|
|
238
|
+
'GIT_SHALLOW_FILE', 'GIT_COMMON_DIR',
|
|
239
|
+
]) {
|
|
240
|
+
delete gitEnv[name];
|
|
241
|
+
}
|
|
242
|
+
const out = execFileSync(
|
|
243
|
+
'git',
|
|
244
|
+
['log', '--all', '--since', sinceDate, '--pretty=format:%s'],
|
|
245
|
+
{ cwd: repoDir, env: gitEnv, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
235
246
|
);
|
|
236
247
|
return out.split('\n').filter(l => l.trim() && FIX_RE.test(l.trim())).length;
|
|
237
248
|
} catch {
|