@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,226 @@
1
+ // Module: api-contracts — API contract auto-detection scanner
2
+ import { randomUUID } from "node:crypto";
3
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
4
+ import { join, relative } from "node:path";
5
+ import type { SiaDb } from "@/graph/db-interface";
6
+
7
+ export interface DetectedContract {
8
+ type: string;
9
+ specPath: string;
10
+ }
11
+
12
+ /**
13
+ * Scan a repository directory for API contracts.
14
+ * Returns detected contracts — does NOT write to the database.
15
+ */
16
+ export async function detectApiContracts(repoPath: string): Promise<DetectedContract[]> {
17
+ const contracts: DetectedContract[] = [];
18
+
19
+ // OpenAPI / Swagger
20
+ for (const name of ["openapi.yaml", "openapi.json", "swagger.yaml", "swagger.json"]) {
21
+ if (existsSync(join(repoPath, name))) {
22
+ contracts.push({ type: "openapi", specPath: name });
23
+ }
24
+ }
25
+
26
+ // GraphQL — root level
27
+ const rootFiles = safeReaddir(repoPath);
28
+ for (const entry of rootFiles) {
29
+ if (entry.isFile() && entry.name.endsWith(".graphql")) {
30
+ contracts.push({ type: "graphql", specPath: entry.name });
31
+ }
32
+ }
33
+ // GraphQL — recursive (depth 3)
34
+ findFilesRecursive(repoPath, ".graphql", contracts, repoPath, 3);
35
+
36
+ // TypeScript project references
37
+ const tsconfigPath = join(repoPath, "tsconfig.json");
38
+ if (existsSync(tsconfigPath)) {
39
+ try {
40
+ const tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
41
+ if (Array.isArray(tsconfig.references)) {
42
+ for (const ref of tsconfig.references) {
43
+ if (typeof ref.path === "string") {
44
+ contracts.push({ type: "ts-reference", specPath: ref.path });
45
+ }
46
+ }
47
+ }
48
+ } catch {
49
+ /* ignore */
50
+ }
51
+ }
52
+
53
+ // C# .csproj ProjectReference
54
+ for (const entry of rootFiles) {
55
+ if (entry.isFile() && entry.name.endsWith(".csproj")) {
56
+ try {
57
+ const content = readFileSync(join(repoPath, entry.name), "utf-8");
58
+ const refPattern = /ProjectReference\s+Include="([^"]+)"/g;
59
+ let match: RegExpExecArray | null = refPattern.exec(content);
60
+ while (match !== null) {
61
+ contracts.push({ type: "csproj-reference", specPath: match[1] });
62
+ match = refPattern.exec(content);
63
+ }
64
+ } catch {
65
+ /* ignore */
66
+ }
67
+ }
68
+ }
69
+
70
+ // Cargo.toml workspace members
71
+ const cargoPath = join(repoPath, "Cargo.toml");
72
+ if (existsSync(cargoPath)) {
73
+ try {
74
+ const content = readFileSync(cargoPath, "utf-8");
75
+ const membersMatch = content.match(/members\s*=\s*\[([^\]]+)\]/);
76
+ if (membersMatch) {
77
+ const members = membersMatch[1]
78
+ .split(",")
79
+ .map((m) => m.trim().replace(/^["']|["']$/g, ""))
80
+ .filter(Boolean);
81
+ for (const member of members) {
82
+ contracts.push({ type: "cargo-dependency", specPath: member });
83
+ }
84
+ }
85
+ } catch {
86
+ /* ignore */
87
+ }
88
+ }
89
+
90
+ // go.mod replace directives
91
+ const goModPath = join(repoPath, "go.mod");
92
+ if (existsSync(goModPath)) {
93
+ try {
94
+ const content = readFileSync(goModPath, "utf-8");
95
+ const replacePattern = /replace\s+\S+\s+=>\s+(\S+)/g;
96
+ let match: RegExpExecArray | null = replacePattern.exec(content);
97
+ while (match !== null) {
98
+ contracts.push({ type: "go-mod-replace", specPath: match[1] });
99
+ match = replacePattern.exec(content);
100
+ }
101
+ } catch {
102
+ /* ignore */
103
+ }
104
+ }
105
+
106
+ // pyproject.toml path dependencies
107
+ const pyprojectPath = join(repoPath, "pyproject.toml");
108
+ if (existsSync(pyprojectPath)) {
109
+ try {
110
+ const content = readFileSync(pyprojectPath, "utf-8");
111
+ const pathPattern = /path\s*=\s*"([^"]+)"/g;
112
+ let match: RegExpExecArray | null = pathPattern.exec(content);
113
+ while (match !== null) {
114
+ contracts.push({ type: "python-path-dep", specPath: match[1] });
115
+ match = pathPattern.exec(content);
116
+ }
117
+ } catch {
118
+ /* ignore */
119
+ }
120
+ }
121
+
122
+ // Gradle settings.gradle / settings.gradle.kts
123
+ for (const name of ["settings.gradle", "settings.gradle.kts"]) {
124
+ const gradlePath = join(repoPath, name);
125
+ if (existsSync(gradlePath)) {
126
+ try {
127
+ const content = readFileSync(gradlePath, "utf-8");
128
+ const includePattern = /include\s*\(?([^\n)]+)\)?/g;
129
+ let lineMatch: RegExpExecArray | null = includePattern.exec(content);
130
+ while (lineMatch !== null) {
131
+ const parts = lineMatch[1].split(",");
132
+ for (const part of parts) {
133
+ const cleaned = part
134
+ .trim()
135
+ .replace(/^\(/, "")
136
+ .replace(/\)$/, "")
137
+ .replace(/^['"]/, "")
138
+ .replace(/['"]$/, "");
139
+ if (cleaned.startsWith(":")) {
140
+ contracts.push({
141
+ type: "gradle-project",
142
+ specPath: cleaned.slice(1).replace(/:/g, "/"),
143
+ });
144
+ }
145
+ }
146
+ lineMatch = includePattern.exec(content);
147
+ }
148
+ } catch {
149
+ /* ignore */
150
+ }
151
+ }
152
+ }
153
+
154
+ return contracts;
155
+ }
156
+
157
+ function safeReaddir(dirPath: string) {
158
+ try {
159
+ return readdirSync(dirPath, { withFileTypes: true });
160
+ } catch {
161
+ return [];
162
+ }
163
+ }
164
+
165
+ /** Recursively find files by extension, avoiding duplicates with root-level scan. */
166
+ function findFilesRecursive(
167
+ dir: string,
168
+ ext: string,
169
+ contracts: DetectedContract[],
170
+ rootDir: string,
171
+ maxDepth: number,
172
+ ): void {
173
+ if (maxDepth <= 0) return;
174
+ const entries = safeReaddir(dir);
175
+ for (const entry of entries) {
176
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
177
+ if (entry.isDirectory()) {
178
+ const childDir = join(dir, entry.name);
179
+ const childEntries = safeReaddir(childDir);
180
+ for (const child of childEntries) {
181
+ if (child.isFile() && child.name.endsWith(ext)) {
182
+ const relPath = relative(rootDir, join(childDir, child.name));
183
+ // Don't duplicate root-level files
184
+ if (!contracts.some((c) => c.specPath === relPath)) {
185
+ contracts.push({ type: "graphql", specPath: relPath });
186
+ }
187
+ }
188
+ }
189
+ findFilesRecursive(childDir, ext, contracts, rootDir, maxDepth - 1);
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Write detected contracts to api_contracts in meta.db with trust_tier=2.
196
+ * Idempotent: upserts by (provider_repo_id, contract_type, spec_path).
197
+ */
198
+ export async function writeDetectedContracts(
199
+ db: SiaDb,
200
+ providerRepoId: string,
201
+ contracts: DetectedContract[],
202
+ ): Promise<void> {
203
+ const now = Date.now();
204
+
205
+ for (const contract of contracts) {
206
+ const existing = await db.execute(
207
+ `SELECT id FROM api_contracts
208
+ WHERE provider_repo_id = ? AND contract_type = ? AND spec_path = ?`,
209
+ [providerRepoId, contract.type, contract.specPath],
210
+ );
211
+
212
+ if (existing.rows.length > 0) {
213
+ await db.execute("UPDATE api_contracts SET detected_at = ? WHERE id = ?", [
214
+ now,
215
+ existing.rows[0]?.id as string,
216
+ ]);
217
+ continue;
218
+ }
219
+
220
+ await db.execute(
221
+ `INSERT INTO api_contracts (id, provider_repo_id, consumer_repo_id, contract_type, spec_path, trust_tier, detected_at)
222
+ VALUES (?, ?, ?, ?, ?, 2, ?)`,
223
+ [randomUUID(), providerRepoId, providerRepoId, contract.type, contract.specPath, now],
224
+ );
225
+ }
226
+ }
@@ -0,0 +1,61 @@
1
+ // Module: cross-repo — ATTACH/DETACH helpers and peer repo discovery
2
+
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import type { SiaDb } from "@/graph/db-interface";
6
+ import { getWorkspaceRepos } from "@/graph/meta-db";
7
+ import { SIA_HOME } from "@/shared/config";
8
+
9
+ /** Info about a peer repo that can be ATTACHed for workspace search. */
10
+ export interface PeerRepo {
11
+ repoId: string;
12
+ graphDbPath: string;
13
+ name: string | null;
14
+ }
15
+
16
+ /**
17
+ * Attach a peer repository database under the given alias.
18
+ *
19
+ * Uses a plain ATTACH DATABASE statement. Does NOT set WAL pragma on the
20
+ * attached connection — the caller opens it read-only.
21
+ */
22
+ export async function attachPeerRepo(db: SiaDb, peerDbPath: string, alias: string): Promise<void> {
23
+ await db.execute(`ATTACH DATABASE ? AS ${alias}`, [peerDbPath]);
24
+ }
25
+
26
+ /**
27
+ * Detach a previously attached peer database.
28
+ */
29
+ export async function detachPeerRepo(db: SiaDb, alias: string): Promise<void> {
30
+ await db.execute(`DETACH DATABASE ${alias}`, []);
31
+ }
32
+
33
+ /**
34
+ * Get peer repos for workspace search (all workspace repos except primaryRepoId).
35
+ * Only returns peers whose graph.db file exists on disk.
36
+ */
37
+ export async function getPeerRepos(
38
+ metaDb: SiaDb,
39
+ workspaceId: string,
40
+ primaryRepoId: string,
41
+ siaHome: string = SIA_HOME,
42
+ ): Promise<PeerRepo[]> {
43
+ const allRepos = await getWorkspaceRepos(metaDb, workspaceId);
44
+ const peers: PeerRepo[] = [];
45
+
46
+ for (const repo of allRepos) {
47
+ const repoId = repo.id as string;
48
+ if (repoId === primaryRepoId) continue;
49
+
50
+ const graphDbPath = join(siaHome, "repos", repoId, "graph.db");
51
+ if (existsSync(graphDbPath)) {
52
+ peers.push({
53
+ repoId,
54
+ graphDbPath,
55
+ name: (repo.name as string | null) ?? (repo.path as string | null) ?? null,
56
+ });
57
+ }
58
+ }
59
+
60
+ return peers;
61
+ }
@@ -0,0 +1,190 @@
1
+ // Module: detector — Monorepo auto-detection and package registration
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
4
+ import { join, relative, resolve } from "node:path";
5
+ import type { SiaDb } from "@/graph/db-interface";
6
+
7
+ /**
8
+ * Detect monorepo packages from the repo root.
9
+ * Detection precedence:
10
+ * 1. pnpm-workspace.yaml
11
+ * 2. package.json "workspaces"
12
+ * 3. nx.json + project.json files
13
+ * 4. settings.gradle / settings.gradle.kts
14
+ * Turborepo (turbo.json) is informational only.
15
+ */
16
+ export async function detectMonorepoPackages(repoRoot: string): Promise<string[]> {
17
+ // 1. pnpm-workspace.yaml
18
+ const pnpmPath = join(repoRoot, "pnpm-workspace.yaml");
19
+ if (existsSync(pnpmPath)) {
20
+ const content = readFileSync(pnpmPath, "utf-8");
21
+ const patterns = parsePnpmWorkspace(content);
22
+ if (patterns.length > 0) return expandGlobs(repoRoot, patterns);
23
+ }
24
+
25
+ // 2. package.json "workspaces"
26
+ const pkgJsonPath = join(repoRoot, "package.json");
27
+ if (existsSync(pkgJsonPath)) {
28
+ try {
29
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
30
+ const workspaces = pkgJson?.workspaces;
31
+ if (workspaces) {
32
+ const patterns = Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? []);
33
+ if (patterns.length > 0) return expandGlobs(repoRoot, patterns as string[]);
34
+ }
35
+ } catch {
36
+ /* ignore malformed package.json */
37
+ }
38
+ }
39
+
40
+ // 3. Nx (nx.json + project.json files in subdirs)
41
+ if (existsSync(join(repoRoot, "nx.json"))) {
42
+ return findProjectJsonRoots(repoRoot);
43
+ }
44
+
45
+ // 4. Gradle (settings.gradle or settings.gradle.kts)
46
+ for (const name of ["settings.gradle", "settings.gradle.kts"]) {
47
+ const gradlePath = join(repoRoot, name);
48
+ if (existsSync(gradlePath)) {
49
+ const content = readFileSync(gradlePath, "utf-8");
50
+ const includes = parseGradleIncludes(content);
51
+ if (includes.length > 0) return includes;
52
+ }
53
+ }
54
+
55
+ // Turborepo: informational only
56
+ if (existsSync(join(repoRoot, "turbo.json"))) {
57
+ console.info(
58
+ "Turborepo project detected; package paths sourced from underlying package manager",
59
+ );
60
+ }
61
+
62
+ return [];
63
+ }
64
+
65
+ function parsePnpmWorkspace(content: string): string[] {
66
+ const patterns: string[] = [];
67
+ let inPackages = false;
68
+
69
+ for (const line of content.split("\n")) {
70
+ const trimmed = line.trim();
71
+ if (/^packages:\s*$/.test(trimmed) || trimmed === "packages:") {
72
+ inPackages = true;
73
+ continue;
74
+ }
75
+ if (inPackages) {
76
+ if (trimmed.startsWith("- ")) {
77
+ const pattern = trimmed.slice(2).trim().replace(/^['"]/, "").replace(/['"]$/, "");
78
+ if (pattern) patterns.push(pattern);
79
+ } else if (trimmed && !trimmed.startsWith("#")) {
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ return patterns;
85
+ }
86
+
87
+ function expandGlobs(repoRoot: string, patterns: string[]): string[] {
88
+ const results: string[] = [];
89
+ for (const pattern of patterns) {
90
+ if (pattern.endsWith("/*")) {
91
+ const prefix = pattern.slice(0, -2);
92
+ const parentDir = join(repoRoot, prefix);
93
+ if (existsSync(parentDir)) {
94
+ const entries = readdirSync(parentDir, { withFileTypes: true });
95
+ for (const entry of entries) {
96
+ if (entry.isDirectory()) {
97
+ results.push(`${prefix}/${entry.name}`);
98
+ }
99
+ }
100
+ }
101
+ } else {
102
+ if (existsSync(join(repoRoot, pattern))) {
103
+ results.push(pattern);
104
+ }
105
+ }
106
+ }
107
+ return results.sort();
108
+ }
109
+
110
+ function findProjectJsonRoots(repoRoot: string): string[] {
111
+ const results: string[] = [];
112
+ walkForFile(repoRoot, "project.json", results, repoRoot, 3);
113
+ return results.sort();
114
+ }
115
+
116
+ function walkForFile(
117
+ dir: string,
118
+ filename: string,
119
+ results: string[],
120
+ rootDir: string,
121
+ maxDepth: number,
122
+ ): void {
123
+ if (maxDepth <= 0) return;
124
+ let entries: import("node:fs").Dirent[];
125
+ try {
126
+ entries = readdirSync(dir, { withFileTypes: true }) as import("node:fs").Dirent[];
127
+ } catch {
128
+ return;
129
+ }
130
+ for (const entry of entries) {
131
+ if ((entry.name as string) === "node_modules" || (entry.name as string) === ".git") continue;
132
+ if (entry.isDirectory()) {
133
+ const childPath = join(dir, entry.name as string);
134
+ if (existsSync(join(childPath, filename))) {
135
+ results.push(relative(rootDir, childPath));
136
+ }
137
+ walkForFile(childPath, filename, results, rootDir, maxDepth - 1);
138
+ }
139
+ }
140
+ }
141
+
142
+ function parseGradleIncludes(content: string): string[] {
143
+ const results: string[] = [];
144
+ const includePattern = /include\s*\(?([^\n)]+)\)?/g;
145
+ let lineMatch: RegExpExecArray | null = includePattern.exec(content);
146
+ while (lineMatch !== null) {
147
+ const parts = lineMatch[1].split(",");
148
+ for (const part of parts) {
149
+ const cleaned = part
150
+ .trim()
151
+ .replace(/^\(/, "")
152
+ .replace(/\)$/, "")
153
+ .replace(/^['"]/, "")
154
+ .replace(/['"]$/, "");
155
+ if (cleaned.startsWith(":")) {
156
+ results.push(cleaned.slice(1).replace(/:/g, "/"));
157
+ }
158
+ }
159
+ lineMatch = includePattern.exec(content);
160
+ }
161
+ return [...new Set(results)].sort();
162
+ }
163
+
164
+ /**
165
+ * Register detected monorepo packages in meta.db.
166
+ */
167
+ export async function registerMonorepoPackages(
168
+ db: SiaDb,
169
+ rootRepoId: string,
170
+ rootPath: string,
171
+ packagePaths: string[],
172
+ ): Promise<void> {
173
+ await db.execute("UPDATE repos SET detected_type = 'monorepo_root' WHERE id = ?", [rootRepoId]);
174
+
175
+ for (const pkgPath of packagePaths) {
176
+ const fullPath = resolve(rootPath, pkgPath);
177
+ const id = createHash("sha256").update(fullPath).digest("hex");
178
+ const now = Date.now();
179
+
180
+ await db.execute(
181
+ `INSERT INTO repos (id, path, name, detected_type, monorepo_root_id, created_at, last_accessed)
182
+ VALUES (?, ?, ?, 'monorepo_package', ?, ?, ?)
183
+ ON CONFLICT(id) DO UPDATE SET
184
+ detected_type = 'monorepo_package',
185
+ monorepo_root_id = ?,
186
+ last_accessed = ?`,
187
+ [id, fullPath, pkgPath, rootRepoId, now, now, rootRepoId, now],
188
+ );
189
+ }
190
+ }
@@ -0,0 +1,141 @@
1
+ // Module: manifest — .sia-manifest.yaml parser and contract writer
2
+ import { randomUUID } from "node:crypto";
3
+ import { parse as parseYaml } from "yaml";
4
+ import type { SiaDb } from "@/graph/db-interface";
5
+
6
+ export interface ManifestContract {
7
+ type: string;
8
+ path?: string;
9
+ package?: string;
10
+ }
11
+
12
+ export interface SiaManifest {
13
+ provides: ManifestContract[];
14
+ consumes: ManifestContract[];
15
+ depends_on: ManifestContract[];
16
+ }
17
+
18
+ /**
19
+ * Parse a .sia-manifest.yaml string into a SiaManifest.
20
+ * Returns null on malformed YAML (logs warning, never throws).
21
+ */
22
+ export function parseManifest(content: string): SiaManifest | null {
23
+ let doc: Record<string, unknown>;
24
+ try {
25
+ doc = parseYaml(content) as Record<string, unknown>;
26
+ } catch (err) {
27
+ console.warn("sia-manifest.yaml: malformed YAML, skipping", err);
28
+ return null;
29
+ }
30
+
31
+ if (!doc || typeof doc !== "object") {
32
+ return { provides: [], consumes: [], depends_on: [] };
33
+ }
34
+
35
+ return {
36
+ provides: parseContractList(doc.provides),
37
+ consumes: parseContractList(doc.consumes),
38
+ depends_on: parseContractList(doc.depends_on),
39
+ };
40
+ }
41
+
42
+ function parseContractList(raw: unknown): ManifestContract[] {
43
+ if (!Array.isArray(raw)) return [];
44
+ return raw
45
+ .filter(
46
+ (item): item is Record<string, unknown> =>
47
+ item !== null && typeof item === "object" && typeof item.type === "string",
48
+ )
49
+ .map((item) => ({
50
+ type: item.type as string,
51
+ path: typeof item.path === "string" ? item.path : undefined,
52
+ package: typeof item.package === "string" ? item.package : undefined,
53
+ }));
54
+ }
55
+
56
+ /**
57
+ * Write manifest contracts to api_contracts in meta.db.
58
+ * All writes idempotent (upsert by provider+consumer+type).
59
+ */
60
+ export async function writeManifestContracts(
61
+ db: SiaDb,
62
+ providerRepoId: string,
63
+ consumerRepoId: string,
64
+ manifest: SiaManifest,
65
+ ): Promise<void> {
66
+ const now = Date.now();
67
+
68
+ for (const contract of manifest.provides) {
69
+ await upsertContract(db, {
70
+ providerRepoId,
71
+ consumerRepoId,
72
+ type: contract.type,
73
+ specPath: contract.path ?? null,
74
+ trustTier: 1,
75
+ now,
76
+ });
77
+ }
78
+
79
+ for (const contract of manifest.depends_on) {
80
+ await upsertContract(db, {
81
+ providerRepoId: consumerRepoId,
82
+ consumerRepoId: providerRepoId,
83
+ type: contract.type,
84
+ specPath: contract.path ?? null,
85
+ trustTier: 1,
86
+ now,
87
+ });
88
+ }
89
+
90
+ for (const contract of manifest.consumes) {
91
+ await upsertContract(db, {
92
+ providerRepoId: consumerRepoId,
93
+ consumerRepoId: providerRepoId,
94
+ type: contract.type,
95
+ specPath: contract.path ?? contract.package ?? null,
96
+ trustTier: 1,
97
+ now,
98
+ });
99
+ }
100
+ }
101
+
102
+ async function upsertContract(
103
+ db: SiaDb,
104
+ opts: {
105
+ providerRepoId: string;
106
+ consumerRepoId: string;
107
+ type: string;
108
+ specPath: string | null;
109
+ trustTier: number;
110
+ now: number;
111
+ },
112
+ ): Promise<void> {
113
+ const existing = await db.execute(
114
+ `SELECT id FROM api_contracts
115
+ WHERE provider_repo_id = ? AND consumer_repo_id = ? AND contract_type = ?`,
116
+ [opts.providerRepoId, opts.consumerRepoId, opts.type],
117
+ );
118
+
119
+ if (existing.rows.length > 0) {
120
+ await db.execute("UPDATE api_contracts SET detected_at = ?, spec_path = ? WHERE id = ?", [
121
+ opts.now,
122
+ opts.specPath,
123
+ existing.rows[0]?.id as string,
124
+ ]);
125
+ return;
126
+ }
127
+
128
+ await db.execute(
129
+ `INSERT INTO api_contracts (id, provider_repo_id, consumer_repo_id, contract_type, spec_path, trust_tier, detected_at)
130
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
131
+ [
132
+ randomUUID(),
133
+ opts.providerRepoId,
134
+ opts.consumerRepoId,
135
+ opts.type,
136
+ opts.specPath,
137
+ opts.trustTier,
138
+ opts.now,
139
+ ],
140
+ );
141
+ }