@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,87 @@
1
+ // Module: learn-progress — Cross-crash recovery for sia-learn.
2
+ //
3
+ // Writes a .sia-learn-progress.json file to the project root tracking
4
+ // which phases completed and how many files were indexed. On re-run
5
+ // after a crash, the orchestrator reads this file and resumes.
6
+
7
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+
10
+ const PROGRESS_FILE = ".sia-learn-progress.json";
11
+
12
+ export interface LearnProgress {
13
+ started_at: number;
14
+ repo_hash: string;
15
+ branch: string;
16
+ phases_completed: number[];
17
+ files_indexed: number;
18
+ total_files: number;
19
+ last_checkpoint_at: number;
20
+ }
21
+
22
+ /**
23
+ * Read the progress file from a directory. Returns null if not found or invalid.
24
+ */
25
+ export function readProgress(dir: string): LearnProgress | null {
26
+ const path = join(dir, PROGRESS_FILE);
27
+ if (!existsSync(path)) return null;
28
+ try {
29
+ return JSON.parse(readFileSync(path, "utf-8")) as LearnProgress;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Write (or update) the progress file.
37
+ */
38
+ export function writeProgress(dir: string, progress: LearnProgress): void {
39
+ const path = join(dir, PROGRESS_FILE);
40
+ writeFileSync(path, JSON.stringify(progress, null, 2), "utf-8");
41
+ }
42
+
43
+ /**
44
+ * Delete the progress file (called on successful completion).
45
+ */
46
+ export function deleteProgress(dir: string): void {
47
+ const path = join(dir, PROGRESS_FILE);
48
+ try {
49
+ unlinkSync(path);
50
+ } catch {
51
+ // File doesn't exist — that's fine
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Run a function with retry and exponential backoff.
57
+ * Returns the result on success, or null if all retries are exhausted.
58
+ *
59
+ * @param phaseName - Name for logging
60
+ * @param fn - The async function to run
61
+ * @param maxRetries - Maximum attempts (default 3)
62
+ * @param baseDelayMs - Base delay for backoff (default 1000ms)
63
+ */
64
+ export async function runWithRetry<T>(
65
+ phaseName: string,
66
+ fn: () => Promise<T>,
67
+ maxRetries: number = 3,
68
+ baseDelayMs: number = 1000,
69
+ ): Promise<T | null> {
70
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
71
+ try {
72
+ return await fn();
73
+ } catch (err) {
74
+ const msg = err instanceof Error ? err.message : String(err);
75
+ process.stderr.write(
76
+ `[sia-learn] ${phaseName} failed (attempt ${attempt}/${maxRetries}): ${msg}\n`,
77
+ );
78
+ if (attempt === maxRetries) {
79
+ process.stderr.write(`[sia-learn] ${phaseName} failed permanently — skipping\n`);
80
+ return null;
81
+ }
82
+ // Exponential backoff: base * 4^(attempt-1)
83
+ await new Promise((r) => setTimeout(r, baseDelayMs * 4 ** (attempt - 1)));
84
+ }
85
+ }
86
+ return null;
87
+ }
@@ -0,0 +1,344 @@
1
+ // Module: detection-bridge — Community detection with native/JS fallback
2
+ //
3
+ // Routes to the Rust Leiden implementation when @sia/native is available,
4
+ // otherwise runs a simplified JavaScript Louvain algorithm.
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Public types
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export interface CommunityLevelResult {
11
+ /** community label for each node (index = node index) */
12
+ membership: number[];
13
+ /** modularity Q score for this level */
14
+ modularity: number;
15
+ /** number of distinct communities at this level */
16
+ n_communities: number;
17
+ }
18
+
19
+ export interface CommunityResult {
20
+ levels: CommunityLevelResult[];
21
+ backend: "rust-leiden" | "js-louvain";
22
+ }
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Native module probe (same pattern as bridge.ts)
26
+ // ---------------------------------------------------------------------------
27
+
28
+ interface NativeLeidenModule {
29
+ detectCommunities(
30
+ edges: Array<[number, number, number]>,
31
+ nodeCount: number,
32
+ resolutions?: number[],
33
+ ): CommunityResult;
34
+ }
35
+
36
+ function loadNativeLeiden(): NativeLeidenModule | null {
37
+ try {
38
+ return require("@sia/native") as NativeLeidenModule;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Simplified Louvain — pure TypeScript fallback
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /**
49
+ * Compute modularity Q for a given partition.
50
+ *
51
+ * Q = (1/2m) * sum_{ij}[ A_ij - k_i * k_j / (2m) ] * delta(c_i, c_j)
52
+ *
53
+ * where m = total edge weight, k_i = weighted degree of node i,
54
+ * and A_ij = weight of edge (i,j).
55
+ */
56
+ function computeModularity(
57
+ adjacency: Map<number, Map<number, number>>,
58
+ membership: number[],
59
+ totalWeight: number,
60
+ degrees: Float64Array,
61
+ ): number {
62
+ if (totalWeight === 0) return 0;
63
+ const m2 = 2 * totalWeight;
64
+ let q = 0;
65
+
66
+ for (const [i, nbrs] of adjacency) {
67
+ for (const [j, w] of nbrs) {
68
+ if (membership[i] === membership[j]) {
69
+ q += w - (degrees[i] * degrees[j]) / m2;
70
+ }
71
+ }
72
+ }
73
+
74
+ return q / m2;
75
+ }
76
+
77
+ /**
78
+ * Compute the modularity gain from moving node `v` into community `C`.
79
+ *
80
+ * Standard Louvain formula:
81
+ * ΔQ = k_v_in / m - resolution * k_v * sigma_tot / (2m^2)
82
+ * = 2*k_v_in / m2 - 2*resolution * k_v * sigma_tot / m2^2
83
+ *
84
+ * where m2 = 2m, k_v_in = edge weight sum between v and C,
85
+ * sigma_tot = sum of degrees in C.
86
+ */
87
+ function modularityGain(
88
+ kVIn: number,
89
+ kV: number,
90
+ sigmaTot: number,
91
+ m2: number,
92
+ resolution: number,
93
+ ): number {
94
+ return (2 * kVIn) / m2 - (2 * resolution * kV * sigmaTot) / (m2 * m2);
95
+ }
96
+
97
+ function buildAdjacency(
98
+ edges: Array<[number, number, number]>,
99
+ nodeCount: number,
100
+ ): {
101
+ adj: Map<number, Map<number, number>>;
102
+ degrees: Float64Array;
103
+ totalWeight: number;
104
+ } {
105
+ const adj = new Map<number, Map<number, number>>();
106
+ for (let i = 0; i < nodeCount; i++) adj.set(i, new Map());
107
+
108
+ let totalWeight = 0;
109
+ for (const [from, to, weight] of edges) {
110
+ if (from < 0 || to < 0 || from >= nodeCount || to >= nodeCount) continue;
111
+ const w = weight ?? 1;
112
+
113
+ // Undirected: add both directions
114
+ const fwd = adj.get(from);
115
+ if (fwd) fwd.set(to, (fwd.get(to) ?? 0) + w);
116
+
117
+ if (from !== to) {
118
+ const bwd = adj.get(to);
119
+ if (bwd) bwd.set(from, (bwd.get(from) ?? 0) + w);
120
+ }
121
+
122
+ totalWeight += w;
123
+ }
124
+
125
+ const degrees = new Float64Array(nodeCount);
126
+ for (const [node, nbrs] of adj) {
127
+ let deg = 0;
128
+ for (const w of nbrs.values()) deg += w;
129
+ degrees[node] = deg;
130
+ }
131
+
132
+ return { adj, degrees, totalWeight };
133
+ }
134
+
135
+ /**
136
+ * Run one pass of the Louvain algorithm at a given resolution.
137
+ * Returns the membership array.
138
+ */
139
+ function louvainPass(
140
+ adj: Map<number, Map<number, number>>,
141
+ degrees: Float64Array,
142
+ totalWeight: number,
143
+ initialMembership: number[],
144
+ resolution: number,
145
+ maxIterations = 100,
146
+ ): number[] {
147
+ const n = degrees.length;
148
+ if (n === 0) return [];
149
+
150
+ const m2 = 2 * totalWeight;
151
+ const membership = [...initialMembership];
152
+
153
+ // sigma_tot[c] = sum of degrees in community c
154
+ const sigmaTot = new Map<number, number>();
155
+ for (let i = 0; i < n; i++) {
156
+ const c = membership[i];
157
+ sigmaTot.set(c, (sigmaTot.get(c) ?? 0) + degrees[i]);
158
+ }
159
+
160
+ let improved = true;
161
+ let iterations = 0;
162
+
163
+ while (improved && iterations < maxIterations) {
164
+ improved = false;
165
+ iterations++;
166
+
167
+ for (let v = 0; v < n; v++) {
168
+ const currentComm = membership[v];
169
+ const kV = degrees[v];
170
+
171
+ // Weight from v to each neighboring community
172
+ const commWeights = new Map<number, number>();
173
+ for (const [nbr, w] of adj.get(v) ?? new Map()) {
174
+ const c = membership[nbr];
175
+ commWeights.set(c, (commWeights.get(c) ?? 0) + w);
176
+ }
177
+
178
+ // Gain from removing v from current community
179
+ const kVInCurrent = commWeights.get(currentComm) ?? 0;
180
+ const sigmaCurrent = sigmaTot.get(currentComm) ?? 0;
181
+
182
+ // Try moving v to each neighboring community
183
+ let bestGain = 0;
184
+ let bestComm = currentComm;
185
+
186
+ for (const [targetComm, kVIn] of commWeights) {
187
+ if (targetComm === currentComm) continue;
188
+ const sigmaTarget = sigmaTot.get(targetComm) ?? 0;
189
+
190
+ // Gain from adding v to targetComm minus cost of removing from currentComm
191
+ const gain =
192
+ modularityGain(kVIn, kV, sigmaTarget, m2, resolution) -
193
+ modularityGain(kVInCurrent, kV, sigmaCurrent - kV, m2, resolution);
194
+
195
+ if (gain > bestGain) {
196
+ bestGain = gain;
197
+ bestComm = targetComm;
198
+ }
199
+ }
200
+
201
+ if (bestComm !== currentComm) {
202
+ // Move v to bestComm
203
+ sigmaTot.set(currentComm, (sigmaTot.get(currentComm) ?? 0) - kV);
204
+ sigmaTot.set(bestComm, (sigmaTot.get(bestComm) ?? 0) + kV);
205
+ membership[v] = bestComm;
206
+ improved = true;
207
+ }
208
+ }
209
+ }
210
+
211
+ // Compact community IDs to [0, k)
212
+ const remap = new Map<number, number>();
213
+ let nextId = 0;
214
+ for (let i = 0; i < n; i++) {
215
+ if (!remap.has(membership[i])) {
216
+ remap.set(membership[i], nextId++);
217
+ }
218
+ membership[i] = remap.get(membership[i]) ?? 0;
219
+ }
220
+
221
+ return membership;
222
+ }
223
+
224
+ /**
225
+ * Post-process: split disconnected nodes within a community into separate
226
+ * communities using BFS. The largest (first-found) component keeps the
227
+ * original community ID; additional components get new IDs.
228
+ */
229
+ function splitDisconnected(
230
+ adj: Map<number, Map<number, number>>,
231
+ membership: number[],
232
+ n: number,
233
+ ): number[] {
234
+ const result = [...membership];
235
+ let nextComm = Math.max(0, ...membership) + 1;
236
+
237
+ // Group nodes by community
238
+ const communityNodes = new Map<number, number[]>();
239
+ for (let i = 0; i < n; i++) {
240
+ const c = membership[i];
241
+ if (!communityNodes.has(c)) communityNodes.set(c, []);
242
+ communityNodes.get(c)?.push(i);
243
+ }
244
+
245
+ for (const [, nodes] of communityNodes) {
246
+ if (nodes.length <= 1) continue;
247
+ const nodeSet = new Set(nodes);
248
+ const visited = new Set<number>();
249
+ let isFirstComponent = true;
250
+
251
+ for (const start of nodes) {
252
+ if (visited.has(start)) continue;
253
+
254
+ // BFS within community
255
+ const queue = [start];
256
+ const component: number[] = [];
257
+ visited.add(start);
258
+ while (queue.length > 0) {
259
+ const cur = queue.shift();
260
+ if (cur === undefined) break;
261
+ component.push(cur);
262
+ for (const nbr of adj.get(cur)?.keys() ?? []) {
263
+ if (nodeSet.has(nbr) && !visited.has(nbr)) {
264
+ visited.add(nbr);
265
+ queue.push(nbr);
266
+ }
267
+ }
268
+ }
269
+
270
+ if (isFirstComponent) {
271
+ // First component keeps the original community ID
272
+ isFirstComponent = false;
273
+ } else {
274
+ // Subsequent disconnected components get new community IDs
275
+ for (const node of component) {
276
+ result[node] = nextComm;
277
+ }
278
+ nextComm++;
279
+ }
280
+ }
281
+ }
282
+
283
+ return result;
284
+ }
285
+
286
+ function jsLouvain(
287
+ edges: Array<[number, number, number]>,
288
+ nodeCount: number,
289
+ resolutions: number[],
290
+ ): CommunityResult {
291
+ if (nodeCount === 0) {
292
+ return {
293
+ levels: [],
294
+ backend: "js-louvain",
295
+ };
296
+ }
297
+
298
+ const { adj, degrees, totalWeight } = buildAdjacency(edges, nodeCount);
299
+ const levels: CommunityLevelResult[] = [];
300
+
301
+ for (const resolution of resolutions) {
302
+ // Each node starts in its own community
303
+ const initial = Array.from({ length: nodeCount }, (_, i) => i);
304
+ let membership = louvainPass(adj, degrees, totalWeight, initial, resolution);
305
+ membership = splitDisconnected(adj, membership, nodeCount);
306
+
307
+ const n_communities = new Set(membership).size;
308
+ const modularity = computeModularity(adj, membership, totalWeight, degrees);
309
+
310
+ levels.push({ membership, modularity, n_communities });
311
+ }
312
+
313
+ return { levels, backend: "js-louvain" };
314
+ }
315
+
316
+ // ---------------------------------------------------------------------------
317
+ // Public API
318
+ // ---------------------------------------------------------------------------
319
+
320
+ /**
321
+ * Detect communities in a weighted graph.
322
+ *
323
+ * @param edges Array of [from, to, weight] triples (node indices).
324
+ * @param nodeCount Total number of nodes.
325
+ * @param resolutions Resolution parameters per level. Defaults to [1.0].
326
+ * Higher resolution → more, smaller communities.
327
+ */
328
+ export function detectCommunities(
329
+ edges: Array<[number, number, number]>,
330
+ nodeCount: number,
331
+ resolutions: number[] = [1.0],
332
+ ): CommunityResult {
333
+ // Try native Rust Leiden implementation first
334
+ const nativeMod = loadNativeLeiden();
335
+ if (nativeMod) {
336
+ try {
337
+ return nativeMod.detectCommunities(edges, nodeCount, resolutions);
338
+ } catch {
339
+ // Fall through to JS implementation
340
+ }
341
+ }
342
+
343
+ return jsLouvain(edges, nodeCount, resolutions);
344
+ }