@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.
Files changed (228) hide show
  1. package/dist/audit/knowledge-audit-llm.d.ts +19 -0
  2. package/dist/audit/knowledge-audit-llm.d.ts.map +1 -0
  3. package/dist/audit/knowledge-audit-llm.js +164 -0
  4. package/dist/audit/knowledge-audit.d.ts +61 -0
  5. package/dist/audit/knowledge-audit.d.ts.map +1 -0
  6. package/dist/audit/knowledge-audit.js +203 -0
  7. package/dist/audit/knowledge-fix-plan-llm.d.ts +11 -0
  8. package/dist/audit/knowledge-fix-plan-llm.d.ts.map +1 -0
  9. package/dist/audit/knowledge-fix-plan-llm.js +141 -0
  10. package/dist/audit/knowledge-fix-plan.d.ts +41 -0
  11. package/dist/audit/knowledge-fix-plan.d.ts.map +1 -0
  12. package/dist/audit/knowledge-fix-plan.js +125 -0
  13. package/dist/audit/pipeline-audit-llm.d.ts +11 -0
  14. package/dist/audit/pipeline-audit-llm.d.ts.map +1 -0
  15. package/dist/audit/pipeline-audit-llm.js +134 -0
  16. package/dist/audit/pipeline-audit.d.ts +69 -0
  17. package/dist/audit/pipeline-audit.d.ts.map +1 -0
  18. package/dist/audit/pipeline-audit.js +166 -0
  19. package/dist/audit/templates-audit-llm.d.ts +19 -0
  20. package/dist/audit/templates-audit-llm.d.ts.map +1 -0
  21. package/dist/audit/templates-audit-llm.js +207 -0
  22. package/dist/audit/templates-audit.d.ts +63 -0
  23. package/dist/audit/templates-audit.d.ts.map +1 -0
  24. package/dist/audit/templates-audit.js +171 -0
  25. package/dist/audit/templates-fix-plan-llm.d.ts +19 -0
  26. package/dist/audit/templates-fix-plan-llm.d.ts.map +1 -0
  27. package/dist/audit/templates-fix-plan-llm.js +162 -0
  28. package/dist/audit/templates-fix-plan.d.ts +37 -0
  29. package/dist/audit/templates-fix-plan.d.ts.map +1 -0
  30. package/dist/audit/templates-fix-plan.js +174 -0
  31. package/dist/command-registry.d.ts +28 -0
  32. package/dist/command-registry.d.ts.map +1 -1
  33. package/dist/command-registry.js +91 -1
  34. package/dist/commands/ai-status.command.d.ts +19 -0
  35. package/dist/commands/ai-status.command.d.ts.map +1 -0
  36. package/dist/commands/ai-status.command.js +94 -0
  37. package/dist/commands/api-diff.command.d.ts +11 -0
  38. package/dist/commands/api-diff.command.d.ts.map +1 -0
  39. package/dist/commands/api-diff.command.js +144 -0
  40. package/dist/commands/apply.command.d.ts.map +1 -1
  41. package/dist/commands/apply.command.js +10 -2
  42. package/dist/commands/arch.command.d.ts +9 -0
  43. package/dist/commands/arch.command.d.ts.map +1 -0
  44. package/dist/commands/arch.command.js +186 -0
  45. package/dist/commands/ask.command.d.ts.map +1 -1
  46. package/dist/commands/ask.command.js +10 -9
  47. package/dist/commands/cache-align.command.d.ts +12 -0
  48. package/dist/commands/cache-align.command.d.ts.map +1 -0
  49. package/dist/commands/cache-align.command.js +78 -0
  50. package/dist/commands/check.command.d.ts.map +1 -1
  51. package/dist/commands/check.command.js +19 -2
  52. package/dist/commands/code-intel.command.d.ts +18 -0
  53. package/dist/commands/code-intel.command.d.ts.map +1 -0
  54. package/dist/commands/code-intel.command.js +146 -0
  55. package/dist/commands/codemod.command.d.ts.map +1 -1
  56. package/dist/commands/codemod.command.js +27 -6
  57. package/dist/commands/command-catalog.d.ts +15 -3
  58. package/dist/commands/command-catalog.d.ts.map +1 -1
  59. package/dist/commands/command-catalog.js +387 -34
  60. package/dist/commands/commands.command.d.ts.map +1 -1
  61. package/dist/commands/commands.command.js +4 -4
  62. package/dist/commands/completion.command.d.ts +10 -0
  63. package/dist/commands/completion.command.d.ts.map +1 -0
  64. package/dist/commands/completion.command.js +121 -0
  65. package/dist/commands/compress.command.d.ts +8 -0
  66. package/dist/commands/compress.command.d.ts.map +1 -0
  67. package/dist/commands/compress.command.js +147 -0
  68. package/dist/commands/constructs.command.d.ts.map +1 -1
  69. package/dist/commands/constructs.command.js +89 -23
  70. package/dist/commands/context.command.d.ts.map +1 -1
  71. package/dist/commands/context.command.js +121 -1
  72. package/dist/commands/contract-gate.command.d.ts.map +1 -1
  73. package/dist/commands/contract-gate.command.js +5 -1
  74. package/dist/commands/delegate.command.d.ts +65 -0
  75. package/dist/commands/delegate.command.d.ts.map +1 -0
  76. package/dist/commands/delegate.command.js +657 -0
  77. package/dist/commands/deps-audit.command.d.ts +23 -0
  78. package/dist/commands/deps-audit.command.d.ts.map +1 -0
  79. package/dist/commands/deps-audit.command.js +270 -0
  80. package/dist/commands/dev.command.d.ts.map +1 -1
  81. package/dist/commands/dev.command.js +5 -1
  82. package/dist/commands/diff-check.command.d.ts +30 -0
  83. package/dist/commands/diff-check.command.d.ts.map +1 -0
  84. package/dist/commands/diff-check.command.js +210 -0
  85. package/dist/commands/doctor.command.d.ts.map +1 -1
  86. package/dist/commands/doctor.command.js +162 -10
  87. package/dist/commands/export.command.d.ts.map +1 -1
  88. package/dist/commands/export.command.js +76 -3
  89. package/dist/commands/framework.command.d.ts +12 -0
  90. package/dist/commands/framework.command.d.ts.map +1 -0
  91. package/dist/commands/framework.command.js +180 -0
  92. package/dist/commands/gate.command.d.ts +15 -0
  93. package/dist/commands/gate.command.d.ts.map +1 -0
  94. package/dist/commands/gate.command.js +300 -0
  95. package/dist/commands/gen.command.d.ts.map +1 -1
  96. package/dist/commands/gen.command.js +13 -1
  97. package/dist/commands/graph-code-subverbs.d.ts +33 -0
  98. package/dist/commands/graph-code-subverbs.d.ts.map +1 -0
  99. package/dist/commands/graph-code-subverbs.js +1366 -0
  100. package/dist/commands/graph.command.d.ts.map +1 -1
  101. package/dist/commands/graph.command.js +31 -2
  102. package/dist/commands/help.command.d.ts +4 -3
  103. package/dist/commands/help.command.d.ts.map +1 -1
  104. package/dist/commands/help.command.js +86 -18
  105. package/dist/commands/helper.command.js +1 -1
  106. package/dist/commands/impact.command.d.ts.map +1 -1
  107. package/dist/commands/impact.command.js +171 -1
  108. package/dist/commands/import.command.d.ts.map +1 -1
  109. package/dist/commands/import.command.js +121 -5
  110. package/dist/commands/ingest.command.d.ts.map +1 -1
  111. package/dist/commands/ingest.command.js +5 -1
  112. package/dist/commands/init.command.d.ts.map +1 -1
  113. package/dist/commands/init.command.js +174 -7
  114. package/dist/commands/knowledge-author.command.d.ts.map +1 -1
  115. package/dist/commands/knowledge-author.command.js +9 -0
  116. package/dist/commands/knowledge-propose.command.d.ts.map +1 -1
  117. package/dist/commands/knowledge-propose.command.js +4 -2
  118. package/dist/commands/knowledge.command.d.ts.map +1 -1
  119. package/dist/commands/knowledge.command.js +26 -3
  120. package/dist/commands/migrate.command.d.ts +13 -0
  121. package/dist/commands/migrate.command.d.ts.map +1 -0
  122. package/dist/commands/migrate.command.js +152 -0
  123. package/dist/commands/move-plan.command.d.ts +23 -0
  124. package/dist/commands/move-plan.command.d.ts.map +1 -0
  125. package/dist/commands/move-plan.command.js +360 -0
  126. package/dist/commands/packs-new.d.ts +1 -1
  127. package/dist/commands/packs-new.d.ts.map +1 -1
  128. package/dist/commands/packs-new.js +5 -36
  129. package/dist/commands/packs.command.d.ts.map +1 -1
  130. package/dist/commands/packs.command.js +2 -10
  131. package/dist/commands/plan-context.command.d.ts +11 -0
  132. package/dist/commands/plan-context.command.d.ts.map +1 -0
  133. package/dist/commands/plan-context.command.js +85 -0
  134. package/dist/commands/preflight.command.d.ts.map +1 -1
  135. package/dist/commands/preflight.command.js +15 -0
  136. package/dist/commands/profiles.command.js +4 -4
  137. package/dist/commands/recommend.command.d.ts +6 -0
  138. package/dist/commands/recommend.command.d.ts.map +1 -1
  139. package/dist/commands/recommend.command.js +119 -5
  140. package/dist/commands/release.command.js +13 -13
  141. package/dist/commands/rule-graph-subverbs.d.ts +3 -0
  142. package/dist/commands/rule-graph-subverbs.d.ts.map +1 -0
  143. package/dist/commands/rule-graph-subverbs.js +132 -0
  144. package/dist/commands/rules.command.d.ts.map +1 -1
  145. package/dist/commands/rules.command.js +20 -3
  146. package/dist/commands/scaffold-validate.command.d.ts +22 -0
  147. package/dist/commands/scaffold-validate.command.d.ts.map +1 -0
  148. package/dist/commands/scaffold-validate.command.js +215 -0
  149. package/dist/commands/search-structural.command.d.ts +18 -0
  150. package/dist/commands/search-structural.command.d.ts.map +1 -0
  151. package/dist/commands/search-structural.command.js +376 -0
  152. package/dist/commands/search.command.js +1 -1
  153. package/dist/commands/smart-context.command.d.ts +67 -0
  154. package/dist/commands/smart-context.command.d.ts.map +1 -0
  155. package/dist/commands/smart-context.command.js +4728 -0
  156. package/dist/commands/spike.command.d.ts +22 -0
  157. package/dist/commands/spike.command.d.ts.map +1 -0
  158. package/dist/commands/spike.command.js +235 -0
  159. package/dist/commands/surface.command.d.ts +1 -0
  160. package/dist/commands/surface.command.d.ts.map +1 -1
  161. package/dist/commands/surface.command.js +10 -3
  162. package/dist/commands/task-context.command.d.ts.map +1 -1
  163. package/dist/commands/task-context.command.js +5 -17
  164. package/dist/commands/task.command.d.ts.map +1 -1
  165. package/dist/commands/task.command.js +8 -2
  166. package/dist/commands/template-quality.command.d.ts.map +1 -1
  167. package/dist/commands/template-quality.command.js +39 -3
  168. package/dist/commands/templates.command.d.ts.map +1 -1
  169. package/dist/commands/templates.command.js +37 -2
  170. package/dist/commands/tests.command.d.ts.map +1 -1
  171. package/dist/commands/tests.command.js +13 -2
  172. package/dist/commands/watch.command.d.ts +26 -0
  173. package/dist/commands/watch.command.d.ts.map +1 -0
  174. package/dist/commands/watch.command.js +456 -0
  175. package/dist/dashboard/code-intelligence-data.d.ts +33 -0
  176. package/dist/dashboard/code-intelligence-data.d.ts.map +1 -0
  177. package/dist/dashboard/code-intelligence-data.js +329 -0
  178. package/dist/dashboard/dashboard-api-server.d.ts.map +1 -1
  179. package/dist/dashboard/dashboard-api-server.js +256 -2
  180. package/dist/dashboard/knowledge-ask.d.ts +4 -0
  181. package/dist/dashboard/knowledge-ask.d.ts.map +1 -0
  182. package/dist/dashboard/knowledge-ask.js +112 -0
  183. package/dist/env/load-dotenv.d.ts +15 -0
  184. package/dist/env/load-dotenv.d.ts.map +1 -0
  185. package/dist/env/load-dotenv.js +70 -0
  186. package/dist/export/claude-commands-export.d.ts +60 -0
  187. package/dist/export/claude-commands-export.d.ts.map +1 -0
  188. package/dist/export/claude-commands-export.js +276 -0
  189. package/dist/export/export-formats.d.ts +1 -1
  190. package/dist/export/export-formats.d.ts.map +1 -1
  191. package/dist/export/export-formats.js +139 -12
  192. package/dist/index.d.ts +3 -0
  193. package/dist/index.d.ts.map +1 -1
  194. package/dist/index.js +3 -0
  195. package/dist/init/init-templates.d.ts.map +1 -1
  196. package/dist/init/init-templates.js +133 -113
  197. package/dist/init/paths-advisory.d.ts +20 -0
  198. package/dist/init/paths-advisory.d.ts.map +1 -0
  199. package/dist/init/paths-advisory.js +88 -0
  200. package/dist/main.d.ts.map +1 -1
  201. package/dist/main.js +331 -17
  202. package/dist/output/ccr-store-config.d.ts +18 -0
  203. package/dist/output/ccr-store-config.d.ts.map +1 -0
  204. package/dist/output/ccr-store-config.js +41 -0
  205. package/dist/output/format-output.d.ts.map +1 -1
  206. package/dist/output/format-output.js +6 -1
  207. package/dist/output/output-compression.d.ts +15 -0
  208. package/dist/output/output-compression.d.ts.map +1 -0
  209. package/dist/output/output-compression.js +60 -0
  210. package/dist/output/resolve-compress-type.d.ts +22 -0
  211. package/dist/output/resolve-compress-type.d.ts.map +1 -0
  212. package/dist/output/resolve-compress-type.js +21 -0
  213. package/dist/output/watch-loop.d.ts +9 -1
  214. package/dist/output/watch-loop.d.ts.map +1 -1
  215. package/dist/output/watch-loop.js +13 -3
  216. package/dist/schemas/json-schemas.d.ts +384 -36
  217. package/dist/schemas/json-schemas.d.ts.map +1 -1
  218. package/dist/schemas/json-schemas.js +247 -36
  219. package/dist/surface/profiles.d.ts.map +1 -1
  220. package/dist/surface/profiles.js +54 -9
  221. package/dist/surface/surface-config-writer.d.ts.map +1 -1
  222. package/dist/surface/surface-config-writer.js +23 -11
  223. package/dist/validation/run-validation-loop.d.ts.map +1 -1
  224. package/dist/validation/run-validation-loop.js +5 -1
  225. package/package.json +35 -21
  226. package/dist/commands/plugin.command.d.ts +0 -11
  227. package/dist/commands/plugin.command.d.ts.map +0 -1
  228. 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":"AAsDA,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;AAcD,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAiC1F;AA4ZD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
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"}