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.
- cairn/__init__.py +1 -0
- cairn/__main__.py +3 -0
- cairn/agents.py +538 -0
- cairn/capture.py +36 -0
- cairn/cli.py +1054 -0
- cairn/core.py +711 -0
- cairn/daemon.py +173 -0
- cairn/drift.py +139 -0
- cairn/engines/__init__.py +0 -0
- cairn/engines/chronicle.py +268 -0
- cairn/engines/graph/__init__.py +37 -0
- cairn/engines/graph/__main__.py +10 -0
- cairn/engines/graph/_minhash.py +107 -0
- cairn/engines/graph/affected.py +318 -0
- cairn/engines/graph/analyze.py +772 -0
- cairn/engines/graph/api.py +429 -0
- cairn/engines/graph/assets/d3.v7.min.js +2 -0
- cairn/engines/graph/assets/mermaid.min.js +3636 -0
- cairn/engines/graph/assets/vis-network.min.js +33 -0
- cairn/engines/graph/assets.py +50 -0
- cairn/engines/graph/benchmark.py +152 -0
- cairn/engines/graph/build.py +2459 -0
- cairn/engines/graph/cache.py +1802 -0
- cairn/engines/graph/callflow_html.py +2078 -0
- cairn/engines/graph/cargo_introspect.py +166 -0
- cairn/engines/graph/cli.py +4978 -0
- cairn/engines/graph/cluster.py +425 -0
- cairn/engines/graph/cross_repo_calls.py +247 -0
- cairn/engines/graph/cross_repo_types.py +75 -0
- cairn/engines/graph/csharp_dispatch.py +154 -0
- cairn/engines/graph/dedup.py +1220 -0
- cairn/engines/graph/detect.py +2669 -0
- cairn/engines/graph/diagnostics.py +422 -0
- cairn/engines/graph/export.py +1362 -0
- cairn/engines/graph/exporters/__init__.py +1 -0
- cairn/engines/graph/exporters/base.py +14 -0
- cairn/engines/graph/exporters/graphdb.py +173 -0
- cairn/engines/graph/exporters/html.py +685 -0
- cairn/engines/graph/extract.py +8739 -0
- cairn/engines/graph/extractors/__init__.py +76 -0
- cairn/engines/graph/extractors/apex.py +215 -0
- cairn/engines/graph/extractors/base.py +85 -0
- cairn/engines/graph/extractors/bash.py +579 -0
- cairn/engines/graph/extractors/blade.py +53 -0
- cairn/engines/graph/extractors/cobol.py +276 -0
- cairn/engines/graph/extractors/commonlisp.py +540 -0
- cairn/engines/graph/extractors/csharp.py +448 -0
- cairn/engines/graph/extractors/dart.py +564 -0
- cairn/engines/graph/extractors/dm.py +494 -0
- cairn/engines/graph/extractors/elixir.py +250 -0
- cairn/engines/graph/extractors/engine.py +7175 -0
- cairn/engines/graph/extractors/erlang.py +333 -0
- cairn/engines/graph/extractors/fortran.py +311 -0
- cairn/engines/graph/extractors/go.py +564 -0
- cairn/engines/graph/extractors/json_config.py +240 -0
- cairn/engines/graph/extractors/julia.py +289 -0
- cairn/engines/graph/extractors/markdown.py +509 -0
- cairn/engines/graph/extractors/models.py +131 -0
- cairn/engines/graph/extractors/objc.py +566 -0
- cairn/engines/graph/extractors/ocaml.py +289 -0
- cairn/engines/graph/extractors/pascal.py +688 -0
- cairn/engines/graph/extractors/pascal_forms.py +196 -0
- cairn/engines/graph/extractors/powershell.py +522 -0
- cairn/engines/graph/extractors/r.py +438 -0
- cairn/engines/graph/extractors/razor.py +192 -0
- cairn/engines/graph/extractors/resolution.py +3806 -0
- cairn/engines/graph/extractors/robot.py +296 -0
- cairn/engines/graph/extractors/rust.py +574 -0
- cairn/engines/graph/extractors/sln.py +92 -0
- cairn/engines/graph/extractors/solidity.py +413 -0
- cairn/engines/graph/extractors/sql.py +720 -0
- cairn/engines/graph/extractors/terraform.py +440 -0
- cairn/engines/graph/extractors/vbnet.py +409 -0
- cairn/engines/graph/extractors/verilog.py +364 -0
- cairn/engines/graph/extractors/yaml.py +162 -0
- cairn/engines/graph/extractors/zig.py +181 -0
- cairn/engines/graph/file_slice.py +246 -0
- cairn/engines/graph/global_graph.py +216 -0
- cairn/engines/graph/google_workspace.py +238 -0
- cairn/engines/graph/hooks.py +550 -0
- cairn/engines/graph/ids.py +93 -0
- cairn/engines/graph/ingest.py +358 -0
- cairn/engines/graph/llm.py +2421 -0
- cairn/engines/graph/manifest.py +4 -0
- cairn/engines/graph/manifest_ingest.py +311 -0
- cairn/engines/graph/markdown_resolution.py +231 -0
- cairn/engines/graph/mcp_ingest.py +386 -0
- cairn/engines/graph/multigraph_compat.py +212 -0
- cairn/engines/graph/pascal_resolution.py +129 -0
- cairn/engines/graph/paths.py +677 -0
- cairn/engines/graph/pg_introspect.py +165 -0
- cairn/engines/graph/prs.py +701 -0
- cairn/engines/graph/querylog.py +81 -0
- cairn/engines/graph/reflect.py +882 -0
- cairn/engines/graph/report.py +368 -0
- cairn/engines/graph/repos.py +438 -0
- cairn/engines/graph/resolver_registry.py +85 -0
- cairn/engines/graph/ruby_resolution.py +524 -0
- cairn/engines/graph/scip_ingest.py +363 -0
- cairn/engines/graph/security.py +461 -0
- cairn/engines/graph/semantic_cleanup.py +336 -0
- cairn/engines/graph/serve.py +2437 -0
- cairn/engines/graph/symbol_resolution.py +556 -0
- cairn/engines/graph/transcribe.py +186 -0
- cairn/engines/graph/tree_html.py +618 -0
- cairn/engines/graph/validate.py +95 -0
- cairn/engines/graph/watch.py +2434 -0
- cairn/engines/graph/wiki.py +417 -0
- cairn/engines/history.py +194 -0
- cairn/engines/journal.py +207 -0
- cairn/engines/mapper.py +522 -0
- cairn/engines/memory.py +1008 -0
- cairn/engines/memstore/__init__.py +32 -0
- cairn/engines/memstore/api.py +315 -0
- cairn/engines/memstore/base.py +63 -0
- cairn/engines/memstore/commands.py +163 -0
- cairn/engines/memstore/configs/__init__.py +0 -0
- cairn/engines/memstore/configs/base.py +104 -0
- cairn/engines/memstore/configs/embeddings/__init__.py +0 -0
- cairn/engines/memstore/configs/embeddings/base.py +45 -0
- cairn/engines/memstore/configs/enums.py +7 -0
- cairn/engines/memstore/configs/llms/__init__.py +0 -0
- cairn/engines/memstore/configs/llms/base.py +81 -0
- cairn/engines/memstore/configs/rerankers/__init__.py +0 -0
- cairn/engines/memstore/configs/rerankers/base.py +17 -0
- cairn/engines/memstore/configs/rerankers/config.py +14 -0
- cairn/engines/memstore/configs/rerankers/huggingface.py +17 -0
- cairn/engines/memstore/configs/rerankers/llm.py +54 -0
- cairn/engines/memstore/configs/rerankers/sentence_transformer.py +16 -0
- cairn/engines/memstore/configs/vector_stores/__init__.py +0 -0
- cairn/engines/memstore/configs/vector_stores/cassandra.py +76 -0
- cairn/engines/memstore/configs/vector_stores/chroma.py +58 -0
- cairn/engines/memstore/configs/vector_stores/elasticsearch.py +68 -0
- cairn/engines/memstore/configs/vector_stores/faiss.py +40 -0
- cairn/engines/memstore/configs/vector_stores/langchain.py +30 -0
- cairn/engines/memstore/configs/vector_stores/milvus.py +42 -0
- cairn/engines/memstore/configs/vector_stores/mongodb.py +27 -0
- cairn/engines/memstore/configs/vector_stores/opensearch.py +49 -0
- cairn/engines/memstore/configs/vector_stores/oracledb.py +115 -0
- cairn/engines/memstore/configs/vector_stores/pgvector.py +54 -0
- cairn/engines/memstore/configs/vector_stores/qdrant.py +51 -0
- cairn/engines/memstore/configs/vector_stores/redis.py +9 -0
- cairn/engines/memstore/configs/vector_stores/supabase.py +46 -0
- cairn/engines/memstore/configs/vector_stores/valkey.py +32 -0
- cairn/engines/memstore/configs/vector_stores/weaviate.py +41 -0
- cairn/engines/memstore/embeddings/__init__.py +0 -0
- cairn/engines/memstore/embeddings/base.py +47 -0
- cairn/engines/memstore/embeddings/configs.py +20 -0
- cairn/engines/memstore/embeddings/local.py +44 -0
- cairn/engines/memstore/exceptions.py +485 -0
- cairn/engines/memstore/graph.py +129 -0
- cairn/engines/memstore/llms/__init__.py +0 -0
- cairn/engines/memstore/llms/base.py +67 -0
- cairn/engines/memstore/llms/configs.py +17 -0
- cairn/engines/memstore/llms/router.py +255 -0
- cairn/engines/memstore/main.py +2350 -0
- cairn/engines/memstore/prompts.py +1111 -0
- cairn/engines/memstore/proxy.py +136 -0
- cairn/engines/memstore/reranker/__init__.py +23 -0
- cairn/engines/memstore/reranker/base.py +20 -0
- cairn/engines/memstore/reranker/huggingface_reranker.py +173 -0
- cairn/engines/memstore/reranker/llm_reranker.py +188 -0
- cairn/engines/memstore/reranker/sentence_transformer_reranker.py +120 -0
- cairn/engines/memstore/scopes.py +44 -0
- cairn/engines/memstore/storage.py +351 -0
- cairn/engines/memstore/utils/__init__.py +0 -0
- cairn/engines/memstore/utils/entity_extraction.py +821 -0
- cairn/engines/memstore/utils/factory.py +223 -0
- cairn/engines/memstore/utils/lemmatization.py +95 -0
- cairn/engines/memstore/utils/messages.py +312 -0
- cairn/engines/memstore/utils/nlp_models.py +102 -0
- cairn/engines/memstore/utils/scoring.py +139 -0
- cairn/engines/memstore/utils/text.py +54 -0
- cairn/engines/memstore/vector_stores/__init__.py +0 -0
- cairn/engines/memstore/vector_stores/base.py +100 -0
- cairn/engines/memstore/vector_stores/cassandra.py +503 -0
- cairn/engines/memstore/vector_stores/chroma.py +364 -0
- cairn/engines/memstore/vector_stores/configs.py +62 -0
- cairn/engines/memstore/vector_stores/elasticsearch.py +311 -0
- cairn/engines/memstore/vector_stores/faiss.py +469 -0
- cairn/engines/memstore/vector_stores/filtering.py +97 -0
- cairn/engines/memstore/vector_stores/langchain.py +216 -0
- cairn/engines/memstore/vector_stores/milvus.py +374 -0
- cairn/engines/memstore/vector_stores/mongodb.py +432 -0
- cairn/engines/memstore/vector_stores/opensearch.py +416 -0
- cairn/engines/memstore/vector_stores/oracledb.py +602 -0
- cairn/engines/memstore/vector_stores/pgvector.py +563 -0
- cairn/engines/memstore/vector_stores/qdrant.py +606 -0
- cairn/engines/memstore/vector_stores/redis.py +370 -0
- cairn/engines/memstore/vector_stores/supabase.py +259 -0
- cairn/engines/memstore/vector_stores/valkey.py +819 -0
- cairn/engines/memstore/vector_stores/weaviate.py +387 -0
- cairn/engines/process_dialect.py +274 -0
- cairn/engines/recall/__init__.py +19 -0
- cairn/engines/recall/adapters.py +399 -0
- cairn/engines/recall/api.py +366 -0
- cairn/engines/recall/cli.py +329 -0
- cairn/engines/recall/context.py +557 -0
- cairn/engines/recall/corpus.py +693 -0
- cairn/engines/recall/fallback.py +234 -0
- cairn/engines/recall/fmt.py +104 -0
- cairn/engines/recall/folders.py +804 -0
- cairn/engines/recall/health.py +178 -0
- cairn/engines/recall/hooks.py +573 -0
- cairn/engines/recall/ingest.py +153 -0
- cairn/engines/recall/integrations.py +2170 -0
- cairn/engines/recall/mcp.py +363 -0
- cairn/engines/recall/modes/code--ar.json +24 -0
- cairn/engines/recall/modes/code--bn.json +24 -0
- cairn/engines/recall/modes/code--chill.json +8 -0
- cairn/engines/recall/modes/code--cs.json +24 -0
- cairn/engines/recall/modes/code--da.json +24 -0
- cairn/engines/recall/modes/code--de.json +24 -0
- cairn/engines/recall/modes/code--el.json +24 -0
- cairn/engines/recall/modes/code--es.json +24 -0
- cairn/engines/recall/modes/code--fi.json +24 -0
- cairn/engines/recall/modes/code--fr.json +24 -0
- cairn/engines/recall/modes/code--he.json +24 -0
- cairn/engines/recall/modes/code--hi.json +24 -0
- cairn/engines/recall/modes/code--hu.json +24 -0
- cairn/engines/recall/modes/code--id.json +24 -0
- cairn/engines/recall/modes/code--it.json +24 -0
- cairn/engines/recall/modes/code--ja.json +24 -0
- cairn/engines/recall/modes/code--ko.json +24 -0
- cairn/engines/recall/modes/code--nl.json +24 -0
- cairn/engines/recall/modes/code--no.json +24 -0
- cairn/engines/recall/modes/code--pl.json +24 -0
- cairn/engines/recall/modes/code--pt-br.json +24 -0
- cairn/engines/recall/modes/code--ro.json +24 -0
- cairn/engines/recall/modes/code--ru.json +24 -0
- cairn/engines/recall/modes/code--sv.json +24 -0
- cairn/engines/recall/modes/code--th.json +24 -0
- cairn/engines/recall/modes/code--tr.json +24 -0
- cairn/engines/recall/modes/code--uk.json +24 -0
- cairn/engines/recall/modes/code--ur.json +25 -0
- cairn/engines/recall/modes/code--vi.json +24 -0
- cairn/engines/recall/modes/code--zh.json +24 -0
- cairn/engines/recall/modes/code.json +146 -0
- cairn/engines/recall/modes/email-investigation.json +120 -0
- cairn/engines/recall/modes/law-study--chill.json +7 -0
- cairn/engines/recall/modes/law-study-CLAUDE.md +85 -0
- cairn/engines/recall/modes/law-study.json +120 -0
- cairn/engines/recall/modes/meme-tokens.json +125 -0
- cairn/engines/recall/modes.py +138 -0
- cairn/engines/recall/observer.py +503 -0
- cairn/engines/recall/parser.py +303 -0
- cairn/engines/recall/platforms.py +30 -0
- cairn/engines/recall/projects.py +216 -0
- cairn/engines/recall/prompts.py +291 -0
- cairn/engines/recall/remote.py +500 -0
- cairn/engines/recall/schema.py +520 -0
- cairn/engines/recall/search.py +605 -0
- cairn/engines/recall/settings.py +172 -0
- cairn/engines/recall/skills/cairn-do/SKILL.md +45 -0
- cairn/engines/recall/skills/cairn-how-it-works/SKILL.md +22 -0
- cairn/engines/recall/skills/cairn-how-it-works/onboarding-explainer.md +17 -0
- cairn/engines/recall/skills/cairn-knowledge-agent/SKILL.md +82 -0
- cairn/engines/recall/skills/cairn-learn-codebase/SKILL.md +21 -0
- cairn/engines/recall/skills/cairn-make-plan/SKILL.md +63 -0
- cairn/engines/recall/skills/cairn-mode-creator/SKILL.md +116 -0
- cairn/engines/recall/skills/cairn-mode-creator/evals/evals.json +23 -0
- cairn/engines/recall/skills/cairn-mode-creator/references/mode-authoring.md +96 -0
- cairn/engines/recall/skills/cairn-mode-creator/scripts/install_mode.py +239 -0
- cairn/engines/recall/skills/cairn-pathfinder/SKILL.md +111 -0
- cairn/engines/recall/skills/cairn-recall-search/SKILL.md +166 -0
- cairn/engines/recall/skills/cairn-smart-explore/SKILL.md +189 -0
- cairn/engines/recall/skills/cairn-standup/SKILL.md +144 -0
- cairn/engines/recall/skills/cairn-standup/agent-brief.md +47 -0
- cairn/engines/recall/skills/cairn-standup/standup.py +588 -0
- cairn/engines/recall/skills/cairn-timeline-report/SKILL.md +211 -0
- cairn/engines/recall/skills/cairn-weekly-digests/SKILL.md +264 -0
- cairn/engines/recall/skills/cairn-what-the/SKILL.md +6 -0
- cairn/engines/recall/smartread.py +1974 -0
- cairn/engines/recall/sqlsearch.py +244 -0
- cairn/engines/recall/store.py +1043 -0
- cairn/engines/recall/tags.py +98 -0
- cairn/engines/recall/transcript_parser.py +100 -0
- cairn/engines/recall/transcripts.py +1621 -0
- cairn/engines/recall/vectorsync.py +245 -0
- cairn/engines/recall/viewer.py +541 -0
- cairn/engines/recall/worker.py +437 -0
- cairn/engines/recall/worktrees.py +359 -0
- cairn/engines/specs.py +722 -0
- cairn/engines/temporal/__init__.py +67 -0
- cairn/engines/temporal/api.py +241 -0
- cairn/engines/temporal/cli.py +202 -0
- cairn/engines/temporal/cross_encoder/__init__.py +10 -0
- cairn/engines/temporal/cross_encoder/bge_reranker_client.py +37 -0
- cairn/engines/temporal/cross_encoder/client.py +24 -0
- cairn/engines/temporal/cross_encoder/local.py +176 -0
- cairn/engines/temporal/cross_encoder/router_reranker.py +94 -0
- cairn/engines/temporal/decorators.py +117 -0
- cairn/engines/temporal/driver/__init__.py +33 -0
- cairn/engines/temporal/driver/driver.py +200 -0
- cairn/engines/temporal/driver/falkordb/__init__.py +35 -0
- cairn/engines/temporal/driver/falkordb/fulltext.py +83 -0
- cairn/engines/temporal/driver/falkordb/operations/__init__.py +33 -0
- cairn/engines/temporal/driver/falkordb/operations/community_edge_ops.py +170 -0
- cairn/engines/temporal/driver/falkordb/operations/community_node_ops.py +206 -0
- cairn/engines/temporal/driver/falkordb/operations/entity_edge_ops.py +259 -0
- cairn/engines/temporal/driver/falkordb/operations/entity_node_ops.py +245 -0
- cairn/engines/temporal/driver/falkordb/operations/episode_node_ops.py +285 -0
- cairn/engines/temporal/driver/falkordb/operations/episodic_edge_ops.py +184 -0
- cairn/engines/temporal/driver/falkordb/operations/graph_ops.py +226 -0
- cairn/engines/temporal/driver/falkordb/operations/has_episode_edge_ops.py +179 -0
- cairn/engines/temporal/driver/falkordb/operations/next_episode_edge_ops.py +179 -0
- cairn/engines/temporal/driver/falkordb/operations/saga_node_ops.py +192 -0
- cairn/engines/temporal/driver/falkordb/operations/search_ops.py +621 -0
- cairn/engines/temporal/driver/falkordb_driver.py +359 -0
- cairn/engines/temporal/driver/graph_operations/__init__.py +0 -0
- cairn/engines/temporal/driver/graph_operations/graph_operations.py +877 -0
- cairn/engines/temporal/driver/kuzu/__init__.py +0 -0
- cairn/engines/temporal/driver/kuzu/operations/__init__.py +27 -0
- cairn/engines/temporal/driver/kuzu/operations/community_edge_ops.py +147 -0
- cairn/engines/temporal/driver/kuzu/operations/community_node_ops.py +182 -0
- cairn/engines/temporal/driver/kuzu/operations/entity_edge_ops.py +217 -0
- cairn/engines/temporal/driver/kuzu/operations/entity_node_ops.py +220 -0
- cairn/engines/temporal/driver/kuzu/operations/episode_node_ops.py +250 -0
- cairn/engines/temporal/driver/kuzu/operations/episodic_edge_ops.py +169 -0
- cairn/engines/temporal/driver/kuzu/operations/graph_ops.py +208 -0
- cairn/engines/temporal/driver/kuzu/operations/has_episode_edge_ops.py +155 -0
- cairn/engines/temporal/driver/kuzu/operations/next_episode_edge_ops.py +155 -0
- cairn/engines/temporal/driver/kuzu/operations/record_parsers.py +30 -0
- cairn/engines/temporal/driver/kuzu/operations/saga_node_ops.py +171 -0
- cairn/engines/temporal/driver/kuzu/operations/search_ops.py +746 -0
- cairn/engines/temporal/driver/kuzu_driver.py +352 -0
- cairn/engines/temporal/driver/neo4j/__init__.py +0 -0
- cairn/engines/temporal/driver/neo4j/operations/__init__.py +29 -0
- cairn/engines/temporal/driver/neo4j/operations/community_edge_ops.py +148 -0
- cairn/engines/temporal/driver/neo4j/operations/community_node_ops.py +189 -0
- cairn/engines/temporal/driver/neo4j/operations/entity_edge_ops.py +238 -0
- cairn/engines/temporal/driver/neo4j/operations/entity_node_ops.py +223 -0
- cairn/engines/temporal/driver/neo4j/operations/episode_node_ops.py +271 -0
- cairn/engines/temporal/driver/neo4j/operations/episodic_edge_ops.py +162 -0
- cairn/engines/temporal/driver/neo4j/operations/graph_ops.py +198 -0
- cairn/engines/temporal/driver/neo4j/operations/has_episode_edge_ops.py +156 -0
- cairn/engines/temporal/driver/neo4j/operations/next_episode_edge_ops.py +156 -0
- cairn/engines/temporal/driver/neo4j/operations/saga_node_ops.py +174 -0
- cairn/engines/temporal/driver/neo4j/operations/search_ops.py +638 -0
- cairn/engines/temporal/driver/neo4j_driver.py +228 -0
- cairn/engines/temporal/driver/neptune/__init__.py +0 -0
- cairn/engines/temporal/driver/neptune/operations/__init__.py +33 -0
- cairn/engines/temporal/driver/neptune/operations/community_edge_ops.py +147 -0
- cairn/engines/temporal/driver/neptune/operations/community_node_ops.py +195 -0
- cairn/engines/temporal/driver/neptune/operations/entity_edge_ops.py +236 -0
- cairn/engines/temporal/driver/neptune/operations/entity_node_ops.py +216 -0
- cairn/engines/temporal/driver/neptune/operations/episode_node_ops.py +259 -0
- cairn/engines/temporal/driver/neptune/operations/episodic_edge_ops.py +161 -0
- cairn/engines/temporal/driver/neptune/operations/graph_ops.py +203 -0
- cairn/engines/temporal/driver/neptune/operations/has_episode_edge_ops.py +155 -0
- cairn/engines/temporal/driver/neptune/operations/next_episode_edge_ops.py +155 -0
- cairn/engines/temporal/driver/neptune/operations/saga_node_ops.py +172 -0
- cairn/engines/temporal/driver/neptune/operations/search_ops.py +672 -0
- cairn/engines/temporal/driver/neptune_driver.py +379 -0
- cairn/engines/temporal/driver/operations/__init__.py +25 -0
- cairn/engines/temporal/driver/operations/community_edge_ops.py +53 -0
- cairn/engines/temporal/driver/operations/community_node_ops.py +79 -0
- cairn/engines/temporal/driver/operations/entity_edge_ops.py +92 -0
- cairn/engines/temporal/driver/operations/entity_node_ops.py +87 -0
- cairn/engines/temporal/driver/operations/episode_node_ops.py +91 -0
- cairn/engines/temporal/driver/operations/episodic_edge_ops.py +62 -0
- cairn/engines/temporal/driver/operations/graph_ops.py +78 -0
- cairn/engines/temporal/driver/operations/graph_utils.py +49 -0
- cairn/engines/temporal/driver/operations/has_episode_edge_ops.py +62 -0
- cairn/engines/temporal/driver/operations/next_episode_edge_ops.py +62 -0
- cairn/engines/temporal/driver/operations/saga_node_ops.py +72 -0
- cairn/engines/temporal/driver/operations/search_ops.py +149 -0
- cairn/engines/temporal/driver/query_executor.py +25 -0
- cairn/engines/temporal/driver/record_parsers.py +104 -0
- cairn/engines/temporal/driver/search_interface/__init__.py +0 -0
- cairn/engines/temporal/driver/search_interface/search_interface.py +334 -0
- cairn/engines/temporal/edges.py +1032 -0
- cairn/engines/temporal/embedder/__init__.py +10 -0
- cairn/engines/temporal/embedder/client.py +23 -0
- cairn/engines/temporal/embedder/local.py +73 -0
- cairn/engines/temporal/engine.py +1756 -0
- cairn/engines/temporal/engine_types.py +17 -0
- cairn/engines/temporal/errors.py +78 -0
- cairn/engines/temporal/formatting.py +98 -0
- cairn/engines/temporal/graph_queries.py +175 -0
- cairn/engines/temporal/helpers.py +209 -0
- cairn/engines/temporal/llm_client/__init__.py +19 -0
- cairn/engines/temporal/llm_client/cache.py +52 -0
- cairn/engines/temporal/llm_client/client.py +270 -0
- cairn/engines/temporal/llm_client/config.py +47 -0
- cairn/engines/temporal/llm_client/errors.py +22 -0
- cairn/engines/temporal/llm_client/local_extractor_client.py +305 -0
- cairn/engines/temporal/llm_client/router_client.py +311 -0
- cairn/engines/temporal/llm_client/token_tracker.py +136 -0
- cairn/engines/temporal/llm_client/utils.py +18 -0
- cairn/engines/temporal/migrations/__init__.py +133 -0
- cairn/engines/temporal/models/__init__.py +0 -0
- cairn/engines/temporal/models/edges/__init__.py +0 -0
- cairn/engines/temporal/models/edges/edge_db_queries.py +327 -0
- cairn/engines/temporal/models/nodes/__init__.py +0 -0
- cairn/engines/temporal/models/nodes/node_db_queries.py +382 -0
- cairn/engines/temporal/namespaces/__init__.py +7 -0
- cairn/engines/temporal/namespaces/edges.py +339 -0
- cairn/engines/temporal/namespaces/nodes.py +339 -0
- cairn/engines/temporal/nodes.py +1106 -0
- cairn/engines/temporal/ontology.py +512 -0
- cairn/engines/temporal/prompts/__init__.py +4 -0
- cairn/engines/temporal/prompts/dedupe_edges.py +87 -0
- cairn/engines/temporal/prompts/dedupe_nodes.py +209 -0
- cairn/engines/temporal/prompts/eval.py +148 -0
- cairn/engines/temporal/prompts/extract_edges.py +293 -0
- cairn/engines/temporal/prompts/extract_nodes.py +638 -0
- cairn/engines/temporal/prompts/extract_nodes_and_edges.py +296 -0
- cairn/engines/temporal/prompts/lib.py +86 -0
- cairn/engines/temporal/prompts/models.py +16 -0
- cairn/engines/temporal/prompts/prompt_helpers.py +24 -0
- cairn/engines/temporal/prompts/snippets.py +18 -0
- cairn/engines/temporal/prompts/summarize_nodes.py +126 -0
- cairn/engines/temporal/prompts/summarize_sagas.py +123 -0
- cairn/engines/temporal/py.typed +1 -0
- cairn/engines/temporal/queue.py +100 -0
- cairn/engines/temporal/search/__init__.py +0 -0
- cairn/engines/temporal/search/search.py +859 -0
- cairn/engines/temporal/search/search_config.py +144 -0
- cairn/engines/temporal/search/search_config_recipes.py +207 -0
- cairn/engines/temporal/search/search_filters.py +267 -0
- cairn/engines/temporal/search/search_helpers.py +56 -0
- cairn/engines/temporal/search/search_utils.py +2048 -0
- cairn/engines/temporal/service.py +951 -0
- cairn/engines/temporal/tracer.py +177 -0
- cairn/engines/temporal/utils/__init__.py +0 -0
- cairn/engines/temporal/utils/bulk_utils.py +618 -0
- cairn/engines/temporal/utils/content_chunking.py +810 -0
- cairn/engines/temporal/utils/datetime_utils.py +44 -0
- cairn/engines/temporal/utils/maintenance/__init__.py +11 -0
- cairn/engines/temporal/utils/maintenance/attribute_utils.py +238 -0
- cairn/engines/temporal/utils/maintenance/combined_extraction.py +297 -0
- cairn/engines/temporal/utils/maintenance/community_operations.py +367 -0
- cairn/engines/temporal/utils/maintenance/dedup_helpers.py +280 -0
- cairn/engines/temporal/utils/maintenance/edge_operations.py +895 -0
- cairn/engines/temporal/utils/maintenance/graph_data_operations.py +151 -0
- cairn/engines/temporal/utils/maintenance/node_operations.py +1019 -0
- cairn/engines/temporal/utils/ontology_utils/__init__.py +0 -0
- cairn/engines/temporal/utils/ontology_utils/entity_types_utils.py +21 -0
- cairn/engines/temporal/utils/text_utils.py +59 -0
- cairn/engines/vectors.py +594 -0
- cairn/engines/workflow/__init__.py +482 -0
- cairn/engines/workflow/_agent_config.py +56 -0
- cairn/engines/workflow/_assets.py +176 -0
- cairn/engines/workflow/_console.py +273 -0
- cairn/engines/workflow/_download_security.py +1275 -0
- cairn/engines/workflow/_init_options.py +83 -0
- cairn/engines/workflow/_installed_list_json.py +84 -0
- cairn/engines/workflow/_invocation_style.py +81 -0
- cairn/engines/workflow/_project.py +78 -0
- cairn/engines/workflow/_toml_string.py +60 -0
- cairn/engines/workflow/_utils.py +375 -0
- cairn/engines/workflow/agents.py +1390 -0
- cairn/engines/workflow/artifacts/__init__.py +37 -0
- cairn/engines/workflow/artifacts/_commands.py +89 -0
- cairn/engines/workflow/artifacts/_identifiers.py +162 -0
- cairn/engines/workflow/artifacts/catalog.py +1163 -0
- cairn/engines/workflow/artifacts/command_info.py +64 -0
- cairn/engines/workflow/artifacts/command_list.py +42 -0
- cairn/engines/workflow/artifacts/command_lookup.py +55 -0
- cairn/engines/workflow/artifacts/models.py +182 -0
- cairn/engines/workflow/artifacts/resolution.py +498 -0
- cairn/engines/workflow/assets/bundles/assess/README.md +40 -0
- cairn/engines/workflow/assets/bundles/assess/bundle.yml +25 -0
- cairn/engines/workflow/assets/bundles/bugfix/README.md +38 -0
- cairn/engines/workflow/assets/bundles/bugfix/bundle.yml +25 -0
- cairn/engines/workflow/assets/bundles/catalog.json +51 -0
- cairn/engines/workflow/assets/commands/analyze.md +255 -0
- cairn/engines/workflow/assets/commands/checklist.md +379 -0
- cairn/engines/workflow/assets/commands/clarify.md +301 -0
- cairn/engines/workflow/assets/commands/constitution.md +179 -0
- cairn/engines/workflow/assets/commands/converge.md +279 -0
- cairn/engines/workflow/assets/commands/implement.md +233 -0
- cairn/engines/workflow/assets/commands/plan.md +178 -0
- cairn/engines/workflow/assets/commands/specify.md +345 -0
- cairn/engines/workflow/assets/commands/tasks.md +233 -0
- cairn/engines/workflow/assets/commands/taskstoissues.md +106 -0
- cairn/engines/workflow/assets/extensions/agent-context/README.md +63 -0
- cairn/engines/workflow/assets/extensions/agent-context/agent-context-config.yml +24 -0
- cairn/engines/workflow/assets/extensions/agent-context/agent-context-defaults.json +43 -0
- cairn/engines/workflow/assets/extensions/agent-context/commands/cairn.agent-context.update.md +27 -0
- cairn/engines/workflow/assets/extensions/agent-context/extension.yml +34 -0
- cairn/engines/workflow/assets/extensions/agent-context/scripts/bash/update-agent-context.sh +453 -0
- cairn/engines/workflow/assets/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +509 -0
- cairn/engines/workflow/assets/extensions/agent-context/scripts/python/update_agent_context.py +368 -0
- cairn/engines/workflow/assets/extensions/assess/README.md +127 -0
- cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.decide.md +97 -0
- cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.define.md +85 -0
- cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.intake.md +120 -0
- cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.research.md +102 -0
- cairn/engines/workflow/assets/extensions/assess/commands/cairn.assess.shape.md +82 -0
- cairn/engines/workflow/assets/extensions/assess/extension.yml +40 -0
- cairn/engines/workflow/assets/extensions/bug/README.md +80 -0
- cairn/engines/workflow/assets/extensions/bug/commands/cairn.bug.assess.md +173 -0
- cairn/engines/workflow/assets/extensions/bug/commands/cairn.bug.fix.md +112 -0
- cairn/engines/workflow/assets/extensions/bug/commands/cairn.bug.test.md +117 -0
- cairn/engines/workflow/assets/extensions/bug/extension.yml +31 -0
- cairn/engines/workflow/assets/extensions/catalog.json +67 -0
- cairn/engines/workflow/assets/extensions/git/README.md +119 -0
- cairn/engines/workflow/assets/extensions/git/commands/cairn.git.commit.md +63 -0
- cairn/engines/workflow/assets/extensions/git/commands/cairn.git.feature.md +82 -0
- cairn/engines/workflow/assets/extensions/git/commands/cairn.git.initialize.md +49 -0
- cairn/engines/workflow/assets/extensions/git/commands/cairn.git.remote.md +45 -0
- cairn/engines/workflow/assets/extensions/git/commands/cairn.git.validate.md +49 -0
- cairn/engines/workflow/assets/extensions/git/config-template.yml +79 -0
- cairn/engines/workflow/assets/extensions/git/extension.yml +142 -0
- cairn/engines/workflow/assets/extensions/git/git-config.yml +79 -0
- cairn/engines/workflow/assets/extensions/git/scripts/bash/auto-commit.sh +211 -0
- cairn/engines/workflow/assets/extensions/git/scripts/bash/create-new-feature-branch.sh +639 -0
- cairn/engines/workflow/assets/extensions/git/scripts/bash/git-common.sh +56 -0
- cairn/engines/workflow/assets/extensions/git/scripts/bash/initialize-repo.sh +54 -0
- cairn/engines/workflow/assets/extensions/git/scripts/powershell/auto-commit.ps1 +230 -0
- cairn/engines/workflow/assets/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +594 -0
- cairn/engines/workflow/assets/extensions/git/scripts/powershell/git-common.ps1 +52 -0
- cairn/engines/workflow/assets/extensions/git/scripts/powershell/initialize-repo.ps1 +69 -0
- cairn/engines/workflow/assets/extensions/git/scripts/python/auto_commit.py +195 -0
- cairn/engines/workflow/assets/extensions/git/scripts/python/create_new_feature_branch.py +641 -0
- cairn/engines/workflow/assets/extensions/git/scripts/python/git_common.py +81 -0
- cairn/engines/workflow/assets/extensions/git/scripts/python/initialize_repo.py +89 -0
- cairn/engines/workflow/assets/integrations/catalog.json +376 -0
- cairn/engines/workflow/assets/presets/catalog.json +53 -0
- cairn/engines/workflow/assets/presets/constitution-sync/README.md +135 -0
- cairn/engines/workflow/assets/presets/constitution-sync/commands/cairn.constitution.md +54 -0
- cairn/engines/workflow/assets/presets/constitution-sync/preset.yml +30 -0
- cairn/engines/workflow/assets/presets/lean/README.md +45 -0
- cairn/engines/workflow/assets/presets/lean/commands/cairn.constitution.md +33 -0
- cairn/engines/workflow/assets/presets/lean/commands/cairn.implement.md +24 -0
- cairn/engines/workflow/assets/presets/lean/commands/cairn.plan.md +20 -0
- cairn/engines/workflow/assets/presets/lean/commands/cairn.specify.md +23 -0
- cairn/engines/workflow/assets/presets/lean/commands/cairn.tasks.md +21 -0
- cairn/engines/workflow/assets/presets/lean/preset.yml +51 -0
- cairn/engines/workflow/assets/scripts/bash/check-prerequisites.sh +243 -0
- cairn/engines/workflow/assets/scripts/bash/common.sh +933 -0
- cairn/engines/workflow/assets/scripts/bash/create-new-feature.sh +427 -0
- cairn/engines/workflow/assets/scripts/bash/resolve-template.sh +57 -0
- cairn/engines/workflow/assets/scripts/bash/setup-plan.sh +85 -0
- cairn/engines/workflow/assets/scripts/bash/setup-tasks.sh +94 -0
- cairn/engines/workflow/assets/scripts/powershell/check-prerequisites.ps1 +185 -0
- cairn/engines/workflow/assets/scripts/powershell/common.ps1 +803 -0
- cairn/engines/workflow/assets/scripts/powershell/create-new-feature.ps1 +335 -0
- cairn/engines/workflow/assets/scripts/powershell/resolve-template.ps1 +38 -0
- cairn/engines/workflow/assets/scripts/powershell/setup-plan.ps1 +88 -0
- cairn/engines/workflow/assets/scripts/powershell/setup-tasks.ps1 +93 -0
- cairn/engines/workflow/assets/scripts/python/check_prerequisites.py +289 -0
- cairn/engines/workflow/assets/scripts/python/common.py +560 -0
- cairn/engines/workflow/assets/scripts/python/create_new_feature.py +451 -0
- cairn/engines/workflow/assets/scripts/python/resolve_template.py +63 -0
- cairn/engines/workflow/assets/scripts/python/setup_plan.py +99 -0
- cairn/engines/workflow/assets/scripts/python/setup_tasks.py +175 -0
- cairn/engines/workflow/assets/templates/checklist-template.md +45 -0
- cairn/engines/workflow/assets/templates/constitution-template.md +50 -0
- cairn/engines/workflow/assets/templates/plan-template.md +113 -0
- cairn/engines/workflow/assets/templates/spec-template.md +131 -0
- cairn/engines/workflow/assets/templates/tasks-template.md +252 -0
- cairn/engines/workflow/assets/templates/vscode-settings.json +13 -0
- cairn/engines/workflow/assets/workflows/assess/workflow.yml +62 -0
- cairn/engines/workflow/assets/workflows/bugfix/workflow.yml +50 -0
- cairn/engines/workflow/assets/workflows/cairn/workflow.yml +74 -0
- cairn/engines/workflow/assets/workflows/catalog.json +37 -0
- cairn/engines/workflow/assets/workflows/step-catalog.json +6 -0
- cairn/engines/workflow/authentication/__init__.py +50 -0
- cairn/engines/workflow/authentication/azure_devops.py +188 -0
- cairn/engines/workflow/authentication/base.py +57 -0
- cairn/engines/workflow/authentication/config.py +245 -0
- cairn/engines/workflow/authentication/github.py +24 -0
- cairn/engines/workflow/authentication/github_http.py +300 -0
- cairn/engines/workflow/authentication/http.py +233 -0
- cairn/engines/workflow/bundler.py +5 -0
- cairn/engines/workflow/bundles/__init__.py +20 -0
- cairn/engines/workflow/bundles/_commands.py +165 -0
- cairn/engines/workflow/bundles/adapters.py +313 -0
- cairn/engines/workflow/bundles/catalog/__init__.py +20 -0
- cairn/engines/workflow/bundles/catalog/command_add.py +48 -0
- cairn/engines/workflow/bundles/catalog/command_list.py +38 -0
- cairn/engines/workflow/bundles/catalog/command_remove.py +31 -0
- cairn/engines/workflow/bundles/catalog_config.py +252 -0
- cairn/engines/workflow/bundles/catalog_stack.py +122 -0
- cairn/engines/workflow/bundles/catalogs.py +332 -0
- cairn/engines/workflow/bundles/command_add.py +32 -0
- cairn/engines/workflow/bundles/command_build.py +38 -0
- cairn/engines/workflow/bundles/command_info.py +157 -0
- cairn/engines/workflow/bundles/command_init.py +51 -0
- cairn/engines/workflow/bundles/command_install.py +137 -0
- cairn/engines/workflow/bundles/command_list.py +45 -0
- cairn/engines/workflow/bundles/command_remove.py +32 -0
- cairn/engines/workflow/bundles/command_search.py +65 -0
- cairn/engines/workflow/bundles/command_update.py +69 -0
- cairn/engines/workflow/bundles/command_validate.py +65 -0
- cairn/engines/workflow/bundles/conflict.py +54 -0
- cairn/engines/workflow/bundles/installer.py +276 -0
- cairn/engines/workflow/bundles/manifest.py +292 -0
- cairn/engines/workflow/bundles/packager.py +153 -0
- cairn/engines/workflow/bundles/primitives.py +500 -0
- cairn/engines/workflow/bundles/project.py +102 -0
- cairn/engines/workflow/bundles/records.py +245 -0
- cairn/engines/workflow/bundles/references.py +127 -0
- cairn/engines/workflow/bundles/resolver.py +132 -0
- cairn/engines/workflow/bundles/sources.py +360 -0
- cairn/engines/workflow/bundles/validator.py +60 -0
- cairn/engines/workflow/bundles/versioning.py +99 -0
- cairn/engines/workflow/bundles/yamlio.py +202 -0
- cairn/engines/workflow/catalogs.py +197 -0
- cairn/engines/workflow/cli.py +58 -0
- cairn/engines/workflow/command_check.py +52 -0
- cairn/engines/workflow/command_init.py +1202 -0
- cairn/engines/workflow/command_version.py +102 -0
- cairn/engines/workflow/events/__init__.py +2644 -0
- cairn/engines/workflow/events/command_run.py +54 -0
- cairn/engines/workflow/extensions/__init__.py +5478 -0
- cairn/engines/workflow/extensions/_command_update_artifacts.py +164 -0
- cairn/engines/workflow/extensions/_command_update_discovery.py +145 -0
- cairn/engines/workflow/extensions/_command_update_transaction.py +871 -0
- cairn/engines/workflow/extensions/_commands.py +690 -0
- cairn/engines/workflow/extensions/catalog/__init__.py +32 -0
- cairn/engines/workflow/extensions/catalog/_helpers.py +37 -0
- cairn/engines/workflow/extensions/catalog/command_add.py +114 -0
- cairn/engines/workflow/extensions/catalog/command_list.py +96 -0
- cairn/engines/workflow/extensions/catalog/command_remove.py +66 -0
- cairn/engines/workflow/extensions/command_add.py +292 -0
- cairn/engines/workflow/extensions/command_disable.py +62 -0
- cairn/engines/workflow/extensions/command_enable.py +84 -0
- cairn/engines/workflow/extensions/command_info.py +261 -0
- cairn/engines/workflow/extensions/command_list.py +75 -0
- cairn/engines/workflow/extensions/command_remove.py +86 -0
- cairn/engines/workflow/extensions/command_search.py +111 -0
- cairn/engines/workflow/extensions/command_set_priority.py +66 -0
- cairn/engines/workflow/extensions/command_update.py +25 -0
- cairn/engines/workflow/integration_runtime.py +113 -0
- cairn/engines/workflow/integration_state.py +252 -0
- cairn/engines/workflow/integration_status.py +663 -0
- cairn/engines/workflow/integrations/__init__.py +1002 -0
- cairn/engines/workflow/integrations/_command_scaffold_generation.py +281 -0
- cairn/engines/workflow/integrations/_command_upgrade_layout.py +188 -0
- cairn/engines/workflow/integrations/_commands.py +47 -0
- cairn/engines/workflow/integrations/_helpers.py +603 -0
- cairn/engines/workflow/integrations/agy/__init__.py +106 -0
- cairn/engines/workflow/integrations/alquimia/__init__.py +195 -0
- cairn/engines/workflow/integrations/amp/__init__.py +54 -0
- cairn/engines/workflow/integrations/auggie/__init__.py +21 -0
- cairn/engines/workflow/integrations/base.py +1910 -0
- cairn/engines/workflow/integrations/bob/__init__.py +373 -0
- cairn/engines/workflow/integrations/catalog/__init__.py +48 -0
- cairn/engines/workflow/integrations/catalog/command_add.py +49 -0
- cairn/engines/workflow/integrations/catalog/command_list.py +67 -0
- cairn/engines/workflow/integrations/catalog/command_remove.py +28 -0
- cairn/engines/workflow/integrations/claude/__init__.py +245 -0
- cairn/engines/workflow/integrations/cline/__init__.py +200 -0
- cairn/engines/workflow/integrations/codebuddy/__init__.py +21 -0
- cairn/engines/workflow/integrations/codex/__init__.py +80 -0
- cairn/engines/workflow/integrations/command_code/__init__.py +41 -0
- cairn/engines/workflow/integrations/command_info.py +114 -0
- cairn/engines/workflow/integrations/command_install.py +199 -0
- cairn/engines/workflow/integrations/command_list.py +114 -0
- cairn/engines/workflow/integrations/command_scaffold.py +55 -0
- cairn/engines/workflow/integrations/command_search.py +109 -0
- cairn/engines/workflow/integrations/command_status.py +91 -0
- cairn/engines/workflow/integrations/command_switch.py +369 -0
- cairn/engines/workflow/integrations/command_uninstall.py +126 -0
- cairn/engines/workflow/integrations/command_upgrade.py +356 -0
- cairn/engines/workflow/integrations/command_use.py +69 -0
- cairn/engines/workflow/integrations/copilot/__init__.py +669 -0
- cairn/engines/workflow/integrations/cursor_agent/__init__.py +131 -0
- cairn/engines/workflow/integrations/devin/__init__.py +99 -0
- cairn/engines/workflow/integrations/docker_agent/__init__.py +207 -0
- cairn/engines/workflow/integrations/droid/__init__.py +143 -0
- cairn/engines/workflow/integrations/dsh/__init__.py +72 -0
- cairn/engines/workflow/integrations/firebender/__init__.py +32 -0
- cairn/engines/workflow/integrations/forge/__init__.py +248 -0
- cairn/engines/workflow/integrations/gemini/__init__.py +49 -0
- cairn/engines/workflow/integrations/generic/__init__.py +258 -0
- cairn/engines/workflow/integrations/goose/__init__.py +111 -0
- cairn/engines/workflow/integrations/grok/__init__.py +68 -0
- cairn/engines/workflow/integrations/hermes/__init__.py +312 -0
- cairn/engines/workflow/integrations/junie/__init__.py +178 -0
- cairn/engines/workflow/integrations/kilocode/__init__.py +22 -0
- cairn/engines/workflow/integrations/kimi/__init__.py +380 -0
- cairn/engines/workflow/integrations/kiro_cli/__init__.py +36 -0
- cairn/engines/workflow/integrations/lingma/__init__.py +41 -0
- cairn/engines/workflow/integrations/manifest.py +522 -0
- cairn/engines/workflow/integrations/muse/__init__.py +71 -0
- cairn/engines/workflow/integrations/omp/__init__.py +53 -0
- cairn/engines/workflow/integrations/opencode/__init__.py +76 -0
- cairn/engines/workflow/integrations/pi/__init__.py +21 -0
- cairn/engines/workflow/integrations/qodercli/__init__.py +28 -0
- cairn/engines/workflow/integrations/qwen/__init__.py +44 -0
- cairn/engines/workflow/integrations/rovodev/__init__.py +264 -0
- cairn/engines/workflow/integrations/shai/__init__.py +21 -0
- cairn/engines/workflow/integrations/tabnine/__init__.py +47 -0
- cairn/engines/workflow/integrations/trae/__init__.py +40 -0
- cairn/engines/workflow/integrations/vibe/__init__.py +202 -0
- cairn/engines/workflow/integrations/zcode/__init__.py +42 -0
- cairn/engines/workflow/integrations/zed/__init__.py +33 -0
- cairn/engines/workflow/presets/__init__.py +6259 -0
- cairn/engines/workflow/presets/_commands.py +99 -0
- cairn/engines/workflow/presets/catalog/__init__.py +25 -0
- cairn/engines/workflow/presets/catalog/command_add.py +124 -0
- cairn/engines/workflow/presets/catalog/command_list.py +80 -0
- cairn/engines/workflow/presets/catalog/command_remove.py +69 -0
- cairn/engines/workflow/presets/command_add.py +381 -0
- cairn/engines/workflow/presets/command_disable.py +50 -0
- cairn/engines/workflow/presets/command_enable.py +49 -0
- cairn/engines/workflow/presets/command_info.py +96 -0
- cairn/engines/workflow/presets/command_list.py +89 -0
- cairn/engines/workflow/presets/command_remove.py +30 -0
- cairn/engines/workflow/presets/command_resolve.py +121 -0
- cairn/engines/workflow/presets/command_search.py +46 -0
- cairn/engines/workflow/presets/command_set_priority.py +70 -0
- cairn/engines/workflow/presets/command_update.py +99 -0
- cairn/engines/workflow/shared_infra.py +769 -0
- cairn/engines/workflow/workflows/__init__.py +213 -0
- cairn/engines/workflow/workflows/_command_resume_state.py +49 -0
- cairn/engines/workflow/workflows/_command_run_ownership.py +188 -0
- cairn/engines/workflow/workflows/_commands.py +1536 -0
- cairn/engines/workflow/workflows/base.py +149 -0
- cairn/engines/workflow/workflows/catalog/__init__.py +60 -0
- cairn/engines/workflow/workflows/catalog/_domain.py +831 -0
- cairn/engines/workflow/workflows/catalog/command_add.py +31 -0
- cairn/engines/workflow/workflows/catalog/command_list.py +38 -0
- cairn/engines/workflow/workflows/catalog/command_remove.py +26 -0
- cairn/engines/workflow/workflows/command_add.py +508 -0
- cairn/engines/workflow/workflows/command_disable.py +16 -0
- cairn/engines/workflow/workflows/command_enable.py +13 -0
- cairn/engines/workflow/workflows/command_info.py +121 -0
- cairn/engines/workflow/workflows/command_list.py +36 -0
- cairn/engines/workflow/workflows/command_remove.py +127 -0
- cairn/engines/workflow/workflows/command_resolve.py +17 -0
- cairn/engines/workflow/workflows/command_resume.py +113 -0
- cairn/engines/workflow/workflows/command_run.py +168 -0
- cairn/engines/workflow/workflows/command_search.py +43 -0
- cairn/engines/workflow/workflows/command_status.py +129 -0
- cairn/engines/workflow/workflows/command_update.py +149 -0
- cairn/engines/workflow/workflows/engine.py +1845 -0
- cairn/engines/workflow/workflows/expressions.py +1312 -0
- cairn/engines/workflow/workflows/overlay/__init__.py +39 -0
- cairn/engines/workflow/workflows/overlay/command_add.py +23 -0
- cairn/engines/workflow/workflows/overlay/command_disable.py +19 -0
- cairn/engines/workflow/workflows/overlay/command_enable.py +19 -0
- cairn/engines/workflow/workflows/overlay/command_list.py +18 -0
- cairn/engines/workflow/workflows/overlay/command_remove.py +19 -0
- cairn/engines/workflow/workflows/overlay/command_set_priority.py +24 -0
- cairn/engines/workflow/workflows/overlay/composer.py +97 -0
- cairn/engines/workflow/workflows/overlay/layer_sources.py +250 -0
- cairn/engines/workflow/workflows/overlay/merge.py +466 -0
- cairn/engines/workflow/workflows/overlay/operations.py +509 -0
- cairn/engines/workflow/workflows/overlay/resolver.py +95 -0
- cairn/engines/workflow/workflows/overlay/schema.py +187 -0
- cairn/engines/workflow/workflows/step/__init__.py +28 -0
- cairn/engines/workflow/workflows/step/_helpers.py +107 -0
- cairn/engines/workflow/workflows/step/catalog/__init__.py +48 -0
- cairn/engines/workflow/workflows/step/catalog/_domain.py +710 -0
- cairn/engines/workflow/workflows/step/catalog/command_add.py +34 -0
- cairn/engines/workflow/workflows/step/catalog/command_list.py +38 -0
- cairn/engines/workflow/workflows/step/catalog/command_remove.py +27 -0
- cairn/engines/workflow/workflows/step/command/__init__.py +383 -0
- cairn/engines/workflow/workflows/step/command_add.py +376 -0
- cairn/engines/workflow/workflows/step/command_info.py +78 -0
- cairn/engines/workflow/workflows/step/command_list.py +49 -0
- cairn/engines/workflow/workflows/step/command_remove.py +97 -0
- cairn/engines/workflow/workflows/step/command_search.py +47 -0
- cairn/engines/workflow/workflows/step/do_while/__init__.py +163 -0
- cairn/engines/workflow/workflows/step/fan_in/__init__.py +132 -0
- cairn/engines/workflow/workflows/step/fan_out/__init__.py +107 -0
- cairn/engines/workflow/workflows/step/gate/__init__.py +340 -0
- cairn/engines/workflow/workflows/step/if_then/__init__.py +145 -0
- cairn/engines/workflow/workflows/step/init/__init__.py +321 -0
- cairn/engines/workflow/workflows/step/prompt/__init__.py +306 -0
- cairn/engines/workflow/workflows/step/shell/__init__.py +169 -0
- cairn/engines/workflow/workflows/step/slot/__init__.py +67 -0
- cairn/engines/workflow/workflows/step/switch/__init__.py +141 -0
- cairn/engines/workflow/workflows/step/while_loop/__init__.py +176 -0
- cairn/graphviews.py +169 -0
- cairn/hooks.py +153 -0
- cairn/linker.py +102 -0
- cairn/mcp_server.py +304 -0
- cairn/platform/__init__.py +21 -0
- cairn/platform/commands.py +433 -0
- cairn/platform/config.py +149 -0
- cairn/platform/db.py +193 -0
- cairn/platform/errors.py +45 -0
- cairn/platform/gitops.py +158 -0
- cairn/platform/models.py +247 -0
- cairn/platform/rbac.py +97 -0
- cairn/platform/security.py +193 -0
- cairn/platform/service.py +1442 -0
- cairn/platform/web.py +931 -0
- cairn/project.py +337 -0
- cairn/router.py +390 -0
- cairn/server.py +1225 -0
- cairn/shellcmd.py +114 -0
- cairn/standup.py +109 -0
- cairn/store.py +355 -0
- cairn/sync.py +202 -0
- cairn/templates/claude/agents/cairn-architect.md +11 -0
- cairn/templates/claude/agents/cairn-implementer.md +10 -0
- cairn/templates/claude/agents/cairn-reviewer.md +10 -0
- cairn/templates/claude/agents/cairn-scout.md +11 -0
- cairn/templates/claude/commands/ask.md +6 -0
- cairn/templates/claude/commands/brief.md +5 -0
- cairn/templates/claude/commands/drift.md +7 -0
- cairn/templates/claude/commands/impact.md +7 -0
- cairn/templates/claude/commands/remember.md +7 -0
- cairn/templates/claude/commands/why.md +7 -0
- cairn/templates/instructions.md +13 -0
- cairn/ui/app/api.js +298 -0
- cairn/ui/app/components/archmap.js +97 -0
- cairn/ui/app/components/communitymap.js +227 -0
- cairn/ui/app/components/narrate.js +133 -0
- cairn/ui/app/components/repomap.js +131 -0
- cairn/ui/app/components/scalebar.js +29 -0
- cairn/ui/app/components/shell.js +293 -0
- cairn/ui/app/components/stepper.js +41 -0
- cairn/ui/app/components/traversal.js +122 -0
- cairn/ui/app/components/ui.js +170 -0
- cairn/ui/app/lib/contour.js +74 -0
- cairn/ui/app/lib/cyto.js +45 -0
- cairn/ui/app/lib/format.js +114 -0
- cairn/ui/app/lib/md.js +38 -0
- cairn/ui/app/main.js +219 -0
- cairn/ui/app/router.js +80 -0
- cairn/ui/app/state.js +81 -0
- cairn/ui/app/views/account.js +69 -0
- cairn/ui/app/views/auth.js +142 -0
- cairn/ui/app/views/impact.js +167 -0
- cairn/ui/app/views/map.js +735 -0
- cairn/ui/app/views/memory.js +168 -0
- cairn/ui/app/views/overview.js +253 -0
- cairn/ui/app/views/sessions.js +278 -0
- cairn/ui/app/views/settings.js +122 -0
- cairn/ui/app/views/specs.js +259 -0
- cairn/ui/app/views/team.js +326 -0
- cairn/ui/app/views/timeline.js +230 -0
- cairn/ui/app/views/users.js +36 -0
- cairn/ui/fonts/OFL-mono.txt +93 -0
- cairn/ui/fonts/OFL.txt +93 -0
- cairn/ui/fonts/overpass-latin-ext-wght-normal.woff2 +0 -0
- cairn/ui/fonts/overpass-latin-wght-normal.woff2 +0 -0
- cairn/ui/fonts/overpass-mono-latin-ext-wght-normal.woff2 +0 -0
- cairn/ui/fonts/overpass-mono-latin-wght-normal.woff2 +0 -0
- cairn/ui/index.html +40 -0
- cairn/ui/mock/data/cairn.json +1 -0
- cairn/ui/mock/data/platform.json +1 -0
- cairn/ui/mock/mock.js +1053 -0
- cairn/ui/mock/views/callflow.html +8 -0
- cairn/ui/mock/views/graph.html +8 -0
- cairn/ui/mock/views/tree.html +8 -0
- cairn/ui/styles/base.css +308 -0
- cairn/ui/styles/shell.css +159 -0
- cairn/ui/styles/tokens.css +99 -0
- cairn/ui/styles/views.css +791 -0
- cairn/ui/vendor/cose-base/LICENSE +21 -0
- cairn/ui/vendor/cose-base/cose-base.js +3214 -0
- cairn/ui/vendor/cytoscape/LICENSE +19 -0
- cairn/ui/vendor/cytoscape/cytoscape.min.js +31 -0
- cairn/ui/vendor/cytoscape-fcose/LICENSE +19 -0
- cairn/ui/vendor/cytoscape-fcose/cytoscape-fcose.js +1549 -0
- cairn/ui/vendor/d3-hierarchy/LICENSE +13 -0
- cairn/ui/vendor/d3-hierarchy/d3-hierarchy.min.js +2 -0
- cairn/ui/vendor/dompurify/LICENSE +202 -0
- cairn/ui/vendor/dompurify/LICENSE-MPL +373 -0
- cairn/ui/vendor/dompurify/purify.es.mjs +2463 -0
- cairn/ui/vendor/htm/LICENSE +202 -0
- cairn/ui/vendor/htm/htm.module.js +1 -0
- cairn/ui/vendor/layout-base/LICENSE +21 -0
- cairn/ui/vendor/layout-base/layout-base.js +5230 -0
- cairn/ui/vendor/marked/LICENSE.md +44 -0
- cairn/ui/vendor/marked/marked.esm.js +2189 -0
- cairn/ui/vendor/preact/LICENSE +21 -0
- cairn/ui/vendor/preact/hooks.module.js +2 -0
- cairn/ui/vendor/preact/preact.module.js +2 -0
- cairn_brain-0.2.1.dist-info/METADATA +302 -0
- cairn_brain-0.2.1.dist-info/RECORD +875 -0
- cairn_brain-0.2.1.dist-info/WHEEL +4 -0
- cairn_brain-0.2.1.dist-info/entry_points.txt +2 -0
- cairn_brain-0.2.1.dist-info/licenses/LICENSE +202 -0
- 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
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())
|