cc-codeconductor 1.1.0 → 1.2.0
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.
- package/README.md +4 -2
- package/dist/core/verification/verification-runner.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1413 -309
- package/dist/library.js +29 -1
- package/dist/validation/schemas.d.ts +97 -26
- package/package.json +1 -1
- package/presets/agy/AGENTS.md +10 -9
- package/presets/agy/hooks.json +2 -2
- package/presets/agy/scripts/invoke-hook.cjs +115 -0
- package/presets/agy/skills/backlog/SKILL.md +40 -70
- package/presets/agy/skills/cc-spec-mutation/SKILL.md +165 -0
- package/presets/agy/skills/cc-tdd-cycle/SKILL.md +3 -0
- package/presets/agy/skills/evaluation/SKILL.md +61 -2
- package/presets/agy/skills/openspec/SKILL.md +49 -19
- package/presets/agy/skills/testing-tdd/SKILL.md +53 -0
- package/presets/agy/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/agy/workflows/cc-api-contract.md +14 -0
- package/presets/agy/workflows/cc-db-migration.md +14 -0
- package/presets/agy/workflows/cc-feature.md +18 -0
- package/presets/agy/workflows/cc-fix.md +14 -0
- package/presets/agy/workflows/cc-iterative.md +14 -0
- package/presets/agy/workflows/cc-openspec.md +14 -0
- package/presets/agy/workflows/cc-scorecard.md +2 -0
- package/presets/agy/workflows/cc-spec-mutation.md +191 -0
- package/presets/agy/workflows/cc-tdd-cycle.md +14 -0
- package/presets/claude/commands/cc/api-contract.md +14 -0
- package/presets/claude/commands/cc/db-migration.md +14 -0
- package/presets/claude/commands/cc/feature.md +18 -0
- package/presets/claude/commands/cc/fix.md +17 -0
- package/presets/claude/commands/cc/iterative.md +14 -0
- package/presets/claude/commands/cc/openspec.md +14 -0
- package/presets/claude/commands/cc/review.md +3 -0
- package/presets/claude/commands/cc/scorecard.md +2 -0
- package/presets/claude/commands/cc/spec-mutation.md +190 -0
- package/presets/claude/commands/cc/tdd-cycle.md +17 -0
- package/presets/claude/settings.json +13 -11
- package/presets/claude/skills/backlog/SKILL.md +40 -70
- package/presets/claude/skills/evaluation/SKILL.md +47 -24
- package/presets/claude/skills/openspec/SKILL.md +46 -38
- package/presets/claude/skills/testing-tdd/SKILL.md +53 -0
- package/presets/claude/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/codex/AGENTS.md +16 -12
- package/presets/codex/skills/backlog/SKILL.md +61 -0
- package/presets/codex/skills/cc-api-contract/SKILL.md +87 -0
- package/presets/codex/skills/cc-backlog/SKILL.md +108 -0
- package/presets/codex/skills/cc-clarify/SKILL.md +36 -0
- package/presets/codex/skills/cc-council/SKILL.md +92 -0
- package/presets/codex/skills/cc-db-migration/SKILL.md +88 -0
- package/presets/codex/skills/cc-explore/SKILL.md +40 -0
- package/presets/codex/skills/cc-feature/SKILL.md +154 -0
- package/presets/codex/skills/cc-fix/SKILL.md +165 -0
- package/presets/codex/skills/cc-handoff/SKILL.md +45 -0
- package/presets/codex/skills/cc-iterative/SKILL.md +150 -0
- package/presets/codex/skills/cc-openspec/SKILL.md +191 -0
- package/presets/codex/skills/cc-pagespeed/SKILL.md +124 -0
- package/presets/codex/skills/cc-prototype/SKILL.md +42 -0
- package/presets/codex/skills/cc-refactor/SKILL.md +163 -0
- package/presets/codex/skills/cc-review/SKILL.md +152 -0
- package/presets/codex/skills/cc-scorecard/SKILL.md +82 -0
- package/presets/codex/skills/cc-security/SKILL.md +182 -0
- package/presets/codex/skills/cc-spec-mutation/SKILL.md +192 -0
- package/presets/codex/skills/cc-tdd-cycle/SKILL.md +266 -0
- package/presets/codex/skills/cc-test-plan/SKILL.md +153 -0
- package/presets/codex/skills/cc-triage/SKILL.md +38 -0
- package/presets/codex/skills/evaluation/SKILL.md +65 -0
- package/presets/codex/skills/openspec/SKILL.md +66 -0
- package/presets/codex/skills/testing-tdd/SKILL.md +53 -0
- package/presets/codex/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/cursor/commands/cc/api-contract.md +14 -0
- package/presets/cursor/commands/cc/db-migration.md +14 -0
- package/presets/cursor/commands/cc/feature.md +18 -0
- package/presets/cursor/commands/cc/fix.md +17 -0
- package/presets/cursor/commands/cc/iterative.md +14 -0
- package/presets/cursor/commands/cc/openspec.md +14 -0
- package/presets/cursor/commands/cc/scorecard.md +2 -0
- package/presets/cursor/commands/cc/spec-mutation.md +190 -0
- package/presets/cursor/commands/cc/tdd-cycle.md +14 -0
- package/presets/cursor/skills/backlog/SKILL.md +40 -70
- package/presets/cursor/skills/evaluation/SKILL.md +61 -4
- package/presets/cursor/skills/openspec/SKILL.md +46 -36
- package/presets/cursor/skills/testing-tdd/SKILL.md +35 -574
- package/presets/cursor/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/gemini/commands/cc/api-contract.toml +82 -0
- package/presets/gemini/commands/cc/ask.toml +54 -0
- package/presets/gemini/commands/cc/backlog.toml +103 -0
- package/presets/gemini/commands/cc/clarify.toml +31 -0
- package/presets/gemini/commands/cc/council.toml +87 -0
- package/presets/gemini/commands/cc/db-migration.toml +83 -0
- package/presets/gemini/commands/cc/explore.toml +35 -0
- package/presets/gemini/commands/cc/feature.toml +153 -0
- package/presets/gemini/commands/cc/fix.toml +163 -0
- package/presets/gemini/commands/cc/handoff.toml +40 -0
- package/presets/gemini/commands/cc/iterative.toml +145 -0
- package/presets/gemini/commands/cc/openspec.toml +186 -0
- package/presets/gemini/commands/cc/pagespeed.toml +119 -0
- package/presets/gemini/commands/cc/prototype.toml +37 -0
- package/presets/gemini/commands/cc/refactor.toml +158 -0
- package/presets/gemini/commands/cc/review.toml +150 -0
- package/presets/gemini/commands/cc/scorecard.toml +77 -0
- package/presets/gemini/commands/cc/security.toml +177 -0
- package/presets/gemini/commands/cc/spec-mutation.toml +187 -0
- package/presets/gemini/commands/cc/tdd-cycle.toml +264 -0
- package/presets/gemini/commands/cc/test-plan.toml +148 -0
- package/presets/gemini/commands/cc/triage.toml +33 -0
- package/presets/opencode/README.md +24 -21
- package/presets/opencode/agents/architect.md +6 -0
- package/presets/opencode/agents/implementer.md +7 -0
- package/presets/opencode/agents/reviewer.md +6 -0
- package/presets/opencode/agents/tester.md +6 -0
- package/presets/opencode/commands/cc-api-contract.md +14 -0
- package/presets/opencode/commands/cc-db-migration.md +14 -0
- package/presets/opencode/commands/cc-feature.md +18 -0
- package/presets/opencode/commands/cc-fix.md +17 -0
- package/presets/opencode/commands/cc-iterative.md +14 -0
- package/presets/opencode/commands/cc-openspec.md +14 -0
- package/presets/opencode/commands/cc-scorecard.md +2 -0
- package/presets/opencode/commands/cc-spec-mutation.md +190 -0
- package/presets/opencode/commands/cc-tdd-cycle.md +14 -0
- package/presets/opencode/opencode.jsonc +1 -1
- package/presets/opencode/prompts/v1.0.0/architect.md +6 -0
- package/presets/opencode/prompts/v1.0.0/implementer.md +7 -0
- package/presets/opencode/prompts/v1.0.0/reviewer.md +6 -0
- package/presets/opencode/prompts/v1.0.0/tester.md +6 -0
- package/presets/opencode/skills/backlog/SKILL.md +40 -70
- package/presets/opencode/skills/evaluation/SKILL.md +61 -2
- package/presets/opencode/skills/openspec/SKILL.md +46 -34
- package/presets/opencode/skills/testing-tdd/SKILL.md +35 -574
- package/presets/opencode/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/shared/__pycache__/mutation_runner.cpython-314.pyc +0 -0
- package/presets/shared/invoke-hook.cjs +115 -0
- package/presets/shared/mutation_runner.py +273 -0
- package/src/presets/manifests/agy.yml +2 -0
- package/src/presets/manifests/claude.yml +3 -0
- package/src/presets/manifests/gemini.yml +15 -0
- package/src/presets/models/agy.yml +24 -24
- package/src/presets/models/claude.yml +10 -10
- package/src/presets/models/codex.yml +10 -10
- package/src/presets/models/cursor.yml +10 -10
- package/src/presets/models/gemini.yml +10 -10
- package/src/presets/models/opencode.yml +10 -10
- package/presets/agy/scripts/post-tool.sh +0 -25
- package/presets/agy/scripts/pre-tool.sh +0 -56
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const { existsSync } = require('node:fs');
|
|
6
|
+
const { join, resolve } = require('node:path');
|
|
7
|
+
|
|
8
|
+
const VALID_EVENTS = ['pre-tool', 'post-tool', 'session-start'];
|
|
9
|
+
const SPAWN_TIMEOUT = 10000;
|
|
10
|
+
|
|
11
|
+
const rawArgs = process.argv.slice(1);
|
|
12
|
+
const args = rawArgs.filter((arg) => {
|
|
13
|
+
if (VALID_EVENTS.includes(arg) || arg.startsWith('-')) return true;
|
|
14
|
+
return false;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const event = args.find((a) => VALID_EVENTS.includes(a)) || 'pre-tool';
|
|
18
|
+
const extra = args.filter((a) => a !== event);
|
|
19
|
+
const isAgy = process.argv.some((a) => a === '--format=agy') || extra.includes('--format=agy');
|
|
20
|
+
|
|
21
|
+
function findProjectRoot() {
|
|
22
|
+
const candidates = [
|
|
23
|
+
process.env.PROJECT_ROOT,
|
|
24
|
+
process.env.WORKSPACE_DIR,
|
|
25
|
+
resolve(__dirname, '..', '..'),
|
|
26
|
+
process.cwd(),
|
|
27
|
+
].filter(Boolean);
|
|
28
|
+
|
|
29
|
+
for (const dir of candidates) {
|
|
30
|
+
if (
|
|
31
|
+
existsSync(join(dir, 'package.json')) ||
|
|
32
|
+
existsSync(join(dir, '.git')) ||
|
|
33
|
+
existsSync(join(dir, '.agents')) ||
|
|
34
|
+
existsSync(join(dir, '.claude'))
|
|
35
|
+
) {
|
|
36
|
+
return dir;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return resolve(__dirname, '..', '..');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const projectRoot = findProjectRoot();
|
|
43
|
+
|
|
44
|
+
function canRun(bin) {
|
|
45
|
+
try {
|
|
46
|
+
const result = spawnSync(bin, ['--version'], {
|
|
47
|
+
encoding: 'utf8',
|
|
48
|
+
windowsHide: true,
|
|
49
|
+
stdio: 'ignore',
|
|
50
|
+
timeout: SPAWN_TIMEOUT,
|
|
51
|
+
});
|
|
52
|
+
return result.status === 0;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function tryRun(bin, runArgs) {
|
|
59
|
+
try {
|
|
60
|
+
const result = spawnSync(bin, runArgs, {
|
|
61
|
+
cwd: projectRoot,
|
|
62
|
+
stdio: 'inherit',
|
|
63
|
+
windowsHide: true,
|
|
64
|
+
env: process.env,
|
|
65
|
+
timeout: SPAWN_TIMEOUT,
|
|
66
|
+
});
|
|
67
|
+
if (result.error) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (result.status === 0) {
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
if (!isAgy && result.status === 2) {
|
|
74
|
+
process.exit(2);
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function fallback() {
|
|
83
|
+
if (isAgy) {
|
|
84
|
+
if (event === 'post-tool' || event === 'session-start') {
|
|
85
|
+
process.stdout.write('{}\n');
|
|
86
|
+
} else {
|
|
87
|
+
process.stdout.write('{"decision":"allow"}\n');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const srcMain = join(projectRoot, 'src', 'cli', 'main.ts');
|
|
95
|
+
if (existsSync(srcMain) && canRun('bun')) {
|
|
96
|
+
tryRun('bun', ['run', srcMain, 'hook', event, ...extra]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const packaged = join(projectRoot, 'node_modules', 'cc-codeconductor', 'dist', 'index.js');
|
|
100
|
+
if (existsSync(packaged)) {
|
|
101
|
+
tryRun(process.execPath, [packaged, 'hook', event, ...extra]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const localDist = join(projectRoot, 'dist', 'index.js');
|
|
105
|
+
if (existsSync(localDist)) {
|
|
106
|
+
tryRun(process.execPath, [localDist, 'hook', event, ...extra]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
tryRun('npx', ['--no-install', 'cc-codeconductor', 'hook', event, ...extra]);
|
|
110
|
+
} catch {
|
|
111
|
+
// Ignore errors in runner attempts
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fallback();
|
|
115
|
+
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Deterministic mutation-testing runner (stdlib only).
|
|
3
|
+
|
|
4
|
+
Applies single syntactic mutations over comparison operators in a Python
|
|
5
|
+
production file and re-runs the test suite per mutant. If a mutant survives
|
|
6
|
+
(tests still pass), the runner writes a handoff for the `tdd_craftsman` agent
|
|
7
|
+
(`specs/handover.md`, appended to `specs/implementation-summary.md`, optional
|
|
8
|
+
Engram/MCP memory hook) and exits with code 2 (HANDS_OFF). If every mutant is
|
|
9
|
+
killed, it exits 0 (PASS). The original source is always restored.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python3 mutation_runner.py --target src/billing.py \
|
|
13
|
+
--test-command "pytest tests/test_billing.py" [--spec-folder specs]
|
|
14
|
+
"""
|
|
15
|
+
import argparse
|
|
16
|
+
import ast
|
|
17
|
+
import difflib
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from dataclasses import asdict, dataclass
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Dict, List, Optional, Tuple
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class MutationRecord:
|
|
30
|
+
mutant_id: str
|
|
31
|
+
target_file: str
|
|
32
|
+
line_number: int
|
|
33
|
+
original_op: str
|
|
34
|
+
mutated_op: str
|
|
35
|
+
diff: str
|
|
36
|
+
survived: bool
|
|
37
|
+
details: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DeterministicOperatorMutator(ast.NodeTransformer):
|
|
41
|
+
"""Applies one deterministic syntactic mutation over a comparison operator."""
|
|
42
|
+
|
|
43
|
+
OP_MAP = {
|
|
44
|
+
ast.Gt: ast.LtE, # > -> <=
|
|
45
|
+
ast.Lt: ast.GtE, # < -> >=
|
|
46
|
+
ast.GtE: ast.Lt, # >= -> <
|
|
47
|
+
ast.LtE: ast.Gt, # <= -> >
|
|
48
|
+
ast.Eq: ast.NotEq, # == -> !=
|
|
49
|
+
ast.NotEq: ast.Eq, # != -> ==
|
|
50
|
+
ast.Is: ast.IsNot, # is -> is not
|
|
51
|
+
ast.IsNot: ast.Is, # is not -> is
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def __init__(self, target_index: int):
|
|
55
|
+
super().__init__()
|
|
56
|
+
self.target_index = target_index
|
|
57
|
+
self.current_index = 0
|
|
58
|
+
self.applied_mutation: Optional[Tuple[int, str, str]] = None
|
|
59
|
+
|
|
60
|
+
def visit_Compare(self, node: ast.Compare) -> ast.AST:
|
|
61
|
+
mutated_ops = []
|
|
62
|
+
modified = False
|
|
63
|
+
|
|
64
|
+
for op in node.ops:
|
|
65
|
+
op_type = type(op)
|
|
66
|
+
if op_type in self.OP_MAP:
|
|
67
|
+
if self.current_index == self.target_index:
|
|
68
|
+
new_op = self.OP_MAP[op_type]()
|
|
69
|
+
mutated_ops.append(new_op)
|
|
70
|
+
modified = True
|
|
71
|
+
self.applied_mutation = (
|
|
72
|
+
node.lineno,
|
|
73
|
+
op_type.__name__,
|
|
74
|
+
type(new_op).__name__,
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
mutated_ops.append(op)
|
|
78
|
+
self.current_index += 1
|
|
79
|
+
else:
|
|
80
|
+
mutated_ops.append(op)
|
|
81
|
+
|
|
82
|
+
if modified:
|
|
83
|
+
node.ops = mutated_ops
|
|
84
|
+
return node
|
|
85
|
+
return self.generic_visit(node)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MutationEngine:
|
|
89
|
+
def __init__(self, target_file: str, test_command: str, spec_folder: str = "specs"):
|
|
90
|
+
self.target_file = Path(target_file).resolve()
|
|
91
|
+
self.test_command = test_command
|
|
92
|
+
self.spec_folder = Path(spec_folder)
|
|
93
|
+
self.original_source = self.target_file.read_text(encoding="utf-8")
|
|
94
|
+
self.spec_folder.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
|
|
96
|
+
def _count_mutation_points(self) -> int:
|
|
97
|
+
tree = ast.parse(self.original_source)
|
|
98
|
+
count = 0
|
|
99
|
+
for node in ast.walk(tree):
|
|
100
|
+
if isinstance(node, ast.Compare):
|
|
101
|
+
for op in node.ops:
|
|
102
|
+
if type(op) in DeterministicOperatorMutator.OP_MAP:
|
|
103
|
+
count += 1
|
|
104
|
+
return count
|
|
105
|
+
|
|
106
|
+
def _clear_bytecode_cache(self) -> None:
|
|
107
|
+
"""Removes cached bytecode for the target so mutants are not shadowed
|
|
108
|
+
by a stale .pyc with an equal-mtimestamp source."""
|
|
109
|
+
cache_dir = self.target_file.parent / "__pycache__"
|
|
110
|
+
if cache_dir.is_dir():
|
|
111
|
+
for pyc in cache_dir.glob(f"{self.target_file.stem}.*.pyc"):
|
|
112
|
+
pyc.unlink(missing_ok=True)
|
|
113
|
+
|
|
114
|
+
def _run_test_suite(self) -> bool:
|
|
115
|
+
"""Runs the test suite. Returns True if every test passes."""
|
|
116
|
+
self._clear_bytecode_cache()
|
|
117
|
+
env = {**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}
|
|
118
|
+
result = subprocess.run(
|
|
119
|
+
self.test_command,
|
|
120
|
+
shell=True,
|
|
121
|
+
stdout=subprocess.PIPE,
|
|
122
|
+
stderr=subprocess.PIPE,
|
|
123
|
+
text=True,
|
|
124
|
+
env=env,
|
|
125
|
+
)
|
|
126
|
+
return result.returncode == 0
|
|
127
|
+
|
|
128
|
+
def _create_diff(self, mutated_source: str) -> str:
|
|
129
|
+
return "\n".join(
|
|
130
|
+
difflib.unified_diff(
|
|
131
|
+
self.original_source.splitlines(),
|
|
132
|
+
mutated_source.splitlines(),
|
|
133
|
+
fromfile=f"a/{self.target_file.name}",
|
|
134
|
+
tofile=f"b/{self.target_file.name}",
|
|
135
|
+
lineterm="",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def _persist_to_engram_or_markdown(self, record: MutationRecord) -> None:
|
|
140
|
+
"""Documents the handoff in handover.md and implementation-summary.md."""
|
|
141
|
+
markdown_summary = self.spec_folder / "implementation-summary.md"
|
|
142
|
+
handover_file = self.spec_folder / "handover.md"
|
|
143
|
+
|
|
144
|
+
payload_md = f"""## Mutation Gate: Survivor Detected (TDD Gap)
|
|
145
|
+
- **Target File**: `{record.target_file}`
|
|
146
|
+
- **Line**: {record.line_number}
|
|
147
|
+
- **Mutant ID**: `{record.mutant_id}`
|
|
148
|
+
- **Operator Change**: `{record.original_op}` -> `{record.mutated_op}`
|
|
149
|
+
- **Status**: SURVIVED (Scorecard Real Coverage: 0)
|
|
150
|
+
|
|
151
|
+
### Reproduction Diff
|
|
152
|
+
```diff
|
|
153
|
+
{record.diff}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Action Required by `tdd_craftsman`
|
|
157
|
+
|
|
158
|
+
1. Do NOT touch production implementation.
|
|
159
|
+
2. Write a minimal failing test (TDD Law 1 & 2) that asserts this branch condition.
|
|
160
|
+
3. Verify the test turns red before re-verifying production code.
|
|
161
|
+
"""
|
|
162
|
+
# Handover update for continuity
|
|
163
|
+
handover_file.write_text(payload_md, encoding="utf-8")
|
|
164
|
+
|
|
165
|
+
with markdown_summary.open("a", encoding="utf-8") as f:
|
|
166
|
+
f.write(f"\n\n{payload_md}")
|
|
167
|
+
|
|
168
|
+
# Optional Engram / MCP memory integration
|
|
169
|
+
self._notify_engram(record)
|
|
170
|
+
|
|
171
|
+
def _notify_engram(self, record: MutationRecord) -> None:
|
|
172
|
+
"""Invokes the local Engram/Memory Engine hook or CLI if present."""
|
|
173
|
+
if shutil.which("engram"):
|
|
174
|
+
try:
|
|
175
|
+
subprocess.run(
|
|
176
|
+
[
|
|
177
|
+
"engram",
|
|
178
|
+
"memory:save",
|
|
179
|
+
"--tag",
|
|
180
|
+
"mutation_testing",
|
|
181
|
+
"--title",
|
|
182
|
+
f"Survivor {record.mutant_id}",
|
|
183
|
+
"--body",
|
|
184
|
+
json.dumps(asdict(record)),
|
|
185
|
+
],
|
|
186
|
+
check=False,
|
|
187
|
+
stdout=subprocess.DEVNULL,
|
|
188
|
+
stderr=subprocess.DEVNULL,
|
|
189
|
+
)
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
def run(self) -> Dict[str, object]:
|
|
194
|
+
# 1. Pre-flight check: tests must pass on the clean base code
|
|
195
|
+
if not self._run_test_suite():
|
|
196
|
+
raise RuntimeError("The test suite is already failing on unmutated base code.")
|
|
197
|
+
|
|
198
|
+
total_points = self._count_mutation_points()
|
|
199
|
+
survivors: List[MutationRecord] = []
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
for idx in range(total_points):
|
|
203
|
+
mutator = DeterministicOperatorMutator(target_index=idx)
|
|
204
|
+
tree = ast.parse(self.original_source)
|
|
205
|
+
mutated_tree = mutator.visit(tree)
|
|
206
|
+
ast.fix_missing_locations(mutated_tree)
|
|
207
|
+
|
|
208
|
+
mutated_source = ast.unparse(mutated_tree)
|
|
209
|
+
diff = self._create_diff(mutated_source)
|
|
210
|
+
|
|
211
|
+
# Apply mutant to disk
|
|
212
|
+
self.target_file.write_text(mutated_source, encoding="utf-8")
|
|
213
|
+
|
|
214
|
+
# Run tests against the mutant
|
|
215
|
+
passed = self._run_test_suite()
|
|
216
|
+
|
|
217
|
+
if passed:
|
|
218
|
+
# The mutant survived: tests are blind to this change
|
|
219
|
+
lineno, orig, mut = mutator.applied_mutation or (0, "Unknown", "Unknown")
|
|
220
|
+
survivor = MutationRecord(
|
|
221
|
+
mutant_id=f"MUT_{idx:03d}_L{lineno}",
|
|
222
|
+
target_file=str(self.target_file),
|
|
223
|
+
line_number=lineno,
|
|
224
|
+
original_op=orig,
|
|
225
|
+
mutated_op=mut,
|
|
226
|
+
diff=diff,
|
|
227
|
+
survived=True,
|
|
228
|
+
details="Tests passed with inverted comparison logic. Missing explicit assert.",
|
|
229
|
+
)
|
|
230
|
+
survivors.append(survivor)
|
|
231
|
+
|
|
232
|
+
# Safe Fail-Fast protocol: immediate handoff to the craftsman
|
|
233
|
+
self._persist_to_engram_or_markdown(survivor)
|
|
234
|
+
return {
|
|
235
|
+
"status": "HANDS_OFF_TRIGGERED",
|
|
236
|
+
"target_agent": "tdd_craftsman",
|
|
237
|
+
"surviving_mutant": asdict(survivor),
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
finally:
|
|
241
|
+
# Unconditional deterministic rollback: restore original source
|
|
242
|
+
self.target_file.write_text(self.original_source, encoding="utf-8")
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
"status": "PASS",
|
|
246
|
+
"total_mutants_killed": total_points,
|
|
247
|
+
"survivors_count": 0,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
252
|
+
parser = argparse.ArgumentParser(description="Deterministic stdlib mutation runner.")
|
|
253
|
+
parser.add_argument("--target", required=True, help="Python production file to mutate.")
|
|
254
|
+
parser.add_argument("--test-command", required=True, help="Shell command that runs the test suite.")
|
|
255
|
+
parser.add_argument("--spec-folder", default="specs", help="Folder for handoff artifacts.")
|
|
256
|
+
args = parser.parse_args(argv)
|
|
257
|
+
|
|
258
|
+
engine = MutationEngine(
|
|
259
|
+
target_file=args.target,
|
|
260
|
+
test_command=args.test_command,
|
|
261
|
+
spec_folder=args.spec_folder,
|
|
262
|
+
)
|
|
263
|
+
outcome = engine.run()
|
|
264
|
+
print(json.dumps(outcome, indent=2))
|
|
265
|
+
|
|
266
|
+
if outcome["status"] == "HANDS_OFF_TRIGGERED":
|
|
267
|
+
# Exit code 2 tells the orchestrator (craftsman_lead) to reroute to tdd_craftsman
|
|
268
|
+
return 2
|
|
269
|
+
return 0
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
sys.exit(main())
|
|
@@ -19,12 +19,14 @@ entries:
|
|
|
19
19
|
- src: agy/hooks.json
|
|
20
20
|
dest: .agents/hooks.json
|
|
21
21
|
strategy: overwrite
|
|
22
|
+
globalStrategy: skip
|
|
22
23
|
- src: agy/mcp_config.json
|
|
23
24
|
dest: .agents/mcp_config.json
|
|
24
25
|
strategy: overwrite
|
|
25
26
|
- src: agy/scripts
|
|
26
27
|
dest: .agents/scripts
|
|
27
28
|
strategy: overwrite
|
|
29
|
+
globalStrategy: skip
|
|
28
30
|
- src: opencode/skills
|
|
29
31
|
dest: .agents/skills
|
|
30
32
|
strategy: overwrite
|
|
@@ -9,6 +9,9 @@ entries:
|
|
|
9
9
|
dest: .claude/settings.json
|
|
10
10
|
strategy: overwrite
|
|
11
11
|
globalStrategy: merge-json
|
|
12
|
+
- src: shared/invoke-hook.cjs
|
|
13
|
+
dest: .claude/hooks/invoke-hook.cjs
|
|
14
|
+
strategy: overwrite
|
|
12
15
|
- src: claude/claude.json
|
|
13
16
|
dest: .claude.json
|
|
14
17
|
strategy: merge-json
|
|
@@ -8,3 +8,18 @@ entries:
|
|
|
8
8
|
dest: .gemini/prompts/v1.0.0
|
|
9
9
|
strategy: overwrite
|
|
10
10
|
template: true
|
|
11
|
+
- src: gemini/commands
|
|
12
|
+
dest: .gemini/commands
|
|
13
|
+
strategy: overwrite
|
|
14
|
+
- src: claude/skills/openspec
|
|
15
|
+
dest: .gemini/skills/openspec
|
|
16
|
+
strategy: overwrite
|
|
17
|
+
- src: claude/skills/backlog
|
|
18
|
+
dest: .gemini/skills/backlog
|
|
19
|
+
strategy: overwrite
|
|
20
|
+
- src: claude/skills/evaluation
|
|
21
|
+
dest: .gemini/skills/evaluation
|
|
22
|
+
strategy: overwrite
|
|
23
|
+
- src: cursor/skills/testing-tdd
|
|
24
|
+
dest: .gemini/skills/testing-tdd
|
|
25
|
+
strategy: overwrite
|
|
@@ -8,15 +8,15 @@ agents:
|
|
|
8
8
|
gemini: gemini-3.1-pro-preview
|
|
9
9
|
cursor: claude-opus-5-thinking-high
|
|
10
10
|
grok: cursor-grok-4.6-high-fast
|
|
11
|
-
agy: gemini-3.1-pro
|
|
11
|
+
agy: gemini-3.1-pro
|
|
12
12
|
implementer:
|
|
13
13
|
claude: claude-sonnet-5
|
|
14
|
-
opencode: opencode-go/mimo-v2.5
|
|
14
|
+
opencode: opencode-go/mimo-v2.5
|
|
15
15
|
codex: gpt-5.6-terra
|
|
16
16
|
gemini: gemini-3.7-flash
|
|
17
17
|
cursor: composer-2.5-fast
|
|
18
18
|
grok: cursor-grok-4.6-high-fast
|
|
19
|
-
agy:
|
|
19
|
+
agy: claude-sonnet-4.6-thinking
|
|
20
20
|
tester:
|
|
21
21
|
claude: claude-sonnet-5
|
|
22
22
|
opencode: opencode-go/minimax-m3
|
|
@@ -24,7 +24,7 @@ agents:
|
|
|
24
24
|
gemini: gemini-3.7-flash
|
|
25
25
|
cursor: composer-2.5-fast
|
|
26
26
|
grok: cursor-grok-4.6-high-fast
|
|
27
|
-
agy:
|
|
27
|
+
agy: claude-sonnet-4.6-thinking
|
|
28
28
|
orchestrator:
|
|
29
29
|
claude: claude-sonnet-5
|
|
30
30
|
opencode: opencode-go/qwen3.7-plus
|
|
@@ -32,39 +32,39 @@ agents:
|
|
|
32
32
|
gemini: gemini-3.7-flash
|
|
33
33
|
cursor: composer-2.5
|
|
34
34
|
grok: cursor-grok-4.6-high-fast
|
|
35
|
-
agy:
|
|
35
|
+
agy: claude-sonnet-4.6-thinking
|
|
36
36
|
reviewer:
|
|
37
37
|
claude: claude-opus-5
|
|
38
|
-
opencode: opencode-go/qwen3.
|
|
38
|
+
opencode: opencode-go/qwen3.8-max
|
|
39
39
|
codex: gpt-5.6-sol
|
|
40
40
|
gemini: gemini-3.1-pro-preview
|
|
41
41
|
cursor: claude-sonnet-5-thinking-high
|
|
42
42
|
grok: cursor-grok-4.6-high-fast
|
|
43
|
-
agy: gemini-3.1-pro
|
|
43
|
+
agy: gemini-3.1-pro
|
|
44
44
|
docs:
|
|
45
45
|
claude: claude-haiku-4-5-20251001
|
|
46
|
-
opencode: opencode-go/
|
|
46
|
+
opencode: opencode-go/hy3
|
|
47
47
|
codex: gpt-5.6-luna
|
|
48
48
|
gemini: gemini-3.7-flash
|
|
49
49
|
cursor: claude-4.5-haiku-thinking
|
|
50
50
|
grok: cursor-grok-4.6-high-fast
|
|
51
|
-
agy: gemini-3.
|
|
51
|
+
agy: gemini-3.8-flash
|
|
52
52
|
task-coach:
|
|
53
53
|
claude: claude-haiku-4-5-20251001
|
|
54
|
-
opencode: opencode-go/
|
|
54
|
+
opencode: opencode-go/gpt-5.6-luna
|
|
55
55
|
codex: gpt-5.6-luna
|
|
56
56
|
gemini: gemini-3.7-flash
|
|
57
57
|
cursor: claude-4.5-haiku-thinking
|
|
58
58
|
grok: cursor-grok-4.6-high-fast
|
|
59
|
-
agy: gemini-3.
|
|
59
|
+
agy: gemini-3.8-flash
|
|
60
60
|
repo-explorer:
|
|
61
61
|
claude: claude-haiku-4-5-20251001
|
|
62
|
-
opencode: opencode-go/
|
|
62
|
+
opencode: opencode-go/longcat-2.0
|
|
63
63
|
codex: gpt-5.6-luna
|
|
64
64
|
gemini: gemini-3.7-flash
|
|
65
65
|
cursor: composer-2.5-fast
|
|
66
66
|
grok: cursor-grok-4.6-high-fast
|
|
67
|
-
agy: gemini-3.
|
|
67
|
+
agy: gemini-3.8-flash
|
|
68
68
|
goal-planner:
|
|
69
69
|
claude: claude-haiku-4-5-20251001
|
|
70
70
|
opencode: opencode-go/deepseek-v4-flash
|
|
@@ -72,47 +72,47 @@ agents:
|
|
|
72
72
|
gemini: gemini-3.7-flash
|
|
73
73
|
cursor: claude-4.5-haiku-thinking
|
|
74
74
|
grok: cursor-grok-4.6-high-fast
|
|
75
|
-
agy: gemini-3.
|
|
75
|
+
agy: gemini-3.8-flash
|
|
76
76
|
complexity-auditor:
|
|
77
77
|
claude: claude-sonnet-5
|
|
78
|
-
opencode: opencode-go/
|
|
78
|
+
opencode: opencode-go/glm-5.3
|
|
79
79
|
codex: gpt-5.6-terra
|
|
80
80
|
gemini: gemini-3.7-flash
|
|
81
81
|
cursor: claude-sonnet-5-thinking-high
|
|
82
82
|
grok: cursor-grok-4.6-high-fast
|
|
83
|
-
agy:
|
|
83
|
+
agy: claude-sonnet-4.6-thinking
|
|
84
84
|
security-reviewer:
|
|
85
85
|
claude: claude-opus-5
|
|
86
|
-
opencode: opencode-go/
|
|
86
|
+
opencode: opencode-go/kimi-k3
|
|
87
87
|
codex: gpt-5.6-sol
|
|
88
88
|
gemini: gemini-3.1-pro-preview
|
|
89
89
|
cursor: claude-opus-5-thinking-high
|
|
90
90
|
grok: cursor-grok-4.6-high-fast
|
|
91
|
-
agy: gemini-3.1-pro
|
|
91
|
+
agy: gemini-3.1-pro
|
|
92
92
|
contract-builder:
|
|
93
93
|
claude: claude-opus-5
|
|
94
|
-
opencode: opencode-go/qwen3.
|
|
94
|
+
opencode: opencode-go/qwen3.8-max
|
|
95
95
|
codex: gpt-5.6-sol
|
|
96
96
|
gemini: gemini-3.1-pro-preview
|
|
97
97
|
cursor: claude-sonnet-5-thinking-high
|
|
98
98
|
grok: cursor-grok-4.6-high-fast
|
|
99
|
-
agy: gemini-3.1-pro
|
|
99
|
+
agy: gemini-3.1-pro
|
|
100
100
|
planner:
|
|
101
101
|
claude: claude-haiku-4-5-20251001
|
|
102
|
-
opencode: opencode-go/
|
|
102
|
+
opencode: opencode-go/gpt-5.6-luna
|
|
103
103
|
codex: gpt-5.6-luna
|
|
104
104
|
gemini: gemini-3.7-flash
|
|
105
105
|
cursor: claude-4.5-haiku-thinking
|
|
106
106
|
grok: cursor-grok-4.6-high-fast
|
|
107
|
-
agy: gemini-3.
|
|
107
|
+
agy: gemini-3.8-flash
|
|
108
108
|
devil:
|
|
109
109
|
claude: claude-opus-5
|
|
110
|
-
opencode: opencode-go/
|
|
110
|
+
opencode: opencode-go/glm-5.3
|
|
111
111
|
codex: gpt-5.6-sol
|
|
112
112
|
gemini: gemini-3.1-pro-preview
|
|
113
113
|
cursor: claude-opus-5-thinking-high
|
|
114
114
|
grok: cursor-grok-4.6-high-fast
|
|
115
|
-
agy: gemini-3.1-pro
|
|
115
|
+
agy: gemini-3.1-pro
|
|
116
116
|
tools:
|
|
117
117
|
Read:
|
|
118
118
|
agy: view_file
|
|
@@ -12,7 +12,7 @@ agents:
|
|
|
12
12
|
grok: cursor-grok-4.6-high-fast
|
|
13
13
|
implementer:
|
|
14
14
|
claude: claude-sonnet-5
|
|
15
|
-
opencode: opencode-go/mimo-v2.5
|
|
15
|
+
opencode: opencode-go/mimo-v2.5
|
|
16
16
|
codex: gpt-5.6-terra
|
|
17
17
|
gemini: gemini-3.7-flash
|
|
18
18
|
cursor: composer-2.5-fast
|
|
@@ -33,28 +33,28 @@ agents:
|
|
|
33
33
|
grok: cursor-grok-4.6-high-fast
|
|
34
34
|
reviewer:
|
|
35
35
|
claude: claude-opus-5
|
|
36
|
-
opencode: opencode-go/qwen3.
|
|
36
|
+
opencode: opencode-go/qwen3.8-max
|
|
37
37
|
codex: gpt-5.6-sol
|
|
38
38
|
gemini: gemini-3.1-pro-preview
|
|
39
39
|
cursor: claude-sonnet-5-thinking-high
|
|
40
40
|
grok: cursor-grok-4.6-high-fast
|
|
41
41
|
docs:
|
|
42
42
|
claude: claude-haiku-4-5-20251001
|
|
43
|
-
opencode: opencode-go/
|
|
43
|
+
opencode: opencode-go/hy3
|
|
44
44
|
codex: gpt-5.6-luna
|
|
45
45
|
gemini: gemini-3.7-flash
|
|
46
46
|
cursor: claude-4.5-haiku-thinking
|
|
47
47
|
grok: cursor-grok-4.6-high-fast
|
|
48
48
|
task-coach:
|
|
49
49
|
claude: claude-haiku-4-5-20251001
|
|
50
|
-
opencode: opencode-go/
|
|
50
|
+
opencode: opencode-go/gpt-5.6-luna
|
|
51
51
|
codex: gpt-5.6-luna
|
|
52
52
|
gemini: gemini-3.7-flash
|
|
53
53
|
cursor: claude-4.5-haiku-thinking
|
|
54
54
|
grok: cursor-grok-4.6-high-fast
|
|
55
55
|
repo-explorer:
|
|
56
56
|
claude: claude-haiku-4-5-20251001
|
|
57
|
-
opencode: opencode-go/
|
|
57
|
+
opencode: opencode-go/longcat-2.0
|
|
58
58
|
codex: gpt-5.6-luna
|
|
59
59
|
gemini: gemini-3.7-flash
|
|
60
60
|
cursor: composer-2.5-fast
|
|
@@ -68,35 +68,35 @@ agents:
|
|
|
68
68
|
grok: cursor-grok-4.6-high-fast
|
|
69
69
|
complexity-auditor:
|
|
70
70
|
claude: claude-sonnet-5
|
|
71
|
-
opencode: opencode-go/
|
|
71
|
+
opencode: opencode-go/glm-5.3
|
|
72
72
|
codex: gpt-5.6-terra
|
|
73
73
|
gemini: gemini-3.7-flash
|
|
74
74
|
cursor: claude-sonnet-5-thinking-high
|
|
75
75
|
grok: cursor-grok-4.6-high-fast
|
|
76
76
|
security-reviewer:
|
|
77
77
|
claude: claude-opus-5
|
|
78
|
-
opencode: opencode-go/
|
|
78
|
+
opencode: opencode-go/kimi-k3
|
|
79
79
|
codex: gpt-5.6-sol
|
|
80
80
|
gemini: gemini-3.1-pro-preview
|
|
81
81
|
cursor: claude-opus-5-thinking-high
|
|
82
82
|
grok: cursor-grok-4.6-high-fast
|
|
83
83
|
contract-builder:
|
|
84
84
|
claude: claude-opus-5
|
|
85
|
-
opencode: opencode-go/qwen3.
|
|
85
|
+
opencode: opencode-go/qwen3.8-max
|
|
86
86
|
codex: gpt-5.6-sol
|
|
87
87
|
gemini: gemini-3.1-pro-preview
|
|
88
88
|
cursor: claude-sonnet-5-thinking-high
|
|
89
89
|
grok: cursor-grok-4.6-high-fast
|
|
90
90
|
planner:
|
|
91
91
|
claude: claude-haiku-4-5-20251001
|
|
92
|
-
opencode: opencode-go/
|
|
92
|
+
opencode: opencode-go/gpt-5.6-luna
|
|
93
93
|
codex: gpt-5.6-luna
|
|
94
94
|
gemini: gemini-3.7-flash
|
|
95
95
|
cursor: claude-4.5-haiku-thinking
|
|
96
96
|
grok: cursor-grok-4.6-high-fast
|
|
97
97
|
devil:
|
|
98
98
|
claude: claude-opus-5
|
|
99
|
-
opencode: opencode-go/
|
|
99
|
+
opencode: opencode-go/glm-5.3
|
|
100
100
|
codex: gpt-5.6-sol
|
|
101
101
|
gemini: gemini-3.1-pro-preview
|
|
102
102
|
cursor: claude-opus-5-thinking-high
|