@shrkcrft/cli 0.1.0-alpha.29 → 0.1.0-alpha.30
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 +25 -0
- package/dist/commands/baseline.command.d.ts.map +1 -1
- package/dist/commands/baseline.command.js +23 -7
- package/dist/commands/changelog-data.d.ts.map +1 -1
- package/dist/commands/changelog-data.js +26 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +19 -2
- package/dist/commands/code-intel.command.d.ts.map +1 -1
- package/dist/commands/code-intel.command.js +5 -1
- package/dist/commands/docs-references.command.d.ts +7 -0
- package/dist/commands/docs-references.command.d.ts.map +1 -0
- package/dist/commands/docs-references.command.js +323 -0
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +3 -2
- package/dist/commands/gates.command.d.ts +2 -0
- package/dist/commands/gates.command.d.ts.map +1 -1
- package/dist/commands/gates.command.js +640 -16
- package/dist/commands/generated.command.d.ts +32 -0
- package/dist/commands/generated.command.d.ts.map +1 -1
- package/dist/commands/generated.command.js +180 -49
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
- package/dist/commands/graph-code-subverbs.js +5 -1
- package/dist/commands/wiring.command.d.ts.map +1 -1
- package/dist/commands/wiring.command.js +4 -3
- package/dist/gates/gate-envelope.d.ts +1 -1
- package/dist/gates/gate-envelope.d.ts.map +1 -1
- package/dist/gates/gate-rule-globs.d.ts +33 -0
- package/dist/gates/gate-rule-globs.d.ts.map +1 -0
- package/dist/gates/gate-rule-globs.js +101 -0
- package/dist/gates/gate-rule-view.d.ts +9 -3
- package/dist/gates/gate-rule-view.d.ts.map +1 -1
- package/dist/gates/gate-rule-view.js +13 -0
- package/dist/gates/rule-coverage.d.ts +39 -1
- package/dist/gates/rule-coverage.d.ts.map +1 -1
- package/dist/gates/rule-coverage.js +123 -8
- package/dist/gates/run-gate-planes.d.ts +44 -0
- package/dist/gates/run-gate-planes.d.ts.map +1 -0
- package/dist/gates/run-gate-planes.js +261 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -1
- package/package.json +33 -33
|
@@ -1,4 +1,29 @@
|
|
|
1
|
+
import type { IBaselineRule } from '@shrkcrft/core';
|
|
2
|
+
import { type IBaselineDiff } from '@shrkcrft/boundaries';
|
|
1
3
|
import { type ICommandHandler } from '../command-registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* One rule's outcome, shared by check / diff / update — and by `shrk gates
|
|
6
|
+
* check`, which aggregates every plane. Exported so the aggregate runs the
|
|
7
|
+
* IDENTICAL evaluation as the per-plane verb: two implementations of "did this
|
|
8
|
+
* baseline drift?" would eventually disagree, and the one nobody runs would be
|
|
9
|
+
* the one that is wrong.
|
|
10
|
+
*/
|
|
11
|
+
export interface IBaselineOutcome {
|
|
12
|
+
readonly rule: IBaselineRule;
|
|
13
|
+
readonly status: 'passed' | 'failed' | 'skipped' | 'error';
|
|
14
|
+
readonly diff?: IBaselineDiff;
|
|
15
|
+
readonly committed?: string;
|
|
16
|
+
readonly current?: string;
|
|
17
|
+
readonly committedCount: number;
|
|
18
|
+
readonly currentCount: number;
|
|
19
|
+
readonly error?: string;
|
|
20
|
+
readonly skipReason?: string;
|
|
21
|
+
/** The recompute produced 0 entries while the baseline has some — likely a broken compute. */
|
|
22
|
+
readonly emptyCompute?: boolean;
|
|
23
|
+
/** No committed artifact exists yet — `committed` is absent, not zero. */
|
|
24
|
+
readonly missingBaseline?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function evaluateBaselineRule(cwd: string, rule: IBaselineRule, excludeDirs: readonly string[], changedFiles: readonly string[] | undefined): IBaselineOutcome;
|
|
2
27
|
export declare const baselineListCommand: ICommandHandler;
|
|
3
28
|
export declare const baselineCheckCommand: ICommandHandler;
|
|
4
29
|
export declare const baselineDiffCommand: ICommandHandler;
|
|
@@ -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":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAuFhC;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,8FAA8F;IAC9F,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAC1C,gBAAgB,CAiHlB;AA4FD,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,eAiEpC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,eAc7B,CAAC"}
|
|
@@ -17,7 +17,7 @@
|
|
|
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
|
+
import { resolveSourceGlobs, failsWhenEmpty } from '@shrkcrft/core';
|
|
21
21
|
import { baselineCount, baselineFails, computeBaselineFromExtractor, diffBaseline, matchesAny, } from '@shrkcrft/boundaries';
|
|
22
22
|
import { resolveChangedFiles, resolveProjectConfig } from '@shrkcrft/inspector';
|
|
23
23
|
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
@@ -87,7 +87,7 @@ function computeCurrent(cwd, rule, excludeDirs) {
|
|
|
87
87
|
}
|
|
88
88
|
return { text: String(child.stdout ?? '') };
|
|
89
89
|
}
|
|
90
|
-
function
|
|
90
|
+
export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
|
|
91
91
|
// --changed-only is honest about what it CANNOT scope: a command compute with
|
|
92
92
|
// no `watchFiles` has no file footprint, so it is reported as skipped rather
|
|
93
93
|
// than quietly passing.
|
|
@@ -95,7 +95,7 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
95
95
|
const globs = rule.watchFiles && rule.watchFiles.length > 0
|
|
96
96
|
? rule.watchFiles
|
|
97
97
|
: rule.compute.kind === 'extractor'
|
|
98
|
-
? (rule.compute.source
|
|
98
|
+
? (resolveSourceGlobs(rule.compute.source ?? { files: [] }))
|
|
99
99
|
: undefined;
|
|
100
100
|
if (globs === undefined) {
|
|
101
101
|
return {
|
|
@@ -158,6 +158,19 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
158
158
|
// the baseline has entries and the recompute has none, that is real drift
|
|
159
159
|
// (everything vanished) and must be reported as such, not swallowed as a skip.
|
|
160
160
|
if (currentCount === 0 && committedCount === 0) {
|
|
161
|
+
// A fence ASSERTS emptiness, so for it the empty case is the verified pass
|
|
162
|
+
// rather than "nothing was checked" — otherwise the rule could never be
|
|
163
|
+
// green and could not gate anything.
|
|
164
|
+
if (rule.expectEmpty === true) {
|
|
165
|
+
return {
|
|
166
|
+
rule,
|
|
167
|
+
status: 'passed',
|
|
168
|
+
committed,
|
|
169
|
+
current: computed.text,
|
|
170
|
+
committedCount,
|
|
171
|
+
currentCount,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
161
174
|
return {
|
|
162
175
|
rule,
|
|
163
176
|
status: failsWhenEmpty(rule) ? 'failed' : 'skipped',
|
|
@@ -320,7 +333,7 @@ export const baselineCheckCommand = {
|
|
|
320
333
|
const json = flagBool(args, 'json');
|
|
321
334
|
if (prep.rules.length === 0)
|
|
322
335
|
return writeNoRules(json);
|
|
323
|
-
const outcomes = prep.rules.map((r) =>
|
|
336
|
+
const outcomes = prep.rules.map((r) => evaluateBaselineRule(prep.cwd, r, prep.excludeDirs, prep.changedFiles));
|
|
324
337
|
const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
|
|
325
338
|
const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
|
|
326
339
|
// 0 only when a NON-EMPTY scope was actually compared; 2 when nothing was.
|
|
@@ -407,7 +420,7 @@ export const baselineDiffCommand = {
|
|
|
407
420
|
const json = flagBool(args, 'json');
|
|
408
421
|
if (prep.rules.length === 0)
|
|
409
422
|
return writeNoRules(json);
|
|
410
|
-
const outcomes = prep.rules.map((r) =>
|
|
423
|
+
const outcomes = prep.rules.map((r) => evaluateBaselineRule(prep.cwd, r, prep.excludeDirs, undefined));
|
|
411
424
|
if (json) {
|
|
412
425
|
process.stdout.write(asJson({ schema: SCHEMA, results: outcomes.map(outcomeJson), inspection: true }) + '\n');
|
|
413
426
|
return ExitCode.VerifiedPass;
|
|
@@ -494,7 +507,7 @@ export const baselineExplainCommand = {
|
|
|
494
507
|
process.stderr.write(`No baseline "${id}". Declared: ${prep.all.map((r) => r.id).join(', ') || '(none)'}\n`);
|
|
495
508
|
return ExitCode.UsageError;
|
|
496
509
|
}
|
|
497
|
-
const outcome =
|
|
510
|
+
const outcome = evaluateBaselineRule(prep.cwd, rule, prep.excludeDirs, undefined);
|
|
498
511
|
if (flagBool(args, 'json')) {
|
|
499
512
|
process.stdout.write(asJson({
|
|
500
513
|
schema: 'sharkcraft.baseline-explain/v1',
|
|
@@ -508,7 +521,10 @@ export const baselineExplainCommand = {
|
|
|
508
521
|
if (rule.description)
|
|
509
522
|
process.stdout.write(` ${rule.description}\n`);
|
|
510
523
|
process.stdout.write(kv('committed', rule.baseline) + '\n');
|
|
511
|
-
process.stdout.write(kv('compute', rule.compute.kind === 'command'
|
|
524
|
+
process.stdout.write(kv('compute', rule.compute.kind === 'command'
|
|
525
|
+
? `command · ${rule.compute.run}`
|
|
526
|
+
: `extractor · ${rule.compute.source?.extract ?? 'sugar'}` +
|
|
527
|
+
(rule.compute.source?.$use ? ` (via $use:${rule.compute.source.$use})` : '')) + '\n');
|
|
512
528
|
process.stdout.write(kv('direction', rule.direction ?? 'two-way') + '\n');
|
|
513
529
|
process.stdout.write(kv('canonical', outcome.diff?.canonical ?? rule.compute.canonical ?? 'auto') + '\n');
|
|
514
530
|
if (rule.keyBy)
|
|
@@ -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,EA4KjE,CAAC"}
|
|
@@ -134,6 +134,32 @@ export const RELEASE_SURFACE_DELTAS = [
|
|
|
134
134
|
],
|
|
135
135
|
removed: [],
|
|
136
136
|
},
|
|
137
|
+
{
|
|
138
|
+
version: '0.1.0-alpha.30',
|
|
139
|
+
title: 'The engine taps the import graph, and reads its own prose',
|
|
140
|
+
added: [
|
|
141
|
+
'`shrk docs references check|explain|list` — the seventh rule plane (`docReferences[]`): ids cited in free-text PROSE (READMEs, docs, agent skill files) that no longer resolve. Markdown has no build, so these drift with zero signal. `requireContext` (default `backtick`), `exempt[]`, a per-line `<!-- ref-allow: why -->` marker, and a length-scaled `did you mean` keep it low-noise. Reads dot-directories a glob explicitly names, so `.claude/skills/**` works.',
|
|
142
|
+
'`shrk gates check` — every rule plane in ONE pass with one exit code (the CI / pre-commit primitive); `--changed-only` scopes by rule footprint, `--strict` promotes warning-severity findings to failures.',
|
|
143
|
+
'`shrk gates try --rule-file <f> [--full]` — dry-run a candidate rule against the tree without adding it to config. Dumps every captured token, so an over-matching `tokenPattern` is visible in one run instead of arriving as noise in CI.',
|
|
144
|
+
'`import-edges` extractor — the resolved dependency graph as a rule input (`emit: edge|symbol|from`, targetable by `to.module` / `to.modulePattern` / `to.files` / `to.match`). Existing planes now express adoption ledgers, orphan detection, deprecation ratchets and targeted fences. Alias-aware; no persisted index, so no staleness.',
|
|
145
|
+
'`filenames` extractor + `mode: parity` — companion-file invariants (every `X.ts` has an `X.test.ts`).',
|
|
146
|
+
'Shared extractors: a top-level `extractors` map plus `{ $use: "<id>" }` on any plane (local fields override). The planes describing one id set can no longer drift apart, and a typo\'d id fails the config load rather than silently matching nothing.',
|
|
147
|
+
'`generatedArtifacts[]` for MIXED trees: `sources[]` (N writers, each verifying its own sub-glob), `handMaintained[]` (literal filenames only, so the exemption cannot silently widen) and `handMaintainedMarker` for an in-file bless. A file under the tree owned by neither is an `unclassified` finding.',
|
|
148
|
+
'`expectEmpty: true` on `baselines[]` — a fence that fails the moment the edge set becomes non-empty.',
|
|
149
|
+
'`shrk check wiring --fix` now adds the missing IMPORT alongside the array member when the specifier is a pure function of the member name AND resolves to the declaring file; otherwise it refuses with `needs-import`.',
|
|
150
|
+
],
|
|
151
|
+
changed: [
|
|
152
|
+
'FIXED (P1): `--fix --write` could green the wiring gate over a file that no longer compiles. The common registry shape IMPORTS its array members, and appending the token alone left `TS2304: Cannot find name`. It now writes both edits or refuses.',
|
|
153
|
+
'FIXED (P1): reference resolution ran in THREE modules — the prose linter and both self-config doctors — each from its own sources, agreeing only by coincidence. The self-config doctor reported 17 of shrk\'s own correctly-registered ids as unknown (its hand-written "known ids" union omitted policies, decisions, scaffold patterns and paths). One resolver now answers for every kind, and each kind reads the source its `list` verb reads.',
|
|
154
|
+
'FIXED (P2): `shrk graph status` judged index freshness by working-tree DIVERGENCE while `shrk code-intel` judged it by wall-clock age — contradictory verdicts on the same index in the same second, with every arch/cycle count derived from it inheriting the staleness unmarked. Divergence is now the single verdict; age is a display detail (`current (indexed 5d ago)` / `STALE — N file(s) changed since index`).',
|
|
155
|
+
'A finding DERIVED from a stale graph index is reported `NOT VERIFIED` rather than as a count — the loud-skip contract the rule planes already honour, extended to the graph-derived surfaces. An unmeasured verdict is never a pass.',
|
|
156
|
+
'An ERRORED rule is no longer counted as `evaluated` by `gates check` / `docs references check`, so a warning-severity rule that could not run exits `2` instead of printing "not blocking" above exit `0`.',
|
|
157
|
+
'A doc-reference rule whose registries are ALL empty refuses loudly instead of reporting every id as unresolved — a gate that confidently flags correct usage is worse than no gate.',
|
|
158
|
+
'`detectGraphFreshness` no longer throws on a store whose `meta.json` exists but whose remaining parts do not (an interrupted index); it reports "could not measure", which reads as not-verified.',
|
|
159
|
+
'`shrk graph status` labels its snapshot size `files indexed` — it deliberately does not move when a new file appears on disk; that shows up on the `drift` line.',
|
|
160
|
+
],
|
|
161
|
+
removed: [],
|
|
162
|
+
},
|
|
137
163
|
{
|
|
138
164
|
version: '0.1.0-alpha.29',
|
|
139
165
|
title: 'The exit code has to agree with the sentence',
|
|
@@ -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;AA2jChC,eAAO,MAAM,YAAY,EAAE,eA+G1B,CAAC"}
|
|
@@ -524,7 +524,8 @@ async function checkBoundariesOnce(args) {
|
|
|
524
524
|
// ────────────────────────────────────────────────────────────────────────
|
|
525
525
|
// Subcommand: wiring — "declared but not wired" completeness checks
|
|
526
526
|
// ────────────────────────────────────────────────────────────────────────
|
|
527
|
-
const WIRING_CHECK_USAGE = 'shrk check wiring [--changed-only] [--since <ref>] [--base <ref>] [--only <ids>] [--explain <ruleId>]
|
|
527
|
+
const WIRING_CHECK_USAGE = 'shrk check wiring [--changed-only] [--since <ref>] [--base <ref>] [--only <ids>] [--explain <ruleId>]\n' +
|
|
528
|
+
' [--fix [--write]] [--json] [--strict]\n' +
|
|
528
529
|
' Cross-file "declared but not wired" completeness gate. Scope flags select rules by\n' +
|
|
529
530
|
' FOOTPRINT — a rule fires when the diff touches EITHER its declared or its registered\n' +
|
|
530
531
|
' side (so a registration edited in file B fires a rule declared in file A).\n' +
|
|
@@ -532,6 +533,10 @@ const WIRING_CHECK_USAGE = 'shrk check wiring [--changed-only] [--since <ref>] [
|
|
|
532
533
|
' --since <ref> scope the diff to changes since <ref> (--base is a synonym)\n' +
|
|
533
534
|
' --only <ids> run only these rule ids (comma-separated)\n' +
|
|
534
535
|
' --explain <id> dry-run ONE rule and print the declared/registered sets it extracts\n' +
|
|
536
|
+
' --fix plan the deterministic repair (append the token to its sink array);\n' +
|
|
537
|
+
' dry-run by default, prints every edit AND any import it must add.\n' +
|
|
538
|
+
' Refuses anything ambiguous — see `needs-import` in docs/wiring.md.\n' +
|
|
539
|
+
' --write apply the planned edits (only meaningful with --fix)\n' +
|
|
535
540
|
' Exit: 0 verified pass · 1 violations · 2 not-verified (0 rules evaluated in scope).';
|
|
536
541
|
/**
|
|
537
542
|
* The honest exit code for a wiring run.
|
|
@@ -606,7 +611,14 @@ function runWiringFix(cwd, rules, report, write, wantJson) {
|
|
|
606
611
|
}
|
|
607
612
|
for (const e of allEdits) {
|
|
608
613
|
process.stdout.write(` ${write ? 'wrote ' : 'would add'} ${e.token} → ${e.file}:${e.line}\n`);
|
|
609
|
-
|
|
614
|
+
// Show BOTH halves of the edit. An array append whose import is invisible
|
|
615
|
+
// in the preview is exactly how the reviewer approves a change they have
|
|
616
|
+
// not actually seen — and the import is the half that decides whether the
|
|
617
|
+
// result compiles.
|
|
618
|
+
if (e.importInsert !== undefined) {
|
|
619
|
+
process.stdout.write(` + ${e.importInsert}${e.importLine !== undefined ? ` (line ${e.importLine})` : ''}\n`);
|
|
620
|
+
}
|
|
621
|
+
process.stdout.write(` + ${e.insert}${e.importInsert !== undefined ? ` (line ${e.line})` : ''}\n`);
|
|
610
622
|
}
|
|
611
623
|
if (allSkips.length > 0) {
|
|
612
624
|
process.stdout.write(`\n Left untouched (${allSkips.length}) — not mechanically unambiguous:\n`);
|
|
@@ -815,6 +827,11 @@ async function checkWiring(args) {
|
|
|
815
827
|
continue;
|
|
816
828
|
process.stdout.write(`\n ! ${r.ruleId}: the registered side extracted 0 ids while ${r.declaredCount} were declared —\n` +
|
|
817
829
|
` every declared token "fails". Check the registered glob before chasing the tokens.\n`);
|
|
830
|
+
// When the engine knows WHY the sink came back empty, say it here — the
|
|
831
|
+
// generic advice above would otherwise send the reader to inspect a glob
|
|
832
|
+
// that is perfectly fine.
|
|
833
|
+
if (r.sinkHint)
|
|
834
|
+
process.stdout.write(` → ${r.sinkHint}\n`);
|
|
818
835
|
}
|
|
819
836
|
if (report.violations.length === 0 && report.diagnostics.length === 0) {
|
|
820
837
|
// A failOnEmpty skip produces no violation object but IS a failure.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-intel.command.d.ts","sourceRoot":"","sources":["../../src/commands/code-intel.command.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"code-intel.command.d.ts","sourceRoot":"","sources":["../../src/commands/code-intel.command.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAIhC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,EAAE,eA6E9B,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildCodeIntelligenceChecks, DoctorSeverity, } from '@shrkcrft/inspector';
|
|
2
2
|
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
3
|
+
import { detectGraphFreshness } from '@shrkcrft/graph';
|
|
3
4
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
4
5
|
/**
|
|
5
6
|
* `shrk code-intel` — one-shot view of the 14 code-intelligence
|
|
@@ -28,7 +29,10 @@ export const codeIntelCommand = {
|
|
|
28
29
|
const checkId = flagString(args, 'check');
|
|
29
30
|
const staleDaysRaw = flagString(args, 'stale-days');
|
|
30
31
|
const staleDays = staleDaysRaw ? Number.parseInt(staleDaysRaw, 10) : undefined;
|
|
31
|
-
|
|
32
|
+
// Divergence against the working tree — the SAME check `shrk graph status`
|
|
33
|
+
// reports, so the two surfaces cannot contradict each other. Injected
|
|
34
|
+
// because `@shrkcrft/graph` sits above inspector in the layer order.
|
|
35
|
+
const options = { graphDivergence: detectGraphFreshness(cwd) };
|
|
32
36
|
if (typeof staleDays === 'number' && Number.isFinite(staleDays) && staleDays > 0) {
|
|
33
37
|
options.staleThresholdDays = staleDays;
|
|
34
38
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ICommandHandler } from '../command-registry.js';
|
|
2
|
+
export declare const docsReferencesCheckCommand: ICommandHandler;
|
|
3
|
+
export declare const docsReferencesExplainCommand: ICommandHandler;
|
|
4
|
+
export declare const docsReferencesListCommand: ICommandHandler;
|
|
5
|
+
/** `shrk docs references <verb>` — dispatches to check / explain / list. */
|
|
6
|
+
export declare const docsReferencesCommand: ICommandHandler;
|
|
7
|
+
//# sourceMappingURL=docs-references.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-references.command.d.ts","sourceRoot":"","sources":["../../src/commands/docs-references.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAsGhC,eAAO,MAAM,0BAA0B,EAAE,eAuIxC,CAAC;AAEF,eAAO,MAAM,4BAA4B,EAAE,eA4D1C,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,eAyBvC,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,EAAE,eAkBnC,CAAC"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `shrk docs references {check,explain,list}` — the prose-reference linter.
|
|
3
|
+
*
|
|
4
|
+
* shrk docs references check [--id X] [--json] # unresolved id → finding
|
|
5
|
+
* shrk docs references explain --id X # every token + where it resolved
|
|
6
|
+
* shrk docs references list # the declared rules
|
|
7
|
+
*
|
|
8
|
+
* shrk validates the STRUCTURED `references[]` on knowledge entries. The same
|
|
9
|
+
* ids written as prose — a README, an architecture doc, an agent skill file
|
|
10
|
+
* saying `shrk gen nge.foo` — had no gate at all, and markdown has no build
|
|
11
|
+
* behind it. This points the existing reference resolver at that surface.
|
|
12
|
+
*
|
|
13
|
+
* Read-only: it scans documents and consults registries. Nothing is written and
|
|
14
|
+
* nothing is spawned.
|
|
15
|
+
*/
|
|
16
|
+
import * as nodePath from 'node:path';
|
|
17
|
+
import { checkDocReferences, inspectSharkcraft, warmReferenceRegistries, resolveProjectConfig, } from '@shrkcrft/inspector';
|
|
18
|
+
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
19
|
+
import { ExitCode } from "../exit-codes.js";
|
|
20
|
+
import { asJson, header, kv } from "../output/format-output.js";
|
|
21
|
+
import { buildGateEnvelope } from "../gates/gate-envelope.js";
|
|
22
|
+
const SCHEMA = 'sharkcraft.doc-references/v1';
|
|
23
|
+
async function prepare(args) {
|
|
24
|
+
const cwd = resolveCwd(args);
|
|
25
|
+
const json = flagBool(args, 'json');
|
|
26
|
+
const loaded = await resolveProjectConfig(cwd);
|
|
27
|
+
if (!loaded.ok) {
|
|
28
|
+
const msg = loaded.error.message;
|
|
29
|
+
if (json)
|
|
30
|
+
process.stdout.write(asJson({ schema: SCHEMA, error: msg }) + '\n');
|
|
31
|
+
else
|
|
32
|
+
process.stderr.write(`Could not load config: ${msg}\n Run \`shrk doctor\` for details.\n`);
|
|
33
|
+
return { ok: false, code: ExitCode.UsageError };
|
|
34
|
+
}
|
|
35
|
+
const all = loaded.value.config.docReferences ?? [];
|
|
36
|
+
const rel = nodePath.relative(cwd, loaded.value.sharkcraftDir).split(nodePath.sep).join('/');
|
|
37
|
+
let rules = all;
|
|
38
|
+
const id = flagString(args, 'id');
|
|
39
|
+
if (id) {
|
|
40
|
+
const wanted = id.split(',').map((s) => s.trim()).filter(Boolean);
|
|
41
|
+
const known = new Set(all.map((r) => r.id));
|
|
42
|
+
const unknown = wanted.filter((w) => !known.has(w));
|
|
43
|
+
if (unknown.length > 0) {
|
|
44
|
+
process.stderr.write(`Unknown doc-reference rule id(s): ${unknown.join(', ')}. Declared: ${[...known].join(', ') || '(none)'}\n`);
|
|
45
|
+
return { ok: false, code: ExitCode.UsageError };
|
|
46
|
+
}
|
|
47
|
+
rules = all.filter((r) => wanted.includes(r.id));
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
ok: true,
|
|
51
|
+
value: {
|
|
52
|
+
cwd,
|
|
53
|
+
rules,
|
|
54
|
+
all,
|
|
55
|
+
excludeDirs: rel && !rel.startsWith('..') ? [rel] : [],
|
|
56
|
+
planeDiagnostics: loaded.value.planeDiagnostics,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the inspection the resolver needs.
|
|
62
|
+
*
|
|
63
|
+
* Deliberately lazy — it is only paid for when a `docReferences` rule exists,
|
|
64
|
+
* so a repo without the plane never loads the registries just to be told it has
|
|
65
|
+
* no rules.
|
|
66
|
+
*/
|
|
67
|
+
async function inspectionFor(cwd) {
|
|
68
|
+
// Playbook / construct ids come from a cache an ASYNC load populates; the
|
|
69
|
+
// resolver is sync. Warming here is what makes a correct pack playbook cited
|
|
70
|
+
// in prose actually resolve.
|
|
71
|
+
const inspection = await inspectSharkcraft({ cwd });
|
|
72
|
+
await warmReferenceRegistries(inspection);
|
|
73
|
+
return inspection;
|
|
74
|
+
}
|
|
75
|
+
function writeNoRules(json) {
|
|
76
|
+
if (json) {
|
|
77
|
+
process.stdout.write(asJson({ schema: SCHEMA, results: [], evaluated: 0, verdict: 'not-verified' }) + '\n');
|
|
78
|
+
return ExitCode.NotVerified;
|
|
79
|
+
}
|
|
80
|
+
process.stdout.write(header('Doc references'));
|
|
81
|
+
process.stdout.write(' No doc-reference rules declared. Add `docReferences[]` to sharkcraft.config.ts to\n' +
|
|
82
|
+
' catch ids cited in prose (READMEs, docs, agent skill files) that no longer resolve\n' +
|
|
83
|
+
' (see docs/doc-references.md).\n');
|
|
84
|
+
return ExitCode.NotVerified;
|
|
85
|
+
}
|
|
86
|
+
function resultJson(r) {
|
|
87
|
+
return {
|
|
88
|
+
id: r.ruleId,
|
|
89
|
+
...(r.description ? { description: r.description } : {}),
|
|
90
|
+
status: r.status,
|
|
91
|
+
severity: r.severity,
|
|
92
|
+
filesScanned: r.filesScanned,
|
|
93
|
+
tokensChecked: r.tokensChecked,
|
|
94
|
+
tokensSkipped: r.tokensSkipped,
|
|
95
|
+
findings: r.findings,
|
|
96
|
+
...(r.skipReason ? { skipReason: r.skipReason } : {}),
|
|
97
|
+
...(r.error ? { error: r.error } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export const docsReferencesCheckCommand = {
|
|
101
|
+
name: 'check',
|
|
102
|
+
description: 'Scan configured docs for id-shaped tokens and assert each resolves to a registered id. Catches a README or skill file citing a template that no longer exists.',
|
|
103
|
+
usage: 'shrk docs references check [--id <ids>] [--json]',
|
|
104
|
+
booleanFlags: new Set(['json']),
|
|
105
|
+
async run(args) {
|
|
106
|
+
const prep = await prepare(args);
|
|
107
|
+
if (!prep.ok)
|
|
108
|
+
return prep.code;
|
|
109
|
+
const json = flagBool(args, 'json');
|
|
110
|
+
if (prep.value.rules.length === 0)
|
|
111
|
+
return writeNoRules(json);
|
|
112
|
+
const inspection = await inspectionFor(prep.value.cwd);
|
|
113
|
+
const results = prep.value.rules.map((r) => checkDocReferences(prep.value.cwd, r, inspection, prep.value.excludeDirs));
|
|
114
|
+
// A rule that ERRORED proved nothing — it never got as far as checking an
|
|
115
|
+
// id. So it is not `evaluated`, whatever its severity: a warning-severity
|
|
116
|
+
// rule that could not run must not leave the banner saying "not blocking"
|
|
117
|
+
// over an exit code of 0, which is the same half-truth as a green banner
|
|
118
|
+
// above a list of findings.
|
|
119
|
+
const errored = results.filter((r) => r.status === 'error');
|
|
120
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
121
|
+
const blocking = [...failed, ...errored].filter((r) => r.severity === 'error');
|
|
122
|
+
const evaluated = results.filter((r) => r.status !== 'skipped' && r.status !== 'error').length;
|
|
123
|
+
const skipped = results.length - evaluated;
|
|
124
|
+
const exit = blocking.length > 0
|
|
125
|
+
? ExitCode.Failure
|
|
126
|
+
: evaluated === 0 || skipped > 0
|
|
127
|
+
? ExitCode.NotVerified
|
|
128
|
+
: ExitCode.VerifiedPass;
|
|
129
|
+
if (json) {
|
|
130
|
+
process.stdout.write(asJson({
|
|
131
|
+
schema: SCHEMA,
|
|
132
|
+
results: results.map(resultJson),
|
|
133
|
+
evaluated,
|
|
134
|
+
skipped,
|
|
135
|
+
verdict: blocking.length > 0 ? 'errors' : evaluated === 0 ? 'not-verified' : 'pass',
|
|
136
|
+
diagnostics: prep.value.planeDiagnostics,
|
|
137
|
+
exitCode: exit,
|
|
138
|
+
gate: buildGateEnvelope('docs references check', exit, results.map((r) => ({
|
|
139
|
+
id: r.ruleId,
|
|
140
|
+
type: 'doc-reference',
|
|
141
|
+
status: r.status,
|
|
142
|
+
severity: r.severity,
|
|
143
|
+
counts: { files: r.filesScanned, tokens: r.tokensChecked, skipped: r.tokensSkipped },
|
|
144
|
+
violations: r.findings.map((f) => ({
|
|
145
|
+
id: f.token,
|
|
146
|
+
file: f.file,
|
|
147
|
+
line: f.line,
|
|
148
|
+
message: f.message,
|
|
149
|
+
...(f.didYouMean.length > 0 ? { hint: `did you mean: ${f.didYouMean.join(', ')}` } : {}),
|
|
150
|
+
})),
|
|
151
|
+
...(r.skipReason ? { skipReason: r.skipReason } : {}),
|
|
152
|
+
...(r.error ? { error: r.error } : {}),
|
|
153
|
+
}))),
|
|
154
|
+
}) + '\n');
|
|
155
|
+
return exit;
|
|
156
|
+
}
|
|
157
|
+
process.stdout.write(header('Doc references'));
|
|
158
|
+
process.stdout.write(kv('evaluated', `${evaluated} of ${prep.value.rules.length}`) + '\n');
|
|
159
|
+
for (const r of results) {
|
|
160
|
+
if (r.status === 'skipped') {
|
|
161
|
+
process.stdout.write(` – ${r.ruleId} SKIPPED — ${r.skipReason}\n`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (r.status === 'error') {
|
|
165
|
+
process.stdout.write(` ! ${r.ruleId} ${r.error}\n`);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (r.status === 'passed') {
|
|
169
|
+
process.stdout.write(` ✓ ${r.ruleId} (${r.tokensChecked} reference(s) across ${r.filesScanned} doc(s) all resolve)\n`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// A rule that CHECKED nothing failed for a different reason than one with
|
|
173
|
+
// findings, and the loud-skip contract is worthless if it does not say
|
|
174
|
+
// which. `0 unresolved of 0 checked` would send the reader hunting for a
|
|
175
|
+
// bad id when the real problem is a moved directory.
|
|
176
|
+
if (r.skipReason) {
|
|
177
|
+
process.stdout.write(` ✗ ${r.ruleId} FAILED — ${r.skipReason}\n`);
|
|
178
|
+
process.stdout.write(' Nothing was checked, so nothing was enforced. Fix the glob, or set\n' +
|
|
179
|
+
' `failOnEmpty: false` if this rule may legitimately cover no docs.\n');
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
process.stdout.write(` ✗ ${r.ruleId} ${r.findings.length} unresolved reference(s) of ${r.tokensChecked} checked\n`);
|
|
183
|
+
for (const f of r.findings.slice(0, 25)) {
|
|
184
|
+
process.stdout.write(` • ${f.token} (${f.file}:${f.line})\n`);
|
|
185
|
+
if (f.didYouMean.length > 0) {
|
|
186
|
+
process.stdout.write(` did you mean: ${f.didYouMean.join(', ')}\n`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (r.findings.length > 25) {
|
|
190
|
+
process.stdout.write(` … (${r.findings.length - 25} more)\n`);
|
|
191
|
+
}
|
|
192
|
+
const hint = r.findings.find((f) => f.hint)?.hint;
|
|
193
|
+
if (hint)
|
|
194
|
+
process.stdout.write(` → ${hint}\n`);
|
|
195
|
+
}
|
|
196
|
+
for (const d of prep.value.planeDiagnostics)
|
|
197
|
+
process.stdout.write(` ! ${d}\n`);
|
|
198
|
+
// A warning-severity rule reports without blocking, but the banner must
|
|
199
|
+
// still say it FIRED. "Every id resolves ✓" printed under a list of
|
|
200
|
+
// unresolved ids is the kind of half-truth that trains people to stop
|
|
201
|
+
// reading the output.
|
|
202
|
+
const warned = failed.filter((r) => r.severity !== 'error');
|
|
203
|
+
if (exit === ExitCode.VerifiedPass && warned.length > 0) {
|
|
204
|
+
const count = warned.reduce((n, r) => n + r.findings.length, 0);
|
|
205
|
+
process.stdout.write(`\n${count} unresolved reference(s) reported by ${warned.length} warning rule(s) — not blocking.\n`);
|
|
206
|
+
}
|
|
207
|
+
else if (exit === ExitCode.VerifiedPass) {
|
|
208
|
+
process.stdout.write('\nEvery id cited in prose resolves. ✓\n');
|
|
209
|
+
}
|
|
210
|
+
else if (exit === ExitCode.NotVerified) {
|
|
211
|
+
process.stdout.write(errored.length > 0
|
|
212
|
+
? `\n${errored.length} rule(s) could not run — nothing was proved. This is NOT a pass.\n`
|
|
213
|
+
: '\nNothing was checked — this is NOT a pass.\n');
|
|
214
|
+
}
|
|
215
|
+
return exit;
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
export const docsReferencesExplainCommand = {
|
|
219
|
+
name: 'explain',
|
|
220
|
+
description: 'Show every id-shaped token ONE rule considered: where it was found, whether it resolved, and why a token was skipped.',
|
|
221
|
+
usage: 'shrk docs references explain --id <id> [--json]',
|
|
222
|
+
booleanFlags: new Set(['json']),
|
|
223
|
+
async run(args) {
|
|
224
|
+
const id = flagString(args, 'id') ?? args.positional[0];
|
|
225
|
+
if (!id) {
|
|
226
|
+
process.stderr.write('Usage: shrk docs references explain --id <id>\n');
|
|
227
|
+
return ExitCode.UsageError;
|
|
228
|
+
}
|
|
229
|
+
const forwarded = { ...args, flags: new Map(args.flags) };
|
|
230
|
+
forwarded.flags.set('id', id);
|
|
231
|
+
const prep = await prepare(forwarded);
|
|
232
|
+
if (!prep.ok)
|
|
233
|
+
return prep.code;
|
|
234
|
+
const rule = prep.value.rules[0];
|
|
235
|
+
if (!rule)
|
|
236
|
+
return writeNoRules(flagBool(args, 'json'));
|
|
237
|
+
const inspection = await inspectionFor(prep.value.cwd);
|
|
238
|
+
const result = checkDocReferences(prep.value.cwd, rule, inspection, prep.value.excludeDirs);
|
|
239
|
+
if (flagBool(args, 'json')) {
|
|
240
|
+
process.stdout.write(asJson({
|
|
241
|
+
schema: 'sharkcraft.doc-references-explain/v1',
|
|
242
|
+
...resultJson(result),
|
|
243
|
+
tokens: result.tokens,
|
|
244
|
+
resolvesAs: rule.resolvesAs,
|
|
245
|
+
requireContext: rule.requireContext ?? 'backtick',
|
|
246
|
+
}) + '\n');
|
|
247
|
+
return ExitCode.VerifiedPass;
|
|
248
|
+
}
|
|
249
|
+
process.stdout.write(header(`Doc references: ${rule.id}`));
|
|
250
|
+
if (rule.description)
|
|
251
|
+
process.stdout.write(` ${rule.description}\n`);
|
|
252
|
+
process.stdout.write(kv('files', rule.files.join(', ')) + '\n');
|
|
253
|
+
process.stdout.write(kv('token pattern', `/${rule.tokenPattern}/`) + '\n');
|
|
254
|
+
process.stdout.write(kv('resolves as', rule.resolvesAs.join(', ')) + '\n');
|
|
255
|
+
process.stdout.write(kv('context gate', rule.requireContext ?? 'backtick') + '\n');
|
|
256
|
+
process.stdout.write(kv('docs scanned', String(result.filesScanned)) + '\n');
|
|
257
|
+
process.stdout.write(kv('tokens', `${result.tokensChecked} checked, ${result.tokensSkipped} skipped`) + '\n');
|
|
258
|
+
if (result.error)
|
|
259
|
+
process.stdout.write(` ! ${result.error}\n`);
|
|
260
|
+
process.stdout.write('\n');
|
|
261
|
+
for (const t of result.tokens.slice(0, 100)) {
|
|
262
|
+
const verdict = t.skipped
|
|
263
|
+
? `skipped (${t.skipped})`
|
|
264
|
+
: t.resolvedAs
|
|
265
|
+
? `✓ ${t.resolvedAs}`
|
|
266
|
+
: '✗ UNRESOLVED';
|
|
267
|
+
process.stdout.write(` ${t.token} (${t.file}:${t.line}) — ${verdict}\n`);
|
|
268
|
+
}
|
|
269
|
+
if (result.tokens.length > 100) {
|
|
270
|
+
process.stdout.write(` … (${result.tokens.length - 100} more)\n`);
|
|
271
|
+
}
|
|
272
|
+
return ExitCode.VerifiedPass;
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
export const docsReferencesListCommand = {
|
|
276
|
+
name: 'list',
|
|
277
|
+
description: 'List every declared doc-reference rule: its globs, token shape, and target registries.',
|
|
278
|
+
usage: 'shrk docs references list [--json]',
|
|
279
|
+
booleanFlags: new Set(['json']),
|
|
280
|
+
async run(args) {
|
|
281
|
+
const prep = await prepare(args);
|
|
282
|
+
if (!prep.ok)
|
|
283
|
+
return prep.code;
|
|
284
|
+
const json = flagBool(args, 'json');
|
|
285
|
+
if (prep.value.all.length === 0)
|
|
286
|
+
return writeNoRules(json);
|
|
287
|
+
if (json) {
|
|
288
|
+
process.stdout.write(asJson({ schema: SCHEMA, rules: prep.value.all }) + '\n');
|
|
289
|
+
return ExitCode.VerifiedPass;
|
|
290
|
+
}
|
|
291
|
+
process.stdout.write(header(`Doc-reference rules (${prep.value.all.length})`));
|
|
292
|
+
for (const r of prep.value.all) {
|
|
293
|
+
process.stdout.write(` • ${r.id}\n`);
|
|
294
|
+
process.stdout.write(` files ${r.files.join(', ')}\n`);
|
|
295
|
+
process.stdout.write(` token /${r.tokenPattern}/\n`);
|
|
296
|
+
process.stdout.write(` resolves ${r.resolvesAs.join(', ')}\n`);
|
|
297
|
+
process.stdout.write(` context ${r.requireContext ?? 'backtick'}\n`);
|
|
298
|
+
if (r.description)
|
|
299
|
+
process.stdout.write(` ${r.description}\n`);
|
|
300
|
+
}
|
|
301
|
+
return ExitCode.VerifiedPass;
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
/** `shrk docs references <verb>` — dispatches to check / explain / list. */
|
|
305
|
+
export const docsReferencesCommand = {
|
|
306
|
+
name: 'references',
|
|
307
|
+
description: 'Prose-reference linter: assert every id cited in free text (READMEs, docs, agent skill files) still resolves to a registered id.',
|
|
308
|
+
usage: 'shrk docs references check | explain --id <id> | list',
|
|
309
|
+
booleanFlags: new Set(['json']),
|
|
310
|
+
async run(args) {
|
|
311
|
+
const sub = args.positional[0];
|
|
312
|
+
const rest = { ...args, positional: args.positional.slice(1) };
|
|
313
|
+
if (sub === 'check')
|
|
314
|
+
return docsReferencesCheckCommand.run(rest);
|
|
315
|
+
if (sub === 'explain')
|
|
316
|
+
return docsReferencesExplainCommand.run(rest);
|
|
317
|
+
if (sub === 'list')
|
|
318
|
+
return docsReferencesListCommand.run(rest);
|
|
319
|
+
process.stderr.write((sub ? `Unknown subcommand "${sub}". ` : '') +
|
|
320
|
+
'Usage: shrk docs references check | explain --id <id> | list\n');
|
|
321
|
+
return ExitCode.UsageError;
|
|
322
|
+
},
|
|
323
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.command.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.command.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.command.ts"],"names":[],"mappings":"AAsBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA6PhC,eAAO,MAAM,aAAa,EAAE,eAW3B,CAAC;AA2fF,eAAO,MAAM,qBAAqB,EAAE,eAmCnC,CAAC;AAuDF,eAAO,MAAM,yBAAyB,EAAE,eAavC,CAAC;AAIF,eAAO,MAAM,wBAAwB,EAAE,eA2CtC,CAAC;AAgCF,eAAO,MAAM,6BAA6B,EAAE,eAa3C,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { detectGraphFreshness } from '@shrkcrft/graph';
|
|
1
2
|
import { buildAcknowledgement, buildAiReadinessReport, buildSuppressionEntry, doctorSuppressionsFile, DoctorSeverity, filterDoctorResult, inspectSharkcraft, loadDoctorSuppressions, renderAcknowledgementsText, runDoctor, saveDoctorSuppressions, summarizeAcknowledgements, } from '@shrkcrft/inspector';
|
|
2
3
|
import { detectProjectShape } from '@shrkcrft/workspace';
|
|
3
4
|
import { loadSurfaceContext } from "../surface/load-surface-context.js";
|
|
@@ -236,7 +237,7 @@ async function doctorCommandImpl(args) {
|
|
|
236
237
|
inspectOpts.loaderTimeoutMs = loaderTimeout;
|
|
237
238
|
}
|
|
238
239
|
const inspection = await inspectSharkcraft(inspectOpts);
|
|
239
|
-
const result = augmentWithSemanticIndexCheck(runDoctor(inspection), cwd);
|
|
240
|
+
const result = augmentWithSemanticIndexCheck(runDoctor(inspection, { graphDivergence: detectGraphFreshness(cwd) }), cwd);
|
|
240
241
|
const report = buildAiReadinessReport(inspection);
|
|
241
242
|
if (debug) {
|
|
242
243
|
process.stderr.write(`[debug] inspection elapsed ${inspection.inspectionElapsedMs}ms cache=${inspection.cacheEnabled ? 'on' : 'off'} loaders=${inspection.loaderDiagnostics.length}\n`);
|
|
@@ -707,7 +708,7 @@ async function suppressionsListRun(args) {
|
|
|
707
708
|
async function suppressionsCheckRun(args) {
|
|
708
709
|
const cwd = resolveCwd(args);
|
|
709
710
|
const inspection = await inspectSharkcraft({ cwd });
|
|
710
|
-
const result = runDoctor(inspection);
|
|
711
|
+
const result = runDoctor(inspection, { graphDivergence: detectGraphFreshness(cwd) });
|
|
711
712
|
const cfg = loadDoctorSuppressions(cwd);
|
|
712
713
|
const filtered = filterDoctorResult(result, { suppressions: cfg.doctorSuppressions });
|
|
713
714
|
const unused = filtered.appliedSuppressions.filter((a) => a.matched === 0);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
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
|
+
export declare const gatesCheckCommand: ICommandHandler;
|
|
5
|
+
export declare const gatesTryCommand: ICommandHandler;
|
|
4
6
|
export declare const gatesExplainCommand: ICommandHandler;
|
|
5
7
|
/**
|
|
6
8
|
* Try to explain `id` as a data-defined rule on ANY plane.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gates.command.d.ts","sourceRoot":"","sources":["../../src/commands/gates.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gates.command.d.ts","sourceRoot":"","sources":["../../src/commands/gates.command.ts"],"names":[],"mappings":"AA6CA,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,UAAU,EAChB,MAAM,wBAAwB,CAAC;AAgOhC,eAAO,MAAM,gBAAgB,EAAE,eAsD9B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eA+KlC,CAAC;AAGF,eAAO,MAAM,iBAAiB,EAAE,eAwK/B,CAAC;AAkCF,eAAO,MAAM,eAAe,EAAE,eAgN7B,CAAC;AA+FF,eAAO,MAAM,mBAAmB,EAAE,eAmGjC,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,eAuB1B,CAAC"}
|