@boyingliu01/xp-gate 0.18.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapter-common.sh +27 -0
- package/adapters/powershell.sh +85 -33
- 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/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 +5 -5
- package/mock-policy/__tests__/gate-m3.test.ts +0 -5
- package/mock-policy/__tests__/scope-scanner.test.ts +8 -2
- package/mutation/AGENTS.md +4 -4
- 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 +13 -12
- 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 +5 -5
- 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 +4 -4
- 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 +13 -12
- 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 +5 -5
- 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 +4 -4
- 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 +15 -17
- 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 +5 -5
- 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 +4 -4
- package/principles/AGENTS.md +4 -4
- 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 +13 -12
- 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 +5 -5
- 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 +4 -4
- package/sprint-gate.sh +291 -0
|
@@ -25,20 +25,58 @@ function cleanupDir(dir) {
|
|
|
25
25
|
describe('check-version.js — REQ-001-01', () => {
|
|
26
26
|
let mod;
|
|
27
27
|
let origHome;
|
|
28
|
+
let origCacheDir;
|
|
28
29
|
let tmpHome;
|
|
30
|
+
let tmpCacheDir;
|
|
29
31
|
let origReadFileSync;
|
|
32
|
+
let origHttpsGet;
|
|
33
|
+
|
|
34
|
+
function installFakeHttpsGet(latestVersion) {
|
|
35
|
+
const https = require('https');
|
|
36
|
+
const saved = https.get;
|
|
37
|
+
https.get = (_url, options, callbackArg) => {
|
|
38
|
+
const callback = typeof options === 'function' ? options : callbackArg;
|
|
39
|
+
if (!callback) return { on: () => undefined, destroy: () => undefined };
|
|
40
|
+
const response = {
|
|
41
|
+
statusCode: 200,
|
|
42
|
+
on: (event, handler) => {
|
|
43
|
+
const eventArgs = {
|
|
44
|
+
data: [JSON.stringify({ latest: latestVersion })],
|
|
45
|
+
end: [],
|
|
46
|
+
};
|
|
47
|
+
if (Object.hasOwn(eventArgs, event)) handler(...eventArgs[event]);
|
|
48
|
+
return response;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
callback(response);
|
|
52
|
+
return { on: () => undefined, destroy: () => undefined };
|
|
53
|
+
};
|
|
54
|
+
return () => {
|
|
55
|
+
https.get = saved;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
30
58
|
|
|
31
59
|
beforeEach(() => {
|
|
32
60
|
vi.resetModules();
|
|
33
61
|
origHome = process.env.HOME;
|
|
34
|
-
|
|
62
|
+
origCacheDir = process.env.XP_GATE_CACHE_DIR;
|
|
35
63
|
origReadFileSync = fs.readFileSync;
|
|
64
|
+
origHttpsGet = require('https').get;
|
|
65
|
+
tmpHome = undefined;
|
|
66
|
+
tmpCacheDir = undefined;
|
|
67
|
+
tmpHome = fakeHome();
|
|
68
|
+
tmpCacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ckv-cache-'));
|
|
69
|
+
process.env.XP_GATE_CACHE_DIR = tmpCacheDir;
|
|
36
70
|
});
|
|
37
71
|
|
|
38
72
|
afterEach(() => {
|
|
39
73
|
process.env.HOME = origHome;
|
|
74
|
+
if (origCacheDir === undefined) delete process.env.XP_GATE_CACHE_DIR;
|
|
75
|
+
else process.env.XP_GATE_CACHE_DIR = origCacheDir;
|
|
40
76
|
cleanupDir(tmpHome);
|
|
41
|
-
|
|
77
|
+
cleanupDir(tmpCacheDir);
|
|
78
|
+
if (origReadFileSync) fs.readFileSync = origReadFileSync;
|
|
79
|
+
if (origHttpsGet) require('https').get = origHttpsGet;
|
|
42
80
|
});
|
|
43
81
|
|
|
44
82
|
// ──────────────────────────────────────────
|
|
@@ -159,6 +197,14 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
159
197
|
expect(() => mod.clearCache()).not.toThrow();
|
|
160
198
|
});
|
|
161
199
|
|
|
200
|
+
it('does not touch the home-directory cache when XP_GATE_CACHE_DIR is set', () => {
|
|
201
|
+
const homeCache = path.join(tmpHome, '.xp-gate', 'version-cache.json');
|
|
202
|
+
fs.mkdirSync(path.dirname(homeCache), { recursive: true });
|
|
203
|
+
fs.writeFileSync(homeCache, 'sentinel');
|
|
204
|
+
mod.clearCache();
|
|
205
|
+
expect(fs.readFileSync(homeCache, 'utf8')).toBe('sentinel');
|
|
206
|
+
});
|
|
207
|
+
|
|
162
208
|
it('cachePath returns null when XP_GATE_DIR is inaccessible', () => {
|
|
163
209
|
// Verify clearCache handles null cachePath gracefully
|
|
164
210
|
expect(() => mod.clearCache()).not.toThrow();
|
|
@@ -216,31 +262,14 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
216
262
|
delete require.cache[resolved];
|
|
217
263
|
}
|
|
218
264
|
|
|
219
|
-
function withMockedHttps(latestVersion, fn) {
|
|
265
|
+
async function withMockedHttps(latestVersion, fn) {
|
|
220
266
|
const fsm = require('fs');
|
|
221
|
-
const
|
|
222
|
-
const cpPath = require('path').join(osm.homedir(), '.xp-gate', 'version-cache.json');
|
|
267
|
+
const cpPath = require('path').join(process.env.XP_GATE_CACHE_DIR, 'version-cache.json');
|
|
223
268
|
if (fsm.existsSync(cpPath)) {
|
|
224
269
|
try { fsm.unlinkSync(cpPath); } catch { }
|
|
225
270
|
}
|
|
226
271
|
evictCache();
|
|
227
|
-
const
|
|
228
|
-
const saved = https.get;
|
|
229
|
-
const body = JSON.stringify({ latest: latestVersion });
|
|
230
|
-
https.get = (_url, _opts, cb) => {
|
|
231
|
-
const callback = typeof _opts === 'function' ? _opts : cb;
|
|
232
|
-
if (!callback) return { on: () => undefined, destroy: () => undefined };
|
|
233
|
-
const mockRes = {
|
|
234
|
-
statusCode: 200,
|
|
235
|
-
on: (evt, handler) => {
|
|
236
|
-
if (evt === 'data') handler(body);
|
|
237
|
-
if (evt === 'end') handler();
|
|
238
|
-
return mockRes;
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
callback(mockRes);
|
|
242
|
-
return { on: () => undefined, destroy: () => undefined };
|
|
243
|
-
};
|
|
272
|
+
const restoreHttpsGet = installFakeHttpsGet(latestVersion);
|
|
244
273
|
// Mock getLocalVersion to return a fixed version so tests don't
|
|
245
274
|
// depend on the real package.json version (which drifts over time).
|
|
246
275
|
const savedReadFileSync = fsm.readFileSync;
|
|
@@ -256,9 +285,9 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
256
285
|
};
|
|
257
286
|
try {
|
|
258
287
|
const m = require('../check-version');
|
|
259
|
-
return fn(m);
|
|
288
|
+
return await fn(m);
|
|
260
289
|
} finally {
|
|
261
|
-
|
|
290
|
+
restoreHttpsGet();
|
|
262
291
|
fsm.readFileSync = savedReadFileSync;
|
|
263
292
|
}
|
|
264
293
|
}
|
|
@@ -301,39 +330,31 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
301
330
|
delete require.cache[resolved];
|
|
302
331
|
}
|
|
303
332
|
|
|
304
|
-
function withMockedHttpsBox(latestVersion, fn) {
|
|
333
|
+
async function withMockedHttpsBox(latestVersion, fn) {
|
|
305
334
|
const fsm = require('fs');
|
|
306
|
-
const
|
|
307
|
-
const cpPath = require('path').join(osm.homedir(), '.xp-gate', 'version-cache.json');
|
|
335
|
+
const cpPath = require('path').join(process.env.XP_GATE_CACHE_DIR, 'version-cache.json');
|
|
308
336
|
if (fsm.existsSync(cpPath)) {
|
|
309
337
|
try { fsm.unlinkSync(cpPath); } catch { }
|
|
310
338
|
}
|
|
311
339
|
evictCache();
|
|
312
|
-
const
|
|
313
|
-
const saved = https.get;
|
|
314
|
-
const body = JSON.stringify({ latest: latestVersion });
|
|
315
|
-
https.get = (_url, _opts, cb) => {
|
|
316
|
-
const callback = typeof _opts === 'function' ? _opts : cb;
|
|
317
|
-
if (!callback) return { on: () => undefined, destroy: () => undefined };
|
|
318
|
-
const mockRes = {
|
|
319
|
-
statusCode: 200,
|
|
320
|
-
on: (evt, handler) => {
|
|
321
|
-
if (evt === 'data') handler(body);
|
|
322
|
-
if (evt === 'end') handler();
|
|
323
|
-
return mockRes;
|
|
324
|
-
},
|
|
325
|
-
};
|
|
326
|
-
callback(mockRes);
|
|
327
|
-
return { on: () => undefined, destroy: () => undefined };
|
|
328
|
-
};
|
|
340
|
+
const restoreHttpsGet = installFakeHttpsGet(latestVersion);
|
|
329
341
|
try {
|
|
330
342
|
const m = require('../check-version');
|
|
331
|
-
return fn(m, cpPath);
|
|
343
|
+
return await fn(m, cpPath);
|
|
332
344
|
} finally {
|
|
333
|
-
|
|
345
|
+
restoreHttpsGet();
|
|
334
346
|
}
|
|
335
347
|
}
|
|
336
348
|
|
|
349
|
+
it('restores HTTPS after an asynchronous callback rejects', async () => {
|
|
350
|
+
const savedGet = require('https').get;
|
|
351
|
+
await expect(withMockedHttpsBox('0.18.0', async () => {
|
|
352
|
+
await Promise.resolve();
|
|
353
|
+
throw new Error('callback failed');
|
|
354
|
+
})).rejects.toThrow('callback failed');
|
|
355
|
+
expect(require('https').get).toBe(savedGet);
|
|
356
|
+
});
|
|
357
|
+
|
|
337
358
|
it('rejects null version from registry — cache NOT written', async () => {
|
|
338
359
|
await withMockedHttpsBox(null, async (m, cpPath) => {
|
|
339
360
|
const r = await m.getRemoteVersion('@test/pkg');
|
|
@@ -66,7 +66,7 @@ describe('doctor', () => {
|
|
|
66
66
|
// and reloading it would restore the full tool list, breaking the test mock setup.
|
|
67
67
|
const modulesToInvalidate = [
|
|
68
68
|
'../doctor', '../uninstall', '../init', '../shared-paths',
|
|
69
|
-
'../check-version.js',
|
|
69
|
+
'../check-version.js', '../doctor-tui',
|
|
70
70
|
];
|
|
71
71
|
for (const mod of modulesToInvalidate) {
|
|
72
72
|
try { delete require.cache[require.resolve(mod)]; } catch { /* first load */ }
|
|
@@ -126,7 +126,7 @@ describe('doctor', () => {
|
|
|
126
126
|
fs.mkdirSync(dir, { recursive: true });
|
|
127
127
|
fs.writeFileSync(
|
|
128
128
|
path.join(dir, 'pre-push'),
|
|
129
|
-
'#!/bin/bash\n# Pre-push Hook
|
|
129
|
+
'#!/bin/bash\n# Pre-push Hook — 8 gates:\n'
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -148,7 +148,7 @@ describe('doctor', () => {
|
|
|
148
148
|
path.join(dir, 'adapters', 'python.sh'),
|
|
149
149
|
'#!/usr/bin/env bash\necho "py adapter"\n'
|
|
150
150
|
);
|
|
151
|
-
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh'];
|
|
151
|
+
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh', 'gate-10.sh'];
|
|
152
152
|
for (const script of gateScripts) {
|
|
153
153
|
fs.writeFileSync(path.join(dir, 'adapters', script), `#!/bin/bash\n# ${script}\n`);
|
|
154
154
|
}
|
|
@@ -187,7 +187,7 @@ describe('doctor', () => {
|
|
|
187
187
|
fs.mkdirSync(globalAdaptersDir(), { recursive: true });
|
|
188
188
|
fs.writeFileSync(path.join(globalAdaptersDir(), 'typescript.sh'), '#!/usr/bin/env bash\necho "ts adapter"\n');
|
|
189
189
|
fs.writeFileSync(path.join(globalAdaptersDir(), 'python.sh'), '#!/usr/bin/env bash\necho "py adapter"\n');
|
|
190
|
-
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh'];
|
|
190
|
+
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh', 'gate-10.sh'];
|
|
191
191
|
for (const script of gateScripts) {
|
|
192
192
|
fs.writeFileSync(path.join(globalAdaptersDir(), script), `#!/bin/bash\n# ${script}\n`);
|
|
193
193
|
}
|
|
@@ -941,7 +941,7 @@ describe('doctor', () => {
|
|
|
941
941
|
|
|
942
942
|
function createGateScripts(dir) {
|
|
943
943
|
fs.mkdirSync(dir, { recursive: true });
|
|
944
|
-
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh'];
|
|
944
|
+
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh', 'gate-10.sh'];
|
|
945
945
|
for (const script of gateScripts) {
|
|
946
946
|
fs.writeFileSync(path.join(dir, script), `#!/bin/bash\n# ${script}\n`);
|
|
947
947
|
}
|
|
@@ -962,7 +962,7 @@ describe('doctor', () => {
|
|
|
962
962
|
expect.stringContaining('Gate scripts')
|
|
963
963
|
);
|
|
964
964
|
expect(logSpy).toHaveBeenCalledWith(
|
|
965
|
-
expect.stringContaining('
|
|
965
|
+
expect.stringContaining('6 gate script(s)')
|
|
966
966
|
);
|
|
967
967
|
});
|
|
968
968
|
|
|
@@ -1005,6 +1005,7 @@ describe('doctor', () => {
|
|
|
1005
1005
|
expect(fs.existsSync(path.join(projectAdaptersDir(), 'gate-7.sh'))).toBe(true);
|
|
1006
1006
|
expect(fs.existsSync(path.join(projectAdaptersDir(), 'gate-8.sh'))).toBe(true);
|
|
1007
1007
|
expect(fs.existsSync(path.join(projectAdaptersDir(), 'gate-9.sh'))).toBe(true);
|
|
1008
|
+
expect(fs.existsSync(path.join(projectAdaptersDir(), 'gate-10.sh'))).toBe(true);
|
|
1008
1009
|
expect(logSpy).toHaveBeenCalledWith(
|
|
1009
1010
|
expect.stringContaining('Restored')
|
|
1010
1011
|
);
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-standalone-gates gate-runner.js
|
|
3
|
+
* @intent 验证 runGateAdapter 独立运行 gate 适配器片段时能提供钩子上下文
|
|
4
|
+
* (gate_start_ms / record_gate_audit / now_ms / PROJECT_LANG / CHANGED_FILES),
|
|
5
|
+
* 避免 "command not found" 误报。
|
|
6
|
+
* @covers AC-STANDALONE-GATES-01, AC-STANDALONE-GATES-02, AC-STANDALONE-GATES-03, AC-STANDALONE-GATES-04, AC-STANDALONE-GATES-05
|
|
7
|
+
*/
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const os = require('node:os');
|
|
11
|
+
const { execFileSync, spawnSync } = require('node:child_process');
|
|
12
|
+
|
|
13
|
+
let tmpProject;
|
|
14
|
+
let tmpAdapters;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'xpgate-runner-'));
|
|
18
|
+
tmpAdapters = path.join(tmpProject, 'githooks', 'adapters');
|
|
19
|
+
fs.mkdirSync(tmpAdapters, { recursive: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
if (tmpProject && fs.existsSync(tmpProject)) {
|
|
24
|
+
fs.rmSync(tmpProject, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function writeAdapterCommon() {
|
|
29
|
+
fs.writeFileSync(
|
|
30
|
+
path.join(tmpAdapters, 'adapter-common.sh'),
|
|
31
|
+
'detect_project_lang() { echo "typescript"; }'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeGateFragment(fragmentLines) {
|
|
36
|
+
fs.writeFileSync(
|
|
37
|
+
path.join(tmpAdapters, 'gate-9.sh'),
|
|
38
|
+
['# gate-9 fragment', ...fragmentLines, 'exit 0'].join('\n')
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function shellParameter(expression) {
|
|
43
|
+
return ['$', '{', expression, '}'].join('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function initializeGitProject() {
|
|
47
|
+
execFileSync('git', ['init', '-q'], { cwd: tmpProject });
|
|
48
|
+
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: tmpProject });
|
|
49
|
+
execFileSync('git', ['config', 'user.name', 'XP-Gate Test'], { cwd: tmpProject });
|
|
50
|
+
fs.writeFileSync(path.join(tmpProject, 'tracked.ts'), 'const value = 1;\n');
|
|
51
|
+
execFileSync('git', ['add', 'tracked.ts'], { cwd: tmpProject });
|
|
52
|
+
execFileSync('git', ['-c', 'core.hooksPath=/dev/null', 'commit', '-qm', 'initial'], {
|
|
53
|
+
cwd: tmpProject,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Runs runGateAdapter in a child node process (worker threads forbid process
|
|
58
|
+
// chdir, and runGateAdapter execs bash), capturing stdout/exit status.
|
|
59
|
+
function runChildAdapter(gateRunnerAbsPath, fragmentPath, cwd = tmpProject) {
|
|
60
|
+
const script = [
|
|
61
|
+
`const { runGateAdapter } = require(${JSON.stringify(gateRunnerAbsPath)});`,
|
|
62
|
+
`runGateAdapter(${JSON.stringify(fragmentPath)}, ${JSON.stringify(cwd)});`,
|
|
63
|
+
"console.log('__ADAPTER_DONE__');",
|
|
64
|
+
].join('\n');
|
|
65
|
+
try {
|
|
66
|
+
const stdout = execFileSync(process.execPath, ['-e', script], {
|
|
67
|
+
cwd,
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
timeout: 30000,
|
|
70
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
+
});
|
|
72
|
+
return { status: 0, stdout };
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return { status: err.status ?? 1, stdout: (err.stdout || '').toString() };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('gate-runner.js — runGateAdapter', () => {
|
|
79
|
+
it('sources the fragment with hook helpers available (REQ-standalone-gates-01)', () => {
|
|
80
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
81
|
+
writeAdapterCommon();
|
|
82
|
+
writeGateFragment([
|
|
83
|
+
'USES_START=$(gate_start_ms)',
|
|
84
|
+
`if [ -z "${shellParameter('PROJECT_LANG:-')}" ]; then exit 99; fi`,
|
|
85
|
+
`if [ -z "${shellParameter('CHANGED_FILES+x')}" ]; then exit 98; fi`,
|
|
86
|
+
'record_gate_audit "gate-9" "sast" "PASS" "0" "$USES_START"',
|
|
87
|
+
]);
|
|
88
|
+
const fragPath = path.join(tmpAdapters, 'gate-9.sh');
|
|
89
|
+
const { status, stdout } = runChildAdapter(runnerAbsPath, fragPath);
|
|
90
|
+
expect(status).toBe(0);
|
|
91
|
+
expect(stdout).toContain('__ADAPTER_DONE__');
|
|
92
|
+
}, 30000);
|
|
93
|
+
|
|
94
|
+
it('defines now_ms/gate_start_ms/record_gate_audit/PROJECT_LANG/CHANGED_FILES before sourcing (REQ-standalone-gates-02)', () => {
|
|
95
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
96
|
+
writeAdapterCommon();
|
|
97
|
+
writeGateFragment([
|
|
98
|
+
'if ! command -v gate_start_ms >/dev/null 2>&1; then exit 97; fi',
|
|
99
|
+
'if ! command -v record_gate_audit >/dev/null 2>&1; then exit 96; fi',
|
|
100
|
+
'if ! command -v now_ms >/dev/null 2>&1; then exit 95; fi',
|
|
101
|
+
`if [ -z "${shellParameter('PROJECT_LANG:-')}" ]; then exit 94; fi`,
|
|
102
|
+
`if [ -z "${shellParameter('CHANGED_FILES+x')}" ]; then exit 93; fi`,
|
|
103
|
+
]);
|
|
104
|
+
const fragPath = path.join(tmpAdapters, 'gate-9.sh');
|
|
105
|
+
const { status, stdout } = runChildAdapter(runnerAbsPath, fragPath);
|
|
106
|
+
expect(status).toBe(0);
|
|
107
|
+
expect(stdout).toContain('__ADAPTER_DONE__');
|
|
108
|
+
}, 30000);
|
|
109
|
+
|
|
110
|
+
it('sources adapter-common.sh to provide detect_project_lang (REQ-standalone-gates-03)', () => {
|
|
111
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
112
|
+
writeAdapterCommon();
|
|
113
|
+
writeGateFragment([
|
|
114
|
+
`if [ "${shellParameter('PROJECT_LANG:-')}" != "typescript" ]; then exit 92; fi`,
|
|
115
|
+
]);
|
|
116
|
+
const fragPath = path.join(tmpAdapters, 'gate-9.sh');
|
|
117
|
+
const { status, stdout } = runChildAdapter(runnerAbsPath, fragPath);
|
|
118
|
+
expect(status).toBe(0);
|
|
119
|
+
expect(stdout).toContain('__ADAPTER_DONE__');
|
|
120
|
+
}, 30000);
|
|
121
|
+
|
|
122
|
+
it('propagates changed files from the project git working tree (REQ-standalone-gates-04)', () => {
|
|
123
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
124
|
+
writeAdapterCommon();
|
|
125
|
+
initializeGitProject();
|
|
126
|
+
fs.writeFileSync(path.join(tmpProject, 'tracked.ts'), 'const value = 2;\n');
|
|
127
|
+
writeGateFragment([
|
|
128
|
+
`if [ "${shellParameter('CHANGED_FILES')}" != "tracked.ts" ]; then exit 91; fi`,
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
const fragPath = path.join(tmpAdapters, 'gate-9.sh');
|
|
132
|
+
const { status, stdout } = runChildAdapter(runnerAbsPath, fragPath);
|
|
133
|
+
|
|
134
|
+
expect(status).toBe(0);
|
|
135
|
+
expect(stdout).toContain('__ADAPTER_DONE__');
|
|
136
|
+
}, 30000);
|
|
137
|
+
|
|
138
|
+
it('runs a standalone gate bash fallback through the public CLI (REQ-standalone-gates-05)', () => {
|
|
139
|
+
const packageFixture = path.join(tmpProject, 'package');
|
|
140
|
+
fs.cpSync(path.resolve(__dirname, '../..'), packageFixture, { recursive: true });
|
|
141
|
+
const cliPath = path.join(packageFixture, 'bin', 'xp-gate.js');
|
|
142
|
+
const gatesDir = path.join(tmpProject, 'githooks', 'gates');
|
|
143
|
+
fs.mkdirSync(gatesDir, { recursive: true });
|
|
144
|
+
fs.writeFileSync(
|
|
145
|
+
path.join(gatesDir, 'gate-9-public-fallback.sh'),
|
|
146
|
+
[
|
|
147
|
+
'if ! command -v gate_start_ms >/dev/null 2>&1; then exit 90; fi',
|
|
148
|
+
`if [ -z "${shellParameter('CHANGED_FILES+x')}" ]; then exit 89; fi`,
|
|
149
|
+
'echo "__PUBLIC_FALLBACK__"',
|
|
150
|
+
].join('\n')
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const result = spawnSync(process.execPath, [cliPath, 'check', '9'], {
|
|
154
|
+
cwd: tmpProject,
|
|
155
|
+
encoding: 'utf8',
|
|
156
|
+
timeout: 30000,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.status).toBe(0);
|
|
160
|
+
expect(result.stdout).toContain('__PUBLIC_FALLBACK__');
|
|
161
|
+
}, 30000);
|
|
162
|
+
|
|
163
|
+
it('isolates fallback resolution and git context to the requested target path (REQ-standalone-gates-06)', () => {
|
|
164
|
+
const packageFixture = path.join(tmpProject, 'package');
|
|
165
|
+
const callerProject = path.join(tmpProject, 'caller');
|
|
166
|
+
const targetProject = path.join(tmpProject, 'target');
|
|
167
|
+
fs.cpSync(path.resolve(__dirname, '../..'), packageFixture, { recursive: true });
|
|
168
|
+
fs.mkdirSync(path.join(callerProject, 'githooks', 'gates'), { recursive: true });
|
|
169
|
+
fs.mkdirSync(path.join(targetProject, 'githooks', 'gates'), { recursive: true });
|
|
170
|
+
fs.writeFileSync(
|
|
171
|
+
path.join(callerProject, 'githooks', 'gates', 'gate-9-caller.sh'),
|
|
172
|
+
'echo "__CALLER_REPO__"\n'
|
|
173
|
+
);
|
|
174
|
+
fs.writeFileSync(
|
|
175
|
+
path.join(targetProject, 'githooks', 'gates', 'gate-9-target.sh'),
|
|
176
|
+
[
|
|
177
|
+
'if [ "$PWD" != "$EXPECTED_TARGET" ]; then exit 88; fi',
|
|
178
|
+
'echo "__TARGET_REPO__"',
|
|
179
|
+
].join('\n')
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const result = spawnSync(
|
|
183
|
+
process.execPath,
|
|
184
|
+
[path.join(packageFixture, 'bin', 'xp-gate.js'), 'check', '9', targetProject],
|
|
185
|
+
{
|
|
186
|
+
cwd: callerProject,
|
|
187
|
+
env: { ...process.env, EXPECTED_TARGET: targetProject },
|
|
188
|
+
encoding: 'utf8',
|
|
189
|
+
timeout: 30000,
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(result.status).toBe(0);
|
|
194
|
+
expect(result.stdout).toContain('__TARGET_REPO__');
|
|
195
|
+
expect(result.stdout).not.toContain('__CALLER_REPO__');
|
|
196
|
+
}, 30000);
|
|
197
|
+
|
|
198
|
+
it('sources fragment and helper paths containing shell metacharacters literally (REQ-standalone-gates-07)', () => {
|
|
199
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
200
|
+
const hostileRoot = path.join(tmpProject, 'space $HOME `marker` "quoted"');
|
|
201
|
+
tmpAdapters = path.join(hostileRoot, 'githooks', 'adapters');
|
|
202
|
+
fs.mkdirSync(tmpAdapters, { recursive: true });
|
|
203
|
+
writeAdapterCommon();
|
|
204
|
+
writeGateFragment([
|
|
205
|
+
`if [ "${shellParameter('PROJECT_LANG')}" != "typescript" ]; then exit 87; fi`,
|
|
206
|
+
'echo "__HOSTILE_PATH_OK__"',
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
const result = runChildAdapter(
|
|
210
|
+
runnerAbsPath,
|
|
211
|
+
path.join(tmpAdapters, 'gate-9.sh'),
|
|
212
|
+
hostileRoot
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(result.status).toBe(0);
|
|
216
|
+
expect(result.stdout).toContain('__HOSTILE_PATH_OK__');
|
|
217
|
+
}, 30000);
|
|
218
|
+
|
|
219
|
+
it('loads parent-layout adapter-common and packaged now-ms through the public CLI (REQ-standalone-gates-08)', () => {
|
|
220
|
+
const packageFixture = path.join(tmpProject, 'package');
|
|
221
|
+
fs.cpSync(path.resolve(__dirname, '../..'), packageFixture, { recursive: true });
|
|
222
|
+
const gatesDir = path.join(tmpProject, 'githooks', 'gates');
|
|
223
|
+
fs.mkdirSync(gatesDir, { recursive: true });
|
|
224
|
+
fs.writeFileSync(
|
|
225
|
+
path.join(tmpProject, 'githooks', 'adapter-common.sh'),
|
|
226
|
+
'detect_project_lang() { echo "parent-layout-language"; }\n'
|
|
227
|
+
);
|
|
228
|
+
fs.writeFileSync(
|
|
229
|
+
path.join(gatesDir, 'gate-9-parent-helper.sh'),
|
|
230
|
+
[
|
|
231
|
+
`if [ "${shellParameter('PROJECT_LANG')}" != "parent-layout-language" ]; then exit 86; fi`,
|
|
232
|
+
'if [ "$(now_ms)" != "424242" ]; then exit 85; fi',
|
|
233
|
+
'echo "__PARENT_HELPER_OK__"',
|
|
234
|
+
].join('\n')
|
|
235
|
+
);
|
|
236
|
+
fs.writeFileSync(
|
|
237
|
+
path.join(packageFixture, 'hooks', 'lib', 'now-ms.sh'),
|
|
238
|
+
'now_ms() { echo 424242; }\n'
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const result = spawnSync(
|
|
242
|
+
process.execPath,
|
|
243
|
+
[path.join(packageFixture, 'bin', 'xp-gate.js'), 'check', '9'],
|
|
244
|
+
{ cwd: tmpProject, encoding: 'utf8', timeout: 30000 }
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(result.status).toBe(0);
|
|
248
|
+
expect(result.stdout).toContain('__PARENT_HELPER_OK__');
|
|
249
|
+
}, 30000);
|
|
250
|
+
|
|
251
|
+
it('propagates a failing fragment exit through the public CLI (REQ-standalone-gates-09)', () => {
|
|
252
|
+
const packageFixture = path.join(tmpProject, 'package');
|
|
253
|
+
const targetProject = path.join(tmpProject, 'failing-target');
|
|
254
|
+
fs.cpSync(path.resolve(__dirname, '../..'), packageFixture, { recursive: true });
|
|
255
|
+
const gatesDir = path.join(targetProject, 'githooks', 'gates');
|
|
256
|
+
fs.mkdirSync(gatesDir, { recursive: true });
|
|
257
|
+
fs.writeFileSync(path.join(gatesDir, 'gate-9-failure.sh'), 'exit 23\n');
|
|
258
|
+
|
|
259
|
+
const result = spawnSync(
|
|
260
|
+
process.execPath,
|
|
261
|
+
[path.join(packageFixture, 'bin', 'xp-gate.js'), 'check', '9', targetProject],
|
|
262
|
+
{ cwd: tmpProject, encoding: 'utf8', timeout: 30000 }
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(result.status).toBe(1);
|
|
266
|
+
}, 30000);
|
|
267
|
+
});
|
|
@@ -447,4 +447,36 @@ describe('init', () => {
|
|
|
447
447
|
expect(fs.existsSync(path.join(tmpProject, '.qoder', 'agents'))).toBe(false);
|
|
448
448
|
});
|
|
449
449
|
});
|
|
450
|
+
|
|
451
|
+
describe('installModuleRuntimeDeps', () => {
|
|
452
|
+
it('writes a package.json with module runtime deps and runs npm install', () => {
|
|
453
|
+
let execArgs = null;
|
|
454
|
+
vi.spyOn(childProcess, 'execSync').mockImplementation((cmd, opts) => {
|
|
455
|
+
execArgs = { cmd, cwd: opts && opts.cwd };
|
|
456
|
+
return Buffer.from('');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const { installModuleRuntimeDeps } = require('../init');
|
|
460
|
+
installModuleRuntimeDeps(tmpHome);
|
|
461
|
+
|
|
462
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(tmpHome, 'package.json'), 'utf8'));
|
|
463
|
+
expect(pkg.dependencies['js-yaml']).toBeDefined();
|
|
464
|
+
expect(pkg.dependencies['zod']).toBeDefined();
|
|
465
|
+
expect(execArgs).not.toBeNull();
|
|
466
|
+
expect(execArgs.cmd).toContain('npm install');
|
|
467
|
+
expect(execArgs.cwd).toBe(tmpHome);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('fails gracefully (warning, no throw) when npm install fails', () => {
|
|
471
|
+
vi.spyOn(childProcess, 'execSync').mockImplementation(() => {
|
|
472
|
+
throw new Error('registry offline');
|
|
473
|
+
});
|
|
474
|
+
const warnBefore = console.warn;
|
|
475
|
+
|
|
476
|
+
const { installModuleRuntimeDeps } = require('../init');
|
|
477
|
+
expect(() => installModuleRuntimeDeps(tmpHome)).not.toThrow();
|
|
478
|
+
|
|
479
|
+
expect(console.warn).toHaveBeenCalled();
|
|
480
|
+
});
|
|
481
|
+
});
|
|
450
482
|
});
|
|
@@ -97,3 +97,16 @@ describe('publishConfig for scoped packages', () => {
|
|
|
97
97
|
expect(pkg.publishConfig?.access).toBe('public');
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
|
+
|
|
101
|
+
describe('published adapter-common.sh', () => {
|
|
102
|
+
it('matches the canonical Git hook adapter helper', () => {
|
|
103
|
+
const canonical = fs.readFileSync(
|
|
104
|
+
path.join(pkgDir, '..', '..', 'githooks', 'adapter-common.sh'),
|
|
105
|
+
'utf8'
|
|
106
|
+
);
|
|
107
|
+
const published = fs.readFileSync(path.join(pkgDir, 'adapter-common.sh'), 'utf8');
|
|
108
|
+
|
|
109
|
+
expect(published).toBe(canonical);
|
|
110
|
+
expect(published).toContain('run_without_git_context');
|
|
111
|
+
});
|
|
112
|
+
});
|