@shrkcrft/cli 0.1.0-alpha.12 → 0.1.0-alpha.13

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 (50) 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/commands/ai-status.command.d.ts +19 -0
  32. package/dist/commands/ai-status.command.d.ts.map +1 -0
  33. package/dist/commands/ai-status.command.js +94 -0
  34. package/dist/commands/command-catalog.d.ts.map +1 -1
  35. package/dist/commands/command-catalog.js +10 -0
  36. package/dist/commands/doctor.command.d.ts.map +1 -1
  37. package/dist/commands/doctor.command.js +40 -2
  38. package/dist/commands/smart-context.command.d.ts +28 -0
  39. package/dist/commands/smart-context.command.d.ts.map +1 -1
  40. package/dist/commands/smart-context.command.js +762 -1
  41. package/dist/commands/surface.command.d.ts +1 -0
  42. package/dist/commands/surface.command.d.ts.map +1 -1
  43. package/dist/commands/surface.command.js +10 -3
  44. package/dist/commands/template-quality.command.d.ts.map +1 -1
  45. package/dist/commands/template-quality.command.js +39 -3
  46. package/dist/commands/templates.command.d.ts.map +1 -1
  47. package/dist/commands/templates.command.js +37 -2
  48. package/dist/main.d.ts.map +1 -1
  49. package/dist/main.js +40 -18
  50. package/package.json +32 -32
@@ -10,6 +10,7 @@ import { type ICommandHandler } from '../command-registry.js';
10
10
  * - unhide <command> reverse hide
11
11
  * - reset clear surface.enabled + surface.hidden
12
12
  * - explain <command> why this command has its current tier
13
+ * - profiles [get <id>] list surface profiles (or show one)
13
14
  */
14
15
  export declare const surfaceCommand: ICommandHandler;
15
16
  //# sourceMappingURL=surface.command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"surface.command.d.ts","sourceRoot":"","sources":["../../src/commands/surface.command.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiBhC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,EAAE,eAmC5B,CAAC"}
