@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,106 @@
1
+ // Module: query-classifier — Local vs global query routing + task-type boosts
2
+
3
+ import type { SiaDb } from "@/graph/db-interface";
4
+
5
+ export type QueryMode = "local" | "global";
6
+
7
+ export interface ClassificationResult {
8
+ mode: QueryMode;
9
+ globalUnavailable: boolean;
10
+ }
11
+
12
+ const GLOBAL_KEYWORDS: string[] = [
13
+ "architecture",
14
+ "overview",
15
+ "explain",
16
+ "structure",
17
+ "high-level",
18
+ "design",
19
+ "modules",
20
+ "subsystems",
21
+ ];
22
+
23
+ const LOCAL_KEYWORDS: string[] = [
24
+ "function",
25
+ "class",
26
+ "method",
27
+ "variable",
28
+ "import",
29
+ "error",
30
+ "bug",
31
+ "fix",
32
+ "implement",
33
+ "where is",
34
+ "how does",
35
+ "what does",
36
+ ];
37
+
38
+ /**
39
+ * Classify a query as local (three-stage pipeline) or global (community summaries).
40
+ *
41
+ * Keyword-based classification: count matches against global and local keyword
42
+ * lists, default to local when tied. If the graph is too small for meaningful
43
+ * community summaries (fewer than `config.communityMinGraphSize` active
44
+ * entities), force local and set `globalUnavailable: true`.
45
+ */
46
+ export async function classifyQuery(
47
+ db: SiaDb,
48
+ query: string,
49
+ config: { communityMinGraphSize: number },
50
+ ): Promise<ClassificationResult> {
51
+ const lower = query.toLowerCase();
52
+
53
+ let globalScore = 0;
54
+ for (const kw of GLOBAL_KEYWORDS) {
55
+ if (lower.includes(kw)) {
56
+ globalScore++;
57
+ }
58
+ }
59
+
60
+ let localScore = 0;
61
+ for (const kw of LOCAL_KEYWORDS) {
62
+ if (lower.includes(kw)) {
63
+ localScore++;
64
+ }
65
+ }
66
+
67
+ // Default to local when tied (localScore >= globalScore means local wins on tie)
68
+ let mode: QueryMode = globalScore > localScore ? "global" : "local";
69
+ let globalUnavailable = false;
70
+
71
+ // Check graph size — force local if too few entities for community summaries
72
+ if (mode === "global") {
73
+ const result = await db.execute(
74
+ "SELECT COUNT(*) AS cnt FROM graph_nodes WHERE t_valid_until IS NULL AND archived_at IS NULL",
75
+ );
76
+ const count = Number((result.rows[0] as { cnt: number }).cnt);
77
+ if (count < config.communityMinGraphSize) {
78
+ mode = "local";
79
+ globalUnavailable = true;
80
+ }
81
+ }
82
+
83
+ return { mode, globalUnavailable };
84
+ }
85
+
86
+ /**
87
+ * Task-type boost vectors: maps task type strings to sets of entity types
88
+ * that should receive a scoring boost during reranking.
89
+ */
90
+ export const TASK_TYPE_BOOSTS: Record<string, Set<string>> = {
91
+ "bug-fix": new Set(["Bug", "Solution"]),
92
+ regression: new Set(["Bug", "Solution"]),
93
+ feature: new Set(["Concept", "Decision"]),
94
+ review: new Set(["Convention"]),
95
+ };
96
+
97
+ /**
98
+ * Package-path boost: returns 0.15 when the entity's package matches the
99
+ * active package, 0 otherwise.
100
+ */
101
+ export function packagePathBoost(entityPkg: string | null, activePkg: string | null): number {
102
+ if (entityPkg != null && activePkg != null && entityPkg === activePkg) {
103
+ return 0.15;
104
+ }
105
+ return 0;
106
+ }
@@ -0,0 +1,156 @@
1
+ // Module: reranker — RRF combination + trust-weighted scoring
2
+
3
+ import type { SiaDb } from "@/graph/db-interface";
4
+ import type { Entity } from "@/graph/entities";
5
+ import type { SiaSearchResult } from "@/mcp/tools/sia-search";
6
+ import { packagePathBoost, TASK_TYPE_BOOSTS } from "@/retrieval/query-classifier";
7
+
8
+ /** A candidate with an entity ID and a score from a single retrieval signal. */
9
+ export interface RankedCandidate {
10
+ entityId: string;
11
+ score: number;
12
+ }
13
+
14
+ /** Options for the rerank function. */
15
+ export interface RerankOpts {
16
+ /** Task type for task-type boosting (e.g. "bug-fix", "feature"). */
17
+ taskType?: string;
18
+ /** Active package path for same-package boosting. */
19
+ packagePath?: string;
20
+ /** If true, exclude Tier 4 entities. */
21
+ paranoid?: boolean;
22
+ /** Maximum number of results to return. */
23
+ limit?: number;
24
+ /** If true, include extraction_method in results. */
25
+ includeProvenance?: boolean;
26
+ }
27
+
28
+ /** Trust weights keyed by tier number (1-4). No index-0. */
29
+ const TRUST_WEIGHTS: Record<number, number> = {
30
+ 1: 1.0,
31
+ 2: 0.9,
32
+ 3: 0.7,
33
+ 4: 0.5,
34
+ };
35
+
36
+ /**
37
+ * Combine multiple ranked candidate lists via Reciprocal Rank Fusion (k=60).
38
+ *
39
+ * Each list is sorted by score DESC. For each entity in each list,
40
+ * the contribution is `1 / (k + rank + 1)` where rank is 0-based.
41
+ * Scores are summed across all lists.
42
+ */
43
+ export function rrfCombine(...lists: RankedCandidate[][]): Map<string, number> {
44
+ const k = 60;
45
+ const scores = new Map<string, number>();
46
+
47
+ for (const list of lists) {
48
+ // Sort by score descending to establish rank order
49
+ const sorted = [...list].sort((a, b) => b.score - a.score);
50
+
51
+ for (let rank = 0; rank < sorted.length; rank++) {
52
+ const candidate = sorted[rank];
53
+ const rrfScore = 1 / (k + rank + 1);
54
+ const current = scores.get(candidate.entityId) ?? 0;
55
+ scores.set(candidate.entityId, current + rrfScore);
56
+ }
57
+ }
58
+
59
+ return scores;
60
+ }
61
+
62
+ /** Batch size for fetching entities from the database. */
63
+ const ENTITY_BATCH_SIZE = 500;
64
+
65
+ /**
66
+ * Rerank entities by combining RRF scores with trust weights, importance,
67
+ * confidence, task-type boosts, and package-path boosts.
68
+ *
69
+ * Formula: rrf_score * importance * confidence * trust_weight[tier] * (1 + task_boost * 0.3) + package_boost
70
+ */
71
+ export async function rerank(
72
+ db: SiaDb,
73
+ rrfScores: Map<string, number>,
74
+ opts?: RerankOpts,
75
+ ): Promise<SiaSearchResult[]> {
76
+ if (rrfScores.size === 0) {
77
+ return [];
78
+ }
79
+
80
+ const entityIds = Array.from(rrfScores.keys());
81
+
82
+ // Fetch entities in batches of 500
83
+ const entities = new Map<string, Entity>();
84
+ for (let i = 0; i < entityIds.length; i += ENTITY_BATCH_SIZE) {
85
+ const batch = entityIds.slice(i, i + ENTITY_BATCH_SIZE);
86
+ const placeholders = batch.map(() => "?").join(", ");
87
+ const sql = `SELECT * FROM graph_nodes WHERE id IN (${placeholders}) AND t_valid_until IS NULL AND archived_at IS NULL`;
88
+ const result = await db.execute(sql, batch);
89
+ for (const row of result.rows) {
90
+ entities.set(row.id as string, row as unknown as Entity);
91
+ }
92
+ }
93
+
94
+ // Determine task-type boosted entity types
95
+ const boostedTypes: Set<string> | undefined = opts?.taskType
96
+ ? TASK_TYPE_BOOSTS[opts.taskType]
97
+ : undefined;
98
+
99
+ // Score and filter
100
+ const scored: Array<{ entity: Entity; finalScore: number }> = [];
101
+
102
+ for (const [entityId, rrfScore] of rrfScores) {
103
+ const entity = entities.get(entityId);
104
+ if (!entity) {
105
+ // Entity was invalidated, archived, or doesn't exist
106
+ continue;
107
+ }
108
+
109
+ // Paranoid filter: remove Tier 4
110
+ if (opts?.paranoid && entity.trust_tier === 4) {
111
+ continue;
112
+ }
113
+
114
+ const trustWeight = TRUST_WEIGHTS[entity.trust_tier] ?? 0.5;
115
+ const taskBoost = boostedTypes?.has(entity.type) ? 1 : 0;
116
+ const pkgBoost = packagePathBoost(entity.package_path, opts?.packagePath ?? null);
117
+
118
+ const finalScore =
119
+ rrfScore * entity.importance * entity.confidence * trustWeight * (1 + taskBoost * 0.3) +
120
+ pkgBoost;
121
+
122
+ scored.push({ entity, finalScore });
123
+ }
124
+
125
+ // Sort by finalScore DESC
126
+ scored.sort((a, b) => b.finalScore - a.finalScore);
127
+
128
+ // Apply limit
129
+ const limit = opts?.limit ?? 15;
130
+ const top = scored.slice(0, limit);
131
+
132
+ // Map to SiaSearchResult
133
+ return top.map(({ entity, finalScore: _finalScore }) => {
134
+ const base: SiaSearchResult = {
135
+ id: entity.id,
136
+ type: entity.type,
137
+ name: entity.name,
138
+ summary: entity.summary,
139
+ content: entity.content,
140
+ trust_tier: entity.trust_tier,
141
+ confidence: entity.confidence,
142
+ importance: entity.importance,
143
+ tags: entity.tags,
144
+ file_paths: entity.file_paths,
145
+ conflict_group_id: entity.conflict_group_id ?? null,
146
+ t_valid_from: entity.t_valid_from ?? null,
147
+ source_repo_name: null,
148
+ };
149
+
150
+ if (opts?.includeProvenance) {
151
+ base.extraction_method = entity.extraction_method ?? null;
152
+ }
153
+
154
+ return base;
155
+ });
156
+ }
@@ -0,0 +1,236 @@
1
+ // Module: search — Three-stage pipeline orchestration
2
+ //
3
+ // Stage 1: Parallel BM25 + graph traversal + vector search
4
+ // Stage 2: 1-hop neighbor expansion for candidates
5
+ // Stage 3: RRF combination + trust-weighted reranking
6
+ // Global queries bypass the pipeline and return community summaries.
7
+
8
+ import type { Embedder } from "@/capture/embedder";
9
+ import type { SiaDb } from "@/graph/db-interface";
10
+ import type { SiaSearchResult } from "@/mcp/tools/sia-search";
11
+ import { bm25Search } from "@/retrieval/bm25-search";
12
+ import { graphTraversalSearch } from "@/retrieval/graph-traversal";
13
+ import { classifyQuery } from "@/retrieval/query-classifier";
14
+ import { type RankedCandidate, rerank, rrfCombine } from "@/retrieval/reranker";
15
+ import { vectorSearch } from "@/retrieval/vector-search";
16
+
17
+ /** Options accepted by hybridSearch. */
18
+ export interface SearchOptions {
19
+ query: string;
20
+ taskType?: string;
21
+ nodeTypes?: string[];
22
+ packagePath?: string;
23
+ paranoid?: boolean;
24
+ limit?: number;
25
+ includeProvenance?: boolean;
26
+ communityMinGraphSize?: number;
27
+ }
28
+
29
+ /** Result returned by hybridSearch. */
30
+ export interface SearchResult {
31
+ results: SiaSearchResult[];
32
+ mode: "local" | "global";
33
+ globalUnavailable: boolean;
34
+ }
35
+
36
+ /** Default minimum graph size before community summaries are available. */
37
+ const DEFAULT_COMMUNITY_MIN_GRAPH_SIZE = 100;
38
+
39
+ /**
40
+ * Three-stage hybrid retrieval pipeline.
41
+ *
42
+ * 1. Classify query as local or global.
43
+ * 2. If global, return community summaries from the `communities` table.
44
+ * 3. Stage 1: parallel BM25 + graph traversal + vector search.
45
+ * 4. Stage 2: expand 1-hop neighbors for every candidate.
46
+ * 5. Stage 3: RRF combine + trust-weighted rerank.
47
+ * 6. Post-filter by nodeTypes if specified.
48
+ * 7. Attach extraction_method if includeProvenance is set.
49
+ *
50
+ * The `embedder` parameter is nullable -- when null, vector search is skipped
51
+ * and the pipeline runs on BM25 + graph traversal only.
52
+ */
53
+ export async function hybridSearch(
54
+ db: SiaDb,
55
+ embedder: Embedder | null,
56
+ opts: SearchOptions,
57
+ ): Promise<SearchResult> {
58
+ const limit = opts.limit ?? 15;
59
+ const communityMinGraphSize = opts.communityMinGraphSize ?? DEFAULT_COMMUNITY_MIN_GRAPH_SIZE;
60
+
61
+ // --- Classify query ---------------------------------------------------
62
+ const classification = await classifyQuery(db, opts.query, {
63
+ communityMinGraphSize,
64
+ });
65
+
66
+ // --- Global mode: return community summaries --------------------------
67
+ if (classification.mode === "global") {
68
+ const communities = await fetchCommunitySummaries(db, limit);
69
+ return {
70
+ results: communities,
71
+ mode: "global",
72
+ globalUnavailable: false,
73
+ };
74
+ }
75
+
76
+ // --- Stage 1: parallel retrieval signals ------------------------------
77
+ const searchOpts = {
78
+ limit: limit * 3, // over-fetch to leave room for reranking
79
+ paranoid: opts.paranoid,
80
+ packagePath: opts.packagePath,
81
+ };
82
+
83
+ const [bm25Results, graphResults, vecResults] = await Promise.all([
84
+ bm25Search(db, opts.query, searchOpts),
85
+ graphTraversalSearch(db, opts.query, searchOpts),
86
+ embedder ? vectorSearch(db, opts.query, embedder, searchOpts) : Promise.resolve([]),
87
+ ]);
88
+
89
+ // --- Stage 2: expand 1-hop neighbors ----------------------------------
90
+ const expandedGraphResults = await expandNeighbors(db, graphResults, opts.paranoid);
91
+
92
+ // --- Stage 3: RRF combine + rerank ------------------------------------
93
+ const bm25Candidates: RankedCandidate[] = bm25Results.map((r) => ({
94
+ entityId: r.entityId,
95
+ score: r.score,
96
+ }));
97
+ const graphCandidates: RankedCandidate[] = expandedGraphResults.map((r) => ({
98
+ entityId: r.entityId,
99
+ score: r.score,
100
+ }));
101
+ const vecCandidates: RankedCandidate[] = vecResults.map((r) => ({
102
+ entityId: r.entityId,
103
+ score: r.score,
104
+ }));
105
+
106
+ const rrfScores = rrfCombine(bm25Candidates, graphCandidates, vecCandidates);
107
+
108
+ let results = await rerank(db, rrfScores, {
109
+ taskType: opts.taskType,
110
+ packagePath: opts.packagePath,
111
+ paranoid: opts.paranoid,
112
+ limit,
113
+ includeProvenance: opts.includeProvenance,
114
+ });
115
+
116
+ // --- Post-filter by nodeTypes ------------------------------------------
117
+ if (opts.nodeTypes && opts.nodeTypes.length > 0) {
118
+ const allowed = new Set(opts.nodeTypes);
119
+ results = results.filter((r) => allowed.has(r.type));
120
+ }
121
+
122
+ // --- Provenance --------------------------------------------------------
123
+ if (opts.includeProvenance) {
124
+ await attachProvenance(db, results);
125
+ }
126
+
127
+ return {
128
+ results,
129
+ mode: "local",
130
+ globalUnavailable: classification.globalUnavailable,
131
+ };
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // Internal helpers
136
+ // ---------------------------------------------------------------------------
137
+
138
+ /**
139
+ * Fetch community summaries for global-mode queries.
140
+ * Returns communities that have a non-NULL summary, ordered by member_count DESC.
141
+ */
142
+ async function fetchCommunitySummaries(db: SiaDb, limit: number): Promise<SiaSearchResult[]> {
143
+ const result = await db.execute(
144
+ "SELECT * FROM communities WHERE summary IS NOT NULL ORDER BY member_count DESC LIMIT ?",
145
+ [limit],
146
+ );
147
+
148
+ return (result.rows as Record<string, unknown>[]).map((row) => ({
149
+ id: row.id as string,
150
+ type: "Community",
151
+ name: row.id as string,
152
+ summary: (row.summary as string) ?? "",
153
+ content: (row.summary as string) ?? "",
154
+ trust_tier: 1,
155
+ confidence: 1.0,
156
+ importance: 1.0,
157
+ tags: "[]",
158
+ file_paths: "[]",
159
+ conflict_group_id: null,
160
+ t_valid_from: null,
161
+ source_repo_name: null,
162
+ }));
163
+ }
164
+
165
+ /**
166
+ * Stage 2: expand 1-hop neighbors for each candidate entity.
167
+ *
168
+ * For each entity in the input list, query the `edges` table for active
169
+ * 1-hop neighbors. Neighbors not already present in the result set are
170
+ * added at score 0.7.
171
+ */
172
+ async function expandNeighbors(
173
+ db: SiaDb,
174
+ results: Array<{ entityId: string; score: number }>,
175
+ paranoid?: boolean,
176
+ ): Promise<Array<{ entityId: string; score: number }>> {
177
+ const scoreMap = new Map<string, number>();
178
+
179
+ // Seed with existing results
180
+ for (const r of results) {
181
+ const existing = scoreMap.get(r.entityId);
182
+ if (existing === undefined || r.score > existing) {
183
+ scoreMap.set(r.entityId, r.score);
184
+ }
185
+ }
186
+
187
+ // Expand each candidate
188
+ const candidateIds = results.map((r) => r.entityId);
189
+ for (const entityId of candidateIds) {
190
+ const edgeResult = await db.execute(
191
+ "SELECT from_id, to_id FROM graph_edges WHERE (from_id = ? OR to_id = ?) AND t_valid_until IS NULL",
192
+ [entityId, entityId],
193
+ );
194
+
195
+ for (const row of edgeResult.rows) {
196
+ const fromId = row.from_id as string;
197
+ const toId = row.to_id as string;
198
+ const neighborId = fromId === entityId ? toId : fromId;
199
+
200
+ // Skip if already in the result set
201
+ if (scoreMap.has(neighborId)) continue;
202
+
203
+ // Validate the neighbor is active (and paranoid-safe)
204
+ const paranoidClause = paranoid ? " AND trust_tier < 4" : "";
205
+ const check = await db.execute(
206
+ `SELECT id FROM graph_nodes WHERE id = ? AND t_valid_until IS NULL AND archived_at IS NULL${paranoidClause}`,
207
+ [neighborId],
208
+ );
209
+ if (check.rows.length === 0) continue;
210
+
211
+ scoreMap.set(neighborId, 0.7);
212
+ }
213
+ }
214
+
215
+ return [...scoreMap.entries()].map(([entityId, score]) => ({
216
+ entityId,
217
+ score,
218
+ }));
219
+ }
220
+
221
+ /**
222
+ * Attach extraction_method to results that don't already have it set.
223
+ * Only queries the DB for results where extraction_method is undefined.
224
+ */
225
+ async function attachProvenance(db: SiaDb, results: SiaSearchResult[]): Promise<void> {
226
+ for (const result of results) {
227
+ if (result.extraction_method === undefined) {
228
+ const row = await db.execute("SELECT extraction_method FROM graph_nodes WHERE id = ?", [
229
+ result.id,
230
+ ]);
231
+ if (row.rows.length > 0) {
232
+ result.extraction_method = (row.rows[0].extraction_method as string | null) ?? null;
233
+ }
234
+ }
235
+ }
236
+ }
@@ -0,0 +1,102 @@
1
+ // Module: throttle — Progressive rate limiting for MCP tool calls
2
+
3
+ import type { SiaDb } from "@/graph/db-interface";
4
+
5
+ export type ThrottleMode = "normal" | "reduced" | "blocked";
6
+
7
+ export interface ThrottleResult {
8
+ mode: ThrottleMode;
9
+ callCount: number;
10
+ warning?: string;
11
+ }
12
+
13
+ export interface ThrottleConfig {
14
+ normalMax: number;
15
+ reducedMax: number;
16
+ }
17
+
18
+ const DEFAULT_THROTTLE_CONFIG: ThrottleConfig = {
19
+ normalMax: 3,
20
+ reducedMax: 8,
21
+ };
22
+
23
+ export const THROTTLED_TOOLS = new Set([
24
+ "sia_search",
25
+ "sia_execute",
26
+ "sia_execute_file",
27
+ "sia_fetch_and_index",
28
+ "sia_by_file",
29
+ "sia_expand",
30
+ "sia_at_time",
31
+ "sia_backlinks",
32
+ ]);
33
+
34
+ /**
35
+ * Progressive throttle backed by the search_throttle table in graph.db.
36
+ *
37
+ * Thresholds (inclusive):
38
+ * - count <= normalMax → "normal"
39
+ * - count <= reducedMax → "reduced" (with warning)
40
+ * - count > reducedMax → "blocked" (with warning mentioning sia_batch_execute)
41
+ */
42
+ export class ProgressiveThrottle {
43
+ private config: ThrottleConfig;
44
+
45
+ constructor(
46
+ private db: SiaDb,
47
+ config?: Partial<ThrottleConfig>,
48
+ ) {
49
+ this.config = { ...DEFAULT_THROTTLE_CONFIG, ...config };
50
+ }
51
+
52
+ /**
53
+ * Record a tool call for the given session/tool pair and return the throttle mode.
54
+ */
55
+ async check(sessionId: string, toolName: string): Promise<ThrottleResult> {
56
+ const now = Date.now();
57
+
58
+ // Upsert: insert or increment call_count
59
+ await this.db.execute(
60
+ `INSERT INTO search_throttle (session_id, tool_name, call_count, last_called_at)
61
+ VALUES (?, ?, 1, ?)
62
+ ON CONFLICT(session_id, tool_name) DO UPDATE SET
63
+ call_count = call_count + 1,
64
+ last_called_at = ?`,
65
+ [sessionId, toolName, now, now],
66
+ );
67
+
68
+ // Read back the updated count
69
+ const { rows } = await this.db.execute(
70
+ "SELECT call_count FROM search_throttle WHERE session_id = ? AND tool_name = ?",
71
+ [sessionId, toolName],
72
+ );
73
+
74
+ const callCount = (rows[0]?.call_count as number) ?? 1;
75
+ const { normalMax, reducedMax } = this.config;
76
+
77
+ if (callCount <= normalMax) {
78
+ return { mode: "normal", callCount };
79
+ }
80
+
81
+ if (callCount <= reducedMax) {
82
+ return {
83
+ mode: "reduced",
84
+ callCount,
85
+ warning: `Reducing results (${callCount} calls). Consider sia_batch_execute for batch operations.`,
86
+ };
87
+ }
88
+
89
+ return {
90
+ mode: "blocked",
91
+ callCount,
92
+ warning: `Tool blocked for this session (${callCount} calls). Use sia_batch_execute instead.`,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Clear all throttle entries for the given session.
98
+ */
99
+ async reset(sessionId: string): Promise<void> {
100
+ await this.db.execute("DELETE FROM search_throttle WHERE session_id = ?", [sessionId]);
101
+ }
102
+ }