@shrkcrft/inspector 0.1.0-alpha.17 → 0.1.0-alpha.19

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 (60) hide show
  1. package/dist/agent-instructions.d.ts.map +1 -1
  2. package/dist/agent-instructions.js +11 -0
  3. package/dist/ai-readiness.d.ts.map +1 -1
  4. package/dist/ai-readiness.js +20 -5
  5. package/dist/changed-preflight.d.ts +7 -0
  6. package/dist/changed-preflight.d.ts.map +1 -1
  7. package/dist/changed-preflight.js +56 -9
  8. package/dist/check-guardrail-globs.d.ts +16 -0
  9. package/dist/check-guardrail-globs.d.ts.map +1 -0
  10. package/dist/check-guardrail-globs.js +38 -0
  11. package/dist/code-intelligence-doctor.js +25 -5
  12. package/dist/command-recommender.d.ts.map +1 -1
  13. package/dist/command-recommender.js +16 -4
  14. package/dist/contract-file-rule.d.ts +8 -0
  15. package/dist/contract-file-rule.d.ts.map +1 -1
  16. package/dist/contract-file-rule.js +8 -3
  17. package/dist/coverage-report.d.ts.map +1 -1
  18. package/dist/coverage-report.js +14 -1
  19. package/dist/delegate-catalog.d.ts +45 -0
  20. package/dist/delegate-catalog.d.ts.map +1 -0
  21. package/dist/delegate-catalog.js +50 -0
  22. package/dist/delegate-doctor.d.ts +15 -0
  23. package/dist/delegate-doctor.d.ts.map +1 -0
  24. package/dist/delegate-doctor.js +36 -0
  25. package/dist/delegate-pack-recipes.d.ts +29 -0
  26. package/dist/delegate-pack-recipes.d.ts.map +1 -0
  27. package/dist/delegate-pack-recipes.js +77 -0
  28. package/dist/git-helpers.d.ts +15 -0
  29. package/dist/git-helpers.d.ts.map +1 -1
  30. package/dist/git-helpers.js +41 -0
  31. package/dist/impact-analysis.d.ts.map +1 -1
  32. package/dist/impact-analysis.js +10 -2
  33. package/dist/index.d.ts +5 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +5 -0
  36. package/dist/pack-contributions-inventory.d.ts.map +1 -1
  37. package/dist/pack-contributions-inventory.js +6 -0
  38. package/dist/pack-signature-status.d.ts.map +1 -1
  39. package/dist/pack-signature-status.js +18 -1
  40. package/dist/plan-simulation.d.ts +13 -0
  41. package/dist/plan-simulation.d.ts.map +1 -1
  42. package/dist/plan-simulation.js +1 -1
  43. package/dist/propose-knowledge.d.ts +15 -0
  44. package/dist/propose-knowledge.d.ts.map +1 -1
  45. package/dist/propose-knowledge.js +37 -4
  46. package/dist/resolve-verification-commands.d.ts +26 -0
  47. package/dist/resolve-verification-commands.d.ts.map +1 -0
  48. package/dist/resolve-verification-commands.js +55 -0
  49. package/dist/rule-drift.d.ts.map +1 -1
  50. package/dist/rule-drift.js +24 -9
  51. package/dist/sharkcraft-inspector.d.ts.map +1 -1
  52. package/dist/sharkcraft-inspector.js +7 -0
  53. package/dist/start-here.d.ts +1 -1
  54. package/dist/start-here.d.ts.map +1 -1
  55. package/dist/start-here.js +15 -0
  56. package/dist/task-packet.d.ts.map +1 -1
  57. package/dist/task-packet.js +13 -1
  58. package/dist/test-impact.d.ts.map +1 -1
  59. package/dist/test-impact.js +5 -2
  60. package/package.json +17 -17
