agent-sdlc 0.3.0__tar.gz → 0.4.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.
- agent_sdlc-0.4.0/PKG-INFO +205 -0
- agent_sdlc-0.4.0/README.md +190 -0
- agent_sdlc-0.4.0/agent_sdlc.egg-info/PKG-INFO +205 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/__init__.py +1 -1
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/cli.py +13 -2
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/no_leaks.py +5 -1
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/runner.py +8 -2
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/rules/tickets.md +6 -0
- agent_sdlc-0.4.0/tests/test_say_narrate_bin.py +35 -0
- agent_sdlc-0.3.0/PKG-INFO +0 -115
- agent_sdlc-0.3.0/README.md +0 -100
- agent_sdlc-0.3.0/agent_sdlc.egg-info/PKG-INFO +0 -115
- agent_sdlc-0.3.0/tests/test_say_narrate_bin.py +0 -26
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/LICENSE +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/NOTICE.md +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/agent_sdlc.egg-info/SOURCES.txt +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/agent_sdlc.egg-info/dependency_links.txt +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/agent_sdlc.egg-info/entry_points.txt +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/agent_sdlc.egg-info/requires.txt +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/agent_sdlc.egg-info/top_level.txt +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/adversary.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/commit_msg.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/coverage_map.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/diff_discipline.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/model_vv.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/mutants.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/no_comments.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/no_new_docs.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/repo.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/rules.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/token.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/vocabulary.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/vocabulary_check.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/vocabulary_core.toml +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/vocabulary_molds.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/vocabulary_path.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/vocabulary_wordnet.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/mutation_gate/waivers.py +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/pyproject.toml +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/rules/code-clarity.md +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/rules/diff-discipline.md +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/rules/model-vv.md +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/rules/naming.md +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/rules/testing.md +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/rules/voice.md +0 -0
- {agent_sdlc-0.3.0 → agent_sdlc-0.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-sdlc
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Diff-scoped mutation gate for pre-commit and agent Stop hooks
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/PavelGuzenfeld/agent-sdlc
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: NOTICE.md
|
|
11
|
+
Requires-Dist: ast-grep-cli==0.45.3
|
|
12
|
+
Provides-Extra: vocabulary
|
|
13
|
+
Requires-Dist: nltk; extra == "vocabulary"
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# agent-sdlc
|
|
17
|
+
|
|
18
|
+
[](https://github.com/PavelGuzenfeld/agent-sdlc/actions/workflows/ci.yml)
|
|
19
|
+
|
|
20
|
+
A software development lifecycle shipped as agent config for Claude Code and
|
|
21
|
+
Codex: rules, skills, slash commands, hooks, and a diff-scoped mutation gate.
|
|
22
|
+
|
|
23
|
+
Docs: <https://pavelguzenfeld.com/agent-sdlc/>
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
A coding agent writes the code and the tests for it, and tests written next
|
|
28
|
+
to the code tend to agree with it, bugs included. We planted small realistic
|
|
29
|
+
bugs in a mature, well-tested project, and the suite stayed green for 57% of
|
|
30
|
+
them. Unit tests can each pass while the feature, entered the way a user
|
|
31
|
+
enters it, still fails. agent-sdlc puts those checks, plus scope, intent and
|
|
32
|
+
git safety, into hooks and a gate instead of leaving them to the agent's
|
|
33
|
+
memory.
|
|
34
|
+
|
|
35
|
+
| Problem | What catches it |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Tests that pass but assert nothing | `mutation-gate`: a surviving mutant blocks the commit |
|
|
38
|
+
| Tests shaped to the code | An adversary review that sees intent and tests, never the code |
|
|
39
|
+
| Unit tests green, feature broken | One vertical-slice test per ticket, entered where a real consumer enters, red before any code |
|
|
40
|
+
| Scope creep | 40-line limit without a ticket; one ticket, one branch, one PR |
|
|
41
|
+
| Code bloat | Smallest-change and zero-comment rules; a `no-comments` hook; every added line must kill its mutants |
|
|
42
|
+
| Design notes rotting in the tree | New `.md` files are blocked; intent lives in the tracker |
|
|
43
|
+
| Destructive git | A hook that denies `reset --hard`, `add -A`, force-push |
|
|
44
|
+
| AI tells and leaked identity in commits | `commit-msg` and `no-leaks` hooks |
|
|
45
|
+
|
|
46
|
+
## How it fits together
|
|
47
|
+
|
|
48
|
+
`x` marks a blocker. `[name]` is the rule in `rules/` that applies.
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
rules/*.md sync into .claude/rules and AGENTS.md; every session loads them
|
|
52
|
+
|
|
53
|
+
idea
|
|
54
|
+
| /grill, or gh issue create
|
|
55
|
+
v
|
|
56
|
+
+--------------------------+
|
|
57
|
+
| ticket | intent lives here, not in the tree [tickets]
|
|
58
|
+
+--------------------------+
|
|
59
|
+
| triage: model:* label + assignee
|
|
60
|
+
v x assigned to someone else: /kata stops
|
|
61
|
+
unlabelled or unassigned: /kata asks first
|
|
62
|
+
+--------------------------+
|
|
63
|
+
| /kata N | one ticket, one branch, one PR [tickets]
|
|
64
|
+
| branch off origin/main |
|
|
65
|
+
+--------------------------+
|
|
66
|
+
v
|
|
67
|
+
+--------------------------+
|
|
68
|
+
| red slice test | enters where a user enters, [testing]
|
|
69
|
+
| | watched failing first
|
|
70
|
+
+--------------------------+
|
|
71
|
+
v
|
|
72
|
+
+--------------------------+
|
|
73
|
+
| implement | smallest change [diff-discipline]
|
|
74
|
+
| | no comments, canonical names
|
|
75
|
+
+--------------------------+ [code-clarity] [naming]
|
|
76
|
+
| every Bash call: git-guardrail x reset --hard, add -A, force-push
|
|
77
|
+
v
|
|
78
|
+
+--------------------------+
|
|
79
|
+
| git commit |
|
|
80
|
+
| pre-commit |
|
|
81
|
+
| rules-check | x .claude/rules drifted from the pack
|
|
82
|
+
| mutation-gate ---------+- x surviving mutant --> new test, or waiver
|
|
83
|
+
| no-new-docs | x new .md file not allowlisted
|
|
84
|
+
| commit-msg |
|
|
85
|
+
| commit-msg | x banned words, AI attribution [voice]
|
|
86
|
+
| diff-discipline | x over 40 lines, no ticket ref
|
|
87
|
+
| no-leaks | x email, private IP, home path
|
|
88
|
+
+--------------------------+
|
|
89
|
+
| gate green -> adversary review: sees intent and tests, never
|
|
90
|
+
| the code; reports, never blocks; answer every finding
|
|
91
|
+
| Stop hook: runs the same gate if no commit did
|
|
92
|
+
v
|
|
93
|
+
+--------------------------+
|
|
94
|
+
| push, PR "Closes #N" | x CI red
|
|
95
|
+
+--------------------------+
|
|
96
|
+
v
|
|
97
|
+
+--------------------------+
|
|
98
|
+
| you review, type LGTM | squash merge, branch deleted
|
|
99
|
+
+--------------------------+
|
|
100
|
+
v
|
|
101
|
+
/done: handoff, follow-up tickets --> back to "ticket"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Example
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
def is_adult(age):
|
|
108
|
+
return age >= 18
|
|
109
|
+
|
|
110
|
+
def test_adult():
|
|
111
|
+
assert is_adult(30)
|
|
112
|
+
|
|
113
|
+
def test_child():
|
|
114
|
+
assert not is_adult(5)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
BLOCKED: 2 mutant(s) survived with no waiver.
|
|
119
|
+
age.py:2:11:operator:age >= 18 => age > 18
|
|
120
|
+
age.py:2:18:literal:18 => 19
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
|
|
124
|
+
|
|
125
|
+
## Install
|
|
126
|
+
|
|
127
|
+
Claude Code:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
claude plugin marketplace add PavelGuzenfeld/agent-sdlc
|
|
131
|
+
claude plugin install agent-sdlc@agent-sdlc
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Tools the gate needs, and Codex:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/PavelGuzenfeld/agent-sdlc
|
|
138
|
+
cd agent-sdlc
|
|
139
|
+
./install.sh --deps
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
| Want | Do |
|
|
143
|
+
|---|---|
|
|
144
|
+
| Claude Code | The plugin above; `--scope local` or `--scope project` keeps it to one repo |
|
|
145
|
+
| Codex | `./install.sh --target codex`, or add the repo as a Codex marketplace source |
|
|
146
|
+
| Gate dependencies | `./install.sh --deps` (`--deps=say` adds voice) |
|
|
147
|
+
| Only the gate | `pip install agent-sdlc` (WordNet check: `agent-sdlc[vocabulary]`) |
|
|
148
|
+
| The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
|
|
149
|
+
|
|
150
|
+
- `install.sh` links `skills/` into `~/.codex` and renders `commands/` as
|
|
151
|
+
Codex skills. It installs nothing into `~/.claude`. A second run changes
|
|
152
|
+
nothing.
|
|
153
|
+
- Installed an older `install.sh` into `~/.claude`? `./install.sh
|
|
154
|
+
--uninstall-legacy` removes those links and hooks, or every hook fires twice.
|
|
155
|
+
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
156
|
+
`ast-grep`.
|
|
157
|
+
|
|
158
|
+
## Use it
|
|
159
|
+
|
|
160
|
+
In Claude Code (Codex: `$name` instead of `/name`):
|
|
161
|
+
|
|
162
|
+
1. Install the plugin and `./install.sh --deps`
|
|
163
|
+
2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
|
|
164
|
+
3. `claude`, then `/grill <idea>` or `gh issue create …`
|
|
165
|
+
4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
|
|
166
|
+
5. `/kata 42`: a worker writes the failing slice test, implements, gates, opens the PR
|
|
167
|
+
6. Review, then type `LGTM`: it squash-merges and cleans up
|
|
168
|
+
7. `/done` to close out the session
|
|
169
|
+
|
|
170
|
+
Full walkthrough, and what differs in Codex:
|
|
171
|
+
<https://pavelguzenfeld.com/agent-sdlc/usage/>
|
|
172
|
+
|
|
173
|
+
## With standard
|
|
174
|
+
|
|
175
|
+
[standard](https://github.com/PavelGuzenfeld/standard) runs C++ and Python
|
|
176
|
+
quality gates on the PR. agent-sdlc runs before the commit. Use both:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
agent session -> git commit -> PR -> merge
|
|
180
|
+
'------ agent-sdlc -------' '-- standard --'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Run standard's setup first, then `mutation-gate rules sync`. Copying its
|
|
184
|
+
`AGENTS.md` afterwards drops the rules block. In CI, the agent-sdlc diff
|
|
185
|
+
checks need `--range`, because `pre-commit run --from-ref` doesn't enforce
|
|
186
|
+
them. Setup order, the CI job and the `docs/` allowlist:
|
|
187
|
+
<https://pavelguzenfeld.com/agent-sdlc/usage/#with-standard>
|
|
188
|
+
|
|
189
|
+
## What's inside
|
|
190
|
+
|
|
191
|
+
| Path | What |
|
|
192
|
+
|---|---|
|
|
193
|
+
| `rules/` | How to work; synced into a repo with `mutation-gate rules sync` |
|
|
194
|
+
| `commands/` | Slash commands: `/kata`, `/done`, `/grill`, `/rectify` … |
|
|
195
|
+
| `skills/` | On-demand playbooks: `/diagnose`, `/sol-budget`, `/verify-generated-diff` … |
|
|
196
|
+
| `bin/` | Hooks and helpers: `git-guardrail.sh`, the `/say` stack |
|
|
197
|
+
| `mutation_gate/` | The gate and the `mutation-gate` CLI |
|
|
198
|
+
|
|
199
|
+
CI runs `scripts/no-leaks.sh` on every PR. It flags emails, RFC1918
|
|
200
|
+
addresses, user-at-host references, `/home/<user>/` paths and non-personal
|
|
201
|
+
`ghcr.io/` paths, and prints only `file:line`.
|
|
202
|
+
|
|
203
|
+
MIT, see [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md) for one vendored
|
|
204
|
+
third-party skill. Contributions: [CONTRIBUTING.md](CONTRIBUTING.md), bound
|
|
205
|
+
by [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security: [SECURITY.md](SECURITY.md).
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# agent-sdlc
|
|
2
|
+
|
|
3
|
+
[](https://github.com/PavelGuzenfeld/agent-sdlc/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A software development lifecycle shipped as agent config for Claude Code and
|
|
6
|
+
Codex: rules, skills, slash commands, hooks, and a diff-scoped mutation gate.
|
|
7
|
+
|
|
8
|
+
Docs: <https://pavelguzenfeld.com/agent-sdlc/>
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
A coding agent writes the code and the tests for it, and tests written next
|
|
13
|
+
to the code tend to agree with it, bugs included. We planted small realistic
|
|
14
|
+
bugs in a mature, well-tested project, and the suite stayed green for 57% of
|
|
15
|
+
them. Unit tests can each pass while the feature, entered the way a user
|
|
16
|
+
enters it, still fails. agent-sdlc puts those checks, plus scope, intent and
|
|
17
|
+
git safety, into hooks and a gate instead of leaving them to the agent's
|
|
18
|
+
memory.
|
|
19
|
+
|
|
20
|
+
| Problem | What catches it |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Tests that pass but assert nothing | `mutation-gate`: a surviving mutant blocks the commit |
|
|
23
|
+
| Tests shaped to the code | An adversary review that sees intent and tests, never the code |
|
|
24
|
+
| Unit tests green, feature broken | One vertical-slice test per ticket, entered where a real consumer enters, red before any code |
|
|
25
|
+
| Scope creep | 40-line limit without a ticket; one ticket, one branch, one PR |
|
|
26
|
+
| Code bloat | Smallest-change and zero-comment rules; a `no-comments` hook; every added line must kill its mutants |
|
|
27
|
+
| Design notes rotting in the tree | New `.md` files are blocked; intent lives in the tracker |
|
|
28
|
+
| Destructive git | A hook that denies `reset --hard`, `add -A`, force-push |
|
|
29
|
+
| AI tells and leaked identity in commits | `commit-msg` and `no-leaks` hooks |
|
|
30
|
+
|
|
31
|
+
## How it fits together
|
|
32
|
+
|
|
33
|
+
`x` marks a blocker. `[name]` is the rule in `rules/` that applies.
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
rules/*.md sync into .claude/rules and AGENTS.md; every session loads them
|
|
37
|
+
|
|
38
|
+
idea
|
|
39
|
+
| /grill, or gh issue create
|
|
40
|
+
v
|
|
41
|
+
+--------------------------+
|
|
42
|
+
| ticket | intent lives here, not in the tree [tickets]
|
|
43
|
+
+--------------------------+
|
|
44
|
+
| triage: model:* label + assignee
|
|
45
|
+
v x assigned to someone else: /kata stops
|
|
46
|
+
unlabelled or unassigned: /kata asks first
|
|
47
|
+
+--------------------------+
|
|
48
|
+
| /kata N | one ticket, one branch, one PR [tickets]
|
|
49
|
+
| branch off origin/main |
|
|
50
|
+
+--------------------------+
|
|
51
|
+
v
|
|
52
|
+
+--------------------------+
|
|
53
|
+
| red slice test | enters where a user enters, [testing]
|
|
54
|
+
| | watched failing first
|
|
55
|
+
+--------------------------+
|
|
56
|
+
v
|
|
57
|
+
+--------------------------+
|
|
58
|
+
| implement | smallest change [diff-discipline]
|
|
59
|
+
| | no comments, canonical names
|
|
60
|
+
+--------------------------+ [code-clarity] [naming]
|
|
61
|
+
| every Bash call: git-guardrail x reset --hard, add -A, force-push
|
|
62
|
+
v
|
|
63
|
+
+--------------------------+
|
|
64
|
+
| git commit |
|
|
65
|
+
| pre-commit |
|
|
66
|
+
| rules-check | x .claude/rules drifted from the pack
|
|
67
|
+
| mutation-gate ---------+- x surviving mutant --> new test, or waiver
|
|
68
|
+
| no-new-docs | x new .md file not allowlisted
|
|
69
|
+
| commit-msg |
|
|
70
|
+
| commit-msg | x banned words, AI attribution [voice]
|
|
71
|
+
| diff-discipline | x over 40 lines, no ticket ref
|
|
72
|
+
| no-leaks | x email, private IP, home path
|
|
73
|
+
+--------------------------+
|
|
74
|
+
| gate green -> adversary review: sees intent and tests, never
|
|
75
|
+
| the code; reports, never blocks; answer every finding
|
|
76
|
+
| Stop hook: runs the same gate if no commit did
|
|
77
|
+
v
|
|
78
|
+
+--------------------------+
|
|
79
|
+
| push, PR "Closes #N" | x CI red
|
|
80
|
+
+--------------------------+
|
|
81
|
+
v
|
|
82
|
+
+--------------------------+
|
|
83
|
+
| you review, type LGTM | squash merge, branch deleted
|
|
84
|
+
+--------------------------+
|
|
85
|
+
v
|
|
86
|
+
/done: handoff, follow-up tickets --> back to "ticket"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Example
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
def is_adult(age):
|
|
93
|
+
return age >= 18
|
|
94
|
+
|
|
95
|
+
def test_adult():
|
|
96
|
+
assert is_adult(30)
|
|
97
|
+
|
|
98
|
+
def test_child():
|
|
99
|
+
assert not is_adult(5)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
BLOCKED: 2 mutant(s) survived with no waiver.
|
|
104
|
+
age.py:2:11:operator:age >= 18 => age > 18
|
|
105
|
+
age.py:2:18:literal:18 => 19
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
|
|
109
|
+
|
|
110
|
+
## Install
|
|
111
|
+
|
|
112
|
+
Claude Code:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
claude plugin marketplace add PavelGuzenfeld/agent-sdlc
|
|
116
|
+
claude plugin install agent-sdlc@agent-sdlc
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Tools the gate needs, and Codex:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git clone https://github.com/PavelGuzenfeld/agent-sdlc
|
|
123
|
+
cd agent-sdlc
|
|
124
|
+
./install.sh --deps
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| Want | Do |
|
|
128
|
+
|---|---|
|
|
129
|
+
| Claude Code | The plugin above; `--scope local` or `--scope project` keeps it to one repo |
|
|
130
|
+
| Codex | `./install.sh --target codex`, or add the repo as a Codex marketplace source |
|
|
131
|
+
| Gate dependencies | `./install.sh --deps` (`--deps=say` adds voice) |
|
|
132
|
+
| Only the gate | `pip install agent-sdlc` (WordNet check: `agent-sdlc[vocabulary]`) |
|
|
133
|
+
| The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
|
|
134
|
+
|
|
135
|
+
- `install.sh` links `skills/` into `~/.codex` and renders `commands/` as
|
|
136
|
+
Codex skills. It installs nothing into `~/.claude`. A second run changes
|
|
137
|
+
nothing.
|
|
138
|
+
- Installed an older `install.sh` into `~/.claude`? `./install.sh
|
|
139
|
+
--uninstall-legacy` removes those links and hooks, or every hook fires twice.
|
|
140
|
+
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
141
|
+
`ast-grep`.
|
|
142
|
+
|
|
143
|
+
## Use it
|
|
144
|
+
|
|
145
|
+
In Claude Code (Codex: `$name` instead of `/name`):
|
|
146
|
+
|
|
147
|
+
1. Install the plugin and `./install.sh --deps`
|
|
148
|
+
2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
|
|
149
|
+
3. `claude`, then `/grill <idea>` or `gh issue create …`
|
|
150
|
+
4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
|
|
151
|
+
5. `/kata 42`: a worker writes the failing slice test, implements, gates, opens the PR
|
|
152
|
+
6. Review, then type `LGTM`: it squash-merges and cleans up
|
|
153
|
+
7. `/done` to close out the session
|
|
154
|
+
|
|
155
|
+
Full walkthrough, and what differs in Codex:
|
|
156
|
+
<https://pavelguzenfeld.com/agent-sdlc/usage/>
|
|
157
|
+
|
|
158
|
+
## With standard
|
|
159
|
+
|
|
160
|
+
[standard](https://github.com/PavelGuzenfeld/standard) runs C++ and Python
|
|
161
|
+
quality gates on the PR. agent-sdlc runs before the commit. Use both:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
agent session -> git commit -> PR -> merge
|
|
165
|
+
'------ agent-sdlc -------' '-- standard --'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Run standard's setup first, then `mutation-gate rules sync`. Copying its
|
|
169
|
+
`AGENTS.md` afterwards drops the rules block. In CI, the agent-sdlc diff
|
|
170
|
+
checks need `--range`, because `pre-commit run --from-ref` doesn't enforce
|
|
171
|
+
them. Setup order, the CI job and the `docs/` allowlist:
|
|
172
|
+
<https://pavelguzenfeld.com/agent-sdlc/usage/#with-standard>
|
|
173
|
+
|
|
174
|
+
## What's inside
|
|
175
|
+
|
|
176
|
+
| Path | What |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `rules/` | How to work; synced into a repo with `mutation-gate rules sync` |
|
|
179
|
+
| `commands/` | Slash commands: `/kata`, `/done`, `/grill`, `/rectify` … |
|
|
180
|
+
| `skills/` | On-demand playbooks: `/diagnose`, `/sol-budget`, `/verify-generated-diff` … |
|
|
181
|
+
| `bin/` | Hooks and helpers: `git-guardrail.sh`, the `/say` stack |
|
|
182
|
+
| `mutation_gate/` | The gate and the `mutation-gate` CLI |
|
|
183
|
+
|
|
184
|
+
CI runs `scripts/no-leaks.sh` on every PR. It flags emails, RFC1918
|
|
185
|
+
addresses, user-at-host references, `/home/<user>/` paths and non-personal
|
|
186
|
+
`ghcr.io/` paths, and prints only `file:line`.
|
|
187
|
+
|
|
188
|
+
MIT, see [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md) for one vendored
|
|
189
|
+
third-party skill. Contributions: [CONTRIBUTING.md](CONTRIBUTING.md), bound
|
|
190
|
+
by [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security: [SECURITY.md](SECURITY.md).
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-sdlc
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Diff-scoped mutation gate for pre-commit and agent Stop hooks
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/PavelGuzenfeld/agent-sdlc
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: NOTICE.md
|
|
11
|
+
Requires-Dist: ast-grep-cli==0.45.3
|
|
12
|
+
Provides-Extra: vocabulary
|
|
13
|
+
Requires-Dist: nltk; extra == "vocabulary"
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# agent-sdlc
|
|
17
|
+
|
|
18
|
+
[](https://github.com/PavelGuzenfeld/agent-sdlc/actions/workflows/ci.yml)
|
|
19
|
+
|
|
20
|
+
A software development lifecycle shipped as agent config for Claude Code and
|
|
21
|
+
Codex: rules, skills, slash commands, hooks, and a diff-scoped mutation gate.
|
|
22
|
+
|
|
23
|
+
Docs: <https://pavelguzenfeld.com/agent-sdlc/>
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
A coding agent writes the code and the tests for it, and tests written next
|
|
28
|
+
to the code tend to agree with it, bugs included. We planted small realistic
|
|
29
|
+
bugs in a mature, well-tested project, and the suite stayed green for 57% of
|
|
30
|
+
them. Unit tests can each pass while the feature, entered the way a user
|
|
31
|
+
enters it, still fails. agent-sdlc puts those checks, plus scope, intent and
|
|
32
|
+
git safety, into hooks and a gate instead of leaving them to the agent's
|
|
33
|
+
memory.
|
|
34
|
+
|
|
35
|
+
| Problem | What catches it |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Tests that pass but assert nothing | `mutation-gate`: a surviving mutant blocks the commit |
|
|
38
|
+
| Tests shaped to the code | An adversary review that sees intent and tests, never the code |
|
|
39
|
+
| Unit tests green, feature broken | One vertical-slice test per ticket, entered where a real consumer enters, red before any code |
|
|
40
|
+
| Scope creep | 40-line limit without a ticket; one ticket, one branch, one PR |
|
|
41
|
+
| Code bloat | Smallest-change and zero-comment rules; a `no-comments` hook; every added line must kill its mutants |
|
|
42
|
+
| Design notes rotting in the tree | New `.md` files are blocked; intent lives in the tracker |
|
|
43
|
+
| Destructive git | A hook that denies `reset --hard`, `add -A`, force-push |
|
|
44
|
+
| AI tells and leaked identity in commits | `commit-msg` and `no-leaks` hooks |
|
|
45
|
+
|
|
46
|
+
## How it fits together
|
|
47
|
+
|
|
48
|
+
`x` marks a blocker. `[name]` is the rule in `rules/` that applies.
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
rules/*.md sync into .claude/rules and AGENTS.md; every session loads them
|
|
52
|
+
|
|
53
|
+
idea
|
|
54
|
+
| /grill, or gh issue create
|
|
55
|
+
v
|
|
56
|
+
+--------------------------+
|
|
57
|
+
| ticket | intent lives here, not in the tree [tickets]
|
|
58
|
+
+--------------------------+
|
|
59
|
+
| triage: model:* label + assignee
|
|
60
|
+
v x assigned to someone else: /kata stops
|
|
61
|
+
unlabelled or unassigned: /kata asks first
|
|
62
|
+
+--------------------------+
|
|
63
|
+
| /kata N | one ticket, one branch, one PR [tickets]
|
|
64
|
+
| branch off origin/main |
|
|
65
|
+
+--------------------------+
|
|
66
|
+
v
|
|
67
|
+
+--------------------------+
|
|
68
|
+
| red slice test | enters where a user enters, [testing]
|
|
69
|
+
| | watched failing first
|
|
70
|
+
+--------------------------+
|
|
71
|
+
v
|
|
72
|
+
+--------------------------+
|
|
73
|
+
| implement | smallest change [diff-discipline]
|
|
74
|
+
| | no comments, canonical names
|
|
75
|
+
+--------------------------+ [code-clarity] [naming]
|
|
76
|
+
| every Bash call: git-guardrail x reset --hard, add -A, force-push
|
|
77
|
+
v
|
|
78
|
+
+--------------------------+
|
|
79
|
+
| git commit |
|
|
80
|
+
| pre-commit |
|
|
81
|
+
| rules-check | x .claude/rules drifted from the pack
|
|
82
|
+
| mutation-gate ---------+- x surviving mutant --> new test, or waiver
|
|
83
|
+
| no-new-docs | x new .md file not allowlisted
|
|
84
|
+
| commit-msg |
|
|
85
|
+
| commit-msg | x banned words, AI attribution [voice]
|
|
86
|
+
| diff-discipline | x over 40 lines, no ticket ref
|
|
87
|
+
| no-leaks | x email, private IP, home path
|
|
88
|
+
+--------------------------+
|
|
89
|
+
| gate green -> adversary review: sees intent and tests, never
|
|
90
|
+
| the code; reports, never blocks; answer every finding
|
|
91
|
+
| Stop hook: runs the same gate if no commit did
|
|
92
|
+
v
|
|
93
|
+
+--------------------------+
|
|
94
|
+
| push, PR "Closes #N" | x CI red
|
|
95
|
+
+--------------------------+
|
|
96
|
+
v
|
|
97
|
+
+--------------------------+
|
|
98
|
+
| you review, type LGTM | squash merge, branch deleted
|
|
99
|
+
+--------------------------+
|
|
100
|
+
v
|
|
101
|
+
/done: handoff, follow-up tickets --> back to "ticket"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Example
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
def is_adult(age):
|
|
108
|
+
return age >= 18
|
|
109
|
+
|
|
110
|
+
def test_adult():
|
|
111
|
+
assert is_adult(30)
|
|
112
|
+
|
|
113
|
+
def test_child():
|
|
114
|
+
assert not is_adult(5)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
BLOCKED: 2 mutant(s) survived with no waiver.
|
|
119
|
+
age.py:2:11:operator:age >= 18 => age > 18
|
|
120
|
+
age.py:2:18:literal:18 => 19
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
|
|
124
|
+
|
|
125
|
+
## Install
|
|
126
|
+
|
|
127
|
+
Claude Code:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
claude plugin marketplace add PavelGuzenfeld/agent-sdlc
|
|
131
|
+
claude plugin install agent-sdlc@agent-sdlc
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Tools the gate needs, and Codex:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/PavelGuzenfeld/agent-sdlc
|
|
138
|
+
cd agent-sdlc
|
|
139
|
+
./install.sh --deps
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
| Want | Do |
|
|
143
|
+
|---|---|
|
|
144
|
+
| Claude Code | The plugin above; `--scope local` or `--scope project` keeps it to one repo |
|
|
145
|
+
| Codex | `./install.sh --target codex`, or add the repo as a Codex marketplace source |
|
|
146
|
+
| Gate dependencies | `./install.sh --deps` (`--deps=say` adds voice) |
|
|
147
|
+
| Only the gate | `pip install agent-sdlc` (WordNet check: `agent-sdlc[vocabulary]`) |
|
|
148
|
+
| The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
|
|
149
|
+
|
|
150
|
+
- `install.sh` links `skills/` into `~/.codex` and renders `commands/` as
|
|
151
|
+
Codex skills. It installs nothing into `~/.claude`. A second run changes
|
|
152
|
+
nothing.
|
|
153
|
+
- Installed an older `install.sh` into `~/.claude`? `./install.sh
|
|
154
|
+
--uninstall-legacy` removes those links and hooks, or every hook fires twice.
|
|
155
|
+
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
156
|
+
`ast-grep`.
|
|
157
|
+
|
|
158
|
+
## Use it
|
|
159
|
+
|
|
160
|
+
In Claude Code (Codex: `$name` instead of `/name`):
|
|
161
|
+
|
|
162
|
+
1. Install the plugin and `./install.sh --deps`
|
|
163
|
+
2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
|
|
164
|
+
3. `claude`, then `/grill <idea>` or `gh issue create …`
|
|
165
|
+
4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
|
|
166
|
+
5. `/kata 42`: a worker writes the failing slice test, implements, gates, opens the PR
|
|
167
|
+
6. Review, then type `LGTM`: it squash-merges and cleans up
|
|
168
|
+
7. `/done` to close out the session
|
|
169
|
+
|
|
170
|
+
Full walkthrough, and what differs in Codex:
|
|
171
|
+
<https://pavelguzenfeld.com/agent-sdlc/usage/>
|
|
172
|
+
|
|
173
|
+
## With standard
|
|
174
|
+
|
|
175
|
+
[standard](https://github.com/PavelGuzenfeld/standard) runs C++ and Python
|
|
176
|
+
quality gates on the PR. agent-sdlc runs before the commit. Use both:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
agent session -> git commit -> PR -> merge
|
|
180
|
+
'------ agent-sdlc -------' '-- standard --'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Run standard's setup first, then `mutation-gate rules sync`. Copying its
|
|
184
|
+
`AGENTS.md` afterwards drops the rules block. In CI, the agent-sdlc diff
|
|
185
|
+
checks need `--range`, because `pre-commit run --from-ref` doesn't enforce
|
|
186
|
+
them. Setup order, the CI job and the `docs/` allowlist:
|
|
187
|
+
<https://pavelguzenfeld.com/agent-sdlc/usage/#with-standard>
|
|
188
|
+
|
|
189
|
+
## What's inside
|
|
190
|
+
|
|
191
|
+
| Path | What |
|
|
192
|
+
|---|---|
|
|
193
|
+
| `rules/` | How to work; synced into a repo with `mutation-gate rules sync` |
|
|
194
|
+
| `commands/` | Slash commands: `/kata`, `/done`, `/grill`, `/rectify` … |
|
|
195
|
+
| `skills/` | On-demand playbooks: `/diagnose`, `/sol-budget`, `/verify-generated-diff` … |
|
|
196
|
+
| `bin/` | Hooks and helpers: `git-guardrail.sh`, the `/say` stack |
|
|
197
|
+
| `mutation_gate/` | The gate and the `mutation-gate` CLI |
|
|
198
|
+
|
|
199
|
+
CI runs `scripts/no-leaks.sh` on every PR. It flags emails, RFC1918
|
|
200
|
+
addresses, user-at-host references, `/home/<user>/` paths and non-personal
|
|
201
|
+
`ghcr.io/` paths, and prints only `file:line`.
|
|
202
|
+
|
|
203
|
+
MIT, see [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md) for one vendored
|
|
204
|
+
third-party skill. Contributions: [CONTRIBUTING.md](CONTRIBUTING.md), bound
|
|
205
|
+
by [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security: [SECURITY.md](SECURITY.md).
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import argparse
|
|
13
13
|
import json
|
|
14
|
+
import select
|
|
14
15
|
import sys
|
|
15
16
|
from datetime import datetime, timezone
|
|
16
17
|
from pathlib import Path
|
|
@@ -20,6 +21,7 @@ from .repo import CACHE_ROOT, CONFIG_NAME, GateError, discover, git, skip_reason
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
TIMEOUT_FACTOR = 6.0
|
|
24
|
+
STOP_HOOK_PAYLOAD_WAIT_SECONDS = 1.0
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
def _emit(line: str = "") -> None:
|
|
@@ -154,10 +156,19 @@ def _gate_file(
|
|
|
154
156
|
return blocked, survivors, cands
|
|
155
157
|
|
|
156
158
|
|
|
159
|
+
def _stdin_is_ready() -> bool:
|
|
160
|
+
try:
|
|
161
|
+
readable, _, _ = select.select([sys.stdin], [], [], STOP_HOOK_PAYLOAD_WAIT_SECONDS)
|
|
162
|
+
except (ValueError, OSError):
|
|
163
|
+
return True
|
|
164
|
+
return bool(readable)
|
|
165
|
+
|
|
166
|
+
|
|
157
167
|
def _stop_hook_payload() -> dict:
|
|
158
168
|
"""The Stop hook's JSON on stdin carries `cwd` (#68 item 1) and
|
|
159
|
-
`transcript_path` (#100) — read once, since stdin is a one-shot stream.
|
|
160
|
-
|
|
169
|
+
`transcript_path` (#100) — read once, since stdin is a one-shot stream.
|
|
170
|
+
An agent shell's stdin is an open pipe that never writes or closes (#337)."""
|
|
171
|
+
if sys.stdin.isatty() or not _stdin_is_ready():
|
|
161
172
|
return {}
|
|
162
173
|
try:
|
|
163
174
|
payload = json.loads(sys.stdin.read())
|
|
@@ -39,6 +39,9 @@ _GIT_SSH_CLONE_RE = re.compile(
|
|
|
39
39
|
_NPM_VERSION_RE = re.compile(
|
|
40
40
|
r"(^|[^A-Za-z0-9._%+/-])[A-Za-z0-9._%+-]+@[0-9]+\.[0-9]+\.[0-9]+([^0-9.]|$)"
|
|
41
41
|
)
|
|
42
|
+
_CLAUDE_PLUGIN_ID_RE = re.compile(
|
|
43
|
+
r"(^|[^A-Za-z0-9._%+/-])[A-Za-z0-9._-]+@[a]gent-sdlc([^A-Za-z0-9.-]|$)"
|
|
44
|
+
)
|
|
42
45
|
|
|
43
46
|
_BULLET_RE = re.compile(r"^\s*-\s*")
|
|
44
47
|
_ARROW = "→"
|
|
@@ -54,7 +57,8 @@ class BannedName:
|
|
|
54
57
|
|
|
55
58
|
def _carve_out(line: str) -> str:
|
|
56
59
|
line = _GIT_SSH_CLONE_RE.sub(r"\1public-git-ssh-clone-url", line)
|
|
57
|
-
|
|
60
|
+
line = _NPM_VERSION_RE.sub(r"\1npm-package-version\2", line)
|
|
61
|
+
return _CLAUDE_PLUGIN_ID_RE.sub(r"\1claude-plugin-id\2", line)
|
|
58
62
|
|
|
59
63
|
|
|
60
64
|
def generic_hit(line: str) -> bool:
|
|
@@ -45,18 +45,24 @@ TERM_GRACE_SECONDS = 15.0
|
|
|
45
45
|
|
|
46
46
|
DOCKER_RUN_RE = re.compile(r"\bdocker\s+run\b")
|
|
47
47
|
|
|
48
|
+
UNWRITTEN_PYCACHE = "/nonexistent/mutation-gate-pycache"
|
|
49
|
+
|
|
48
50
|
# Root in the container writes these into the bind-mounted repo as directories a
|
|
49
51
|
# host user then cannot rm without starting another container (#74).
|
|
50
|
-
NO_ROOT_CACHE =
|
|
52
|
+
NO_ROOT_CACHE = (
|
|
53
|
+
f"-e PYTHONDONTWRITEBYTECODE=1 -e PYTHONPYCACHEPREFIX={UNWRITTEN_PYCACHE}"
|
|
54
|
+
" -e PYTEST_ADDOPTS='-p no:cacheprovider'"
|
|
55
|
+
)
|
|
51
56
|
|
|
52
57
|
GIT_REPO_SCOPED = ("GIT_DIR", "GIT_INDEX_FILE", "GIT_WORK_TREE")
|
|
58
|
+
NO_BYTECODE = {"PYTHONDONTWRITEBYTECODE": "1", "PYTHONPYCACHEPREFIX": UNWRITTEN_PYCACHE}
|
|
53
59
|
|
|
54
60
|
|
|
55
61
|
def _test_env() -> dict[str, str]:
|
|
56
62
|
"""The environment a repo's own command runs in. Git exports these into every
|
|
57
63
|
hook and they override `-C`, so a suite that builds a temp repo stages into
|
|
58
64
|
the commit being gated instead (flowdiff#83)."""
|
|
59
|
-
return {k: v for k, v in os.environ.items() if k not in GIT_REPO_SCOPED}
|
|
65
|
+
return {k: v for k, v in os.environ.items() if k not in GIT_REPO_SCOPED} | NO_BYTECODE
|
|
60
66
|
|
|
61
67
|
|
|
62
68
|
@dataclass
|
|
@@ -30,6 +30,12 @@ sets it alongside the `model:*` label — the label approves, the assignee
|
|
|
30
30
|
names who works it. An agent works only tickets assigned to the `gh` user
|
|
31
31
|
it runs as, `@me`, and its PR carries that same assignee.
|
|
32
32
|
|
|
33
|
+
A PR waits for CI in one foreground call — `gh pr checks <PR> --watch
|
|
34
|
+
--fail-fast` or `gh run watch <run> --exit-status` — with the Bash timeout at
|
|
35
|
+
its 600000 ms maximum and the output to a file, re-issued on timeout. The call
|
|
36
|
+
blocks until CI settles, so no turn is spent on a status check: every turn
|
|
37
|
+
re-reads the whole context.
|
|
38
|
+
|
|
33
39
|
## Follow-ups
|
|
34
40
|
|
|
35
41
|
A follow-up starts from `/done`, a kata worker, any agent mid-task, or the
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
MODULE_PATH = Path(__file__).resolve().parents[1] / "bin" / "say-narrate.py"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _load_from(path: Path):
|
|
9
|
+
spec = importlib.util.spec_from_file_location("say_narrate_probe", path)
|
|
10
|
+
module = importlib.util.module_from_spec(spec)
|
|
11
|
+
spec.loader.exec_module(module)
|
|
12
|
+
return module
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_bin_is_the_scripts_own_directory_under_a_plugin_only_install(monkeypatch, tmp_path):
|
|
16
|
+
monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False)
|
|
17
|
+
monkeypatch.setenv("HOME", str(tmp_path / "home-without-claude"))
|
|
18
|
+
plugin_bin = tmp_path / "plugin" / "bin"
|
|
19
|
+
plugin_bin.mkdir(parents=True)
|
|
20
|
+
shutil.copy(MODULE_PATH, plugin_bin / "say-narrate.py")
|
|
21
|
+
|
|
22
|
+
module = _load_from(plugin_bin / "say-narrate.py")
|
|
23
|
+
|
|
24
|
+
assert module.BIN == str(plugin_bin)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_bin_ignores_a_plugin_root_that_points_somewhere_else(monkeypatch, tmp_path):
|
|
28
|
+
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", "/fake/plugin/root")
|
|
29
|
+
legacy_bin = tmp_path / ".claude" / "bin"
|
|
30
|
+
legacy_bin.mkdir(parents=True)
|
|
31
|
+
(legacy_bin / "say-narrate.py").symlink_to(MODULE_PATH)
|
|
32
|
+
|
|
33
|
+
module = _load_from(legacy_bin / "say-narrate.py")
|
|
34
|
+
|
|
35
|
+
assert module.BIN == str(legacy_bin)
|
agent_sdlc-0.3.0/PKG-INFO
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: agent-sdlc
|
|
3
|
-
Version: 0.3.0
|
|
4
|
-
Summary: Diff-scoped mutation gate for pre-commit and agent Stop hooks
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/PavelGuzenfeld/agent-sdlc
|
|
7
|
-
Requires-Python: >=3.11
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
License-File: NOTICE.md
|
|
11
|
-
Requires-Dist: ast-grep-cli==0.45.3
|
|
12
|
-
Provides-Extra: vocabulary
|
|
13
|
-
Requires-Dist: nltk; extra == "vocabulary"
|
|
14
|
-
Dynamic: license-file
|
|
15
|
-
|
|
16
|
-
# agent-sdlc
|
|
17
|
-
|
|
18
|
-
[](https://github.com/PavelGuzenfeld/agent-sdlc/actions/workflows/ci.yml)
|
|
19
|
-
|
|
20
|
-
A software development lifecycle shipped as agent config for Claude Code and
|
|
21
|
-
Codex: rules, skills, slash commands, hooks, and a diff-scoped mutation gate.
|
|
22
|
-
|
|
23
|
-
Docs: <https://pavelguzenfeld.com/agent-sdlc/>
|
|
24
|
-
|
|
25
|
-
## Why
|
|
26
|
-
|
|
27
|
-
A coding agent writes the code and the tests for it, and tests written next
|
|
28
|
-
to the code tend to agree with it, bugs included. On a mature suite, 57% of
|
|
29
|
-
real bug-class mutants survived. agent-sdlc puts that check, plus scope,
|
|
30
|
-
intent and git safety, into hooks and a gate instead of leaving them to the
|
|
31
|
-
agent's memory.
|
|
32
|
-
|
|
33
|
-
| Problem | What catches it |
|
|
34
|
-
|---|---|
|
|
35
|
-
| Tests that pass but assert nothing | `mutation-gate`: a surviving mutant blocks the commit |
|
|
36
|
-
| Tests shaped to the code | An adversary review that sees intent and tests, never the code |
|
|
37
|
-
| Scope creep | 40-line limit without a ticket; one ticket, one branch, one PR |
|
|
38
|
-
| Design notes rotting in the tree | New `.md` files are blocked; intent lives in the tracker |
|
|
39
|
-
| Destructive git | A hook that denies `reset --hard`, `add -A`, force-push |
|
|
40
|
-
| AI tells and leaked identity in commits | `commit-msg` and `no-leaks` hooks |
|
|
41
|
-
|
|
42
|
-
## Example
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
def is_adult(age):
|
|
46
|
-
return age >= 18
|
|
47
|
-
|
|
48
|
-
def test_adult():
|
|
49
|
-
assert is_adult(30)
|
|
50
|
-
|
|
51
|
-
def test_child():
|
|
52
|
-
assert not is_adult(5)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```text
|
|
56
|
-
BLOCKED: 2 mutant(s) survived with no waiver.
|
|
57
|
-
age.py:2:11:operator:age >= 18 => age > 18
|
|
58
|
-
age.py:2:18:literal:18 => 19
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
|
|
62
|
-
|
|
63
|
-
## Install
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
git clone https://github.com/PavelGuzenfeld/agent-sdlc
|
|
67
|
-
cd agent-sdlc
|
|
68
|
-
./install.sh --target all --deps
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
| Want | Do |
|
|
72
|
-
|---|---|
|
|
73
|
-
| The whole pack | `./install.sh --target all --deps` (targets: `claude`, `codex`, `all`; `--deps=say` adds voice) |
|
|
74
|
-
| Only the gate | `pip install agent-sdlc` (WordNet check: `agent-sdlc[vocabulary]`) |
|
|
75
|
-
| A plugin | `claude --plugin-dir /path/to/agent-sdlc`, or add the repo as a Codex marketplace source |
|
|
76
|
-
| The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
|
|
77
|
-
|
|
78
|
-
- `install.sh` links `skills/` into `~/.claude` and `~/.codex`, links
|
|
79
|
-
`commands/` and `bin/` into `~/.claude`, renders `commands/` as Codex
|
|
80
|
-
skills and merges the hooks. A second run changes nothing.
|
|
81
|
-
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
82
|
-
`ast-grep`.
|
|
83
|
-
|
|
84
|
-
## Use it
|
|
85
|
-
|
|
86
|
-
In Claude Code (Codex: `$name` instead of `/name`):
|
|
87
|
-
|
|
88
|
-
1. `./install.sh --target claude --deps`
|
|
89
|
-
2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
|
|
90
|
-
3. `claude`, then `/grill <idea>` or `gh issue create …`
|
|
91
|
-
4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
|
|
92
|
-
5. `/kata 42`: a worker writes the failing test, implements, gates, opens the PR
|
|
93
|
-
6. Review, then type `LGTM`: it squash-merges and cleans up
|
|
94
|
-
7. `/done` to close out the session
|
|
95
|
-
|
|
96
|
-
Full walkthrough, and what differs in Codex:
|
|
97
|
-
<https://pavelguzenfeld.com/agent-sdlc/usage/>
|
|
98
|
-
|
|
99
|
-
## What's inside
|
|
100
|
-
|
|
101
|
-
| Path | What |
|
|
102
|
-
|---|---|
|
|
103
|
-
| `rules/` | How to work; synced into a repo with `mutation-gate rules sync` |
|
|
104
|
-
| `commands/` | Slash commands: `/kata`, `/done`, `/grill`, `/rectify` … |
|
|
105
|
-
| `skills/` | On-demand playbooks: `/diagnose`, `/sol-budget`, `/verify-generated-diff` … |
|
|
106
|
-
| `bin/` | Hooks and helpers: `git-guardrail.sh`, the `/say` stack |
|
|
107
|
-
| `mutation_gate/` | The gate and the `mutation-gate` CLI |
|
|
108
|
-
|
|
109
|
-
CI runs `scripts/no-leaks.sh` on every PR. It flags emails, RFC1918
|
|
110
|
-
addresses, user-at-host references, `/home/<user>/` paths and non-personal
|
|
111
|
-
`ghcr.io/` paths, and prints only `file:line`.
|
|
112
|
-
|
|
113
|
-
MIT, see [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md) for one vendored
|
|
114
|
-
third-party skill. Contributions: [CONTRIBUTING.md](CONTRIBUTING.md), bound
|
|
115
|
-
by [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security: [SECURITY.md](SECURITY.md).
|
agent_sdlc-0.3.0/README.md
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# agent-sdlc
|
|
2
|
-
|
|
3
|
-
[](https://github.com/PavelGuzenfeld/agent-sdlc/actions/workflows/ci.yml)
|
|
4
|
-
|
|
5
|
-
A software development lifecycle shipped as agent config for Claude Code and
|
|
6
|
-
Codex: rules, skills, slash commands, hooks, and a diff-scoped mutation gate.
|
|
7
|
-
|
|
8
|
-
Docs: <https://pavelguzenfeld.com/agent-sdlc/>
|
|
9
|
-
|
|
10
|
-
## Why
|
|
11
|
-
|
|
12
|
-
A coding agent writes the code and the tests for it, and tests written next
|
|
13
|
-
to the code tend to agree with it, bugs included. On a mature suite, 57% of
|
|
14
|
-
real bug-class mutants survived. agent-sdlc puts that check, plus scope,
|
|
15
|
-
intent and git safety, into hooks and a gate instead of leaving them to the
|
|
16
|
-
agent's memory.
|
|
17
|
-
|
|
18
|
-
| Problem | What catches it |
|
|
19
|
-
|---|---|
|
|
20
|
-
| Tests that pass but assert nothing | `mutation-gate`: a surviving mutant blocks the commit |
|
|
21
|
-
| Tests shaped to the code | An adversary review that sees intent and tests, never the code |
|
|
22
|
-
| Scope creep | 40-line limit without a ticket; one ticket, one branch, one PR |
|
|
23
|
-
| Design notes rotting in the tree | New `.md` files are blocked; intent lives in the tracker |
|
|
24
|
-
| Destructive git | A hook that denies `reset --hard`, `add -A`, force-push |
|
|
25
|
-
| AI tells and leaked identity in commits | `commit-msg` and `no-leaks` hooks |
|
|
26
|
-
|
|
27
|
-
## Example
|
|
28
|
-
|
|
29
|
-
```python
|
|
30
|
-
def is_adult(age):
|
|
31
|
-
return age >= 18
|
|
32
|
-
|
|
33
|
-
def test_adult():
|
|
34
|
-
assert is_adult(30)
|
|
35
|
-
|
|
36
|
-
def test_child():
|
|
37
|
-
assert not is_adult(5)
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```text
|
|
41
|
-
BLOCKED: 2 mutant(s) survived with no waiver.
|
|
42
|
-
age.py:2:11:operator:age >= 18 => age > 18
|
|
43
|
-
age.py:2:18:literal:18 => 19
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
|
|
47
|
-
|
|
48
|
-
## Install
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
git clone https://github.com/PavelGuzenfeld/agent-sdlc
|
|
52
|
-
cd agent-sdlc
|
|
53
|
-
./install.sh --target all --deps
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
| Want | Do |
|
|
57
|
-
|---|---|
|
|
58
|
-
| The whole pack | `./install.sh --target all --deps` (targets: `claude`, `codex`, `all`; `--deps=say` adds voice) |
|
|
59
|
-
| Only the gate | `pip install agent-sdlc` (WordNet check: `agent-sdlc[vocabulary]`) |
|
|
60
|
-
| A plugin | `claude --plugin-dir /path/to/agent-sdlc`, or add the repo as a Codex marketplace source |
|
|
61
|
-
| The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
|
|
62
|
-
|
|
63
|
-
- `install.sh` links `skills/` into `~/.claude` and `~/.codex`, links
|
|
64
|
-
`commands/` and `bin/` into `~/.claude`, renders `commands/` as Codex
|
|
65
|
-
skills and merges the hooks. A second run changes nothing.
|
|
66
|
-
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
67
|
-
`ast-grep`.
|
|
68
|
-
|
|
69
|
-
## Use it
|
|
70
|
-
|
|
71
|
-
In Claude Code (Codex: `$name` instead of `/name`):
|
|
72
|
-
|
|
73
|
-
1. `./install.sh --target claude --deps`
|
|
74
|
-
2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
|
|
75
|
-
3. `claude`, then `/grill <idea>` or `gh issue create …`
|
|
76
|
-
4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
|
|
77
|
-
5. `/kata 42`: a worker writes the failing test, implements, gates, opens the PR
|
|
78
|
-
6. Review, then type `LGTM`: it squash-merges and cleans up
|
|
79
|
-
7. `/done` to close out the session
|
|
80
|
-
|
|
81
|
-
Full walkthrough, and what differs in Codex:
|
|
82
|
-
<https://pavelguzenfeld.com/agent-sdlc/usage/>
|
|
83
|
-
|
|
84
|
-
## What's inside
|
|
85
|
-
|
|
86
|
-
| Path | What |
|
|
87
|
-
|---|---|
|
|
88
|
-
| `rules/` | How to work; synced into a repo with `mutation-gate rules sync` |
|
|
89
|
-
| `commands/` | Slash commands: `/kata`, `/done`, `/grill`, `/rectify` … |
|
|
90
|
-
| `skills/` | On-demand playbooks: `/diagnose`, `/sol-budget`, `/verify-generated-diff` … |
|
|
91
|
-
| `bin/` | Hooks and helpers: `git-guardrail.sh`, the `/say` stack |
|
|
92
|
-
| `mutation_gate/` | The gate and the `mutation-gate` CLI |
|
|
93
|
-
|
|
94
|
-
CI runs `scripts/no-leaks.sh` on every PR. It flags emails, RFC1918
|
|
95
|
-
addresses, user-at-host references, `/home/<user>/` paths and non-personal
|
|
96
|
-
`ghcr.io/` paths, and prints only `file:line`.
|
|
97
|
-
|
|
98
|
-
MIT, see [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md) for one vendored
|
|
99
|
-
third-party skill. Contributions: [CONTRIBUTING.md](CONTRIBUTING.md), bound
|
|
100
|
-
by [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security: [SECURITY.md](SECURITY.md).
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: agent-sdlc
|
|
3
|
-
Version: 0.3.0
|
|
4
|
-
Summary: Diff-scoped mutation gate for pre-commit and agent Stop hooks
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/PavelGuzenfeld/agent-sdlc
|
|
7
|
-
Requires-Python: >=3.11
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
License-File: NOTICE.md
|
|
11
|
-
Requires-Dist: ast-grep-cli==0.45.3
|
|
12
|
-
Provides-Extra: vocabulary
|
|
13
|
-
Requires-Dist: nltk; extra == "vocabulary"
|
|
14
|
-
Dynamic: license-file
|
|
15
|
-
|
|
16
|
-
# agent-sdlc
|
|
17
|
-
|
|
18
|
-
[](https://github.com/PavelGuzenfeld/agent-sdlc/actions/workflows/ci.yml)
|
|
19
|
-
|
|
20
|
-
A software development lifecycle shipped as agent config for Claude Code and
|
|
21
|
-
Codex: rules, skills, slash commands, hooks, and a diff-scoped mutation gate.
|
|
22
|
-
|
|
23
|
-
Docs: <https://pavelguzenfeld.com/agent-sdlc/>
|
|
24
|
-
|
|
25
|
-
## Why
|
|
26
|
-
|
|
27
|
-
A coding agent writes the code and the tests for it, and tests written next
|
|
28
|
-
to the code tend to agree with it, bugs included. On a mature suite, 57% of
|
|
29
|
-
real bug-class mutants survived. agent-sdlc puts that check, plus scope,
|
|
30
|
-
intent and git safety, into hooks and a gate instead of leaving them to the
|
|
31
|
-
agent's memory.
|
|
32
|
-
|
|
33
|
-
| Problem | What catches it |
|
|
34
|
-
|---|---|
|
|
35
|
-
| Tests that pass but assert nothing | `mutation-gate`: a surviving mutant blocks the commit |
|
|
36
|
-
| Tests shaped to the code | An adversary review that sees intent and tests, never the code |
|
|
37
|
-
| Scope creep | 40-line limit without a ticket; one ticket, one branch, one PR |
|
|
38
|
-
| Design notes rotting in the tree | New `.md` files are blocked; intent lives in the tracker |
|
|
39
|
-
| Destructive git | A hook that denies `reset --hard`, `add -A`, force-push |
|
|
40
|
-
| AI tells and leaked identity in commits | `commit-msg` and `no-leaks` hooks |
|
|
41
|
-
|
|
42
|
-
## Example
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
def is_adult(age):
|
|
46
|
-
return age >= 18
|
|
47
|
-
|
|
48
|
-
def test_adult():
|
|
49
|
-
assert is_adult(30)
|
|
50
|
-
|
|
51
|
-
def test_child():
|
|
52
|
-
assert not is_adult(5)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```text
|
|
56
|
-
BLOCKED: 2 mutant(s) survived with no waiver.
|
|
57
|
-
age.py:2:11:operator:age >= 18 => age > 18
|
|
58
|
-
age.py:2:18:literal:18 => 19
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
|
|
62
|
-
|
|
63
|
-
## Install
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
git clone https://github.com/PavelGuzenfeld/agent-sdlc
|
|
67
|
-
cd agent-sdlc
|
|
68
|
-
./install.sh --target all --deps
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
| Want | Do |
|
|
72
|
-
|---|---|
|
|
73
|
-
| The whole pack | `./install.sh --target all --deps` (targets: `claude`, `codex`, `all`; `--deps=say` adds voice) |
|
|
74
|
-
| Only the gate | `pip install agent-sdlc` (WordNet check: `agent-sdlc[vocabulary]`) |
|
|
75
|
-
| A plugin | `claude --plugin-dir /path/to/agent-sdlc`, or add the repo as a Codex marketplace source |
|
|
76
|
-
| The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
|
|
77
|
-
|
|
78
|
-
- `install.sh` links `skills/` into `~/.claude` and `~/.codex`, links
|
|
79
|
-
`commands/` and `bin/` into `~/.claude`, renders `commands/` as Codex
|
|
80
|
-
skills and merges the hooks. A second run changes nothing.
|
|
81
|
-
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
82
|
-
`ast-grep`.
|
|
83
|
-
|
|
84
|
-
## Use it
|
|
85
|
-
|
|
86
|
-
In Claude Code (Codex: `$name` instead of `/name`):
|
|
87
|
-
|
|
88
|
-
1. `./install.sh --target claude --deps`
|
|
89
|
-
2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
|
|
90
|
-
3. `claude`, then `/grill <idea>` or `gh issue create …`
|
|
91
|
-
4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
|
|
92
|
-
5. `/kata 42`: a worker writes the failing test, implements, gates, opens the PR
|
|
93
|
-
6. Review, then type `LGTM`: it squash-merges and cleans up
|
|
94
|
-
7. `/done` to close out the session
|
|
95
|
-
|
|
96
|
-
Full walkthrough, and what differs in Codex:
|
|
97
|
-
<https://pavelguzenfeld.com/agent-sdlc/usage/>
|
|
98
|
-
|
|
99
|
-
## What's inside
|
|
100
|
-
|
|
101
|
-
| Path | What |
|
|
102
|
-
|---|---|
|
|
103
|
-
| `rules/` | How to work; synced into a repo with `mutation-gate rules sync` |
|
|
104
|
-
| `commands/` | Slash commands: `/kata`, `/done`, `/grill`, `/rectify` … |
|
|
105
|
-
| `skills/` | On-demand playbooks: `/diagnose`, `/sol-budget`, `/verify-generated-diff` … |
|
|
106
|
-
| `bin/` | Hooks and helpers: `git-guardrail.sh`, the `/say` stack |
|
|
107
|
-
| `mutation_gate/` | The gate and the `mutation-gate` CLI |
|
|
108
|
-
|
|
109
|
-
CI runs `scripts/no-leaks.sh` on every PR. It flags emails, RFC1918
|
|
110
|
-
addresses, user-at-host references, `/home/<user>/` paths and non-personal
|
|
111
|
-
`ghcr.io/` paths, and prints only `file:line`.
|
|
112
|
-
|
|
113
|
-
MIT, see [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md) for one vendored
|
|
114
|
-
third-party skill. Contributions: [CONTRIBUTING.md](CONTRIBUTING.md), bound
|
|
115
|
-
by [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security: [SECURITY.md](SECURITY.md).
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import importlib.util
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
MODULE_PATH = Path(__file__).resolve().parents[1] / "bin" / "say-narrate.py"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _load(monkeypatch, plugin_root):
|
|
8
|
-
if plugin_root is None:
|
|
9
|
-
monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False)
|
|
10
|
-
else:
|
|
11
|
-
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", plugin_root)
|
|
12
|
-
spec = importlib.util.spec_from_file_location("say_narrate_probe", MODULE_PATH)
|
|
13
|
-
module = importlib.util.module_from_spec(spec)
|
|
14
|
-
spec.loader.exec_module(module)
|
|
15
|
-
return module
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_bin_resolves_under_plugin_root_when_set(monkeypatch):
|
|
19
|
-
module = _load(monkeypatch, "/fake/plugin/root")
|
|
20
|
-
assert module.BIN == "/fake/plugin/root/bin"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_bin_falls_back_to_legacy_claude_bin_when_unset(monkeypatch, tmp_path):
|
|
24
|
-
monkeypatch.setenv("HOME", str(tmp_path))
|
|
25
|
-
module = _load(monkeypatch, None)
|
|
26
|
-
assert module.BIN == str(tmp_path / ".claude" / "bin")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|