cairn-brain 0.2.1__py3-none-any.whl

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 (875) hide show
  1. cairn/__init__.py +1 -0
  2. cairn/__main__.py +3 -0
  3. cairn/agents.py +538 -0
  4. cairn/capture.py +36 -0
  5. cairn/cli.py +1054 -0
  6. cairn/core.py +711 -0
  7. cairn/daemon.py +173 -0
  8. cairn/drift.py +139 -0
  9. cairn/engines/__init__.py +0 -0
  10. cairn/engines/chronicle.py +268 -0
  11. cairn/engines/graph/__init__.py +37 -0
  12. cairn/engines/graph/__main__.py +10 -0
  13. cairn/engines/graph/_minhash.py +107 -0
  14. cairn/engines/graph/affected.py +318 -0
  15. cairn/engines/graph/analyze.py +772 -0
  16. cairn/engines/graph/api.py +429 -0
  17. cairn/engines/graph/assets/d3.v7.min.js +2 -0
  18. cairn/engines/graph/assets/mermaid.min.js +3636 -0
  19. cairn/engines/graph/assets/vis-network.min.js +33 -0
  20. cairn/engines/graph/assets.py +50 -0
  21. cairn/engines/graph/benchmark.py +152 -0
  22. cairn/engines/graph/build.py +2459 -0
  23. cairn/engines/graph/cache.py +1802 -0
  24. cairn/engines/graph/callflow_html.py +2078 -0
  25. cairn/engines/graph/cargo_introspect.py +166 -0
  26. cairn/engines/graph/cli.py +4978 -0
  27. cairn/engines/graph/cluster.py +425 -0
  28. cairn/engines/graph/cross_repo_calls.py +247 -0
  29. cairn/engines/graph/cross_repo_types.py +75 -0
  30. cairn/engines/graph/csharp_dispatch.py +154 -0
  31. cairn/engines/graph/dedup.py +1220 -0
  32. cairn/engines/graph/detect.py +2669 -0
  33. cairn/engines/graph/diagnostics.py +422 -0
  34. cairn/engines/graph/export.py +1362 -0
  35. cairn/engines/graph/exporters/__init__.py +1 -0
  36. cairn/engines/graph/exporters/base.py +14 -0
  37. cairn/engines/graph/exporters/graphdb.py +173 -0
  38. cairn/engines/graph/exporters/html.py +685 -0
  39. cairn/engines/graph/extract.py +8739 -0
  40. cairn/engines/graph/extractors/__init__.py +76 -0
  41. cairn/engines/graph/extractors/apex.py +215 -0
  42. cairn/engines/graph/extractors/base.py +85 -0
  43. cairn/engines/graph/extractors/bash.py +579 -0
  44. cairn/engines/graph/extractors/blade.py +53 -0
  45. cairn/engines/graph/extractors/cobol.py +276 -0
  46. cairn/engines/graph/extractors/commonlisp.py +540 -0
  47. cairn/engines/graph/extractors/csharp.py +448 -0
  48. cairn/engines/graph/extractors/dart.py +564 -0
  49. cairn/engines/graph/extractors/dm.py +494 -0
  50. cairn/engines/graph/extractors/elixir.py +250 -0
  51. cairn/engines/graph/extractors/engine.py +7175 -0
  52. cairn/engines/graph/extractors/erlang.py +333 -0
  53. cairn/engines/graph/extractors/fortran.py +311 -0
  54. cairn/engines/graph/extractors/go.py +564 -0
  55. cairn/engines/graph/extractors/json_config.py +240 -0
  56. cairn/engines/graph/extractors/julia.py +289 -0
  57. cairn/engines/graph/extractors/markdown.py +509 -0
  58. cairn/engines/graph/extractors/models.py +131 -0
  59. cairn/engines/graph/extractors/objc.py +566 -0
  60. cairn/engines/graph/extractors/ocaml.py +289 -0
  61. cairn/engines/graph/extractors/pascal.py +688 -0
  62. cairn/engines/graph/extractors/pascal_forms.py +196 -0
  63. cairn/engines/graph/extractors/powershell.py +522 -0
  64. cairn/engines/graph/extractors/r.py +438 -0
  65. cairn/engines/graph/extractors/razor.py +192 -0
  66. cairn/engines/graph/extractors/resolution.py +3806 -0
  67. cairn/engines/graph/extractors/robot.py +296 -0
  68. cairn/engines/graph/extractors/rust.py +574 -0
  69. cairn/engines/graph/extractors/sln.py +92 -0
  70. cairn/engines/graph/extractors/solidity.py +413 -0
  71. cairn/engines/graph/extractors/sql.py +720 -0
  72. cairn/engines/graph/extractors/terraform.py +440 -0
  73. cairn/engines/graph/extractors/vbnet.py +409 -0
  74. cairn/engines/graph/extractors/verilog.py +364 -0
  75. cairn/engines/graph/extractors/yaml.py +162 -0
  76. cairn/engines/graph/extractors/zig.py +181 -0
  77. cairn/engines/graph/file_slice.py +246 -0
  78. cairn/engines/graph/global_graph.py +216 -0
  79. cairn/engines/graph/google_workspace.py +238 -0
  80. cairn/engines/graph/hooks.py +550 -0
  81. cairn/engines/graph/ids.py +93 -0
  82. cairn/engines/graph/ingest.py +358 -0
  83. cairn/engines/graph/llm.py +2421 -0
  84. cairn/engines/graph/manifest.py +4 -0
  85. cairn/engines/graph/manifest_ingest.py +311 -0
  86. cairn/engines/graph/markdown_resolution.py +231 -0
  87. cairn/engines/graph/mcp_ingest.py +386 -0
  88. cairn/engines/graph/multigraph_compat.py +212 -0
  89. cairn/engines/graph/pascal_resolution.py +129 -0
  90. cairn/engines/graph/paths.py +677 -0
  91. cairn/engines/graph/pg_introspect.py +165 -0
  92. cairn/engines/graph/prs.py +701 -0
  93. cairn/engines/graph/querylog.py +81 -0
  94. cairn/engines/graph/reflect.py +882 -0
  95. cairn/engines/graph/report.py +368 -0
  96. cairn/engines/graph/repos.py +438 -0
  97. cairn/engines/graph/resolver_registry.py +85 -0
  98. cairn/engines/graph/ruby_resolution.py +524 -0
  99. cairn/engines/graph/scip_ingest.py +363 -0
  100. cairn/engines/graph/security.py +461 -0
  101. cairn/engines/graph/semantic_cleanup.py +336 -0
  102. cairn/engines/graph/serve.py +2437 -0
  103. cairn/engines/graph/symbol_resolution.py +556 -0
  104. cairn/engines/graph/transcribe.py +186 -0
  105. cairn/engines/graph/tree_html.py +618 -0
  106. cairn/engines/graph/validate.py +95 -0
  107. cairn/engines/graph/watch.py +2434 -0
  108. cairn/engines/graph/wiki.py +417 -0
  109. cairn/engines/history.py +194 -0
  110. cairn/engines/journal.py +207 -0
  111. cairn/engines/mapper.py +522 -0
  112. cairn/engines/memory.py +1008 -0
  113. cairn/engines/memstore/__init__.py +32 -0
  114. cairn/engines/memstore/api.py +315 -0
  115. cairn/engines/memstore/base.py +63 -0
  116. cairn/engines/memstore/commands.py +163 -0
  117. cairn/engines/memstore/configs/__init__.py +0 -0
  118. cairn/engines/memstore/configs/base.py +104 -0
  119. cairn/engines/memstore/configs/embeddings/__init__.py +0 -0
  120. cairn/engines/memstore/configs/embeddings/base.py +45 -0
  121. cairn/engines/memstore/configs/enums.py +7 -0
  122. cairn/engines/memstore/configs/llms/__init__.py +0 -0
  123. cairn/engines/memstore/configs/llms/base.py +81 -0
  124. cairn/engines/memstore/configs/rerankers/__init__.py +0 -0
  125. cairn/engines/memstore/configs/rerankers/base.py +17 -0
  126. cairn/engines/memstore/configs/rerankers/config.py +14 -0
  127. cairn/engines/memstore/configs/rerankers/huggingface.py +17 -0
  128. cairn/engines/memstore/configs/rerankers/llm.py +54 -0
  129. cairn/engines/memstore/configs/rerankers/sentence_transformer.py +16 -0
  130. cairn/engines/memstore/configs/vector_stores/__init__.py +0 -0
  131. cairn/engines/memstore/configs/vector_stores/cassandra.py +76 -0
  132. cairn/engines/memstore/configs/vector_stores/chroma.py +58 -0
  133. cairn/engines/memstore/configs/vector_stores/elasticsearch.py +68 -0
  134. cairn/engines/memstore/configs/vector_stores/faiss.py +40 -0
  135. cairn/engines/memstore/configs/vector_stores/langchain.py +30 -0
  136. cairn/engines/memstore/configs/vector_stores/milvus.py +42 -0
  137. cairn/engines/memstore/configs/vector_stores/mongodb.py +27 -0
  138. cairn/engines/memstore/configs/vector_stores/opensearch.py +49 -0
  139. cairn/engines/memstore/configs/vector_stores/oracledb.py +115 -0
  140. cairn/engines/memstore/configs/vector_stores/pgvector.py +54 -0
  141. cairn/engines/memstore/configs/vector_stores/qdrant.py +51 -0
  142. cairn/engines/memstore/configs/vector_stores/redis.py +9 -0
  143. cairn/engines/memstore/configs/vector_stores/supabase.py +46 -0
  144. cairn/engines/memstore/configs/vector_stores/valkey.py +32 -0
  145. cairn/engines/memstore/configs/vector_stores/weaviate.py +41 -0
  146. cairn/engines/memstore/embeddings/__init__.py +0 -0
  147. cairn/engines/memstore/embeddings/base.py +47 -0
  148. cairn/engines/memstore/embeddings/configs.py +20 -0
  149. cairn/engines/memstore/embeddings/local.py +44 -0
  150. cairn/engines/memstore/exceptions.py +485 -0
  151. cairn/engines/memstore/graph.py +129 -0
  152. cairn/engines/memstore/llms/__init__.py +0 -0
  153. cairn/engines/memstore/llms/base.py +67 -0
  154. cairn/engines/memstore/llms/configs.py +17 -0
  155. cairn/engines/memstore/llms/router.py +255 -0
  156. cairn/engines/memstore/main.py +2350 -0
  157. cairn/engines/memstore/prompts.py +1111 -0
  158. cairn/engines/memstore/proxy.py +136 -0
  159. cairn/engines/memstore/reranker/__init__.py +23 -0
  160. cairn/engines/memstore/reranker/base.py +20 -0
  161. cairn/engines/memstore/reranker/huggingface_reranker.py +173 -0
  162. cairn/engines/memstore/reranker/llm_reranker.py +188 -0
  163. cairn/engines/memstore/reranker/sentence_transformer_reranker.py +120 -0
  164. cairn/engines/memstore/scopes.py +44 -0
  165. cairn/engines/memstore/storage.py +351 -0
  166. cairn/engines/memstore/utils/__init__.py +0 -0
  167. cairn/engines/memstore/utils/entity_extraction.py +821 -0
  168. cairn/engines/memstore/utils/factory.py +223 -0
  169. cairn/engines/memstore/utils/lemmatization.py +95 -0
  170. cairn/engines/memstore/utils/messages.py +312 -0
  171. cairn/engines/memstore/utils/nlp_models.py +102 -0
  172. cairn/engines/memstore/utils/scoring.py +139 -0
  173. cairn/engines/memstore/utils/text.py +54 -0
  174. cairn/engines/memstore/vector_stores/__init__.py +0 -0
  175. cairn/engines/memstore/vector_stores/base.py +100 -0
  176. cairn/engines/memstore/vector_stores/cassandra.py +503 -0
  177. cairn/engines/memstore/vector_stores/chroma.py +364 -0
  178. cairn/engines/memstore/vector_stores/configs.py +62 -0
  179. cairn/engines/memstore/vector_stores/elasticsearch.py +311 -0
  180. cairn/engines/memstore/vector_stores/faiss.py +469 -0
  181. cairn/engines/memstore/vector_stores/filtering.py +97 -0
  182. cairn/engines/memstore/vector_stores/langchain.py +216 -0
  183. cairn/engines/memstore/vector_stores/milvus.py +374 -0
  184. cairn/engines/memstore/vector_stores/mongodb.py +432 -0
  185. cairn/engines/memstore/vector_stores/opensearch.py +416 -0
  186. cairn/engines/memstore/vector_stores/oracledb.py +602 -0
  187. cairn/engines/memstore/vector_stores/pgvector.py +563 -0
  188. cairn/engines/memstore/vector_stores/qdrant.py +606 -0
  189. cairn/engines/memstore/vector_stores/redis.py +370 -0
  190. cairn/engines/memstore/vector_stores/supabase.py +259 -0
  191. cairn/engines/memstore/vector_stores/valkey.py +819 -0
  192. cairn/engines/memstore/vector_stores/weaviate.py +387 -0
  193. cairn/engines/process_dialect.py +274 -0
  194. cairn/engines/recall/__init__.py +19 -0
  195. cairn/engines/recall/adapters.py +399 -0
  196. cairn/engines/recall/api.py +366 -0
  197. cairn/engines/recall/cli.py +329 -0
  198. cairn/engines/recall/context.py +557 -0
  199. cairn/engines/recall/corpus.py +693 -0
  200. cairn/engines/recall/fallback.py +234 -0
  201. cairn/engines/recall/fmt.py +104 -0
  202. cairn/engines/recall/folders.py +804 -0
  203. cairn/engines/recall/health.py +178 -0
  204. cairn/engines/recall/hooks.py +573 -0
  205. cairn/engines/recall/ingest.py +153 -0
  206. cairn/engines/recall/integrations.py +2170 -0
  207. cairn/engines/recall/mcp.py +363 -0
  208. cairn/engines/recall/modes/code--ar.json +24 -0
  209. cairn/engines/recall/modes/code--bn.json +24 -0
  210. cairn/engines/recall/modes/code--chill.json +8 -0
  211. cairn/engines/recall/modes/code--cs.json +24 -0
  212. cairn/engines/recall/modes/code--da.json +24 -0
  213. cairn/engines/recall/modes/code--de.json +24 -0
  214. cairn/engines/recall/modes/code--el.json +24 -0
  215. cairn/engines/recall/modes/code--es.json +24 -0
  216. cairn/engines/recall/modes/code--fi.json +24 -0
  217. cairn/engines/recall/modes/code--fr.json +24 -0
  218. cairn/engines/recall/modes/code--he.json +24 -0
  219. cairn/engines/recall/modes/code--hi.json +24 -0
  220. cairn/engines/recall/modes/code--hu.json +24 -0
  221. cairn/engines/recall/modes/code--id.json +24 -0
  222. cairn/engines/recall/modes/code--it.json +24 -0
  223. cairn/engines/recall/modes/code--ja.json +24 -0
  224. cairn/engines/recall/modes/code--ko.json +24 -0
  225. cairn/engines/recall/modes/code--nl.json +24 -0
  226. cairn/engines/recall/modes/code--no.json +24 -0
  227. cairn/engines/recall/modes/code--pl.json +24 -0
  228. cairn/engines/recall/modes/code--pt-br.json +24 -0
  229. cairn/engines/recall/modes/code--ro.json +24 -0
  230. cairn/engines/recall/modes/code--ru.json +24 -0
  231. cairn/engines/recall/modes/code--sv.json +24 -0
  232. cairn/engines/recall/modes/code--th.json +24 -0
  233. cairn/engines/recall/modes/code--tr.json +24 -0
  234. cairn/engines/recall/modes/code--uk.json +24 -0
  235. cairn/engines/recall/modes/code--ur.json +25 -0
  236. cairn/engines/recall/modes/code--vi.json +24 -0
  237. cairn/engines/recall/modes/code--zh.json +24 -0
  238. cairn/engines/recall/modes/code.json +146 -0
  239. cairn/engines/recall/modes/email-investigation.json +120 -0
  240. cairn/engines/recall/modes/law-study--chill.json +7 -0
  241. cairn/engines/recall/modes/law-study-CLAUDE.md +85 -0
  242. cairn/engines/recall/modes/law-study.json +120 -0
  243. cairn/engines/recall/modes/meme-tokens.json +125 -0
  244. cairn/engines/recall/modes.py +138 -0
  245. cairn/engines/recall/observer.py +503 -0
  246. cairn/engines/recall/parser.py +303 -0
  247. cairn/engines/recall/platforms.py +30 -0
  248. cairn/engines/recall/projects.py +216 -0
  249. cairn/engines/recall/prompts.py +291 -0
  250. cairn/engines/recall/remote.py +500 -0
  251. cairn/engines/recall/schema.py +520 -0
  252. cairn/engines/recall/search.py +605 -0
  253. cairn/engines/recall/settings.py +172 -0
  254. cairn/engines/recall/skills/cairn-do/SKILL.md +45 -0
  255. cairn/engines/recall/skills/cairn-how-it-works/SKILL.md +22 -0
  256. cairn/engines/recall/skills/cairn-how-it-works/onboarding-explainer.md +17 -0
  257. cairn/engines/recall/skills/cairn-knowledge-agent/SKILL.md +82 -0
  258. cairn/engines/recall/skills/cairn-learn-codebase/SKILL.md +21 -0
  259. cairn/engines/recall/skills/cairn-make-plan/SKILL.md +63 -0
  260. cairn/engines/recall/skills/cairn-mode-creator/SKILL.md +116 -0
  261. cairn/engines/recall/skills/cairn-mode-creator/evals/evals.json +23 -0
  262. cairn/engines/recall/skills/cairn-mode-creator/references/mode-authoring.md +96 -0
  263. cairn/engines/recall/skills/cairn-mode-creator/scripts/install_mode.py +239 -0
  264. cairn/engines/recall/skills/cairn-pathfinder/SKILL.md +111 -0
  265. cairn/engines/recall/skills/cairn-recall-search/SKILL.md +166 -0
  266. cairn/engines/recall/skills/cairn-smart-explore/SKILL.md +189 -0
  267. cairn/engines/recall/skills/cairn-standup/SKILL.md +144 -0
  268. cairn/engines/recall/skills/cairn-standup/agent-brief.md +47 -0
  269. cairn/engines/recall/skills/cairn-standup/standup.py +588 -0
  270. cairn/engines/recall/skills/cairn-timeline-report/SKILL.md +211 -0
  271. cairn/engines/recall/skills/cairn-weekly-digests/SKILL.md +264 -0
  272. cairn/engines/recall/skills/cairn-what-the/SKILL.md +6 -0
  273. cairn/engines/recall/smartread.py +1974 -0
  274. cairn/engines/recall/sqlsearch.py +244 -0
  275. cairn/engines/recall/store.py +1043 -0
  276. cairn/engines/recall/tags.py +98 -0
  277. cairn/engines/recall/transcript_parser.py +100 -0
  278. cairn/engines/recall/transcripts.py +1621 -0
  279. cairn/engines/recall/vectorsync.py +245 -0
  280. cairn/engines/recall/viewer.py +541 -0
  281. cairn/engines/recall/worker.py +437 -0
  282. cairn/engines/recall/worktrees.py +359 -0
  283. cairn/engines/specs.py +722 -0
  284. cairn/engines/temporal/__init__.py +67 -0
  285. cairn/engines/temporal/api.py +241 -0
  286. cairn/engines/temporal/cli.py +202 -0
  287. cairn/engines/temporal/cross_encoder/__init__.py +10 -0
  288. cairn/engines/temporal/cross_encoder/bge_reranker_client.py +37 -0
  289. cairn/engines/temporal/cross_encoder/client.py +24 -0
  290. cairn/engines/temporal/cross_encoder/local.py +176 -0
  291. cairn/engines/temporal/cross_encoder/router_reranker.py +94 -0
  292. cairn/engines/temporal/decorators.py +117 -0
  293. cairn/engines/temporal/driver/__init__.py +33 -0
  294. cairn/engines/temporal/driver/driver.py +200 -0
  295. cairn/engines/temporal/driver/falkordb/__init__.py +35 -0
  296. cairn/engines/temporal/driver/falkordb/fulltext.py +83 -0
  297. cairn/engines/temporal/driver/falkordb/operations/__init__.py +33 -0
  298. cairn/engines/temporal/driver/falkordb/operations/community_edge_ops.py +170 -0
  299. cairn/engines/temporal/driver/falkordb/operations/community_node_ops.py +206 -0
  300. cairn/engines/temporal/driver/falkordb/operations/entity_edge_ops.py +259 -0
  301. cairn/engines/temporal/driver/falkordb/operations/entity_node_ops.py +245 -0
  302. cairn/engines/temporal/driver/falkordb/operations/episode_node_ops.py +285 -0
  303. cairn/engines/temporal/driver/falkordb/operations/episodic_edge_ops.py +184 -0
  304. cairn/engines/temporal/driver/falkordb/operations/graph_ops.py +226 -0
  305. cairn/engines/temporal/driver/falkordb/operations/has_episode_edge_ops.py +179 -0
  306. cairn/engines/temporal/driver/falkordb/operations/next_episode_edge_ops.py +179 -0
  307. cairn/engines/temporal/driver/falkordb/operations/saga_node_ops.py +192 -0
  308. cairn/engines/temporal/driver/falkordb/operations/search_ops.py +621 -0
  309. cairn/engines/temporal/driver/falkordb_driver.py +359 -0
  310. cairn/engines/temporal/driver/graph_operations/__init__.py +0 -0
  311. cairn/engines/temporal/driver/graph_operations/graph_operations.py +877 -0
  312. cairn/engines/temporal/driver/kuzu/__init__.py +0 -0
  313. cairn/engines/temporal/driver/kuzu/operations/__init__.py +27 -0
  314. cairn/engines/temporal/driver/kuzu/operations/community_edge_ops.py +147 -0
  315. cairn/engines/temporal/driver/kuzu/operations/community_node_ops.py +182 -0
  316. cairn/engines/temporal/driver/kuzu/operations/entity_edge_ops.py +217 -0
  317. cairn/engines/temporal/driver/kuzu/operations/entity_node_ops.py +220 -0
  318. cairn/engines/temporal/driver/kuzu/operations/episode_node_ops.py +250 -0
  319. cairn/engines/temporal/driver/kuzu/operations/episodic_edge_ops.py +169 -0
  320. cairn/engines/temporal/driver/kuzu/operations/graph_ops.py +208 -0
  321. cairn/engines/temporal/driver/kuzu/operations/has_episode_edge_ops.py +155 -0
  322. cairn/engines/temporal/driver/kuzu/operations/next_episode_edge_ops.py +155 -0
  323. cairn/engines/temporal/driver/kuzu/operations/record_parsers.py +30 -0
  324. cairn/engines/temporal/driver/kuzu/operations/saga_node_ops.py +171 -0
  325. cairn/engines/temporal/driver/kuzu/operations/search_ops.py +746 -0
  326. cairn/engines/temporal/driver/kuzu_driver.py +352 -0
  327. cairn/engines/temporal/driver/neo4j/__init__.py +0 -0
  328. cairn/engines/temporal/driver/neo4j/operations/__init__.py +29 -0
  329. cairn/engines/temporal/driver/neo4j/operations/community_edge_ops.py +148 -0
  330. cairn/engines/temporal/driver/neo4j/operations/community_node_ops.py +189 -0
  331. cairn/engines/temporal/driver/neo4j/operations/entity_edge_ops.py +238 -0
  332. cairn/engines/temporal/driver/neo4j/operations/entity_node_ops.py +223 -0
  333. cairn/engines/temporal/driver/neo4j/operations/episode_node_ops.py +271 -0
  334. cairn/engines/temporal/driver/neo4j/operations/episodic_edge_ops.py +162 -0
  335. cairn/engines/temporal/driver/neo4j/operations/graph_ops.py +198 -0
  336. cairn/engines/temporal/driver/neo4j/operations/has_episode_edge_ops.py +156 -0
  337. cairn/engines/temporal/driver/neo4j/operations/next_episode_edge_ops.py +156 -0
  338. cairn/engines/temporal/driver/neo4j/operations/saga_node_ops.py +174 -0
  339. cairn/engines/temporal/driver/neo4j/operations/search_ops.py +638 -0
  340. cairn/engines/temporal/driver/neo4j_driver.py +228 -0
  341. cairn/engines/temporal/driver/neptune/__init__.py +0 -0
  342. cairn/engines/temporal/driver/neptune/operations/__init__.py +33 -0
  343. cairn/engines/temporal/driver/neptune/operations/community_edge_ops.py +147 -0
  344. cairn/engines/temporal/driver/neptune/operations/community_node_ops.py +195 -0
  345. cairn/engines/temporal/driver/neptune/operations/entity_edge_ops.py +236 -0
  346. cairn/engines/temporal/driver/neptune/operations/entity_node_ops.py +216 -0
  347. cairn/engines/temporal/driver/neptune/operations/episode_node_ops.py +259 -0
  348. cairn/engines/temporal/driver/neptune/operations/episodic_edge_ops.py +161 -0
  349. cairn/engines/temporal/driver/neptune/operations/graph_ops.py +203 -0
  350. cairn/engines/temporal/driver/neptune/operations/has_episode_edge_ops.py +155 -0
  351. cairn/engines/temporal/driver/neptune/operations/next_episode_edge_ops.py +155 -0
  352. cairn/engines/temporal/driver/neptune/operations/saga_node_ops.py +172 -0
  353. cairn/engines/temporal/driver/neptune/operations/search_ops.py +672 -0
  354. cairn/engines/temporal/driver/neptune_driver.py +379 -0
  355. cairn/engines/temporal/driver/operations/__init__.py +25 -0
  356. cairn/engines/temporal/driver/operations/community_edge_ops.py +53 -0
  357. cairn/engines/temporal/driver/operations/community_node_ops.py +79 -0
  358. cairn/engines/temporal/driver/operations/entity_edge_ops.py +92 -0
  359. cairn/engines/temporal/driver/operations/entity_node_ops.py +87 -0
  360. cairn/engines/temporal/driver/operations/episode_node_ops.py +91 -0
  361. cairn/engines/temporal/driver/operations/episodic_edge_ops.py +62 -0
  362. cairn/engines/temporal/driver/operations/graph_ops.py +78 -0
  363. cairn/engines/temporal/driver/operations/graph_utils.py +49 -0
  364. cairn/engines/temporal/driver/operations/has_episode_edge_ops.py +62 -0
  365. cairn/engines/temporal/driver/operations/next_episode_edge_ops.py +62 -0
  366. cairn/engines/temporal/driver/operations/saga_node_ops.py +72 -0
  367. cairn/engines/temporal/driver/operations/search_ops.py +149 -0
  368. cairn/engines/temporal/driver/query_executor.py +25 -0
  369. cairn/engines/temporal/driver/record_parsers.py +104 -0
  370. cairn/engines/temporal/driver/search_interface/__init__.py +0 -0
  371. cairn/engines/temporal/driver/search_interface/search_interface.py +334 -0
  372. cairn/engines/temporal/edges.py +1032 -0
  373. cairn/engines/temporal/embedder/__init__.py +10 -0
  374. cairn/engines/temporal/embedder/client.py +23 -0
  375. cairn/engines/temporal/embedder/local.py +73 -0
  376. cairn/engines/temporal/engine.py +1756 -0
  377. cairn/engines/temporal/engine_types.py +17 -0
  378. cairn/engines/temporal/errors.py +78 -0
  379. cairn/engines/temporal/formatting.py +98 -0
  380. cairn/engines/temporal/graph_queries.py +175 -0
  381. cairn/engines/temporal/helpers.py +209 -0
  382. cairn/engines/temporal/llm_client/__init__.py +19 -0
  383. cairn/engines/temporal/llm_client/cache.py +52 -0
  384. cairn/engines/temporal/llm_client/client.py +270 -0
  385. cairn/engines/temporal/llm_client/config.py +47 -0
  386. cairn/engines/temporal/llm_client/errors.py +22 -0
  387. cairn/engines/temporal/llm_client/local_extractor_client.py +305 -0
  388. cairn/engines/temporal/llm_client/router_client.py +311 -0
  389. cairn/engines/temporal/llm_client/token_tracker.py +136 -0
  390. cairn/engines/temporal/llm_client/utils.py +18 -0
  391. cairn/engines/temporal/migrations/__init__.py +133 -0
  392. cairn/engines/temporal/models/__init__.py +0 -0
  393. cairn/engines/temporal/models/edges/__init__.py +0 -0
  394. cairn/engines/temporal/models/edges/edge_db_queries.py +327 -0
  395. cairn/engines/temporal/models/nodes/__init__.py +0 -0
  396. cairn/engines/temporal/models/nodes/node_db_queries.py +382 -0
  397. cairn/engines/temporal/namespaces/__init__.py +7 -0
  398. cairn/engines/temporal/namespaces/edges.py +339 -0
  399. cairn/engines/temporal/namespaces/nodes.py +339 -0
  400. cairn/engines/temporal/nodes.py +1106 -0
  401. cairn/engines/temporal/ontology.py +512 -0
  402. cairn/engines/temporal/prompts/__init__.py +4 -0
  403. cairn/engines/temporal/prompts/dedupe_edges.py +87 -0
  404. cairn/engines/temporal/prompts/dedupe_nodes.py +209 -0
  405. cairn/engines/temporal/prompts/eval.py +148 -0
  406. cairn/engines/temporal/prompts/extract_edges.py +293 -0
  407. cairn/engines/temporal/prompts/extract_nodes.py +638 -0
  408. cairn/engines/temporal/prompts/extract_nodes_and_edges.py +296 -0
  409. cairn/engines/temporal/prompts/lib.py +86 -0
  410. cairn/engines/temporal/prompts/models.py +16 -0
  411. cairn/engines/temporal/prompts/prompt_helpers.py +24 -0
  412. cairn/engines/temporal/prompts/snippets.py +18 -0
  413. cairn/engines/temporal/prompts/summarize_nodes.py +126 -0
  414. cairn/engines/temporal/prompts/summarize_sagas.py +123 -0
  415. cairn/engines/temporal/py.typed +1 -0
  416. cairn/engines/temporal/queue.py +100 -0
  417. cairn/engines/temporal/search/__init__.py +0 -0
  418. cairn/engines/temporal/search/search.py +859 -0
  419. cairn/engines/temporal/search/search_config.py +144 -0
  420. cairn/engines/temporal/search/search_config_recipes.py +207 -0
  421. cairn/engines/temporal/search/search_filters.py +267 -0
  422. cairn/engines/temporal/search/search_helpers.py +56 -0
  423. cairn/engines/temporal/search/search_utils.py +2048 -0
  424. cairn/engines/temporal/service.py +951 -0
  425. cairn/engines/temporal/tracer.py +177 -0
  426. cairn/engines/temporal/utils/__init__.py +0 -0
  427. cairn/engines/temporal/utils/bulk_utils.py +618 -0
  428. cairn/engines/temporal/utils/content_chunking.py +810 -0
  429. cairn/engines/temporal/utils/datetime_utils.py +44 -0
  430. cairn/engines/temporal/utils/maintenance/__init__.py +11 -0
  431. cairn/engines/temporal/utils/maintenance/attribute_utils.py +238 -0
  432. cairn/engines/temporal/utils/maintenance/combined_extraction.py +297 -0
  433. cairn/engines/temporal/utils/maintenance/community_operations.py +367 -0
  434. cairn/engines/temporal/utils/maintenance/dedup_helpers.py +280 -0
  435. cairn/engines/temporal/utils/maintenance/edge_operations.py +895 -0
  436. cairn/engines/temporal/utils/maintenance/graph_data_operations.py +151 -0
  437. cairn/engines/temporal/utils/maintenance/node_operations.py +1019 -0
  438. cairn/engines/temporal/utils/ontology_utils/__init__.py +0 -0
  439. cairn/engines/temporal/utils/ontology_utils/entity_types_utils.py +21 -0
  440. cairn/engines/temporal/utils/text_utils.py +59 -0
  441. cairn/engines/vectors.py +594 -0
  442. cairn/engines/workflow/__init__.py +482 -0
  443. cairn/engines/workflow/_agent_config.py +56 -0
  444. cairn/engines/workflow/_assets.py +176 -0
  445. cairn/engines/workflow/_console.py +273 -0
  446. cairn/engines/workflow/_download_security.py +1275 -0
  447. cairn/engines/workflow/_init_options.py +83 -0
  448. cairn/engines/workflow/_installed_list_json.py +84 -0
  449. cairn/engines/workflow/_invocation_style.py +81 -0
  450. cairn/engines/workflow/_project.py +78 -0
  451. cairn/engines/workflow/_toml_string.py +60 -0
  452. cairn/engines/workflow/_utils.py +375 -0
  453. cairn/engines/workflow/agents.py +1390 -0
  454. cairn/engines/workflow/artifacts/__init__.py +37 -0
  455. cairn/engines/workflow/artifacts/_commands.py +89 -0
  456. cairn/engines/workflow/artifacts/_identifiers.py +162 -0
  457. cairn/engines/workflow/artifacts/catalog.py +1163 -0
  458. cairn/engines/workflow/artifacts/command_info.py +64 -0
  459. cairn/engines/workflow/artifacts/command_list.py +42 -0
  460. cairn/engines/workflow/artifacts/command_lookup.py +55 -0
  461. cairn/engines/workflow/artifacts/models.py +182 -0
  462. cairn/engines/workflow/artifacts/resolution.py +498 -0
  463. cairn/engines/workflow/assets/bundles/assess/README.md +40 -0
  464. cairn/engines/workflow/assets/bundles/assess/bundle.yml +25 -0
  465. cairn/engines/workflow/assets/bundles/bugfix/README.md +38 -0
  466. cairn/engines/workflow/assets/bundles/bugfix/bundle.yml +25 -0
  467. cairn/engines/workflow/assets/bundles/catalog.json +51 -0
  468. cairn/engines/workflow/assets/commands/analyze.md +255 -0
  469. cairn/engines/workflow/assets/commands/checklist.md +379 -0
  470. cairn/engines/workflow/assets/commands/clarify.md +301 -0
  471. cairn/engines/workflow/assets/commands/constitution.md +179 -0
  472. cairn/engines/workflow/assets/commands/converge.md +279 -0
  473. cairn/engines/workflow/assets/commands/implement.md +233 -0
  474. cairn/engines/workflow/assets/commands/plan.md +178 -0
  475. cairn/engines/workflow/assets/commands/specify.md +345 -0
  476. cairn/engines/workflow/assets/commands/tasks.md +233 -0
  477. cairn/engines/workflow/assets/commands/taskstoissues.md +106 -0
  478. cairn/engines/workflow/assets/extensions/agent-context/README.md +63 -0
  479. cairn/engines/workflow/assets/extensions/agent-context/agent-context-config.yml +24 -0
  480. cairn/engines/workflow/assets/extensions/agent-context/agent-context-defaults.json +43 -0
  481. cairn/engines/workflow/assets/extensions/agent-context/commands/cairn.agent-context.update.md +27 -0
  482. cairn/engines/workflow/assets/extensions/agent-context/extension.yml +34 -0
  483. cairn/engines/workflow/assets/extensions/agent-context/scripts/bash/update-agent-context.sh +453 -0
  484. cairn/engines/workflow/assets/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +509 -0
  485. cairn/engines/workflow/assets/extensions/agent-context/scripts/python/update_agent_context.py +368 -0
  486. cairn/engines/workflow/assets/extensions/assess/README.md +127 -0
  487. cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.decide.md +97 -0
  488. cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.define.md +85 -0
  489. cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.intake.md +120 -0
  490. cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.research.md +102 -0
  491. cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.shape.md +82 -0
  492. cairn/engines/workflow/assets/extensions/assess/extension.yml +40 -0
  493. cairn/engines/workflow/assets/extensions/bug/README.md +80 -0
  494. cairn/engines/workflow/assets/extensions/bug/commands/cairn.bug.assess.md +173 -0
  495. cairn/engines/workflow/assets/extensions/bug/commands/cairn.bug.fix.md +112 -0
  496. cairn/engines/workflow/assets/extensions/bug/commands/cairn.bug.test.md +117 -0
  497. cairn/engines/workflow/assets/extensions/bug/extension.yml +31 -0
  498. cairn/engines/workflow/assets/extensions/catalog.json +67 -0
  499. cairn/engines/workflow/assets/extensions/git/README.md +119 -0
  500. cairn/engines/workflow/assets/extensions/git/commands/cairn.git.commit.md +63 -0
  501. cairn/engines/workflow/assets/extensions/git/commands/cairn.git.feature.md +82 -0
  502. cairn/engines/workflow/assets/extensions/git/commands/cairn.git.initialize.md +49 -0
  503. cairn/engines/workflow/assets/extensions/git/commands/cairn.git.remote.md +45 -0
  504. cairn/engines/workflow/assets/extensions/git/commands/cairn.git.validate.md +49 -0
  505. cairn/engines/workflow/assets/extensions/git/config-template.yml +79 -0
  506. cairn/engines/workflow/assets/extensions/git/extension.yml +142 -0
  507. cairn/engines/workflow/assets/extensions/git/git-config.yml +79 -0
  508. cairn/engines/workflow/assets/extensions/git/scripts/bash/auto-commit.sh +211 -0
  509. cairn/engines/workflow/assets/extensions/git/scripts/bash/create-new-feature-branch.sh +639 -0
  510. cairn/engines/workflow/assets/extensions/git/scripts/bash/git-common.sh +56 -0
  511. cairn/engines/workflow/assets/extensions/git/scripts/bash/initialize-repo.sh +54 -0
  512. cairn/engines/workflow/assets/extensions/git/scripts/powershell/auto-commit.ps1 +230 -0
  513. cairn/engines/workflow/assets/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +594 -0
  514. cairn/engines/workflow/assets/extensions/git/scripts/powershell/git-common.ps1 +52 -0
  515. cairn/engines/workflow/assets/extensions/git/scripts/powershell/initialize-repo.ps1 +69 -0
  516. cairn/engines/workflow/assets/extensions/git/scripts/python/auto_commit.py +195 -0
  517. cairn/engines/workflow/assets/extensions/git/scripts/python/create_new_feature_branch.py +641 -0
  518. cairn/engines/workflow/assets/extensions/git/scripts/python/git_common.py +81 -0
  519. cairn/engines/workflow/assets/extensions/git/scripts/python/initialize_repo.py +89 -0
  520. cairn/engines/workflow/assets/integrations/catalog.json +376 -0
  521. cairn/engines/workflow/assets/presets/catalog.json +53 -0
  522. cairn/engines/workflow/assets/presets/constitution-sync/README.md +135 -0
  523. cairn/engines/workflow/assets/presets/constitution-sync/commands/cairn.constitution.md +54 -0
  524. cairn/engines/workflow/assets/presets/constitution-sync/preset.yml +30 -0
  525. cairn/engines/workflow/assets/presets/lean/README.md +45 -0
  526. cairn/engines/workflow/assets/presets/lean/commands/cairn.constitution.md +33 -0
  527. cairn/engines/workflow/assets/presets/lean/commands/cairn.implement.md +24 -0
  528. cairn/engines/workflow/assets/presets/lean/commands/cairn.plan.md +20 -0
  529. cairn/engines/workflow/assets/presets/lean/commands/cairn.specify.md +23 -0
  530. cairn/engines/workflow/assets/presets/lean/commands/cairn.tasks.md +21 -0
  531. cairn/engines/workflow/assets/presets/lean/preset.yml +51 -0
  532. cairn/engines/workflow/assets/scripts/bash/check-prerequisites.sh +243 -0
  533. cairn/engines/workflow/assets/scripts/bash/common.sh +933 -0
  534. cairn/engines/workflow/assets/scripts/bash/create-new-feature.sh +427 -0
  535. cairn/engines/workflow/assets/scripts/bash/resolve-template.sh +57 -0
  536. cairn/engines/workflow/assets/scripts/bash/setup-plan.sh +85 -0
  537. cairn/engines/workflow/assets/scripts/bash/setup-tasks.sh +94 -0
  538. cairn/engines/workflow/assets/scripts/powershell/check-prerequisites.ps1 +185 -0
  539. cairn/engines/workflow/assets/scripts/powershell/common.ps1 +803 -0
  540. cairn/engines/workflow/assets/scripts/powershell/create-new-feature.ps1 +335 -0
  541. cairn/engines/workflow/assets/scripts/powershell/resolve-template.ps1 +38 -0
  542. cairn/engines/workflow/assets/scripts/powershell/setup-plan.ps1 +88 -0
  543. cairn/engines/workflow/assets/scripts/powershell/setup-tasks.ps1 +93 -0
  544. cairn/engines/workflow/assets/scripts/python/check_prerequisites.py +289 -0
  545. cairn/engines/workflow/assets/scripts/python/common.py +560 -0
  546. cairn/engines/workflow/assets/scripts/python/create_new_feature.py +451 -0
  547. cairn/engines/workflow/assets/scripts/python/resolve_template.py +63 -0
  548. cairn/engines/workflow/assets/scripts/python/setup_plan.py +99 -0
  549. cairn/engines/workflow/assets/scripts/python/setup_tasks.py +175 -0
  550. cairn/engines/workflow/assets/templates/checklist-template.md +45 -0
  551. cairn/engines/workflow/assets/templates/constitution-template.md +50 -0
  552. cairn/engines/workflow/assets/templates/plan-template.md +113 -0
  553. cairn/engines/workflow/assets/templates/spec-template.md +131 -0
  554. cairn/engines/workflow/assets/templates/tasks-template.md +252 -0
  555. cairn/engines/workflow/assets/templates/vscode-settings.json +13 -0
  556. cairn/engines/workflow/assets/workflows/assess/workflow.yml +62 -0
  557. cairn/engines/workflow/assets/workflows/bugfix/workflow.yml +50 -0
  558. cairn/engines/workflow/assets/workflows/cairn/workflow.yml +74 -0
  559. cairn/engines/workflow/assets/workflows/catalog.json +37 -0
  560. cairn/engines/workflow/assets/workflows/step-catalog.json +6 -0
  561. cairn/engines/workflow/authentication/__init__.py +50 -0
  562. cairn/engines/workflow/authentication/azure_devops.py +188 -0
  563. cairn/engines/workflow/authentication/base.py +57 -0
  564. cairn/engines/workflow/authentication/config.py +245 -0
  565. cairn/engines/workflow/authentication/github.py +24 -0
  566. cairn/engines/workflow/authentication/github_http.py +300 -0
  567. cairn/engines/workflow/authentication/http.py +233 -0
  568. cairn/engines/workflow/bundler.py +5 -0
  569. cairn/engines/workflow/bundles/__init__.py +20 -0
  570. cairn/engines/workflow/bundles/_commands.py +165 -0
  571. cairn/engines/workflow/bundles/adapters.py +313 -0
  572. cairn/engines/workflow/bundles/catalog/__init__.py +20 -0
  573. cairn/engines/workflow/bundles/catalog/command_add.py +48 -0
  574. cairn/engines/workflow/bundles/catalog/command_list.py +38 -0
  575. cairn/engines/workflow/bundles/catalog/command_remove.py +31 -0
  576. cairn/engines/workflow/bundles/catalog_config.py +252 -0
  577. cairn/engines/workflow/bundles/catalog_stack.py +122 -0
  578. cairn/engines/workflow/bundles/catalogs.py +332 -0
  579. cairn/engines/workflow/bundles/command_add.py +32 -0
  580. cairn/engines/workflow/bundles/command_build.py +38 -0
  581. cairn/engines/workflow/bundles/command_info.py +157 -0
  582. cairn/engines/workflow/bundles/command_init.py +51 -0
  583. cairn/engines/workflow/bundles/command_install.py +137 -0
  584. cairn/engines/workflow/bundles/command_list.py +45 -0
  585. cairn/engines/workflow/bundles/command_remove.py +32 -0
  586. cairn/engines/workflow/bundles/command_search.py +65 -0
  587. cairn/engines/workflow/bundles/command_update.py +69 -0
  588. cairn/engines/workflow/bundles/command_validate.py +65 -0
  589. cairn/engines/workflow/bundles/conflict.py +54 -0
  590. cairn/engines/workflow/bundles/installer.py +276 -0
  591. cairn/engines/workflow/bundles/manifest.py +292 -0
  592. cairn/engines/workflow/bundles/packager.py +153 -0
  593. cairn/engines/workflow/bundles/primitives.py +500 -0
  594. cairn/engines/workflow/bundles/project.py +102 -0
  595. cairn/engines/workflow/bundles/records.py +245 -0
  596. cairn/engines/workflow/bundles/references.py +127 -0
  597. cairn/engines/workflow/bundles/resolver.py +132 -0
  598. cairn/engines/workflow/bundles/sources.py +360 -0
  599. cairn/engines/workflow/bundles/validator.py +60 -0
  600. cairn/engines/workflow/bundles/versioning.py +99 -0
  601. cairn/engines/workflow/bundles/yamlio.py +202 -0
  602. cairn/engines/workflow/catalogs.py +197 -0
  603. cairn/engines/workflow/cli.py +58 -0
  604. cairn/engines/workflow/command_check.py +52 -0
  605. cairn/engines/workflow/command_init.py +1202 -0
  606. cairn/engines/workflow/command_version.py +102 -0
  607. cairn/engines/workflow/events/__init__.py +2644 -0
  608. cairn/engines/workflow/events/command_run.py +54 -0
  609. cairn/engines/workflow/extensions/__init__.py +5478 -0
  610. cairn/engines/workflow/extensions/_command_update_artifacts.py +164 -0
  611. cairn/engines/workflow/extensions/_command_update_discovery.py +145 -0
  612. cairn/engines/workflow/extensions/_command_update_transaction.py +871 -0
  613. cairn/engines/workflow/extensions/_commands.py +690 -0
  614. cairn/engines/workflow/extensions/catalog/__init__.py +32 -0
  615. cairn/engines/workflow/extensions/catalog/_helpers.py +37 -0
  616. cairn/engines/workflow/extensions/catalog/command_add.py +114 -0
  617. cairn/engines/workflow/extensions/catalog/command_list.py +96 -0
  618. cairn/engines/workflow/extensions/catalog/command_remove.py +66 -0
  619. cairn/engines/workflow/extensions/command_add.py +292 -0
  620. cairn/engines/workflow/extensions/command_disable.py +62 -0
  621. cairn/engines/workflow/extensions/command_enable.py +84 -0
  622. cairn/engines/workflow/extensions/command_info.py +261 -0
  623. cairn/engines/workflow/extensions/command_list.py +75 -0
  624. cairn/engines/workflow/extensions/command_remove.py +86 -0
  625. cairn/engines/workflow/extensions/command_search.py +111 -0
  626. cairn/engines/workflow/extensions/command_set_priority.py +66 -0
  627. cairn/engines/workflow/extensions/command_update.py +25 -0
  628. cairn/engines/workflow/integration_runtime.py +113 -0
  629. cairn/engines/workflow/integration_state.py +252 -0
  630. cairn/engines/workflow/integration_status.py +663 -0
  631. cairn/engines/workflow/integrations/__init__.py +1002 -0
  632. cairn/engines/workflow/integrations/_command_scaffold_generation.py +281 -0
  633. cairn/engines/workflow/integrations/_command_upgrade_layout.py +188 -0
  634. cairn/engines/workflow/integrations/_commands.py +47 -0
  635. cairn/engines/workflow/integrations/_helpers.py +603 -0
  636. cairn/engines/workflow/integrations/agy/__init__.py +106 -0
  637. cairn/engines/workflow/integrations/alquimia/__init__.py +195 -0
  638. cairn/engines/workflow/integrations/amp/__init__.py +54 -0
  639. cairn/engines/workflow/integrations/auggie/__init__.py +21 -0
  640. cairn/engines/workflow/integrations/base.py +1910 -0
  641. cairn/engines/workflow/integrations/bob/__init__.py +373 -0
  642. cairn/engines/workflow/integrations/catalog/__init__.py +48 -0
  643. cairn/engines/workflow/integrations/catalog/command_add.py +49 -0
  644. cairn/engines/workflow/integrations/catalog/command_list.py +67 -0
  645. cairn/engines/workflow/integrations/catalog/command_remove.py +28 -0
  646. cairn/engines/workflow/integrations/claude/__init__.py +245 -0
  647. cairn/engines/workflow/integrations/cline/__init__.py +200 -0
  648. cairn/engines/workflow/integrations/codebuddy/__init__.py +21 -0
  649. cairn/engines/workflow/integrations/codex/__init__.py +80 -0
  650. cairn/engines/workflow/integrations/command_code/__init__.py +41 -0
  651. cairn/engines/workflow/integrations/command_info.py +114 -0
  652. cairn/engines/workflow/integrations/command_install.py +199 -0
  653. cairn/engines/workflow/integrations/command_list.py +114 -0
  654. cairn/engines/workflow/integrations/command_scaffold.py +55 -0
  655. cairn/engines/workflow/integrations/command_search.py +109 -0
  656. cairn/engines/workflow/integrations/command_status.py +91 -0
  657. cairn/engines/workflow/integrations/command_switch.py +369 -0
  658. cairn/engines/workflow/integrations/command_uninstall.py +126 -0
  659. cairn/engines/workflow/integrations/command_upgrade.py +356 -0
  660. cairn/engines/workflow/integrations/command_use.py +69 -0
  661. cairn/engines/workflow/integrations/copilot/__init__.py +669 -0
  662. cairn/engines/workflow/integrations/cursor_agent/__init__.py +131 -0
  663. cairn/engines/workflow/integrations/devin/__init__.py +99 -0
  664. cairn/engines/workflow/integrations/docker_agent/__init__.py +207 -0
  665. cairn/engines/workflow/integrations/droid/__init__.py +143 -0
  666. cairn/engines/workflow/integrations/dsh/__init__.py +72 -0
  667. cairn/engines/workflow/integrations/firebender/__init__.py +32 -0
  668. cairn/engines/workflow/integrations/forge/__init__.py +248 -0
  669. cairn/engines/workflow/integrations/gemini/__init__.py +49 -0
  670. cairn/engines/workflow/integrations/generic/__init__.py +258 -0
  671. cairn/engines/workflow/integrations/goose/__init__.py +111 -0
  672. cairn/engines/workflow/integrations/grok/__init__.py +68 -0
  673. cairn/engines/workflow/integrations/hermes/__init__.py +312 -0
  674. cairn/engines/workflow/integrations/junie/__init__.py +178 -0
  675. cairn/engines/workflow/integrations/kilocode/__init__.py +22 -0
  676. cairn/engines/workflow/integrations/kimi/__init__.py +380 -0
  677. cairn/engines/workflow/integrations/kiro_cli/__init__.py +36 -0
  678. cairn/engines/workflow/integrations/lingma/__init__.py +41 -0
  679. cairn/engines/workflow/integrations/manifest.py +522 -0
  680. cairn/engines/workflow/integrations/muse/__init__.py +71 -0
  681. cairn/engines/workflow/integrations/omp/__init__.py +53 -0
  682. cairn/engines/workflow/integrations/opencode/__init__.py +76 -0
  683. cairn/engines/workflow/integrations/pi/__init__.py +21 -0
  684. cairn/engines/workflow/integrations/qodercli/__init__.py +28 -0
  685. cairn/engines/workflow/integrations/qwen/__init__.py +44 -0
  686. cairn/engines/workflow/integrations/rovodev/__init__.py +264 -0
  687. cairn/engines/workflow/integrations/shai/__init__.py +21 -0
  688. cairn/engines/workflow/integrations/tabnine/__init__.py +47 -0
  689. cairn/engines/workflow/integrations/trae/__init__.py +40 -0
  690. cairn/engines/workflow/integrations/vibe/__init__.py +202 -0
  691. cairn/engines/workflow/integrations/zcode/__init__.py +42 -0
  692. cairn/engines/workflow/integrations/zed/__init__.py +33 -0
  693. cairn/engines/workflow/presets/__init__.py +6259 -0
  694. cairn/engines/workflow/presets/_commands.py +99 -0
  695. cairn/engines/workflow/presets/catalog/__init__.py +25 -0
  696. cairn/engines/workflow/presets/catalog/command_add.py +124 -0
  697. cairn/engines/workflow/presets/catalog/command_list.py +80 -0
  698. cairn/engines/workflow/presets/catalog/command_remove.py +69 -0
  699. cairn/engines/workflow/presets/command_add.py +381 -0
  700. cairn/engines/workflow/presets/command_disable.py +50 -0
  701. cairn/engines/workflow/presets/command_enable.py +49 -0
  702. cairn/engines/workflow/presets/command_info.py +96 -0
  703. cairn/engines/workflow/presets/command_list.py +89 -0
  704. cairn/engines/workflow/presets/command_remove.py +30 -0
  705. cairn/engines/workflow/presets/command_resolve.py +121 -0
  706. cairn/engines/workflow/presets/command_search.py +46 -0
  707. cairn/engines/workflow/presets/command_set_priority.py +70 -0
  708. cairn/engines/workflow/presets/command_update.py +99 -0
  709. cairn/engines/workflow/shared_infra.py +769 -0
  710. cairn/engines/workflow/workflows/__init__.py +213 -0
  711. cairn/engines/workflow/workflows/_command_resume_state.py +49 -0
  712. cairn/engines/workflow/workflows/_command_run_ownership.py +188 -0
  713. cairn/engines/workflow/workflows/_commands.py +1536 -0
  714. cairn/engines/workflow/workflows/base.py +149 -0
  715. cairn/engines/workflow/workflows/catalog/__init__.py +60 -0
  716. cairn/engines/workflow/workflows/catalog/_domain.py +831 -0
  717. cairn/engines/workflow/workflows/catalog/command_add.py +31 -0
  718. cairn/engines/workflow/workflows/catalog/command_list.py +38 -0
  719. cairn/engines/workflow/workflows/catalog/command_remove.py +26 -0
  720. cairn/engines/workflow/workflows/command_add.py +508 -0
  721. cairn/engines/workflow/workflows/command_disable.py +16 -0
  722. cairn/engines/workflow/workflows/command_enable.py +13 -0
  723. cairn/engines/workflow/workflows/command_info.py +121 -0
  724. cairn/engines/workflow/workflows/command_list.py +36 -0
  725. cairn/engines/workflow/workflows/command_remove.py +127 -0
  726. cairn/engines/workflow/workflows/command_resolve.py +17 -0
  727. cairn/engines/workflow/workflows/command_resume.py +113 -0
  728. cairn/engines/workflow/workflows/command_run.py +168 -0
  729. cairn/engines/workflow/workflows/command_search.py +43 -0
  730. cairn/engines/workflow/workflows/command_status.py +129 -0
  731. cairn/engines/workflow/workflows/command_update.py +149 -0
  732. cairn/engines/workflow/workflows/engine.py +1845 -0
  733. cairn/engines/workflow/workflows/expressions.py +1312 -0
  734. cairn/engines/workflow/workflows/overlay/__init__.py +39 -0
  735. cairn/engines/workflow/workflows/overlay/command_add.py +23 -0
  736. cairn/engines/workflow/workflows/overlay/command_disable.py +19 -0
  737. cairn/engines/workflow/workflows/overlay/command_enable.py +19 -0
  738. cairn/engines/workflow/workflows/overlay/command_list.py +18 -0
  739. cairn/engines/workflow/workflows/overlay/command_remove.py +19 -0
  740. cairn/engines/workflow/workflows/overlay/command_set_priority.py +24 -0
  741. cairn/engines/workflow/workflows/overlay/composer.py +97 -0
  742. cairn/engines/workflow/workflows/overlay/layer_sources.py +250 -0
  743. cairn/engines/workflow/workflows/overlay/merge.py +466 -0
  744. cairn/engines/workflow/workflows/overlay/operations.py +509 -0
  745. cairn/engines/workflow/workflows/overlay/resolver.py +95 -0
  746. cairn/engines/workflow/workflows/overlay/schema.py +187 -0
  747. cairn/engines/workflow/workflows/step/__init__.py +28 -0
  748. cairn/engines/workflow/workflows/step/_helpers.py +107 -0
  749. cairn/engines/workflow/workflows/step/catalog/__init__.py +48 -0
  750. cairn/engines/workflow/workflows/step/catalog/_domain.py +710 -0
  751. cairn/engines/workflow/workflows/step/catalog/command_add.py +34 -0
  752. cairn/engines/workflow/workflows/step/catalog/command_list.py +38 -0
  753. cairn/engines/workflow/workflows/step/catalog/command_remove.py +27 -0
  754. cairn/engines/workflow/workflows/step/command/__init__.py +383 -0
  755. cairn/engines/workflow/workflows/step/command_add.py +376 -0
  756. cairn/engines/workflow/workflows/step/command_info.py +78 -0
  757. cairn/engines/workflow/workflows/step/command_list.py +49 -0
  758. cairn/engines/workflow/workflows/step/command_remove.py +97 -0
  759. cairn/engines/workflow/workflows/step/command_search.py +47 -0
  760. cairn/engines/workflow/workflows/step/do_while/__init__.py +163 -0
  761. cairn/engines/workflow/workflows/step/fan_in/__init__.py +132 -0
  762. cairn/engines/workflow/workflows/step/fan_out/__init__.py +107 -0
  763. cairn/engines/workflow/workflows/step/gate/__init__.py +340 -0
  764. cairn/engines/workflow/workflows/step/if_then/__init__.py +145 -0
  765. cairn/engines/workflow/workflows/step/init/__init__.py +321 -0
  766. cairn/engines/workflow/workflows/step/prompt/__init__.py +306 -0
  767. cairn/engines/workflow/workflows/step/shell/__init__.py +169 -0
  768. cairn/engines/workflow/workflows/step/slot/__init__.py +67 -0
  769. cairn/engines/workflow/workflows/step/switch/__init__.py +141 -0
  770. cairn/engines/workflow/workflows/step/while_loop/__init__.py +176 -0
  771. cairn/graphviews.py +169 -0
  772. cairn/hooks.py +153 -0
  773. cairn/linker.py +102 -0
  774. cairn/mcp_server.py +304 -0
  775. cairn/platform/__init__.py +21 -0
  776. cairn/platform/commands.py +433 -0
  777. cairn/platform/config.py +149 -0
  778. cairn/platform/db.py +193 -0
  779. cairn/platform/errors.py +45 -0
  780. cairn/platform/gitops.py +158 -0
  781. cairn/platform/models.py +247 -0
  782. cairn/platform/rbac.py +97 -0
  783. cairn/platform/security.py +193 -0
  784. cairn/platform/service.py +1442 -0
  785. cairn/platform/web.py +931 -0
  786. cairn/project.py +337 -0
  787. cairn/router.py +390 -0
  788. cairn/server.py +1225 -0
  789. cairn/shellcmd.py +114 -0
  790. cairn/standup.py +109 -0
  791. cairn/store.py +355 -0
  792. cairn/sync.py +202 -0
  793. cairn/templates/claude/agents/cairn-architect.md +11 -0
  794. cairn/templates/claude/agents/cairn-implementer.md +10 -0
  795. cairn/templates/claude/agents/cairn-reviewer.md +10 -0
  796. cairn/templates/claude/agents/cairn-scout.md +11 -0
  797. cairn/templates/claude/commands/ask.md +6 -0
  798. cairn/templates/claude/commands/brief.md +5 -0
  799. cairn/templates/claude/commands/drift.md +7 -0
  800. cairn/templates/claude/commands/impact.md +7 -0
  801. cairn/templates/claude/commands/remember.md +7 -0
  802. cairn/templates/claude/commands/why.md +7 -0
  803. cairn/templates/instructions.md +13 -0
  804. cairn/ui/app/api.js +298 -0
  805. cairn/ui/app/components/archmap.js +97 -0
  806. cairn/ui/app/components/communitymap.js +227 -0
  807. cairn/ui/app/components/narrate.js +133 -0
  808. cairn/ui/app/components/repomap.js +131 -0
  809. cairn/ui/app/components/scalebar.js +29 -0
  810. cairn/ui/app/components/shell.js +293 -0
  811. cairn/ui/app/components/stepper.js +41 -0
  812. cairn/ui/app/components/traversal.js +122 -0
  813. cairn/ui/app/components/ui.js +170 -0
  814. cairn/ui/app/lib/contour.js +74 -0
  815. cairn/ui/app/lib/cyto.js +45 -0
  816. cairn/ui/app/lib/format.js +114 -0
  817. cairn/ui/app/lib/md.js +38 -0
  818. cairn/ui/app/main.js +219 -0
  819. cairn/ui/app/router.js +80 -0
  820. cairn/ui/app/state.js +81 -0
  821. cairn/ui/app/views/account.js +69 -0
  822. cairn/ui/app/views/auth.js +142 -0
  823. cairn/ui/app/views/impact.js +167 -0
  824. cairn/ui/app/views/map.js +735 -0
  825. cairn/ui/app/views/memory.js +168 -0
  826. cairn/ui/app/views/overview.js +253 -0
  827. cairn/ui/app/views/sessions.js +278 -0
  828. cairn/ui/app/views/settings.js +122 -0
  829. cairn/ui/app/views/specs.js +259 -0
  830. cairn/ui/app/views/team.js +326 -0
  831. cairn/ui/app/views/timeline.js +230 -0
  832. cairn/ui/app/views/users.js +36 -0
  833. cairn/ui/fonts/OFL-mono.txt +93 -0
  834. cairn/ui/fonts/OFL.txt +93 -0
  835. cairn/ui/fonts/overpass-latin-ext-wght-normal.woff2 +0 -0
  836. cairn/ui/fonts/overpass-latin-wght-normal.woff2 +0 -0
  837. cairn/ui/fonts/overpass-mono-latin-ext-wght-normal.woff2 +0 -0
  838. cairn/ui/fonts/overpass-mono-latin-wght-normal.woff2 +0 -0
  839. cairn/ui/index.html +40 -0
  840. cairn/ui/mock/data/cairn.json +1 -0
  841. cairn/ui/mock/data/platform.json +1 -0
  842. cairn/ui/mock/mock.js +1053 -0
  843. cairn/ui/mock/views/callflow.html +8 -0
  844. cairn/ui/mock/views/graph.html +8 -0
  845. cairn/ui/mock/views/tree.html +8 -0
  846. cairn/ui/styles/base.css +308 -0
  847. cairn/ui/styles/shell.css +159 -0
  848. cairn/ui/styles/tokens.css +99 -0
  849. cairn/ui/styles/views.css +791 -0
  850. cairn/ui/vendor/cose-base/LICENSE +21 -0
  851. cairn/ui/vendor/cose-base/cose-base.js +3214 -0
  852. cairn/ui/vendor/cytoscape/LICENSE +19 -0
  853. cairn/ui/vendor/cytoscape/cytoscape.min.js +31 -0
  854. cairn/ui/vendor/cytoscape-fcose/LICENSE +19 -0
  855. cairn/ui/vendor/cytoscape-fcose/cytoscape-fcose.js +1549 -0
  856. cairn/ui/vendor/d3-hierarchy/LICENSE +13 -0
  857. cairn/ui/vendor/d3-hierarchy/d3-hierarchy.min.js +2 -0
  858. cairn/ui/vendor/dompurify/LICENSE +202 -0
  859. cairn/ui/vendor/dompurify/LICENSE-MPL +373 -0
  860. cairn/ui/vendor/dompurify/purify.es.mjs +2463 -0
  861. cairn/ui/vendor/htm/LICENSE +202 -0
  862. cairn/ui/vendor/htm/htm.module.js +1 -0
  863. cairn/ui/vendor/layout-base/LICENSE +21 -0
  864. cairn/ui/vendor/layout-base/layout-base.js +5230 -0
  865. cairn/ui/vendor/marked/LICENSE.md +44 -0
  866. cairn/ui/vendor/marked/marked.esm.js +2189 -0
  867. cairn/ui/vendor/preact/LICENSE +21 -0
  868. cairn/ui/vendor/preact/hooks.module.js +2 -0
  869. cairn/ui/vendor/preact/preact.module.js +2 -0
  870. cairn_brain-0.2.1.dist-info/METADATA +302 -0
  871. cairn_brain-0.2.1.dist-info/RECORD +875 -0
  872. cairn_brain-0.2.1.dist-info/WHEEL +4 -0
  873. cairn_brain-0.2.1.dist-info/entry_points.txt +2 -0
  874. cairn_brain-0.2.1.dist-info/licenses/LICENSE +202 -0
  875. cairn_brain-0.2.1.dist-info/licenses/NOTICE +24 -0
