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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/command-registry.d.ts +10 -0
  2. package/dist/command-registry.d.ts.map +1 -1
  3. package/dist/command-registry.js +7 -1
  4. package/dist/commands/command-catalog.d.ts.map +1 -1
  5. package/dist/commands/command-catalog.js +12 -0
  6. package/dist/commands/compress.command.d.ts +0 -7
  7. package/dist/commands/compress.command.d.ts.map +1 -1
  8. package/dist/commands/compress.command.js +7 -0
  9. package/dist/commands/delegate.command.d.ts +65 -0
  10. package/dist/commands/delegate.command.d.ts.map +1 -0
  11. package/dist/commands/delegate.command.js +657 -0
  12. package/dist/commands/deps-audit.command.js +1 -1
  13. package/dist/commands/doctor.command.d.ts.map +1 -1
  14. package/dist/commands/doctor.command.js +24 -3
  15. package/dist/commands/gen.command.d.ts.map +1 -1
  16. package/dist/commands/gen.command.js +13 -1
  17. package/dist/commands/graph-code-subverbs.d.ts +22 -0
  18. package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
  19. package/dist/commands/graph-code-subverbs.js +476 -55
  20. package/dist/commands/graph.command.d.ts.map +1 -1
  21. package/dist/commands/graph.command.js +9 -3
  22. package/dist/commands/help.command.d.ts.map +1 -1
  23. package/dist/commands/help.command.js +7 -18
  24. package/dist/commands/knowledge-author.command.d.ts.map +1 -1
  25. package/dist/commands/knowledge-author.command.js +9 -0
  26. package/dist/commands/knowledge-propose.command.d.ts.map +1 -1
  27. package/dist/commands/knowledge-propose.command.js +4 -2
  28. package/dist/commands/knowledge.command.d.ts.map +1 -1
  29. package/dist/commands/knowledge.command.js +22 -2
  30. package/dist/commands/move-plan.command.js +1 -1
  31. package/dist/commands/preflight.command.d.ts.map +1 -1
  32. package/dist/commands/preflight.command.js +15 -0
  33. package/dist/commands/recommend.command.d.ts +6 -0
  34. package/dist/commands/recommend.command.d.ts.map +1 -1
  35. package/dist/commands/recommend.command.js +72 -0
  36. package/dist/commands/rules.command.d.ts.map +1 -1
  37. package/dist/commands/rules.command.js +20 -3
  38. package/dist/commands/smart-context.command.d.ts +26 -17
  39. package/dist/commands/smart-context.command.d.ts.map +1 -1
  40. package/dist/commands/smart-context.command.js +113 -16
  41. package/dist/commands/tests.command.d.ts.map +1 -1
  42. package/dist/commands/tests.command.js +13 -2
  43. package/dist/dashboard/code-intelligence-data.d.ts.map +1 -1
  44. package/dist/dashboard/code-intelligence-data.js +25 -3
  45. package/dist/main.d.ts.map +1 -1
  46. package/dist/main.js +3 -1
  47. package/dist/output/ccr-store-config.d.ts +1 -1
  48. package/dist/output/ccr-store-config.d.ts.map +1 -1
  49. package/dist/output/ccr-store-config.js +21 -2
  50. package/package.json +33 -33
@@ -7,7 +7,7 @@ import { buildContext } from '@shrkcrft/context';
7
7
  import { EdgeKind, GraphQueryApi, GraphStore, NodeKind } from '@shrkcrft/graph';
8
8
  import { buildProjectOverview, buildTaskPacket, inspectSharkcraft, renderOverviewText, } from '@shrkcrft/inspector';
