@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,278 @@
1
+ // Module: qa-report — Generate QA-focused testing intelligence report
2
+ //
3
+ // Queries the graph for bugs, solutions, decisions, and recent changes,
4
+ // then produces a markdown report grouped by risk level with test
5
+ // recommendations derived from bug history.
6
+
7
+ import { writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import type { SiaDb } from "@/graph/db-interface";
10
+
11
+ export interface QaReportOptions {
12
+ since?: number;
13
+ outputPath?: string;
14
+ }
15
+
16
+ interface EntityRow {
17
+ id: string;
18
+ type: string;
19
+ name: string;
20
+ content: string;
21
+ summary: string | null;
22
+ trust_tier: number;
23
+ importance: number;
24
+ created_at: number;
25
+ file_paths: string | null;
26
+ edge_count: number;
27
+ }
28
+
29
+ function formatDate(ms: number): string {
30
+ return new Date(ms).toISOString().split("T")[0];
31
+ }
32
+
33
+ function riskLevel(
34
+ bugCount: number,
35
+ recentCount: number,
36
+ edgeCount: number,
37
+ ): { label: string; score: number } {
38
+ // Weighted score: bug density 40%, change velocity 35%, dependency fan-out 25%
39
+ const bugScore = Math.min(bugCount * 20, 100);
40
+ const changeScore = Math.min(recentCount * 15, 100);
41
+ const edgeScore = Math.min(edgeCount * 5, 100);
42
+ const score = Math.round(bugScore * 0.4 + changeScore * 0.35 + edgeScore * 0.25);
43
+
44
+ if (score > 70) return { label: "HIGH", score };
45
+ if (score >= 40) return { label: "MEDIUM", score };
46
+ return { label: "LOW", score };
47
+ }
48
+
49
+ export async function generateQaReport(db: SiaDb, opts: QaReportOptions = {}): Promise<string> {
50
+ const since = opts.since ?? Date.now() - 86400000 * 14; // default: 14 days
51
+
52
+ // Query all active entities
53
+ const allResult = await db.execute(
54
+ "SELECT COUNT(*) as cnt FROM graph_nodes WHERE t_valid_until IS NULL AND archived_at IS NULL",
55
+ );
56
+ const totalEntities = (allResult.rows[0] as any).cnt;
57
+
58
+ if (totalEntities === 0) {
59
+ return `# QA Report\n\n*No entities in the knowledge graph yet. Run \`/sia-learn\` to build the graph.*\n`;
60
+ }
61
+
62
+ // Query bugs
63
+ const bugsResult = await db.execute(
64
+ `SELECT id, type, name, content, summary, trust_tier, importance, created_at, file_paths, edge_count
65
+ FROM graph_nodes
66
+ WHERE type = 'Bug' AND t_valid_until IS NULL AND archived_at IS NULL
67
+ ORDER BY created_at DESC
68
+ LIMIT 50`,
69
+ );
70
+ const bugs = bugsResult.rows as unknown as EntityRow[];
71
+
72
+ // Query solutions
73
+ const solutionsResult = await db.execute(
74
+ `SELECT id, type, name, content, summary, trust_tier, importance, created_at, file_paths, edge_count
75
+ FROM graph_nodes
76
+ WHERE type = 'Solution' AND t_valid_until IS NULL AND archived_at IS NULL
77
+ ORDER BY created_at DESC
78
+ LIMIT 50`,
79
+ );
80
+ const solutions = solutionsResult.rows as unknown as EntityRow[];
81
+
82
+ // Query recent decisions
83
+ const decisionsResult = await db.execute(
84
+ `SELECT id, type, name, content, summary, trust_tier, importance, created_at, file_paths, edge_count
85
+ FROM graph_nodes
86
+ WHERE type = 'Decision' AND t_valid_until IS NULL AND archived_at IS NULL AND created_at >= ?
87
+ ORDER BY created_at DESC
88
+ LIMIT 50`,
89
+ [since],
90
+ );
91
+ const recentDecisions = decisionsResult.rows as unknown as EntityRow[];
92
+
93
+ // Recent entities (all types)
94
+ const recentResult = await db.execute(
95
+ `SELECT id, type, name, content, summary, trust_tier, importance, created_at, file_paths, edge_count
96
+ FROM graph_nodes
97
+ WHERE t_valid_until IS NULL AND archived_at IS NULL AND created_at >= ?
98
+ ORDER BY created_at DESC
99
+ LIMIT 100`,
100
+ [since],
101
+ );
102
+ const recentEntities = recentResult.rows as unknown as EntityRow[];
103
+
104
+ const recentBugs = bugs.filter((b) => b.created_at >= since);
105
+ const totalEdgeCount = bugs.reduce((sum, b) => sum + (b.edge_count || 0), 0);
106
+
107
+ const risk = riskLevel(bugs.length, recentEntities.length, totalEdgeCount);
108
+
109
+ const sections: string[] = [];
110
+
111
+ // Header
112
+ sections.push("# QA Report");
113
+ sections.push(
114
+ `\n*Generated on ${formatDate(Date.now())} | Since: ${formatDate(since)} | ${totalEntities} total entities*\n`,
115
+ );
116
+ sections.push("---\n");
117
+
118
+ // Summary
119
+ sections.push("## Summary\n");
120
+ sections.push(`| Metric | Count |`);
121
+ sections.push(`|---|---|`);
122
+ sections.push(`| Total entities | ${totalEntities} |`);
123
+ sections.push(`| Recent changes (since ${formatDate(since)}) | ${recentEntities.length} |`);
124
+ sections.push(`| Total bugs | ${bugs.length} |`);
125
+ sections.push(`| Recent bugs | ${recentBugs.length} |`);
126
+ sections.push(`| Solutions | ${solutions.length} |`);
127
+ sections.push(`| Recent decisions | ${recentDecisions.length} |`);
128
+ sections.push(`| Overall risk | **${risk.label}** (score: ${risk.score}) |`);
129
+ sections.push("");
130
+
131
+ // High-Risk Areas
132
+ sections.push("## High-Risk Areas\n");
133
+ if (bugs.length === 0 && recentEntities.length === 0) {
134
+ sections.push("*No risk areas identified — no bugs or recent changes.*\n");
135
+ } else {
136
+ // Group bugs by file path to identify risky areas
137
+ const areaMap = new Map<string, { bugs: number; recent: number; edges: number }>();
138
+ for (const bug of bugs) {
139
+ const area = bug.file_paths && bug.file_paths !== "[]" ? bug.file_paths : "Unknown";
140
+ const entry = areaMap.get(area) ?? { bugs: 0, recent: 0, edges: 0 };
141
+ entry.bugs++;
142
+ entry.edges += bug.edge_count || 0;
143
+ if (bug.created_at >= since) entry.recent++;
144
+ areaMap.set(area, entry);
145
+ }
146
+
147
+ if (areaMap.size > 0) {
148
+ const sorted = [...areaMap.entries()].sort((a, b) => {
149
+ const riskA = riskLevel(a[1].bugs, a[1].recent, a[1].edges);
150
+ const riskB = riskLevel(b[1].bugs, b[1].recent, b[1].edges);
151
+ return riskB.score - riskA.score;
152
+ });
153
+
154
+ sections.push("| Area | Bugs | Recent Changes | Risk |");
155
+ sections.push("|---|---|---|---|");
156
+ for (const [area, data] of sorted) {
157
+ const r = riskLevel(data.bugs, data.recent, data.edges);
158
+ sections.push(`| ${area} | ${data.bugs} | ${data.recent} | **${r.label}** (${r.score}) |`);
159
+ }
160
+ sections.push("");
161
+ } else {
162
+ sections.push("*No bug-affected areas found.*\n");
163
+ }
164
+ }
165
+
166
+ // Bug Activity
167
+ sections.push("## Bug Activity\n");
168
+ if (bugs.length === 0) {
169
+ sections.push("*No bugs recorded in the knowledge graph.*\n");
170
+ } else {
171
+ sections.push(`| Bug | Date | Trust | Files |`);
172
+ sections.push(`|---|---|---|---|`);
173
+ for (const bug of bugs) {
174
+ const date = formatDate(bug.created_at);
175
+ const tier =
176
+ bug.trust_tier === 1 ? "verified" : bug.trust_tier === 2 ? "code-derived" : "inferred";
177
+ const files = bug.file_paths && bug.file_paths !== "[]" ? bug.file_paths : "—";
178
+ sections.push(`| ${bug.name} | ${date} | ${tier} | ${files} |`);
179
+ }
180
+ sections.push("");
181
+ }
182
+
183
+ // Solutions
184
+ if (solutions.length > 0) {
185
+ sections.push("## Solutions Applied\n");
186
+ sections.push(`| Solution | Date | Files |`);
187
+ sections.push(`|---|---|---|`);
188
+ for (const sol of solutions) {
189
+ const date = formatDate(sol.created_at);
190
+ const files = sol.file_paths && sol.file_paths !== "[]" ? sol.file_paths : "—";
191
+ sections.push(`| ${sol.name} | ${date} | ${files} |`);
192
+ }
193
+ sections.push("");
194
+ }
195
+
196
+ // Recent Decisions
197
+ if (recentDecisions.length > 0) {
198
+ sections.push("## Recent Decisions\n");
199
+ for (const dec of recentDecisions) {
200
+ sections.push(`### ${dec.name}`);
201
+ sections.push(`*${formatDate(dec.created_at)}*\n`);
202
+ if (dec.content) sections.push(`${dec.content.slice(0, 300)}\n`);
203
+ }
204
+ }
205
+
206
+ // Test Recommendations
207
+ sections.push("## Test Recommendations\n");
208
+ if (bugs.length === 0) {
209
+ sections.push("*No bugs in the graph — no specific test cases to recommend.*\n");
210
+ } else {
211
+ sections.push("Based on bug history, prioritize testing these scenarios:\n");
212
+ for (let i = 0; i < bugs.length; i++) {
213
+ const bug = bugs[i];
214
+ sections.push(`${i + 1}. **${bug.name}** — ${bug.content.slice(0, 150)}`);
215
+ }
216
+ sections.push("");
217
+ }
218
+
219
+ // Coverage Gaps
220
+ sections.push("## Coverage Gaps\n");
221
+ const codeEntitiesResult = await db.execute(
222
+ `SELECT COUNT(*) as cnt FROM graph_nodes
223
+ WHERE type = 'CodeEntity' AND t_valid_until IS NULL AND archived_at IS NULL`,
224
+ );
225
+ const codeCount = (codeEntitiesResult.rows[0] as any).cnt;
226
+ if (codeCount > 0 && bugs.length > 0) {
227
+ sections.push(`- ${codeCount} code entities tracked, ${bugs.length} bugs found`);
228
+ sections.push(`- Areas with bugs but no corresponding solutions may indicate coverage gaps`);
229
+ const unresolvedCount = bugs.length - solutions.length;
230
+ if (unresolvedCount > 0) {
231
+ sections.push(
232
+ `- **${unresolvedCount} bugs without matching solutions** — potential untested areas`,
233
+ );
234
+ }
235
+ } else if (codeCount === 0) {
236
+ sections.push("*No code entities indexed yet — run `/sia-learn` for coverage analysis.*");
237
+ } else {
238
+ sections.push("*No bugs recorded — coverage analysis not applicable.*");
239
+ }
240
+ sections.push("");
241
+
242
+ // Footer
243
+ sections.push("---\n");
244
+ sections.push("*Generated by SIA QA Report — run `/sia-qa-report` to regenerate.*");
245
+
246
+ return sections.join("\n");
247
+ }
248
+
249
+ export async function runQaReport(args: string[]): Promise<void> {
250
+ const { resolveRepoHash } = await import("@/capture/hook");
251
+ const { openGraphDb } = await import("@/graph/semantic-db");
252
+ const { resolveSiaHome } = await import("@/shared/config");
253
+
254
+ let outputPath = "QA-REPORT.md";
255
+ let since: number | undefined;
256
+
257
+ for (let i = 0; i < args.length; i++) {
258
+ if (args[i] === "--output" && args[i + 1]) outputPath = args[++i];
259
+ if (args[i] === "--since" && args[i + 1]) {
260
+ const parsed = Date.parse(args[++i]);
261
+ if (!Number.isNaN(parsed)) since = parsed;
262
+ }
263
+ }
264
+
265
+ const cwd = process.cwd();
266
+ const repoHash = resolveRepoHash(cwd);
267
+ const siaHome = resolveSiaHome();
268
+ const db = openGraphDb(repoHash, siaHome);
269
+
270
+ try {
271
+ const markdown = await generateQaReport(db, { since, outputPath });
272
+ const fullPath = join(cwd, outputPath);
273
+ writeFileSync(fullPath, markdown, "utf-8");
274
+ console.log(`QA report written to ${outputPath} (${markdown.length} chars)`);
275
+ } finally {
276
+ await db.close();
277
+ }
278
+ }
@@ -0,0 +1,104 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { type IndexResult, indexRepository } from "@/ast/indexer";
5
+ import { openMetaDb, registerRepo } from "@/graph/meta-db";
6
+ import { openGraphDb } from "@/graph/semantic-db";
7
+ import { getConfig } from "@/shared/config";
8
+ import { detectApiContracts, writeDetectedContracts } from "@/workspace/api-contracts";
9
+ import { detectMonorepoPackages, registerMonorepoPackages } from "@/workspace/detector";
10
+
11
+ export interface ReindexOptions {
12
+ cwd?: string;
13
+ siaHome?: string;
14
+ dryRun?: boolean;
15
+ }
16
+
17
+ export interface ReindexResult extends IndexResult {
18
+ repoHash: string;
19
+ dryRun: boolean;
20
+ packagesDetected: number;
21
+ contractsDetected: number;
22
+ }
23
+
24
+ function findRepoRoot(startDir: string): string | null {
25
+ let dir = resolve(startDir);
26
+ const root = resolve("/");
27
+ while (dir !== root) {
28
+ if (existsSync(join(dir, ".git"))) {
29
+ return dir;
30
+ }
31
+ const parent = resolve(dir, "..");
32
+ if (parent === dir) break;
33
+ dir = parent;
34
+ }
35
+ if (existsSync(join(dir, ".git"))) {
36
+ return dir;
37
+ }
38
+ return null;
39
+ }
40
+
41
+ export async function siaReindex(opts: ReindexOptions = {}): Promise<ReindexResult> {
42
+ const cwd = opts.cwd ?? process.cwd();
43
+ const repoRoot = findRepoRoot(cwd);
44
+ if (!repoRoot) {
45
+ throw new Error(`No .git directory found from ${cwd}`);
46
+ }
47
+
48
+ const resolvedRoot = resolve(repoRoot);
49
+ const repoHash = createHash("sha256").update(resolvedRoot).digest("hex");
50
+ const isDryRun = opts.dryRun ?? false;
51
+ const prefix = isDryRun ? "(dry-run) " : "";
52
+
53
+ const config = getConfig(opts.siaHome);
54
+ const db = openGraphDb(repoHash, opts.siaHome);
55
+
56
+ let packagesDetected = 0;
57
+ let contractsDetected = 0;
58
+
59
+ try {
60
+ // Re-detect monorepo structure and API contracts
61
+ if (!isDryRun) {
62
+ const metaDb = openMetaDb(opts.siaHome);
63
+ try {
64
+ const repoId = await registerRepo(metaDb, resolvedRoot);
65
+
66
+ const packages = await detectMonorepoPackages(resolvedRoot);
67
+ if (packages.length > 0) {
68
+ await registerMonorepoPackages(metaDb, repoId, resolvedRoot, packages);
69
+ packagesDetected = packages.length;
70
+ }
71
+
72
+ const contracts = await detectApiContracts(resolvedRoot);
73
+ if (contracts.length > 0) {
74
+ await writeDetectedContracts(metaDb, repoId, contracts);
75
+ contractsDetected = contracts.length;
76
+ }
77
+ } finally {
78
+ await metaDb.close();
79
+ }
80
+ }
81
+
82
+ const result = await indexRepository(resolvedRoot, db, config, {
83
+ dryRun: isDryRun,
84
+ repoHash,
85
+ onProgress: ({ filesProcessed, entitiesCreated, file }) => {
86
+ console.log(`${prefix}[${filesProcessed}] ${file ?? "..."} (${entitiesCreated} entities)`);
87
+ },
88
+ });
89
+
90
+ console.log(
91
+ `${prefix}Reindex complete: ${result.filesProcessed} files, ${result.entitiesCreated} entities, ${packagesDetected} packages, ${contractsDetected} contracts.`,
92
+ );
93
+
94
+ return {
95
+ ...result,
96
+ repoHash,
97
+ dryRun: isDryRun,
98
+ packagesDetected,
99
+ contractsDetected,
100
+ };
101
+ } finally {
102
+ await db.close();
103
+ }
104
+ }
@@ -0,0 +1,70 @@
1
+ // Module: rollback — restore graph from a snapshot
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import type { SiaDb } from "@/graph/db-interface";
5
+ import type { SnapshotData } from "@/graph/snapshots";
6
+ import { findNearestSnapshot, listSnapshots, restoreSnapshot } from "@/graph/snapshots";
7
+
8
+ export interface RollbackOpts {
9
+ /** Target date string (YYYY-MM-DD) or timestamp to roll back to. */
10
+ target?: string | number;
11
+ /** Override SIA_HOME. */
12
+ siaHome?: string;
13
+ }
14
+
15
+ export interface RollbackResult {
16
+ snapshotUsed: string;
17
+ restoredEntities: number;
18
+ restoredEdges: number;
19
+ }
20
+
21
+ /**
22
+ * Roll back the graph database to a previous snapshot.
23
+ *
24
+ * - If `opts.target` is a YYYY-MM-DD string, it is parsed to a UTC timestamp.
25
+ * - If `opts.target` is a number, it is used directly as a timestamp.
26
+ * - If no target is provided, the most recent snapshot is used.
27
+ */
28
+ export async function rollbackGraph(
29
+ db: SiaDb,
30
+ repoHash: string,
31
+ opts?: RollbackOpts,
32
+ ): Promise<RollbackResult> {
33
+ const siaHome = opts?.siaHome;
34
+ let snapshotPath: string | null = null;
35
+
36
+ if (opts?.target != null) {
37
+ const targetTs =
38
+ typeof opts.target === "string" ? new Date(opts.target).getTime() : opts.target;
39
+
40
+ snapshotPath = findNearestSnapshot(repoHash, targetTs, siaHome);
41
+ } else {
42
+ // No target — use the most recent snapshot (last item)
43
+ const all = listSnapshots(repoHash, siaHome);
44
+ snapshotPath = all.length > 0 ? all[all.length - 1] : null;
45
+ }
46
+
47
+ if (!snapshotPath) {
48
+ throw new Error("No snapshot found for the specified date");
49
+ }
50
+
51
+ // Restore the snapshot into the database
52
+ await restoreSnapshot(db, snapshotPath, repoHash, siaHome);
53
+
54
+ // Read the snapshot file to count entities and edges
55
+ const raw = readFileSync(snapshotPath, "utf-8");
56
+ const data = JSON.parse(raw) as SnapshotData;
57
+
58
+ return {
59
+ snapshotUsed: snapshotPath,
60
+ restoredEntities: data.entities.length,
61
+ restoredEdges: data.edges.length,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * List all available snapshot file paths for a repo, sorted oldest-first.
67
+ */
68
+ export function listAvailableSnapshots(repoHash: string, siaHome?: string): string[] {
69
+ return listSnapshots(repoHash, siaHome);
70
+ }
@@ -0,0 +1,103 @@
1
+ // Module: search — CLI search against the knowledge graph
2
+
3
+ import type { SiaDb } from "@/graph/db-interface";
4
+
5
+ export interface SearchOpts {
6
+ limit?: number;
7
+ taskType?: string;
8
+ packagePath?: string;
9
+ }
10
+
11
+ export interface SearchResultItem {
12
+ id: string;
13
+ name: string;
14
+ type: string;
15
+ content: string;
16
+ summary: string;
17
+ importance: number;
18
+ confidence: number;
19
+ trustTier: number;
20
+ score: number;
21
+ }
22
+
23
+ /**
24
+ * Sanitize an FTS5 query string: strip special characters and wrap tokens
25
+ * in double-quotes so SQLite treats them as literals.
26
+ */
27
+ function sanitizeFtsQuery(query: string): string {
28
+ // Remove FTS5 operators and special chars, keep alphanumerics, underscores, hyphens, spaces
29
+ const cleaned = query.replace(/[^a-zA-Z0-9_\- ]/g, " ").trim();
30
+ if (cleaned.length === 0) return '""';
31
+
32
+ // Wrap each token in double-quotes to avoid FTS5 syntax errors
33
+ const tokens = cleaned.split(/\s+/).filter(Boolean);
34
+ return tokens.map((t) => `"${t}"`).join(" ");
35
+ }
36
+
37
+ /**
38
+ * Map a raw database row to a SearchResultItem.
39
+ */
40
+ function rowToItem(row: Record<string, unknown>, score: number): SearchResultItem {
41
+ return {
42
+ id: String(row.id ?? ""),
43
+ name: String(row.name ?? ""),
44
+ type: String(row.type ?? ""),
45
+ content: String(row.content ?? ""),
46
+ summary: String(row.summary ?? ""),
47
+ importance: Number(row.importance ?? 0),
48
+ confidence: Number(row.confidence ?? 0),
49
+ trustTier: Number(row.trust_tier ?? 0),
50
+ score,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Search the knowledge graph for entities matching the given query.
56
+ *
57
+ * Tries FTS5 first for ranked full-text search; falls back to a LIKE-based
58
+ * query if the FTS5 virtual table is unavailable or the query fails.
59
+ */
60
+ export async function searchGraph(
61
+ db: SiaDb,
62
+ query: string,
63
+ opts?: SearchOpts,
64
+ ): Promise<SearchResultItem[]> {
65
+ const limit = opts?.limit ?? 20;
66
+
67
+ // --- Attempt FTS5 search ---
68
+ try {
69
+ const ftsQuery = sanitizeFtsQuery(query);
70
+ const result = await db.execute(
71
+ `SELECT graph_nodes.id, graph_nodes.name, graph_nodes.type, graph_nodes.content,
72
+ graph_nodes.summary, graph_nodes.importance, graph_nodes.confidence,
73
+ graph_nodes.trust_tier, graph_nodes_fts.rank
74
+ FROM graph_nodes_fts
75
+ JOIN graph_nodes ON graph_nodes.rowid = graph_nodes_fts.rowid
76
+ WHERE graph_nodes_fts MATCH ?
77
+ AND graph_nodes.t_valid_until IS NULL
78
+ AND graph_nodes.archived_at IS NULL
79
+ ORDER BY rank
80
+ LIMIT ?`,
81
+ [ftsQuery, limit],
82
+ );
83
+
84
+ return result.rows.map((row) => rowToItem(row, Number(row.rank ?? 0)));
85
+ } catch {
86
+ // FTS5 table missing or query error — fall through to LIKE search
87
+ }
88
+
89
+ // --- Fallback: LIKE search ---
90
+ const likePattern = `%${query}%`;
91
+ const result = await db.execute(
92
+ `SELECT id, name, type, content, summary, importance, confidence, trust_tier
93
+ FROM graph_nodes
94
+ WHERE (name LIKE ? OR content LIKE ?)
95
+ AND t_valid_until IS NULL
96
+ AND archived_at IS NULL
97
+ ORDER BY importance DESC
98
+ LIMIT ?`,
99
+ [likePattern, likePattern, limit],
100
+ );
101
+
102
+ return result.rows.map((row) => rowToItem(row, Number(row.importance ?? 0)));
103
+ }
@@ -0,0 +1,91 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { SIA_HOME } from "@/shared/config";
6
+
7
+ const SERVER_DIR = join(SIA_HOME, "server");
8
+ const CONFIG_PATH = join(SERVER_DIR, "server.json");
9
+ const ENV_PATH = join(SERVER_DIR, ".env");
10
+ const COMPOSE_PATH = join(SERVER_DIR, "docker-compose.yml");
11
+
12
+ interface ServerConfig {
13
+ url: string;
14
+ running: boolean;
15
+ startedAt: number | null;
16
+ }
17
+
18
+ function readConfig(): ServerConfig {
19
+ if (!existsSync(CONFIG_PATH)) {
20
+ return { url: "", running: false, startedAt: null };
21
+ }
22
+ try {
23
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8")) as ServerConfig;
24
+ } catch {
25
+ return { url: "", running: false, startedAt: null };
26
+ }
27
+ }
28
+
29
+ function writeServerConfig(config: ServerConfig): void {
30
+ mkdirSync(SERVER_DIR, { recursive: true });
31
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
32
+ }
33
+
34
+ const COMPOSE_TEMPLATE = `version: "3.8"
35
+ services:
36
+ sqld:
37
+ image: ghcr.io/tursodatabase/libsql-server:latest
38
+ ports:
39
+ - "8080:8080"
40
+ env_file:
41
+ - .env
42
+ volumes:
43
+ - sqld-data:/var/lib/sqld
44
+ volumes:
45
+ sqld-data:
46
+ `;
47
+
48
+ export function serverStart(opts?: { port?: number }): ServerConfig {
49
+ const port = opts?.port ?? 8080;
50
+ mkdirSync(SERVER_DIR, { recursive: true });
51
+
52
+ // Generate JWT secret
53
+ const jwtSecret = randomBytes(32).toString("hex");
54
+ writeFileSync(ENV_PATH, `SQLD_AUTH_JWT_KEY=${jwtSecret}\n`, { encoding: "utf-8", mode: 0o600 });
55
+
56
+ // Write docker-compose.yml
57
+ const compose = COMPOSE_TEMPLATE.replace("8080:8080", `${port}:8080`);
58
+ writeFileSync(COMPOSE_PATH, compose, "utf-8");
59
+
60
+ // Start container
61
+ try {
62
+ execFileSync("docker", ["compose", "-f", COMPOSE_PATH, "up", "-d"], { stdio: "pipe" });
63
+ } catch (err) {
64
+ throw new Error(`Failed to start server: ${(err as Error).message}`);
65
+ }
66
+
67
+ const config: ServerConfig = {
68
+ url: `http://localhost:${port}`,
69
+ running: true,
70
+ startedAt: Date.now(),
71
+ };
72
+ writeServerConfig(config);
73
+ return config;
74
+ }
75
+
76
+ export function serverStop(): ServerConfig {
77
+ try {
78
+ if (existsSync(COMPOSE_PATH)) {
79
+ execFileSync("docker", ["compose", "-f", COMPOSE_PATH, "down"], { stdio: "pipe" });
80
+ }
81
+ } catch {
82
+ // Container may not be running
83
+ }
84
+ const config: ServerConfig = { url: "", running: false, startedAt: null };
85
+ writeServerConfig(config);
86
+ return config;
87
+ }
88
+
89
+ export function serverStatus(): ServerConfig {
90
+ return readConfig();
91
+ }
@@ -0,0 +1,33 @@
1
+ // Module: share — adjust entity visibility for sharing
2
+
3
+ import type { SiaDb } from "@/graph/db-interface";
4
+ import { updateEntity } from "@/graph/entities";
5
+ import { openMetaDb, resolveWorkspaceName } from "@/graph/meta-db";
6
+ import type { SyncConfig } from "@/shared/config";
7
+ import { pushChanges } from "@/sync/push";
8
+
9
+ export async function shareEntity(
10
+ db: SiaDb,
11
+ entityId: string,
12
+ opts: { team?: boolean; project?: string | null; siaHome?: string; syncConfig?: SyncConfig } = {},
13
+ ): Promise<void> {
14
+ let workspaceScope: string | null = null;
15
+ if (opts.project) {
16
+ const metaDb = openMetaDb(opts.siaHome);
17
+ try {
18
+ const wsId = await resolveWorkspaceName(metaDb, opts.project);
19
+ if (!wsId) throw new Error(`Workspace '${opts.project}' not found`);
20
+ workspaceScope = wsId;
21
+ } finally {
22
+ await metaDb.close();
23
+ }
24
+ }
25
+
26
+ const visibility = opts.team ? "team" : opts.project ? "project" : "private";
27
+ await updateEntity(db, entityId, { visibility, workspace_scope: workspaceScope });
28
+
29
+ // Trigger immediate push
30
+ if (opts.syncConfig?.enabled) {
31
+ await pushChanges(db, opts.syncConfig);
32
+ }
33
+ }