@shrkcrft/cli 0.1.0-alpha.25 → 0.1.0-alpha.27
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/command-registry.d.ts +22 -0
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +41 -0
- package/dist/commands/changelog-data.d.ts.map +1 -1
- package/dist/commands/changelog-data.js +45 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +65 -8
- package/dist/commands/compress.command.d.ts.map +1 -1
- package/dist/commands/compress.command.js +31 -1
- package/dist/commands/delegate.command.d.ts +76 -1
- package/dist/commands/delegate.command.d.ts.map +1 -1
- package/dist/commands/delegate.command.js +585 -25
- package/dist/commands/finish.command.js +4 -4
- package/dist/commands/gate.command.d.ts.map +1 -1
- package/dist/commands/gate.command.js +57 -2
- package/dist/commands/graph.command.d.ts.map +1 -1
- package/dist/commands/graph.command.js +68 -1
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +73 -0
- package/dist/commands/registry-resolve.d.ts +48 -0
- package/dist/commands/registry-resolve.d.ts.map +1 -0
- package/dist/commands/registry-resolve.js +115 -0
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +23 -8
- package/dist/commands/reuse.command.d.ts +20 -0
- package/dist/commands/reuse.command.d.ts.map +1 -1
- package/dist/commands/reuse.command.js +81 -11
- package/dist/commands/trace.command.d.ts.map +1 -1
- package/dist/commands/trace.command.js +7 -1
- package/dist/commands/wiring.command.d.ts.map +1 -1
- package/dist/commands/wiring.command.js +88 -14
- package/dist/exit-codes.d.ts +90 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +146 -0
- package/dist/finish/run-finish.d.ts +22 -3
- package/dist/finish/run-finish.d.ts.map +1 -1
- package/dist/finish/run-finish.js +185 -15
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +44 -4
- package/dist/output/output-compression.d.ts.map +1 -1
- package/dist/output/output-compression.js +4 -1
- package/package.json +33 -33
|
@@ -60,17 +60,47 @@ function isConfidentMatch(detail, queryTokenCount) {
|
|
|
60
60
|
return queryTokenCount <= 1;
|
|
61
61
|
}
|
|
62
62
|
const INDEX_RE = /(^|\/)index\.[cm]?[jt]sx?$/;
|
|
63
|
+
/**
|
|
64
|
+
* Rank ALL primitives by the matcher's own score (descending; ties broken by
|
|
65
|
+
* symbol name so the order is deterministic), then return the top-`k` as scored
|
|
66
|
+
* did-you-mean suggestions. Pure — no graph, no IO — so it is directly
|
|
67
|
+
* unit-testable. When every candidate scores 0 (a nonsense intent that shares no
|
|
68
|
+
* term) the result is the alphabetically-first `k` primitives, each with
|
|
69
|
+
* `score: 0`; the caller states "no candidate shares any term" in that case
|
|
70
|
+
* rather than dumping the whole catalog.
|
|
71
|
+
*/
|
|
72
|
+
export function rankReuseSuggestions(primitives, tokens, k) {
|
|
73
|
+
const cap = Math.max(1, Math.floor(k));
|
|
74
|
+
return primitives
|
|
75
|
+
.map((p) => ({ p, detail: scorePrimitive(p, tokens) }))
|
|
76
|
+
.sort((a, b) => b.detail.score - a.detail.score || a.p.symbol.localeCompare(b.p.symbol))
|
|
77
|
+
.slice(0, cap)
|
|
78
|
+
.map(({ p, detail }) => ({
|
|
79
|
+
symbol: p.symbol,
|
|
80
|
+
score: detail.score,
|
|
81
|
+
confidence: tokens.length === 0 ? 0 : detail.matched.length / tokens.length,
|
|
82
|
+
matched: detail.matched,
|
|
83
|
+
roles: p.roles,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
63
86
|
export const reuseCommand = {
|
|
64
87
|
name: 'reuse',
|
|
65
88
|
description: 'Intent → the canonical primitive to reuse. Matches your intent against configured reusePrimitives[], then resolves the symbol in the code graph to its declaration, public import path, sibling exports, and real consumer files to copy. Deterministic; no AI.',
|
|
66
|
-
usage: 'shrk reuse "<what I want to build>" [--limit N] [--json]',
|
|
89
|
+
usage: 'shrk reuse "<what I want to build>" [--limit N] [--all] [--json]',
|
|
90
|
+
booleanFlags: new Set(['json', 'all']),
|
|
67
91
|
async run(args) {
|
|
68
92
|
const cwd = resolveCwd(args);
|
|
69
93
|
const wantJson = flagBool(args, 'json');
|
|
70
|
-
const
|
|
94
|
+
const wantAll = flagBool(args, 'all');
|
|
95
|
+
// `--limit N` caps both the confident results (historic default 3) and the
|
|
96
|
+
// did-you-mean suggestion list. When omitted, suggestions default to 5 (a
|
|
97
|
+
// couple more than results — the point of a did-you-mean is a short menu).
|
|
98
|
+
const limitFlag = flagNumber(args, 'limit');
|
|
99
|
+
const limit = limitFlag ?? 3;
|
|
100
|
+
const suggestK = limitFlag ?? 5;
|
|
71
101
|
const intent = args.positional.join(' ').trim();
|
|
72
102
|
if (!intent) {
|
|
73
|
-
process.stderr.write('Usage: shrk reuse "<what I want to build>" [--limit N] [--json]\n');
|
|
103
|
+
process.stderr.write('Usage: shrk reuse "<what I want to build>" [--limit N] [--all] [--json]\n');
|
|
74
104
|
return 2;
|
|
75
105
|
}
|
|
76
106
|
const loaded = await resolveProjectConfig(cwd);
|
|
@@ -115,17 +145,39 @@ export const reuseCommand = {
|
|
|
115
145
|
const ranked = confident.slice(0, Math.max(1, limit));
|
|
116
146
|
const store = new GraphStore(cwd);
|
|
117
147
|
const api = store.exists() ? GraphQueryApi.fromStore(cwd) : null;
|
|
118
|
-
// Zero keyword overlap: nothing matched at all
|
|
148
|
+
// Zero keyword overlap: nothing matched at all. Rather than dump the entire
|
|
149
|
+
// declared catalog (dozens of lines an agent must re-read), rank ALL
|
|
150
|
+
// candidates and surface the nearest top-K by name — every score is 0 here,
|
|
151
|
+
// so this is an alphabetized short menu, stated as such. The full catalog is
|
|
152
|
+
// available only behind an explicit `--all`.
|
|
119
153
|
if (scored.length === 0) {
|
|
154
|
+
const suggestions = rankReuseSuggestions(primitives, tokens, suggestK);
|
|
120
155
|
const roles = [...new Set(primitives.flatMap((p) => p.roles))].sort();
|
|
121
156
|
if (wantJson) {
|
|
122
|
-
process.stdout.write(asJson({
|
|
157
|
+
process.stdout.write(asJson({
|
|
158
|
+
schema: 'sharkcraft.reuse/v1',
|
|
159
|
+
intent,
|
|
160
|
+
confident: false,
|
|
161
|
+
results: [],
|
|
162
|
+
suggestions,
|
|
163
|
+
...(wantAll ? { availableRoles: roles } : {}),
|
|
164
|
+
...planeJson,
|
|
165
|
+
}) + '\n');
|
|
123
166
|
return 0;
|
|
124
167
|
}
|
|
125
168
|
process.stdout.write(header(`Reuse: "${intent}"`));
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
169
|
+
if (wantAll) {
|
|
170
|
+
process.stdout.write(' No primitive matched — no candidate shares any term — showing full catalog:\n');
|
|
171
|
+
for (const r of roles.slice(0, 40))
|
|
172
|
+
process.stdout.write(` • ${r}\n`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
process.stdout.write(' No strong match — no candidate shares any term with the intent.\n' +
|
|
176
|
+
' Nearest primitives (pass --all for the full catalog):\n');
|
|
177
|
+
for (const s of suggestions) {
|
|
178
|
+
process.stdout.write(` • ${s.symbol} (score ${s.score}; roles: ${s.roles.join(', ') || '—'})\n`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
129
181
|
writePlaneNotes();
|
|
130
182
|
return 0;
|
|
131
183
|
}
|
|
@@ -133,23 +185,41 @@ export const reuseCommand = {
|
|
|
133
185
|
// entry): below the confidence floor. A miss must look like a miss — never
|
|
134
186
|
// return the nearest collision as a confident answer. Offer did-you-mean.
|
|
135
187
|
if (ranked.length === 0) {
|
|
136
|
-
|
|
188
|
+
// Rank the weakly-overlapping candidates (score > 0) and cap at K — never
|
|
189
|
+
// the whole catalog. `suggestions` and the legacy `didYouMean` alias carry
|
|
190
|
+
// the same scored rows; `--all` additionally dumps every declared role.
|
|
191
|
+
const suggestions = scored.slice(0, Math.max(1, suggestK)).map((x) => ({
|
|
137
192
|
symbol: x.p.symbol,
|
|
138
193
|
score: x.detail.score,
|
|
139
194
|
confidence: confidenceOf(x.detail.matched.length),
|
|
140
195
|
matched: x.detail.matched,
|
|
141
196
|
roles: x.p.roles,
|
|
142
197
|
}));
|
|
198
|
+
const roles = [...new Set(primitives.flatMap((p) => p.roles))].sort();
|
|
143
199
|
if (wantJson) {
|
|
144
|
-
process.stdout.write(asJson({
|
|
200
|
+
process.stdout.write(asJson({
|
|
201
|
+
schema: 'sharkcraft.reuse/v1',
|
|
202
|
+
intent,
|
|
203
|
+
confident: false,
|
|
204
|
+
results: [],
|
|
205
|
+
suggestions,
|
|
206
|
+
didYouMean: suggestions,
|
|
207
|
+
...(wantAll ? { availableRoles: roles } : {}),
|
|
208
|
+
...planeJson,
|
|
209
|
+
}) + '\n');
|
|
145
210
|
return 0;
|
|
146
211
|
}
|
|
147
212
|
process.stdout.write(header(`Reuse: "${intent}"`));
|
|
148
213
|
process.stdout.write(' No confident match — the intent only weakly overlaps existing primitives.\n' +
|
|
149
214
|
' Did you mean (weak, verify before reusing):\n');
|
|
150
|
-
for (const s of
|
|
215
|
+
for (const s of suggestions) {
|
|
151
216
|
process.stdout.write(` • ${s.symbol} (score ${s.score}, ${Math.round(s.confidence * 100)}% of intent; matched: ${s.matched.join(', ') || '—'})\n`);
|
|
152
217
|
}
|
|
218
|
+
if (wantAll) {
|
|
219
|
+
process.stdout.write(' Full catalog (all declared roles):\n');
|
|
220
|
+
for (const r of roles.slice(0, 40))
|
|
221
|
+
process.stdout.write(` • ${r}\n`);
|
|
222
|
+
}
|
|
153
223
|
writePlaneNotes();
|
|
154
224
|
return 0;
|
|
155
225
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.command.d.ts","sourceRoot":"","sources":["../../src/commands/trace.command.ts"],"names":[],"mappings":"AAmBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"trace.command.d.ts","sourceRoot":"","sources":["../../src/commands/trace.command.ts"],"names":[],"mappings":"AAmBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA8EhC,eAAO,MAAM,YAAY,EAAE,eA+H1B,CAAC"}
|
|
@@ -17,6 +17,7 @@ const TRACE_ROLE_ORDER = [
|
|
|
17
17
|
TraceRole.Declare,
|
|
18
18
|
TraceRole.Register,
|
|
19
19
|
TraceRole.Consume,
|
|
20
|
+
TraceRole.Render,
|
|
20
21
|
TraceRole.Reference,
|
|
21
22
|
];
|
|
22
23
|
function renderTraceLiteral(report, limit) {
|
|
@@ -31,7 +32,7 @@ function renderTraceLiteral(report, limit) {
|
|
|
31
32
|
}
|
|
32
33
|
// Mirror `shrk registry <name> where`'s `<role> file:line` line idiom so the
|
|
33
34
|
// two surfaces read the same — `trace literal` is the same scanner + classifier
|
|
34
|
-
// without a pre-declared registry, just with
|
|
35
|
+
// without a pre-declared registry, just with extra roles (`register`, `render`,
|
|
35
36
|
// `reference`). The raw source line stays in `--json` (`text`); the human view
|
|
36
37
|
// is classification-first (direction), not a grep-style text dump.
|
|
37
38
|
process.stdout.write('\n');
|
|
@@ -154,6 +155,11 @@ export const traceCommand = {
|
|
|
154
155
|
process.stdout.write(header(`Trace: ${query}`));
|
|
155
156
|
if (!resolution.bestMatch) {
|
|
156
157
|
process.stdout.write(' no matches found.\n');
|
|
158
|
+
// The bare-`trace` path resolves a FUZZY registry query, not an exact
|
|
159
|
+
// string literal — quotes are stripped by the shell, so we can't tell a
|
|
160
|
+
// literal from a query and must not auto-route. Point the agent at the
|
|
161
|
+
// exact-literal tracer (`trace literal`) so a no-match here isn't a dead end.
|
|
162
|
+
process.stderr.write(`hint: to trace an exact string literal across files, use: shrk trace literal "${query}"\n`);
|
|
157
163
|
return 1;
|
|
158
164
|
}
|
|
159
165
|
process.stdout.write(`Confidence: ${resolution.confidence}\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wiring.command.d.ts","sourceRoot":"","sources":["../../src/commands/wiring.command.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,
|
|
1
|
+
{"version":3,"file":"wiring.command.d.ts","sourceRoot":"","sources":["../../src/commands/wiring.command.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAMhC;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAsDrF;AAyZD,eAAO,MAAM,aAAa,EAAE,eAgB3B,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { buildRegistrationGraph, explainWiring, registrationChain, registrationGraphSignature, registrationOrphans, registrationUnprovided, } from '@shrkcrft/boundaries';
|
|
2
|
-
import { resolveProjectConfig } from '@shrkcrft/inspector';
|
|
2
|
+
import { refExists, resolveChangedFiles, resolveProjectConfig } from '@shrkcrft/inspector';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import * as nodePath from 'node:path';
|
|
6
|
-
import { flagBool, resolveCwd } from "../command-registry.js";
|
|
6
|
+
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
7
|
+
import { ExitCode } from "../exit-codes.js";
|
|
7
8
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
8
9
|
const SITE_DISPLAY_CAP = 50;
|
|
9
10
|
/**
|
|
@@ -245,6 +246,33 @@ function noIdiomsHint(wantJson) {
|
|
|
245
246
|
function siteLine(s) {
|
|
246
247
|
return `${s.file}:${s.line} [${s.idiom}]`;
|
|
247
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* The changed-file scope for a `--changed-only` / `--base <ref>` query, or an
|
|
251
|
+
* empty result when neither flag is set (whole-graph query). `--base <ref>` diffs
|
|
252
|
+
* against that ref; bare `--changed-only` uses the working tree. Reuses the same
|
|
253
|
+
* {@link resolveChangedFiles} the `finish` composite and boundary gates use, so
|
|
254
|
+
* the scope semantics match across every changed-only surface.
|
|
255
|
+
*
|
|
256
|
+
* Two honesty guards: an unresolvable `--base` ref returns a distinct `error`
|
|
257
|
+
* (never a silent empty scope that reads as "nothing changed" over a typo'd ref);
|
|
258
|
+
* and SHRK's own engine-written state under `.sharkcraft/` (this command writes a
|
|
259
|
+
* cache + usage log) is excluded so it can't pollute an otherwise-clean tree into
|
|
260
|
+
* a false non-empty scope.
|
|
261
|
+
*/
|
|
262
|
+
function changedScopeFor(args, cwd) {
|
|
263
|
+
const base = flagString(args, 'base');
|
|
264
|
+
const changedOnly = flagBool(args, 'changed-only');
|
|
265
|
+
if (!base && !changedOnly)
|
|
266
|
+
return {};
|
|
267
|
+
if (base && !refExists(cwd, base)) {
|
|
268
|
+
return { error: `cannot resolve --base ref '${base}' — not a valid commit/branch` };
|
|
269
|
+
}
|
|
270
|
+
const opts = base
|
|
271
|
+
? { projectRoot: cwd, since: base }
|
|
272
|
+
: { projectRoot: cwd, includeWorktree: true };
|
|
273
|
+
const files = resolveChangedFiles(opts).files.filter((f) => f !== '.sharkcraft' && !f.startsWith('.sharkcraft/'));
|
|
274
|
+
return { files };
|
|
275
|
+
}
|
|
248
276
|
async function wiringChain(args) {
|
|
249
277
|
const cwd = resolveCwd(args);
|
|
250
278
|
const wantJson = flagBool(args, 'json');
|
|
@@ -312,15 +340,39 @@ async function wiringUnprovided(args) {
|
|
|
312
340
|
}
|
|
313
341
|
if (!loaded.graph)
|
|
314
342
|
return noIdiomsHint(wantJson);
|
|
315
|
-
const
|
|
343
|
+
const scoped = changedScopeFor(args, cwd);
|
|
344
|
+
if (scoped.error) {
|
|
345
|
+
if (wantJson) {
|
|
346
|
+
process.stdout.write(asJson({ schema: loaded.graph.schema, scoped: true, error: scoped.error, verified: false }) + '\n');
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
process.stderr.write(`error: ${scoped.error}\n`);
|
|
350
|
+
}
|
|
351
|
+
return ExitCode.NotVerified;
|
|
352
|
+
}
|
|
353
|
+
const scope = scoped.files;
|
|
354
|
+
// An empty changed scope evaluated NOTHING — honest `2` (not-verified), never
|
|
355
|
+
// a green `0` that reads as "no unprovided tokens" (a25 §1.1 exit contract).
|
|
356
|
+
if (scope && scope.length === 0) {
|
|
357
|
+
if (wantJson) {
|
|
358
|
+
process.stdout.write(asJson({ schema: loaded.graph.schema, scoped: true, total: 0, unprovided: [], verified: false }) + '\n');
|
|
359
|
+
return ExitCode.NotVerified;
|
|
360
|
+
}
|
|
361
|
+
process.stdout.write(header('Unprovided tokens (declared/injected but never provided)'));
|
|
362
|
+
process.stdout.write(' – No files in the changed scope — nothing to verify (not verified).\n');
|
|
363
|
+
return ExitCode.NotVerified;
|
|
364
|
+
}
|
|
365
|
+
const unprovided = registrationUnprovided(loaded.graph, scope);
|
|
316
366
|
if (wantJson) {
|
|
317
|
-
process.stdout.write(asJson({ schema: loaded.graph.schema, total: unprovided.length, unprovided }) + '\n');
|
|
318
|
-
return unprovided.length > 0 ?
|
|
367
|
+
process.stdout.write(asJson({ schema: loaded.graph.schema, scoped: scope !== undefined, total: unprovided.length, unprovided }) + '\n');
|
|
368
|
+
return unprovided.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
|
|
319
369
|
}
|
|
320
370
|
process.stdout.write(header('Unprovided tokens (declared/injected but never provided)'));
|
|
371
|
+
if (scope)
|
|
372
|
+
process.stdout.write(kv('scope', `changed-only (${scope.length} file(s))`) + '\n');
|
|
321
373
|
if (unprovided.length === 0) {
|
|
322
|
-
process.stdout.write(
|
|
323
|
-
return
|
|
374
|
+
process.stdout.write(` ✓ Every declared/injected token${scope ? ' in the changed scope' : ''} has a provider. ✓\n`);
|
|
375
|
+
return ExitCode.VerifiedPass;
|
|
324
376
|
}
|
|
325
377
|
process.stdout.write(` ${unprovided.length} token(s) resolve to nothing at runtime:\n`);
|
|
326
378
|
for (const u of unprovided) {
|
|
@@ -328,7 +380,7 @@ async function wiringUnprovided(args) {
|
|
|
328
380
|
const where = site ? ` (${siteLine(site)})` : '';
|
|
329
381
|
process.stdout.write(` ✗ ${u.token}${where}\n`);
|
|
330
382
|
}
|
|
331
|
-
return
|
|
383
|
+
return ExitCode.Failure;
|
|
332
384
|
}
|
|
333
385
|
async function wiringOrphans(args) {
|
|
334
386
|
const cwd = resolveCwd(args);
|
|
@@ -343,14 +395,36 @@ async function wiringOrphans(args) {
|
|
|
343
395
|
}
|
|
344
396
|
if (!loaded.graph)
|
|
345
397
|
return noIdiomsHint(wantJson);
|
|
346
|
-
const
|
|
398
|
+
const scoped = changedScopeFor(args, cwd);
|
|
399
|
+
if (scoped.error) {
|
|
400
|
+
if (wantJson) {
|
|
401
|
+
process.stdout.write(asJson({ schema: loaded.graph.schema, scoped: true, error: scoped.error, verified: false }) + '\n');
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
process.stderr.write(`error: ${scoped.error}\n`);
|
|
405
|
+
}
|
|
406
|
+
return ExitCode.NotVerified;
|
|
407
|
+
}
|
|
408
|
+
const scope = scoped.files;
|
|
409
|
+
if (scope && scope.length === 0) {
|
|
410
|
+
if (wantJson) {
|
|
411
|
+
process.stdout.write(asJson({ schema: loaded.graph.schema, scoped: true, total: 0, orphans: [], verified: false }) + '\n');
|
|
412
|
+
return ExitCode.NotVerified;
|
|
413
|
+
}
|
|
414
|
+
process.stdout.write(header('Orphan registrations (provided but nothing consumes)'));
|
|
415
|
+
process.stdout.write(' – No files in the changed scope — nothing to verify (not verified).\n');
|
|
416
|
+
return ExitCode.NotVerified;
|
|
417
|
+
}
|
|
418
|
+
const orphans = registrationOrphans(loaded.graph, scope);
|
|
347
419
|
if (wantJson) {
|
|
348
|
-
process.stdout.write(asJson({ schema: loaded.graph.schema, total: orphans.length, orphans }) + '\n');
|
|
420
|
+
process.stdout.write(asJson({ schema: loaded.graph.schema, scoped: scope !== undefined, total: orphans.length, orphans }) + '\n');
|
|
349
421
|
return 0;
|
|
350
422
|
}
|
|
351
423
|
process.stdout.write(header('Orphan registrations (provided but nothing consumes)'));
|
|
424
|
+
if (scope)
|
|
425
|
+
process.stdout.write(kv('scope', `changed-only (${scope.length} file(s))`) + '\n');
|
|
352
426
|
if (orphans.length === 0) {
|
|
353
|
-
process.stdout.write(
|
|
427
|
+
process.stdout.write(` ✓ Every provided token${scope ? ' in the changed scope' : ''} is consumed somewhere. ✓\n`);
|
|
354
428
|
return 0;
|
|
355
429
|
}
|
|
356
430
|
process.stdout.write(` ${orphans.length} provided token(s) nothing injects:\n`);
|
|
@@ -360,12 +434,12 @@ async function wiringOrphans(args) {
|
|
|
360
434
|
}
|
|
361
435
|
return 0;
|
|
362
436
|
}
|
|
363
|
-
const WIRING_USAGE = 'shrk wiring explain <ruleId> | test <candidate.json|inline> | chain <token> | unprovided | orphans [--json]';
|
|
437
|
+
const WIRING_USAGE = 'shrk wiring explain <ruleId> | test <candidate.json|inline> | chain <token> | unprovided | orphans [--changed-only | --base <ref>] [--json]';
|
|
364
438
|
export const wiringCommand = {
|
|
365
439
|
name: 'wiring',
|
|
366
|
-
description: 'Author-loop + runtime-wiring queries (no config write): `explain <ruleId>` / `test <candidate>` show what a wiring rule extracts; `chain <token>` / `unprovided` / `orphans` query the DI/registration graph (declared→provided→consumed) for the silent-at-runtime bugs imports can\'t see.',
|
|
440
|
+
description: 'Author-loop + runtime-wiring queries (no config write): `explain <ruleId>` / `test <candidate>` show what a wiring rule extracts; `chain <token>` / `unprovided` / `orphans` query the DI/registration graph (declared→provided→consumed) for the silent-at-runtime bugs imports can\'t see. `unprovided` / `orphans` accept `--changed-only` (working tree) or `--base <ref>` to scope the verdict to the changeset.',
|
|
367
441
|
usage: WIRING_USAGE,
|
|
368
|
-
booleanFlags: new Set(['json']),
|
|
442
|
+
booleanFlags: new Set(['json', 'changed-only']),
|
|
369
443
|
async run(args) {
|
|
370
444
|
const sub = args.positional[0];
|
|
371
445
|
if (sub === 'explain')
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical exit-code contract for every gate / verify / check verb.
|
|
3
|
+
*
|
|
4
|
+
* alpha.24 and alpha.25 made the STDOUT verdicts honest ("not verified",
|
|
5
|
+
* "degraded", "0 rules evaluated", "this is not a pass") but left the exit
|
|
6
|
+
* code returning `0` over those same unverified paths. An agent almost never
|
|
7
|
+
* parses the banner — it chains `shrk <cmd> && <next>` on the *exit code*, so a
|
|
8
|
+
* `0` over a "not verified" result marches straight past the gate. This module
|
|
9
|
+
* is the single source of truth so a chained gate can finally tell apart
|
|
10
|
+
* "passed", "failed", and "never ran".
|
|
11
|
+
*
|
|
12
|
+
* 0 VerifiedPass — checks ran over a NON-EMPTY scope and passed. Never
|
|
13
|
+
* returned when zero units were evaluated.
|
|
14
|
+
* 1 Failure — checks ran and found violations.
|
|
15
|
+
* 2 NotVerified — indeterminate: empty evaluation scope, degraded
|
|
16
|
+
* fallback, short-circuit, timeout, or "refused to run".
|
|
17
|
+
* Distinct from both pass and fail so a chain can branch
|
|
18
|
+
* on it (`|| handle-indeterminate`). This is also the
|
|
19
|
+
* code the CLI already uses for usage errors — both mean
|
|
20
|
+
* "did not produce a verified result".
|
|
21
|
+
*
|
|
22
|
+
* The `gen --typecheck` pre-write gate already refuses-to-nonzero rather than
|
|
23
|
+
* emit an unverified artifact; this generalizes that instinct across the gate
|
|
24
|
+
* surface, adding the third code so "unverified" is distinguishable from
|
|
25
|
+
* "broken".
|
|
26
|
+
*/
|
|
27
|
+
export declare enum ExitCode {
|
|
28
|
+
VerifiedPass = 0,
|
|
29
|
+
Failure = 1,
|
|
30
|
+
NotVerified = 2
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Promote a NotVerified (`2`) exit into a Failure-class nonzero (`1`) when the
|
|
34
|
+
* caller opted into `--strict`. This is the one switch an agent flips to make a
|
|
35
|
+
* hard CI gate treat "unverified" as a failure. Any other code passes through
|
|
36
|
+
* unchanged (a real pass stays `0`, a real failure stays `1`). Applied globally
|
|
37
|
+
* in {@link runCli} after the handler returns, so every gate/verify verb honors
|
|
38
|
+
* `--strict` uniformly without threading the flag through each call site.
|
|
39
|
+
*/
|
|
40
|
+
export declare function promoteForStrict(code: number, strict: boolean): number;
|
|
41
|
+
/**
|
|
42
|
+
* True when the argv carries a global `--strict` (bare or `--strict=<level>`).
|
|
43
|
+
* `--strict` is also an established per-command flag (e.g. `check --strict`,
|
|
44
|
+
* `doctor --strict=warnings`) whose local meaning is preserved — this global
|
|
45
|
+
* layer only adds the NotVerified→Failure promotion on top, and only affects a
|
|
46
|
+
* command that actually returned `2`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function argvHasStrict(argv: readonly string[]): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* True when the argv carries the global `--exit-trailer` (before the `--`
|
|
51
|
+
* sentinel). This is the machine channel that survives a pipe: when set, the
|
|
52
|
+
* final verdict is written as the LAST stderr line (`shrk-exit: <code>`), so an
|
|
53
|
+
* agent that pipes a gate to `head`/`grep` can still read shrk's real exit off a
|
|
54
|
+
* channel the pipe can't swallow. See {@link emitPipeExitSignal}.
|
|
55
|
+
*/
|
|
56
|
+
export declare function argvHasExitTrailer(argv: readonly string[]): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Is `commandPath` (space-joined, e.g. `check boundaries` / `wiring unprovided`
|
|
59
|
+
* / `finish`) a gate/verify verb whose exit code carries a chained verdict?
|
|
60
|
+
* Matches the exact path, its first-two-token subverb, or its top-level verb —
|
|
61
|
+
* so `check boundaries --json` (2 tokens) and a bare `finish` (1) both resolve.
|
|
62
|
+
*/
|
|
63
|
+
export declare function isGateVerb(commandPath: string): boolean;
|
|
64
|
+
/** Injectable surface for {@link emitPipeExitSignal} (isTTY + writer + trailer). */
|
|
65
|
+
export interface IPipeExitOptions {
|
|
66
|
+
/** True when shrk's stdout is NOT a terminal (i.e. piped/redirected). */
|
|
67
|
+
readonly piped: boolean;
|
|
68
|
+
/** True when `--exit-trailer` was requested. */
|
|
69
|
+
readonly trailer: boolean;
|
|
70
|
+
/** stderr writer; defaults to `process.stderr.write`. Overridable for tests. */
|
|
71
|
+
readonly write?: (s: string) => void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Keep the honest `0`/`1`/`2` exit code READABLE through the shape agents reach
|
|
75
|
+
* for first — the trailing pipe. `<gate> | head` reports `head`'s `$?`, so a true
|
|
76
|
+
* `2` (not-verified) or `1` (failure) evaporates into a `0`. Two channels survive
|
|
77
|
+
* the pipe because both go to stderr:
|
|
78
|
+
*
|
|
79
|
+
* (a) a one-line WARNING when stdout is piped AND the code is non-zero — a
|
|
80
|
+
* masked `0`→`0` is harmless, so the note is reserved for the case that
|
|
81
|
+
* actually loses information (a masked `1`/`2`);
|
|
82
|
+
* (b) the `shrk-exit: <code>` TRAILER whenever `--exit-trailer` is set (any
|
|
83
|
+
* code), so a caller that opts in gets the verdict machine-readably.
|
|
84
|
+
*
|
|
85
|
+
* A no-op for non-gate verbs. Called once in {@link runCli} after the final
|
|
86
|
+
* (strict-promoted) code is known, so every gate/verify verb is covered without
|
|
87
|
+
* threading anything through each command.
|
|
88
|
+
*/
|
|
89
|
+
export declare function emitPipeExitSignal(commandPath: string, code: number, opts: IPipeExitOptions): void;
|
|
90
|
+
//# sourceMappingURL=exit-codes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit-codes.d.ts","sourceRoot":"","sources":["../src/exit-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,oBAAY,QAAQ;IAClB,YAAY,IAAI;IAChB,OAAO,IAAI;IACX,WAAW,IAAI;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAGtE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAM9D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAMnE;AA4BD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAMvD;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,GACrB,IAAI,CAWN"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical exit-code contract for every gate / verify / check verb.
|
|
3
|
+
*
|
|
4
|
+
* alpha.24 and alpha.25 made the STDOUT verdicts honest ("not verified",
|
|
5
|
+
* "degraded", "0 rules evaluated", "this is not a pass") but left the exit
|
|
6
|
+
* code returning `0` over those same unverified paths. An agent almost never
|
|
7
|
+
* parses the banner — it chains `shrk <cmd> && <next>` on the *exit code*, so a
|
|
8
|
+
* `0` over a "not verified" result marches straight past the gate. This module
|
|
9
|
+
* is the single source of truth so a chained gate can finally tell apart
|
|
10
|
+
* "passed", "failed", and "never ran".
|
|
11
|
+
*
|
|
12
|
+
* 0 VerifiedPass — checks ran over a NON-EMPTY scope and passed. Never
|
|
13
|
+
* returned when zero units were evaluated.
|
|
14
|
+
* 1 Failure — checks ran and found violations.
|
|
15
|
+
* 2 NotVerified — indeterminate: empty evaluation scope, degraded
|
|
16
|
+
* fallback, short-circuit, timeout, or "refused to run".
|
|
17
|
+
* Distinct from both pass and fail so a chain can branch
|
|
18
|
+
* on it (`|| handle-indeterminate`). This is also the
|
|
19
|
+
* code the CLI already uses for usage errors — both mean
|
|
20
|
+
* "did not produce a verified result".
|
|
21
|
+
*
|
|
22
|
+
* The `gen --typecheck` pre-write gate already refuses-to-nonzero rather than
|
|
23
|
+
* emit an unverified artifact; this generalizes that instinct across the gate
|
|
24
|
+
* surface, adding the third code so "unverified" is distinguishable from
|
|
25
|
+
* "broken".
|
|
26
|
+
*/
|
|
27
|
+
export var ExitCode;
|
|
28
|
+
(function (ExitCode) {
|
|
29
|
+
ExitCode[ExitCode["VerifiedPass"] = 0] = "VerifiedPass";
|
|
30
|
+
ExitCode[ExitCode["Failure"] = 1] = "Failure";
|
|
31
|
+
ExitCode[ExitCode["NotVerified"] = 2] = "NotVerified";
|
|
32
|
+
})(ExitCode || (ExitCode = {}));
|
|
33
|
+
/**
|
|
34
|
+
* Promote a NotVerified (`2`) exit into a Failure-class nonzero (`1`) when the
|
|
35
|
+
* caller opted into `--strict`. This is the one switch an agent flips to make a
|
|
36
|
+
* hard CI gate treat "unverified" as a failure. Any other code passes through
|
|
37
|
+
* unchanged (a real pass stays `0`, a real failure stays `1`). Applied globally
|
|
38
|
+
* in {@link runCli} after the handler returns, so every gate/verify verb honors
|
|
39
|
+
* `--strict` uniformly without threading the flag through each call site.
|
|
40
|
+
*/
|
|
41
|
+
export function promoteForStrict(code, strict) {
|
|
42
|
+
if (strict && code === ExitCode.NotVerified)
|
|
43
|
+
return ExitCode.Failure;
|
|
44
|
+
return code;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* True when the argv carries a global `--strict` (bare or `--strict=<level>`).
|
|
48
|
+
* `--strict` is also an established per-command flag (e.g. `check --strict`,
|
|
49
|
+
* `doctor --strict=warnings`) whose local meaning is preserved — this global
|
|
50
|
+
* layer only adds the NotVerified→Failure promotion on top, and only affects a
|
|
51
|
+
* command that actually returned `2`.
|
|
52
|
+
*/
|
|
53
|
+
export function argvHasStrict(argv) {
|
|
54
|
+
for (const t of argv) {
|
|
55
|
+
if (t === '--')
|
|
56
|
+
break;
|
|
57
|
+
if (t === '--strict' || t.startsWith('--strict='))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* True when the argv carries the global `--exit-trailer` (before the `--`
|
|
64
|
+
* sentinel). This is the machine channel that survives a pipe: when set, the
|
|
65
|
+
* final verdict is written as the LAST stderr line (`shrk-exit: <code>`), so an
|
|
66
|
+
* agent that pipes a gate to `head`/`grep` can still read shrk's real exit off a
|
|
67
|
+
* channel the pipe can't swallow. See {@link emitPipeExitSignal}.
|
|
68
|
+
*/
|
|
69
|
+
export function argvHasExitTrailer(argv) {
|
|
70
|
+
for (const t of argv) {
|
|
71
|
+
if (t === '--')
|
|
72
|
+
break;
|
|
73
|
+
if (t === '--exit-trailer')
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Command paths (space-joined top-level + subverb, as {@link extractCommandPath}
|
|
80
|
+
* emits) whose exit code is a HONEST verdict an agent chains on — the set for
|
|
81
|
+
* which a masked exit is a real hazard. Kept deliberately broad over the gate /
|
|
82
|
+
* verify surface; membership only ever gates whether {@link emitPipeExitSignal}
|
|
83
|
+
* may write a one-line stderr note, never behavior.
|
|
84
|
+
*/
|
|
85
|
+
const GATE_VERB_PATHS = new Set([
|
|
86
|
+
'finish',
|
|
87
|
+
'gate',
|
|
88
|
+
'arch',
|
|
89
|
+
'doctor',
|
|
90
|
+
'diff-check',
|
|
91
|
+
'check boundaries',
|
|
92
|
+
'check wiring',
|
|
93
|
+
'check orphans',
|
|
94
|
+
'check policy',
|
|
95
|
+
'check imports',
|
|
96
|
+
'wiring unprovided',
|
|
97
|
+
'wiring orphans',
|
|
98
|
+
'wiring chain',
|
|
99
|
+
'registry',
|
|
100
|
+
'graph why',
|
|
101
|
+
'graph cycles',
|
|
102
|
+
]);
|
|
103
|
+
/**
|
|
104
|
+
* Is `commandPath` (space-joined, e.g. `check boundaries` / `wiring unprovided`
|
|
105
|
+
* / `finish`) a gate/verify verb whose exit code carries a chained verdict?
|
|
106
|
+
* Matches the exact path, its first-two-token subverb, or its top-level verb —
|
|
107
|
+
* so `check boundaries --json` (2 tokens) and a bare `finish` (1) both resolve.
|
|
108
|
+
*/
|
|
109
|
+
export function isGateVerb(commandPath) {
|
|
110
|
+
if (GATE_VERB_PATHS.has(commandPath))
|
|
111
|
+
return true;
|
|
112
|
+
const parts = commandPath.split(' ').filter((p) => p.length > 0);
|
|
113
|
+
if (parts.length >= 2 && GATE_VERB_PATHS.has(`${parts[0]} ${parts[1]}`))
|
|
114
|
+
return true;
|
|
115
|
+
if (parts.length >= 1 && GATE_VERB_PATHS.has(parts[0]))
|
|
116
|
+
return true;
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Keep the honest `0`/`1`/`2` exit code READABLE through the shape agents reach
|
|
121
|
+
* for first — the trailing pipe. `<gate> | head` reports `head`'s `$?`, so a true
|
|
122
|
+
* `2` (not-verified) or `1` (failure) evaporates into a `0`. Two channels survive
|
|
123
|
+
* the pipe because both go to stderr:
|
|
124
|
+
*
|
|
125
|
+
* (a) a one-line WARNING when stdout is piped AND the code is non-zero — a
|
|
126
|
+
* masked `0`→`0` is harmless, so the note is reserved for the case that
|
|
127
|
+
* actually loses information (a masked `1`/`2`);
|
|
128
|
+
* (b) the `shrk-exit: <code>` TRAILER whenever `--exit-trailer` is set (any
|
|
129
|
+
* code), so a caller that opts in gets the verdict machine-readably.
|
|
130
|
+
*
|
|
131
|
+
* A no-op for non-gate verbs. Called once in {@link runCli} after the final
|
|
132
|
+
* (strict-promoted) code is known, so every gate/verify verb is covered without
|
|
133
|
+
* threading anything through each command.
|
|
134
|
+
*/
|
|
135
|
+
export function emitPipeExitSignal(commandPath, code, opts) {
|
|
136
|
+
if (!isGateVerb(commandPath))
|
|
137
|
+
return;
|
|
138
|
+
const write = opts.write ?? ((s) => void process.stderr.write(s));
|
|
139
|
+
if (opts.piped && code !== 0) {
|
|
140
|
+
write(`note: stdout is piped — $? reflects the downstream command, not shrk (exit ${code}); ` +
|
|
141
|
+
`use PIPESTATUS[0] or --exit-trailer to read shrk's verdict.\n`);
|
|
142
|
+
}
|
|
143
|
+
// The trailer is written LAST so it is the final stderr line a caller reads.
|
|
144
|
+
if (opts.trailer)
|
|
145
|
+
write(`shrk-exit: ${code}\n`);
|
|
146
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type IChangedScopeOptions } from '@shrkcrft/inspector';
|
|
2
|
+
import { ExitCode } from '../exit-codes.js';
|
|
2
3
|
export declare const FINISH_SCHEMA: "sharkcraft.finish/v1";
|
|
3
4
|
/** Outcome of one sub-gate. `skipped` = nothing to evaluate (loud, never silent green). */
|
|
4
5
|
export type FinishGateStatus = 'pass' | 'fail' | 'skipped';
|
|
@@ -9,7 +10,7 @@ export interface IFinishItem {
|
|
|
9
10
|
readonly message: string;
|
|
10
11
|
}
|
|
11
12
|
export interface IFinishGate {
|
|
12
|
-
readonly name: 'boundaries' | 'imports' | 'wiring' | 'policy' | 'orphans';
|
|
13
|
+
readonly name: 'boundaries' | 'imports' | 'wiring' | 'unprovided' | 'policy' | 'orphans' | 'arch';
|
|
13
14
|
readonly status: FinishGateStatus;
|
|
14
15
|
/** One-line reason (e.g. why skipped, or the error/warning counts). */
|
|
15
16
|
readonly detail: string;
|
|
@@ -17,6 +18,14 @@ export interface IFinishGate {
|
|
|
17
18
|
readonly warnings: number;
|
|
18
19
|
/** Failing/notable items (capped by the renderer, full in JSON). */
|
|
19
20
|
readonly items: readonly IFinishItem[];
|
|
21
|
+
/**
|
|
22
|
+
* Advisory gates report signal but NEVER decide the verdict: they cannot fail
|
|
23
|
+
* the composite and do not count as "something was evaluated" (so an advisory
|
|
24
|
+
* pass can't turn an all-skipped run green). Used by `arch`, whose cycle
|
|
25
|
+
* findings are change-informative but must not attribute a pre-existing cycle
|
|
26
|
+
* to this changeset.
|
|
27
|
+
*/
|
|
28
|
+
readonly advisory?: boolean;
|
|
20
29
|
}
|
|
21
30
|
export interface IFinishImpact {
|
|
22
31
|
readonly ran: boolean;
|
|
@@ -35,8 +44,18 @@ export interface IFinishReport {
|
|
|
35
44
|
};
|
|
36
45
|
readonly gates: readonly IFinishGate[];
|
|
37
46
|
readonly impact: IFinishImpact;
|
|
38
|
-
/**
|
|
39
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The honest tri-state verdict:
|
|
49
|
+
* `fail` — a deciding gate failed (or the config could not load).
|
|
50
|
+
* `not-verified` — NOTHING was actually evaluated (every deciding gate
|
|
51
|
+
* skipped / the changed scope had nothing to gate). Never a
|
|
52
|
+
* green `pass` — "evaluated nothing" is `2`, not `0`.
|
|
53
|
+
* `pass` — at least one deciding gate ran over a real scope and every
|
|
54
|
+
* deciding gate passed.
|
|
55
|
+
*/
|
|
56
|
+
readonly verdict: 'pass' | 'fail' | 'not-verified';
|
|
57
|
+
/** The exit code this verdict maps to (0 pass / 1 fail / 2 not-verified). */
|
|
58
|
+
readonly exit: ExitCode;
|
|
40
59
|
/** Total warning-severity findings across gates (non-blocking). */
|
|
41
60
|
readonly warnings: number;
|
|
42
61
|
/** Set when sharkcraft.config.ts could not be loaded — forces a `fail`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-finish.d.ts","sourceRoot":"","sources":["../../src/finish/run-finish.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"run-finish.d.ts","sourceRoot":"","sources":["../../src/finish/run-finish.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAiB7B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,eAAO,MAAM,aAAa,EAAG,sBAA+B,CAAC;AAQ7D,2FAA2F;AAC3F,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE3D,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IAClG,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;QACzD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc,CAAC;IACnD,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IACzD,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;CACtC;AAmBD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,CAmJnF"}
|