Files
eugen.thaci f66fb5a19a
CI / lint (push) Failing after 35s
CI / quality (push) Successful in 40s
CI / typecheck (push) Failing after 48s
CI / security (push) Failing after 49s
CI / coverage (push) Has been skipped
CI / build (push) Successful in 20s
CI / helm (push) Successful in 34s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Failing after 2m12s
CI / docker (push) Has been skipped
CI / e2e_tests (push) Failing after 15m52s
CI / integration_tests (push) Failing after 22m11s
CI / status-check (push) Failing after 1s
CI / benchmark-publish (push) Has been cancelled
docs(spec): align ASCII UI tables in specification and related pages
Align Unicode box-drawing blocks and related tables in specification.md,
ADR-044/045/046, and reference pages for consistent MkDocs rendering.

ISSUES CLOSED: #1171
2026-04-03 04:55:21 +00:00

232 lines
8.0 KiB
Markdown

# Repository Indexing Service
The Repository Indexing Service scans linked repository resources, building a
persistent file-level index with language detection, content hashing, and token
estimation. ACMS uses this index for efficient context assembly on projects
with 10K+ files.
## Architecture
```
resource_id + root_path
|
v
┌─── RepoIndexingService ─────┐
│ walk filesystem │
│ apply include/exclude globs│
│ enforce max_file_size │
│ enforce max_total_size │
│ SHA-256 content hashing │
│ extension language detect │
│ estimate token counts │
└──────────┬──────────────────┘
v
┌─── SQLite persistence ──────┐
│ repo_indexes table │
│ indexed_files table │
└─────────────────────────────┘
```
## Domain Models
All models are frozen Pydantic v2 with ULID identifiers and UTC datetimes.
### IndexStatus
Enum representing the state of an index:
| Value | Description |
|-------|-------------|
| `pending` | Index creation requested but not yet started |
| `indexing` | Filesystem walk in progress |
| `ready` | Index complete and available for queries |
| `error` | Indexing failed; see `error_message` |
| `stale` | Source files changed since last index |
### FileRecord
Per-file metadata stored during indexing:
| Field | Type | Description |
|-------|------|-------------|
| `path` | `str` | Relative path from the repository root |
| `content_hash` | `str` | SHA-256 hex digest of file contents |
| `token_count` | `int` | Estimated token count (`size_bytes // 4`) |
| `language` | `str` | Detected programming language |
| `size_bytes` | `int` | File size in bytes |
| `last_modified` | `datetime` | File modification timestamp (UTC) |
### IndexMetadata
Summary record for a repository index:
| Field | Type | Description |
|-------|------|-------------|
| `index_id` | `str` | ULID identifier for this index snapshot |
| `resource_id` | `str` | ULID of the linked resource |
| `indexed_at` | `datetime` | When indexing completed (UTC) |
| `file_count` | `int` | Total files in the index |
| `token_estimate` | `int` | Sum of all file token counts |
| `primary_language` | `str` | Most common language by token count (weighted) |
| `status` | `IndexStatus` | Current index state |
| `error_message` | `str | None` | Error details when `status == error` |
### RepoIndex
Composite object returned by index and refresh operations:
| Field | Type | Description |
|-------|------|-------------|
| `metadata` | `IndexMetadata` | Index summary |
| `files` | `tuple[FileRecord, ...]` | All indexed file records |
## Service API
### `RepoIndexingService(session_factory)`
Constructor. Accepts a SQLAlchemy session factory (injected via DI container).
### `index_resource(resource_id, root_path, *, include_globs, exclude_globs, max_file_size, max_total_size) -> RepoIndex`
Full index of a filesystem tree.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `resource_id` | `str` | required | ULID of the resource |
| `root_path` | `str \| Path` | required | Absolute path to the repository root |
| `include_globs` | `tuple[str, ...]` | `()` | Only index files matching these globs (empty = all) |
| `exclude_globs` | `tuple[str, ...]` | `()` | Skip files matching these globs |
| `max_file_size` | `int \| None` | `None` | Skip files larger than this (bytes); `None` = no limit |
| `max_total_size` | `int \| None` | `None` | Stop indexing when cumulative size exceeds this; `None` = no limit |
Raises `ValueError` if `resource_id` is empty. Raises `FileNotFoundError` if
`root_path` does not exist.
### `refresh_index(resource_id, root_path, **kwargs) -> RepoIndex`
Incremental refresh. Compares content hashes against the existing index and
only re-processes changed files. Falls back to a full index if no prior index
exists. Accepts the same keyword arguments as `index_resource`.
### `get_index(resource_id) -> RepoIndex | None`
Retrieve a full index (metadata + file records) from the database. Returns
`None` if no index exists.
### `get_index_status(resource_id) -> IndexMetadata | None`
Lightweight query returning only the metadata (no file records). Used by
`agents project show` for status display.
### `remove_index(resource_id) -> bool`
Delete all index data for a resource. Returns `True` if records were deleted,
`False` if no index existed.
### `cleanup_stale_indexing() -> int`
Remove orphan `INDEXING` rows left by crashed processes. Should be called at
application startup. Returns the number of stale rows removed.
## Language Detection
Extension-based detection via `detect_language(path)`. Supported mappings:
| Extensions | Language |
|-----------|----------|
| `.py`, `.pyi`, `.pyx` | python |
| `.ts`, `.tsx` | typescript |
| `.js`, `.jsx`, `.mjs`, `.cjs` | javascript |
| `.rs` | rust |
| `.java` | java |
| `.kt`, `.kts` | kotlin |
| `.go` | go |
| `.c`, `.h` | c |
| `.cpp`, `.cc`, `.cxx`, `.hpp` | cpp |
| `.cs` | csharp |
| `.rb` | ruby |
| `.php` | php |
| `.swift` | swift |
| `.scala` | scala |
| `.r` | r |
| `.md`, `.mdx` | markdown |
| `.rst` | restructuredtext |
| `.json` | json |
| `.yaml`, `.yml` | yaml |
| `.toml` | toml |
| `.xml` | xml |
| `.html`, `.htm` | html |
| `.css`, `.scss` | css |
| `.sql` | sql |
| `.sh`, `.bash`, `.zsh` | shell |
| `.ps1` | powershell |
| `.dockerfile` | dockerfile |
| `.tf` | terraform |
| `.lua` | lua |
| `.zig` | zig |
| `.nim` | nim |
| `.ex`, `.exs` | elixir |
| `.erl` | erlang |
| `.hs` | haskell |
| `.ml`, `.mli` | ocaml |
| `.clj` | clojure |
| `.dart` | dart |
| `.v` | v |
| `.jl` | julia |
| `Makefile`, `makefile`, `GNUmakefile` | makefile |
| `Dockerfile`, `Dockerfile.*` | dockerfile |
Files with unrecognized extensions return `"unknown"`.
## Configuration
Indexing behaviour is controlled via the project's `ContextConfig` and
`ContextView`:
| Config Key | Model Field | Description |
|-----------|-------------|-------------|
| `context.include_patterns` | `ContextConfig.include_patterns` | Include globs |
| `context.ignore_patterns` | `ContextConfig.ignore_patterns` | Exclude globs |
| `context.max_file_size` | `ContextConfig.max_file_size` | Per-file size limit |
| `context.max_total_size` | `ContextConfig.max_total_size` | Total index size cap |
| `context.indexing_strategy` | `ContextConfig.indexing_strategy` | `full_text` or `semantic` |
| `context.auto_refresh` | `ContextConfig.auto_refresh` | Auto-refresh on access |
## Database Schema
Two tables are added:
### `repo_indexes`
| Column | Type | Constraints |
|--------|------|-------------|
| `index_id` | `String(26)` | PK |
| `resource_id` | `String(26)` | NOT NULL, UNIQUE, INDEXED |
| `indexed_at` | `String(40)` | NOT NULL (ISO-8601 UTC) |
| `file_count` | `Integer` | NOT NULL, DEFAULT 0 |
| `token_estimate` | `Integer` | NOT NULL, DEFAULT 0 |
| `primary_language` | `String(50)` | NOT NULL, DEFAULT "unknown" |
| `status` | `String(20)` | NOT NULL, DEFAULT "pending", CHECK IN (`pending`, `indexing`, `ready`, `stale`, `error`) |
| `error_message` | `Text` | NULLABLE |
| `created_at` | `String(40)` | NOT NULL (ISO-8601 UTC) |
### `indexed_files`
| Column | Type | Constraints |
|--------|------|-------------|
| `index_id` | `String(26)` | PK (composite), FK -> `repo_indexes.index_id` ON DELETE CASCADE, INDEXED |
| `path` | `String(1024)` | PK (composite) |
| `content_hash` | `String(64)` | NOT NULL |
| `token_count` | `Integer` | NOT NULL, DEFAULT 0 |
| `size_bytes` | `Integer` | NOT NULL, DEFAULT 0 |
| `language` | `String(50)` | NOT NULL, DEFAULT "unknown" |
| `last_modified` | `String(40)` | NOT NULL |
## Spec References
- Lines 727-840: `agents info` repo indexing display
- Lines 2829-2916: `project link-resource` triggers indexing
- Lines 3322-3395: `project show` displays index status
- Lines 19719-19727: Project data model index fields
- Lines 28649-28664: `context.*` configuration keys