@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.
- 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 +162 -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 +37 -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
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { runArchCheck } from '@shrkcrft/architecture-guard';
|
|
4
|
+
import { FrameworkQueryApi } from '@shrkcrft/framework-scanners';
|
|
5
|
+
import { GraphStore } from '@shrkcrft/graph';
|
|
6
|
+
import { findResumePoint, } from '@shrkcrft/migrate';
|
|
7
|
+
import { QualityGateReportStore, runQualityGates } from '@shrkcrft/quality-gates';
|
|
8
|
+
import { BridgeStore } from '@shrkcrft/rule-graph';
|
|
9
|
+
/**
|
|
10
|
+
* Build the Code Intelligence overview response.
|
|
11
|
+
*
|
|
12
|
+
* Reads the three on-disk stores plus runs the architecture-guard
|
|
13
|
+
* check inline. Every section degrades to `available: false` with a
|
|
14
|
+
* `hint` when its backing store is missing — the dashboard renders
|
|
15
|
+
* those as "run `shrk graph index` to enable" cards rather than
|
|
16
|
+
* blocking the whole panel.
|
|
17
|
+
*
|
|
18
|
+
* Pure read — never builds or writes anything.
|
|
19
|
+
*/
|
|
20
|
+
export function buildDashboardCodeIntelligence(projectRoot) {
|
|
21
|
+
const commandHints = [
|
|
22
|
+
{ label: 'Build code graph', command: 'shrk graph index', purpose: 'Refresh the persistent code-graph store.' },
|
|
23
|
+
{ label: 'Build rule-graph bridge', command: 'shrk rule-graph index', purpose: 'Rebuild bridge edges from files to assets.' },
|
|
24
|
+
{ label: 'Build framework index', command: 'shrk framework index', purpose: 'Detect framework entities (NestJS, React, etc.).' },
|
|
25
|
+
{ label: 'Run architecture checks', command: 'shrk arch check', purpose: 'Surface public-API misuse, cycles, fat barrels.' },
|
|
26
|
+
];
|
|
27
|
+
// Graph.
|
|
28
|
+
const graphStore = new GraphStore(projectRoot);
|
|
29
|
+
const graph = graphStore.exists()
|
|
30
|
+
? readGraphSection(graphStore)
|
|
31
|
+
: { available: false, hint: "run 'shrk graph index'" };
|
|
32
|
+
// Bridge.
|
|
33
|
+
const bridgeStore = new BridgeStore(projectRoot);
|
|
34
|
+
const bridge = bridgeStore.exists()
|
|
35
|
+
? readBridgeSection(bridgeStore)
|
|
36
|
+
: { available: false, hint: "run 'shrk rule-graph index'" };
|
|
37
|
+
// Framework.
|
|
38
|
+
const framework = FrameworkQueryApi.missingDescription(projectRoot)
|
|
39
|
+
? { available: false, hint: "run 'shrk framework index'" }
|
|
40
|
+
: readFrameworkSection(projectRoot);
|
|
41
|
+
// Architecture (depends on graph store existing).
|
|
42
|
+
const architecture = graph.available
|
|
43
|
+
? readArchSection(projectRoot)
|
|
44
|
+
: { available: false, errors: 0, warnings: 0, hint: 'graph index missing' };
|
|
45
|
+
return {
|
|
46
|
+
schema: 'sharkcraft.dashboard-code-intelligence/v1',
|
|
47
|
+
available: graph.available || bridge.available || framework.available,
|
|
48
|
+
graph,
|
|
49
|
+
bridge,
|
|
50
|
+
framework,
|
|
51
|
+
architecture,
|
|
52
|
+
commandHints,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build the cross-framework routes panel response.
|
|
57
|
+
*
|
|
58
|
+
* Reads the framework store. For each entity with `subtype: 'route'`
|
|
59
|
+
* (or `api-route`), emits a row with `framework`, `method`, `path`,
|
|
60
|
+
* `handler`, and `file`. Sorted alphabetically by (framework, method,
|
|
61
|
+
* path).
|
|
62
|
+
*/
|
|
63
|
+
export function buildDashboardRoutes(projectRoot) {
|
|
64
|
+
const commandHints = [
|
|
65
|
+
{ label: 'Build framework index', command: 'shrk framework index', purpose: 'Detect routes / components across frameworks.' },
|
|
66
|
+
{ label: 'List a single framework', command: 'shrk framework list --framework <name>', purpose: 'Filter the entity list to one framework.' },
|
|
67
|
+
];
|
|
68
|
+
const missing = FrameworkQueryApi.missingDescription(projectRoot);
|
|
69
|
+
if (missing) {
|
|
70
|
+
return {
|
|
71
|
+
schema: 'sharkcraft.dashboard-routes/v1',
|
|
72
|
+
available: false,
|
|
73
|
+
total: 0,
|
|
74
|
+
byFramework: {},
|
|
75
|
+
routes: [],
|
|
76
|
+
commandHints,
|
|
77
|
+
hint: missing,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const api = FrameworkQueryApi.fromStore(projectRoot);
|
|
81
|
+
const rows = [];
|
|
82
|
+
for (const entity of api.list({ subtype: 'route', limit: 5000 })) {
|
|
83
|
+
rows.push(toRow(entity));
|
|
84
|
+
}
|
|
85
|
+
for (const entity of api.list({ subtype: 'api-route', limit: 5000 })) {
|
|
86
|
+
rows.push(toRow(entity));
|
|
87
|
+
}
|
|
88
|
+
rows.sort((a, b) => a.framework.localeCompare(b.framework) ||
|
|
89
|
+
a.method.localeCompare(b.method) ||
|
|
90
|
+
a.path.localeCompare(b.path));
|
|
91
|
+
const byFramework = {};
|
|
92
|
+
for (const r of rows)
|
|
93
|
+
byFramework[r.framework] = (byFramework[r.framework] ?? 0) + 1;
|
|
94
|
+
return {
|
|
95
|
+
schema: 'sharkcraft.dashboard-routes/v1',
|
|
96
|
+
available: true,
|
|
97
|
+
total: rows.length,
|
|
98
|
+
byFramework,
|
|
99
|
+
routes: rows,
|
|
100
|
+
commandHints,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function readGraphSection(store) {
|
|
104
|
+
const snap = store.loadSnapshot();
|
|
105
|
+
return {
|
|
106
|
+
available: true,
|
|
107
|
+
fileCount: snap.manifest.filesIndexed,
|
|
108
|
+
nodeCount: snap.nodes.size,
|
|
109
|
+
edgeCount: snap.edges.size,
|
|
110
|
+
workspacePackages: snap.manifest.workspacePackages.length,
|
|
111
|
+
lastIndexedAt: snap.manifest.lastIndexedAt,
|
|
112
|
+
nodesByKind: snap.manifest.nodesByKind,
|
|
113
|
+
edgesByKind: snap.manifest.edgesByKind,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function readBridgeSection(store) {
|
|
117
|
+
const snap = store.loadSnapshot();
|
|
118
|
+
return {
|
|
119
|
+
available: true,
|
|
120
|
+
lastBuiltAt: snap.manifest.lastBuiltAt,
|
|
121
|
+
nodesByKind: snap.manifest.nodesByKind,
|
|
122
|
+
edgesByKind: snap.manifest.edgesByKind,
|
|
123
|
+
sourceCounts: snap.manifest.sourceCounts,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function readFrameworkSection(projectRoot) {
|
|
127
|
+
const api = FrameworkQueryApi.fromStore(projectRoot);
|
|
128
|
+
const manifest = api.manifest();
|
|
129
|
+
return {
|
|
130
|
+
available: true,
|
|
131
|
+
lastBuiltAt: manifest.lastBuiltAt,
|
|
132
|
+
frameworks: manifest.frameworks,
|
|
133
|
+
countsByFramework: manifest.countsByFramework,
|
|
134
|
+
countsBySubtype: manifest.countsBySubtype,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function readArchSection(projectRoot) {
|
|
138
|
+
// Honor an optional `sharkcraft/arch.ts` if present, but never load
|
|
139
|
+
// it dynamically inside the dashboard request path — keeping HTTP
|
|
140
|
+
// handlers synchronous and avoiding arbitrary code execution under
|
|
141
|
+
// the request. Default checks only.
|
|
142
|
+
const archPath = nodePath.join(projectRoot, 'sharkcraft', 'arch.ts');
|
|
143
|
+
void archPath;
|
|
144
|
+
const report = runArchCheck({ projectRoot });
|
|
145
|
+
return {
|
|
146
|
+
available: report.diagnostics.length === 0 || !report.diagnostics.some((d) => d.includes('code-graph store missing')),
|
|
147
|
+
errors: report.countsBySeverity.error,
|
|
148
|
+
warnings: report.countsBySeverity.warning,
|
|
149
|
+
violationsByKind: report.countsByKind,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function toRow(entity) {
|
|
153
|
+
const data = entity.data ?? {};
|
|
154
|
+
return {
|
|
155
|
+
framework: String(data['framework'] ?? '?'),
|
|
156
|
+
method: String(data['method'] ?? '?'),
|
|
157
|
+
path: String(data['path'] ?? data['routePath'] ?? '/'),
|
|
158
|
+
handler: String(data['handler'] ??
|
|
159
|
+
(data['className'] && data['handler']
|
|
160
|
+
? `${data['className']}.${data['handler']}`
|
|
161
|
+
: data['name'] ?? '?')),
|
|
162
|
+
file: entity.path ?? '?',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build the Migrations panel response.
|
|
167
|
+
*
|
|
168
|
+
* Reads every `.sharkcraft/migrations/*.state.json` (written by
|
|
169
|
+
* `@shrkcrft/migrate` after each step), shapes each into a dashboard
|
|
170
|
+
* row, and stamps `resumePoint` so the UI can highlight where a
|
|
171
|
+
* partially-failed migration would pick up.
|
|
172
|
+
*/
|
|
173
|
+
export function buildDashboardMigrations(projectRoot) {
|
|
174
|
+
const commandHints = [
|
|
175
|
+
{
|
|
176
|
+
label: 'Plan a migration',
|
|
177
|
+
command: 'shrk migrate plan <id>',
|
|
178
|
+
purpose: 'Preview the migration before any disk writes.',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
label: 'Apply a migration',
|
|
182
|
+
command: 'shrk migrate apply <id>',
|
|
183
|
+
purpose: 'Execute the migration; checkpoints are written after every step.',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
label: 'Resume a halted migration',
|
|
187
|
+
command: 'shrk migrate resume <id>',
|
|
188
|
+
purpose: 'Pick up at the failed step using the saved checkpoint.',
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
const dir = nodePath.join(projectRoot, '.sharkcraft', 'migrations');
|
|
192
|
+
if (!existsSync(dir)) {
|
|
193
|
+
return {
|
|
194
|
+
schema: 'sharkcraft.dashboard-migrations/v1',
|
|
195
|
+
available: false,
|
|
196
|
+
total: 0,
|
|
197
|
+
migrations: [],
|
|
198
|
+
commandHints,
|
|
199
|
+
hint: 'no migrations have been run yet',
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const rows = [];
|
|
203
|
+
let entries;
|
|
204
|
+
try {
|
|
205
|
+
entries = readdirSync(dir);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
entries = [];
|
|
209
|
+
}
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (!entry.endsWith('.state.json'))
|
|
212
|
+
continue;
|
|
213
|
+
const abs = nodePath.join(dir, entry);
|
|
214
|
+
try {
|
|
215
|
+
const report = JSON.parse(readFileSync(abs, 'utf8'));
|
|
216
|
+
const resumePoint = findResumePoint(report);
|
|
217
|
+
rows.push({
|
|
218
|
+
id: report.migration.id,
|
|
219
|
+
title: report.migration.title,
|
|
220
|
+
overall: report.overall,
|
|
221
|
+
dryRun: report.dryRun,
|
|
222
|
+
startedAt: report.startedAt,
|
|
223
|
+
totalDurationMs: report.totalDurationMs,
|
|
224
|
+
steps: report.steps.map((s) => ({
|
|
225
|
+
index: s.index,
|
|
226
|
+
id: s.id,
|
|
227
|
+
kind: s.kind,
|
|
228
|
+
status: s.status,
|
|
229
|
+
message: s.message,
|
|
230
|
+
durationMs: s.durationMs,
|
|
231
|
+
})),
|
|
232
|
+
...(resumePoint !== undefined ? { resumePoint } : {}),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
/* corrupted state file — skip silently */
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
rows.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
240
|
+
return {
|
|
241
|
+
schema: 'sharkcraft.dashboard-migrations/v1',
|
|
242
|
+
available: true,
|
|
243
|
+
total: rows.length,
|
|
244
|
+
migrations: rows,
|
|
245
|
+
commandHints,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Build the Quality-Gates panel response.
|
|
250
|
+
*
|
|
251
|
+
* Runs `runQualityGates` on request — the gate is cheap enough
|
|
252
|
+
* (<500 ms on a medium repo) that a fresh run beats stale data. The
|
|
253
|
+
* graph-fresh gate inside will surface "run `shrk graph index`" as a
|
|
254
|
+
* `nextCommand` when the store is missing, so the panel degrades
|
|
255
|
+
* gracefully.
|
|
256
|
+
*/
|
|
257
|
+
const QUALITY_GATE_REPORT_FRESH_MS = 5 * 60 * 1000;
|
|
258
|
+
export function buildDashboardQualityGates(projectRoot) {
|
|
259
|
+
const commandHints = [
|
|
260
|
+
{
|
|
261
|
+
label: 'Run the gate locally',
|
|
262
|
+
command: 'shrk gate',
|
|
263
|
+
purpose: 'Same as the panel — writes a fresh .sharkcraft/quality-gates/last.json.',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
label: 'Fail on warnings too',
|
|
267
|
+
command: 'shrk gate --strict',
|
|
268
|
+
purpose: 'Make `warn` exit 1 (useful for CI).',
|
|
269
|
+
},
|
|
270
|
+
];
|
|
271
|
+
// Prefer a recent persisted report (written by `shrk gate`) over
|
|
272
|
+
// running every gate on every page load — the gate is cheap but the
|
|
273
|
+
// dashboard's polling loop is not.
|
|
274
|
+
const store = new QualityGateReportStore(projectRoot);
|
|
275
|
+
const saved = store.read();
|
|
276
|
+
const savedAge = store.ageMs();
|
|
277
|
+
const isFresh = saved !== undefined && savedAge !== undefined && savedAge <= QUALITY_GATE_REPORT_FRESH_MS;
|
|
278
|
+
const report = isFresh ? saved : runQualityGates({ projectRoot });
|
|
279
|
+
if (!isFresh) {
|
|
280
|
+
// Cache the freshly-computed report so subsequent dashboard
|
|
281
|
+
// requests reuse it.
|
|
282
|
+
try {
|
|
283
|
+
store.write(report);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
/* best effort */
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const gates = report.gates.map((g) => ({
|
|
290
|
+
id: g.id,
|
|
291
|
+
label: g.label,
|
|
292
|
+
status: g.status,
|
|
293
|
+
message: g.message,
|
|
294
|
+
durationMs: g.durationMs,
|
|
295
|
+
...(g.nextCommands && g.nextCommands.length > 0 ? { nextCommands: g.nextCommands } : {}),
|
|
296
|
+
}));
|
|
297
|
+
return {
|
|
298
|
+
schema: 'sharkcraft.dashboard-quality-gates/v1',
|
|
299
|
+
overall: report.overall,
|
|
300
|
+
startedAt: report.startedAt,
|
|
301
|
+
totalDurationMs: report.totalDurationMs,
|
|
302
|
+
counts: report.counts,
|
|
303
|
+
gates,
|
|
304
|
+
commandHints,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
void statSync;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-api-server.d.ts","sourceRoot":"","sources":["../../src/dashboard/dashboard-api-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dashboard-api-server.d.ts","sourceRoot":"","sources":["../../src/dashboard/dashboard-api-server.ts"],"names":[],"mappings":"AA4DA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AA2BD,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAqC1F;AA2jBD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -17,6 +17,7 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
|
17
17
|
import * as nodePath from 'node:path';
|
|
18
18
|
import { buildDashboardAdoption, buildDashboardArchitecture, buildDashboardBoundaries, buildDashboardCapabilities, buildDashboardCommands, buildDashboardCoverage, buildDashboardDoctor, buildDashboardDrift, buildDashboardGraph, buildDashboardGraphNode, buildDashboardGraphPath, buildDashboardHealth, buildDashboardMcpSummary, buildDashboardOnboarding, buildDashboardOverview, buildDashboardPacks, buildDashboardPipelines, buildDashboardPresets, buildDashboardQuality, buildDashboardReports, buildDashboardReview, buildDashboardSafety, buildDashboardScaffolds, buildDashboardSchemas, buildDashboardSessionDetail, buildDashboardSessions, buildDashboardStats, inspectSharkcraft, renderDevSessionHtml, scanDevSession, } from '@shrkcrft/inspector';
|
|
19
19
|
import { COMMAND_CATALOG } from "../commands/command-catalog.js";
|
|
20
|
+
import { buildDashboardCodeIntelligence, buildDashboardMigrations, buildDashboardQualityGates, buildDashboardRoutes, } from "./code-intelligence-data.js";
|
|
20
21
|
const SCHEMA_ID = 'sharkcraft.dashboard-api/v1';
|
|
21
22
|
export async function startDashboardApiServer(opts) {
|
|
22
23
|
const host = opts.host ?? '127.0.0.1';
|
|
@@ -26,7 +27,7 @@ export async function startDashboardApiServer(opts) {
|
|
|
26
27
|
}
|
|
27
28
|
const startedAt = Date.now();
|
|
28
29
|
const sessionHubs = new Map();
|
|
29
|
-
const ctx = { opts, startedAt, sessionHubs };
|
|
30
|
+
const ctx = { opts, startedAt, sessionHubs, globalHub: null };
|
|
30
31
|
const server = http.createServer(async (req, res) => {
|
|
31
32
|
try {
|
|
32
33
|
await handle(req, res, ctx);
|
|
@@ -47,6 +48,10 @@ export async function startDashboardApiServer(opts) {
|
|
|
47
48
|
for (const hub of sessionHubs.values())
|
|
48
49
|
closeHub(hub);
|
|
49
50
|
sessionHubs.clear();
|
|
51
|
+
if (ctx.globalHub) {
|
|
52
|
+
closeGlobalHub(ctx.globalHub);
|
|
53
|
+
ctx.globalHub = null;
|
|
54
|
+
}
|
|
50
55
|
server.close(() => resolve());
|
|
51
56
|
}),
|
|
52
57
|
};
|
|
@@ -115,6 +120,121 @@ function ensureSessionHub(ctx, sessionId) {
|
|
|
115
120
|
ctx.sessionHubs.set(sessionId, hub);
|
|
116
121
|
return hub;
|
|
117
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Watched subdirectories under `.sharkcraft/` for the global hub.
|
|
125
|
+
* Order matters only for diagnostics — the event emitted is `change:<name>`.
|
|
126
|
+
*
|
|
127
|
+
* - `graph/`: the code-graph store; fires after `shrk graph index`.
|
|
128
|
+
* - `bridge/`: the rule-graph bridge.
|
|
129
|
+
* - `framework/`: framework-scanner outputs.
|
|
130
|
+
* - `migrations/`: migrate run state (one file per migration).
|
|
131
|
+
* - `api-surface/`: api-surface-diff caches.
|
|
132
|
+
* - `quality-gates/`: persisted gate reports.
|
|
133
|
+
*/
|
|
134
|
+
const GLOBAL_WATCH_DIRS = [
|
|
135
|
+
'graph',
|
|
136
|
+
'bridge',
|
|
137
|
+
'framework',
|
|
138
|
+
'migrations',
|
|
139
|
+
'api-surface',
|
|
140
|
+
'quality-gates',
|
|
141
|
+
];
|
|
142
|
+
function closeGlobalHub(hub) {
|
|
143
|
+
if (hub.debounceTimer) {
|
|
144
|
+
clearTimeout(hub.debounceTimer);
|
|
145
|
+
hub.debounceTimer = null;
|
|
146
|
+
}
|
|
147
|
+
for (const w of hub.watchers) {
|
|
148
|
+
try {
|
|
149
|
+
w.close();
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* ignore */
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
hub.watchers = [];
|
|
156
|
+
hub.subscribers.clear();
|
|
157
|
+
}
|
|
158
|
+
function ensureGlobalHub(ctx) {
|
|
159
|
+
if (ctx.globalHub)
|
|
160
|
+
return ctx.globalHub;
|
|
161
|
+
const hub = {
|
|
162
|
+
subscribers: new Set(),
|
|
163
|
+
watchers: [],
|
|
164
|
+
version: 0,
|
|
165
|
+
debounceTimer: null,
|
|
166
|
+
lastEvent: null,
|
|
167
|
+
};
|
|
168
|
+
const broadcast = (name) => {
|
|
169
|
+
if (hub.debounceTimer)
|
|
170
|
+
clearTimeout(hub.debounceTimer);
|
|
171
|
+
hub.debounceTimer = setTimeout(() => {
|
|
172
|
+
hub.version += 1;
|
|
173
|
+
hub.lastEvent = name;
|
|
174
|
+
const line = `event: change\ndata: ${name}\nid: ${hub.version}\n\n`;
|
|
175
|
+
for (const send of [...hub.subscribers]) {
|
|
176
|
+
try {
|
|
177
|
+
send(line);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
hub.subscribers.delete(send);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}, 200);
|
|
184
|
+
};
|
|
185
|
+
for (const dir of GLOBAL_WATCH_DIRS) {
|
|
186
|
+
const target = nodePath.join(ctx.opts.cwd, '.sharkcraft', dir);
|
|
187
|
+
if (!fs.existsSync(target))
|
|
188
|
+
continue;
|
|
189
|
+
try {
|
|
190
|
+
const w = fs.watch(target, { recursive: true }, () => broadcast(dir));
|
|
191
|
+
hub.watchers.push(w);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
try {
|
|
195
|
+
const w = fs.watch(target, () => broadcast(dir));
|
|
196
|
+
hub.watchers.push(w);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
/* ignore */
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
ctx.globalHub = hub;
|
|
204
|
+
return hub;
|
|
205
|
+
}
|
|
206
|
+
function serveGlobalEvents(req, res, ctx) {
|
|
207
|
+
const hub = ensureGlobalHub(ctx);
|
|
208
|
+
res.writeHead(200, {
|
|
209
|
+
'content-type': 'text/event-stream',
|
|
210
|
+
'cache-control': 'no-store',
|
|
211
|
+
'x-content-type-options': 'nosniff',
|
|
212
|
+
connection: 'keep-alive',
|
|
213
|
+
});
|
|
214
|
+
const send = (line) => {
|
|
215
|
+
res.write(line);
|
|
216
|
+
};
|
|
217
|
+
hub.subscribers.add(send);
|
|
218
|
+
send(`event: hello\ndata: ${hub.lastEvent ?? 'ready'}\nid: ${hub.version}\n\n`);
|
|
219
|
+
const ping = setInterval(() => {
|
|
220
|
+
try {
|
|
221
|
+
res.write(`: ping\n\n`);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* ignore */
|
|
225
|
+
}
|
|
226
|
+
}, 15000);
|
|
227
|
+
const close = () => {
|
|
228
|
+
clearInterval(ping);
|
|
229
|
+
hub.subscribers.delete(send);
|
|
230
|
+
if (hub.subscribers.size === 0) {
|
|
231
|
+
closeGlobalHub(hub);
|
|
232
|
+
ctx.globalHub = null;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
req.on('close', close);
|
|
236
|
+
req.on('end', close);
|
|
237
|
+
}
|
|
118
238
|
async function handle(req, res, ctx) {
|
|
119
239
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
120
240
|
res.setHeader('allow', 'GET, HEAD');
|
|
@@ -129,11 +249,21 @@ async function handle(req, res, ctx) {
|
|
|
129
249
|
if (!path.startsWith('/api/') && opts.staticDir) {
|
|
130
250
|
return serveStatic(res, opts.staticDir, path);
|
|
131
251
|
}
|
|
252
|
+
// Non-API request with no static UI installed. Common when @shrkcrft/cli
|
|
253
|
+
// is consumed without @shrkcrft/dashboard present. Return a helpful page
|
|
254
|
+
// instead of falling through to the cryptic "Unknown route: /".
|
|
255
|
+
if (!path.startsWith('/api/') && !opts.staticDir) {
|
|
256
|
+
return serveNoUiHint(res, path);
|
|
257
|
+
}
|
|
132
258
|
// Session live events (SSE). Subscribe via EventSource on the session detail page.
|
|
133
259
|
const sseMatch = path.match(/^\/api\/sessions\/([^/]+)\/events$/);
|
|
134
260
|
if (sseMatch) {
|
|
135
261
|
return serveSessionEvents(req, res, ctx, decodeURIComponent(sseMatch[1]));
|
|
136
262
|
}
|
|
263
|
+
// Global live events (SSE). Subscribers refetch on every change.
|
|
264
|
+
if (path === '/api/events') {
|
|
265
|
+
return serveGlobalEvents(req, res, ctx);
|
|
266
|
+
}
|
|
137
267
|
// Rendered HTML report for the session (or any report on disk under the
|
|
138
268
|
// session dir). Served with a content-security-policy that disallows
|
|
139
269
|
// anything but inline styles — the report renderer never emits scripts,
|
|
@@ -172,6 +302,18 @@ async function handle(req, res, ctx) {
|
|
|
172
302
|
});
|
|
173
303
|
return respond(res, buildEnvelope(projectRoot, stats));
|
|
174
304
|
}
|
|
305
|
+
if (path === '/api/code-intelligence') {
|
|
306
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardCodeIntelligence(projectRoot)));
|
|
307
|
+
}
|
|
308
|
+
if (path === '/api/routes') {
|
|
309
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardRoutes(projectRoot)));
|
|
310
|
+
}
|
|
311
|
+
if (path === '/api/migrations') {
|
|
312
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardMigrations(projectRoot)));
|
|
313
|
+
}
|
|
314
|
+
if (path === '/api/quality-gates') {
|
|
315
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardQualityGates(projectRoot)));
|
|
316
|
+
}
|
|
175
317
|
// For data routes that need inspection, load once per request.
|
|
176
318
|
const needsInspection = path.startsWith('/api/overview') ||
|
|
177
319
|
path.startsWith('/api/doctor') ||
|
|
@@ -369,6 +511,25 @@ function serveSessionReportHtml(res, cwd, sessionId) {
|
|
|
369
511
|
}
|
|
370
512
|
res.end(html);
|
|
371
513
|
}
|
|
514
|
+
function serveNoUiHint(res, urlPath) {
|
|
515
|
+
const accept = (res.req?.headers['accept'] ?? '');
|
|
516
|
+
const wantsHtml = accept.includes('text/html');
|
|
517
|
+
if (wantsHtml) {
|
|
518
|
+
const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>SharkCraft dashboard — UI not installed</title><meta name="viewport" content="width=device-width,initial-scale=1"><style>body{font-family:ui-sans-serif,system-ui,-apple-system,sans-serif;max-width:680px;margin:48px auto;padding:0 16px;color:#1f2937;line-height:1.55}code,pre{background:#f3f4f6;padding:2px 6px;border-radius:4px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}pre{padding:12px;overflow:auto}h1{font-size:20px;margin-top:0}a{color:#2563eb}</style></head><body><h1>SharkCraft dashboard</h1><p>The API is running, but the web UI bundle (<code>@shrkcrft/dashboard</code>) was not found next to <code>@shrkcrft/cli</code>.</p><p>Install or upgrade it alongside the CLI:</p><pre>npm i -D @shrkcrft/dashboard@alpha</pre><p>Or point the server at a local build:</p><pre>shrk dashboard --dev-assets path/to/dashboard/dist</pre><p>The API itself is available — try <a href="/api/health">/api/health</a> or <a href="/api/overview">/api/overview</a>.</p></body></html>`;
|
|
519
|
+
res.statusCode = 200;
|
|
520
|
+
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
521
|
+
res.setHeader('cache-control', 'no-store');
|
|
522
|
+
res.setHeader('x-content-type-options', 'nosniff');
|
|
523
|
+
res.setHeader('referrer-policy', 'no-referrer');
|
|
524
|
+
if ((res.req?.method ?? 'GET') === 'HEAD') {
|
|
525
|
+
res.end();
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
res.end(html);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
respondError(res, 404, 'not-found', `No UI assets installed; ${urlPath} cannot be served. Install @shrkcrft/dashboard or pass --dev-assets.`);
|
|
532
|
+
}
|
|
372
533
|
function serveStatic(res, staticDir, urlPath) {
|
|
373
534
|
// Strip leading / and resolve against staticDir. Reject traversal.
|
|
374
535
|
const rel = urlPath.replace(/^\/+/, '');
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.claude/commands/` generator — emits per-project slash commands
|
|
3
|
+
* for Claude Code. Companion to `claude-skill` export, but with a
|
|
4
|
+
* different inversion semantics:
|
|
5
|
+
*
|
|
6
|
+
* - **claude-skill** loads rules into Claude's prompt automatically
|
|
7
|
+
* based on description match. Passive — Claude reads the rules.
|
|
8
|
+
* - **claude-commands** registers slash commands the USER invokes
|
|
9
|
+
* (`/new-service`, `/check-changes`). Active — the command IS the
|
|
10
|
+
* recipe; Claude follows it step-by-step.
|
|
11
|
+
*
|
|
12
|
+
* Generated commands fall into two buckets:
|
|
13
|
+
*
|
|
14
|
+
* **Static commands** (always present, project-agnostic recipes):
|
|
15
|
+
* - `/follow-shrk` — short reminder of the apply-gate flow.
|
|
16
|
+
* - `/check-changes` — runs `shrk check boundaries --changed-only`
|
|
17
|
+
* scoped to the current diff and reports back.
|
|
18
|
+
* - `/shrk-brief` — runs `shrk brief` and uses the output for the
|
|
19
|
+
* current task.
|
|
20
|
+
* - `/explain-file <path>` — per-file rules / paths / boundary
|
|
21
|
+
* lookup (pairs with `shrk advise <path>` from Phase 3).
|
|
22
|
+
*
|
|
23
|
+
* **Per-template commands** (one per id in `sharkcraft/templates.ts`):
|
|
24
|
+
* - `/new-<template-id>` — Claude runs `shrk gen <template-id>
|
|
25
|
+
* <name> --dry-run --save-plan ...`, reviews the plan, and
|
|
26
|
+
* applies via `shrk apply ... --verify-signature --validate`.
|
|
27
|
+
*
|
|
28
|
+
* Generated files are self-contained markdown — no `@shrkcrft/*`
|
|
29
|
+
* imports, no shell expansions. Each one is a complete "recipe in a
|
|
30
|
+
* file" that Claude Code reads when the user types the slash command.
|
|
31
|
+
*/
|
|
32
|
+
import type { ISharkcraftInspection } from '@shrkcrft/inspector';
|
|
33
|
+
export interface IClaudeCommandFile {
|
|
34
|
+
/** Path relative to project root (e.g. `.claude/commands/new-service.md`). */
|
|
35
|
+
path: string;
|
|
36
|
+
/** Full markdown body, including YAML frontmatter. */
|
|
37
|
+
content: string;
|
|
38
|
+
/** The slash name users type (e.g. `new-service` → `/new-service`). */
|
|
39
|
+
slash: string;
|
|
40
|
+
/** Why this command was generated — surfaces in the dry-run summary. */
|
|
41
|
+
source: 'static' | 'template';
|
|
42
|
+
}
|
|
43
|
+
export interface IClaudeCommandsResult {
|
|
44
|
+
files: readonly IClaudeCommandFile[];
|
|
45
|
+
}
|
|
46
|
+
export interface IClaudeCommandsOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Cap on the number of per-template commands to emit. Default 20.
|
|
49
|
+
* Templates are sorted by id; the first N are kept.
|
|
50
|
+
*/
|
|
51
|
+
maxTemplateCommands?: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the full set of `.claude/commands/*.md` files for a project.
|
|
55
|
+
*
|
|
56
|
+
* Pure — caller writes the bytes. Same shape as the `synthesize-*`
|
|
57
|
+
* functions in this codebase: input inspection → output file list.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildClaudeCommands(inspection: ISharkcraftInspection, options?: IClaudeCommandsOptions): IClaudeCommandsResult;
|
|
60
|
+
//# sourceMappingURL=claude-commands-export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-commands-export.d.ts","sourceRoot":"","sources":["../../src/export/claude-commands-export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACtC;AA0MD,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,qBAAqB,EACjC,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAsDvB"}
|