agent-sdlc 0.2.0__tar.gz → 0.3.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.2.0/agent_sdlc.egg-info → agent_sdlc-0.3.0}/PKG-INFO +16 -1
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/README.md +15 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0/agent_sdlc.egg-info}/PKG-INFO +16 -1
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/__init__.py +1 -1
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/cli.py +1 -1
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/mutants.py +13 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/repo.py +30 -2
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/tickets.md +8 -2
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/LICENSE +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/NOTICE.md +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/SOURCES.txt +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/dependency_links.txt +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/entry_points.txt +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/requires.txt +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/top_level.txt +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/adversary.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/commit_msg.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/coverage_map.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/diff_discipline.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/model_vv.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/no_comments.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/no_leaks.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/no_new_docs.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/rules.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/runner.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/token.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_check.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_core.toml +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_molds.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_path.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_wordnet.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/waivers.py +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/pyproject.toml +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/code-clarity.md +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/diff-discipline.md +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/model-vv.md +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/naming.md +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/testing.md +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/voice.md +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/setup.cfg +0 -0
- {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/tests/test_say_narrate_bin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-sdlc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Diff-scoped mutation gate for pre-commit and agent Stop hooks
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/PavelGuzenfeld/agent-sdlc
|
|
@@ -81,6 +81,21 @@ cd agent-sdlc
|
|
|
81
81
|
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
82
82
|
`ast-grep`.
|
|
83
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
|
+
|
|
84
99
|
## What's inside
|
|
85
100
|
|
|
86
101
|
| Path | What |
|
|
@@ -66,6 +66,21 @@ cd agent-sdlc
|
|
|
66
66
|
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
67
67
|
`ast-grep`.
|
|
68
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
|
+
|
|
69
84
|
## What's inside
|
|
70
85
|
|
|
71
86
|
| Path | What |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-sdlc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Diff-scoped mutation gate for pre-commit and agent Stop hooks
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/PavelGuzenfeld/agent-sdlc
|
|
@@ -81,6 +81,21 @@ cd agent-sdlc
|
|
|
81
81
|
- `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
|
|
82
82
|
`ast-grep`.
|
|
83
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
|
+
|
|
84
99
|
## What's inside
|
|
85
100
|
|
|
86
101
|
| Path | What |
|
|
@@ -121,7 +121,7 @@ def _gate_file(
|
|
|
121
121
|
all_tests = [str(p.relative_to(repo.root)) for p in cands]
|
|
122
122
|
|
|
123
123
|
baseline = runner.baseline_green(repo, all_tests, lang_cfg.test_command)
|
|
124
|
-
configured = repo.config.
|
|
124
|
+
configured = repo.config.mutant_timeout_for(rel)
|
|
125
125
|
timeout = configured if configured else max(30.0, baseline * TIMEOUT_FACTOR)
|
|
126
126
|
source = "configured" if configured else "from baseline"
|
|
127
127
|
_emit(f" {rel}: baseline {baseline:.1f}s, per-mutant timeout {timeout:.0f}s ({source})")
|
|
@@ -405,6 +405,17 @@ def _literal_mutants(
|
|
|
405
405
|
return out
|
|
406
406
|
|
|
407
407
|
|
|
408
|
+
MAIN_MODULE_LITERALS = frozenset({'"__main__"', "'__main__'"})
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _is_main_guard(hit: dict) -> bool:
|
|
412
|
+
"""Flipped, the guard runs the script at import with the test runner's argv
|
|
413
|
+
(#315); a subprocess test covers the entry point instead."""
|
|
414
|
+
operands = hit.get("metaVariables", {}).get("single", {})
|
|
415
|
+
texts = {operands.get(name, {}).get("text") for name in ("A", "B")}
|
|
416
|
+
return "__name__" in texts and bool(texts & MAIN_MODULE_LITERALS)
|
|
417
|
+
|
|
418
|
+
|
|
408
419
|
def _gdscript_config(root: Path) -> Path | None:
|
|
409
420
|
from . import vocabulary_check
|
|
410
421
|
return vocabulary_check._gdscript_ready(root)
|
|
@@ -437,6 +448,8 @@ def generate(root: Path, files: dict[str, set[int]], language: str) -> list[Muta
|
|
|
437
448
|
# BEFORE `seen`, or the identity shadows the real `not in => in` mutant.
|
|
438
449
|
if hit["replacement"] == hit["text"]:
|
|
439
450
|
continue
|
|
451
|
+
if _is_main_guard(hit):
|
|
452
|
+
continue
|
|
440
453
|
if lineno not in lines or span in seen:
|
|
441
454
|
continue
|
|
442
455
|
seen.add(span)
|
|
@@ -159,6 +159,12 @@ class Golden:
|
|
|
159
159
|
artifact: str
|
|
160
160
|
|
|
161
161
|
|
|
162
|
+
@dataclass(frozen=True)
|
|
163
|
+
class PathTimeout:
|
|
164
|
+
path: str
|
|
165
|
+
mutant_timeout: float
|
|
166
|
+
|
|
167
|
+
|
|
162
168
|
@dataclass
|
|
163
169
|
class Config:
|
|
164
170
|
enabled: bool = True
|
|
@@ -197,6 +203,7 @@ class Config:
|
|
|
197
203
|
# baseline, which is right only where a mutant costs what the baseline cost;
|
|
198
204
|
# where a build sits between the edit and the tests it does not (#78).
|
|
199
205
|
mutant_timeout: float | None = None
|
|
206
|
+
timeout: list[PathTimeout] = field(default_factory=list)
|
|
200
207
|
# Import hops from a test to the mutated file. 1 = direct import; raising it
|
|
201
208
|
# selects nearly the whole suite once a common module sits in the path.
|
|
202
209
|
closure_depth: int = 1
|
|
@@ -237,9 +244,10 @@ class Config:
|
|
|
237
244
|
model_exclude = _load_model_exclude(raw.pop("model_exclude", []))
|
|
238
245
|
golden = _load_golden(raw.pop("golden", []))
|
|
239
246
|
doc_allow = _load_doc_allow(raw.pop("doc_allow", []))
|
|
247
|
+
timeout = _load_timeout(raw.pop("timeout", []))
|
|
240
248
|
# Named, not silently dropped: a key that does nothing is how a repo
|
|
241
249
|
# believes it is scoping the gate while the gate ignores it.
|
|
242
|
-
nested = {"languages", "model_exclude", "golden", "doc_allow"}
|
|
250
|
+
nested = {"languages", "model_exclude", "golden", "doc_allow", "timeout"}
|
|
243
251
|
unknown = sorted(set(raw) - {f.name for f in fields(cls) if f.name not in nested})
|
|
244
252
|
if unknown:
|
|
245
253
|
raise GateError(
|
|
@@ -247,7 +255,7 @@ class Config:
|
|
|
247
255
|
"nothing reads them. Scope the gate with exclude_paths."
|
|
248
256
|
)
|
|
249
257
|
cfg = cls(**raw, languages=languages, model_exclude=model_exclude, golden=golden,
|
|
250
|
-
doc_allow=doc_allow)
|
|
258
|
+
doc_allow=doc_allow, timeout=timeout)
|
|
251
259
|
if cfg.model_paths and not cfg.model_spec.startswith("issue:"):
|
|
252
260
|
raise GateError(
|
|
253
261
|
f'{CONFIG_NAME}: model_spec must be "issue:N" naming a pinned issue once '
|
|
@@ -255,6 +263,12 @@ class Config:
|
|
|
255
263
|
)
|
|
256
264
|
return cfg
|
|
257
265
|
|
|
266
|
+
def mutant_timeout_for(self, rel: str) -> float | None:
|
|
267
|
+
matching = [t for t in self.timeout if rel.startswith(t.path)]
|
|
268
|
+
if not matching:
|
|
269
|
+
return self.mutant_timeout
|
|
270
|
+
return max(matching, key=lambda t: len(t.path)).mutant_timeout
|
|
271
|
+
|
|
258
272
|
def test_prefixes(self) -> set[str]:
|
|
259
273
|
paths = set(self.test_paths)
|
|
260
274
|
for lang_cfg in self.languages.values():
|
|
@@ -308,6 +322,20 @@ def _load_golden(raw: list) -> list[Golden]:
|
|
|
308
322
|
return out
|
|
309
323
|
|
|
310
324
|
|
|
325
|
+
def _load_timeout(raw: list) -> list[PathTimeout]:
|
|
326
|
+
out: list[PathTimeout] = []
|
|
327
|
+
for entry in raw:
|
|
328
|
+
cap = entry.get("mutant_timeout")
|
|
329
|
+
if (set(entry) != {"path", "mutant_timeout"} or isinstance(cap, bool)
|
|
330
|
+
or not isinstance(cap, (int, float)) or cap <= 0):
|
|
331
|
+
raise GateError(
|
|
332
|
+
f"{CONFIG_NAME}: every [[timeout]] needs exactly `path` and a positive "
|
|
333
|
+
"`mutant_timeout` in seconds"
|
|
334
|
+
)
|
|
335
|
+
out.append(PathTimeout(path=entry["path"], mutant_timeout=float(cap)))
|
|
336
|
+
return out
|
|
337
|
+
|
|
338
|
+
|
|
311
339
|
def _load_languages(raw: dict) -> dict[str, LanguageConfig]:
|
|
312
340
|
known = {f.name for f in fields(LanguageConfig)}
|
|
313
341
|
languages: dict[str, LanguageConfig] = {}
|
|
@@ -25,6 +25,11 @@ repo defaults to squash-only merges with delete-branch-on-merge.
|
|
|
25
25
|
The one carve-out: a batch of confirmed `size:tiny` follow-ups may share one
|
|
26
26
|
worker, one branch and one PR, with a `Closes #N` line per ticket.
|
|
27
27
|
|
|
28
|
+
Every ticket and its PR name one responsible person as assignee. Triage
|
|
29
|
+
sets it alongside the `model:*` label — the label approves, the assignee
|
|
30
|
+
names who works it. An agent works only tickets assigned to the `gh` user
|
|
31
|
+
it runs as, `@me`, and its PR carries that same assignee.
|
|
32
|
+
|
|
28
33
|
## Follow-ups
|
|
29
34
|
|
|
30
35
|
A follow-up starts from `/done`, a kata worker, any agent mid-task, or the
|
|
@@ -39,8 +44,9 @@ form's label.
|
|
|
39
44
|
The filer proposes `size:tiny` when the ticket is tiny: its evidence names
|
|
40
45
|
one location, it leaves no design choice open, and it needs no new file,
|
|
41
46
|
dependency or config key. The maintainer confirms the proposal by adding its
|
|
42
|
-
model label. A follow-up is never born with a `model:<name>` label
|
|
43
|
-
label-creation step must never add
|
|
47
|
+
model label. A follow-up is never born with a `model:<name>` label or an
|
|
48
|
+
assignee — triage sets both — and a label-creation step must never add the
|
|
49
|
+
label to an untriaged follow-up.
|
|
44
50
|
|
|
45
51
|
Labels are created if missing; if creation is refused, file without them
|
|
46
52
|
rather than drop the item.
|
|
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
|
|
File without changes
|