@shrkcrft/cli 0.1.0-alpha.1 → 0.1.0-alpha.11

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 (120) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/api-diff.command.d.ts +11 -0
  3. package/dist/commands/api-diff.command.d.ts.map +1 -0
  4. package/dist/commands/api-diff.command.js +116 -0
  5. package/dist/commands/arch.command.d.ts +9 -0
  6. package/dist/commands/arch.command.d.ts.map +1 -0
  7. package/dist/commands/arch.command.js +186 -0
  8. package/dist/commands/boundaries.command.d.ts.map +1 -1
  9. package/dist/commands/boundaries.command.js +0 -12
  10. package/dist/commands/check.command.d.ts.map +1 -1
  11. package/dist/commands/check.command.js +20 -30
  12. package/dist/commands/code-intel.command.d.ts +18 -0
  13. package/dist/commands/code-intel.command.d.ts.map +1 -0
  14. package/dist/commands/code-intel.command.js +146 -0
  15. package/dist/commands/command-catalog.d.ts +7 -3
  16. package/dist/commands/command-catalog.d.ts.map +1 -1
  17. package/dist/commands/command-catalog.js +201 -47
  18. package/dist/commands/commands.command.d.ts.map +1 -1
  19. package/dist/commands/commands.command.js +4 -4
  20. package/dist/commands/completion.command.d.ts +10 -0
  21. package/dist/commands/completion.command.d.ts.map +1 -0
  22. package/dist/commands/completion.command.js +121 -0
  23. package/dist/commands/constructs.command.d.ts.map +1 -1
  24. package/dist/commands/constructs.command.js +5 -22
  25. package/dist/commands/context.command.d.ts.map +1 -1
  26. package/dist/commands/context.command.js +89 -0
  27. package/dist/commands/diff-check.command.d.ts +30 -0
  28. package/dist/commands/diff-check.command.d.ts.map +1 -0
  29. package/dist/commands/diff-check.command.js +210 -0
  30. package/dist/commands/doctor.command.d.ts.map +1 -1
  31. package/dist/commands/doctor.command.js +42 -9
  32. package/dist/commands/export.command.d.ts.map +1 -1
  33. package/dist/commands/export.command.js +76 -3
  34. package/dist/commands/framework.command.d.ts +12 -0
  35. package/dist/commands/framework.command.d.ts.map +1 -0
  36. package/dist/commands/framework.command.js +180 -0
  37. package/dist/commands/gate.command.d.ts +15 -0
  38. package/dist/commands/gate.command.d.ts.map +1 -0
  39. package/dist/commands/gate.command.js +296 -0
  40. package/dist/commands/graph-code-subverbs.d.ts +11 -0
  41. package/dist/commands/graph-code-subverbs.d.ts.map +1 -0
  42. package/dist/commands/graph-code-subverbs.js +818 -0
  43. package/dist/commands/graph.command.d.ts.map +1 -1
  44. package/dist/commands/graph.command.js +22 -0
  45. package/dist/commands/help.command.d.ts +4 -3
  46. package/dist/commands/help.command.d.ts.map +1 -1
  47. package/dist/commands/help.command.js +77 -21
  48. package/dist/commands/helper.command.js +1 -1
  49. package/dist/commands/impact.command.d.ts.map +1 -1
  50. package/dist/commands/impact.command.js +170 -1
  51. package/dist/commands/import.command.d.ts.map +1 -1
  52. package/dist/commands/import.command.js +121 -5
  53. package/dist/commands/init.command.d.ts.map +1 -1
  54. package/dist/commands/init.command.js +184 -16
  55. package/dist/commands/mcp.command.d.ts.map +1 -1
  56. package/dist/commands/mcp.command.js +2 -131
  57. package/dist/commands/migrate.command.d.ts +13 -0
  58. package/dist/commands/migrate.command.d.ts.map +1 -0
  59. package/dist/commands/migrate.command.js +152 -0
  60. package/dist/commands/onboard.command.d.ts.map +1 -1
  61. package/dist/commands/onboard.command.js +3 -15
  62. package/dist/commands/packs-new.d.ts +1 -1
  63. package/dist/commands/packs-new.d.ts.map +1 -1
  64. package/dist/commands/packs-new.js +5 -36
  65. package/dist/commands/packs.command.d.ts.map +1 -1
  66. package/dist/commands/packs.command.js +3 -17
  67. package/dist/commands/plan-context.command.d.ts +11 -0
  68. package/dist/commands/plan-context.command.d.ts.map +1 -0
  69. package/dist/commands/plan-context.command.js +77 -0
  70. package/dist/commands/profiles.command.js +4 -4
  71. package/dist/commands/release.command.js +13 -13
  72. package/dist/commands/review.command.d.ts.map +1 -1
  73. package/dist/commands/review.command.js +2 -28
  74. package/dist/commands/rule-graph-subverbs.d.ts +3 -0
  75. package/dist/commands/rule-graph-subverbs.d.ts.map +1 -0
  76. package/dist/commands/rule-graph-subverbs.js +132 -0
  77. package/dist/commands/search-structural.command.d.ts +18 -0
  78. package/dist/commands/search-structural.command.d.ts.map +1 -0
  79. package/dist/commands/search-structural.command.js +376 -0
  80. package/dist/commands/search.command.js +1 -1
  81. package/dist/commands/task-context.command.js +0 -16
  82. package/dist/commands/task.command.d.ts.map +1 -1
  83. package/dist/commands/task.command.js +8 -2
  84. package/dist/dashboard/code-intelligence-data.d.ts +33 -0
  85. package/dist/dashboard/code-intelligence-data.d.ts.map +1 -0
  86. package/dist/dashboard/code-intelligence-data.js +307 -0
  87. package/dist/dashboard/dashboard-api-server.d.ts.map +1 -1
  88. package/dist/dashboard/dashboard-api-server.js +162 -1
  89. package/dist/export/claude-commands-export.d.ts +60 -0
  90. package/dist/export/claude-commands-export.d.ts.map +1 -0
  91. package/dist/export/claude-commands-export.js +276 -0
  92. package/dist/export/export-formats.d.ts +1 -1
  93. package/dist/export/export-formats.d.ts.map +1 -1
  94. package/dist/export/export-formats.js +139 -12
  95. package/dist/init/init-templates.d.ts.map +1 -1
  96. package/dist/init/init-templates.js +133 -113
  97. package/dist/init/paths-advisory.d.ts +20 -0
  98. package/dist/init/paths-advisory.d.ts.map +1 -0
  99. package/dist/init/paths-advisory.js +88 -0
  100. package/dist/main.d.ts +1 -1
  101. package/dist/main.d.ts.map +1 -1
  102. package/dist/main.js +137 -46
  103. package/dist/output/failure-hints.d.ts +1 -9
  104. package/dist/output/failure-hints.d.ts.map +1 -1
  105. package/dist/output/failure-hints.js +2 -8
  106. package/dist/output/watch-loop.d.ts +9 -1
  107. package/dist/output/watch-loop.d.ts.map +1 -1
  108. package/dist/output/watch-loop.js +13 -3
  109. package/dist/schemas/json-schemas.d.ts +36 -36
  110. package/dist/schemas/json-schemas.js +36 -36
  111. package/dist/surface/about.d.ts.map +1 -1
  112. package/dist/surface/about.js +37 -15
  113. package/dist/surface/no-args-landing.d.ts.map +1 -1
  114. package/dist/surface/no-args-landing.js +9 -13
  115. package/dist/surface/surface-config-writer.d.ts.map +1 -1
  116. package/dist/surface/surface-config-writer.js +23 -11
  117. package/package.json +37 -25
  118. package/dist/commands/plugin.command.d.ts +0 -11
  119. package/dist/commands/plugin.command.d.ts.map +0 -1
  120. package/dist/commands/plugin.command.js +0 -394