9
9
  import { flagBool, flagList, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
10
- import { DeclarationKind, PLAN_CACHE_SCHEMA, PlanCache, SemanticIndex, TaskType, buildFocusedContext, classifyTask, encodeEmbedding, getDefaultSourceRoots, listIndexableFiles, parseTaskTypeOverride, renderFocusedContextForPrompt, } from '@shrkcrft/embeddings';
10
+ import { DeclarationKind, PLAN_CACHE_SCHEMA, PlanCache, SemanticIndex, TaskType, buildFocusedContext, classifyTask, encodeEmbedding, getDefaultSourceRoots, listIndexableFiles, parseTaskTypeOverride, pruneDeletedHits, renderFocusedContextForPrompt, } from '@shrkcrft/embeddings';
11
11
  import { SmartContextDetailedPlanSchema, SmartContextExpansionRequestSchema, } from "../schemas/json-schemas.js";
12
12
  import { asJson, header, kv } from "../output/format-output.js";
13
13
  import { printError } from "../output/print-error.js";
@@ -40,8 +40,16 @@ const SMART_CONTEXT_DIR = nodePath.join('.sharkcraft', 'smart-context');
40
40
  * - `smart-context list` — list saved entries.
41
41
  * - `smart-context show <slug>` — print a saved entry.
42
42
  */
43
+ const SMART_CONTEXT_BOOLEAN_FLAGS = new Set([
44
+ 'ai-plan', 'brief', 'debug', 'dry-run', 'enhance', 'fix-plan', 'focused',
45
+ 'json', 'log-prompt', 'no-cache', 'no-enhance', 'no-instructions',
46
+ 'no-polish', 'no-refresh-index', 'no-stale-check', 'only-plan', 'plan',
47
+ 'plus', 'rebuild', 'refresh', 'save', 'save-conversation', 'stream',
48
+ 'tiny-only',
49
+ ]);
43
50
  export const smartContextCommand = {
44
51
  name: 'smart-context',
52
+ booleanFlags: SMART_CONTEXT_BOOLEAN_FLAGS,
45
53
  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).',
46
54
  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]',
47
55
  async run(args) {
@@ -173,8 +181,31 @@ export const smartContextCommand = {
173
181
  model: opts.model,
174
182
  });
175
183
  if (!aiResult.ok) {
176
- printError(aiResult.error);
177
- return 1;
184
+ // Deterministic-always contract (CLAUDE.md): a provider failure must
185
+ // still return the deterministic seed brief and exit 0 — not nothing on
186
+ // exit 1. The agent that asked for fast grounding still gets usable
187
+ // rules / paths / templates / candidate files. The error is advisory on
188
+ // stderr (never stdout, so --json stays valid).
189
+ process.stderr.write('[smart-context] provider unavailable — returning deterministic context only.\n');
190
+ const fallbackEnvelope = buildEnvelope({
191
+ task,
192
+ seed,
193
+ ai: {
194
+ content: renderSeed(seed),
195
+ model: 'deterministic',
196
+ finishReason: 'deterministic-fallback',
197
+ usage: null,
198
+ providerId: 'deterministic',
199
+ },
200
+ mode: opts.mode,
201
+ });
202
+ if (opts.save) {
203
+ const saved = saveEnvelope(cwd, fallbackEnvelope);
204
+ writeSavedNotice(saved, opts.json, fallbackEnvelope);
205
+ return 0;
206
+ }
207
+ writeEnvelope(fallbackEnvelope, opts.json, opts.debug);
208
+ return 0;
178
209
  }
