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.
Files changed (42) hide show
  1. {agent_sdlc-0.2.0/agent_sdlc.egg-info → agent_sdlc-0.3.0}/PKG-INFO +16 -1
  2. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/README.md +15 -0
  3. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0/agent_sdlc.egg-info}/PKG-INFO +16 -1
  4. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/__init__.py +1 -1
  5. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/cli.py +1 -1
  6. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/mutants.py +13 -0
  7. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/repo.py +30 -2
  8. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/tickets.md +8 -2
  9. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/LICENSE +0 -0
  10. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/NOTICE.md +0 -0
  11. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/SOURCES.txt +0 -0
  12. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/dependency_links.txt +0 -0
  13. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/entry_points.txt +0 -0
  14. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/requires.txt +0 -0
  15. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/agent_sdlc.egg-info/top_level.txt +0 -0
  16. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/adversary.py +0 -0
  17. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/commit_msg.py +0 -0
  18. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/coverage_map.py +0 -0
  19. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/diff_discipline.py +0 -0
  20. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/model_vv.py +0 -0
  21. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/no_comments.py +0 -0
  22. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/no_leaks.py +0 -0
  23. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/no_new_docs.py +0 -0
  24. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/rules.py +0 -0
  25. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/runner.py +0 -0
  26. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/token.py +0 -0
  27. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary.py +0 -0
  28. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_check.py +0 -0
  29. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_core.toml +0 -0
  30. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_molds.py +0 -0
  31. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_path.py +0 -0
  32. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/vocabulary_wordnet.py +0 -0
  33. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/mutation_gate/waivers.py +0 -0
  34. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/pyproject.toml +0 -0
  35. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/code-clarity.md +0 -0
  36. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/diff-discipline.md +0 -0
  37. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/model-vv.md +0 -0
  38. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/naming.md +0 -0
  39. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/testing.md +0 -0
  40. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/rules/voice.md +0 -0
  41. {agent_sdlc-0.2.0 → agent_sdlc-0.3.0}/setup.cfg +0 -0
  42. {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.2.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.2.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 |
@@ -1,3 +1,3 @@
1
1
  """Diff-scoped mutation gate. Spec: PavelGuzenfeld/dotfiles#9."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
@@ -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.mutant_timeout
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, and a
43
- label-creation step must never add one to an untriaged follow-up.
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