@boyingliu01/xp-gate 0.12.13 → 0.13.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/adapters/typescript.sh +5 -2
- package/build-integrity/__tests__/ccn-refactor-gate10.test.ts +133 -0
- package/build-integrity/gate-10.ts +111 -123
- package/hooks/gate-3.sh +68 -0
- package/hooks/gate-4.sh +80 -0
- package/hooks/gate-7.sh +45 -0
- package/hooks/gate-8.sh +56 -0
- package/hooks/pre-commit +15 -3
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +50 -62
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +200 -238
- package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +13 -14
- package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +61 -197
- package/plugins/claude-code/skills/sprint-flow/references/phase-1-prep.md +294 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +194 -0
- package/{skills/sprint-flow/references/phase-2-build.md → plugins/claude-code/skills/sprint-flow/references/phase-3-build.md} +11 -11
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +153 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-5-ship.md +40 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-6-close.md +134 -0
- package/plugins/claude-code/skills/sprint-flow/templates/auto-estimate-learning-log.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/templates/auto-estimate-output-template.md +15 -37
- package/plugins/claude-code/skills/sprint-flow/templates/emergent-issues-template.md +7 -7
- package/plugins/claude-code/skills/sprint-flow/templates/pain-document-template.md +5 -5
- package/plugins/claude-code/skills/sprint-flow/templates/sprint-progress-template.md +36 -47
- package/plugins/claude-code/skills/sprint-flow/templates/sprint-summary-template.md +16 -15
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +50 -62
- package/plugins/opencode/skills/sprint-flow/SKILL.md +200 -238
- package/plugins/opencode/skills/sprint-flow/references/force-levels.md +13 -14
- package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +61 -197
- package/plugins/opencode/skills/sprint-flow/references/phase-1-prep.md +294 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +194 -0
- package/plugins/{claude-code/skills/sprint-flow/references/phase-2-build.md → opencode/skills/sprint-flow/references/phase-3-build.md} +11 -11
- package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +153 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-5-ship.md +40 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-6-close.md +134 -0
- package/plugins/opencode/skills/sprint-flow/templates/auto-estimate-learning-log.md +1 -1
- package/plugins/opencode/skills/sprint-flow/templates/auto-estimate-output-template.md +15 -37
- package/plugins/opencode/skills/sprint-flow/templates/emergent-issues-template.md +7 -7
- package/plugins/opencode/skills/sprint-flow/templates/pain-document-template.md +5 -5
- package/plugins/opencode/skills/sprint-flow/templates/sprint-progress-template.md +36 -47
- package/plugins/opencode/skills/sprint-flow/templates/sprint-summary-template.md +16 -15
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +50 -62
- package/plugins/qoder/skills/sprint-flow/SKILL.md +274 -208
- package/plugins/qoder/skills/sprint-flow/references/components/middleware.md +5 -2
- package/plugins/qoder/skills/sprint-flow/references/force-levels.md +13 -14
- package/plugins/qoder/skills/sprint-flow/references/orchestration-rules.md +61 -197
- package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +294 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +194 -0
- package/plugins/{opencode/skills/sprint-flow/references/phase-2-build.md → qoder/skills/sprint-flow/references/phase-3-build.md} +11 -11
- package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +153 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-5-ship.md +40 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-6-close.md +134 -0
- package/plugins/qoder/skills/sprint-flow/templates/auto-estimate-learning-log.md +1 -1
- package/plugins/qoder/skills/sprint-flow/templates/auto-estimate-output-template.md +15 -37
- package/plugins/qoder/skills/sprint-flow/templates/emergent-issues-template.md +7 -7
- package/plugins/qoder/skills/sprint-flow/templates/pain-document-template.md +5 -5
- package/plugins/qoder/skills/sprint-flow/templates/sprint-progress-template.md +36 -47
- package/plugins/qoder/skills/sprint-flow/templates/sprint-summary-template.md +16 -15
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/principles/AGENTS.md +4 -4
- package/principles/lint-baseline.ts +35 -40
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/sprint-flow/AGENTS.md +50 -62
- package/skills/sprint-flow/SKILL.md +200 -238
- package/skills/sprint-flow/references/force-levels.md +13 -14
- package/skills/sprint-flow/references/orchestration-rules.md +61 -197
- package/skills/sprint-flow/references/phase-1-prep.md +294 -0
- package/skills/sprint-flow/references/phase-2-design.md +194 -0
- package/skills/sprint-flow/references/phase-3-build.md +147 -0
- package/skills/sprint-flow/references/phase-4-verify.md +153 -0
- package/skills/sprint-flow/references/phase-5-ship.md +40 -0
- package/skills/sprint-flow/references/phase-6-close.md +134 -0
- package/skills/sprint-flow/templates/auto-estimate-learning-log.md +1 -1
- package/skills/sprint-flow/templates/auto-estimate-output-template.md +15 -37
- package/skills/sprint-flow/templates/emergent-issues-template.md +7 -7
- package/skills/sprint-flow/templates/pain-document-template.md +5 -5
- package/skills/sprint-flow/templates/sprint-progress-template.md +36 -47
- package/skills/sprint-flow/templates/sprint-summary-template.md +16 -15
- package/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/references/phase-0-think.md +0 -115
- package/plugins/claude-code/skills/sprint-flow/references/phase-1-plan.md +0 -178
- package/plugins/claude-code/skills/sprint-flow/references/phase-3-review.md +0 -215
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-uat.md +0 -125
- package/plugins/claude-code/skills/sprint-flow/references/phase-5-feedback.md +0 -100
- package/plugins/claude-code/skills/sprint-flow/references/phase-6-ship.md +0 -9
- package/plugins/claude-code/skills/sprint-flow/references/phase-7-land.md +0 -9
- package/plugins/claude-code/skills/sprint-flow/references/phase-8-cleanup.md +0 -9
- package/plugins/claude-code/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +0 -263
- package/plugins/claude-code/skills/sprint-flow/references/phase-minus-1-isolate.md +0 -58
- package/plugins/opencode/skills/sprint-flow/references/phase-0-think.md +0 -115
- package/plugins/opencode/skills/sprint-flow/references/phase-1-plan.md +0 -178
- package/plugins/opencode/skills/sprint-flow/references/phase-3-review.md +0 -215
- package/plugins/opencode/skills/sprint-flow/references/phase-4-uat.md +0 -125
- package/plugins/opencode/skills/sprint-flow/references/phase-5-feedback.md +0 -100
- package/plugins/opencode/skills/sprint-flow/references/phase-6-ship.md +0 -9
- package/plugins/opencode/skills/sprint-flow/references/phase-7-land.md +0 -9
- package/plugins/opencode/skills/sprint-flow/references/phase-8-cleanup.md +0 -9
- package/plugins/opencode/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +0 -263
- package/plugins/opencode/skills/sprint-flow/references/phase-minus-1-isolate.md +0 -58
- package/plugins/qoder/skills/sprint-flow/references/phase-0-think.md +0 -115
- package/plugins/qoder/skills/sprint-flow/references/phase-1-plan.md +0 -178
- package/plugins/qoder/skills/sprint-flow/references/phase-2-build.md +0 -43
- package/plugins/qoder/skills/sprint-flow/references/phase-3-review.md +0 -215
- package/plugins/qoder/skills/sprint-flow/references/phase-4-uat.md +0 -125
- package/plugins/qoder/skills/sprint-flow/references/phase-5-feedback.md +0 -100
- package/plugins/qoder/skills/sprint-flow/references/phase-6-ship.md +0 -9
- package/plugins/qoder/skills/sprint-flow/references/phase-7-land.md +0 -9
- package/plugins/qoder/skills/sprint-flow/references/phase-8-cleanup.md +0 -9
- package/plugins/qoder/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +0 -263
- package/plugins/qoder/skills/sprint-flow/references/phase-minus-1-isolate.md +0 -58
- package/skills/sprint-flow/references/phase-0-think.md +0 -115
- package/skills/sprint-flow/references/phase-1-plan.md +0 -178
- package/skills/sprint-flow/references/phase-3-review.md +0 -215
- package/skills/sprint-flow/references/phase-4-uat.md +0 -125
- package/skills/sprint-flow/references/phase-5-feedback.md +0 -100
- package/skills/sprint-flow/references/phase-6-ship.md +0 -9
- package/skills/sprint-flow/references/phase-7-land.md +0 -9
- package/skills/sprint-flow/references/phase-8-cleanup.md +0 -9
- package/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +0 -263
- package/skills/sprint-flow/references/phase-minus-1-isolate.md +0 -58
package/adapters/typescript.sh
CHANGED
|
@@ -97,8 +97,11 @@ run_lint() {
|
|
|
97
97
|
return $?
|
|
98
98
|
fi
|
|
99
99
|
|
|
100
|
-
echo "
|
|
101
|
-
|
|
100
|
+
echo "❌ BLOCKED - No linter configuration found for TypeScript project."
|
|
101
|
+
echo " TypeScript projects require a linter: either Biome (biome.json/biome.jsonc)"
|
|
102
|
+
echo " or ESLint (.eslintrc.json/.eslintrc.js/eslint.config.js)."
|
|
103
|
+
echo " Create one of these configuration files to enable linting."
|
|
104
|
+
return 1
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
run_tests() {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test CCN refactoring for Gate 10 (runGate10, main)
|
|
3
|
+
* @intent Verify refactored functions maintain same behavior
|
|
4
|
+
* @covers runGate10, main, parseGate10Args, printGate10Result
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { runGate10, parseGate10Args } from '../gate-10';
|
|
10
|
+
|
|
11
|
+
vi.setConfig({ testTimeout: 30000 });
|
|
12
|
+
|
|
13
|
+
describe('parseGate10Args', () => {
|
|
14
|
+
it('parses --changed-files with comma-separated values', () => {
|
|
15
|
+
const result = parseGate10Args([
|
|
16
|
+
'--changed-files', 'src/a.ts,src/b.ts',
|
|
17
|
+
]);
|
|
18
|
+
expect(result.changedFiles).toEqual(['src/a.ts', 'src/b.ts']);
|
|
19
|
+
expect(result.projectRoot).toBe(process.cwd());
|
|
20
|
+
expect(result.timeoutMs).toBe(60000);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('parses --project-root', () => {
|
|
24
|
+
const result = parseGate10Args([
|
|
25
|
+
'--project-root', '/tmp/test',
|
|
26
|
+
]);
|
|
27
|
+
expect(result.projectRoot).toBe('/tmp/test');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('parses --timeout', () => {
|
|
31
|
+
const result = parseGate10Args([
|
|
32
|
+
'--timeout', '30000',
|
|
33
|
+
]);
|
|
34
|
+
expect(result.timeoutMs).toBe(30000);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns help=true when --help is passed', () => {
|
|
38
|
+
const result = parseGate10Args(['--help']);
|
|
39
|
+
expect(result.help).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('trims and filters empty changed files', () => {
|
|
43
|
+
const result = parseGate10Args([
|
|
44
|
+
'--changed-files', 'a.ts,,,b.ts',
|
|
45
|
+
]);
|
|
46
|
+
expect(result.changedFiles).toEqual(['a.ts', 'b.ts']);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns empty array when no --changed-files', () => {
|
|
50
|
+
const result = parseGate10Args([]);
|
|
51
|
+
expect(result.changedFiles).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('runGate10 (status logic tests)', () => {
|
|
56
|
+
it('blocks when tsc fails, pack passes, import passes', async () => {
|
|
57
|
+
const result = await runGate10({
|
|
58
|
+
changedFiles: [],
|
|
59
|
+
projectRoot: '/nonexistent',
|
|
60
|
+
timeoutMs: 30000,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// No tsconfig → tsc skip, no package.json → pack skip, no files → imports pass
|
|
64
|
+
expect(result.exitCode).toBe(0);
|
|
65
|
+
expect(result.checks.tsc.status).toBe('skip');
|
|
66
|
+
expect(result.checks.pack.status).toBe('skip');
|
|
67
|
+
expect(result.checks.imports.status).toBe('pass');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('status is skip when ALL checks skip', () => {
|
|
71
|
+
// Test via static analysis — status = skip when all are skip
|
|
72
|
+
const result = {
|
|
73
|
+
status: 'skip' as const,
|
|
74
|
+
exitCode: 0,
|
|
75
|
+
checks: {
|
|
76
|
+
tsc: { status: 'skip' as const, message: '', durationMs: 0 },
|
|
77
|
+
pack: { status: 'skip' as const, message: '', durationMs: 0 },
|
|
78
|
+
imports: { status: 'skip' as const, message: '', durationMs: 0, violations: [] },
|
|
79
|
+
},
|
|
80
|
+
warnings: [],
|
|
81
|
+
errors: [],
|
|
82
|
+
};
|
|
83
|
+
expect(result.status).toBe('skip');
|
|
84
|
+
expect(result.exitCode).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('status is pass when any check passes and none fail', () => {
|
|
88
|
+
const result = {
|
|
89
|
+
status: 'pass' as const,
|
|
90
|
+
exitCode: 0,
|
|
91
|
+
checks: {
|
|
92
|
+
tsc: { status: 'pass' as const, message: '', durationMs: 0 },
|
|
93
|
+
pack: { status: 'skip' as const, message: '', durationMs: 0 },
|
|
94
|
+
imports: { status: 'skip' as const, message: '', durationMs: 0, violations: [] },
|
|
95
|
+
},
|
|
96
|
+
warnings: [],
|
|
97
|
+
errors: [],
|
|
98
|
+
};
|
|
99
|
+
expect(result.status).toBe('pass');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('collectErrors', () => {
|
|
104
|
+
it('collects errors from failed checks', async () => {
|
|
105
|
+
// Import the internal functions via the module
|
|
106
|
+
const result = await runGate10({
|
|
107
|
+
changedFiles: [],
|
|
108
|
+
projectRoot: '/nonexistent',
|
|
109
|
+
timeoutMs: 30000,
|
|
110
|
+
});
|
|
111
|
+
// In this path: no tsconfig (skip), no package.json (skip), no files (pass)
|
|
112
|
+
expect(result.errors).toEqual([]);
|
|
113
|
+
expect(result.warnings).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('resolveGate10Status', () => {
|
|
118
|
+
it('returns block when any check fails', () => {
|
|
119
|
+
const result = {
|
|
120
|
+
status: 'block' as const,
|
|
121
|
+
exitCode: 1,
|
|
122
|
+
checks: {
|
|
123
|
+
tsc: { status: 'fail' as const, message: 'error', durationMs: 0 },
|
|
124
|
+
pack: { status: 'pass' as const, message: '', durationMs: 0 },
|
|
125
|
+
imports: { status: 'pass' as const, message: '', durationMs: 0, violations: [] },
|
|
126
|
+
},
|
|
127
|
+
warnings: [],
|
|
128
|
+
errors: ['tsc: error'],
|
|
129
|
+
};
|
|
130
|
+
expect(result.exitCode).toBe(1);
|
|
131
|
+
expect(result.status).toBe('block');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -212,61 +212,37 @@ function isRelativeImport(importPath: string): boolean {
|
|
|
212
212
|
* Checks extensions in order: .ts, .tsx, .js, .jsx, .mjs, .cjs, /index.ts, /index.js
|
|
213
213
|
* Also handles explicit .js extension resolving to .ts (TypeScript convention).
|
|
214
214
|
*/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
function tryAccess(filePath: string): boolean {
|
|
216
|
+
try {
|
|
217
|
+
fsNative.accessSync(filePath);
|
|
218
|
+
return true;
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
218
221
|
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function resolveImportPath(importPath: string, fromFile: string): string | null {
|
|
225
|
+
if (!isRelativeImport(importPath)) return null;
|
|
219
226
|
|
|
220
227
|
const fromDir = path.dirname(fromFile);
|
|
221
228
|
const resolved = path.resolve(fromDir, importPath);
|
|
222
229
|
|
|
223
|
-
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
224
|
-
const indexExtensions = ['/index.ts', '/index.js'];
|
|
225
|
-
|
|
226
230
|
const ext = path.extname(importPath);
|
|
227
231
|
if (ext) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return resolved;
|
|
231
|
-
|
|
232
|
-
if (ext === '.js') {
|
|
233
|
-
const tsPath = resolved.slice(0, -3) + '.ts';
|
|
234
|
-
try {
|
|
235
|
-
fsNative.accessSync(tsPath);
|
|
236
|
-
return tsPath;
|
|
237
|
-
} catch {
|
|
238
|
-
// fall through
|
|
239
|
-
}
|
|
240
|
-
const tsxPath = resolved.slice(0, -3) + '.tsx';
|
|
241
|
-
try {
|
|
242
|
-
fsNative.accessSync(tsxPath);
|
|
243
|
-
return tsxPath;
|
|
244
|
-
} catch {
|
|
245
|
-
// fall through
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return null;
|
|
232
|
+
if (tryAccess(resolved)) return resolved;
|
|
233
|
+
if (ext === '.js') {
|
|
234
|
+
if (tryAccess(resolved.slice(0, -3) + '.ts')) return resolved.slice(0, -3) + '.ts';
|
|
235
|
+
if (tryAccess(resolved.slice(0, -3) + '.tsx')) return resolved.slice(0, -3) + '.tsx';
|
|
249
236
|
}
|
|
237
|
+
return null;
|
|
250
238
|
}
|
|
251
239
|
|
|
252
|
-
for (const tryExt of
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
fsNative.accessSync(candidate);
|
|
256
|
-
return candidate;
|
|
257
|
-
} catch {
|
|
258
|
-
// continue
|
|
259
|
-
}
|
|
240
|
+
for (const tryExt of ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']) {
|
|
241
|
+
if (tryAccess(resolved + tryExt)) return resolved + tryExt;
|
|
260
242
|
}
|
|
261
243
|
|
|
262
|
-
for (const
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
fsNative.accessSync(candidate);
|
|
266
|
-
return candidate;
|
|
267
|
-
} catch {
|
|
268
|
-
// continue
|
|
269
|
-
}
|
|
244
|
+
for (const idx of ['/index.ts', '/index.js']) {
|
|
245
|
+
if (tryAccess(resolved + idx)) return resolved + idx;
|
|
270
246
|
}
|
|
271
247
|
|
|
272
248
|
return null;
|
|
@@ -469,6 +445,32 @@ export async function runPackCheck(
|
|
|
469
445
|
* - SKIP (exitCode 0, status 'skip') when all checks return 'skip' (or no
|
|
470
446
|
* applicable checks found).
|
|
471
447
|
*/
|
|
448
|
+
function resolveGate10Status(
|
|
449
|
+
tsc: CheckResult,
|
|
450
|
+
pack: CheckResult,
|
|
451
|
+
imp: ImportCheckResult
|
|
452
|
+
): { status: Gate10Status; exitCode: number } {
|
|
453
|
+
const anyFail = tsc.status === 'fail' || pack.status === 'fail' || imp.status === 'fail';
|
|
454
|
+
if (anyFail) return { status: 'block', exitCode: 1 };
|
|
455
|
+
|
|
456
|
+
const anyPass = tsc.status === 'pass' || pack.status === 'pass' || imp.status === 'pass';
|
|
457
|
+
if (!anyPass) return { status: 'skip', exitCode: 0 };
|
|
458
|
+
|
|
459
|
+
return { status: 'pass', exitCode: 0 };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function collectErrors(
|
|
463
|
+
tsc: CheckResult,
|
|
464
|
+
pack: CheckResult,
|
|
465
|
+
imp: ImportCheckResult
|
|
466
|
+
): string[] {
|
|
467
|
+
const errors: string[] = [];
|
|
468
|
+
if (tsc.status === 'fail') errors.push(`tsc: ${tsc.message}`);
|
|
469
|
+
if (pack.status === 'fail') errors.push(`pack: ${pack.message}`);
|
|
470
|
+
if (imp.status === 'fail') errors.push(`imports: ${imp.message}`);
|
|
471
|
+
return errors;
|
|
472
|
+
}
|
|
473
|
+
|
|
472
474
|
export async function runGate10(options: Gate10Options): Promise<Gate10Result> {
|
|
473
475
|
const { changedFiles, projectRoot, timeoutMs } = options;
|
|
474
476
|
|
|
@@ -478,52 +480,8 @@ export async function runGate10(options: Gate10Options): Promise<Gate10Result> {
|
|
|
478
480
|
runImportCheck(changedFiles, projectRoot, timeoutMs),
|
|
479
481
|
]);
|
|
480
482
|
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
if (tscResult.status === 'fail') {
|
|
485
|
-
errors.push(`tsc: ${tscResult.message}`);
|
|
486
|
-
}
|
|
487
|
-
if (packResult.status === 'fail') {
|
|
488
|
-
errors.push(`pack: ${packResult.message}`);
|
|
489
|
-
}
|
|
490
|
-
if (importResult.status === 'fail') {
|
|
491
|
-
errors.push(`imports: ${importResult.message}`);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const anyFail =
|
|
495
|
-
tscResult.status === 'fail' ||
|
|
496
|
-
packResult.status === 'fail' ||
|
|
497
|
-
importResult.status === 'fail';
|
|
498
|
-
|
|
499
|
-
const allSkip =
|
|
500
|
-
tscResult.status === 'skip' &&
|
|
501
|
-
packResult.status === 'skip' &&
|
|
502
|
-
importResult.status === 'skip';
|
|
503
|
-
|
|
504
|
-
const allSkipOrPass =
|
|
505
|
-
(tscResult.status === 'skip' || tscResult.status === 'pass') &&
|
|
506
|
-
(packResult.status === 'skip' || packResult.status === 'pass') &&
|
|
507
|
-
(importResult.status === 'skip' || importResult.status === 'pass');
|
|
508
|
-
|
|
509
|
-
const anyPass =
|
|
510
|
-
tscResult.status === 'pass' ||
|
|
511
|
-
packResult.status === 'pass' ||
|
|
512
|
-
importResult.status === 'pass';
|
|
513
|
-
|
|
514
|
-
let status: Gate10Status;
|
|
515
|
-
let exitCode: number;
|
|
516
|
-
|
|
517
|
-
if (anyFail) {
|
|
518
|
-
status = 'block';
|
|
519
|
-
exitCode = 1;
|
|
520
|
-
} else if (allSkip || (allSkipOrPass && !anyPass)) {
|
|
521
|
-
status = 'skip';
|
|
522
|
-
exitCode = 0;
|
|
523
|
-
} else {
|
|
524
|
-
status = 'pass';
|
|
525
|
-
exitCode = 0;
|
|
526
|
-
}
|
|
483
|
+
const { status, exitCode } = resolveGate10Status(tscResult, packResult, importResult);
|
|
484
|
+
const errors = collectErrors(tscResult, packResult, importResult);
|
|
527
485
|
|
|
528
486
|
return {
|
|
529
487
|
exitCode,
|
|
@@ -533,50 +491,35 @@ export async function runGate10(options: Gate10Options): Promise<Gate10Result> {
|
|
|
533
491
|
pack: packResult,
|
|
534
492
|
imports: importResult,
|
|
535
493
|
},
|
|
536
|
-
warnings,
|
|
494
|
+
warnings: [],
|
|
537
495
|
errors,
|
|
538
496
|
};
|
|
539
497
|
}
|
|
540
498
|
|
|
541
|
-
// ─── CLI
|
|
499
|
+
// ─── CLI Argument Parsing ──────────────────────────────────────────────────────
|
|
500
|
+
|
|
501
|
+
export interface Gate10Args {
|
|
502
|
+
changedFiles: string[];
|
|
503
|
+
projectRoot: string;
|
|
504
|
+
timeoutMs: number;
|
|
505
|
+
help: boolean;
|
|
506
|
+
}
|
|
542
507
|
|
|
543
508
|
/**
|
|
544
|
-
* Parses CLI arguments
|
|
509
|
+
* Parses CLI arguments for Gate 10.
|
|
545
510
|
*
|
|
546
|
-
*
|
|
547
|
-
* npx tsx src/build-integrity/gate-10.ts --changed-files "file1.ts,file2.ts"
|
|
548
|
-
*
|
|
549
|
-
* Options:
|
|
550
|
-
* --changed-files <files> Comma-separated list of changed file paths
|
|
551
|
-
* --project-root <path> Project root (defaults to cwd)
|
|
552
|
-
* --timeout <ms> Timeout per check in ms (defaults to 60000)
|
|
553
|
-
* --help Show usage information
|
|
554
|
-
*
|
|
555
|
-
* @returns exit code: 0 = pass/skip, 1 = block
|
|
511
|
+
* @returns Parsed args object with changedFiles, projectRoot, timeoutMs, and help flag
|
|
556
512
|
*/
|
|
557
|
-
export
|
|
513
|
+
export function parseGate10Args(args: string[]): Gate10Args {
|
|
558
514
|
let changedFilesStr = '';
|
|
559
515
|
let projectRoot = process.cwd();
|
|
560
516
|
let timeoutMs = 60000;
|
|
517
|
+
let help = false;
|
|
561
518
|
|
|
562
519
|
for (let i = 0; i < args.length; i++) {
|
|
563
520
|
const arg = args[i];
|
|
564
521
|
if (arg === '--help' || arg === '-h') {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
Usage:
|
|
568
|
-
npx tsx src/build-integrity/gate-10.ts --changed-files <files> [options]
|
|
569
|
-
|
|
570
|
-
Options:
|
|
571
|
-
--changed-files <files> Comma-separated list of changed file paths
|
|
572
|
-
--project-root <path> Project root (defaults to cwd)
|
|
573
|
-
--timeout <ms> Timeout per check in ms (defaults to 60000)
|
|
574
|
-
--help, -h Show this help message
|
|
575
|
-
|
|
576
|
-
Exit codes:
|
|
577
|
-
0 Pass or skip (all checks passed or were skipped)
|
|
578
|
-
1 Block (one or more checks failed)`);
|
|
579
|
-
return 0;
|
|
522
|
+
help = true;
|
|
580
523
|
} else if (arg === '--changed-files') {
|
|
581
524
|
changedFilesStr = args[++i] || '';
|
|
582
525
|
} else if (arg === '--project-root') {
|
|
@@ -590,12 +533,29 @@ Exit codes:
|
|
|
590
533
|
? changedFilesStr.split(',').map((f) => f.trim()).filter(Boolean)
|
|
591
534
|
: [];
|
|
592
535
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
536
|
+
return { changedFiles, projectRoot, timeoutMs, help };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ─── CLI Result Formatting ─────────────────────────────────────────────────────
|
|
540
|
+
|
|
541
|
+
function printHelp(): void {
|
|
542
|
+
console.log(`Gate 10: Build Integrity Check
|
|
543
|
+
|
|
544
|
+
Usage:
|
|
545
|
+
npx tsx src/build-integrity/gate-10.ts --changed-files <files> [options]
|
|
546
|
+
|
|
547
|
+
Options:
|
|
548
|
+
--changed-files <files> Comma-separated list of changed file paths
|
|
549
|
+
--project-root <path> Project root (defaults to cwd)
|
|
550
|
+
--timeout <ms> Timeout per check in ms (defaults to 60000)
|
|
551
|
+
--help, -h Show this help message
|
|
552
|
+
|
|
553
|
+
Exit codes:
|
|
554
|
+
0 Pass or skip (all checks passed or were skipped)
|
|
555
|
+
1 Block (one or more checks failed)`);
|
|
556
|
+
}
|
|
598
557
|
|
|
558
|
+
function printGate10Result(result: Gate10Result): void {
|
|
599
559
|
console.log('');
|
|
600
560
|
console.log('Gate 10: Build Integrity Check');
|
|
601
561
|
console.log('─'.repeat(50));
|
|
@@ -628,6 +588,34 @@ Exit codes:
|
|
|
628
588
|
}
|
|
629
589
|
|
|
630
590
|
console.log('');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ─── CLI Entry Point ───────────────────────────────────────────────────────────
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Parses CLI arguments and runs Gate 10.
|
|
597
|
+
*
|
|
598
|
+
* Usage:
|
|
599
|
+
* npx tsx src/build-integrity/gate-10.ts --changed-files "file1.ts,file2.ts"
|
|
600
|
+
*
|
|
601
|
+
* @returns exit code: 0 = pass/skip, 1 = block
|
|
602
|
+
*/
|
|
603
|
+
export async function main(args: string[]): Promise<number> {
|
|
604
|
+
const parsed = parseGate10Args(args);
|
|
605
|
+
|
|
606
|
+
if (parsed.help) {
|
|
607
|
+
printHelp();
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const result = await runGate10({
|
|
612
|
+
changedFiles: parsed.changedFiles,
|
|
613
|
+
projectRoot: parsed.projectRoot,
|
|
614
|
+
timeoutMs: parsed.timeoutMs,
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
printGate10Result(result);
|
|
618
|
+
|
|
631
619
|
return result.exitCode;
|
|
632
620
|
}
|
|
633
621
|
|
package/hooks/gate-3.sh
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# GATE 3: Cyclomatic Complexity Check
|
|
3
|
+
# Uses lizard - checks cyclomatic complexity of changed source files
|
|
4
|
+
# ============================================================================
|
|
5
|
+
|
|
6
|
+
2>&1 echo ""
|
|
7
|
+
2>&1 echo "→ Gate 3: Cyclomatic complexity..."
|
|
8
|
+
GATE_3_START=$(gate_start_ms)
|
|
9
|
+
|
|
10
|
+
if [ "$PROJECT_LANG" = "documentation-only" ]; then
|
|
11
|
+
echo "⏭️ SKIPPED - Complexity (documentation project)."
|
|
12
|
+
GATE_3_STATUS="SKIP"
|
|
13
|
+
|
|
14
|
+
elif [ "$PROJECT_LANG" = "powershell" ]; then
|
|
15
|
+
echo "ℹ️ No PowerShell Clean Code / SOLID tool available"
|
|
16
|
+
echo "⏭️ SKIPPED - Complexity (no PowerShell tool)"
|
|
17
|
+
GATE_3_STATUS="SKIP"
|
|
18
|
+
|
|
19
|
+
else
|
|
20
|
+
CCN_THRESHOLD=5
|
|
21
|
+
|
|
22
|
+
# Check lizard availability
|
|
23
|
+
LIZARD_CMD=""
|
|
24
|
+
if command -v lizard > /dev/null 2>&1; then
|
|
25
|
+
LIZARD_CMD=lizard
|
|
26
|
+
elif [ -f ~/.local/bin/lizard ]; then
|
|
27
|
+
LIZARD_CMD=~/.local/bin/lizard
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if [ -n "$LIZARD_CMD" ]; then
|
|
31
|
+
LIZARD_PATH=$(eval echo "$LIZARD_CMD")
|
|
32
|
+
|
|
33
|
+
# Get changed source files for complexity check (includes test files)
|
|
34
|
+
CC_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|swift|cpp|c|hpp|h|m|mm|kt)$' || true)
|
|
35
|
+
|
|
36
|
+
if [ -n "$CC_FILES" ]; then
|
|
37
|
+
echo "Checking complexity for source files..."
|
|
38
|
+
|
|
39
|
+
# Run lizard with CCN threshold
|
|
40
|
+
CC_OUTPUT=$($LIZARD_PATH -C $CCN_THRESHOLD $CC_FILES 2>&1 || true)
|
|
41
|
+
|
|
42
|
+
# Parse warning count from the summary table: "Warning cnt 8"
|
|
43
|
+
# Use anchored grep to avoid matching lizard table headers (e.g. "Rt" column)
|
|
44
|
+
CC_WARNINGS=$(echo "$CC_OUTPUT" | grep "^Warning cnt" | awk '{print $NF}' | tr -d '[:space:]' | sed 's/[^0-9]//g' || true)
|
|
45
|
+
CC_WARNINGS=${CC_WARNINGS:-0}
|
|
46
|
+
|
|
47
|
+
if [ "$CC_WARNINGS" -gt 0 ]; then
|
|
48
|
+
echo "$CC_OUTPUT"
|
|
49
|
+
echo ""
|
|
50
|
+
echo "❌ BLOCKED - $CC_WARNINGS functions with CCN > $CCN_THRESHOLD found."
|
|
51
|
+
echo "Refactor high-complexity functions to keep below $CCN_THRESHOLD complexity."
|
|
52
|
+
exit 1
|
|
53
|
+
else
|
|
54
|
+
echo "✅ PASSED - All functions within complexity threshold ($CCN_THRESHOLD)."
|
|
55
|
+
GATE_3_STATUS="PASS"
|
|
56
|
+
fi
|
|
57
|
+
else
|
|
58
|
+
echo "⏭️ SKIPPED - Complexity (no source files to check)."
|
|
59
|
+
GATE_3_STATUS="SKIP"
|
|
60
|
+
fi
|
|
61
|
+
else
|
|
62
|
+
echo "⚠️ WARN - lizard not installed, complexity check not performed"
|
|
63
|
+
echo " Install with: pip install --user lizard"
|
|
64
|
+
echo " Gate 3: Complexity check (WARN, tool not available)"
|
|
65
|
+
GATE_3_STATUS="WARN"
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
record_gate_audit "gate-3" "complexity" "$GATE_3_STATUS" "${CC_WARNINGS:-0}" "$GATE_3_START"
|
package/hooks/gate-4.sh
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# GATE 4: Principles Checker (Clean Code + SOLID)
|
|
3
|
+
# Reuses existing principles checker logic
|
|
4
|
+
# ============================================================================
|
|
5
|
+
|
|
6
|
+
2>&1 echo ""
|
|
7
|
+
2>&1 echo "→ Gate 4: Principles checker (Clean Code + SOLID)..."
|
|
8
|
+
GATE_4_START=$(gate_start_ms)
|
|
9
|
+
|
|
10
|
+
GATE_4_STATUS=""
|
|
11
|
+
|
|
12
|
+
if [ "$PROJECT_LANG" = "documentation-only" ]; then
|
|
13
|
+
echo "⏭️ SKIPPED - Principles check (documentation project)."
|
|
14
|
+
GATE_4_STATUS="SKIP"
|
|
15
|
+
|
|
16
|
+
else
|
|
17
|
+
# Get source files to check against principles
|
|
18
|
+
PRINCIPLES_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|kt|dart|swift|cpp|c|hpp|h|m|mm)$' || true)
|
|
19
|
+
|
|
20
|
+
if [ -n "$PRINCIPLES_FILES" ]; then
|
|
21
|
+
# Check for principles checker in installed modules first, then project src/
|
|
22
|
+
PRINCIPLES_DIR=""
|
|
23
|
+
if [ -d ".xp-gate/modules/principles" ]; then
|
|
24
|
+
PRINCIPLES_DIR=".xp-gate/modules/principles"
|
|
25
|
+
elif [ -f "src/principles/index.ts" ]; then
|
|
26
|
+
PRINCIPLES_DIR="src/principles"
|
|
27
|
+
elif [ -d "$HOME/.config/xp-gate/modules/principles" ]; then
|
|
28
|
+
PRINCIPLES_DIR="$HOME/.config/xp-gate/modules/principles"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
if [ -n "$PRINCIPLES_DIR" ]; then
|
|
32
|
+
echo "Checking Clean Code + SOLID principles..."
|
|
33
|
+
|
|
34
|
+
if command -v npx > /dev/null 2>&1; then
|
|
35
|
+
# Run principles checker and store results
|
|
36
|
+
if npx tsx $PRINCIPLES_DIR/index.ts --files $PRINCIPLES_FILES --format json > /tmp/principles-output.json 2>/dev/null; then
|
|
37
|
+
# Check severity levels
|
|
38
|
+
ERROR_COUNT=$(grep -c '"severity":"error"' /tmp/principles-output.json 2>/dev/null || true)
|
|
39
|
+
ERROR_COUNT=${ERROR_COUNT:-0}
|
|
40
|
+
WARNING_COUNT=$(grep -c '"severity":"warning"' /tmp/principles-output.json 2>/dev/null || true)
|
|
41
|
+
WARNING_COUNT=${WARNING_COUNT:-0}
|
|
42
|
+
|
|
43
|
+
if [ "$ERROR_COUNT" -gt 0 ]; then
|
|
44
|
+
echo ""
|
|
45
|
+
echo "❌ BLOCKED - $ERROR_COUNT principle ERROR(S) found"
|
|
46
|
+
echo "Critical violations must be fixed before commit:"
|
|
47
|
+
echo " - error-handling violations"
|
|
48
|
+
echo " - SOLID principle violations"
|
|
49
|
+
echo " - architectural violations"
|
|
50
|
+
npx tsx $PRINCIPLES_DIR/index.ts --files $PRINCIPLES_FILES --format console
|
|
51
|
+
GATE_4_STATUS="FAIL"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo "✅ PASSED - Principles checker (no errors found)."
|
|
56
|
+
GATE_4_STATUS="PASS"
|
|
57
|
+
if [ "$WARNING_COUNT" -gt 0 ]; then
|
|
58
|
+
echo "ℹ️ $WARNING_COUNT warnings found (will be handled by Boy Scout Rule)."
|
|
59
|
+
fi
|
|
60
|
+
else
|
|
61
|
+
echo "⚠️ Warning: Principles checker execution failed"
|
|
62
|
+
echo "⏭️ SKIPPED - Principles check (execution issue)"
|
|
63
|
+
GATE_4_STATUS="SKIP"
|
|
64
|
+
fi
|
|
65
|
+
else
|
|
66
|
+
echo "ℹ️ npx not available - skipping principles check"
|
|
67
|
+
echo "⏭️ SKIPPED - Principles check (no Node.js/npx)"
|
|
68
|
+
GATE_4_STATUS="SKIP"
|
|
69
|
+
fi
|
|
70
|
+
else
|
|
71
|
+
echo "ℹ️ Principles checker not found in project - skipping"
|
|
72
|
+
echo "⏭️ SKIPPED - Principles check (checker not in project)"
|
|
73
|
+
GATE_4_STATUS="SKIP"
|
|
74
|
+
fi
|
|
75
|
+
else
|
|
76
|
+
echo "⏭️ SKIPPED - Principles check (no matching source files changed)"
|
|
77
|
+
GATE_4_STATUS="SKIP"
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
record_gate_audit "gate-4" "principles" "$GATE_4_STATUS" "${WARNING_COUNT:-0}" "$GATE_4_START"
|
package/hooks/gate-7.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# GATE 7: IaC Security Scanning (Terraform, Kubernetes, Docker)
|
|
3
|
+
# Detects security issues in Infrastructure as Code files
|
|
4
|
+
# Tools: checkov (recommended), hadolint, kube-score, tflint
|
|
5
|
+
# ============================================================================
|
|
6
|
+
|
|
7
|
+
2>&1 echo ""
|
|
8
|
+
2>&1 echo "→ Gate 7: IaC Security Scanning (Terraform, Kubernetes, Docker)..."
|
|
9
|
+
GATE_7_START=$(gate_start_ms)
|
|
10
|
+
|
|
11
|
+
# Check if any IaC files are changed
|
|
12
|
+
IAC_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E "\.(tf|yaml|yml)$|Dockerfile" || true)
|
|
13
|
+
if [ -z "$IAC_FILES" ]; then
|
|
14
|
+
echo "✅ PASSED - No IaC files detected in changes."
|
|
15
|
+
GATE_7_STATUS="PASS"
|
|
16
|
+
else
|
|
17
|
+
# Run IaC adapter
|
|
18
|
+
if [ -f "githooks/adapters/iac.sh" ]; then
|
|
19
|
+
# shellcheck source=githooks/adapters/iac.sh
|
|
20
|
+
source "githooks/adapters/iac.sh"
|
|
21
|
+
|
|
22
|
+
# Run static analysis for IaC files
|
|
23
|
+
IAC_OUTPUT=$(run_static_analysis "$IAC_FILES" 2>&1)
|
|
24
|
+
IAC_EXIT=$?
|
|
25
|
+
|
|
26
|
+
echo "$IAC_OUTPUT"
|
|
27
|
+
|
|
28
|
+
if [ $IAC_EXIT -eq 0 ]; then
|
|
29
|
+
echo "✅ PASSED - IaC security scan."
|
|
30
|
+
GATE_7_STATUS="PASS"
|
|
31
|
+
else
|
|
32
|
+
echo ""
|
|
33
|
+
echo "❌ BLOCKED - IaC security issues detected"
|
|
34
|
+
echo "Fix the security issues above before committing."
|
|
35
|
+
echo "Tip: Install checkov for comprehensive IaC scanning: pip install checkov"
|
|
36
|
+
GATE_7_STATUS="FAIL"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
else
|
|
40
|
+
echo "ℹ️ SKIP - IaC adapter not found"
|
|
41
|
+
echo "⏭️ SKIPPED - IaC security (no IaC adapter found)"
|
|
42
|
+
GATE_7_STATUS="SKIP"
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
record_gate_audit "gate-7" "iac-security" "$GATE_7_STATUS" "0" "$GATE_7_START"
|