cairn/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
cairn/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
cairn/agents.py ADDED
@@ -0,0 +1,538 @@
1
+ """Agent integrations: one MCP server + generated instructions, per detected agent, plus session capture and
2
+ memory at session start.
3
+
4
+ Every agent starts a session knowing the same thing: the Cairn brief (map hubs, active spec, principles, team
5
+ knowledge) and the session-memory timeline (`agent_context`), computed for the repository it works in. Agents
6
+ with a session-start hook (Claude Code, Gemini CLI, Codex, Cursor) and OpenCode (a plugin that adds it to the
7
+ system prompt) get it fresh at session start. Agents that only read instruction files get it from an untracked
8
+ file of their own (listed in `.git/info/exclude`, never committed), rewritten after each session summary, on
9
+ `cairn sync` and when a memory is stored (`refresh_context`). Nothing dynamic goes into a committed file.
10
+ Session capture rides on each agent's own hooks, written into the repository (`engines/recall/integrations.py`).
11
+
12
+ Every write is idempotent and reversible: JSON configs are merged key-by-key, Markdown files get a
13
+ marked block, and generated files carry a header so `cairn uninstall` removes exactly what it added.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import re
19
+ import shutil
20
+ import subprocess
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ from . import shellcmd
25
+ from .project import Project
26
+
27
+ TEMPLATES = Path(__file__).resolve().parent / "templates"
28
+ BEGIN, END = "<!-- cairn:begin -->", "<!-- cairn:end -->"
29
+ GENERATED = "<!-- generated by cairn; safe to edit, removed by `cairn uninstall` -->"
30
+
31
+ AGENTS = {
32
+ "claude": "Claude Code",
33
+ "codex": "Codex",
34
+ "cursor": "Cursor",
35
+ "gemini": "Gemini CLI",
36
+ "opencode": "OpenCode",
37
+ "copilot": "Copilot CLI",
38
+ "vscode": "VS Code / Copilot",
39
+ "windsurf": "Windsurf",
40
+ "antigravity": "Antigravity",
41
+ }
42
+ # Agents that only read instruction files: (untracked file, frontmatter, size limit). Copilot CLI and VS Code
43
+ # read .github/instructions/*.instructions.md (Copilot CLI 1.0 ignores what its session hooks print);
44
+ # Antigravity and Windsurf read always-on rules.
45
+ CONTEXT_FILES = {
46
+ "copilot": (".github/instructions/cairn-context.instructions.md", '---\napplyTo: "**"\n---\n', None),
47
+ "vscode": (".github/instructions/cairn-context.instructions.md", '---\napplyTo: "**"\n---\n', None),
48
+ "antigravity": (".agents/rules/cairn-context.md", "---\ntrigger: always_on\n---\n", None),
49
+ "windsurf": (".windsurf/rules/cairn-context.md", "---\ntrigger: always_on\n---\n", 12_000),
50
+ }
51
+ # The files are plain Markdown: Copilot CLI drops unknown tags from instruction files together with their
52
+ # content. The two HTML comments (which it drops cleanly) only mark the memory, so captured tool output
53
+ # loses everything between them and memory is never built out of memory.
54
+ CONTEXT_OPEN, CONTEXT_CLOSE = "<!-- cairn:memory:begin -->", "<!-- cairn:memory:end -->"
55
+ CONTEXT_HEADING = "# Cairn: what this repository remembers"
56
+ CONTEXT_NOTE = ("<!-- generated by cairn: this repository's memory for agents that read instruction files; "
57
+ "refreshed after each session summary and on `cairn sync`; never committed; removed by "
58
+ "`cairn uninstall` -->")
59
+ MAP_IGNORE = "# cairn: agent hook folders\n.codex/\n.opencode/\n.github/hooks/\n"
60
+
61
+
62
+ def command() -> tuple[str, list[str]]:
63
+ """How agents launch the MCP server. Prefer the portable `cairn` on PATH."""
64
+ if shutil.which("cairn"):
65
+ return "cairn", ["mcp"]
66
+ return sys.executable, ["-m", "cairn", "mcp"]
67
+
68
+
69
+ def detect(project: Project) -> list[str]:
70
+ home, root = Path.home(), project.root
71
+ found = []
72
+ apps = Path("/Applications")
73
+ checks = {
74
+ "claude": [shutil.which("claude"), (home / ".claude").is_dir(), (root / ".claude").is_dir(), (root / "CLAUDE.md").exists()],
75
+ "codex": [shutil.which("codex"), (home / ".codex").is_dir()],
76
+ "cursor": [shutil.which("cursor"), shutil.which("cursor-agent"), (home / ".cursor").is_dir(),
77
+ (root / ".cursor").is_dir()],
78
+ "gemini": [shutil.which("gemini"), (home / ".gemini").is_dir(), (root / "GEMINI.md").exists()],
79
+ "opencode": [shutil.which("opencode"), (home / ".config" / "opencode").is_dir(), (root / ".opencode").is_dir(),
80
+ (root / "opencode.json").exists()],
81
+ "copilot": [shutil.which("copilot"), (home / ".copilot").is_dir()],
82
+ "vscode": [shutil.which("code"), (root / ".vscode").is_dir()],
83
+ "windsurf": [shutil.which("windsurf"), (home / ".codeium" / "windsurf").is_dir(), (root / ".windsurf").is_dir(),
84
+ (apps / "Windsurf.app").exists()],
85
+ "antigravity": [shutil.which("agy"), shutil.which("antigravity"), (apps / "Antigravity.app").exists()],
86
+ }
87
+ for name, signals in checks.items():
88
+ if any(signals):
89
+ found.append(name)
90
+ return found
91
+
92
+
93
+ def installed(project: Project) -> list[str]:
94
+ """Agents wired to this repository (not merely detected): their config launches the Cairn MCP server, or,
95
+ for agents Cairn reaches without MCP, their session-capture hooks are in place."""
96
+ root = project.root
97
+ configs = {"claude": (root / ".mcp.json", "mcpServers"), "cursor": (root / ".cursor" / "mcp.json", "mcpServers"),
98
+ "gemini": (root / ".gemini" / "settings.json", "mcpServers"),
99
+ "vscode": (root / ".vscode" / "mcp.json", "servers")}
100
+ found = []
101
+ for name in AGENTS:
102
+ if name == "codex": # user-level config written by `codex mcp add`
103
+ cfg = Path.home() / ".codex" / "config.toml"
104
+ if cfg.exists() and "mcp_servers.cairn" in cfg.read_text(errors="replace"):
105
+ found.append(name)
106
+ continue
107
+ if name not in configs: # no MCP entry: wired through capture hooks, or only through a context file
108
+ from .engines.recall import integrations as recall_hooks
109
+ state = recall_hooks.project_status(root).get(name)
110
+ if state["capture"] if state else _context_file(root, name) is not None:
111
+ found.append(name)
112
+ continue
113
+ path, key = configs[name]
114
+ try:
115
+ if "cairn" in json.loads(path.read_text()).get(key, {}):
116
+ found.append(name)
117
+ except (OSError, ValueError, AttributeError):
118
+ pass
119
+ return found
120
+
121
+
122
+ # ---- file helpers ------------------------------------------------------------------------------------
123
+ def upsert_block(path: Path, body: str) -> bool:
124
+ text = path.read_text() if path.exists() else ""
125
+ block = f"{BEGIN}\n{body.strip()}\n{END}"
126
+ if BEGIN in text and END in text:
127
+ new = text[: text.index(BEGIN)] + block + text[text.index(END) + len(END):]
128
+ else:
129
+ new = (text.rstrip() + "\n\n" if text.strip() else "") + block + "\n"
130
+ if new != text:
131
+ path.parent.mkdir(parents=True, exist_ok=True)
132
+ path.write_text(new)
133
+ return True
134
+ return False
135
+
136
+
137
+ def remove_block(path: Path) -> bool:
138
+ if not path.exists():
139
+ return False
140
+ text = path.read_text()
141
+ if BEGIN not in text:
142
+ return False
143
+ new = (text[: text.index(BEGIN)] + text[text.index(END) + len(END):]).strip()
144
+ if new:
145
+ path.write_text(new + "\n")
146
+ else:
147
+ path.unlink()
148
+ return True
149
+
150
+
151
+ def merge_json(path: Path, mutate) -> bool:
152
+ try:
153
+ data = json.loads(path.read_text()) if path.exists() else {}
154
+ except json.JSONDecodeError:
155
+ return False # never clobber a file we cannot parse
156
+ before = json.dumps(data, sort_keys=True)
157
+ mutate(data)
158
+ if json.dumps(data, sort_keys=True) != before:
159
+ path.parent.mkdir(parents=True, exist_ok=True)
160
+ path.write_text(json.dumps(data, indent=2) + "\n")
161
+ return True
162
+ return False
163
+
164
+
165
+ def write_generated(path: Path, content: str) -> bool:
166
+ if path.exists() and GENERATED not in path.read_text():
167
+ return False # user-owned file with the same name: leave it
168
+ parts = content.split("---", 2)
169
+ if content.startswith("---") and len(parts) == 3: # keep frontmatter first
170
+ content = f"---{parts[1]}---\n{GENERATED}{parts[2]}"
171
+ else:
172
+ content = f"{GENERATED}\n{content}"
173
+ if path.exists() and path.read_text() == content:
174
+ return False
175
+ path.parent.mkdir(parents=True, exist_ok=True)
176
+ path.write_text(content)
177
+ return True
178
+
179
+
180
+ def _is_ours(entry: object) -> bool:
181
+ """One hook entry Cairn wrote: its session-start brief or status line (the capture entries are recognised by
182
+ the session engine). Matched on the exact command shape, never on a path that merely contains "cairn"."""
183
+ cmd = str(entry.get("command") or "") if isinstance(entry, dict) else ""
184
+ return bool(re.search(r"(^|[\s\"'/])cairn( hook session-start| statusline)$|-m cairn (hook session-start|statusline)$",
185
+ cmd.strip()))
186
+
187
+
188
+ def _without_ours(groups: list) -> list:
189
+ """Drop Cairn's own entries from `[{matcher, hooks: [...]}]`, keeping every other entry and group."""
190
+ out = []
191
+ for g in groups if isinstance(groups, list) else []:
192
+ if isinstance(g, dict) and isinstance(g.get("hooks"), list):
193
+ hooks = [h for h in g["hooks"] if not _is_ours(h)]
194
+ if hooks:
195
+ out.append({**g, "hooks": hooks})
196
+ elif not g["hooks"]:
197
+ out.append(g)
198
+ else:
199
+ out.append(g)
200
+ return out
201
+
202
+
203
+ # ---- memory at session start ------------------------------------------------------------------------
204
+ def project_brief(root: Path | str, cairn=None) -> str:
205
+ """The Cairn brief (map hubs, active spec, principles, team knowledge): what `cairn hook session-start`
206
+ gives Claude Code. Empty when the repository has no Cairn state yet."""
207
+ if cairn is None:
208
+ from .core import Cairn
209
+ proj = Project.discover(Path(root))
210
+ if proj is None or not proj.db_path.exists():
211
+ return ""
212
+ cairn = Cairn(proj)
213
+ return cairn.brief(max_tokens=550)
214
+
215
+
216
+ def session_timeline(root: Path | str) -> str:
217
+ """The session-memory timeline (what the capture SessionStart hook gives Claude Code)."""
218
+ from .engines.recall.context import inject_context
219
+ from .engines.recall.projects import project_context
220
+ return inject_context(root, project_context(str(root)).all_projects)
221
+
222
+
223
+ def agent_context(project: Project, cairn=None) -> str:
224
+ """What every agent is given at session start: the brief, then the timeline."""
225
+ parts = (project_brief(project.root, cairn), session_timeline(project.root))
226
+ return "\n\n".join(p.strip() for p in parts if p and p.strip())
227
+
228
+
229
+ # A context file is not read the moment it is written: leave out what only makes sense right now, so it
230
+ # is rewritten only when the memory itself changes.
231
+ _VOLATILE = (re.compile(r"^(# \[[^\]\n]*\] recent context), [^\n]*$", re.MULTILINE),
232
+ re.compile(r" \((?:\d+(?:y|mo|d|h|m) ago|just now)\)"))
233
+
234
+
235
+ def _context_file(root: Path, agent: str) -> Path | None:
236
+ """The agent's context file, when Cairn wrote one."""
237
+ if agent not in CONTEXT_FILES:
238
+ return None
239
+ path = root / CONTEXT_FILES[agent][0]
240
+ try:
241
+ return path if path.exists() and CONTEXT_NOTE in path.read_text() else None
242
+ except OSError:
243
+ return None
244
+
245
+
246
+ def write_context_file(root: Path, agent: str, text: str) -> bool:
247
+ """Write the agent's untracked context file; True when it changed. A same-named file of the user's is left
248
+ alone."""
249
+ rel, header, limit = CONTEXT_FILES[agent]
250
+ path = root / rel
251
+ if path.exists() and _context_file(root, agent) is None:
252
+ return False
253
+ text = _VOLATILE[1].sub("", _VOLATILE[0].sub(r"\1", text.strip()))
254
+ if limit:
255
+ room = limit - len(header) - len(CONTEXT_NOTE) - len(CONTEXT_OPEN) - len(CONTEXT_HEADING) - len(CONTEXT_CLOSE) - 8
256
+ text = text[:room]
257
+ content = f"{header}{CONTEXT_NOTE}\n{CONTEXT_OPEN}\n{CONTEXT_HEADING}\n\n{text}\n{CONTEXT_CLOSE}\n"
258
+ if path.exists() and path.read_text() == content:
259
+ return False
260
+ path.parent.mkdir(parents=True, exist_ok=True)
261
+ path.write_text(content)
262
+ return True
263
+
264
+
265
+ def _exclude_path(project: Project) -> Path | None:
266
+ """This clone's `.git/info/exclude` (git resolves worktrees and custom git dirs)."""
267
+ if not project.is_git:
268
+ return None
269
+ rel = project.git("rev-parse", "--git-path", "info/exclude").strip()
270
+ if not rel:
271
+ return None
272
+ p = Path(rel)
273
+ return p if p.is_absolute() else (project.root / p).resolve()
274
+
275
+
276
+ def _set_excluded(project: Project, rel: str, excluded: bool) -> bool:
277
+ """Add or remove `/<rel>` in `.git/info/exclude`: untracked for this clone, without touching .gitignore."""
278
+ path = _exclude_path(project)
279
+ if path is None:
280
+ return False
281
+ line = "/" + rel
282
+ lines = path.read_text().splitlines() if path.exists() else []
283
+ if excluded == (line in lines):
284
+ return False
285
+ lines = [*lines, line] if excluded else [ln for ln in lines if ln != line]
286
+ path.parent.mkdir(parents=True, exist_ok=True)
287
+ path.write_text("\n".join(lines) + "\n")
288
+ return True
289
+
290
+
291
+ def remove_legacy_memory_block(path: Path) -> bool:
292
+ """Earlier installs kept the memory in AGENTS.md; it moved out of committed files."""
293
+ if not path.exists():
294
+ return False
295
+ text = path.read_text()
296
+ start, end = text.find(CONTEXT_OPEN), text.find(CONTEXT_CLOSE)
297
+ if start == -1 or end < start:
298
+ return False
299
+ rest = (text[:start].rstrip() + "\n\n" + text[end + len(CONTEXT_CLOSE):].lstrip()).strip()
300
+ if rest:
301
+ path.write_text(rest + "\n")
302
+ else:
303
+ path.unlink()
304
+ return True
305
+
306
+
307
+ def refresh_context(project: Project, cairn=None) -> bool:
308
+ """Rewrite the context files Cairn installed (after a session summary, on sync, when a memory is stored);
309
+ True when one changed. A no-op where none are installed."""
310
+ agents = [a for a in CONTEXT_FILES if _context_file(project.root, a) is not None]
311
+ if not agents:
312
+ return False
313
+ text = agent_context(project, cairn)
314
+ changed = [write_context_file(project.root, a, text) for a in agents] # every file, not just the first
315
+ return any(changed)
316
+
317
+
318
+ def codex_hooks_approved(project: Project) -> bool | None:
319
+ """Whether Codex has approved Cairn's hooks for this repository (read from `~/.codex/config.toml`, never
320
+ written); None when there are no Codex hooks here."""
321
+ from .engines.recall import integrations as recall_hooks
322
+ return recall_hooks.codex_hooks_approved(project.root)
323
+
324
+
325
+ def memory_status(project: Project) -> dict[str, dict]:
326
+ """Per agent: how memory reaches it at session start (`injection`, None when it doesn't) and whether its
327
+ sessions are captured (`capture`)."""
328
+ from .engines.recall import integrations as recall_hooks
329
+ root = project.root
330
+ hooks = recall_hooks.project_status(root)
331
+ brief = False
332
+ try:
333
+ brief = "cairn hook session-start" in json.dumps(
334
+ json.loads((root / ".claude" / "settings.json").read_text()).get("hooks", {}).get("SessionStart", []))
335
+ except (OSError, ValueError, AttributeError):
336
+ pass
337
+ out = {}
338
+ for agent in AGENTS:
339
+ state = hooks.get(agent, {})
340
+ injection = None
341
+ if agent == "claude":
342
+ injection = "SessionStart hook" if brief or state.get("start_hook") else None
343
+ elif agent == "opencode":
344
+ injection = "system prompt (plugin)" if state.get("start_hook") else None
345
+ elif state.get("start_hook"):
346
+ injection = "SessionStart hook"
347
+ elif _context_file(root, agent) is not None:
348
+ injection = f"{CONTEXT_FILES[agent][0]} (untracked)"
349
+ row = {"injection": injection, "capture": bool(state.get("capture"))}
350
+ if agent == "codex" and state.get("start_hook"):
351
+ row["approved"] = recall_hooks.codex_hooks_approved(root)
352
+ out[agent] = row
353
+ return out
354
+
355
+
356
+ def _hook_cmd(event: str) -> str:
357
+ """Claude Code runs hook and status-line commands through sh (Git Bash on Windows, PowerShell there when Git
358
+ Bash is missing): a bare ``cairn`` reads the same in all of them; otherwise the interpreter, spelled for them."""
359
+ if shutil.which("cairn"):
360
+ return f"cairn hook {event}"
361
+ return f"{shellcmd.python_prefix(sys.executable, 'gitbash')} -m cairn hook {event}"
362
+
363
+
364
+ def install(project: Project, agents: list[str] | None = None, capture: bool = True) -> dict[str, list[str]]:
365
+ agents = agents or detect(project)
366
+ root = project.root
367
+ cmd, args = command()
368
+ instructions = (TEMPLATES / "instructions.md").read_text()
369
+ changed: dict[str, list[str]] = {}
370
+
371
+ def note(agent, path, did):
372
+ if did:
373
+ changed.setdefault(agent, []).append(str(Path(path).relative_to(root)) if str(path).startswith(str(root)) else str(path))
374
+
375
+ note("generic", root / "AGENTS.md", upsert_block(root / "AGENTS.md", instructions))
376
+ if "claude" in agents:
377
+ note("claude", root / ".mcp.json", merge_json(root / ".mcp.json", lambda d: d.setdefault("mcpServers", {})
378
+ .update({"cairn": {"command": cmd, "args": args}})))
379
+
380
+ def settings(d):
381
+ brief = {"hooks": [{"type": "command", "command": _hook_cmd("session-start")}]}
382
+ if capture: # session memory: every lifecycle event, handled by the light `cairn.capture` entry
383
+ from .engines.recall import integrations as recall_hooks
384
+ current = dict(d.get("hooks") or {})
385
+ current["SessionStart"] = _without_ours(current.get("SessionStart", []))
386
+ ours = recall_hooks.claude_code_hooks(python=sys.executable)
387
+ ours.setdefault("SessionStart", []).append(brief) # one merge places every Cairn hook, idempotently
388
+ d["hooks"] = recall_hooks.merge_hook_groups(current, ours)
389
+ hooks = d.setdefault("hooks", {})
390
+ ss = hooks.setdefault("SessionStart", [])
391
+ if not any("cairn hook session-start" in json.dumps(h) for h in ss):
392
+ ss.append(brief)
393
+ d.setdefault("statusLine", {"type": "command", "command": _hook_cmd("statusline").replace(
394
+ "hook statusline", "statusline"), "padding": 0})
395
+ perms = d.setdefault("permissions", {}).setdefault("allow", [])
396
+ if "mcp__cairn" not in perms:
397
+ perms.append("mcp__cairn")
398
+ note("claude", root / ".claude/settings.json", merge_json(root / ".claude" / "settings.json", settings))
399
+ for f in (TEMPLATES / "claude" / "commands").glob("*.md"):
400
+ p = root / ".claude" / "commands" / "cairn" / f.name
401
+ note("claude", p, write_generated(p, f.read_text()))
402
+ for f in (TEMPLATES / "claude" / "agents").glob("*.md"):
403
+ p = root / ".claude" / "agents" / f.name
404
+ note("claude", p, write_generated(p, f.read_text()))
405
+ if capture:
406
+ from .engines.recall import integrations as recall_hooks
407
+ for name in recall_hooks.install_skills(root / ".claude" / "skills"):
408
+ note("claude", root / ".claude" / "skills" / name, True)
409
+ note("claude", root / "CLAUDE.md", upsert_block(root / "CLAUDE.md", instructions))
410
+ if "cursor" in agents:
411
+ note("cursor", root / ".cursor/mcp.json", merge_json(root / ".cursor" / "mcp.json", lambda d: d.setdefault(
412
+ "mcpServers", {}).update({"cairn": {"command": cmd, "args": args}})))
413
+ rule = "---\ndescription: Cairn project memory\nalwaysApply: true\n---\n" + instructions
414
+ p = root / ".cursor" / "rules" / "cairn.mdc"
415
+ note("cursor", p, write_generated(p, rule))
416
+ if "gemini" in agents:
417
+ note("gemini", root / ".gemini/settings.json", merge_json(root / ".gemini" / "settings.json", lambda d: d.setdefault(
418
+ "mcpServers", {}).update({"cairn": {"command": cmd, "args": args}})))
419
+ note("gemini", root / "GEMINI.md", upsert_block(root / "GEMINI.md", instructions))
420
+ if "vscode" in agents:
421
+ note("vscode", root / ".vscode/mcp.json", merge_json(root / ".vscode" / "mcp.json", lambda d: d.setdefault(
422
+ "servers", {}).update({"cairn": {"type": "stdio", "command": cmd, "args": args}})))
423
+ if "codex" in agents and shutil.which("codex"):
424
+ try:
425
+ listed = subprocess.run(["codex", "mcp", "list"], capture_output=True, text=True, timeout=20).stdout
426
+ if "cairn" not in listed:
427
+ subprocess.run(["codex", "mcp", "add", "cairn", "--", cmd, *args], capture_output=True, timeout=30)
428
+ changed.setdefault("codex", []).append("~/.codex/config.toml (codex mcp add cairn)")
429
+ except (OSError, subprocess.TimeoutExpired):
430
+ pass
431
+ # session capture (and Gemini CLI's SessionStart memory), in each agent's per-repository hook config
432
+ from .engines.recall import integrations as recall_hooks
433
+ for agent in agents:
434
+ if agent in recall_hooks.PROJECT_AGENTS and agent != "claude":
435
+ rep = recall_hooks.install_project(agent, root, python=sys.executable, capture=capture)
436
+ for path in rep["written"] + rep["removed"]:
437
+ note(agent, path, True)
438
+ if rep.get("error"):
439
+ changed.setdefault(agent, []).append(f"skipped: {rep['error']}")
440
+ ignore = project.dir / "graphignore" # the hook folders are tool config, not part of the system's map
441
+ if project.dir.is_dir() and MAP_IGNORE.split("\n", 1)[0] not in (ignore.read_text() if ignore.exists() else ""):
442
+ text = ignore.read_text() if ignore.exists() else ""
443
+ ignore.write_text(text + ("\n" if text and not text.endswith("\n") else "") + MAP_IGNORE)
444
+ # memory for the agents that only read instruction files: an untracked file each
445
+ wanted = [a for a in agents if a in CONTEXT_FILES]
446
+ if wanted:
447
+ text = agent_context(project)
448
+ for agent in wanted:
449
+ note(agent, root / CONTEXT_FILES[agent][0], write_context_file(root, agent, text))
450
+ if _context_file(root, agent) is not None:
451
+ _set_excluded(project, CONTEXT_FILES[agent][0], True)
452
+ note("generic", root / "AGENTS.md", remove_legacy_memory_block(root / "AGENTS.md"))
453
+ return changed
454
+
455
+
456
+ def connect(project: Project, server_url: str, project_id: str, env_var: str = "CAIRN_TOKEN") -> list[str]:
457
+ """Point this repository's agents at a team server's MCP endpoint instead of the local process.
458
+
459
+ The token is never written to disk: configs reference `${CAIRN_TOKEN}`, which each developer exports (the
460
+ configs are usually committed)."""
461
+ url = f"{server_url.rstrip('/')}/mcp/{project_id}/"
462
+ header = {"Authorization": f"Bearer ${{{env_var}}}"}
463
+ root, changed = project.root, []
464
+ project.set_cfg("team.server", server_url.rstrip("/")) # session capture pushes here (`cairn sessions push`)
465
+ project.set_cfg("team.project", project_id)
466
+ project.set_cfg("team.token_env", env_var) # `cairn sessions push` reads the token from the same variable
467
+ project.set_cfg("recall.worker_model", False) # the server turns pushed events into observations with a model
468
+ changed.append(".cairn/config.toml [team]")
469
+ if merge_json(root / ".mcp.json", lambda d: d.setdefault("mcpServers", {}).update(
470
+ {"cairn": {"type": "http", "url": url, "headers": header}})):
471
+ changed.append(".mcp.json")
472
+ for rel, key in ((".cursor/mcp.json", "mcpServers"), (".gemini/settings.json", "mcpServers")):
473
+ if (root / rel).exists() and merge_json(root / rel, lambda d, k=key: d.setdefault(k, {}).update(
474
+ {"cairn": {"url": url, "headers": header}})):
475
+ changed.append(rel)
476
+ if (root / ".vscode" / "mcp.json").exists() and merge_json(root / ".vscode" / "mcp.json", lambda d: d.setdefault(
477
+ "servers", {}).update({"cairn": {"type": "http", "url": url, "headers": header}})):
478
+ changed.append(".vscode/mcp.json")
479
+ return changed
480
+
481
+
482
+ def uninstall(project: Project) -> list[str]:
483
+ root, removed = project.root, []
484
+ if remove_legacy_memory_block(root / "AGENTS.md"):
485
+ removed.append("AGENTS.md (memory block)")
486
+ for agent in CONTEXT_FILES:
487
+ path = _context_file(root, agent)
488
+ if path is not None:
489
+ path.unlink()
490
+ removed.append(CONTEXT_FILES[agent][0])
491
+ if _set_excluded(project, CONTEXT_FILES[agent][0], False):
492
+ removed.append(f".git/info/exclude (/{CONTEXT_FILES[agent][0]})")
493
+ from .engines.recall import integrations as recall_hooks
494
+ for agent in recall_hooks.PROJECT_AGENTS:
495
+ if agent != "claude":
496
+ rep = recall_hooks.uninstall_project(agent, root)
497
+ removed += [str(Path(p).relative_to(root)) if str(p).startswith(str(root)) else str(p)
498
+ for p in rep["removed"]]
499
+ for md in ("AGENTS.md", "CLAUDE.md", "GEMINI.md"):
500
+ if remove_block(root / md):
501
+ removed.append(md)
502
+ def drop_server(d, k):
503
+ servers = d.get(k)
504
+ if isinstance(servers, dict) and servers.pop("cairn", None) is not None and not servers:
505
+ d.pop(k) # the map only held Cairn's entry
506
+ for rel, key in ((".mcp.json", "mcpServers"), (".cursor/mcp.json", "mcpServers"),
507
+ (".gemini/settings.json", "mcpServers"), (".vscode/mcp.json", "servers")):
508
+ if merge_json(root / rel, lambda d, k=key: drop_server(d, k)):
509
+ removed.append(rel)
510
+
511
+ def clean(d):
512
+ from .engines.recall import integrations as recall_hooks
513
+ hooks, _ = recall_hooks.remove_hook_groups(d.get("hooks") or {}) # the session engine's entries
514
+ for event in list(hooks):
515
+ hooks[event] = _without_ours(hooks[event]) # the brief; the user's own stay
516
+ if not hooks[event]:
517
+ hooks.pop(event)
518
+ if hooks:
519
+ d["hooks"] = hooks
520
+ else:
521
+ d.pop("hooks", None)
522
+ if _is_ours(d.get("statusLine", {})):
523
+ d.pop("statusLine")
524
+ allow = d.get("permissions", {}).get("allow", [])
525
+ if "mcp__cairn" in allow:
526
+ allow.remove("mcp__cairn")
527
+ if merge_json(root / ".claude" / "settings.json", clean):
528
+ removed.append(".claude/settings.json")
529
+ from .engines.recall import integrations as recall_hooks
530
+ removed += [f".claude/skills/{name}" for name in recall_hooks.uninstall_skills(root / ".claude" / "skills")]
531
+ for p in [*(root / ".claude" / "commands" / "cairn").glob("*.md"), *(root / ".claude" / "agents").glob("cairn-*.md"),
532
+ root / ".cursor" / "rules" / "cairn.mdc"]:
533
+ if p.exists() and GENERATED in p.read_text():
534
+ p.unlink()
535
+ removed.append(str(p.relative_to(root)))
536
+ if shutil.which("codex"):
537
+ subprocess.run(["codex", "mcp", "remove", "cairn"], capture_output=True, timeout=30)
538
+ return removed
cairn/capture.py ADDED
@@ -0,0 +1,36 @@
1
+ """Session capture: the agent hook entry point for Cairn's session memory (recall engine).
2
+
3
+ Runs on every prompt and tool call, so it stays tiny: standard library only, a few SQLite writes into
4
+ ``<repo>/.cairn/sessions.db``, no model, and it never breaks the agent (bad input or a locked store
5
+ is ignored, exit code 0). The Stop and SessionEnd events start the recall worker in the background,
6
+ which turns the queued events into observations and summaries (``engines/recall/worker.py``).
7
+
8
+ Usage (from an agent hook, JSON payload on stdin):
9
+ python -m cairn.capture [--platform claude-code|codex|cursor|windsurf|antigravity|gemini|raw] <event>
10
+ events: context, session-init, observation, file-context, summarize, session-end, user-message, file-edit
11
+ (the earlier names prompt, tool and stop still work)
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+
17
+ from .engines.recall.hooks import main, record # noqa: F401 (re-exported entry points)
18
+ from .engines.recall.projects import find_store_root as find_root # noqa: F401
19
+ from .engines.recall.schema import store_path # noqa: F401
20
+ from .engines.recall.tags import redact # noqa: F401
21
+
22
+
23
+ def _rel(path: str, root: Path, cwd: str) -> str:
24
+ """``path`` relative to the repository root ('' when it lies outside)."""
25
+ if not path:
26
+ return ""
27
+ p = Path(path)
28
+ p = p if p.is_absolute() else Path(cwd) / p
29
+ try:
30
+ return p.resolve().relative_to(Path(root).resolve()).as_posix()
31
+ except (ValueError, OSError):
32
+ return ""
33
+
34
+
35
+ if __name__ == "__main__":
36
+ raise SystemExit(main())