@shrkcrft/cli 0.1.0-alpha.28 → 0.1.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/commands/baseline.command.d.ts +25 -0
  2. package/dist/commands/baseline.command.d.ts.map +1 -1
  3. package/dist/commands/baseline.command.js +64 -17
  4. package/dist/commands/changelog-data.d.ts.map +1 -1
  5. package/dist/commands/changelog-data.js +46 -0
  6. package/dist/commands/check.command.d.ts.map +1 -1
  7. package/dist/commands/check.command.js +137 -12
  8. package/dist/commands/code-intel.command.d.ts.map +1 -1
  9. package/dist/commands/code-intel.command.js +5 -1
  10. package/dist/commands/command-catalog.d.ts.map +1 -1
  11. package/dist/commands/command-catalog.js +8 -0
  12. package/dist/commands/daily.commands.d.ts.map +1 -1
  13. package/dist/commands/daily.commands.js +11 -1
  14. package/dist/commands/docs-references.command.d.ts +7 -0
  15. package/dist/commands/docs-references.command.d.ts.map +1 -0
  16. package/dist/commands/docs-references.command.js +323 -0
  17. package/dist/commands/doctor.command.d.ts.map +1 -1
  18. package/dist/commands/doctor.command.js +3 -2
  19. package/dist/commands/gates.command.d.ts +13 -1
  20. package/dist/commands/gates.command.d.ts.map +1 -1
  21. package/dist/commands/gates.command.js +693 -26
  22. package/dist/commands/generated.command.d.ts +32 -0
  23. package/dist/commands/generated.command.d.ts.map +1 -1
  24. package/dist/commands/generated.command.js +214 -53
  25. package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
  26. package/dist/commands/graph-code-subverbs.js +5 -1
  27. package/dist/commands/help.command.d.ts.map +1 -1
  28. package/dist/commands/help.command.js +64 -2
  29. package/dist/commands/policy-lint.command.d.ts.map +1 -1
  30. package/dist/commands/policy-lint.command.js +52 -10
  31. package/dist/commands/registry.command.d.ts.map +1 -1
  32. package/dist/commands/registry.command.js +34 -9
  33. package/dist/commands/wiring.command.d.ts.map +1 -1
  34. package/dist/commands/wiring.command.js +4 -3
  35. package/dist/exit-codes.d.ts +27 -7
  36. package/dist/exit-codes.d.ts.map +1 -1
  37. package/dist/exit-codes.js +47 -8
  38. package/dist/gates/gate-envelope.d.ts +64 -0
  39. package/dist/gates/gate-envelope.d.ts.map +1 -0
  40. package/dist/gates/gate-envelope.js +26 -0
  41. package/dist/gates/gate-rule-globs.d.ts +33 -0
  42. package/dist/gates/gate-rule-globs.d.ts.map +1 -0
  43. package/dist/gates/gate-rule-globs.js +101 -0
  44. package/dist/gates/gate-rule-view.d.ts +9 -3
  45. package/dist/gates/gate-rule-view.d.ts.map +1 -1
  46. package/dist/gates/gate-rule-view.js +18 -4
  47. package/dist/gates/rule-coverage.d.ts +39 -1
  48. package/dist/gates/rule-coverage.d.ts.map +1 -1
  49. package/dist/gates/rule-coverage.js +123 -8
  50. package/dist/gates/run-gate-planes.d.ts +44 -0
  51. package/dist/gates/run-gate-planes.d.ts.map +1 -0
  52. package/dist/gates/run-gate-planes.js +261 -0
  53. package/dist/main.d.ts.map +1 -1
  54. package/dist/main.js +7 -2
  55. package/package.json +33 -33
@@ -1,6 +1,38 @@
1
+ import { type IGeneratedArtifactRule } from '@shrkcrft/core';
2
+ import { type IGeneratedTreeDiff, type IProvenanceFinding } from '@shrkcrft/boundaries';
1
3
  import { type ICommandHandler } from '../command-registry.js';
4
+ /**
5
+ * One rule's outcome. Exported so `shrk gates check` aggregates the SAME
6
+ * evaluation the per-plane verb runs, rather than a second implementation that
7
+ * could drift from it.
8
+ */
9
+ export interface IGeneratedOutcome {
10
+ readonly rule: IGeneratedArtifactRule;
11
+ readonly status: 'passed' | 'failed' | 'skipped' | 'error';
12
+ readonly committedCount: number;
13
+ readonly treeDiff?: IGeneratedTreeDiff;
14
+ readonly provenance: readonly IProvenanceFinding[];
15
+ readonly error?: string;
16
+ readonly skipReason?: string;
17
+ /** True when the drift half was deliberately not run (`--headers-only` / no `regen`). */
18
+ readonly driftChecked: boolean;
19
+ /** Files excluded from every check by `handMaintained`. */
20
+ readonly handMaintained: readonly string[];
21
+ /** Per-writer drift results, for a multi-writer rule. */
22
+ readonly writers: readonly IWriterOutcome[];
23
+ }
24
+ /** One writer's slice result inside a multi-writer rule. */
25
+ interface IWriterOutcome {
26
+ readonly label: string;
27
+ readonly committedCount: number;
28
+ readonly differences: number;
29
+ readonly error?: string;
30
+ }
31
+ export declare function evaluateGeneratedRule(cwd: string, rule: IGeneratedArtifactRule, excludeDirs: readonly string[], headersOnly: boolean): IGeneratedOutcome;
32
+ export declare function generatedHintFor(rule: IGeneratedArtifactRule): string;
2
33
  export declare const generatedListCommand: ICommandHandler;
