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

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 (41) hide show
  1. package/dist/commands/ask.command.d.ts.map +1 -1
  2. package/dist/commands/ask.command.js +10 -9
  3. package/dist/commands/command-catalog.d.ts.map +1 -1
  4. package/dist/commands/command-catalog.js +100 -1
  5. package/dist/commands/deps-audit.command.d.ts +23 -0
  6. package/dist/commands/deps-audit.command.d.ts.map +1 -0
  7. package/dist/commands/deps-audit.command.js +266 -0
  8. package/dist/commands/doctor.command.d.ts.map +1 -1
  9. package/dist/commands/doctor.command.js +60 -1
  10. package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
  11. package/dist/commands/graph-code-subverbs.js +144 -26
  12. package/dist/commands/graph.command.d.ts.map +1 -1
  13. package/dist/commands/graph.command.js +3 -2
  14. package/dist/commands/help.command.d.ts.map +1 -1
  15. package/dist/commands/help.command.js +22 -1
  16. package/dist/commands/impact.command.d.ts.map +1 -1
  17. package/dist/commands/impact.command.js +3 -2
  18. package/dist/commands/move-plan.command.d.ts +23 -0
  19. package/dist/commands/move-plan.command.d.ts.map +1 -0
  20. package/dist/commands/move-plan.command.js +360 -0
  21. package/dist/commands/scaffold-validate.command.d.ts +22 -0
  22. package/dist/commands/scaffold-validate.command.d.ts.map +1 -0
  23. package/dist/commands/scaffold-validate.command.js +215 -0
  24. package/dist/commands/smart-context.command.d.ts +30 -0
  25. package/dist/commands/smart-context.command.d.ts.map +1 -0
  26. package/dist/commands/smart-context.command.js +3763 -0
  27. package/dist/commands/spike.command.d.ts +22 -0
  28. package/dist/commands/spike.command.d.ts.map +1 -0
  29. package/dist/commands/spike.command.js +235 -0
  30. package/dist/commands/watch.command.d.ts +26 -0
  31. package/dist/commands/watch.command.d.ts.map +1 -0
  32. package/dist/commands/watch.command.js +456 -0
  33. package/dist/env/load-dotenv.d.ts +15 -0
  34. package/dist/env/load-dotenv.d.ts.map +1 -0
  35. package/dist/env/load-dotenv.js +70 -0
  36. package/dist/main.d.ts.map +1 -1
  37. package/dist/main.js +83 -2
  38. package/dist/schemas/json-schemas.d.ts +384 -36
  39. package/dist/schemas/json-schemas.d.ts.map +1 -1
  40. package/dist/schemas/json-schemas.js +247 -36
  41. 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;
@@ -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"}