docir 0.1.0__tar.gz

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 (114) hide show
  1. docir-0.1.0/PKG-INFO +205 -0
  2. docir-0.1.0/README.md +187 -0
  3. docir-0.1.0/pyproject.toml +117 -0
  4. docir-0.1.0/src/docir/__init__.py +25 -0
  5. docir-0.1.0/src/docir/__main__.py +7 -0
  6. docir-0.1.0/src/docir/config/__init__.py +0 -0
  7. docir-0.1.0/src/docir/config/settings.py +157 -0
  8. docir-0.1.0/src/docir/entry_points/__init__.py +0 -0
  9. docir-0.1.0/src/docir/entry_points/cli/__init__.py +0 -0
  10. docir-0.1.0/src/docir/entry_points/cli/app.py +516 -0
  11. docir-0.1.0/src/docir/entry_points/cli/body_input.py +32 -0
  12. docir-0.1.0/src/docir/entry_points/cli/rendering.py +209 -0
  13. docir-0.1.0/src/docir/entry_points/cli/runner.py +103 -0
  14. docir-0.1.0/src/docir/entry_points/composition.py +208 -0
  15. docir-0.1.0/src/docir/entry_points/daemon/__init__.py +0 -0
  16. docir-0.1.0/src/docir/entry_points/daemon/cmds.py +69 -0
  17. docir-0.1.0/src/docir/entry_points/daemon/lifecycle.py +138 -0
  18. docir-0.1.0/src/docir/entry_points/daemon/socket_executor.py +32 -0
  19. docir-0.1.0/src/docir/entry_points/dispatch.py +225 -0
  20. docir-0.1.0/src/docir/modules/__init__.py +0 -0
  21. docir-0.1.0/src/docir/modules/agents/CONTRACT.md +50 -0
  22. docir-0.1.0/src/docir/modules/agents/__init__.py +0 -0
  23. docir-0.1.0/src/docir/modules/agents/api.py +46 -0
  24. docir-0.1.0/src/docir/modules/agents/application/__init__.py +0 -0
  25. docir-0.1.0/src/docir/modules/agents/application/ports.py +31 -0
  26. docir-0.1.0/src/docir/modules/agents/application/service.py +146 -0
  27. docir-0.1.0/src/docir/modules/agents/domain/__init__.py +0 -0
  28. docir-0.1.0/src/docir/modules/agents/domain/rendering.py +84 -0
  29. docir-0.1.0/src/docir/modules/agents/domain/results.py +35 -0
  30. docir-0.1.0/src/docir/modules/agents/domain/targets.py +66 -0
  31. docir-0.1.0/src/docir/modules/agents/infra/__init__.py +0 -0
  32. docir-0.1.0/src/docir/modules/agents/infra/file_sink.py +19 -0
  33. docir-0.1.0/src/docir/modules/agents/infra/template_provider.py +22 -0
  34. docir-0.1.0/src/docir/modules/agents/infra/templates/__init__.py +0 -0
  35. docir-0.1.0/src/docir/modules/agents/infra/templates/skill.md +201 -0
  36. docir-0.1.0/src/docir/modules/documents/CONTRACT.md +54 -0
  37. docir-0.1.0/src/docir/modules/documents/__init__.py +0 -0
  38. docir-0.1.0/src/docir/modules/documents/api.py +45 -0
  39. docir-0.1.0/src/docir/modules/documents/application/__init__.py +0 -0
  40. docir-0.1.0/src/docir/modules/documents/application/dto.py +209 -0
  41. docir-0.1.0/src/docir/modules/documents/application/services/__init__.py +0 -0
  42. docir-0.1.0/src/docir/modules/documents/application/services/document_service.py +398 -0
  43. docir-0.1.0/src/docir/modules/documents/application/services/id_generator.py +40 -0
  44. docir-0.1.0/src/docir/modules/documents/application/services/maintenance_service.py +174 -0
  45. docir-0.1.0/src/docir/modules/documents/domain/__init__.py +0 -0
  46. docir-0.1.0/src/docir/modules/documents/domain/entities/__init__.py +0 -0
  47. docir-0.1.0/src/docir/modules/documents/domain/entities/document.py +83 -0
  48. docir-0.1.0/src/docir/modules/documents/domain/entities/relation.py +23 -0
  49. docir-0.1.0/src/docir/modules/documents/domain/schema.py +163 -0
  50. docir-0.1.0/src/docir/modules/documents/domain/services/__init__.py +0 -0
  51. docir-0.1.0/src/docir/modules/documents/domain/services/graph_checks.py +223 -0
  52. docir-0.1.0/src/docir/modules/documents/domain/services/markdown_sections.py +66 -0
  53. docir-0.1.0/src/docir/modules/documents/domain/services/similarity_lint.py +69 -0
  54. docir-0.1.0/src/docir/modules/documents/domain/services/slugify.py +21 -0
  55. docir-0.1.0/src/docir/modules/documents/domain/services/validation.py +93 -0
  56. docir-0.1.0/src/docir/modules/documents/domain/value_objects/__init__.py +0 -0
  57. docir-0.1.0/src/docir/modules/documents/domain/value_objects/identifiers.py +76 -0
  58. docir-0.1.0/src/docir/modules/documents/domain/value_objects/queries.py +24 -0
  59. docir-0.1.0/src/docir/modules/documents/domain/value_objects/relations.py +45 -0
  60. docir-0.1.0/src/docir/modules/documents/infra/__init__.py +0 -0
  61. docir-0.1.0/src/docir/modules/documents/infra/default_schema.py +43 -0
  62. docir-0.1.0/src/docir/modules/documents/infra/profiles.py +176 -0
  63. docir-0.1.0/src/docir/modules/documents/infra/schema_loader.py +177 -0
  64. docir-0.1.0/src/docir/modules/indexing/CONTRACT.md +31 -0
  65. docir-0.1.0/src/docir/modules/indexing/__init__.py +0 -0
  66. docir-0.1.0/src/docir/modules/indexing/api.py +44 -0
  67. docir-0.1.0/src/docir/modules/indexing/application/__init__.py +0 -0
  68. docir-0.1.0/src/docir/modules/indexing/application/ports/__init__.py +0 -0
  69. docir-0.1.0/src/docir/modules/indexing/application/ports/scheduler.py +37 -0
  70. docir-0.1.0/src/docir/modules/indexing/domain/__init__.py +0 -0
  71. docir-0.1.0/src/docir/modules/indexing/domain/results.py +13 -0
  72. docir-0.1.0/src/docir/modules/indexing/domain/scoring.py +73 -0
  73. docir-0.1.0/src/docir/modules/indexing/infra/__init__.py +0 -0
  74. docir-0.1.0/src/docir/modules/indexing/infra/scheduler.py +115 -0
  75. docir-0.1.0/src/docir/modules/tags/CONTRACT.md +32 -0
  76. docir-0.1.0/src/docir/modules/tags/__init__.py +0 -0
  77. docir-0.1.0/src/docir/modules/tags/api.py +12 -0
  78. docir-0.1.0/src/docir/modules/tags/application/__init__.py +0 -0
  79. docir-0.1.0/src/docir/modules/tags/application/dto.py +13 -0
  80. docir-0.1.0/src/docir/modules/tags/application/services/__init__.py +0 -0
  81. docir-0.1.0/src/docir/modules/tags/application/services/tag_service.py +109 -0
  82. docir-0.1.0/src/docir/modules/tags/domain/__init__.py +0 -0
  83. docir-0.1.0/src/docir/modules/tags/domain/entities/__init__.py +0 -0
  84. docir-0.1.0/src/docir/modules/tags/domain/entities/tag.py +19 -0
  85. docir-0.1.0/src/docir/platform/__init__.py +0 -0
  86. docir-0.1.0/src/docir/platform/clock/__init__.py +6 -0
  87. docir-0.1.0/src/docir/platform/clock/port.py +19 -0
  88. docir-0.1.0/src/docir/platform/clock/system.py +14 -0
  89. docir-0.1.0/src/docir/platform/embedding/__init__.py +10 -0
  90. docir-0.1.0/src/docir/platform/embedding/deterministic.py +49 -0
  91. docir-0.1.0/src/docir/platform/embedding/fastembed.py +51 -0
  92. docir-0.1.0/src/docir/platform/embedding/port.py +30 -0
  93. docir-0.1.0/src/docir/platform/embedding/vector.py +49 -0
  94. docir-0.1.0/src/docir/platform/errors/__init__.py +123 -0
  95. docir-0.1.0/src/docir/platform/filesystem/__init__.py +7 -0
  96. docir-0.1.0/src/docir/platform/filesystem/markdown_store.py +185 -0
  97. docir-0.1.0/src/docir/platform/filesystem/ports.py +62 -0
  98. docir-0.1.0/src/docir/platform/filesystem/tag_store.py +35 -0
  99. docir-0.1.0/src/docir/platform/persistence/__init__.py +0 -0
  100. docir-0.1.0/src/docir/platform/persistence/alembic/env.py +45 -0
  101. docir-0.1.0/src/docir/platform/persistence/alembic/script.py.mako +26 -0
  102. docir-0.1.0/src/docir/platform/persistence/alembic/versions/0001_initial_index.py +106 -0
  103. docir-0.1.0/src/docir/platform/persistence/alembic/versions/0002_typed_edges_and_staleness.py +46 -0
  104. docir-0.1.0/src/docir/platform/persistence/engine.py +45 -0
  105. docir-0.1.0/src/docir/platform/persistence/models.py +96 -0
  106. docir-0.1.0/src/docir/platform/persistence/ports.py +134 -0
  107. docir-0.1.0/src/docir/platform/persistence/repositories.py +362 -0
  108. docir-0.1.0/src/docir/platform/persistence/sqlalchemy_uow.py +61 -0
  109. docir-0.1.0/src/docir/platform/persistence/unit_of_work.py +51 -0
  110. docir-0.1.0/src/docir/platform/transport/__init__.py +0 -0
  111. docir-0.1.0/src/docir/platform/transport/client.py +47 -0
  112. docir-0.1.0/src/docir/platform/transport/messages.py +70 -0
  113. docir-0.1.0/src/docir/platform/transport/protocol.py +45 -0
  114. docir-0.1.0/src/docir/platform/transport/server.py +70 -0