@@ -1 +1 @@
1
- {"version":3,"file":"graph.command.d.ts","sourceRoot":"","sources":["../../src/commands/graph.command.ts"],"names":[],"mappings":"AAUA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAehC,eAAO,MAAM,YAAY,EAAE,eA6L1B,CAAC"}
1
+ {"version":3,"file":"graph.command.d.ts","sourceRoot":"","sources":["../../src/commands/graph.command.ts"],"names":[],"mappings":"AAUA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA0BhC,eAAO,MAAM,YAAY,EAAE,eA0M1B,CAAC"}
@@ -3,6 +3,7 @@ import * as nodePath from 'node:path';
3
3
  import { analyzeImportGraph, buildKnowledgeGraph, findGraphPath, getGraphNode, inspectSharkcraft, } from '@shrkcrft/inspector';
4
4
  import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
5
5
  import { asJson, header, kv } from "../output/format-output.js";
6
+ import { runGraphCallers, runGraphContext, runGraphCycles, runGraphDeps, runGraphImpact, runGraphIndex, runGraphSearch, runGraphStatus, runGraphUnresolved, } from "./graph-code-subverbs.js";
6
7
  const KNOWN_KINDS = [
7
8
  'knowledge',
8
9
  'rule',
@@ -19,6 +20,27 @@ export const graphCommand = {
19
20
  description: 'Show the SharkCraft knowledge graph: nodes (knowledge/rules/paths/templates/pipelines/presets/packs/boundaries) and edges (related-template, preset-references, pipeline-step-references, …). Supports text|json|dot|mermaid output and an `export` subcommand for writing to file.',
20
21
  usage: 'shrk [--cwd <dir>] graph [<id>] [--type <kind>] [--format text|json|dot|mermaid] [--output <file>] [--json] | shrk graph export --format dot|mermaid --output <file>',
21
22
  async run(args) {
23
+ // Code-intelligence subverbs (R65) don't need the knowledge graph —
24
+ // dispatch them before the expensive inspection so they stay fast.
25
+ const earlySub = args.positional[0];
26
+ if (earlySub === 'index')
27
+ return runGraphIndex(args);
28
+ if (earlySub === 'status')
29
+ return runGraphStatus(args);
30
+ if (earlySub === 'search')
31
+ return runGraphSearch(args);
32
+ if (earlySub === 'context')
33
+ return runGraphContext(args);
34
+ if (earlySub === 'impact')
35
+ return runGraphImpact(args);
36
+ if (earlySub === 'callers')
37
+ return runGraphCallers(args);
38
+ if (earlySub === 'cycles')
39
+ return runGraphCycles(args);
40
+ if (earlySub === 'unresolved')
41
+ return runGraphUnresolved(args);
42
+ if (earlySub === 'deps')
43
+ return runGraphDeps({ ...args, positional: args.positional.slice(1) });
22
44
  const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
23
45
  const graph = buildKnowledgeGraph(inspection);
24
46
  const sub = args.positional[0];
@@ -1,8 +1,9 @@
1
1
  import type { CommandRegistry } from '../command-registry.js';
2
2
  /**
3
- * Short product start screen for bare `shrk` / `shrk --help`. Pruned
4
- * to the core tier set; extended verbs live one link away via
5
- * `shrk surface list`.
3
+ * Product start screen for bare `shrk` / `shrk --help`. Shows the
4
+ * curated ~20-command "starter" surface organized by workflow phase.
5
+ * Everything else stays callable; users see the full ~70-verb catalog
6
+ * via `shrk --full-help` or browse it through `shrk surface list`.
6
7
  *
7
8
  * Returns the lines (without trailing newline). Pulled into a function
8
9
  * so tests can assert on the structure without grepping stdout.
@@ -1 +1 @@
1
- {"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAyB1C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe;;;;cAK3C;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM;EA2FpF"}
1
+ {"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAI9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAiD1C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe;;;;cAK3C;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM;EAyHpF"}
@@ -1,33 +1,59 @@
1
1
  import { header } from "../output/format-output.js";
2
+ import { COMMAND_CATALOG, defaultShowInHelp } from "./command-catalog.js";
2
3
  /**
3
- * Short product start screen for bare `shrk` / `shrk --help`. Pruned
4
- * to the core tier set; extended verbs live one link away via
5
- * `shrk surface list`.
4
+ * Product start screen for bare `shrk` / `shrk --help`. Shows the
5
+ * curated ~20-command "starter" surface organized by workflow phase.
6
+ * Everything else stays callable; users see the full ~70-verb catalog
7
+ * via `shrk --full-help` or browse it through `shrk surface list`.
6
8
  *
7
9
  * Returns the lines (without trailing newline). Pulled into a function
8
10
  * so tests can assert on the structure without grepping stdout.
9
11
  */
10
12
  export function renderStartScreen() {
11
13
  const lines = [];
12
- lines.push('SharkCraft CLI — the safety layer beneath your AI coding agent.');
14
+ lines.push('SharkCraft CLI — deterministic, local-first project intelligence for AI coding agents.');
13
15
  lines.push('Usage: shrk [--cwd <dir>] <command> [...args]');
14
16
  lines.push('');
15
- lines.push('Prove it in 60 seconds:');
16
- lines.push(' $ shrk check boundaries --changed-only --since main');
17
- lines.push(' $ shrk review packet --v3 --since main');
18
- lines.push(' $ shrk mcp install claude-code copy-paste the MCP entry');
19
- lines.push('');
20
- lines.push('Core (always on):');
17
+ lines.push('Bootstrap:');
18
+ lines.push(' $ shrk init --infer --write — scan the repo + populate sharkcraft/ from real signals (recommended for new repos)');
19
+ lines.push(' $ shrk import claude-md ./CLAUDE.md --populate --write — populate sharkcraft/ from existing CLAUDE.md / AGENTS.md / .cursor/rules');
20
+ lines.push(' $ shrk init --with-claude-skill --write scaffold sharkcraft/ AND inline rules into .claude/skills/ (one-step)');
21
+ lines.push(' $ shrk init — scaffold sharkcraft/ + config skeleton (preset defaults)');
21
22
  lines.push(' $ shrk doctor — is the workspace healthy?');
22
- lines.push(' $ shrk recommend "<task>" what command should I reach for?');
23
- lines.push(' $ shrk init create sharkcraft/ + config skeleton');
24
- lines.push(' $ shrk surface list — every command grouped by tier');
23
+ lines.push(' $ shrk inspect detect frameworks, paths, package manager');
24
+ lines.push(' $ shrk onboard analyze an existing repo (advisory)');
25
+ lines.push('');
26
+ lines.push('Use it for a task:');
27
+ lines.push(' $ shrk brief — single-page brief Claude reads first (project + rules + paths + verification)');
28
+ lines.push(' $ shrk recommend "<task>" — what should I do?');
29
+ lines.push(' $ shrk context --task "<task>" — token-budgeted relevant context');
30
+ lines.push(' $ shrk task "<task>" — full AI-ready task packet (JSON)');
31
+ lines.push(' $ shrk coverage — what knowledge is missing');
32
+ lines.push('');
33
+ lines.push('Generate code safely:');
34
+ lines.push(' $ shrk gen <template> <name> — generate from template (dry-run by default)');
35
+ lines.push(' $ shrk apply <plan.json> — apply a reviewed plan (CLI is the only write path)');
36
+ lines.push(' $ shrk check boundaries — enforce layer / import boundaries');
37
+ lines.push(' $ shrk quality — pre-PR gate (doctor + boundaries + coverage + drift)');
25
38
  lines.push('');
26
- lines.push('Discover the rest (extended tier — always callable):');
27
- lines.push(' $ shrk surface list full surface, grouped by tier and profile');
28
- lines.push(' $ shrk surface explain <cmd> why a command has its current tier');
39
+ lines.push('Browse what shrk knows:');
40
+ lines.push(' $ shrk knowledge list knowledge entries');
41
+ lines.push(' $ shrk rules list rules + conventions');
42
+ lines.push(' $ shrk templates list — generator templates');
43
+ lines.push(' $ shrk import — parse AGENTS.md / CLAUDE.md / .cursor/rules');
44
+ lines.push(' $ shrk export — render to a flat agent-rule file');
45
+ lines.push('');
46
+ lines.push('Run shrk for an agent:');
47
+ lines.push(' $ shrk export claude-skill --write — generate .claude/skills/<name>/SKILL.md (rules INTO the prompt — no MCP roundtrip)');
48
+ lines.push(' $ shrk export agents-md --write — generate AGENTS.md / CLAUDE.md / .cursor/rules / copilot-instructions');
49
+ lines.push(' $ shrk mcp serve — start the MCP server (stdio) for live queries');
50
+ lines.push(' $ shrk dashboard — start the local read-only dashboard');
51
+ lines.push('');
52
+ lines.push('Discover the rest (always callable, hidden from this screen by default):');
53
+ lines.push(' $ shrk surface list — full ~70-verb catalog by tier');
54
+ lines.push(' $ shrk surface profiles — named profiles (small-app / monorepo / ci / agent / pack-author)');
29
55
  lines.push(' $ shrk help <command> — usage for a specific command');
30
- lines.push(' $ shrk --full-help — the long, exhaustive help');
56
+ lines.push(' $ shrk --full-help — long, exhaustive help');
31
57
  lines.push(' $ shrk --about — what shrk is and is not');
32
58
  lines.push('');
33
59
  lines.push('Free-form input is fine — `shrk "<task>"` routes to `shrk recommend`.');
@@ -91,13 +117,41 @@ export function makeHelpCommand(registry) {
91
117
  process.stdout.write(renderStartScreen());
92
118
  return 0;
93
119
  }
94
- process.stdout.write(`SharkCraft CLI — the safety layer beneath your AI coding agent.\n`);
120
+ const wantsAll = args.flags.get('all') === true;
121
+ process.stdout.write(`SharkCraft CLI — structured project intelligence for AI coding agents\n`);
95
122
  process.stdout.write(`Usage: shrk [--cwd <dir>] <command> [...args]\n`);
123
+ // The catalog has ~360 entries; only ~30 top-level verbs pay
124
+ // rent (see PRIMARY_VERBS_ALLOWLIST in command-catalog.ts).
125
+ // Default --full-help filters to that set; `--full-help --all`
126
+ // dumps the entire catalog for power users.
127
+ const visibleVerbs = new Set();
128
+ if (!wantsAll) {
129
+ for (const entry of COMMAND_CATALOG) {
130
+ if (defaultShowInHelp(entry)) {
131
+ const verb = entry.command.split(/\s+/)[0] ?? '';
132
+ if (verb)
133
+ visibleVerbs.add(verb);
134
+ }
135
+ }
136
+ }
137
+ const visibleTopLevel = registry.list().filter((c) => {
138
+ if (wantsAll)
139
+ return true;
140
+ return visibleVerbs.has(c.name);
141
+ });
96
142
  process.stdout.write(header('Top-level commands'));
97
- for (const c of registry.list()) {
143
+ for (const c of visibleTopLevel) {
98
144
  process.stdout.write(` ${c.name.padEnd(10)} — ${c.description}\n`);
99
145
  }
100
- // Show each canonical group once.
146
+ if (!wantsAll) {
147
+ const hiddenCount = registry.list().length - visibleTopLevel.length;
148
+ if (hiddenCount > 0) {
149
+ process.stdout.write(`\n …and ${hiddenCount} more, hidden from default help. Run \`shrk --full-help --all\` to see them, ` +
150
+ `or \`shrk surface list\` to browse by tier.\n`);
151
+ }
152
+ }
153
+ // Show each canonical group once — also filtered by the allowlist
154
+ // unless --all was passed.
101
155
  const canonicalGroups = [];
102
156
  const aliasMap = registry.listGroupAliases();
103
157
  const seen = new Set();
@@ -105,7 +159,9 @@ export function makeHelpCommand(registry) {
105
159
  const canonical = aliasMap.get(g) ?? g;
106
160
  if (!seen.has(canonical)) {
107
161
  seen.add(canonical);
108
- canonicalGroups.push(canonical);
162
+ if (wantsAll || visibleVerbs.has(canonical)) {
163
+ canonicalGroups.push(canonical);
164
+ }
109
165
  }
110
166
  }
111
167
  const aliasesByCanonical = new Map();
@@ -85,7 +85,7 @@ export const helperPlanCommand = {
85
85
  // profile is available. Today the helper-registry detects this inside
86
86
  // buildHelperPlan via requireProfile(); we surface a friendly message.
87
87
  if ('requiresProfile' in def && def.requiresProfile) {
88
- process.stderr.write(`Helper "${id}" requires a plugin-lifecycle profile. Available:\n $ shrk plugin lifecycle profiles\n`);
88
+ process.stderr.write(`Helper "${id}" requires a registered profile. Available:\n $ shrk profiles list\n`);
89
89
  // Still try to build the plan; the registry throws with the same idea.
90
90
  }
91
91
  const cwd = resolveCwd(args);
@@ -1 +1 @@
1
- {"version":3,"file":"impact.command.d.ts","sourceRoot":"","sources":["../../src/commands/impact.command.ts"],"names":[],"mappings":"AAsBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA0QhC,eAAO,MAAM,aAAa,EAAE,eAoV3B,CAAC"}
1
+ {"version":3,"file":"impact.command.d.ts","sourceRoot":"","sources":["../../src/commands/impact.command.ts"],"names":[],"mappings":"AAsBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAyVhC,eAAO,MAAM,aAAa,EAAE,eA8V3B,CAAC"}
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import * as nodePath from 'node:path';
3
3
  import { analyzeImpact, analyzeTestImpact, findSymbolInProject, FuzzyImpactSourceKind, getChangedFiles, ImpactInputKind, inspectSharkcraft, QueryMatchKind, readFeatureBundle, renderImpactGraph, renderImpactHtml, renderImpactMarkdown, renderImpactText, resolveFuzzyImpact, warmConstructCache, } from '@shrkcrft/inspector';
4
4
  import { flagBool, flagNumber, flagString, flagList, resolveCwd, } from "../command-registry.js";
5
- import { asJson, header } from "../output/format-output.js";
5
+ import { asJson, header, kv } from "../output/format-output.js";
6
6
  function collectFiles(args, cwd) {
7
7
  const diagnostics = [];
8
8
  const explicitFiles = flagList(args, 'files');
@@ -199,6 +199,84 @@ async function emitCodemodPlan(opts) {
199
199
  }
200
200
  return 0;
201
201
  }
202
+ /**
203
+ * --via-graph adapter: takes the same input flags as `shrk impact` and
204
+ * routes through `@shrkcrft/impact-engine` for the v3 graph-backed
205
+ * payload. Defers to that engine; emits a clean error when the graph
206
+ * isn't indexed yet.
207
+ */
208
+ async function runViaGraph(args) {
209
+ const cwd = resolveCwd(args);
210
+ const wantJson = flagBool(args, 'json') || flagString(args, 'format') === 'json';
211
+ const positional = args.positional[0];
212
+ const files = flagList(args, 'files');
213
+ const since = flagString(args, 'since');
214
+ const symbol = flagString(args, 'symbol');
215
+ const fileFlag = flagString(args, 'file');
216
+ const limit = flagNumber(args, 'limit') ?? 200;
217
+ const maxDepth = flagNumber(args, 'max-depth') ?? 5;
218
+ const target = positional ?? fileFlag;
219
+ const inputs = files.length > 0
220
+ ? { kind: 'files', files }
221
+ : symbol
222
+ ? { kind: 'symbol', symbolId: symbol }
223
+ : since
224
+ ? { kind: 'gitref', ref: since }
225
+ : target
226
+ ? { kind: 'files', files: [target] }
227
+ : undefined;
228
+ if (!inputs) {
229
+ process.stderr.write('Usage: shrk impact --via-graph <fileOrQuery> | --symbol <name> | --files a,b | --since <ref>\n');
230
+ return 2;
231
+ }
232
+ const { analyzeGraphImpact, ImpactReportStore, snapshotImpactAnalysis } = await import('@shrkcrft/impact-engine');
233
+ const analysis = analyzeGraphImpact(inputs, { projectRoot: cwd, limit, maxDepth });
234
+ // Persist a compact snapshot for the doctor + dashboard to read.
235
+ // `--no-persist` opts out (useful when scripting against many trees
236
+ // or when stdout is the only sink the caller cares about).
237
+ const noPersist = flagBool(args, 'no-persist');
238
+ if (!noPersist) {
239
+ try {
240
+ const summary = inputs.kind === 'files'
241
+ ? inputs.files.slice(0, 3).join(', ') + (inputs.files.length > 3 ? '…' : '')
242
+ : inputs.kind === 'symbol'
243
+ ? `symbol:${inputs.symbolId}`
244
+ : `gitref:${inputs.ref}`;
245
+ new ImpactReportStore(cwd).write(snapshotImpactAnalysis(analysis, summary));
246
+ }
247
+ catch {
248
+ // best-effort — never fail the command on a persistence error
249
+ }
250
+ }
251
+ if (wantJson) {
252
+ process.stdout.write(asJson(analysis) + '\n');
253
+ return 0;
254
+ }
255
+ process.stdout.write(header(`Impact (graph): ${target ?? symbol ?? files.join(',') ?? since}`));
256
+ process.stdout.write(` risk: ${analysis.risk}\n`);
257
+ process.stdout.write(` direct dependents: ${analysis.directDependents.length}\n`);
258
+ process.stdout.write(` transitive dependents: ${analysis.transitiveDependents.length}\n`);
259
+ process.stdout.write(` affected symbols: ${analysis.affectedSymbols.length}\n`);
260
+ process.stdout.write(` caller files: ${analysis.affectedCallerFiles.length}\n`);
261
+ process.stdout.write(` affected packages: ${analysis.affectedPackages.length}\n`);
262
+ process.stdout.write(` affected rules: ${analysis.affectedRules.length}\n`);
263
+ process.stdout.write(` affected templates: ${analysis.affectedTemplates.length}\n`);
264
+ process.stdout.write(` likely tests: ${analysis.likelyTests.length}\n`);
265
+ process.stdout.write(` public API touched: ${analysis.publicApiTouched ? 'yes' : 'no'}\n`);
266
+ if (analysis.riskReasons.length > 0) {
267
+ process.stdout.write('\nRisk reasons:\n');
268
+ for (const r of analysis.riskReasons)
269
+ process.stdout.write(` • ${r}\n`);
270
+ }
271
+ if (analysis.validationScope.length > 0) {
272
+ process.stdout.write('\nRun before merging:\n');
273
+ for (const c of analysis.validationScope)
274
+ process.stdout.write(` $ ${c}\n`);
275
+ }
276
+ for (const d of analysis.diagnostics.slice(0, 5))
277
+ process.stdout.write(`! ${d}\n`);
278
+ return 0;
279
+ }
202
280
  export const impactCommand = {
203
281
  name: 'impact',
204
282
  description: 'Architecture impact analysis: direct + transitive dependents, risk + suggested commands. Supports fuzzy <query> resolution. Read-only.',
@@ -210,6 +288,16 @@ export const impactCommand = {
210
288
  if (args.positional[0] === 'graph') {
211
289
  return runImpactGraph({ ...args, positional: args.positional.slice(1) });
212
290
  }
291
+ if (args.positional[0] === 'baseline') {
292
+ return runImpactBaseline({ ...args, positional: args.positional.slice(1) });
293
+ }
294
+ // --via-graph: route through @shrkcrft/impact-engine for the v3
295
+ // graph-backed payload (sharkcraft.graph-impact-analysis/v3). Keeps
296
+ // the legacy v2 inspector path as the default so existing
297
+ // consumers don't shift.
298
+ if (flagBool(args, 'via-graph')) {
299
+ return runViaGraph(args);
300
+ }
213
301
  const cwd = resolveCwd(args);
214
302
  const inspection = await inspectSharkcraft({ cwd });
215
303
  // Warm construct cache so fuzzy resolution can map plugin keys / events /
@@ -817,3 +905,84 @@ function renderResolutionMd(r) {
817
905
  }
818
906
  // Sentinel — keep `QueryMatchKind` import alive for tooling. Used inside fuzzy-impact.ts.
819
907
  void QueryMatchKind;
908
+ async function runImpactBaseline(args) {
909
+ const cwd = resolveCwd(args);
910
+ const wantJson = flagBool(args, 'json');
911
+ const verb = args.positional[0] ?? 'show';
912
+ const { ImpactReportStore, diffImpactReports } = await import('@shrkcrft/impact-engine');
913
+ const store = new ImpactReportStore(cwd);
914
+ if (verb === 'write') {
915
+ const last = store.read();
916
+ if (!last) {
917
+ const msg = `No recent impact run at ${store.absPath}. Run \`shrk impact --via-graph <target>\` first.\n`;
918
+ if (wantJson) {
919
+ process.stdout.write(asJson({ ok: false, error: 'no-last-run' }) + '\n');
920
+ return 1;
921
+ }
922
+ process.stderr.write(msg);
923
+ return 1;
924
+ }
925
+ store.writeBaseline(last);
926
+ if (wantJson) {
927
+ process.stdout.write(asJson({ wrote: store.baselinePath, baseline: last }) + '\n');
928
+ return 0;
929
+ }
930
+ process.stdout.write(`Impact baseline written → ${store.baselinePath}\n`);
931
+ process.stdout.write(` Input: ${last.inputSummary}\n` +
932
+ ` Risk: ${last.risk}\n` +
933
+ ` Dependents: ${last.directDependentCount} direct, ${last.transitiveDependentCount} transitive\n` +
934
+ ` Packages: ${last.affectedPackageCount}\n`);
935
+ return 0;
936
+ }
937
+ if (verb === 'show') {
938
+ const baseline = store.readBaseline();
939
+ if (!baseline) {
940
+ const msg = `No baseline at ${store.baselinePath}. Run \`shrk impact baseline write\` to freeze the current run.\n`;
941
+ if (wantJson) {
942
+ process.stdout.write(asJson({ baseline: null, path: store.baselinePath }) + '\n');
943
+ return 1;
944
+ }
945
+ process.stderr.write(msg);
946
+ return 1;
947
+ }
948
+ const last = store.read();
949
+ if (wantJson) {
950
+ process.stdout.write(asJson({
951
+ path: store.baselinePath,
952
+ baseline,
953
+ ...(last ? { last, delta: diffImpactReports(baseline, last) } : {}),
954
+ }) + '\n');
955
+ return 0;
956
+ }
957
+ process.stdout.write(header('Impact baseline'));
958
+ process.stdout.write(kv('path', store.baselinePath) + '\n');
959
+ process.stdout.write(kv('input', baseline.inputSummary) + '\n');
960
+ process.stdout.write(kv('risk', baseline.risk) + '\n');
961
+ process.stdout.write(kv('dependents', `${baseline.directDependentCount} direct, ${baseline.transitiveDependentCount} transitive`) + '\n');
962
+ process.stdout.write(kv('packages', String(baseline.affectedPackageCount)) + '\n');
963
+ if (last) {
964
+ const d = diffImpactReports(baseline, last);
965
+ process.stdout.write('\nDelta (last − baseline): ' +
966
+ `dependents ${d.dependentDelta >= 0 ? '+' : ''}${d.dependentDelta}, ` +
967
+ `packages ${d.packageDelta >= 0 ? '+' : ''}${d.packageDelta}` +
968
+ (d.riskDrift ? `, risk ${d.riskDrift}` : '') +
969
+ (d.worsened ? ' ✗ worsened' : ' ✓ within baseline') +
970
+ '\n');
971
+ }
972
+ else {
973
+ process.stdout.write('\n(no recent `last.json` — run `shrk impact --via-graph` to populate it.)\n');
974
+ }
975
+ return 0;
976
+ }
977
+ if (verb === 'clear') {
978
+ const removed = store.clearBaseline();
979
+ if (wantJson) {
980
+ process.stdout.write(asJson({ removed, path: store.baselinePath }) + '\n');
981
+ return 0;
982
+ }
983
+ process.stdout.write(removed ? `Baseline removed: ${store.baselinePath}\n` : 'No baseline to remove.\n');
984
+ return 0;
985
+ }
986
+ process.stderr.write('Usage: shrk impact baseline <write|show|clear> [--json]\n');
987
+ return 2;
988
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"import.command.d.ts","sourceRoot":"","sources":["../../src/commands/import.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAyBhC,eAAO,MAAM,aAAa,EAAE,eA8G3B,CAAC"}
1
+ {"version":3,"file":"import.command.d.ts","sourceRoot":"","sources":["../../src/commands/import.command.ts"],"names":[],"mappings":"AAUA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAyBhC,eAAO,MAAM,aAAa,EAAE,eA8H3B,CAAC"}
@@ -1,8 +1,8 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import * as nodePath from 'node:path';
3
- import { emitKnowledgeTs, importAgentsMd, importClaudeMd, importCursorRules, } from '@shrkcrft/importer';
3
+ import { emitKnowledgeTs, importAgentsMd, importClaudeMd, importCursorRules, synthesizePopulatedFromImport, } from '@shrkcrft/importer';
4
4
  import { flagBool, flagString, flagList, resolveCwd, } from "../command-registry.js";
5
- import { asJson, header, kv } from "../output/format-output.js";
5
+ import { asJson, bullet, header, kv } from "../output/format-output.js";
6
6
  function repeatedFlagList(args, name) {
7
7
  return flagList(args, name, { dedupe: true });
8
8
  }
@@ -22,8 +22,8 @@ function defaultOutput(format) {
22
22
  }
23
23
  export const importCommand = {
24
24
  name: 'import',
25
- description: 'Parse existing agent rule files (AGENTS.md / CLAUDE.md / .cursor/rules) into a structured @shrkcrft/knowledge TypeScript draft. Dry-run by default; --write saves the draft to sharkcraft/imports/.',
26
- usage: 'shrk [--cwd <dir>] import <format> [<path>] [--prefix <id>] [--tag <t>] [--scope <s>] [--output <file>] [--write] [--force] [--json]',
25
+ description: 'Parse existing agent rule files (AGENTS.md / CLAUDE.md / .cursor/rules) into shrk\'s structure. By default (`--write`), saves a single draft TS file under sharkcraft/imports/ for the user to adopt by hand. Pass `--populate` to route entries directly into sharkcraft/rules.ts + paths.ts + knowledge.ts (by type) with a confidence triage report — same shape as `shrk init --infer`. Both modes dry-run by default.',
26
+ usage: 'shrk [--cwd <dir>] import <format> [<path>] [--prefix <id>] [--tag <t>] [--scope <s>] [--output <file>] [--write] [--populate] [--force] [--json]',
27
27
  async run(args) {
28
28
  const format = args.positional[0];
29
29
  if (!format || !KNOWN_FORMATS.includes(format)) {
@@ -57,9 +57,24 @@ export const importCommand = {
57
57
  }
58
58
  const outPath = nodePath.resolve(cwd, flagString(args, 'output') ?? flagString(args, 'out') ?? defaultOutput(fmt));
59
59
  const write = flagBool(args, 'write');
60
+ const populate = flagBool(args, 'populate');
60
61
  const tsSource = emitKnowledgeTs(result.entries, {
61
62
  sourceLabel: `${fmt} (${result.sourceFiles.join(', ') || path})`,
62
63
  });
64
+ // ── `--populate` path: distribute entries into populated
65
+ // sharkcraft/* files (rules.ts / paths.ts / knowledge.ts) with
66
+ // confidence triage. Mirrors `shrk init --infer`'s contract.
67
+ if (populate) {
68
+ return runPopulateImport({
69
+ cwd,
70
+ fmt,
71
+ path,
72
+ result,
73
+ write,
74
+ force: flagBool(args, 'force'),
75
+ wantJson: flagBool(args, 'json'),
76
+ });
77
+ }
63
78
  if (flagBool(args, 'json')) {
64
79
  process.stdout.write(asJson({
65
80
  format: fmt,
@@ -113,3 +128,104 @@ export const importCommand = {
113
128
  return 0;
114
129
  },
115
130
  };
131
+ /**
132
+ * `shrk import <format> --populate` path. Routes parsed entries
133
+ * into populated `sharkcraft/*.ts` files by KnowledgeType, with
134
+ * confidence triage and a companion `.imported-report.md`. Same
135
+ * shape as `shrk init --infer`.
136
+ */
137
+ function runPopulateImport(args) {
138
+ const { cwd, fmt, path, result, write, force, wantJson } = args;
139
+ const sharkcraftDir = nodePath.join(cwd, 'sharkcraft');
140
+ const projectName = readProjectName(cwd) ?? 'project';
141
+ const description = `Imported from ${fmt} (${path})`;
142
+ const populated = synthesizePopulatedFromImport(result.entries, {
143
+ projectName,
144
+ description,
145
+ sourceLabel: `${fmt} (${result.sourceFiles.join(', ') || path})`,
146
+ });
147
+ if (wantJson) {
148
+ process.stdout.write(asJson({
149
+ format: fmt,
150
+ source: path,
151
+ mode: write ? 'populate' : 'populate-dry-run',
152
+ sharkcraftDir,
153
+ entryCount: result.entries.length,
154
+ warnings: result.warnings,
155
+ files: populated.files.map((f) => ({ path: f.path, kind: f.kind })),
156
+ report: populated.report,
157
+ }) + '\n');
158
+ return 0;
159
+ }
160
+ process.stdout.write(header(`Import (${fmt}) — populate`));
161
+ process.stdout.write(kv('source', path) + '\n');
162
+ process.stdout.write(kv('files read', String(result.sourceFiles.length)) + '\n');
163
+ process.stdout.write(kv('entries parsed', String(result.entries.length)) + '\n');
164
+ process.stdout.write(kv('target', sharkcraftDir) + '\n');
165
+ process.stdout.write(kv('mode', write ? 'write' : 'dry-run (preview only)') + '\n\n');
166
+ process.stdout.write(`Triage: ${populated.report.adoptedHigh.length} adopted directly · ` +
167
+ `${populated.report.adoptedMedium.length} marked for review · ` +
168
+ `${populated.report.dropped.length} dropped (in report).\n\n`);
169
+ if (result.warnings.length > 0) {
170
+ process.stdout.write(header('Parser warnings'));
171
+ for (const w of result.warnings) {
172
+ process.stdout.write(` ${w.origin}: ${w.message}\n`);
173
+ }
174
+ process.stdout.write('\n');
175
+ }
176
+ if (!write) {
177
+ process.stdout.write('Would write:\n');
178
+ for (const f of populated.files)
179
+ process.stdout.write(bullet(f.path) + '\n');
180
+ process.stdout.write('\nRe-run with --write to persist.\n');
181
+ return 0;
182
+ }
183
+ const written = [];
184
+ const skipped = [];
185
+ for (const file of populated.files) {
186
+ const fullPath = nodePath.join(sharkcraftDir, file.path);
187
+ mkdirSync(nodePath.dirname(fullPath), { recursive: true });
188
+ if (existsSync(fullPath) && !force) {
189
+ skipped.push(file.path);
190
+ continue;
191
+ }
192
+ writeFileSync(fullPath, file.content, 'utf8');
193
+ written.push(file.path);
194
+ }
195
+ if (written.length) {
196
+ process.stdout.write('Wrote:\n');
197
+ for (const p of written)
198
+ process.stdout.write(bullet(p) + '\n');
199
+ }
200
+ if (skipped.length) {
201
+ process.stdout.write('\nSkipped (already exist; use --force to overwrite):\n');
202
+ for (const p of skipped)
203
+ process.stdout.write(bullet(p) + '\n');
204
+ }
205
+ process.stdout.write(`\nRead the import report: \`${nodePath.join('sharkcraft', '.imported-report.md')}\`\n` +
206
+ `It lists what was adopted high-confidence, what's marked for your review, ` +
207
+ `and what \`shrk import\` deliberately doesn't recover from markdown.\n`);
208
+ process.stdout.write('\nNext:\n');
209
+ process.stdout.write(bullet('$ shrk doctor — verify the populated setup') + '\n');
210
+ process.stdout.write(bullet('$ shrk brief — single-page brief Claude reads first') + '\n');
211
+ process.stdout.write(bullet('$ shrk export claude-skill --write — inline the rules into Claude\'s prompt') + '\n');
212
+ return 0;
213
+ }
214
+ /**
215
+ * Best-effort read of `package.json#name` for use as `projectName`
216
+ * in the generated config. Falls back to undefined; the synthesizer
217
+ * uses 'project' as a default.
218
+ */
219
+ function readProjectName(cwd) {
220
+ const pkgPath = nodePath.join(cwd, 'package.json');
221
+ try {
222
+ if (!existsSync(pkgPath))
223
+ return undefined;
224
+ const raw = readFileSync(pkgPath, 'utf8');
225
+ const parsed = JSON.parse(raw);
226
+ return parsed.name ?? undefined;
227
+ }
228
+ catch {
229
+ return undefined;
230
+ }
231
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"init.command.d.ts","sourceRoot":"","sources":["../../src/commands/init.command.ts"],"names":[],"mappings":"AAeA,OAAO,EAAwB,KAAK,eAAe,EAA+B,MAAM,wBAAwB,CAAC;AA4QjH,eAAO,MAAM,WAAW,EAAE,eA2FzB,CAAC"}
1
+ {"version":3,"file":"init.command.d.ts","sourceRoot":"","sources":["../../src/commands/init.command.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAwB,KAAK,eAAe,EAA+B,MAAM,wBAAwB,CAAC;AAuSjH,eAAO,MAAM,WAAW,EAAE,eA2HzB,CAAC"}