@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,462 @@
1
+ // Module: leiden — community detection via a simplified Louvain/Leiden pass
2
+
3
+ import { randomUUID } from "node:crypto";
4
+ import type { SiaDb } from "@/graph/db-interface";
5
+
6
+ export interface LeidenOpts {
7
+ /** Custom resolution parameters per level (fine → coarse). Defaults: [2.0, 1.0, 0.5]. */
8
+ resolutions?: number[];
9
+ }
10
+
11
+ export interface CommunityResult {
12
+ /** Community counts per level (index = level). */
13
+ levels: number[];
14
+ /** Total communities created across all levels. */
15
+ totalCommunities: number;
16
+ /** Wall-clock duration for the run. */
17
+ durationMs: number;
18
+ }
19
+
20
+ interface EntityRow {
21
+ id: string;
22
+ package_path: string | null;
23
+ importance: number;
24
+ }
25
+
26
+ interface EdgeRow {
27
+ fromId: string;
28
+ toId: string;
29
+ type: string;
30
+ weight: number;
31
+ }
32
+
33
+ interface Unit {
34
+ id: string;
35
+ members: Set<string>;
36
+ }
37
+
38
+ interface DetectedCommunity {
39
+ id: string;
40
+ level: number;
41
+ members: Set<string>;
42
+ parentId: string | null;
43
+ packagePath: string | null;
44
+ }
45
+
46
+ const DEFAULT_RESOLUTIONS = [2.0, 1.0, 0.5];
47
+
48
+ const EDGE_TYPE_WEIGHTS: Record<string, number> = {
49
+ calls: 0.5,
50
+ imports: 0.5,
51
+ inherits_from: 0.5,
52
+ contains: 0.5,
53
+ depends_on: 0.5,
54
+ co_occurrence: 0.3,
55
+ cochange: 0.2,
56
+ "git-cochange": 0.2,
57
+ };
58
+
59
+ function weightForEdge(type: string | null, base: number): number {
60
+ // Specification notes equal weights for now, but keep the map for future tuning.
61
+ const typeWeight = type ? (EDGE_TYPE_WEIGHTS[type] ?? 0.3) : 0.3;
62
+ return base * typeWeight;
63
+ }
64
+
65
+ function dedupeEdges(
66
+ edges: EdgeRow[],
67
+ validNodes: Set<string>,
68
+ ): Array<{ a: string; b: string; w: number }> {
69
+ const map = new Map<string, number>();
70
+ for (const edge of edges) {
71
+ if (!validNodes.has(edge.fromId) || !validNodes.has(edge.toId)) continue;
72
+ const keyParts = [edge.fromId, edge.toId].sort();
73
+ const key = `${keyParts[0]}::${keyParts[1]}`;
74
+ const baseWeight = typeof edge.weight === "number" ? edge.weight : 1;
75
+ const w = weightForEdge(edge.type, baseWeight);
76
+ map.set(key, (map.get(key) ?? 0) + w);
77
+ }
78
+ const result: Array<{ a: string; b: string; w: number }> = [];
79
+ for (const [key, w] of map) {
80
+ const [a, b] = key.split("::");
81
+ result.push({ a, b, w });
82
+ }
83
+ return result;
84
+ }
85
+
86
+ function buildUnitAdjacency(
87
+ edges: Array<{ a: string; b: string; w: number }>,
88
+ entityToUnit: Map<string, string>,
89
+ ): Map<string, Map<string, number>> {
90
+ const adj = new Map<string, Map<string, number>>();
91
+ const ensure = (id: string): Map<string, number> => {
92
+ let entry = adj.get(id);
93
+ if (!entry) {
94
+ entry = new Map();
95
+ adj.set(id, entry);
96
+ }
97
+ return entry;
98
+ };
99
+
100
+ for (const { a, b, w } of edges) {
101
+ const ua = entityToUnit.get(a);
102
+ const ub = entityToUnit.get(b);
103
+ if (!ua || !ub) continue;
104
+
105
+ if (ua === ub) {
106
+ const selfAdj = ensure(ua);
107
+ selfAdj.set(ua, (selfAdj.get(ua) ?? 0) + w);
108
+ continue;
109
+ }
110
+
111
+ const adjA = ensure(ua);
112
+ adjA.set(ub, (adjA.get(ub) ?? 0) + w);
113
+
114
+ const adjB = ensure(ub);
115
+ adjB.set(ua, (adjB.get(ua) ?? 0) + w);
116
+ }
117
+
118
+ return adj;
119
+ }
120
+
121
+ function louvain(
122
+ units: Unit[],
123
+ adjacency: Map<string, Map<string, number>>,
124
+ resolution: number,
125
+ maxIterations = 100,
126
+ ): Map<string, string> {
127
+ const community = new Map<string, string>();
128
+ const sumTot = new Map<string, number>();
129
+ const degrees = new Map<string, number>();
130
+
131
+ for (const unit of units) {
132
+ community.set(unit.id, unit.id);
133
+ const deg = Array.from(adjacency.get(unit.id)?.values() ?? []).reduce((a, b) => a + b, 0);
134
+ degrees.set(unit.id, deg);
135
+ sumTot.set(unit.id, deg);
136
+ }
137
+
138
+ const m2 = Array.from(degrees.values()).reduce((a, b) => a + b, 0) || 1; // 2 * total edge weight
139
+
140
+ let iterations = 0;
141
+ let moved = true;
142
+ while (moved && iterations < maxIterations) {
143
+ iterations++;
144
+ moved = false;
145
+ for (const unit of units) {
146
+ const node = unit.id;
147
+ const nodeComm = community.get(node) ?? node;
148
+ const nodeDegree = degrees.get(node) ?? 0;
149
+
150
+ // Temporarily remove node from its community
151
+ sumTot.set(nodeComm, (sumTot.get(nodeComm) ?? 0) - nodeDegree);
152
+
153
+ const neighborWeights = adjacency.get(node) ?? new Map<string, number>();
154
+ const communityWeights = new Map<string, number>();
155
+ for (const [neighbor, weight] of neighborWeights) {
156
+ const comm = community.get(neighbor);
157
+ if (!comm) continue;
158
+ communityWeights.set(comm, (communityWeights.get(comm) ?? 0) + weight);
159
+ }
160
+
161
+ let bestComm = nodeComm;
162
+ let bestGain = 0;
163
+ for (const [comm, kin] of communityWeights) {
164
+ const tot = sumTot.get(comm) ?? 0;
165
+ const gain = kin - (resolution * nodeDegree * tot) / m2;
166
+ if (gain > bestGain + 1e-9) {
167
+ bestGain = gain;
168
+ bestComm = comm;
169
+ }
170
+ }
171
+
172
+ sumTot.set(bestComm, (sumTot.get(bestComm) ?? 0) + nodeDegree);
173
+ community.set(node, bestComm);
174
+ if (bestComm !== nodeComm) {
175
+ moved = true;
176
+ }
177
+ }
178
+ }
179
+
180
+ // Normalize community identifiers to stable compact ids
181
+ const normalized = new Map<string, string>();
182
+ let idx = 0;
183
+ for (const comm of new Set(community.values())) {
184
+ normalized.set(comm, `c${idx++}`);
185
+ }
186
+
187
+ const assignment = new Map<string, string>();
188
+ for (const [node, comm] of community) {
189
+ assignment.set(node, normalized.get(comm) ?? comm);
190
+ }
191
+ return assignment;
192
+ }
193
+
194
+ function refinePartition(
195
+ assignment: Map<string, string>,
196
+ adjacency: Map<string, Map<string, number>>,
197
+ ): Map<string, string> {
198
+ // Group nodes by community
199
+ const communities = new Map<string, string[]>();
200
+ for (const [node, comm] of assignment) {
201
+ let members = communities.get(comm);
202
+ if (!members) {
203
+ members = [];
204
+ communities.set(comm, members);
205
+ }
206
+ members.push(node);
207
+ }
208
+
209
+ const refined = new Map<string, string>();
210
+ for (const [comm, members] of communities) {
211
+ if (members.length <= 1) {
212
+ for (const m of members) refined.set(m, comm);
213
+ continue;
214
+ }
215
+
216
+ // BFS to find connected components within this community
217
+ const memberSet = new Set(members);
218
+ const visited = new Set<string>();
219
+ let componentIdx = 0;
220
+
221
+ for (const start of members) {
222
+ if (visited.has(start)) continue;
223
+ const component: string[] = [];
224
+ const queue = [start];
225
+ visited.add(start);
226
+
227
+ while (queue.length > 0) {
228
+ const node = queue.shift();
229
+ if (!node) break;
230
+ component.push(node);
231
+ const neighbors = adjacency.get(node);
232
+ if (neighbors) {
233
+ for (const [neighbor] of neighbors) {
234
+ if (memberSet.has(neighbor) && !visited.has(neighbor)) {
235
+ visited.add(neighbor);
236
+ queue.push(neighbor);
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ const compId = componentIdx === 0 ? comm : `${comm}_${componentIdx}`;
243
+ for (const node of component) {
244
+ refined.set(node, compId);
245
+ }
246
+ componentIdx++;
247
+ }
248
+ }
249
+
250
+ return refined;
251
+ }
252
+
253
+ function determinePackagePath(
254
+ members: Set<string>,
255
+ entityPackages: Map<string, string | null>,
256
+ ): string | null {
257
+ let current: string | null | undefined;
258
+ for (const id of members) {
259
+ const pkg = entityPackages.get(id) ?? null;
260
+ if (pkg === null) return null;
261
+ if (current === undefined) {
262
+ current = pkg;
263
+ } else if (current !== pkg) {
264
+ return null;
265
+ }
266
+ }
267
+ return current ?? null;
268
+ }
269
+
270
+ function assignParents(levels: DetectedCommunity[][]): void {
271
+ for (let i = 0; i < levels.length - 1; i++) {
272
+ const parents = levels[i + 1];
273
+ for (const community of levels[i]) {
274
+ const parent = parents.find((p) => {
275
+ for (const member of community.members) {
276
+ if (!p.members.has(member)) return false;
277
+ }
278
+ return true;
279
+ });
280
+ community.parentId = parent?.id ?? null;
281
+ }
282
+ }
283
+ }
284
+
285
+ export async function detectCommunities(
286
+ db: SiaDb,
287
+ opts: LeidenOpts = {},
288
+ ): Promise<CommunityResult> {
289
+ const start = Date.now();
290
+ const resolutions = opts.resolutions ?? DEFAULT_RESOLUTIONS;
291
+
292
+ const entityResult = await db.execute(
293
+ `SELECT id, package_path, importance
294
+ FROM graph_nodes
295
+ WHERE t_valid_until IS NULL AND archived_at IS NULL`,
296
+ );
297
+ const entities = entityResult.rows as unknown as EntityRow[];
298
+ if (entities.length === 0) {
299
+ return { levels: resolutions.map(() => 0), totalCommunities: 0, durationMs: 0 };
300
+ }
301
+
302
+ const entityPackages = new Map<string, string | null>();
303
+ for (const entity of entities) {
304
+ entityPackages.set(entity.id, entity.package_path);
305
+ }
306
+ const validIds = new Set(entities.map((e) => e.id));
307
+
308
+ const edgeResult = await db.execute(
309
+ `SELECT from_id as fromId, to_id as toId, type, weight
310
+ FROM graph_edges
311
+ WHERE t_valid_until IS NULL`,
312
+ );
313
+ const edges = edgeResult.rows as unknown as EdgeRow[];
314
+ const undirectedEdges = dedupeEdges(edges, validIds);
315
+
316
+ let units: Unit[] = entities.map((e) => ({ id: e.id, members: new Set([e.id]) }));
317
+ const levelCommunities: DetectedCommunity[][] = [];
318
+
319
+ for (let level = 0; level < resolutions.length; level++) {
320
+ // Per-package Level 0: group entities by package_path, run separate louvain+refine per package
321
+ if (level === 0 && entityPackages.size > 0) {
322
+ const byPackage = new Map<string, Unit[]>();
323
+ for (const unit of units) {
324
+ const pkg = entityPackages.get([...unit.members][0]) ?? "__root__";
325
+ let pkgList = byPackage.get(pkg);
326
+ if (!pkgList) {
327
+ pkgList = [];
328
+ byPackage.set(pkg, pkgList);
329
+ }
330
+ pkgList.push(unit);
331
+ }
332
+
333
+ const allDetected: DetectedCommunity[] = [];
334
+ for (const [pkg, pkgUnits] of byPackage) {
335
+ const pkgEntityToUnit = new Map<string, string>();
336
+ for (const unit of pkgUnits) {
337
+ for (const member of unit.members) {
338
+ pkgEntityToUnit.set(member, unit.id);
339
+ }
340
+ }
341
+
342
+ const pkgAdj = buildUnitAdjacency(undirectedEdges, pkgEntityToUnit);
343
+ const pkgAssignment = louvain(pkgUnits, pkgAdj, resolutions[level]);
344
+ const pkgRefined = refinePartition(pkgAssignment, pkgAdj);
345
+
346
+ const communityMembers = new Map<string, Set<string>>();
347
+ for (const unit of pkgUnits) {
348
+ const commKey = pkgRefined.get(unit.id) ?? unit.id;
349
+ if (!communityMembers.has(commKey)) communityMembers.set(commKey, new Set());
350
+ for (const member of unit.members) {
351
+ communityMembers.get(commKey)?.add(member);
352
+ }
353
+ }
354
+
355
+ for (const [_key, members] of communityMembers) {
356
+ const id = randomUUID();
357
+ allDetected.push({
358
+ id,
359
+ level: 0,
360
+ members,
361
+ parentId: null,
362
+ packagePath: pkg === "__root__" ? null : pkg,
363
+ });
364
+ }
365
+ }
366
+
367
+ levelCommunities.push(allDetected);
368
+ units = allDetected.map((c) => ({ id: c.id, members: c.members }));
369
+ continue;
370
+ }
371
+
372
+ const entityToUnit = new Map<string, string>();
373
+ for (const unit of units) {
374
+ for (const member of unit.members) {
375
+ entityToUnit.set(member, unit.id);
376
+ }
377
+ }
378
+
379
+ const unitAdj = buildUnitAdjacency(undirectedEdges, entityToUnit);
380
+ const assignment = louvain(units, unitAdj, resolutions[level]);
381
+ const refinedAssignment = refinePartition(assignment, unitAdj);
382
+
383
+ const communityMembers = new Map<string, Set<string>>();
384
+ const communityUnits = new Map<string, Unit[]>();
385
+ for (const unit of units) {
386
+ const commKey = refinedAssignment.get(unit.id) ?? unit.id;
387
+ if (!communityMembers.has(commKey)) {
388
+ communityMembers.set(commKey, new Set());
389
+ communityUnits.set(commKey, []);
390
+ }
391
+ for (const member of unit.members) {
392
+ communityMembers.get(commKey)?.add(member);
393
+ }
394
+ communityUnits.get(commKey)?.push(unit);
395
+ }
396
+
397
+ const detected: DetectedCommunity[] = [];
398
+ for (const [_key, members] of communityMembers) {
399
+ const id = randomUUID();
400
+ const pkg = determinePackagePath(members, entityPackages);
401
+ detected.push({
402
+ id,
403
+ level,
404
+ members,
405
+ parentId: null,
406
+ packagePath: level === 0 ? pkg : null,
407
+ });
408
+ }
409
+
410
+ levelCommunities.push(detected);
411
+
412
+ // Prepare units for next level
413
+ units = detected.map((c) => ({ id: c.id, members: c.members }));
414
+ }
415
+
416
+ assignParents(levelCommunities);
417
+
418
+ // Persist communities and memberships
419
+ await db.transaction(async (tx) => {
420
+ await tx.execute("DELETE FROM community_members");
421
+ await tx.execute("DELETE FROM communities");
422
+
423
+ const now = Date.now();
424
+ const levelsDescending = [...levelCommunities].reverse();
425
+ for (const level of levelsDescending) {
426
+ for (const community of level) {
427
+ const memberCount = community.members.size;
428
+ await tx.execute(
429
+ `INSERT INTO communities (
430
+ id, level, parent_id, summary, summary_hash,
431
+ member_count, last_summary_member_count,
432
+ package_path, created_at, updated_at
433
+ ) VALUES (?, ?, ?, NULL, NULL, ?, 0, ?, ?, ?)`,
434
+ [
435
+ community.id,
436
+ community.level,
437
+ community.parentId,
438
+ memberCount,
439
+ community.packagePath,
440
+ now,
441
+ now,
442
+ ],
443
+ );
444
+
445
+ for (const member of community.members) {
446
+ await tx.execute(
447
+ `INSERT INTO community_members (community_id, entity_id, level)
448
+ VALUES (?, ?, ?)`,
449
+ [community.id, member, community.level],
450
+ );
451
+ }
452
+ }
453
+ }
454
+ });
455
+
456
+ const levels = levelCommunities.map((c) => c.length);
457
+ return {
458
+ levels,
459
+ totalCommunities: levels.reduce((a, b) => a + b, 0),
460
+ durationMs: Date.now() - start,
461
+ };
462
+ }
@@ -0,0 +1,210 @@
1
+ // Module: raptor — RAPTOR summary tree construction
2
+
3
+ import { createHash } from "node:crypto";
4
+ import type { SiaDb } from "@/graph/db-interface";
5
+ import type { LlmClient } from "@/shared/llm-client";
6
+
7
+ interface EntityRow {
8
+ id: string;
9
+ content: string;
10
+ summary: string;
11
+ t_valid_until: number | null;
12
+ }
13
+
14
+ interface CommunityRow {
15
+ id: string;
16
+ summary: string | null;
17
+ }
18
+
19
+ interface SummaryRow {
20
+ id: string;
21
+ level: number;
22
+ scopeId: string;
23
+ content: string;
24
+ tokenCount: number;
25
+ contentHash: string;
26
+ expiresAt: number | null;
27
+ }
28
+
29
+ function wordCount(text: string): number {
30
+ const trimmed = text.trim();
31
+ if (!trimmed) return 0;
32
+ return trimmed.split(/\s+/).length;
33
+ }
34
+
35
+ function hashContent(content: string): string {
36
+ return createHash("sha256").update(content).digest("hex");
37
+ }
38
+
39
+ async function upsertSummary(db: SiaDb, row: SummaryRow, now: number): Promise<void> {
40
+ await db.execute(
41
+ `INSERT INTO summary_tree (id, level, scope_id, content, content_hash, token_count, created_at, expires_at)
42
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
43
+ ON CONFLICT(id) DO UPDATE SET
44
+ level = excluded.level,
45
+ scope_id = excluded.scope_id,
46
+ content = excluded.content,
47
+ content_hash = excluded.content_hash,
48
+ token_count = excluded.token_count,
49
+ created_at = excluded.created_at,
50
+ expires_at = excluded.expires_at`,
51
+ [
52
+ row.id,
53
+ row.level,
54
+ row.scopeId,
55
+ row.content,
56
+ row.contentHash,
57
+ row.tokenCount,
58
+ now,
59
+ row.expiresAt,
60
+ ],
61
+ );
62
+ }
63
+
64
+ export async function buildSummaryTree(db: SiaDb, llmClient?: LlmClient): Promise<void> {
65
+ const now = Date.now();
66
+
67
+ const entityResult = await db.execute(
68
+ `SELECT id, content, summary, t_valid_until
69
+ FROM graph_nodes
70
+ WHERE t_valid_until IS NULL AND archived_at IS NULL`,
71
+ );
72
+ const communityResult = await db.execute(`SELECT id, summary FROM communities`);
73
+
74
+ const entities = entityResult.rows as unknown as EntityRow[];
75
+ const communities = communityResult.rows as unknown as CommunityRow[];
76
+
77
+ await db.transaction(async (tx) => {
78
+ // Level 0 — raw entity content (eager)
79
+ for (const entity of entities) {
80
+ const level0: SummaryRow = {
81
+ id: `lvl0:${entity.id}`,
82
+ level: 0,
83
+ scopeId: entity.id,
84
+ content: entity.content,
85
+ tokenCount: wordCount(entity.content),
86
+ contentHash: hashContent(entity.content),
87
+ expiresAt: null,
88
+ };
89
+ await upsertSummary(tx, level0, now);
90
+ }
91
+
92
+ // Level 1 — lazy (generated on-demand via getOrCreateLevel1Summary)
93
+
94
+ // Level 2 — community/module summaries via LLM
95
+ for (const community of communities) {
96
+ let content: string;
97
+ if (llmClient && community.summary?.trim()) {
98
+ content = await llmClient.summarize(
99
+ `Rewrite this community summary as a coherent paragraph describing the module's purpose:\n\n${community.summary}`,
100
+ );
101
+ } else {
102
+ content = community.summary?.trim()
103
+ ? community.summary
104
+ : `Community ${community.id} has no summary.`;
105
+ }
106
+ const level2: SummaryRow = {
107
+ id: `lvl2:${community.id}`,
108
+ level: 2,
109
+ scopeId: community.id,
110
+ content,
111
+ tokenCount: wordCount(content),
112
+ contentHash: hashContent(content),
113
+ expiresAt: null,
114
+ };
115
+ await upsertSummary(tx, level2, now);
116
+ }
117
+
118
+ // Level 3 — architectural overview (only regenerate weekly)
119
+ if (communities.length > 0) {
120
+ const existingLevel3 = await tx.execute(
121
+ "SELECT created_at FROM summary_tree WHERE id = 'lvl3:overview'",
122
+ );
123
+ const lastLevel3At = (existingLevel3.rows[0]?.created_at as number) ?? 0;
124
+ const oneWeek = 7 * 24 * 60 * 60 * 1000;
125
+ if (now - lastLevel3At < oneWeek && existingLevel3.rows.length > 0) {
126
+ // Skip Level 3 regeneration — generated less than 7 days ago
127
+ } else {
128
+ const overviewBody = communities
129
+ .map((c) => {
130
+ const text =
131
+ c.summary && c.summary.trim().length > 0
132
+ ? c.summary.trim()
133
+ : `Community ${c.id} has no summary.`;
134
+ return `- ${text}`;
135
+ })
136
+ .join("\n");
137
+ const overview = `Architectural overview:\n${overviewBody}`;
138
+ const level3: SummaryRow = {
139
+ id: "lvl3:overview",
140
+ level: 3,
141
+ scopeId: "all",
142
+ content: overview,
143
+ tokenCount: wordCount(overview),
144
+ contentHash: hashContent(overview),
145
+ expiresAt: null,
146
+ };
147
+ await upsertSummary(tx, level3, now);
148
+ }
149
+ }
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Lazily get or create a Level 1 summary for a single entity.
155
+ * Returns the summary text, or null if the entity does not exist.
156
+ */
157
+ export async function getOrCreateLevel1Summary(
158
+ db: SiaDb,
159
+ entityId: string,
160
+ llmClient?: LlmClient,
161
+ ): Promise<string | null> {
162
+ // Check if Level 1 summary already exists and is not expired
163
+ const existing = await db.execute(
164
+ "SELECT content FROM summary_tree WHERE id = ? AND expires_at IS NULL",
165
+ [`lvl1:${entityId}`],
166
+ );
167
+ if (existing.rows.length > 0) {
168
+ return existing.rows[0].content as string;
169
+ }
170
+
171
+ // Fetch entity
172
+ const entityResult = await db.execute(
173
+ "SELECT id, content, summary, t_valid_until FROM graph_nodes WHERE id = ?",
174
+ [entityId],
175
+ );
176
+ if (entityResult.rows.length === 0) return null;
177
+ const entity = entityResult.rows[0] as {
178
+ id: string;
179
+ content: string;
180
+ summary: string;
181
+ t_valid_until: number | null;
182
+ };
183
+
184
+ // Generate via LLM or fallback
185
+ let summaryText: string;
186
+ if (llmClient) {
187
+ summaryText = await llmClient.summarize(
188
+ `Write a one-paragraph summary of this code entity:\n\nName: ${entity.id}\nContent: ${entity.content.slice(0, 500)}`,
189
+ );
190
+ } else {
191
+ summaryText = entity.summary?.trim() ? entity.summary : entity.content.slice(0, 240);
192
+ }
193
+
194
+ const now = Date.now();
195
+ await upsertSummary(
196
+ db,
197
+ {
198
+ id: `lvl1:${entityId}`,
199
+ level: 1,
200
+ scopeId: entityId,
201
+ content: summaryText,
202
+ tokenCount: wordCount(summaryText),
203
+ contentHash: hashContent(summaryText),
204
+ expiresAt: entity.t_valid_until ? now : null,
205
+ },
206
+ now,
207
+ );
208
+
209
+ return summaryText;
210
+ }