agent-sdlc 0.3.0__tar.gz → 0.3.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.
Files changed (43) hide show
  1. {agent_sdlc-0.3.0/agent_sdlc.egg-info → agent_sdlc-0.3.1}/PKG-INFO +20 -8
  2. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/README.md +19 -7
  3. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1/agent_sdlc.egg-info}/PKG-INFO +20 -8
  4. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/__init__.py +1 -1
  5. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/no_leaks.py +5 -1
  6. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/runner.py +8 -2
  7. agent_sdlc-0.3.1/tests/test_say_narrate_bin.py +35 -0
  8. agent_sdlc-0.3.0/tests/test_say_narrate_bin.py +0 -26
  9. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/LICENSE +0 -0
  10. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/NOTICE.md +0 -0
  11. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/agent_sdlc.egg-info/SOURCES.txt +0 -0
  12. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/agent_sdlc.egg-info/dependency_links.txt +0 -0
  13. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/agent_sdlc.egg-info/entry_points.txt +0 -0
  14. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/agent_sdlc.egg-info/requires.txt +0 -0
  15. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/agent_sdlc.egg-info/top_level.txt +0 -0
  16. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/adversary.py +0 -0
  17. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/cli.py +0 -0
  18. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/commit_msg.py +0 -0
  19. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/coverage_map.py +0 -0
  20. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/diff_discipline.py +0 -0
  21. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/model_vv.py +0 -0
  22. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/mutants.py +0 -0
  23. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/no_comments.py +0 -0
  24. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/no_new_docs.py +0 -0
  25. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/repo.py +0 -0
  26. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/rules.py +0 -0
  27. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/token.py +0 -0
  28. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/vocabulary.py +0 -0
  29. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/vocabulary_check.py +0 -0
  30. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/vocabulary_core.toml +0 -0
  31. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/vocabulary_molds.py +0 -0
  32. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/vocabulary_path.py +0 -0
  33. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/vocabulary_wordnet.py +0 -0
  34. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/mutation_gate/waivers.py +0 -0
  35. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/pyproject.toml +0 -0
  36. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/rules/code-clarity.md +0 -0
  37. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/rules/diff-discipline.md +0 -0
  38. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/rules/model-vv.md +0 -0
  39. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/rules/naming.md +0 -0
  40. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/rules/testing.md +0 -0
  41. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/rules/tickets.md +0 -0
  42. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/rules/voice.md +0 -0
  43. {agent_sdlc-0.3.0 → agent_sdlc-0.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-sdlc
3
- Version: 0.3.0
3
+ Version: 0.3.1
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
@@ -62,22 +62,34 @@ Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
62
62
 
63
63
  ## Install
64
64
 
65
+ Claude Code:
66
+
67
+ ```bash
68
+ claude plugin marketplace add PavelGuzenfeld/agent-sdlc
69
+ claude plugin install agent-sdlc@agent-sdlc
70
+ ```
71
+
72
+ Tools the gate needs, and Codex:
73
+
65
74
  ```bash
66
75
  git clone https://github.com/PavelGuzenfeld/agent-sdlc
67
76
  cd agent-sdlc
68
- ./install.sh --target all --deps
77
+ ./install.sh --deps
69
78
  ```
70
79
 
71
80
  | Want | Do |
72
81
  |---|---|
73
- | The whole pack | `./install.sh --target all --deps` (targets: `claude`, `codex`, `all`; `--deps=say` adds voice) |
82
+ | Claude Code | The plugin above; `--scope local` or `--scope project` keeps it to one repo |
83
+ | Codex | `./install.sh --target codex`, or add the repo as a Codex marketplace source |
84
+ | Gate dependencies | `./install.sh --deps` (`--deps=say` adds voice) |
74
85
  | 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
86
  | The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
77
87
 
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.
88
+ - `install.sh` links `skills/` into `~/.codex` and renders `commands/` as
89
+ Codex skills. It installs nothing into `~/.claude`. A second run changes
90
+ nothing.
91
+ - Installed an older `install.sh` into `~/.claude`? `./install.sh
92
+ --uninstall-legacy` removes those links and hooks, or every hook fires twice.
81
93
  - `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
82
94
  `ast-grep`.
83
95
 
@@ -85,7 +97,7 @@ cd agent-sdlc
85
97
 
86
98
  In Claude Code (Codex: `$name` instead of `/name`):
87
99
 
88
- 1. `./install.sh --target claude --deps`
100
+ 1. Install the plugin and `./install.sh --deps`
89
101
  2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
90
102
  3. `claude`, then `/grill <idea>` or `gh issue create …`
91
103
  4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
@@ -47,22 +47,34 @@ Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
47
47
 
48
48
  ## Install
49
49
 
50
+ Claude Code:
51
+
52
+ ```bash
53
+ claude plugin marketplace add PavelGuzenfeld/agent-sdlc
54
+ claude plugin install agent-sdlc@agent-sdlc
55
+ ```
56
+
57
+ Tools the gate needs, and Codex:
58
+
50
59
  ```bash
51
60
  git clone https://github.com/PavelGuzenfeld/agent-sdlc
52
61
  cd agent-sdlc
53
- ./install.sh --target all --deps
62
+ ./install.sh --deps
54
63
  ```
55
64
 
56
65
  | Want | Do |
57
66
  |---|---|
58
- | The whole pack | `./install.sh --target all --deps` (targets: `claude`, `codex`, `all`; `--deps=say` adds voice) |
67
+ | Claude Code | The plugin above; `--scope local` or `--scope project` keeps it to one repo |
68
+ | Codex | `./install.sh --target codex`, or add the repo as a Codex marketplace source |
69
+ | Gate dependencies | `./install.sh --deps` (`--deps=say` adds voice) |
59
70
  | 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
71
  | The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
62
72
 
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.
73
+ - `install.sh` links `skills/` into `~/.codex` and renders `commands/` as
74
+ Codex skills. It installs nothing into `~/.claude`. A second run changes
75
+ nothing.
76
+ - Installed an older `install.sh` into `~/.claude`? `./install.sh
77
+ --uninstall-legacy` removes those links and hooks, or every hook fires twice.
66
78
  - `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
67
79
  `ast-grep`.
68
80
 
@@ -70,7 +82,7 @@ cd agent-sdlc
70
82
 
71
83
  In Claude Code (Codex: `$name` instead of `/name`):
72
84
 
73
- 1. `./install.sh --target claude --deps`
85
+ 1. Install the plugin and `./install.sh --deps`
74
86
  2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
75
87
  3. `claude`, then `/grill <idea>` or `gh issue create …`
76
88
  4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-sdlc
3
- Version: 0.3.0
3
+ Version: 0.3.1
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
@@ -62,22 +62,34 @@ Add `assert is_adult(18)` and `assert not is_adult(17)`, and the gate passes.
62
62
 
63
63
  ## Install
64
64
 
65
+ Claude Code:
66
+
67
+ ```bash
68
+ claude plugin marketplace add PavelGuzenfeld/agent-sdlc
69
+ claude plugin install agent-sdlc@agent-sdlc
70
+ ```
71
+
72
+ Tools the gate needs, and Codex:
73
+
65
74
  ```bash
66
75
  git clone https://github.com/PavelGuzenfeld/agent-sdlc
67
76
  cd agent-sdlc
68
- ./install.sh --target all --deps
77
+ ./install.sh --deps
69
78
  ```
70
79
 
71
80
  | Want | Do |
72
81
  |---|---|
73
- | The whole pack | `./install.sh --target all --deps` (targets: `claude`, `codex`, `all`; `--deps=say` adds voice) |
82
+ | Claude Code | The plugin above; `--scope local` or `--scope project` keeps it to one repo |
83
+ | Codex | `./install.sh --target codex`, or add the repo as a Codex marketplace source |
84
+ | Gate dependencies | `./install.sh --deps` (`--deps=say` adds voice) |
74
85
  | 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
86
  | The gate on a repo | Add `.mutation-gate.toml`, wire the pre-commit hooks |
77
87
 
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.
88
+ - `install.sh` links `skills/` into `~/.codex` and renders `commands/` as
89
+ Codex skills. It installs nothing into `~/.claude`. A second run changes
90
+ nothing.
91
+ - Installed an older `install.sh` into `~/.claude`? `./install.sh
92
+ --uninstall-legacy` removes those links and hooks, or every hook fires twice.
81
93
  - `ast-grep-cli` adds an `sg` shim that can shadow the system `sg`; call
82
94
  `ast-grep`.
83
95
 
@@ -85,7 +97,7 @@ cd agent-sdlc
85
97
 
86
98
  In Claude Code (Codex: `$name` instead of `/name`):
87
99
 
88
- 1. `./install.sh --target claude --deps`
100
+ 1. Install the plugin and `./install.sh --deps`
89
101
  2. In your repo: `touch .mutation-gate.toml && mutation-gate rules sync`
90
102
  3. `claude`, then `/grill <idea>` or `gh issue create …`
91
103
  4. Approve the ticket: `gh issue edit 42 --add-label model:sonnet`
@@ -1,3 +1,3 @@
1
1
  """Diff-scoped mutation gate. Spec: PavelGuzenfeld/dotfiles#9."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.1"
@@ -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
- return _NPM_VERSION_RE.sub(r"\1npm-package-version\2", line)
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 = "-e PYTHONDONTWRITEBYTECODE=1 -e PYTEST_ADDOPTS='-p no:cacheprovider'"
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
@@ -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)
@@ -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