1
+ {"version":3,"file":"surface.command.d.ts","sourceRoot":"","sources":["../../src/commands/surface.command.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiBhC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,EAAE,eAmC5B,CAAC"}
@@ -15,11 +15,12 @@ import { applySurfaceEdit, defaultConfigFile, planSurfaceEdit, } from "../surfac
15
15
  * - unhide <command> reverse hide
16
16
  * - reset clear surface.enabled + surface.hidden
17
17
  * - explain <command> why this command has its current tier
18
+ * - profiles [get <id>] list surface profiles (or show one)
18
19
  */
19
20
  export const surfaceCommand = {
20
21
  name: 'surface',
21
22
  description: 'Inspect or change the adaptive command surface (core / extended / experimental tiers).',
22
- usage: 'shrk surface <list|enable|disable|hide|unhide|reset|explain> [name] [--write] [--json]',
23
+ usage: 'shrk surface <list|enable|disable|hide|unhide|reset|explain|profiles> [name] [--write] [--json]',
23
24
  async run(args) {
24
25
  const [verb, ...rest] = args.positional;
25
26
  const json = flagBool(args, 'json');
@@ -84,7 +85,7 @@ async function runProfiles(opts) {
84
85
  const found = availableProfiles.find((p) => p.id === opts.target);
85
86
  if (!found) {
86
87
  process.stderr.write(`Unknown profile: ${opts.target}\n`);
87
- return 1;
88
+ return 2;
88
89
  }
89
90
  if (opts.json) {
90
91
  process.stdout.write(asJson(found) + '\n');
@@ -171,10 +172,16 @@ async function runExplain({ cwd, json, target }) {
171
172
  process.stderr.write('Usage: shrk surface explain <command>\n');
172
173
  return 2;
173
174
  }
174
- const { context, activeProfile } = await loadSurfaceContext({ cwd });
175
+ const { context, activeProfile, availableProfiles } = await loadSurfaceContext({ cwd });
175
176
  const summary = buildSurfaceSummary(context);
176
177
  const view = findCommandInSummary(summary, target);
177
178
  if (!view) {
179
+ // A common mix-up: `surface explain <profile>`. `explain` is for commands;
180
+ // point the user at the profile-aware verb instead of a bare "unknown".
181
+ if (availableProfiles.some((p) => p.id === target)) {
182
+ process.stderr.write(`'${target}' is a surface profile, not a command. Try: shrk surface profiles get ${target}\n`);
183
+ return 2;
184
+ }
178
185
  process.stderr.write(`Unknown command: ${target}\n`);
179
186
  return 2;
180
187
  }
@@ -1 +1 @@
1
- {"version":3,"file":"template-quality.command.d.ts","sourceRoot":"","sources":["../../src/commands/template-quality.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC,eAAO,MAAM,oBAAoB,EAAE,eAkClC,CAAC;AAqDF,eAAO,MAAM,oBAAoB,EAAE,eAmBlC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eAuBtC,CAAC"}
1
+ {"version":3,"file":"template-quality.command.d.ts","sourceRoot":"","sources":["../../src/commands/template-quality.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAQhC,eAAO,MAAM,oBAAoB,EAAE,eAsDlC,CAAC;AA4EF,eAAO,MAAM,oBAAoB,EAAE,eAmBlC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eAuBtC,CAAC"}
@@ -3,18 +3,32 @@ import * as nodePath from 'node:path';
3
3
  import { inspectSharkcraft, lintTemplates, testTemplates, } from '@shrkcrft/inspector';
4
4
  import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
5
5
  import { asJson, header } from "../output/format-output.js";
6
+ import { enrichWithLlmRecommendations, renderRecommendationsMarkdown, } from '@shrkcrft/ai';
6
7
  export const templatesLintCommand = {
7
8
  name: 'lint',
8
- description: 'Lint registered templates (titles, vars, target safety, placeholders). --fix-preview emits a TODO patch per finding under .sharkcraft/fixes/templates-lint/ (preview only — never mutates source).',
9
- usage: 'shrk templates lint [<id>] [--fix-preview] [--json]',
9
+ description: 'Lint registered templates (titles, vars, target safety, placeholders). --fix-preview emits a TODO patch per finding under .sharkcraft/fixes/templates-lint/ (preview only — never mutates source). --llm-recommendations layers concrete next-steps onto the deterministic findings when a local LLM is reachable.',
10
+ usage: 'shrk templates lint [<id>] [--fix-preview] [--json] [--llm-recommendations] [--provider auto|ollama|llamacpp]',
10
11
  async run(args) {
11
12
  const cwd = resolveCwd(args);
12
13
  const inspection = await inspectSharkcraft({ cwd });
13
14
  const ids = args.positional.length > 0 ? args.positional : undefined;
14
15
  const report = lintTemplates(inspection, ids);
15
16
  const fixPreview = flagBool(args, 'fix-preview');
17
+ const wantLlm = flagBool(args, 'llm-recommendations');
18
+ const llmEnvelope = wantLlm
19
+ ? await enrichWithLlmRecommendations({
20
+ surface: 'templates-lint',
21
+ deterministicSummary: summariseLintResults(report.results),
22
+ providerKind: flagString(args, 'provider') ?? undefined,
23
+ ask: 'For each non-passing template, suggest ONE concrete edit in sharkcraft/templates.ts: name the field (description, variables[i].examples, related[i], etc.) and the literal value or removal. Skip templates with only `info`-level issues unless a clear improvement exists.',
24
+ maxTokens: 1024,
25
+ })
26
+ : null;
16
27
  if (flagBool(args, 'json')) {
17
- process.stdout.write(asJson(report) + '\n');
28
+ process.stdout.write(asJson({
29
+ ...report,
30
+ ...(llmEnvelope ? { llmRecommendations: llmEnvelope } : {}),
31
+ }) + '\n');
18
32
  if (fixPreview)
19
33
  writeFixPreviewPatches(cwd, report.results);
20
34
  return report.summary.errors > 0 ? 1 : 0;
@@ -35,9 +49,31 @@ export const templatesLintCommand = {
35
49
  process.stdout.write('\nNo fix-preview patches needed.\n');
36
50
  }
37
51
  }
52
+ if (llmEnvelope) {
53
+ process.stdout.write('\n');
54
+ process.stdout.write(renderRecommendationsMarkdown(llmEnvelope));
55
+ }
38
56
  return report.summary.errors > 0 ? 1 : 0;
39
57
  },
40
58
  };
59
+ function summariseLintResults(results) {
60
+ const lines = [];
61
+ for (const r of results) {
62
+ lines.push(`## ${r.passed ? 'OK' : 'FAIL'} ${r.templateId}`);
63
+ if (r.issues.length === 0) {
64
+ lines.push('(no issues)');
65
+ }
66
+ else {
67
+ for (const i of r.issues) {
68
+ lines.push(`- ${i.severity} \`${i.code}\` — ${i.message}`);
69
+ }
70
+ }
71
+ lines.push('');
72
+ }
73
+ if (lines.length === 0)
74
+ lines.push('(no templates registered)');
75
+ return lines.join('\n');
76
+ }
41
77
  /** Write a per-template TODO patch under `.sharkcraft/fixes/templates-lint/`. */
42
78
  function writeFixPreviewPatches(cwd, results) {
43
79
  const dir = nodePath.join(cwd, '.sharkcraft', 'fixes', 'templates-lint');
@@ -1 +1 @@
1
- {"version":3,"file":"templates.command.d.ts","sourceRoot":"","sources":["../../src/commands/templates.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAKhC,eAAO,MAAM,oBAAoB,EAAE,eA6DlC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAmBlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eA+CjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAgBpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiDrC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAWnC,CAAC;AAyKF,eAAO,MAAM,2BAA2B,EAAE,eA+BzC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAOnC,CAAC;AA2GF,eAAO,MAAM,wBAAwB,EAAE,eA+FtC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,eAKjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,eAyCpC,CAAC;AAwHF,eAAO,MAAM,sBAAsB,EAAE,eAiKpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAkEpC,CAAC"}
1
+ {"version":3,"file":"templates.command.d.ts","sourceRoot":"","sources":["../../src/commands/templates.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAUhC,eAAO,MAAM,oBAAoB,EAAE,eA6DlC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAmBlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eA+CjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAgBpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiDrC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAWnC,CAAC;AAgNF,eAAO,MAAM,2BAA2B,EAAE,eA+BzC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAOnC,CAAC;AA2GF,eAAO,MAAM,wBAAwB,EAAE,eA+FtC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,eAKjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,eAyCpC,CAAC;AAwHF,eAAO,MAAM,sBAAsB,EAAE,eAiKpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAkEpC,CAAC"}
@@ -9,6 +9,7 @@ import { flagBool, flagList, flagString, flagVars, resolveCwd, } from "../comman
9
9
  import { asJson, header, kv } from "../output/format-output.js";
10
10
  import { maybeRunInWatchMode } from "../output/watch-loop.js";
11
11
  import { renderFailureHints, templateDriftHints } from "../output/failure-hints.js";
12
+ import { enrichWithLlmRecommendations, renderRecommendationsMarkdown, } from '@shrkcrft/ai';
12
13
  export const templatesVarsCommand = {
13
14
  name: 'vars',
14
15
  description: 'Show the variables a template accepts (required/optional/defaults/examples).',
@@ -209,8 +210,8 @@ export const templatesPreviewCommand = {
209
210
  };
210
211
  export const templatesDriftCommand = {
211
212
  name: 'drift',
212
- description: 'Verify every registered template against the workspace. Severity controls and `--watch [--once] [--debounce N]` supported. Read-only.',
213
- usage: 'shrk templates drift [--template <id>] [--pack <packId>] [--var key=value ...] [--min-severity error|warning|info] [--hide <code>[,<code>...]] [--strict] [--ci] [--format text|markdown|html|json] [--report] [--output <path>] [--json] [--watch [--once] [--debounce N]]',
213
+ description: 'Verify every registered template against the workspace. Severity controls and `--watch [--once] [--debounce N]` supported. `--llm-recommendations` layers a local-LLM-derived list of concrete next-steps on top of the deterministic findings (no-op when no provider reachable). Read-only.',
214
+ usage: 'shrk templates drift [--template <id>] [--pack <packId>] [--var key=value ...] [--min-severity error|warning|info] [--hide <code>[,<code>...]] [--strict] [--ci] [--format text|markdown|html|json] [--report] [--output <path>] [--json] [--llm-recommendations] [--provider auto|ollama|llamacpp] [--watch [--once] [--debounce N]]',
214
215
  async run(args) {
215
216
  const watchExit = await maybeRunInWatchMode(args, templatesDriftImpl);
216
217
  if (watchExit !== null)
@@ -272,6 +273,16 @@ async function templatesDriftImpl(args) {
272
273
  else
273
274
  filteredFail++;
274
275
  }
276
+ const wantLlmRecs = flagBool(args, 'llm-recommendations');
277
+ const llmEnvelope = wantLlmRecs
278
+ ? await enrichWithLlmRecommendations({
279
+ surface: 'templates-drift',
280
+ deterministicSummary: summariseDriftEntries(filteredEntries),
281
+ providerKind: flagString(args, 'provider') ?? undefined,
282
+ ask: 'For each FAIL or WARN entry, propose ONE concrete fix the maintainer can apply — name the specific field in `sharkcraft/templates.ts` (or a peer file) to edit. Skip PASS entries unless something is genuinely worth nudging.',
283
+ maxTokens: 1024,
284
+ })
285
+ : null;
275
286
  const ciPayload = {
276
287
  ...report,
277
288
  entries: filteredEntries,
@@ -284,6 +295,7 @@ async function templatesDriftImpl(args) {
284
295
  minSeverity: minSeverityRaw || 'info',
285
296
  hideCodes: [...hideCodes],
286
297
  },
298
+ ...(llmEnvelope ? { llmRecommendations: llmEnvelope } : {}),
287
299
  };
288
300
  const exitNonZero = ci
289
301
  ? filteredFail > 0 || (strict && filteredWarn > 0)
@@ -330,8 +342,31 @@ async function templatesDriftImpl(args) {
330
342
  if (exitNonZero) {
331
343
  process.stdout.write(renderFailureHints(templateDriftHints()));
332
344
  }
345
+ if (llmEnvelope) {
346
+ process.stdout.write('\n');
347
+ process.stdout.write(renderRecommendationsMarkdown(llmEnvelope));
348
+ }
333
349
  return exitNonZero ? 1 : 0;
334
350
  }
351
+ function summariseDriftEntries(entries) {
352
+ const lines = [];
353
+ for (const e of entries) {
354
+ const tag = e.status === TemplateDriftStatus.Pass ? 'PASS' : e.status === TemplateDriftStatus.Warn ? 'WARN' : 'FAIL';
355
+ lines.push(`## [${tag}] ${e.templateId}${e.templateName ? ` — ${e.templateName}` : ''}`);
356
+ if (e.issues.length === 0) {
357
+ lines.push('(no issues)');
358
+ }
359
+ else {
360
+ for (const i of e.issues) {
361
+ lines.push(`- ${i.severity} \`${i.code}\` — ${i.message}${i.suggestedFix ? ` (suggested: ${i.suggestedFix})` : ''}`);
362
+ }
363
+ }
364
+ lines.push('');
365
+ }
366
+ if (lines.length === 0)
367
+ lines.push('(no templates in this drift report)');
368
+ return lines.join('\n');
369
+ }
335
370
  function renderDriftMarkdown(p) {
336
371
  const out = [];
337
372
  out.push('# Template drift');
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAIhB,MAAM,uBAAuB,CAAC;AAgX/B,wBAAgB,aAAa,IAAI,eAAe,CAmX/C;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BrE;AAkGD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA4CxE"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAIhB,MAAM,uBAAuB,CAAC;AAoX/B,wBAAgB,aAAa,IAAI,eAAe,CAuX/C;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BrE;AAkGD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA4CxE"}
package/dist/main.js CHANGED
@@ -56,7 +56,8 @@ import { dashboardCommand } from "./commands/dashboard.command.js";
56
56
  import { dashboardDiffCommand, dashboardExportCommand, } from "./commands/dashboard-export.command.js";
57
57
  import { importCommand } from "./commands/import.command.js";
58
58
  import { askCommand } from "./commands/ask.command.js";
59
- import { smartContextCommand, smartContextEmbeddingsBuildCommand, smartContextEmbeddingsStatusCommand, smartContextListCommand, smartContextPlanAheadCommand, smartContextShowCommand, } from "./commands/smart-context.command.js";
59
+ import { aiStatusCommand } from "./commands/ai-status.command.js";
60
+ import { smartContextAuditKnowledgeCommand, smartContextAuditPipelinesCommand, smartContextAuditTemplatesCommand, smartContextCommand, smartContextEmbeddingsBuildCommand, smartContextEmbeddingsStatusCommand, smartContextListCommand, smartContextPlanAheadCommand, smartContextShowCommand, } from "./commands/smart-context.command.js";
60
61
  import { spikeCommand } from "./commands/spike.command.js";
61
62
  import { depsAuditCommand } from "./commands/deps-audit.command.js";
62
63
  import { scaffoldValidateCommand } from "./commands/scaffold-validate.command.js";
@@ -142,6 +143,7 @@ export function buildRegistry() {
142
143
  registry.register(initCommand);
143
144
  registry.register(inspectCommand);
144
145
  registry.register(doctorCommand);
146
+ registry.register(aiStatusCommand);
145
147
  registry.registerSubcommand('doctor', doctorSuppressCommand);
146
148
  registry.registerSubcommand('doctor', doctorSuppressionsCommand);
147
149
  // Acknowledgements with required reason + expiry.
@@ -217,6 +219,9 @@ export function buildRegistry() {
217
219
  registry.registerSubcommand('smart-context', smartContextShowCommand);
218
220
  registry.registerSubcommand('smart-context', smartContextEmbeddingsBuildCommand);
219
221
  registry.registerSubcommand('smart-context', smartContextEmbeddingsStatusCommand);
222
+ registry.registerSubcommand('smart-context', smartContextAuditTemplatesCommand);
223
+ registry.registerSubcommand('smart-context', smartContextAuditKnowledgeCommand);
224
+ registry.registerSubcommand('smart-context', smartContextAuditPipelinesCommand);
220
225
  registry.register(spikeCommand);
221
226
  registry.register(depsAuditCommand);
222
227
  registry.register(scaffoldValidateCommand);
@@ -864,24 +869,41 @@ if (isMain ||
864
869
  catch {
865
870
  // ignore flush failures
866
871
  }
867
- // Prefer `_exit` over `exit` on Node. Even after our explicit
868
- // disposes above, node-llama-cpp's libggml-metal destructor
869
- // still aborts in `__cxa_finalize_ranges` because the Metal
870
- // device list isn't drained by the current dispose API
871
- // (`GGML_ASSERT([rsets->data count] == 0)` fires from
872
- // `ggml_metal_device_free`, surfacing as `zsh: abort` AFTER
873
- // the user's result has already printed). `process._exit`
874
- // skips the libc++ static-destructor pass entirely, which is
875
- // safe here because we have already torn down the runtimes
876
- // we care about above and the OS will reclaim the rest.
872
+ // Prefer a low-level exit over `process.exit` on Node. Without
873
+ // this, libc++ static destructors run during `process.exit`, and
874
+ // native bindings still resident in memory abort with libc++abi
875
+ // errors AFTER the user's result has already printed:
876
+ // - node-llama-cpp's libggml-metal hits `GGML_ASSERT([rsets->data
877
+ // count] == 0)` in `ggml_metal_device_free` `zsh: abort`.
878
+ // - onnxruntime-node's worker pool aborts with
879
+ // `libc++abi: mutex lock failed: Invalid argument` after a
880
+ // successful `shrk smart-context embeddings-build`. NOTE:
881
+ // `pipeline.dispose()` returns cleanly, but ONNX worker threads
882
+ // are not actually joined — they continue running briefly and
883
+ // hit the pthread mutex teardown race. There is no JS-layer
884
+ // fix for this; upstream onnxruntime-node 1.21 has the bug.
885
+ // The low-level exit at least suppresses the destructor pass
886
+ // so the failure mode is "noisy stderr, exit code preserved"
887
+ // rather than "destructor cascade".
877
888
  //
878
- // Bun's process object doesn't expose `_exit`, but Bun also
879
- // doesn't drive shutdown through libuv + libc++ static
880
- // destructors the same way Node does, so the Metal crash
881
- // doesn't reproduce there. Fall back to `process.exit` when
882
- // `_exit` is unavailable.
883
- const lowLevelExit = process._exit;
884
- if (typeof lowLevelExit === 'function') {
889
+ // Node exposes the low-level exit under two names depending on the
890
+ // version:
891
+ // - `process._exit` — public alias (older Node; removed from
892
+ // the public surface on Node 22+).
893
+ // - `process.reallyExit` Node-internal name; still present on
894
+ // Node 22 even when `_exit` is undefined.
895
+ // Try both in order. Bun's process object doesn't expose either,
896
+ // but Bun also doesn't drive shutdown through libuv + libc++ static
897
+ // destructors the same way Node does, so the crash doesn't
898
+ // reproduce there. Fall back to `process.exit` when no low-level
899
+ // hook is available.
900
+ const proc = process;
901
+ const lowLevelExit = typeof proc._exit === 'function'
902
+ ? proc._exit
903
+ : typeof proc.reallyExit === 'function'
904
+ ? proc.reallyExit
905
+ : null;
906
+ if (lowLevelExit !== null) {
885
907
  lowLevelExit(code);
886
908
  }
887
909
  process.exit(code);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/cli",
3
- "version": "0.1.0-alpha.12",
3
+ "version": "0.1.0-alpha.13",
4
4
  "description": "SharkCraft CLI (`shrk`): structured project intelligence for AI coding agents.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -47,37 +47,37 @@
47
47
  "typecheck": "tsc --noEmit -p tsconfig.json"
48
48
  },
49
49
  "dependencies": {
50
- "@shrkcrft/core": "^0.1.0-alpha.12",
51
- "@shrkcrft/config": "^0.1.0-alpha.12",
52
- "@shrkcrft/workspace": "^0.1.0-alpha.12",
53
- "@shrkcrft/knowledge": "^0.1.0-alpha.12",
54
- "@shrkcrft/context": "^0.1.0-alpha.12",
55
- "@shrkcrft/rules": "^0.1.0-alpha.12",
56
- "@shrkcrft/paths": "^0.1.0-alpha.12",
57
- "@shrkcrft/templates": "^0.1.0-alpha.12",
58
- "@shrkcrft/plugin-api": "^0.1.0-alpha.12",
59
- "@shrkcrft/dashboard": "^0.1.0-alpha.12",
60
- "@shrkcrft/dashboard-api": "^0.1.0-alpha.12",
61
- "@shrkcrft/pipelines": "^0.1.0-alpha.12",
62
- "@shrkcrft/presets": "^0.1.0-alpha.12",
63
- "@shrkcrft/boundaries": "^0.1.0-alpha.12",
64
- "@shrkcrft/graph": "^0.1.0-alpha.12",
65
- "@shrkcrft/rule-graph": "^0.1.0-alpha.12",
66
- "@shrkcrft/structural-search": "^0.1.0-alpha.12",
67
- "@shrkcrft/impact-engine": "^0.1.0-alpha.12",
68
- "@shrkcrft/context-planner": "^0.1.0-alpha.12",
69
- "@shrkcrft/architecture-guard": "^0.1.0-alpha.12",
70
- "@shrkcrft/framework-scanners": "^0.1.0-alpha.12",
71
- "@shrkcrft/api-surface-diff": "^0.1.0-alpha.12",
72
- "@shrkcrft/quality-gates": "^0.1.0-alpha.12",
73
- "@shrkcrft/migrate": "^0.1.0-alpha.12",
74
- "@shrkcrft/generator": "^0.1.0-alpha.12",
75
- "@shrkcrft/importer": "^0.1.0-alpha.12",
76
- "@shrkcrft/inspector": "^0.1.0-alpha.12",
77
- "@shrkcrft/ai": "^0.1.0-alpha.12",
78
- "@shrkcrft/embeddings": "^0.1.0-alpha.12",
79
- "@shrkcrft/shared": "^0.1.0-alpha.12",
80
- "@shrkcrft/mcp-server": "^0.1.0-alpha.12",
50
+ "@shrkcrft/core": "^0.1.0-alpha.13",
51
+ "@shrkcrft/config": "^0.1.0-alpha.13",
52
+ "@shrkcrft/workspace": "^0.1.0-alpha.13",
53
+ "@shrkcrft/knowledge": "^0.1.0-alpha.13",
54
+ "@shrkcrft/context": "^0.1.0-alpha.13",
55
+ "@shrkcrft/rules": "^0.1.0-alpha.13",
56
+ "@shrkcrft/paths": "^0.1.0-alpha.13",
57
+ "@shrkcrft/templates": "^0.1.0-alpha.13",
58
+ "@shrkcrft/plugin-api": "^0.1.0-alpha.13",
59
+ "@shrkcrft/dashboard": "^0.1.0-alpha.13",
60
+ "@shrkcrft/dashboard-api": "^0.1.0-alpha.13",
61
+ "@shrkcrft/pipelines": "^0.1.0-alpha.13",
62
+ "@shrkcrft/presets": "^0.1.0-alpha.13",
63
+ "@shrkcrft/boundaries": "^0.1.0-alpha.13",
64
+ "@shrkcrft/graph": "^0.1.0-alpha.13",
65
+ "@shrkcrft/rule-graph": "^0.1.0-alpha.13",
66
+ "@shrkcrft/structural-search": "^0.1.0-alpha.13",
67
+ "@shrkcrft/impact-engine": "^0.1.0-alpha.13",
68
+ "@shrkcrft/context-planner": "^0.1.0-alpha.13",
69
+ "@shrkcrft/architecture-guard": "^0.1.0-alpha.13",
70
+ "@shrkcrft/framework-scanners": "^0.1.0-alpha.13",
71
+ "@shrkcrft/api-surface-diff": "^0.1.0-alpha.13",
72
+ "@shrkcrft/quality-gates": "^0.1.0-alpha.13",
73
+ "@shrkcrft/migrate": "^0.1.0-alpha.13",
74
+ "@shrkcrft/generator": "^0.1.0-alpha.13",
75
+ "@shrkcrft/importer": "^0.1.0-alpha.13",
76
+ "@shrkcrft/inspector": "^0.1.0-alpha.13",
77
+ "@shrkcrft/ai": "^0.1.0-alpha.13",
78
+ "@shrkcrft/embeddings": "^0.1.0-alpha.13",
79
+ "@shrkcrft/shared": "^0.1.0-alpha.13",
80
+ "@shrkcrft/mcp-server": "^0.1.0-alpha.13",
81
81
  "@huggingface/transformers": "^3.7.5"
82
82
  },
83
83
  "publishConfig": {