3
34
  export declare const generatedCheckCommand: ICommandHandler;
4
35
  export declare const generatedUpdateCommand: ICommandHandler;
5
36
  export declare const generatedExplainCommand: ICommandHandler;
37
+ export {};
6
38
  //# sourceMappingURL=generated.command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generated.command.d.ts","sourceRoot":"","sources":["../../src/commands/generated.command.ts"],"names":[],"mappings":"AA+BA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAwShC,eAAO,MAAM,oBAAoB,EAAE,eA2ClC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAuFnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAmDpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiErC,CAAC"}
1
+ {"version":3,"file":"generated.command.d.ts","sourceRoot":"","sources":["../../src/commands/generated.command.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAML,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA0LhC;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAC3D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,yFAAyF;IACzF,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;CAC7C;AAED,4DAA4D;AAC5D,UAAU,cAAc;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,sBAAsB,EAC5B,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,WAAW,EAAE,OAAO,GACnB,iBAAiB,CAqFnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,sBAAsB,GAAG,MAAM,CAOrE;AAwED,eAAO,MAAM,oBAAoB,EAAE,eA4DlC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAgInC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAgEpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAyGrC,CAAC"}
@@ -20,11 +20,13 @@ import { spawnSync } from 'node:child_process';
20
20
  import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
21
21
  import * as nodeOs from 'node:os';
22
22
  import * as nodePath from 'node:path';
23
+ import { failsWhenEmpty } from '@shrkcrft/core';
23
24
  import { checkProvenanceHeaders, compareGeneratedTrees, scanGeneratedFiles, } from '@shrkcrft/boundaries';
24
25
  import { resolveProjectConfig } from '@shrkcrft/inspector';
25
26
  import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
26
27
  import { ExitCode } from "../exit-codes.js";
27
28
  import { asJson, header, kv } from "../output/format-output.js";
29
+ import { buildGateEnvelope } from "../gates/gate-envelope.js";
28
30
  const SCHEMA = 'sharkcraft.generated-drift/v1';
29
31
  const DEFAULT_TIMEOUT_MS = 120_000;
30
32
  /** Cap on the temp tree read — a runaway regen must not be read into memory whole. */
@@ -125,16 +127,23 @@ function alignToCommitted(temp, committed) {
125
127
  }
126
128
  return out;
127
129
  }
