@shrkcrft/cli 0.1.0-alpha.27 → 0.1.0-alpha.29
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/commands/baseline.command.d.ts +8 -0
- package/dist/commands/baseline.command.d.ts.map +1 -0
- package/dist/commands/baseline.command.js +542 -0
- package/dist/commands/changelog-data.d.ts.map +1 -1
- package/dist/commands/changelog-data.js +42 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +147 -12
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +120 -0
- package/dist/commands/daily.commands.d.ts.map +1 -1
- package/dist/commands/daily.commands.js +11 -1
- package/dist/commands/gates.command.d.ts +16 -0
- package/dist/commands/gates.command.d.ts.map +1 -0
- package/dist/commands/gates.command.js +377 -0
- package/dist/commands/generated.command.d.ts +6 -0
- package/dist/commands/generated.command.d.ts.map +1 -0
- package/dist/commands/generated.command.js +544 -0
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +64 -2
- package/dist/commands/ingest.command.d.ts +11 -0
- package/dist/commands/ingest.command.d.ts.map +1 -1
- package/dist/commands/ingest.command.js +49 -23
- package/dist/commands/policy-lint.command.d.ts +37 -0
- package/dist/commands/policy-lint.command.d.ts.map +1 -1
- package/dist/commands/policy-lint.command.js +167 -8
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +70 -13
- package/dist/commands/wiring.command.d.ts.map +1 -1
- package/dist/commands/wiring.command.js +25 -0
- package/dist/exit-codes.d.ts +27 -7
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +47 -8
- package/dist/finish/run-finish.d.ts.map +1 -1
- package/dist/finish/run-finish.js +9 -3
- package/dist/gates/gate-envelope.d.ts +64 -0
- package/dist/gates/gate-envelope.d.ts.map +1 -0
- package/dist/gates/gate-envelope.js +26 -0
- package/dist/gates/gate-rule-view.d.ts +35 -0
- package/dist/gates/gate-rule-view.d.ts.map +1 -0
- package/dist/gates/gate-rule-view.js +81 -0
- package/dist/gates/rule-coverage.d.ts +53 -0
- package/dist/gates/rule-coverage.d.ts.map +1 -0
- package/dist/gates/rule-coverage.js +165 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +27 -3
- package/package.json +33 -33
package/dist/exit-codes.js
CHANGED
|
@@ -12,12 +12,19 @@
|
|
|
12
12
|
* 0 VerifiedPass — checks ran over a NON-EMPTY scope and passed. Never
|
|
13
13
|
* returned when zero units were evaluated.
|
|
14
14
|
* 1 Failure — checks ran and found violations.
|
|
15
|
-
* 2 NotVerified — indeterminate: empty evaluation scope,
|
|
16
|
-
* fallback, short-circuit, timeout, or
|
|
17
|
-
* Distinct from both pass and fail so a
|
|
18
|
-
* on it (`|| handle-indeterminate`).
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* 2 NotVerified — indeterminate: empty evaluation scope, all rules
|
|
16
|
+
* skipped, degraded fallback, short-circuit, timeout, or
|
|
17
|
+
* "refused to run". Distinct from both pass and fail so a
|
|
18
|
+
* chain can branch on it (`|| handle-indeterminate`).
|
|
19
|
+
* 3 UsageError — the request itself was malformed: unloadable config, an
|
|
20
|
+
* unknown rule id, a bad flag value. Split out of `2` on
|
|
21
|
+
* the GATE verbs because the two demand different
|
|
22
|
+
* responses: `2` means "the gate ran but proved nothing"
|
|
23
|
+
* (investigate the rules), `3` means "the gate never
|
|
24
|
+
* started" (fix the invocation or the config). Non-gate
|
|
25
|
+
* verbs keep returning `2` for usage errors — widening the
|
|
26
|
+
* split across the whole CLI would churn a documented
|
|
27
|
+
* contract far beyond what it buys.
|
|
21
28
|
*
|
|
22
29
|
* The `gen --typecheck` pre-write gate already refuses-to-nonzero rather than
|
|
23
30
|
* emit an unverified artifact; this generalizes that instinct across the gate
|
|
@@ -29,6 +36,7 @@ export var ExitCode;
|
|
|
29
36
|
ExitCode[ExitCode["VerifiedPass"] = 0] = "VerifiedPass";
|
|
30
37
|
ExitCode[ExitCode["Failure"] = 1] = "Failure";
|
|
31
38
|
ExitCode[ExitCode["NotVerified"] = 2] = "NotVerified";
|
|
39
|
+
ExitCode[ExitCode["UsageError"] = 3] = "UsageError";
|
|
32
40
|
})(ExitCode || (ExitCode = {}));
|
|
33
41
|
/**
|
|
34
42
|
* Promote a NotVerified (`2`) exit into a Failure-class nonzero (`1`) when the
|
|
@@ -116,6 +124,32 @@ export function isGateVerb(commandPath) {
|
|
|
116
124
|
return true;
|
|
117
125
|
return false;
|
|
118
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* True when the argv carries the global `--no-hints` (before the `--`
|
|
129
|
+
* sentinel). Suppresses advisory one-liners like the piped-exit note while
|
|
130
|
+
* leaving every real diagnostic — and the `--exit-trailer` machine channel —
|
|
131
|
+
* untouched. For a caller that has already internalised the warning and just
|
|
132
|
+
* wants clean stderr in captured logs.
|
|
133
|
+
*/
|
|
134
|
+
export function argvHasNoHints(argv) {
|
|
135
|
+
for (const t of argv) {
|
|
136
|
+
if (t === '--')
|
|
137
|
+
break;
|
|
138
|
+
if (t === '--no-hints')
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Process-lifetime latch for the piped-exit hint. The note is advisory, so it
|
|
145
|
+
* pays rent once: a command that emits several gate verdicts in one process
|
|
146
|
+
* (or a `--watch` loop) should not repeat the same paragraph every cycle.
|
|
147
|
+
*/
|
|
148
|
+
let pipeHintEmitted = false;
|
|
149
|
+
/** Reset the one-time hint latch. Test-only seam. */
|
|
150
|
+
export function resetPipeHintLatch() {
|
|
151
|
+
pipeHintEmitted = false;
|
|
152
|
+
}
|
|
119
153
|
/**
|
|
120
154
|
* Keep the honest `0`/`1`/`2` exit code READABLE through the shape agents reach
|
|
121
155
|
* for first — the trailing pipe. `<gate> | head` reports `head`'s `$?`, so a true
|
|
@@ -136,9 +170,14 @@ export function emitPipeExitSignal(commandPath, code, opts) {
|
|
|
136
170
|
if (!isGateVerb(commandPath))
|
|
137
171
|
return;
|
|
138
172
|
const write = opts.write ?? ((s) => void process.stderr.write(s));
|
|
139
|
-
|
|
173
|
+
// Advisory, and therefore rationed: stderr only, only when a NON-zero verdict
|
|
174
|
+
// would actually be lost to the pipe, at most once per process, and never
|
|
175
|
+
// under `--no-hints`. The structured channel (`--exit-trailer`) is unaffected
|
|
176
|
+
// by all of these — it is opt-in and always emitted when asked.
|
|
177
|
+
if (opts.piped && code !== 0 && !opts.noHints && !pipeHintEmitted) {
|
|
178
|
+
pipeHintEmitted = true;
|
|
140
179
|
write(`note: stdout is piped — $? reflects the downstream command, not shrk (exit ${code}); ` +
|
|
141
|
-
`use PIPESTATUS[0] or --exit-trailer to read shrk's verdict.\n`);
|
|
180
|
+
`use PIPESTATUS[0] or --exit-trailer to read shrk's verdict (--no-hints silences this).\n`);
|
|
142
181
|
}
|
|
143
182
|
// The trailer is written LAST so it is the final stderr line a caller reads.
|
|
144
183
|
if (opts.trailer)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-finish.d.ts","sourceRoot":"","sources":["../../src/finish/run-finish.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAiB7B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,eAAO,MAAM,aAAa,EAAG,sBAA+B,CAAC;AAQ7D,2FAA2F;AAC3F,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE3D,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IAClG,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;QACzD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc,CAAC;IACnD,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IACzD,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;CACtC;AAmBD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"run-finish.d.ts","sourceRoot":"","sources":["../../src/finish/run-finish.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAiB7B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,eAAO,MAAM,aAAa,EAAG,sBAA+B,CAAC;AAQ7D,2FAA2F;AAC3F,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE3D,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IAClG,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;QACzD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc,CAAC;IACnD,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IACzD,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;CACtC;AAmBD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,CA2JnF"}
|
|
@@ -88,16 +88,22 @@ export async function runFinishGates(input) {
|
|
|
88
88
|
const report = buildImportHygieneReport(cwd, { files: codeChanged });
|
|
89
89
|
const errors = report.counts?.['error'] ?? (report.verdict === 'errors' ? report.findings.length : 0);
|
|
90
90
|
const warnings = report.counts?.['warning'] ?? (report.verdict === 'warnings' ? report.findings.length : 0);
|
|
91
|
+
// Only the findings that actually DRIVE the verdict become "failing items".
|
|
92
|
+
// An allowlisted import is downgraded to `info` by design and is not what
|
|
93
|
+
// failed — listing it anyway pads the renderer's 15-item cap and can push a
|
|
94
|
+
// real error out of view, while its allowlist justification reads like a
|
|
95
|
+
// fix instruction. A fix-list that names non-failures is not a fix-list.
|
|
96
|
+
const driving = report.findings.filter((f) => report.verdict === 'errors' ? f.severity === 'error' : f.severity === 'warning');
|
|
91
97
|
gates.push({
|
|
92
98
|
name: 'imports',
|
|
93
99
|
status: report.verdict === 'errors' ? 'fail' : 'pass',
|
|
94
|
-
detail: `verdict=${report.verdict} (${report.findings.length} finding(s))`,
|
|
100
|
+
detail: `verdict=${report.verdict} (${driving.length} of ${report.findings.length} finding(s) drive it)`,
|
|
95
101
|
errors,
|
|
96
102
|
warnings,
|
|
97
|
-
items:
|
|
103
|
+
items: driving.map((f) => ({
|
|
98
104
|
file: f.file,
|
|
99
105
|
line: f.line,
|
|
100
|
-
message:
|
|
106
|
+
message: `[${f.severity}] ${f.kind}: ${f.suggestedFix || f.reason || f.snippet}`.trim(),
|
|
101
107
|
})),
|
|
102
108
|
});
|
|
103
109
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One machine-readable shape for every gate verb.
|
|
3
|
+
*
|
|
4
|
+
* `--json` has always been available on each plane, but each emitted its own
|
|
5
|
+
* schema (`sharkcraft.wiring/v1`, `sharkcraft.baseline/v1`, …), so a CI step or
|
|
6
|
+
* agent had to parse four shapes to answer one question: which rules ran, which
|
|
7
|
+
* failed, and why. This envelope is that answer, identical across planes.
|
|
8
|
+
*
|
|
9
|
+
* It is ADDITIVE. The per-plane payloads are published, documented, and asserted
|
|
10
|
+
* by tests; replacing them would break every existing consumer. The envelope
|
|
11
|
+
* rides along under a `gate` key, so `jq .gate` is the uniform read and nothing
|
|
12
|
+
* that worked before stops working.
|
|
13
|
+
*/
|
|
14
|
+
export declare const GATE_ENVELOPE_SCHEMA: "sharkcraft.gate/v1";
|
|
15
|
+
/** Which data-defined plane produced a rule result. */
|
|
16
|
+
export type GateRuleType = 'wiring' | 'policy' | 'registry' | 'registration' | 'baseline' | 'generated';
|
|
17
|
+
/**
|
|
18
|
+
* Per-rule outcome, uniform across planes. `skipped` is deliberately a
|
|
19
|
+
* first-class status, not folded into `passed` — a rule that matched nothing
|
|
20
|
+
* enforced nothing.
|
|
21
|
+
*/
|
|
22
|
+
export type GateRuleStatus = 'passed' | 'failed' | 'skipped' | 'error';
|
|
23
|
+
/** One violation, normalized. `id` is the offending token / entry / file. */
|
|
24
|
+
export interface IGateViolation {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly file?: string;
|
|
27
|
+
readonly line?: number;
|
|
28
|
+
readonly message?: string;
|
|
29
|
+
readonly hint?: string;
|
|
30
|
+
}
|
|
31
|
+
/** One rule's result in the shared envelope. */
|
|
32
|
+
export interface IGateRuleResult {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly type: GateRuleType;
|
|
35
|
+
readonly status: GateRuleStatus;
|
|
36
|
+
readonly severity: 'error' | 'warning';
|
|
37
|
+
/**
|
|
38
|
+
* Plane-appropriate match counts — e.g. `{declared, registered}` for wiring,
|
|
39
|
+
* `{committed, current}` for baseline, `{units, findings}` for policy. Always
|
|
40
|
+
* present so "what did this rule actually see?" is answerable uniformly.
|
|
41
|
+
*/
|
|
42
|
+
readonly counts: Readonly<Record<string, number>>;
|
|
43
|
+
readonly violations: readonly IGateViolation[];
|
|
44
|
+
/** Why the rule checked nothing, when `status` is `skipped`. */
|
|
45
|
+
readonly skipReason?: string;
|
|
46
|
+
/** Set when the rule is misconfigured (`status: 'error'`). */
|
|
47
|
+
readonly error?: string;
|
|
48
|
+
}
|
|
49
|
+
/** The envelope emitted under the `gate` key of every gate verb's `--json`. */
|
|
50
|
+
export interface IGateEnvelope {
|
|
51
|
+
readonly schema: typeof GATE_ENVELOPE_SCHEMA;
|
|
52
|
+
/** The verb that produced this, space-joined (e.g. `check wiring`). */
|
|
53
|
+
readonly verb: string;
|
|
54
|
+
/** The exit code this run returns — the same number the process exits with. */
|
|
55
|
+
readonly exit: number;
|
|
56
|
+
readonly rules: readonly IGateRuleResult[];
|
|
57
|
+
/** Rules that ran a real comparison (status !== 'skipped'). */
|
|
58
|
+
readonly evaluated: number;
|
|
59
|
+
readonly skipped: number;
|
|
60
|
+
readonly failed: number;
|
|
61
|
+
}
|
|
62
|
+
/** Build the envelope from already-normalized per-rule results. */
|
|
63
|
+
export declare function buildGateEnvelope(verb: string, exit: number, rules: readonly IGateRuleResult[]): IGateEnvelope;
|
|
64
|
+
//# sourceMappingURL=gate-envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-envelope.d.ts","sourceRoot":"","sources":["../../src/gates/gate-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,EAAG,oBAA6B,CAAC;AAElE,uDAAuD;AACvD,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,cAAc,GACd,UAAU,GACV,WAAW,CAAC;AAEhB;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAEvE,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;IAC/C,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,oBAAoB,CAAC;IAC7C,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,eAAe,EAAE,GAChC,aAAa,CAUf"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One machine-readable shape for every gate verb.
|
|
3
|
+
*
|
|
4
|
+
* `--json` has always been available on each plane, but each emitted its own
|
|
5
|
+
* schema (`sharkcraft.wiring/v1`, `sharkcraft.baseline/v1`, …), so a CI step or
|
|
6
|
+
* agent had to parse four shapes to answer one question: which rules ran, which
|
|
7
|
+
* failed, and why. This envelope is that answer, identical across planes.
|
|
8
|
+
*
|
|
9
|
+
* It is ADDITIVE. The per-plane payloads are published, documented, and asserted
|
|
10
|
+
* by tests; replacing them would break every existing consumer. The envelope
|
|
11
|
+
* rides along under a `gate` key, so `jq .gate` is the uniform read and nothing
|
|
12
|
+
* that worked before stops working.
|
|
13
|
+
*/
|
|
14
|
+
export const GATE_ENVELOPE_SCHEMA = 'sharkcraft.gate/v1';
|
|
15
|
+
/** Build the envelope from already-normalized per-rule results. */
|
|
16
|
+
export function buildGateEnvelope(verb, exit, rules) {
|
|
17
|
+
return {
|
|
18
|
+
schema: GATE_ENVELOPE_SCHEMA,
|
|
19
|
+
verb,
|
|
20
|
+
exit,
|
|
21
|
+
rules,
|
|
22
|
+
evaluated: rules.filter((r) => r.status !== 'skipped').length,
|
|
23
|
+
skipped: rules.filter((r) => r.status === 'skipped').length,
|
|
24
|
+
failed: rules.filter((r) => r.status === 'failed' || r.status === 'error').length,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type IBaselineRule, type IGeneratedArtifactRule, type IPolicyRule, type IRegistrationIdiom, type IRegistryDeclaration, type IRuleSelfTest, type IWiringRule } from '@shrkcrft/core';
|
|
2
|
+
/** Which data-defined plane a rule belongs to. */
|
|
3
|
+
export type GatePlane = 'wiring' | 'policy' | 'registry' | 'registration' | 'baseline' | 'generated';
|
|
4
|
+
/** Every plane, in the order `shrk gates list` prints them. */
|
|
5
|
+
export declare const GATE_PLANES: readonly GatePlane[];
|
|
6
|
+
/**
|
|
7
|
+
* One data-defined rule, normalized across planes.
|
|
8
|
+
*
|
|
9
|
+
* The trust layer's whole job is to answer "what did this rule actually match?"
|
|
10
|
+
* for ANY rule, so it needs one shape to iterate. The plane-specific rule object
|
|
11
|
+
* rides along in `raw` for the explain dispatch.
|
|
12
|
+
*/
|
|
13
|
+
export interface IGateRuleView {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly plane: GatePlane;
|
|
16
|
+
readonly description?: string;
|
|
17
|
+
readonly severity: 'error' | 'warning';
|
|
18
|
+
/** True when a zero-match is a hard failure rather than a loud skip. */
|
|
19
|
+
readonly failOnEmpty: boolean;
|
|
20
|
+
readonly selfTest?: IRuleSelfTest;
|
|
21
|
+
/** The underlying rule, for the plane-specific explainer. */
|
|
22
|
+
readonly raw: IWiringRule | IPolicyRule | IRegistryDeclaration | IRegistrationIdiom | IBaselineRule | IGeneratedArtifactRule;
|
|
23
|
+
}
|
|
24
|
+
/** The config planes this view is built from. */
|
|
25
|
+
export interface IGatePlanes {
|
|
26
|
+
readonly wiringRules?: readonly IWiringRule[];
|
|
27
|
+
readonly policyRules?: readonly IPolicyRule[];
|
|
28
|
+
readonly registries?: readonly IRegistryDeclaration[];
|
|
29
|
+
readonly registrationGraph?: readonly IRegistrationIdiom[];
|
|
30
|
+
readonly baselines?: readonly IBaselineRule[];
|
|
31
|
+
readonly generatedArtifacts?: readonly IGeneratedArtifactRule[];
|
|
32
|
+
}
|
|
33
|
+
/** Flatten every declared rule across every plane into one iterable list. */
|
|
34
|
+
export declare function collectGateRules(planes: IGatePlanes): IGateRuleView[];
|
|
35
|
+
//# sourceMappingURL=gate-rule-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-rule-view.d.ts","sourceRoot":"","sources":["../../src/gates/gate-rule-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AAExB,kDAAkD;AAClD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,CAAC;AAErG,+DAA+D;AAC/D,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAO3C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;IAClC,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EACR,WAAW,GACX,WAAW,GACX,oBAAoB,GACpB,kBAAkB,GAClB,aAAa,GACb,sBAAsB,CAAC;CAC5B;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACtD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC3D,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACjE;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,EAAE,CAqErE"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { failsWhenEmpty, } from '@shrkcrft/core';
|
|
2
|
+
/** Every plane, in the order `shrk gates list` prints them. */
|
|
3
|
+
export const GATE_PLANES = [
|
|
4
|
+
'wiring',
|
|
5
|
+
'policy',
|
|
6
|
+
'registry',
|
|
7
|
+
'registration',
|
|
8
|
+
'baseline',
|
|
9
|
+
'generated',
|
|
10
|
+
];
|
|
11
|
+
/** Flatten every declared rule across every plane into one iterable list. */
|
|
12
|
+
export function collectGateRules(planes) {
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const r of planes.wiringRules ?? []) {
|
|
15
|
+
out.push({
|
|
16
|
+
id: r.id,
|
|
17
|
+
plane: 'wiring',
|
|
18
|
+
...(r.description ? { description: r.description } : {}),
|
|
19
|
+
severity: r.severity ?? 'error',
|
|
20
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
21
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
22
|
+
raw: r,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
for (const r of planes.policyRules ?? []) {
|
|
26
|
+
out.push({
|
|
27
|
+
id: r.id,
|
|
28
|
+
plane: 'policy',
|
|
29
|
+
...(r.description ? { description: r.description } : {}),
|
|
30
|
+
severity: r.severity ?? 'error',
|
|
31
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
32
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
33
|
+
raw: r,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
for (const r of planes.registries ?? []) {
|
|
37
|
+
out.push({
|
|
38
|
+
id: r.name,
|
|
39
|
+
plane: 'registry',
|
|
40
|
+
...(r.description ? { description: r.description } : {}),
|
|
41
|
+
// A registry is an inventory, not a gate — it never fails a build on its
|
|
42
|
+
// own, so it carries no severity of its own.
|
|
43
|
+
severity: 'warning',
|
|
44
|
+
failOnEmpty: false,
|
|
45
|
+
raw: r,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
for (const r of planes.registrationGraph ?? []) {
|
|
49
|
+
out.push({
|
|
50
|
+
id: r.name,
|
|
51
|
+
plane: 'registration',
|
|
52
|
+
...(r.description ? { description: r.description } : {}),
|
|
53
|
+
severity: 'warning',
|
|
54
|
+
failOnEmpty: false,
|
|
55
|
+
raw: r,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
for (const r of planes.baselines ?? []) {
|
|
59
|
+
out.push({
|
|
60
|
+
id: r.id,
|
|
61
|
+
plane: 'baseline',
|
|
62
|
+
...(r.description ? { description: r.description } : {}),
|
|
63
|
+
severity: r.severity ?? 'error',
|
|
64
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
65
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
66
|
+
raw: r,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
for (const r of planes.generatedArtifacts ?? []) {
|
|
70
|
+
out.push({
|
|
71
|
+
id: r.id,
|
|
72
|
+
plane: 'generated',
|
|
73
|
+
...(r.description ? { description: r.description } : {}),
|
|
74
|
+
severity: r.severity ?? 'error',
|
|
75
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
76
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
77
|
+
raw: r,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { IGateRuleView } from './gate-rule-view.js';
|
|
2
|
+
export declare const GATE_COVERAGE_SCHEMA: "sharkcraft.gate-coverage/v1";
|
|
3
|
+
/**
|
|
4
|
+
* Whether a rule is connected to anything at all.
|
|
5
|
+
*
|
|
6
|
+
* `empty` is the finding this whole surface exists for: a stale selector that
|
|
7
|
+
* matches nothing passes every gate forever, so the ONLY way to notice is to
|
|
8
|
+
* report the match count itself and flag zero. `failed-expectation` is the
|
|
9
|
+
* stronger form — the author wrote down what the rule should match, and it
|
|
10
|
+
* doesn't.
|
|
11
|
+
*/
|
|
12
|
+
export type GateCoverageStatus = 'ok' | 'empty' | 'error' | 'failed-expectation';
|
|
13
|
+
export interface IGateCoverage {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly plane: IGateRuleView['plane'];
|
|
16
|
+
readonly description?: string;
|
|
17
|
+
readonly status: GateCoverageStatus;
|
|
18
|
+
/** Files the rule's primary selector matched. */
|
|
19
|
+
readonly filesMatched: number;
|
|
20
|
+
/** Ids/units the rule extracted (findings scanned, for the policy plane). */
|
|
21
|
+
readonly unitsMatched: number;
|
|
22
|
+
/** What "units" means for this plane, for honest reporting. */
|
|
23
|
+
readonly unitLabel: string;
|
|
24
|
+
/** A few of the extracted ids, so the author can eyeball correctness. */
|
|
25
|
+
readonly sampleIds: readonly string[];
|
|
26
|
+
/** True when a zero match is a hard failure for this rule. */
|
|
27
|
+
readonly failOnEmpty: boolean;
|
|
28
|
+
readonly error?: string;
|
|
29
|
+
/** Unmet `selfTest` expectations, each a human-readable sentence. */
|
|
30
|
+
readonly expectationFailures: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
export interface IGateCoverageReport {
|
|
33
|
+
readonly schema: typeof GATE_COVERAGE_SCHEMA;
|
|
34
|
+
readonly rules: readonly IGateCoverage[];
|
|
35
|
+
readonly total: number;
|
|
36
|
+
readonly empty: number;
|
|
37
|
+
readonly errored: number;
|
|
38
|
+
readonly expectationFailures: number;
|
|
39
|
+
/**
|
|
40
|
+
* `pass` — every rule matched something and met its expectations.
|
|
41
|
+
* `stale` — at least one rule matched nothing (or broke an expectation).
|
|
42
|
+
*/
|
|
43
|
+
readonly verdict: 'pass' | 'stale';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve every declared rule against the live tree and report what it matched.
|
|
47
|
+
*
|
|
48
|
+
* The `command`-compute baseline is the one rule kind that cannot be inspected
|
|
49
|
+
* without side effects; it is reported as un-inspected (never as `empty`), so
|
|
50
|
+
* the report never claims a fact it did not check.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildGateCoverage(cwd: string, rules: readonly IGateRuleView[], excludeDirs?: readonly string[]): IGateCoverageReport;
|
|
53
|
+
//# sourceMappingURL=rule-coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-coverage.d.ts","sourceRoot":"","sources":["../../src/gates/rule-coverage.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,oBAAoB,EAAG,6BAAsC,CAAC;AAE3E;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,GAAG,OAAO,GAAG,oBAAoB,CAAC;AAEjF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,iDAAiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CACjD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,OAAO,oBAAoB,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC;AAsHD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,mBAAmB,CA4DrB"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { inspectSource, runPolicyLint, scanGeneratedFiles, wiringSourceSide, } from '@shrkcrft/boundaries';
|
|
2
|
+
export const GATE_COVERAGE_SCHEMA = 'sharkcraft.gate-coverage/v1';
|
|
3
|
+
/** Evaluate the author's declared expectations against what the rule extracted. */
|
|
4
|
+
function checkExpectations(selfTest, ids, unitsMatched) {
|
|
5
|
+
if (!selfTest)
|
|
6
|
+
return [];
|
|
7
|
+
const out = [];
|
|
8
|
+
if (selfTest.expectMatchesAtLeast !== undefined &&
|
|
9
|
+
unitsMatched < selfTest.expectMatchesAtLeast) {
|
|
10
|
+
out.push(`expected at least ${selfTest.expectMatchesAtLeast} match(es), got ${unitsMatched}`);
|
|
11
|
+
}
|
|
12
|
+
const present = new Set(ids);
|
|
13
|
+
for (const id of selfTest.expectIds ?? []) {
|
|
14
|
+
if (!present.has(id))
|
|
15
|
+
out.push(`expected id "${id}" was NOT extracted`);
|
|
16
|
+
}
|
|
17
|
+
for (const id of selfTest.expectNotIds ?? []) {
|
|
18
|
+
if (present.has(id))
|
|
19
|
+
out.push(`id "${id}" was extracted but is listed in expectNotIds`);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function matchWiring(cwd, rule, excludeDirs) {
|
|
24
|
+
const side = wiringSourceSide(rule);
|
|
25
|
+
if (!side)
|
|
26
|
+
return { filesMatched: 0, unitsMatched: 0, unitLabel: 'ids', ids: [], error: 'rule sets no source side' };
|
|
27
|
+
const insp = inspectSource(cwd, side, excludeDirs);
|
|
28
|
+
return {
|
|
29
|
+
filesMatched: insp.filesScanned,
|
|
30
|
+
unitsMatched: insp.ids.length,
|
|
31
|
+
unitLabel: 'ids',
|
|
32
|
+
ids: insp.ids,
|
|
33
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function matchPolicy(cwd, rule, excludeDirs) {
|
|
37
|
+
const report = runPolicyLint(cwd, [rule], { excludeDirs });
|
|
38
|
+
const result = report.rules[0];
|
|
39
|
+
return {
|
|
40
|
+
// A policy rule's "files" and "units" differ only for inline templates.
|
|
41
|
+
filesMatched: result?.unitsScanned ?? 0,
|
|
42
|
+
unitsMatched: result?.unitsScanned ?? 0,
|
|
43
|
+
unitLabel: 'content units',
|
|
44
|
+
ids: [],
|
|
45
|
+
...(result?.error ? { error: result.error } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function matchRegistry(cwd, decl, excludeDirs) {
|
|
49
|
+
const insp = inspectSource(cwd, decl.source, excludeDirs);
|
|
50
|
+
return {
|
|
51
|
+
filesMatched: insp.filesScanned,
|
|
52
|
+
unitsMatched: insp.ids.length,
|
|
53
|
+
unitLabel: 'ids',
|
|
54
|
+
ids: insp.ids,
|
|
55
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function matchRegistration(cwd, idiom, excludeDirs) {
|
|
59
|
+
const insp = inspectSource(cwd, idiom.declared, excludeDirs);
|
|
60
|
+
return {
|
|
61
|
+
filesMatched: insp.filesScanned,
|
|
62
|
+
unitsMatched: insp.ids.length,
|
|
63
|
+
unitLabel: 'declared tokens',
|
|
64
|
+
ids: insp.ids,
|
|
65
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function matchBaseline(cwd, rule, excludeDirs) {
|
|
69
|
+
// Only the EXTRACTOR half can be inspected without spawning. A command
|
|
70
|
+
// compute is reported honestly as un-inspectable rather than guessed at —
|
|
71
|
+
// `shrk baseline explain --id X` runs it on purpose.
|
|
72
|
+
if (rule.compute.kind !== 'extractor' || !rule.compute.source) {
|
|
73
|
+
return {
|
|
74
|
+
filesMatched: 0,
|
|
75
|
+
unitsMatched: 0,
|
|
76
|
+
unitLabel: 'entries (command compute — not inspected)',
|
|
77
|
+
ids: [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const insp = inspectSource(cwd, rule.compute.source, excludeDirs);
|
|
81
|
+
return {
|
|
82
|
+
filesMatched: insp.filesScanned,
|
|
83
|
+
unitsMatched: insp.ids.length,
|
|
84
|
+
unitLabel: 'entries',
|
|
85
|
+
ids: insp.ids,
|
|
86
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function matchGenerated(cwd, rule, excludeDirs) {
|
|
90
|
+
const scan = scanGeneratedFiles(cwd, rule, excludeDirs);
|
|
91
|
+
const files = [...scan.generated.keys()];
|
|
92
|
+
return {
|
|
93
|
+
filesMatched: files.length,
|
|
94
|
+
unitsMatched: files.length,
|
|
95
|
+
unitLabel: 'generated files',
|
|
96
|
+
ids: files,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve every declared rule against the live tree and report what it matched.
|
|
101
|
+
*
|
|
102
|
+
* The `command`-compute baseline is the one rule kind that cannot be inspected
|
|
103
|
+
* without side effects; it is reported as un-inspected (never as `empty`), so
|
|
104
|
+
* the report never claims a fact it did not check.
|
|
105
|
+
*/
|
|
106
|
+
export function buildGateCoverage(cwd, rules, excludeDirs = []) {
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const view of rules) {
|
|
109
|
+
let match;
|
|
110
|
+
switch (view.plane) {
|
|
111
|
+
case 'wiring':
|
|
112
|
+
match = matchWiring(cwd, view.raw, excludeDirs);
|
|
113
|
+
break;
|
|
114
|
+
case 'policy':
|
|
115
|
+
match = matchPolicy(cwd, view.raw, excludeDirs);
|
|
116
|
+
break;
|
|
117
|
+
case 'registry':
|
|
118
|
+
match = matchRegistry(cwd, view.raw, excludeDirs);
|
|
119
|
+
break;
|
|
120
|
+
case 'registration':
|
|
121
|
+
match = matchRegistration(cwd, view.raw, excludeDirs);
|
|
122
|
+
break;
|
|
123
|
+
case 'baseline':
|
|
124
|
+
match = matchBaseline(cwd, view.raw, excludeDirs);
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
match = matchGenerated(cwd, view.raw, excludeDirs);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
const expectationFailures = checkExpectations(view.selfTest, match.ids, match.unitsMatched);
|
|
131
|
+
const uninspected = match.unitLabel.includes('not inspected');
|
|
132
|
+
const status = match.error !== undefined
|
|
133
|
+
? 'error'
|
|
134
|
+
: expectationFailures.length > 0
|
|
135
|
+
? 'failed-expectation'
|
|
136
|
+
: match.unitsMatched === 0 && !uninspected
|
|
137
|
+
? 'empty'
|
|
138
|
+
: 'ok';
|
|
139
|
+
out.push({
|
|
140
|
+
id: view.id,
|
|
141
|
+
plane: view.plane,
|
|
142
|
+
...(view.description ? { description: view.description } : {}),
|
|
143
|
+
status,
|
|
144
|
+
filesMatched: match.filesMatched,
|
|
145
|
+
unitsMatched: match.unitsMatched,
|
|
146
|
+
unitLabel: match.unitLabel,
|
|
147
|
+
sampleIds: match.ids.slice(0, 5),
|
|
148
|
+
failOnEmpty: view.failOnEmpty,
|
|
149
|
+
...(match.error ? { error: match.error } : {}),
|
|
150
|
+
expectationFailures,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const empty = out.filter((r) => r.status === 'empty').length;
|
|
154
|
+
const errored = out.filter((r) => r.status === 'error').length;
|
|
155
|
+
const expectationFailures = out.filter((r) => r.status === 'failed-expectation').length;
|
|
156
|
+
return {
|
|
157
|
+
schema: GATE_COVERAGE_SCHEMA,
|
|
158
|
+
rules: out,
|
|
159
|
+
total: out.length,
|
|
160
|
+
empty,
|
|
161
|
+
errored,
|
|
162
|
+
expectationFailures,
|
|
163
|
+
verdict: empty + errored + expectationFailures > 0 ? 'stale' : 'pass',
|
|
164
|
+
};
|
|
165
|
+
}
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAMhB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAMhB,MAAM,uBAAuB,CAAC;AA0Z/B,wBAAgB,aAAa,IAAI,eAAe,CAqZ/C;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgDrE;AAwID;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAoDxE"}
|
package/dist/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { loadDotenv } from "./env/load-dotenv.js";
|
|
3
3
|
import { CommandRegistry, extractGlobalCompress, extractGlobalCwd, extractGlobalExitTrailer, parseArgs, } from "./command-registry.js";
|
|
4
|
-
import { argvHasExitTrailer, argvHasStrict, emitPipeExitSignal, promoteForStrict } from "./exit-codes.js";
|
|
4
|
+
import { argvHasExitTrailer, argvHasNoHints, argvHasStrict, emitPipeExitSignal, promoteForStrict, } from "./exit-codes.js";
|
|
5
5
|
import { runCommandWithCompression } from "./output/output-compression.js";
|
|
6
6
|
import { initCommand } from "./commands/init.command.js";
|
|
7
7
|
import { inspectCommand } from "./commands/inspect.command.js";
|
|
@@ -34,7 +34,10 @@ import { archCommand } from "./commands/arch.command.js";
|
|
|
34
34
|
import { frameworkCommand } from "./commands/framework.command.js";
|
|
35
35
|
import { apiDiffCommand } from "./commands/api-diff.command.js";
|
|
36
36
|
import { gateCommand } from "./commands/gate.command.js";
|
|
37
|
-
import { policyLintCommand } from "./commands/policy-lint.command.js";
|
|
37
|
+
import { policyLintCommand, policyLintExplainCommand } from "./commands/policy-lint.command.js";
|
|
38
|
+
import { baselineCheckCommand, baselineCommand, baselineDiffCommand, baselineExplainCommand, baselineListCommand, baselineUpdateCommand, } from "./commands/baseline.command.js";
|
|
39
|
+
import { generatedCheckCommand, generatedExplainCommand, generatedListCommand, generatedUpdateCommand, } from "./commands/generated.command.js";
|
|
40
|
+
import { gatesCommand, gatesCoverageCommand, gatesExplainCommand, gatesListCommand, } from "./commands/gates.command.js";
|
|
38
41
|
import { wiringCommand } from "./commands/wiring.command.js";
|
|
39
42
|
import { reuseCommand } from "./commands/reuse.command.js";
|
|
40
43
|
import { migrateCommand } from "./commands/migrate.command.js";
|
|
@@ -44,7 +47,7 @@ import { compressCommand, expandCommand } from "./commands/compress.command.js";
|
|
|
44
47
|
import { alignCommand, unalignCommand } from "./commands/cache-align.command.js";
|
|
45
48
|
import { reviewCommand } from "./commands/review.command.js";
|
|
46
49
|
import { onboardCommand } from "./commands/onboard.command.js";
|
|
47
|
-
import { contradictionsCommand, generatedCommand, ingestCommand, } from "./commands/ingest.command.js";
|
|
50
|
+
import { contradictionsCommand, generatedCommand, generatedProtectCommand, generatedReportCommand, ingestCommand, } from "./commands/ingest.command.js";
|
|
48
51
|
import { contextBuildCommand, contextRefreshCommand, contextStatusCommand, understandTaskCommand, validateChangeCommand, } from "./commands/task-context.command.js";
|
|
49
52
|
import { testCommand } from "./commands/test.command.js";
|
|
50
53
|
import { planParentCommand, planReviewCommand } from "./commands/plan.command.js";
|
|
@@ -206,6 +209,26 @@ export function buildRegistry() {
|
|
|
206
209
|
registry.register(apiDiffCommand);
|
|
207
210
|
registry.register(gateCommand);
|
|
208
211
|
registry.register(policyLintCommand);
|
|
212
|
+
registry.registerSubcommand('policy-lint', policyLintExplainCommand);
|
|
213
|
+
// The two shell-executing planes (baseline/generated) + the trust layer.
|
|
214
|
+
registry.register(baselineCommand);
|
|
215
|
+
registry.registerSubcommand('baseline', baselineListCommand);
|
|
216
|
+
registry.registerSubcommand('baseline', baselineCheckCommand);
|
|
217
|
+
registry.registerSubcommand('baseline', baselineDiffCommand);
|
|
218
|
+
registry.registerSubcommand('baseline', baselineUpdateCommand);
|
|
219
|
+
registry.registerSubcommand('baseline', baselineExplainCommand);
|
|
220
|
+
// `generated` already exists as the generated-code CLASSIFIER (report /
|
|
221
|
+
// protect). These add the drift GATE under the same noun.
|
|
222
|
+
registry.registerSubcommand('generated', generatedReportCommand);
|
|
223
|
+
registry.registerSubcommand('generated', generatedProtectCommand);
|
|
224
|
+
registry.registerSubcommand('generated', generatedListCommand);
|
|
225
|
+
registry.registerSubcommand('generated', generatedCheckCommand);
|
|
226
|
+
registry.registerSubcommand('generated', generatedUpdateCommand);
|
|
227
|
+
registry.registerSubcommand('generated', generatedExplainCommand);
|
|
228
|
+
registry.register(gatesCommand);
|
|
229
|
+
registry.registerSubcommand('gates', gatesListCommand);
|
|
230
|
+
registry.registerSubcommand('gates', gatesCoverageCommand);
|
|
231
|
+
registry.registerSubcommand('gates', gatesExplainCommand);
|
|
209
232
|
registry.register(wiringCommand);
|
|
210
233
|
registry.register(reuseCommand);
|
|
211
234
|
registry.register(migrateCommand);
|
|
@@ -532,6 +555,7 @@ export async function runCli(argv) {
|
|
|
532
555
|
emitPipeExitSignal(command, exitCode, {
|
|
533
556
|
piped: !process.stdout.isTTY,
|
|
534
557
|
trailer: argvHasExitTrailer(argv),
|
|
558
|
+
noHints: argvHasNoHints(argv),
|
|
535
559
|
});
|
|
536
560
|
}
|
|
537
561
|
return exitCode;
|