@shrkcrft/cli 0.1.0-alpha.1 → 0.1.0-alpha.10
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.
- package/README.md +1 -1
- package/dist/commands/api-diff.command.d.ts +11 -0
- package/dist/commands/api-diff.command.d.ts.map +1 -0
- package/dist/commands/api-diff.command.js +116 -0
- package/dist/commands/arch.command.d.ts +9 -0
- package/dist/commands/arch.command.d.ts.map +1 -0
- package/dist/commands/arch.command.js +186 -0
- package/dist/commands/boundaries.command.d.ts.map +1 -1
- package/dist/commands/boundaries.command.js +0 -12
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +20 -30
- package/dist/commands/code-intel.command.d.ts +18 -0
- package/dist/commands/code-intel.command.d.ts.map +1 -0
- package/dist/commands/code-intel.command.js +146 -0
- package/dist/commands/command-catalog.d.ts +7 -3
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +201 -47
- package/dist/commands/commands.command.d.ts.map +1 -1
- package/dist/commands/commands.command.js +4 -4
- package/dist/commands/completion.command.d.ts +10 -0
- package/dist/commands/completion.command.d.ts.map +1 -0
- package/dist/commands/completion.command.js +121 -0
- package/dist/commands/constructs.command.d.ts.map +1 -1
- package/dist/commands/constructs.command.js +5 -22
- package/dist/commands/context.command.d.ts.map +1 -1
- package/dist/commands/context.command.js +89 -0
- package/dist/commands/diff-check.command.d.ts +30 -0
- package/dist/commands/diff-check.command.d.ts.map +1 -0
- package/dist/commands/diff-check.command.js +210 -0
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +42 -9
- package/dist/commands/export.command.d.ts.map +1 -1
- package/dist/commands/export.command.js +76 -3
- package/dist/commands/framework.command.d.ts +12 -0
- package/dist/commands/framework.command.d.ts.map +1 -0
- package/dist/commands/framework.command.js +180 -0
- package/dist/commands/gate.command.d.ts +15 -0
- package/dist/commands/gate.command.d.ts.map +1 -0
- package/dist/commands/gate.command.js +296 -0
- package/dist/commands/graph-code-subverbs.d.ts +11 -0
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -0
- package/dist/commands/graph-code-subverbs.js +818 -0
- package/dist/commands/graph.command.d.ts.map +1 -1
- package/dist/commands/graph.command.js +22 -0
- package/dist/commands/help.command.d.ts +4 -3
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +77 -21
- package/dist/commands/helper.command.js +1 -1
- package/dist/commands/impact.command.d.ts.map +1 -1
- package/dist/commands/impact.command.js +170 -1
- package/dist/commands/import.command.d.ts.map +1 -1
- package/dist/commands/import.command.js +121 -5
- package/dist/commands/init.command.d.ts.map +1 -1
- package/dist/commands/init.command.js +184 -16
- package/dist/commands/mcp.command.d.ts.map +1 -1
- package/dist/commands/mcp.command.js +2 -131
- package/dist/commands/migrate.command.d.ts +13 -0
- package/dist/commands/migrate.command.d.ts.map +1 -0
- package/dist/commands/migrate.command.js +152 -0
- package/dist/commands/onboard.command.d.ts.map +1 -1
- package/dist/commands/onboard.command.js +3 -15
- package/dist/commands/packs-new.d.ts +1 -1
- package/dist/commands/packs-new.d.ts.map +1 -1
- package/dist/commands/packs-new.js +5 -36
- package/dist/commands/packs.command.d.ts.map +1 -1
- package/dist/commands/packs.command.js +3 -17
- package/dist/commands/plan-context.command.d.ts +11 -0
- package/dist/commands/plan-context.command.d.ts.map +1 -0
- package/dist/commands/plan-context.command.js +77 -0
- package/dist/commands/profiles.command.js +4 -4
- package/dist/commands/release.command.js +13 -13
- package/dist/commands/review.command.d.ts.map +1 -1
- package/dist/commands/review.command.js +2 -28
- package/dist/commands/rule-graph-subverbs.d.ts +3 -0
- package/dist/commands/rule-graph-subverbs.d.ts.map +1 -0
- package/dist/commands/rule-graph-subverbs.js +132 -0
- package/dist/commands/search-structural.command.d.ts +18 -0
- package/dist/commands/search-structural.command.d.ts.map +1 -0
- package/dist/commands/search-structural.command.js +376 -0
- package/dist/commands/search.command.js +1 -1
- package/dist/commands/task-context.command.js +0 -16
- package/dist/commands/task.command.d.ts.map +1 -1
- package/dist/commands/task.command.js +8 -2
- package/dist/dashboard/code-intelligence-data.d.ts +33 -0
- package/dist/dashboard/code-intelligence-data.d.ts.map +1 -0
- package/dist/dashboard/code-intelligence-data.js +307 -0
- package/dist/dashboard/dashboard-api-server.d.ts.map +1 -1
- package/dist/dashboard/dashboard-api-server.js +137 -1
- package/dist/export/claude-commands-export.d.ts +60 -0
- package/dist/export/claude-commands-export.d.ts.map +1 -0
- package/dist/export/claude-commands-export.js +276 -0
- package/dist/export/export-formats.d.ts +1 -1
- package/dist/export/export-formats.d.ts.map +1 -1
- package/dist/export/export-formats.js +139 -12
- package/dist/init/init-templates.d.ts.map +1 -1
- package/dist/init/init-templates.js +133 -113
- package/dist/init/paths-advisory.d.ts +20 -0
- package/dist/init/paths-advisory.d.ts.map +1 -0
- package/dist/init/paths-advisory.js +88 -0
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +137 -46
- package/dist/output/failure-hints.d.ts +1 -9
- package/dist/output/failure-hints.d.ts.map +1 -1
- package/dist/output/failure-hints.js +2 -8
- package/dist/output/watch-loop.d.ts +9 -1
- package/dist/output/watch-loop.d.ts.map +1 -1
- package/dist/output/watch-loop.js +13 -3
- package/dist/schemas/json-schemas.d.ts +36 -36
- package/dist/schemas/json-schemas.js +36 -36
- package/dist/surface/about.d.ts.map +1 -1
- package/dist/surface/about.js +37 -15
- package/dist/surface/no-args-landing.d.ts.map +1 -1
- package/dist/surface/no-args-landing.js +9 -13
- package/dist/surface/surface-config-writer.d.ts.map +1 -1
- package/dist/surface/surface-config-writer.js +23 -11
- package/package.json +36 -25
- package/dist/commands/plugin.command.d.ts +0 -11
- package/dist/commands/plugin.command.d.ts.map +0 -1
- 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;
|
|
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,
|
|
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
|
-
|
|
63
|
-
|
|
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 (
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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: '
|
|
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"}
|