@@ -1 +1 @@
1
- {"version":3,"file":"agent-instructions.d.ts","sourceRoot":"","sources":["../src/agent-instructions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,QAoBvB,CAAC"}
1
+ {"version":3,"file":"agent-instructions.d.ts","sourceRoot":"","sources":["../src/agent-instructions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,QA+BvB,CAAC"}
@@ -9,6 +9,17 @@ Default behavior:
9
9
  - For generation, list_templates → get_template → create_generation_plan (dry-run by default).
10
10
  - Respect path conventions (list_path_conventions, get_path_convention).
11
11
 
12
+ Understand existing code before you edit (prefer the graph over grep — it returns path:line truth):
13
+ - Who calls / where is a symbol used? call get_graph_callers or code_find_usages (returns path:line).
14
+ - What breaks if I change this file/symbol? call get_graph_impact.
15
+ - What is the load-bearing code (change carefully / understand first)? call get_graph_hubs — most-referenced symbols + most-imported files.
16
+ - Is code A actually wired to code B? call get_graph_path (shortest import/call/implements path between two files or symbols — the deterministic "is X wired to Y").
17
+ - Is X wired / how does this file connect (incl. subtypes/supertypes)? call get_graph_context (get_graph_search to locate it first).
18
+ - These read an index; if a graph tool reports it's missing or stale, run \`shrk graph index\` once.
19
+
20
+ Save your tokens on MECHANICAL edits — delegate them to a local worker:
21
+ - For a repetitive, deterministically-verifiable edit (add a barrel export, ensure an import, a glob-scoped rename) call delegate_task to get a compact brief + the exact \`shrk delegate run\` command, then run it. The local model produces the edit, the engine verifies it (and auto-reverts on failure), and you pay for the brief + result — not for reading the whole file and writing the edit yourself. \`shrk delegate list\` shows what's delegatable.
22
+
12
23
  Safety:
13
24
  - Never write files unless the user explicitly asks; always prefer dry-run first.
14
25
  - Never overwrite existing files unless the plan resolves all conflicts.
@@ -1 +1 @@
1
- {"version":3,"file":"ai-readiness.d.ts","sourceRoot":"","sources":["../src/ai-readiness.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAIvE;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,eAAe,CAAC;AAEvE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,OAAO,EAAE,kBAAkB,CAAC;IAC5B,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,mBAAmB,EAAE,OAAO,CAAC;IAC7B,qEAAqE;IACrE,kBAAkB,EAAE,OAAO,CAAC;IAC5B,sDAAsD;IACtD,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,8EAA8E;IAC9E,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,wEAAwE;IACxE,cAAc,EAAE,eAAe,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,UAAU,EAAE,OAAO,CAAC;IACpB,wHAAwH;IACxH,aAAa,EAAE,OAAO,CAAC;CACxB;AAyED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,qBAAqB,GAAG,gBAAgB,CA2U1F"}
1
+ {"version":3,"file":"ai-readiness.d.ts","sourceRoot":"","sources":["../src/ai-readiness.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAYvE;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,eAAe,CAAC;AAEvE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,OAAO,EAAE,kBAAkB,CAAC;IAC5B,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,mBAAmB,EAAE,OAAO,CAAC;IAC7B,qEAAqE;IACrE,kBAAkB,EAAE,OAAO,CAAC;IAC5B,sDAAsD;IACtD,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,8EAA8E;IAC9E,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,wEAAwE;IACxE,cAAc,EAAE,eAAe,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,UAAU,EAAE,OAAO,CAAC;IACpB,wHAAwH;IACxH,aAAa,EAAE,OAAO,CAAC;CACxB;AAyED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,qBAAqB,GAAG,gBAAgB,CAwV1F"}
@@ -2,6 +2,13 @@ import { KnowledgeType, hasActionHints } from '@shrkcrft/knowledge';
2
2
  import { WorkspaceProfile } from '@shrkcrft/workspace';
3
3
  import { diagnoseActionHints } from "./action-hint-diagnostics.js";
4
4
  import { runDoctor } from "./sharkcraft-inspector.js";
5
+ /**
6
+ * Entry types that describe a *location* or a *fact* rather than an action, so
7
+ * they are not expected to carry actionHints and are excluded from the
8
+ * action-hint coverage metric. Mirrors the skip set in
9
+ * `action-hint-diagnostics.ts` (keep the two in sync).
10
+ */
11
+ const HINT_EXEMPT_TYPES = new Set(['path', 'overview', 'technical']);
5
12
  function gradeOf(score) {
6
13
  if (score >= 85)
7
14
  return 'excellent';
@@ -217,19 +224,27 @@ export function buildAiReadinessReport(inspection) {
217
224
  });
218
225
  // No recommendation when pipelines is advisory — the old "add a pipeline"
219
226
  // exhortation fired on libraries it shouldn't have.
220
- // 7) Action-hint coverage — fraction of entries that carry hints.
221
- const withHints = inspection.knowledgeEntries.filter((e) => hasActionHints(e)).length;
222
- const hintsScore = k === 0 ? 0 : Math.min(10, Math.round((withHints / Math.max(k, 1)) * 20));
227
+ // 7) Action-hint coverage — fraction of HINT-ELIGIBLE entries that carry
228
+ // hints. Path / overview / technical entries describe a location or a fact
229
+ // rather than an action, so the action-hint quality doctor deliberately
230
+ // doesn't grade them (see action-hint-diagnostics). Counting them in the
231
+ // denominator penalised the score for structural non-gaps — an entry that
232
+ // *can't* meaningfully carry action hints isn't a missing one — so they are
233
+ // excluded from both numerator and denominator here too.
234
+ const hintEligible = inspection.knowledgeEntries.filter((e) => !HINT_EXEMPT_TYPES.has(String(e.type).toLowerCase()));
235
+ const eligibleCount = hintEligible.length;
236
+ const withHints = hintEligible.filter((e) => hasActionHints(e)).length;
237
+ const hintsScore = eligibleCount === 0 ? 0 : Math.min(10, Math.round((withHints / eligibleCount) * 20));
223
238
  dims.push({
224
239
  id: 'action-hints',
225
240
  title: 'Entries with action hints',
226
241
  weight: 1.2,
227
242
  score: hintsScore,
228
- note: `${withHints} of ${k} entries carry actionHints`,
243
+ note: `${withHints} of ${eligibleCount} hint-eligible entries carry actionHints`,
229
244
  applies: 'core',
230
245
  });
231
246
  if (hintsScore < 7)
232
- recs.push('Add actionHints to high-priority rules (commands, mcpTools, forbiddenActions).');
247
+ recs.push('Add actionHints to high-priority entries (commands, mcpTools, forbiddenActions, relatedKnowledge).');
233
248
  // 8) Verification commands
234
249
  const haveVerify = inspection.knowledgeEntries.some((e) => (e.actionHints?.verificationCommands?.length ?? 0) > 0);
235
250
  dims.push({
@@ -54,6 +54,13 @@ export declare function planChangedPreflight(options: {
54
54
  readonly projectRoot: string;
55
55
  readonly changedFiles: ReadonlyArray<string>;
56
56
  readonly profile?: PreflightProfile;
57
+ /** Override the test gate command (defaults to `bun test`). Grounded in the
58
+ * project's declared verification commands by the CLI caller. */
59
+ readonly testCommand?: string;
60
+ /** Override the typecheck gate command (defaults to the base-tsconfig run). */
61
+ readonly typecheckCommand?: string;
62
+ /** Extra path prefixes that count as engine source (e.g. config-declared). */
63
+ readonly sourceGlobs?: readonly string[];
57
64
  }): IChangedPreflightPlan;
58
65
  export declare function renderChangedPreflightText(plan: IChangedPreflightPlan): string;
59
66
  //# sourceMappingURL=changed-preflight.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"changed-preflight.d.ts","sourceRoot":"","sources":["../src/changed-preflight.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAE1E,oBAAY,gBAAgB;IAC1B,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED,oBAAY,eAAe;IACzB,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,SAAS,cAAc;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,WAAW,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,wBAAwB,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,eAAe,EAAE,2BAA2B,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CACnC;AA+CD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC;CACrC,GAAG,qBAAqB,CA8QxB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,CAiB9E"}
1
+ {"version":3,"file":"changed-preflight.d.ts","sourceRoot":"","sources":["../src/changed-preflight.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AA6B1E,oBAAY,gBAAgB;IAC1B,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED,oBAAY,eAAe;IACzB,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,SAAS,cAAc;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,WAAW,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,wBAAwB,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,eAAe,EAAE,2BAA2B,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CACnC;AA+DD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IACpC;sEACkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C,GAAG,qBAAqB,CAmRxB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,CAiB9E"}
@@ -11,8 +11,37 @@
11
11
  * planner is intentionally conservative: a gate's `skip` decision is always
12
12
  * accompanied by a reason so the operator can see what was elided.
13
13
  */
14
+ import { existsSync, readFileSync } from 'node:fs';
14
15
  import * as nodePath from 'node:path';
15
16
  export const CHANGED_PREFLIGHT_SCHEMA = 'sharkcraft.changed-preflight/v1';
17
+ /**
18
+ * Cheap, best-effort probe: does this look like a generic JS/TS monorepo
19
+ * (nx / pnpm workspaces / package.json workspaces / apps+libs)? Used to widen
20
+ * the "engine source changed" detection beyond SharkCraft's own
21
+ * `packages/<package-name>/src/` layout. A couple of stat calls — no full inspect.
22
+ */
23
+ function isMonorepoLike(projectRoot) {
24
+ try {
25
+ if (existsSync(nodePath.join(projectRoot, 'nx.json')))
26
+ return true;
27
+ if (existsSync(nodePath.join(projectRoot, 'pnpm-workspace.yaml')))
28
+ return true;
29
+ if (existsSync(nodePath.join(projectRoot, 'apps')) ||
30
+ existsSync(nodePath.join(projectRoot, 'libs'))) {
31
+ return true;
32
+ }
33
+ const pkgPath = nodePath.join(projectRoot, 'package.json');
34
+ if (existsSync(pkgPath)) {
35
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
36
+ if (pkg.workspaces)
37
+ return true;
38
+ }
39
+ }
40
+ catch {
41
+ // Best-effort: treat any read/parse failure as "not obviously a monorepo".
42
+ }
43
+ return false;
44
+ }
16
45
  export var PreflightProfile;
17
46
  (function (PreflightProfile) {
18
47
  PreflightProfile["Quick"] = "quick";
@@ -25,10 +54,23 @@ export var PreflightAction;
25
54
  PreflightAction["Skip"] = "skip";
26
55
  PreflightAction["Recommend"] = "recommend";
27
56
  })(PreflightAction || (PreflightAction = {}));
28
- function classifyChangedFiles(files) {
57
+ function classifyChangedFiles(files, opts = {}) {
29
58
  const norm = files.map((f) => f.replace(/\\/g, '/'));
30
59
  const match = (re) => norm.some((p) => re.test(p));
31
- const anyEngineSource = match(/^packages\/[^/]+\/src\//);
60
+ let anyEngineSource = match(/^packages\/[^/]+\/src\//);
61
+ // Monorepo-aware: on an nx / workspaces / apps+libs repo, source also lives
62
+ // under apps/*/src, libs/*/src, or a project-root src/ — not only
63
+ // SharkCraft's own packages/*/src/. Without this, a large nx change is
64
+ // misclassified as "no engine src changed" and every src-sensitive gate is
65
+ // skipped. Only widen when a monorepo signal is present, so this repo's own
66
+ // classification is unchanged.
67
+ if (!anyEngineSource && opts.projectRoot && isMonorepoLike(opts.projectRoot)) {
68
+ anyEngineSource = match(/^(apps|libs|packages)\/[^/]+\/(src|lib)\//) || match(/^src\//);
69
+ }
70
+ // Caller-supplied source roots (e.g. derived from config) always count.
71
+ if (!anyEngineSource && opts.sourceGlobs?.length) {
72
+ anyEngineSource = norm.some((p) => opts.sourceGlobs.some((g) => p.startsWith(g)));
73
+ }
32
74
  const anyPackages = match(/^packages\//);
33
75
  const anyTests = match(/__tests__\/|\.test\.ts$|e2e\//);
34
76
  const anySharkcraftAssets = match(/^sharkcraft\//);
@@ -71,7 +113,12 @@ function classifyChangedFiles(files) {
71
113
  */
72
114
  export function planChangedPreflight(options) {
73
115
  const profile = options.profile ?? PreflightProfile.Standard;
74
- const cls = classifyChangedFiles(options.changedFiles);
116
+ const cls = classifyChangedFiles(options.changedFiles, {
117
+ projectRoot: options.projectRoot,
118
+ ...(options.sourceGlobs ? { sourceGlobs: options.sourceGlobs } : {}),
119
+ });
120
+ const testCmd = options.testCommand ?? 'bun test';
121
+ const typecheckCmd = options.typecheckCommand ?? 'bun x tsc -p tsconfig.base.json --noEmit';
75
122
  const explanations = [];
76
123
  const gates = [];
77
124
  // 1) Boundary check — needed when engine src changed.
@@ -249,7 +296,7 @@ export function planChangedPreflight(options) {
249
296
  gates.push({
250
297
  id: 'tests',
251
298
  title: 'Bun test suite (focused subset where possible)',
252
- command: 'bun test',
299
+ command: testCmd,
253
300
  action: PreflightAction.Run,
254
301
  reason: 'strict profile + engine src changed',
255
302
  canFail: false,
@@ -259,7 +306,7 @@ export function planChangedPreflight(options) {
259
306
  gates.push({
260
307
  id: 'tests',
261
308
  title: 'Bun test suite',
262
- command: 'bun test',
309
+ command: testCmd,
263
310
  action: PreflightAction.Recommend,
264
311
  reason: 'engine src or tests changed; full suite not auto-run',
265
312
  canFail: true,
@@ -269,7 +316,7 @@ export function planChangedPreflight(options) {
269
316
  gates.push({
270
317
  id: 'tests',
271
318
  title: 'Bun test suite',
272
- command: 'bun test',
319
+ command: testCmd,
273
320
  action: PreflightAction.Skip,
274
321
  reason: 'no engine src or test changes',
275
322
  canFail: true,
@@ -281,7 +328,7 @@ export function planChangedPreflight(options) {
281
328
  gates.push({
282
329
  id: 'typecheck',
283
330
  title: 'TypeScript --noEmit',
284
- command: 'bun x tsc -p tsconfig.base.json --noEmit',
331
+ command: typecheckCmd,
285
332
  action: PreflightAction.Recommend,
286
333
  reason: 'engine src changed; not auto-run on quick profile',
287
334
  canFail: true,
@@ -291,7 +338,7 @@ export function planChangedPreflight(options) {
291
338
  gates.push({
292
339
  id: 'typecheck',
293
340
  title: 'TypeScript --noEmit',
294
- command: 'bun x tsc -p tsconfig.base.json --noEmit',
341
+ command: typecheckCmd,
295
342
  action: PreflightAction.Run,
296
343
  reason: 'engine src changed',
297
344
  canFail: false,
@@ -302,7 +349,7 @@ export function planChangedPreflight(options) {
302
349
  gates.push({
303
350
  id: 'typecheck',
304
351
  title: 'TypeScript --noEmit',
305
- command: 'bun x tsc -p tsconfig.base.json --noEmit',
352
+ command: typecheckCmd,
306
353
  action: PreflightAction.Skip,
307
354
  reason: 'no engine src changed',
308
355
  canFail: true,
@@ -0,0 +1,16 @@
1
+ export interface IGuardrailGlobResult {
2
+ /** Target paths covered by at least one guardrail glob. */
3
+ allowed: readonly string[];
4
+ /** Target paths covered by NO guardrail glob — these must not be written. */
5
+ refused: readonly string[];
6
+ /** True when every target path is allowed. */
7
+ ok: boolean;
8
+ }
9
+ /**
10
+ * Partition `targetPaths` into allowed / refused against the recipe's
11
+ * `guardrailGlobs` (allow-list). An empty glob list refuses everything — a
12
+ * recipe with no blast-radius fence must not write (the config validator also
13
+ * rejects this, but the check is defensive here too).
14
+ */
15
+ export declare function checkGuardrailGlobs(targetPaths: readonly string[], guardrailGlobs: readonly string[]): IGuardrailGlobResult;
16
+ //# sourceMappingURL=check-guardrail-globs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-guardrail-globs.d.ts","sourceRoot":"","sources":["../src/check-guardrail-globs.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,6EAA6E;IAC7E,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,8CAA8C;IAC9C,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,cAAc,EAAE,SAAS,MAAM,EAAE,GAChC,oBAAoB,CAWtB"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Guardrail-glob enforcement for the delegate worker.
3
+ *
4
+ * A recipe declares an ALLOW-LIST of globs (`guardrailGlobs`). A worker-emitted
5
+ * target path that matches NONE of them is refused before any write — this is
6
+ * one of the deterministic fences the model cannot influence, layered ON TOP of
7
+ * the generator's `safeResolveTargetPath` floor.
8
+ *
9
+ * CRITICAL: the caller MUST pass the NORMALIZED relative path that will actually
10
+ * be written (the output of `safeResolveTargetPath`), not the raw string a
11
+ * worker emitted — otherwise a `../` traversal whose `**` the glob swallows
12
+ * would pass the fence yet write elsewhere in-root. Matching here is
13
+ * CASE-SENSITIVE (file paths are case-significant on the platforms that matter),
14
+ * so `src/**` does not also grant `SRC/…`.
15
+ *
16
+ * Pure: deterministic glob match; no I/O, no model.
17
+ */
18
+ import { globToRegex, toPosix } from "./contract-file-rule.js";
19
+ /**
20
+ * Partition `targetPaths` into allowed / refused against the recipe's
21
+ * `guardrailGlobs` (allow-list). An empty glob list refuses everything — a
22
+ * recipe with no blast-radius fence must not write (the config validator also
23
+ * rejects this, but the check is defensive here too).
24
+ */
25
+ export function checkGuardrailGlobs(targetPaths, guardrailGlobs) {
26
+ const matchers = guardrailGlobs.map((g) => globToRegex(toPosix(g)));
27
+ const allowed = [];
28
+ const refused = [];
29
+ for (const path of targetPaths) {
30
+ const f = toPosix(path);
31
+ const covered = matchers.some((re) => re.test(f));
32
+ if (covered)
33
+ allowed.push(path);
34
+ else
35
+ refused.push(path);
36
+ }
37
+ return { allowed, refused, ok: refused.length === 0 };
38
+ }
@@ -56,7 +56,7 @@ export function buildCodeIntelligenceChecks(projectRoot, options = {}) {
56
56
  checks.push(...graphChecks(projectRoot, nowMs, staleDays));
57
57
  checks.push(...ruleGraphChecks(projectRoot, nowMs, staleDays));
58
58
  checks.push(...apiSurfaceChecks(projectRoot, nowMs, staleDays));
59
- checks.push(...qualityGateChecks(projectRoot, nowMs));
59
+ checks.push(...qualityGateChecks(projectRoot, nowMs, staleDays));
60
60
  checks.push(...migrationChecks(projectRoot));
61
61
  checks.push(...architectureChecks(projectRoot, nowMs, staleDays));
62
62
  checks.push(...impactRunChecks(projectRoot, nowMs, staleDays));
@@ -319,7 +319,7 @@ function apiSurfaceChecks(projectRoot, nowMs, staleDays) {
319
319
  },
320
320
  ];
321
321
  }
322
- function qualityGateChecks(projectRoot, nowMs) {
322
+ function qualityGateChecks(projectRoot, nowMs, staleDays) {
323
323
  const reportPath = nodePath.join(projectRoot, '.sharkcraft', 'quality-gates', 'last.json');
324
324
  if (!existsSync(reportPath)) {
325
325
  return [];
@@ -338,6 +338,7 @@ function qualityGateChecks(projectRoot, nowMs) {
338
338
  }
339
339
  const days = ageDays(report.startedAt, nowMs);
340
340
  const ageStr = days !== undefined ? ` ${fmtAge(days)}` : '';
341
+ const stale = days !== undefined && days > staleDays;
341
342
  const status = report.overall ?? 'unknown';
342
343
  const failingGates = (report.gates ?? [])
343
344
  .filter((g) => g.status === 'fail')
@@ -354,15 +355,34 @@ function qualityGateChecks(projectRoot, nowMs) {
354
355
  ];
355
356
  }
356
357
  if (status === 'fail') {
358
+ const failMsg = failingGates.length > 0
359
+ ? `Last gate FAIL${ageStr} — ${failingGates.join(', ')}.`
360
+ : `Last gate FAIL${ageStr}.`;
361
+ // An old FAIL is stale maintenance, not a verified current regression:
362
+ // age it out into a folded advisory that nudges a re-run instead of a
363
+ // hard Warning that masks the (now-unknown) state of the tree. A fresh
364
+ // FAIL stays loud. Mirrors the stale handling in apiSurfaceChecks /
365
+ // architectureChecks so the whole code-intelligence section is consistent.
366
+ if (stale) {
367
+ return [
368
+ {
369
+ id: 'code-intelligence-quality-gate',
370
+ title: 'Quality gate (last run)',
371
+ severity: DoctorSeverity.Info,
372
+ advisory: true,
373
+ category: CATEGORY,
374
+ message: `${failMsg} Stale (>${staleDays}d) — may not reflect the current tree.`,
375
+ fix: 'Re-run `shrk gate` to refresh.',
376
+ },
377
+ ];
378
+ }
357
379
  return [
358
380
  {
359
381
  id: 'code-intelligence-quality-gate',
360
382
  title: 'Quality gate (last run)',
361
383
  severity: DoctorSeverity.Warning,
362
384
  category: CATEGORY,
363
- message: failingGates.length > 0
364
- ? `Last gate FAIL${ageStr} — ${failingGates.join(', ')}.`
365
- : `Last gate FAIL${ageStr}.`,
385
+ message: failMsg,
366
386
  fix: 'Re-run with `shrk gate` and address the failing gate(s).',
367
387
  whyThisMatters: 'The quality gate is the one-shot pass/fail the dashboard and CI agents trust — leaving it red hides real regressions.',
368
388
  },
@@ -1 +1 @@
1
- {"version":3,"file":"command-recommender.d.ts","sourceRoot":"","sources":["../src/command-recommender.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,eAAO,MAAM,0BAA0B,sCAAsC,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,WAAW,GAAG,eAAe,GAAG,gBAAgB,GAAG,eAAe,GAAG,YAAY,CAAC;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,0BAA0B,CAAC;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,iEAAiE;IACjE,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAqED,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,qBAAqB,EACjC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,4BAA4B,CAAC,CAoFvC"}
1
+ {"version":3,"file":"command-recommender.d.ts","sourceRoot":"","sources":["../src/command-recommender.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,eAAO,MAAM,0BAA0B,sCAAsC,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,WAAW,GAAG,eAAe,GAAG,gBAAgB,GAAG,eAAe,GAAG,YAAY,CAAC;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,0BAA0B,CAAC;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,iEAAiE;IACjE,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAgFD,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,qBAAqB,EACjC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,4BAA4B,CAAC,CAoFvC"}
@@ -51,18 +51,30 @@ const RECIPES = [
51
51
  ],
52
52
  },
53
53
  {
54
- match: /code[-\s]?intel|code intelligence|code graph|graph status|graph health|import cycle|unresolved import|blast radius|callers|dependents/i,
54
+ match: /code[-\s]?intel|code intelligence|code graph|graph status|graph health|import cycle|unresolved import|blast radius|callers|dependents|who calls|who uses|where is .*\bused|find usages|usages? of|is .*\bwired|wired (up|to)|wire[ds]? .*\bto\b|path (from|between)|reach(es|able)|connected to|who implements|implementations? of|subclass|subtype|load[-\s]?bearing|\bhubs?\b|most[-\s](depended|imported|referenced)|what.*change carefully|important.*\b(code|files?|symbols?)|what breaks if|what calls|call sites?|trace .*\b(symbol|function|usage)/i,
55
55
  recommendations: [
56
+ { command: 'shrk graph callers <symbol>', why: 'Who calls / references a symbol, as path:line — the grep replacement for "who calls X / where is X used".' },
57
+ { command: 'shrk graph path <from> <to>', why: 'Is code A actually wired to code B? Shortest import/call/implements path between two files or symbols — the deterministic answer to "is X wired to Y".' },
58
+ { command: 'shrk graph hubs', why: 'The most-depended-on symbols/files (biggest blast radius) — what to change carefully or understand first when onboarding.' },
59
+ { command: 'shrk graph context <file-or-symbol>', why: 'Inspect one file or symbol with imports, callers, subtypes/supertypes, bridge context, and framework hits — answers "is X wired".' },
60
+ { command: 'shrk graph impact <file-or-symbol> --full', why: 'What breaks if you change it: graph-backed dependents, caller files, rules, and likely tests.' },
56
61
  { command: 'shrk code-intel', why: 'One-shot health view across the code graph, bridge, and quality gates.' },
57
62
  { command: 'shrk graph status', why: 'Check whether the code graph is present, fresh, and internally consistent.' },
58
- { command: 'shrk graph cycles', why: 'List import cycles that inflate blast-radius calculations.' },
59
63
  { command: 'shrk graph unresolved', why: 'Find unresolved imports that undercut graph accuracy.' },
60
- { command: 'shrk graph context <file-or-symbol>', why: 'Inspect one file or symbol with imports, callers, bridge context, and framework hits.' },
61
- { command: 'shrk graph impact <file-or-symbol> --full', why: 'Estimate blast radius with graph-backed dependents, caller files, rules, and likely tests.' },
64
+ ],
65
+ },
66
+ {
67
+ match: /delegate|mechanical (edit|task|change|refactor)|grunt (work|task)|boilerplate|repetitive edit|hand (this|it|off) (off |over )?to (a |the )?(local |worker|model)|add (a |an )?(barrel )?(export|import)\b|local (llm|model) (do|handle|make)/i,
68
+ recommendations: [
69
+ { command: 'shrk delegate list', why: 'See the MECHANICAL task recipes a local-LLM worker can handle (the engine verifies the result + auto-reverts on failure). Each is fenced to specific files + op kinds.' },
70
+ { command: 'shrk delegate run "<task>" --recipe <id> --apply', why: 'Hand a mechanical, deterministically-verifiable edit to the LOCAL worker — you pay for a compact brief + result instead of reading the whole file and writing the edit. The edit lands only if it passes the recipe verification.' },
71
+ { command: 'shrk delegate explain <id>', why: 'Audit a recipe before trusting it: the allowed ops, guardrail globs, and whether its verification is bound.' },
62
72
  ],
63
73
  },
64
74
  ];
65
75
  function safetyLevelFor(command) {
76
+ if (/^shrk delegate run/i.test(command))
77
+ return 'writes-source';
66
78
  if (/^shrk (gen|init|apply|import|presets apply --write|packs (sign|new))/i.test(command))
67
79
  return 'writes-source';
68
80
  if (/^shrk (onboard|brief|dev start|handoff|export|report site|impact|ci scaffold|simulate|orchestrate)/i.test(command))
@@ -19,6 +19,14 @@ export interface IContractFileRule {
19
19
  reason?: string;
20
20
  severity?: ContractFileRuleSeverity;
21
21
  }
22
+ export declare function toPosix(p: string): string;
23
+ /**
24
+ * Translate a deterministic POSIX-style glob to a regex anchored at both ends.
25
+ * Case-sensitive by itself — `matchContractFileRule` lowercases its inputs for
26
+ * the contract use-case, but security-sensitive callers (the delegate guardrail)
27
+ * use this directly to keep file-path case significant.
28
+ */
29
+ export declare function globToRegex(pattern: string): RegExp;
22
30
  /** Pure matcher — returns true when `file` is covered by `rule`. */
23
31
  export declare function matchContractFileRule(rule: IContractFileRule, file: string): boolean;
24
32
  export interface IContractFileRuleMatch {
@@ -1 +1 @@
1
- {"version":3,"file":"contract-file-rule.d.ts","sourceRoot":"","sources":["../src/contract-file-rule.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAY,oBAAoB;IAC9B,IAAI,SAAS;IACb,UAAU,gBAAgB;IAC1B,KAAK,UAAU;IACf,QAAQ,aAAa;CACtB;AAED,oBAAY,wBAAwB;IAClC,KAAK,UAAU;IACf,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,oBAAoB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,wBAAwB,CAAC;CACrC;AAyCD,oEAAoE;AACpE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAapF;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,sEAAsE;AACtE,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,SAAS,sBAAsB,EAAE,CAQnC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,SAAS,iBAAiB,EAAE,CAE9B;AAED,6DAA6D;AAC7D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAQxE"}
1
+ {"version":3,"file":"contract-file-rule.d.ts","sourceRoot":"","sources":["../src/contract-file-rule.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAY,oBAAoB;IAC9B,IAAI,SAAS;IACb,UAAU,gBAAgB;IAC1B,KAAK,UAAU;IACf,QAAQ,aAAa;CACtB;AAED,oBAAY,wBAAwB;IAClC,KAAK,UAAU;IACf,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,oBAAoB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,wBAAwB,CAAC;CACrC;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAMD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA4BnD;AAED,oEAAoE;AACpE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAapF;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,sEAAsE;AACtE,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,SAAS,sBAAsB,EAAE,CAQnC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,SAAS,iBAAiB,EAAE,CAE9B;AAED,6DAA6D;AAC7D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAQxE"}
@@ -15,14 +15,19 @@ export var ContractFileRuleSeverity;
15
15
  ContractFileRuleSeverity["Error"] = "error";
16
16
  ContractFileRuleSeverity["Warning"] = "warning";
17
17
  })(ContractFileRuleSeverity || (ContractFileRuleSeverity = {}));
18
- function toPosix(p) {
18
+ export function toPosix(p) {
19
19
  return p.replace(/\\/g, '/').replace(/^\.\//, '');
20
20
  }
21
21
  function escapeRegex(s) {
22
22
  return s.replace(/[.+^$(){}|\\[\]]/g, '\\$&');
23
23
  }
24
- /** Translate a deterministic POSIX-style glob to a regex anchored at both ends. */
25
- function globToRegex(pattern) {
24
+ /**
25
+ * Translate a deterministic POSIX-style glob to a regex anchored at both ends.
26
+ * Case-sensitive by itself — `matchContractFileRule` lowercases its inputs for
27
+ * the contract use-case, but security-sensitive callers (the delegate guardrail)
28
+ * use this directly to keep file-path case significant.
29
+ */
30
+ export function globToRegex(pattern) {
26
31
  const p = toPosix(pattern);
27
32
  let re = '';
28
33
  let i = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"coverage-report.d.ts","sourceRoot":"","sources":["../src/coverage-report.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAGvE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAOD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,qBAAqB,GAAG,eAAe,CAiLtF"}
1
+ {"version":3,"file":"coverage-report.d.ts","sourceRoot":"","sources":["../src/coverage-report.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAGvE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAOD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,qBAAqB,GAAG,eAAe,CA6LtF"}
@@ -181,7 +181,20 @@ export function buildCoverageReport(inspection) {
181
181
  missing,
182
182
  });
183
183
  }
184
- const overall = Math.round(categories.reduce((s, c) => s + c.score, 0) / Math.max(categories.length, 1));
184
+ // `overall` measures how well the USER has grounded THIS repo, so two kinds
185
+ // of category must not inflate it:
186
+ // 1. Empty categories (total === 0) — they vacuously score 100 (see `pct`),
187
+ // so a repo that just hasn't configured boundaries/packs would read as
188
+ // perfectly grounded and tell the agent to skip adding rules.
189
+ // 2. `preset-references` — it scores the BUILT-IN preset registry's
190
+ // validity (≈72/72 always), not the user's configuration, so on an
191
+ // otherwise-empty repo it alone would peg the overall at 100.
192
+ // Both stay as displayed categories; they're just out of the average.
193
+ // 0 when nothing user-authored is configured (honest: no coverage to report).
194
+ const scored = categories.filter((c) => c.total > 0 && c.id !== 'preset-references');
195
+ const overall = scored.length > 0
196
+ ? Math.round(scored.reduce((s, c) => s + c.score, 0) / scored.length)
197
+ : 0;
185
198
  if (overall < 60)
186
199
  suggestions.push('Pair every critical rule with action hints + verification commands.');
187
200
  if (overall < 80)
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Resolve the delegate-recipe catalog from a loaded config (read-only, NO model).
3
+ *
4
+ * Merges the delegation-level `provider`/`model` defaults into each recipe and
5
+ * checks every `verificationId` against the config's `verificationCommands[]`.
6
+ * A recipe is `delegatable` only when ALL its verification ids are bound — a
7
+ * recipe with an unbound (or empty) verification has no deterministic gate, so
8
+ * `shrk delegate explain` can show an author the fence is real before trusting
9
+ * it. Pack-contributed recipes (Phase 4) will merge in here too.
10
+ */
11
+ import type { IDelegateRecipe, ISharkCraftConfig } from '@shrkcrft/config';
12
+ export interface IResolvedDelegateRecipe extends IDelegateRecipe {
13
+ /** Provider after applying the delegation-level default (never undefined). */
14
+ resolvedProvider: 'auto' | 'ollama' | 'llamacpp';
15
+ /** Model after applying the delegation-level default (undefined = provider default). */
16
+ resolvedModel?: string;
17
+ /** verificationIds that do NOT resolve to a `verificationCommands[].id`. */
18
+ unboundVerificationIds: readonly string[];
19
+ /** True when every verificationId resolves AND at least one is declared. */
20
+ verificationBound: boolean;
21
+ /** True when the recipe is safe to delegate (verification fully bound). */
22
+ delegatable: boolean;
23
+ /** Where the recipe came from. */
24
+ source: 'config' | 'pack';
25
+ /** Contributing pack, when `source === 'pack'`. */
26
+ packageName?: string;
27
+ }
28
+ /** A pack-contributed recipe (shape from `loadDelegateRecipesFromPacks`). */
29
+ export interface IPackRecipeInput {
30
+ recipe: IDelegateRecipe;
31
+ packageName: string;
32
+ }
33
+ /**
34
+ * Resolve the delegate catalog from config + pack-contributed recipes.
35
+ *
36
+ * Merge order: pack recipes first, then INLINE config recipes override a pack
37
+ * recipe of the same id. `recipeOverrides` (by id) then patch model /
38
+ * verificationIds / guardrailGlobs, or drop the recipe (`enabled: false`).
39
+ * Finally each recipe's `verificationIds` are checked against
40
+ * `verificationCommands[]` — `delegatable` only when all are bound.
41
+ */
42
+ export declare function resolveDelegateCatalog(config: ISharkCraftConfig, packRecipes?: readonly IPackRecipeInput[]): readonly IResolvedDelegateRecipe[];
43
+ /** Look up one resolved recipe by id (config recipes only; no pack loading). */
44
+ export declare function findDelegateRecipe(config: ISharkCraftConfig, recipeId: string): IResolvedDelegateRecipe | undefined;
45
+ //# sourceMappingURL=delegate-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegate-catalog.d.ts","sourceRoot":"","sources":["../src/delegate-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE3E,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC9D,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IACjD,wFAAwF;IACxF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,4EAA4E;IAC5E,iBAAiB,EAAE,OAAO,CAAC;IAC3B,2EAA2E;IAC3E,WAAW,EAAE,OAAO,CAAC;IACrB,kCAAkC;IAClC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC1B,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,iBAAiB,EACzB,WAAW,GAAE,SAAS,gBAAgB,EAAO,GAC5C,SAAS,uBAAuB,EAAE,CAkCpC;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,QAAQ,EAAE,MAAM,GACf,uBAAuB,GAAG,SAAS,CAErC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Resolve the delegate catalog from config + pack-contributed recipes.
3
+ *
4
+ * Merge order: pack recipes first, then INLINE config recipes override a pack
5
+ * recipe of the same id. `recipeOverrides` (by id) then patch model /
6
+ * verificationIds / guardrailGlobs, or drop the recipe (`enabled: false`).
7
+ * Finally each recipe's `verificationIds` are checked against
8
+ * `verificationCommands[]` — `delegatable` only when all are bound.
9
+ */
10
+ export function resolveDelegateCatalog(config, packRecipes = []) {
11
+ const delegation = config.delegation;
12
+ if (!delegation)
13
+ return [];
14
+ const known = new Set((config.verificationCommands ?? []).map((v) => v.id));
15
+ const overrides = delegation.recipeOverrides ?? {};
16
+ const byId = new Map();
17
+ for (const pr of packRecipes)
18
+ byId.set(pr.recipe.id, { recipe: pr.recipe, source: 'pack', packageName: pr.packageName });
19
+ for (const recipe of delegation.recipes ?? [])
20
+ byId.set(recipe.id, { recipe, source: 'config' });
21
+ const out = [];
22
+ for (const { recipe, source, packageName } of byId.values()) {
23
+ const ov = overrides[recipe.id];
24
+ if (ov?.enabled === false)
25
+ continue; // dropped by override
26
+ const merged = {
27
+ ...recipe,
28
+ ...(ov?.model !== undefined ? { model: ov.model } : {}),
29
+ ...(ov?.verificationIds !== undefined ? { verificationIds: ov.verificationIds } : {}),
30
+ ...(ov?.guardrailGlobs !== undefined ? { guardrailGlobs: ov.guardrailGlobs } : {}),
31
+ };
32
+ const unbound = (merged.verificationIds ?? []).filter((id) => !known.has(id));
33
+ const verificationBound = unbound.length === 0 && (merged.verificationIds ?? []).length > 0;
34
+ out.push({
35
+ ...merged,
36
+ resolvedProvider: merged.provider ?? delegation.provider ?? 'auto',
37
+ ...(merged.model ?? delegation.model ? { resolvedModel: merged.model ?? delegation.model } : {}),
38
+ unboundVerificationIds: unbound,
39
+ verificationBound,
40
+ delegatable: verificationBound,
41
+ source,
42
+ ...(packageName ? { packageName } : {}),
43
+ });
44
+ }
45
+ return out;
46
+ }
47
+ /** Look up one resolved recipe by id (config recipes only; no pack loading). */
48
+ export function findDelegateRecipe(config, recipeId) {
49
+ return resolveDelegateCatalog(config).find((r) => r.id === recipeId);
50
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `shrk doctor` checks for delegate-worker recipe health.
3
+ *
4
+ * Surfaces recipes that are NOT delegatable — a recipe whose `verificationIds`
5
+ * don't all resolve to `verificationCommands[]` (incl. after `recipeOverrides`)
6
+ * has no deterministic gate, so `shrk delegate run` would refuse it. `runDoctor`
7
+ * has no other surface for this (it doesn't run `validateConfig`), so this is the
8
+ * proactive catch. Sync + config-only (pack-recipe health is `delegate explain`,
9
+ * which can load pack files async); silent when the repo hasn't opted into
10
+ * delegation.
11
+ */
12
+ import type { ISharkCraftConfig } from '@shrkcrft/config';
13
+ import { type IDoctorCheck } from './doctor-result.js';
14
+ export declare function buildDelegateRecipeChecks(config: ISharkCraftConfig | null): IDoctorCheck[];
15
+ //# sourceMappingURL=delegate-doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegate-doctor.d.ts","sourceRoot":"","sources":["../src/delegate-doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,GAAG,YAAY,EAAE,CAiC1F"}