@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,5 +1,8 @@
1
1
  import { entrypointBanner, inspectSharkcraft } from '@shrkcrft/inspector';
2
2
  import { buildContext } from '@shrkcrft/context';
3
+ import { loadIntentBenchmark, runIntentBenchmark, STARTER_INTENT_BENCHMARK, writeBenchmarkRun, } from '@shrkcrft/context-planner';
4
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
5
+ import * as nodePath from 'node:path';
3
6
  import { buildUniversalSearch, explainTaskRouting, recommendCommands, renderOverviewText, buildProjectOverview, } from '@shrkcrft/inspector';
4
7
  import { flagBool, flagNumber, flagString, flagList, resolveCwd, } from "../command-registry.js";
5
8
  import { asJson, header } from "../output/format-output.js";
@@ -19,6 +22,10 @@ export const contextCommand = {
19
22
  return contextRefreshCommand.run(sliced);
20
23
  return contextStatusCommand.run(sliced);
21
24
  }
25
+ if (sub === 'benchmark') {
26
+ const sliced = { ...args, positional: args.positional.slice(1) };
27
+ return runContextBenchmark(sliced);
28
+ }
22
29
  const task = flagString(args, 'task');
23
30
  if (!task) {
24
31
  process.stderr.write('Missing --task\n');
@@ -118,3 +125,85 @@ export const contextCommand = {
118
125
  return 0;
119
126
  },
120
127
  };
