@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,195 @@
1
+ // Module: communities — Community CRUD layer with audit logging
2
+
3
+ import { randomUUID } from "node:crypto";
4
+ import { writeAuditEntry } from "@/graph/audit";
5
+ import type { SiaDb } from "@/graph/db-interface";
6
+ import type {
7
+ Community,
8
+ CommunityMember,
9
+ CommunitySummary,
10
+ InsertCommunityInput,
11
+ UpdateCommunityInput,
12
+ } from "@/graph/types";
13
+
14
+ /**
15
+ * Insert a new community into the graph database.
16
+ * Generates a UUID, sets created_at and updated_at to now.
17
+ * Writes an ADD entry to the audit log.
18
+ */
19
+ export async function insertCommunity(db: SiaDb, input: InsertCommunityInput): Promise<Community> {
20
+ const now = Date.now();
21
+ const id = randomUUID();
22
+
23
+ const community: Community = {
24
+ id,
25
+ level: input.level,
26
+ parent_id: input.parent_id ?? null,
27
+ summary: null,
28
+ summary_hash: null,
29
+ member_count: 0,
30
+ last_summary_member_count: 0,
31
+ package_path: input.package_path ?? null,
32
+ created_at: now,
33
+ updated_at: now,
34
+ };
35
+
36
+ await db.execute(
37
+ `INSERT INTO communities (
38
+ id, level, parent_id, summary, summary_hash,
39
+ member_count, last_summary_member_count,
40
+ package_path, created_at, updated_at
41
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
42
+ [
43
+ community.id,
44
+ community.level,
45
+ community.parent_id,
46
+ community.summary,
47
+ community.summary_hash,
48
+ community.member_count,
49
+ community.last_summary_member_count,
50
+ community.package_path,
51
+ community.created_at,
52
+ community.updated_at,
53
+ ],
54
+ );
55
+
56
+ await writeAuditEntry(db, "ADD", {});
57
+
58
+ return community;
59
+ }
60
+
61
+ /**
62
+ * Retrieve a single community by id.
63
+ * Returns the community or null if not found.
64
+ */
65
+ export async function getCommunity(db: SiaDb, id: string): Promise<Community | null> {
66
+ const result = await db.execute("SELECT * FROM communities WHERE id = ?", [id]);
67
+ return (result.rows[0] as unknown as Community | undefined) ?? null;
68
+ }
69
+
70
+ /**
71
+ * Retrieve communities filtered by level and optional package path.
72
+ */
73
+ export async function getCommunityByLevel(
74
+ db: SiaDb,
75
+ level: 0 | 1 | 2,
76
+ packagePath?: string | null,
77
+ ): Promise<Community[]> {
78
+ if (packagePath !== undefined && packagePath !== null) {
79
+ const result = await db.execute(
80
+ "SELECT * FROM communities WHERE level = ? AND package_path = ?",
81
+ [level, packagePath],
82
+ );
83
+ return result.rows as unknown as Community[];
84
+ }
85
+
86
+ const result = await db.execute("SELECT * FROM communities WHERE level = ?", [level]);
87
+ return result.rows as unknown as Community[];
88
+ }
89
+
90
+ /**
91
+ * Update partial fields on an existing community.
92
+ * Always updates updated_at to now.
93
+ * Writes an UPDATE entry to the audit log.
94
+ */
95
+ export async function updateCommunity(
96
+ db: SiaDb,
97
+ id: string,
98
+ updates: UpdateCommunityInput,
99
+ ): Promise<void> {
100
+ const entries = Object.entries(updates).filter(([, v]) => v !== undefined);
101
+ const now = Date.now();
102
+
103
+ // Always update updated_at
104
+ const setClauses = [...entries.map(([key]) => `${key} = ?`), "updated_at = ?"].join(", ");
105
+ const values = [...entries.map(([, v]) => v), now];
106
+
107
+ await db.execute(`UPDATE communities SET ${setClauses} WHERE id = ?`, [...values, id]);
108
+
109
+ await writeAuditEntry(db, "UPDATE", {});
110
+ }
111
+
112
+ /**
113
+ * Add a member entity to a community.
114
+ * Uses INSERT OR IGNORE so duplicate adds are safe (idempotent).
115
+ */
116
+ export async function addMember(
117
+ db: SiaDb,
118
+ communityId: string,
119
+ entityId: string,
120
+ level: 0 | 1 | 2,
121
+ ): Promise<void> {
122
+ await db.execute(
123
+ "INSERT OR IGNORE INTO community_members (community_id, entity_id, level) VALUES (?, ?, ?)",
124
+ [communityId, entityId, level],
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Remove all members from a community.
130
+ * Used during Leiden algorithm re-runs to clear stale membership.
131
+ */
132
+ export async function removeMembers(db: SiaDb, communityId: string): Promise<void> {
133
+ await db.execute("DELETE FROM community_members WHERE community_id = ?", [communityId]);
134
+ }
135
+
136
+ /**
137
+ * Retrieve all members of a community.
138
+ */
139
+ export async function getMembers(db: SiaDb, communityId: string): Promise<CommunityMember[]> {
140
+ const result = await db.execute("SELECT * FROM community_members WHERE community_id = ?", [
141
+ communityId,
142
+ ]);
143
+ return result.rows as unknown as CommunityMember[];
144
+ }
145
+
146
+ /**
147
+ * Retrieve community summaries (WHERE summary IS NOT NULL).
148
+ * Ordered by member_count DESC. Optionally filtered by level and limited.
149
+ */
150
+ export async function getSummaries(
151
+ db: SiaDb,
152
+ level?: 0 | 1 | 2,
153
+ limit?: number,
154
+ ): Promise<CommunitySummary[]> {
155
+ let sql: string;
156
+ const params: Array<number | string> = [];
157
+
158
+ if (level !== undefined) {
159
+ sql =
160
+ "SELECT id, level, summary, member_count FROM communities WHERE summary IS NOT NULL AND level = ? ORDER BY member_count DESC";
161
+ params.push(level);
162
+ } else {
163
+ sql =
164
+ "SELECT id, level, summary, member_count FROM communities WHERE summary IS NOT NULL ORDER BY member_count DESC";
165
+ }
166
+
167
+ if (limit !== undefined) {
168
+ sql += " LIMIT ?";
169
+ params.push(limit);
170
+ }
171
+
172
+ const result = await db.execute(sql, params);
173
+
174
+ return (
175
+ result.rows as Array<{ id: string; level: 0 | 1 | 2; summary: string; member_count: number }>
176
+ ).map((row) => ({
177
+ id: row.id,
178
+ level: row.level,
179
+ summary: row.summary,
180
+ member_count: row.member_count,
181
+ top_entities: [],
182
+ }));
183
+ }
184
+
185
+ /**
186
+ * Pure function: returns true if the community needs re-summarization.
187
+ * Triggered when the relative change in member count exceeds 20%.
188
+ * Formula: |member_count - last_summary_member_count| / max(last_summary_member_count, 1) > 0.20
189
+ */
190
+ export function needsResummarization(community: Community): boolean {
191
+ const last = community.last_summary_member_count;
192
+ const current = community.member_count;
193
+ const change = Math.abs(current - last) / Math.max(last, 1);
194
+ return change > 0.2;
195
+ }
@@ -0,0 +1,259 @@
1
+ // Module: db-interface — SiaDb interface and BunSqliteDb adapter
2
+
3
+ import { Database, type SQLQueryBindings } from "bun:sqlite";
4
+ import { mkdirSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import type { SyncConfig } from "@/shared/config";
7
+ import { SIA_HOME } from "@/shared/config";
8
+
9
+ /** Returns true for SQL statements that produce rows (SELECT, PRAGMA, RETURNING, etc.) */
10
+ function isReadStatement(sql: string): boolean {
11
+ const trimmed = sql.trimStart().toUpperCase();
12
+ return (
13
+ trimmed.startsWith("SELECT") ||
14
+ trimmed.startsWith("PRAGMA") ||
15
+ trimmed.startsWith("WITH") ||
16
+ trimmed.startsWith("EXPLAIN") ||
17
+ trimmed.includes("RETURNING")
18
+ );
19
+ }
20
+
21
+ /**
22
+ * Unified database adapter interface.
23
+ * All CRUD code writes against SiaDb, never directly against bun:sqlite or @libsql/client.
24
+ */
25
+ export interface SiaDb {
26
+ execute(sql: string, params?: unknown[]): Promise<{ rows: Record<string, unknown>[] }>;
27
+ executeMany(statements: Array<{ sql: string; params?: unknown[] }>): Promise<void>;
28
+ transaction(fn: (db: SiaDb) => Promise<void>): Promise<void>;
29
+ close(): Promise<void>;
30
+ rawSqlite(): Database | null;
31
+ sync?(): Promise<void>;
32
+ }
33
+
34
+ /**
35
+ * Bun:sqlite implementation of SiaDb (used when sync.enabled = false).
36
+ */
37
+ export class BunSqliteDb implements SiaDb {
38
+ private inTransaction = false;
39
+
40
+ constructor(private readonly db: Database) {}
41
+
42
+ /** Internal: executes SQL without the reentrancy guard. Used by txProxy. */
43
+ private _executeRaw(sql: string, params: unknown[] = []): { rows: Record<string, unknown>[] } {
44
+ const stmt = this.db.prepare(sql);
45
+ if (isReadStatement(sql)) {
46
+ const rows = stmt.all(...(params as SQLQueryBindings[])) as Record<string, unknown>[];
47
+ return { rows };
48
+ }
49
+ stmt.run(...(params as SQLQueryBindings[]));
50
+ return { rows: [] };
51
+ }
52
+
53
+ async execute(sql: string, params: unknown[] = []): Promise<{ rows: Record<string, unknown>[] }> {
54
+ if (this.inTransaction) {
55
+ throw new Error(
56
+ "Cannot call execute() on outer db during active transaction() — use the transaction proxy",
57
+ );
58
+ }
59
+ return this._executeRaw(sql, params);
60
+ }
61
+
62
+ async executeMany(statements: Array<{ sql: string; params?: unknown[] }>): Promise<void> {
63
+ if (this.inTransaction) {
64
+ throw new Error(
65
+ "Cannot call executeMany() on outer db during active transaction() — use the transaction proxy",
66
+ );
67
+ }
68
+ this.db.prepare("BEGIN").run();
69
+ try {
70
+ for (const { sql, params = [] } of statements) {
71
+ this.db.prepare(sql).run(...(params as SQLQueryBindings[]));
72
+ }
73
+ this.db.prepare("COMMIT").run();
74
+ } catch (e) {
75
+ this.db.prepare("ROLLBACK").run();
76
+ throw e;
77
+ }
78
+ }
79
+
80
+ async transaction(fn: (db: SiaDb) => Promise<void>): Promise<void> {
81
+ // bun:sqlite's db.transaction() is synchronous — passing an async callback
82
+ // would commit before awaited operations complete (torn writes).
83
+ // Instead, manage transaction boundaries explicitly around the async fn.
84
+ //
85
+ // Pass a proxy to fn rather than `this` so that any nested call to
86
+ // txProxy.transaction() throws immediately with a clear error message,
87
+ // matching LibSqlDb's behaviour.
88
+ // txProxy calls _executeRaw directly to bypass the reentrancy guard.
89
+ const txProxy: SiaDb = {
90
+ execute: (sql, params) => Promise.resolve(this._executeRaw(sql, params)),
91
+ executeMany: async (stmts) => {
92
+ for (const { sql, params = [] } of stmts) {
93
+ this._executeRaw(sql, params);
94
+ }
95
+ },
96
+ transaction: () => {
97
+ throw new Error("Nested transactions not supported");
98
+ },
99
+ close: async () => {},
100
+ rawSqlite: () => this.db,
101
+ };
102
+ this.inTransaction = true;
103
+ this.db.prepare("BEGIN").run();
104
+ try {
105
+ await fn(txProxy);
106
+ this.db.prepare("COMMIT").run();
107
+ } catch (e) {
108
+ this.db.prepare("ROLLBACK").run();
109
+ throw e;
110
+ } finally {
111
+ this.inTransaction = false;
112
+ }
113
+ }
114
+
115
+ async close(): Promise<void> {
116
+ this.db.close();
117
+ }
118
+
119
+ rawSqlite(): Database {
120
+ return this.db;
121
+ }
122
+
123
+ async sync(): Promise<void> {
124
+ // No-op for local-only mode
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Create an in-memory BunSqliteDb instance (for testing).
130
+ */
131
+ export function createMemoryDb(): BunSqliteDb {
132
+ return new BunSqliteDb(new Database(":memory:"));
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // LibSqlDb — embedded replica via @libsql/client
137
+ // ---------------------------------------------------------------------------
138
+
139
+ export class LibSqlDb implements SiaDb {
140
+ constructor(
141
+ private readonly client: {
142
+ execute: (...args: unknown[]) => Promise<{ rows?: unknown[] }>;
143
+ batch?: (...args: unknown[]) => Promise<unknown>;
144
+ sync?: () => Promise<void>;
145
+ close?: () => Promise<void>;
146
+ },
147
+ ) {}
148
+
149
+ async execute(sql: string, params: unknown[] = []): Promise<{ rows: Record<string, unknown>[] }> {
150
+ const result = await this.client.execute({ sql, args: params as unknown[] });
151
+ const rows = Array.isArray(result?.rows)
152
+ ? result.rows.map((row: unknown) => row as Record<string, unknown>)
153
+ : [];
154
+ return { rows };
155
+ }
156
+
157
+ async executeMany(statements: Array<{ sql: string; params?: unknown[] }>): Promise<void> {
158
+ if (typeof this.client.batch === "function") {
159
+ await this.client.batch(
160
+ statements.map(({ sql, params = [] }) => ({ sql, args: params as unknown[] })),
161
+ "write",
162
+ );
163
+ return;
164
+ }
165
+
166
+ for (const { sql, params = [] } of statements) {
167
+ await this.client.execute({ sql, args: params as unknown[] });
168
+ }
169
+ }
170
+
171
+ async transaction(fn: (db: SiaDb) => Promise<void>): Promise<void> {
172
+ const txProxy: SiaDb = {
173
+ execute: (sql, params) => this.execute(sql, params),
174
+ executeMany: (stmts) => this.executeMany(stmts),
175
+ transaction: async () => {
176
+ throw new Error("Nested transactions not supported");
177
+ },
178
+ close: async () => {},
179
+ rawSqlite: () => null,
180
+ };
181
+
182
+ await this.client.execute("BEGIN");
183
+ try {
184
+ await fn(txProxy);
185
+ await this.client.execute("COMMIT");
186
+ } catch (err) {
187
+ await this.client.execute("ROLLBACK");
188
+ throw err;
189
+ }
190
+ }
191
+
192
+ async close(): Promise<void> {
193
+ if (typeof this.client.close === "function") {
194
+ await this.client.close();
195
+ }
196
+ }
197
+
198
+ rawSqlite(): Database | null {
199
+ return null;
200
+ }
201
+
202
+ async sync(): Promise<void> {
203
+ if (typeof this.client.sync === "function") {
204
+ await this.client.sync();
205
+ }
206
+ }
207
+ }
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // openDb factory & openSiaDb router
211
+ // ---------------------------------------------------------------------------
212
+
213
+ /** Options for openDb / openSiaDb. */
214
+ export interface OpenDbOpts {
215
+ readonly?: boolean;
216
+ /** Override SIA_HOME (useful for testing). */
217
+ siaHome?: string;
218
+ }
219
+
220
+ /**
221
+ * Open (or create) a bun:sqlite database at `{siaHome}/repos/{repoHash}/graph.db`.
222
+ * Sets WAL journal mode, NORMAL synchronous, and foreign_keys ON unless `readonly`.
223
+ */
224
+ export function openDb(repoHash: string, opts: OpenDbOpts = {}): BunSqliteDb {
225
+ const home = opts.siaHome ?? SIA_HOME;
226
+ const dir = join(home, "repos", repoHash);
227
+ mkdirSync(dir, { recursive: true });
228
+
229
+ const dbPath = join(dir, "graph.db");
230
+ const db = new Database(dbPath, { readonly: opts.readonly ?? false });
231
+
232
+ if (!opts.readonly) {
233
+ (db as unknown as { pragma: (s: string) => void }).pragma("journal_mode = WAL");
234
+ (db as unknown as { pragma: (s: string) => void }).pragma("synchronous = NORMAL");
235
+ (db as unknown as { pragma: (s: string) => void }).pragma("foreign_keys = ON");
236
+ }
237
+
238
+ return new BunSqliteDb(db);
239
+ }
240
+
241
+ /**
242
+ * Open the appropriate SiaDb implementation based on SyncConfig.
243
+ *
244
+ * - sync disabled or no serverUrl => local bun:sqlite via openDb
245
+ * - sync enabled + serverUrl => dynamic import createSiaDb from @/sync/client
246
+ */
247
+ export async function openSiaDb(
248
+ repoHash: string,
249
+ syncConfig: SyncConfig,
250
+ opts: OpenDbOpts = {},
251
+ ): Promise<SiaDb> {
252
+ if (!syncConfig.enabled || !syncConfig.serverUrl) {
253
+ return openDb(repoHash, opts);
254
+ }
255
+
256
+ // Dynamic import so that @/sync/client is only loaded when sync is actually used
257
+ const { createSiaDb } = await import("@/sync/client");
258
+ return createSiaDb(repoHash, syncConfig, opts);
259
+ }
@@ -0,0 +1,163 @@
1
+ // Module: edges — Edge CRUD layer (bi-temporal, never hard-deletes)
2
+
3
+ import { randomUUID } from "node:crypto";
4
+ import { writeAuditEntry } from "@/graph/audit";
5
+ import type { SiaDb } from "@/graph/db-interface";
6
+
7
+ /** Shape accepted by insertEdge. Caller supplies classification and endpoints; we generate id & timestamps. */
8
+ export interface NewEdge {
9
+ from_id: string;
10
+ to_id: string;
11
+ type: string;
12
+ weight?: number;
13
+ confidence?: number;
14
+ trust_tier?: number;
15
+ t_valid_from?: number | null;
16
+ source_episode?: string | null;
17
+ extraction_method?: string | null;
18
+ }
19
+
20
+ /** Row shape returned by edge queries. */
21
+ export interface EdgeRow {
22
+ id: string;
23
+ from_id: string;
24
+ to_id: string;
25
+ type: string;
26
+ weight: number;
27
+ confidence: number;
28
+ trust_tier: number;
29
+ t_created: number;
30
+ t_expired: number | null;
31
+ t_valid_from: number | null;
32
+ t_valid_until: number | null;
33
+ hlc_created: number | null;
34
+ hlc_modified: number | null;
35
+ source_episode: string | null;
36
+ extraction_method: string | null;
37
+ }
38
+
39
+ /**
40
+ * Insert a new edge into the graph.
41
+ *
42
+ * Generates a UUID for `id`, sets `t_created = Date.now()`, `t_valid_until = null`.
43
+ * Writes an 'ADD' audit log entry.
44
+ */
45
+ export async function insertEdge(db: SiaDb, edge: NewEdge): Promise<EdgeRow> {
46
+ const id = randomUUID();
47
+ const now = Date.now();
48
+
49
+ await db.execute(
50
+ `INSERT INTO graph_edges (
51
+ id, from_id, to_id, type, weight, confidence, trust_tier,
52
+ t_created, t_expired, t_valid_from, t_valid_until,
53
+ source_episode, extraction_method
54
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, NULL, ?, ?)`,
55
+ [
56
+ id,
57
+ edge.from_id,
58
+ edge.to_id,
59
+ edge.type,
60
+ edge.weight ?? 1.0,
61
+ edge.confidence ?? 0.7,
62
+ edge.trust_tier ?? 3,
63
+ now,
64
+ edge.t_valid_from ?? null,
65
+ edge.source_episode ?? null,
66
+ edge.extraction_method ?? null,
67
+ ],
68
+ );
69
+
70
+ await writeAuditEntry(db, "ADD", { edge_id: id });
71
+
72
+ return {
73
+ id,
74
+ from_id: edge.from_id,
75
+ to_id: edge.to_id,
76
+ type: edge.type,
77
+ weight: edge.weight ?? 1.0,
78
+ confidence: edge.confidence ?? 0.7,
79
+ trust_tier: edge.trust_tier ?? 3,
80
+ t_created: now,
81
+ t_expired: null,
82
+ t_valid_from: edge.t_valid_from ?? null,
83
+ t_valid_until: null,
84
+ hlc_created: null,
85
+ hlc_modified: null,
86
+ source_episode: edge.source_episode ?? null,
87
+ extraction_method: edge.extraction_method ?? null,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Invalidate an edge (soft-delete).
93
+ *
94
+ * Sets BOTH `t_valid_until` AND `t_expired` to the given timestamp (default: now).
95
+ * Writes an 'INVALIDATE' audit log entry.
96
+ * Never hard-deletes.
97
+ */
98
+ export async function invalidateEdge(db: SiaDb, id: string, tValidUntil?: number): Promise<void> {
99
+ const ts = tValidUntil ?? Date.now();
100
+
101
+ await db.execute("UPDATE graph_edges SET t_valid_until = ?, t_expired = ? WHERE id = ?", [
102
+ ts,
103
+ ts,
104
+ id,
105
+ ]);
106
+
107
+ await writeAuditEntry(db, "INVALIDATE", { edge_id: id });
108
+ }
109
+
110
+ /**
111
+ * Get all currently active edges for an entity (as source or target).
112
+ *
113
+ * Active = `t_valid_until IS NULL`.
114
+ */
115
+ export async function getActiveEdges(db: SiaDb, entityId: string): Promise<EdgeRow[]> {
116
+ const result = await db.execute(
117
+ `SELECT * FROM graph_edges
118
+ WHERE (from_id = ? OR to_id = ?)
119
+ AND t_valid_until IS NULL`,
120
+ [entityId, entityId],
121
+ );
122
+ return result.rows as unknown as EdgeRow[];
123
+ }
124
+
125
+ /**
126
+ * Get currently active edges from a source node filtered by type(s).
127
+ *
128
+ * Returns edges where `from_id = fromId`, `type IN types`, and `t_valid_until IS NULL`.
129
+ */
130
+ export async function getEdgesByType(
131
+ db: SiaDb,
132
+ fromId: string,
133
+ types: string[],
134
+ ): Promise<Record<string, unknown>[]> {
135
+ const placeholders = types.map(() => "?").join(", ");
136
+ const { rows } = await db.execute(
137
+ `SELECT * FROM graph_edges WHERE from_id = ? AND type IN (${placeholders}) AND t_valid_until IS NULL`,
138
+ [fromId, ...types],
139
+ );
140
+ return rows;
141
+ }
142
+
143
+ /**
144
+ * Get edges for an entity that were valid at a specific point in time.
145
+ *
146
+ * Matches edges where:
147
+ * - `t_valid_from IS NULL OR t_valid_from <= asOfMs` (edge had started)
148
+ * - `t_valid_until IS NULL OR t_valid_until > asOfMs` (edge had not yet ended)
149
+ */
150
+ export async function getEdgesAsOf(
151
+ db: SiaDb,
152
+ entityId: string,
153
+ asOfMs: number,
154
+ ): Promise<EdgeRow[]> {
155
+ const result = await db.execute(
156
+ `SELECT * FROM graph_edges
157
+ WHERE (from_id = ? OR to_id = ?)
158
+ AND (t_valid_from IS NULL OR t_valid_from <= ?)
159
+ AND (t_valid_until IS NULL OR t_valid_until > ?)`,
160
+ [entityId, entityId, asOfMs, asOfMs],
161
+ );
162
+ return result.rows as unknown as EdgeRow[];
163
+ }