@shrkcrft/quality-gates 0.1.0-alpha.10
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/dist/gates/api-diff-gate.d.ts +21 -0
- package/dist/gates/api-diff-gate.d.ts.map +1 -0
- package/dist/gates/api-diff-gate.js +91 -0
- package/dist/gates/arch-gate.d.ts +8 -0
- package/dist/gates/arch-gate.d.ts.map +1 -0
- package/dist/gates/arch-gate.js +55 -0
- package/dist/gates/graph-cycles-gate.d.ts +17 -0
- package/dist/gates/graph-cycles-gate.d.ts.map +1 -0
- package/dist/gates/graph-cycles-gate.js +49 -0
- package/dist/gates/graph-fresh-gate.d.ts +8 -0
- package/dist/gates/graph-fresh-gate.d.ts.map +1 -0
- package/dist/gates/graph-fresh-gate.js +39 -0
- package/dist/gates/graph-unresolved-gate.d.ts +16 -0
- package/dist/gates/graph-unresolved-gate.d.ts.map +1 -0
- package/dist/gates/graph-unresolved-gate.js +44 -0
- package/dist/gates/impact-baseline-gate.d.ts +16 -0
- package/dist/gates/impact-baseline-gate.d.ts.map +1 -0
- package/dist/gates/impact-baseline-gate.js +61 -0
- package/dist/gates/impact-gate.d.ts +15 -0
- package/dist/gates/impact-gate.d.ts.map +1 -0
- package/dist/gates/impact-gate.js +54 -0
- package/dist/gates/intent-classifier-gate.d.ts +15 -0
- package/dist/gates/intent-classifier-gate.d.ts.map +1 -0
- package/dist/gates/intent-classifier-gate.js +64 -0
- package/dist/gates/structural-patterns-gate.d.ts +16 -0
- package/dist/gates/structural-patterns-gate.d.ts.map +1 -0
- package/dist/gates/structural-patterns-gate.js +50 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/runner/render-markdown.d.ts +15 -0
- package/dist/runner/render-markdown.d.ts.map +1 -0
- package/dist/runner/render-markdown.js +82 -0
- package/dist/runner/report-store.d.ts +19 -0
- package/dist/runner/report-store.d.ts.map +1 -0
- package/dist/runner/report-store.js +45 -0
- package/dist/runner/run-gates.d.ts +53 -0
- package/dist/runner/run-gates.d.ts.map +1 -0
- package/dist/runner/run-gates.js +79 -0
- package/dist/schema/quality-gate.d.ts +28 -0
- package/dist/schema/quality-gate.d.ts.map +1 -0
- package/dist/schema/quality-gate.js +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
export interface IApiDiffGateOptions {
|
|
3
|
+
/** Path to a saved `IApiSurface` baseline. Required. */
|
|
4
|
+
baselinePath: string;
|
|
5
|
+
/** When true, breaking changes fail the gate. Default true. */
|
|
6
|
+
failOnBreaking?: boolean;
|
|
7
|
+
/** Restrict diff to these packages. */
|
|
8
|
+
packageFilter?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* API-surface diff gate. Compares the current code-graph's public
|
|
12
|
+
* surface to a saved baseline (typically committed at the last
|
|
13
|
+
* release) and fails when any breaking changes appear.
|
|
14
|
+
*
|
|
15
|
+
* Skipped when:
|
|
16
|
+
* - the baseline file doesn't exist (use `shrk api-diff capture` to
|
|
17
|
+
* create one)
|
|
18
|
+
* - the code-graph isn't indexed
|
|
19
|
+
*/
|
|
20
|
+
export declare function apiDiffGate(projectRoot: string, options: IApiDiffGateOptions): IGateResult;
|
|
21
|
+
//# sourceMappingURL=api-diff-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-diff-gate.d.ts","sourceRoot":"","sources":["../../src/gates/api-diff-gate.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uCAAuC;IACvC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,WAAW,CA2E1F"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { diffApiSurfaces, extractApiSurface, } from '@shrkcrft/api-surface-diff';
|
|
4
|
+
import { GraphStore } from '@shrkcrft/graph';
|
|
5
|
+
/**
|
|
6
|
+
* API-surface diff gate. Compares the current code-graph's public
|
|
7
|
+
* surface to a saved baseline (typically committed at the last
|
|
8
|
+
* release) and fails when any breaking changes appear.
|
|
9
|
+
*
|
|
10
|
+
* Skipped when:
|
|
11
|
+
* - the baseline file doesn't exist (use `shrk api-diff capture` to
|
|
12
|
+
* create one)
|
|
13
|
+
* - the code-graph isn't indexed
|
|
14
|
+
*/
|
|
15
|
+
export function apiDiffGate(projectRoot, options) {
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
const failOnBreaking = options.failOnBreaking ?? true;
|
|
18
|
+
const abs = nodePath.isAbsolute(options.baselinePath)
|
|
19
|
+
? options.baselinePath
|
|
20
|
+
: nodePath.resolve(projectRoot, options.baselinePath);
|
|
21
|
+
let baseline;
|
|
22
|
+
try {
|
|
23
|
+
baseline = JSON.parse(readFileSync(abs, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return {
|
|
27
|
+
id: 'api-diff',
|
|
28
|
+
label: 'API surface',
|
|
29
|
+
status: 'skipped',
|
|
30
|
+
message: `Baseline read failed: ${e.message}`,
|
|
31
|
+
nextCommands: ['shrk api-diff capture --output ' + options.baselinePath],
|
|
32
|
+
durationMs: Date.now() - start,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const store = new GraphStore(projectRoot);
|
|
36
|
+
if (!store.exists()) {
|
|
37
|
+
return {
|
|
38
|
+
id: 'api-diff',
|
|
39
|
+
label: 'API surface',
|
|
40
|
+
status: 'skipped',
|
|
41
|
+
message: 'Skipped — graph index missing.',
|
|
42
|
+
nextCommands: ['shrk graph index'],
|
|
43
|
+
durationMs: Date.now() - start,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const snap = store.loadSnapshot();
|
|
47
|
+
const current = extractApiSurface(snap, {
|
|
48
|
+
...(options.packageFilter && options.packageFilter.length > 0
|
|
49
|
+
? { packageFilter: options.packageFilter }
|
|
50
|
+
: {}),
|
|
51
|
+
});
|
|
52
|
+
const diff = diffApiSurfaces(baseline, current);
|
|
53
|
+
if (failOnBreaking && diff.breakingCount > 0) {
|
|
54
|
+
return {
|
|
55
|
+
id: 'api-diff',
|
|
56
|
+
label: 'API surface',
|
|
57
|
+
status: 'fail',
|
|
58
|
+
message: `${diff.breakingCount} breaking API change(s).`,
|
|
59
|
+
details: {
|
|
60
|
+
added: diff.added,
|
|
61
|
+
removed: diff.removed,
|
|
62
|
+
changed: diff.changed,
|
|
63
|
+
breaking: diff.breakingCount,
|
|
64
|
+
},
|
|
65
|
+
nextCommands: [`shrk api-diff ${options.baselinePath}`],
|
|
66
|
+
durationMs: Date.now() - start,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (diff.added > 0 || diff.removed > 0 || diff.changed > 0) {
|
|
70
|
+
return {
|
|
71
|
+
id: 'api-diff',
|
|
72
|
+
label: 'API surface',
|
|
73
|
+
status: diff.breakingCount > 0 ? 'warn' : 'pass',
|
|
74
|
+
message: `${diff.added} added, ${diff.removed} removed, ${diff.changed} changed (no breaking).`,
|
|
75
|
+
details: {
|
|
76
|
+
added: diff.added,
|
|
77
|
+
removed: diff.removed,
|
|
78
|
+
changed: diff.changed,
|
|
79
|
+
breaking: diff.breakingCount,
|
|
80
|
+
},
|
|
81
|
+
durationMs: Date.now() - start,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
id: 'api-diff',
|
|
86
|
+
label: 'API surface',
|
|
87
|
+
status: 'pass',
|
|
88
|
+
message: 'No API surface changes.',
|
|
89
|
+
durationMs: Date.now() - start,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Architecture-guard gate. Pass when zero error-severity violations.
|
|
4
|
+
* Warnings (fat barrels, 2-node cycles, adapter leaks) are reported
|
|
5
|
+
* as `warn` — the gate doesn't fail, but the human sees them.
|
|
6
|
+
*/
|
|
7
|
+
export declare function archGate(projectRoot: string): IGateResult;
|
|
8
|
+
//# sourceMappingURL=arch-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arch-gate.d.ts","sourceRoot":"","sources":["../../src/gates/arch-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAgDzD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { runArchCheck } from '@shrkcrft/architecture-guard';
|
|
2
|
+
/**
|
|
3
|
+
* Architecture-guard gate. Pass when zero error-severity violations.
|
|
4
|
+
* Warnings (fat barrels, 2-node cycles, adapter leaks) are reported
|
|
5
|
+
* as `warn` — the gate doesn't fail, but the human sees them.
|
|
6
|
+
*/
|
|
7
|
+
export function archGate(projectRoot) {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
const report = runArchCheck({ projectRoot });
|
|
10
|
+
if (report.diagnostics.some((d) => d.includes('code-graph store missing'))) {
|
|
11
|
+
return {
|
|
12
|
+
id: 'arch',
|
|
13
|
+
label: 'Architecture',
|
|
14
|
+
status: 'skipped',
|
|
15
|
+
message: 'Skipped — graph index missing.',
|
|
16
|
+
nextCommands: ['shrk graph index'],
|
|
17
|
+
durationMs: Date.now() - start,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const errors = report.countsBySeverity.error;
|
|
21
|
+
const warnings = report.countsBySeverity.warning;
|
|
22
|
+
if (errors > 0) {
|
|
23
|
+
return {
|
|
24
|
+
id: 'arch',
|
|
25
|
+
label: 'Architecture',
|
|
26
|
+
status: 'fail',
|
|
27
|
+
message: `${errors} architecture error(s).`,
|
|
28
|
+
details: {
|
|
29
|
+
errors,
|
|
30
|
+
warnings,
|
|
31
|
+
kinds: report.countsByKind,
|
|
32
|
+
},
|
|
33
|
+
nextCommands: ['shrk arch check'],
|
|
34
|
+
durationMs: Date.now() - start,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (warnings > 0) {
|
|
38
|
+
return {
|
|
39
|
+
id: 'arch',
|
|
40
|
+
label: 'Architecture',
|
|
41
|
+
status: 'warn',
|
|
42
|
+
message: `${warnings} architecture warning(s).`,
|
|
43
|
+
details: { warnings, kinds: report.countsByKind },
|
|
44
|
+
nextCommands: ['shrk arch check'],
|
|
45
|
+
durationMs: Date.now() - start,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
id: 'arch',
|
|
50
|
+
label: 'Architecture',
|
|
51
|
+
status: 'pass',
|
|
52
|
+
message: 'No architecture violations.',
|
|
53
|
+
durationMs: Date.now() - start,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
export interface IGraphCyclesGateOptions {
|
|
3
|
+
/** A cycle counts as "large" at or above this size. Default 3. */
|
|
4
|
+
largeCycleSize?: number;
|
|
5
|
+
/** Total cycles at or above this count triggers warn. Default 5. */
|
|
6
|
+
manyCycleThreshold?: number;
|
|
7
|
+
/** When true, large/many cycles fail the gate instead of warning. */
|
|
8
|
+
failOnLarge?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Surface import cycles in the file-import graph. Uses the persisted
|
|
12
|
+
* `cycleCount` / `largestCycleSize` from `IGraphManifest` so the gate
|
|
13
|
+
* is O(1) — no recomputation. Mirrors the doctor's heuristic: large
|
|
14
|
+
* cycles or many cycles → advisory by default, fail when caller opts in.
|
|
15
|
+
*/
|
|
16
|
+
export declare function graphCyclesGate(projectRoot: string, options?: IGraphCyclesGateOptions): IGateResult;
|
|
17
|
+
//# sourceMappingURL=graph-cycles-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-cycles-gate.d.ts","sourceRoot":"","sources":["../../src/gates/graph-cycles-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,uBAAuB;IACtC,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,uBAA4B,GACpC,WAAW,CA0Cb"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { GraphStore } from '@shrkcrft/graph';
|
|
2
|
+
/**
|
|
3
|
+
* Surface import cycles in the file-import graph. Uses the persisted
|
|
4
|
+
* `cycleCount` / `largestCycleSize` from `IGraphManifest` so the gate
|
|
5
|
+
* is O(1) — no recomputation. Mirrors the doctor's heuristic: large
|
|
6
|
+
* cycles or many cycles → advisory by default, fail when caller opts in.
|
|
7
|
+
*/
|
|
8
|
+
export function graphCyclesGate(projectRoot, options = {}) {
|
|
9
|
+
const start = Date.now();
|
|
10
|
+
const large = options.largeCycleSize ?? 3;
|
|
11
|
+
const many = options.manyCycleThreshold ?? 5;
|
|
12
|
+
const failOnLarge = options.failOnLarge ?? false;
|
|
13
|
+
const store = new GraphStore(projectRoot);
|
|
14
|
+
if (!store.exists()) {
|
|
15
|
+
return {
|
|
16
|
+
id: 'graph-cycles',
|
|
17
|
+
label: 'Graph cycles',
|
|
18
|
+
status: 'skipped',
|
|
19
|
+
message: 'Skipped — graph index missing.',
|
|
20
|
+
nextCommands: ['shrk graph index'],
|
|
21
|
+
durationMs: Date.now() - start,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const snap = store.loadSnapshot();
|
|
25
|
+
const cycleCount = snap.manifest.cycleCount ?? 0;
|
|
26
|
+
const largestCycleSize = snap.manifest.largestCycleSize ?? 0;
|
|
27
|
+
const filesInCycles = snap.manifest.filesInCycles ?? 0;
|
|
28
|
+
const triggered = largestCycleSize >= large || cycleCount >= many;
|
|
29
|
+
if (!triggered) {
|
|
30
|
+
return {
|
|
31
|
+
id: 'graph-cycles',
|
|
32
|
+
label: 'Graph cycles',
|
|
33
|
+
status: 'pass',
|
|
34
|
+
message: `${cycleCount} cycle(s); largest ${largestCycleSize}.`,
|
|
35
|
+
details: { cycleCount, largestCycleSize, filesInCycles },
|
|
36
|
+
durationMs: Date.now() - start,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
id: 'graph-cycles',
|
|
41
|
+
label: 'Graph cycles',
|
|
42
|
+
status: failOnLarge ? 'fail' : 'warn',
|
|
43
|
+
message: `${cycleCount} import cycle(s); largest spans ${largestCycleSize} files; ` +
|
|
44
|
+
`${filesInCycles} file(s) participate.`,
|
|
45
|
+
details: { cycleCount, largestCycleSize, filesInCycles, threshold: { large, many } },
|
|
46
|
+
nextCommands: ['shrk graph cycles', 'shrk arch check'],
|
|
47
|
+
durationMs: Date.now() - start,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check whether the code-graph store exists AND the manifest digest
|
|
4
|
+
* matches the on-disk JSONL fingerprints. Catches: missing index;
|
|
5
|
+
* tampered / partial store; schema mismatch.
|
|
6
|
+
*/
|
|
7
|
+
export declare function graphFreshGate(projectRoot: string): IGateResult;
|
|
8
|
+
//# sourceMappingURL=graph-fresh-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-fresh-gate.d.ts","sourceRoot":"","sources":["../../src/gates/graph-fresh-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAgC/D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { GraphStore } from '@shrkcrft/graph';
|
|
2
|
+
/**
|
|
3
|
+
* Check whether the code-graph store exists AND the manifest digest
|
|
4
|
+
* matches the on-disk JSONL fingerprints. Catches: missing index;
|
|
5
|
+
* tampered / partial store; schema mismatch.
|
|
6
|
+
*/
|
|
7
|
+
export function graphFreshGate(projectRoot) {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
const store = new GraphStore(projectRoot);
|
|
10
|
+
if (!store.exists()) {
|
|
11
|
+
return {
|
|
12
|
+
id: 'graph-fresh',
|
|
13
|
+
label: 'Code graph indexed',
|
|
14
|
+
status: 'fail',
|
|
15
|
+
message: 'Code-graph store missing.',
|
|
16
|
+
nextCommands: ['shrk graph index'],
|
|
17
|
+
durationMs: Date.now() - start,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const verify = store.verifyDigest();
|
|
21
|
+
if (!verify.ok) {
|
|
22
|
+
return {
|
|
23
|
+
id: 'graph-fresh',
|
|
24
|
+
label: 'Code graph indexed',
|
|
25
|
+
status: 'fail',
|
|
26
|
+
message: 'Code-graph digest mismatch — store may be tampered or partial.',
|
|
27
|
+
details: { expected: verify.expected, actual: verify.actual },
|
|
28
|
+
nextCommands: ['shrk graph index'],
|
|
29
|
+
durationMs: Date.now() - start,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
id: 'graph-fresh',
|
|
34
|
+
label: 'Code graph indexed',
|
|
35
|
+
status: 'pass',
|
|
36
|
+
message: 'Code-graph index is fresh.',
|
|
37
|
+
durationMs: Date.now() - start,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
export interface IGraphUnresolvedGateOptions {
|
|
3
|
+
/**
|
|
4
|
+
* When true, any unresolved import fails the gate. Default `false`
|
|
5
|
+
* (warn) — dynamic / runtime-only imports are sometimes intentional.
|
|
6
|
+
*/
|
|
7
|
+
failOnAny?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Read the persisted unresolved-import counts from the graph manifest
|
|
11
|
+
* and surface them. Warn by default (since some unresolved specifiers
|
|
12
|
+
* are intentional — dynamic imports, optional peer deps); fail when
|
|
13
|
+
* `failOnAny` is true.
|
|
14
|
+
*/
|
|
15
|
+
export declare function graphUnresolvedGate(projectRoot: string, options?: IGraphUnresolvedGateOptions): IGateResult;
|
|
16
|
+
//# sourceMappingURL=graph-unresolved-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-unresolved-gate.d.ts","sourceRoot":"","sources":["../../src/gates/graph-unresolved-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,2BAAgC,GACxC,WAAW,CAoCb"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { GraphStore } from '@shrkcrft/graph';
|
|
2
|
+
/**
|
|
3
|
+
* Read the persisted unresolved-import counts from the graph manifest
|
|
4
|
+
* and surface them. Warn by default (since some unresolved specifiers
|
|
5
|
+
* are intentional — dynamic imports, optional peer deps); fail when
|
|
6
|
+
* `failOnAny` is true.
|
|
7
|
+
*/
|
|
8
|
+
export function graphUnresolvedGate(projectRoot, options = {}) {
|
|
9
|
+
const start = Date.now();
|
|
10
|
+
const failOnAny = options.failOnAny ?? false;
|
|
11
|
+
const store = new GraphStore(projectRoot);
|
|
12
|
+
if (!store.exists()) {
|
|
13
|
+
return {
|
|
14
|
+
id: 'graph-unresolved',
|
|
15
|
+
label: 'Graph unresolved imports',
|
|
16
|
+
status: 'skipped',
|
|
17
|
+
message: 'Skipped — graph index missing.',
|
|
18
|
+
nextCommands: ['shrk graph index'],
|
|
19
|
+
durationMs: Date.now() - start,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const snap = store.loadSnapshot();
|
|
23
|
+
const count = snap.manifest.unresolvedImportCount ?? 0;
|
|
24
|
+
const files = snap.manifest.filesWithUnresolvedImports ?? 0;
|
|
25
|
+
const samples = snap.manifest.unresolvedImportSamples ?? [];
|
|
26
|
+
if (count === 0) {
|
|
27
|
+
return {
|
|
28
|
+
id: 'graph-unresolved',
|
|
29
|
+
label: 'Graph unresolved imports',
|
|
30
|
+
status: 'pass',
|
|
31
|
+
message: 'No unresolved imports.',
|
|
32
|
+
durationMs: Date.now() - start,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: 'graph-unresolved',
|
|
37
|
+
label: 'Graph unresolved imports',
|
|
38
|
+
status: failOnAny ? 'fail' : 'warn',
|
|
39
|
+
message: `${count} unresolved import(s) across ${files} file(s).`,
|
|
40
|
+
details: { count, files, samples },
|
|
41
|
+
nextCommands: ['shrk graph unresolved'],
|
|
42
|
+
durationMs: Date.now() - start,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
export interface IImpactBaselineGateOptions {
|
|
3
|
+
/**
|
|
4
|
+
* When true, a worsened baseline fails the gate. Default `false`
|
|
5
|
+
* (warn) — growing impact can be intentional in early development.
|
|
6
|
+
*/
|
|
7
|
+
failOnWorsened?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Compare the last impact run against the frozen baseline. Skipped
|
|
11
|
+
* when either side is missing. Warn / fail when the run is worse
|
|
12
|
+
* than baseline along any axis (more dependents, more packages, or a
|
|
13
|
+
* higher risk classification).
|
|
14
|
+
*/
|
|
15
|
+
export declare function impactBaselineGate(projectRoot: string, options?: IImpactBaselineGateOptions): IGateResult;
|
|
16
|
+
//# sourceMappingURL=impact-baseline-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impact-baseline-gate.d.ts","sourceRoot":"","sources":["../../src/gates/impact-baseline-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,0BAA+B,GACvC,WAAW,CAuDb"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ImpactReportStore, diffImpactReports } from '@shrkcrft/impact-engine';
|
|
2
|
+
/**
|
|
3
|
+
* Compare the last impact run against the frozen baseline. Skipped
|
|
4
|
+
* when either side is missing. Warn / fail when the run is worse
|
|
5
|
+
* than baseline along any axis (more dependents, more packages, or a
|
|
6
|
+
* higher risk classification).
|
|
7
|
+
*/
|
|
8
|
+
export function impactBaselineGate(projectRoot, options = {}) {
|
|
9
|
+
const start = Date.now();
|
|
10
|
+
const failOnWorsened = options.failOnWorsened ?? false;
|
|
11
|
+
const store = new ImpactReportStore(projectRoot);
|
|
12
|
+
const baseline = store.readBaseline();
|
|
13
|
+
const last = store.read();
|
|
14
|
+
if (!baseline) {
|
|
15
|
+
return {
|
|
16
|
+
id: 'impact-baseline',
|
|
17
|
+
label: 'Impact baseline',
|
|
18
|
+
status: 'skipped',
|
|
19
|
+
message: 'No baseline frozen. Run `shrk impact baseline write` to opt in.',
|
|
20
|
+
durationMs: Date.now() - start,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (!last) {
|
|
24
|
+
return {
|
|
25
|
+
id: 'impact-baseline',
|
|
26
|
+
label: 'Impact baseline',
|
|
27
|
+
status: 'skipped',
|
|
28
|
+
message: 'Baseline present but no recent impact run to compare against.',
|
|
29
|
+
nextCommands: ['shrk impact --via-graph'],
|
|
30
|
+
durationMs: Date.now() - start,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const delta = diffImpactReports(baseline, last);
|
|
34
|
+
if (!delta.worsened) {
|
|
35
|
+
return {
|
|
36
|
+
id: 'impact-baseline',
|
|
37
|
+
label: 'Impact baseline',
|
|
38
|
+
status: 'pass',
|
|
39
|
+
message: `Within baseline — dependents ${delta.dependentDelta >= 0 ? '+' : ''}${delta.dependentDelta}, ` +
|
|
40
|
+
`packages ${delta.packageDelta >= 0 ? '+' : ''}${delta.packageDelta}.`,
|
|
41
|
+
details: { delta },
|
|
42
|
+
durationMs: Date.now() - start,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
id: 'impact-baseline',
|
|
47
|
+
label: 'Impact baseline',
|
|
48
|
+
status: failOnWorsened ? 'fail' : 'warn',
|
|
49
|
+
message: `Worsened — dependents ${delta.dependentDelta >= 0 ? '+' : ''}${delta.dependentDelta}, ` +
|
|
50
|
+
`packages ${delta.packageDelta >= 0 ? '+' : ''}${delta.packageDelta}` +
|
|
51
|
+
(delta.riskDrift ? `, risk ${delta.riskDrift}` : '') +
|
|
52
|
+
'.',
|
|
53
|
+
details: { delta },
|
|
54
|
+
nextCommands: [
|
|
55
|
+
'shrk impact baseline show',
|
|
56
|
+
'shrk impact --via-graph <target>',
|
|
57
|
+
'shrk impact baseline write',
|
|
58
|
+
],
|
|
59
|
+
durationMs: Date.now() - start,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
export interface IImpactGateOptions {
|
|
3
|
+
/** Git ref to diff against (e.g. 'main', 'origin/main', 'HEAD~5'). */
|
|
4
|
+
sinceRef?: string;
|
|
5
|
+
/** Risk levels that fail the gate. Default ['critical']. */
|
|
6
|
+
failOn?: readonly ('high' | 'critical')[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Impact-engine gate. Reads `git diff --name-only` against `sinceRef`
|
|
10
|
+
* (default 'main') and runs `analyzeGraphImpact`. Fails when the
|
|
11
|
+
* computed risk is in `failOn`. Skipped when no files changed or the
|
|
12
|
+
* graph isn't indexed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function impactGate(projectRoot: string, options?: IImpactGateOptions): IGateResult;
|
|
15
|
+
//# sourceMappingURL=impact-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impact-gate.d.ts","sourceRoot":"","sources":["../../src/gates/impact-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC;CAC3C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,WAAW,CA+C7F"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { analyzeGraphImpact } from '@shrkcrft/impact-engine';
|
|
2
|
+
/**
|
|
3
|
+
* Impact-engine gate. Reads `git diff --name-only` against `sinceRef`
|
|
4
|
+
* (default 'main') and runs `analyzeGraphImpact`. Fails when the
|
|
5
|
+
* computed risk is in `failOn`. Skipped when no files changed or the
|
|
6
|
+
* graph isn't indexed.
|
|
7
|
+
*/
|
|
8
|
+
export function impactGate(projectRoot, options = {}) {
|
|
9
|
+
const start = Date.now();
|
|
10
|
+
const sinceRef = options.sinceRef ?? 'main';
|
|
11
|
+
const failOn = new Set(options.failOn ?? ['critical']);
|
|
12
|
+
const analysis = analyzeGraphImpact({ kind: 'gitref', ref: sinceRef }, { projectRoot });
|
|
13
|
+
if (analysis.diagnostics.some((d) => d.includes('code-graph store missing'))) {
|
|
14
|
+
return {
|
|
15
|
+
id: 'impact',
|
|
16
|
+
label: 'Impact (since ' + sinceRef + ')',
|
|
17
|
+
status: 'skipped',
|
|
18
|
+
message: 'Skipped — graph index missing.',
|
|
19
|
+
nextCommands: ['shrk graph index'],
|
|
20
|
+
durationMs: Date.now() - start,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (analysis.normalizedTargets.length === 0) {
|
|
24
|
+
return {
|
|
25
|
+
id: 'impact',
|
|
26
|
+
label: 'Impact (since ' + sinceRef + ')',
|
|
27
|
+
status: 'skipped',
|
|
28
|
+
message: `No files changed since ${sinceRef}.`,
|
|
29
|
+
durationMs: Date.now() - start,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const status = failOn.has(analysis.risk) ? 'fail' : analysis.risk === 'high' ? 'warn' : 'pass';
|
|
33
|
+
const message = status === 'fail'
|
|
34
|
+
? `Risk: ${analysis.risk}. ${analysis.directDependents.length} direct dependents.`
|
|
35
|
+
: status === 'warn'
|
|
36
|
+
? `Risk: ${analysis.risk}. Review the validation scope.`
|
|
37
|
+
: `Risk: ${analysis.risk}. Looks safe.`;
|
|
38
|
+
return {
|
|
39
|
+
id: 'impact',
|
|
40
|
+
label: 'Impact (since ' + sinceRef + ')',
|
|
41
|
+
status,
|
|
42
|
+
message,
|
|
43
|
+
details: {
|
|
44
|
+
risk: analysis.risk,
|
|
45
|
+
reasons: analysis.riskReasons,
|
|
46
|
+
direct: analysis.directDependents.length,
|
|
47
|
+
transitive: analysis.transitiveDependents.length,
|
|
48
|
+
publicApiTouched: analysis.publicApiTouched,
|
|
49
|
+
validationScope: analysis.validationScope,
|
|
50
|
+
},
|
|
51
|
+
nextCommands: analysis.validationScope.length > 0 ? [...analysis.validationScope] : undefined,
|
|
52
|
+
durationMs: Date.now() - start,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
export interface IIntentClassifierGateOptions {
|
|
3
|
+
/** Accuracy below this threshold fails the gate. Default 0.6. */
|
|
4
|
+
failBelow?: number;
|
|
5
|
+
/** Accuracy below this threshold warns. Default 0.95. */
|
|
6
|
+
warnBelow?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Run the intent-classifier benchmark in-process and gate on the
|
|
10
|
+
* accuracy. Skipped when no fixture is present at
|
|
11
|
+
* `sharkcraft/intent-benchmark.json` (the feature is opt-in). The gate
|
|
12
|
+
* does NOT persist a fresh run report — that's the CLI's job.
|
|
13
|
+
*/
|
|
14
|
+
export declare function intentClassifierGate(projectRoot: string, options?: IIntentClassifierGateOptions): IGateResult;
|
|
15
|
+
//# sourceMappingURL=intent-classifier-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-classifier-gate.d.ts","sourceRoot":"","sources":["../../src/gates/intent-classifier-gate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,4BAA4B;IAC3C,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,4BAAiC,GACzC,WAAW,CAyDb"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { loadIntentBenchmark, runIntentBenchmark, } from '@shrkcrft/context-planner';
|
|
2
|
+
/**
|
|
3
|
+
* Run the intent-classifier benchmark in-process and gate on the
|
|
4
|
+
* accuracy. Skipped when no fixture is present at
|
|
5
|
+
* `sharkcraft/intent-benchmark.json` (the feature is opt-in). The gate
|
|
6
|
+
* does NOT persist a fresh run report — that's the CLI's job.
|
|
7
|
+
*/
|
|
8
|
+
export function intentClassifierGate(projectRoot, options = {}) {
|
|
9
|
+
const start = Date.now();
|
|
10
|
+
const failBelow = options.failBelow ?? 0.6;
|
|
11
|
+
const warnBelow = options.warnBelow ?? 0.95;
|
|
12
|
+
const benchmark = loadIntentBenchmark(projectRoot);
|
|
13
|
+
if (!benchmark) {
|
|
14
|
+
return {
|
|
15
|
+
id: 'intent-classifier',
|
|
16
|
+
label: 'Intent classifier accuracy',
|
|
17
|
+
status: 'skipped',
|
|
18
|
+
message: 'No fixture at sharkcraft/intent-benchmark.json.',
|
|
19
|
+
durationMs: Date.now() - start,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (benchmark.cases.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
id: 'intent-classifier',
|
|
25
|
+
label: 'Intent classifier accuracy',
|
|
26
|
+
status: 'skipped',
|
|
27
|
+
message: 'Benchmark has zero cases.',
|
|
28
|
+
durationMs: Date.now() - start,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const run = runIntentBenchmark(benchmark);
|
|
32
|
+
const pct = Math.round(run.accuracy * 1000) / 10;
|
|
33
|
+
if (run.accuracy >= warnBelow) {
|
|
34
|
+
return {
|
|
35
|
+
id: 'intent-classifier',
|
|
36
|
+
label: 'Intent classifier accuracy',
|
|
37
|
+
status: 'pass',
|
|
38
|
+
message: `Accuracy ${pct}% (${run.passed}/${run.total}).`,
|
|
39
|
+
details: { accuracy: run.accuracy, passed: run.passed, failed: run.failed, total: run.total },
|
|
40
|
+
durationMs: Date.now() - start,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const status = run.accuracy < failBelow ? 'fail' : 'warn';
|
|
44
|
+
const sample = run.cases
|
|
45
|
+
.filter((c) => !c.passed)
|
|
46
|
+
.slice(0, 3)
|
|
47
|
+
.map((c) => `expected=${c.expected}, actual=${c.actual}`)
|
|
48
|
+
.join('; ');
|
|
49
|
+
return {
|
|
50
|
+
id: 'intent-classifier',
|
|
51
|
+
label: 'Intent classifier accuracy',
|
|
52
|
+
status,
|
|
53
|
+
message: `Accuracy ${pct}% (${run.passed}/${run.total}). ${run.failed} miss(es): ${sample}.`,
|
|
54
|
+
details: {
|
|
55
|
+
accuracy: run.accuracy,
|
|
56
|
+
passed: run.passed,
|
|
57
|
+
failed: run.failed,
|
|
58
|
+
total: run.total,
|
|
59
|
+
threshold: { failBelow, warnBelow },
|
|
60
|
+
},
|
|
61
|
+
nextCommands: ['shrk context benchmark'],
|
|
62
|
+
durationMs: Date.now() - start,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IGateResult } from '../schema/quality-gate.js';
|
|
2
|
+
export interface IStructuralPatternsGateOptions {
|
|
3
|
+
/**
|
|
4
|
+
* When true, an invalid registry entry fails the gate. Default
|
|
5
|
+
* `false` (warn) — broken patterns are a config issue, not a
|
|
6
|
+
* shippability blocker by themselves.
|
|
7
|
+
*/
|
|
8
|
+
failOnInvalid?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate the structural-pattern registry. Skipped when the registry
|
|
12
|
+
* file doesn't exist. Re-validates every entry in-memory (no fs writes
|
|
13
|
+
* here — the CLI's `registry validate` is the persistence path).
|
|
14
|
+
*/
|
|
15
|
+
export declare function structuralPatternsGate(projectRoot: string, options?: IStructuralPatternsGateOptions): IGateResult;
|
|
16
|
+
//# sourceMappingURL=structural-patterns-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structural-patterns-gate.d.ts","sourceRoot":"","sources":["../../src/gates/structural-patterns-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,8BAAmC,GAC3C,WAAW,CA2Cb"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PatternRegistryStore } from '@shrkcrft/structural-search';
|
|
2
|
+
/**
|
|
3
|
+
* Validate the structural-pattern registry. Skipped when the registry
|
|
4
|
+
* file doesn't exist. Re-validates every entry in-memory (no fs writes
|
|
5
|
+
* here — the CLI's `registry validate` is the persistence path).
|
|
6
|
+
*/
|
|
7
|
+
export function structuralPatternsGate(projectRoot, options = {}) {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
const failOnInvalid = options.failOnInvalid ?? false;
|
|
10
|
+
const store = new PatternRegistryStore(projectRoot);
|
|
11
|
+
if (!store.exists()) {
|
|
12
|
+
return {
|
|
13
|
+
id: 'structural-patterns',
|
|
14
|
+
label: 'Structural pattern registry',
|
|
15
|
+
status: 'skipped',
|
|
16
|
+
message: 'No pattern registry — feature is opt-in.',
|
|
17
|
+
durationMs: Date.now() - start,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const reg = store.read();
|
|
21
|
+
if (reg.patterns.length === 0) {
|
|
22
|
+
return {
|
|
23
|
+
id: 'structural-patterns',
|
|
24
|
+
label: 'Structural pattern registry',
|
|
25
|
+
status: 'skipped',
|
|
26
|
+
message: 'Pattern registry is empty.',
|
|
27
|
+
durationMs: Date.now() - start,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const result = store.validateAll();
|
|
31
|
+
if (result.failed === 0) {
|
|
32
|
+
return {
|
|
33
|
+
id: 'structural-patterns',
|
|
34
|
+
label: 'Structural pattern registry',
|
|
35
|
+
status: 'pass',
|
|
36
|
+
message: `${result.total} pattern(s) valid.`,
|
|
37
|
+
details: { total: result.total },
|
|
38
|
+
durationMs: Date.now() - start,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
id: 'structural-patterns',
|
|
43
|
+
label: 'Structural pattern registry',
|
|
44
|
+
status: failOnInvalid ? 'fail' : 'warn',
|
|
45
|
+
message: `${result.failed}/${result.total} pattern(s) failed validation.`,
|
|
46
|
+
details: { total: result.total, failed: result.failed, errors: result.errors },
|
|
47
|
+
nextCommands: ['shrk search-structural registry validate', 'shrk search-structural registry list'],
|
|
48
|
+
durationMs: Date.now() - start,
|
|
49
|
+
};
|
|
50
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './schema/quality-gate.js';
|
|
2
|
+
export * from './gates/graph-fresh-gate.js';
|
|
3
|
+
export * from './gates/arch-gate.js';
|
|
4
|
+
export * from './gates/impact-gate.js';
|
|
5
|
+
export * from './gates/api-diff-gate.js';
|
|
6
|
+
export * from './gates/graph-cycles-gate.js';
|
|
7
|
+
export * from './gates/graph-unresolved-gate.js';
|
|
8
|
+
export * from './gates/impact-baseline-gate.js';
|
|
9
|
+
export * from './gates/structural-patterns-gate.js';
|
|
10
|
+
export * from './gates/intent-classifier-gate.js';
|
|
11
|
+
export * from './runner/run-gates.js';
|
|
12
|
+
export * from './runner/report-store.js';
|
|
13
|
+
export * from './runner/render-markdown.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./schema/quality-gate.js";
|
|
2
|
+
export * from "./gates/graph-fresh-gate.js";
|
|
3
|
+
export * from "./gates/arch-gate.js";
|
|
4
|
+
export * from "./gates/impact-gate.js";
|
|
5
|
+
export * from "./gates/api-diff-gate.js";
|
|
6
|
+
export * from "./gates/graph-cycles-gate.js";
|
|
7
|
+
export * from "./gates/graph-unresolved-gate.js";
|
|
8
|
+
export * from "./gates/impact-baseline-gate.js";
|
|
9
|
+
export * from "./gates/structural-patterns-gate.js";
|
|
10
|
+
export * from "./gates/intent-classifier-gate.js";
|
|
11
|
+
export * from "./runner/run-gates.js";
|
|
12
|
+
export * from "./runner/report-store.js";
|
|
13
|
+
export * from "./runner/render-markdown.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IQualityGateReport } from '../schema/quality-gate.js';
|
|
2
|
+
/**
|
|
3
|
+
* PR-comment-ready markdown rendering of a quality-gate report.
|
|
4
|
+
*
|
|
5
|
+
* Sections (top-down):
|
|
6
|
+
* 1. Header with overall status badge.
|
|
7
|
+
* 2. Counts table (pass / warn / fail / skipped).
|
|
8
|
+
* 3. Per-gate detail rows with status icon, label, message, and
|
|
9
|
+
* a fenced code block of next commands when present.
|
|
10
|
+
*
|
|
11
|
+
* Stable structure so downstream GitHub bots can diff it across runs
|
|
12
|
+
* without parsing JSON. Pure function — no I/O.
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderGateReportMarkdown(report: IQualityGateReport): string;
|
|
15
|
+
//# sourceMappingURL=render-markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-markdown.d.ts","sourceRoot":"","sources":["../../src/runner/render-markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEjF;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CA6B3E"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR-comment-ready markdown rendering of a quality-gate report.
|
|
3
|
+
*
|
|
4
|
+
* Sections (top-down):
|
|
5
|
+
* 1. Header with overall status badge.
|
|
6
|
+
* 2. Counts table (pass / warn / fail / skipped).
|
|
7
|
+
* 3. Per-gate detail rows with status icon, label, message, and
|
|
8
|
+
* a fenced code block of next commands when present.
|
|
9
|
+
*
|
|
10
|
+
* Stable structure so downstream GitHub bots can diff it across runs
|
|
11
|
+
* without parsing JSON. Pure function — no I/O.
|
|
12
|
+
*/
|
|
13
|
+
export function renderGateReportMarkdown(report) {
|
|
14
|
+
const lines = [];
|
|
15
|
+
lines.push(`# SharkCraft quality gates: ${statusBadge(report.overall)}`);
|
|
16
|
+
lines.push('');
|
|
17
|
+
lines.push(`Total duration: \`${report.totalDurationMs}ms\`.`);
|
|
18
|
+
lines.push('');
|
|
19
|
+
lines.push('| pass | warn | fail | skipped |');
|
|
20
|
+
lines.push('|---|---|---|---|');
|
|
21
|
+
lines.push(`| ${report.counts.pass} | ${report.counts.warn} | ${report.counts.fail} | ${report.counts.skipped} |`);
|
|
22
|
+
lines.push('');
|
|
23
|
+
if (report.gates.length === 0) {
|
|
24
|
+
lines.push('_(no gates ran)_');
|
|
25
|
+
return lines.join('\n') + '\n';
|
|
26
|
+
}
|
|
27
|
+
lines.push('## Gates');
|
|
28
|
+
lines.push('');
|
|
29
|
+
for (const gate of report.gates) {
|
|
30
|
+
lines.push(renderGate(gate));
|
|
31
|
+
lines.push('');
|
|
32
|
+
}
|
|
33
|
+
if (report.diagnostics.length > 0) {
|
|
34
|
+
lines.push('## Diagnostics');
|
|
35
|
+
for (const d of report.diagnostics.slice(0, 20)) {
|
|
36
|
+
lines.push(`- ${d}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return lines.join('\n') + '\n';
|
|
40
|
+
}
|
|
41
|
+
function statusBadge(status) {
|
|
42
|
+
switch (status) {
|
|
43
|
+
case 'pass':
|
|
44
|
+
return '✅ PASS';
|
|
45
|
+
case 'warn':
|
|
46
|
+
return '⚠️ WARN';
|
|
47
|
+
case 'fail':
|
|
48
|
+
return '❌ FAIL';
|
|
49
|
+
case 'skipped':
|
|
50
|
+
return '⏭️ SKIPPED';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function gateIcon(status) {
|
|
54
|
+
switch (status) {
|
|
55
|
+
case 'pass':
|
|
56
|
+
return '✅';
|
|
57
|
+
case 'warn':
|
|
58
|
+
return '⚠️';
|
|
59
|
+
case 'fail':
|
|
60
|
+
return '❌';
|
|
61
|
+
case 'skipped':
|
|
62
|
+
return '⏭️';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function renderGate(gate) {
|
|
66
|
+
const parts = [];
|
|
67
|
+
parts.push(`### ${gateIcon(gate.status)} \`${gate.id}\` — ${gate.label}`);
|
|
68
|
+
parts.push('');
|
|
69
|
+
parts.push(`**Status:** \`${gate.status}\` · **Duration:** \`${gate.durationMs}ms\``);
|
|
70
|
+
parts.push('');
|
|
71
|
+
parts.push(gate.message);
|
|
72
|
+
if (gate.nextCommands && gate.nextCommands.length > 0) {
|
|
73
|
+
parts.push('');
|
|
74
|
+
parts.push('Next steps:');
|
|
75
|
+
parts.push('');
|
|
76
|
+
parts.push('```bash');
|
|
77
|
+
for (const c of gate.nextCommands)
|
|
78
|
+
parts.push(c);
|
|
79
|
+
parts.push('```');
|
|
80
|
+
}
|
|
81
|
+
return parts.join('\n');
|
|
82
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IQualityGateReport } from '../schema/quality-gate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Persist + load the most recent quality-gate report for a project.
|
|
4
|
+
*
|
|
5
|
+
* The dashboard's `/api/quality-gates` endpoint prefers a recent saved
|
|
6
|
+
* report over running the gates fresh on every page load. The CLI's
|
|
7
|
+
* `shrk gate` writes here after each run so the two paths agree.
|
|
8
|
+
*/
|
|
9
|
+
export declare class QualityGateReportStore {
|
|
10
|
+
private readonly projectRoot;
|
|
11
|
+
readonly absPath: string;
|
|
12
|
+
constructor(projectRoot: string);
|
|
13
|
+
exists(): boolean;
|
|
14
|
+
read(): IQualityGateReport | undefined;
|
|
15
|
+
write(report: IQualityGateReport): void;
|
|
16
|
+
/** Age of the saved report in milliseconds, or `undefined` when missing. */
|
|
17
|
+
ageMs(): number | undefined;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=report-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-store.d.ts","sourceRoot":"","sources":["../../src/runner/report-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAIpE;;;;;;GAMG;AACH,qBAAa,sBAAsB;IAGrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAFxC,SAAgB,OAAO,EAAE,MAAM,CAAC;gBAEH,WAAW,EAAE,MAAM;IAIhD,MAAM,IAAI,OAAO;IAIjB,IAAI,IAAI,kBAAkB,GAAG,SAAS;IAStC,KAAK,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAKvC,4EAA4E;IAC5E,KAAK,IAAI,MAAM,GAAG,SAAS;CAO5B"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
const REPORT_REL = '.sharkcraft/quality-gates/last.json';
|
|
4
|
+
/**
|
|
5
|
+
* Persist + load the most recent quality-gate report for a project.
|
|
6
|
+
*
|
|
7
|
+
* The dashboard's `/api/quality-gates` endpoint prefers a recent saved
|
|
8
|
+
* report over running the gates fresh on every page load. The CLI's
|
|
9
|
+
* `shrk gate` writes here after each run so the two paths agree.
|
|
10
|
+
*/
|
|
11
|
+
export class QualityGateReportStore {
|
|
12
|
+
projectRoot;
|
|
13
|
+
absPath;
|
|
14
|
+
constructor(projectRoot) {
|
|
15
|
+
this.projectRoot = projectRoot;
|
|
16
|
+
this.absPath = nodePath.join(projectRoot, REPORT_REL);
|
|
17
|
+
}
|
|
18
|
+
exists() {
|
|
19
|
+
return existsSync(this.absPath);
|
|
20
|
+
}
|
|
21
|
+
read() {
|
|
22
|
+
if (!this.exists())
|
|
23
|
+
return undefined;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(this.absPath, 'utf8'));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
write(report) {
|
|
32
|
+
mkdirSync(nodePath.dirname(this.absPath), { recursive: true });
|
|
33
|
+
writeFileSync(this.absPath, JSON.stringify(report, null, 2), 'utf8');
|
|
34
|
+
}
|
|
35
|
+
/** Age of the saved report in milliseconds, or `undefined` when missing. */
|
|
36
|
+
ageMs() {
|
|
37
|
+
const r = this.read();
|
|
38
|
+
if (!r)
|
|
39
|
+
return undefined;
|
|
40
|
+
const t = Date.parse(r.startedAt);
|
|
41
|
+
if (Number.isNaN(t))
|
|
42
|
+
return undefined;
|
|
43
|
+
return Date.now() - t;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type IApiDiffGateOptions } from '../gates/api-diff-gate.js';
|
|
2
|
+
import { type IGraphCyclesGateOptions } from '../gates/graph-cycles-gate.js';
|
|
3
|
+
import { type IGraphUnresolvedGateOptions } from '../gates/graph-unresolved-gate.js';
|
|
4
|
+
import { type IImpactGateOptions } from '../gates/impact-gate.js';
|
|
5
|
+
import { type IImpactBaselineGateOptions } from '../gates/impact-baseline-gate.js';
|
|
6
|
+
import { type IIntentClassifierGateOptions } from '../gates/intent-classifier-gate.js';
|
|
7
|
+
import { type IStructuralPatternsGateOptions } from '../gates/structural-patterns-gate.js';
|
|
8
|
+
import { type IQualityGateReport } from '../schema/quality-gate.js';
|
|
9
|
+
export interface IRunGatesOptions {
|
|
10
|
+
projectRoot: string;
|
|
11
|
+
/** Optional impact-gate config (sinceRef, failOn). */
|
|
12
|
+
impact?: IImpactGateOptions;
|
|
13
|
+
/**
|
|
14
|
+
* Optional api-diff gate config. When omitted, the api-diff gate is
|
|
15
|
+
* skipped — the baseline file is what opts a project in.
|
|
16
|
+
*/
|
|
17
|
+
apiDiff?: IApiDiffGateOptions;
|
|
18
|
+
/** Optional graph-cycles gate config. */
|
|
19
|
+
graphCycles?: IGraphCyclesGateOptions;
|
|
20
|
+
/** Optional graph-unresolved gate config. */
|
|
21
|
+
graphUnresolved?: IGraphUnresolvedGateOptions;
|
|
22
|
+
/** Optional impact-baseline gate config. */
|
|
23
|
+
impactBaseline?: IImpactBaselineGateOptions;
|
|
24
|
+
/** Optional structural-patterns gate config. */
|
|
25
|
+
structuralPatterns?: IStructuralPatternsGateOptions;
|
|
26
|
+
/** Optional intent-classifier gate config. */
|
|
27
|
+
intentClassifier?: IIntentClassifierGateOptions;
|
|
28
|
+
/** Disable specific gates by id. */
|
|
29
|
+
disable?: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run the quality-gate aggregator over a project.
|
|
33
|
+
*
|
|
34
|
+
* Default gate set:
|
|
35
|
+
* - `graph-fresh`: code graph exists + digest matches
|
|
36
|
+
* - `arch`: architecture-guard checks pass (no errors)
|
|
37
|
+
* - `impact`: impact-engine risk is below threshold (since `main`)
|
|
38
|
+
* - `graph-cycles`: surface import cycles (warn on threshold)
|
|
39
|
+
* - `graph-unresolved`: surface unresolved imports (warn by default)
|
|
40
|
+
* - `impact-baseline`: skipped unless a baseline is frozen, warn
|
|
41
|
+
* when worsened since baseline
|
|
42
|
+
* - `structural-patterns`: skipped unless a registry exists, warn
|
|
43
|
+
* on any invalid entry
|
|
44
|
+
* - `intent-classifier`: skipped unless a fixture exists, warn /
|
|
45
|
+
* fail below the configured accuracy threshold
|
|
46
|
+
* - `api-diff`: skipped unless options.apiDiff is provided
|
|
47
|
+
*
|
|
48
|
+
* Returns a single structured report with an `overall` status that's
|
|
49
|
+
* `fail` if any gate failed, `warn` if any gate warned, `pass`
|
|
50
|
+
* otherwise.
|
|
51
|
+
*/
|
|
52
|
+
export declare function runQualityGates(options: IRunGatesOptions): IQualityGateReport;
|
|
53
|
+
//# sourceMappingURL=run-gates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-gates.d.ts","sourceRoot":"","sources":["../../src/runner/run-gates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAElF,OAAO,EAAmB,KAAK,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAE9F,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,yCAAyC;IACzC,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,6CAA6C;IAC7C,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,4CAA4C;IAC5C,cAAc,CAAC,EAAE,0BAA0B,CAAC;IAC5C,gDAAgD;IAChD,kBAAkB,CAAC,EAAE,8BAA8B,CAAC;IACpD,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,4BAA4B,CAAC;IAChD,oCAAoC;IACpC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,kBAAkB,CAkD7E"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { apiDiffGate } from "../gates/api-diff-gate.js";
|
|
2
|
+
import { archGate } from "../gates/arch-gate.js";
|
|
3
|
+
import { graphCyclesGate } from "../gates/graph-cycles-gate.js";
|
|
4
|
+
import { graphFreshGate } from "../gates/graph-fresh-gate.js";
|
|
5
|
+
import { graphUnresolvedGate, } from "../gates/graph-unresolved-gate.js";
|
|
6
|
+
import { impactGate } from "../gates/impact-gate.js";
|
|
7
|
+
import { impactBaselineGate, } from "../gates/impact-baseline-gate.js";
|
|
8
|
+
import { intentClassifierGate, } from "../gates/intent-classifier-gate.js";
|
|
9
|
+
import { structuralPatternsGate, } from "../gates/structural-patterns-gate.js";
|
|
10
|
+
import { QUALITY_GATE_SCHEMA, } from "../schema/quality-gate.js";
|
|
11
|
+
/**
|
|
12
|
+
* Run the quality-gate aggregator over a project.
|
|
13
|
+
*
|
|
14
|
+
* Default gate set:
|
|
15
|
+
* - `graph-fresh`: code graph exists + digest matches
|
|
16
|
+
* - `arch`: architecture-guard checks pass (no errors)
|
|
17
|
+
* - `impact`: impact-engine risk is below threshold (since `main`)
|
|
18
|
+
* - `graph-cycles`: surface import cycles (warn on threshold)
|
|
19
|
+
* - `graph-unresolved`: surface unresolved imports (warn by default)
|
|
20
|
+
* - `impact-baseline`: skipped unless a baseline is frozen, warn
|
|
21
|
+
* when worsened since baseline
|
|
22
|
+
* - `structural-patterns`: skipped unless a registry exists, warn
|
|
23
|
+
* on any invalid entry
|
|
24
|
+
* - `intent-classifier`: skipped unless a fixture exists, warn /
|
|
25
|
+
* fail below the configured accuracy threshold
|
|
26
|
+
* - `api-diff`: skipped unless options.apiDiff is provided
|
|
27
|
+
*
|
|
28
|
+
* Returns a single structured report with an `overall` status that's
|
|
29
|
+
* `fail` if any gate failed, `warn` if any gate warned, `pass`
|
|
30
|
+
* otherwise.
|
|
31
|
+
*/
|
|
32
|
+
export function runQualityGates(options) {
|
|
33
|
+
const startedAt = new Date().toISOString();
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
const disabled = new Set(options.disable ?? []);
|
|
36
|
+
const diagnostics = [];
|
|
37
|
+
const gates = [];
|
|
38
|
+
// graph-fresh is always first — other gates depend on it.
|
|
39
|
+
if (!disabled.has('graph-fresh')) {
|
|
40
|
+
gates.push(graphFreshGate(options.projectRoot));
|
|
41
|
+
}
|
|
42
|
+
if (!disabled.has('arch')) {
|
|
43
|
+
gates.push(archGate(options.projectRoot));
|
|
44
|
+
}
|
|
45
|
+
if (!disabled.has('impact')) {
|
|
46
|
+
gates.push(impactGate(options.projectRoot, options.impact ?? {}));
|
|
47
|
+
}
|
|
48
|
+
if (!disabled.has('graph-cycles')) {
|
|
49
|
+
gates.push(graphCyclesGate(options.projectRoot, options.graphCycles ?? {}));
|
|
50
|
+
}
|
|
51
|
+
if (!disabled.has('graph-unresolved')) {
|
|
52
|
+
gates.push(graphUnresolvedGate(options.projectRoot, options.graphUnresolved ?? {}));
|
|
53
|
+
}
|
|
54
|
+
if (!disabled.has('impact-baseline')) {
|
|
55
|
+
gates.push(impactBaselineGate(options.projectRoot, options.impactBaseline ?? {}));
|
|
56
|
+
}
|
|
57
|
+
if (!disabled.has('structural-patterns')) {
|
|
58
|
+
gates.push(structuralPatternsGate(options.projectRoot, options.structuralPatterns ?? {}));
|
|
59
|
+
}
|
|
60
|
+
if (!disabled.has('intent-classifier')) {
|
|
61
|
+
gates.push(intentClassifierGate(options.projectRoot, options.intentClassifier ?? {}));
|
|
62
|
+
}
|
|
63
|
+
if (!disabled.has('api-diff') && options.apiDiff) {
|
|
64
|
+
gates.push(apiDiffGate(options.projectRoot, options.apiDiff));
|
|
65
|
+
}
|
|
66
|
+
const counts = { pass: 0, fail: 0, warn: 0, skipped: 0 };
|
|
67
|
+
for (const g of gates)
|
|
68
|
+
counts[g.status] += 1;
|
|
69
|
+
const overall = counts.fail > 0 ? 'fail' : counts.warn > 0 ? 'warn' : counts.pass > 0 ? 'pass' : 'skipped';
|
|
70
|
+
return {
|
|
71
|
+
schema: QUALITY_GATE_SCHEMA,
|
|
72
|
+
overall,
|
|
73
|
+
startedAt,
|
|
74
|
+
totalDurationMs: Date.now() - start,
|
|
75
|
+
counts,
|
|
76
|
+
gates,
|
|
77
|
+
diagnostics,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const QUALITY_GATE_SCHEMA: "sharkcraft.quality-gate-report/v1";
|
|
2
|
+
export type GateStatus = 'pass' | 'fail' | 'warn' | 'skipped';
|
|
3
|
+
export interface IGateResult {
|
|
4
|
+
/** Stable id (e.g. 'graph-fresh', 'arch', 'impact'). */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Display label. */
|
|
7
|
+
label: string;
|
|
8
|
+
status: GateStatus;
|
|
9
|
+
/** Short human-readable headline. */
|
|
10
|
+
message: string;
|
|
11
|
+
/** Optional details a renderer can use (counts, severity, etc.). */
|
|
12
|
+
details?: Readonly<Record<string, unknown>>;
|
|
13
|
+
/** Suggested follow-up command(s) the human / agent can run. */
|
|
14
|
+
nextCommands?: readonly string[];
|
|
15
|
+
/** Wall-clock duration of the gate, in ms. */
|
|
16
|
+
durationMs: number;
|
|
17
|
+
}
|
|
18
|
+
export interface IQualityGateReport {
|
|
19
|
+
schema: typeof QUALITY_GATE_SCHEMA;
|
|
20
|
+
overall: GateStatus;
|
|
21
|
+
startedAt: string;
|
|
22
|
+
totalDurationMs: number;
|
|
23
|
+
/** Counts by status, for fast renderers. */
|
|
24
|
+
counts: Readonly<Record<GateStatus, number>>;
|
|
25
|
+
gates: readonly IGateResult[];
|
|
26
|
+
diagnostics: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=quality-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality-gate.d.ts","sourceRoot":"","sources":["../../src/schema/quality-gate.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,EAAG,mCAA4C,CAAC;AAEhF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,gEAAgE;IAChE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,mBAAmB,CAAC;IACnC,OAAO,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7C,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const QUALITY_GATE_SCHEMA = 'sharkcraft.quality-gate-report/v1';
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shrkcrft/quality-gates",
|
|
3
|
+
"version": "0.1.0-alpha.10",
|
|
4
|
+
"description": "SharkCraft quality gates: single aggregator that runs the code-intelligence checks (graph index, boundaries, architecture, impact summary) and emits one pass/fail report. The pre-merge gate for AI-agent-authored changes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SharkCraft contributors",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/sharkcraft/sharkcraft.git",
|
|
25
|
+
"directory": "packages/quality-gates"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/sharkcraft/sharkcraft",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/sharkcraft/sharkcraft/issues"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"sharkcraft",
|
|
33
|
+
"quality-gates",
|
|
34
|
+
"pre-merge",
|
|
35
|
+
"code-intelligence"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"bun": ">=1.1.0",
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@shrkcrft/core": "^0.1.0-alpha.10",
|
|
46
|
+
"@shrkcrft/graph": "^0.1.0-alpha.10",
|
|
47
|
+
"@shrkcrft/architecture-guard": "^0.1.0-alpha.10",
|
|
48
|
+
"@shrkcrft/impact-engine": "^0.1.0-alpha.10",
|
|
49
|
+
"@shrkcrft/api-surface-diff": "^0.1.0-alpha.10",
|
|
50
|
+
"@shrkcrft/boundaries": "^0.1.0-alpha.10",
|
|
51
|
+
"@shrkcrft/structural-search": "^0.1.0-alpha.10",
|
|
52
|
+
"@shrkcrft/context-planner": "^0.1.0-alpha.10"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
}
|
|
57
|
+
}
|