docir 0.1.0__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.
- docir/__init__.py +25 -0
- docir/__main__.py +7 -0
- docir/config/__init__.py +0 -0
- docir/config/settings.py +157 -0
- docir/entry_points/__init__.py +0 -0
- docir/entry_points/cli/__init__.py +0 -0
- docir/entry_points/cli/app.py +516 -0
- docir/entry_points/cli/body_input.py +32 -0
- docir/entry_points/cli/rendering.py +209 -0
- docir/entry_points/cli/runner.py +103 -0
- docir/entry_points/composition.py +208 -0
- docir/entry_points/daemon/__init__.py +0 -0
- docir/entry_points/daemon/cmds.py +69 -0
- docir/entry_points/daemon/lifecycle.py +138 -0
- docir/entry_points/daemon/socket_executor.py +32 -0
- docir/entry_points/dispatch.py +225 -0
- docir/modules/__init__.py +0 -0
- docir/modules/agents/CONTRACT.md +50 -0
- docir/modules/agents/__init__.py +0 -0
- docir/modules/agents/api.py +46 -0
- docir/modules/agents/application/__init__.py +0 -0
- docir/modules/agents/application/ports.py +31 -0
- docir/modules/agents/application/service.py +146 -0
- docir/modules/agents/domain/__init__.py +0 -0
- docir/modules/agents/domain/rendering.py +84 -0
- docir/modules/agents/domain/results.py +35 -0
- docir/modules/agents/domain/targets.py +66 -0
- docir/modules/agents/infra/__init__.py +0 -0
- docir/modules/agents/infra/file_sink.py +19 -0
- docir/modules/agents/infra/template_provider.py +22 -0
- docir/modules/agents/infra/templates/__init__.py +0 -0
- docir/modules/agents/infra/templates/skill.md +201 -0
- docir/modules/documents/CONTRACT.md +54 -0
- docir/modules/documents/__init__.py +0 -0
- docir/modules/documents/api.py +45 -0
- docir/modules/documents/application/__init__.py +0 -0
- docir/modules/documents/application/dto.py +209 -0
- docir/modules/documents/application/services/__init__.py +0 -0
- docir/modules/documents/application/services/document_service.py +398 -0
- docir/modules/documents/application/services/id_generator.py +40 -0
- docir/modules/documents/application/services/maintenance_service.py +174 -0
- docir/modules/documents/domain/__init__.py +0 -0
- docir/modules/documents/domain/entities/__init__.py +0 -0
- docir/modules/documents/domain/entities/document.py +83 -0
- docir/modules/documents/domain/entities/relation.py +23 -0
- docir/modules/documents/domain/schema.py +163 -0
- docir/modules/documents/domain/services/__init__.py +0 -0
- docir/modules/documents/domain/services/graph_checks.py +223 -0
- docir/modules/documents/domain/services/markdown_sections.py +66 -0
- docir/modules/documents/domain/services/similarity_lint.py +69 -0
- docir/modules/documents/domain/services/slugify.py +21 -0
- docir/modules/documents/domain/services/validation.py +93 -0
- docir/modules/documents/domain/value_objects/__init__.py +0 -0
- docir/modules/documents/domain/value_objects/identifiers.py +76 -0
- docir/modules/documents/domain/value_objects/queries.py +24 -0
- docir/modules/documents/domain/value_objects/relations.py +45 -0
- docir/modules/documents/infra/__init__.py +0 -0
- docir/modules/documents/infra/default_schema.py +43 -0
- docir/modules/documents/infra/profiles.py +176 -0
- docir/modules/documents/infra/schema_loader.py +177 -0
- docir/modules/indexing/CONTRACT.md +31 -0
- docir/modules/indexing/__init__.py +0 -0
- docir/modules/indexing/api.py +44 -0
- docir/modules/indexing/application/__init__.py +0 -0
- docir/modules/indexing/application/ports/__init__.py +0 -0
- docir/modules/indexing/application/ports/scheduler.py +37 -0
- docir/modules/indexing/domain/__init__.py +0 -0
- docir/modules/indexing/domain/results.py +13 -0
- docir/modules/indexing/domain/scoring.py +73 -0
- docir/modules/indexing/infra/__init__.py +0 -0
- docir/modules/indexing/infra/scheduler.py +115 -0
- docir/modules/tags/CONTRACT.md +32 -0
- docir/modules/tags/__init__.py +0 -0
- docir/modules/tags/api.py +12 -0
- docir/modules/tags/application/__init__.py +0 -0
- docir/modules/tags/application/dto.py +13 -0
- docir/modules/tags/application/services/__init__.py +0 -0
- docir/modules/tags/application/services/tag_service.py +109 -0
- docir/modules/tags/domain/__init__.py +0 -0
- docir/modules/tags/domain/entities/__init__.py +0 -0
- docir/modules/tags/domain/entities/tag.py +19 -0
- docir/platform/__init__.py +0 -0
- docir/platform/clock/__init__.py +6 -0
- docir/platform/clock/port.py +19 -0
- docir/platform/clock/system.py +14 -0
- docir/platform/embedding/__init__.py +10 -0
- docir/platform/embedding/deterministic.py +49 -0
- docir/platform/embedding/fastembed.py +51 -0
- docir/platform/embedding/port.py +30 -0
- docir/platform/embedding/vector.py +49 -0
- docir/platform/errors/__init__.py +123 -0
- docir/platform/filesystem/__init__.py +7 -0
- docir/platform/filesystem/markdown_store.py +185 -0
- docir/platform/filesystem/ports.py +62 -0
- docir/platform/filesystem/tag_store.py +35 -0
- docir/platform/persistence/__init__.py +0 -0
- docir/platform/persistence/alembic/env.py +45 -0
- docir/platform/persistence/alembic/script.py.mako +26 -0
- docir/platform/persistence/alembic/versions/0001_initial_index.py +106 -0
- docir/platform/persistence/alembic/versions/0002_typed_edges_and_staleness.py +46 -0
- docir/platform/persistence/engine.py +45 -0
- docir/platform/persistence/models.py +96 -0
- docir/platform/persistence/ports.py +134 -0
- docir/platform/persistence/repositories.py +362 -0
- docir/platform/persistence/sqlalchemy_uow.py +61 -0
- docir/platform/persistence/unit_of_work.py +51 -0
- docir/platform/transport/__init__.py +0 -0
- docir/platform/transport/client.py +47 -0
- docir/platform/transport/messages.py +70 -0
- docir/platform/transport/protocol.py +45 -0
- docir/platform/transport/server.py +70 -0
- docir-0.1.0.dist-info/METADATA +205 -0
- docir-0.1.0.dist-info/RECORD +115 -0
- docir-0.1.0.dist-info/WHEEL +4 -0
- docir-0.1.0.dist-info/entry_points.txt +3 -0
docir/__init__.py
ADDED
|
@@ -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"
|
docir/__main__.py
ADDED
docir/config/__init__.py
ADDED
|
File without changes
|
docir/config/settings.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Runtime settings and the ``~/.docir`` path layout.
|
|
2
|
+
|
|
3
|
+
Backed by ``pydantic-settings``: the ``home`` and ``idle_timeout`` fields are
|
|
4
|
+
populated from ``DOCIR_HOME`` / ``DOCIR_IDLE_TIMEOUT`` (the ``DOCIR_`` env
|
|
5
|
+
prefix) or their defaults. Everything the application persists lives under the
|
|
6
|
+
single home directory; pointing ``DOCIR_HOME`` at a temp dir is what makes the
|
|
7
|
+
whole system hermetic and testable — no global state leaks between runs.
|
|
8
|
+
|
|
9
|
+
The derived paths are plain ``@property`` computations over ``home``; the
|
|
10
|
+
settings object is frozen (immutable), which those read-only properties are
|
|
11
|
+
unaffected by.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import os
|
|
18
|
+
import tempfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from pydantic import Field, field_validator
|
|
22
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
23
|
+
|
|
24
|
+
#: Environment variable naming the root data directory (``DOCIR_`` + ``HOME``).
|
|
25
|
+
HOME_ENV = "DOCIR_HOME"
|
|
26
|
+
#: Force in-process execution (bypass the daemon) — used by tests and CI.
|
|
27
|
+
NO_DAEMON_ENV = "DOCIR_NO_DAEMON"
|
|
28
|
+
#: Idle timeout (seconds) before the daemon shuts itself down.
|
|
29
|
+
DEFAULT_IDLE_TIMEOUT = 900.0
|
|
30
|
+
#: The per-project store directory name, discovered by walking up from the CWD
|
|
31
|
+
#: (the ``.git`` model). ``docir init`` creates one; commands then scope to it.
|
|
32
|
+
PROJECT_STORE_DIRNAME = ".docir"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def discover_project_home(start: Path | None = None) -> Path | None:
|
|
36
|
+
"""Walk up from ``start`` (default CWD) for a ``.docir`` store directory.
|
|
37
|
+
|
|
38
|
+
Returns the first ``.docir`` directory found on the path to the filesystem
|
|
39
|
+
root, or ``None`` if there is none — mirroring how git locates ``.git``. This
|
|
40
|
+
is what makes a project-local store (created by ``docir init``) take effect
|
|
41
|
+
without setting ``DOCIR_HOME`` in every shell.
|
|
42
|
+
"""
|
|
43
|
+
current = (start or Path.cwd()).resolve()
|
|
44
|
+
for directory in (current, *current.parents):
|
|
45
|
+
candidate = directory / PROJECT_STORE_DIRNAME
|
|
46
|
+
if candidate.is_dir():
|
|
47
|
+
return candidate
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Settings(BaseSettings):
|
|
52
|
+
"""Resolved paths and tunables for one docir installation.
|
|
53
|
+
|
|
54
|
+
Field sources, highest precedence first: constructor kwargs → ``DOCIR_*``
|
|
55
|
+
environment variables → field defaults.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
model_config = SettingsConfigDict(
|
|
59
|
+
env_prefix="DOCIR_",
|
|
60
|
+
case_sensitive=False,
|
|
61
|
+
extra="ignore",
|
|
62
|
+
frozen=True,
|
|
63
|
+
validate_default=True,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
home: Path = Field(default_factory=lambda: Path.home() / ".docir")
|
|
67
|
+
idle_timeout: float = DEFAULT_IDLE_TIMEOUT
|
|
68
|
+
use_daemon: bool = False
|
|
69
|
+
|
|
70
|
+
@field_validator("home")
|
|
71
|
+
@classmethod
|
|
72
|
+
def _normalize_home(cls, value: Path) -> Path:
|
|
73
|
+
"""Expand ``~`` and resolve to an absolute path however home was set."""
|
|
74
|
+
return Path(value).expanduser().resolve()
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def resolve(
|
|
78
|
+
cls,
|
|
79
|
+
home: str | os.PathLike[str] | None = None,
|
|
80
|
+
*,
|
|
81
|
+
use_daemon: bool | None = None,
|
|
82
|
+
) -> Settings:
|
|
83
|
+
"""Build settings, applying the inverted ``DOCIR_NO_DAEMON`` semantics.
|
|
84
|
+
|
|
85
|
+
The daemon is used by default; a set ``DOCIR_NO_DAEMON`` env var (or an
|
|
86
|
+
explicit ``use_daemon=False``) forces in-process execution.
|
|
87
|
+
|
|
88
|
+
Home precedence, highest first: an explicit ``home`` argument (the
|
|
89
|
+
``--home`` flag) → the ``DOCIR_HOME`` env var → a project-local
|
|
90
|
+
``.docir`` discovered by walking up from the CWD → the global
|
|
91
|
+
``~/.docir`` default. The discovery step is what lets ``docir init``
|
|
92
|
+
scope a repo's docs to the repo without exporting ``DOCIR_HOME``.
|
|
93
|
+
"""
|
|
94
|
+
if use_daemon is None:
|
|
95
|
+
use_daemon = os.environ.get(NO_DAEMON_ENV, "") == ""
|
|
96
|
+
if home is not None:
|
|
97
|
+
return cls(home=Path(home), use_daemon=use_daemon)
|
|
98
|
+
if os.environ.get(HOME_ENV):
|
|
99
|
+
# Let pydantic read DOCIR_HOME (env_prefix DOCIR_ + field ``home``).
|
|
100
|
+
return cls(use_daemon=use_daemon)
|
|
101
|
+
discovered = discover_project_home()
|
|
102
|
+
if discovered is not None:
|
|
103
|
+
return cls(home=discovered, use_daemon=use_daemon)
|
|
104
|
+
return cls(use_daemon=use_daemon)
|
|
105
|
+
|
|
106
|
+
# -- derived paths ------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def docs_root(self) -> Path:
|
|
110
|
+
"""Where the canonical markdown files live."""
|
|
111
|
+
return self.home / "docs"
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def db_path(self) -> Path:
|
|
115
|
+
"""The derived SQLite index file."""
|
|
116
|
+
return self.home / "index.db"
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def schema_path(self) -> Path:
|
|
120
|
+
"""The per-type schema config."""
|
|
121
|
+
return self.home / "docs-schema.yaml"
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def tags_path(self) -> Path:
|
|
125
|
+
"""The canonical tag registry file."""
|
|
126
|
+
return self.docs_root / "tags.yaml"
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def socket_path(self) -> Path:
|
|
130
|
+
"""The daemon's Unix domain socket.
|
|
131
|
+
|
|
132
|
+
Placed under the system temp dir with a short, home-derived name rather
|
|
133
|
+
than inside ``home`` — a deep home path would blow past the platform's
|
|
134
|
+
~104-char ``AF_UNIX`` limit. The name is stable per home, so every
|
|
135
|
+
client for the same installation targets the same socket.
|
|
136
|
+
"""
|
|
137
|
+
digest = hashlib.sha1(str(self.home).encode("utf-8")).hexdigest()[:12]
|
|
138
|
+
return Path(tempfile.gettempdir()) / f"docir-{digest}.sock"
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def pid_path(self) -> Path:
|
|
142
|
+
"""The daemon's PID file."""
|
|
143
|
+
return self.home / "daemon.pid"
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def log_path(self) -> Path:
|
|
147
|
+
"""The daemon's log file."""
|
|
148
|
+
return self.home / "daemon.log"
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def database_url(self) -> str:
|
|
152
|
+
"""The SQLAlchemy URL for the index database."""
|
|
153
|
+
return f"sqlite:///{self.db_path}"
|
|
154
|
+
|
|
155
|
+
def ensure_directories(self) -> None:
|
|
156
|
+
"""Create the home and docs directories if they do not yet exist."""
|
|
157
|
+
self.docs_root.mkdir(parents=True, exist_ok=True)
|
|
File without changes
|
|
File without changes
|