@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseline.command.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"baseline.command.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.command.ts"],"names":[],"mappings":"AA8BA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA6ShC,eAAO,MAAM,mBAAmB,EAAE,eAyCjC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAyGlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eA8BjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAwDnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA2DpC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,eAc7B,CAAC"}
|
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
import { spawnSync } from 'node:child_process';
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
19
|
import * as nodePath from 'node:path';
|
|
20
|
+
import { failsWhenEmpty } from '@shrkcrft/core';
|
|
20
21
|
import { baselineCount, baselineFails, computeBaselineFromExtractor, diffBaseline, matchesAny, } from '@shrkcrft/boundaries';
|
|
21
22
|
import { resolveChangedFiles, resolveProjectConfig } from '@shrkcrft/inspector';
|
|
22
23
|
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
23
24
|
import { ExitCode } from "../exit-codes.js";
|
|
24
25
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
26
|
+
import { buildGateEnvelope } from "../gates/gate-envelope.js";
|
|
25
27
|
const SCHEMA = 'sharkcraft.baseline/v1';
|
|
26
28
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
27
29
|
async function loadBaselines(cwd) {
|
|
@@ -116,12 +118,19 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
116
118
|
}
|
|
117
119
|
const abs = nodePath.resolve(cwd, rule.baseline);
|
|
118
120
|
if (!existsSync(abs)) {
|
|
121
|
+
// No artifact yet. `check` must still fail (nothing to compare against),
|
|
122
|
+
// but the CURRENT side is knowable and is exactly what the author needs to
|
|
123
|
+
// see before blessing — so compute it rather than reporting a false `0`.
|
|
124
|
+
const first = computeCurrent(cwd, rule, excludeDirs);
|
|
119
125
|
return {
|
|
120
126
|
rule,
|
|
121
127
|
status: 'error',
|
|
122
128
|
committedCount: 0,
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
...(first.error ? {} : { current: first.text }),
|
|
130
|
+
currentCount: first.error ? 0 : baselineCount(rule, first.text),
|
|
131
|
+
missingBaseline: true,
|
|
132
|
+
error: first.error ??
|
|
133
|
+
`committed baseline ${rule.baseline} does not exist — create it with \`shrk baseline update --id ${rule.id}\``,
|
|
125
134
|
};
|
|
126
135
|
}
|
|
127
136
|
let committed;
|
|
@@ -151,7 +160,7 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
151
160
|
if (currentCount === 0 && committedCount === 0) {
|
|
152
161
|
return {
|
|
153
162
|
rule,
|
|
154
|
-
status: rule
|
|
163
|
+
status: failsWhenEmpty(rule) ? 'failed' : 'skipped',
|
|
155
164
|
committed,
|
|
156
165
|
current: computed.text,
|
|
157
166
|
committedCount,
|
|
@@ -190,6 +199,7 @@ function outcomeJson(o) {
|
|
|
190
199
|
...(o.error ? { error: o.error } : {}),
|
|
191
200
|
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
192
201
|
...(o.emptyCompute ? { emptyCompute: true } : {}),
|
|
202
|
+
...(o.missingBaseline ? { missingBaseline: true } : {}),
|
|
193
203
|
hint: hintFor(o.rule),
|
|
194
204
|
};
|
|
195
205
|
}
|
|
@@ -216,12 +226,12 @@ async function prepare(args, opts) {
|
|
|
216
226
|
process.stdout.write(asJson({ schema: SCHEMA, error: loaded.message }) + '\n');
|
|
217
227
|
else
|
|
218
228
|
process.stderr.write(`Could not load config: ${loaded.message}\n Run \`shrk doctor\` for details.\n`);
|
|
219
|
-
return { ok: false, code: ExitCode.
|
|
229
|
+
return { ok: false, code: ExitCode.UsageError };
|
|
220
230
|
}
|
|
221
231
|
const selected = selectRules(loaded.value.rules, flagString(args, 'id') ?? undefined);
|
|
222
232
|
if (!selected.ok) {
|
|
223
233
|
process.stderr.write(selected.message + '\n');
|
|
224
|
-
return { ok: false, code: ExitCode.
|
|
234
|
+
return { ok: false, code: ExitCode.UsageError };
|
|
225
235
|
}
|
|
226
236
|
let changedFiles;
|
|
227
237
|
if (opts.changedAware) {
|
|
@@ -314,9 +324,11 @@ export const baselineCheckCommand = {
|
|
|
314
324
|
const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
|
|
315
325
|
const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
|
|
316
326
|
// 0 only when a NON-EMPTY scope was actually compared; 2 when nothing was.
|
|
327
|
+
// A skipped baseline is "partially verified", never a green 0.
|
|
328
|
+
const skippedCount = outcomes.filter((o) => o.status === 'skipped').length;
|
|
317
329
|
const exit = failed.length > 0
|
|
318
330
|
? ExitCode.Failure
|
|
319
|
-
: evaluated === 0
|
|
331
|
+
: evaluated === 0 || skippedCount > 0
|
|
320
332
|
? ExitCode.NotVerified
|
|
321
333
|
: ExitCode.VerifiedPass;
|
|
322
334
|
if (json) {
|
|
@@ -327,6 +339,19 @@ export const baselineCheckCommand = {
|
|
|
327
339
|
skipped: outcomes.filter((o) => o.status === 'skipped').length,
|
|
328
340
|
verdict: failed.length > 0 ? 'errors' : evaluated === 0 ? 'not-verified' : 'pass',
|
|
329
341
|
diagnostics: prep.planeDiagnostics,
|
|
342
|
+
gate: buildGateEnvelope('baseline check', exit, outcomes.map((o) => ({
|
|
343
|
+
id: o.rule.id,
|
|
344
|
+
type: 'baseline',
|
|
345
|
+
status: o.status,
|
|
346
|
+
severity: o.rule.severity ?? 'error',
|
|
347
|
+
counts: { committed: o.committedCount, current: o.currentCount },
|
|
348
|
+
violations: [
|
|
349
|
+
...(o.diff?.added ?? []).map((id) => ({ id, message: 'added', hint: hintFor(o.rule) })),
|
|
350
|
+
...(o.diff?.removed ?? []).map((id) => ({ id, message: 'removed', hint: hintFor(o.rule) })),
|
|
351
|
+
],
|
|
352
|
+
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
353
|
+
...(o.error ? { error: o.error } : {}),
|
|
354
|
+
}))),
|
|
330
355
|
}) + '\n');
|
|
331
356
|
return exit;
|
|
332
357
|
}
|
|
@@ -343,6 +368,10 @@ export const baselineCheckCommand = {
|
|
|
343
368
|
}
|
|
344
369
|
if (o.status === 'error') {
|
|
345
370
|
process.stdout.write(` ! ${o.rule.id} ${o.error}\n`);
|
|
371
|
+
if (o.missingBaseline && o.currentCount > 0) {
|
|
372
|
+
process.stdout.write(` the compute currently yields ${o.currentCount} entr${o.currentCount === 1 ? 'y' : 'ies'} — ` +
|
|
373
|
+
`run \`shrk baseline update --id ${o.rule.id}\` to bless them.\n`);
|
|
374
|
+
}
|
|
346
375
|
continue;
|
|
347
376
|
}
|
|
348
377
|
const added = o.diff?.added.length ?? 0;
|
|
@@ -414,7 +443,7 @@ export const baselineUpdateCommand = {
|
|
|
414
443
|
errors.push({ id: rule.id, error: computed.error });
|
|
415
444
|
continue;
|
|
416
445
|
}
|
|
417
|
-
if (computed.text.trim() === '' && rule
|
|
446
|
+
if (computed.text.trim() === '' && failsWhenEmpty(rule)) {
|
|
418
447
|
errors.push({
|
|
419
448
|
id: rule.id,
|
|
420
449
|
error: 'refusing to write an EMPTY baseline for a `failOnEmpty` rule — fix the compute first',
|
|
@@ -455,7 +484,7 @@ export const baselineExplainCommand = {
|
|
|
455
484
|
const id = flagString(args, 'id') ?? args.positional[0];
|
|
456
485
|
if (!id) {
|
|
457
486
|
process.stderr.write('Usage: shrk baseline explain --id <id>\n');
|
|
458
|
-
return ExitCode.
|
|
487
|
+
return ExitCode.UsageError;
|
|
459
488
|
}
|
|
460
489
|
const prep = await prepare(args, { changedAware: false });
|
|
461
490
|
if (!prep.ok)
|
|
@@ -463,7 +492,7 @@ export const baselineExplainCommand = {
|
|
|
463
492
|
const rule = prep.all.find((r) => r.id === id);
|
|
464
493
|
if (!rule) {
|
|
465
494
|
process.stderr.write(`No baseline "${id}". Declared: ${prep.all.map((r) => r.id).join(', ') || '(none)'}\n`);
|
|
466
|
-
return ExitCode.
|
|
495
|
+
return ExitCode.UsageError;
|
|
467
496
|
}
|
|
468
497
|
const outcome = evaluateRule(prep.cwd, rule, prep.excludeDirs, undefined);
|
|
469
498
|
if (flagBool(args, 'json')) {
|
|
@@ -484,7 +513,9 @@ export const baselineExplainCommand = {
|
|
|
484
513
|
process.stdout.write(kv('canonical', outcome.diff?.canonical ?? rule.compute.canonical ?? 'auto') + '\n');
|
|
485
514
|
if (rule.keyBy)
|
|
486
515
|
process.stdout.write(kv('keyBy', rule.keyBy) + '\n');
|
|
487
|
-
process.stdout.write(kv('entries',
|
|
516
|
+
process.stdout.write(kv('entries', outcome.missingBaseline
|
|
517
|
+
? `committed (none yet) → ${outcome.currentCount} now`
|
|
518
|
+
: `${outcome.committedCount} committed → ${outcome.currentCount} now`) + '\n');
|
|
488
519
|
process.stdout.write(kv('status', outcome.status) + '\n');
|
|
489
520
|
if (outcome.error)
|
|
490
521
|
process.stdout.write(` ! ${outcome.error}\n`);
|
|
@@ -506,6 +537,6 @@ export const baselineCommand = {
|
|
|
506
537
|
const sub = args.positional[0];
|
|
507
538
|
process.stderr.write((sub ? `Unknown subcommand "${sub}". ` : '') +
|
|
508
539
|
'Usage: shrk baseline list | check [--id X] | diff | update | explain --id <id>\n');
|
|
509
|
-
return ExitCode.
|
|
540
|
+
return ExitCode.UsageError;
|
|
510
541
|
},
|
|
511
542
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changelog-data.d.ts","sourceRoot":"","sources":["../../src/commands/changelog-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,eAAO,MAAM,sBAAsB,EAAE,SAAS,oBAAoB,
|
|
1
|
+
{"version":3,"file":"changelog-data.d.ts","sourceRoot":"","sources":["../../src/commands/changelog-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,eAAO,MAAM,sBAAsB,EAAE,SAAS,oBAAoB,EAkJjE,CAAC"}
|
|
@@ -134,4 +134,24 @@ export const RELEASE_SURFACE_DELTAS = [
|
|
|
134
134
|
],
|
|
135
135
|
removed: [],
|
|
136
136
|
},
|
|
137
|
+
{
|
|
138
|
+
version: '0.1.0-alpha.29',
|
|
139
|
+
title: 'The exit code has to agree with the sentence',
|
|
140
|
+
added: [
|
|
141
|
+
'`shrk check wiring --fix` — deterministic autofix for the mechanically-unambiguous `declared-but-not-registered` case: append the missing id to its sink array. Dry-run by default, `--write` applies. Refuses ambiguity (N sinks, non-array sink, non-unique file/array, chain rules, parity violations) and lists what it left alone WITH the reason — it never guesses.',
|
|
142
|
+
'`shrk explain <ruleId>` resolves a rule id across ALL planes and dispatches to the right explainer; a non-rule token still gets the original topic search. The per-plane forms remain.',
|
|
143
|
+
'A shared `gate` envelope (`sharkcraft.gate/v1`) inside every gate verb\'s `--json`: `verb` / `exit` / normalized per-rule `{id,type,status,severity,counts,violations,skipReason}`. Additive — the per-plane payloads are unchanged. See docs/gate-json.md.',
|
|
144
|
+
'Global `--no-hints` silences the advisory piped-exit note (the structured `--exit-trailer` channel is unaffected).',
|
|
145
|
+
'Exit code `3` = usage error (unloadable config / unknown rule id / bad flag value) on the gate verbs, split out of `2` so "the gate proved nothing" and "the gate never started" are distinguishable.',
|
|
146
|
+
],
|
|
147
|
+
changed: [
|
|
148
|
+
'A SKIPPED rule is no longer masked by a passing sibling. `failOnEmpty` now defaults to TRUE for `error`-severity rules (an error rule matching zero subjects is a bug in the rule, not a pass) and any skip with no failures exits `2`, never `0`. `warning`-severity rules still default to `false`. BEHAVIOUR CHANGE: an error-severity rule that matches nothing now exits `1` — fix the selector or set `failOnEmpty: false`.',
|
|
149
|
+
'`shrk help <multi-word verb>` resolves catalog-documented paths (`check wiring`, `wiring unprovided`, …) that are dispatched from a parent handler and therefore never appear in the command trie; it also lists sibling verbs. Previously answered "Unknown command".',
|
|
150
|
+
'`shrk baseline explain` always computes the CURRENT side, so a rule with no committed artifact yet reports `committed (none yet) → N now` instead of a false `0 now`. `baseline check` names what would be blessed.',
|
|
151
|
+
'`shrk registry` accepts both argument orders — `registry list <name>` now works alongside `registry <name> list`; a verb-first call with an unknown name names the correct grammar.',
|
|
152
|
+
'`shrk policy-lint` returns `3` (usage) rather than `1` (violations) when the config cannot be loaded, and `2` when it scanned nothing.',
|
|
153
|
+
'The piped-exit note is emitted at most once per process.',
|
|
154
|
+
],
|
|
155
|
+
removed: [],
|
|
156
|
+
},
|
|
137
157
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.command.d.ts","sourceRoot":"","sources":["../../src/commands/check.command.ts"],"names":[],"mappings":"AAoBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"check.command.d.ts","sourceRoot":"","sources":["../../src/commands/check.command.ts"],"names":[],"mappings":"AAoBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA2iChC,eAAO,MAAM,YAAY,EAAE,eA+G1B,CAAC"}
|
|
@@ -9,7 +9,8 @@ import { computeDeletedOrphans } from "../diff/deleted-orphans.js";
|
|
|
9
9
|
import { renderWiringExplain } from "./wiring.command.js";
|
|
10
10
|
import { validateTemplateVariables } from '@shrkcrft/templates';
|
|
11
11
|
import { FileChangeType, planGeneration } from '@shrkcrft/generator';
|
|
12
|
-
import { evaluateBoundaries, explainWiring, loadTsconfigPaths, runWiring, scanImports, summarizeImports, } from '@shrkcrft/boundaries';
|
|
12
|
+
import { evaluateBoundaries, explainWiring, loadTsconfigPaths, runWiring, scanImports, summarizeImports, planWiringFix, readMatchingFiles, wiringGlobsOf, } from '@shrkcrft/boundaries';
|
|
13
|
+
import { buildGateEnvelope } from "../gates/gate-envelope.js";
|
|
13
14
|
function knowledgeGroup(inspection) {
|
|
14
15
|
const dup = inspection.validationIssues.filter((i) => i.code === 'duplicate-id');
|
|
15
16
|
const missing = inspection.validationIssues.filter((i) => i.code !== 'duplicate-id');
|
|
@@ -532,6 +533,93 @@ const WIRING_CHECK_USAGE = 'shrk check wiring [--changed-only] [--since <ref>] [
|
|
|
532
533
|
' --only <ids> run only these rule ids (comma-separated)\n' +
|
|
533
534
|
' --explain <id> dry-run ONE rule and print the declared/registered sets it extracts\n' +
|
|
534
535
|
' Exit: 0 verified pass · 1 violations · 2 not-verified (0 rules evaluated in scope).';
|
|
536
|
+
/**
|
|
537
|
+
* The honest exit code for a wiring run.
|
|
538
|
+
*
|
|
539
|
+
* The banner already said "Not a full green" when some rules were skipped, but
|
|
540
|
+
* the code an agent chains on returned `0` — a rule that enforced NOTHING was
|
|
541
|
+
* masked by its passing siblings. The verdict must match the sentence:
|
|
542
|
+
*
|
|
543
|
+
* `1` violations (or a `failOnEmpty` skip, which the engine folds into the verdict)
|
|
544
|
+
* `2` nothing evaluated, OR anything skipped — partially verified is not verified
|
|
545
|
+
* `0` only when every configured rule actually ran and passed
|
|
546
|
+
*/
|
|
547
|
+
function wiringExitCode(report, evaluated) {
|
|
548
|
+
if (report.verdict === 'errors')
|
|
549
|
+
return ExitCode.Failure;
|
|
550
|
+
if (evaluated === 0)
|
|
551
|
+
return ExitCode.NotVerified;
|
|
552
|
+
if (report.skipped.length > 0)
|
|
553
|
+
return ExitCode.NotVerified;
|
|
554
|
+
return ExitCode.VerifiedPass;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* `check wiring --fix` — append a declared-but-unregistered token to its sink
|
|
558
|
+
* array, but ONLY where the edit is mechanically unambiguous.
|
|
559
|
+
*
|
|
560
|
+
* Dry-run by default: it prints the exact diff it would make and writes
|
|
561
|
+
* nothing. `--write` applies it. Anything the planner will not touch is listed
|
|
562
|
+
* with the reason, because a gate that silently half-fixes is worse than one
|
|
563
|
+
* that does nothing — the user must be able to see what was left.
|
|
564
|
+
*/
|
|
565
|
+
function runWiringFix(cwd, rules, report, write, wantJson) {
|
|
566
|
+
const allEdits = [];
|
|
567
|
+
const allSkips = [];
|
|
568
|
+
// Read every file a sink could live in, once.
|
|
569
|
+
const globs = [...new Set(rules.flatMap((r) => wiringGlobsOf(r)))];
|
|
570
|
+
const cache = readMatchingFiles(cwd, globs, new Set());
|
|
571
|
+
const files = [...cache.entries()].map(([path, content]) => ({ path, content }));
|
|
572
|
+
for (const rule of rules) {
|
|
573
|
+
const violations = report.violations.filter((v) => v.ruleId === rule.id);
|
|
574
|
+
if (violations.length === 0)
|
|
575
|
+
continue;
|
|
576
|
+
const plan = planWiringFix(rule, violations, files);
|
|
577
|
+
allEdits.push(...plan.edits);
|
|
578
|
+
allSkips.push(...plan.skipped);
|
|
579
|
+
}
|
|
580
|
+
// One write per file: the planner threads each file's content forward, so the
|
|
581
|
+
// LAST edit for a path carries every earlier insertion.
|
|
582
|
+
const finalByFile = new Map();
|
|
583
|
+
for (const e of allEdits)
|
|
584
|
+
finalByFile.set(e.file, e.nextContent);
|
|
585
|
+
let written = 0;
|
|
586
|
+
if (write) {
|
|
587
|
+
for (const [rel, content] of finalByFile) {
|
|
588
|
+
writeFileSync(nodePath.resolve(cwd, rel), content, 'utf8');
|
|
589
|
+
written += 1;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (wantJson) {
|
|
593
|
+
process.stdout.write(asJson({
|
|
594
|
+
schema: 'sharkcraft.wiring-fix/v1',
|
|
595
|
+
applied: write,
|
|
596
|
+
filesWritten: written,
|
|
597
|
+
edits: allEdits.map(({ nextContent: _drop, ...rest }) => rest),
|
|
598
|
+
skipped: allSkips,
|
|
599
|
+
}) + '\n');
|
|
600
|
+
return allEdits.length === 0 && allSkips.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
|
|
601
|
+
}
|
|
602
|
+
process.stdout.write(header(write ? 'Wiring fix (applied)' : 'Wiring fix (dry run)'));
|
|
603
|
+
if (allEdits.length === 0 && allSkips.length === 0) {
|
|
604
|
+
process.stdout.write(' Nothing to fix — no declared-but-unregistered tokens.\n');
|
|
605
|
+
return ExitCode.VerifiedPass;
|
|
606
|
+
}
|
|
607
|
+
for (const e of allEdits) {
|
|
608
|
+
process.stdout.write(` ${write ? 'wrote ' : 'would add'} ${e.token} → ${e.file}:${e.line}\n`);
|
|
609
|
+
process.stdout.write(` + ${e.insert}\n`);
|
|
610
|
+
}
|
|
611
|
+
if (allSkips.length > 0) {
|
|
612
|
+
process.stdout.write(`\n Left untouched (${allSkips.length}) — not mechanically unambiguous:\n`);
|
|
613
|
+
for (const sk of allSkips) {
|
|
614
|
+
process.stdout.write(` • ${sk.token} [${sk.reason}] ${sk.detail}\n`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
process.stdout.write(write
|
|
618
|
+
? `\n${written} file(s) written. Re-run \`shrk check wiring\` to confirm, and review the diff.\n`
|
|
619
|
+
: '\nDry run — nothing written. Re-run with `--write` to apply.\n');
|
|
620
|
+
// An unfixable violation still means the gate is red.
|
|
621
|
+
return allSkips.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
|
|
622
|
+
}
|
|
535
623
|
async function checkWiring(args) {
|
|
536
624
|
const cwd = resolveCwd(args);
|
|
537
625
|
// `--help`/`-h` on the subverb documents its own scoping flags instead of
|
|
@@ -570,7 +658,7 @@ async function checkWiring(args) {
|
|
|
570
658
|
const explainId = flagString(args, 'explain');
|
|
571
659
|
if (!explainId) {
|
|
572
660
|
process.stderr.write('Usage: shrk check wiring --explain <ruleId> [--json]\n');
|
|
573
|
-
return
|
|
661
|
+
return ExitCode.UsageError;
|
|
574
662
|
}
|
|
575
663
|
const rule = rules.find((r) => r.id === explainId);
|
|
576
664
|
if (!rule) {
|
|
@@ -580,7 +668,7 @@ async function checkWiring(args) {
|
|
|
580
668
|
return 2;
|
|
581
669
|
}
|
|
582
670
|
process.stderr.write(`No wiring rule "${explainId}". Configured rules: ${ids.length > 0 ? ids.join(', ') : '(none)'}\n`);
|
|
583
|
-
return
|
|
671
|
+
return ExitCode.UsageError;
|
|
584
672
|
}
|
|
585
673
|
return renderWiringExplain(explainWiring(cwd, rule), wantJson);
|
|
586
674
|
}
|
|
@@ -637,6 +725,10 @@ async function checkWiring(args) {
|
|
|
637
725
|
// blast radius, not just the rules whose literal files were edited (a25 §3.2).
|
|
638
726
|
const selectedRuleIds = report.rules.map((r) => r.ruleId);
|
|
639
727
|
const scoped = changedOnly || since !== undefined;
|
|
728
|
+
// ── --fix: deterministic, heavily-guarded autofix ─────────────────────
|
|
729
|
+
if (flagBool(args, 'fix')) {
|
|
730
|
+
return runWiringFix(cwd, rules, report, flagBool(args, 'write'), wantJson);
|
|
731
|
+
}
|
|
640
732
|
if (wantJson) {
|
|
641
733
|
// Carry the honest counts so a machine consumer can tell "0 evaluated" from
|
|
642
734
|
// a real green, and see how many rules the scope skipped + which fired.
|
|
@@ -650,15 +742,31 @@ async function checkWiring(args) {
|
|
|
650
742
|
notVerified,
|
|
651
743
|
selectedRuleIds,
|
|
652
744
|
// Distinguish verified-pass / failure / not-verified for a chained gate.
|
|
653
|
-
exitCode:
|
|
745
|
+
exitCode: wiringExitCode(report, evaluated),
|
|
746
|
+
// One shape across every plane — see docs/gate-json.md.
|
|
747
|
+
gate: buildGateEnvelope('check wiring', wiringExitCode(report, evaluated), report.rules.map((r) => {
|
|
748
|
+
const skip = report.skipped.find((s) => s.ruleId === r.ruleId);
|
|
749
|
+
return {
|
|
750
|
+
id: r.ruleId,
|
|
751
|
+
type: 'wiring',
|
|
752
|
+
status: r.status,
|
|
753
|
+
severity: r.severity,
|
|
754
|
+
counts: { declared: r.declaredCount, registered: r.registeredCount },
|
|
755
|
+
violations: r.violations.map((v) => ({
|
|
756
|
+
id: v.token,
|
|
757
|
+
file: v.file,
|
|
758
|
+
line: v.line,
|
|
759
|
+
...(v.message ? { message: v.message } : {}),
|
|
760
|
+
...(v.hint ? { hint: v.hint } : {}),
|
|
761
|
+
})),
|
|
762
|
+
...(skip ? { skipReason: skip.reason } : {}),
|
|
763
|
+
...(r.error ? { error: r.error } : {}),
|
|
764
|
+
};
|
|
765
|
+
})),
|
|
654
766
|
}) + '\n');
|
|
655
767
|
// `evaluated === 0` = nothing checked in scope → NOT verified (`2`), never a
|
|
656
768
|
// silent `0` an agent's `&& next` would march past (a25 §1.1).
|
|
657
|
-
return evaluated
|
|
658
|
-
? ExitCode.NotVerified
|
|
659
|
-
: report.verdict === 'errors'
|
|
660
|
-
? ExitCode.Failure
|
|
661
|
-
: ExitCode.VerifiedPass;
|
|
769
|
+
return wiringExitCode(report, evaluated);
|
|
662
770
|
}
|
|
663
771
|
process.stdout.write(header('Wiring check'));
|
|
664
772
|
// `evaluated` counts rules that actually ran a comparison (globs matched >0
|
|
@@ -722,10 +830,10 @@ async function checkWiring(args) {
|
|
|
722
830
|
process.stdout.write(`\nNo wiring violations among the ${evaluated} rule(s) evaluated — ` +
|
|
723
831
|
`${notVerified} of ${configured} NOT verified${breakdown}. Not a full green.\n`);
|
|
724
832
|
}
|
|
725
|
-
return
|
|
833
|
+
return wiringExitCode(report, evaluated);
|
|
726
834
|
}
|
|
727
835
|
if (report.violations.length === 0) {
|
|
728
|
-
return report
|
|
836
|
+
return wiringExitCode(report, evaluated);
|
|
729
837
|
}
|
|
730
838
|
// Group by rule for a readable report.
|
|
731
839
|
for (const r of report.rules) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-catalog.d.ts","sourceRoot":"","sources":["../../src/commands/command-catalog.ts"],"names":[],"mappings":"AAAA,oBAAY,WAAW;IACrB,QAAQ,cAAc;IACtB,iBAAiB,mBAAmB;IACpC,gBAAgB,kBAAkB;IAClC,YAAY,kBAAkB;IAC9B,SAAS,eAAe;IACxB,cAAc,oBAAoB;CACnC;AAED;;;;;;GAMG;AACH,oBAAY,cAAc;IACxB,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED,8BAA8B;AAC9B,oBAAY,eAAe;IACzB,KAAK,UAAU;IACf,KAAK,UAAU;IACf,EAAE,OAAO;IACT,UAAU,gBAAgB;IAC1B,UAAU,eAAe;CAC1B;AAED;;;;;;GAMG;AACH,oBAAY,eAAe;IACzB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,MAAM,WAAW;CAClB;AAED;;;;;;GAMG;AACH,oBAAY,gBAAgB;IAC1B,MAAM,WAAW;IACjB,SAAS,cAAc;IACvB,KAAK,UAAU;IACf,UAAU,eAAe;IACzB,OAAO,YAAY;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,oBAAY,WAAW;IACrB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,YAAY,iBAAiB;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,kCAAkC;IAClC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,oBAAoB,
|
|
1
|
+
{"version":3,"file":"command-catalog.d.ts","sourceRoot":"","sources":["../../src/commands/command-catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -406,6 +406,14 @@ export const COMMAND_CATALOG = Object.freeze([
|
|
|
406
406
|
surface: CommandSurface.Common,
|
|
407
407
|
taskRole: CommandTaskRole.Validate,
|
|
408
408
|
}),
|
|
409
|
+
entry({
|
|
410
|
+
command: 'check wiring --fix',
|
|
411
|
+
description: 'Deterministic autofix for `declared-but-not-registered`: append the missing id to its sink array. Dry-run by default; `--write` applies. Refuses anything ambiguous (N sinks, non-array sink, non-unique file/array) and lists what it left alone with the reason — it never guesses. [--write] [--json]',
|
|
412
|
+
category: 'core',
|
|
413
|
+
safetyLevel: SafetyLevel.WritesSource,
|
|
414
|
+
surface: CommandSurface.Advanced,
|
|
415
|
+
taskRole: CommandTaskRole.Apply,
|
|
416
|
+
}),
|
|
409
417
|
entry({
|
|
410
418
|
command: 'policy-lint explain',
|
|
411
419
|
description: 'Dry-run ONE policyRule and print every hit with file:line — INCLUDING the hits an exemption (exemptFiles / exemptLines) or the lexical scan zone dropped, each labelled with which one applied. The author-loop view of what the gate sees. [--json]',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daily.commands.d.ts","sourceRoot":"","sources":["../../src/commands/daily.commands.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"daily.commands.d.ts","sourceRoot":"","sources":["../../src/commands/daily.commands.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAOhC,eAAO,MAAM,WAAW,EAAE,eAsFzB,CAAC;AAcF,eAAO,MAAM,WAAW,EAAE,eAoGzB,CAAC;AAKF,eAAO,MAAM,cAAc,EAAE,eAmH5B,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { buildAiReadinessReport, buildTaskPacket, inspectSharkcraft, runDoctor,
|
|
|
2
2
|
import { recommendPresets } from '@shrkcrft/presets';
|
|
3
3
|
import { flagBool, flagNumber, resolveCwd, } from "../command-registry.js";
|
|
4
4
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
5
|
+
import { tryExplainGateRule } from "./gates.command.js";
|
|
5
6
|
// ────────────────────────────────────────────────────────────────────────
|
|
6
7
|
// shrk next — "what should I do right now in this repo?"
|
|
7
8
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -177,9 +178,18 @@ export const explainCommand = {
|
|
|
177
178
|
async run(args) {
|
|
178
179
|
const topic = args.positional.join(' ').trim();
|
|
179
180
|
if (!topic) {
|
|
180
|
-
process.stderr.write('Usage: shrk explain "<topic>"\n');
|
|
181
|
+
process.stderr.write('Usage: shrk explain "<topic>" (a topic, or the id of any data-defined rule)\n');
|
|
181
182
|
return 2;
|
|
182
183
|
}
|
|
184
|
+
// A single token that exactly names a declared rule on ANY plane is
|
|
185
|
+
// explained as that rule — the one entrypoint the trust layer promises.
|
|
186
|
+
// Anything else keeps the original topic search, so no existing call
|
|
187
|
+
// changes meaning.
|
|
188
|
+
if (args.positional.length === 1) {
|
|
189
|
+
const asRule = await tryExplainGateRule(args, topic);
|
|
190
|
+
if (asRule !== undefined)
|
|
191
|
+
return asRule;
|
|
192
|
+
}
|
|
183
193
|
const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
|
|
184
194
|
const maxEntries = flagNumber(args, 'max-entries') ?? 6;
|
|
185
195
|
const packet = buildTaskPacket(inspection, topic, { maxTokens: 2500 });
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import { type ICommandHandler } from '../command-registry.js';
|
|
1
|
+
import { type ICommandHandler, type ParsedArgs } from '../command-registry.js';
|
|
2
2
|
export declare const gatesListCommand: ICommandHandler;
|
|
3
3
|
export declare const gatesCoverageCommand: ICommandHandler;
|
|
4
4
|
export declare const gatesExplainCommand: ICommandHandler;
|
|
5
|
+
/**
|
|
6
|
+
* Try to explain `id` as a data-defined rule on ANY plane.
|
|
7
|
+
*
|
|
8
|
+
* Returns the exit code when the id resolves to exactly one declared rule, or
|
|
9
|
+
* `undefined` when it is not a rule id at all — which lets `shrk explain` keep
|
|
10
|
+
* its original topic-search behaviour for everything else. This is the D2
|
|
11
|
+
* unification: a user holding a rule id no longer has to know which plane owns
|
|
12
|
+
* it, and no existing invocation changes meaning.
|
|
13
|
+
*/
|
|
14
|
+
export declare function tryExplainGateRule(args: ParsedArgs, id: string): Promise<number | undefined>;
|
|
5
15
|
export declare const gatesCommand: ICommandHandler;
|
|
6
16
|
//# sourceMappingURL=gates.command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gates.command.d.ts","sourceRoot":"","sources":["../../src/commands/gates.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAIL,KAAK,eAAe,
|
|
1
|
+
{"version":3,"file":"gates.command.d.ts","sourceRoot":"","sources":["../../src/commands/gates.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,UAAU,EAChB,MAAM,wBAAwB,CAAC;AAgFhC,eAAO,MAAM,gBAAgB,EAAE,eAsD9B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAuGlC,CAAC;AAuCF,eAAO,MAAM,mBAAmB,EAAE,eA4FjC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,EAChB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B;AAED,eAAO,MAAM,YAAY,EAAE,eAe1B,CAAC"}
|
|
@@ -22,6 +22,7 @@ import { ExitCode } from "../exit-codes.js";
|
|
|
22
22
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
23
23
|
import { collectGateRules, GATE_PLANES, } from "../gates/gate-rule-view.js";
|
|
24
24
|
import { buildGateCoverage } from "../gates/rule-coverage.js";
|
|
25
|
+
import { buildGateEnvelope } from "../gates/gate-envelope.js";
|
|
25
26
|
import { baselineExplainCommand } from "./baseline.command.js";
|
|
26
27
|
import { generatedExplainCommand } from "./generated.command.js";
|
|
27
28
|
import { renderPolicyExplain, runPolicyExplain } from "./policy-lint.command.js";
|
|
@@ -37,7 +38,7 @@ async function prepare(args) {
|
|
|
37
38
|
process.stdout.write(asJson({ schema: SCHEMA, error: msg }) + '\n');
|
|
38
39
|
else
|
|
39
40
|
process.stderr.write(`Could not load config: ${msg}\n Run \`shrk doctor\` for details.\n`);
|
|
40
|
-
return { ok: false, code: ExitCode.
|
|
41
|
+
return { ok: false, code: ExitCode.UsageError };
|
|
41
42
|
}
|
|
42
43
|
const rel = nodePath.relative(cwd, loaded.value.sharkcraftDir).split(nodePath.sep).join('/');
|
|
43
44
|
return {
|
|
@@ -89,7 +90,7 @@ export const gatesListCommand = {
|
|
|
89
90
|
return prep.code;
|
|
90
91
|
const planes = parsePlanes(args);
|
|
91
92
|
if (!planes.ok)
|
|
92
|
-
return ExitCode.
|
|
93
|
+
return ExitCode.UsageError;
|
|
93
94
|
const json = flagBool(args, 'json');
|
|
94
95
|
const rules = planes.planes
|
|
95
96
|
? prep.value.rules.filter((r) => planes.planes.has(r.plane))
|
|
@@ -146,7 +147,7 @@ export const gatesCoverageCommand = {
|
|
|
146
147
|
return prep.code;
|
|
147
148
|
const planes = parsePlanes(args);
|
|
148
149
|
if (!planes.ok)
|
|
149
|
-
return ExitCode.
|
|
150
|
+
return ExitCode.UsageError;
|
|
150
151
|
const json = flagBool(args, 'json');
|
|
151
152
|
const rules = planes.planes
|
|
152
153
|
? prep.value.rules.filter((r) => planes.planes.has(r.plane))
|
|
@@ -165,7 +166,29 @@ export const gatesCoverageCommand = {
|
|
|
165
166
|
? ExitCode.NotVerified
|
|
166
167
|
: ExitCode.VerifiedPass;
|
|
167
168
|
if (json) {
|
|
168
|
-
process.stdout.write(asJson({
|
|
169
|
+
process.stdout.write(asJson({
|
|
170
|
+
...report,
|
|
171
|
+
hardFailures: hardFailures.length,
|
|
172
|
+
exitCode: exit,
|
|
173
|
+
gate: buildGateEnvelope('gates coverage', exit, report.rules.map((r) => ({
|
|
174
|
+
id: r.id,
|
|
175
|
+
type: r.plane,
|
|
176
|
+
status: r.status === 'ok'
|
|
177
|
+
? 'passed'
|
|
178
|
+
: r.status === 'empty'
|
|
179
|
+
? r.failOnEmpty
|
|
180
|
+
? 'failed'
|
|
181
|
+
: 'skipped'
|
|
182
|
+
: r.status === 'error'
|
|
183
|
+
? 'error'
|
|
184
|
+
: 'failed',
|
|
185
|
+
severity: r.failOnEmpty ? 'error' : 'warning',
|
|
186
|
+
counts: { files: r.filesMatched, units: r.unitsMatched },
|
|
187
|
+
violations: r.expectationFailures.map((f) => ({ id: r.id, message: f })),
|
|
188
|
+
...(r.status === 'empty' ? { skipReason: `matched 0 ${r.unitLabel}` } : {}),
|
|
189
|
+
...(r.error ? { error: r.error } : {}),
|
|
190
|
+
}))),
|
|
191
|
+
}) + '\n');
|
|
169
192
|
return exit;
|
|
170
193
|
}
|
|
171
194
|
process.stdout.write(header('Gate-rule coverage'));
|
|
@@ -244,7 +267,7 @@ export const gatesExplainCommand = {
|
|
|
244
267
|
const id = args.positional[0] ?? flagString(args, 'id');
|
|
245
268
|
if (!id) {
|
|
246
269
|
process.stderr.write('Usage: shrk gates explain <id> [--json]\n');
|
|
247
|
-
return ExitCode.
|
|
270
|
+
return ExitCode.UsageError;
|
|
248
271
|
}
|
|
249
272
|
const prep = await prepare(args);
|
|
250
273
|
if (!prep.ok)
|
|
@@ -252,25 +275,25 @@ export const gatesExplainCommand = {
|
|
|
252
275
|
const matches = prep.value.rules.filter((r) => r.id === id);
|
|
253
276
|
if (matches.length === 0) {
|
|
254
277
|
process.stderr.write(`No gate rule "${id}". Run \`shrk gates list\` to see the ${prep.value.rules.length} declared rule(s).\n`);
|
|
255
|
-
return ExitCode.
|
|
278
|
+
return ExitCode.UsageError;
|
|
256
279
|
}
|
|
257
280
|
// An id may legitimately exist on two planes (a wiring rule and a registry
|
|
258
281
|
// can share a name); `--plane` disambiguates instead of guessing.
|
|
259
282
|
const planes = parsePlanes(args);
|
|
260
283
|
if (!planes.ok)
|
|
261
|
-
return ExitCode.
|
|
284
|
+
return ExitCode.UsageError;
|
|
262
285
|
const candidates = planes.planes
|
|
263
286
|
? matches.filter((r) => planes.planes.has(r.plane))
|
|
264
287
|
: matches;
|
|
265
288
|
if (candidates.length > 1) {
|
|
266
289
|
process.stderr.write(`"${id}" exists on ${candidates.length} planes (${candidates.map((c) => c.plane).join(', ')}). ` +
|
|
267
290
|
'Disambiguate with --plane <p>.\n');
|
|
268
|
-
return ExitCode.
|
|
291
|
+
return ExitCode.UsageError;
|
|
269
292
|
}
|
|
270
293
|
const view = candidates[0];
|
|
271
294
|
if (!view) {
|
|
272
295
|
process.stderr.write(`No gate rule "${id}" on the requested plane.\n`);
|
|
273
|
-
return ExitCode.
|
|
296
|
+
return ExitCode.UsageError;
|
|
274
297
|
}
|
|
275
298
|
const json = flagBool(args, 'json');
|
|
276
299
|
// The two shell-executing planes own their explain output (and their trust
|
|
@@ -319,6 +342,26 @@ export const gatesExplainCommand = {
|
|
|
319
342
|
return ExitCode.VerifiedPass;
|
|
320
343
|
},
|
|
321
344
|
};
|
|
345
|
+
/**
|
|
346
|
+
* Try to explain `id` as a data-defined rule on ANY plane.
|
|
347
|
+
*
|
|
348
|
+
* Returns the exit code when the id resolves to exactly one declared rule, or
|
|
349
|
+
* `undefined` when it is not a rule id at all — which lets `shrk explain` keep
|
|
350
|
+
* its original topic-search behaviour for everything else. This is the D2
|
|
351
|
+
* unification: a user holding a rule id no longer has to know which plane owns
|
|
352
|
+
* it, and no existing invocation changes meaning.
|
|
353
|
+
*/
|
|
354
|
+
export async function tryExplainGateRule(args, id) {
|
|
355
|
+
const cwd = resolveCwd(args);
|
|
356
|
+
const loaded = await resolveProjectConfig(cwd);
|
|
357
|
+
if (!loaded.ok)
|
|
358
|
+
return undefined;
|
|
359
|
+
const rules = collectGateRules(loaded.value.config);
|
|
360
|
+
if (!rules.some((r) => r.id === id))
|
|
361
|
+
return undefined;
|
|
362
|
+
const forwarded = { ...args, positional: [id] };
|
|
363
|
+
return gatesExplainCommand.run(forwarded);
|
|
364
|
+
}
|
|
322
365
|
export const gatesCommand = {
|
|
323
366
|
name: 'gates',
|
|
324
367
|
description: 'Rule-authoring trust layer: list every data-defined rule, show what each one MATCHED (the stale-selector detector), and explain any one of them. Read-only. Not `shrk gate`, which runs the quality-gate pipeline.',
|
|
@@ -329,6 +372,6 @@ export const gatesCommand = {
|
|
|
329
372
|
process.stderr.write((sub ? `Unknown subcommand "${sub}". ` : '') +
|
|
330
373
|
'Usage: shrk gates list | coverage [--plane <p>] [--strict] | explain <id>\n' +
|
|
331
374
|
'(`shrk gate`, singular, runs the quality-gate pipeline — a different verb.)\n');
|
|
332
|
-
return ExitCode.
|
|
375
|
+
return ExitCode.UsageError;
|
|
333
376
|
},
|
|
334
377
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated.command.d.ts","sourceRoot":"","sources":["../../src/commands/generated.command.ts"],"names":[],"mappings":"AA+BA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"generated.command.d.ts","sourceRoot":"","sources":["../../src/commands/generated.command.ts"],"names":[],"mappings":"AA+BA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAyShC,eAAO,MAAM,oBAAoB,EAAE,eA2ClC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAuHnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAmDpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiErC,CAAC"}
|