@shrkcrft/cli 0.1.0-alpha.28 → 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.map +1 -1
- package/dist/commands/baseline.command.js +42 -11
- package/dist/commands/changelog-data.d.ts.map +1 -1
- package/dist/commands/changelog-data.js +20 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +119 -11
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +8 -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 +11 -1
- package/dist/commands/gates.command.d.ts.map +1 -1
- package/dist/commands/gates.command.js +53 -10
- package/dist/commands/generated.command.d.ts.map +1 -1
- package/dist/commands/generated.command.js +36 -6
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +64 -2
- package/dist/commands/policy-lint.command.d.ts.map +1 -1
- package/dist/commands/policy-lint.command.js +52 -10
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +34 -9
- 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/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 +1 -1
- package/dist/gates/gate-rule-view.d.ts.map +1 -1
- package/dist/gates/gate-rule-view.js +5 -4
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -1
- package/package.json +33 -33
|
@@ -20,11 +20,13 @@ import { spawnSync } from 'node:child_process';
|
|
|
20
20
|
import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
21
21
|
import * as nodeOs from 'node:os';
|
|
22
22
|
import * as nodePath from 'node:path';
|
|
23
|
+
import { failsWhenEmpty } from '@shrkcrft/core';
|
|
23
24
|
import { checkProvenanceHeaders, compareGeneratedTrees, scanGeneratedFiles, } from '@shrkcrft/boundaries';
|
|
24
25
|
import { resolveProjectConfig } from '@shrkcrft/inspector';
|
|
25
26
|
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
26
27
|
import { ExitCode } from "../exit-codes.js";
|
|
27
28
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
29
|
+
import { buildGateEnvelope } from "../gates/gate-envelope.js";
|
|
28
30
|
const SCHEMA = 'sharkcraft.generated-drift/v1';
|
|
29
31
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
30
32
|
/** Cap on the temp tree read — a runaway regen must not be read into memory whole. */
|
|
@@ -157,7 +159,7 @@ function evaluateRule(cwd, rule, excludeDirs, headersOnly) {
|
|
|
157
159
|
const scan = scanGeneratedFiles(cwd, rule, excludeDirs);
|
|
158
160
|
const severity = rule.severity ?? 'error';
|
|
159
161
|
if (scan.generated.size === 0) {
|
|
160
|
-
const failed = rule
|
|
162
|
+
const failed = failsWhenEmpty(rule);
|
|
161
163
|
return {
|
|
162
164
|
rule,
|
|
163
165
|
status: failed ? 'failed' : 'skipped',
|
|
@@ -235,7 +237,7 @@ async function prepare(args) {
|
|
|
235
237
|
process.stdout.write(asJson({ schema: SCHEMA, error: loaded.message }) + '\n');
|
|
236
238
|
else
|
|
237
239
|
process.stderr.write(`Could not load config: ${loaded.message}\n Run \`shrk doctor\` for details.\n`);
|
|
238
|
-
return { ok: false, code: ExitCode.
|
|
240
|
+
return { ok: false, code: ExitCode.UsageError };
|
|
239
241
|
}
|
|
240
242
|
const id = flagString(args, 'id');
|
|
241
243
|
let rules = loaded.value.rules;
|
|
@@ -245,7 +247,7 @@ async function prepare(args) {
|
|
|
245
247
|
const unknown = wanted.filter((w) => !known.has(w));
|
|
246
248
|
if (unknown.length > 0) {
|
|
247
249
|
process.stderr.write(`Unknown generated-artifact id(s): ${unknown.join(', ')}. Declared: ${[...known].join(', ') || '(none)'}\n`);
|
|
248
|
-
return { ok: false, code: ExitCode.
|
|
250
|
+
return { ok: false, code: ExitCode.UsageError };
|
|
249
251
|
}
|
|
250
252
|
rules = rules.filter((r) => wanted.includes(r.id));
|
|
251
253
|
}
|
|
@@ -329,9 +331,10 @@ export const generatedCheckCommand = {
|
|
|
329
331
|
const outcomes = prep.rules.map((r) => evaluateRule(prep.cwd, r, prep.excludeDirs, headersOnly));
|
|
330
332
|
const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
|
|
331
333
|
const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
|
|
334
|
+
const skippedCount = outcomes.length - evaluated;
|
|
332
335
|
const exit = failed.length > 0
|
|
333
336
|
? ExitCode.Failure
|
|
334
|
-
: evaluated === 0
|
|
337
|
+
: evaluated === 0 || skippedCount > 0
|
|
335
338
|
? ExitCode.NotVerified
|
|
336
339
|
: ExitCode.VerifiedPass;
|
|
337
340
|
if (json) {
|
|
@@ -343,6 +346,33 @@ export const generatedCheckCommand = {
|
|
|
343
346
|
skipped: outcomes.filter((o) => o.status === 'skipped').length,
|
|
344
347
|
verdict: failed.length > 0 ? 'errors' : evaluated === 0 ? 'not-verified' : 'pass',
|
|
345
348
|
diagnostics: prep.planeDiagnostics,
|
|
349
|
+
gate: buildGateEnvelope('generated check', exit, outcomes.map((o) => ({
|
|
350
|
+
id: o.rule.id,
|
|
351
|
+
type: 'generated',
|
|
352
|
+
status: o.status,
|
|
353
|
+
severity: o.rule.severity ?? 'error',
|
|
354
|
+
counts: {
|
|
355
|
+
files: o.committedCount,
|
|
356
|
+
differences: o.treeDiff?.differences.length ?? 0,
|
|
357
|
+
provenance: o.provenance.length,
|
|
358
|
+
},
|
|
359
|
+
violations: [
|
|
360
|
+
...(o.treeDiff?.differences ?? []).map((d) => ({
|
|
361
|
+
id: d.file,
|
|
362
|
+
file: d.file,
|
|
363
|
+
message: d.kind,
|
|
364
|
+
hint: hintFor(o.rule),
|
|
365
|
+
})),
|
|
366
|
+
...o.provenance.map((f) => ({
|
|
367
|
+
id: f.file,
|
|
368
|
+
file: f.file,
|
|
369
|
+
message: f.message,
|
|
370
|
+
hint: hintFor(o.rule),
|
|
371
|
+
})),
|
|
372
|
+
],
|
|
373
|
+
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
374
|
+
...(o.error ? { error: o.error } : {}),
|
|
375
|
+
}))),
|
|
346
376
|
}) + '\n');
|
|
347
377
|
return exit;
|
|
348
378
|
}
|
|
@@ -460,7 +490,7 @@ export const generatedExplainCommand = {
|
|
|
460
490
|
const id = flagString(args, 'id') ?? args.positional[0];
|
|
461
491
|
if (!id) {
|
|
462
492
|
process.stderr.write('Usage: shrk generated explain --id <id>\n');
|
|
463
|
-
return ExitCode.
|
|
493
|
+
return ExitCode.UsageError;
|
|
464
494
|
}
|
|
465
495
|
const prep = await prepare(args);
|
|
466
496
|
if (!prep.ok)
|
|
@@ -468,7 +498,7 @@ export const generatedExplainCommand = {
|
|
|
468
498
|
const rule = prep.all.find((r) => r.id === id);
|
|
469
499
|
if (!rule) {
|
|
470
500
|
process.stderr.write(`No generated-artifact rule "${id}". Declared: ${prep.all.map((r) => r.id).join(', ') || '(none)'}\n`);
|
|
471
|
-
return ExitCode.
|
|
501
|
+
return ExitCode.UsageError;
|
|
472
502
|
}
|
|
473
503
|
// explain never spawns — the point is to show what the rule SEES.
|
|
474
504
|
const scan = scanGeneratedFiles(prep.cwd, rule, prep.excludeDirs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA4H9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAwC1C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe;;;;cAK3C;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM;EA2KpF"}
|
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import { header } from "../output/format-output.js";
|
|
2
2
|
import { COMMAND_CATALOG, defaultShowInHelp, listExplainFamily } from "./command-catalog.js";
|
|
3
|
+
/**
|
|
4
|
+
* Every multi-token command path the catalog documents. These are real,
|
|
5
|
+
* callable verbs that the command trie never sees, because their parent
|
|
6
|
+
* dispatches them from a positional argument rather than registering them.
|
|
7
|
+
*/
|
|
8
|
+
function catalogHelpPaths() {
|
|
9
|
+
const paths = new Set();
|
|
10
|
+
for (const entry of COMMAND_CATALOG) {
|
|
11
|
+
// Strip the flag/argument tail a few catalog entries carry in `command`.
|
|
12
|
+
const clean = entry.command.split(/\s+--/)[0].trim();
|
|
13
|
+
if (clean.length > 0)
|
|
14
|
+
paths.add(clean);
|
|
15
|
+
}
|
|
16
|
+
return paths;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Render help for a command path the trie could not resolve but the catalog
|
|
20
|
+
* documents, plus its sibling verbs under the same parent so the family is
|
|
21
|
+
* discoverable from any one member.
|
|
22
|
+
*/
|
|
23
|
+
function renderCatalogHelp(tokens) {
|
|
24
|
+
const want = tokens.join(' ');
|
|
25
|
+
const entry = COMMAND_CATALOG.find((e) => e.command.split(/\s+--/)[0].trim() === want);
|
|
26
|
+
if (!entry)
|
|
27
|
+
return undefined;
|
|
28
|
+
let out = `${want} — ${entry.description}\n`;
|
|
29
|
+
const extra = EXTRA_HELP_LINES[want];
|
|
30
|
+
if (extra)
|
|
31
|
+
out += extra.join('\n') + '\n';
|
|
32
|
+
const parent = tokens.slice(0, -1).join(' ');
|
|
33
|
+
if (parent.length > 0) {
|
|
34
|
+
const siblings = [...catalogHelpPaths()]
|
|
35
|
+
.filter((p) => p !== want && p.startsWith(parent + ' ') && !p.slice(parent.length + 1).includes(' '))
|
|
36
|
+
.sort();
|
|
37
|
+
if (siblings.length > 0) {
|
|
38
|
+
out += `\nSiblings: ${siblings.join(', ')}\n`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
3
43
|
/** First sentence of a catalog description, for the compact explain-family list. */
|
|
4
44
|
function firstSentence(description) {
|
|
5
45
|
const dot = description.indexOf('. ');
|
|
@@ -156,8 +196,17 @@ export function makeHelpCommand(registry) {
|
|
|
156
196
|
// that exits 0. Error out honestly instead, with a did-you-mean when
|
|
157
197
|
// a real topic is a near-typo of the request.
|
|
158
198
|
const attempt = tokens.join(' ');
|
|
199
|
+
// The trie matched nothing, but the CATALOG may still document this
|
|
200
|
+
// path — a documented verb whose parent isn't a registered command
|
|
201
|
+
// is still real and callable.
|
|
202
|
+
const viaCatalog = renderCatalogHelp(tokens);
|
|
203
|
+
if (viaCatalog !== undefined) {
|
|
204
|
+
process.stdout.write(viaCatalog);
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
159
207
|
process.stderr.write(`no such help topic: '${attempt}'\n`);
|
|
160
|
-
const suggestion = nearestHelpTopic(attempt, realHelpTopics(registry))
|
|
208
|
+
const suggestion = nearestHelpTopic(attempt, realHelpTopics(registry)) ??
|
|
209
|
+
nearestHelpTopic(attempt, catalogHelpPaths());
|
|
161
210
|
if (suggestion)
|
|
162
211
|
process.stderr.write(`Did you mean: ${suggestion}?\n`);
|
|
163
212
|
return 1;
|
|
@@ -199,7 +248,20 @@ export function makeHelpCommand(registry) {
|
|
|
199
248
|
}
|
|
200
249
|
return 0;
|
|
201
250
|
}
|
|
202
|
-
|
|
251
|
+
// The trie could not resolve the full path, but the CATALOG may still
|
|
252
|
+
// document it. Verbs like `check wiring` are dispatched from inside
|
|
253
|
+
// their parent's handler on a positional, so they are real, callable
|
|
254
|
+
// and documented — yet never trie nodes. Falling through to "Unknown
|
|
255
|
+
// command" made an entire documented surface look non-existent.
|
|
256
|
+
const catalogHelp = renderCatalogHelp(tokens);
|
|
257
|
+
if (catalogHelp !== undefined) {
|
|
258
|
+
process.stdout.write(catalogHelp);
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
process.stderr.write(`no such help topic: '${tokens.join(' ')}'\n`);
|
|
262
|
+
const near = nearestHelpTopic(tokens.join(' '), catalogHelpPaths());
|
|
263
|
+
if (near)
|
|
264
|
+
process.stderr.write(`Did you mean: shrk help ${near}?\n`);
|
|
203
265
|
return 1;
|
|
204
266
|
}
|
|
205
267
|
if (!wantsFull) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-lint.command.d.ts","sourceRoot":"","sources":["../../src/commands/policy-lint.command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"policy-lint.command.d.ts","sourceRoot":"","sources":["../../src/commands/policy-lint.command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAQhC,eAAO,MAAM,qBAAqB,EAAG,8BAAuC,CAAC;AAE7E,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,iDAAiD;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,EACjB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,cAAc,CAqBhB;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CA0CtF;AAGD,eAAO,MAAM,wBAAwB,EAAE,eA8BtC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,eAoQ/B,CAAC"}
|
|
@@ -2,6 +2,8 @@ import * as nodePath from 'node:path';
|
|
|
2
2
|
import { runPolicyLint, } from '@shrkcrft/boundaries';
|
|
3
3
|
import { classifyChangedScope, resolveChangedFiles, resolveProjectConfig } from '@shrkcrft/inspector';
|
|
4
4
|
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
5
|
+
import { ExitCode } from "../exit-codes.js";
|
|
6
|
+
import { buildGateEnvelope } from "../gates/gate-envelope.js";
|
|
5
7
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
6
8
|
const VALID_SURFACES = new Set(['template', 'style', 'ts']);
|
|
7
9
|
export const POLICY_EXPLAIN_SCHEMA = 'sharkcraft.policy-explain/v1';
|
|
@@ -86,19 +88,19 @@ export const policyLintExplainCommand = {
|
|
|
86
88
|
const id = args.positional[0] ?? flagString(args, 'id');
|
|
87
89
|
if (!id) {
|
|
88
90
|
process.stderr.write('Usage: shrk policy-lint explain <ruleId> [--json]\n');
|
|
89
|
-
return
|
|
91
|
+
return ExitCode.UsageError;
|
|
90
92
|
}
|
|
91
93
|
const cwd = resolveCwd(args);
|
|
92
94
|
const loaded = await resolveProjectConfig(cwd);
|
|
93
95
|
if (!loaded.ok) {
|
|
94
96
|
process.stderr.write(`Could not load config: ${loaded.error.message}\n`);
|
|
95
|
-
return
|
|
97
|
+
return ExitCode.UsageError;
|
|
96
98
|
}
|
|
97
99
|
const rules = loaded.value.config.policyRules ?? [];
|
|
98
100
|
const rule = rules.find((r) => r.id === id);
|
|
99
101
|
if (!rule) {
|
|
100
102
|
process.stderr.write(`No policy rule "${id}". Configured: ${rules.map((r) => r.id).join(', ') || '(none)'}\n`);
|
|
101
|
-
return
|
|
103
|
+
return ExitCode.UsageError;
|
|
102
104
|
}
|
|
103
105
|
const rel = nodePath.relative(cwd, loaded.value.sharkcraftDir).split(nodePath.sep).join('/');
|
|
104
106
|
const excludeDirs = rel && !rel.startsWith('..') ? [rel] : [];
|
|
@@ -127,7 +129,7 @@ export const policyLintCommand = {
|
|
|
127
129
|
const bad = parts.filter((s) => !VALID_SURFACES.has(s));
|
|
128
130
|
if (bad.length > 0) {
|
|
129
131
|
process.stderr.write(`Unknown --surface "${bad.join(', ')}". Use template | style | ts.\n`);
|
|
130
|
-
return
|
|
132
|
+
return ExitCode.UsageError;
|
|
131
133
|
}
|
|
132
134
|
surfaces = parts;
|
|
133
135
|
}
|
|
@@ -137,11 +139,12 @@ export const policyLintCommand = {
|
|
|
137
139
|
const msg = loaded.error.message;
|
|
138
140
|
if (wantJson) {
|
|
139
141
|
process.stdout.write(asJson({ schema: 'sharkcraft.policy-lint/v1', error: msg, rules: [], findings: [], diagnostics: [msg], evaluated: 0, verdict: 'errors' }) + '\n');
|
|
140
|
-
return
|
|
142
|
+
return ExitCode.UsageError;
|
|
141
143
|
}
|
|
142
144
|
process.stdout.write(header('Policy lint'));
|
|
143
145
|
process.stdout.write(` ✗ Could not load config: ${msg}\n Run \`shrk doctor\` for details.\n`);
|
|
144
|
-
|
|
146
|
+
// A broken config is a USAGE error (3), not "violations found" (1).
|
|
147
|
+
return ExitCode.UsageError;
|
|
145
148
|
}
|
|
146
149
|
const rules = loaded.value.config.policyRules ?? [];
|
|
147
150
|
const planeDiagnostics = loaded.value.planeDiagnostics;
|
|
@@ -162,7 +165,7 @@ export const policyLintCommand = {
|
|
|
162
165
|
const unknown = requested.filter((id) => !known.has(id));
|
|
163
166
|
if (unknown.length > 0) {
|
|
164
167
|
process.stderr.write(`Unknown --only rule id(s): ${unknown.join(', ')}. Configured: ${[...known].join(', ') || '(none)'}\n`);
|
|
165
|
-
return
|
|
168
|
+
return ExitCode.UsageError;
|
|
166
169
|
}
|
|
167
170
|
}
|
|
168
171
|
let changedFiles;
|
|
@@ -218,8 +221,41 @@ export const policyLintCommand = {
|
|
|
218
221
|
report = { ...report, findings: newFindings, verdict };
|
|
219
222
|
}
|
|
220
223
|
if (wantJson) {
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
const exit = report.verdict === 'errors' || report.skipped.some((sk) => sk.failed)
|
|
225
|
+
? ExitCode.Failure
|
|
226
|
+
: report.evaluated === 0 || report.skipped.length > 0
|
|
227
|
+
? ExitCode.NotVerified
|
|
228
|
+
: ExitCode.VerifiedPass;
|
|
229
|
+
process.stdout.write(asJson({
|
|
230
|
+
...report,
|
|
231
|
+
...(newOnly ? { newOnly: true, hiddenBaseline } : {}),
|
|
232
|
+
gate: buildGateEnvelope('policy-lint', exit, report.rules.map((r) => {
|
|
233
|
+
const skip = report.skipped.find((s) => s.ruleId === r.ruleId);
|
|
234
|
+
return {
|
|
235
|
+
id: r.ruleId,
|
|
236
|
+
type: 'policy',
|
|
237
|
+
status: r.status,
|
|
238
|
+
severity: r.severity,
|
|
239
|
+
counts: {
|
|
240
|
+
units: r.unitsScanned,
|
|
241
|
+
findings: r.findingCount,
|
|
242
|
+
suppressed: r.suppressedCount,
|
|
243
|
+
},
|
|
244
|
+
violations: report.findings
|
|
245
|
+
.filter((f) => f.ruleId === r.ruleId)
|
|
246
|
+
.map((f) => ({
|
|
247
|
+
id: f.match,
|
|
248
|
+
file: f.file,
|
|
249
|
+
line: f.line,
|
|
250
|
+
message: f.message,
|
|
251
|
+
...(f.suggest ? { hint: f.suggest } : {}),
|
|
252
|
+
})),
|
|
253
|
+
...(skip ? { skipReason: skip.reason } : {}),
|
|
254
|
+
...(r.error ? { error: r.error } : {}),
|
|
255
|
+
};
|
|
256
|
+
})),
|
|
257
|
+
}) + '\n');
|
|
258
|
+
return exit;
|
|
223
259
|
}
|
|
224
260
|
process.stdout.write(header('Policy lint'));
|
|
225
261
|
// `evaluated` counts rules that actually scanned ≥1 file. When 0 rules
|
|
@@ -254,9 +290,15 @@ export const policyLintCommand = {
|
|
|
254
290
|
process.stdout.write(` ! ${d}\n`);
|
|
255
291
|
}
|
|
256
292
|
if (report.skipped.some((sk) => sk.failed)) {
|
|
257
|
-
process.stdout.write('\nA rule
|
|
293
|
+
process.stdout.write('\nA rule that matched nothing is a bug in the rule, not a pass. (`error`-severity\n' +
|
|
294
|
+
' rules fail on empty by default — set `failOnEmpty: false` if the set may be empty.)\n');
|
|
258
295
|
return 1;
|
|
259
296
|
}
|
|
297
|
+
// A non-failing skip still means "partially verified" — never a green 0.
|
|
298
|
+
if (report.skipped.length > 0 && report.findings.length === 0) {
|
|
299
|
+
process.stdout.write(`\n${report.skipped.length} rule(s) scanned nothing — partially verified, not a full green.\n`);
|
|
300
|
+
return 2;
|
|
301
|
+
}
|
|
260
302
|
if (report.findings.length === 0 && report.diagnostics.length === 0) {
|
|
261
303
|
process.stdout.write(newOnly
|
|
262
304
|
? `\nNo NEW policy violations from this change${hiddenBaseline > 0 ? ` (${hiddenBaseline} pre-existing hidden)` : ''}. ✓\n`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.command.d.ts","sourceRoot":"","sources":["../../src/commands/registry.command.ts"],"names":[],"mappings":"AA8BA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.command.d.ts","sourceRoot":"","sources":["../../src/commands/registry.command.ts"],"names":[],"mappings":"AA8BA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAQhC,eAAO,MAAM,wBAAwB,EAAE,eA8CtC,CAAC;AAkMF,eAAO,MAAM,eAAe,EAAE,eA0C7B,CAAC"}
|
|
@@ -20,6 +20,8 @@ import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
|
20
20
|
import { ExitCode } from "../exit-codes.js";
|
|
21
21
|
import { asJson } from "../output/format-output.js";
|
|
22
22
|
import { resolveRegistryNoun } from "./registry-resolve.js";
|
|
23
|
+
/** The inventory verbs, used to detect (and forgive) a verb-first invocation. */
|
|
24
|
+
const INVENTORY_VERBS = new Set(['list', 'exists', 'where', 'duplicates']);
|
|
23
25
|
export const registryLifecycleCommand = {
|
|
24
26
|
name: 'lifecycle',
|
|
25
27
|
description: 'Scan the workspace for register/remove symmetry. Read-only.',
|
|
@@ -98,11 +100,20 @@ async function runRegistryInventory(args, name) {
|
|
|
98
100
|
if (!decl) {
|
|
99
101
|
const names = loaded.registries.map((r) => r.name);
|
|
100
102
|
const avail = names.length > 0 ? `Declared registries: ${names.join(', ')}.` : 'No registries declared in sharkcraft.config.ts `registries[]`.';
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
// Typing the verb first is the common stumble (siblings are verb-first).
|
|
104
|
+
// Name the correct grammar instead of only reporting the miss.
|
|
105
|
+
const verbFirst = INVENTORY_VERBS.has(name);
|
|
106
|
+
if (json) {
|
|
107
|
+
process.stdout.write(asJson({ error: `unknown registry "${name}"`, available: names, ...(verbFirst ? { hint: `did you mean 'registry <name> ${name}'?` } : {}) }) + '\n');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
104
110
|
process.stderr.write(`No registry named "${name}". ${avail}\n`);
|
|
105
|
-
|
|
111
|
+
if (verbFirst) {
|
|
112
|
+
const example = names[0] ?? '<name>';
|
|
113
|
+
process.stderr.write(`"${name}" is a verb, not a registry — did you mean \`shrk registry ${example} ${name}\`?\n`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return ExitCode.UsageError;
|
|
106
117
|
}
|
|
107
118
|
const inventory = scanRegistry(cwd, decl);
|
|
108
119
|
if (action === 'list' || action === undefined) {
|
|
@@ -125,13 +136,13 @@ async function runRegistryInventory(args, name) {
|
|
|
125
136
|
if (action === 'exists') {
|
|
126
137
|
if (!id) {
|
|
127
138
|
process.stderr.write(`Usage: shrk registry ${name} exists <id> [--resolve] [--fail-if-taken|--fail-if-missing]\n`);
|
|
128
|
-
return
|
|
139
|
+
return ExitCode.UsageError;
|
|
129
140
|
}
|
|
130
141
|
const failIfTaken = flagBool(args, 'fail-if-taken');
|
|
131
142
|
const failIfMissing = flagBool(args, 'fail-if-missing');
|
|
132
143
|
if (failIfTaken && failIfMissing) {
|
|
133
144
|
process.stderr.write('Pass at most one of --fail-if-taken / --fail-if-missing.\n');
|
|
134
|
-
return
|
|
145
|
+
return ExitCode.UsageError;
|
|
135
146
|
}
|
|
136
147
|
// `--resolve` maps a human noun to the canonical registered id before the
|
|
137
148
|
// existence test — via the registry's declared `aliases` map AND generic
|
|
@@ -201,7 +212,7 @@ async function runRegistryInventory(args, name) {
|
|
|
201
212
|
if (action === 'where') {
|
|
202
213
|
if (!id) {
|
|
203
214
|
process.stderr.write(`Usage: shrk registry ${name} where <id>\n`);
|
|
204
|
-
return
|
|
215
|
+
return ExitCode.UsageError;
|
|
205
216
|
}
|
|
206
217
|
const entry = registryWhere(inventory, id);
|
|
207
218
|
if (json) {
|
|
@@ -220,7 +231,7 @@ async function runRegistryInventory(args, name) {
|
|
|
220
231
|
return 0;
|
|
221
232
|
}
|
|
222
233
|
process.stderr.write(`Unknown action "${action}". Usage: shrk registry ${name} list | exists <id> | where <id> | duplicates\n`);
|
|
223
|
-
return
|
|
234
|
+
return ExitCode.UsageError;
|
|
224
235
|
}
|
|
225
236
|
export const registryCommand = {
|
|
226
237
|
name: 'registry',
|
|
@@ -237,7 +248,21 @@ export const registryCommand = {
|
|
|
237
248
|
return registryLifecycleCommand.run(args);
|
|
238
249
|
}
|
|
239
250
|
if (sub !== undefined && sub.length > 0) {
|
|
240
|
-
//
|
|
251
|
+
// Grammar is `registry <name> <verb>`, but `baseline`/`generated` read
|
|
252
|
+
// verb-first, so `registry list <name>` is the instinctive form. Accept
|
|
253
|
+
// BOTH: when arg1 is a known verb and arg2 names a declared registry,
|
|
254
|
+
// swap them. The canonical order is unchanged; the stumble is removed.
|
|
255
|
+
const second = args.positional[1];
|
|
256
|
+
if (INVENTORY_VERBS.has(sub) && second !== undefined && second.length > 0) {
|
|
257
|
+
const loaded = await loadRegistries(resolveCwd(args));
|
|
258
|
+
const known = loaded.ok && findRegistry(loaded.registries, second) !== undefined;
|
|
259
|
+
if (known) {
|
|
260
|
+
// [verb, name, ...rest] → [name, verb, ...rest]
|
|
261
|
+
args.positional = [second, sub, ...args.positional.slice(2)];
|
|
262
|
+
return runRegistryInventory(args, second);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// `<name> list | exists <id> | where <id> | duplicates` — sub is the name.
|
|
241
266
|
return runRegistryInventory(args, sub);
|
|
242
267
|
}
|
|
243
268
|
const cwd = resolveCwd(args);
|
package/dist/exit-codes.d.ts
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
|
|
@@ -27,7 +34,8 @@
|
|
|
27
34
|
export declare enum ExitCode {
|
|
28
35
|
VerifiedPass = 0,
|
|
29
36
|
Failure = 1,
|
|
30
|
-
NotVerified = 2
|
|
37
|
+
NotVerified = 2,
|
|
38
|
+
UsageError = 3
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
41
|
* Promote a NotVerified (`2`) exit into a Failure-class nonzero (`1`) when the
|
|
@@ -61,12 +69,24 @@ export declare function argvHasExitTrailer(argv: readonly string[]): boolean;
|
|
|
61
69
|
* so `check boundaries --json` (2 tokens) and a bare `finish` (1) both resolve.
|
|
62
70
|
*/
|
|
63
71
|
export declare function isGateVerb(commandPath: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* True when the argv carries the global `--no-hints` (before the `--`
|
|
74
|
+
* sentinel). Suppresses advisory one-liners like the piped-exit note while
|
|
75
|
+
* leaving every real diagnostic — and the `--exit-trailer` machine channel —
|
|
76
|
+
* untouched. For a caller that has already internalised the warning and just
|
|
77
|
+
* wants clean stderr in captured logs.
|
|
78
|
+
*/
|
|
79
|
+
export declare function argvHasNoHints(argv: readonly string[]): boolean;
|
|
80
|
+
/** Reset the one-time hint latch. Test-only seam. */
|
|
81
|
+
export declare function resetPipeHintLatch(): void;
|
|
64
82
|
/** Injectable surface for {@link emitPipeExitSignal} (isTTY + writer + trailer). */
|
|
65
83
|
export interface IPipeExitOptions {
|
|
66
84
|
/** True when shrk's stdout is NOT a terminal (i.e. piped/redirected). */
|
|
67
85
|
readonly piped: boolean;
|
|
68
86
|
/** True when `--exit-trailer` was requested. */
|
|
69
87
|
readonly trailer: boolean;
|
|
88
|
+
/** True when `--no-hints` was requested — suppress the advisory note only. */
|
|
89
|
+
readonly noHints?: boolean;
|
|
70
90
|
/** stderr writer; defaults to `process.stderr.write`. Overridable for tests. */
|
|
71
91
|
readonly write?: (s: string) => void;
|
|
72
92
|
}
|
package/dist/exit-codes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exit-codes.d.ts","sourceRoot":"","sources":["../src/exit-codes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"exit-codes.d.ts","sourceRoot":"","sources":["../src/exit-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,oBAAY,QAAQ;IAClB,YAAY,IAAI;IAChB,OAAO,IAAI;IACX,WAAW,IAAI;IACf,UAAU,IAAI;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAGtE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAM9D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAMnE;AA4BD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAMvD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAM/D;AASD,qDAAqD;AACrD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,GACrB,IAAI,CAgBN"}
|
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)
|
|
@@ -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"}
|