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
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
232 lines
8.0 KiB
Markdown
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
|