179
210
  if (opts.saveConversation) {
180
211
  const path = writeConversationFile({
@@ -1340,6 +1371,7 @@ function readCommonOptions(args) {
1340
1371
  ...(flagString(args, 'instructions') ? { instructionsPath: flagString(args, 'instructions') } : {}),
1341
1372
  noInstructions: flagBool(args, 'no-instructions'),
1342
1373
  noRefreshIndex: flagBool(args, 'no-refresh-index'),
1374
+ refresh: flagBool(args, 'refresh'),
1343
1375
  noCache: flagBool(args, 'no-cache'),
1344
1376
  cacheReplayThreshold: flagNumber(args, 'cache-replay-threshold') ?? 0.95,
1345
1377
  cacheReferenceThreshold: flagNumber(args, 'cache-reference-threshold') ?? 0.75,
@@ -1362,6 +1394,21 @@ function readCommonOptions(args) {
1362
1394
  : {}),
1363
1395
  };
1364
1396
  }
1397
+ /**
1398
+ * Render the one-line freshness warning for a stale semantic index — including
1399
+ * how many deleted-file suggestions were dropped from the results this query.
1400
+ * Returns null when the index is current (no noise). Pure + testable.
1401
+ */
1402
+ export function renderIndexFreshnessWarning(f) {
1403
+ if (!f || f.behind <= 0)
1404
+ return null;
1405
+ const pruned = f.prunedDeleted && f.prunedDeleted > 0
1406
+ ? ` ${f.prunedDeleted} deleted-file suggestion(s) were dropped from the list above.`
1407
+ : '';
1408
+ return (`> ⚠ Semantic index is ${f.behind} file(s) behind the working tree ` +
1409
+ `(${f.stale} changed, ${f.missing} deleted, ${f.untracked} new).${pruned} ` +
1410
+ 'Verify the file suggestions above before editing — run `shrk smart-context --refresh` to rebuild.');
1411
+ }
1365
1412
  async function buildSmartContextSeed(input) {
1366
1413
  const { cwd, task, inspection, options } = input;
1367
1414
  const overview = buildProjectOverview(inspection.workspace, inspection.config?.projectName);
@@ -1385,6 +1432,7 @@ async function buildSmartContextSeed(input) {
1385
1432
  documentationHits: collectDocumentationHits(cwd, tokenizeTask(task), 10),
1386
1433
  semanticCandidates: semantic.hits,
1387
1434
  semanticModel: semantic.model,
1435
+ ...(semantic.freshness ? { indexFreshness: semantic.freshness } : {}),
1388
1436
  };
1389
1437
  }
1390
1438
  const AUTO_REFRESH_FILE_CAP = 30;
@@ -2159,22 +2207,30 @@ function collectChangedPathsWithNeighbors(cwd, gitRef) {
2159
2207
  }
2160
2208
  async function tryLoadSemanticHits(cwd, task, k, options) {
2161
2209
  if (isSemanticAutomationDisabled()) {
2162
- return { hits: [], model: null, index: null };
2210
+ return { hits: [], model: null, index: null, freshness: null };
2163
2211
  }
2164
2212
  try {
2165
2213
  const index = await SemanticIndex.tryLoad(cwd);
2166
2214
  if (!index) {
2167
2215
  maybePrintMissingIndexHint(options);
2168
- return { hits: [], model: null, index: null };
2216
+ return { hits: [], model: null, index: null, freshness: null };
2169
2217
  }
2218
+ let freshness = null;
2170
2219
  if (!options.noRefreshIndex && !options.dryRun) {
2171
- await maybeAutoRefresh(cwd, index, options);
2220
+ freshness = await maybeAutoRefresh(cwd, index, options);
2221
+ }
2222
+ // Over-fetch, then DROP hits whose file no longer exists on disk — a stale
2223
+ // embedding index must never suggest a deleted file (the reason an agent
2224
+ // would fall back to grep). The freshness block separately reports the drift.
2225
+ const rawHits = await index.searchFiles(task, Math.min(Math.max(k * 2, k), 200));
2226
+ const { hits, prunedDeleted } = pruneDeletedHits(rawHits, cwd, k);
2227
+ if (freshness && prunedDeleted > 0) {
2228
+ freshness = { ...freshness, prunedDeleted };
2172
2229
  }
2173
- const hits = await index.searchFiles(task, k);
2174
- return { hits, model: index.modelName, index };
2230
+ return { hits, model: index.modelName, index, freshness };
2175
2231
  }
2176
2232
  catch {
2177
- return { hits: [], model: null, index: null };
2233
+ return { hits: [], model: null, index: null, freshness: null };
2178
2234
  }
2179
2235
  }
2180
2236
  async function lookupPlanCache(cwd, task, options) {
@@ -2200,17 +2256,35 @@ async function lookupPlanCache(cwd, task, options) {
2200
2256
  return { replay: null, reference: null, embedding: null, index: null };
2201
2257
  }
2202
2258
  }
2259
+ function freshnessFrom(report, refreshed) {
2260
+ const behind = report.stale + report.missing + report.untracked;
2261
+ return {
2262
+ indexed: report.indexed,
2263
+ behind,
2264
+ stale: report.stale,
2265
+ missing: report.missing,
2266
+ untracked: report.untracked,
2267
+ refreshed,
2268
+ ...(behind > 0 && !refreshed
2269
+ ? { nextCommand: 'shrk smart-context --refresh' }
2270
+ : {}),
2271
+ };
2272
+ }
2203
2273
  async function maybeAutoRefresh(cwd, index, options) {
2204
2274
  const current = listIndexableFiles(cwd, 5000);
2205
2275
  const report = SemanticIndex.freshnessReport(cwd, current);
2206
2276
  const driftCount = report.stale + report.missing + report.untracked;
2207
2277
  if (driftCount === 0)
2208
- return;
2209
- if (driftCount > AUTO_REFRESH_FILE_CAP) {
2210
- if (!options.json) {
2211
- process.stderr.write(`[smart-context] semantic index drifted by ${driftCount} files — too many for auto-refresh. Run \`shrk smart-context embeddings-build\`.\n`);
2212
- }
2213
- return;
2278
+ return freshnessFrom(report, false);
2279
+ // `--refresh` forces an incremental rebuild regardless of the auto cap, so an
2280
+ // agent can bring the index current on demand (mirrors `graph --refresh`).
2281
+ const forced = options.refresh === true;
2282
+ if (!forced && driftCount > AUTO_REFRESH_FILE_CAP) {
2283
+ // Advisory on STDERR (never stdout, so --json stays valid). The honest
2284
+ // `indexFreshness` block in the brief/envelope carries the same signal to
2285
+ // the JSON consumer, which is exactly the agent that needs it.
2286
+ process.stderr.write(`[smart-context] semantic index ${driftCount} files behind — too many for auto-refresh. Run \`shrk smart-context --refresh\` (or \`embeddings-build\`).\n`);
2287
+ return freshnessFrom(report, false);
2214
2288
  }
2215
2289
  const entries = current.map((path) => ({
2216
2290
  path,
@@ -2219,8 +2293,11 @@ async function maybeAutoRefresh(cwd, index, options) {
2219
2293
  }));
2220
2294
  const refreshReport = await index.refresh(entries);
2221
2295
  if (!options.json && (refreshReport.added + refreshReport.changed + refreshReport.removed) > 0) {
2222
- process.stderr.write(`[smart-context] auto-refreshed semantic index: +${refreshReport.added} ~${refreshReport.changed} -${refreshReport.removed} (unchanged ${refreshReport.unchanged}).\n`);
2296
+ process.stderr.write(`[smart-context] refreshed semantic index: +${refreshReport.added} ~${refreshReport.changed} -${refreshReport.removed} (unchanged ${refreshReport.unchanged}).\n`);
2223
2297
  }
2298
+ // Recompute against the now-updated index so `indexFreshness` is accurate.
2299
+ const after = SemanticIndex.freshnessReport(cwd, listIndexableFiles(cwd, 5000));
2300
+ return freshnessFrom(after, true);
2224
2301
  }
2225
2302
  let missingIndexHintShown = false;
2226
2303
  function maybePrintMissingIndexHint(options) {
@@ -2566,6 +2643,12 @@ function renderSeed(seed) {
2566
2643
  }
2567
2644
  lines.push('');
2568
2645
  }
2646
+ // Honest freshness signal: a stale embedding index returns suggestions for
2647
+ // moved/deleted/never-indexed files. Surface it so the agent verifies (or
2648
+ // rebuilds) instead of trusting silently-stale grounding.
2649
+ const freshnessWarning = renderIndexFreshnessWarning(seed.indexFreshness);
2650
+ if (freshnessWarning)
2651
+ lines.push(freshnessWarning, '');
2569
2652
  lines.push('# Knowledge context (engine-ranked, token-budgeted)');
2570
2653
  lines.push(seed.contextBody.trim());
2571
2654
  return lines.join('\n');
@@ -2810,6 +2893,7 @@ function buildEnvelope(input) {
2810
2893
  recommendedCommands: input.seed.packet.recommendedCliCommands,
2811
2894
  },
2812
2895
  content: input.content ?? input.ai.content,
2896
+ ...(input.seed.indexFreshness ? { indexFreshness: input.seed.indexFreshness } : {}),
2813
2897
  ...(input.aiPlan ? { aiPlan: input.aiPlan } : {}),
2814
2898
  ...(input.enhancement ? { enhancement: input.enhancement } : {}),
2815
2899
  };
@@ -2941,11 +3025,24 @@ function readSavedIndex(cwd) {
2941
3025
  for (const name of readdirSync(dir)) {
2942
3026
  if (!name.endsWith('.json'))
2943
3027
  continue;
3028
+ // Skip sidecar files (a saved entry also writes <slug>.raw.json /
3029
+ // .plan.json / .conversation.json) so `list` doesn't show them as rows.
3030
+ if (/\.(raw|plan|conversation)\.json$/.test(name))
3031
+ continue;
2944
3032
  const jsonPath = nodePath.join(dir, name);
2945
3033
  try {
2946
3034
  if (!statSync(jsonPath).isFile())
2947
3035
  continue;
2948
3036
  const env = JSON.parse(readFileSync(jsonPath, 'utf8'));
3037
+ // Shape-guard: only real smart-context envelopes — other commands dump
3038
+ // foreign JSON (audit-/fix-/pipelines-…) into this dir that merely parses
3039
+ // as JSON and otherwise renders as `[undefined] undefined` noise.
3040
+ if (typeof env.task !== 'string' ||
3041
+ typeof env.mode !== 'string' ||
3042
+ typeof env.savedAt !== 'string' ||
3043
+ typeof env.content !== 'string') {
3044
+ continue;
3045
+ }
2949
3046
  const slugBase = name.replace(/\.json$/, '');
2950
3047
  const mdPath = nodePath.join(dir, `${slugBase}.md`);
2951
3048
  out.push({
@@ -1 +1 @@
1
- {"version":3,"file":"tests.command.d.ts","sourceRoot":"","sources":["../../src/commands/tests.command.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiChC,eAAO,MAAM,kBAAkB,EAAE,eAyBhC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eAajC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eAgBjC,CAAC"}
1
+ {"version":3,"file":"tests.command.d.ts","sourceRoot":"","sources":["../../src/commands/tests.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAwChC,eAAO,MAAM,kBAAkB,EAAE,eAyBhC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eAajC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eAgBjC,CAAC"}
@@ -1,13 +1,24 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import * as nodePath from 'node:path';
3
- import { analyzeTestImpact, inspectSharkcraft, readFeatureBundle, suggestTestPathFor, } from '@shrkcrft/inspector';
3
+ import { analyzeTestImpact, getChangedFiles, inspectSharkcraft, readFeatureBundle, suggestTestPathFor, } from '@shrkcrft/inspector';
4
4
  import { flagBool, flagString, flagList, resolveCwd, } from "../command-registry.js";
5
5
  import { asJson, header } from "../output/format-output.js";
6
6
  function collectFiles(cwd, args) {
7
7
  const files = flagList(args, 'files');
8
8
  const planFile = flagString(args, 'plan');
9
9
  const bundleId = flagString(args, 'bundle');
10
+ const since = flagString(args, 'since');
11
+ const staged = flagBool(args, 'staged');
10
12
  const out = [...files];
13
+ // `--since <ref>` / `--staged`: collect changed files straight from git so
14
+ // the agent's reflex `shrk tests impact --since main` works the same way as
15
+ // `shrk impact --since` (was --files-only, forcing a manual diff).
16
+ if (since)
17
+ for (const f of getChangedFiles(cwd, { since }))
18
+ out.push(f);
19
+ if (staged)
20
+ for (const f of getChangedFiles(cwd, { staged: true }))
21
+ out.push(f);
11
22
  if (planFile) {
12
23
  const path = nodePath.isAbsolute(planFile) ? planFile : nodePath.join(cwd, planFile);
13
24
  if (existsSync(path)) {
@@ -38,7 +49,7 @@ function collectFiles(cwd, args) {
38
49
  export const testsImpactCommand = {
39
50
  name: 'impact',
40
51
  description: 'Test impact analysis for changed files / plan / bundle.',
41
- usage: 'shrk tests impact [--files a,b] [--plan <plan>] [--bundle <id>] "<task>"',
52
+ usage: 'shrk tests impact [--files a,b] [--since <ref>] [--staged] [--plan <plan>] [--bundle <id>] "<task>"',
42
53
  async run(args) {
43
54
  const cwd = resolveCwd(args);
44
55
  const inspection = await inspectSharkcraft({ cwd });
@@ -1 +1 @@
1
- {"version":3,"file":"code-intelligence-data.d.ts","sourceRoot":"","sources":["../../src/dashboard/code-intelligence-data.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,kCAAkC,EAElC,4BAA4B,EAE5B,8BAA8B,EAC9B,wBAAwB,EAEzB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,kCAAkC,CAuCtG;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,wBAAwB,CAyClF;AA0ED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,4BAA4B,CAuE1F;AAaD,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,8BAA8B,CAgD9F"}
1
+ {"version":3,"file":"code-intelligence-data.d.ts","sourceRoot":"","sources":["../../src/dashboard/code-intelligence-data.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,kCAAkC,EAGlC,4BAA4B,EAE5B,8BAA8B,EAC9B,wBAAwB,EAEzB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,kCAAkC,CAuCtG;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,wBAAwB,CAyClF;AAmGD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,4BAA4B,CAuE1F;AAaD,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,8BAA8B,CAgD9F"}
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import * as nodePath from 'node:path';
3
3
  import { runArchCheck } from '@shrkcrft/architecture-guard';
4
4
  import { FrameworkQueryApi } from '@shrkcrft/framework-scanners';
5
- import { GraphStore } from '@shrkcrft/graph';
5
+ import { detectGraphFreshness, GraphQueryApi, GraphStore } from '@shrkcrft/graph';
6
6
  import { findResumePoint, } from '@shrkcrft/migrate';
7
7
  import { QualityGateReportStore, runQualityGates } from '@shrkcrft/quality-gates';
8
8
  import { BridgeStore } from '@shrkcrft/rule-graph';
@@ -27,7 +27,7 @@ export function buildDashboardCodeIntelligence(projectRoot) {
27
27
  // Graph.
28
28
  const graphStore = new GraphStore(projectRoot);
29
29
  const graph = graphStore.exists()
30
- ? readGraphSection(graphStore)
30
+ ? readGraphSection(graphStore, projectRoot)
31
31
  : { available: false, hint: "run 'shrk graph index'" };
32
32
  // Bridge.
33
33
  const bridgeStore = new BridgeStore(projectRoot);
@@ -100,8 +100,23 @@ export function buildDashboardRoutes(projectRoot) {
100
100
  commandHints,
101
101
  };
102
102
  }
103
- function readGraphSection(store) {
103
+ function readGraphSection(store, projectRoot) {
104
104
  const snap = store.loadSnapshot();
105
+ const api = new GraphQueryApi(snap);
106
+ const hubs = api.topHubs(8);
107
+ const toRow = (h) => ({
108
+ id: h.node.id,
109
+ label: h.node.label,
110
+ ...(h.node.path ? { path: h.node.path } : {}),
111
+ inDegree: h.inDegree,
112
+ });
113
+ // Freshness vs the working tree — the same signal `shrk graph status` reports.
114
+ // `corrupt` (store self-integrity) outranks `stale` (disk drift): a digest
115
+ // failure means the counts themselves can't be trusted.
116
+ const fresh = detectGraphFreshness(projectRoot);
117
+ const behind = fresh.modified.length + fresh.added.length + fresh.deleted.length;
118
+ const verify = store.verifyDigest();
119
+ const state = !verify.ok ? 'corrupt' : behind > 0 ? 'stale' : 'fresh';
105
120
  return {
106
121
  available: true,
107
122
  fileCount: snap.manifest.filesIndexed,
@@ -111,6 +126,13 @@ function readGraphSection(store) {
111
126
  lastIndexedAt: snap.manifest.lastIndexedAt,
112
127
  nodesByKind: snap.manifest.nodesByKind,
113
128
  edgesByKind: snap.manifest.edgesByKind,
129
+ freshness: {
130
+ state,
131
+ modified: fresh.modified.length,
132
+ added: fresh.added.length,
133
+ deleted: fresh.deleted.length,
134
+ },
135
+ hubs: { symbols: hubs.symbols.map(toRow), files: hubs.files.map(toRow) },
114
136
  };
115
137
  }
116
138
  function readBridgeSection(store) {
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAKhB,MAAM,uBAAuB,CAAC;AAuX/B,wBAAgB,aAAa,IAAI,eAAe,CA2X/C;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BrE;AAoHD;;;;;;;;;;;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,EAKhB,MAAM,uBAAuB,CAAC;AAwX/B,wBAAgB,aAAa,IAAI,eAAe,CA4X/C;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BrE;AAsHD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA4CxE"}
package/dist/main.js CHANGED
@@ -50,6 +50,7 @@ import { contextCommand } from "./commands/context.command.js";
50
50
  import { diffParentCommand, diffRoundsCommand, roundsCaptureCommand, roundsListCommand, roundsParentCommand, roundsShowCommand, } from "./commands/rounds.command.js";
51
51
  import { genCommand } from "./commands/gen.command.js";
52
52
  import { applyCommand } from "./commands/apply.command.js";
53
+ import { delegateCommand } from "./commands/delegate.command.js";
53
54
  import { groundingCommand } from "./commands/grounding.command.js";
54
55
  import { planCheckCommand } from "./commands/plan-check.command.js";
55
56
  import { whyCommand } from "./commands/why.command.js";
@@ -155,6 +156,7 @@ export function buildRegistry() {
155
156
  registry.register(contextCommand);
156
157
  registry.register(genCommand);
157
158
  registry.register(applyCommand);
159
+ registry.register(delegateCommand);
158
160
  // `shrk grounding` thin context primer.
159
161
  registry.register(groundingCommand);
160
162
  // feedback3 — `shrk why <file>` (closes the dangling ide-suggested verb).
@@ -611,7 +613,7 @@ async function runCliInner(argv) {
611
613
  process.stderr.write(renderSurfaceNotEnabledText(err));
612
614
  return SURFACE_NOT_ENABLED_EXIT_CODE;
613
615
  }
614
- return await handler.run(parseArgs(leftover, { globalCwd }));
616
+ return await handler.run(parseArgs(leftover, { globalCwd, booleanFlags: handler.booleanFlags }));
615
617
  }
616
618
  // No handler at the deepest match. If we landed on a group node (has
617
619
  // children), show that group's help so the user discovers the verbs.
@@ -8,7 +8,7 @@ import { TtlFileCcrStore } from '@shrkcrft/compress';
8
8
  * the cap is exceeded.
9
9
  */
10
10
  export declare const CCR_MAX_ENTRIES = 1000;
11
- /** Absolute path to the per-workspace CCR cache directory. */
11
+ /** Absolute path to the per-project CCR cache directory (project-root-relative). */
12
12
  export declare function ccrDir(cwd: string): string;
13
13
  /**
14
14
  * Open the bounded, cross-process CCR store the CLI write/read paths share.
@@ -1 +1 @@
1
- {"version":3,"file":"ccr-store-config.d.ts","sourceRoot":"","sources":["../../src/output/ccr-store-config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,8DAA8D;AAC9D,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAEzD"}
1
+ {"version":3,"file":"ccr-store-config.d.ts","sourceRoot":"","sources":["../../src/output/ccr-store-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAmBrD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,oFAAoF;AACpF,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAEzD"}
@@ -1,5 +1,24 @@
1
+ import { existsSync } from 'node:fs';
1
2
  import * as nodePath from 'node:path';
2
3
  import { TtlFileCcrStore } from '@shrkcrft/compress';
4
+ /**
5
+ * Walk up from `cwd` to the nearest ancestor containing a `.sharkcraft/` dir
6
+ * (the project root), so `compress` and `expand` share ONE cache per project —
7
+ * a `<<ccr:KEY>>` cached at the repo root stays recoverable from any subdir
8
+ * instead of being unrecoverable because `expand` looked in `<subdir>/.sharkcraft/ccr`.
9
+ * Falls back to `cwd` when no project root is found (a fresh repo).
10
+ */
11
+ function ccrRoot(cwd) {
12
+ let dir = nodePath.resolve(cwd);
13
+ for (;;) {
14
+ if (existsSync(nodePath.join(dir, '.sharkcraft')))
15
+ return dir;
16
+ const parent = nodePath.dirname(dir);
17
+ if (parent === dir)
18
+ return cwd; // reached the filesystem root
19
+ dir = parent;
20
+ }
21
+ }
3
22
  /**
4
23
  * Upper bound on cached CCR originals under `.sharkcraft/ccr/`. The store evicts
5
24
  * the oldest entries past this on every `put`, so the compress cache stays
@@ -9,9 +28,9 @@ import { TtlFileCcrStore } from '@shrkcrft/compress';
9
28
  * the cap is exceeded.
10
29
  */
11
30
  export const CCR_MAX_ENTRIES = 1000;
12
- /** Absolute path to the per-workspace CCR cache directory. */
31
+ /** Absolute path to the per-project CCR cache directory (project-root-relative). */
13
32
  export function ccrDir(cwd) {
14
- return nodePath.join(cwd, '.sharkcraft', 'ccr');
33
+ return nodePath.join(ccrRoot(cwd), '.sharkcraft', 'ccr');
15
34
  }
16
35
  /**
17
36
  * Open the bounded, cross-process CCR store the CLI write/read paths share.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/cli",
3
- "version": "0.1.0-alpha.17",
3
+ "version": "0.1.0-alpha.19",
4
4
  "description": "SharkCraft CLI (`shrk`): structured project intelligence for AI coding agents.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -47,38 +47,38 @@
47
47
  "typecheck": "tsc --noEmit -p tsconfig.json"
48
48
  },
49
49
  "dependencies": {
50
- "@shrkcrft/core": "^0.1.0-alpha.17",
51
- "@shrkcrft/compress": "^0.1.0-alpha.17",
52
- "@shrkcrft/config": "^0.1.0-alpha.17",
53
- "@shrkcrft/workspace": "^0.1.0-alpha.17",
54
- "@shrkcrft/knowledge": "^0.1.0-alpha.17",
55
- "@shrkcrft/context": "^0.1.0-alpha.17",
56
- "@shrkcrft/rules": "^0.1.0-alpha.17",
57
- "@shrkcrft/paths": "^0.1.0-alpha.17",
58
- "@shrkcrft/templates": "^0.1.0-alpha.17",
59
- "@shrkcrft/plugin-api": "^0.1.0-alpha.17",
60
- "@shrkcrft/dashboard": "^0.1.0-alpha.17",
61
- "@shrkcrft/dashboard-api": "^0.1.0-alpha.17",
62
- "@shrkcrft/pipelines": "^0.1.0-alpha.17",
63
- "@shrkcrft/presets": "^0.1.0-alpha.17",
64
- "@shrkcrft/boundaries": "^0.1.0-alpha.17",
65
- "@shrkcrft/graph": "^0.1.0-alpha.17",
66
- "@shrkcrft/rule-graph": "^0.1.0-alpha.17",
67
- "@shrkcrft/structural-search": "^0.1.0-alpha.17",
68
- "@shrkcrft/impact-engine": "^0.1.0-alpha.17",
69
- "@shrkcrft/context-planner": "^0.1.0-alpha.17",
70
- "@shrkcrft/architecture-guard": "^0.1.0-alpha.17",
71
- "@shrkcrft/framework-scanners": "^0.1.0-alpha.17",
72
- "@shrkcrft/api-surface-diff": "^0.1.0-alpha.17",
73
- "@shrkcrft/quality-gates": "^0.1.0-alpha.17",
74
- "@shrkcrft/migrate": "^0.1.0-alpha.17",
75
- "@shrkcrft/generator": "^0.1.0-alpha.17",
76
- "@shrkcrft/importer": "^0.1.0-alpha.17",
77
- "@shrkcrft/inspector": "^0.1.0-alpha.17",
78
- "@shrkcrft/ai": "^0.1.0-alpha.17",
79
- "@shrkcrft/embeddings": "^0.1.0-alpha.17",
80
- "@shrkcrft/shared": "^0.1.0-alpha.17",
81
- "@shrkcrft/mcp-server": "^0.1.0-alpha.17",
50
+ "@shrkcrft/core": "^0.1.0-alpha.19",
51
+ "@shrkcrft/compress": "^0.1.0-alpha.19",
52
+ "@shrkcrft/config": "^0.1.0-alpha.19",
53
+ "@shrkcrft/workspace": "^0.1.0-alpha.19",
54
+ "@shrkcrft/knowledge": "^0.1.0-alpha.19",
55
+ "@shrkcrft/context": "^0.1.0-alpha.19",
56
+ "@shrkcrft/rules": "^0.1.0-alpha.19",
57
+ "@shrkcrft/paths": "^0.1.0-alpha.19",
58
+ "@shrkcrft/templates": "^0.1.0-alpha.19",
59
+ "@shrkcrft/plugin-api": "^0.1.0-alpha.19",
60
+ "@shrkcrft/dashboard": "^0.1.0-alpha.19",
61
+ "@shrkcrft/dashboard-api": "^0.1.0-alpha.19",
62
+ "@shrkcrft/pipelines": "^0.1.0-alpha.19",
63
+ "@shrkcrft/presets": "^0.1.0-alpha.19",
64
+ "@shrkcrft/boundaries": "^0.1.0-alpha.19",
65
+ "@shrkcrft/graph": "^0.1.0-alpha.19",
66
+ "@shrkcrft/rule-graph": "^0.1.0-alpha.19",
67
+ "@shrkcrft/structural-search": "^0.1.0-alpha.19",
68
+ "@shrkcrft/impact-engine": "^0.1.0-alpha.19",
69
+ "@shrkcrft/context-planner": "^0.1.0-alpha.19",
70
+ "@shrkcrft/architecture-guard": "^0.1.0-alpha.19",
71
+ "@shrkcrft/framework-scanners": "^0.1.0-alpha.19",
72
+ "@shrkcrft/api-surface-diff": "^0.1.0-alpha.19",
73
+ "@shrkcrft/quality-gates": "^0.1.0-alpha.19",
74
+ "@shrkcrft/migrate": "^0.1.0-alpha.19",
75
+ "@shrkcrft/generator": "^0.1.0-alpha.19",
76
+ "@shrkcrft/importer": "^0.1.0-alpha.19",
77
+ "@shrkcrft/inspector": "^0.1.0-alpha.19",
78
+ "@shrkcrft/ai": "^0.1.0-alpha.19",
79
+ "@shrkcrft/embeddings": "^0.1.0-alpha.19",
80
+ "@shrkcrft/shared": "^0.1.0-alpha.19",
81
+ "@shrkcrft/mcp-server": "^0.1.0-alpha.19",
82
82
  "@huggingface/transformers": "^3.7.5"
83
83
  },
84
84
  "publishConfig": {