@shrkcrft/cli 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (228) hide show
  1. package/dist/audit/knowledge-audit-llm.d.ts +19 -0
  2. package/dist/audit/knowledge-audit-llm.d.ts.map +1 -0
  3. package/dist/audit/knowledge-audit-llm.js +164 -0
  4. package/dist/audit/knowledge-audit.d.ts +61 -0
  5. package/dist/audit/knowledge-audit.d.ts.map +1 -0
  6. package/dist/audit/knowledge-audit.js +203 -0
  7. package/dist/audit/knowledge-fix-plan-llm.d.ts +11 -0
  8. package/dist/audit/knowledge-fix-plan-llm.d.ts.map +1 -0
  9. package/dist/audit/knowledge-fix-plan-llm.js +141 -0
  10. package/dist/audit/knowledge-fix-plan.d.ts +41 -0
  11. package/dist/audit/knowledge-fix-plan.d.ts.map +1 -0
  12. package/dist/audit/knowledge-fix-plan.js +125 -0
  13. package/dist/audit/pipeline-audit-llm.d.ts +11 -0
  14. package/dist/audit/pipeline-audit-llm.d.ts.map +1 -0
  15. package/dist/audit/pipeline-audit-llm.js +134 -0
  16. package/dist/audit/pipeline-audit.d.ts +69 -0
  17. package/dist/audit/pipeline-audit.d.ts.map +1 -0
  18. package/dist/audit/pipeline-audit.js +166 -0
  19. package/dist/audit/templates-audit-llm.d.ts +19 -0
  20. package/dist/audit/templates-audit-llm.d.ts.map +1 -0
  21. package/dist/audit/templates-audit-llm.js +207 -0
  22. package/dist/audit/templates-audit.d.ts +63 -0
  23. package/dist/audit/templates-audit.d.ts.map +1 -0
  24. package/dist/audit/templates-audit.js +171 -0
  25. package/dist/audit/templates-fix-plan-llm.d.ts +19 -0
  26. package/dist/audit/templates-fix-plan-llm.d.ts.map +1 -0
  27. package/dist/audit/templates-fix-plan-llm.js +162 -0
  28. package/dist/audit/templates-fix-plan.d.ts +37 -0
  29. package/dist/audit/templates-fix-plan.d.ts.map +1 -0
  30. package/dist/audit/templates-fix-plan.js +174 -0
  31. package/dist/command-registry.d.ts +28 -0
  32. package/dist/command-registry.d.ts.map +1 -1
  33. package/dist/command-registry.js +91 -1
  34. package/dist/commands/ai-status.command.d.ts +19 -0
  35. package/dist/commands/ai-status.command.d.ts.map +1 -0
  36. package/dist/commands/ai-status.command.js +94 -0
  37. package/dist/commands/api-diff.command.d.ts +11 -0
  38. package/dist/commands/api-diff.command.d.ts.map +1 -0
  39. package/dist/commands/api-diff.command.js +144 -0
  40. package/dist/commands/apply.command.d.ts.map +1 -1
  41. package/dist/commands/apply.command.js +10 -2
  42. package/dist/commands/arch.command.d.ts +9 -0
  43. package/dist/commands/arch.command.d.ts.map +1 -0
  44. package/dist/commands/arch.command.js +186 -0
  45. package/dist/commands/ask.command.d.ts.map +1 -1
  46. package/dist/commands/ask.command.js +10 -9
  47. package/dist/commands/cache-align.command.d.ts +12 -0
  48. package/dist/commands/cache-align.command.d.ts.map +1 -0
  49. package/dist/commands/cache-align.command.js +78 -0
  50. package/dist/commands/check.command.d.ts.map +1 -1
  51. package/dist/commands/check.command.js +26 -2
  52. package/dist/commands/code-intel.command.d.ts +18 -0
  53. package/dist/commands/code-intel.command.d.ts.map +1 -0
  54. package/dist/commands/code-intel.command.js +146 -0
  55. package/dist/commands/codemod.command.d.ts.map +1 -1
  56. package/dist/commands/codemod.command.js +27 -6
  57. package/dist/commands/command-catalog.d.ts +15 -3
  58. package/dist/commands/command-catalog.d.ts.map +1 -1
  59. package/dist/commands/command-catalog.js +407 -34
  60. package/dist/commands/commands.command.d.ts.map +1 -1
  61. package/dist/commands/commands.command.js +4 -4
  62. package/dist/commands/completion.command.d.ts +10 -0
  63. package/dist/commands/completion.command.d.ts.map +1 -0
  64. package/dist/commands/completion.command.js +121 -0
  65. package/dist/commands/compress.command.d.ts +8 -0
  66. package/dist/commands/compress.command.d.ts.map +1 -0
  67. package/dist/commands/compress.command.js +147 -0
  68. package/dist/commands/constructs.command.d.ts.map +1 -1
  69. package/dist/commands/constructs.command.js +89 -23
  70. package/dist/commands/context.command.d.ts.map +1 -1
  71. package/dist/commands/context.command.js +121 -1
  72. package/dist/commands/contract-gate.command.d.ts.map +1 -1
  73. package/dist/commands/contract-gate.command.js +5 -1
  74. package/dist/commands/delegate.command.d.ts +65 -0
  75. package/dist/commands/delegate.command.d.ts.map +1 -0
  76. package/dist/commands/delegate.command.js +657 -0
  77. package/dist/commands/deps-audit.command.d.ts +23 -0
  78. package/dist/commands/deps-audit.command.d.ts.map +1 -0
  79. package/dist/commands/deps-audit.command.js +270 -0
  80. package/dist/commands/dev.command.d.ts.map +1 -1
  81. package/dist/commands/dev.command.js +5 -2
  82. package/dist/commands/diff-check.command.d.ts +30 -0
  83. package/dist/commands/diff-check.command.d.ts.map +1 -0
  84. package/dist/commands/diff-check.command.js +210 -0
  85. package/dist/commands/doctor.command.d.ts.map +1 -1
  86. package/dist/commands/doctor.command.js +162 -10
  87. package/dist/commands/export.command.d.ts.map +1 -1
  88. package/dist/commands/export.command.js +76 -3
  89. package/dist/commands/framework.command.d.ts +12 -0
  90. package/dist/commands/framework.command.d.ts.map +1 -0
  91. package/dist/commands/framework.command.js +180 -0
  92. package/dist/commands/gate.command.d.ts +15 -0
  93. package/dist/commands/gate.command.d.ts.map +1 -0
  94. package/dist/commands/gate.command.js +300 -0
  95. package/dist/commands/gen.command.d.ts.map +1 -1
  96. package/dist/commands/gen.command.js +13 -1
  97. package/dist/commands/graph-code-subverbs.d.ts +33 -0
  98. package/dist/commands/graph-code-subverbs.d.ts.map +1 -0
  99. package/dist/commands/graph-code-subverbs.js +1385 -0
  100. package/dist/commands/graph.command.d.ts.map +1 -1
  101. package/dist/commands/graph.command.js +31 -2
  102. package/dist/commands/help.command.d.ts +4 -3
  103. package/dist/commands/help.command.d.ts.map +1 -1
  104. package/dist/commands/help.command.js +86 -18
  105. package/dist/commands/helper.command.js +1 -1
  106. package/dist/commands/impact.command.d.ts.map +1 -1
  107. package/dist/commands/impact.command.js +171 -1
  108. package/dist/commands/import.command.d.ts.map +1 -1
  109. package/dist/commands/import.command.js +121 -5
  110. package/dist/commands/ingest.command.d.ts.map +1 -1
  111. package/dist/commands/ingest.command.js +5 -1
  112. package/dist/commands/init.command.d.ts.map +1 -1
  113. package/dist/commands/init.command.js +174 -7
  114. package/dist/commands/knowledge-author.command.d.ts.map +1 -1
  115. package/dist/commands/knowledge-author.command.js +9 -0
  116. package/dist/commands/knowledge-propose.command.d.ts.map +1 -1
  117. package/dist/commands/knowledge-propose.command.js +4 -2
  118. package/dist/commands/knowledge.command.d.ts.map +1 -1
  119. package/dist/commands/knowledge.command.js +26 -3
  120. package/dist/commands/migrate.command.d.ts +13 -0
  121. package/dist/commands/migrate.command.d.ts.map +1 -0
  122. package/dist/commands/migrate.command.js +152 -0
  123. package/dist/commands/move-plan.command.d.ts +23 -0
  124. package/dist/commands/move-plan.command.d.ts.map +1 -0
  125. package/dist/commands/move-plan.command.js +360 -0
  126. package/dist/commands/packs-new.d.ts +1 -1
  127. package/dist/commands/packs-new.d.ts.map +1 -1
  128. package/dist/commands/packs-new.js +5 -36
  129. package/dist/commands/packs.command.d.ts.map +1 -1
  130. package/dist/commands/packs.command.js +2 -10
  131. package/dist/commands/plan-context.command.d.ts +11 -0
  132. package/dist/commands/plan-context.command.d.ts.map +1 -0
  133. package/dist/commands/plan-context.command.js +85 -0
  134. package/dist/commands/preflight.command.d.ts.map +1 -1
  135. package/dist/commands/preflight.command.js +15 -0
  136. package/dist/commands/profiles.command.js +4 -4
  137. package/dist/commands/recommend.command.d.ts +6 -0
  138. package/dist/commands/recommend.command.d.ts.map +1 -1
  139. package/dist/commands/recommend.command.js +119 -5
  140. package/dist/commands/release.command.js +13 -13
  141. package/dist/commands/rule-graph-subverbs.d.ts +3 -0
  142. package/dist/commands/rule-graph-subverbs.d.ts.map +1 -0
  143. package/dist/commands/rule-graph-subverbs.js +132 -0
  144. package/dist/commands/rules.command.d.ts.map +1 -1
  145. package/dist/commands/rules.command.js +20 -3
  146. package/dist/commands/scaffold-validate.command.d.ts +22 -0
  147. package/dist/commands/scaffold-validate.command.d.ts.map +1 -0
  148. package/dist/commands/scaffold-validate.command.js +215 -0
  149. package/dist/commands/search-structural.command.d.ts +18 -0
  150. package/dist/commands/search-structural.command.d.ts.map +1 -0
  151. package/dist/commands/search-structural.command.js +376 -0
  152. package/dist/commands/search.command.js +1 -1
  153. package/dist/commands/smart-context.command.d.ts +67 -0
  154. package/dist/commands/smart-context.command.d.ts.map +1 -0
  155. package/dist/commands/smart-context.command.js +4728 -0
  156. package/dist/commands/spike.command.d.ts +22 -0
  157. package/dist/commands/spike.command.d.ts.map +1 -0
  158. package/dist/commands/spike.command.js +235 -0
  159. package/dist/commands/surface.command.d.ts +1 -0
  160. package/dist/commands/surface.command.d.ts.map +1 -1
  161. package/dist/commands/surface.command.js +10 -3
  162. package/dist/commands/task-context.command.d.ts.map +1 -1
  163. package/dist/commands/task-context.command.js +5 -17
  164. package/dist/commands/task.command.d.ts.map +1 -1
  165. package/dist/commands/task.command.js +8 -2
  166. package/dist/commands/template-quality.command.d.ts.map +1 -1
  167. package/dist/commands/template-quality.command.js +39 -3
  168. package/dist/commands/templates.command.d.ts.map +1 -1
  169. package/dist/commands/templates.command.js +37 -2
  170. package/dist/commands/tests.command.d.ts.map +1 -1
  171. package/dist/commands/tests.command.js +13 -2
  172. package/dist/commands/watch.command.d.ts +26 -0
  173. package/dist/commands/watch.command.d.ts.map +1 -0
  174. package/dist/commands/watch.command.js +456 -0
  175. package/dist/dashboard/code-intelligence-data.d.ts +33 -0
  176. package/dist/dashboard/code-intelligence-data.d.ts.map +1 -0
  177. package/dist/dashboard/code-intelligence-data.js +329 -0
  178. package/dist/dashboard/dashboard-api-server.d.ts.map +1 -1
  179. package/dist/dashboard/dashboard-api-server.js +256 -2
  180. package/dist/dashboard/knowledge-ask.d.ts +4 -0
  181. package/dist/dashboard/knowledge-ask.d.ts.map +1 -0
  182. package/dist/dashboard/knowledge-ask.js +112 -0
  183. package/dist/env/load-dotenv.d.ts +15 -0
  184. package/dist/env/load-dotenv.d.ts.map +1 -0
  185. package/dist/env/load-dotenv.js +70 -0
  186. package/dist/export/claude-commands-export.d.ts +60 -0
  187. package/dist/export/claude-commands-export.d.ts.map +1 -0
  188. package/dist/export/claude-commands-export.js +276 -0
  189. package/dist/export/export-formats.d.ts +1 -1
  190. package/dist/export/export-formats.d.ts.map +1 -1
  191. package/dist/export/export-formats.js +139 -12
  192. package/dist/index.d.ts +3 -0
  193. package/dist/index.d.ts.map +1 -1
  194. package/dist/index.js +3 -0
  195. package/dist/init/init-templates.d.ts.map +1 -1
  196. package/dist/init/init-templates.js +133 -113
  197. package/dist/init/paths-advisory.d.ts +20 -0
  198. package/dist/init/paths-advisory.d.ts.map +1 -0
  199. package/dist/init/paths-advisory.js +88 -0
  200. package/dist/main.d.ts.map +1 -1
  201. package/dist/main.js +331 -17
  202. package/dist/output/ccr-store-config.d.ts +18 -0
  203. package/dist/output/ccr-store-config.d.ts.map +1 -0
  204. package/dist/output/ccr-store-config.js +41 -0
  205. package/dist/output/format-output.d.ts.map +1 -1
  206. package/dist/output/format-output.js +6 -1
  207. package/dist/output/output-compression.d.ts +15 -0
  208. package/dist/output/output-compression.d.ts.map +1 -0
  209. package/dist/output/output-compression.js +60 -0
  210. package/dist/output/resolve-compress-type.d.ts +22 -0
  211. package/dist/output/resolve-compress-type.d.ts.map +1 -0
  212. package/dist/output/resolve-compress-type.js +21 -0
  213. package/dist/output/watch-loop.d.ts +9 -1
  214. package/dist/output/watch-loop.d.ts.map +1 -1
  215. package/dist/output/watch-loop.js +13 -3
  216. package/dist/schemas/json-schemas.d.ts +384 -36
  217. package/dist/schemas/json-schemas.d.ts.map +1 -1
  218. package/dist/schemas/json-schemas.js +247 -36
  219. package/dist/surface/profiles.d.ts.map +1 -1
  220. package/dist/surface/profiles.js +54 -10
  221. package/dist/surface/surface-config-writer.d.ts.map +1 -1
  222. package/dist/surface/surface-config-writer.js +23 -11
  223. package/dist/validation/run-validation-loop.d.ts.map +1 -1
  224. package/dist/validation/run-validation-loop.js +5 -1
  225. package/package.json +35 -21
  226. package/dist/commands/plugin.command.d.ts +0 -11
  227. package/dist/commands/plugin.command.d.ts.map +0 -1
  228. package/dist/commands/plugin.command.js +0 -394
