@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,89 @@
1
+ // Module: sia-by-file — Retrieve knowledge graph entities associated with a file path
2
+
3
+ import type { z } from "zod";
4
+ import type { SiaDb } from "@/graph/db-interface";
5
+ import type { Entity } from "@/graph/entities";
6
+ import { annotateFreshness } from "@/mcp/freshness-annotator";
7
+ import type { SiaByFileInput } from "@/mcp/server";
8
+ import type { WorkspaceDeps } from "@/mcp/tools/sia-search";
9
+ import { workspaceSearch } from "@/retrieval/workspace-search";
10
+
11
+ /** Result shape — same as SiaSearchResult (array of entities). */
12
+ export interface SiaByFileResult {
13
+ entities: Entity[];
14
+ }
15
+
16
+ /**
17
+ * Find active entities whose `file_paths` JSON array contains the given file path.
18
+ *
19
+ * 1. Exact match on `file_path` using json_each.
20
+ * 2. If no exact match, fall back to filename stem match (LIKE '%/<stem>%').
21
+ * 3. Results ordered by importance DESC, capped at `limit` (default 10).
22
+ */
23
+ export async function handleSiaByFile(
24
+ db: SiaDb,
25
+ input: z.infer<typeof SiaByFileInput>,
26
+ workspaceDeps?: WorkspaceDeps,
27
+ ): Promise<SiaByFileResult> {
28
+ const limit = input.limit ?? 10;
29
+ const filePath = input.file_path;
30
+
31
+ // Workspace-scoped search
32
+ if (input.workspace && workspaceDeps) {
33
+ const result = await workspaceSearch({
34
+ primaryDb: db,
35
+ metaDb: workspaceDeps.metaDb,
36
+ bridgeDb: workspaceDeps.bridgeDb,
37
+ workspaceId: workspaceDeps.workspaceId,
38
+ primaryRepoId: workspaceDeps.primaryRepoId,
39
+ query: filePath,
40
+ siaHome: workspaceDeps.siaHome,
41
+ limit,
42
+ });
43
+ const annotated = await annotateFreshness(
44
+ result.entities as unknown as Record<string, unknown>[],
45
+ db,
46
+ );
47
+ return { entities: annotated as unknown as Entity[] };
48
+ }
49
+
50
+ // --- Exact match ---
51
+ const exactResult = await db.execute(
52
+ `SELECT * FROM graph_nodes
53
+ WHERE EXISTS (SELECT 1 FROM json_each(file_paths) WHERE value = ?)
54
+ AND t_valid_until IS NULL
55
+ AND archived_at IS NULL
56
+ ORDER BY importance DESC
57
+ LIMIT ?`,
58
+ [filePath, limit],
59
+ );
60
+
61
+ if (exactResult.rows.length > 0) {
62
+ const annotated = await annotateFreshness(
63
+ exactResult.rows as unknown as Record<string, unknown>[],
64
+ db,
65
+ );
66
+ return { entities: annotated as unknown as Entity[] };
67
+ }
68
+
69
+ // --- Filename stem fallback ---
70
+ // Extract filename from the path (last segment after '/')
71
+ const parts = filePath.split("/");
72
+ const filename = parts[parts.length - 1] ?? filePath;
73
+
74
+ const stemResult = await db.execute(
75
+ `SELECT * FROM graph_nodes
76
+ WHERE EXISTS (SELECT 1 FROM json_each(file_paths) WHERE value LIKE '%/' || ? || '%')
77
+ AND t_valid_until IS NULL
78
+ AND archived_at IS NULL
79
+ ORDER BY importance DESC
80
+ LIMIT ?`,
81
+ [filename, limit],
82
+ );
83
+
84
+ const annotated = await annotateFreshness(
85
+ stemResult.rows as unknown as Record<string, unknown>[],
86
+ db,
87
+ );
88
+ return { entities: annotated as unknown as Entity[] };
89
+ }
@@ -0,0 +1,113 @@
1
+ // Module: sia-community — Community-level summary retrieval for Phase 3
2
+ //
3
+ // Looks up communities by entity membership, text query (LIKE match),
4
+ // level, and package_path. Returns up to 3 CommunitySummary objects.
5
+ // Full vector search upgrades arrive in Phase 7.
6
+
7
+ import type { z } from "zod";
8
+ import type { SiaDb } from "@/graph/db-interface";
9
+ import type { SiaCommunityInput } from "@/mcp/server";
10
+
11
+ /** Shape returned for each community in sia_community results. */
12
+ export interface CommunitySummary {
13
+ id: string;
14
+ level: number;
15
+ summary: string | null;
16
+ member_count: number;
17
+ parent_id: string | null;
18
+ package_path: string | null;
19
+ }
20
+
21
+ /** Top-level result envelope for sia_community. */
22
+ export interface SiaCommunityResult {
23
+ communities: CommunitySummary[];
24
+ global_unavailable?: boolean;
25
+ }
26
+
27
+ /** Maximum number of community results returned. */
28
+ const MAX_RESULTS = 3;
29
+
30
+ /**
31
+ * Execute the sia_community tool logic.
32
+ *
33
+ * Lookup strategies (applied in order of precedence):
34
+ * 1. `entity_id` — find the community containing that entity via community_members
35
+ * 2. `query` — LIKE match against community summary text
36
+ * 3. Neither — return all communities (subject to level/package_path filters)
37
+ *
38
+ * Additional filters applied on top:
39
+ * - `level` — exact match on community level (0 | 1 | 2)
40
+ * - `package_path` — exact match on community package_path
41
+ *
42
+ * When the graph has no communities and the total entity count is < 100,
43
+ * returns `{ communities: [], global_unavailable: true }`.
44
+ */
45
+ export async function handleSiaCommunity(
46
+ db: SiaDb,
47
+ input: z.infer<typeof SiaCommunityInput>,
48
+ ): Promise<SiaCommunityResult> {
49
+ const clauses: string[] = [];
50
+ const params: unknown[] = [];
51
+
52
+ // --- entity_id lookup via community_members join -----------------------
53
+ if (input.entity_id) {
54
+ clauses.push("c.id IN (SELECT community_id FROM community_members WHERE entity_id = ?)");
55
+ params.push(input.entity_id);
56
+ }
57
+
58
+ // --- query: simple LIKE match on summary text --------------------------
59
+ if (input.query) {
60
+ clauses.push("c.summary LIKE ?");
61
+ params.push(`%${input.query}%`);
62
+ }
63
+
64
+ // --- level filter ------------------------------------------------------
65
+ if (input.level !== undefined) {
66
+ clauses.push("c.level = ?");
67
+ params.push(input.level);
68
+ }
69
+
70
+ // --- package_path filter -----------------------------------------------
71
+ if (input.package_path) {
72
+ clauses.push("c.package_path = ?");
73
+ params.push(input.package_path);
74
+ }
75
+
76
+ const whereClause = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
77
+ params.push(MAX_RESULTS);
78
+
79
+ const sql = `SELECT c.id, c.level, c.summary, c.member_count, c.parent_id, c.package_path
80
+ FROM communities c
81
+ ${whereClause}
82
+ ORDER BY c.member_count DESC
83
+ LIMIT ?`;
84
+
85
+ const result = await db.execute(sql, params);
86
+
87
+ const communities: CommunitySummary[] = result.rows.map((row) => ({
88
+ id: row.id as string,
89
+ level: row.level as number,
90
+ summary: (row.summary as string | null) ?? null,
91
+ member_count: (row.member_count as number) ?? 0,
92
+ parent_id: (row.parent_id as string | null) ?? null,
93
+ package_path: (row.package_path as string | null) ?? null,
94
+ }));
95
+
96
+ // If no communities found, check whether this is because the graph is too
97
+ // small (< 100 entities) to have generated communities at all.
98
+ if (communities.length === 0) {
99
+ const countResult = await db.execute("SELECT COUNT(*) AS cnt FROM communities", []);
100
+ const totalCommunities = (countResult.rows[0]?.cnt as number) ?? 0;
101
+
102
+ if (totalCommunities === 0) {
103
+ const entityCountResult = await db.execute("SELECT COUNT(*) AS cnt FROM graph_nodes", []);
104
+ const totalEntities = (entityCountResult.rows[0]?.cnt as number) ?? 0;
105
+
106
+ if (totalEntities < 100) {
107
+ return { communities: [], global_unavailable: true };
108
+ }
109
+ }
110
+ }
111
+
112
+ return { communities };
113
+ }
@@ -0,0 +1,73 @@
1
+ // Module: sia-doctor — Handler for the sia_doctor diagnostic tool
2
+
3
+ import type { z } from "zod";
4
+ import type { SiaDb } from "@/graph/db-interface";
5
+ import type { SiaDoctorInput } from "@/mcp/server";
6
+ import { RUNTIME_MAP } from "@/sandbox/executor";
7
+ import {
8
+ checkFts5,
9
+ checkOrphanEdges,
10
+ checkRuntime,
11
+ checkTemporalInvariants,
12
+ type DiagnosticCheck,
13
+ } from "@/shared/diagnostics";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // SiaDoctorResult
17
+ // ---------------------------------------------------------------------------
18
+
19
+ export interface SiaDoctorResult {
20
+ checks: DiagnosticCheck[];
21
+ healthy: boolean;
22
+ warnings: string[];
23
+ }
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // handleSiaDoctor
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export async function handleSiaDoctor(
30
+ db: SiaDb,
31
+ input: z.infer<typeof SiaDoctorInput>,
32
+ ): Promise<SiaDoctorResult> {
33
+ const requested = input.checks ?? ["all"];
34
+ const runAll = requested.includes("all");
35
+
36
+ const checks: DiagnosticCheck[] = [];
37
+
38
+ // -----------------------------------------------------------------------
39
+ // Runtimes (14 entries from RUNTIME_MAP)
40
+ // -----------------------------------------------------------------------
41
+ if (runAll || requested.includes("runtimes")) {
42
+ const runtimeChecks = await Promise.all(
43
+ Object.entries(RUNTIME_MAP).map(([lang, def]) => checkRuntime(lang, def.cmd)),
44
+ );
45
+ checks.push(...runtimeChecks);
46
+ }
47
+
48
+ // -----------------------------------------------------------------------
49
+ // FTS5
50
+ // -----------------------------------------------------------------------
51
+ if (runAll || requested.includes("fts5")) {
52
+ checks.push(await checkFts5(db));
53
+ }
54
+
55
+ // -----------------------------------------------------------------------
56
+ // Graph integrity: orphan edges + temporal invariants
57
+ // -----------------------------------------------------------------------
58
+ if (runAll || requested.includes("graph_integrity")) {
59
+ const [orphanResult, temporalResult] = await Promise.all([
60
+ checkOrphanEdges(db),
61
+ checkTemporalInvariants(db),
62
+ ]);
63
+ checks.push(orphanResult, temporalResult);
64
+ }
65
+
66
+ // -----------------------------------------------------------------------
67
+ // Aggregate
68
+ // -----------------------------------------------------------------------
69
+ const healthy = checks.every((c) => c.status === "ok");
70
+ const warnings = checks.filter((c) => c.status !== "ok").map((c) => c.message);
71
+
72
+ return { checks, healthy, warnings };
73
+ }
@@ -0,0 +1,122 @@
1
+ // Module: sia-execute-file — Execute an existing file in a sandbox subprocess with throttle + context mode
2
+ // Raw file content never enters the agent's context window — it is copied to a temp dir and executed from there.
3
+
4
+ import { copyFileSync, existsSync, mkdtempSync, rmSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { basename, join } from "node:path";
7
+ import type { z } from "zod";
8
+ import type { Embedder } from "@/capture/embedder";
9
+ import type { SiaDb } from "@/graph/db-interface";
10
+ import type { SiaExecuteFileInput as SiaExecuteFileInputSchema } from "@/mcp/server";
11
+ import type { ProgressiveThrottle } from "@/retrieval/throttle";
12
+ import type { ContextModeResult } from "@/sandbox/context-mode";
13
+ import { applyContextMode, lineChunker } from "@/sandbox/context-mode";
14
+ import { buildSandboxEnv } from "@/sandbox/credential-pass";
15
+ import { executeSubprocess } from "@/sandbox/executor";
16
+
17
+ export type SiaExecuteFileInput = z.infer<typeof SiaExecuteFileInputSchema>;
18
+
19
+ export interface SiaExecuteFileConfig {
20
+ sandboxTimeoutMs: number;
21
+ sandboxOutputMaxBytes: number;
22
+ contextModeThreshold: number;
23
+ contextModeTopK: number;
24
+ }
25
+
26
+ const DEFAULT_CONFIG: SiaExecuteFileConfig = {
27
+ sandboxTimeoutMs: 30_000,
28
+ sandboxOutputMaxBytes: 1_048_576,
29
+ contextModeThreshold: 10_240,
30
+ contextModeTopK: 5,
31
+ };
32
+
33
+ export interface SiaExecuteFileResult {
34
+ stdout?: string;
35
+ stderr?: string;
36
+ exitCode?: number | null;
37
+ timedOut?: boolean;
38
+ runtimeMs?: number;
39
+ contextMode?: ContextModeResult;
40
+ error?: string;
41
+ }
42
+
43
+ /**
44
+ * Handle a sia_execute_file request: validate file exists, throttle check,
45
+ * copy to sandbox temp dir, execute, apply optional context mode.
46
+ */
47
+ export async function handleSiaExecuteFile(
48
+ db: SiaDb,
49
+ input: SiaExecuteFileInput,
50
+ embedder: Embedder,
51
+ throttle: ProgressiveThrottle,
52
+ sessionId: string,
53
+ config?: Partial<SiaExecuteFileConfig>,
54
+ ): Promise<SiaExecuteFileResult> {
55
+ const cfg: SiaExecuteFileConfig = { ...DEFAULT_CONFIG, ...config };
56
+
57
+ // 1. Validate file exists
58
+ if (!existsSync(input.file_path)) {
59
+ return { error: `File not found: ${input.file_path}` };
60
+ }
61
+
62
+ // 2. Throttle check — if blocked, return error
63
+ const throttleResult = await throttle.check(sessionId, "sia_execute_file");
64
+ if (throttleResult.mode === "blocked") {
65
+ return { error: throttleResult.warning ?? "Tool blocked for this session." };
66
+ }
67
+
68
+ // 3. Copy file to sandbox temp dir so raw content stays on disk, not in context
69
+ const sandboxDir = mkdtempSync(join(tmpdir(), "sia-exec-file-"));
70
+ const copiedPath = join(sandboxDir, basename(input.file_path));
71
+ copyFileSync(input.file_path, copiedPath);
72
+
73
+ // 4. Build sandbox env
74
+ const env = buildSandboxEnv();
75
+
76
+ // 5. Execute subprocess using the copied file path
77
+ const result = await executeSubprocess({
78
+ language: input.language,
79
+ filePath: copiedPath,
80
+ code: "",
81
+ timeout: input.timeout ?? cfg.sandboxTimeoutMs,
82
+ cwd: sandboxDir,
83
+ env,
84
+ outputMaxBytes: cfg.sandboxOutputMaxBytes,
85
+ });
86
+ try {
87
+ rmSync(sandboxDir, { recursive: true, force: true });
88
+ } catch (e) {
89
+ console.error("[sia-execute-file] cleanup failed:", (e as Error).message);
90
+ }
91
+
92
+ // 6. Apply context mode if output large + intent provided
93
+ if (result.stdout.length > cfg.contextModeThreshold && input.intent !== undefined) {
94
+ const contextMode = await applyContextMode(
95
+ result.stdout,
96
+ input.intent,
97
+ lineChunker,
98
+ db,
99
+ embedder,
100
+ sessionId,
101
+ { threshold: cfg.contextModeThreshold, topK: cfg.contextModeTopK },
102
+ );
103
+
104
+ return {
105
+ ...(contextMode.applied ? {} : { stdout: result.stdout }),
106
+ stderr: result.stderr || undefined,
107
+ exitCode: result.exitCode,
108
+ timedOut: result.timedOut || undefined,
109
+ runtimeMs: result.runtimeMs,
110
+ contextMode,
111
+ };
112
+ }
113
+
114
+ // 7. Return plain result
115
+ return {
116
+ stdout: result.stdout,
117
+ stderr: result.stderr || undefined,
118
+ exitCode: result.exitCode,
119
+ timedOut: result.timedOut || undefined,
120
+ runtimeMs: result.runtimeMs,
121
+ };
122
+ }
@@ -0,0 +1,104 @@
1
+ // Module: sia-execute — Execute code in a sandbox subprocess with throttle + context mode
2
+
3
+ import type { z } from "zod";
4
+ import type { Embedder } from "@/capture/embedder";
5
+ import type { SiaDb } from "@/graph/db-interface";
6
+ import type { SiaExecuteInput as SiaExecuteInputSchema } from "@/mcp/server";
7
+ import type { ProgressiveThrottle } from "@/retrieval/throttle";
8
+ import type { ContextModeResult } from "@/sandbox/context-mode";
9
+ import { applyContextMode, lineChunker } from "@/sandbox/context-mode";
10
+ import { buildSandboxEnv } from "@/sandbox/credential-pass";
11
+ import { executeSubprocess } from "@/sandbox/executor";
12
+
13
+ export type SiaExecuteInput = z.infer<typeof SiaExecuteInputSchema>;
14
+
15
+ export interface SiaExecuteConfig {
16
+ sandboxTimeoutMs: number;
17
+ sandboxOutputMaxBytes: number;
18
+ contextModeThreshold: number;
19
+ contextModeTopK: number;
20
+ }
21
+
22
+ const DEFAULT_CONFIG: SiaExecuteConfig = {
23
+ sandboxTimeoutMs: 30_000,
24
+ sandboxOutputMaxBytes: 1_048_576,
25
+ contextModeThreshold: 10_240,
26
+ contextModeTopK: 5,
27
+ };
28
+
29
+ export interface SiaExecuteResult {
30
+ stdout?: string;
31
+ stderr?: string;
32
+ exitCode?: number | null;
33
+ timedOut?: boolean;
34
+ truncated?: boolean;
35
+ runtimeMs?: number;
36
+ contextMode?: ContextModeResult;
37
+ error?: string;
38
+ }
39
+
40
+ /**
41
+ * Handle a sia_execute request: throttle check, sandbox execution, optional context mode.
42
+ */
43
+ export async function handleSiaExecute(
44
+ db: SiaDb,
45
+ input: SiaExecuteInput,
46
+ embedder: Embedder,
47
+ throttle: ProgressiveThrottle,
48
+ sessionId: string,
49
+ config?: Partial<SiaExecuteConfig>,
50
+ ): Promise<SiaExecuteResult> {
51
+ const cfg: SiaExecuteConfig = { ...DEFAULT_CONFIG, ...config };
52
+
53
+ // 1. Throttle check — if blocked, return error
54
+ const throttleResult = await throttle.check(sessionId, "sia_execute");
55
+ if (throttleResult.mode === "blocked") {
56
+ return { error: throttleResult.warning ?? "Tool blocked for this session." };
57
+ }
58
+
59
+ // 2. Build sandbox env
60
+ const env = buildSandboxEnv(input.env);
61
+
62
+ // 3. Execute subprocess
63
+ const result = await executeSubprocess({
64
+ language: input.language,
65
+ code: input.code,
66
+ timeout: input.timeout ?? cfg.sandboxTimeoutMs,
67
+ env,
68
+ outputMaxBytes: cfg.sandboxOutputMaxBytes,
69
+ });
70
+
71
+ // 4. Apply context mode if output large + intent provided
72
+ if (result.stdout.length > cfg.contextModeThreshold && input.intent !== undefined) {
73
+ const contextMode = await applyContextMode(
74
+ result.stdout,
75
+ input.intent,
76
+ lineChunker,
77
+ db,
78
+ embedder,
79
+ sessionId,
80
+ { threshold: cfg.contextModeThreshold, topK: cfg.contextModeTopK },
81
+ );
82
+
83
+ return {
84
+ // Omit stdout when context mode was applied
85
+ ...(contextMode.applied ? {} : { stdout: result.stdout }),
86
+ stderr: result.stderr || undefined,
87
+ exitCode: result.exitCode,
88
+ timedOut: result.timedOut || undefined,
89
+ truncated: result.truncated || undefined,
90
+ runtimeMs: result.runtimeMs,
91
+ contextMode,
92
+ };
93
+ }
94
+
95
+ // 5. Return plain result
96
+ return {
97
+ stdout: result.stdout,
98
+ stderr: result.stderr || undefined,
99
+ exitCode: result.exitCode,
100
+ timedOut: result.timedOut || undefined,
101
+ truncated: result.truncated || undefined,
102
+ runtimeMs: result.runtimeMs,
103
+ };
104
+ }
@@ -0,0 +1,158 @@
1
+ // Module: sia-expand — BFS neighbourhood expansion from an entity in the knowledge graph
2
+
3
+ import type { z } from "zod";
4
+ import { getOrCreateLevel1Summary } from "@/community/raptor";
5
+ import type { SiaDb } from "@/graph/db-interface";
6
+ import type { EdgeRow } from "@/graph/edges";
7
+ import type { Entity } from "@/graph/entities";
8
+ import { annotateFreshness } from "@/mcp/freshness-annotator";
9
+ import type { SiaExpandInput } from "@/mcp/server";
10
+
11
+ /** Result shape for sia_expand. */
12
+ export interface SiaExpandResult {
13
+ entity: Entity;
14
+ neighbors: Entity[];
15
+ edges: EdgeRow[];
16
+ edge_count: number;
17
+ }
18
+
19
+ /** Error result when root entity is not found. */
20
+ export interface SiaExpandError {
21
+ error: string;
22
+ }
23
+
24
+ const MAX_ENTITIES = 50;
25
+ const MAX_EDGES = 200;
26
+
27
+ /**
28
+ * BFS expansion from `entity_id` through active edges.
29
+ *
30
+ * - Configurable depth (default 1, max 3).
31
+ * - Hard cap of 50 entities in the result.
32
+ * - Optional `edge_types` filter restricts which edge types are traversed.
33
+ * - `edge_count` is the total number of active edges found (before the 200 cap).
34
+ * - `edges[]` is capped at 200 entries.
35
+ * - Returns an error object if the root entity does not exist or is inactive.
36
+ */
37
+ export async function handleSiaExpand(
38
+ db: SiaDb,
39
+ input: z.infer<typeof SiaExpandInput>,
40
+ ): Promise<SiaExpandResult | SiaExpandError> {
41
+ const depth = input.depth ?? 1;
42
+ const edgeTypes = input.edge_types;
43
+
44
+ // --- Fetch root entity ---
45
+ const rootResult = await db.execute(
46
+ "SELECT * FROM graph_nodes WHERE id = ? AND t_valid_until IS NULL AND archived_at IS NULL",
47
+ [input.entity_id],
48
+ );
49
+
50
+ if (rootResult.rows.length === 0) {
51
+ return { error: `Entity not found or inactive: ${input.entity_id}` };
52
+ }
53
+
54
+ const rootEntity = rootResult.rows[0] as unknown as Entity;
55
+
56
+ // Fire-and-forget: lazily generate Level 1 summary for this entity
57
+ void getOrCreateLevel1Summary(db, input.entity_id);
58
+
59
+ // --- BFS ---
60
+ const visited = new Set<string>([input.entity_id]);
61
+ const allEdges: EdgeRow[] = [];
62
+ let totalEdgeCount = 0;
63
+
64
+ // Current frontier of entity IDs to expand
65
+ let frontier = [input.entity_id];
66
+
67
+ for (let d = 0; d < depth; d++) {
68
+ if (frontier.length === 0) break;
69
+ if (visited.size >= MAX_ENTITIES) break;
70
+
71
+ const nextFrontier: string[] = [];
72
+
73
+ for (const entityId of frontier) {
74
+ if (visited.size >= MAX_ENTITIES) break;
75
+
76
+ // Build edge query with optional type filter
77
+ let edgeSql: string;
78
+ let edgeParams: unknown[];
79
+
80
+ if (edgeTypes && edgeTypes.length > 0) {
81
+ const placeholders = edgeTypes.map(() => "?").join(", ");
82
+ edgeSql = `SELECT * FROM graph_edges
83
+ WHERE (from_id = ? OR to_id = ?)
84
+ AND t_valid_until IS NULL
85
+ AND type IN (${placeholders})`;
86
+ edgeParams = [entityId, entityId, ...edgeTypes];
87
+ } else {
88
+ edgeSql = `SELECT * FROM graph_edges
89
+ WHERE (from_id = ? OR to_id = ?)
90
+ AND t_valid_until IS NULL`;
91
+ edgeParams = [entityId, entityId];
92
+ }
93
+
94
+ const edgeResult = await db.execute(edgeSql, edgeParams);
95
+ const edges = edgeResult.rows as unknown as EdgeRow[];
96
+
97
+ for (const edge of edges) {
98
+ totalEdgeCount++;
99
+
100
+ // Track the edge (up to cap)
101
+ if (allEdges.length < MAX_EDGES) {
102
+ // Avoid duplicate edges
103
+ if (!allEdges.some((e) => e.id === edge.id)) {
104
+ allEdges.push(edge);
105
+ }
106
+ }
107
+
108
+ // Determine the neighbor on the other side
109
+ const neighborId = edge.from_id === entityId ? edge.to_id : edge.from_id;
110
+
111
+ if (!visited.has(neighborId) && visited.size < MAX_ENTITIES) {
112
+ visited.add(neighborId);
113
+ nextFrontier.push(neighborId);
114
+ }
115
+ }
116
+ }
117
+
118
+ frontier = nextFrontier;
119
+ }
120
+
121
+ // --- Fetch neighbor entities (active only) ---
122
+ const neighborIds = [...visited].filter((id) => id !== input.entity_id);
123
+ const neighbors: Entity[] = [];
124
+
125
+ for (const nid of neighborIds) {
126
+ const nResult = await db.execute(
127
+ "SELECT * FROM graph_nodes WHERE id = ? AND t_valid_until IS NULL AND archived_at IS NULL",
128
+ [nid],
129
+ );
130
+ if (nResult.rows.length > 0) {
131
+ neighbors.push(nResult.rows[0] as unknown as Entity);
132
+ }
133
+ }
134
+
135
+ // Deduplicate allEdges by id (edges may have been encountered from both sides)
136
+ const seenEdgeIds = new Set<string>();
137
+ const dedupedEdges: EdgeRow[] = [];
138
+ for (const edge of allEdges) {
139
+ if (!seenEdgeIds.has(edge.id)) {
140
+ seenEdgeIds.add(edge.id);
141
+ dedupedEdges.push(edge);
142
+ }
143
+ }
144
+
145
+ const allEntities = [rootEntity, ...neighbors];
146
+ const annotated = await annotateFreshness(
147
+ allEntities as unknown as Record<string, unknown>[],
148
+ db,
149
+ );
150
+ const [annotatedRoot, ...annotatedNeighbors] = annotated;
151
+
152
+ return {
153
+ entity: annotatedRoot as unknown as Entity,
154
+ neighbors: annotatedNeighbors as unknown as Entity[],
155
+ edges: dedupedEdges.slice(0, MAX_EDGES),
156
+ edge_count: totalEdgeCount,
157
+ };
158
+ }