docir-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.3
2
+ Name: docir
3
+ Version: 0.1.0
4
+ Summary: Doc-Index CLI — git-backed markdown documents with a derived SQLite/FTS5/semantic index, served by a warm local daemon.
5
+ Author: Sergei Konovalov
6
+ License: MIT
7
+ Requires-Dist: typer>=0.15
8
+ Requires-Dist: rich>=13.9
9
+ Requires-Dist: pyyaml>=6.0
10
+ Requires-Dist: python-frontmatter>=1.1
11
+ Requires-Dist: sqlalchemy>=2.0
12
+ Requires-Dist: alembic>=1.14
13
+ Requires-Dist: pydantic-settings>=2.7
14
+ Requires-Dist: fastembed>=0.5 ; extra == 'embeddings'
15
+ Requires-Python: >=3.12
16
+ Provides-Extra: embeddings
17
+ Description-Content-Type: text/markdown
18
+
19
+ <div align="center">
20
+
21
+ <picture>
22
+ <source media="(prefers-color-scheme: dark)" srcset="assets/logo/docir-lockup-dark.png" />
23
+ <img src="assets/logo/docir-lockup.png" alt="docir" width="260" />
24
+ </picture>
25
+
26
+ **doc**uments as **IR** — a CLI that *compiles* git-backed markdown<br />into a verifiable, read-optimized index for AI coding agents.
27
+
28
+ [![PyPI](https://img.shields.io/pypi/v/docir)](https://pypi.org/project/docir/) [![Python](https://img.shields.io/pypi/pyversions/docir)](https://pypi.org/project/docir/) [![CI](https://img.shields.io/github/actions/workflow/status/l0kifs/docir/ci.yml?branch=main)](https://github.com/l0kifs/docir/actions) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
29
+
30
+ [The idea](#the-idea) · [Quickstart](#quickstart) · [Why not just…](#why-not-just) · [Commands](#commands) · [Docs](docs/)
31
+
32
+ </div>
33
+
34
+ ---
35
+
36
+ ## The idea
37
+
38
+ "IR" is *intermediate representation* — the thing a compiler turns source code into.
39
+ docir treats your markdown the same way: the **files are the source**, and the SQLite
40
+ index (metadata + FTS5 full-text + a typed relation graph + semantic embeddings) is a
41
+ **derived artifact you can throw away and rebuild**.
42
+
43
+ ```text
44
+ source of truth docir derived index
45
+ canonical the compiler rebuildable · gitignored
46
+ ─────────────── ────────────────── ─────────────────────────
47
+ decisions/*.md parse · validate metadata · FTS5
48
+ issues/*.md ──▶ allocate ids ──▶ relation graph (typed)
49
+ tags.yaml embed (deferred) vector embeddings
50
+ ```
51
+
52
+ > **Git is canonical.** `docir reindex` rebuilds the entire index from the files. When the
53
+ > database and the files disagree, the files win.
54
+
55
+ ## Why not just…
56
+
57
+ | | plain `.md` files | RAG over your docs | **docir** |
58
+ |---|:---:|:---:|:---:|
59
+ | Consistent frontmatter / schema | ❌ | ❌ | ✅ enforced |
60
+ | Retrieval by meaning | ❌ | ✅ | ✅ (lexical + semantic) |
61
+ | Typed relation graph | ❌ | ❌ | ✅ |
62
+ | Knows what's stale | ❌ | ❌ | ✅ |
63
+ | Works offline, nothing to run | ✅ | ⚠️ | ✅ |
64
+ | Token-cheap for agents | ❌ | ⚠️ | ✅ skeletons |
65
+
66
+ *Rough orientation, not a benchmark — the right tool depends on your setup.*
67
+
68
+ ## Quickstart
69
+
70
+ ```bash
71
+ # 1. install
72
+ uv tool install docir # or: pipx install docir
73
+
74
+ # 2. scope docs to this repo (creates ./.docir, like `git init`)
75
+ docir init
76
+
77
+ # 3. teach this repo's AI agent to drive docir (writes a Claude Code skill)
78
+ docir agent install # add --agent agents for an AGENTS.md block
79
+
80
+ # 4. capture a decision…
81
+ docir add --type decision --title "Auth strategy" \
82
+ --description "How the service authenticates API clients." --stdin < draft.md
83
+
84
+ # 5. …and retrieve it by intent, next session
85
+ docir context "implement a new auth endpoint"
86
+ ```
87
+
88
+ In a terminal, `docir context` prints ranked, body-less **skeletons** — frontmatter and
89
+ typed edges, no body — so you scan wide, then fetch a body by id with `docir get`:
90
+
91
+ ```console
92
+ $ docir context "implement a new auth endpoint"
93
+ ┏━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
94
+ ┃ id ┃ type ┃ status ┃ title ┃ description ┃ score ┃
95
+ ┡━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
96
+ │ adr-0001 │ decision │ proposed │ Auth strategy │ How the service authenticates │ 0.033 │
97
+ │ │ │ │ │ API clients. │ │
98
+ │ issue-0001 │ issue │ open │ Token refresh race │ Refresh token race under │ 0.016 │
99
+ │ │ │ │ │ concurrent logins. │ │
100
+ └────────────┴──────────┴──────────┴────────────────────┴──────────────────────────────────┴───────┘
101
+ ```
102
+
103
+ Built for agents, though: when the output is **captured** (stdout isn't a TTY), the same
104
+ command emits **compact, trimmed JSON** — no borders, empty fields dropped, ~40% fewer tokens:
105
+
106
+ ```console
107
+ $ docir context "implement a new auth endpoint" | cat
108
+ [{"id":"adr-0001","title":"Auth strategy","description":"How the service authenticates API clients.","type":"decision","status":"proposed","tags":["auth"],"archived":false,"stale":false,"score":0.0328,"via_graph":false}, ...]
109
+ ```
110
+
111
+ *An absent field means its default (no owner, not stale); the relevance `score` is a
112
+ reciprocal-rank fusion of full-text + semantic matches, so ordering is the point. `--json`
113
+ forces JSON anywhere, `--pretty` forces the table, `--no-trim` keeps every field.*
114
+
115
+ ## The model
116
+
117
+ - **Git is the source of truth.** The index is a compile artifact — derived,
118
+ `.gitignore`d, rebuildable. Nothing lives only in the database.
119
+ - **One write path.** Agents never edit markdown directly; every write goes through the
120
+ CLI, which guarantees frontmatter/schema consistency and collision-free id allocation.
121
+ - **Reads return skeletons.** `query` / `search` / `context` return frontmatter + typed
122
+ edges + staleness — *no body*. Fetch bodies by id with `get`. An agent scans wide cheaply,
123
+ then reads deep only where it matters.
124
+ - **Staleness is data, not a guess.** Optional `owner` / `verified` fields plus a per-type
125
+ review cadence make "is this doc still true?" a first-class, checkable fact (`docir check`).
126
+ - **Relations are typed.** A `related` edge carries a *kind* (`supersedes`, `depends_on`,
127
+ `implements`, …) — a real graph, not a bag of links.
128
+
129
+ ## Commands
130
+
131
+ | Command | What it does |
132
+ |---|---|
133
+ | `docir init` | Scope docs to a project-local `./.docir` store (like `git init`) |
134
+ | `docir add` | Create a document — the single write path |
135
+ | `docir update` | Edit content, metadata, or relations of an existing document |
136
+ | `docir context <query>` | Hybrid lexical + semantic ranked relevant set (skeletons) |
137
+ | `docir search` / `query` | Full-text search / structured filter (skeletons) |
138
+ | `docir get <id>` | Full document with body |
139
+ | `docir check` | Structural graph warnings — staleness, unknown types (`--strict` for CI) |
140
+ | `docir agent install` | Teach this repo's AI agent to drive docir |
141
+
142
+ ### Full command reference
143
+
144
+ ```
145
+ init · add · update · archive · unarchive · delete
146
+ get · query · search · context
147
+ tag {add, list, rename, rm}
148
+ agent {install, update}
149
+ check · lint · reindex · embed · version
150
+ daemon serve
151
+ ```
152
+
153
+ Store precedence (highest first): `--home` → `DOCIR_HOME` → a project-local `.docir/`
154
+ found by walking up from the CWD → the global `~/.docir` default. `--no-daemon` runs any
155
+ command in-process instead of over the daemon socket. Output is a Rich table at a TTY and
156
+ compact JSON when piped; `--json` / `--pretty` force either, and `--no-trim` keeps every field.
157
+
158
+ ## How state is stored
159
+
160
+ State lives in one resolved store per invocation. Run `docir init` in a repo to keep its
161
+ docs with the code: `.docir/docs/` and `docs-schema.yaml` are **committed**; the derived
162
+ index (SQLite + embeddings) is **gitignored** and rebuilds with `docir reindex`. Without
163
+ `init`, docir falls back to a global `~/.docir`.
164
+
165
+ The daemon keeps the embedding model warm and serializes writes; the CLI is a thin,
166
+ stateless client that spawns and respawns it transparently. Embeddings are the one
167
+ deferred, eventually-consistent piece — a content change flags the vector dirty and returns;
168
+ everything else (file, metadata, FTS, relations) is synchronous. Force a flush with
169
+ `--wait-embeddings`, `docir embed --flush`, or `docir reindex --embeddings`.
170
+
171
+ ## Schema: core + profiles
172
+
173
+ Documents are constrained by a per-type schema (required fields, status grammar, allowed
174
+ relations). docir ships a frozen, domain-agnostic **core** plus swappable **profiles** —
175
+ `software` (default: `decision` / `issue` / `architecture`), `research`, `ops`, `legal`.
176
+ A `docs-schema.yaml` merges `core → profiles → inline`, so you extend it without mutating
177
+ the base.
178
+
179
+ ## Architecture
180
+
181
+ Vertical bounded-context **modules** (`documents`, `tags`, `indexing`, `agents`) over a
182
+ shared **platform**, wired by thin **entry_points**. Dependencies flow
183
+ `entry_points → modules → platform → config`; boundaries are enforced by
184
+ [tach](https://docs.gauge.sh) in CI — not by convention. Each module exposes exactly one
185
+ public file (`api.py`) described by a `CONTRACT.md`.
186
+
187
+ See [docs/doc-index-architecture.md](docs/doc-index-architecture.md) for the design
188
+ rationale and [docs/architecture-rules.md](docs/architecture-rules.md) for the module rules.
189
+
190
+ ## Contributing
191
+
192
+ Issues and PRs welcome. Read the [architecture rules](docs/architecture-rules.md) and the
193
+ [ADRs](docs/adr/) first — module boundaries are machine-checked by [tach](https://docs.gauge.sh)
194
+ in CI, alongside lint, type-check, and a coverage gate. Every design deviation is recorded
195
+ as an ADR.
196
+
197
+ ```bash
198
+ uv sync # dev environment
199
+ uv run pytest --cov=docir --cov-fail-under=90 # tests + coverage gate
200
+ uv run ruff check . && uv run ty check && uv run tach check
201
+ ```
202
+
203
+ ## License
204
+
205
+ MIT © Sergei Konovalov
docir-0.1.0/README.md ADDED
@@ -0,0 +1,187 @@
1
+ <div align="center">
2
+
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="assets/logo/docir-lockup-dark.png" />
5
+ <img src="assets/logo/docir-lockup.png" alt="docir" width="260" />
6
+ </picture>
7
+
8
+ **doc**uments as **IR** — a CLI that *compiles* git-backed markdown<br />into a verifiable, read-optimized index for AI coding agents.
9
+
10
+ [![PyPI](https://img.shields.io/pypi/v/docir)](https://pypi.org/project/docir/) [![Python](https://img.shields.io/pypi/pyversions/docir)](https://pypi.org/project/docir/) [![CI](https://img.shields.io/github/actions/workflow/status/l0kifs/docir/ci.yml?branch=main)](https://github.com/l0kifs/docir/actions) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
11
+
12
+ [The idea](#the-idea) · [Quickstart](#quickstart) · [Why not just…](#why-not-just) · [Commands](#commands) · [Docs](docs/)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## The idea
19
+
20
+ "IR" is *intermediate representation* — the thing a compiler turns source code into.
21
+ docir treats your markdown the same way: the **files are the source**, and the SQLite
22
+ index (metadata + FTS5 full-text + a typed relation graph + semantic embeddings) is a
23
+ **derived artifact you can throw away and rebuild**.
24
+
25
+ ```text
26
+ source of truth docir derived index
27
+ canonical the compiler rebuildable · gitignored
28
+ ─────────────── ────────────────── ─────────────────────────
29
+ decisions/*.md parse · validate metadata · FTS5
30
+ issues/*.md ──▶ allocate ids ──▶ relation graph (typed)
31
+ tags.yaml embed (deferred) vector embeddings
32
+ ```
33
+
34
+ > **Git is canonical.** `docir reindex` rebuilds the entire index from the files. When the
35
+ > database and the files disagree, the files win.
36
+
37
+ ## Why not just…
38
+
39
+ | | plain `.md` files | RAG over your docs | **docir** |
40
+ |---|:---:|:---:|:---:|
41
+ | Consistent frontmatter / schema | ❌ | ❌ | ✅ enforced |
42
+ | Retrieval by meaning | ❌ | ✅ | ✅ (lexical + semantic) |
43
+ | Typed relation graph | ❌ | ❌ | ✅ |
44
+ | Knows what's stale | ❌ | ❌ | ✅ |
45
+ | Works offline, nothing to run | ✅ | ⚠️ | ✅ |
46
+ | Token-cheap for agents | ❌ | ⚠️ | ✅ skeletons |
47
+
48
+ *Rough orientation, not a benchmark — the right tool depends on your setup.*
49
+
50
+ ## Quickstart
51
+
52
+ ```bash
53
+ # 1. install
54
+ uv tool install docir # or: pipx install docir
55
+
56
+ # 2. scope docs to this repo (creates ./.docir, like `git init`)
57
+ docir init
58
+
59
+ # 3. teach this repo's AI agent to drive docir (writes a Claude Code skill)
60
+ docir agent install # add --agent agents for an AGENTS.md block
61
+
62
+ # 4. capture a decision…
63
+ docir add --type decision --title "Auth strategy" \
64
+ --description "How the service authenticates API clients." --stdin < draft.md
65
+
66
+ # 5. …and retrieve it by intent, next session
67
+ docir context "implement a new auth endpoint"
68
+ ```
69
+
70
+ In a terminal, `docir context` prints ranked, body-less **skeletons** — frontmatter and
71
+ typed edges, no body — so you scan wide, then fetch a body by id with `docir get`:
72
+
73
+ ```console
74
+ $ docir context "implement a new auth endpoint"
75
+ ┏━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
76
+ ┃ id ┃ type ┃ status ┃ title ┃ description ┃ score ┃
77
+ ┡━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
78
+ │ adr-0001 │ decision │ proposed │ Auth strategy │ How the service authenticates │ 0.033 │
79
+ │ │ │ │ │ API clients. │ │
80
+ │ issue-0001 │ issue │ open │ Token refresh race │ Refresh token race under │ 0.016 │
81
+ │ │ │ │ │ concurrent logins. │ │
82
+ └────────────┴──────────┴──────────┴────────────────────┴──────────────────────────────────┴───────┘
83
+ ```
84
+
85
+ Built for agents, though: when the output is **captured** (stdout isn't a TTY), the same
86
+ command emits **compact, trimmed JSON** — no borders, empty fields dropped, ~40% fewer tokens:
87
+
88
+ ```console
89
+ $ docir context "implement a new auth endpoint" | cat
90
+ [{"id":"adr-0001","title":"Auth strategy","description":"How the service authenticates API clients.","type":"decision","status":"proposed","tags":["auth"],"archived":false,"stale":false,"score":0.0328,"via_graph":false}, ...]
91
+ ```
92
+
93
+ *An absent field means its default (no owner, not stale); the relevance `score` is a
94
+ reciprocal-rank fusion of full-text + semantic matches, so ordering is the point. `--json`
95
+ forces JSON anywhere, `--pretty` forces the table, `--no-trim` keeps every field.*
96
+
97
+ ## The model
98
+
99
+ - **Git is the source of truth.** The index is a compile artifact — derived,
100
+ `.gitignore`d, rebuildable. Nothing lives only in the database.
101
+ - **One write path.** Agents never edit markdown directly; every write goes through the
102
+ CLI, which guarantees frontmatter/schema consistency and collision-free id allocation.
103
+ - **Reads return skeletons.** `query` / `search` / `context` return frontmatter + typed
104
+ edges + staleness — *no body*. Fetch bodies by id with `get`. An agent scans wide cheaply,
105
+ then reads deep only where it matters.
106
+ - **Staleness is data, not a guess.** Optional `owner` / `verified` fields plus a per-type
107
+ review cadence make "is this doc still true?" a first-class, checkable fact (`docir check`).
108
+ - **Relations are typed.** A `related` edge carries a *kind* (`supersedes`, `depends_on`,
109
+ `implements`, …) — a real graph, not a bag of links.
110
+
111
+ ## Commands
112
+
113
+ | Command | What it does |
114
+ |---|---|
115
+ | `docir init` | Scope docs to a project-local `./.docir` store (like `git init`) |
116
+ | `docir add` | Create a document — the single write path |
117
+ | `docir update` | Edit content, metadata, or relations of an existing document |
118
+ | `docir context <query>` | Hybrid lexical + semantic ranked relevant set (skeletons) |
119
+ | `docir search` / `query` | Full-text search / structured filter (skeletons) |
120
+ | `docir get <id>` | Full document with body |
121
+ | `docir check` | Structural graph warnings — staleness, unknown types (`--strict` for CI) |
122
+ | `docir agent install` | Teach this repo's AI agent to drive docir |
123
+
124
+ ### Full command reference
125
+
126
+ ```
127
+ init · add · update · archive · unarchive · delete
128
+ get · query · search · context
129
+ tag {add, list, rename, rm}
130
+ agent {install, update}
131
+ check · lint · reindex · embed · version
132
+ daemon serve
133
+ ```
134
+
135
+ Store precedence (highest first): `--home` → `DOCIR_HOME` → a project-local `.docir/`
136
+ found by walking up from the CWD → the global `~/.docir` default. `--no-daemon` runs any
137
+ command in-process instead of over the daemon socket. Output is a Rich table at a TTY and
138
+ compact JSON when piped; `--json` / `--pretty` force either, and `--no-trim` keeps every field.
139
+
140
+ ## How state is stored
141
+
142
+ State lives in one resolved store per invocation. Run `docir init` in a repo to keep its
143
+ docs with the code: `.docir/docs/` and `docs-schema.yaml` are **committed**; the derived
144
+ index (SQLite + embeddings) is **gitignored** and rebuilds with `docir reindex`. Without
145
+ `init`, docir falls back to a global `~/.docir`.
146
+
147
+ The daemon keeps the embedding model warm and serializes writes; the CLI is a thin,
148
+ stateless client that spawns and respawns it transparently. Embeddings are the one
149
+ deferred, eventually-consistent piece — a content change flags the vector dirty and returns;
150
+ everything else (file, metadata, FTS, relations) is synchronous. Force a flush with
151
+ `--wait-embeddings`, `docir embed --flush`, or `docir reindex --embeddings`.
152
+
153
+ ## Schema: core + profiles
154
+
155
+ Documents are constrained by a per-type schema (required fields, status grammar, allowed
156
+ relations). docir ships a frozen, domain-agnostic **core** plus swappable **profiles** —
157
+ `software` (default: `decision` / `issue` / `architecture`), `research`, `ops`, `legal`.
158
+ A `docs-schema.yaml` merges `core → profiles → inline`, so you extend it without mutating
159
+ the base.
160
+
161
+ ## Architecture
162
+
163
+ Vertical bounded-context **modules** (`documents`, `tags`, `indexing`, `agents`) over a
164
+ shared **platform**, wired by thin **entry_points**. Dependencies flow
165
+ `entry_points → modules → platform → config`; boundaries are enforced by
166
+ [tach](https://docs.gauge.sh) in CI — not by convention. Each module exposes exactly one
167
+ public file (`api.py`) described by a `CONTRACT.md`.
168
+
169
+ See [docs/doc-index-architecture.md](docs/doc-index-architecture.md) for the design
170
+ rationale and [docs/architecture-rules.md](docs/architecture-rules.md) for the module rules.
171
+
172
+ ## Contributing
173
+
174
+ Issues and PRs welcome. Read the [architecture rules](docs/architecture-rules.md) and the
175
+ [ADRs](docs/adr/) first — module boundaries are machine-checked by [tach](https://docs.gauge.sh)
176
+ in CI, alongside lint, type-check, and a coverage gate. Every design deviation is recorded
177
+ as an ADR.
178
+
179
+ ```bash
180
+ uv sync # dev environment
181
+ uv run pytest --cov=docir --cov-fail-under=90 # tests + coverage gate
182
+ uv run ruff check . && uv run ty check && uv run tach check
183
+ ```
184
+
185
+ ## License
186
+
187
+ MIT © Sergei Konovalov
@@ -0,0 +1,117 @@
1
+ [project]
2
+ name = "docir"
3
+ version = "0.1.0"
4
+ description = "Doc-Index CLI — git-backed markdown documents with a derived SQLite/FTS5/semantic index, served by a warm local daemon."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Sergei Konovalov" }]
9
+ dependencies = [
10
+ "typer>=0.15",
11
+ "rich>=13.9",
12
+ "pyyaml>=6.0",
13
+ "python-frontmatter>=1.1",
14
+ "sqlalchemy>=2.0",
15
+ "alembic>=1.14",
16
+ "pydantic-settings>=2.7",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ # Real ONNX semantic embeddings. Kept optional so the default install and the
21
+ # test suite stay light and hermetic and fall back to the deterministic embedder.
22
+ embeddings = ["fastembed>=0.5"]
23
+
24
+ [project.scripts]
25
+ docir = "docir.entry_points.cli.app:main"
26
+
27
+ [dependency-groups]
28
+ dev = [
29
+ "pytest>=8.3",
30
+ "pytest-cov>=6.0",
31
+ "ruff>=0.9",
32
+ "tach>=0.35.0",
33
+ "ty>=0.0.1a1",
34
+ "vulture>=2.14",
35
+ ]
36
+
37
+ [build-system]
38
+ requires = ["uv_build>=0.11,<0.13"]
39
+ build-backend = "uv_build"
40
+
41
+ [tool.uv.build-backend]
42
+ module-name = "docir"
43
+ module-root = "src"
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Ruff — linter + formatter (refined against docs.astral.sh/ruff)
47
+ # ---------------------------------------------------------------------------
48
+ [tool.ruff]
49
+ line-length = 100
50
+ target-version = "py312"
51
+ src = ["src", "tests"]
52
+ extend-exclude = ["migrations", "src/docir/platform/persistence/alembic"]
53
+
54
+ [tool.ruff.lint]
55
+ select = ["E", "F", "I", "N", "UP", "B", "C4", "SIM", "RUF"]
56
+ ignore = []
57
+
58
+ [tool.ruff.lint.per-file-ignores]
59
+ "**/__init__.py" = ["F401"]
60
+ "tests/**" = ["N802", "N803", "S101"]
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # ty — Astral type checker (refined against docs.astral.sh/ty)
64
+ # ---------------------------------------------------------------------------
65
+ [tool.ty.environment]
66
+ python-version = "3.12"
67
+ root = ["./src"]
68
+
69
+ [tool.ty.src]
70
+ include = ["src"]
71
+ # The fastembed adapter imports an optional, not-installed dependency and is
72
+ # exercised only when the `embeddings` extra is present.
73
+ exclude = ["src/docir/platform/embedding/fastembed.py"]
74
+
75
+ [tool.ty.terminal]
76
+ output-format = "concise"
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Vulture — dead-code finder
80
+ # ---------------------------------------------------------------------------
81
+ [tool.vulture]
82
+ paths = ["src"]
83
+ min_confidence = 70
84
+ exclude = ["*/alembic/*"]
85
+ ignore_decorators = [
86
+ "@app.command",
87
+ "@app.callback",
88
+ "@tag_app.command",
89
+ "@daemon_app.command",
90
+ "@agent_app.command",
91
+ ]
92
+
93
+ # ---------------------------------------------------------------------------
94
+ # Pytest / coverage
95
+ # ---------------------------------------------------------------------------
96
+ [tool.pytest.ini_options]
97
+ testpaths = ["tests"]
98
+ addopts = "-ra"
99
+ markers = ["slow: end-to-end tests that spawn a real daemon subprocess"]
100
+
101
+ [tool.coverage.run]
102
+ source = ["docir"]
103
+ branch = true
104
+ omit = [
105
+ "*/alembic/*",
106
+ "*/platform/embedding/fastembed.py",
107
+ ]
108
+
109
+ [tool.coverage.report]
110
+ show_missing = true
111
+ skip_covered = false
112
+ exclude_lines = [
113
+ "pragma: no cover",
114
+ "if TYPE_CHECKING:",
115
+ "raise NotImplementedError",
116
+ "@abstractmethod",
117
+ ]
@@ -0,0 +1,25 @@
1
+ # docir — Doc-Index CLI.
2
+ #
3
+ # A git-backed markdown document system with a derived, read-optimized index.
4
+ # The markdown files under the docs root are the source of truth; the SQLite
5
+ # index (metadata + FTS5 + relation graph + semantic embeddings) is a
6
+ # rebuildable projection on top of them.
7
+ #
8
+ # The package is split into four Clean Architecture layers, each in its own
9
+ # sub-package with dependencies pointing strictly inward:
10
+ #
11
+ # presentation -> application -> domain
12
+ # infrastructure ------------^ (implements domain ports)
13
+ #
14
+ # * domain — enterprise rules: entities, value objects, ports
15
+ # (interfaces), domain services, errors. Depends on
16
+ # nothing else in the package.
17
+ # * application — use cases orchestrating the domain via its ports,
18
+ # plus the DTOs that cross the boundary.
19
+ # * infrastructure — concrete adapters implementing the domain ports
20
+ # (SQLAlchemy index, filesystem store, embedder,
21
+ # scheduler, daemon transport).
22
+ # * presentation — the Typer/Rich CLI and the composition root that
23
+ # wires infrastructure adapters into the use cases.
24
+
25
+ __version__ = "0.1.0"
@@ -0,0 +1,7 @@
1
+ # Module entry point: enables `python -m docir ...`, which is how the daemon
2
+ # lifecycle spawns its detached background process.
3
+
4
+ from docir.entry_points.cli.app import main
5
+
6
+ if __name__ == "__main__":
7
+ main()
File without changes