@rkarim08/sia 1.0.0

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 (355) hide show
  1. package/.claude-plugin/marketplace.json +35 -0
  2. package/.claude-plugin/plugin.json +27 -0
  3. package/.mcp.json +13 -0
  4. package/CLAUDE.md +226 -0
  5. package/LICENSE +202 -0
  6. package/PLUGIN_README.md +253 -0
  7. package/README.md +1013 -0
  8. package/agents/sia-changelog-writer.md +89 -0
  9. package/agents/sia-code-reviewer.md +86 -0
  10. package/agents/sia-conflict-resolver.md +100 -0
  11. package/agents/sia-convention-enforcer.md +69 -0
  12. package/agents/sia-debug.md +106 -0
  13. package/agents/sia-decision-reviewer.md +101 -0
  14. package/agents/sia-dependency-tracker.md +80 -0
  15. package/agents/sia-explain.md +126 -0
  16. package/agents/sia-feature.md +116 -0
  17. package/agents/sia-knowledge-capture.md +117 -0
  18. package/agents/sia-lead-architecture-advisor.md +93 -0
  19. package/agents/sia-lead-team-health.md +107 -0
  20. package/agents/sia-migration.md +100 -0
  21. package/agents/sia-onboarding.md +115 -0
  22. package/agents/sia-orientation.md +99 -0
  23. package/agents/sia-pm-briefing.md +106 -0
  24. package/agents/sia-pm-risk-advisor.md +82 -0
  25. package/agents/sia-qa-analyst.md +116 -0
  26. package/agents/sia-qa-regression-map.md +94 -0
  27. package/agents/sia-refactor.md +115 -0
  28. package/agents/sia-regression.md +112 -0
  29. package/agents/sia-security-audit.md +125 -0
  30. package/agents/sia-test-advisor.md +91 -0
  31. package/hooks/hooks.json +98 -0
  32. package/migrations/bridge/001_initial.sql +34 -0
  33. package/migrations/episodic/001_initial.sql +35 -0
  34. package/migrations/meta/001_initial.sql +68 -0
  35. package/migrations/semantic/001_initial.sql +292 -0
  36. package/migrations/semantic/002_ontology.sql +89 -0
  37. package/migrations/semantic/003_freshness.sql +63 -0
  38. package/migrations/semantic/004_v5_unified_schema.sql +194 -0
  39. package/migrations/semantic/005_backfill_event_kinds.sql +8 -0
  40. package/migrations/semantic/006_tree_sitter.sql +6 -0
  41. package/migrations/semantic/007_branch_snapshots.sql +22 -0
  42. package/package.json +110 -0
  43. package/scripts/branch-switch.sh +13 -0
  44. package/scripts/build-wasm-grammars.sh +81 -0
  45. package/scripts/post-compact.sh +8 -0
  46. package/scripts/post-tool-use.sh +10 -0
  47. package/scripts/pre-compact.sh +8 -0
  48. package/scripts/session-end.sh +8 -0
  49. package/scripts/session-start.sh +8 -0
  50. package/scripts/start-mcp.ts +45 -0
  51. package/scripts/stop-hook.sh +8 -0
  52. package/scripts/user-prompt-submit.sh +8 -0
  53. package/scripts/viz-server.ts +152 -0
  54. package/skills/sia-brainstorm/SKILL.md +156 -0
  55. package/skills/sia-brainstorm/scripts/frame-template.html +214 -0
  56. package/skills/sia-brainstorm/scripts/helper.js +95 -0
  57. package/skills/sia-brainstorm/scripts/server.cjs +338 -0
  58. package/skills/sia-brainstorm/scripts/start-server.sh +153 -0
  59. package/skills/sia-brainstorm/scripts/stop-server.sh +55 -0
  60. package/skills/sia-brainstorm/spec-document-reviewer-prompt.md +49 -0
  61. package/skills/sia-brainstorm/visual-companion.md +286 -0
  62. package/skills/sia-capture/SKILL.md +64 -0
  63. package/skills/sia-compare/SKILL.md +33 -0
  64. package/skills/sia-conflicts/SKILL.md +38 -0
  65. package/skills/sia-debug-workflow/SKILL.md +120 -0
  66. package/skills/sia-debug-workflow/root-cause-tracing.md +70 -0
  67. package/skills/sia-debug-workflow/scripts/find-polluter.sh +64 -0
  68. package/skills/sia-debug-workflow/temporal-investigation.md +72 -0
  69. package/skills/sia-digest/SKILL.md +23 -0
  70. package/skills/sia-dispatch/SKILL.md +69 -0
  71. package/skills/sia-dispatch/agent-task-template.md +99 -0
  72. package/skills/sia-doctor/SKILL.md +39 -0
  73. package/skills/sia-execute/SKILL.md +70 -0
  74. package/skills/sia-execute-plan/SKILL.md +85 -0
  75. package/skills/sia-export-import/SKILL.md +49 -0
  76. package/skills/sia-export-knowledge/SKILL.md +46 -0
  77. package/skills/sia-finish/SKILL.md +100 -0
  78. package/skills/sia-finish/pr-summary-template.md +54 -0
  79. package/skills/sia-freshness/SKILL.md +38 -0
  80. package/skills/sia-history/SKILL.md +42 -0
  81. package/skills/sia-impact/SKILL.md +70 -0
  82. package/skills/sia-index/SKILL.md +54 -0
  83. package/skills/sia-install/SKILL.md +39 -0
  84. package/skills/sia-lead-compliance/SKILL.md +16 -0
  85. package/skills/sia-lead-drift-report/SKILL.md +16 -0
  86. package/skills/sia-lead-knowledge-map/SKILL.md +16 -0
  87. package/skills/sia-learn/SKILL.md +58 -0
  88. package/skills/sia-plan/SKILL.md +68 -0
  89. package/skills/sia-plan/plan-reviewer-prompt.md +63 -0
  90. package/skills/sia-playbooks/SKILL.md +29 -0
  91. package/skills/sia-playbooks/reference-feature.md +100 -0
  92. package/skills/sia-playbooks/reference-flagging.md +50 -0
  93. package/skills/sia-playbooks/reference-orientation.md +92 -0
  94. package/skills/sia-playbooks/reference-regression.md +115 -0
  95. package/skills/sia-playbooks/reference-review.md +64 -0
  96. package/skills/sia-playbooks/reference-tools.md +239 -0
  97. package/skills/sia-pm-decision-log/SKILL.md +28 -0
  98. package/skills/sia-pm-risk-dashboard/SKILL.md +24 -0
  99. package/skills/sia-pm-sprint-summary/SKILL.md +27 -0
  100. package/skills/sia-prune/SKILL.md +45 -0
  101. package/skills/sia-qa-coverage/SKILL.md +28 -0
  102. package/skills/sia-qa-flaky/SKILL.md +20 -0
  103. package/skills/sia-qa-report/SKILL.md +26 -0
  104. package/skills/sia-reindex/SKILL.md +30 -0
  105. package/skills/sia-review-respond/SKILL.md +88 -0
  106. package/skills/sia-review-respond/pushback-patterns.md +90 -0
  107. package/skills/sia-search/SKILL.md +47 -0
  108. package/skills/sia-setup/SKILL.md +82 -0
  109. package/skills/sia-setup/setup-checklist.md +97 -0
  110. package/skills/sia-stats/SKILL.md +36 -0
  111. package/skills/sia-status/SKILL.md +44 -0
  112. package/skills/sia-sync/SKILL.md +46 -0
  113. package/skills/sia-team/SKILL.md +64 -0
  114. package/skills/sia-test/SKILL.md +92 -0
  115. package/skills/sia-test/testing-anti-patterns.md +104 -0
  116. package/skills/sia-tour/SKILL.md +29 -0
  117. package/skills/sia-upgrade/SKILL.md +43 -0
  118. package/skills/sia-verify/SKILL.md +81 -0
  119. package/skills/sia-visualize/SKILL.md +28 -0
  120. package/skills/sia-visualize-live/SKILL.md +55 -0
  121. package/skills/sia-visualize-live/scripts/graph-template.html +389 -0
  122. package/skills/sia-visualize-live/scripts/start-visualizer.sh +161 -0
  123. package/skills/sia-visualize-live/scripts/stop-visualizer.sh +55 -0
  124. package/skills/sia-visualize-live/scripts/visualizer-server.cjs +264 -0
  125. package/skills/sia-workspace/SKILL.md +57 -0
  126. package/src/agent/claude-md-template-flagging.md +219 -0
  127. package/src/agent/claude-md-template.md +213 -0
  128. package/src/agent/modules/sia-feature.md +100 -0
  129. package/src/agent/modules/sia-flagging.md +50 -0
  130. package/src/agent/modules/sia-orientation.md +92 -0
  131. package/src/agent/modules/sia-regression.md +115 -0
  132. package/src/agent/modules/sia-review.md +64 -0
  133. package/src/agent/modules/sia-tools.md +239 -0
  134. package/src/ast/extractors/c-include.ts +189 -0
  135. package/src/ast/extractors/csharp-project.ts +260 -0
  136. package/src/ast/extractors/prisma-schema.ts +44 -0
  137. package/src/ast/extractors/project-manifest.ts +111 -0
  138. package/src/ast/extractors/sql-schema.ts +67 -0
  139. package/src/ast/extractors/tier-a.ts +423 -0
  140. package/src/ast/extractors/tier-b.ts +289 -0
  141. package/src/ast/extractors/tier-dispatch.ts +247 -0
  142. package/src/ast/index-worker.ts +108 -0
  143. package/src/ast/indexer.ts +484 -0
  144. package/src/ast/languages.ts +408 -0
  145. package/src/ast/pagerank-builder.ts +125 -0
  146. package/src/ast/path-utils.ts +137 -0
  147. package/src/ast/tree-sitter/backends/native.ts +57 -0
  148. package/src/ast/tree-sitter/backends/wasm.ts +39 -0
  149. package/src/ast/tree-sitter/call-walker.ts +44 -0
  150. package/src/ast/tree-sitter/edit-computer.ts +55 -0
  151. package/src/ast/tree-sitter/query-runner.ts +46 -0
  152. package/src/ast/tree-sitter/service.ts +174 -0
  153. package/src/ast/tree-sitter/tree-cache.ts +39 -0
  154. package/src/ast/tree-sitter/types.ts +79 -0
  155. package/src/ast/watcher.ts +322 -0
  156. package/src/capture/chunker.ts +169 -0
  157. package/src/capture/consolidate.ts +127 -0
  158. package/src/capture/edge-inferrer.ts +161 -0
  159. package/src/capture/embedder.ts +166 -0
  160. package/src/capture/embedding-cache.ts +73 -0
  161. package/src/capture/flag-processor.ts +64 -0
  162. package/src/capture/hook.ts +67 -0
  163. package/src/capture/pipeline.ts +450 -0
  164. package/src/capture/prompts/consolidate.ts +25 -0
  165. package/src/capture/prompts/edge-infer.ts +29 -0
  166. package/src/capture/prompts/extract-flagged.ts +36 -0
  167. package/src/capture/prompts/extract.ts +42 -0
  168. package/src/capture/tokenizer.ts +147 -0
  169. package/src/capture/track-a-ast.ts +93 -0
  170. package/src/capture/track-b-llm.ts +149 -0
  171. package/src/capture/types.ts +64 -0
  172. package/src/cli/commands/community.ts +137 -0
  173. package/src/cli/commands/compare.ts +123 -0
  174. package/src/cli/commands/conflicts.ts +41 -0
  175. package/src/cli/commands/digest.ts +197 -0
  176. package/src/cli/commands/disable-flagging.ts +34 -0
  177. package/src/cli/commands/doctor.ts +240 -0
  178. package/src/cli/commands/download-model.ts +161 -0
  179. package/src/cli/commands/enable-flagging.ts +34 -0
  180. package/src/cli/commands/export-knowledge.ts +208 -0
  181. package/src/cli/commands/export.ts +85 -0
  182. package/src/cli/commands/freshness.ts +164 -0
  183. package/src/cli/commands/graph.ts +51 -0
  184. package/src/cli/commands/history.ts +139 -0
  185. package/src/cli/commands/import.ts +335 -0
  186. package/src/cli/commands/install.ts +156 -0
  187. package/src/cli/commands/lead-report.ts +241 -0
  188. package/src/cli/commands/learn.ts +321 -0
  189. package/src/cli/commands/pm-report.ts +413 -0
  190. package/src/cli/commands/prune.ts +75 -0
  191. package/src/cli/commands/qa-report.ts +278 -0
  192. package/src/cli/commands/reindex.ts +104 -0
  193. package/src/cli/commands/rollback.ts +70 -0
  194. package/src/cli/commands/search.ts +103 -0
  195. package/src/cli/commands/server.ts +91 -0
  196. package/src/cli/commands/share.ts +33 -0
  197. package/src/cli/commands/stats.ts +79 -0
  198. package/src/cli/commands/status.ts +176 -0
  199. package/src/cli/commands/sync.ts +96 -0
  200. package/src/cli/commands/team.ts +118 -0
  201. package/src/cli/commands/tour.ts +157 -0
  202. package/src/cli/commands/visualize-live.ts +162 -0
  203. package/src/cli/commands/workspace.ts +117 -0
  204. package/src/cli/index.ts +424 -0
  205. package/src/cli/learn-progress.ts +87 -0
  206. package/src/community/detection-bridge.ts +344 -0
  207. package/src/community/leiden.ts +462 -0
  208. package/src/community/raptor.ts +210 -0
  209. package/src/community/scheduler.ts +74 -0
  210. package/src/community/summarize.ts +115 -0
  211. package/src/decay/archiver.ts +73 -0
  212. package/src/decay/bridge-orphan-cleanup.ts +212 -0
  213. package/src/decay/consolidation-sweep.ts +112 -0
  214. package/src/decay/decay.ts +116 -0
  215. package/src/decay/deep-validator.ts +62 -0
  216. package/src/decay/episodic-promoter.ts +132 -0
  217. package/src/decay/maintenance-scheduler.ts +326 -0
  218. package/src/decay/scheduler.ts +6 -0
  219. package/src/decay/session-sweeper.ts +79 -0
  220. package/src/decay/types.ts +17 -0
  221. package/src/freshness/confidence-decay.ts +122 -0
  222. package/src/freshness/cuckoo-filter.ts +176 -0
  223. package/src/freshness/deep-validation.ts +345 -0
  224. package/src/freshness/dirty-tracker.ts +237 -0
  225. package/src/freshness/file-watcher-layer.ts +119 -0
  226. package/src/freshness/firewall.ts +64 -0
  227. package/src/freshness/git-reconcile-layer.ts +161 -0
  228. package/src/freshness/inverted-index.ts +158 -0
  229. package/src/freshness/stale-read-layer.ts +222 -0
  230. package/src/graph/audit.ts +69 -0
  231. package/src/graph/bridge-db.ts +141 -0
  232. package/src/graph/communities.ts +195 -0
  233. package/src/graph/db-interface.ts +259 -0
  234. package/src/graph/edges.ts +163 -0
  235. package/src/graph/entities.ts +327 -0
  236. package/src/graph/episodic-db.ts +113 -0
  237. package/src/graph/flags.ts +31 -0
  238. package/src/graph/meta-db.ts +200 -0
  239. package/src/graph/semantic-db.ts +101 -0
  240. package/src/graph/session-resume.ts +56 -0
  241. package/src/graph/snapshots.ts +342 -0
  242. package/src/graph/staging.ts +151 -0
  243. package/src/graph/types.ts +128 -0
  244. package/src/hooks/adapters/claude-code.ts +21 -0
  245. package/src/hooks/adapters/cline.ts +43 -0
  246. package/src/hooks/adapters/cursor.ts +65 -0
  247. package/src/hooks/adapters/generic.ts +12 -0
  248. package/src/hooks/agent-detect.ts +34 -0
  249. package/src/hooks/claude-md-directives.ts +32 -0
  250. package/src/hooks/event-router.ts +182 -0
  251. package/src/hooks/extractors/pattern-detector.ts +111 -0
  252. package/src/hooks/handlers/post-compact.ts +30 -0
  253. package/src/hooks/handlers/post-tool-use.ts +403 -0
  254. package/src/hooks/handlers/pre-compact.ts +100 -0
  255. package/src/hooks/handlers/session-end.ts +47 -0
  256. package/src/hooks/handlers/session-start.ts +154 -0
  257. package/src/hooks/handlers/stop.ts +128 -0
  258. package/src/hooks/handlers/user-prompt-submit.ts +68 -0
  259. package/src/hooks/plugin-branch-switch.ts +68 -0
  260. package/src/hooks/plugin-common.ts +47 -0
  261. package/src/hooks/plugin-post-compact.ts +28 -0
  262. package/src/hooks/plugin-post-tool-use.ts +38 -0
  263. package/src/hooks/plugin-pre-compact.ts +37 -0
  264. package/src/hooks/plugin-session-end.ts +37 -0
  265. package/src/hooks/plugin-session-start.ts +75 -0
  266. package/src/hooks/plugin-stop.ts +61 -0
  267. package/src/hooks/plugin-user-prompt-submit.ts +47 -0
  268. package/src/hooks/types.ts +43 -0
  269. package/src/knowledge/discovery.ts +238 -0
  270. package/src/knowledge/external-refs.ts +98 -0
  271. package/src/knowledge/freshness.ts +221 -0
  272. package/src/knowledge/ingest.ts +330 -0
  273. package/src/knowledge/markdown-export.ts +229 -0
  274. package/src/knowledge/markdown-import.ts +359 -0
  275. package/src/knowledge/patterns.ts +74 -0
  276. package/src/knowledge/templates.ts +307 -0
  277. package/src/llm/ai-sdk-adapter.ts +46 -0
  278. package/src/llm/config.ts +88 -0
  279. package/src/llm/cost-tracker.ts +110 -0
  280. package/src/llm/prompts/extraction.ts +55 -0
  281. package/src/llm/prompts/summarization.ts +36 -0
  282. package/src/llm/prompts/validation.ts +37 -0
  283. package/src/llm/provider-registry.ts +68 -0
  284. package/src/llm/reliability.ts +179 -0
  285. package/src/llm/schemas.ts +52 -0
  286. package/src/mcp/freshness-annotator.ts +69 -0
  287. package/src/mcp/server.ts +949 -0
  288. package/src/mcp/tools/sia-ast-query.ts +225 -0
  289. package/src/mcp/tools/sia-at-time.ts +151 -0
  290. package/src/mcp/tools/sia-backlinks.ts +87 -0
  291. package/src/mcp/tools/sia-batch-execute.ts +169 -0
  292. package/src/mcp/tools/sia-by-file.ts +89 -0
  293. package/src/mcp/tools/sia-community.ts +113 -0
  294. package/src/mcp/tools/sia-doctor.ts +73 -0
  295. package/src/mcp/tools/sia-execute-file.ts +122 -0
  296. package/src/mcp/tools/sia-execute.ts +104 -0
  297. package/src/mcp/tools/sia-expand.ts +158 -0
  298. package/src/mcp/tools/sia-fetch-and-index.ts +241 -0
  299. package/src/mcp/tools/sia-flag.ts +65 -0
  300. package/src/mcp/tools/sia-index.ts +111 -0
  301. package/src/mcp/tools/sia-note.ts +134 -0
  302. package/src/mcp/tools/sia-search.ts +105 -0
  303. package/src/mcp/tools/sia-stats.ts +63 -0
  304. package/src/mcp/tools/sia-sync-status.ts +44 -0
  305. package/src/mcp/tools/sia-upgrade.ts +247 -0
  306. package/src/mcp/truncate.ts +231 -0
  307. package/src/native/bridge.ts +167 -0
  308. package/src/native/fallback-ast-diff.ts +144 -0
  309. package/src/native/fallback-graph.ts +325 -0
  310. package/src/ontology/constraints.ts +56 -0
  311. package/src/ontology/errors.ts +8 -0
  312. package/src/ontology/middleware.ts +266 -0
  313. package/src/retrieval/bm25-search.ts +151 -0
  314. package/src/retrieval/context-assembly.ts +76 -0
  315. package/src/retrieval/graph-traversal.ts +168 -0
  316. package/src/retrieval/pagerank.ts +40 -0
  317. package/src/retrieval/query-classifier.ts +106 -0
  318. package/src/retrieval/reranker.ts +156 -0
  319. package/src/retrieval/search.ts +236 -0
  320. package/src/retrieval/throttle.ts +102 -0
  321. package/src/retrieval/vector-search.ts +203 -0
  322. package/src/retrieval/workspace-search.ts +130 -0
  323. package/src/sandbox/context-mode.ts +285 -0
  324. package/src/sandbox/credential-pass.ts +55 -0
  325. package/src/sandbox/executor.ts +235 -0
  326. package/src/security/pattern-detector.ts +127 -0
  327. package/src/security/rule-of-two.ts +50 -0
  328. package/src/security/sanitize.ts +46 -0
  329. package/src/security/semantic-consistency.ts +93 -0
  330. package/src/security/staging-promoter.ts +154 -0
  331. package/src/shared/config.ts +302 -0
  332. package/src/shared/diagnostics.ts +210 -0
  333. package/src/shared/errors.ts +48 -0
  334. package/src/shared/git-utils.ts +143 -0
  335. package/src/shared/llm-client.ts +120 -0
  336. package/src/shared/logger.ts +99 -0
  337. package/src/shared/types.ts +79 -0
  338. package/src/sync/client.ts +43 -0
  339. package/src/sync/conflict.ts +106 -0
  340. package/src/sync/dedup.ts +183 -0
  341. package/src/sync/hlc.ts +117 -0
  342. package/src/sync/keychain.ts +144 -0
  343. package/src/sync/pull.ts +232 -0
  344. package/src/sync/push.ts +131 -0
  345. package/src/types/chokidar.d.ts +23 -0
  346. package/src/visualization/graph-renderer.ts +312 -0
  347. package/src/visualization/subgraph-extract.ts +208 -0
  348. package/src/visualization/views/community-clusters.ts +246 -0
  349. package/src/visualization/views/dependency-map.ts +189 -0
  350. package/src/visualization/views/graph-explorer.ts +364 -0
  351. package/src/visualization/views/timeline.ts +247 -0
  352. package/src/workspace/api-contracts.ts +226 -0
  353. package/src/workspace/cross-repo.ts +61 -0
  354. package/src/workspace/detector.ts +190 -0
  355. package/src/workspace/manifest.ts +141 -0
