repocodex 0.0.1__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.
- repocodex-0.0.1/LICENSE +21 -0
- repocodex-0.0.1/PKG-INFO +127 -0
- repocodex-0.0.1/README.md +104 -0
- repocodex-0.0.1/pyproject.toml +64 -0
- repocodex-0.0.1/setup.cfg +4 -0
- repocodex-0.0.1/src/repocodex/__init__.py +6 -0
- repocodex-0.0.1/src/repocodex/__main__.py +8 -0
- repocodex-0.0.1/src/repocodex/cli.py +189 -0
- repocodex-0.0.1/src/repocodex/commands/__init__.py +3 -0
- repocodex-0.0.1/src/repocodex/commands/advisory.py +95 -0
- repocodex-0.0.1/src/repocodex/commands/audit.py +153 -0
- repocodex-0.0.1/src/repocodex/commands/bootstrap.py +152 -0
- repocodex-0.0.1/src/repocodex/commands/context.py +28 -0
- repocodex-0.0.1/src/repocodex/commands/install.py +194 -0
- repocodex-0.0.1/src/repocodex/commands/reconcile.py +72 -0
- repocodex-0.0.1/src/repocodex/commands/relocate.py +131 -0
- repocodex-0.0.1/src/repocodex/commands/repair.py +125 -0
- repocodex-0.0.1/src/repocodex/commands/validate.py +321 -0
- repocodex-0.0.1/src/repocodex/commands/write.py +161 -0
- repocodex-0.0.1/src/repocodex/config.py +162 -0
- repocodex-0.0.1/src/repocodex/data/action/repocodex.yml +91 -0
- repocodex-0.0.1/src/repocodex/data/hooks/pre-commit +23 -0
- repocodex-0.0.1/src/repocodex/data/plugin/hooks/claude-pre-commit +2 -0
- repocodex-0.0.1/src/repocodex/data/plugin/hooks/cursor-pre-commit +4 -0
- repocodex-0.0.1/src/repocodex/data/plugin/hooks/pre-commit +23 -0
- repocodex-0.0.1/src/repocodex/data/plugin/mcp.json +8 -0
- repocodex-0.0.1/src/repocodex/data/plugin/plugin.json +7 -0
- repocodex-0.0.1/src/repocodex/data/plugin/skills/repocodex-coding/SKILL.md +70 -0
- repocodex-0.0.1/src/repocodex/data/plugin/skills/repocodex-review/SKILL.md +36 -0
- repocodex-0.0.1/src/repocodex/data/rules/claude/CLAUDE.md +3 -0
- repocodex-0.0.1/src/repocodex/data/rules/cursor/repocodex.mdc +6 -0
- repocodex-0.0.1/src/repocodex/data/skills/repocodex-coding/SKILL.md +70 -0
- repocodex-0.0.1/src/repocodex/data/skills/repocodex-review/SKILL.md +36 -0
- repocodex-0.0.1/src/repocodex/engine/__init__.py +3 -0
- repocodex-0.0.1/src/repocodex/engine/ack.py +34 -0
- repocodex-0.0.1/src/repocodex/engine/blocking.py +13 -0
- repocodex-0.0.1/src/repocodex/engine/code_impact.py +51 -0
- repocodex-0.0.1/src/repocodex/engine/contradiction.py +76 -0
- repocodex-0.0.1/src/repocodex/engine/dilution.py +69 -0
- repocodex-0.0.1/src/repocodex/engine/gate.py +329 -0
- repocodex-0.0.1/src/repocodex/engine/impact.py +84 -0
- repocodex-0.0.1/src/repocodex/engine/liveness.py +203 -0
- repocodex-0.0.1/src/repocodex/engine/match.py +232 -0
- repocodex-0.0.1/src/repocodex/engine/ratchet.py +289 -0
- repocodex-0.0.1/src/repocodex/engine/relocate.py +139 -0
- repocodex-0.0.1/src/repocodex/mcp_server.py +126 -0
- repocodex-0.0.1/src/repocodex/metrics.py +73 -0
- repocodex-0.0.1/src/repocodex/retrieval.py +166 -0
- repocodex-0.0.1/src/repocodex/schema.py +381 -0
- repocodex-0.0.1/src/repocodex/store/__init__.py +3 -0
- repocodex-0.0.1/src/repocodex/store/bundle.py +267 -0
- repocodex-0.0.1/src/repocodex/store/reverse_index.py +250 -0
- repocodex-0.0.1/src/repocodex/tools/__init__.py +3 -0
- repocodex-0.0.1/src/repocodex/tools/git.py +76 -0
- repocodex-0.0.1/src/repocodex/tools/ripgrep.py +102 -0
- repocodex-0.0.1/src/repocodex.egg-info/PKG-INFO +127 -0
- repocodex-0.0.1/src/repocodex.egg-info/SOURCES.txt +79 -0
- repocodex-0.0.1/src/repocodex.egg-info/dependency_links.txt +1 -0
- repocodex-0.0.1/src/repocodex.egg-info/entry_points.txt +2 -0
- repocodex-0.0.1/src/repocodex.egg-info/requires.txt +10 -0
- repocodex-0.0.1/src/repocodex.egg-info/top_level.txt +1 -0
- repocodex-0.0.1/tests/test_claims_required.py +70 -0
- repocodex-0.0.1/tests/test_cli_scaffold.py +22 -0
- repocodex-0.0.1/tests/test_commands.py +75 -0
- repocodex-0.0.1/tests/test_config.py +45 -0
- repocodex-0.0.1/tests/test_e2e.py +50 -0
- repocodex-0.0.1/tests/test_enforcement.py +99 -0
- repocodex-0.0.1/tests/test_engine.py +132 -0
- repocodex-0.0.1/tests/test_first_touch.py +229 -0
- repocodex-0.0.1/tests/test_identity_prefix.py +141 -0
- repocodex-0.0.1/tests/test_mcp.py +99 -0
- repocodex-0.0.1/tests/test_okf_v02.py +464 -0
- repocodex-0.0.1/tests/test_retrieval.py +79 -0
- repocodex-0.0.1/tests/test_review_gaps.py +599 -0
- repocodex-0.0.1/tests/test_schema.py +83 -0
- repocodex-0.0.1/tests/test_sharding.py +33 -0
- repocodex-0.0.1/tests/test_skill_type_recipe.py +67 -0
- repocodex-0.0.1/tests/test_store.py +79 -0
- repocodex-0.0.1/tests/test_tools.py +31 -0
- repocodex-0.0.1/tests/test_v2_review_gaps.py +692 -0
- repocodex-0.0.1/tests/test_validate.py +94 -0
repocodex-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ajay Lamba
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
repocodex-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: repocodex
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Repository-native executable memory for coding agents
|
|
5
|
+
Author: Ajay Lamba
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/azaylamba/repocodex
|
|
8
|
+
Project-URL: Source, https://github.com/azaylamba/repocodex
|
|
9
|
+
Project-URL: Issues, https://github.com/azaylamba/repocodex/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: typer>=0.27.1
|
|
15
|
+
Requires-Dist: pydantic>=2.13.4
|
|
16
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=9.1.1; extra == "dev"
|
|
19
|
+
Requires-Dist: ruff>=0.12.0; extra == "dev"
|
|
20
|
+
Provides-Extra: mcp
|
|
21
|
+
Requires-Dist: mcp>=2.1.1; extra == "mcp"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# RepoCodex
|
|
25
|
+
|
|
26
|
+
[](https://github.com/azaylamba/repocodex/actions/workflows/engine-tests.yml)
|
|
27
|
+
|
|
28
|
+
**Git-native why next to code — with a pin check that proves it still matches.**
|
|
29
|
+
|
|
30
|
+
Coding agents write syntax well and forget _why_. Comments rot. Instruction files (`CLAUDE.md`, `AGENTS.md`, Cursor rules) never prove they still describe live text. Tests check behavior you remembered to assert; they do not keep institutional why attached to the lines that implement it.
|
|
31
|
+
|
|
32
|
+
RepoCodex stores that why in git beside the code. Agents retrieve it before they edit. A deterministic pin check (ripgrep + git) attests the attachment. Built for repositories where coding agents make the changes.
|
|
33
|
+
|
|
34
|
+
**Not** a test suite. **Not** another instruction file. **Not** a linter.
|
|
35
|
+
|
|
36
|
+
Experimental `0.0.1`. Requires Python 3.11+ and [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) on `PATH`.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install "repocodex==0.0.1"
|
|
42
|
+
# or from a local clone: pip install -e .
|
|
43
|
+
repocodex install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`repocodex install` writes the pre-commit hook, GitHub Action, agent skills, and `.repocodex.toml` (engine pin). The product is that loop: agents retrieve stored why before they edit, so they do not silently break existing behavior; the pin check fails the turn when why and code diverge, and a first substantive edit of an uncovered file is denied until a pinning concept is written.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
repocodex context src/billing/PaymentGateway.ts # retrieve why before edit
|
|
50
|
+
repocodex validate --diff # attest pins still hold
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Pin the engine in `.repocodex.toml`. Hook, local CLI, and CI resolve that pin so verdicts agree.
|
|
54
|
+
|
|
55
|
+
## What a concept looks like
|
|
56
|
+
|
|
57
|
+
Types are orthogonal — one change may write more than one. Three common shapes:
|
|
58
|
+
|
|
59
|
+
**TechnicalDecision** (`decisions/…`) — why this construct exists:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
---
|
|
63
|
+
title: Capture streams enterprise retries instead of buffering
|
|
64
|
+
type: TechnicalDecision
|
|
65
|
+
verification:
|
|
66
|
+
engine: ripgrep
|
|
67
|
+
anchors:
|
|
68
|
+
- path: src/billing/PaymentGateway.ts
|
|
69
|
+
all_of: ["yield", "ENTERPRISE", "capturePayment"]
|
|
70
|
+
---
|
|
71
|
+
Enterprise capture is a generator so retries stay backpressure-aware. Do not
|
|
72
|
+
replace with an in-memory list of attempts.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**InvariantContract** (`invariants/…`) — must-hold token (requires `claims`):
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
---
|
|
79
|
+
title: Enterprise capture grace is three attempts
|
|
80
|
+
type: InvariantContract
|
|
81
|
+
verification:
|
|
82
|
+
engine: ripgrep
|
|
83
|
+
anchors:
|
|
84
|
+
- path: src/billing/PaymentGateway.ts
|
|
85
|
+
all_of: ["ENTERPRISE", "grace", "= 3"]
|
|
86
|
+
claims:
|
|
87
|
+
- subject: enterprise_grace_attempts
|
|
88
|
+
literal: "3"
|
|
89
|
+
---
|
|
90
|
+
Enterprise plans get three capture retries before failure. Do not silently shrink this window.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**BusinessWorkflow** (`workflows/…`) — thin cross-package flow (one anchor per site):
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
---
|
|
97
|
+
title: Checkout capture flows api → billing → ledger
|
|
98
|
+
type: BusinessWorkflow
|
|
99
|
+
verification:
|
|
100
|
+
engine: ripgrep
|
|
101
|
+
anchors:
|
|
102
|
+
- path: src/api/checkout.ts
|
|
103
|
+
all_of: ["capturePayment", "billing"]
|
|
104
|
+
- path: src/billing/PaymentGateway.ts
|
|
105
|
+
all_of: ["capturePayment", "ledger"]
|
|
106
|
+
- path: src/ledger/posting.ts
|
|
107
|
+
all_of: ["postCapture", "idempotency"]
|
|
108
|
+
---
|
|
109
|
+
Checkout capture crosses api, billing, then ledger. Keep that order; do not
|
|
110
|
+
post to the ledger from the API layer.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Docs
|
|
114
|
+
|
|
115
|
+
| Doc | Job |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| [How it works](https://github.com/azaylamba/repocodex/blob/main/docs/how-it-works.md) | Purpose, benefit, and the retrieve → read → edit → update why → pin-check loop |
|
|
118
|
+
| [Memory](https://github.com/azaylamba/repocodex/blob/main/docs/memory.md) | How to read `.context/` |
|
|
119
|
+
| [Agents](https://github.com/azaylamba/repocodex/blob/main/docs/agents.md) | How coding agents (and optionally humans) run the loop |
|
|
120
|
+
| [Install](https://github.com/azaylamba/repocodex/blob/main/docs/install.md) | CLI, pin, hook, GitHub Action, optional MCP |
|
|
121
|
+
| [Architecture](https://github.com/azaylamba/repocodex/blob/main/docs/architecture.md) | Current engine architecture (further reading) |
|
|
122
|
+
|
|
123
|
+
## License and contributing
|
|
124
|
+
|
|
125
|
+
[MIT](https://github.com/azaylamba/repocodex/blob/main/LICENSE) · [Contributing](https://github.com/azaylamba/repocodex/blob/main/CONTRIBUTING.md) · [Security](https://github.com/azaylamba/repocodex/blob/main/SECURITY.md)
|
|
126
|
+
|
|
127
|
+
Created by [Ajay Lamba](https://github.com/azaylamba/repocodex).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# RepoCodex
|
|
2
|
+
|
|
3
|
+
[](https://github.com/azaylamba/repocodex/actions/workflows/engine-tests.yml)
|
|
4
|
+
|
|
5
|
+
**Git-native why next to code — with a pin check that proves it still matches.**
|
|
6
|
+
|
|
7
|
+
Coding agents write syntax well and forget _why_. Comments rot. Instruction files (`CLAUDE.md`, `AGENTS.md`, Cursor rules) never prove they still describe live text. Tests check behavior you remembered to assert; they do not keep institutional why attached to the lines that implement it.
|
|
8
|
+
|
|
9
|
+
RepoCodex stores that why in git beside the code. Agents retrieve it before they edit. A deterministic pin check (ripgrep + git) attests the attachment. Built for repositories where coding agents make the changes.
|
|
10
|
+
|
|
11
|
+
**Not** a test suite. **Not** another instruction file. **Not** a linter.
|
|
12
|
+
|
|
13
|
+
Experimental `0.0.1`. Requires Python 3.11+ and [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) on `PATH`.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install "repocodex==0.0.1"
|
|
19
|
+
# or from a local clone: pip install -e .
|
|
20
|
+
repocodex install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`repocodex install` writes the pre-commit hook, GitHub Action, agent skills, and `.repocodex.toml` (engine pin). The product is that loop: agents retrieve stored why before they edit, so they do not silently break existing behavior; the pin check fails the turn when why and code diverge, and a first substantive edit of an uncovered file is denied until a pinning concept is written.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
repocodex context src/billing/PaymentGateway.ts # retrieve why before edit
|
|
27
|
+
repocodex validate --diff # attest pins still hold
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Pin the engine in `.repocodex.toml`. Hook, local CLI, and CI resolve that pin so verdicts agree.
|
|
31
|
+
|
|
32
|
+
## What a concept looks like
|
|
33
|
+
|
|
34
|
+
Types are orthogonal — one change may write more than one. Three common shapes:
|
|
35
|
+
|
|
36
|
+
**TechnicalDecision** (`decisions/…`) — why this construct exists:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
---
|
|
40
|
+
title: Capture streams enterprise retries instead of buffering
|
|
41
|
+
type: TechnicalDecision
|
|
42
|
+
verification:
|
|
43
|
+
engine: ripgrep
|
|
44
|
+
anchors:
|
|
45
|
+
- path: src/billing/PaymentGateway.ts
|
|
46
|
+
all_of: ["yield", "ENTERPRISE", "capturePayment"]
|
|
47
|
+
---
|
|
48
|
+
Enterprise capture is a generator so retries stay backpressure-aware. Do not
|
|
49
|
+
replace with an in-memory list of attempts.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**InvariantContract** (`invariants/…`) — must-hold token (requires `claims`):
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
---
|
|
56
|
+
title: Enterprise capture grace is three attempts
|
|
57
|
+
type: InvariantContract
|
|
58
|
+
verification:
|
|
59
|
+
engine: ripgrep
|
|
60
|
+
anchors:
|
|
61
|
+
- path: src/billing/PaymentGateway.ts
|
|
62
|
+
all_of: ["ENTERPRISE", "grace", "= 3"]
|
|
63
|
+
claims:
|
|
64
|
+
- subject: enterprise_grace_attempts
|
|
65
|
+
literal: "3"
|
|
66
|
+
---
|
|
67
|
+
Enterprise plans get three capture retries before failure. Do not silently shrink this window.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**BusinessWorkflow** (`workflows/…`) — thin cross-package flow (one anchor per site):
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
---
|
|
74
|
+
title: Checkout capture flows api → billing → ledger
|
|
75
|
+
type: BusinessWorkflow
|
|
76
|
+
verification:
|
|
77
|
+
engine: ripgrep
|
|
78
|
+
anchors:
|
|
79
|
+
- path: src/api/checkout.ts
|
|
80
|
+
all_of: ["capturePayment", "billing"]
|
|
81
|
+
- path: src/billing/PaymentGateway.ts
|
|
82
|
+
all_of: ["capturePayment", "ledger"]
|
|
83
|
+
- path: src/ledger/posting.ts
|
|
84
|
+
all_of: ["postCapture", "idempotency"]
|
|
85
|
+
---
|
|
86
|
+
Checkout capture crosses api, billing, then ledger. Keep that order; do not
|
|
87
|
+
post to the ledger from the API layer.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Docs
|
|
91
|
+
|
|
92
|
+
| Doc | Job |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| [How it works](https://github.com/azaylamba/repocodex/blob/main/docs/how-it-works.md) | Purpose, benefit, and the retrieve → read → edit → update why → pin-check loop |
|
|
95
|
+
| [Memory](https://github.com/azaylamba/repocodex/blob/main/docs/memory.md) | How to read `.context/` |
|
|
96
|
+
| [Agents](https://github.com/azaylamba/repocodex/blob/main/docs/agents.md) | How coding agents (and optionally humans) run the loop |
|
|
97
|
+
| [Install](https://github.com/azaylamba/repocodex/blob/main/docs/install.md) | CLI, pin, hook, GitHub Action, optional MCP |
|
|
98
|
+
| [Architecture](https://github.com/azaylamba/repocodex/blob/main/docs/architecture.md) | Current engine architecture (further reading) |
|
|
99
|
+
|
|
100
|
+
## License and contributing
|
|
101
|
+
|
|
102
|
+
[MIT](https://github.com/azaylamba/repocodex/blob/main/LICENSE) · [Contributing](https://github.com/azaylamba/repocodex/blob/main/CONTRIBUTING.md) · [Security](https://github.com/azaylamba/repocodex/blob/main/SECURITY.md)
|
|
103
|
+
|
|
104
|
+
Created by [Ajay Lamba](https://github.com/azaylamba/repocodex).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=84.0.0", "wheel>=0.48.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "repocodex"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Repository-native executable memory for coding agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Ajay Lamba" }]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"typer>=0.27.1",
|
|
19
|
+
"pydantic>=2.13.4",
|
|
20
|
+
"pyyaml>=6.0.3",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/azaylamba/repocodex"
|
|
25
|
+
Source = "https://github.com/azaylamba/repocodex"
|
|
26
|
+
Issues = "https://github.com/azaylamba/repocodex/issues"
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=9.1.1",
|
|
31
|
+
"ruff>=0.12.0",
|
|
32
|
+
]
|
|
33
|
+
mcp = [
|
|
34
|
+
"mcp>=2.1.1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
repocodex = "repocodex.cli:app"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
repocodex = ["data/**/*"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
pythonpath = ["src"]
|
|
48
|
+
testpaths = ["tests"]
|
|
49
|
+
addopts = "-q"
|
|
50
|
+
filterwarnings = ["ignore::DeprecationWarning"]
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
target-version = "py311"
|
|
54
|
+
src = ["src", "tests"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
select = ["D"]
|
|
58
|
+
ignore = ["D105", "D107"]
|
|
59
|
+
|
|
60
|
+
[tool.ruff.lint.pydocstyle]
|
|
61
|
+
convention = "google"
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.per-file-ignores]
|
|
64
|
+
"tests/test_*.py" = ["D101", "D102", "D103"]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Typer CLI for RepoCodex. Commands print JSON envelopes and exit non-zero on failure."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from repocodex.commands.advisory import advisory as run_advisory
|
|
12
|
+
from repocodex.commands.audit import audit as run_audit
|
|
13
|
+
from repocodex.commands.bootstrap import bootstrap as run_bootstrap
|
|
14
|
+
from repocodex.commands.context import context_for
|
|
15
|
+
from repocodex.commands.install import install as run_install
|
|
16
|
+
from repocodex.commands.reconcile import apply_anchor_patch, reconcile_memory
|
|
17
|
+
from repocodex.commands.relocate import relocate_memory
|
|
18
|
+
from repocodex.commands.repair import repair as run_repair
|
|
19
|
+
from repocodex.commands.validate import validate as run_validate
|
|
20
|
+
from repocodex.commands.write import write_memory
|
|
21
|
+
from repocodex.config import EngineVersionMismatch
|
|
22
|
+
from repocodex.schema import envelope
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(no_args_is_help=True, add_completion=False, help="RepoCodex executable memory CLI")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _emit(payload: dict, exit_code: int = 0) -> None:
|
|
28
|
+
"""Print ``payload`` as JSON and exit with ``exit_code``."""
|
|
29
|
+
typer.echo(json.dumps(payload, indent=2))
|
|
30
|
+
raise typer.Exit(exit_code)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _repo() -> Path:
|
|
34
|
+
"""Return the process working directory as the target repository."""
|
|
35
|
+
return Path.cwd()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _guarded(fn):
|
|
39
|
+
"""Run ``fn`` and emit an engine-version-mismatch envelope on pin failure."""
|
|
40
|
+
try:
|
|
41
|
+
return fn()
|
|
42
|
+
except EngineVersionMismatch as exc:
|
|
43
|
+
_emit(exc.to_json(), 1)
|
|
44
|
+
raise
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command("validate")
|
|
48
|
+
def validate_command(
|
|
49
|
+
diff: bool = typer.Option(False, "--diff", help="Attest anchors intersecting the diff"),
|
|
50
|
+
base: Optional[str] = typer.Option(None, "--base", help="Git diff base, e.g. origin/main...HEAD"),
|
|
51
|
+
staged: bool = typer.Option(False, "--staged"),
|
|
52
|
+
all_concepts: bool = typer.Option(False, "--all"),
|
|
53
|
+
check: bool = typer.Option(False, "--check", help="Exit 1 on deterministic blocking outcomes"),
|
|
54
|
+
hook: bool = typer.Option(False, "--hook"),
|
|
55
|
+
memory_exempt: bool = typer.Option(False, "--memory-exempt"),
|
|
56
|
+
review_ack: bool = typer.Option(False, "--review-ack", hidden=True),
|
|
57
|
+
ack_file: Optional[Path] = typer.Option(None, "--ack-file", help="Tracked review-agent acknowledgment record"),
|
|
58
|
+
apply_patches: bool = typer.Option(False, "--apply-patches"),
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Attest anchors on the working tree or diff. JSON includes engine_version."""
|
|
61
|
+
payload = _guarded(
|
|
62
|
+
lambda: run_validate(
|
|
63
|
+
_repo(),
|
|
64
|
+
base=base,
|
|
65
|
+
staged=staged or hook,
|
|
66
|
+
all_concepts=all_concepts or not diff,
|
|
67
|
+
memory_exempt=memory_exempt,
|
|
68
|
+
review_ack=review_ack,
|
|
69
|
+
ack_file=str(ack_file) if ack_file else None,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
if apply_patches:
|
|
73
|
+
for patch in payload.get("patches") or []:
|
|
74
|
+
apply_anchor_patch(_repo(), patch)
|
|
75
|
+
payload.setdefault("applied_patches", []).append(patch)
|
|
76
|
+
code = 1 if (check or hook) and payload.get("blocking") else 0
|
|
77
|
+
_emit(payload, code)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.command("write")
|
|
81
|
+
def write_command(
|
|
82
|
+
concept: Optional[Path] = typer.Argument(None),
|
|
83
|
+
identity: Optional[str] = typer.Option(None, "--identity"),
|
|
84
|
+
stdin: bool = typer.Option(False, "--stdin"),
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Write-gate a concept into .context/."""
|
|
87
|
+
text = None
|
|
88
|
+
if stdin:
|
|
89
|
+
text = typer.get_text_stream("stdin").read()
|
|
90
|
+
if concept is None and text is None:
|
|
91
|
+
raise typer.BadParameter("provide a concept file or --stdin")
|
|
92
|
+
payload = _guarded(lambda: write_memory(_repo(), concept or Path("."), identity=identity, stdin_text=text))
|
|
93
|
+
_emit(payload, 0 if payload.get("accepted") else 1)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command("relocate")
|
|
97
|
+
def relocate_command(
|
|
98
|
+
identity: Optional[str] = typer.Argument(None),
|
|
99
|
+
mismatched: bool = typer.Option(
|
|
100
|
+
False, "--mismatched", help="Move all authored-type concepts with wrong prefixes"
|
|
101
|
+
),
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Move authored concepts into the type-folder identity required by their type."""
|
|
104
|
+
if not mismatched and not identity:
|
|
105
|
+
raise typer.BadParameter("provide an identity or --mismatched")
|
|
106
|
+
payload = _guarded(lambda: relocate_memory(_repo(), identity, mismatched=mismatched))
|
|
107
|
+
_emit(payload)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@app.command("reconcile")
|
|
111
|
+
def reconcile_command(
|
|
112
|
+
concept: Optional[Path] = typer.Argument(None),
|
|
113
|
+
identity: Optional[str] = typer.Option(None, "--identity"),
|
|
114
|
+
apply_patch: Optional[str] = typer.Option(None, "--apply-patch", help="JSON patch object"),
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Repair DRIFT with gate-enforced new anchors, or apply a REANCHOR patch."""
|
|
117
|
+
repo = _repo()
|
|
118
|
+
if apply_patch:
|
|
119
|
+
patch = json.loads(apply_patch)
|
|
120
|
+
path = apply_anchor_patch(repo, patch)
|
|
121
|
+
_emit(envelope({"applied": True, "path": str(path)}))
|
|
122
|
+
if concept is None:
|
|
123
|
+
raise typer.BadParameter("provide a concept file")
|
|
124
|
+
payload = _guarded(lambda: reconcile_memory(repo, concept, identity=identity))
|
|
125
|
+
_emit(payload, 0 if payload.get("accepted") else 1)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@app.command("context")
|
|
129
|
+
def context_command(
|
|
130
|
+
paths: list[Path] = typer.Argument(..., metavar="PATHS"),
|
|
131
|
+
drafts: bool = typer.Option(False, "--drafts"),
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Staged retrieval: reverse index → catalogs → bodies + one link-hop of titles."""
|
|
134
|
+
payload = _guarded(lambda: context_for(_repo(), [str(p) for p in paths], include_drafts=drafts))
|
|
135
|
+
_emit(payload)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@app.command("repair")
|
|
139
|
+
def repair_command() -> None:
|
|
140
|
+
"""Invoke a repair agent against the current RECONCILE state."""
|
|
141
|
+
payload = _guarded(lambda: run_repair(_repo()))
|
|
142
|
+
code = 1 if payload.get("error") else 0
|
|
143
|
+
_emit(payload, code)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.command("install")
|
|
147
|
+
def install_command(
|
|
148
|
+
mcp: bool = typer.Option(False, "--mcp", help="Register optional MCP wrapper"),
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Install pre-commit hook, GitHub Action, skills, and optional MCP."""
|
|
151
|
+
payload = _guarded(lambda: run_install(_repo(), mcp=mcp))
|
|
152
|
+
_emit(payload, 0 if payload.get("ok", True) else 1)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@app.command("bootstrap")
|
|
156
|
+
def bootstrap_command() -> None:
|
|
157
|
+
"""Mine history/comments/docs; keep only gate-passing drafts."""
|
|
158
|
+
_emit(_guarded(lambda: run_bootstrap(_repo())))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@app.command("audit")
|
|
162
|
+
def audit_command(
|
|
163
|
+
sample_size: int = typer.Option(10, "--sample-size"),
|
|
164
|
+
seed: int = typer.Option(0, "--seed"),
|
|
165
|
+
findings: Optional[Path] = typer.Option(None, "--findings", help="Out-of-band screening result JSON"),
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Emit a screening payload for out-of-band review. No model is invoked."""
|
|
168
|
+
_emit(
|
|
169
|
+
_guarded(
|
|
170
|
+
lambda: run_audit(_repo(), sample_size=sample_size, seed=seed, findings_path=findings)
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@app.command("advisory")
|
|
176
|
+
def advisory_command(
|
|
177
|
+
base: Optional[str] = typer.Option(None, "--base"),
|
|
178
|
+
staged: bool = typer.Option(False, "--staged"),
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Agent-judged findings for the advisory CI check. Never affects the required verdict."""
|
|
181
|
+
_emit(_guarded(lambda: run_advisory(_repo(), base=base, staged=staged)))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@app.command("mcp")
|
|
185
|
+
def mcp_command() -> None:
|
|
186
|
+
"""Run the optional MCP server wrapping the CLI."""
|
|
187
|
+
from repocodex.mcp_server import run_mcp
|
|
188
|
+
|
|
189
|
+
run_mcp()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Rank code-side impact and wrap optional agent judgments into a non-blocking advisory envelope."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from repocodex.commands.validate import changed_files
|
|
9
|
+
from repocodex.config import load_config
|
|
10
|
+
from repocodex.engine.code_impact import rank_code_hits
|
|
11
|
+
from repocodex.schema import envelope
|
|
12
|
+
|
|
13
|
+
NOT_EVALUATED = "not_evaluated"
|
|
14
|
+
EVALUATED = "evaluated"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _category(status: str, findings: list[dict] | None = None) -> dict[str, Any]:
|
|
18
|
+
"""Build a category payload, attaching findings only when evaluated."""
|
|
19
|
+
payload: dict[str, Any] = {"status": status}
|
|
20
|
+
if status == EVALUATED:
|
|
21
|
+
payload["findings"] = findings or []
|
|
22
|
+
return payload
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def advisory(
|
|
26
|
+
repo: Path,
|
|
27
|
+
*,
|
|
28
|
+
base: str | None = None,
|
|
29
|
+
staged: bool = False,
|
|
30
|
+
judgments: dict[str, list[dict]] | None = None,
|
|
31
|
+
) -> dict:
|
|
32
|
+
"""Return a non-blocking advisory envelope for the current diff.
|
|
33
|
+
|
|
34
|
+
Skips ``.context/`` paths and ``reverse-index.md``. Judgment categories
|
|
35
|
+
without a key in ``judgments`` stay ``not_evaluated``.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Envelope with ``kind`` ``advisory``, ``code_side_impact`` (path/hits
|
|
39
|
+
rows), ``prose_versus_diff``, ``skipped_recipe_steps``, ``churn_flags``
|
|
40
|
+
(each ``status`` plus optional ``findings``), ``agent_judgment``, and
|
|
41
|
+
``required_verdict_unaffected`` always ``True``.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
config = load_config(repo)
|
|
45
|
+
files = changed_files(repo, base=base, staged=staged)
|
|
46
|
+
code_side: list[dict] = []
|
|
47
|
+
for path in files:
|
|
48
|
+
if path.startswith(".context/") or path.endswith("reverse-index.md"):
|
|
49
|
+
continue
|
|
50
|
+
symbols = Path(path).stem
|
|
51
|
+
hits = rank_code_hits(path, symbols, repo, cap=config.impact_read_cap, exclusions=config.all_exclusions)
|
|
52
|
+
if hits:
|
|
53
|
+
code_side.append({"path": path, "hits": hits})
|
|
54
|
+
|
|
55
|
+
judgments = judgments or {}
|
|
56
|
+
|
|
57
|
+
def category_for(name: str) -> dict[str, Any]:
|
|
58
|
+
if name in judgments:
|
|
59
|
+
return _category(EVALUATED, judgments[name])
|
|
60
|
+
return _category(NOT_EVALUATED)
|
|
61
|
+
|
|
62
|
+
prose = category_for("prose_versus_diff")
|
|
63
|
+
skipped = category_for("skipped_recipe_steps")
|
|
64
|
+
churn = category_for("churn_flags")
|
|
65
|
+
any_judgment = any(cat["status"] == EVALUATED for cat in (prose, skipped, churn))
|
|
66
|
+
return envelope(
|
|
67
|
+
{
|
|
68
|
+
"kind": "advisory",
|
|
69
|
+
"code_side_impact": code_side,
|
|
70
|
+
"prose_versus_diff": prose,
|
|
71
|
+
"skipped_recipe_steps": skipped,
|
|
72
|
+
"churn_flags": churn,
|
|
73
|
+
"agent_judgment": any_judgment,
|
|
74
|
+
"required_verdict_unaffected": True,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def scenario_integrity_status(root: Path) -> dict:
|
|
80
|
+
"""Report whether an OKF concept bundle exists for scenario integrity.
|
|
81
|
+
|
|
82
|
+
Never falls back to a test table.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
``{"status": "unsatisfied", "reason": "no_okf_bundle"}`` when no
|
|
86
|
+
concepts are loaded, otherwise
|
|
87
|
+
``{"status": "available", "reason": "agent_read_okf"}``.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
from repocodex.store.bundle import load_concepts
|
|
91
|
+
|
|
92
|
+
concepts = load_concepts(root)
|
|
93
|
+
if not concepts:
|
|
94
|
+
return {"status": "unsatisfied", "reason": "no_okf_bundle"}
|
|
95
|
+
return {"status": "available", "reason": "agent_read_okf"}
|