@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,117 @@
1
+ // Module: hlc — Hybrid Logical Clock utilities for team sync
2
+ // HLC is a plain bigint: upper 48 bits = wall-clock ms, lower 16 bits = counter.
3
+
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { SIA_HOME } from "@/shared/config";
7
+
8
+ /** HLC is a plain bigint — no mutable struct. */
9
+ export type HLC = bigint;
10
+
11
+ const COUNTER_BITS = 16n;
12
+ const COUNTER_MASK = (1n << COUNTER_BITS) - 1n;
13
+ const MAX_COUNTER = 0xffff;
14
+
15
+ /** Pack wall-clock ms and counter into a single bigint. */
16
+ export function pack(wallMs: number, counter: number): bigint {
17
+ return (BigInt(wallMs) << COUNTER_BITS) | BigInt(counter);
18
+ }
19
+
20
+ /** Unpack a bigint HLC into wall-clock ms and counter. */
21
+ export function unpack(value: bigint): { wallMs: number; counter: number } {
22
+ const counter = Number(value & COUNTER_MASK);
23
+ const wallMs = Number(value >> COUNTER_BITS);
24
+ return { wallMs, counter };
25
+ }
26
+
27
+ /**
28
+ * Generate the next local HLC value. NO mutation — returns a new bigint.
29
+ * Monotonic: if wall clock hasn't advanced, increments the counter.
30
+ */
31
+ export function hlcNow(local: bigint): bigint {
32
+ const now = Date.now();
33
+ const { wallMs, counter } = unpack(local);
34
+ if (now > wallMs) {
35
+ return pack(now, 0);
36
+ }
37
+ if (counter >= MAX_COUNTER) {
38
+ return pack(wallMs + 1, 0);
39
+ }
40
+ return pack(wallMs, counter + 1);
41
+ }
42
+
43
+ /**
44
+ * Merge a remote HLC into the local clock (Lamport/HLC merge rules).
45
+ * NO mutation — returns a new bigint that is causally after both inputs.
46
+ */
47
+ export function hlcReceive(local: bigint, remote: bigint): bigint {
48
+ const now = Date.now();
49
+ const l = unpack(local);
50
+ const r = unpack(remote);
51
+ const maxWall = Math.max(l.wallMs, r.wallMs, now);
52
+
53
+ if (maxWall === l.wallMs && maxWall === r.wallMs) {
54
+ const newCounter = Math.max(l.counter, r.counter) + 1;
55
+ if (newCounter > MAX_COUNTER) {
56
+ return pack(maxWall + 1, 0);
57
+ }
58
+ return pack(maxWall, newCounter);
59
+ }
60
+ if (maxWall === l.wallMs) {
61
+ if (l.counter >= MAX_COUNTER) {
62
+ return pack(maxWall + 1, 0);
63
+ }
64
+ return pack(maxWall, l.counter + 1);
65
+ }
66
+ if (maxWall === r.wallMs) {
67
+ if (r.counter >= MAX_COUNTER) {
68
+ return pack(maxWall + 1, 0);
69
+ }
70
+ return pack(maxWall, r.counter + 1);
71
+ }
72
+ // now is strictly greater than both
73
+ return pack(maxWall, 0);
74
+ }
75
+
76
+ /**
77
+ * Persist HLC to disk at `{siaHome}/repos/{repoHash}/hlc.json`.
78
+ * Creates directory if needed. Writes the bigint as a decimal string.
79
+ */
80
+ export function persistHlc(repoHash: string, hlc: bigint, siaHome: string = SIA_HOME): void {
81
+ const dir = join(siaHome, "repos", repoHash);
82
+ mkdirSync(dir, { recursive: true });
83
+ const filePath = join(dir, "hlc.json");
84
+ writeFileSync(filePath, JSON.stringify({ hlc: hlc.toString() }), "utf-8");
85
+ }
86
+
87
+ /**
88
+ * Load HLC from disk for the given repo.
89
+ * Falls back to `pack(Date.now(), 0)` on any error (missing file, parse error, etc.).
90
+ */
91
+ export function loadHlc(repoHash: string, siaHome: string = SIA_HOME): bigint {
92
+ const filePath = join(siaHome, "repos", repoHash, "hlc.json");
93
+ if (!existsSync(filePath)) {
94
+ return pack(Date.now(), 0);
95
+ }
96
+ try {
97
+ const raw = JSON.parse(readFileSync(filePath, "utf-8")) as { hlc?: string };
98
+ if (raw.hlc !== undefined) {
99
+ return BigInt(raw.hlc);
100
+ }
101
+ return pack(Date.now(), 0);
102
+ } catch {
103
+ return pack(Date.now(), 0);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Safely convert database column values to BigInt.
109
+ * Null or undefined -> 0n.
110
+ */
111
+ export function hlcFromDb(value: unknown): bigint {
112
+ if (value === null || value === undefined) return 0n;
113
+ if (typeof value === "bigint") return value;
114
+ if (typeof value === "number") return BigInt(value);
115
+ if (typeof value === "string") return BigInt(value);
116
+ throw new TypeError("Unsupported HLC column type");
117
+ }
@@ -0,0 +1,144 @@
1
+ // Module: keychain — OS keychain integration with file fallback
2
+
3
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { SIA_HOME } from "@/shared/config";
6
+
7
+ const SERVICE_NAME = "sia-sync";
8
+ const FALLBACK_PATH = join(SIA_HOME, ".tokens");
9
+ const FALLBACK_ENV_FLAG = "SIA_KEYCHAIN_FALLBACK";
10
+
11
+ function shouldUseFallback(): boolean {
12
+ return process.env[FALLBACK_ENV_FLAG] === "1";
13
+ }
14
+
15
+ type KeyringModule = typeof import("@napi-rs/keyring");
16
+
17
+ let keyringModulePromise: Promise<KeyringModule | null> | null = null;
18
+
19
+ async function getKeyring(): Promise<KeyringModule | null> {
20
+ if (!keyringModulePromise) {
21
+ keyringModulePromise = import("@napi-rs/keyring").catch(() => null);
22
+ }
23
+ return keyringModulePromise;
24
+ }
25
+
26
+ type KeychainEntry = {
27
+ setPassword(pw: string): Promise<void>;
28
+ getPassword(): Promise<string | null>;
29
+ deletePassword(): Promise<void>;
30
+ };
31
+
32
+ async function getKeychainEntry(serverUrl: string): Promise<KeychainEntry | null> {
33
+ const keyring = await getKeyring();
34
+ if (!keyring) return null;
35
+
36
+ const kr = keyring as Record<string, unknown>;
37
+ const krDefault = kr.default as Record<string, unknown> | undefined;
38
+ const EntryCtor =
39
+ (kr.Entry as (new (...args: unknown[]) => KeychainEntry) | undefined) ??
40
+ (krDefault?.Entry as (new (...args: unknown[]) => KeychainEntry) | undefined) ??
41
+ (kr.Keyring as (new (...args: unknown[]) => KeychainEntry) | undefined) ??
42
+ (krDefault as unknown as (new (...args: unknown[]) => KeychainEntry) | undefined);
43
+
44
+ if (!EntryCtor) return null;
45
+ try {
46
+ return new EntryCtor(SERVICE_NAME, serverUrl) as KeychainEntry;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function readFallback(): Record<string, string> {
53
+ if (!existsSync(FALLBACK_PATH)) return {};
54
+ try {
55
+ const parsed = JSON.parse(readFileSync(FALLBACK_PATH, "utf-8")) as Record<string, string>;
56
+ return parsed;
57
+ } catch {
58
+ return {};
59
+ }
60
+ }
61
+
62
+ function writeFallback(map: Record<string, string>): void {
63
+ const dir = dirname(FALLBACK_PATH);
64
+ mkdirSync(dir, { recursive: true });
65
+ writeFileSync(FALLBACK_PATH, JSON.stringify(map, null, 2), { encoding: "utf-8", mode: 0o600 });
66
+ try {
67
+ chmodSync(FALLBACK_PATH, 0o600);
68
+ } catch {
69
+ // best effort — continue
70
+ }
71
+ }
72
+
73
+ export async function storeToken(serverUrl: string, token: string): Promise<void> {
74
+ if (shouldUseFallback()) {
75
+ const map = readFallback();
76
+ map[serverUrl] = token;
77
+ writeFallback(map);
78
+ return;
79
+ }
80
+
81
+ const entry = await getKeychainEntry(serverUrl);
82
+ if (entry) {
83
+ try {
84
+ await entry.setPassword(token);
85
+ return;
86
+ } catch {
87
+ // Fall back to file store on keychain errors
88
+ }
89
+ }
90
+
91
+ console.warn("OS keychain unavailable — falling back to file storage");
92
+ const map = readFallback();
93
+ map[serverUrl] = token;
94
+ writeFallback(map);
95
+ }
96
+
97
+ export async function getToken(serverUrl: string): Promise<string | null> {
98
+ if (shouldUseFallback()) {
99
+ const map = readFallback();
100
+ return map[serverUrl] ?? null;
101
+ }
102
+
103
+ const entry = await getKeychainEntry(serverUrl);
104
+ if (entry) {
105
+ try {
106
+ const result = await entry.getPassword();
107
+ return result ?? null;
108
+ } catch {
109
+ // Fall back to file store
110
+ }
111
+ }
112
+
113
+ console.warn("OS keychain unavailable — falling back to file storage");
114
+ const map = readFallback();
115
+ return map[serverUrl] ?? null;
116
+ }
117
+
118
+ export async function deleteToken(serverUrl: string): Promise<void> {
119
+ if (shouldUseFallback()) {
120
+ const map = readFallback();
121
+ if (map[serverUrl]) {
122
+ delete map[serverUrl];
123
+ writeFallback(map);
124
+ }
125
+ return;
126
+ }
127
+
128
+ const entry = await getKeychainEntry(serverUrl);
129
+ if (entry) {
130
+ try {
131
+ await entry.deletePassword();
132
+ return;
133
+ } catch {
134
+ // Fall back to file store
135
+ }
136
+ }
137
+
138
+ console.warn("OS keychain unavailable — falling back to file storage");
139
+ const map = readFallback();
140
+ if (map[serverUrl]) {
141
+ delete map[serverUrl];
142
+ writeFallback(map);
143
+ }
144
+ }
@@ -0,0 +1,232 @@
1
+ // Module: pull — Pull remote changes via libSQL sync
2
+
3
+ import type { CandidateFact } from "@/capture/types";
4
+ import { writeAuditEntry } from "@/graph/audit";
5
+ import type { SiaDb } from "@/graph/db-interface";
6
+ import type { SyncConfig } from "@/shared/config";
7
+ import { hlcFromDb, hlcReceive, loadHlc, persistHlc } from "@/sync/hlc";
8
+
9
+ export interface PullResult {
10
+ entitiesReceived: number;
11
+ edgesReceived: number;
12
+ vssRefreshed: number;
13
+ }
14
+
15
+ export async function pullChanges(
16
+ db: SiaDb,
17
+ _bridgeDb: SiaDb,
18
+ config: SyncConfig,
19
+ repoHash?: string,
20
+ siaHome?: string,
21
+ metaDb?: SiaDb,
22
+ ): Promise<PullResult> {
23
+ if (!config.enabled) {
24
+ return { entitiesReceived: 0, edgesReceived: 0, vssRefreshed: 0 };
25
+ }
26
+
27
+ // Trigger replication if available (safe optional call)
28
+ await db.sync?.();
29
+
30
+ // Get received entities: non-private with hlc_modified > synced_at (or synced_at IS NULL)
31
+ const entityRows = await db.execute(
32
+ `SELECT * FROM graph_nodes
33
+ WHERE visibility != 'private'
34
+ AND hlc_modified IS NOT NULL
35
+ AND (synced_at IS NULL OR synced_at < hlc_modified)`,
36
+ );
37
+ const edgeRowsResult = await db.execute(
38
+ `SELECT id, from_id, to_id, type, weight, confidence, trust_tier,
39
+ t_created, t_expired, t_valid_from, t_valid_until,
40
+ hlc_created, hlc_modified, source_episode, extraction_method
41
+ FROM graph_edges
42
+ WHERE hlc_modified IS NOT NULL
43
+ AND (t_valid_until IS NULL)`,
44
+ );
45
+ const receivedEdges = edgeRowsResult.rows as Array<Record<string, unknown>>;
46
+
47
+ const receivedEntities = entityRows.rows as Array<Record<string, unknown>>;
48
+
49
+ // --- Consolidation via @/capture/consolidate ---
50
+ // Convert received entities to CandidateFact[] for consolidation.
51
+ // We dynamically import to avoid circular dependency issues.
52
+ const processedEntityIds: string[] = [];
53
+ if (receivedEntities.length > 0) {
54
+ try {
55
+ const { consolidate } = await import("@/capture/consolidate");
56
+ const candidates: CandidateFact[] = receivedEntities.map((row) => ({
57
+ type: row.type as string as CandidateFact["type"],
58
+ name: row.name as string,
59
+ content: row.content as string,
60
+ summary: row.summary as string,
61
+ tags: (() => {
62
+ try {
63
+ return JSON.parse((row.tags as string) || "[]") as string[];
64
+ } catch {
65
+ return [];
66
+ }
67
+ })(),
68
+ file_paths: (() => {
69
+ try {
70
+ return JSON.parse((row.file_paths as string) || "[]") as string[];
71
+ } catch {
72
+ return [];
73
+ }
74
+ })(),
75
+ trust_tier: row.trust_tier as number as CandidateFact["trust_tier"],
76
+ confidence: row.confidence as number,
77
+ extraction_method: (row.extraction_method as string) ?? undefined,
78
+ }));
79
+
80
+ await consolidate(db, candidates);
81
+
82
+ for (const row of receivedEntities) {
83
+ processedEntityIds.push(row.id as string);
84
+ }
85
+ } catch (err) {
86
+ // If consolidation fails, still track the entities as received
87
+ console.warn("Consolidation during pull failed:", err);
88
+ for (const row of receivedEntities) {
89
+ processedEntityIds.push(row.id as string);
90
+ }
91
+ }
92
+ }
93
+
94
+ // --- Update local HLC high-water ---
95
+ const maxHlcRow = await db.execute(
96
+ "SELECT MAX(hlc_modified) as max_hlc FROM graph_nodes WHERE hlc_modified IS NOT NULL",
97
+ );
98
+ const maxHlc = hlcFromDb((maxHlcRow.rows[0] as { max_hlc?: unknown })?.max_hlc ?? 0n);
99
+
100
+ if (repoHash) {
101
+ let localHlc = loadHlc(repoHash, siaHome);
102
+ if (maxHlc > 0n) {
103
+ localHlc = hlcReceive(localHlc, maxHlc);
104
+ }
105
+ persistHlc(repoHash, localHlc, siaHome);
106
+ } else if (maxHlc > 0n) {
107
+ // No repoHash — merge into a fresh clock (result discarded)
108
+ hlcReceive(0n, maxHlc);
109
+ }
110
+
111
+ // --- sync_peers ---
112
+ // Update all known peers' last_seen_at and last_seen_hlc (to the max HLC received).
113
+ if (metaDb) {
114
+ const maxHlcNumber = Number(maxHlc);
115
+ await metaDb.execute(`UPDATE sync_peers SET last_seen_at = ?, last_seen_hlc = ?`, [
116
+ Date.now(),
117
+ maxHlcNumber,
118
+ ]);
119
+ }
120
+
121
+ // Audit the received items.
122
+ for (const row of receivedEntities) {
123
+ await writeAuditEntry(db, "SYNC_RECV", { entity_id: row.id as string });
124
+ }
125
+
126
+ // --- Apply received edges to local graph ---
127
+ // For each edge row fetched, INSERT if new or UPDATE if it already exists locally.
128
+ for (const edge of receivedEdges) {
129
+ const existingRow = await db.execute("SELECT id FROM graph_edges WHERE id = ?", [edge.id]);
130
+ if ((existingRow.rows as Array<unknown>).length > 0) {
131
+ // UPDATE existing edge
132
+ await db.execute(
133
+ `UPDATE graph_edges SET
134
+ from_id = ?, to_id = ?, type = ?, weight = ?, confidence = ?, trust_tier = ?,
135
+ t_created = ?, t_expired = ?, t_valid_from = ?, t_valid_until = ?,
136
+ hlc_created = ?, hlc_modified = ?, source_episode = ?, extraction_method = ?
137
+ WHERE id = ?`,
138
+ [
139
+ edge.from_id,
140
+ edge.to_id,
141
+ edge.type,
142
+ edge.weight,
143
+ edge.confidence,
144
+ edge.trust_tier,
145
+ edge.t_created,
146
+ edge.t_expired ?? null,
147
+ edge.t_valid_from ?? null,
148
+ edge.t_valid_until ?? null,
149
+ edge.hlc_created ?? null,
150
+ edge.hlc_modified ?? null,
151
+ edge.source_episode ?? null,
152
+ edge.extraction_method ?? null,
153
+ edge.id,
154
+ ],
155
+ );
156
+ } else {
157
+ // INSERT new edge
158
+ await db.execute(
159
+ `INSERT INTO graph_edges (
160
+ id, from_id, to_id, type, weight, confidence, trust_tier,
161
+ t_created, t_expired, t_valid_from, t_valid_until,
162
+ hlc_created, hlc_modified, source_episode, extraction_method
163
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
164
+ [
165
+ edge.id,
166
+ edge.from_id,
167
+ edge.to_id,
168
+ edge.type,
169
+ edge.weight,
170
+ edge.confidence,
171
+ edge.trust_tier,
172
+ edge.t_created,
173
+ edge.t_expired ?? null,
174
+ edge.t_valid_from ?? null,
175
+ edge.t_valid_until ?? null,
176
+ edge.hlc_created ?? null,
177
+ edge.hlc_modified ?? null,
178
+ edge.source_episode ?? null,
179
+ edge.extraction_method ?? null,
180
+ ],
181
+ );
182
+ }
183
+ await writeAuditEntry(db, "SYNC_RECV", { edge_id: edge.id as string });
184
+ }
185
+
186
+ // --- Scoped VSS refresh ---
187
+ // Only refresh entities that were actually received (use their IDs, not full table).
188
+ let vssRefreshed = 0;
189
+ const sqlite = db.rawSqlite();
190
+ if (sqlite && processedEntityIds.length > 0) {
191
+ try {
192
+ // Build scoped query for just the received entity IDs
193
+ const placeholders = processedEntityIds.map(() => "?").join(", ");
194
+ const embedRows = await db.execute(
195
+ `SELECT rowid, embedding FROM graph_nodes
196
+ WHERE id IN (${placeholders}) AND embedding IS NOT NULL AND archived_at IS NULL`,
197
+ processedEntityIds,
198
+ );
199
+
200
+ // Use rawSqlite() directly for VSS INSERT
201
+ for (const row of embedRows.rows as Array<{ rowid: number; embedding: Uint8Array }>) {
202
+ try {
203
+ sqlite
204
+ .prepare("INSERT OR REPLACE INTO graph_nodes_vss(rowid, embedding) VALUES (?, ?)")
205
+ .run(row.rowid, row.embedding);
206
+ vssRefreshed++;
207
+ } catch {
208
+ // Ignore individual VSS insert failures
209
+ }
210
+ }
211
+
212
+ if (vssRefreshed > 0) {
213
+ for (const row of embedRows.rows as Array<{ rowid: number }>) {
214
+ await writeAuditEntry(db, "VSS_REFRESH", { entity_id: String(row.rowid) });
215
+ }
216
+ }
217
+ } catch {
218
+ // Ignore if extension/table not available
219
+ }
220
+ } else if (!sqlite && processedEntityIds.length > 0) {
221
+ console.warn("rawSqlite() returned null — skipping VSS refresh for received entities");
222
+ }
223
+
224
+ // Trigger replication after processing (safe optional call)
225
+ await db.sync?.();
226
+
227
+ return {
228
+ entitiesReceived: receivedEntities.length,
229
+ edgesReceived: receivedEdges.length,
230
+ vssRefreshed,
231
+ };
232
+ }
@@ -0,0 +1,131 @@
1
+ // Module: push — Push local changes to remote via libSQL sync
2
+
3
+ import { writeAuditEntry } from "@/graph/audit";
4
+ import type { SiaDb } from "@/graph/db-interface";
5
+ import type { SyncConfig } from "@/shared/config";
6
+ import { hlcNow, loadHlc, persistHlc } from "@/sync/hlc";
7
+
8
+ export interface PushResult {
9
+ entitiesPushed: number;
10
+ edgesPushed: number;
11
+ bridgeEdgesPushed: number;
12
+ }
13
+
14
+ export async function pushChanges(
15
+ db: SiaDb,
16
+ config: SyncConfig,
17
+ bridgeDb?: SiaDb,
18
+ repoHash?: string,
19
+ siaHome?: string,
20
+ ): Promise<PushResult> {
21
+ if (!config.enabled) {
22
+ return { entitiesPushed: 0, edgesPushed: 0, bridgeEdgesPushed: 0 };
23
+ }
24
+
25
+ // Determine the timestamp for synced_at — use HLC if repoHash is provided, else Date.now()
26
+ let syncedAt: number;
27
+ let hlc: bigint | undefined;
28
+ if (repoHash) {
29
+ hlc = loadHlc(repoHash, siaHome);
30
+ hlc = hlcNow(hlc);
31
+ syncedAt = Number(hlc >> 16n); // wall-clock ms from HLC
32
+ } else {
33
+ syncedAt = Date.now();
34
+ }
35
+
36
+ // --- Push entities ---
37
+ const candidates = await db.execute(
38
+ `SELECT id FROM graph_nodes
39
+ WHERE visibility != 'private'
40
+ AND (synced_at IS NULL OR (hlc_modified IS NOT NULL AND synced_at < hlc_modified))`,
41
+ );
42
+
43
+ const entityIds = (candidates.rows as Array<{ id: string }>).map((r) => r.id);
44
+ const entityIdSet = new Set(entityIds);
45
+
46
+ if (entityIds.length > 0) {
47
+ // Batch update synced_at in chunks of 500
48
+ for (let i = 0; i < entityIds.length; i += 500) {
49
+ const chunk = entityIds.slice(i, i + 500);
50
+ const placeholders = chunk.map(() => "?").join(", ");
51
+ await db.execute(`UPDATE graph_nodes SET synced_at = ? WHERE id IN (${placeholders})`, [
52
+ syncedAt,
53
+ ...chunk,
54
+ ]);
55
+ }
56
+ for (const id of entityIds) {
57
+ await writeAuditEntry(db, "SYNC_SEND", { entity_id: id });
58
+ }
59
+ }
60
+
61
+ // --- Push edges ---
62
+ // Query edges where both from_id AND to_id are in pushed entity IDs set
63
+ let edgesPushed = 0;
64
+ if (entityIdSet.size > 0) {
65
+ const allEdges = await db.execute(
66
+ `SELECT id, from_id, to_id FROM graph_edges
67
+ WHERE t_valid_until IS NULL`,
68
+ );
69
+
70
+ const eligibleEdgeIds: string[] = [];
71
+ for (const row of allEdges.rows as Array<{ id: string; from_id: string; to_id: string }>) {
72
+ if (entityIdSet.has(row.from_id) && entityIdSet.has(row.to_id)) {
73
+ eligibleEdgeIds.push(row.id);
74
+ }
75
+ }
76
+
77
+ // Batch update hlc_modified on edges in chunks of 500
78
+ for (let i = 0; i < eligibleEdgeIds.length; i += 500) {
79
+ const chunk = eligibleEdgeIds.slice(i, i + 500);
80
+ const placeholders = chunk.map(() => "?").join(", ");
81
+ await db.execute(`UPDATE graph_edges SET hlc_modified = ? WHERE id IN (${placeholders})`, [
82
+ syncedAt,
83
+ ...chunk,
84
+ ]);
85
+ }
86
+ for (const id of eligibleEdgeIds) {
87
+ await writeAuditEntry(db, "SYNC_SEND", { edge_id: id });
88
+ }
89
+ edgesPushed = eligibleEdgeIds.length;
90
+ }
91
+
92
+ // --- Push bridge edges ---
93
+ let bridgeEdgesPushed = 0;
94
+ if (bridgeDb && entityIdSet.size > 0) {
95
+ const bridgeEdges = await bridgeDb.execute(
96
+ `SELECT id, source_entity_id, target_entity_id FROM cross_repo_edges
97
+ WHERE t_valid_until IS NULL`,
98
+ );
99
+
100
+ const eligibleBridgeIds: string[] = [];
101
+ for (const row of bridgeEdges.rows as Array<{
102
+ id: string;
103
+ source_entity_id: string;
104
+ target_entity_id: string;
105
+ }>) {
106
+ if (entityIdSet.has(row.source_entity_id) && entityIdSet.has(row.target_entity_id)) {
107
+ eligibleBridgeIds.push(row.id);
108
+ }
109
+ }
110
+
111
+ for (let i = 0; i < eligibleBridgeIds.length; i += 500) {
112
+ const chunk = eligibleBridgeIds.slice(i, i + 500);
113
+ const placeholders = chunk.map(() => "?").join(", ");
114
+ await bridgeDb.execute(
115
+ `UPDATE cross_repo_edges SET hlc_modified = ? WHERE id IN (${placeholders})`,
116
+ [syncedAt, ...chunk],
117
+ );
118
+ }
119
+ bridgeEdgesPushed = eligibleBridgeIds.length;
120
+ }
121
+
122
+ // Persist updated HLC after push
123
+ if (repoHash && hlc !== undefined) {
124
+ persistHlc(repoHash, hlc, siaHome);
125
+ }
126
+
127
+ // Trigger replication if available (safe optional call)
128
+ await db.sync?.();
129
+
130
+ return { entitiesPushed: entityIds.length, edgesPushed, bridgeEdgesPushed };
131
+ }
@@ -0,0 +1,23 @@
1
+ declare module "chokidar" {
2
+ export interface WatchOptions {
3
+ ignoreInitial?: boolean;
4
+ ignored?:
5
+ | ((path: string, stats?: { isDirectory(): boolean }) => boolean)
6
+ | RegExp
7
+ | string
8
+ | string[];
9
+ }
10
+
11
+ export interface FSWatcher {
12
+ on(event: "add" | "change" | "unlink", listener: (path: string) => void): this;
13
+ close(): Promise<void> | void;
14
+ }
15
+
16
+ export function watch(paths: string | string[], options?: WatchOptions): FSWatcher;
17
+
18
+ const chokidar: {
19
+ watch: typeof watch;
20
+ };
21
+
22
+ export default chokidar;
23
+ }