@shrkcrft/cli 0.1.0-alpha.2 → 0.1.0-alpha.20
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/command-registry.d.ts +28 -0
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +91 -1
- 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/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 +144 -0
- package/dist/commands/apply.command.d.ts.map +1 -1
- package/dist/commands/apply.command.js +10 -2
- 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/ask.command.d.ts.map +1 -1
- package/dist/commands/ask.command.js +10 -9
- package/dist/commands/cache-align.command.d.ts +12 -0
- package/dist/commands/cache-align.command.d.ts.map +1 -0
- package/dist/commands/cache-align.command.js +78 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +19 -2
- 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/codemod.command.d.ts.map +1 -1
- package/dist/commands/codemod.command.js +27 -6
- package/dist/commands/command-catalog.d.ts +15 -3
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +387 -34
- 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/compress.command.d.ts +8 -0
- package/dist/commands/compress.command.d.ts.map +1 -0
- package/dist/commands/compress.command.js +147 -0
- package/dist/commands/constructs.command.d.ts.map +1 -1
- package/dist/commands/constructs.command.js +89 -23
- package/dist/commands/context.command.d.ts.map +1 -1
- package/dist/commands/context.command.js +121 -1
- package/dist/commands/contract-gate.command.d.ts.map +1 -1
- package/dist/commands/contract-gate.command.js +5 -1
- package/dist/commands/delegate.command.d.ts +65 -0
- package/dist/commands/delegate.command.d.ts.map +1 -0
- package/dist/commands/delegate.command.js +657 -0
- 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 +270 -0
- package/dist/commands/dev.command.d.ts.map +1 -1
- package/dist/commands/dev.command.js +5 -1
- 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 +162 -10
- 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 +300 -0
- package/dist/commands/gen.command.d.ts.map +1 -1
- package/dist/commands/gen.command.js +13 -1
- package/dist/commands/graph-code-subverbs.d.ts +33 -0
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -0
- package/dist/commands/graph-code-subverbs.js +1366 -0
- package/dist/commands/graph.command.d.ts.map +1 -1
- package/dist/commands/graph.command.js +31 -2
- 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 +86 -18
- package/dist/commands/helper.command.js +1 -1
- package/dist/commands/impact.command.d.ts.map +1 -1
- package/dist/commands/impact.command.js +171 -1
- package/dist/commands/import.command.d.ts.map +1 -1
- package/dist/commands/import.command.js +121 -5
- package/dist/commands/ingest.command.d.ts.map +1 -1
- package/dist/commands/ingest.command.js +5 -1
- package/dist/commands/init.command.d.ts.map +1 -1
- package/dist/commands/init.command.js +174 -7
- package/dist/commands/knowledge-author.command.d.ts.map +1 -1
- package/dist/commands/knowledge-author.command.js +9 -0
- package/dist/commands/knowledge-propose.command.d.ts.map +1 -1
- package/dist/commands/knowledge-propose.command.js +4 -2
- package/dist/commands/knowledge.command.d.ts.map +1 -1
- package/dist/commands/knowledge.command.js +26 -3
- 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/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/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 +2 -10
- 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 +85 -0
- package/dist/commands/preflight.command.d.ts.map +1 -1
- package/dist/commands/preflight.command.js +15 -0
- package/dist/commands/profiles.command.js +4 -4
- package/dist/commands/recommend.command.d.ts +6 -0
- package/dist/commands/recommend.command.d.ts.map +1 -1
- package/dist/commands/recommend.command.js +119 -5
- package/dist/commands/release.command.js +13 -13
- 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/rules.command.d.ts.map +1 -1
- package/dist/commands/rules.command.js +20 -3
- 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/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/smart-context.command.d.ts +67 -0
- package/dist/commands/smart-context.command.d.ts.map +1 -0
- package/dist/commands/smart-context.command.js +4728 -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/task-context.command.d.ts.map +1 -1
- package/dist/commands/task-context.command.js +5 -17
- package/dist/commands/task.command.d.ts.map +1 -1
- package/dist/commands/task.command.js +8 -2
- 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/tests.command.d.ts.map +1 -1
- package/dist/commands/tests.command.js +13 -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/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 +329 -0
- package/dist/dashboard/dashboard-api-server.d.ts.map +1 -1
- package/dist/dashboard/dashboard-api-server.js +256 -2
- package/dist/dashboard/knowledge-ask.d.ts +4 -0
- package/dist/dashboard/knowledge-ask.d.ts.map +1 -0
- package/dist/dashboard/knowledge-ask.js +112 -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/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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- 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.map +1 -1
- package/dist/main.js +331 -17
- package/dist/output/ccr-store-config.d.ts +18 -0
- package/dist/output/ccr-store-config.d.ts.map +1 -0
- package/dist/output/ccr-store-config.js +41 -0
- package/dist/output/format-output.d.ts.map +1 -1
- package/dist/output/format-output.js +6 -1
- package/dist/output/output-compression.d.ts +15 -0
- package/dist/output/output-compression.d.ts.map +1 -0
- package/dist/output/output-compression.js +60 -0
- package/dist/output/resolve-compress-type.d.ts +22 -0
- package/dist/output/resolve-compress-type.d.ts.map +1 -0
- package/dist/output/resolve-compress-type.js +21 -0
- 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 +384 -36
- package/dist/schemas/json-schemas.d.ts.map +1 -1
- package/dist/schemas/json-schemas.js +247 -36
- package/dist/surface/profiles.d.ts.map +1 -1
- package/dist/surface/profiles.js +54 -9
- package/dist/surface/surface-config-writer.d.ts.map +1 -1
- package/dist/surface/surface-config-writer.js +23 -11
- package/dist/validation/run-validation-loop.d.ts.map +1 -1
- package/dist/validation/run-validation-loop.js +5 -1
- package/package.json +35 -21
- 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,329 @@
|
|
|
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 { detectGraphFreshness, GraphQueryApi, 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, projectRoot)
|
|
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, projectRoot) {
|
|
104
|
+
const snap = store.loadSnapshot();
|
|
105
|
+
const api = new GraphQueryApi(snap);
|
|
106
|
+
const hubs = api.topHubs(8);
|
|
107
|
+
const toRow = (h) => ({
|
|
108
|
+
id: h.node.id,
|
|
109
|
+
label: h.node.label,
|
|
110
|
+
...(h.node.path ? { path: h.node.path } : {}),
|
|
111
|
+
inDegree: h.inDegree,
|
|
112
|
+
});
|
|
113
|
+
// Freshness vs the working tree — the same signal `shrk graph status` reports.
|
|
114
|
+
// `corrupt` (store self-integrity) outranks `stale` (disk drift): a digest
|
|
115
|
+
// failure means the counts themselves can't be trusted.
|
|
116
|
+
const fresh = detectGraphFreshness(projectRoot);
|
|
117
|
+
const behind = fresh.modified.length + fresh.added.length + fresh.deleted.length;
|
|
118
|
+
const verify = store.verifyDigest();
|
|
119
|
+
const state = !verify.ok ? 'corrupt' : behind > 0 ? 'stale' : 'fresh';
|
|
120
|
+
return {
|
|
121
|
+
available: true,
|
|
122
|
+
fileCount: snap.manifest.filesIndexed,
|
|
123
|
+
nodeCount: snap.nodes.size,
|
|
124
|
+
edgeCount: snap.edges.size,
|
|
125
|
+
workspacePackages: snap.manifest.workspacePackages.length,
|
|
126
|
+
lastIndexedAt: snap.manifest.lastIndexedAt,
|
|
127
|
+
nodesByKind: snap.manifest.nodesByKind,
|
|
128
|
+
edgesByKind: snap.manifest.edgesByKind,
|
|
129
|
+
freshness: {
|
|
130
|
+
state,
|
|
131
|
+
modified: fresh.modified.length,
|
|
132
|
+
added: fresh.added.length,
|
|
133
|
+
deleted: fresh.deleted.length,
|
|
134
|
+
},
|
|
135
|
+
hubs: { symbols: hubs.symbols.map(toRow), files: hubs.files.map(toRow) },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function readBridgeSection(store) {
|
|
139
|
+
const snap = store.loadSnapshot();
|
|
140
|
+
return {
|
|
141
|
+
available: true,
|
|
142
|
+
lastBuiltAt: snap.manifest.lastBuiltAt,
|
|
143
|
+
nodesByKind: snap.manifest.nodesByKind,
|
|
144
|
+
edgesByKind: snap.manifest.edgesByKind,
|
|
145
|
+
sourceCounts: snap.manifest.sourceCounts,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function readFrameworkSection(projectRoot) {
|
|
149
|
+
const api = FrameworkQueryApi.fromStore(projectRoot);
|
|
150
|
+
const manifest = api.manifest();
|
|
151
|
+
return {
|
|
152
|
+
available: true,
|
|
153
|
+
lastBuiltAt: manifest.lastBuiltAt,
|
|
154
|
+
frameworks: manifest.frameworks,
|
|
155
|
+
countsByFramework: manifest.countsByFramework,
|
|
156
|
+
countsBySubtype: manifest.countsBySubtype,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function readArchSection(projectRoot) {
|
|
160
|
+
// Honor an optional `sharkcraft/arch.ts` if present, but never load
|
|
161
|
+
// it dynamically inside the dashboard request path — keeping HTTP
|
|
162
|
+
// handlers synchronous and avoiding arbitrary code execution under
|
|
163
|
+
// the request. Default checks only.
|
|
164
|
+
const archPath = nodePath.join(projectRoot, 'sharkcraft', 'arch.ts');
|
|
165
|
+
void archPath;
|
|
166
|
+
const report = runArchCheck({ projectRoot });
|
|
167
|
+
return {
|
|
168
|
+
available: report.diagnostics.length === 0 || !report.diagnostics.some((d) => d.includes('code-graph store missing')),
|
|
169
|
+
errors: report.countsBySeverity.error,
|
|
170
|
+
warnings: report.countsBySeverity.warning,
|
|
171
|
+
violationsByKind: report.countsByKind,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function toRow(entity) {
|
|
175
|
+
const data = entity.data ?? {};
|
|
176
|
+
return {
|
|
177
|
+
framework: String(data['framework'] ?? '?'),
|
|
178
|
+
method: String(data['method'] ?? '?'),
|
|
179
|
+
path: String(data['path'] ?? data['routePath'] ?? '/'),
|
|
180
|
+
handler: String(data['handler'] ??
|
|
181
|
+
(data['className'] && data['handler']
|
|
182
|
+
? `${data['className']}.${data['handler']}`
|
|
183
|
+
: data['name'] ?? '?')),
|
|
184
|
+
file: entity.path ?? '?',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Build the Migrations panel response.
|
|
189
|
+
*
|
|
190
|
+
* Reads every `.sharkcraft/migrations/*.state.json` (written by
|
|
191
|
+
* `@shrkcrft/migrate` after each step), shapes each into a dashboard
|
|
192
|
+
* row, and stamps `resumePoint` so the UI can highlight where a
|
|
193
|
+
* partially-failed migration would pick up.
|
|
194
|
+
*/
|
|
195
|
+
export function buildDashboardMigrations(projectRoot) {
|
|
196
|
+
const commandHints = [
|
|
197
|
+
{
|
|
198
|
+
label: 'Plan a migration',
|
|
199
|
+
command: 'shrk migrate plan <id>',
|
|
200
|
+
purpose: 'Preview the migration before any disk writes.',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
label: 'Apply a migration',
|
|
204
|
+
command: 'shrk migrate apply <id>',
|
|
205
|
+
purpose: 'Execute the migration; checkpoints are written after every step.',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
label: 'Resume a halted migration',
|
|
209
|
+
command: 'shrk migrate resume <id>',
|
|
210
|
+
purpose: 'Pick up at the failed step using the saved checkpoint.',
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
const dir = nodePath.join(projectRoot, '.sharkcraft', 'migrations');
|
|
214
|
+
if (!existsSync(dir)) {
|
|
215
|
+
return {
|
|
216
|
+
schema: 'sharkcraft.dashboard-migrations/v1',
|
|
217
|
+
available: false,
|
|
218
|
+
total: 0,
|
|
219
|
+
migrations: [],
|
|
220
|
+
commandHints,
|
|
221
|
+
hint: 'no migrations have been run yet',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const rows = [];
|
|
225
|
+
let entries;
|
|
226
|
+
try {
|
|
227
|
+
entries = readdirSync(dir);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
entries = [];
|
|
231
|
+
}
|
|
232
|
+
for (const entry of entries) {
|
|
233
|
+
if (!entry.endsWith('.state.json'))
|
|
234
|
+
continue;
|
|
235
|
+
const abs = nodePath.join(dir, entry);
|
|
236
|
+
try {
|
|
237
|
+
const report = JSON.parse(readFileSync(abs, 'utf8'));
|
|
238
|
+
const resumePoint = findResumePoint(report);
|
|
239
|
+
rows.push({
|
|
240
|
+
id: report.migration.id,
|
|
241
|
+
title: report.migration.title,
|
|
242
|
+
overall: report.overall,
|
|
243
|
+
dryRun: report.dryRun,
|
|
244
|
+
startedAt: report.startedAt,
|
|
245
|
+
totalDurationMs: report.totalDurationMs,
|
|
246
|
+
steps: report.steps.map((s) => ({
|
|
247
|
+
index: s.index,
|
|
248
|
+
id: s.id,
|
|
249
|
+
kind: s.kind,
|
|
250
|
+
status: s.status,
|
|
251
|
+
message: s.message,
|
|
252
|
+
durationMs: s.durationMs,
|
|
253
|
+
})),
|
|
254
|
+
...(resumePoint !== undefined ? { resumePoint } : {}),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
/* corrupted state file — skip silently */
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
rows.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
262
|
+
return {
|
|
263
|
+
schema: 'sharkcraft.dashboard-migrations/v1',
|
|
264
|
+
available: true,
|
|
265
|
+
total: rows.length,
|
|
266
|
+
migrations: rows,
|
|
267
|
+
commandHints,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Build the Quality-Gates panel response.
|
|
272
|
+
*
|
|
273
|
+
* Runs `runQualityGates` on request — the gate is cheap enough
|
|
274
|
+
* (<500 ms on a medium repo) that a fresh run beats stale data. The
|
|
275
|
+
* graph-fresh gate inside will surface "run `shrk graph index`" as a
|
|
276
|
+
* `nextCommand` when the store is missing, so the panel degrades
|
|
277
|
+
* gracefully.
|
|
278
|
+
*/
|
|
279
|
+
const QUALITY_GATE_REPORT_FRESH_MS = 5 * 60 * 1000;
|
|
280
|
+
export function buildDashboardQualityGates(projectRoot) {
|
|
281
|
+
const commandHints = [
|
|
282
|
+
{
|
|
283
|
+
label: 'Run the gate locally',
|
|
284
|
+
command: 'shrk gate',
|
|
285
|
+
purpose: 'Same as the panel — writes a fresh .sharkcraft/quality-gates/last.json.',
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
label: 'Fail on warnings too',
|
|
289
|
+
command: 'shrk gate --strict',
|
|
290
|
+
purpose: 'Make `warn` exit 1 (useful for CI).',
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
// Prefer a recent persisted report (written by `shrk gate`) over
|
|
294
|
+
// running every gate on every page load — the gate is cheap but the
|
|
295
|
+
// dashboard's polling loop is not.
|
|
296
|
+
const store = new QualityGateReportStore(projectRoot);
|
|
297
|
+
const saved = store.read();
|
|
298
|
+
const savedAge = store.ageMs();
|
|
299
|
+
const isFresh = saved !== undefined && savedAge !== undefined && savedAge <= QUALITY_GATE_REPORT_FRESH_MS;
|
|
300
|
+
const report = isFresh ? saved : runQualityGates({ projectRoot });
|
|
301
|
+
if (!isFresh) {
|
|
302
|
+
// Cache the freshly-computed report so subsequent dashboard
|
|
303
|
+
// requests reuse it.
|
|
304
|
+
try {
|
|
305
|
+
store.write(report);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
/* best effort */
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const gates = report.gates.map((g) => ({
|
|
312
|
+
id: g.id,
|
|
313
|
+
label: g.label,
|
|
314
|
+
status: g.status,
|
|
315
|
+
message: g.message,
|
|
316
|
+
durationMs: g.durationMs,
|
|
317
|
+
...(g.nextCommands && g.nextCommands.length > 0 ? { nextCommands: g.nextCommands } : {}),
|
|
318
|
+
}));
|
|
319
|
+
return {
|
|
320
|
+
schema: 'sharkcraft.dashboard-quality-gates/v1',
|
|
321
|
+
overall: report.overall,
|
|
322
|
+
startedAt: report.startedAt,
|
|
323
|
+
totalDurationMs: report.totalDurationMs,
|
|
324
|
+
counts: report.counts,
|
|
325
|
+
gates,
|
|
326
|
+
commandHints,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
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":"AAuIA,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;AA0lBD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -15,9 +15,71 @@ import * as http from 'node:http';
|
|
|
15
15
|
import * as fs from 'node:fs';
|
|
16
16
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
17
17
|
import * as nodePath from 'node:path';
|
|
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';
|
|
18
|
+
import { buildDashboardAdoption, buildDashboardArchitecture, buildDashboardBoundaries, buildDashboardCapabilities, buildDashboardCommands, buildDashboardCoverage, buildDashboardDoctor, buildDashboardDrift, buildDashboardGraph, buildDashboardGraphNode, buildDashboardGraphPath, buildDashboardHealth, buildDashboardKnowledgeList, buildDashboardKnowledgeEntry, buildDashboardKnowledgeGraph, buildDashboardKnowledgeSimilar, buildDashboardMcpSummary, buildDashboardOnboarding, buildDashboardOverview, buildDashboardPacks, buildDashboardPipelines, buildDashboardPresets, buildDashboardQuality, buildDashboardReports, buildDashboardReview, buildDashboardSafety, buildDashboardScaffolds, buildDashboardSchemas, buildDashboardSessionDetail, buildDashboardSessions, buildDashboardStats, inspectSharkcraft, renderDevSessionHtml, scanDevSession, } from '@shrkcrft/inspector';
|
|
19
|
+
import { EContentType, compactArrayToColumnar, estimateTokens } from '@shrkcrft/compress';
|
|
19
20
|
import { COMMAND_CATALOG } from "../commands/command-catalog.js";
|
|
21
|
+
import { buildDashboardCodeIntelligence, buildDashboardMigrations, buildDashboardQualityGates, buildDashboardRoutes, } from "./code-intelligence-data.js";
|
|
22
|
+
import { buildKnowledgeAsk } from "./knowledge-ask.js";
|
|
20
23
|
const SCHEMA_ID = 'sharkcraft.dashboard-api/v1';
|
|
24
|
+
/**
|
|
25
|
+
* Compute the deterministic compression layer's per-surface token savings for
|
|
26
|
+
* the dashboard. Measured on the live workspace (no timestamps → stable).
|
|
27
|
+
*
|
|
28
|
+
* `realTokens`, when supplied, is an exact BPE tokenizer (cl100k_base); the
|
|
29
|
+
* panel then reports exact counts and flags `tokensAreEstimated: false`. With
|
|
30
|
+
* no tokenizer (the default in a published install, where the dev-only
|
|
31
|
+
* `gpt-tokenizer` is absent) it falls back to the engine's estimator — whose
|
|
32
|
+
* *percentages* are sound but whose *absolutes* are rough — and flags
|
|
33
|
+
* `tokensAreEstimated: true` so the UI never presents an estimate as exact.
|
|
34
|
+
*/
|
|
35
|
+
function buildDashboardCompression(inspection, realTokens) {
|
|
36
|
+
const graph = buildDashboardKnowledgeGraph(inspection);
|
|
37
|
+
// Both encodings are JSON, so when estimating, score them with the JSON
|
|
38
|
+
// divisor — the same ratio the engine uses — for the closest absolute counts.
|
|
39
|
+
const count = (text) => realTokens ? realTokens(text) : estimateTokens(text, EContentType.Json);
|
|
40
|
+
const surfaces = [];
|
|
41
|
+
const add = (surface, strategy, beforeText, afterText) => {
|
|
42
|
+
const before = count(beforeText);
|
|
43
|
+
// Net-loss guard: columnar/legend overhead can exceed the raw encoding on
|
|
44
|
+
// tiny arrays. The engine ships whichever is smaller, so report what the
|
|
45
|
+
// agent actually pays — never a negative saving.
|
|
46
|
+
const after = Math.min(count(afterText), before);
|
|
47
|
+
surfaces.push({ surface, strategy, before, after, savedPct: before > 0 ? Math.round((1 - after / before) * 100) : 0 });
|
|
48
|
+
};
|
|
49
|
+
const nodes = [...graph.nodes];
|
|
50
|
+
const edges = [...graph.edges];
|
|
51
|
+
add('knowledge graph', 'columnar table', JSON.stringify({ nodes, edges }), JSON.stringify({ nodes: compactArrayToColumnar(nodes) ?? nodes, edges: compactArrayToColumnar(edges) ?? edges }));
|
|
52
|
+
add('knowledge nodes', 'columnar table', JSON.stringify(nodes), JSON.stringify(compactArrayToColumnar(nodes) ?? nodes));
|
|
53
|
+
const totalsBefore = surfaces.reduce((s, x) => s + x.before, 0);
|
|
54
|
+
const totalsAfter = surfaces.reduce((s, x) => s + x.after, 0);
|
|
55
|
+
return {
|
|
56
|
+
surfaces,
|
|
57
|
+
totals: {
|
|
58
|
+
before: totalsBefore,
|
|
59
|
+
after: totalsAfter,
|
|
60
|
+
savedPct: totalsBefore > 0 ? Math.round((1 - totalsAfter / totalsBefore) * 100) : 0,
|
|
61
|
+
},
|
|
62
|
+
tokensAreEstimated: !realTokens,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Best-effort load of the optional dev tokenizer for exact dashboard counts.
|
|
67
|
+
* Guarded dynamic import: `gpt-tokenizer` is not a runtime dependency of the
|
|
68
|
+
* CLI, so this resolves to `undefined` in any install that did not ship it, and
|
|
69
|
+
* the dashboard transparently falls back to the estimator.
|
|
70
|
+
*/
|
|
71
|
+
async function loadDashboardTokenizer() {
|
|
72
|
+
try {
|
|
73
|
+
const mod = (await import('gpt-tokenizer'));
|
|
74
|
+
if (typeof mod.encode !== 'function')
|
|
75
|
+
return undefined;
|
|
76
|
+
const encode = mod.encode;
|
|
77
|
+
return (text) => (text ? encode(text).length : 0);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
21
83
|
export async function startDashboardApiServer(opts) {
|
|
22
84
|
const host = opts.host ?? '127.0.0.1';
|
|
23
85
|
const port = opts.port ?? 0;
|
|
@@ -26,7 +88,7 @@ export async function startDashboardApiServer(opts) {
|
|
|
26
88
|
}
|
|
27
89
|
const startedAt = Date.now();
|
|
28
90
|
const sessionHubs = new Map();
|
|
29
|
-
const ctx = { opts, startedAt, sessionHubs };
|
|
91
|
+
const ctx = { opts, startedAt, sessionHubs, globalHub: null };
|
|
30
92
|
const server = http.createServer(async (req, res) => {
|
|
31
93
|
try {
|
|
32
94
|
await handle(req, res, ctx);
|
|
@@ -47,6 +109,10 @@ export async function startDashboardApiServer(opts) {
|
|
|
47
109
|
for (const hub of sessionHubs.values())
|
|
48
110
|
closeHub(hub);
|
|
49
111
|
sessionHubs.clear();
|
|
112
|
+
if (ctx.globalHub) {
|
|
113
|
+
closeGlobalHub(ctx.globalHub);
|
|
114
|
+
ctx.globalHub = null;
|
|
115
|
+
}
|
|
50
116
|
server.close(() => resolve());
|
|
51
117
|
}),
|
|
52
118
|
};
|
|
@@ -115,6 +181,121 @@ function ensureSessionHub(ctx, sessionId) {
|
|
|
115
181
|
ctx.sessionHubs.set(sessionId, hub);
|
|
116
182
|
return hub;
|
|
117
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Watched subdirectories under `.sharkcraft/` for the global hub.
|
|
186
|
+
* Order matters only for diagnostics — the event emitted is `change:<name>`.
|
|
187
|
+
*
|
|
188
|
+
* - `graph/`: the code-graph store; fires after `shrk graph index`.
|
|
189
|
+
* - `bridge/`: the rule-graph bridge.
|
|
190
|
+
* - `framework/`: framework-scanner outputs.
|
|
191
|
+
* - `migrations/`: migrate run state (one file per migration).
|
|
192
|
+
* - `api-surface/`: api-surface-diff caches.
|
|
193
|
+
* - `quality-gates/`: persisted gate reports.
|
|
194
|
+
*/
|
|
195
|
+
const GLOBAL_WATCH_DIRS = [
|
|
196
|
+
'graph',
|
|
197
|
+
'bridge',
|
|
198
|
+
'framework',
|
|
199
|
+
'migrations',
|
|
200
|
+
'api-surface',
|
|
201
|
+
'quality-gates',
|
|
202
|
+
];
|
|
203
|
+
function closeGlobalHub(hub) {
|
|
204
|
+
if (hub.debounceTimer) {
|
|
205
|
+
clearTimeout(hub.debounceTimer);
|
|
206
|
+
hub.debounceTimer = null;
|
|
207
|
+
}
|
|
208
|
+
for (const w of hub.watchers) {
|
|
209
|
+
try {
|
|
210
|
+
w.close();
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
/* ignore */
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
hub.watchers = [];
|
|
217
|
+
hub.subscribers.clear();
|
|
218
|
+
}
|
|
219
|
+
function ensureGlobalHub(ctx) {
|
|
220
|
+
if (ctx.globalHub)
|
|
221
|
+
return ctx.globalHub;
|
|
222
|
+
const hub = {
|
|
223
|
+
subscribers: new Set(),
|
|
224
|
+
watchers: [],
|
|
225
|
+
version: 0,
|
|
226
|
+
debounceTimer: null,
|
|
227
|
+
lastEvent: null,
|
|
228
|
+
};
|
|
229
|
+
const broadcast = (name) => {
|
|
230
|
+
if (hub.debounceTimer)
|
|
231
|
+
clearTimeout(hub.debounceTimer);
|
|
232
|
+
hub.debounceTimer = setTimeout(() => {
|
|
233
|
+
hub.version += 1;
|
|
234
|
+
hub.lastEvent = name;
|
|
235
|
+
const line = `event: change\ndata: ${name}\nid: ${hub.version}\n\n`;
|
|
236
|
+
for (const send of [...hub.subscribers]) {
|
|
237
|
+
try {
|
|
238
|
+
send(line);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
hub.subscribers.delete(send);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}, 200);
|
|
245
|
+
};
|
|
246
|
+
for (const dir of GLOBAL_WATCH_DIRS) {
|
|
247
|
+
const target = nodePath.join(ctx.opts.cwd, '.sharkcraft', dir);
|
|
248
|
+
if (!fs.existsSync(target))
|
|
249
|
+
continue;
|
|
250
|
+
try {
|
|
251
|
+
const w = fs.watch(target, { recursive: true }, () => broadcast(dir));
|
|
252
|
+
hub.watchers.push(w);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
try {
|
|
256
|
+
const w = fs.watch(target, () => broadcast(dir));
|
|
257
|
+
hub.watchers.push(w);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
/* ignore */
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
ctx.globalHub = hub;
|
|
265
|
+
return hub;
|
|
266
|
+
}
|
|
267
|
+
function serveGlobalEvents(req, res, ctx) {
|
|
268
|
+
const hub = ensureGlobalHub(ctx);
|
|
269
|
+
res.writeHead(200, {
|
|
270
|
+
'content-type': 'text/event-stream',
|
|
271
|
+
'cache-control': 'no-store',
|
|
272
|
+
'x-content-type-options': 'nosniff',
|
|
273
|
+
connection: 'keep-alive',
|
|
274
|
+
});
|
|
275
|
+
const send = (line) => {
|
|
276
|
+
res.write(line);
|
|
277
|
+
};
|
|
278
|
+
hub.subscribers.add(send);
|
|
279
|
+
send(`event: hello\ndata: ${hub.lastEvent ?? 'ready'}\nid: ${hub.version}\n\n`);
|
|
280
|
+
const ping = setInterval(() => {
|
|
281
|
+
try {
|
|
282
|
+
res.write(`: ping\n\n`);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
/* ignore */
|
|
286
|
+
}
|
|
287
|
+
}, 15000);
|
|
288
|
+
const close = () => {
|
|
289
|
+
clearInterval(ping);
|
|
290
|
+
hub.subscribers.delete(send);
|
|
291
|
+
if (hub.subscribers.size === 0) {
|
|
292
|
+
closeGlobalHub(hub);
|
|
293
|
+
ctx.globalHub = null;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
req.on('close', close);
|
|
297
|
+
req.on('end', close);
|
|
298
|
+
}
|
|
118
299
|
async function handle(req, res, ctx) {
|
|
119
300
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
120
301
|
res.setHeader('allow', 'GET, HEAD');
|
|
@@ -129,11 +310,21 @@ async function handle(req, res, ctx) {
|
|
|
129
310
|
if (!path.startsWith('/api/') && opts.staticDir) {
|
|
130
311
|
return serveStatic(res, opts.staticDir, path);
|
|
131
312
|
}
|
|
313
|
+
// Non-API request with no static UI installed. Common when @shrkcrft/cli
|
|
314
|
+
// is consumed without @shrkcrft/dashboard present. Return a helpful page
|
|
315
|
+
// instead of falling through to the cryptic "Unknown route: /".
|
|
316
|
+
if (!path.startsWith('/api/') && !opts.staticDir) {
|
|
317
|
+
return serveNoUiHint(res, path);
|
|
318
|
+
}
|
|
132
319
|
// Session live events (SSE). Subscribe via EventSource on the session detail page.
|
|
133
320
|
const sseMatch = path.match(/^\/api\/sessions\/([^/]+)\/events$/);
|
|
134
321
|
if (sseMatch) {
|
|
135
322
|
return serveSessionEvents(req, res, ctx, decodeURIComponent(sseMatch[1]));
|
|
136
323
|
}
|
|
324
|
+
// Global live events (SSE). Subscribers refetch on every change.
|
|
325
|
+
if (path === '/api/events') {
|
|
326
|
+
return serveGlobalEvents(req, res, ctx);
|
|
327
|
+
}
|
|
137
328
|
// Rendered HTML report for the session (or any report on disk under the
|
|
138
329
|
// session dir). Served with a content-security-policy that disallows
|
|
139
330
|
// anything but inline styles — the report renderer never emits scripts,
|
|
@@ -172,6 +363,18 @@ async function handle(req, res, ctx) {
|
|
|
172
363
|
});
|
|
173
364
|
return respond(res, buildEnvelope(projectRoot, stats));
|
|
174
365
|
}
|
|
366
|
+
if (path === '/api/code-intelligence') {
|
|
367
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardCodeIntelligence(projectRoot)));
|
|
368
|
+
}
|
|
369
|
+
if (path === '/api/routes') {
|
|
370
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardRoutes(projectRoot)));
|
|
371
|
+
}
|
|
372
|
+
if (path === '/api/migrations') {
|
|
373
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardMigrations(projectRoot)));
|
|
374
|
+
}
|
|
375
|
+
if (path === '/api/quality-gates') {
|
|
376
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardQualityGates(projectRoot)));
|
|
377
|
+
}
|
|
175
378
|
// For data routes that need inspection, load once per request.
|
|
176
379
|
const needsInspection = path.startsWith('/api/overview') ||
|
|
177
380
|
path.startsWith('/api/doctor') ||
|
|
@@ -182,8 +385,10 @@ async function handle(req, res, ctx) {
|
|
|
182
385
|
path.startsWith('/api/pipelines') ||
|
|
183
386
|
path.startsWith('/api/architecture') ||
|
|
184
387
|
path.startsWith('/api/graph') ||
|
|
388
|
+
path.startsWith('/api/knowledge') ||
|
|
185
389
|
path.startsWith('/api/onboarding') ||
|
|
186
390
|
path.startsWith('/api/review') ||
|
|
391
|
+
path.startsWith('/api/compression') ||
|
|
187
392
|
path.startsWith('/api/scaffolds');
|
|
188
393
|
const inspection = needsInspection
|
|
189
394
|
? await inspectSharkcraft({ cwd: projectRoot })
|
|
@@ -203,6 +408,10 @@ async function handle(req, res, ctx) {
|
|
|
203
408
|
if (path === '/api/packs') {
|
|
204
409
|
return respond(res, buildEnvelope(projectRoot, buildDashboardPacks(inspection)));
|
|
205
410
|
}
|
|
411
|
+
if (path === '/api/compression') {
|
|
412
|
+
const realTokens = await loadDashboardTokenizer();
|
|
413
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardCompression(inspection, realTokens)));
|
|
414
|
+
}
|
|
206
415
|
if (path === '/api/presets') {
|
|
207
416
|
return respond(res, buildEnvelope(projectRoot, buildDashboardPresets(inspection)));
|
|
208
417
|
}
|
|
@@ -235,6 +444,32 @@ async function handle(req, res, ctx) {
|
|
|
235
444
|
return respondError(res, 400, 'bad-request', 'from and to query params required');
|
|
236
445
|
return respond(res, buildEnvelope(projectRoot, buildDashboardGraphPath(inspection, from, to)));
|
|
237
446
|
}
|
|
447
|
+
// Knowledge explorer. Specific sub-paths (ask / graph / entry) are matched
|
|
448
|
+
// before the bare list route. The ask route synthesizes a grounded answer
|
|
449
|
+
// with the local LLM — still a read: it never writes, and is wall-clock
|
|
450
|
+
// bounded so it cannot hang the server.
|
|
451
|
+
if (path === '/api/knowledge/ask') {
|
|
452
|
+
const q = url.searchParams.get('q');
|
|
453
|
+
if (!q || !q.trim())
|
|
454
|
+
return respondError(res, 400, 'bad-request', 'q query param required');
|
|
455
|
+
return respond(res, buildEnvelope(projectRoot, await buildKnowledgeAsk(inspection, q)));
|
|
456
|
+
}
|
|
457
|
+
if (path === '/api/knowledge/graph') {
|
|
458
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardKnowledgeGraph(inspection)));
|
|
459
|
+
}
|
|
460
|
+
const knowledgeSimilarMatch = path.match(/^\/api\/knowledge\/similar\/(.+)$/);
|
|
461
|
+
if (knowledgeSimilarMatch) {
|
|
462
|
+
const id = decodeURIComponent(knowledgeSimilarMatch[1]);
|
|
463
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardKnowledgeSimilar(inspection, id)));
|
|
464
|
+
}
|
|
465
|
+
const knowledgeEntryMatch = path.match(/^\/api\/knowledge\/entry\/(.+)$/);
|
|
466
|
+
if (knowledgeEntryMatch) {
|
|
467
|
+
const id = decodeURIComponent(knowledgeEntryMatch[1]);
|
|
468
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardKnowledgeEntry(inspection, id)));
|
|
469
|
+
}
|
|
470
|
+
if (path === '/api/knowledge') {
|
|
471
|
+
return respond(res, buildEnvelope(projectRoot, buildDashboardKnowledgeList(inspection)));
|
|
472
|
+
}
|
|
238
473
|
if (path === '/api/onboarding') {
|
|
239
474
|
return respond(res, buildEnvelope(projectRoot, buildDashboardOnboarding(inspection)));
|
|
240
475
|
}
|
|
@@ -369,6 +604,25 @@ function serveSessionReportHtml(res, cwd, sessionId) {
|
|
|
369
604
|
}
|
|
370
605
|
res.end(html);
|
|
371
606
|
}
|
|
607
|
+
function serveNoUiHint(res, urlPath) {
|
|
608
|
+
const accept = (res.req?.headers['accept'] ?? '');
|
|
609
|
+
const wantsHtml = accept.includes('text/html');
|
|
610
|
+
if (wantsHtml) {
|
|
611
|
+
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>`;
|
|
612
|
+
res.statusCode = 200;
|
|
613
|
+
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
614
|
+
res.setHeader('cache-control', 'no-store');
|
|
615
|
+
res.setHeader('x-content-type-options', 'nosniff');
|
|
616
|
+
res.setHeader('referrer-policy', 'no-referrer');
|
|
617
|
+
if ((res.req?.method ?? 'GET') === 'HEAD') {
|
|
618
|
+
res.end();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
res.end(html);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
respondError(res, 404, 'not-found', `No UI assets installed; ${urlPath} cannot be served. Install @shrkcrft/dashboard or pass --dev-assets.`);
|
|
625
|
+
}
|
|
372
626
|
function serveStatic(res, staticDir, urlPath) {
|
|
373
627
|
// Strip leading / and resolve against staticDir. Reject traversal.
|
|
374
628
|
const rel = urlPath.replace(/^\/+/, '');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ISharkcraftInspection } from '@shrkcrft/inspector';
|
|
2
|
+
import type { IDashboardKnowledgeAskResponse } from '@shrkcrft/dashboard-api';
|
|
3
|
+
export declare function buildKnowledgeAsk(inspection: ISharkcraftInspection, question: string): Promise<IDashboardKnowledgeAskResponse>;
|
|
4
|
+
//# sourceMappingURL=knowledge-ask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-ask.d.ts","sourceRoot":"","sources":["../../src/dashboard/knowledge-ask.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EACV,8BAA8B,EAE/B,MAAM,yBAAyB,CAAC;AAwCjC,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,qBAAqB,EACjC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,8BAA8B,CAAC,CA0EzC"}
|