@shrkcrft/quality-gates 0.1.0-alpha.30 → 0.1.0-alpha.31

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.
@@ -1 +1 @@
1
- {"version":3,"file":"arch-gate.d.ts","sourceRoot":"","sources":["../../src/gates/arch-gate.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,WAAW,CAyKzF"}
1
+ {"version":3,"file":"arch-gate.d.ts","sourceRoot":"","sources":["../../src/gates/arch-gate.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,WAAW,CAyKzF"}
@@ -1,4 +1,4 @@
1
- import { ArchReportStore, diffSnapshots, runArchCheck, snapshotFromReport, violationId, } from '@shrkcrft/architecture-guard';
1
+ import { ArchReportStore, archStoreMissing, diffSnapshots, runArchCheck, snapshotFromReport, violationId, } from '@shrkcrft/architecture-guard';
2
2
  /**
3
3
  * Architecture-guard gate.
4
4
  *
@@ -21,7 +21,7 @@ import { ArchReportStore, diffSnapshots, runArchCheck, snapshotFromReport, viola
21
21
  export function archGate(projectRoot, options = {}) {
22
22
  const start = Date.now();
23
23
  const report = runArchCheck({ projectRoot });
24
- if (report.diagnostics.some((d) => d.includes('code-graph store missing'))) {
24
+ if (archStoreMissing(report)) {
25
25
  return {
26
26
  id: 'arch',
27
27
  label: 'Architecture',
@@ -1,8 +1,18 @@
1
+ import { type IGraphFreshness } from '@shrkcrft/graph';
1
2
  import type { IGateResult } from '../schema/quality-gate.js';
2
3
  /**
3
- * Check whether the code-graph store exists AND the manifest digest
4
- * matches the on-disk JSONL fingerprints. Catches: missing index;
5
- * tampered / partial store; schema mismatch.
4
+ * Check whether the code-graph store exists, its manifest digest matches the
5
+ * on-disk JSONL fingerprints, AND it is current with the working tree.
6
+ * Catches: missing index; tampered / partial store; schema mismatch; an index
7
+ * behind the working tree.
8
+ *
9
+ * Digest-valid is not current (round 11 review R11-GAP-2): a store can be
10
+ * intact yet stale. Freshness is decided by THE authority —
11
+ * `detectGraphFreshness`, the one `graph status`, doctor, code-intel and MCP
12
+ * read (the runner passes the measurement it already made) — so this gate can
13
+ * never call "fresh" an index `graph status` calls stale. A stale index is a
14
+ * `warn` carrying the shared coverage record, which settles `shrk gate` and MCP
15
+ * `get_quality_gate` to NOT VERIFIED (2).
6
16
  */
7
- export declare function graphFreshGate(projectRoot: string): IGateResult;
17
+ export declare function graphFreshGate(projectRoot: string, freshness?: IGraphFreshness): IGateResult;
8
18
  //# sourceMappingURL=graph-fresh-gate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-fresh-gate.d.ts","sourceRoot":"","sources":["../../src/gates/graph-fresh-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAgC/D"}
1
+ {"version":3,"file":"graph-fresh-gate.d.ts","sourceRoot":"","sources":["../../src/gates/graph-fresh-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,eAAe,GAAG,WAAW,CAsD5F"}
@@ -1,10 +1,19 @@
1
- import { GraphStore } from '@shrkcrft/graph';
1
+ import { detectGraphFreshness, GraphStore, graphFreshnessBehind, graphFreshnessCoverage, graphFreshnessRemedy, } from '@shrkcrft/graph';
2
2
  /**
3
- * Check whether the code-graph store exists AND the manifest digest
4
- * matches the on-disk JSONL fingerprints. Catches: missing index;
5
- * tampered / partial store; schema mismatch.
3
+ * Check whether the code-graph store exists, its manifest digest matches the
4
+ * on-disk JSONL fingerprints, AND it is current with the working tree.
5
+ * Catches: missing index; tampered / partial store; schema mismatch; an index
6
+ * behind the working tree.
7
+ *
8
+ * Digest-valid is not current (round 11 review R11-GAP-2): a store can be
9
+ * intact yet stale. Freshness is decided by THE authority —
10
+ * `detectGraphFreshness`, the one `graph status`, doctor, code-intel and MCP
11
+ * read (the runner passes the measurement it already made) — so this gate can
12
+ * never call "fresh" an index `graph status` calls stale. A stale index is a
13
+ * `warn` carrying the shared coverage record, which settles `shrk gate` and MCP
14
+ * `get_quality_gate` to NOT VERIFIED (2).
6
15
  */
7
- export function graphFreshGate(projectRoot) {
16
+ export function graphFreshGate(projectRoot, freshness) {
8
17
  const start = Date.now();
9
18
  const store = new GraphStore(projectRoot);
10
19
  if (!store.exists()) {
@@ -29,6 +38,28 @@ export function graphFreshGate(projectRoot) {
29
38
  durationMs: Date.now() - start,
30
39
  };
31
40
  }
41
+ const fresh = freshness ?? detectGraphFreshness(projectRoot);
42
+ const coverage = graphFreshnessCoverage(fresh, 'graph-fresh');
43
+ if (coverage) {
44
+ return {
45
+ id: 'graph-fresh',
46
+ label: 'Code graph indexed',
47
+ status: 'warn',
48
+ message: fresh.hasIndex
49
+ ? `Code-graph index is stale — ${fresh.modified.length} modified, ${fresh.added.length} new, ${fresh.deleted.length} deleted, ${fresh.packagesChanged.length} package(s) changed since it was built (NOT VERIFIED).`
50
+ : 'Code-graph index freshness could not be measured against the working tree (NOT VERIFIED).',
51
+ details: {
52
+ behind: graphFreshnessBehind(fresh),
53
+ modified: fresh.modified.length,
54
+ added: fresh.added.length,
55
+ deleted: fresh.deleted.length,
56
+ packagesChanged: fresh.packagesChanged,
57
+ },
58
+ coverage: [coverage],
59
+ nextCommands: [graphFreshnessRemedy(fresh)],
60
+ durationMs: Date.now() - start,
61
+ };
62
+ }
32
63
  return {
33
64
  id: 'graph-fresh',
34
65
  label: 'Code graph indexed',
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge-symbol-gate.d.ts","sourceRoot":"","sources":["../../src/gates/knowledge-symbol-gate.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAK7D,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,2BAAgC,GACxC,WAAW,CA0Eb"}
1
+ {"version":3,"file":"knowledge-symbol-gate.d.ts","sourceRoot":"","sources":["../../src/gates/knowledge-symbol-gate.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAK7D,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,2BAAgC,GACxC,WAAW,CAmHb"}
@@ -1,5 +1,6 @@
1
+ import { coverageShortfall } from '@shrkcrft/core';
1
2
  import { loadGraphApiCached } from '@shrkcrft/graph';
2
- import { buildKnowledgeStaleReport, ReferenceCheckOutcome, } from '@shrkcrft/inspector';
3
+ import { buildKnowledgeStaleReport, declaredReferenceCoverage, ReferenceCheckOutcome, } from '@shrkcrft/inspector';
3
4
  /** Reference kinds this gate is responsible for (symbol-ref integrity). */
4
5
  const SCOPED_REF_KINDS = new Set(['symbol', 'file']);
5
6
  /**
@@ -50,13 +51,51 @@ export function knowledgeSymbolGate(projectRoot, options = {}) {
50
51
  };
51
52
  }
52
53
  const broken = scoped.filter((c) => c.outcome === ReferenceCheckOutcome.Stale || c.outcome === ReferenceCheckOutcome.Missing);
54
+ // THE declared-reference fold (`declaredReferenceCoverage`) — the one the
55
+ // stale-check's own verdict settles on — restricted to this gate's kinds.
56
+ // Only a CHECKED reference counts as examined: an `unknown` one (unpinned,
57
+ // ambiguous, a file that failed to read) is neither resolving nor broken,
58
+ // and a malformed one was never checked. This gate used to count both as
59
+ // "resolve" and PASS where `shrk knowledge stale-check` exits 2.
60
+ const refCoverage = { ...declaredReferenceCoverage({ references: scoped }), subject: 'knowledge-symbol' };
61
+ const gateCoverage = [refCoverage];
62
+ const ok = scoped.filter((c) => c.outcome === ReferenceCheckOutcome.Ok).length;
63
+ const unknown = scoped.filter((c) => c.outcome === ReferenceCheckOutcome.Unknown).length;
64
+ const invalid = scoped.filter((c) => c.outcome === ReferenceCheckOutcome.Invalid).length;
65
+ const tally = `${ok} of ${evaluated} symbol/file reference(s) resolve` +
66
+ (unknown > 0 ? `; ${unknown} could not be verified (unpinned, ambiguous or unreadable)` : '') +
67
+ (invalid > 0 ? `; ${invalid} malformed` : '');
68
+ // This gate is scoped to symbol/file references, so its pass says nothing
69
+ // about entries that declare none — name them instead of letting "N resolve"
70
+ // read as "the corpus is healthy". (`shrk knowledge stale-check` gates them.)
71
+ const coverage = report.coverage;
72
+ const unverifiableNote = coverage.unverifiable > 0
73
+ ? `; ${coverage.unverifiable} of ${coverage.entriesInScope} entries unverifiable (no checkable reference — see \`shrk knowledge stale-check\`)`
74
+ : '';
53
75
  if (broken.length === 0) {
76
+ const shortfall = coverageShortfall(refCoverage);
77
+ if (shortfall !== undefined) {
78
+ // Nothing broken among what was checked, but not everything asked for
79
+ // was checked: NOT VERIFIED — `shrk gate` settles it to 2 through the
80
+ // coverage below, as `knowledge stale-check` does.
81
+ return {
82
+ id: 'knowledge-symbol',
83
+ label: 'Knowledge symbol refs',
84
+ status: 'warn',
85
+ message: `NOT VERIFIED — ${shortfall}. ${tally}${unverifiableNote}. This is not a pass.`,
86
+ details: { evaluated, ok, unknown, invalid, graphResolved: Boolean(graph), coverage, shortfalls: [shortfall] },
87
+ coverage: gateCoverage,
88
+ nextCommands: ['shrk knowledge stale-check'],
89
+ durationMs: Date.now() - start,
90
+ };
91
+ }
54
92
  return {
55
93
  id: 'knowledge-symbol',
56
94
  label: 'Knowledge symbol refs',
57
95
  status: 'pass',
58
- message: `${evaluated} symbol/file reference(s) resolve.`,
59
- details: { evaluated, graphResolved: Boolean(graph) },
96
+ message: `${tally}${unverifiableNote}.`,
97
+ details: { evaluated, ok, unknown, invalid, graphResolved: Boolean(graph), coverage },
98
+ coverage: gateCoverage,
60
99
  durationMs: Date.now() - start,
61
100
  };
62
101
  }
@@ -73,7 +112,8 @@ export function knowledgeSymbolGate(projectRoot, options = {}) {
73
112
  label: 'Knowledge symbol refs',
74
113
  status: failOnStale ? 'fail' : 'warn',
75
114
  message: `${broken.length}/${evaluated} symbol/file reference(s) stale or missing (moved/renamed).`,
76
- details: { evaluated, broken: broken.length, samples, graphResolved: Boolean(graph) },
115
+ details: { evaluated, broken: broken.length, ok, unknown, invalid, samples, graphResolved: Boolean(graph) },
116
+ coverage: gateCoverage,
77
117
  nextCommands: ['shrk knowledge audit', 'shrk doctor'],
78
118
  durationMs: Date.now() - start,
79
119
  };
@@ -1,4 +1,4 @@
1
- import type { IPolicyRule } from '@shrkcrft/core';
1
+ import { type IPolicyRule } from '@shrkcrft/core';
2
2
  import type { IGateResult } from '../schema/quality-gate.js';
3
3
  export interface IPolicyLintGateOptions {
4
4
  /** The project's policy rules (from `sharkcraft.config.ts` `policyRules[]`). */
@@ -13,6 +13,12 @@ export interface IPolicyLintGateOptions {
13
13
  * disable the policy plane.
14
14
  */
15
15
  configError?: string;
16
+ /**
17
+ * Project-relative directories the walk prunes: THE plane scan scope
18
+ * (`planeScanExcludeDirs`), the one `policy-lint` uses. The caller passes it
19
+ * so this gate and the verb examine the same files for the same rule.
20
+ */
21
+ excludeDirs?: readonly string[];
16
22
  }
17
23
  /**
18
24
  * The "policy plane" gate: runs the project's data-defined policy-lint rules
@@ -1 +1 @@
1
- {"version":3,"file":"policy-lint-gate.d.ts","sourceRoot":"","sources":["../../src/gates/policy-lint-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,yFAAyF;IACzF,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,WAAW,CA2ErG"}
1
+ {"version":3,"file":"policy-lint-gate.d.ts","sourceRoot":"","sources":["../../src/gates/policy-lint-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,yFAAyF;IACzF,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,WAAW,CA4IrG"}
@@ -1,3 +1,4 @@
1
+ import { coverageShortfall } from '@shrkcrft/core';
1
2
  import { runPolicyLint } from '@shrkcrft/boundaries';
2
3
  /**
3
4
  * The "policy plane" gate: runs the project's data-defined policy-lint rules
@@ -14,6 +15,16 @@ export function policyLintGate(projectRoot, options = {}) {
14
15
  label: 'Policy lint',
15
16
  status: 'warn',
16
17
  message: `Config could not be loaded — policy rules not evaluated: ${options.configError}`,
18
+ // Nothing declared was examined — never a clean `shrk gate` exit.
19
+ coverage: [
20
+ {
21
+ unit: 'config files',
22
+ expected: 1,
23
+ examined: 0,
24
+ subject: 'policy',
25
+ reason: 'failed to load, so no policy rule was evaluated',
26
+ },
27
+ ],
17
28
  nextCommands: ['shrk doctor'],
18
29
  durationMs: Date.now() - start,
19
30
  };
@@ -30,12 +41,14 @@ export function policyLintGate(projectRoot, options = {}) {
30
41
  }
31
42
  const report = runPolicyLint(projectRoot, rules, {
32
43
  ...(options.changedOnly ? { changedOnly: true, changedFiles: options.changedFiles ?? [] } : {}),
44
+ ...(options.excludeDirs ? { excludeDirs: options.excludeDirs } : {}),
33
45
  });
34
- // Loud zero case: rules exist but none actually scanned a file (their globs
35
- // matched no files e.g. a `style` rule in a project with no stylesheets or
36
- // `--changed-only` filtered them all out). Surface it as skipped rather than a
37
- // silent green pass.
38
- if (report.rules.length === 0 || report.evaluated === 0) {
46
+ // Nothing SELECTED: no rule has content in the run's scope (`--changed-only`
47
+ // left noneno rule's globs match content the change touched). That is
48
+ // deliberate narrowing, not a gap in any rule skipped, loudly, never a
49
+ // pass. A rule that WAS selected but scanned nothing is not this case; it
50
+ // carries its coverage below.
51
+ if (report.rules.length === 0) {
39
52
  return {
40
53
  id: 'policy',
41
54
  label: 'Policy lint',
@@ -45,35 +58,86 @@ export function policyLintGate(projectRoot, options = {}) {
45
58
  durationMs: Date.now() - start,
46
59
  };
47
60
  }
61
+ // The engine's per-rule coverage (`IPolicyRuleResult.coverage`), owned by the
62
+ // rule id — the ONE record `policy-lint`, `gates check` and `quality` settle
63
+ // on. Carried on EVERY result below, so `shrk gate` settles its exit on the
64
+ // same records (settleVerdict): a rule that scanned nothing is not a pass
65
+ // here either, whether it sits next to a live rule or alone.
66
+ const coverage = report.rules.map((r) => ({ ...r.coverage, subject: r.coverage.subject ?? r.ruleId }));
67
+ const shortfalls = coverage.flatMap((c) => {
68
+ const s = coverageShortfall(c);
69
+ return s === undefined ? [] : [`${c.subject}: ${s}`];
70
+ });
71
+ const more = shortfalls.length > 1 ? ` (+${shortfalls.length - 1} more)` : '';
72
+ // A rule that matched nothing under `failOnEmpty` (the default at `error`
73
+ // severity) FAILED: `policy-lint` exits 1 on it, so this gate fails too.
74
+ const emptyFailed = report.skipped.filter((s) => s.failed).map((s) => s.ruleId);
75
+ const failedOnEmpty = emptyFailed.length > 0 ? { failedOnEmpty: emptyFailed } : {};
76
+ const failed = report.verdict === 'errors' || emptyFailed.length > 0;
48
77
  const errors = report.findings.filter((f) => f.severity === 'error').length;
49
78
  const warnings = report.findings.filter((f) => f.severity === 'warning').length;
50
79
  const samples = report.findings
51
80
  .slice(0, 8)
52
81
  .map((f) => `${f.ruleId}: ${f.match} (${f.file}:${f.line})`);
53
82
  const diagnostics = report.diagnostics;
83
+ // Rules were selected, but none scanned a content unit (their globs matched
84
+ // no files). The selected rule set proved nothing: `warn` + coverage (NOT
85
+ // VERIFIED — `shrk gate` settles it to 2), or `fail` when a failOnEmpty rule
86
+ // failed. Never `skipped`, which `shrk gate` would read as a clean 0.
87
+ if (report.evaluated === 0) {
88
+ return {
89
+ id: 'policy',
90
+ label: 'Policy lint',
91
+ status: failed ? 'fail' : 'warn',
92
+ message: failed
93
+ ? `FAILED — nothing evaluated: ${emptyFailed.length} rule(s) matched no content and fail on empty (${emptyFailed.join(', ')}). A rule that matches nothing is a bug in the rule.`
94
+ : `NOT VERIFIED — nothing evaluated: ${shortfalls[0] ?? 'no selected rule scanned a content unit'}${more}. This is not a pass.`,
95
+ details: { rules: report.rules.length, evaluated: 0, shortfalls, ...failedOnEmpty },
96
+ coverage,
97
+ nextCommands: ['shrk policy-lint'],
98
+ durationMs: Date.now() - start,
99
+ };
100
+ }
54
101
  if (report.verdict === 'pass') {
102
+ if (shortfalls.length > 0) {
103
+ return {
104
+ id: 'policy',
105
+ label: 'Policy lint',
106
+ status: 'warn',
107
+ message: `NOT VERIFIED — ${shortfalls[0]}${more}. No violations among what was scanned; this is not a pass.`,
108
+ details: { rules: report.rules.length, evaluated: report.evaluated, shortfalls },
109
+ coverage,
110
+ nextCommands: ['shrk policy-lint'],
111
+ durationMs: Date.now() - start,
112
+ };
113
+ }
55
114
  return {
56
115
  id: 'policy',
57
116
  label: 'Policy lint',
58
117
  status: 'pass',
59
118
  message: `${report.rules.length} policy rule(s) — no violations.`,
60
119
  details: { rules: report.rules.length, evaluated: report.evaluated },
120
+ coverage,
61
121
  durationMs: Date.now() - start,
62
122
  };
63
123
  }
64
124
  const misconfig = diagnostics.length > 0 ? `, ${diagnostics.length} misconfigured rule(s)` : '';
125
+ const empty = emptyFailed.length > 0 ? `, ${emptyFailed.length} rule(s) matched nothing (failOnEmpty: ${emptyFailed.join(', ')})` : '';
65
126
  return {
66
127
  id: 'policy',
67
128
  label: 'Policy lint',
68
- status: report.verdict === 'errors' ? 'fail' : 'warn',
69
- message: `${errors} error(s), ${warnings} warning(s)${misconfig}: policy violation(s).`,
129
+ status: failed ? 'fail' : 'warn',
130
+ message: `${errors} error(s), ${warnings} warning(s)${misconfig}${empty}: policy violation(s).`,
70
131
  details: {
71
132
  errors,
72
133
  warnings,
73
134
  samples,
74
135
  evaluated: report.evaluated,
75
136
  ...(diagnostics.length > 0 ? { diagnostics } : {}),
137
+ ...(shortfalls.length > 0 ? { shortfalls } : {}),
138
+ ...failedOnEmpty,
76
139
  },
140
+ coverage,
77
141
  nextCommands: ['shrk policy-lint'],
78
142
  durationMs: Date.now() - start,
79
143
  };
@@ -1,4 +1,4 @@
1
- import type { IWiringRule } from '@shrkcrft/core';
1
+ import { type IWiringRule } from '@shrkcrft/core';
2
2
  import type { IGateResult } from '../schema/quality-gate.js';
3
3
  export interface IWiringGateOptions {
4
4
  /** The project's wiring rules (from `sharkcraft.config.ts` `wiringRules[]`). */
@@ -13,6 +13,12 @@ export interface IWiringGateOptions {
13
13
  * disable the wiring plane.
14
14
  */
15
15
  configError?: string;
16
+ /**
17
+ * Project-relative directories the walk prunes: THE plane scan scope
18
+ * (`planeScanExcludeDirs`), the one `check wiring` uses. The caller passes
19
+ * it so this gate and the verb examine the same files for the same rule.
20
+ */
21
+ excludeDirs?: readonly string[];
16
22
  }
17
23
  /**
18
24
  * The "completeness plane" gate: runs the project's data-defined wiring rules
@@ -1 +1 @@
1
- {"version":3,"file":"wiring-gate.d.ts","sourceRoot":"","sources":["../../src/gates/wiring-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,WAAW,CAwE7F"}
1
+ {"version":3,"file":"wiring-gate.d.ts","sourceRoot":"","sources":["../../src/gates/wiring-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,WAAW,CA+I7F"}
@@ -1,3 +1,4 @@
1
+ import { coverageShortfall } from '@shrkcrft/core';
1
2
  import { runWiring } from '@shrkcrft/boundaries';
2
3
  /**
3
4
  * The "completeness plane" gate: runs the project's data-defined wiring rules
@@ -12,6 +13,16 @@ export function wiringGate(projectRoot, options = {}) {
12
13
  label: 'Wiring (completeness)',
13
14
  status: 'warn',
14
15
  message: `Config could not be loaded — wiring rules not evaluated: ${options.configError}`,
16
+ // Nothing declared was examined — never a clean `shrk gate` exit.
17
+ coverage: [
18
+ {
19
+ unit: 'config files',
20
+ expected: 1,
21
+ examined: 0,
22
+ subject: 'wiring',
23
+ reason: 'failed to load, so no wiring rule was evaluated',
24
+ },
25
+ ],
15
26
  nextCommands: ['shrk doctor'],
16
27
  durationMs: Date.now() - start,
17
28
  };
@@ -28,11 +39,13 @@ export function wiringGate(projectRoot, options = {}) {
28
39
  }
29
40
  const report = runWiring(projectRoot, rules, {
30
41
  ...(options.changedOnly ? { changedOnly: true, changedFiles: options.changedFiles ?? [] } : {}),
42
+ ...(options.excludeDirs ? { excludeDirs: options.excludeDirs } : {}),
31
43
  });
32
- // Loud zero case: rules exist but none actually ran a comparison (their globs
33
- // matched no files, or `--changed-only` filtered them all out). Surface it as
34
- // skipped rather than a silent green pass.
35
- if (report.rules.length === 0 || report.evaluated === 0) {
44
+ // Nothing SELECTED: `--changed-only` filtered every rule out (no rule's
45
+ // footprint intersects the changeset). That is deliberate narrowing, not a
46
+ // gap in any rule — skipped, loudly, never a pass. A rule that WAS selected
47
+ // but examined nothing is not this case; it carries its coverage below.
48
+ if (report.rules.length === 0) {
36
49
  return {
37
50
  id: 'wiring',
38
51
  label: 'Wiring (completeness)',
@@ -46,29 +59,86 @@ export function wiringGate(projectRoot, options = {}) {
46
59
  const warnings = report.violations.filter((v) => v.severity === 'warning').length;
47
60
  const samples = report.violations.slice(0, 8).map((v) => `${v.ruleId}: ${v.token} (${v.file}:${v.line})`);
48
61
  const diagnostics = report.diagnostics;
62
+ // Every rule's coverage shortfall, through core's one rule — the same set
63
+ // `check wiring` / `gates check` settle their exit on. A rule that passed
64
+ // over part of its scope (a subset rule whose declared selector never
65
+ // produced some registered tokens) or checked nothing is NOT a pass here
66
+ // either, so `shrk gate`, the MCP quality-gate tool and the dashboard cannot
67
+ // read green where `check wiring` reads not-verified.
68
+ // The engine's per-rule coverage, owned by the rule id — carried on the
69
+ // result so `shrk gate` settles its EXIT on the same records (settleVerdict),
70
+ // and the `shortfalls` detail below is a rendering of them, not a re-derivation.
71
+ const coverage = report.rules.map((r) => ({ ...r.coverage, subject: r.coverage.subject ?? r.ruleId }));
72
+ const shortfalls = coverage.flatMap((c) => {
73
+ const s = coverageShortfall(c);
74
+ return s === undefined ? [] : [`${c.subject}: ${s}`];
75
+ });
76
+ const more = shortfalls.length > 1 ? ` (+${shortfalls.length - 1} more)` : '';
77
+ // A rule that matched nothing under `failOnEmpty` carries no violation of its
78
+ // own — named next to the counts so a failing result says what failed.
79
+ const emptyFailed = report.skipped.filter((s) => s.failed).map((s) => s.ruleId);
80
+ const failedOnEmpty = emptyFailed.length > 0 ? { failedOnEmpty: emptyFailed } : {};
81
+ // Rules were SELECTED, but none ran a comparison (their source globs matched
82
+ // no files, or extracted no ids). The selected rule set proved nothing:
83
+ // `warn` + coverage (NOT VERIFIED — `shrk gate` settles it to 2), or `fail`
84
+ // when an error-severity failOnEmpty rule failed — exactly where `check
85
+ // wiring` exits 1. Never `skipped`, which `shrk gate` would read as a 0.
86
+ if (report.evaluated === 0) {
87
+ const failed = report.verdict === 'errors';
88
+ return {
89
+ id: 'wiring',
90
+ label: 'Wiring (completeness)',
91
+ status: failed ? 'fail' : 'warn',
92
+ message: failed
93
+ ? `FAILED — nothing evaluated: ${emptyFailed.length} rule(s) matched nothing and fail on empty (${emptyFailed.join(', ')}). A rule that matches nothing is a bug in the rule.`
94
+ : `NOT VERIFIED — nothing evaluated: ${shortfalls[0] ?? 'no selected rule ran a comparison'}${more}. This is not a pass.`,
95
+ details: { rules: report.rules.length, evaluated: 0, shortfalls, ...failedOnEmpty },
96
+ coverage,
97
+ nextCommands: ['shrk check wiring'],
98
+ durationMs: Date.now() - start,
99
+ };
100
+ }
49
101
  if (report.verdict === 'pass') {
102
+ if (shortfalls.length > 0) {
103
+ return {
104
+ id: 'wiring',
105
+ label: 'Wiring (completeness)',
106
+ status: 'warn',
107
+ message: `NOT VERIFIED — ${shortfalls[0]}${more}. No violations among what was examined; this is not a pass.`,
108
+ details: { rules: report.rules.length, evaluated: report.evaluated, shortfalls },
109
+ coverage,
110
+ nextCommands: ['shrk check wiring'],
111
+ durationMs: Date.now() - start,
112
+ };
113
+ }
50
114
  return {
51
115
  id: 'wiring',
52
116
  label: 'Wiring (completeness)',
53
117
  status: 'pass',
54
118
  message: `${report.rules.length} wiring rule(s) — every declared token is wired.`,
55
119
  details: { rules: report.rules.length, evaluated: report.evaluated },
120
+ // Carries any explicit acceptance (registeredExtras) so it is printed.
121
+ coverage,
56
122
  durationMs: Date.now() - start,
57
123
  };
58
124
  }
59
125
  const misconfig = diagnostics.length > 0 ? `, ${diagnostics.length} misconfigured rule(s)` : '';
126
+ const empty = emptyFailed.length > 0 ? `, ${emptyFailed.length} rule(s) matched nothing (failOnEmpty: ${emptyFailed.join(', ')})` : '';
60
127
  return {
61
128
  id: 'wiring',
62
129
  label: 'Wiring (completeness)',
63
130
  status: report.verdict === 'errors' ? 'fail' : 'warn',
64
- message: `${errors} error(s), ${warnings} warning(s)${misconfig}: declared but not wired.`,
131
+ message: `${errors} error(s), ${warnings} warning(s)${misconfig}${empty}: declared but not wired.`,
65
132
  details: {
66
133
  errors,
67
134
  warnings,
68
135
  samples,
69
136
  evaluated: report.evaluated,
70
137
  ...(diagnostics.length > 0 ? { diagnostics } : {}),
138
+ ...(shortfalls.length > 0 ? { shortfalls } : {}),
139
+ ...failedOnEmpty,
71
140
  },
141
+ coverage,
72
142
  nextCommands: ['shrk check wiring'],
73
143
  durationMs: Date.now() - start,
74
144
  };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,9 @@ export * from './gates/wiring-gate.js';
13
13
  export * from './gates/policy-lint-gate.js';
14
14
  export * from './gates/knowledge-symbol-gate.js';
15
15
  export * from './runner/run-gates.js';
16
+ export * from './runner/prepare-gate-run.js';
17
+ export * from './schema/gate-run-request.js';
18
+ export * from './schema/prepared-gate-run.js';
16
19
  export * from './runner/report-store.js';
17
20
  export * from './runner/render-markdown.js';
18
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,mCAAmC,CAAC;AAClD,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,mCAAmC,CAAC;AAClD,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -13,5 +13,8 @@ export * from "./gates/wiring-gate.js";
13
13
  export * from "./gates/policy-lint-gate.js";
14
14
  export * from "./gates/knowledge-symbol-gate.js";
15
15
  export * from "./runner/run-gates.js";
16
+ export * from "./runner/prepare-gate-run.js";
17
+ export * from "./schema/gate-run-request.js";
18
+ export * from "./schema/prepared-gate-run.js";
16
19
  export * from "./runner/report-store.js";
17
20
  export * from "./runner/render-markdown.js";
@@ -0,0 +1,34 @@
1
+ import { type ISettledVerdict } from '@shrkcrft/core';
2
+ import type { IGateRunRequest } from '../schema/gate-run-request.js';
3
+ import type { IPreparedGateRun } from '../schema/prepared-gate-run.js';
4
+ import type { GateStatus, IQualityGateReport } from '../schema/quality-gate.js';
5
+ /**
6
+ * THE option assembly for a quality-gate run — `shrk gate` and MCP
7
+ * `get_quality_gate` both call it, so the two cannot run different gate sets.
8
+ *
9
+ * Round 11 review: the MCP tool passed only the impact options, so it never
10
+ * loaded the project's wiring / policy rules or the knowledge inspection —
11
+ * it said "No wiring rules configured" over a failing rule and returned
12
+ * `overall: pass` where `shrk gate` exited 1.
13
+ *
14
+ * - wiring + policy rules come from `resolveProjectConfig` (pack
15
+ * contributions included); an INVALID config is surfaced to both gates
16
+ * (a `warn` with coverage), never a silent disable;
17
+ * - both gates walk THE plane scan scope (`planeScanExcludeDirs`);
18
+ * - `--changed-only` / `--staged` / `--files` / `--since` scope wiring,
19
+ * policy and knowledge-symbol to the changeset and drive the impact gate;
20
+ * - the architecture gate's NEW is always change-scoped (the worktree diff
21
+ * vs HEAD when no scope is given);
22
+ * - blast-radius risk is advisory unless `failOn` is given.
23
+ */
24
+ export declare function prepareQualityGateRun(req: IGateRunRequest): Promise<IPreparedGateRun>;
25
+ /** The PROPOSED exit from the overall status; the settle may veto a clean one. */
26
+ export declare function qualityGateProposedExit(overall: GateStatus, strict: boolean): number;
27
+ /**
28
+ * THE settlement of a quality-gate report: the proposed exit (from `overall`,
29
+ * `--strict` promoting a warn) vetoed by any gate coverage with a shortfall —
30
+ * core's `settleVerdict`, so a `warn` that says "this is not a pass" never
31
+ * settles to 0. `shrk gate` and MCP `get_quality_gate` both call it.
32
+ */
33
+ export declare function settleQualityGateReport(report: IQualityGateReport, strict?: boolean): ISettledVerdict;
34
+ //# sourceMappingURL=prepare-gate-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-gate-run.d.ts","sourceRoot":"","sources":["../../src/runner/prepare-gate-run.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAOrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiF3F;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAIpF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,UAAQ,GAAG,eAAe,CAKnG"}
@@ -0,0 +1,121 @@
1
+ import { planeScanExcludeDirs } from '@shrkcrft/boundaries';
2
+ import { settleVerdict } from '@shrkcrft/core';
3
+ import { inspectSharkcraft, resolveChangedFiles, resolveProjectConfig, } from '@shrkcrft/inspector';
4
+ /**
5
+ * THE option assembly for a quality-gate run — `shrk gate` and MCP
6
+ * `get_quality_gate` both call it, so the two cannot run different gate sets.
7
+ *
8
+ * Round 11 review: the MCP tool passed only the impact options, so it never
9
+ * loaded the project's wiring / policy rules or the knowledge inspection —
10
+ * it said "No wiring rules configured" over a failing rule and returned
11
+ * `overall: pass` where `shrk gate` exited 1.
12
+ *
13
+ * - wiring + policy rules come from `resolveProjectConfig` (pack
14
+ * contributions included); an INVALID config is surfaced to both gates
15
+ * (a `warn` with coverage), never a silent disable;
16
+ * - both gates walk THE plane scan scope (`planeScanExcludeDirs`);
17
+ * - `--changed-only` / `--staged` / `--files` / `--since` scope wiring,
18
+ * policy and knowledge-symbol to the changeset and drive the impact gate;
19
+ * - the architecture gate's NEW is always change-scoped (the worktree diff
20
+ * vs HEAD when no scope is given);
21
+ * - blast-radius risk is advisory unless `failOn` is given.
22
+ */
23
+ export async function prepareQualityGateRun(req) {
24
+ const cwd = req.cwd;
25
+ const files = req.files ?? [];
26
+ const wantChangedScope = req.changedOnly === true || req.staged === true || Boolean(req.sinceRef) || files.length > 0;
27
+ let changedFiles;
28
+ if (wantChangedScope) {
29
+ changedFiles = resolveChangedFiles({
30
+ projectRoot: cwd,
31
+ ...(files.length > 0 ? { files: [...files] } : {}),
32
+ ...(req.staged ? { staged: true } : {}),
33
+ ...(req.sinceRef ? { since: req.sinceRef } : {}),
34
+ ...(req.changedOnly && !req.staged && !req.sinceRef && files.length === 0 ? { includeWorktree: true } : {}),
35
+ }).files;
36
+ }
37
+ const loadedConfig = await resolveProjectConfig(cwd);
38
+ const wiringRules = loadedConfig.ok ? (loadedConfig.value.config.wiringRules ?? []) : [];
39
+ const policyRules = loadedConfig.ok ? (loadedConfig.value.config.policyRules ?? []) : [];
40
+ const configError = loadedConfig.ok ? undefined : loadedConfig.error.message;
41
+ const excludeDirs = loadedConfig.ok ? planeScanExcludeDirs(cwd, loadedConfig.value.sharkcraftDir) : undefined;
42
+ const scopeOpts = wantChangedScope ? { changedOnly: true, changedFiles: changedFiles ?? [] } : {};
43
+ // §3.1 — the architecture gate's "NEW" is ALWAYS change-scoped: a NEW error
44
+ // means one introduced by the working change (diff vs HEAD), never drift
45
+ // against a frozen (possibly months-old) baseline in a file the change never
46
+ // touched. `archAll` (baselineRelative:false) ignores this and fails on total
47
+ // errors, keeping a clean-tree CI demand expressible.
48
+ const archChangedFiles = wantChangedScope
49
+ ? (changedFiles ?? [])
50
+ : resolveChangedFiles({ projectRoot: cwd, includeWorktree: true }).files;
51
+ // Knowledge symbol-ref integrity needs the loaded knowledge entries. A
52
+ // caller that already has an inspection (MCP) passes it; otherwise one is
53
+ // built — best-effort: a failed inspection skips the gate, never fails it.
54
+ let inspection = req.inspection;
55
+ if (!inspection && !req.disable?.includes('knowledge-symbol')) {
56
+ try {
57
+ inspection = await inspectSharkcraft({ cwd });
58
+ }
59
+ catch {
60
+ inspection = undefined;
61
+ }
62
+ }
63
+ return {
64
+ options: {
65
+ projectRoot: cwd,
66
+ arch: {
67
+ ...(req.archAll ? { baselineRelative: false } : {}),
68
+ changedFiles: archChangedFiles,
69
+ },
70
+ wiring: {
71
+ ...(configError ? { configError } : wiringRules.length > 0 ? { rules: wiringRules } : {}),
72
+ ...scopeOpts,
73
+ ...(excludeDirs ? { excludeDirs } : {}),
74
+ },
75
+ policy: {
76
+ ...(configError ? { configError } : policyRules.length > 0 ? { rules: policyRules } : {}),
77
+ ...scopeOpts,
78
+ ...(excludeDirs ? { excludeDirs } : {}),
79
+ },
80
+ ...(inspection
81
+ ? {
82
+ knowledgeSymbol: {
83
+ inspection,
84
+ ...(wantChangedScope ? { changedFiles: changedFiles ?? [] } : {}),
85
+ },
86
+ }
87
+ : {}),
88
+ impact: {
89
+ ...(req.sinceRef ? { sinceRef: req.sinceRef } : {}),
90
+ // Blast-radius risk is PRE-EXISTING structure, so the composite gate
91
+ // treats it as advisory by default — `failOn: []` warns instead of
92
+ // redding. `failOn: ['critical']` opts into a hard fail.
93
+ failOn: req.failOn ?? [],
94
+ // With `--since` the gitref diff drives it; with `--changed-only` /
95
+ // `--staged` / `--files` the resolved changed-file set does.
96
+ ...(wantChangedScope && !req.sinceRef ? { files: changedFiles ?? [] } : {}),
97
+ },
98
+ ...(req.apiDiff ? { apiDiff: req.apiDiff } : {}),
99
+ ...(req.disable ? { disable: req.disable } : {}),
100
+ },
101
+ planeDiagnostics: loadedConfig.ok ? loadedConfig.value.planeDiagnostics : [],
102
+ ...(changedFiles ? { changedFiles } : {}),
103
+ };
104
+ }
105
+ /** The PROPOSED exit from the overall status; the settle may veto a clean one. */
106
+ export function qualityGateProposedExit(overall, strict) {
107
+ if (overall === 'fail')
108
+ return 1;
109
+ if (overall === 'warn' && strict)
110
+ return 1;
111
+ return 0;
112
+ }
113
+ /**
114
+ * THE settlement of a quality-gate report: the proposed exit (from `overall`,
115
+ * `--strict` promoting a warn) vetoed by any gate coverage with a shortfall —
116
+ * core's `settleVerdict`, so a `warn` that says "this is not a pass" never
117
+ * settles to 0. `shrk gate` and MCP `get_quality_gate` both call it.
118
+ */
119
+ export function settleQualityGateReport(report, strict = false) {
120
+ return settleVerdict(qualityGateProposedExit(report.overall, strict), report.gates.flatMap((g) => g.coverage ?? []));
121
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"run-gates.d.ts","sourceRoot":"","sources":["../../src/runner/run-gates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAElF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAmB,KAAK,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAE9F,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,sDAAsD;IACtD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,yCAAyC;IACzC,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,6CAA6C;IAC7C,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,4CAA4C;IAC5C,cAAc,CAAC,EAAE,0BAA0B,CAAC;IAC5C,gDAAgD;IAChD,kBAAkB,CAAC,EAAE,8BAA8B,CAAC;IACpD,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,4BAA4B,CAAC;IAChD,8EAA8E;IAC9E,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,mFAAmF;IACnF,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC;;;OAGG;IACH,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,oCAAoC;IACpC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,kBAAkB,CA2D7E"}
1
+ {"version":3,"file":"run-gates.d.ts","sourceRoot":"","sources":["../../src/runner/run-gates.ts"],"names":[],"mappings":"AAQA,OAAO,EAAe,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAElF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAmB,KAAK,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAE9F,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,sDAAsD;IACtD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,yCAAyC;IACzC,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,6CAA6C;IAC7C,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,4CAA4C;IAC5C,cAAc,CAAC,EAAE,0BAA0B,CAAC;IAC5C,gDAAgD;IAChD,kBAAkB,CAAC,EAAE,8BAA8B,CAAC;IACpD,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,4BAA4B,CAAC;IAChD,8EAA8E;IAC9E,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,mFAAmF;IACnF,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC;;;OAGG;IACH,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,oCAAoC;IACpC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AA4BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,kBAAkB,CAwE7E"}
@@ -1,3 +1,4 @@
1
+ import { detectGraphFreshness, GraphStore, graphFreshnessBehind, graphFreshnessCoverage, graphFreshnessRemedy, } from '@shrkcrft/graph';
1
2
  import { apiDiffGate } from "../gates/api-diff-gate.js";
2
3
  import { archGate } from "../gates/arch-gate.js";
3
4
  import { graphCyclesGate } from "../gates/graph-cycles-gate.js";
@@ -11,6 +12,32 @@ import { wiringGate } from "../gates/wiring-gate.js";
11
12
  import { policyLintGate } from "../gates/policy-lint-gate.js";
12
13
  import { knowledgeSymbolGate, } from "../gates/knowledge-symbol-gate.js";
13
14
  import { QUALITY_GATE_SCHEMA, } from "../schema/quality-gate.js";
15
+ /** The gates whose answer is DERIVED from the persisted code-graph index. */
16
+ const INDEX_DERIVED_GATES = new Set(['arch', 'impact', 'graph-cycles', 'graph-unresolved']);
17
+ /**
18
+ * The loud skip of a gate derived from an index behind the working tree: it is
19
+ * not run over the stale input (a cycle count, an arch verdict or a blast
20
+ * radius from it misses real findings and reports fixed ones), and it carries
21
+ * the shared freshness coverage record, so the run settles NOT VERIFIED (2).
22
+ * `undefined` when the index is current (or there is none — each gate reports
23
+ * a missing store itself).
24
+ */
25
+ function staleIndexSkip(id, label, freshness) {
26
+ if (freshness === undefined)
27
+ return undefined;
28
+ const coverage = graphFreshnessCoverage(freshness, id);
29
+ if (coverage === undefined)
30
+ return undefined;
31
+ return {
32
+ id,
33
+ label,
34
+ status: 'skipped',
35
+ message: `Skipped — derived from the code-graph index, which is behind the working tree (${graphFreshnessBehind(freshness)} change(s) since it was built) — NOT VERIFIED.`,
36
+ nextCommands: [graphFreshnessRemedy(freshness)],
37
+ coverage: [coverage],
38
+ durationMs: 0,
39
+ };
40
+ }
14
41
  /**
15
42
  * Run the quality-gate aggregator over a project.
16
43
  *
@@ -38,21 +65,28 @@ export function runQualityGates(options) {
38
65
  const disabled = new Set(options.disable ?? []);
39
66
  const diagnostics = [];
40
67
  const gates = [];
68
+ // THE freshness answer (`detectGraphFreshness`, the authority `graph status`
69
+ // reads), measured ONCE for graph-fresh and every gate derived from the index
70
+ // (round 11 review R11-GAP-2): a digest-valid store can be behind the tree.
71
+ const wantsIndex = ['graph-fresh', ...INDEX_DERIVED_GATES].some((id) => !disabled.has(id));
72
+ const freshness = wantsIndex && new GraphStore(options.projectRoot).exists() ? detectGraphFreshness(options.projectRoot) : undefined;
41
73
  // graph-fresh is always first — other gates depend on it.
42
74
  if (!disabled.has('graph-fresh')) {
43
- gates.push(graphFreshGate(options.projectRoot));
75
+ gates.push(graphFreshGate(options.projectRoot, freshness));
44
76
  }
45
77
  if (!disabled.has('arch')) {
46
- gates.push(archGate(options.projectRoot, options.arch ?? {}));
78
+ gates.push(staleIndexSkip('arch', 'Architecture', freshness) ?? archGate(options.projectRoot, options.arch ?? {}));
47
79
  }
48
80
  if (!disabled.has('impact')) {
49
- gates.push(impactGate(options.projectRoot, options.impact ?? {}));
81
+ gates.push(staleIndexSkip('impact', 'Impact', freshness) ?? impactGate(options.projectRoot, options.impact ?? {}));
50
82
  }
51
83
  if (!disabled.has('graph-cycles')) {
52
- gates.push(graphCyclesGate(options.projectRoot, options.graphCycles ?? {}));
84
+ gates.push(staleIndexSkip('graph-cycles', 'Graph cycles', freshness) ??
85
+ graphCyclesGate(options.projectRoot, options.graphCycles ?? {}));
53
86
  }
54
87
  if (!disabled.has('graph-unresolved')) {
55
- gates.push(graphUnresolvedGate(options.projectRoot, options.graphUnresolved ?? {}));
88
+ gates.push(staleIndexSkip('graph-unresolved', 'Graph unresolved imports', freshness) ??
89
+ graphUnresolvedGate(options.projectRoot, options.graphUnresolved ?? {}));
56
90
  }
57
91
  if (!disabled.has('impact-baseline')) {
58
92
  gates.push(impactBaselineGate(options.projectRoot, options.impactBaseline ?? {}));
@@ -0,0 +1,32 @@
1
+ import type { ISharkcraftInspection } from '@shrkcrft/inspector';
2
+ import type { IApiDiffGateOptions } from '../gates/api-diff-gate.js';
3
+ /**
4
+ * What a caller asks the quality-gate aggregator to run — `shrk gate`'s flags,
5
+ * or MCP `get_quality_gate`'s input. `prepareQualityGateRun` turns it into the
6
+ * one `IRunGatesOptions` both surfaces run.
7
+ */
8
+ export interface IGateRunRequest {
9
+ readonly cwd: string;
10
+ /** `--since <ref>`: drives the impact gate AND scopes wiring / policy / knowledge-symbol to the diff. */
11
+ readonly sinceRef?: string;
12
+ /** `--changed-only` (tracked + untracked worktree). */
13
+ readonly changedOnly?: boolean;
14
+ /** `--staged`. */
15
+ readonly staged?: boolean;
16
+ /** `--files a,b`. */
17
+ readonly files?: readonly string[];
18
+ /** `--fail-on high,critical` for the impact gate; absent = advisory (`[]`). */
19
+ readonly failOn?: readonly ('high' | 'critical')[];
20
+ /** `--arch-all`: fail on TOTAL architecture errors, ignoring the frozen baseline. */
21
+ readonly archAll?: boolean;
22
+ /** Gate ids to skip. */
23
+ readonly disable?: readonly string[];
24
+ /** `--api-baseline` opts the api-diff gate in. */
25
+ readonly apiDiff?: IApiDiffGateOptions;
26
+ /**
27
+ * An inspection already loaded (MCP's `ctx.inspection`) for the
28
+ * knowledge-symbol gate. When omitted and the gate is enabled, one is built.
29
+ */
30
+ readonly inspection?: ISharkcraftInspection;
31
+ }
32
+ //# sourceMappingURL=gate-run-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-run-request.d.ts","sourceRoot":"","sources":["../../src/schema/gate-run-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yGAAyG;IACzG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,kBAAkB;IAClB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC;IACnD,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,wBAAwB;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,qBAAqB,CAAC;CAC7C"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { IRunGatesOptions } from '../runner/run-gates.js';
2
+ /** A gate run ready to execute: the options, plus the config merge notes to surface (stderr / JSON). */
3
+ export interface IPreparedGateRun {
4
+ readonly options: IRunGatesOptions;
5
+ /** Pack-plane merge notes (missing / invalid pack rule files, dropped collisions). */
6
+ readonly planeDiagnostics: readonly string[];
7
+ /** The resolved changeset, when the request was scoped. */
8
+ readonly changedFiles?: readonly string[];
9
+ }
10
+ //# sourceMappingURL=prepared-gate-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepared-gate-run.d.ts","sourceRoot":"","sources":["../../src/schema/prepared-gate-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,wGAAwG;AACxG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,sFAAsF;IACtF,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,4 @@
1
+ import type { IVerdictCoverage } from '@shrkcrft/core';
1
2
  export declare const QUALITY_GATE_SCHEMA: "sharkcraft.quality-gate-report/v1";
2
3
  export type GateStatus = 'pass' | 'fail' | 'warn' | 'skipped';
3
4
  export interface IGateResult {
@@ -12,6 +13,14 @@ export interface IGateResult {
12
13
  details?: Readonly<Record<string, unknown>>;
13
14
  /** Suggested follow-up command(s) the human / agent can run. */
14
15
  nextCommands?: readonly string[];
16
+ /**
17
+ * What the gate examined against what it was asked to — one record per unit
18
+ * of work (e.g. per wiring rule, `subject` = the rule id). A gate that found
19
+ * nothing wrong over a record with a coverage shortfall is NOT VERIFIED, not
20
+ * a pass: `shrk gate` settles its exit on these through the CLI's one guard
21
+ * (`settleVerdict`), so a `warn` that says "not a pass" can never exit 0.
22
+ */
23
+ coverage?: readonly IVerdictCoverage[];
15
24
  /** Wall-clock duration of the gate, in ms. */
16
25
  durationMs: number;
17
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"quality-gate.d.ts","sourceRoot":"","sources":["../../src/schema/quality-gate.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,EAAG,mCAA4C,CAAC;AAEhF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,gEAAgE;IAChE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,mBAAmB,CAAC;IACnC,OAAO,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7C,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC"}
1
+ {"version":3,"file":"quality-gate.d.ts","sourceRoot":"","sources":["../../src/schema/quality-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,eAAO,MAAM,mBAAmB,EAAG,mCAA4C,CAAC;AAEhF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,gEAAgE;IAChE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACvC,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,mBAAmB,CAAC;IACnC,OAAO,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7C,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/quality-gates",
3
- "version": "0.1.0-alpha.30",
3
+ "version": "0.1.0-alpha.31",
4
4
  "description": "SharkCraft quality gates: single aggregator that runs the code-intelligence checks (graph index, boundaries, architecture, impact summary) and emits one pass/fail report. The pre-merge gate for AI-agent-authored changes.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -43,15 +43,15 @@
43
43
  "typecheck": "tsc --noEmit -p tsconfig.json"
44
44
  },
45
45
  "dependencies": {
46
- "@shrkcrft/core": "^0.1.0-alpha.30",
47
- "@shrkcrft/graph": "^0.1.0-alpha.30",
48
- "@shrkcrft/architecture-guard": "^0.1.0-alpha.30",
49
- "@shrkcrft/impact-engine": "^0.1.0-alpha.30",
50
- "@shrkcrft/api-surface-diff": "^0.1.0-alpha.30",
51
- "@shrkcrft/boundaries": "^0.1.0-alpha.30",
52
- "@shrkcrft/structural-search": "^0.1.0-alpha.30",
53
- "@shrkcrft/context-planner": "^0.1.0-alpha.30",
54
- "@shrkcrft/inspector": "^0.1.0-alpha.30"
46
+ "@shrkcrft/core": "^0.1.0-alpha.31",
47
+ "@shrkcrft/graph": "^0.1.0-alpha.31",
48
+ "@shrkcrft/architecture-guard": "^0.1.0-alpha.31",
49
+ "@shrkcrft/impact-engine": "^0.1.0-alpha.31",
50
+ "@shrkcrft/api-surface-diff": "^0.1.0-alpha.31",
51
+ "@shrkcrft/boundaries": "^0.1.0-alpha.31",
52
+ "@shrkcrft/structural-search": "^0.1.0-alpha.31",
53
+ "@shrkcrft/context-planner": "^0.1.0-alpha.31",
54
+ "@shrkcrft/inspector": "^0.1.0-alpha.31"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public"