@shrkcrft/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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/dist/audit/knowledge-audit-llm.d.ts +19 -0
- package/dist/audit/knowledge-audit-llm.d.ts.map +1 -0
- package/dist/audit/knowledge-audit-llm.js +164 -0
- package/dist/audit/knowledge-audit.d.ts +61 -0
- package/dist/audit/knowledge-audit.d.ts.map +1 -0
- package/dist/audit/knowledge-audit.js +203 -0
- package/dist/audit/knowledge-fix-plan-llm.d.ts +11 -0
- package/dist/audit/knowledge-fix-plan-llm.d.ts.map +1 -0
- package/dist/audit/knowledge-fix-plan-llm.js +141 -0
- package/dist/audit/knowledge-fix-plan.d.ts +41 -0
- package/dist/audit/knowledge-fix-plan.d.ts.map +1 -0
- package/dist/audit/knowledge-fix-plan.js +125 -0
- package/dist/audit/pipeline-audit-llm.d.ts +11 -0
- package/dist/audit/pipeline-audit-llm.d.ts.map +1 -0
- package/dist/audit/pipeline-audit-llm.js +134 -0
- package/dist/audit/pipeline-audit.d.ts +69 -0
- package/dist/audit/pipeline-audit.d.ts.map +1 -0
- package/dist/audit/pipeline-audit.js +166 -0
- package/dist/audit/templates-audit-llm.d.ts +19 -0
- package/dist/audit/templates-audit-llm.d.ts.map +1 -0
- package/dist/audit/templates-audit-llm.js +207 -0
- package/dist/audit/templates-audit.d.ts +63 -0
- package/dist/audit/templates-audit.d.ts.map +1 -0
- package/dist/audit/templates-audit.js +171 -0
- package/dist/audit/templates-fix-plan-llm.d.ts +19 -0
- package/dist/audit/templates-fix-plan-llm.d.ts.map +1 -0
- package/dist/audit/templates-fix-plan-llm.js +162 -0
- package/dist/audit/templates-fix-plan.d.ts +37 -0
- package/dist/audit/templates-fix-plan.d.ts.map +1 -0
- package/dist/audit/templates-fix-plan.js +174 -0
- package/dist/commands/ai-status.command.d.ts +19 -0
- package/dist/commands/ai-status.command.d.ts.map +1 -0
- package/dist/commands/ai-status.command.js +94 -0
- package/dist/commands/ask.command.d.ts.map +1 -1
- package/dist/commands/ask.command.js +10 -9
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +110 -1
- package/dist/commands/deps-audit.command.d.ts +23 -0
- package/dist/commands/deps-audit.command.d.ts.map +1 -0
- package/dist/commands/deps-audit.command.js +266 -0
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +100 -3
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
- package/dist/commands/graph-code-subverbs.js +144 -26
- package/dist/commands/graph.command.d.ts.map +1 -1
- package/dist/commands/graph.command.js +3 -2
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +22 -1
- package/dist/commands/impact.command.d.ts.map +1 -1
- package/dist/commands/impact.command.js +3 -2
- package/dist/commands/move-plan.command.d.ts +23 -0
- package/dist/commands/move-plan.command.d.ts.map +1 -0
- package/dist/commands/move-plan.command.js +360 -0
- package/dist/commands/scaffold-validate.command.d.ts +22 -0
- package/dist/commands/scaffold-validate.command.d.ts.map +1 -0
- package/dist/commands/scaffold-validate.command.js +215 -0
- package/dist/commands/smart-context.command.d.ts +58 -0
- package/dist/commands/smart-context.command.d.ts.map +1 -0
- package/dist/commands/smart-context.command.js +4524 -0
- package/dist/commands/spike.command.d.ts +22 -0
- package/dist/commands/spike.command.d.ts.map +1 -0
- package/dist/commands/spike.command.js +235 -0
- package/dist/commands/surface.command.d.ts +1 -0
- package/dist/commands/surface.command.d.ts.map +1 -1
- package/dist/commands/surface.command.js +10 -3
- package/dist/commands/template-quality.command.d.ts.map +1 -1
- package/dist/commands/template-quality.command.js +39 -3
- package/dist/commands/templates.command.d.ts.map +1 -1
- package/dist/commands/templates.command.js +37 -2
- package/dist/commands/watch.command.d.ts +26 -0
- package/dist/commands/watch.command.d.ts.map +1 -0
- package/dist/commands/watch.command.js +456 -0
- package/dist/env/load-dotenv.d.ts +15 -0
- package/dist/env/load-dotenv.d.ts.map +1 -0
- package/dist/env/load-dotenv.js +70 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +105 -2
- package/dist/schemas/json-schemas.d.ts +384 -36
- package/dist/schemas/json-schemas.d.ts.map +1 -1
- package/dist/schemas/json-schemas.js +247 -36
- package/package.json +33 -31
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ICommandHandler } from '../command-registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* `shrk spike <slug>` — turn a saved smart-context plan into starter
|
|
4
|
+
* files for the recommended MVP.
|
|
5
|
+
*
|
|
6
|
+
* Reads `.sharkcraft/smart-context/<slug>.plan.json` (written by
|
|
7
|
+
* smart-context when --save + focused/ai-plan succeeded), grabs
|
|
8
|
+
* `firstSpike.proposedFiles[]`, and writes a placeholder file at each
|
|
9
|
+
* proposed path that does not already exist. The placeholders carry a
|
|
10
|
+
* short header derived from `purpose` plus the plan's
|
|
11
|
+
* `schemaOutline` when present, so the human opening the file knows
|
|
12
|
+
* why it exists and what shape to fill in.
|
|
13
|
+
*
|
|
14
|
+
* Safety:
|
|
15
|
+
* - Never overwrites an existing file.
|
|
16
|
+
* - Refuses to write outside the workspace.
|
|
17
|
+
* - `--dry-run` prints what would happen.
|
|
18
|
+
* - `--force` is intentionally absent — a re-spike should mean
|
|
19
|
+
* "delete then rerun" so the user gets a fresh prompt about it.
|
|
20
|
+
*/
|
|
21
|
+
export declare const spikeCommand: ICommandHandler;
|
|
22
|
+
//# sourceMappingURL=spike.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spike.command.d.ts","sourceRoot":"","sources":["../../src/commands/spike.command.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAuBhC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,EAAE,eAsI1B,CAAC"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { flagBool, resolveCwd, } from "../command-registry.js";
|
|
4
|
+
import { asJson, header, kv } from "../output/format-output.js";
|
|
5
|
+
const SMART_CONTEXT_DIR = nodePath.join('.sharkcraft', 'smart-context');
|
|
6
|
+
/**
|
|
7
|
+
* `shrk spike <slug>` — turn a saved smart-context plan into starter
|
|
8
|
+
* files for the recommended MVP.
|
|
9
|
+
*
|
|
10
|
+
* Reads `.sharkcraft/smart-context/<slug>.plan.json` (written by
|
|
11
|
+
* smart-context when --save + focused/ai-plan succeeded), grabs
|
|
12
|
+
* `firstSpike.proposedFiles[]`, and writes a placeholder file at each
|
|
13
|
+
* proposed path that does not already exist. The placeholders carry a
|
|
14
|
+
* short header derived from `purpose` plus the plan's
|
|
15
|
+
* `schemaOutline` when present, so the human opening the file knows
|
|
16
|
+
* why it exists and what shape to fill in.
|
|
17
|
+
*
|
|
18
|
+
* Safety:
|
|
19
|
+
* - Never overwrites an existing file.
|
|
20
|
+
* - Refuses to write outside the workspace.
|
|
21
|
+
* - `--dry-run` prints what would happen.
|
|
22
|
+
* - `--force` is intentionally absent — a re-spike should mean
|
|
23
|
+
* "delete then rerun" so the user gets a fresh prompt about it.
|
|
24
|
+
*/
|
|
25
|
+
export const spikeCommand = {
|
|
26
|
+
name: 'spike',
|
|
27
|
+
description: 'Scaffold starter files for a saved smart-context plan\'s recommended MVP. Reads .sharkcraft/smart-context/<slug>.plan.json.',
|
|
28
|
+
usage: 'shrk spike <slug> [--dry-run] [--json]',
|
|
29
|
+
async run(args) {
|
|
30
|
+
const slug = args.positional[0]?.trim();
|
|
31
|
+
if (!slug) {
|
|
32
|
+
process.stderr.write('Usage: shrk spike <slug> [--dry-run] [--json]\n');
|
|
33
|
+
process.stderr.write(' (slug comes from `shrk smart-context list`)\n');
|
|
34
|
+
return 2;
|
|
35
|
+
}
|
|
36
|
+
const cwd = resolveCwd(args);
|
|
37
|
+
const json = flagBool(args, 'json');
|
|
38
|
+
const dryRun = flagBool(args, 'dry-run');
|
|
39
|
+
const planPath = resolvePlanPath(cwd, slug);
|
|
40
|
+
if (!planPath) {
|
|
41
|
+
process.stderr.write(`No structured plan found for slug "${slug}". Looked under .sharkcraft/smart-context/.\n` +
|
|
42
|
+
`Tip: run smart-context with --save and a structured-output mode (--ai-plan or --focused --plan) first.\n`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
let plan;
|
|
46
|
+
try {
|
|
47
|
+
plan = JSON.parse(readFileSync(planPath, 'utf8'));
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
process.stderr.write(`Failed to parse ${planPath}: ${e.message}\n`);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
const proposed = plan.firstSpike?.proposedFiles ?? [];
|
|
54
|
+
if (proposed.length === 0) {
|
|
55
|
+
process.stderr.write(`Plan at ${planPath} has no firstSpike.proposedFiles[]. Re-run smart-context with --focused --plan (architecture tasks fill this in).\n`);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
const items = [];
|
|
59
|
+
for (const entry of proposed) {
|
|
60
|
+
const raw = entry?.path?.trim();
|
|
61
|
+
if (!raw || raw.length === 0)
|
|
62
|
+
continue;
|
|
63
|
+
// Refuse placeholder-y paths like `.../<timestamp>.json`.
|
|
64
|
+
if (/[<>{}]/.test(raw)) {
|
|
65
|
+
items.push({ path: raw, absolute: '', action: 'skip-unsafe', reason: 'placeholder syntax' });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const safe = safeJoin(cwd, raw);
|
|
69
|
+
if (!safe) {
|
|
70
|
+
items.push({ path: raw, absolute: '', action: 'skip-unsafe', reason: 'escapes workspace' });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (existsSync(safe)) {
|
|
74
|
+
items.push({ path: raw, absolute: safe, action: 'skip-exists' });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
items.push({ path: raw, absolute: safe, action: 'create' });
|
|
78
|
+
}
|
|
79
|
+
const willCreate = items.filter((i) => i.action === 'create');
|
|
80
|
+
const skipExists = items.filter((i) => i.action === 'skip-exists');
|
|
81
|
+
const skipUnsafe = items.filter((i) => i.action === 'skip-unsafe');
|
|
82
|
+
if (!dryRun) {
|
|
83
|
+
for (const item of willCreate) {
|
|
84
|
+
const purpose = proposed.find((p) => p.path === item.path)?.purpose ?? '';
|
|
85
|
+
const body = renderStarter({
|
|
86
|
+
path: item.path,
|
|
87
|
+
purpose,
|
|
88
|
+
plan,
|
|
89
|
+
});
|
|
90
|
+
mkdirSync(nodePath.dirname(item.absolute), { recursive: true });
|
|
91
|
+
writeFileSync(item.absolute, body, 'utf8');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (json) {
|
|
95
|
+
process.stdout.write(asJson({
|
|
96
|
+
slug,
|
|
97
|
+
planPath,
|
|
98
|
+
dryRun,
|
|
99
|
+
created: willCreate.map((i) => i.path),
|
|
100
|
+
skippedExisting: skipExists.map((i) => i.path),
|
|
101
|
+
skippedUnsafe: skipUnsafe.map((i) => ({ path: i.path, reason: i.reason })),
|
|
102
|
+
proposedCommand: plan.firstSpike?.proposedCommand ?? null,
|
|
103
|
+
}) + '\n');
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
process.stdout.write(header(`Spike for: ${slug}`));
|
|
107
|
+
process.stdout.write(kv('plan', planPath) + '\n');
|
|
108
|
+
if (plan.recommendedMvp?.architectureName) {
|
|
109
|
+
process.stdout.write(kv('mvp', plan.recommendedMvp.architectureName) + '\n');
|
|
110
|
+
}
|
|
111
|
+
process.stdout.write('\n');
|
|
112
|
+
if (willCreate.length > 0) {
|
|
113
|
+
process.stdout.write(`Files ${dryRun ? 'that would be created' : 'created'} (${willCreate.length}):\n`);
|
|
114
|
+
for (const item of willCreate) {
|
|
115
|
+
const purpose = proposed.find((p) => p.path === item.path)?.purpose ?? '';
|
|
116
|
+
process.stdout.write(` + ${item.path}${purpose ? ` — ${purpose}` : ''}\n`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
process.stdout.write('Nothing new to create.\n');
|
|
121
|
+
}
|
|
122
|
+
if (skipExists.length > 0) {
|
|
123
|
+
process.stdout.write(`\nSkipped (already exist):\n`);
|
|
124
|
+
for (const item of skipExists)
|
|
125
|
+
process.stdout.write(` · ${item.path}\n`);
|
|
126
|
+
}
|
|
127
|
+
if (skipUnsafe.length > 0) {
|
|
128
|
+
process.stdout.write(`\nSkipped (unsafe):\n`);
|
|
129
|
+
for (const item of skipUnsafe) {
|
|
130
|
+
process.stdout.write(` ! ${item.path} — ${item.reason ?? 'unsafe'}\n`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (plan.firstSpike?.proposedCommand) {
|
|
134
|
+
process.stdout.write(`\nNext: run \`${plan.firstSpike.proposedCommand}\`\n`);
|
|
135
|
+
}
|
|
136
|
+
if (plan.firstSpike?.successCriteria && plan.firstSpike.successCriteria.length > 0) {
|
|
137
|
+
process.stdout.write(`\nSuccess criteria:\n`);
|
|
138
|
+
for (const c of plan.firstSpike.successCriteria)
|
|
139
|
+
process.stdout.write(` - ${c}\n`);
|
|
140
|
+
}
|
|
141
|
+
return 0;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
function resolvePlanPath(cwd, slug) {
|
|
145
|
+
const dir = nodePath.join(cwd, SMART_CONTEXT_DIR);
|
|
146
|
+
if (!existsSync(dir))
|
|
147
|
+
return null;
|
|
148
|
+
// Exact match first.
|
|
149
|
+
const exact = nodePath.join(dir, `${slug}.plan.json`);
|
|
150
|
+
if (existsSync(exact))
|
|
151
|
+
return exact;
|
|
152
|
+
// Otherwise tolerate <slug>-plan.plan.json and similar suffix patterns.
|
|
153
|
+
try {
|
|
154
|
+
for (const name of readdirSync(dir)) {
|
|
155
|
+
if (!name.endsWith('.plan.json'))
|
|
156
|
+
continue;
|
|
157
|
+
const base = name.replace(/\.plan\.json$/, '');
|
|
158
|
+
if (base === slug || base.startsWith(slug)) {
|
|
159
|
+
return nodePath.join(dir, name);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// ignore
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function safeJoin(cwd, candidate) {
|
|
169
|
+
const normalised = candidate.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
170
|
+
if (nodePath.isAbsolute(normalised)) {
|
|
171
|
+
// Permit only if inside cwd.
|
|
172
|
+
const resolved = nodePath.resolve(normalised);
|
|
173
|
+
if (!resolved.startsWith(cwd + nodePath.sep) && resolved !== cwd)
|
|
174
|
+
return null;
|
|
175
|
+
return resolved;
|
|
176
|
+
}
|
|
177
|
+
const resolved = nodePath.resolve(cwd, normalised);
|
|
178
|
+
if (!resolved.startsWith(cwd + nodePath.sep))
|
|
179
|
+
return null;
|
|
180
|
+
return resolved;
|
|
181
|
+
}
|
|
182
|
+
function renderStarter(input) {
|
|
183
|
+
const ext = nodePath.extname(input.path).toLowerCase();
|
|
184
|
+
const commentOpen = pickCommentOpen(ext);
|
|
185
|
+
const lines = [];
|
|
186
|
+
const taskOrSummary = input.plan.summary ?? input.plan.task ?? 'smart-context spike';
|
|
187
|
+
lines.push(`${commentOpen} Spike scaffold: ${input.path}`);
|
|
188
|
+
if (input.purpose)
|
|
189
|
+
lines.push(`${commentOpen} Purpose: ${input.purpose}`);
|
|
190
|
+
lines.push(`${commentOpen} Generated by \`shrk spike\` from a smart-context plan.`);
|
|
191
|
+
lines.push(`${commentOpen} Plan summary: ${taskOrSummary}`);
|
|
192
|
+
const mvp = input.plan.recommendedMvp?.architectureName;
|
|
193
|
+
if (mvp)
|
|
194
|
+
lines.push(`${commentOpen} MVP: ${mvp}`);
|
|
195
|
+
lines.push(`${commentOpen} Replace this header with the real implementation.`);
|
|
196
|
+
if (input.plan.firstSpike?.schemaOutline !== undefined && (ext === '.json' || ext === '.md')) {
|
|
197
|
+
lines.push('');
|
|
198
|
+
if (ext === '.json') {
|
|
199
|
+
const outlined = typeof input.plan.firstSpike.schemaOutline === 'string'
|
|
200
|
+
? input.plan.firstSpike.schemaOutline
|
|
201
|
+
: JSON.stringify(input.plan.firstSpike.schemaOutline, null, 2);
|
|
202
|
+
lines.push(outlined);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
lines.push('```json');
|
|
206
|
+
lines.push(typeof input.plan.firstSpike.schemaOutline === 'string'
|
|
207
|
+
? input.plan.firstSpike.schemaOutline
|
|
208
|
+
: JSON.stringify(input.plan.firstSpike.schemaOutline, null, 2));
|
|
209
|
+
lines.push('```');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// For .ts/.tsx files leave a TODO line so the file is syntactically valid.
|
|
213
|
+
if (ext === '.ts' || ext === '.tsx' || ext === '.js' || ext === '.jsx') {
|
|
214
|
+
lines.push('');
|
|
215
|
+
lines.push('// TODO(spike): implement.');
|
|
216
|
+
lines.push('export {};');
|
|
217
|
+
}
|
|
218
|
+
return lines.join('\n') + '\n';
|
|
219
|
+
}
|
|
220
|
+
function pickCommentOpen(ext) {
|
|
221
|
+
switch (ext) {
|
|
222
|
+
case '.json':
|
|
223
|
+
return '//'; // not technically valid JSON; we replace this body below for .json files
|
|
224
|
+
case '.md':
|
|
225
|
+
return '<!--';
|
|
226
|
+
case '.yml':
|
|
227
|
+
case '.yaml':
|
|
228
|
+
return '#';
|
|
229
|
+
case '.sh':
|
|
230
|
+
return '#';
|
|
231
|
+
default:
|
|
232
|
+
return '//';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
void statSync;
|
|
@@ -10,6 +10,7 @@ import { type ICommandHandler } from '../command-registry.js';
|
|
|
10
10
|
* - unhide <command> reverse hide
|
|
11
11
|
* - reset clear surface.enabled + surface.hidden
|
|
12
12
|
* - explain <command> why this command has its current tier
|
|
13
|
+
* - profiles [get <id>] list surface profiles (or show one)
|
|
13
14
|
*/
|
|
14
15
|
export declare const surfaceCommand: ICommandHandler;
|
|
15
16
|
//# sourceMappingURL=surface.command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface.command.d.ts","sourceRoot":"","sources":["../../src/commands/surface.command.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiBhC
|
|
1
|
+
{"version":3,"file":"surface.command.d.ts","sourceRoot":"","sources":["../../src/commands/surface.command.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiBhC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,EAAE,eAmC5B,CAAC"}
|
|
@@ -15,11 +15,12 @@ import { applySurfaceEdit, defaultConfigFile, planSurfaceEdit, } from "../surfac
|
|
|
15
15
|
* - unhide <command> reverse hide
|
|
16
16
|
* - reset clear surface.enabled + surface.hidden
|
|
17
17
|
* - explain <command> why this command has its current tier
|
|
18
|
+
* - profiles [get <id>] list surface profiles (or show one)
|
|
18
19
|
*/
|
|
19
20
|
export const surfaceCommand = {
|
|
20
21
|
name: 'surface',
|
|
21
22
|
description: 'Inspect or change the adaptive command surface (core / extended / experimental tiers).',
|
|
22
|
-
usage: 'shrk surface <list|enable|disable|hide|unhide|reset|explain> [name] [--write] [--json]',
|
|
23
|
+
usage: 'shrk surface <list|enable|disable|hide|unhide|reset|explain|profiles> [name] [--write] [--json]',
|
|
23
24
|
async run(args) {
|
|
24
25
|
const [verb, ...rest] = args.positional;
|
|
25
26
|
const json = flagBool(args, 'json');
|
|
@@ -84,7 +85,7 @@ async function runProfiles(opts) {
|
|
|
84
85
|
const found = availableProfiles.find((p) => p.id === opts.target);
|
|
85
86
|
if (!found) {
|
|
86
87
|
process.stderr.write(`Unknown profile: ${opts.target}\n`);
|
|
87
|
-
return
|
|
88
|
+
return 2;
|
|
88
89
|
}
|
|
89
90
|
if (opts.json) {
|
|
90
91
|
process.stdout.write(asJson(found) + '\n');
|
|
@@ -171,10 +172,16 @@ async function runExplain({ cwd, json, target }) {
|
|
|
171
172
|
process.stderr.write('Usage: shrk surface explain <command>\n');
|
|
172
173
|
return 2;
|
|
173
174
|
}
|
|
174
|
-
const { context, activeProfile } = await loadSurfaceContext({ cwd });
|
|
175
|
+
const { context, activeProfile, availableProfiles } = await loadSurfaceContext({ cwd });
|
|
175
176
|
const summary = buildSurfaceSummary(context);
|
|
176
177
|
const view = findCommandInSummary(summary, target);
|
|
177
178
|
if (!view) {
|
|
179
|
+
// A common mix-up: `surface explain <profile>`. `explain` is for commands;
|
|
180
|
+
// point the user at the profile-aware verb instead of a bare "unknown".
|
|
181
|
+
if (availableProfiles.some((p) => p.id === target)) {
|
|
182
|
+
process.stderr.write(`'${target}' is a surface profile, not a command. Try: shrk surface profiles get ${target}\n`);
|
|
183
|
+
return 2;
|
|
184
|
+
}
|
|
178
185
|
process.stderr.write(`Unknown command: ${target}\n`);
|
|
179
186
|
return 2;
|
|
180
187
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-quality.command.d.ts","sourceRoot":"","sources":["../../src/commands/template-quality.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"template-quality.command.d.ts","sourceRoot":"","sources":["../../src/commands/template-quality.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAQhC,eAAO,MAAM,oBAAoB,EAAE,eAsDlC,CAAC;AA4EF,eAAO,MAAM,oBAAoB,EAAE,eAmBlC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eAuBtC,CAAC"}
|
|
@@ -3,18 +3,32 @@ import * as nodePath from 'node:path';
|
|
|
3
3
|
import { inspectSharkcraft, lintTemplates, testTemplates, } from '@shrkcrft/inspector';
|
|
4
4
|
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
5
5
|
import { asJson, header } from "../output/format-output.js";
|
|
6
|
+
import { enrichWithLlmRecommendations, renderRecommendationsMarkdown, } from '@shrkcrft/ai';
|
|
6
7
|
export const templatesLintCommand = {
|
|
7
8
|
name: 'lint',
|
|
8
|
-
description: 'Lint registered templates (titles, vars, target safety, placeholders). --fix-preview emits a TODO patch per finding under .sharkcraft/fixes/templates-lint/ (preview only — never mutates source).',
|
|
9
|
-
usage: 'shrk templates lint [<id>] [--fix-preview] [--json]',
|
|
9
|
+
description: 'Lint registered templates (titles, vars, target safety, placeholders). --fix-preview emits a TODO patch per finding under .sharkcraft/fixes/templates-lint/ (preview only — never mutates source). --llm-recommendations layers concrete next-steps onto the deterministic findings when a local LLM is reachable.',
|
|
10
|
+
usage: 'shrk templates lint [<id>] [--fix-preview] [--json] [--llm-recommendations] [--provider auto|ollama|llamacpp]',
|
|
10
11
|
async run(args) {
|
|
11
12
|
const cwd = resolveCwd(args);
|
|
12
13
|
const inspection = await inspectSharkcraft({ cwd });
|
|
13
14
|
const ids = args.positional.length > 0 ? args.positional : undefined;
|
|
14
15
|
const report = lintTemplates(inspection, ids);
|
|
15
16
|
const fixPreview = flagBool(args, 'fix-preview');
|
|
17
|
+
const wantLlm = flagBool(args, 'llm-recommendations');
|
|
18
|
+
const llmEnvelope = wantLlm
|
|
19
|
+
? await enrichWithLlmRecommendations({
|
|
20
|
+
surface: 'templates-lint',
|
|
21
|
+
deterministicSummary: summariseLintResults(report.results),
|
|
22
|
+
providerKind: flagString(args, 'provider') ?? undefined,
|
|
23
|
+
ask: 'For each non-passing template, suggest ONE concrete edit in sharkcraft/templates.ts: name the field (description, variables[i].examples, related[i], etc.) and the literal value or removal. Skip templates with only `info`-level issues unless a clear improvement exists.',
|
|
24
|
+
maxTokens: 1024,
|
|
25
|
+
})
|
|
26
|
+
: null;
|
|
16
27
|
if (flagBool(args, 'json')) {
|
|
17
|
-
process.stdout.write(asJson(
|
|
28
|
+
process.stdout.write(asJson({
|
|
29
|
+
...report,
|
|
30
|
+
...(llmEnvelope ? { llmRecommendations: llmEnvelope } : {}),
|
|
31
|
+
}) + '\n');
|
|
18
32
|
if (fixPreview)
|
|
19
33
|
writeFixPreviewPatches(cwd, report.results);
|
|
20
34
|
return report.summary.errors > 0 ? 1 : 0;
|
|
@@ -35,9 +49,31 @@ export const templatesLintCommand = {
|
|
|
35
49
|
process.stdout.write('\nNo fix-preview patches needed.\n');
|
|
36
50
|
}
|
|
37
51
|
}
|
|
52
|
+
if (llmEnvelope) {
|
|
53
|
+
process.stdout.write('\n');
|
|
54
|
+
process.stdout.write(renderRecommendationsMarkdown(llmEnvelope));
|
|
55
|
+
}
|
|
38
56
|
return report.summary.errors > 0 ? 1 : 0;
|
|
39
57
|
},
|
|
40
58
|
};
|
|
59
|
+
function summariseLintResults(results) {
|
|
60
|
+
const lines = [];
|
|
61
|
+
for (const r of results) {
|
|
62
|
+
lines.push(`## ${r.passed ? 'OK' : 'FAIL'} ${r.templateId}`);
|
|
63
|
+
if (r.issues.length === 0) {
|
|
64
|
+
lines.push('(no issues)');
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
for (const i of r.issues) {
|
|
68
|
+
lines.push(`- ${i.severity} \`${i.code}\` — ${i.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
lines.push('');
|
|
72
|
+
}
|
|
73
|
+
if (lines.length === 0)
|
|
74
|
+
lines.push('(no templates registered)');
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
41
77
|
/** Write a per-template TODO patch under `.sharkcraft/fixes/templates-lint/`. */
|
|
42
78
|
function writeFixPreviewPatches(cwd, results) {
|
|
43
79
|
const dir = nodePath.join(cwd, '.sharkcraft', 'fixes', 'templates-lint');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.command.d.ts","sourceRoot":"","sources":["../../src/commands/templates.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"templates.command.d.ts","sourceRoot":"","sources":["../../src/commands/templates.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAUhC,eAAO,MAAM,oBAAoB,EAAE,eA6DlC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAmBlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eA+CjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAgBpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiDrC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAWnC,CAAC;AAgNF,eAAO,MAAM,2BAA2B,EAAE,eA+BzC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAOnC,CAAC;AA2GF,eAAO,MAAM,wBAAwB,EAAE,eA+FtC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,eAKjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,eAyCpC,CAAC;AAwHF,eAAO,MAAM,sBAAsB,EAAE,eAiKpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAkEpC,CAAC"}
|
|
@@ -9,6 +9,7 @@ import { flagBool, flagList, flagString, flagVars, resolveCwd, } from "../comman
|
|
|
9
9
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
10
10
|
import { maybeRunInWatchMode } from "../output/watch-loop.js";
|
|
11
11
|
import { renderFailureHints, templateDriftHints } from "../output/failure-hints.js";
|
|
12
|
+
import { enrichWithLlmRecommendations, renderRecommendationsMarkdown, } from '@shrkcrft/ai';
|
|
12
13
|
export const templatesVarsCommand = {
|
|
13
14
|
name: 'vars',
|
|
14
15
|
description: 'Show the variables a template accepts (required/optional/defaults/examples).',
|
|
@@ -209,8 +210,8 @@ export const templatesPreviewCommand = {
|
|
|
209
210
|
};
|
|
210
211
|
export const templatesDriftCommand = {
|
|
211
212
|
name: 'drift',
|
|
212
|
-
description: 'Verify every registered template against the workspace. Severity controls and `--watch [--once] [--debounce N]` supported. Read-only.',
|
|
213
|
-
usage: 'shrk templates drift [--template <id>] [--pack <packId>] [--var key=value ...] [--min-severity error|warning|info] [--hide <code>[,<code>...]] [--strict] [--ci] [--format text|markdown|html|json] [--report] [--output <path>] [--json] [--watch [--once] [--debounce N]]',
|
|
213
|
+
description: 'Verify every registered template against the workspace. Severity controls and `--watch [--once] [--debounce N]` supported. `--llm-recommendations` layers a local-LLM-derived list of concrete next-steps on top of the deterministic findings (no-op when no provider reachable). Read-only.',
|
|
214
|
+
usage: 'shrk templates drift [--template <id>] [--pack <packId>] [--var key=value ...] [--min-severity error|warning|info] [--hide <code>[,<code>...]] [--strict] [--ci] [--format text|markdown|html|json] [--report] [--output <path>] [--json] [--llm-recommendations] [--provider auto|ollama|llamacpp] [--watch [--once] [--debounce N]]',
|
|
214
215
|
async run(args) {
|
|
215
216
|
const watchExit = await maybeRunInWatchMode(args, templatesDriftImpl);
|
|
216
217
|
if (watchExit !== null)
|
|
@@ -272,6 +273,16 @@ async function templatesDriftImpl(args) {
|
|
|
272
273
|
else
|
|
273
274
|
filteredFail++;
|
|
274
275
|
}
|
|
276
|
+
const wantLlmRecs = flagBool(args, 'llm-recommendations');
|
|
277
|
+
const llmEnvelope = wantLlmRecs
|
|
278
|
+
? await enrichWithLlmRecommendations({
|
|
279
|
+
surface: 'templates-drift',
|
|
280
|
+
deterministicSummary: summariseDriftEntries(filteredEntries),
|
|
281
|
+
providerKind: flagString(args, 'provider') ?? undefined,
|
|
282
|
+
ask: 'For each FAIL or WARN entry, propose ONE concrete fix the maintainer can apply — name the specific field in `sharkcraft/templates.ts` (or a peer file) to edit. Skip PASS entries unless something is genuinely worth nudging.',
|
|
283
|
+
maxTokens: 1024,
|
|
284
|
+
})
|
|
285
|
+
: null;
|
|
275
286
|
const ciPayload = {
|
|
276
287
|
...report,
|
|
277
288
|
entries: filteredEntries,
|
|
@@ -284,6 +295,7 @@ async function templatesDriftImpl(args) {
|
|
|
284
295
|
minSeverity: minSeverityRaw || 'info',
|
|
285
296
|
hideCodes: [...hideCodes],
|
|
286
297
|
},
|
|
298
|
+
...(llmEnvelope ? { llmRecommendations: llmEnvelope } : {}),
|
|
287
299
|
};
|
|
288
300
|
const exitNonZero = ci
|
|
289
301
|
? filteredFail > 0 || (strict && filteredWarn > 0)
|
|
@@ -330,8 +342,31 @@ async function templatesDriftImpl(args) {
|
|
|
330
342
|
if (exitNonZero) {
|
|
331
343
|
process.stdout.write(renderFailureHints(templateDriftHints()));
|
|
332
344
|
}
|
|
345
|
+
if (llmEnvelope) {
|
|
346
|
+
process.stdout.write('\n');
|
|
347
|
+
process.stdout.write(renderRecommendationsMarkdown(llmEnvelope));
|
|
348
|
+
}
|
|
333
349
|
return exitNonZero ? 1 : 0;
|
|
334
350
|
}
|
|
351
|
+
function summariseDriftEntries(entries) {
|
|
352
|
+
const lines = [];
|
|
353
|
+
for (const e of entries) {
|
|
354
|
+
const tag = e.status === TemplateDriftStatus.Pass ? 'PASS' : e.status === TemplateDriftStatus.Warn ? 'WARN' : 'FAIL';
|
|
355
|
+
lines.push(`## [${tag}] ${e.templateId}${e.templateName ? ` — ${e.templateName}` : ''}`);
|
|
356
|
+
if (e.issues.length === 0) {
|
|
357
|
+
lines.push('(no issues)');
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
for (const i of e.issues) {
|
|
361
|
+
lines.push(`- ${i.severity} \`${i.code}\` — ${i.message}${i.suggestedFix ? ` (suggested: ${i.suggestedFix})` : ''}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
lines.push('');
|
|
365
|
+
}
|
|
366
|
+
if (lines.length === 0)
|
|
367
|
+
lines.push('(no templates in this drift report)');
|
|
368
|
+
return lines.join('\n');
|
|
369
|
+
}
|
|
335
370
|
function renderDriftMarkdown(p) {
|
|
336
371
|
const out = [];
|
|
337
372
|
out.push('# Template drift');
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ICommandHandler } from '../command-registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* `shrk watch "<task>"` — emit a fresh task-focused context bundle on
|
|
4
|
+
* stdout JSONL each time the workspace changes.
|
|
5
|
+
*
|
|
6
|
+
* Designed to run in a terminal *next to* Claude Code so the agent has
|
|
7
|
+
* a continuously-refreshed "feed" of the most relevant code for the
|
|
8
|
+
* current task — no extra LLM calls, just BGE-ranked deltas.
|
|
9
|
+
*
|
|
10
|
+
* Surfaces:
|
|
11
|
+
* - stdout: one JSON line per packet, NDJSON-style.
|
|
12
|
+
* - filesystem: same packet appended to
|
|
13
|
+
* `.sharkcraft/feed/<slug>.jsonl` so other tools (or the user)
|
|
14
|
+
* can tail it from elsewhere.
|
|
15
|
+
*
|
|
16
|
+
* Cost: every emission is O(BGE-search + re-rank) — typically
|
|
17
|
+
* 100–300 ms for ~10 candidate files. We never call a generative LLM.
|
|
18
|
+
*/
|
|
19
|
+
export declare const watchCommand: ICommandHandler;
|
|
20
|
+
/** `shrk watch list` — show all active and stale watch manifests. */
|
|
21
|
+
export declare const watchListCommand: ICommandHandler;
|
|
22
|
+
/** `shrk watch stop <slug>` — SIGTERM the matching watcher. */
|
|
23
|
+
export declare const watchStopCommand: ICommandHandler;
|
|
24
|
+
/** `shrk watch prune` — remove stale manifests. */
|
|
25
|
+
export declare const watchPruneCommand: ICommandHandler;
|
|
26
|
+
//# sourceMappingURL=watch.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.command.d.ts","sourceRoot":"","sources":["../../src/commands/watch.command.ts"],"names":[],"mappings":"AAwBA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA2ChC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,EAAE,eAkL1B,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,EAAE,eAwD9B,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,eA4C9B,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,iBAAiB,EAAE,eA8B/B,CAAC"}
|