128
+ async function runContextBenchmark(args) {
129
+ const cwd = resolveCwd(args);
130
+ const wantJson = flagBool(args, 'json');
131
+ // `shrk context benchmark seed` writes a starter fixture to
132
+ // sharkcraft/intent-benchmark.json. Useful first step for adopting
133
+ // the surface — fixture is opinionated but small and easy to prune.
134
+ if (args.positional[0] === 'seed') {
135
+ return runContextBenchmarkSeed(cwd, args);
136
+ }
137
+ const noPersist = flagBool(args, 'no-persist');
138
+ const benchmark = loadIntentBenchmark(cwd);
139
+ if (!benchmark) {
140
+ const msg = `No benchmark at sharkcraft/intent-benchmark.json. Create one with schema "sharkcraft.intent-benchmark/v1" and a "cases" array of { task, expected } entries.\n`;
141
+ if (wantJson) {
142
+ process.stdout.write(asJson({ ok: false, error: 'benchmark-missing' }) + '\n');
143
+ return 1;
144
+ }
145
+ process.stderr.write(msg);
146
+ return 1;
147
+ }
148
+ const run = runIntentBenchmark(benchmark);
149
+ if (!noPersist) {
150
+ try {
151
+ writeBenchmarkRun(cwd, run);
152
+ }
153
+ catch {
154
+ // best-effort
155
+ }
156
+ }
157
+ if (wantJson) {
158
+ process.stdout.write(asJson(run) + '\n');
159
+ return run.failed === 0 ? 0 : 1;
160
+ }
161
+ process.stdout.write(header('Intent classifier benchmark'));
162
+ process.stdout.write(` total ${run.total}\n`);
163
+ process.stdout.write(` passed ${run.passed}\n`);
164
+ process.stdout.write(` failed ${run.failed}\n`);
165
+ process.stdout.write(` accuracy ${Math.round(run.accuracy * 1000) / 10}%\n`);
166
+ const failures = run.cases.filter((c) => !c.passed);
167
+ if (failures.length > 0) {
168
+ process.stdout.write('\nFailures:\n');
169
+ for (const c of failures.slice(0, 20)) {
170
+ process.stdout.write(` ✗ task="${truncateTask(c.task)}" expected=${c.expected} actual=${c.actual}\n`);
171
+ }
172
+ if (failures.length > 20) {
173
+ process.stdout.write(` … (${failures.length - 20} more)\n`);
174
+ }
175
+ }
176
+ return run.failed === 0 ? 0 : 1;
177
+ }
178
+ function truncateTask(s) {
179
+ if (s.length <= 60)
180
+ return s;
181
+ return s.slice(0, 57) + '…';
182
+ }
183
+ function runContextBenchmarkSeed(cwd, args) {
184
+ const wantJson = flagBool(args, 'json');
185
+ const force = flagBool(args, 'force');
186
+ const target = nodePath.join(cwd, 'sharkcraft', 'intent-benchmark.json');
187
+ if (existsSync(target) && !force) {
188
+ const msg = `${target} already exists. Use --force to overwrite.\n`;
189
+ if (wantJson) {
190
+ process.stdout.write(asJson({ ok: false, error: 'exists', path: target }) + '\n');
191
+ return 1;
192
+ }
193
+ process.stderr.write(msg);
194
+ return 1;
195
+ }
196
+ mkdirSync(nodePath.dirname(target), { recursive: true });
197
+ writeFileSync(target, JSON.stringify(STARTER_INTENT_BENCHMARK, null, 2), 'utf8');
198
+ if (wantJson) {
199
+ process.stdout.write(asJson({
200
+ ok: true,
201
+ wrote: target,
202
+ cases: STARTER_INTENT_BENCHMARK.cases.length,
203
+ }) + '\n');
204
+ return 0;
205
+ }
206
+ process.stdout.write(`Seeded ${STARTER_INTENT_BENCHMARK.cases.length} starter intent case(s) → ${target}\n`);
207
+ process.stdout.write('Run `shrk context benchmark` to record accuracy.\n');
208
+ return 0;
209
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `shrk diff-check` — agent self-validation after edits.
3
+ *
4
+ * The story this command tells:
5
+ * 1. An AI agent (Claude Code, Cursor, etc.) makes some file changes.
6
+ * 2. Before declaring "done", the agent runs `shrk diff-check`.
7
+ * 3. The command scopes both the boundary check and the
8
+ * import-hygiene check to only the files the agent touched in the
9
+ * current git diff.
10
+ * 4. The output is a single agent-friendly JSON envelope with a
11
+ * verdict (ok / warnings / errors) and a one-line next action.
12
+ *
13
+ * Why a new command instead of "just run `shrk check boundaries
14
+ * --changed-only` and `shrk check imports --changed-only`":
15
+ *
16
+ * - One command instead of two — agents reliably run the *one* tool
17
+ * they're told to run; chained-command workflows get skipped.
18
+ * - One verdict — no need to OR two separate JSON outputs.
19
+ * - Stable, narrow schema — designed for agent consumption, not
20
+ * human terminals. Won't grow flags over time.
21
+ * - Concrete `nextAction` line — the agent knows exactly what to do
22
+ * next (declare done, fix N things, or re-run after a manual fix).
23
+ *
24
+ * This is a pure composer — all real logic stays in
25
+ * `@shrkcrft/inspector` and `@shrkcrft/boundaries`. We just stitch
26
+ * their outputs together with consistent scoping.
27
+ */
28
+ import { type ICommandHandler } from '../command-registry.js';
29
+ export declare const diffCheckCommand: ICommandHandler;
30
+ //# sourceMappingURL=diff-check.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-check.command.d.ts","sourceRoot":"","sources":["../../src/commands/diff-check.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAcH,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA0GhC,eAAO,MAAM,gBAAgB,EAAE,eAiI9B,CAAC"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * `shrk diff-check` — agent self-validation after edits.
3
+ *
4
+ * The story this command tells:
5
+ * 1. An AI agent (Claude Code, Cursor, etc.) makes some file changes.
6
+ * 2. Before declaring "done", the agent runs `shrk diff-check`.
7
+ * 3. The command scopes both the boundary check and the
8
+ * import-hygiene check to only the files the agent touched in the
9
+ * current git diff.
10
+ * 4. The output is a single agent-friendly JSON envelope with a
11
+ * verdict (ok / warnings / errors) and a one-line next action.
12
+ *
13
+ * Why a new command instead of "just run `shrk check boundaries
14
+ * --changed-only` and `shrk check imports --changed-only`":
15
+ *
16
+ * - One command instead of two — agents reliably run the *one* tool
17
+ * they're told to run; chained-command workflows get skipped.
18
+ * - One verdict — no need to OR two separate JSON outputs.
19
+ * - Stable, narrow schema — designed for agent consumption, not
20
+ * human terminals. Won't grow flags over time.
21
+ * - Concrete `nextAction` line — the agent knows exactly what to do
22
+ * next (declare done, fix N things, or re-run after a manual fix).
23
+ *
24
+ * This is a pure composer — all real logic stays in
25
+ * `@shrkcrft/inspector` and `@shrkcrft/boundaries`. We just stitch
26
+ * their outputs together with consistent scoping.
27
+ */
28
+ import { buildImportHygieneReport, filterViolationsToChangedScope, inspectSharkcraft, resolveChangedFiles, } from '@shrkcrft/inspector';
29
+ import { evaluateBoundaries, loadTsconfigPaths, scanImports, } from '@shrkcrft/boundaries';
30
+ import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
31
+ import { asJson, bullet, header, kv } from "../output/format-output.js";
32
+ const SCHEMA = 'sharkcraft.diff-check/v1';
33
+ function resolveScope(args, cwd) {
34
+ const staged = flagBool(args, 'staged');
35
+ const since = flagString(args, 'since');
36
+ const filesRaw = flagString(args, 'files');
37
+ const files = filesRaw
38
+ ? filesRaw.split(',').map((s) => s.trim()).filter((s) => s.length > 0)
39
+ : [];
40
+ if (files.length > 0) {
41
+ return { mode: 'files', options: { projectRoot: cwd, files } };
42
+ }
43
+ if (staged) {
44
+ return { mode: 'staged', options: { projectRoot: cwd, staged: true } };
45
+ }
46
+ if (since) {
47
+ return { mode: 'since', options: { projectRoot: cwd, since } };
48
+ }
49
+ // Default: worktree (== `--changed-only` from `shrk check boundaries`).
50
+ return {
51
+ mode: 'worktree',
52
+ options: { projectRoot: cwd, includeWorktree: true },
53
+ };
54
+ }
55
+ function deriveVerdict(env) {
56
+ const bErr = env.boundaries.counts.error;
57
+ const bWarn = env.boundaries.counts.warning;
58
+ const iErr = env.imports.verdict === 'errors' ? (env.imports.counts.error ?? env.imports.findings.length) : 0;
59
+ const iWarn = env.imports.verdict === 'warnings' ? (env.imports.counts.warning ?? env.imports.findings.length) : 0;
60
+ if (env.scope.fileCount === 0) {
61
+ return {
62
+ verdict: 'ok',
63
+ summary: 'No files changed in the current diff scope.',
64
+ nextAction: 'Nothing to check. If you expected changes, verify your `--staged` / `--since <ref>` flag or save your edits first.',
65
+ };
66
+ }
67
+ if (bErr > 0 || iErr > 0) {
68
+ const parts = [];
69
+ if (bErr > 0)
70
+ parts.push(`${bErr} boundary violation${bErr === 1 ? '' : 's'}`);
71
+ if (iErr > 0)
72
+ parts.push(`${iErr} import-hygiene error${iErr === 1 ? '' : 's'}`);
73
+ return {
74
+ verdict: 'errors',
75
+ summary: `Diff fails the gate: ${parts.join(', ')}.`,
76
+ nextAction: 'Fix every error in `boundaries.violations` and `imports.findings` (look at each entry\'s `suggestedFix` line), then re-run `shrk diff-check`.',
77
+ };
78
+ }
79
+ if (bWarn > 0 || iWarn > 0) {
80
+ const parts = [];
81
+ if (bWarn > 0)
82
+ parts.push(`${bWarn} boundary warning${bWarn === 1 ? '' : 's'}`);
83
+ if (iWarn > 0)
84
+ parts.push(`${iWarn} import-hygiene warning${iWarn === 1 ? '' : 's'}`);
85
+ return {
86
+ verdict: 'warnings',
87
+ summary: `Diff passes the gate with ${parts.join(', ')}.`,
88
+ nextAction: 'Safe to declare done. Review warnings if the diff touches a sensitive area; otherwise these are non-blocking.',
89
+ };
90
+ }
91
+ return {
92
+ verdict: 'ok',
93
+ summary: `Diff passes the gate (${env.scope.fileCount} file${env.scope.fileCount === 1 ? '' : 's'}, 0 violations).`,
94
+ nextAction: 'Safe to declare done.',
95
+ };
96
+ }
97
+ export const diffCheckCommand = {
98
+ name: 'diff-check',
99
+ description: 'Self-check the current git diff against this project\'s boundary + import-hygiene rules. Single-call composite of `shrk check boundaries --changed-only` + `shrk check imports --changed-only`, with one verdict and one nextAction line. Designed for AI agents to run after editing — pass --json for the structured envelope.',
100
+ usage: 'shrk [--cwd <dir>] diff-check [--staged | --since <ref> | --files a.ts,b.ts] [--json]',
101
+ async run(args) {
102
+ const cwd = resolveCwd(args);
103
+ const wantJson = flagBool(args, 'json');
104
+ const { mode, options: scopeOptions } = resolveScope(args, cwd);
105
+ // 1. Resolve the changed file set once. Both engines re-use it.
106
+ const changed = resolveChangedFiles(scopeOptions);
107
+ const changedFiles = changed.files;
108
+ // 2. Boundary engine — only if rules exist.
109
+ const inspection = await inspectSharkcraft({ cwd });
110
+ const rules = inspection.boundaryRegistry.list();
111
+ let boundaryBlock = {
112
+ ran: false,
113
+ rulesEvaluated: 0,
114
+ counts: { error: 0, warning: 0, info: 0 },
115
+ violations: [],
116
+ };
117
+ if (rules.length > 0 && changedFiles.length > 0) {
118
+ const scan = scanImports({ projectRoot: cwd });
119
+ const tsconfigPaths = loadTsconfigPaths(cwd);
120
+ const evalResult = evaluateBoundaries(scan, rules, {
121
+ ...(tsconfigPaths.aliases.size > 0 ? { tsconfigPaths } : {}),
122
+ });
123
+ const filtered = filterViolationsToChangedScope(evalResult.violations, scopeOptions);
124
+ boundaryBlock = {
125
+ ran: true,
126
+ rulesEvaluated: evalResult.rulesEvaluated,
127
+ counts: {
128
+ error: filtered.includedViolations.filter((v) => v.severity === 'error').length,
129
+ warning: filtered.includedViolations.filter((v) => v.severity === 'warning').length,
130
+ info: filtered.includedViolations.filter((v) => v.severity === 'info').length,
131
+ },
132
+ violations: filtered.includedViolations,
133
+ };
134
+ }
135
+ else if (rules.length > 0 && changedFiles.length === 0) {
136
+ boundaryBlock = { ...boundaryBlock, ran: true, rulesEvaluated: rules.length };
137
+ }
138
+ // 3. Import-hygiene engine — always runs, but scoped to changed files.
139
+ let importsBlock = {
140
+ ran: false,
141
+ verdict: 'skipped',
142
+ counts: {},
143
+ findings: [],
144
+ };
145
+ if (changedFiles.length > 0) {
146
+ const report = buildImportHygieneReport(cwd, { files: changedFiles });
147
+ importsBlock = {
148
+ ran: true,
149
+ verdict: report.verdict,
150
+ counts: report.counts ?? {},
151
+ findings: report.findings,
152
+ };
153
+ }
154
+ // 4. Build envelope + derive verdict.
155
+ const partial = {
156
+ schema: SCHEMA,
157
+ generatedAt: new Date().toISOString(),
158
+ scope: {
159
+ mode,
160
+ files: changedFiles,
161
+ fileCount: changedFiles.length,
162
+ },
163
+ boundaries: boundaryBlock,
164
+ imports: importsBlock,
165
+ };
166
+ const { verdict, summary, nextAction } = deriveVerdict(partial);
167
+ const envelope = { ...partial, verdict, summary, nextAction };
168
+ // 5. Render.
169
+ if (wantJson) {
170
+ process.stdout.write(asJson(envelope) + '\n');
171
+ return verdict === 'errors' ? 1 : 0;
172
+ }
173
+ process.stdout.write(header('Diff check'));
174
+ process.stdout.write(kv('scope', `${envelope.scope.mode} (${envelope.scope.fileCount} file${envelope.scope.fileCount === 1 ? '' : 's'})`) + '\n');
175
+ process.stdout.write(kv('boundaries', envelope.boundaries.ran
176
+ ? `${envelope.boundaries.counts.error} errors, ${envelope.boundaries.counts.warning} warnings`
177
+ : '(no rules configured or no scoped files)') + '\n');
178
+ process.stdout.write(kv('imports', envelope.imports.ran
179
+ ? `verdict=${envelope.imports.verdict} (${envelope.imports.findings.length} finding${envelope.imports.findings.length === 1 ? '' : 's'})`
180
+ : '(no scoped files)') + '\n');
181
+ process.stdout.write(kv('verdict', envelope.verdict) + '\n');
182
+ process.stdout.write('\n');
183
+ process.stdout.write(envelope.summary + '\n');
184
+ if (envelope.boundaries.violations.length > 0) {
185
+ process.stdout.write('\nBoundary violations:\n');
186
+ for (const v of envelope.boundaries.violations.slice(0, 10)) {
187
+ const file = String(v.file ?? '');
188
+ const rule = String(v.ruleId ?? '');
189
+ const fix = v.suggestedFix ? ` — ${String(v.suggestedFix)}` : '';
190
+ process.stdout.write(bullet(`${rule} in ${file}${fix}`) + '\n');
191
+ }
192
+ if (envelope.boundaries.violations.length > 10) {
193
+ process.stdout.write(` … and ${envelope.boundaries.violations.length - 10} more (pass --json for full list).\n`);
194
+ }
195
+ }
196
+ if (envelope.imports.findings.length > 0) {
197
+ process.stdout.write('\nImport findings:\n');
198
+ for (const f of envelope.imports.findings.slice(0, 10)) {
199
+ const file = String(f.path ?? f.file ?? '');
200
+ const kind = String(f.kind ?? '');
201
+ process.stdout.write(bullet(`${kind} in ${file}`) + '\n');
202
+ }
203
+ if (envelope.imports.findings.length > 10) {
204
+ process.stdout.write(` … and ${envelope.imports.findings.length - 10} more (pass --json for full list).\n`);
205
+ }
206
+ }
207
+ process.stdout.write(`\nNext: ${envelope.nextAction}\n`);
208
+ return verdict === 'errors' ? 1 : 0;
209
+ },
210
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.command.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.command.ts"],"names":[],"mappings":"AAqBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAwJhC,eAAO,MAAM,aAAa,EAAE,eAW3B,CAAC;AA+ZF,eAAO,MAAM,qBAAqB,EAAE,eAmCnC,CAAC;AAuDF,eAAO,MAAM,yBAAyB,EAAE,eAavC,CAAC;AAIF,eAAO,MAAM,wBAAwB,EAAE,eA2CtC,CAAC;AAgCF,eAAO,MAAM,6BAA6B,EAAE,eAa3C,CAAC"}
1
+ {"version":3,"file":"doctor.command.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.command.ts"],"names":[],"mappings":"AAqBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA0JhC,eAAO,MAAM,aAAa,EAAE,eAW3B,CAAC;AA+bF,eAAO,MAAM,qBAAqB,EAAE,eAmCnC,CAAC;AAuDF,eAAO,MAAM,yBAAyB,EAAE,eAavC,CAAC;AAIF,eAAO,MAAM,wBAAwB,EAAE,eA2CtC,CAAC;AAgCF,eAAO,MAAM,6BAA6B,EAAE,eAa3C,CAAC"}
@@ -38,7 +38,7 @@ function describeStrictMode(mode) {
38
38
  case 'all':
39
39
  return 'strict=all (every warning fails)';
40
40
  case 'warnings':
41
- return 'strict=warnings (structural warnings fail, hint-quality excluded)';
41
+ return 'strict=warnings (structural warnings fail, advisory excluded)';
42
42
  case 'errors':
43
43
  return 'strict=errors (only errors fail)';
44
44
  case 'off':
@@ -59,8 +59,10 @@ function evaluateStrict(mode, checks, errorCount) {
59
59
  for (const c of checks) {
60
60
  if (c.severity !== DoctorSeverity.Warning)
61
61
  continue;
62
- const isHintQuality = c.id.startsWith('actionhints-');
63
- if (mode === 'warnings' && isHintQuality) {
62
+ // `--strict=warnings` excludes anything the inspector flagged as
63
+ // advisory (action-hint quality today, any future advisory category
64
+ // tomorrow). `--strict=all` counts every warning, advisory or not.
65
+ if (mode === 'warnings' && c.advisory === true) {
64
66
  excludedWarnings += 1;
65
67
  }
66
68
  else {
@@ -74,7 +76,7 @@ function evaluateStrict(mode, checks, errorCount) {
74
76
  excludedWarnings,
75
77
  reason: mode === 'all'
76
78
  ? 'any warning'
77
- : 'structural warnings only (hint-quality excluded)',
79
+ : 'structural warnings only (advisory excluded)',
78
80
  };
79
81
  }
80
82
  function buildFilterOptions(args, suppressions) {
@@ -420,7 +422,7 @@ async function doctorCommandImpl(args) {
420
422
  void saveDoctorSuppressions;
421
423
  void existsSync;
422
424
  if (strictMode === 'warnings' && strictEval.excludedWarnings > 0) {
423
- process.stdout.write(` (strict=warnings excluded ${strictEval.excludedWarnings} hint-quality warning(s); use --strict=all to include)\n`);
425
+ process.stdout.write(` (strict=warnings excluded ${strictEval.excludedWarnings} advisory warning(s); use --strict=all to include)\n`);
424
426
  }
425
427
  // Surface acknowledgement state. Bare suppressions don't qualify as
426
428
  // acknowledgements; expiring/expired ones get a callout so authors don't
@@ -458,10 +460,27 @@ async function doctorCommandImpl(args) {
458
460
  if (!inspection.sharkcraftDir) {
459
461
  process.stdout.write('\nNothing here yet — try `shrk init --zero-config` to detect your stack and pick a preset.\n');
460
462
  }
461
- process.stdout.write(`\nAI-readiness: ${report.score} / 100 (${report.grade})\n`);
463
+ // Honest binary verdicts come first — yes/no on the two questions
464
+ // users actually want answered (can I let an agent write here? can
465
+ // I let an agent read here?). The 0..100 score follows, scoped to
466
+ // the workspace shape so libraries don't get dinged for "no
467
+ // pipelines" and apps don't get dinged for "no published API".
468
+ process.stdout.write(`\nShape: ${report.workspaceShape.label}` +
469
+ ` (score counts ${report.dimensions.filter((d) => d.applies === 'core').length} of ${report.dimensions.length} dimensions)\n`);
470
+ process.stdout.write(` ${report.verdicts.readyForAgentReads ? '✓' : '✗'} Ready for agent reads (context / task lookups)\n`);
471
+ process.stdout.write(` ${report.verdicts.readyForAgentWrites ? '✓' : '✗'} Ready for agent writes (apply / generate)\n`);
472
+ if (report.verdicts.blockers.length > 0) {
473
+ process.stdout.write(` Blockers:\n`);
474
+ for (const b of report.verdicts.blockers) {
475
+ process.stdout.write(` • ${b}\n`);
476
+ }
477
+ }
478
+ process.stdout.write(`\nAI-readiness: ${report.score} / 100 (${report.grade}, shape-aware)\n`);
462
479
  if (report.topRecommendations.length) {
463
480
  // Keep the default doctor output short: top 3 recommendations,
464
- // pass `--verbose` for the full list.
481
+ // pass `--verbose` for the full list. Recommendations only fire
482
+ // from `core` dimensions now — n/a-for-shape dimensions stop
483
+ // generating the misleading "add a pipeline" advice for libraries.
465
484
  const verbose = flagBool(args, 'verbose');
466
485
  const visible = verbose ? report.topRecommendations : report.topRecommendations.slice(0, 3);
467
486
  process.stdout.write(`Top recommendations${verbose ? '' : ` (top ${visible.length})`}:\n`);
@@ -471,6 +490,21 @@ async function doctorCommandImpl(args) {
471
490
  process.stdout.write(` … (${report.topRecommendations.length - visible.length} more — pass --verbose to see all)\n`);
472
491
  }
473
492
  }
493
+ // Surface N/A dimensions when --show-na is passed, so users can see
494
+ // what was deliberately skipped and disagree if they want to.
495
+ if (flagBool(args, 'show-na')) {
496
+ const skipped = report.dimensions.filter((d) => d.applies !== 'core');
497
+ if (skipped.length > 0) {
498
+ process.stdout.write(`\nNot counted in score (${skipped.length} dimensions):\n`);
499
+ for (const d of skipped) {
500
+ const tag = d.applies === 'n/a-for-shape' ? 'n/a' : 'advisory';
501
+ process.stdout.write(` [${tag}] ${d.title}: ${d.note}`);
502
+ if (d.appliesReason)
503
+ process.stdout.write(` — ${d.appliesReason}`);
504
+ process.stdout.write('\n');
505
+ }
506
+ }
507
+ }
474
508
  if (strictEval.failed) {
475
509
  process.stdout.write(`\nStrict mode: failing because ${strictEval.countedWarnings} warning(s) + ${result.summary.errors} error(s) exist (${strictEval.reason}).\n`);
476
510
  }
@@ -479,8 +513,7 @@ async function doctorCommandImpl(args) {
479
513
  }
480
514
  // Failure-to-success hints surface only when something is wrong.
481
515
  if (overallExitCode !== 0 || result.summary.warnings > 0) {
482
- const folderMissing = result.checks.some((c) => c.id === 'sharkcraft-folder' && (c.severity === 'error' || c.severity === 'warning'));
483
- process.stdout.write(renderFailureHints(doctorHints({ sharkcraftFolderMissing: folderMissing })));
516
+ process.stdout.write(renderFailureHints(doctorHints()));
484
517
  }
485
518
  // When there are preview-eligible findings (action-hints,
486
519
  // knowledge-stale, template-drift, self-config, pack-conflict,
@@ -1 +1 @@
1
- {"version":3,"file":"export.command.d.ts","sourceRoot":"","sources":["../../src/commands/export.command.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgBhC,eAAO,MAAM,aAAa,EAAE,eAkF3B,CAAC"}
1
+ {"version":3,"file":"export.command.d.ts","sourceRoot":"","sources":["../../src/commands/export.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgBhC,eAAO,MAAM,aAAa,EAAE,eAuF3B,CAAC"}
@@ -2,8 +2,9 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { inspectSharkcraft } from '@shrkcrft/inspector';
4
4
  import { ALL_EXPORT_FORMATS, isExportFormat, renderExport, } from "../export/export-formats.js";
5
+ import { buildClaudeCommands } from "../export/claude-commands-export.js";
5
6
  import { flagBool, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
6
- import { asJson, header } from "../output/format-output.js";
7
+ import { asJson, bullet, header } from "../output/format-output.js";
7
8
  import { exportBundleCommand, exportSessionCommand, exportQualityCommand, exportReviewCommand, } from "./export-bundle.command.js";
8
9
  const ARCHIVE_SUBCOMMANDS = {
9
10
  bundle: exportBundleCommand,
@@ -13,7 +14,7 @@ const ARCHIVE_SUBCOMMANDS = {
13
14
  };
14
15
  export const exportCommand = {
15
16
  name: 'export',
16
- description: 'Render SharkCraft knowledge as a flat agent-rule file (AGENTS.md / CLAUDE.md / .cursor/rules / copilot-instructions). Dry-run by default; pass --write to save.',
17
+ description: 'Inversion — pull SharkCraft rules into the agent\'s prompt instead of the agent calling back to shrk. Single-file outputs: claude-skill (.claude/skills/<name>/SKILL.md, recommended), agents-md (AGENTS.md), claude-md (CLAUDE.md), cursor-rules (.cursor/rules/*.mdc), copilot-instructions. Multi-file output: claude-commands (.claude/commands/*.md — per-project slash commands like /new-service, /check-changes, /follow-shrk). Dry-run by default; pass --write to save.',
17
18
  usage: 'shrk [--cwd <dir>] export <format> [--write] [--output <path>] [--task "<task>"] [--max-rules N] [--max-paths N] [--json]',
18
19
  async run(args) {
19
20
  const format = args.positional[0];
@@ -27,8 +28,13 @@ export const exportCommand = {
27
28
  const sub = { ...args, positional: args.positional.slice(1) };
28
29
  return archive.run(sub);
29
30
  }
31
+ // Multi-file `claude-commands` dispatches separately — it emits
32
+ // one .md per slash command, not a single rendered file.
33
+ if (format === 'claude-commands') {
34
+ return runClaudeCommandsExport(args);
35
+ }
30
36
  if (!isExportFormat(format)) {
31
- process.stderr.write(`Unknown export format "${format}".\nFormats: ${ALL_EXPORT_FORMATS.join(', ')}, bundle, session, quality, review\n`);
37
+ process.stderr.write(`Unknown export format "${format}".\nFormats: ${ALL_EXPORT_FORMATS.join(', ')}, claude-commands, bundle, session, quality, review\n`);
32
38
  return 2;
33
39
  }
34
40
  const cwd = resolveCwd(args);
@@ -81,3 +87,70 @@ export const exportCommand = {
81
87
  return 0;
82
88
  },
83
89
  };
90
+ /**
91
+ * `shrk export claude-commands` — multi-file generator for Claude
92
+ * Code's native `.claude/commands/` slash-command primitive. Produces
93
+ * one .md per command (static + per-template).
94
+ *
95
+ * Unlike single-file exports (claude-skill / claude-md / etc.) this
96
+ * writes a SET of files. Each file is a complete recipe Claude Code
97
+ * loads when the user types the matching slash command.
98
+ */
99
+ async function runClaudeCommandsExport(args) {
100
+ const cwd = resolveCwd(args);
101
+ const inspection = await inspectSharkcraft({ cwd });
102
+ const result = buildClaudeCommands(inspection);
103
+ const wantJson = flagBool(args, 'json');
104
+ const doWrite = flagBool(args, 'write');
105
+ const force = flagBool(args, 'force');
106
+ if (wantJson) {
107
+ process.stdout.write(asJson({
108
+ format: 'claude-commands',
109
+ write: doWrite,
110
+ files: result.files.map((f) => ({
111
+ path: f.path,
112
+ slash: f.slash,
113
+ source: f.source,
114
+ })),
115
+ }) + '\n');
116
+ return 0;
117
+ }
118
+ if (!doWrite) {
119
+ process.stdout.write(header('Export (claude-commands) — dry-run'));
120
+ process.stdout.write(`Would write ${result.files.length} command file(s):\n\n`);
121
+ for (const f of result.files) {
122
+ process.stdout.write(` ${f.path}\n`);
123
+ process.stdout.write(` → users type \`/${f.slash}\` in Claude Code (${f.source})\n`);
124
+ }
125
+ process.stdout.write('\nRe-run with --write to save.\n');
126
+ return 0;
127
+ }
128
+ const written = [];
129
+ const skipped = [];
130
+ for (const file of result.files) {
131
+ const fullPath = join(cwd, file.path);
132
+ mkdirSync(dirname(fullPath), { recursive: true });
133
+ if (existsSync(fullPath) && !force) {
134
+ skipped.push(file.path);
135
+ continue;
136
+ }
137
+ writeFileSync(fullPath, file.content, 'utf8');
138
+ written.push(file.path);
139
+ }
140
+ process.stdout.write(header('Claude commands exported'));
141
+ if (written.length) {
142
+ process.stdout.write(`Wrote ${written.length} command file(s):\n`);
143
+ for (const p of written) {
144
+ const f = result.files.find((x) => x.path === p);
145
+ process.stdout.write(bullet(`${p} → \`/${f.slash}\``) + '\n');
146
+ }
147
+ }
148
+ if (skipped.length) {
149
+ process.stdout.write(`\nSkipped ${skipped.length} (already exist; use --force to overwrite):\n`);
150
+ for (const p of skipped)
151
+ process.stdout.write(bullet(p) + '\n');
152
+ }
153
+ process.stdout.write('\nClaude Code picks up `.claude/commands/*.md` automatically. ' +
154
+ 'Open the project in Claude Code, type `/` — the slash commands are in the palette.\n');
155
+ return 0;
156
+ }
@@ -0,0 +1,12 @@
1
+ import { type ICommandHandler } from '../command-registry.js';
2
+ /**
3
+ * `shrk framework` — run / inspect the framework-aware extractors.
4
+ *
5
+ * Sub-verbs:
6
+ * - shrk framework index run extractors over the project
7
+ * - shrk framework status report store health
8
+ * - shrk framework list [filters] list entities (--framework, --subtype, --file)
9
+ * - shrk framework routes NestJS route table (method, path, handler, file)
10
+ */
11
+ export declare const frameworkCommand: ICommandHandler;
12
+ //# sourceMappingURL=framework.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework.command.d.ts","sourceRoot":"","sources":["../../src/commands/framework.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,eAe9B,CAAC"}