@@ -0,0 +1,657 @@
1
+ /**
2
+ * `shrk delegate` — hand a mechanical, deterministically-verifiable edit to a
3
+ * LOCAL-LLM worker, gated end-to-end by the deterministic engine.
4
+ *
5
+ * Flow (the worker is the ONLY stochastic step):
6
+ * provider.send → parseDelegateEdit → checkGuardrailGlobs → packageDelegatePlan
7
+ * → signPlan → savePlanToFile → (apply) verify → evaluateSavedPlanInPlace
8
+ * → writeSyntheticPlan → runValidationLoop → auto-revert on verify failure.
9
+ *
10
+ * The model never writes: its output becomes a SIGNED synthetic plan that flows
11
+ * through the same apply primitives `shrk apply` uses. A failed verification
12
+ * auto-reverts the edit, so a bad generation costs a retry, never a wrong write.
13
+ */
14
+ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
15
+ import * as nodePath from 'node:path';
16
+ import { containsTraversal, safeResolveTargetPath } from '@shrkcrft/core';
17
+ import { AiMessageRole, callDelegateWithRetry, delegateRepromptMessage, selectAiProvider, } from '@shrkcrft/ai';
18
+ import { loadProjectConfig } from '@shrkcrft/config';
19
+ import { compressCode, compressDiff } from '@shrkcrft/compress';
20
+ import { listIndexableFiles } from '@shrkcrft/embeddings';
21
+ import { checkGuardrailGlobs, resolveDelegateCatalogForProject, unifiedDiff, } from '@shrkcrft/inspector';
22
+ import { evaluateSavedPlanInPlace, packageDelegatePlan, savePlanToFile, signPlan, verifyPlan, writeSyntheticPlan, } from '@shrkcrft/generator';
23
+ import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
24
+ import { asJson, header, kv } from "../output/format-output.js";
25
+ import { runValidationLoop } from "../validation/run-validation-loop.js";
26
+ const DEFAULT_MAX_BUDGET_MS = 60_000;
27
+ /** A concrete sample op for a kind — a few-shot anchor for weak local models. */
28
+ function opExample(kind) {
29
+ switch (kind) {
30
+ case 'export':
31
+ return { targetPath: 'src/index.ts', operation: { kind: 'export', from: './health' } };
32
+ case 'ensure-import':
33
+ return { targetPath: 'src/service.ts', operation: { kind: 'ensure-import', from: './logger', symbols: ['log'] } };
34
+ case 'replace':
35
+ return { targetPath: 'src/config.ts', operation: { kind: 'replace', find: 'timeoutMs = 30000', replaceWith: 'timeoutMs = 60000', expectMatches: 1 } };
36
+ case 'create':
37
+ return { targetPath: 'src/new-file.ts', operation: { kind: 'create', content: 'export const x = 1;\n' } };
38
+ case 'insert-array-entry':
39
+ return { targetPath: 'src/registry.ts', operation: { kind: 'insert-array-entry', arrayName: 'ALL', entryValue: 'newEntry' } };
40
+ case 'insert-enum-entry':
41
+ return { targetPath: 'src/kinds.ts', operation: { kind: 'insert-enum-entry', enumName: 'Kind', entryName: 'NEW', entryValue: 'new' } };
42
+ default:
43
+ return null;
44
+ }
45
+ }
46
+ const RECIPE_CONTEXT_FILE_CAP = 8;
47
+ /**
48
+ * The in-scope files (current contents, compressed to signatures via the
49
+ * code-outline pass) handed to the LOCAL worker so it can pick the right
50
+ * targetPath, check idempotency, and find exact text to replace — instead of
51
+ * guessing. This goes in the WORKER's prompt, read locally, so it costs the
52
+ * orchestrator (Claude) NOTHING. Bounded to keep a small local model's context
53
+ * focused. Returns '' when there's nothing in scope / on any error.
54
+ */
55
+ export function gatherRecipeContext(projectRoot, recipe) {
56
+ let candidates;
57
+ try {
58
+ const all = listIndexableFiles(projectRoot, 3000);
59
+ candidates = checkGuardrailGlobs(all, recipe.guardrailGlobs).allowed;
60
+ }
61
+ catch {
62
+ return '';
63
+ }
64
+ if (candidates.length === 0)
65
+ return '';
66
+ const blocks = [];
67
+ for (const rel of candidates.slice(0, RECIPE_CONTEXT_FILE_CAP)) {
68
+ try {
69
+ const outline = compressCode(readFileSync(nodePath.join(projectRoot, rel), 'utf8')).compressed;
70
+ blocks.push(`## ${rel}\n${outline}`);
71
+ }
72
+ catch {
73
+ /* skip unreadable */
74
+ }
75
+ }
76
+ if (blocks.length === 0)
77
+ return '';
78
+ const more = candidates.length > blocks.length ? `\n\n(showing ${blocks.length} of ${candidates.length} in-scope files)` : '';
79
+ return `Files in scope you may edit (current contents):\n\n${blocks.join('\n\n')}${more}`;
80
+ }
81
+ function systemPrompt(recipe) {
82
+ const example = recipe.allowedOps.map(opExample).find((e) => e !== null);
83
+ const lines = [
84
+ 'You are a deterministic mechanical code-edit worker.',
85
+ 'Output ONLY a single JSON object matching the provided schema — no prose, no markdown fences.',
86
+ `You may emit ONLY operations of these kinds: ${recipe.allowedOps.join(', ')}.`,
87
+ `You may target ONLY files matching one of these globs: ${recipe.guardrailGlobs.join(', ')}.`,
88
+ 'Make the SMALLEST mechanical edit that satisfies the task. Never invent files, never change unrelated code, never reformat.',
89
+ 'Each op has a "targetPath" (relative to project root) and an "operation" with a "kind" and the fields that kind needs.',
90
+ ];
91
+ if (example) {
92
+ // A concrete few-shot anchor — weak local models reliably copy the SHAPE
93
+ // from an example even when they ignore a bare schema.
94
+ lines.push(`Example of a valid reply (copy the shape, not the values): ${JSON.stringify({ ops: [example] })}`);
95
+ }
96
+ return lines.join('\n');
97
+ }
98
+ /** Statuses worth re-prompting the worker for (the model can plausibly fix). */
99
+ const RETRYABLE_STATUSES = new Set([
100
+ 'generate-failed',
101
+ 'guardrail-refused',
102
+ 'package-error',
103
+ 'conflicts',
104
+ 'verify-failed',
105
+ ]);
106
+ /**
107
+ * The testable orchestration core: a bounded GENERATE→VERIFY retry loop. On a
108
+ * retryable failure (parse / guardrail / bad-op / conflict / verification) the
109
+ * worker is re-prompted with the failure injected, up to `recipe.maxAttempts`,
110
+ * then the run escalates. A provider / signing / environment failure is NOT
111
+ * retried. Takes an already-resolved recipe + provider so tests inject a fake.
112
+ */
113
+ export async function executeDelegateRun(input) {
114
+ // No local LLM → deterministic no-op (NOT an error).
115
+ if (input.provider === null) {
116
+ return {
117
+ status: 'no-provider',
118
+ recipeId: input.recipe.id,
119
+ message: 'No local LLM reachable — delegate is a no-op. Start Ollama / set LLAMACPP_MODEL_PATH to enable.',
120
+ };
121
+ }
122
+ const maxAttempts = Math.max(1, Math.min(5, input.recipe.maxAttempts ?? 2));
123
+ // In-scope file context for the LOCAL worker (free to the orchestrator).
124
+ const fileContext = gatherRecipeContext(input.projectRoot, input.recipe);
125
+ const baseMessages = [
126
+ { role: AiMessageRole.System, content: systemPrompt(input.recipe) },
127
+ { role: AiMessageRole.User, content: fileContext ? `Task: ${input.task}\n\n${fileContext}` : `Task: ${input.task}` },
128
+ ];
129
+ const feedback = [];
130
+ let last = {
131
+ status: 'generate-failed',
132
+ recipeId: input.recipe.id,
133
+ message: 'no attempt ran',
134
+ };
135
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
136
+ last = { ...(await runOneDelegateAttempt(input, [...baseMessages, ...feedback])), attempts: attempt };
137
+ if (!RETRYABLE_STATUSES.has(last.status) || attempt === maxAttempts)
138
+ return last;
139
+ feedback.push(buildRetryFeedback(last, input.recipe));
140
+ }
141
+ return last;
142
+ }
143
+ /** Build the User message that tells the worker why the previous attempt failed. */
144
+ function buildRetryFeedback(r, recipe) {
145
+ let detail;
146
+ if (r.conflicts && r.conflicts.length > 0) {
147
+ detail = `Your previous edit was REFUSED with conflicts: ${r.conflicts.join('; ')}. Fix the target paths / anchors and try again.`;
148
+ }
149
+ else if (r.status === 'verify-failed') {
150
+ detail = `Your previous edit FAILED verification (${r.verification?.commandsFailed.join(', ') || 'see logs'}) and was reverted. Produce a CORRECT edit.`;
151
+ }
152
+ else if (r.status === 'guardrail-refused') {
153
+ detail = `You targeted files outside the allowed scope (${(r.refused ?? []).join(', ')}). You may ONLY touch files matching: ${recipe.guardrailGlobs.join(', ')}.`;
154
+ }
155
+ else if (r.status === 'package-error') {
156
+ detail = `${r.message}. You may ONLY use op kinds: ${recipe.allowedOps.join(', ')}.`;
157
+ }
158
+ else {
159
+ detail = `Your previous reply was unusable: ${r.message}.`;
160
+ }
161
+ return {
162
+ role: AiMessageRole.User,
163
+ content: `${detail}\nReturn a corrected single JSON object matching the schema — no prose.`,
164
+ };
165
+ }
166
+ /** One generate→guardrail→package→sign→apply→verify pass. */
167
+ async function runOneDelegateAttempt(input, messages) {
168
+ const { recipe, projectRoot } = input;
169
+ const provider = input.provider; // non-null: the wrapper handled no-provider
170
+ // Generate (the only stochastic step). The plan secret is NEVER put in the
171
+ // messages — only the task + recipe constraints are.
172
+ const call = await callDelegateWithRetry({
173
+ provider,
174
+ messages,
175
+ ...(recipe.model ? { model: recipe.model } : {}),
176
+ timeoutMs: recipe.maxBudgetMs ?? DEFAULT_MAX_BUDGET_MS,
177
+ reprompt: (bad, error) => [...messages, delegateRepromptMessage(bad, error)],
178
+ });
179
+ if (!call.ok) {
180
+ return {
181
+ status: 'generate-failed',
182
+ recipeId: recipe.id,
183
+ message: `worker failed to produce a valid edit: ${call.error.message}`,
184
+ };
185
+ }
186
+ const edit = call.value.edit;
187
+ // 3. Guardrail globs — refuse any target outside the recipe's blast radius.
188
+ // The check MUST run on the NORMALIZED path that will actually be written
189
+ // (a `..` traversal whose `**` the glob swallows would otherwise pass the
190
+ // fence yet normalize to a file OUTSIDE the fenced dir but still in-root).
191
+ // So: reject any `..` segment, resolve through the engine's path floor,
192
+ // and glob-check the resulting relative path — the same string the write
193
+ // uses via `safeResolveTargetPath` in `evaluateSavedPlanInPlace`.
194
+ const normalizedTargets = [];
195
+ for (const op of edit.ops) {
196
+ if (containsTraversal(op.targetPath)) {
197
+ return {
198
+ status: 'guardrail-refused',
199
+ recipeId: recipe.id,
200
+ message: `worker target "${op.targetPath}" contains a \`..\` traversal segment`,
201
+ refused: [op.targetPath],
202
+ retried: call.value.retried,
203
+ };
204
+ }
205
+ try {
206
+ normalizedTargets.push(safeResolveTargetPath(op.targetPath, projectRoot).relativePath);
207
+ }
208
+ catch {
209
+ return {
210
+ status: 'guardrail-refused',
211
+ recipeId: recipe.id,
212
+ message: `worker target "${op.targetPath}" escapes the project root`,
213
+ refused: [op.targetPath],
214
+ retried: call.value.retried,
215
+ };
216
+ }
217
+ }
218
+ const guard = checkGuardrailGlobs(normalizedTargets, recipe.guardrailGlobs);
219
+ if (!guard.ok) {
220
+ return {
221
+ status: 'guardrail-refused',
222
+ recipeId: recipe.id,
223
+ message: `worker targeted ${guard.refused.length} file(s) outside the recipe's guardrail globs`,
224
+ refused: guard.refused,
225
+ retried: call.value.retried,
226
+ };
227
+ }
228
+ // 4. Package into a synthetic plan (drops disallowed ops, evaluates conflicts).
229
+ const packaged = packageDelegatePlan({
230
+ ops: edit.ops,
231
+ allowedOps: recipe.allowedOps,
232
+ recipeId: recipe.id,
233
+ projectRoot,
234
+ });
235
+ if (!packaged.ok) {
236
+ return {
237
+ status: 'package-error',
238
+ recipeId: recipe.id,
239
+ message: packaged.error.message,
240
+ retried: call.value.retried,
241
+ };
242
+ }
243
+ if (!packaged.value.ready || !packaged.value.plan) {
244
+ const conflicts = packaged.value.generation.changes
245
+ .filter((c) => String(c.type) === 'conflict')
246
+ .map((c) => `${c.relativePath}: ${c.reason}`);
247
+ return {
248
+ status: 'conflicts',
249
+ recipeId: recipe.id,
250
+ message: `edit evaluated to ${conflicts.length} conflict(s) — refused before any write`,
251
+ conflicts,
252
+ droppedOps: packaged.value.droppedOps,
253
+ retried: call.value.retried,
254
+ };
255
+ }
256
+ // 5. Sign + save the plan.
257
+ const signed = signPlan(packaged.value.plan, input.planSecret ? { secret: input.planSecret } : {});
258
+ if (!signed.ok) {
259
+ return {
260
+ status: 'sign-failed',
261
+ recipeId: recipe.id,
262
+ message: signed.error.message,
263
+ retried: call.value.retried,
264
+ };
265
+ }
266
+ const planPath = input.planPath ?? nodePath.join(projectRoot, '.sharkcraft', 'delegate', `${recipe.id}.plan.json`);
267
+ const saved = savePlanToFile(signed.value, planPath);
268
+ if (!saved.ok) {
269
+ return { status: 'apply-failed', recipeId: recipe.id, message: saved.error.message, planPath };
270
+ }
271
+ const baseResult = {
272
+ status: 'generated',
273
+ recipeId: recipe.id,
274
+ message: input.apply ? '' : `signed plan written to ${planPath} (not applied; review the diff, then \`shrk apply ${planPath} --verify-signature\` or re-run with --apply)`,
275
+ planPath,
276
+ ops: edit.ops.length,
277
+ droppedOps: packaged.value.droppedOps,
278
+ ...(call.value.usage ? { usage: call.value.usage } : {}),
279
+ retried: call.value.retried,
280
+ };
281
+ if (!input.apply) {
282
+ // Preview: show exactly what the worker WOULD write, so the agent can review
283
+ // before landing it (the plan is signed + saved but unapplied).
284
+ return { ...baseResult, ...(buildPreviewDiff(packaged.value.generation.changes, input.task) ?? {}) };
285
+ }
286
+ // A recipe with no verification has no deterministic gate — refuse to apply an
287
+ // unverified edit (runValidationLoop reports passed:true when no command runs,
288
+ // so this must be caught here). The plan is already signed + saved on disk.
289
+ if (recipe.verificationIds.length === 0) {
290
+ return {
291
+ ...baseResult,
292
+ status: 'no-verification',
293
+ message: `recipe "${recipe.id}" declares no verificationIds — refusing to apply an unverified edit (signed plan at ${planPath})`,
294
+ };
295
+ }
296
+ // 6. Apply through the same primitives `shrk apply` uses.
297
+ const verify = verifyPlan(signed.value, input.planSecret ? { secret: input.planSecret } : {});
298
+ if (!verify.ok) {
299
+ return { ...baseResult, status: 'apply-failed', message: `signature verification failed: ${verify.message}` };
300
+ }
301
+ const livePlan = evaluateSavedPlanInPlace(signed.value, projectRoot);
302
+ if (livePlan.hasConflicts) {
303
+ const conflicts = livePlan.changes.filter((c) => String(c.type) === 'conflict').map((c) => `${c.relativePath}: ${c.reason}`);
304
+ return { ...baseResult, status: 'conflicts', message: 'plan diverged at apply time', conflicts };
305
+ }
306
+ // Snapshot originals so a verify failure (or a partial-write failure) can be
307
+ // auto-reverted.
308
+ const snapshots = snapshotChanges(livePlan.changes);
309
+ const write = writeSyntheticPlan(livePlan);
310
+ if (!write.ok) {
311
+ // A mid-write failure can leave earlier files written — revert them.
312
+ revertSnapshots(snapshots);
313
+ return { ...baseResult, status: 'apply-failed', message: write.error.message, reverted: true };
314
+ }
315
+ const written = write.value.written.map((c) => c.relativePath);
316
+ // Compact result hand-back: a compressed unified diff of exactly what changed,
317
+ // so the orchestrator confirms the edit without re-reading the file.
318
+ const diffField = buildCompressedDiff(snapshots, write.value.written, input.task);
319
+ // 7. Deterministic verification gate.
320
+ const validation = await runValidationLoop({
321
+ cwd: projectRoot,
322
+ verificationIds: recipe.verificationIds,
323
+ allVerifications: false,
324
+ allowPackCommands: false,
325
+ reportDir: input.reportDir ?? nodePath.join(projectRoot, '.sharkcraft', 'delegate', 'reports'),
326
+ });
327
+ if (!validation.passed) {
328
+ revertSnapshots(snapshots);
329
+ return {
330
+ ...baseResult,
331
+ status: 'verify-failed',
332
+ message: `edit verification FAILED (${validation.commandsFailed.join(', ') || 'boundary violations'}) — auto-reverted`,
333
+ written,
334
+ reverted: true,
335
+ verification: { passed: false, commandsFailed: validation.commandsFailed },
336
+ ...(diffField ?? {}),
337
+ };
338
+ }
339
+ return {
340
+ ...baseResult,
341
+ status: 'applied',
342
+ message: `applied + verified (${written.length} file(s))`,
343
+ written,
344
+ verification: { passed: true, commandsFailed: [] },
345
+ ...(diffField ?? {}),
346
+ };
347
+ }
348
+ /** A compressed unified diff of the written changes (before = snapshot, after = contents). */
349
+ function compressedDiffOf(pairs, task) {
350
+ if (pairs.length === 0)
351
+ return null;
352
+ const bodies = pairs.map((p) => unifiedDiff(p.before, p.after, { relativePath: p.relativePath, maxLines: 60 }).body);
353
+ const compressed = compressDiff(bodies.join('\n'), { query: task });
354
+ return { diff: compressed.compressed, ...(compressed.ccrKey ? { diffCcrKey: compressed.ccrKey } : {}) };
355
+ }
356
+ /** Diff of the APPLIED edit: before = snapshot, after = written contents. */
357
+ function buildCompressedDiff(snapshots, written, task) {
358
+ const before = new Map(snapshots.map((s) => [s.absolutePath, s.original ?? '']));
359
+ return compressedDiffOf(written.map((c) => ({ relativePath: c.relativePath, before: before.get(c.absolutePath) ?? '', after: c.contents })), task);
360
+ }
361
+ /**
362
+ * PREVIEW diff for a `delegate run` without `--apply`: before = the file on disk
363
+ * now, after = the proposed contents from the evaluated plan. Lets the agent
364
+ * review exactly what the worker would write before deciding to land it.
365
+ */
366
+ function buildPreviewDiff(changes, task) {
367
+ const pairs = changes
368
+ .filter((c) => String(c.type) !== 'skip' && String(c.type) !== 'conflict')
369
+ .map((c) => ({
370
+ relativePath: c.relativePath,
371
+ before: existsSync(c.absolutePath) ? readFileSync(c.absolutePath, 'utf8') : '',
372
+ after: c.contents,
373
+ }));
374
+ return compressedDiffOf(pairs, task);
375
+ }
376
+ function snapshotChanges(changes) {
377
+ const out = [];
378
+ const seen = new Set();
379
+ for (const c of changes) {
380
+ if (c.type === 'skip' || c.type === 'conflict')
381
+ continue;
382
+ if (seen.has(c.absolutePath))
383
+ continue;
384
+ seen.add(c.absolutePath);
385
+ out.push({
386
+ absolutePath: c.absolutePath,
387
+ original: existsSync(c.absolutePath) ? readFileSync(c.absolutePath, 'utf8') : null,
388
+ });
389
+ }
390
+ return out;
391
+ }
392
+ function revertSnapshots(snapshots) {
393
+ for (const s of snapshots) {
394
+ try {
395
+ if (s.original === null) {
396
+ if (existsSync(s.absolutePath))
397
+ rmSync(s.absolutePath);
398
+ }
399
+ else {
400
+ writeFileSync(s.absolutePath, s.original, 'utf8');
401
+ }
402
+ }
403
+ catch {
404
+ /* best-effort revert; report still surfaces verify-failed */
405
+ }
406
+ }
407
+ }
408
+ // ─── recipe resolution ───────────────────────────────────────────────────────
409
+ /**
410
+ * Load the resolved delegate catalog: config recipes + pack-contributed recipes
411
+ * (best-effort) + `recipeOverrides`. Pack discovery failures degrade to
412
+ * config-only — a missing/odd node_modules never blocks a configured recipe.
413
+ */
414
+ async function loadResolvedCatalog(cwd) {
415
+ const loaded = await loadProjectConfig(cwd);
416
+ if (!loaded.ok)
417
+ return { ok: false, message: `could not load config: ${loaded.error.message}` };
418
+ const catalog = await resolveDelegateCatalogForProject(loaded.value.config, loaded.value.projectRoot);
419
+ return { ok: true, config: loaded.value.config, projectRoot: loaded.value.projectRoot, catalog };
420
+ }
421
+ async function resolveRecipe(cwd, recipeId) {
422
+ const c = await loadResolvedCatalog(cwd);
423
+ if (!c.ok)
424
+ return { ok: false, message: c.message };
425
+ const delegation = c.config.delegation;
426
+ if (!delegation || delegation.enabled === false) {
427
+ return { ok: false, message: 'delegation is not enabled in sharkcraft.config.ts' };
428
+ }
429
+ if (c.catalog.length === 0)
430
+ return { ok: false, message: 'no delegate recipes are configured' };
431
+ if (!recipeId) {
432
+ return { ok: false, message: `--recipe <id> is required. Available: ${c.catalog.map((r) => r.id).join(', ')}` };
433
+ }
434
+ const found = c.catalog.find((r) => r.id === recipeId);
435
+ if (!found) {
436
+ return { ok: false, message: `unknown recipe "${recipeId}". Available: ${c.catalog.map((r) => r.id).join(', ')}` };
437
+ }
438
+ // Fold the resolved provider/model onto the recipe for executeDelegateRun.
439
+ const recipe = {
440
+ ...found,
441
+ provider: found.resolvedProvider,
442
+ ...(found.resolvedModel ? { model: found.resolvedModel } : {}),
443
+ };
444
+ return { ok: true, recipe, projectRoot: c.projectRoot };
445
+ }
446
+ // ─── CLI surface ─────────────────────────────────────────────────────────────
447
+ async function runDelegateRun(args) {
448
+ const cwd = resolveCwd(args);
449
+ const wantJson = flagBool(args, 'json');
450
+ const task = args.positional.slice(1).join(' ').trim();
451
+ if (!task) {
452
+ process.stderr.write('Usage: shrk delegate run "<task>" --recipe <id> [--apply] [--provider auto] [--json]\n');
453
+ return 2;
454
+ }
455
+ const resolved = await resolveRecipe(cwd, flagString(args, 'recipe'));
456
+ if (!resolved.ok) {
457
+ if (wantJson)
458
+ process.stdout.write(asJson({ ok: false, error: resolved.message }) + '\n');
459
+ else
460
+ process.stderr.write(resolved.message + '\n');
461
+ return 1;
462
+ }
463
+ const providerKind = flagString(args, 'provider') ?? resolved.recipe.provider ?? 'auto';
464
+ const { provider } = selectAiProvider(providerKind);
465
+ const result = await executeDelegateRun({
466
+ task,
467
+ recipe: resolved.recipe,
468
+ projectRoot: resolved.projectRoot,
469
+ provider,
470
+ apply: flagBool(args, 'apply'),
471
+ });
472
+ if (wantJson) {
473
+ process.stdout.write(asJson({ ok: isOkStatus(result.status), ...result }) + '\n');
474
+ return exitFor(result.status);
475
+ }
476
+ process.stdout.write(header(`Delegate: ${result.recipeId}`));
477
+ process.stdout.write(kv('status', result.status) + '\n');
478
+ if (result.attempts && result.attempts > 1)
479
+ process.stdout.write(kv('attempts', String(result.attempts)) + '\n');
480
+ process.stdout.write(kv('message', result.message) + '\n');
481
+ if (result.refused && result.refused.length > 0) {
482
+ process.stdout.write('\nRefused (outside guardrail globs):\n');
483
+ for (const f of result.refused)
484
+ process.stdout.write(` ✗ ${f}\n`);
485
+ }
486
+ if (result.conflicts && result.conflicts.length > 0) {
487
+ process.stdout.write('\nConflicts:\n');
488
+ for (const c of result.conflicts)
489
+ process.stdout.write(` ! ${c}\n`);
490
+ }
491
+ if (result.droppedOps && result.droppedOps.length > 0) {
492
+ process.stdout.write('\nDropped ops (kind not allowed):\n');
493
+ for (const d of result.droppedOps)
494
+ process.stdout.write(` - ${d.kind} → ${d.targetPath}\n`);
495
+ }
496
+ if (result.written && result.written.length > 0) {
497
+ process.stdout.write(`\n${result.reverted ? 'Reverted' : 'Wrote'} ${result.written.length} file(s):\n`);
498
+ for (const w of result.written)
499
+ process.stdout.write(` ${result.reverted ? '↺' : '✓'} ${w}\n`);
500
+ }
501
+ if (result.diff) {
502
+ process.stdout.write(`\nDiff:\n${result.diff}\n`);
503
+ if (result.diffCcrKey)
504
+ process.stdout.write(`(compressed — recover with \`shrk expand ${result.diffCcrKey}\`)\n`);
505
+ }
506
+ return exitFor(result.status);
507
+ }
508
+ async function runDelegateBrief(args) {
509
+ const cwd = resolveCwd(args);
510
+ const wantJson = flagBool(args, 'json');
511
+ const task = args.positional.slice(1).join(' ').trim();
512
+ const resolved = await resolveRecipe(cwd, flagString(args, 'recipe'));
513
+ if (!resolved.ok) {
514
+ if (wantJson)
515
+ process.stdout.write(asJson({ ok: false, error: resolved.message }) + '\n');
516
+ else
517
+ process.stderr.write(resolved.message + '\n');
518
+ return 1;
519
+ }
520
+ const r = resolved.recipe;
521
+ const brief = {
522
+ schema: 'sharkcraft.delegate-brief/v1',
523
+ recipeId: r.id,
524
+ title: r.title ?? r.id,
525
+ task: task || null,
526
+ allowedOps: r.allowedOps,
527
+ guardrailGlobs: r.guardrailGlobs,
528
+ verificationIds: r.verificationIds,
529
+ provider: r.provider ?? 'auto',
530
+ model: r.model ?? null,
531
+ next: `shrk delegate run "${task || '<task>'}" --recipe ${r.id} --apply`,
532
+ note: 'Read-only. The worker may only emit the allowed ops, only touch the guardrail globs, and the edit is verified deterministically before it is kept.',
533
+ };
534
+ if (wantJson) {
535
+ process.stdout.write(asJson(brief) + '\n');
536
+ return 0;
537
+ }
538
+ process.stdout.write(header(`Delegate brief: ${brief.title}`));
539
+ process.stdout.write(kv('recipe', r.id) + '\n');
540
+ if (task)
541
+ process.stdout.write(kv('task', task) + '\n');
542
+ process.stdout.write(kv('allowed ops', r.allowedOps.join(', ')) + '\n');
543
+ process.stdout.write(kv('guardrail globs', r.guardrailGlobs.join(', ')) + '\n');
544
+ process.stdout.write(kv('verification', r.verificationIds.join(', ') || '(none)') + '\n');
545
+ process.stdout.write(kv('provider', `${brief.provider}${r.model ? ` (${r.model})` : ''}`) + '\n');
546
+ process.stdout.write(`\nNext:\n ${brief.next}\n`);
547
+ return 0;
548
+ }
549
+ async function runDelegateList(args) {
550
+ const cwd = resolveCwd(args);
551
+ const wantJson = flagBool(args, 'json');
552
+ const c = await loadResolvedCatalog(cwd);
553
+ if (!c.ok) {
554
+ if (wantJson)
555
+ process.stdout.write(asJson({ ok: false, error: c.message }) + '\n');
556
+ else
557
+ process.stderr.write(c.message + '\n');
558
+ return 1;
559
+ }
560
+ const catalog = c.catalog;
561
+ if (wantJson) {
562
+ process.stdout.write(asJson({ ok: true, total: catalog.length, recipes: catalog }) + '\n');
563
+ return 0;
564
+ }
565
+ process.stdout.write(header('Delegate recipes'));
566
+ if (catalog.length === 0) {
567
+ process.stdout.write(' (none configured — add a delegation { recipes: [...] } block to sharkcraft.config.ts)\n');
568
+ return 0;
569
+ }
570
+ for (const r of catalog) {
571
+ const src = r.source === 'pack' ? ` [pack: ${r.packageName}]` : '';
572
+ process.stdout.write(` ${r.delegatable ? '✓' : '✗'} ${r.id} — ${r.title ?? r.id}${src}\n`);
573
+ process.stdout.write(` ops: ${r.allowedOps.join(', ')} | globs: ${r.guardrailGlobs.join(', ')} | verify: ${r.verificationIds.join(', ') || '(none)'}\n`);
574
+ if (!r.delegatable) {
575
+ process.stdout.write(` ⚠ NOT delegatable — ${r.unboundVerificationIds.length > 0 ? `unbound verificationIds: ${r.unboundVerificationIds.join(', ')}` : 'no verificationIds declared'}\n`);
576
+ }
577
+ }
578
+ process.stdout.write(`\nRun \`shrk delegate explain <id>\` for the full fence.\n`);
579
+ return 0;
580
+ }
581
+ async function runDelegateExplain(args) {
582
+ const cwd = resolveCwd(args);
583
+ const wantJson = flagBool(args, 'json');
584
+ const recipeId = args.positional[1];
585
+ if (!recipeId) {
586
+ process.stderr.write('Usage: shrk delegate explain <recipeId>\n');
587
+ return 2;
588
+ }
589
+ const c = await loadResolvedCatalog(cwd);
590
+ if (!c.ok) {
591
+ if (wantJson)
592
+ process.stdout.write(asJson({ ok: false, error: c.message }) + '\n');
593
+ else
594
+ process.stderr.write(c.message + '\n');
595
+ return 1;
596
+ }
597
+ const r = c.catalog.find((x) => x.id === recipeId);
598
+ if (!r) {
599
+ if (wantJson)
600
+ process.stdout.write(asJson({ ok: false, error: `unknown recipe "${recipeId}"` }) + '\n');
601
+ else
602
+ process.stderr.write(`unknown recipe "${recipeId}"\n`);
603
+ return 1;
604
+ }
605
+ const known = new Set((c.config.verificationCommands ?? []).map((v) => v.id));
606
+ const verifications = r.verificationIds.map((id) => ({ id, bound: known.has(id) }));
607
+ if (wantJson) {
608
+ process.stdout.write(asJson({ ok: true, recipe: r, verifications }) + '\n');
609
+ return r.delegatable ? 0 : 1;
610
+ }
611
+ process.stdout.write(header(`Delegate recipe: ${r.id}`));
612
+ process.stdout.write(kv('title', r.title ?? r.id) + '\n');
613
+ process.stdout.write(kv('source', r.source === 'pack' ? `pack: ${r.packageName}` : 'config') + '\n');
614
+ process.stdout.write(kv('delegatable', r.delegatable ? 'yes' : 'no — fix the verification binding first') + '\n');
615
+ process.stdout.write(kv('allowed ops', r.allowedOps.join(', ')) + '\n');
616
+ process.stdout.write(kv('guardrail globs', r.guardrailGlobs.join(', ')) + '\n');
617
+ process.stdout.write(kv('provider', `${r.resolvedProvider}${r.resolvedModel ? ` (${r.resolvedModel})` : ''}`) + '\n');
618
+ process.stdout.write(kv('risk ceiling', r.riskCeiling ?? '(none)') + '\n');
619
+ process.stdout.write(kv('max attempts', String(r.maxAttempts ?? 2)) + '\n');
620
+ process.stdout.write('\nVerification (must pass or the edit is reverted):\n');
621
+ if (verifications.length === 0) {
622
+ process.stdout.write(' ⚠ none declared — the edit would apply UNVERIFIED (refused at apply-time)\n');
623
+ }
624
+ for (const v of verifications) {
625
+ process.stdout.write(` ${v.bound ? '✓' : '✗'} ${v.id}${v.bound ? '' : ' (NOT in verificationCommands[] — would un-gate the edit)'}\n`);
626
+ }
627
+ process.stdout.write('\nThe worker may emit ONLY the allowed ops and touch ONLY the guardrail globs;\nthe edit is verified deterministically and auto-reverted on failure.\n');
628
+ return 0;
629
+ }
630
+ function isOkStatus(s) {
631
+ return s === 'applied' || s === 'generated' || s === 'no-provider';
632
+ }
633
+ function exitFor(s) {
634
+ return isOkStatus(s) ? 0 : 1;
635
+ }
636
+ export const delegateCommand = {
637
+ name: 'delegate',
638
+ description: 'Hand a mechanical, deterministically-verifiable edit to a local-LLM worker. The engine verifies the result (config verificationCommands) and auto-reverts on failure — a bad generation costs a retry, never a wrong write. Local-only.',
639
+ usage: 'shrk delegate run "<task>" --recipe <id> [--apply] [--provider auto|ollama|llamacpp] [--json]\n' +
640
+ 'shrk delegate brief "<task>" --recipe <id> [--json]\n' +
641
+ 'shrk delegate list [--json] — recipes + whether each is safely delegatable\n' +
642
+ 'shrk delegate explain <id> [--json] — the full fence for one recipe',
643
+ booleanFlags: new Set(['apply', 'json']),
644
+ async run(args) {
645
+ const sub = args.positional[0];
646
+ if (sub === 'run')
647
+ return runDelegateRun(args);
648
+ if (sub === 'brief')
649
+ return runDelegateBrief(args);
650
+ if (sub === 'list')
651
+ return runDelegateList(args);
652
+ if (sub === 'explain')
653
+ return runDelegateExplain(args);
654
+ process.stderr.write('Usage: shrk delegate run|brief|list|explain ...\n');
655
+ return 2;
656
+ },
657
+ };
@@ -0,0 +1,23 @@
1
+ import { type ICommandHandler } from '../command-registry.js';
2
+ /**
3
+ * `shrk deps-audit` — for each workspace package, compare the
4
+ * `package.json` `dependencies` / `devDependencies` / `peerDependencies`
5
+ * against the *specifiers actually imported* from source under
6
+ * `<pkg>/src/` (per the SharkCraft graph).
7
+ *
8
+ * Reports:
9
+ * - missing deps: imported but not declared (likely build failure
10
+ * in the wild — the package depends on its host's resolution)
11
+ * - unused deps: declared but never imported (lint waste)
12
+ *
13
+ * Read-only. JSON output via `--json`. Optionally restricted to one
14
+ * package via `--package <name>`.
15
+ *
16
+ * Known limitations:
17
+ * - Type-only imports (`import type x from 'y'`) still count; the
18
+ * graph can't tell them apart in v3.
19
+ * - Subpath imports (`pkg/sub`) are reduced to their root specifier.
20
+ * - Built-in node modules (`node:fs`, `fs`, …) are ignored.
21
+ */
22
+ export declare const depsAuditCommand: ICommandHandler;
23
+ //# sourceMappingURL=deps-audit.command.d.ts.map