@shrkcrft/cli 0.1.0-alpha.14 → 0.1.0-alpha.16

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.
@@ -0,0 +1,12 @@
1
+ import { type ICommandHandler } from '../command-registry.js';
2
+ /**
3
+ * `shrk align` — replace volatile tokens (UUIDs/JWTs/timestamps/hashes) with
4
+ * stable placeholders so a provider KV-cache prefix stays steady across turns.
5
+ * Aligned text → stdout; the reversible map is written to `--map <path>` (or
6
+ * `.sharkcraft/cache-align/align.json`). Pass an existing `--map` to carry
7
+ * ordinals forward. `shrk unalign` restores.
8
+ */
9
+ export declare const alignCommand: ICommandHandler;
10
+ /** `shrk unalign` — the restore half: turn placeholders back into originals. */
11
+ export declare const unalignCommand: ICommandHandler;
12
+ //# sourceMappingURL=cache-align.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-align.command.d.ts","sourceRoot":"","sources":["../../src/commands/cache-align.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAkBhC;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,eA4B1B,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,cAAc,EAAE,eAuB5B,CAAC"}
@@ -0,0 +1,78 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { alignVolatileTokens, restoreVolatileTokens, } from '@shrkcrft/compress';
4
+ import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
5
+ import { asJson } from "../output/format-output.js";
6
+ function readInput(args) {
7
+ const positional = args.positional[0];
8
+ const useStdin = flagBool(args, 'stdin') || positional === undefined || positional === '-';
9
+ return useStdin ? readFileSync(0, 'utf8') : readFileSync(positional, 'utf8');
10
+ }
11
+ function loadMap(path) {
12
+ if (!path || !existsSync(path))
13
+ return undefined;
14
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
15
+ if (typeof parsed === 'object' && parsed !== null && Array.isArray(parsed.bindings)) {
16
+ return parsed;
17
+ }
18
+ return undefined;
19
+ }
20
+ /**
21
+ * `shrk align` — replace volatile tokens (UUIDs/JWTs/timestamps/hashes) with
22
+ * stable placeholders so a provider KV-cache prefix stays steady across turns.
23
+ * Aligned text → stdout; the reversible map is written to `--map <path>` (or
24
+ * `.sharkcraft/cache-align/align.json`). Pass an existing `--map` to carry
25
+ * ordinals forward. `shrk unalign` restores.
26
+ */
27
+ export const alignCommand = {
28
+ name: 'align',
29
+ description: 'Replace volatile tokens with stable placeholders for KV-cache prefix stability; reversible via `shrk unalign`.',
30
+ usage: 'shrk [--cwd <dir>] align [<file>|-] [--stdin] [--map <path>] [--json]',
31
+ run(args) {
32
+ const cwd = resolveCwd(args);
33
+ let content;
34
+ try {
35
+ content = readInput(args);
36
+ }
37
+ catch (e) {
38
+ process.stderr.write(`align: cannot read input — ${e.message}\n`);
39
+ return 1;
40
+ }
41
+ const mapPath = flagString(args, 'map') ?? nodePath.join(cwd, '.sharkcraft', 'cache-align', 'align.json');
42
+ const result = alignVolatileTokens(content, loadMap(mapPath));
43
+ mkdirSync(nodePath.dirname(mapPath), { recursive: true });
44
+ writeFileSync(mapPath, JSON.stringify(result.map, null, 2), 'utf8');
45
+ if (flagBool(args, 'json')) {
46
+ process.stdout.write(asJson({ aligned: result.aligned, map: result.map, replaced: result.replaced }) + '\n');
47
+ return 0;
48
+ }
49
+ process.stdout.write(result.aligned + '\n');
50
+ process.stderr.write(`aligned: ${result.replaced} token(s) replaced · map → ${mapPath}\n`);
51
+ return 0;
52
+ },
53
+ };
54
+ /** `shrk unalign` — the restore half: turn placeholders back into originals. */
55
+ export const unalignCommand = {
56
+ name: 'unalign',
57
+ description: 'Restore the original volatile tokens in aligned text using its `--map`.',
58
+ usage: 'shrk [--cwd <dir>] unalign [<file>|-] [--stdin] --map <path>',
59
+ run(args) {
60
+ const cwd = resolveCwd(args);
61
+ const mapPath = flagString(args, 'map') ?? nodePath.join(cwd, '.sharkcraft', 'cache-align', 'align.json');
62
+ const map = loadMap(mapPath);
63
+ if (!map) {
64
+ process.stderr.write(`unalign: no alignment map at "${mapPath}" (pass --map <path>).\n`);
65
+ return 1;
66
+ }
67
+ let content;
68
+ try {
69
+ content = readInput(args);
70
+ }
71
+ catch (e) {
72
+ process.stderr.write(`unalign: cannot read input — ${e.message}\n`);
73
+ return 1;
74
+ }
75
+ process.stdout.write(restoreVolatileTokens(content, map) + '\n');
76
+ return 0;
77
+ },
78
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"command-catalog.d.ts","sourceRoot":"","sources":["../../src/commands/command-catalog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"command-catalog.d.ts","sourceRoot":"","sources":["../../src/commands/command-catalog.ts"],"names":[],"mappings":"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"}
@@ -1495,6 +1495,48 @@ export const COMMAND_CATALOG = Object.freeze([
1495
1495
  intendedAudience: [CommandAudience.Human, CommandAudience.Ci],
1496
1496
  taskRole: CommandTaskRole.Inspect,
1497
1497
  }),
1498
+ entry({
1499
+ command: 'compress',
1500
+ description: 'Deterministically compress a blob (file or stdin) to cut tokens — JSON→table, logs/search/diffs→signal. Reversible via `shrk expand`.',
1501
+ category: 'core',
1502
+ safetyLevel: SafetyLevel.WritesSessionOnly,
1503
+ writesFiles: true,
1504
+ mcpAvailable: true,
1505
+ surface: CommandSurface.Common,
1506
+ intendedAudience: [CommandAudience.Agent, CommandAudience.Human],
1507
+ taskRole: CommandTaskRole.Context,
1508
+ }),
1509
+ entry({
1510
+ command: 'expand',
1511
+ description: 'Retrieve the full original a `shrk compress` run cached, by its `<<ccr:KEY>>` key.',
1512
+ category: 'core',
1513
+ safetyLevel: SafetyLevel.ReadOnly,
1514
+ mcpAvailable: true,
1515
+ surface: CommandSurface.Common,
1516
+ intendedAudience: [CommandAudience.Agent, CommandAudience.Human],
1517
+ taskRole: CommandTaskRole.Context,
1518
+ }),
1519
+ entry({
1520
+ command: 'align',
1521
+ description: 'Replace volatile tokens (UUIDs/JWTs/timestamps/hashes) with stable placeholders for KV-cache prefix stability; reversible via `shrk unalign`.',
1522
+ category: 'core',
1523
+ safetyLevel: SafetyLevel.WritesSessionOnly,
1524
+ writesFiles: true,
1525
+ mcpAvailable: true,
1526
+ surface: CommandSurface.Advanced,
1527
+ intendedAudience: [CommandAudience.Agent, CommandAudience.Human],
1528
+ taskRole: CommandTaskRole.Context,
1529
+ }),
1530
+ entry({
1531
+ command: 'unalign',
1532
+ description: 'Restore the original volatile tokens in aligned text using its `--map`.',
1533
+ category: 'core',
1534
+ safetyLevel: SafetyLevel.ReadOnly,
1535
+ mcpAvailable: true,
1536
+ surface: CommandSurface.Advanced,
1537
+ intendedAudience: [CommandAudience.Agent, CommandAudience.Human],
1538
+ taskRole: CommandTaskRole.Context,
1539
+ }),
1498
1540
  // ── Backend feature expansion ─────────────────────────────────────────
1499
1541
  entry({
1500
1542
  command: 'bundle create',
@@ -0,0 +1,15 @@
1
+ import { type ICommandHandler } from '../command-registry.js';
2
+ /**
3
+ * `shrk compress` — deterministically compress a blob (file or stdin) before
4
+ * it re-enters an agent prompt. Same information, fewer tokens. Lossy passes
5
+ * cache the original under `.sharkcraft/ccr/` so `shrk expand <key>` can get
6
+ * it back. The compressed text goes to stdout (pipeable); the savings summary
7
+ * goes to stderr unless `--json` is set.
8
+ */
9
+ export declare const compressCommand: ICommandHandler;
10
+ /**
11
+ * `shrk expand` — the retrieve half of CCR. Print the full original that
12
+ * `shrk compress` cached for a `<<ccr:KEY>>` key.
13
+ */
14
+ export declare const expandCommand: ICommandHandler;
15
+ //# sourceMappingURL=compress.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compress.command.d.ts","sourceRoot":"","sources":["../../src/commands/compress.command.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgBhC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,eAkE7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,eA0B3B,CAAC"}
@@ -0,0 +1,111 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { compressContent, EContentType, FileCcrStore, } from '@shrkcrft/compress';
4
+ import { flagBool, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
5
+ import { asJson } from "../output/format-output.js";
6
+ const CONTENT_TYPES = new Set(Object.values(EContentType));
7
+ function ccrDir(cwd) {
8
+ return nodePath.join(cwd, '.sharkcraft', 'ccr');
9
+ }
10
+ function readInput(args) {
11
+ const positional = args.positional[0];
12
+ const useStdin = flagBool(args, 'stdin') || positional === undefined || positional === '-';
13
+ if (useStdin)
14
+ return readFileSync(0, 'utf8');
15
+ return readFileSync(positional, 'utf8');
16
+ }
17
+ /**
18
+ * `shrk compress` — deterministically compress a blob (file or stdin) before
19
+ * it re-enters an agent prompt. Same information, fewer tokens. Lossy passes
20
+ * cache the original under `.sharkcraft/ccr/` so `shrk expand <key>` can get
21
+ * it back. The compressed text goes to stdout (pipeable); the savings summary
22
+ * goes to stderr unless `--json` is set.
23
+ */
24
+ export const compressCommand = {
25
+ name: 'compress',
26
+ description: 'Compress a blob (file or stdin) deterministically to cut tokens — JSON→table, logs/search/diffs→signal. Reversible via `shrk expand`.',
27
+ usage: 'shrk [--cwd <dir>] compress [<file>|-] [--stdin] [--type <content-type>] [--query <text>] [--max <n>] [--no-cache] [--json]',
28
+ run(args) {
29
+ const cwd = resolveCwd(args);
30
+ let content;
31
+ try {
32
+ content = readInput(args);
33
+ }
34
+ catch (e) {
35
+ process.stderr.write(`compress: cannot read input — ${e.message}\n`);
36
+ return 1;
37
+ }
38
+ if (content.length === 0) {
39
+ process.stderr.write('compress: empty input.\n');
40
+ return 1;
41
+ }
42
+ const opts = {};
43
+ if (!flagBool(args, 'no-cache'))
44
+ opts.store = new FileCcrStore(ccrDir(cwd));
45
+ const query = flagString(args, 'query');
46
+ if (query)
47
+ opts.query = query;
48
+ const max = flagNumber(args, 'max');
49
+ if (max !== undefined && max > 0)
50
+ opts.maxItems = Math.floor(max);
51
+ const type = flagString(args, 'type');
52
+ if (type && CONTENT_TYPES.has(type))
53
+ opts.contentType = type;
54
+ const result = compressContent(content, opts);
55
+ const pct = Math.round(result.savings.ratio * 100);
56
+ // A lossy result with no cached original (i.e. --no-cache) can't be undone
57
+ // by `shrk expand`. Warn so the dropped detail isn't lost silently.
58
+ if (result.lossy && !result.ccrKey) {
59
+ process.stderr.write('warning: compressed lossily but the original was NOT cached (--no-cache) — ' +
60
+ 'detail is unrecoverable; omit --no-cache to keep it retrievable via `shrk expand`.\n');
61
+ }
62
+ if (flagBool(args, 'json')) {
63
+ process.stdout.write(asJson({
64
+ contentType: result.contentType,
65
+ strategy: result.strategy,
66
+ lossy: result.lossy,
67
+ tokensBefore: result.savings.before,
68
+ tokensAfter: result.savings.after,
69
+ tokensSaved: result.savings.saved,
70
+ savedRatio: result.savings.ratio,
71
+ ccrKey: result.ccrKey ?? null,
72
+ note: result.note,
73
+ compressed: result.compressed,
74
+ }) + '\n');
75
+ return 0;
76
+ }
77
+ process.stdout.write(result.compressed + '\n');
78
+ const cached = result.ccrKey ? ` · original cached as ${result.ccrKey} (shrk expand ${result.ccrKey})` : '';
79
+ process.stderr.write(`${result.strategy}: ${result.savings.before} → ${result.savings.after} tokens (−${pct}%)${cached}\n`);
80
+ return 0;
81
+ },
82
+ };
83
+ /**
84
+ * `shrk expand` — the retrieve half of CCR. Print the full original that
85
+ * `shrk compress` cached for a `<<ccr:KEY>>` key.
86
+ */
87
+ export const expandCommand = {
88
+ name: 'expand',
89
+ description: 'Retrieve the full original a `shrk compress` run cached, by its `<<ccr:KEY>>` key.',
90
+ usage: 'shrk [--cwd <dir>] expand <key> [--json]',
91
+ run(args) {
92
+ const cwd = resolveCwd(args);
93
+ const key = (args.positional[0] ?? '').trim();
94
+ if (key.length === 0) {
95
+ process.stderr.write('expand: a CCR key is required (e.g. `shrk expand a1b2c3d4e5f60718`).\n');
96
+ return 1;
97
+ }
98
+ const store = new FileCcrStore(ccrDir(cwd));
99
+ const entry = store.get(key);
100
+ if (!entry) {
101
+ process.stderr.write(`expand: no cached original for key "${key}" under ${ccrDir(cwd)}.\n`);
102
+ return 1;
103
+ }
104
+ if (flagBool(args, 'json')) {
105
+ process.stdout.write(asJson({ key: entry.key, bytes: entry.bytes, content: entry.content }) + '\n');
106
+ return 0;
107
+ }
108
+ process.stdout.write(entry.content + '\n');
109
+ return 0;
110
+ },
111
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"context.command.d.ts","sourceRoot":"","sources":["../../src/commands/context.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC,eAAO,MAAM,cAAc,EAAE,eAiI5B,CAAC"}
1
+ {"version":3,"file":"context.command.d.ts","sourceRoot":"","sources":["../../src/commands/context.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgChC,eAAO,MAAM,cAAc,EAAE,eAyI5B,CAAC"}
@@ -6,10 +6,33 @@ import * as nodePath from 'node:path';
6
6
  import { buildUniversalSearch, explainTaskRouting, recommendCommands, renderOverviewText, buildProjectOverview, } from '@shrkcrft/inspector';
7
7
  import { flagBool, flagNumber, flagString, flagList, resolveCwd, } from "../command-registry.js";
8
8
  import { asJson, header } from "../output/format-output.js";
9
+ /**
10
+ * Minimal JSON shape for agent / skill consumption — the context-side mirror
11
+ * of `shrk task --compact`. Drops the heavy `body` and `request` echo and
12
+ * carries the section map + structured action hints (so the agent reads
13
+ * forbiddenActions / verificationCommands / preferredFlow directly instead of
14
+ * regexing the markdown body). The schema marker is distinct so consumers can
15
+ * tell the shapes apart at a glance.
16
+ */
17
+ function minimalContext(task, result, commands) {
18
+ return {
19
+ schema: 'sharkcraft.context/v1-compact',
20
+ task,
21
+ tokens: { used: result.totalTokens, max: result.maxTokens },
22
+ sections: result.sections.map((s) => ({
23
+ title: s.title,
24
+ tokens: s.tokens,
25
+ ...(s.truncated ? { truncated: true } : {}),
26
+ })),
27
+ omittedSections: result.omittedSections,
28
+ actionHints: result.actionHints,
29
+ topCommands: (commands?.recommendations ?? []).slice(0, 5).map((r) => r.command),
30
+ };
31
+ }
9
32
  export const contextCommand = {
10
33
  name: 'context',
11
34
  description: 'Build relevant AI-ready context for a task (token-budgeted). Subcommands: build / refresh / status.',
12
- usage: 'shrk context [build|refresh|status] --task "<task>" [--max-tokens 3000] [--framework x] [--area y] [--json]',
35
+ usage: 'shrk context [build|refresh|status] --task "<task>" [--max-tokens 3000] [--framework x] [--area y] [--json] [--compact] [--full]',
13
36
  async run(args) {
14
37
  // Dispatch subcommands (build / refresh / status) based on first positional.
15
38
  const sub = args.positional[0];
@@ -79,6 +102,14 @@ export const contextCommand = {
79
102
  // ignore — fall back to legacy context only.
80
103
  }
81
104
  if (flagBool(args, 'json') || flagBool(args, 'machine-json')) {
105
+ // `--compact` emits a minimal, structured agent shape (no long body /
106
+ // request echo) — the context-side mirror of `shrk task --compact`.
107
+ // Carries the load-bearing action hints as structured data so the agent
108
+ // never has to parse the markdown body. Full shape stays the default.
109
+ if (flagBool(args, 'compact')) {
110
+ process.stdout.write(asJson(minimalContext(task, result, commandRecommendations)) + '\n');
111
+ return 0;
112
+ }
82
113
  process.stdout.write(asJson({
83
114
  ...result,
84
115
  commands: commandRecommendations,
@@ -1 +1 @@
1
- {"version":3,"file":"smart-context.command.d.ts","sourceRoot":"","sources":["../../src/commands/smart-context.command.ts"],"names":[],"mappings":"AAuBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAyDhC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,EAAE,eA+JjC,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,EAAE,eAmF1C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,uBAAuB,EAAE,eAwBrC,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,uBAAuB,EAAE,eAkCrC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iCAAiC,EAAE,eAmH/C,CAAC;AA2JF;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,EAAE,eAoH/C,CAAC;AA2JF;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,EAAE,eAgG/C,CAAC;AAiMF,4EAA4E;AAC5E,eAAO,MAAM,kCAAkC,EAAE,eAuHhD,CAAC;AAMF,iFAAiF;AACjF,eAAO,MAAM,mCAAmC,EAAE,eAsCjD,CAAC"}
1
+ {"version":3,"file":"smart-context.command.d.ts","sourceRoot":"","sources":["../../src/commands/smart-context.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAyDhC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,EAAE,eA6LjC,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,EAAE,eAmF1C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,uBAAuB,EAAE,eAwBrC,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,uBAAuB,EAAE,eAkCrC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iCAAiC,EAAE,eAmH/C,CAAC;AA2JF;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,EAAE,eAoH/C,CAAC;AA2JF;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,EAAE,eAgG/C,CAAC;AAyPF,4EAA4E;AAC5E,eAAO,MAAM,kCAAkC,EAAE,eAuHhD,CAAC;AAMF,iFAAiF;AACjF,eAAO,MAAM,mCAAmC,EAAE,eAsCjD,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { spawn, spawnSync } from 'node:child_process';
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
3
3
  import * as nodePath from 'node:path';
4
- import { AiMessageRole, buildPromptMessages, EnhancementPipeline, EnhancementStageKind, OllamaProvider, buildDefaultEnhancementStages, selectAiProvider, } from '@shrkcrft/ai';
4
+ import * as os from 'node:os';
5
+ import { AiMessageRole, buildPromptMessages, EnhancementPipeline, EnhancementStageKind, OllamaProvider, buildDefaultEnhancementStages, buildFastEnhancementStages, selectAiProvider, } from '@shrkcrft/ai';
5
6
  import { buildContext } from '@shrkcrft/context';
6
7
  import { EdgeKind, GraphQueryApi, GraphStore, NodeKind } from '@shrkcrft/graph';
7
8
  import { buildProjectOverview, buildTaskPacket, inspectSharkcraft, renderOverviewText, } from '@shrkcrft/inspector';
@@ -42,13 +43,28 @@ const SMART_CONTEXT_DIR = nodePath.join('.sharkcraft', 'smart-context');
42
43
  export const smartContextCommand = {
43
44
  name: 'smart-context',
44
45
  description: 'Build deterministic context and ask an AI provider to synthesise an enriched brief (default), structured plan (--plan), or two-stage development plan (--ai-plan).',
45
- usage: 'shrk smart-context "<task>" [--plan] [--ai-plan] [--save] [--provider auto|ollama|llamacpp] [--enhance|--no-enhance] [--enhance-passes N] [--instructions <path>] [--no-instructions] [--model <id>] [--max-tokens N] [--stage1-max-tokens N] [--seed-tokens N] [--expansion-tokens N] [--expansion-limit N] [--log-prompt] [--save-conversation[=<path>]] [--dry-run] [--debug] [--json]',
46
+ usage: 'shrk smart-context "<task>" [--plus] [--budget <seconds>] [--plan] [--ai-plan] [--save] [--provider auto|ollama|llamacpp] [--enhance|--no-enhance] [--enhance-passes N] [--instructions <path>] [--no-instructions] [--model <id>] [--max-tokens N] [--stage1-max-tokens N] [--seed-tokens N] [--expansion-tokens N] [--expansion-limit N] [--log-prompt] [--save-conversation[=<path>]] [--dry-run] [--debug] [--json]',
46
47
  async run(args) {
47
48
  const task = args.positional.join(' ').trim();
48
49
  if (!task) {
49
50
  process.stderr.write('Usage: shrk smart-context "<task>" [--plan] [--ai-plan] [--save]\n');
50
51
  return 2;
51
52
  }
53
+ // Isolate the LLM / native-runtime work in a child process. On macOS the
54
+ // node-llama-cpp (ggml/Metal) and ONNX static destructors abort during
55
+ // `exit()` — surfacing a GGML backtrace + `libc++abi … mutex lock failed`
56
+ // (and a shell `abort`) AFTER a perfectly good result. There is no JS hook
57
+ // in this Node build to skip libc++ finalizers, so instead the child
58
+ // self-contains that noise (fd 2 → log on exit) and hands its real exit
59
+ // code back through a sentinel file; the parent never loads a native
60
+ // runtime, so it exits cleanly with the correct code. Dry-run does no
61
+ // native work, so it stays in-process. Gated on SHRK_CLI so a unit test
62
+ // calling `run()` in-process never spawns a subprocess.
63
+ if (process.env.SHRK_CLI === '1' &&
64
+ process.env.SHRK_SMART_CONTEXT_WORKER !== '1' &&
65
+ !flagBool(args, 'dry-run')) {
66
+ return runSmartContextInChild();
67
+ }
52
68
  const cwd = resolveCwd(args);
53
69
  const opts = readCommonOptions(args);
54
70
  const inspection = await inspectSharkcraft({ cwd });
@@ -125,6 +141,15 @@ export const smartContextCommand = {
125
141
  process.stderr.write(`[smart-context] conversation saved → ${path}\n`);
126
142
  }
127
143
  }
144
+ const enh = enhanced.value.enhancement;
145
+ if (!opts.json && !enh.deterministicFallback) {
146
+ if (enh.budgetExhausted) {
147
+ process.stderr.write(`[smart-context] budget reached before all ${enh.plannedPasses} passes finished — output is the best so far. Try a smaller --model or raise --budget.\n`);
148
+ }
149
+ if (!enh.plus) {
150
+ process.stderr.write(`[smart-context] fast ${enh.plannedPasses}-pass enhancement. Pass --plus for the full draft→critique→refine→polish (denser, slower).\n`);
151
+ }
152
+ }
128
153
  const envelope = buildEnvelope({
129
154
  task,
130
155
  seed,
@@ -1040,6 +1065,63 @@ function isEmbeddingsCleanupNoise(line) {
1040
1065
  * already set the kernel-visible exit code before the abort. We
1041
1066
  * surface that code verbatim.
1042
1067
  */
1068
+ /**
1069
+ * Run a smart-context brief/plan in an isolated child and return its real exit
1070
+ * code. stdio is inherited so progress + result flow straight to the user's
1071
+ * terminal; the child redirects fd 2 to a log file before its native teardown
1072
+ * abort, so no backtrace reaches the console. The child writes its true exit
1073
+ * code to a sentinel file (read back here) because the SIGABRT during teardown
1074
+ * would otherwise clobber it with 134. The parent loads no native runtime, so
1075
+ * it exits cleanly — no `zsh: abort`, correct code.
1076
+ */
1077
+ function runSmartContextInChild() {
1078
+ return new Promise((resolve) => {
1079
+ const exitFile = nodePath.join(os.tmpdir(), `shrk-sc-exit-${process.pid}-${Date.now()}.code`);
1080
+ const child = spawn(process.execPath, process.argv.slice(1), {
1081
+ env: {
1082
+ ...process.env,
1083
+ SHRK_SMART_CONTEXT_WORKER: '1',
1084
+ SHRK_WORKER_EXITCODE_FILE: exitFile,
1085
+ },
1086
+ stdio: 'inherit',
1087
+ });
1088
+ child.on('error', (err) => {
1089
+ process.stderr.write(`Failed to spawn smart-context worker: ${err.message}\n`);
1090
+ resolve(1);
1091
+ });
1092
+ child.on('close', (code, signal) => {
1093
+ // Prefer the sentinel — the worker writes its true exit code before the
1094
+ // native teardown can abort the process.
1095
+ let real = null;
1096
+ try {
1097
+ if (existsSync(exitFile)) {
1098
+ const raw = readFileSync(exitFile, 'utf8').trim();
1099
+ if (raw.length > 0 && Number.isFinite(Number(raw)))
1100
+ real = Number(raw);
1101
+ try {
1102
+ unlinkSync(exitFile);
1103
+ }
1104
+ catch {
1105
+ // best-effort cleanup
1106
+ }
1107
+ }
1108
+ }
1109
+ catch {
1110
+ // fall through to the signal/code-based result below
1111
+ }
1112
+ if (real !== null) {
1113
+ resolve(real);
1114
+ return;
1115
+ }
1116
+ // No sentinel (worker crashed mid-run, not during teardown) → surface a
1117
+ // failure rather than masking it. SIGABRT with no sentinel ⇒ non-zero.
1118
+ if (typeof code === 'number')
1119
+ resolve(code);
1120
+ else
1121
+ resolve(signal ? 1 : 0);
1122
+ });
1123
+ });
1124
+ }
1043
1125
  function runEmbeddingsBuildInChild() {
1044
1126
  return new Promise((resolve) => {
1045
1127
  const child = spawn(process.execPath, process.argv.slice(1), {
@@ -1269,6 +1351,10 @@ function readCommonOptions(args) {
1269
1351
  stream: flagBool(args, 'stream'),
1270
1352
  enhance: resolveEnhanceFlag(args),
1271
1353
  enhancePasses: flagNumber(args, 'enhance-passes') ?? readEnhancePassesEnv(),
1354
+ plus: flagBool(args, 'plus'),
1355
+ ...(flagNumber(args, 'budget') !== undefined
1356
+ ? { budgetMs: Math.max(1, flagNumber(args, 'budget')) * 1000 }
1357
+ : {}),
1272
1358
  logPrompt: flagBool(args, 'log-prompt'),
1273
1359
  saveConversation: flagBool(args, 'save-conversation') || flagString(args, 'save-conversation') !== undefined,
1274
1360
  ...(flagString(args, 'save-conversation')
@@ -2514,13 +2600,29 @@ function truncateLine(text, max) {
2514
2600
  * system body verbatim across stages so the model never loses
2515
2601
  * grounding; only the user turn changes per stage.
2516
2602
  */
2603
+ /**
2604
+ * Wall-clock ceilings for the enhancement pipeline. These are anti-hang
2605
+ * guards, not target runtimes — the speed win comes from running fewer passes
2606
+ * by default and from picking a smaller `--model`. A slow model that overruns
2607
+ * degrades to the best output so far (or the deterministic seed). Override per
2608
+ * invocation with `--budget <seconds>`.
2609
+ */
2610
+ const PER_STAGE_TIMEOUT_MS = 90_000;
2611
+ const FAST_ENHANCE_BUDGET_MS = 150_000;
2612
+ const PLUS_ENHANCE_BUDGET_MS = 360_000;
2517
2613
  async function runEnhancementPipeline(input) {
2518
2614
  const provider = input.provider;
2519
2615
  const systemMsg = input.messages.find((m) => m.role === AiMessageRole.System);
2520
2616
  const userMsg = input.messages.find((m) => m.role === AiMessageRole.User);
2521
2617
  const originalContext = systemMsg?.content ?? '';
2522
2618
  const taskBody = userMsg?.content ?? input.seed.task;
2523
- const pipeline = new EnhancementPipeline(buildDefaultEnhancementStages());
2619
+ // Default is the fast 2-pass draft→polish; `--plus` opts into the full
2620
+ // draft→critique→refine→polish for denser output. Both are wall-clock
2621
+ // bounded so a slow local model degrades gracefully instead of hanging.
2622
+ const plus = input.options.plus;
2623
+ const stages = plus ? buildDefaultEnhancementStages() : buildFastEnhancementStages();
2624
+ const budgetMs = input.options.budgetMs ?? (plus ? PLUS_ENHANCE_BUDGET_MS : FAST_ENHANCE_BUDGET_MS);
2625
+ const pipeline = new EnhancementPipeline(stages);
2524
2626
  const stageInputs = [];
2525
2627
  const stageResponses = [];
2526
2628
  // Tee per-stage prompts/responses so we can rebuild the conversation
@@ -2540,6 +2642,8 @@ async function runEnhancementPipeline(input) {
2540
2642
  const piRun = await pipeline.run({ task: taskBody, originalContext }, recordingProvider, {
2541
2643
  ...(input.options.enhancePasses ? { maxPasses: input.options.enhancePasses } : {}),
2542
2644
  maxTokensPerStage: input.options.maxTokens,
2645
+ budgetMs,
2646
+ perStageTimeoutMs: PER_STAGE_TIMEOUT_MS,
2543
2647
  ...(input.options.model ? { model: input.options.model } : {}),
2544
2648
  onStage: (e) => {
2545
2649
  if (!input.options.json) {
@@ -2607,6 +2711,9 @@ async function runEnhancementPipeline(input) {
2607
2711
  })),
2608
2712
  totalUsage: piRun.value.totalUsage,
2609
2713
  deterministicFallback: piRun.value.deterministicFallback,
2714
+ budgetExhausted: piRun.value.budgetExhausted,
2715
+ plannedPasses: stages.length,
2716
+ plus,
2610
2717
  },
2611
2718
  turns,
2612
2719
  },
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-api-server.d.ts","sourceRoot":"","sources":["../../src/dashboard/dashboard-api-server.ts"],"names":[],"mappings":"AA4DA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AA2BD,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAqC1F;AA2jBD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"dashboard-api-server.d.ts","sourceRoot":"","sources":["../../src/dashboard/dashboard-api-server.ts"],"names":[],"mappings":"AAuIA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AA2BD,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAqC1F;AA0lBD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
@@ -15,10 +15,71 @@ import * as http from 'node:http';
15
15
  import * as fs from 'node:fs';
16
16
  import { existsSync, readFileSync, statSync } from 'node:fs';
17
17
  import * as nodePath from 'node:path';
18
- import { buildDashboardAdoption, buildDashboardArchitecture, buildDashboardBoundaries, buildDashboardCapabilities, buildDashboardCommands, buildDashboardCoverage, buildDashboardDoctor, buildDashboardDrift, buildDashboardGraph, buildDashboardGraphNode, buildDashboardGraphPath, buildDashboardHealth, buildDashboardMcpSummary, buildDashboardOnboarding, buildDashboardOverview, buildDashboardPacks, buildDashboardPipelines, buildDashboardPresets, buildDashboardQuality, buildDashboardReports, buildDashboardReview, buildDashboardSafety, buildDashboardScaffolds, buildDashboardSchemas, buildDashboardSessionDetail, buildDashboardSessions, buildDashboardStats, inspectSharkcraft, renderDevSessionHtml, scanDevSession, } from '@shrkcrft/inspector';
18
+ import { buildDashboardAdoption, buildDashboardArchitecture, buildDashboardBoundaries, buildDashboardCapabilities, buildDashboardCommands, buildDashboardCoverage, buildDashboardDoctor, buildDashboardDrift, buildDashboardGraph, buildDashboardGraphNode, buildDashboardGraphPath, buildDashboardHealth, buildDashboardKnowledgeList, buildDashboardKnowledgeEntry, buildDashboardKnowledgeGraph, buildDashboardKnowledgeSimilar, buildDashboardMcpSummary, buildDashboardOnboarding, buildDashboardOverview, buildDashboardPacks, buildDashboardPipelines, buildDashboardPresets, buildDashboardQuality, buildDashboardReports, buildDashboardReview, buildDashboardSafety, buildDashboardScaffolds, buildDashboardSchemas, buildDashboardSessionDetail, buildDashboardSessions, buildDashboardStats, inspectSharkcraft, renderDevSessionHtml, scanDevSession, } from '@shrkcrft/inspector';
19
+ import { EContentType, compactArrayToColumnar, estimateTokens } from '@shrkcrft/compress';
19
20
  import { COMMAND_CATALOG } from "../commands/command-catalog.js";
20
21
  import { buildDashboardCodeIntelligence, buildDashboardMigrations, buildDashboardQualityGates, buildDashboardRoutes, } from "./code-intelligence-data.js";
22
+ import { buildKnowledgeAsk } from "./knowledge-ask.js";
21
23
  const SCHEMA_ID = 'sharkcraft.dashboard-api/v1';
24
+ /**
25
+ * Compute the deterministic compression layer's per-surface token savings for
26
+ * the dashboard. Measured on the live workspace (no timestamps → stable).
27
+ *
28
+ * `realTokens`, when supplied, is an exact BPE tokenizer (cl100k_base); the
29
+ * panel then reports exact counts and flags `tokensAreEstimated: false`. With
30
+ * no tokenizer (the default in a published install, where the dev-only
31
+ * `gpt-tokenizer` is absent) it falls back to the engine's estimator — whose
32
+ * *percentages* are sound but whose *absolutes* are rough — and flags
33
+ * `tokensAreEstimated: true` so the UI never presents an estimate as exact.
34
+ */
35
+ function buildDashboardCompression(inspection, realTokens) {
36
+ const graph = buildDashboardKnowledgeGraph(inspection);
37
+ // Both encodings are JSON, so when estimating, score them with the JSON
38
+ // divisor — the same ratio the engine uses — for the closest absolute counts.
39
+ const count = (text) => realTokens ? realTokens(text) : estimateTokens(text, EContentType.Json);
40
+ const surfaces = [];
41
+ const add = (surface, strategy, beforeText, afterText) => {
42
+ const before = count(beforeText);
43
+ // Net-loss guard: columnar/legend overhead can exceed the raw encoding on
44
+ // tiny arrays. The engine ships whichever is smaller, so report what the
45
+ // agent actually pays — never a negative saving.
46
+ const after = Math.min(count(afterText), before);
47
+ surfaces.push({ surface, strategy, before, after, savedPct: before > 0 ? Math.round((1 - after / before) * 100) : 0 });
48
+ };
49
+ const nodes = [...graph.nodes];
50
+ const edges = [...graph.edges];
51
+ add('knowledge graph', 'columnar table', JSON.stringify({ nodes, edges }), JSON.stringify({ nodes: compactArrayToColumnar(nodes) ?? nodes, edges: compactArrayToColumnar(edges) ?? edges }));
52
+ add('knowledge nodes', 'columnar table', JSON.stringify(nodes), JSON.stringify(compactArrayToColumnar(nodes) ?? nodes));
53
+ const totalsBefore = surfaces.reduce((s, x) => s + x.before, 0);
54
+ const totalsAfter = surfaces.reduce((s, x) => s + x.after, 0);
55
+ return {
56
+ surfaces,
57
+ totals: {
58
+ before: totalsBefore,
59
+ after: totalsAfter,
60
+ savedPct: totalsBefore > 0 ? Math.round((1 - totalsAfter / totalsBefore) * 100) : 0,
61
+ },
62
+ tokensAreEstimated: !realTokens,
63
+ };
64
+ }
65
+ /**
66
+ * Best-effort load of the optional dev tokenizer for exact dashboard counts.
67
+ * Guarded dynamic import: `gpt-tokenizer` is not a runtime dependency of the
68
+ * CLI, so this resolves to `undefined` in any install that did not ship it, and
69
+ * the dashboard transparently falls back to the estimator.
70
+ */
71
+ async function loadDashboardTokenizer() {
72
+ try {
73
+ const mod = (await import('gpt-tokenizer'));
74
+ if (typeof mod.encode !== 'function')
75
+ return undefined;
76
+ const encode = mod.encode;
77
+ return (text) => (text ? encode(text).length : 0);
78
+ }
79
+ catch {
80
+ return undefined;
81
+ }
82
+ }
22
83
  export async function startDashboardApiServer(opts) {
23
84
  const host = opts.host ?? '127.0.0.1';
24
85
  const port = opts.port ?? 0;
@@ -324,8 +385,10 @@ async function handle(req, res, ctx) {
324
385
  path.startsWith('/api/pipelines') ||
325
386
  path.startsWith('/api/architecture') ||
326
387
  path.startsWith('/api/graph') ||
388
+ path.startsWith('/api/knowledge') ||
327
389
  path.startsWith('/api/onboarding') ||
328
390
  path.startsWith('/api/review') ||
391
+ path.startsWith('/api/compression') ||
329
392
  path.startsWith('/api/scaffolds');
330
393
  const inspection = needsInspection
331
394
  ? await inspectSharkcraft({ cwd: projectRoot })
@@ -345,6 +408,10 @@ async function handle(req, res, ctx) {
345
408
  if (path === '/api/packs') {
346
409
  return respond(res, buildEnvelope(projectRoot, buildDashboardPacks(inspection)));
347
410
  }
411
+ if (path === '/api/compression') {
412
+ const realTokens = await loadDashboardTokenizer();
413
+ return respond(res, buildEnvelope(projectRoot, buildDashboardCompression(inspection, realTokens)));
414
+ }
348
415
  if (path === '/api/presets') {
349
416
  return respond(res, buildEnvelope(projectRoot, buildDashboardPresets(inspection)));
350
417
  }
@@ -377,6 +444,32 @@ async function handle(req, res, ctx) {
377
444
  return respondError(res, 400, 'bad-request', 'from and to query params required');
378
445
  return respond(res, buildEnvelope(projectRoot, buildDashboardGraphPath(inspection, from, to)));
379
446
  }
447
+ // Knowledge explorer. Specific sub-paths (ask / graph / entry) are matched
448
+ // before the bare list route. The ask route synthesizes a grounded answer
449
+ // with the local LLM — still a read: it never writes, and is wall-clock
450
+ // bounded so it cannot hang the server.
451
+ if (path === '/api/knowledge/ask') {
452
+ const q = url.searchParams.get('q');
453
+ if (!q || !q.trim())
454
+ return respondError(res, 400, 'bad-request', 'q query param required');
455
+ return respond(res, buildEnvelope(projectRoot, await buildKnowledgeAsk(inspection, q)));
456
+ }
457
+ if (path === '/api/knowledge/graph') {
458
+ return respond(res, buildEnvelope(projectRoot, buildDashboardKnowledgeGraph(inspection)));
459
+ }
460
+ const knowledgeSimilarMatch = path.match(/^\/api\/knowledge\/similar\/(.+)$/);
461
+ if (knowledgeSimilarMatch) {
462
+ const id = decodeURIComponent(knowledgeSimilarMatch[1]);
463
+ return respond(res, buildEnvelope(projectRoot, buildDashboardKnowledgeSimilar(inspection, id)));
464
+ }
465
+ const knowledgeEntryMatch = path.match(/^\/api\/knowledge\/entry\/(.+)$/);
466
+ if (knowledgeEntryMatch) {
467
+ const id = decodeURIComponent(knowledgeEntryMatch[1]);
468
+ return respond(res, buildEnvelope(projectRoot, buildDashboardKnowledgeEntry(inspection, id)));
469
+ }
470
+ if (path === '/api/knowledge') {
471
+ return respond(res, buildEnvelope(projectRoot, buildDashboardKnowledgeList(inspection)));
472
+ }
380
473
  if (path === '/api/onboarding') {
381
474
  return respond(res, buildEnvelope(projectRoot, buildDashboardOnboarding(inspection)));
382
475
  }
@@ -0,0 +1,4 @@
1
+ import type { ISharkcraftInspection } from '@shrkcrft/inspector';
2
+ import type { IDashboardKnowledgeAskResponse } from '@shrkcrft/dashboard-api';
3
+ export declare function buildKnowledgeAsk(inspection: ISharkcraftInspection, question: string): Promise<IDashboardKnowledgeAskResponse>;
4
+ //# sourceMappingURL=knowledge-ask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge-ask.d.ts","sourceRoot":"","sources":["../../src/dashboard/knowledge-ask.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EACV,8BAA8B,EAE/B,MAAM,yBAAyB,CAAC;AAwCjC,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,qBAAqB,EACjC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,8BAA8B,CAAC,CA0EzC"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Read-only "ask a question over the knowledge base" for the dashboard.
3
+ *
4
+ * Deterministic retrieval (searchKnowledge) is the source of truth and ALWAYS
5
+ * populates `sources`. The local LLM only synthesizes a prose `answer` grounded
6
+ * in those entries. When no LLM is reachable, or it times out / errors, the
7
+ * response degrades gracefully to the retrieved entries — the GET handler never
8
+ * hangs and never writes anything.
9
+ */
10
+ import { ERROR_CODES } from '@shrkcrft/core';
11
+ import { AiMessageRole, selectAiProvider } from '@shrkcrft/ai';
12
+ import { searchKnowledge } from '@shrkcrft/knowledge';
13
+ /** Hard wall-clock bound so a slow local model can't hang a dashboard GET. */
14
+ const ASK_TIMEOUT_MS = 15_000;
15
+ /** How many entries to retrieve and feed as grounding. */
16
+ const RETRIEVE_LIMIT = 8;
17
+ /** Per-entry grounding budget (chars) — keeps the prompt small for local models. */
18
+ const ENTRY_CHARS = 1100;
19
+ const SYSTEM_PROMPT = [
20
+ 'You are the SharkCraft knowledge assistant for a single repository.',
21
+ 'Answer the question USING ONLY the knowledge entries provided below as context.',
22
+ 'Every entry is delimited and prefixed with its id in [brackets].',
23
+ 'Cite the entries you used by writing their id in [brackets] inline.',
24
+ 'If the answer is not present in the provided entries, say so plainly — do not invent.',
25
+ 'Be concise: a few short paragraphs or bullets. No preamble, no sign-off.',
26
+ ].join('\n');
27
+ function roundScore(n) {
28
+ return Math.round(n * 100) / 100;
29
+ }
30
+ function buildMessages(question, grounding) {
31
+ return [
32
+ { role: AiMessageRole.System, content: SYSTEM_PROMPT },
33
+ {
34
+ role: AiMessageRole.User,
35
+ content: [
36
+ '# Knowledge entries',
37
+ grounding,
38
+ '',
39
+ '# Question',
40
+ question.trim(),
41
+ '',
42
+ 'Answer now, citing entry ids in [brackets].',
43
+ ].join('\n'),
44
+ },
45
+ ];
46
+ }
47
+ export async function buildKnowledgeAsk(inspection, question) {
48
+ const startedAt = Date.now();
49
+ const trimmed = question.trim();
50
+ // 1. Deterministic retrieval — always available, the engine's ground truth.
51
+ const results = searchKnowledge(inspection.knowledgeEntries, {
52
+ query: trimmed,
53
+ limit: RETRIEVE_LIMIT,
54
+ });
55
+ const sources = results.map((r) => ({
56
+ id: r.entry.id,
57
+ title: r.entry.title,
58
+ type: String(r.entry.type),
59
+ score: roundScore(r.score),
60
+ }));
61
+ const degrade = (note, provider) => ({
62
+ question: trimmed,
63
+ llmAvailable: false,
64
+ ...(provider ? { provider } : {}),
65
+ answer: null,
66
+ degraded: true,
67
+ note,
68
+ sources,
69
+ citedEntryIds: [],
70
+ durationMs: Date.now() - startedAt,
71
+ });
72
+ // 2. Provider selection — local-only, may be absent.
73
+ const selection = selectAiProvider(process.env.AI_PROVIDER);
74
+ if (!selection.provider) {
75
+ return degrade('No local LLM is reachable, so this is the deterministic top-matches view. Start an Ollama daemon or set LLAMACPP_MODEL_PATH to enable synthesized answers.');
76
+ }
77
+ if (results.length === 0) {
78
+ return degrade('No knowledge entries matched the question.', selection.provider.id);
79
+ }
80
+ // 3. Grounded prompt from the retrieved entries.
81
+ const grounding = results
82
+ .map((r) => {
83
+ const body = (r.entry.summary ?? r.entry.content).slice(0, ENTRY_CHARS);
84
+ return `### [${r.entry.id}] ${r.entry.title}\n${body}`;
85
+ })
86
+ .join('\n\n');
87
+ // 4. Bounded LLM call; any failure degrades to retrieval-only.
88
+ const res = await selection.provider.send({
89
+ messages: buildMessages(trimmed, grounding),
90
+ maxTokens: 1024,
91
+ temperature: 0.2,
92
+ timeoutMs: ASK_TIMEOUT_MS,
93
+ });
94
+ if (!res.ok) {
95
+ const note = res.error.code === ERROR_CODES.TIMEOUT
96
+ ? 'The local LLM timed out — showing the deterministic top matches instead.'
97
+ : `The local LLM could not answer (${res.error.message}) — showing the deterministic top matches instead.`;
98
+ return degrade(note, selection.provider.id);
99
+ }
100
+ const answer = res.value.content.trim();
101
+ const citedEntryIds = sources.map((s) => s.id).filter((id) => answer.includes(id));
102
+ return {
103
+ question: trimmed,
104
+ llmAvailable: true,
105
+ provider: selection.provider.id,
106
+ answer,
107
+ degraded: false,
108
+ sources,
109
+ citedEntryIds,
110
+ durationMs: Date.now() - startedAt,
111
+ };
112
+ }
package/dist/index.d.ts CHANGED
@@ -17,6 +17,9 @@ export { packsListCommand, packsGetCommand, packsInspectCommand, packsDoctorComm
17
17
  export { pipelinesListCommand, pipelinesGetCommand, pipelinesContextCommand, pipelinesPlanCommand, pipelinesScriptCommand, pipelinesNextCommand, } from './commands/pipelines.command.js';
18
18
  export { schemasListCommand, schemasGetCommand, schemasWriteCommand, } from './commands/schemas.command.js';
19
19
  export { versionCommand } from './commands/version.command.js';
20
+ export { compressCommand, expandCommand } from './commands/compress.command.js';
21
+ export { alignCommand, unalignCommand } from './commands/cache-align.command.js';
20
22
  export { makeHelpCommand } from './commands/help.command.js';
21
23
  export { buildRegistry, runCli } from './main.js';
24
+ export { buildWatchPlan, maybeRunInWatchMode, runWatchLoop, } from './output/watch-loop.js';
22
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,YAAY,GACb,MAAM,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -17,5 +17,8 @@ export { packsListCommand, packsGetCommand, packsInspectCommand, packsDoctorComm
17
17
  export { pipelinesListCommand, pipelinesGetCommand, pipelinesContextCommand, pipelinesPlanCommand, pipelinesScriptCommand, pipelinesNextCommand, } from "./commands/pipelines.command.js";
18
18
  export { schemasListCommand, schemasGetCommand, schemasWriteCommand, } from "./commands/schemas.command.js";
19
19
  export { versionCommand } from "./commands/version.command.js";
20
+ export { compressCommand, expandCommand } from "./commands/compress.command.js";
21
+ export { alignCommand, unalignCommand } from "./commands/cache-align.command.js";
20
22
  export { makeHelpCommand } from "./commands/help.command.js";
21
23
  export { buildRegistry, runCli } from "./main.js";
24
+ export { buildWatchPlan, maybeRunInWatchMode, runWatchLoop, } from "./output/watch-loop.js";
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAIhB,MAAM,uBAAuB,CAAC;AAsX/B,wBAAgB,aAAa,IAAI,eAAe,CA2X/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
@@ -34,6 +34,8 @@ import { gateCommand } from "./commands/gate.command.js";
34
34
  import { migrateCommand } from "./commands/migrate.command.js";
35
35
  import { coverageCommand } from "./commands/coverage.command.js";
36
36
  import { statsCommand } from "./commands/stats.command.js";
37
+ import { compressCommand, expandCommand } from "./commands/compress.command.js";
38
+ import { alignCommand, unalignCommand } from "./commands/cache-align.command.js";
37
39
  import { reviewCommand } from "./commands/review.command.js";
38
40
  import { onboardCommand } from "./commands/onboard.command.js";
39
41
  import { contradictionsCommand, generatedCommand, ingestCommand, } from "./commands/ingest.command.js";
@@ -196,6 +198,10 @@ export function buildRegistry() {
196
198
  registry.register(migrateCommand);
197
199
  registry.register(coverageCommand);
198
200
  registry.register(statsCommand);
201
+ registry.register(compressCommand);
202
+ registry.register(expandCommand);
203
+ registry.register(alignCommand);
204
+ registry.register(unalignCommand);
199
205
  registry.register(reviewCommand);
200
206
  registry.register(onboardCommand);
201
207
  registry.register(ingestCommand);
@@ -816,6 +822,39 @@ function printDidYouMean(attempted) {
816
822
  if (footer)
817
823
  process.stderr.write(renderErrorFooter(footer));
818
824
  }
825
+ /**
826
+ * Point fd 2 (stderr) at a log file so native-runtime teardown noise written
827
+ * during process exit lands in a file instead of the user's terminal. Returns
828
+ * silently on any failure (the worst case is the pre-existing noisy stderr).
829
+ *
830
+ * The log path can be overridden with `SHRK_NATIVE_TEARDOWN_LOG`; default is
831
+ * `<tmpdir>/shrk-native-teardown.log`. We append, with a timestamped header,
832
+ * so the trace is recoverable for debugging without ever touching the console.
833
+ */
834
+ async function redirectStderrToTeardownLog() {
835
+ try {
836
+ const fs = await import('node:fs');
837
+ const os = await import('node:os');
838
+ const path = await import('node:path');
839
+ const logPath = process.env.SHRK_NATIVE_TEARDOWN_LOG?.trim() ||
840
+ path.join(os.tmpdir(), 'shrk-native-teardown.log');
841
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
842
+ // Close fd 2; the next open() reclaims the lowest free descriptor (2),
843
+ // so all subsequent stderr — including native C++ writes during
844
+ // `__cxa_finalize` — flows to the log file.
845
+ fs.closeSync(2);
846
+ const fd = fs.openSync(logPath, 'a');
847
+ if (fd !== 2) {
848
+ // Couldn't reclaim fd 2 — leave things as they are rather than risk
849
+ // writing the result to the wrong descriptor.
850
+ return;
851
+ }
852
+ fs.writeSync(2, `\n--- shrk native-runtime teardown @ ${new Date().toISOString()} ---\n`);
853
+ }
854
+ catch {
855
+ // Best-effort containment; never let log redirection break the exit.
856
+ }
857
+ }
819
858
  // Entry point when invoked directly.
820
859
  //
821
860
  // Bun exposes `import.meta.main`; Node does not. When Node runs the
@@ -830,6 +869,10 @@ if (isMain ||
830
869
  entryPath.endsWith('shrk') ||
831
870
  entryPath.endsWith('shrk.js') ||
832
871
  entryPath.endsWith('shrk.cmd')) {
872
+ // Marks a real CLI invocation (vs. a command handler imported directly by a
873
+ // test). Commands that re-exec themselves in an isolated child gate on this
874
+ // so unit tests calling `run()` in-process never spawn a subprocess.
875
+ process.env.SHRK_CLI = '1';
833
876
  loadDotenv(process.cwd());
834
877
  const argv = process.argv.slice(2);
835
878
  const cleanShutdown = async (code) => {
@@ -839,10 +882,16 @@ if (isMain ||
839
882
  // work completed — the user sees their result then `zsh: abort`.
840
883
  // Dynamic imports keep these off the hot path for commands that
841
884
  // never touched them.
885
+ // Track whether any native runtime (ONNX via embeddings, Metal/ggml via
886
+ // node-llama-cpp) was actually loaded this run. If so, its static
887
+ // destructors can still abort with a backtrace during `exit()` below —
888
+ // and there is no JS hook in this Node version to skip libc++ finalizers.
889
+ // We contain that by redirecting fd 2 to a log file just before exit.
890
+ let nativeRuntimeLoaded = false;
842
891
  try {
843
892
  const mod = (await import('@shrkcrft/embeddings'));
844
893
  if (typeof mod.disposeSemanticIndexPipeline === 'function') {
845
- await mod.disposeSemanticIndexPipeline();
894
+ nativeRuntimeLoaded = (await mod.disposeSemanticIndexPipeline()) || nativeRuntimeLoaded;
846
895
  }
847
896
  }
848
897
  catch {
@@ -851,7 +900,7 @@ if (isMain ||
851
900
  try {
852
901
  const mod = (await import('@shrkcrft/ai'));
853
902
  if (typeof mod.disposeLlamaCppRuntime === 'function') {
854
- await mod.disposeLlamaCppRuntime();
903
+ nativeRuntimeLoaded = (await mod.disposeLlamaCppRuntime()) || nativeRuntimeLoaded;
855
904
  }
856
905
  }
857
906
  catch {
@@ -869,6 +918,29 @@ if (isMain ||
869
918
  catch {
870
919
  // ignore flush failures
871
920
  }
921
+ // When running as an isolated worker (e.g. the smart-context child), hand
922
+ // the real exit code back to the parent via a sentinel file. The native
923
+ // teardown abort below would otherwise clobber it with SIGABRT (134).
924
+ const exitCodeFile = process.env.SHRK_WORKER_EXITCODE_FILE;
925
+ if (exitCodeFile) {
926
+ try {
927
+ const fs = await import('node:fs');
928
+ fs.writeFileSync(exitCodeFile, String(code), 'utf8');
929
+ }
930
+ catch {
931
+ // best-effort; parent falls back to the child's signal/code.
932
+ }
933
+ }
934
+ // Contain native-runtime teardown noise. The ggml/ONNX destructors write a
935
+ // backtrace + `libc++abi: terminating … mutex lock failed` straight to fd 2
936
+ // during `exit()`, AFTER our real output is already on screen. That bypasses
937
+ // any JS stream wrapper, so the only reliable way to keep it off the user's
938
+ // terminal is to point fd 2 at a log file first: close(2) frees the lowest
939
+ // fd, and the next open() reclaims it. Gated on `nativeRuntimeLoaded` so
940
+ // ordinary commands keep their stderr untouched.
941
+ if (nativeRuntimeLoaded) {
942
+ await redirectStderrToTeardownLog();
943
+ }
872
944
  // Prefer a low-level exit over `process.exit` on Node. Without
873
945
  // this, libc++ static destructors run during `process.exit`, and
874
946
  // native bindings still resident in memory abort with libc++abi
@@ -1 +1 @@
1
- {"version":3,"file":"format-output.d.ts","sourceRoot":"","sources":["../../src/output/format-output.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAG3F;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE7C;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GAAG,MAAM,CAWlE;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB"}
1
+ {"version":3,"file":"format-output.d.ts","sourceRoot":"","sources":["../../src/output/format-output.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAG3F;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAO7C;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GAAG,MAAM,CAWlE;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB"}
@@ -9,7 +9,12 @@ export function kv(key, value) {
9
9
  return ` ${key.padEnd(18)} ${v}`;
10
10
  }
11
11
  export function asJson(value) {
12
- return JSON.stringify(value, null, 2);
12
+ // Minified by default: `--json` output is for machine / agent consumption, so
13
+ // we emit the smallest valid JSON (mirrors the MCP wire's default). The shape
14
+ // is unchanged — only whitespace is removed — so `JSON.parse` consumers are
15
+ // unaffected. Set SHRK_JSON_PRETTY=1 for human-readable 2-space indentation.
16
+ const pretty = process.env.SHRK_JSON_PRETTY === '1' || process.env.SHRK_JSON_PRETTY === 'true';
17
+ return pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value);
13
18
  }
14
19
  export function table(rows) {
15
20
  if (rows.length === 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/cli",
3
- "version": "0.1.0-alpha.14",
3
+ "version": "0.1.0-alpha.16",
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,38 @@
47
47
  "typecheck": "tsc --noEmit -p tsconfig.json"
48
48
  },
49
49
  "dependencies": {
50
- "@shrkcrft/core": "^0.1.0-alpha.14",
51
- "@shrkcrft/config": "^0.1.0-alpha.14",
52
- "@shrkcrft/workspace": "^0.1.0-alpha.14",
53
- "@shrkcrft/knowledge": "^0.1.0-alpha.14",
54
- "@shrkcrft/context": "^0.1.0-alpha.14",
55
- "@shrkcrft/rules": "^0.1.0-alpha.14",
56
- "@shrkcrft/paths": "^0.1.0-alpha.14",
57
- "@shrkcrft/templates": "^0.1.0-alpha.14",
58
- "@shrkcrft/plugin-api": "^0.1.0-alpha.14",
59
- "@shrkcrft/dashboard": "^0.1.0-alpha.14",
60
- "@shrkcrft/dashboard-api": "^0.1.0-alpha.14",
61
- "@shrkcrft/pipelines": "^0.1.0-alpha.14",
62
- "@shrkcrft/presets": "^0.1.0-alpha.14",
63
- "@shrkcrft/boundaries": "^0.1.0-alpha.14",
64
- "@shrkcrft/graph": "^0.1.0-alpha.14",
65
- "@shrkcrft/rule-graph": "^0.1.0-alpha.14",
66
- "@shrkcrft/structural-search": "^0.1.0-alpha.14",
67
- "@shrkcrft/impact-engine": "^0.1.0-alpha.14",
68
- "@shrkcrft/context-planner": "^0.1.0-alpha.14",
69
- "@shrkcrft/architecture-guard": "^0.1.0-alpha.14",
70
- "@shrkcrft/framework-scanners": "^0.1.0-alpha.14",
71
- "@shrkcrft/api-surface-diff": "^0.1.0-alpha.14",
72
- "@shrkcrft/quality-gates": "^0.1.0-alpha.14",
73
- "@shrkcrft/migrate": "^0.1.0-alpha.14",
74
- "@shrkcrft/generator": "^0.1.0-alpha.14",
75
- "@shrkcrft/importer": "^0.1.0-alpha.14",
76
- "@shrkcrft/inspector": "^0.1.0-alpha.14",
77
- "@shrkcrft/ai": "^0.1.0-alpha.14",
78
- "@shrkcrft/embeddings": "^0.1.0-alpha.14",
79
- "@shrkcrft/shared": "^0.1.0-alpha.14",
80
- "@shrkcrft/mcp-server": "^0.1.0-alpha.14",
50
+ "@shrkcrft/core": "^0.1.0-alpha.16",
51
+ "@shrkcrft/compress": "^0.1.0-alpha.16",
52
+ "@shrkcrft/config": "^0.1.0-alpha.16",
53
+ "@shrkcrft/workspace": "^0.1.0-alpha.16",
54
+ "@shrkcrft/knowledge": "^0.1.0-alpha.16",
55
+ "@shrkcrft/context": "^0.1.0-alpha.16",
56
+ "@shrkcrft/rules": "^0.1.0-alpha.16",
57
+ "@shrkcrft/paths": "^0.1.0-alpha.16",
58
+ "@shrkcrft/templates": "^0.1.0-alpha.16",
59
+ "@shrkcrft/plugin-api": "^0.1.0-alpha.16",
60
+ "@shrkcrft/dashboard": "^0.1.0-alpha.16",
61
+ "@shrkcrft/dashboard-api": "^0.1.0-alpha.16",
62
+ "@shrkcrft/pipelines": "^0.1.0-alpha.16",
63
+ "@shrkcrft/presets": "^0.1.0-alpha.16",
64
+ "@shrkcrft/boundaries": "^0.1.0-alpha.16",
65
+ "@shrkcrft/graph": "^0.1.0-alpha.16",
66
+ "@shrkcrft/rule-graph": "^0.1.0-alpha.16",
67
+ "@shrkcrft/structural-search": "^0.1.0-alpha.16",
68
+ "@shrkcrft/impact-engine": "^0.1.0-alpha.16",
69
+ "@shrkcrft/context-planner": "^0.1.0-alpha.16",
70
+ "@shrkcrft/architecture-guard": "^0.1.0-alpha.16",
71
+ "@shrkcrft/framework-scanners": "^0.1.0-alpha.16",
72
+ "@shrkcrft/api-surface-diff": "^0.1.0-alpha.16",
73
+ "@shrkcrft/quality-gates": "^0.1.0-alpha.16",
74
+ "@shrkcrft/migrate": "^0.1.0-alpha.16",
75
+ "@shrkcrft/generator": "^0.1.0-alpha.16",
76
+ "@shrkcrft/importer": "^0.1.0-alpha.16",
77
+ "@shrkcrft/inspector": "^0.1.0-alpha.16",
78
+ "@shrkcrft/ai": "^0.1.0-alpha.16",
79
+ "@shrkcrft/embeddings": "^0.1.0-alpha.16",
80
+ "@shrkcrft/shared": "^0.1.0-alpha.16",
81
+ "@shrkcrft/mcp-server": "^0.1.0-alpha.16",
81
82
  "@huggingface/transformers": "^3.7.5"
82
83
  },
83
84
  "publishConfig": {