@@ -0,0 +1,312 @@
1
+ // Module: graph-renderer — Generate self-contained HTML visualization with D3.js
2
+
3
+ import type { SubgraphData } from "@/visualization/subgraph-extract";
4
+
5
+ /** Color palette by entity type category. */
6
+ const TYPE_COLORS: Record<string, string> = {
7
+ FileNode: "#4A90D9",
8
+ CodeEntity: "#4A90D9",
9
+ PackageNode: "#4A90D9",
10
+ Decision: "#5DB85D",
11
+ Convention: "#5DB85D",
12
+ Bug: "#5DB85D",
13
+ Solution: "#5DB85D",
14
+ Concept: "#5DB85D",
15
+ Community: "#9B59B6",
16
+ ContentChunk: "#E67E22",
17
+ Dependency: "#95A5A6",
18
+ };
19
+
20
+ const CATEGORY_LABELS: Array<{ label: string; color: string; types: string[] }> = [
21
+ { label: "Structural", color: "#4A90D9", types: ["FileNode", "CodeEntity", "PackageNode"] },
22
+ {
23
+ label: "Semantic",
24
+ color: "#5DB85D",
25
+ types: ["Decision", "Convention", "Bug", "Solution", "Concept"],
26
+ },
27
+ { label: "Community", color: "#9B59B6", types: ["Community"] },
28
+ { label: "Content", color: "#E67E22", types: ["ContentChunk"] },
29
+ { label: "Other", color: "#95A5A6", types: ["Dependency"] },
30
+ ];
31
+
32
+ const DEFAULT_COLOR = "#95A5A6";
33
+
34
+ /**
35
+ * Generate a self-contained HTML file with D3.js force-directed graph visualization.
36
+ * All CSS is inlined. D3 v7 is loaded from CDN.
37
+ */
38
+ export function renderGraphHtml(data: SubgraphData, title?: string): string {
39
+ const pageTitle = title ?? "Sia Knowledge Graph";
40
+ const nodesJson = JSON.stringify(data.nodes);
41
+ const edgesJson = JSON.stringify(data.edges);
42
+ const typeColorsJson = JSON.stringify(TYPE_COLORS);
43
+ const defaultColor = DEFAULT_COLOR;
44
+
45
+ // Build legend HTML
46
+ const legendHtml = CATEGORY_LABELS.map(
47
+ (cat) =>
48
+ `<div class="legend-item">
49
+ <span class="legend-dot" style="background:${cat.color}"></span>
50
+ <span class="legend-label">${cat.label} (${cat.types.join(", ")})</span>
51
+ </div>`,
52
+ ).join("\n");
53
+
54
+ // Build type checkboxes for filter panel
55
+ const allTypes = [...new Set(data.nodes.map((n) => n.type))].sort();
56
+ const filterCheckboxes = allTypes
57
+ .map(
58
+ (t) =>
59
+ `<label class="filter-checkbox">
60
+ <input type="checkbox" value="${t}" checked onchange="applyFilters()"> ${t}
61
+ </label>`,
62
+ )
63
+ .join("\n");
64
+
65
+ return `<!DOCTYPE html>
66
+ <html lang="en">
67
+ <head>
68
+ <meta charset="UTF-8">
69
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
70
+ <title>${escapeHtml(pageTitle)}</title>
71
+ <style>
72
+ * { margin: 0; padding: 0; box-sizing: border-box; }
73
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; overflow: hidden; }
74
+ #graph-container { width: 100vw; height: 100vh; }
75
+ svg { width: 100%; height: 100%; }
76
+ .controls { position: fixed; top: 16px; left: 16px; z-index: 10; display: flex; flex-direction: column; gap: 8px; }
77
+ .search-box { padding: 8px 12px; border-radius: 6px; border: 1px solid #444; background: #2a2a4a; color: #e0e0e0; font-size: 14px; width: 240px; }
78
+ .search-box::placeholder { color: #888; }
79
+ .panel { position: fixed; top: 16px; right: 16px; z-index: 10; background: #2a2a4a; border: 1px solid #444; border-radius: 8px; padding: 16px; max-width: 320px; max-height: 80vh; overflow-y: auto; }
80
+ .panel h2 { font-size: 16px; margin-bottom: 8px; color: #fff; }
81
+ .panel h3 { font-size: 14px; margin-bottom: 4px; color: #ccc; }
82
+ .panel p { font-size: 12px; color: #aaa; margin-bottom: 4px; }
83
+ .panel .node-type { font-size: 11px; padding: 2px 8px; border-radius: 10px; display: inline-block; margin-bottom: 8px; }
84
+ .info-panel { display: none; }
85
+ .info-panel.visible { display: block; }
86
+ .legend { position: fixed; bottom: 16px; left: 16px; z-index: 10; background: #2a2a4a; border: 1px solid #444; border-radius: 8px; padding: 12px 16px; }
87
+ .legend-item { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
88
+ .legend-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
89
+ .legend-label { font-size: 12px; color: #ccc; }
90
+ .filter-panel { position: fixed; bottom: 16px; right: 16px; z-index: 10; background: #2a2a4a; border: 1px solid #444; border-radius: 8px; padding: 12px 16px; }
91
+ .filter-panel h3 { font-size: 13px; margin-bottom: 8px; color: #fff; }
92
+ .filter-checkbox { display: block; font-size: 12px; color: #ccc; margin-bottom: 4px; cursor: pointer; }
93
+ .filter-checkbox input { margin-right: 6px; }
94
+ .title-bar { position: fixed; top: 16px; left: 50%; transform: translateX(-50%); z-index: 10; font-size: 18px; font-weight: 600; color: #fff; text-shadow: 0 2px 4px rgba(0,0,0,0.5); }
95
+ .stats { font-size: 11px; color: #888; text-align: center; margin-top: 2px; }
96
+ line.edge { stroke-opacity: 0.4; }
97
+ line.edge:hover { stroke-opacity: 0.8; }
98
+ circle.node { cursor: pointer; stroke: #fff; stroke-width: 1.5; }
99
+ circle.node:hover { stroke-width: 3; }
100
+ circle.node.highlighted { stroke: #FFD700; stroke-width: 3; }
101
+ circle.node.dimmed { opacity: 0.15; }
102
+ line.edge.dimmed { opacity: 0.05; }
103
+ text.node-label { font-size: 10px; fill: #ccc; pointer-events: none; text-anchor: middle; }
104
+ text.node-label.dimmed { opacity: 0.1; }
105
+ </style>
106
+ </head>
107
+ <body>
108
+ <div class="title-bar">
109
+ <div>${escapeHtml(pageTitle)}</div>
110
+ <div class="stats" id="stats"></div>
111
+ </div>
112
+ <div class="controls">
113
+ <input type="text" class="search-box" id="search" placeholder="Search nodes by name..." oninput="onSearch(this.value)">
114
+ </div>
115
+ <div class="panel info-panel" id="info-panel">
116
+ <h2 id="info-name"></h2>
117
+ <span class="node-type" id="info-type"></span>
118
+ <p id="info-summary"></p>
119
+ <p><strong>Importance:</strong> <span id="info-importance"></span></p>
120
+ <p><strong>Trust Tier:</strong> <span id="info-trust"></span></p>
121
+ <p><strong>ID:</strong> <span id="info-id" style="font-size:10px;word-break:break-all"></span></p>
122
+ </div>
123
+ <div class="legend">
124
+ ${legendHtml}
125
+ </div>
126
+ <div class="filter-panel" id="filter-panel">
127
+ <h3>Filter by Type</h3>
128
+ ${filterCheckboxes}
129
+ </div>
130
+ <div id="graph-container"></div>
131
+ <script src="https://d3js.org/d3.v7.min.js"></script>
132
+ <script>
133
+ (function() {
134
+ const rawNodes = ${nodesJson};
135
+ const rawEdges = ${edgesJson};
136
+ const typeColors = ${typeColorsJson};
137
+ const defaultColor = "${defaultColor}";
138
+
139
+ document.getElementById("stats").textContent =
140
+ rawNodes.length + " nodes, " + rawEdges.length + " edges";
141
+
142
+ // Build working copies
143
+ let nodes = rawNodes.map(function(n) { return Object.assign({}, n); });
144
+ let edges = rawEdges.map(function(e) { return Object.assign({}, e, { source: e.from_id, target: e.to_id }); });
145
+
146
+ const nodeById = new Map();
147
+ nodes.forEach(function(n) { nodeById.set(n.id, n); });
148
+
149
+ // Filter edges to only include those whose endpoints exist
150
+ edges = edges.filter(function(e) { return nodeById.has(e.source) && nodeById.has(e.target); });
151
+
152
+ const width = window.innerWidth;
153
+ const height = window.innerHeight;
154
+
155
+ const svg = d3.select("#graph-container")
156
+ .append("svg")
157
+ .attr("width", width)
158
+ .attr("height", height);
159
+
160
+ const g = svg.append("g");
161
+
162
+ // Zoom
163
+ const zoom = d3.zoom()
164
+ .scaleExtent([0.1, 8])
165
+ .on("zoom", function(event) { g.attr("transform", event.transform); });
166
+ svg.call(zoom);
167
+
168
+ // Simulation
169
+ const simulation = d3.forceSimulation(nodes)
170
+ .force("link", d3.forceLink(edges).id(function(d) { return d.id; }).distance(80))
171
+ .force("charge", d3.forceManyBody().strength(-200))
172
+ .force("center", d3.forceCenter(width / 2, height / 2))
173
+ .force("collision", d3.forceCollide().radius(function(d) { return nodeRadius(d) + 2; }));
174
+
175
+ function nodeRadius(d) {
176
+ return 5 + (d.importance || 0.5) * 15;
177
+ }
178
+
179
+ function nodeColor(d) {
180
+ return typeColors[d.type] || defaultColor;
181
+ }
182
+
183
+ // Draw edges
184
+ const edgeElements = g.append("g")
185
+ .selectAll("line")
186
+ .data(edges)
187
+ .join("line")
188
+ .attr("class", "edge")
189
+ .attr("stroke", "#556")
190
+ .attr("stroke-width", function(d) { return Math.max(1, (d.weight || 1) * 2); });
191
+
192
+ // Draw nodes
193
+ const nodeElements = g.append("g")
194
+ .selectAll("circle")
195
+ .data(nodes)
196
+ .join("circle")
197
+ .attr("class", "node")
198
+ .attr("r", nodeRadius)
199
+ .attr("fill", nodeColor)
200
+ .on("click", function(event, d) { showInfo(d); })
201
+ .call(d3.drag()
202
+ .on("start", dragStarted)
203
+ .on("drag", dragged)
204
+ .on("end", dragEnded));
205
+
206
+ // Draw labels
207
+ const labelElements = g.append("g")
208
+ .selectAll("text")
209
+ .data(nodes)
210
+ .join("text")
211
+ .attr("class", "node-label")
212
+ .text(function(d) { return d.name.length > 20 ? d.name.slice(0, 18) + "..." : d.name; })
213
+ .attr("dy", function(d) { return nodeRadius(d) + 12; });
214
+
215
+ simulation.on("tick", function() {
216
+ edgeElements
217
+ .attr("x1", function(d) { return d.source.x; })
218
+ .attr("y1", function(d) { return d.source.y; })
219
+ .attr("x2", function(d) { return d.target.x; })
220
+ .attr("y2", function(d) { return d.target.y; });
221
+ nodeElements
222
+ .attr("cx", function(d) { return d.x; })
223
+ .attr("cy", function(d) { return d.y; });
224
+ labelElements
225
+ .attr("x", function(d) { return d.x; })
226
+ .attr("y", function(d) { return d.y; });
227
+ });
228
+
229
+ // Drag handlers
230
+ function dragStarted(event, d) {
231
+ if (!event.active) simulation.alphaTarget(0.3).restart();
232
+ d.fx = d.x;
233
+ d.fy = d.y;
234
+ }
235
+ function dragged(event, d) {
236
+ d.fx = event.x;
237
+ d.fy = event.y;
238
+ }
239
+ function dragEnded(event, d) {
240
+ if (!event.active) simulation.alphaTarget(0);
241
+ d.fx = null;
242
+ d.fy = null;
243
+ }
244
+
245
+ // Info panel
246
+ function showInfo(d) {
247
+ var panel = document.getElementById("info-panel");
248
+ panel.classList.add("visible");
249
+ document.getElementById("info-name").textContent = d.name;
250
+ var typeEl = document.getElementById("info-type");
251
+ typeEl.textContent = d.type;
252
+ typeEl.style.background = nodeColor(d);
253
+ typeEl.style.color = "#fff";
254
+ document.getElementById("info-summary").textContent = d.summary || "(no summary)";
255
+ document.getElementById("info-importance").textContent = d.importance.toFixed(2);
256
+ document.getElementById("info-trust").textContent = d.trustTier;
257
+ document.getElementById("info-id").textContent = d.id;
258
+ }
259
+
260
+ // Search
261
+ window.onSearch = function(query) {
262
+ var q = query.toLowerCase().trim();
263
+ if (!q) {
264
+ nodeElements.classed("highlighted", false).classed("dimmed", false);
265
+ edgeElements.classed("dimmed", false);
266
+ labelElements.classed("dimmed", false);
267
+ return;
268
+ }
269
+ var matchIds = new Set();
270
+ nodes.forEach(function(n) {
271
+ if (n.name.toLowerCase().includes(q)) matchIds.add(n.id);
272
+ });
273
+ nodeElements.classed("highlighted", function(d) { return matchIds.has(d.id); });
274
+ nodeElements.classed("dimmed", function(d) { return !matchIds.has(d.id); });
275
+ edgeElements.classed("dimmed", function(d) {
276
+ var sid = typeof d.source === "object" ? d.source.id : d.source;
277
+ var tid = typeof d.target === "object" ? d.target.id : d.target;
278
+ return !matchIds.has(sid) && !matchIds.has(tid);
279
+ });
280
+ labelElements.classed("dimmed", function(d) { return !matchIds.has(d.id); });
281
+ };
282
+
283
+ // Filter by type
284
+ window.applyFilters = function() {
285
+ var checkboxes = document.querySelectorAll(".filter-checkbox input");
286
+ var activeTypes = new Set();
287
+ checkboxes.forEach(function(cb) { if (cb.checked) activeTypes.add(cb.value); });
288
+
289
+ nodeElements.style("display", function(d) { return activeTypes.has(d.type) ? null : "none"; });
290
+ labelElements.style("display", function(d) { return activeTypes.has(d.type) ? null : "none"; });
291
+ edgeElements.style("display", function(d) {
292
+ var sid = typeof d.source === "object" ? d.source.id : d.source;
293
+ var tid = typeof d.target === "object" ? d.target.id : d.target;
294
+ var sn = nodeById.get(sid);
295
+ var tn = nodeById.get(tid);
296
+ return (sn && activeTypes.has(sn.type) && tn && activeTypes.has(tn.type)) ? null : "none";
297
+ });
298
+ };
299
+ })();
300
+ </script>
301
+ </body>
302
+ </html>`;
303
+ }
304
+
305
+ /** Escape HTML special characters. */
306
+ function escapeHtml(s: string): string {
307
+ return s
308
+ .replace(/&/g, "&amp;")
309
+ .replace(/</g, "&lt;")
310
+ .replace(/>/g, "&gt;")
311
+ .replace(/"/g, "&quot;");
312
+ }
@@ -0,0 +1,208 @@
1
+ // Module: subgraph-extract — Extract relevant subgraph for visualization
2
+
3
+ import type { SiaDb } from "@/graph/db-interface";
4
+
5
+ export interface VisNode {
6
+ id: string;
7
+ type: string;
8
+ name: string;
9
+ summary: string;
10
+ importance: number;
11
+ trustTier: number;
12
+ }
13
+
14
+ export interface VisEdge {
15
+ id: string;
16
+ from_id: string;
17
+ to_id: string;
18
+ type: string;
19
+ weight: number;
20
+ }
21
+
22
+ export interface SubgraphData {
23
+ nodes: VisNode[];
24
+ edges: VisEdge[];
25
+ }
26
+
27
+ export interface ExtractOpts {
28
+ scope?: string;
29
+ nodeType?: string;
30
+ maxNodes?: number;
31
+ }
32
+
33
+ const DEFAULT_MAX_NODES = 200;
34
+
35
+ /**
36
+ * Build a safe SQL IN clause from an array of hex-UUID strings.
37
+ * UUIDs contain only [0-9a-f-] so they are safe to inline without parameterisation,
38
+ * which avoids SQLite's host-parameter limit (SQLITE_MAX_VARIABLE_NUMBER = 999).
39
+ */
40
+ function inClause(ids: string[]): string {
41
+ return ids.map((id) => `'${id}'`).join(",");
42
+ }
43
+
44
+ /** Map a raw entity row to a VisNode. */
45
+ function toVisNode(row: Record<string, unknown>): VisNode {
46
+ return {
47
+ id: row.id as string,
48
+ type: row.type as string,
49
+ name: row.name as string,
50
+ summary: (row.summary as string) ?? "",
51
+ importance: (row.importance as number) ?? 0.5,
52
+ trustTier: (row.trust_tier as number) ?? 3,
53
+ };
54
+ }
55
+
56
+ /** Map a raw edge row to a VisEdge. */
57
+ function toVisEdge(row: Record<string, unknown>): VisEdge {
58
+ return {
59
+ id: row.id as string,
60
+ from_id: row.from_id as string,
61
+ to_id: row.to_id as string,
62
+ type: row.type as string,
63
+ weight: (row.weight as number) ?? 1.0,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Fetch all active edges where both endpoints are in the given id set.
69
+ */
70
+ async function edgesBetween(db: SiaDb, ids: string[]): Promise<VisEdge[]> {
71
+ if (ids.length === 0) return [];
72
+ const list = inClause(ids);
73
+ const { rows } = await db.execute(
74
+ `SELECT id, from_id, to_id, type, weight FROM graph_edges
75
+ WHERE from_id IN (${list}) AND to_id IN (${list})
76
+ AND t_valid_until IS NULL`,
77
+ );
78
+ return rows.map(toVisEdge);
79
+ }
80
+
81
+ /**
82
+ * Get 1-hop neighbor entity IDs for a set of seed IDs (via active edges).
83
+ */
84
+ async function neighborIds(db: SiaDb, seedIds: string[]): Promise<string[]> {
85
+ if (seedIds.length === 0) return [];
86
+ const list = inClause(seedIds);
87
+ const { rows } = await db.execute(
88
+ `SELECT DISTINCT from_id AS nid FROM graph_edges
89
+ WHERE to_id IN (${list}) AND t_valid_until IS NULL
90
+ UNION
91
+ SELECT DISTINCT to_id AS nid FROM graph_edges
92
+ WHERE from_id IN (${list}) AND t_valid_until IS NULL`,
93
+ );
94
+ return rows.map((r) => r.nid as string);
95
+ }
96
+
97
+ /**
98
+ * Fetch entity rows by a list of IDs (active only).
99
+ */
100
+ async function fetchEntitiesById(db: SiaDb, ids: string[]): Promise<VisNode[]> {
101
+ if (ids.length === 0) return [];
102
+ const list = inClause(ids);
103
+ const { rows } = await db.execute(
104
+ `SELECT id, type, name, summary, importance, trust_tier FROM graph_nodes
105
+ WHERE id IN (${list})
106
+ AND t_valid_until IS NULL AND archived_at IS NULL`,
107
+ );
108
+ return rows.map(toVisNode);
109
+ }
110
+
111
+ /**
112
+ * Default extraction: top N nodes by importance with edges between them.
113
+ */
114
+ async function extractDefault(db: SiaDb, maxNodes: number): Promise<SubgraphData> {
115
+ const { rows } = await db.execute(
116
+ `SELECT id, type, name, summary, importance, trust_tier FROM graph_nodes
117
+ WHERE t_valid_until IS NULL AND archived_at IS NULL
118
+ ORDER BY importance DESC LIMIT ?`,
119
+ [maxNodes],
120
+ );
121
+ const nodes = rows.map(toVisNode);
122
+ const nodeIds = nodes.map((n) => n.id);
123
+ const edges = await edgesBetween(db, nodeIds);
124
+ return { nodes, edges };
125
+ }
126
+
127
+ /**
128
+ * Scope extraction: FileNode/CodeEntity under path + 2-hop neighbors.
129
+ */
130
+ async function extractScoped(db: SiaDb, scope: string, maxNodes: number): Promise<SubgraphData> {
131
+ // Find seed entities whose file_paths contain the scope prefix
132
+ const { rows: seedRows } = await db.execute(
133
+ `SELECT id, type, name, summary, importance, trust_tier FROM graph_nodes
134
+ WHERE (type = 'FileNode' OR type = 'CodeEntity')
135
+ AND file_paths LIKE ?
136
+ AND t_valid_until IS NULL AND archived_at IS NULL`,
137
+ [`%${scope}%`],
138
+ );
139
+ const seedNodes = seedRows.map(toVisNode);
140
+ const seedIds = seedNodes.map((n) => n.id);
141
+
142
+ // 1-hop neighbors
143
+ const hop1Ids = await neighborIds(db, seedIds);
144
+ // 2-hop neighbors
145
+ const hop2Ids = await neighborIds(db, hop1Ids);
146
+
147
+ // Combine all unique IDs, prioritising seeds
148
+ const allIds = new Set<string>([...seedIds, ...hop1Ids, ...hop2Ids]);
149
+
150
+ // Fetch entities for non-seed IDs
151
+ const extraIds = [...allIds].filter((id) => !seedIds.includes(id));
152
+ const extraNodes = await fetchEntitiesById(db, extraIds);
153
+
154
+ // Merge and cap
155
+ const allNodes = [...seedNodes, ...extraNodes].slice(0, maxNodes);
156
+ const cappedIds = allNodes.map((n) => n.id);
157
+ const edges = await edgesBetween(db, cappedIds);
158
+
159
+ return { nodes: allNodes, edges };
160
+ }
161
+
162
+ /**
163
+ * NodeType extraction: all nodes of that type + direct (1-hop) neighbors.
164
+ */
165
+ async function extractByType(db: SiaDb, nodeType: string, maxNodes: number): Promise<SubgraphData> {
166
+ const { rows: typeRows } = await db.execute(
167
+ `SELECT id, type, name, summary, importance, trust_tier FROM graph_nodes
168
+ WHERE type = ?
169
+ AND t_valid_until IS NULL AND archived_at IS NULL`,
170
+ [nodeType],
171
+ );
172
+ const typeNodes = typeRows.map(toVisNode);
173
+ const typeIds = typeNodes.map((n) => n.id);
174
+
175
+ // 1-hop neighbors
176
+ const hop1Ids = await neighborIds(db, typeIds);
177
+ const extraIds = hop1Ids.filter((id) => !typeIds.includes(id));
178
+ const extraNodes = await fetchEntitiesById(db, extraIds);
179
+
180
+ // Merge and cap
181
+ const allNodes = [...typeNodes, ...extraNodes].slice(0, maxNodes);
182
+ const cappedIds = allNodes.map((n) => n.id);
183
+ const edges = await edgesBetween(db, cappedIds);
184
+
185
+ return { nodes: allNodes, edges };
186
+ }
187
+
188
+ /**
189
+ * Extract a relevant subgraph for visualization.
190
+ *
191
+ * Three modes:
192
+ * - Default (no scope/type): top N nodes by importance + edges between them
193
+ * - With scope: FileNode/CodeEntity under path + 2-hop neighbors, capped at maxNodes
194
+ * - With nodeType: all nodes of that type + direct neighbors, capped at maxNodes
195
+ */
196
+ export async function extractSubgraph(db: SiaDb, opts?: ExtractOpts): Promise<SubgraphData> {
197
+ const maxNodes = opts?.maxNodes ?? DEFAULT_MAX_NODES;
198
+
199
+ if (opts?.scope) {
200
+ return extractScoped(db, opts.scope, maxNodes);
201
+ }
202
+
203
+ if (opts?.nodeType) {
204
+ return extractByType(db, opts.nodeType, maxNodes);
205
+ }
206
+
207
+ return extractDefault(db, maxNodes);
208
+ }