128
- /** Run `regen` into a fresh temp dir and read the result back. Always cleans up. */
129
- function runRegen(cwd, rule, committed) {
130
+ /**
131
+ * Run ONE regen command into a fresh temp dir and read the result back, keyed
132
+ * onto the committed paths it corresponds to. Always cleans up.
133
+ *
134
+ * `committed` is the slice this command OWNS, not the whole tree — that is what
135
+ * keeps a multi-writer tree honest: each command's output is aligned against
136
+ * (and later diffed against) only its own files, so writer A's output can never
137
+ * be reported as writer B's stale committed file.
138
+ */
139
+ function runOneRegen(cwd, command, committed, timeoutMs) {
130
140
  const tmp = mkdtempSync(nodePath.join(nodeOs.tmpdir(), 'shrk-generated-'));
131
141
  try {
132
- const command = rule.regen.split('{TMP}').join(tmp);
133
- const child = spawnSync(command, {
142
+ const child = spawnSync(command.split('{TMP}').join(tmp), {
134
143
  cwd,
135
144
  shell: true,
136
145
  encoding: 'utf8',
137
- timeout: rule.timeoutMs ?? DEFAULT_TIMEOUT_MS,
146
+ timeout: timeoutMs,
138
147
  maxBuffer: 16 * 1024 * 1024,
139
148
  });
140
149
  if (child.error)
@@ -153,21 +162,48 @@ function runRegen(cwd, rule, committed) {
153
162
  rmSync(tmp, { recursive: true, force: true });
154
163
  }
155
164
  }
156
- function evaluateRule(cwd, rule, excludeDirs, headersOnly) {
165
+ /**
166
+ * The regen units for a rule: one per `sources[]` writer, or a single unit for
167
+ * the classic `regen` form. A rule declaring neither yields none (header-only).
168
+ */
169
+ function regenUnitsOf(rule, scan) {
170
+ const fallback = rule.timeoutMs ?? DEFAULT_TIMEOUT_MS;
171
+ if (scan.slices.length > 0) {
172
+ return scan.slices.map((slice) => ({
173
+ label: slice.label,
174
+ command: slice.regen,
175
+ committed: slice.files,
176
+ timeoutMs: slice.timeoutMs ?? fallback,
177
+ }));
178
+ }
179
+ if (rule.regen === undefined)
180
+ return [];
181
+ // Single-writer: the checkable set, so a hand-maintained bless inside the
182
+ // glob is not compared against output no generator produces.
183
+ return [{ label: rule.id, command: rule.regen, committed: scan.checkable, timeoutMs: fallback }];
184
+ }
185
+ export function evaluateGeneratedRule(cwd, rule, excludeDirs, headersOnly) {
157
186
  const scan = scanGeneratedFiles(cwd, rule, excludeDirs);
158
187
  const severity = rule.severity ?? 'error';
159
188
  if (scan.generated.size === 0) {
160
- const failed = rule.failOnEmpty === true;
189
+ const failed = failsWhenEmpty(rule);
161
190
  return {
162
191
  rule,
163
192
  status: failed ? 'failed' : 'skipped',
164
193
  committedCount: 0,
165
194
  provenance: [],
166
195
  driftChecked: false,
196
+ handMaintained: [],
197
+ writers: [],
167
198
  skipReason: `0 files matched generatedGlob (${rule.generatedGlob.join(', ')})`,
168
199
  };
169
200
  }
170
- const headers = checkProvenanceHeaders(rule, scan.generated, scan.outside);
201
+ // Headers + byte checks run over the CHECKABLE set — a hand-maintained bless
202
+ // is excluded from both, which is the whole point of declaring it.
203
+ const headers = checkProvenanceHeaders(rule, scan.checkable, scan.outside, {
204
+ unclassified: scan.unclassified,
205
+ staleHandMaintained: scan.staleHandMaintained,
206
+ });
171
207
  if (headers.error) {
172
208
  return {
173
209
  rule,
@@ -175,21 +211,39 @@ function evaluateRule(cwd, rule, excludeDirs, headersOnly) {
175
211
  committedCount: scan.generated.size,
176
212
  provenance: [],
177
213
  driftChecked: false,
214
+ handMaintained: scan.handMaintained,
215
+ writers: [],
178
216
  error: headers.error,
179
217
  };
180
218
  }
181
- let treeDiff;
182
- let error;
183
- const wantDrift = !headersOnly && rule.regen !== undefined;
184
- if (wantDrift) {
185
- const regen = runRegen(cwd, rule, scan.generated);
186
- if (regen.error)
187
- error = regen.error;
188
- else
189
- treeDiff = compareGeneratedTrees(scan.generated, regen.files, rule.compare ?? 'bytes');
219
+ const units = headersOnly ? [] : regenUnitsOf(rule, scan);
220
+ const differences = [];
221
+ const writers = [];
222
+ const errors = [];
223
+ let committedCompared = 0;
224
+ let regeneratedCount = 0;
225
+ for (const unit of units) {
226
+ const regen = runOneRegen(cwd, unit.command, unit.committed, unit.timeoutMs);
227
+ if (regen.error) {
228
+ errors.push(units.length > 1 ? `${unit.label}: ${regen.error}` : regen.error);
229
+ writers.push({ label: unit.label, committedCount: unit.committed.size, differences: 0, error: regen.error });
230
+ continue;
231
+ }
232
+ const diff = compareGeneratedTrees(unit.committed, regen.files, rule.compare ?? 'bytes');
233
+ differences.push(...diff.differences);
234
+ committedCompared += diff.committedCount;
235
+ regeneratedCount += diff.regeneratedCount;
236
+ writers.push({ label: unit.label, committedCount: unit.committed.size, differences: diff.differences.length });
190
237
  }
238
+ const ranAnyWriter = units.length > 0 && errors.length < units.length;
239
+ const treeDiff = ranAnyWriter
240
+ ? { differences, committedCount: committedCompared, regeneratedCount }
241
+ : undefined;
242
+ const error = errors.length > 0 ? errors.join(' · ') : undefined;
191
243
  const hardFindings = headers.findings.filter((f) => f.severity === 'error');
192
- const drifted = (treeDiff?.differences.length ?? 0) > 0;
244
+ const drifted = differences.length > 0;
245
+ // A writer that failed to RUN proved nothing, so the rule is in error even if
246
+ // its siblings were clean — a partial regen must never read as a full pass.
193
247
  const status = error !== undefined
194
248
  ? 'error'
195
249
  : drifted || (hardFindings.length > 0 && severity === 'error')
@@ -202,12 +256,14 @@ function evaluateRule(cwd, rule, excludeDirs, headersOnly) {
202
256
  ...(treeDiff ? { treeDiff } : {}),
203
257
  provenance: headers.findings,
204
258
  ...(error ? { error } : {}),
205
- driftChecked: wantDrift && error === undefined,
259
+ driftChecked: units.length > 0 && errors.length === 0,
260
+ handMaintained: scan.handMaintained,
261
+ writers: writers.length > 1 ? writers : [],
206
262
  };
207
263
  }
208
- function hintFor(rule) {
264
+ export function generatedHintFor(rule) {
209
265
  return (rule.hint ??
210
- (rule.regen
266
+ (rule.regen || rule.sources
211
267
  ? `regenerate with \`shrk generated update --id ${rule.id}\` and commit the result`
212
268
  : 'add the provenance header to the generated file, or move it out of the generated glob'));
213
269
  }
@@ -219,11 +275,13 @@ function outcomeJson(o) {
219
275
  severity: o.rule.severity ?? 'error',
220
276
  committedCount: o.committedCount,
221
277
  driftChecked: o.driftChecked,
278
+ handMaintained: o.handMaintained,
279
+ ...(o.writers.length > 0 ? { writers: o.writers } : {}),
222
280
  ...(o.treeDiff ? { differences: o.treeDiff.differences, regeneratedCount: o.treeDiff.regeneratedCount } : {}),
223
281
  provenance: o.provenance,
224
282
  ...(o.error ? { error: o.error } : {}),
225
283
  ...(o.skipReason ? { skipReason: o.skipReason } : {}),
226
- hint: hintFor(o.rule),
284
+ hint: generatedHintFor(o.rule),
227
285
  };
228
286
  }
229
287
  async function prepare(args) {
@@ -235,7 +293,7 @@ async function prepare(args) {
235
293
  process.stdout.write(asJson({ schema: SCHEMA, error: loaded.message }) + '\n');
236
294
  else
237
295
  process.stderr.write(`Could not load config: ${loaded.message}\n Run \`shrk doctor\` for details.\n`);
238
- return { ok: false, code: ExitCode.NotVerified };
296
+ return { ok: false, code: ExitCode.UsageError };
239
297
  }
240
298
  const id = flagString(args, 'id');
241
299
  let rules = loaded.value.rules;
@@ -245,7 +303,7 @@ async function prepare(args) {
245
303
  const unknown = wanted.filter((w) => !known.has(w));
246
304
  if (unknown.length > 0) {
247
305
  process.stderr.write(`Unknown generated-artifact id(s): ${unknown.join(', ')}. Declared: ${[...known].join(', ') || '(none)'}\n`);
248
- return { ok: false, code: ExitCode.NotVerified };
306
+ return { ok: false, code: ExitCode.UsageError };
249
307
  }
250
308
  rules = rules.filter((r) => wanted.includes(r.id));
251
309
  }
@@ -289,6 +347,9 @@ export const generatedListCommand = {
289
347
  description: r.description ?? null,
290
348
  generatedGlob: r.generatedGlob,
291
349
  regen: r.regen ?? null,
350
+ sources: r.sources ?? null,
351
+ handMaintained: r.handMaintained ?? null,
352
+ handMaintainedMarker: r.handMaintainedMarker ?? null,
292
353
  compare: r.compare ?? 'bytes',
293
354
  provenanceHeader: r.provenanceHeader ?? null,
294
355
  failOnEmpty: r.failOnEmpty === true,
@@ -301,7 +362,22 @@ export const generatedListCommand = {
301
362
  for (const r of prep.all) {
302
363
  process.stdout.write(` • ${r.id}\n`);
303
364
  process.stdout.write(` glob ${r.generatedGlob.join(', ')}\n`);
304
- process.stdout.write(` regen ${r.regen ?? '(header-only never spawns)'}\n`);
365
+ if (r.sources && r.sources.length > 0) {
366
+ process.stdout.write(` regen ${r.sources.length} writer(s):\n`);
367
+ r.sources.forEach((src, i) => {
368
+ process.stdout.write(` [${src.id ?? i}] ${src.regen}\n`);
369
+ process.stdout.write(` owns ${src.glob.join(', ')}\n`);
370
+ });
371
+ }
372
+ else {
373
+ process.stdout.write(` regen ${r.regen ?? '(header-only — never spawns)'}\n`);
374
+ }
375
+ if (r.handMaintained && r.handMaintained.length > 0) {
376
+ process.stdout.write(` hand ${r.handMaintained.join(', ')} (exempt from header + byte checks)\n`);
377
+ }
378
+ if (r.handMaintainedMarker) {
379
+ process.stdout.write(` marker /${r.handMaintainedMarker}/ (in-file bless)\n`);
380
+ }
305
381
  if (r.provenanceHeader) {
306
382
  process.stdout.write(` header /${r.provenanceHeader.mustMatch}/${r.provenanceHeader.forbidOutside ? ' + mislabel check' : ''}\n`);
307
383
  }
@@ -326,12 +402,13 @@ export const generatedCheckCommand = {
326
402
  const headersOnly = flagBool(args, 'headers-only');
327
403
  if (prep.rules.length === 0)
328
404
  return writeNoRules(json);
329
- const outcomes = prep.rules.map((r) => evaluateRule(prep.cwd, r, prep.excludeDirs, headersOnly));
405
+ const outcomes = prep.rules.map((r) => evaluateGeneratedRule(prep.cwd, r, prep.excludeDirs, headersOnly));
330
406
  const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
331
407
  const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
408
+ const skippedCount = outcomes.length - evaluated;
332
409
  const exit = failed.length > 0
333
410
  ? ExitCode.Failure
334
- : evaluated === 0
411
+ : evaluated === 0 || skippedCount > 0
335
412
  ? ExitCode.NotVerified
336
413
  : ExitCode.VerifiedPass;
337
414
  if (json) {
@@ -343,6 +420,33 @@ export const generatedCheckCommand = {
343
420
  skipped: outcomes.filter((o) => o.status === 'skipped').length,
344
421
  verdict: failed.length > 0 ? 'errors' : evaluated === 0 ? 'not-verified' : 'pass',
345
422
  diagnostics: prep.planeDiagnostics,
423
+ gate: buildGateEnvelope('generated check', exit, outcomes.map((o) => ({
424
+ id: o.rule.id,
425
+ type: 'generated',
426
+ status: o.status,
427
+ severity: o.rule.severity ?? 'error',
428
+ counts: {
429
+ files: o.committedCount,
430
+ differences: o.treeDiff?.differences.length ?? 0,
431
+ provenance: o.provenance.length,
432
+ },
433
+ violations: [
434
+ ...(o.treeDiff?.differences ?? []).map((d) => ({
435
+ id: d.file,
436
+ file: d.file,
437
+ message: d.kind,
438
+ hint: generatedHintFor(o.rule),
439
+ })),
440
+ ...o.provenance.map((f) => ({
441
+ id: f.file,
442
+ file: f.file,
443
+ message: f.message,
444
+ hint: generatedHintFor(o.rule),
445
+ })),
446
+ ],
447
+ ...(o.skipReason ? { skipReason: o.skipReason } : {}),
448
+ ...(o.error ? { error: o.error } : {}),
449
+ }))),
346
450
  }) + '\n');
347
451
  return exit;
348
452
  }
@@ -361,11 +465,18 @@ export const generatedCheckCommand = {
361
465
  }
362
466
  const diffs = o.treeDiff?.differences ?? [];
363
467
  if (o.status === 'passed') {
364
- process.stdout.write(` ✓ ${o.rule.id} (${o.committedCount} files` +
468
+ const exempt = o.handMaintained.length > 0 ? `, ${o.handMaintained.length} hand-maintained` : '';
469
+ process.stdout.write(` ✓ ${o.rule.id} (${o.committedCount} files${exempt}` +
365
470
  `${o.driftChecked ? ', byte-identical to a fresh regen' : ', headers only'})\n`);
471
+ for (const w of o.writers) {
472
+ process.stdout.write(` · ${w.label}: ${w.committedCount} file(s) verified\n`);
473
+ }
366
474
  }
367
475
  else {
368
476
  process.stdout.write(` ✗ ${o.rule.id} ${diffs.length} file(s) differ from a fresh regen\n`);
477
+ for (const w of o.writers) {
478
+ process.stdout.write(` · ${w.label}: ${w.error ? `ERROR — ${w.error}` : `${w.differences} of ${w.committedCount} differ`}\n`);
479
+ }
369
480
  for (const d of diffs.slice(0, 25)) {
370
481
  const label = d.kind === 'content'
371
482
  ? 'hand-edited (or source changed)'
@@ -384,7 +495,7 @@ export const generatedCheckCommand = {
384
495
  process.stdout.write(` … (${o.provenance.length - 25} more header finding(s))\n`);
385
496
  }
386
497
  if (o.status === 'failed')
387
- process.stdout.write(` → ${hintFor(o.rule)}\n`);
498
+ process.stdout.write(` → ${generatedHintFor(o.rule)}\n`);
388
499
  }
389
500
  for (const d of prep.planeDiagnostics)
390
501
  process.stdout.write(` ! ${d}\n`);
@@ -411,29 +522,41 @@ export const generatedUpdateCommand = {
411
522
  return writeNoRules(json);
412
523
  const results = [];
413
524
  for (const rule of prep.rules) {
414
- if (!rule.regen) {
525
+ // A multi-writer rule blesses by running EVERY writer — regenerating one
526
+ // slice and calling the artifact updated is how a mixed tree drifts.
527
+ const commands = rule.sources && rule.sources.length > 0
528
+ ? rule.sources.map((src, i) => ({
529
+ label: `${rule.id}/${src.id ?? i}`,
530
+ command: src.regen,
531
+ timeoutMs: src.timeoutMs ?? rule.timeoutMs ?? DEFAULT_TIMEOUT_MS,
532
+ }))
533
+ : rule.regen
534
+ ? [{ label: rule.id, command: rule.regen, timeoutMs: rule.timeoutMs ?? DEFAULT_TIMEOUT_MS }]
535
+ : [];
536
+ if (commands.length === 0) {
415
537
  results.push({ id: rule.id, ran: false, error: 'header-only rule — nothing to regenerate' });
416
538
  continue;
417
539
  }
418
- // `{TMP}` is the CHECK contract; `update` writes in place, so it is
419
- // substituted with the project root and the regen writes its real output.
420
- const command = rule.regen.split('{TMP}').join(prep.cwd);
421
- const child = spawnSync(command, {
422
- cwd: prep.cwd,
423
- shell: true,
424
- encoding: 'utf8',
425
- timeout: rule.timeoutMs ?? DEFAULT_TIMEOUT_MS,
426
- maxBuffer: 16 * 1024 * 1024,
427
- stdio: json ? 'pipe' : 'inherit',
428
- });
429
- if (child.error) {
430
- results.push({ id: rule.id, ran: false, error: child.error.message });
431
- }
432
- else if (child.status !== 0) {
433
- results.push({ id: rule.id, ran: true, error: `exited ${child.status ?? 'null'}` });
434
- }
435
- else {
436
- results.push({ id: rule.id, ran: true });
540
+ for (const { label, command, timeoutMs } of commands) {
541
+ // `{TMP}` is the CHECK contract; `update` writes in place, so it is
542
+ // substituted with the project root and the regen writes its real output.
543
+ const child = spawnSync(command.split('{TMP}').join(prep.cwd), {
544
+ cwd: prep.cwd,
545
+ shell: true,
546
+ encoding: 'utf8',
547
+ timeout: timeoutMs,
548
+ maxBuffer: 16 * 1024 * 1024,
549
+ stdio: json ? 'pipe' : 'inherit',
550
+ });
551
+ if (child.error) {
552
+ results.push({ id: label, ran: false, error: child.error.message });
553
+ }
554
+ else if (child.status !== 0) {
555
+ results.push({ id: label, ran: true, error: `exited ${child.status ?? 'null'}` });
556
+ }
557
+ else {
558
+ results.push({ id: label, ran: true });
559
+ }
437
560
  }
438
561
  }
439
562
  const failed = results.filter((r) => r.error !== undefined);
@@ -460,7 +583,7 @@ export const generatedExplainCommand = {
460
583
  const id = flagString(args, 'id') ?? args.positional[0];
461
584
  if (!id) {
462
585
  process.stderr.write('Usage: shrk generated explain --id <id>\n');
463
- return ExitCode.NotVerified;
586
+ return ExitCode.UsageError;
464
587
  }
465
588
  const prep = await prepare(args);
466
589
  if (!prep.ok)
@@ -468,16 +591,27 @@ export const generatedExplainCommand = {
468
591
  const rule = prep.all.find((r) => r.id === id);
469
592
  if (!rule) {
470
593
  process.stderr.write(`No generated-artifact rule "${id}". Declared: ${prep.all.map((r) => r.id).join(', ') || '(none)'}\n`);
471
- return ExitCode.NotVerified;
594
+ return ExitCode.UsageError;
472
595
  }
473
596
  // explain never spawns — the point is to show what the rule SEES.
474
597
  const scan = scanGeneratedFiles(prep.cwd, rule, prep.excludeDirs);
475
- const outcome = evaluateRule(prep.cwd, rule, prep.excludeDirs, true);
598
+ const outcome = evaluateGeneratedRule(prep.cwd, rule, prep.excludeDirs, true);
476
599
  if (flagBool(args, 'json')) {
477
600
  process.stdout.write(asJson({
478
601
  schema: 'sharkcraft.generated-explain/v1',
479
602
  ...outcomeJson(outcome),
480
603
  files: [...scan.generated.keys()],
604
+ checkable: [...scan.checkable.keys()],
605
+ handMaintained: scan.handMaintained,
606
+ markedHandMaintained: scan.markedHandMaintained,
607
+ staleHandMaintained: scan.staleHandMaintained,
608
+ unclassified: scan.unclassified,
609
+ slices: scan.slices.map((sl) => ({
610
+ label: sl.label,
611
+ regen: sl.regen,
612
+ glob: sl.glob,
613
+ files: [...sl.files.keys()],
614
+ })),
481
615
  outsideScanned: scan.outside.size,
482
616
  outsideGlobs: scan.outsideGlobs,
483
617
  regen: rule.regen ?? null,
@@ -489,7 +623,34 @@ export const generatedExplainCommand = {
489
623
  process.stdout.write(` ${rule.description}\n`);
490
624
  process.stdout.write(kv('glob', rule.generatedGlob.join(', ')) + '\n');
491
625
  process.stdout.write(kv('files matched', String(scan.generated.size)) + '\n');
492
- process.stdout.write(kv('regen', rule.regen ?? '(header-only)') + '\n');
626
+ if (scan.slices.length > 0) {
627
+ process.stdout.write(kv('writers', String(scan.slices.length)) + '\n');
628
+ for (const sl of scan.slices) {
629
+ process.stdout.write(` [${sl.label}] ${sl.files.size} file(s) — ${sl.regen}\n`);
630
+ process.stdout.write(` owns ${sl.glob.join(', ')}\n`);
631
+ }
632
+ }
633
+ else {
634
+ process.stdout.write(kv('regen', rule.regen ?? '(header-only)') + '\n');
635
+ }
636
+ if (scan.handMaintained.length > 0) {
637
+ process.stdout.write(kv('hand-maintained', `${scan.handMaintained.length} file(s) — exempt from header + byte checks`) + '\n');
638
+ const marked = new Set(scan.markedHandMaintained);
639
+ // Say WHICH mechanism blessed each file: a config entry and an in-file
640
+ // marker are reviewed in different places, so "why is this exempt?" has
641
+ // two different answers and the reader needs to know which one applies.
642
+ for (const f of scan.handMaintained.slice(0, 20)) {
643
+ process.stdout.write(` ${f}${marked.has(f) ? ' (in-file marker)' : ' (handMaintained[])'}\n`);
644
+ }
645
+ }
646
+ if (scan.unclassified.length > 0) {
647
+ process.stdout.write(kv('unclassified', `${scan.unclassified.length} file(s) — owned by no writer, not blessed`) + '\n');
648
+ for (const f of scan.unclassified.slice(0, 20))
649
+ process.stdout.write(` ${f}\n`);
650
+ }
651
+ for (const pattern of scan.staleHandMaintained) {
652
+ process.stdout.write(` ! handMaintained "${pattern}" matches no file — stale bless\n`);
653
+ }
493
654
  process.stdout.write(kv('compare', rule.compare ?? 'bytes') + '\n');
494
655
  if (rule.provenanceHeader) {
495
656
  process.stdout.write(kv('header', `/${rule.provenanceHeader.mustMatch}/`) + '\n');
@@ -1 +1 @@
1
- {"version":3,"file":"graph-code-subverbs.d.ts","sourceRoot":"","sources":["../../src/commands/graph-code-subverbs.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAiE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAgLxH,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBrE;AA4FD,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkFtE;AAiBD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA2F1E;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+EpE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+FtE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwEtE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA2MvE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+HtE;AAID;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgEpE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAiGvE;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgHpE"}
1
+ {"version":3,"file":"graph-code-subverbs.d.ts","sourceRoot":"","sources":["../../src/commands/graph-code-subverbs.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAiE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAgLxH,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBrE;AA4FD,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkFtE;AAiBD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA2F1E;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+EpE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAmGtE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwEtE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA2MvE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+HtE;AAID;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgEpE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAiGvE;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgHpE"}
@@ -594,7 +594,11 @@ export async function runGraphStatus(args) {
594
594
  }
595
595
  process.stdout.write(header('Graph status'));
596
596
  process.stdout.write(kv('schema', payload.schema) + '\n');
597
- process.stdout.write(kv('files', String(payload.fileCount)) + '\n');
597
+ // "files indexed", not "files": this is the size of the STORED snapshot, and
598
+ // it deliberately does not move when a new file appears on disk — that shows
599
+ // up on the `drift` line below. Reading it as a live count is how an added
600
+ // file looks invisible to the index.
601
+ process.stdout.write(kv('files indexed', String(payload.fileCount)) + '\n');
598
602
  process.stdout.write(kv('nodes', String(payload.nodeCount)) + '\n');
599
603
  process.stdout.write(kv('edges', String(payload.edgeCount)) + '\n');
600
604
  process.stdout.write(kv('packages', String(payload.workspacePackages.length)) + '\n');
@@ -1 +1 @@
1
- {"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAqF9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAwC1C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe;;;;cAK3C;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM;EAqJpF"}
1
+ {"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA4H9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAwC1C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe;;;;cAK3C;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM;EA2KpF"}
@@ -1,5 +1,45 @@
1
1
  import { header } from "../output/format-output.js";
2
2
  import { COMMAND_CATALOG, defaultShowInHelp, listExplainFamily } from "./command-catalog.js";
3
+ /**
4
+ * Every multi-token command path the catalog documents. These are real,
5
+ * callable verbs that the command trie never sees, because their parent
6
+ * dispatches them from a positional argument rather than registering them.
7
+ */
8
+ function catalogHelpPaths() {
9
+ const paths = new Set();
10
+ for (const entry of COMMAND_CATALOG) {
11
+ // Strip the flag/argument tail a few catalog entries carry in `command`.
12
+ const clean = entry.command.split(/\s+--/)[0].trim();
13
+ if (clean.length > 0)
14
+ paths.add(clean);
15
+ }
16
+ return paths;
17
+ }
18
+ /**
19
+ * Render help for a command path the trie could not resolve but the catalog
20
+ * documents, plus its sibling verbs under the same parent so the family is
21
+ * discoverable from any one member.
22
+ */
23
+ function renderCatalogHelp(tokens) {
24
+ const want = tokens.join(' ');
25
+ const entry = COMMAND_CATALOG.find((e) => e.command.split(/\s+--/)[0].trim() === want);
26
+ if (!entry)
27
+ return undefined;
28
+ let out = `${want} — ${entry.description}\n`;
29
+ const extra = EXTRA_HELP_LINES[want];
30
+ if (extra)
31
+ out += extra.join('\n') + '\n';
32
+ const parent = tokens.slice(0, -1).join(' ');
33
+ if (parent.length > 0) {
34
+ const siblings = [...catalogHelpPaths()]
35
+ .filter((p) => p !== want && p.startsWith(parent + ' ') && !p.slice(parent.length + 1).includes(' '))
36
+ .sort();
37
+ if (siblings.length > 0) {
38
+ out += `\nSiblings: ${siblings.join(', ')}\n`;
39
+ }
40
+ }
41
+ return out;
42
+ }
3
43
  /** First sentence of a catalog description, for the compact explain-family list. */
4
44
  function firstSentence(description) {
5
45
  const dot = description.indexOf('. ');
@@ -156,8 +196,17 @@ export function makeHelpCommand(registry) {
156
196
  // that exits 0. Error out honestly instead, with a did-you-mean when
157
197
  // a real topic is a near-typo of the request.
158
198
  const attempt = tokens.join(' ');
199
+ // The trie matched nothing, but the CATALOG may still document this
200
+ // path — a documented verb whose parent isn't a registered command
201
+ // is still real and callable.
202
+ const viaCatalog = renderCatalogHelp(tokens);
203
+ if (viaCatalog !== undefined) {
204
+ process.stdout.write(viaCatalog);
205
+ return 0;
206
+ }
159
207
  process.stderr.write(`no such help topic: '${attempt}'\n`);
160
- const suggestion = nearestHelpTopic(attempt, realHelpTopics(registry));
208
+ const suggestion = nearestHelpTopic(attempt, realHelpTopics(registry)) ??
209
+ nearestHelpTopic(attempt, catalogHelpPaths());
161
210
  if (suggestion)
162
211
  process.stderr.write(`Did you mean: ${suggestion}?\n`);
163
212
  return 1;
@@ -199,7 +248,20 @@ export function makeHelpCommand(registry) {
199
248
  }
200
249
  return 0;
201
250
  }
202
- process.stderr.write(`Unknown command: ${tokens.join(' ')}\n`);
251
+ // The trie could not resolve the full path, but the CATALOG may still
252
+ // document it. Verbs like `check wiring` are dispatched from inside
253
+ // their parent's handler on a positional, so they are real, callable
254
+ // and documented — yet never trie nodes. Falling through to "Unknown
255
+ // command" made an entire documented surface look non-existent.
256
+ const catalogHelp = renderCatalogHelp(tokens);
257
+ if (catalogHelp !== undefined) {
258
+ process.stdout.write(catalogHelp);
259
+ return 0;
260
+ }
261
+ process.stderr.write(`no such help topic: '${tokens.join(' ')}'\n`);
262
+ const near = nearestHelpTopic(tokens.join(' '), catalogHelpPaths());
263
+ if (near)
264
+ process.stderr.write(`Did you mean: shrk help ${near}?\n`);
203
265
  return 1;
204
266
  }
205
267
  if (!wantsFull) {
@@ -1 +1 @@
1
- {"version":3,"file":"policy-lint.command.d.ts","sourceRoot":"","sources":["../../src/commands/policy-lint.command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAMhC,eAAO,MAAM,qBAAqB,EAAG,8BAAuC,CAAC;AAE7E,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,iDAAiD;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,EACjB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,cAAc,CAqBhB;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CA0CtF;AAGD,eAAO,MAAM,wBAAwB,EAAE,eA8BtC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,eAmN/B,CAAC"}
1
+ {"version":3,"file":"policy-lint.command.d.ts","sourceRoot":"","sources":["../../src/commands/policy-lint.command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAQhC,eAAO,MAAM,qBAAqB,EAAG,8BAAuC,CAAC;AAE7E,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,iDAAiD;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,EACjB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,cAAc,CAqBhB;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CA0CtF;AAGD,eAAO,MAAM,wBAAwB,EAAE,eA8BtC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,eAoQ/B,CAAC"}