swarm-orchestrator 7.0.0-alpha.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.
Files changed (174) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +199 -0
  3. package/agents/reviewer.agent.md +50 -0
  4. package/agents/worker.agent.md +41 -0
  5. package/config/default-agents.yaml +56 -0
  6. package/config/pm-agent.yaml +36 -0
  7. package/config/quality-gates.yaml +88 -0
  8. package/config/repair-agent.yaml +34 -0
  9. package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
  10. package/config/semgrep-rules/exception-swallowing.yaml +13 -0
  11. package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
  12. package/config/semgrep-rules/mock-mutation.yaml +6 -0
  13. package/config/semgrep-rules/test-modification.yaml +6 -0
  14. package/config/user-agents.yaml +23 -0
  15. package/dist/src/adapters/adapter-factory.js +29 -0
  16. package/dist/src/adapters/agent-adapter.js +32 -0
  17. package/dist/src/adapters/claude-code-adapter.js +126 -0
  18. package/dist/src/adapters/claude-code-teams.js +119 -0
  19. package/dist/src/adapters/codex-adapter.js +118 -0
  20. package/dist/src/adapters/copilot-adapter.js +224 -0
  21. package/dist/src/adapters/fatal-error-classifier.js +88 -0
  22. package/dist/src/adapters/index.js +44 -0
  23. package/dist/src/adapters/persistent-session.js +204 -0
  24. package/dist/src/adapters/process-supervisor.js +199 -0
  25. package/dist/src/agents-exporter.js +398 -0
  26. package/dist/src/analytics-log.js +161 -0
  27. package/dist/src/baseline-scanner.js +65 -0
  28. package/dist/src/bootstrap-evidence.js +88 -0
  29. package/dist/src/bootstrap-orchestrator.js +205 -0
  30. package/dist/src/bootstrap-types.js +5 -0
  31. package/dist/src/branch-merger.js +427 -0
  32. package/dist/src/cli/attest-handlers.js +44 -0
  33. package/dist/src/cli/cost-prompt.js +51 -0
  34. package/dist/src/cli/demo-handlers.js +274 -0
  35. package/dist/src/cli/flags.js +152 -0
  36. package/dist/src/cli/index.js +57 -0
  37. package/dist/src/cli/live-status.js +186 -0
  38. package/dist/src/cli/misc-handlers.js +242 -0
  39. package/dist/src/cli/plan-handlers.js +350 -0
  40. package/dist/src/cli/share-handlers.js +163 -0
  41. package/dist/src/cli/status-handlers.js +457 -0
  42. package/dist/src/cli/swarm-handlers.js +638 -0
  43. package/dist/src/cli/usage.js +72 -0
  44. package/dist/src/cli.js +217 -0
  45. package/dist/src/commit-pattern-detector.js +143 -0
  46. package/dist/src/commit-quality-analyzer.js +38 -0
  47. package/dist/src/config-loader.js +339 -0
  48. package/dist/src/context-broker.js +358 -0
  49. package/dist/src/copilot-cli-wrapper.js +258 -0
  50. package/dist/src/cost-estimator.js +241 -0
  51. package/dist/src/defaults.js +18 -0
  52. package/dist/src/demo-mode.js +180 -0
  53. package/dist/src/deployment-handler.js +78 -0
  54. package/dist/src/deployment-manager.js +228 -0
  55. package/dist/src/execution-queue.js +196 -0
  56. package/dist/src/external-tool-manager.js +184 -0
  57. package/dist/src/gate-prompt-builder.js +117 -0
  58. package/dist/src/gate-remediation.js +173 -0
  59. package/dist/src/github-issues-ingester.js +152 -0
  60. package/dist/src/github-mcp-integrator.js +164 -0
  61. package/dist/src/hook-generator.js +229 -0
  62. package/dist/src/index.js +48 -0
  63. package/dist/src/knowledge-base.js +300 -0
  64. package/dist/src/logger.js +111 -0
  65. package/dist/src/meta-analyzer.js +208 -0
  66. package/dist/src/metrics-collector.js +209 -0
  67. package/dist/src/metrics-types.js +5 -0
  68. package/dist/src/multi-repo-coordinator.js +179 -0
  69. package/dist/src/orchestrator/async-meta-analysis.js +84 -0
  70. package/dist/src/orchestrator/fatal-run-error.js +43 -0
  71. package/dist/src/orchestrator/final-gates-remediation.js +258 -0
  72. package/dist/src/orchestrator/git-state-utils.js +119 -0
  73. package/dist/src/orchestrator/pause-controller.js +55 -0
  74. package/dist/src/orchestrator/replan-runner.js +258 -0
  75. package/dist/src/orchestrator/step-executor.js +391 -0
  76. package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
  77. package/dist/src/owasp-mapper.js +153 -0
  78. package/dist/src/owasp-report-renderer.js +39 -0
  79. package/dist/src/plan-files.js +142 -0
  80. package/dist/src/plan-generator.js +1207 -0
  81. package/dist/src/pm-agent.js +296 -0
  82. package/dist/src/post-run-reporter.js +203 -0
  83. package/dist/src/pr-automation.js +103 -0
  84. package/dist/src/pr-manager.js +318 -0
  85. package/dist/src/presenter/index.js +209 -0
  86. package/dist/src/prompt-builder.js +173 -0
  87. package/dist/src/quality-gates/config-loader.js +196 -0
  88. package/dist/src/quality-gates/default-config.js +243 -0
  89. package/dist/src/quality-gates/file-utils.js +152 -0
  90. package/dist/src/quality-gates/gate-runner.js +171 -0
  91. package/dist/src/quality-gates/gates/accessibility.js +355 -0
  92. package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
  93. package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
  94. package/dist/src/quality-gates/gates/readme-claims.js +62 -0
  95. package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
  96. package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
  97. package/dist/src/quality-gates/gates/test-coverage.js +240 -0
  98. package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
  99. package/dist/src/quality-gates/gates/test-isolation.js +74 -0
  100. package/dist/src/quality-gates/index.js +17 -0
  101. package/dist/src/quality-gates/registry.js +205 -0
  102. package/dist/src/quality-gates/types.js +2 -0
  103. package/dist/src/quick-fix-mode.js +302 -0
  104. package/dist/src/recipe-loader.js +122 -0
  105. package/dist/src/repair-agent.js +396 -0
  106. package/dist/src/repo-analyzer.js +342 -0
  107. package/dist/src/report-generator.js +146 -0
  108. package/dist/src/report-renderer.js +90 -0
  109. package/dist/src/requirement-filter.js +52 -0
  110. package/dist/src/sarif-formatter.js +140 -0
  111. package/dist/src/scheduling/dependency-analyzer.js +178 -0
  112. package/dist/src/scheduling/work-stealing-queue.js +123 -0
  113. package/dist/src/secret-redactor.js +90 -0
  114. package/dist/src/session-executor.js +587 -0
  115. package/dist/src/session-manager.js +221 -0
  116. package/dist/src/share/transcript-verification.js +128 -0
  117. package/dist/src/share-parser.js +477 -0
  118. package/dist/src/spinner.js +146 -0
  119. package/dist/src/step-runner.js +365 -0
  120. package/dist/src/swarm-orchestrator.js +686 -0
  121. package/dist/src/task-classifier.js +134 -0
  122. package/dist/src/test-command-discovery.js +123 -0
  123. package/dist/src/text-similarity.js +24 -0
  124. package/dist/src/tier-maps.js +158 -0
  125. package/dist/src/types.js +5 -0
  126. package/dist/src/url-shortener.js +135 -0
  127. package/dist/src/verification/attestation.js +166 -0
  128. package/dist/src/verification/cheat-detector.js +240 -0
  129. package/dist/src/verification/command-runner.js +70 -0
  130. package/dist/src/verification/composite-score.js +157 -0
  131. package/dist/src/verification/cosign-attestation.js +137 -0
  132. package/dist/src/verification/diff-analysis.js +89 -0
  133. package/dist/src/verification/differential-gate.js +173 -0
  134. package/dist/src/verification/index.js +41 -0
  135. package/dist/src/verification/mutation-gate.js +242 -0
  136. package/dist/src/verification/property-gate.js +201 -0
  137. package/dist/src/verification/test-synthesizer.js +243 -0
  138. package/dist/src/verifier/outcome-checks.js +396 -0
  139. package/dist/src/verifier/transcript-checks.js +207 -0
  140. package/dist/src/verifier/verification-reporters.js +148 -0
  141. package/dist/src/verifier-engine.js +491 -0
  142. package/dist/src/wave-resizer.js +131 -0
  143. package/dist/src/wave-scheduler.js +44 -0
  144. package/dist/src/worktree-manager.js +368 -0
  145. package/dist/src/worktree-reserved-paths.js +97 -0
  146. package/package.json +80 -0
  147. package/plugin/agents/.gitkeep +0 -0
  148. package/plugin/agents/backend_master.agent.md +72 -0
  149. package/plugin/agents/devops_pro.agent.md +45 -0
  150. package/plugin/agents/frontend_expert.agent.md +59 -0
  151. package/plugin/agents/integrator_finalizer.agent.md +44 -0
  152. package/plugin/agents/meta_reviewer.agent.md +44 -0
  153. package/plugin/agents/security_auditor.agent.md +43 -0
  154. package/plugin/agents/tester_elite.agent.md +43 -0
  155. package/plugin/hooks/hooks.json +19 -0
  156. package/plugin/hooks/post-tool-use.json +9 -0
  157. package/plugin/hooks/pre-tool-use.json +9 -0
  158. package/plugin/plugin.json +47 -0
  159. package/plugin/skills/swarm-gates/SKILL.md +53 -0
  160. package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
  161. package/plugin/skills/swarm-verify/SKILL.md +44 -0
  162. package/templates/README.md +35 -0
  163. package/templates/cli-tool.json +31 -0
  164. package/templates/fullstack.json +52 -0
  165. package/templates/library.json +38 -0
  166. package/templates/react-app.json +45 -0
  167. package/templates/recipes/add-api-docs.json +35 -0
  168. package/templates/recipes/add-auth.json +47 -0
  169. package/templates/recipes/add-ci.json +39 -0
  170. package/templates/recipes/add-tests.json +39 -0
  171. package/templates/recipes/migrate-to-ts.json +42 -0
  172. package/templates/recipes/refactor-modularize.json +44 -0
  173. package/templates/recipes/security-audit.json +35 -0
  174. package/templates/rest-api.json +38 -0
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.discoverPropertyTargets = discoverPropertyTargets;
37
+ exports.runPropertyGate = runPropertyGate;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const command_runner_1 = require("./command-runner");
41
+ function isSupportedFile(filePath) {
42
+ return /\.(?:ts|tsx|js|jsx|py)$/.test(filePath) && !filePath.endsWith('.d.ts');
43
+ }
44
+ function safeName(value) {
45
+ return value.replace(/[^a-zA-Z0-9._-]/g, '-');
46
+ }
47
+ function isTypedTypeScriptSignature(params, suffix) {
48
+ return /:\s*[\w.[\]<>|]+/.test(params) || /^\s*:\s*[\w.[\]<>|]+/.test(suffix);
49
+ }
50
+ function isTypedPythonSignature(params, suffix) {
51
+ return /:\s*[\w.[\]<>|]+/.test(params) || /->\s*[\w.[\]<>|]+/.test(suffix);
52
+ }
53
+ function discoverInSource(filePath, source) {
54
+ const ext = path.extname(filePath);
55
+ const targets = [];
56
+ if (ext === '.py') {
57
+ for (const match of source.matchAll(/(?:^|\n)def\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*([^:]*):/g)) {
58
+ const typed = isTypedPythonSignature(match[2] ?? '', match[3] ?? '');
59
+ targets.push({
60
+ language: 'python',
61
+ filePath,
62
+ functionName: match[1] ?? 'unknown',
63
+ typed,
64
+ advisoryOnly: !typed,
65
+ });
66
+ }
67
+ return targets;
68
+ }
69
+ const language = ext === '.ts' || ext === '.tsx' ? 'typescript' : 'javascript';
70
+ const patterns = [
71
+ /(?:export\s+)?function\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*([^ {=>]*)/g,
72
+ /(?:export\s+)?const\s+([A-Za-z_]\w*)\s*=\s*\(([^)]*)\)\s*([^=]*)=>/g,
73
+ ];
74
+ for (const pattern of patterns) {
75
+ for (const match of source.matchAll(pattern)) {
76
+ const typed = language === 'typescript'
77
+ ? isTypedTypeScriptSignature(match[2] ?? '', match[3] ?? '')
78
+ : false;
79
+ targets.push({
80
+ language,
81
+ filePath,
82
+ functionName: match[1] ?? 'unknown',
83
+ typed,
84
+ advisoryOnly: language === 'javascript' || !typed,
85
+ });
86
+ }
87
+ }
88
+ return targets;
89
+ }
90
+ /**
91
+ * Discover modified functions that can receive generated property tests.
92
+ *
93
+ * @param repoPath - Target repository root.
94
+ * @param changedFiles - Repo-relative changed file paths.
95
+ * @returns Function targets with type-system coverage metadata.
96
+ */
97
+ function discoverPropertyTargets(repoPath, changedFiles) {
98
+ const targets = [];
99
+ for (const rel of changedFiles.filter(isSupportedFile)) {
100
+ if (rel.startsWith('/') || path.normalize(rel).startsWith('..'))
101
+ continue;
102
+ const full = path.join(repoPath, rel);
103
+ if (!fs.existsSync(full) || !fs.statSync(full).isFile())
104
+ continue;
105
+ targets.push(...discoverInSource(rel, fs.readFileSync(full, 'utf8')));
106
+ }
107
+ return targets;
108
+ }
109
+ function jsHarness(target, importRel) {
110
+ const importPath = importRel.startsWith('.') ? importRel : './' + importRel;
111
+ return [
112
+ "const fc = require('fast-check');",
113
+ `const mod = require('${importPath}');`,
114
+ `const fn = mod.${target.functionName};`,
115
+ "if (typeof fn !== 'function') throw new Error('target function is not exported');",
116
+ 'fc.assert(fc.property(fc.anything(), fc.anything(), (a, b) => {',
117
+ ' try { fn(a, b); return true; }',
118
+ " catch (err) { throw new Error('Counterexample: ' + JSON.stringify([a, b]) + ' -> ' + err.message); }",
119
+ '}), { numRuns: 100 });',
120
+ '',
121
+ ].join('\n');
122
+ }
123
+ function pythonHarness(target, moduleName) {
124
+ return [
125
+ 'from hypothesis import given, strategies as st',
126
+ `from ${moduleName} import ${target.functionName}`,
127
+ '',
128
+ '@given(st.integers(), st.integers())',
129
+ 'def test_generated_property(a, b):',
130
+ ` ${target.functionName}(a, b)`,
131
+ '',
132
+ 'if __name__ == "__main__":',
133
+ ' test_generated_property()',
134
+ '',
135
+ ].join('\n');
136
+ }
137
+ function pythonModuleName(filePath) {
138
+ return filePath.replace(/\.py$/, '').replace(/[\\/]/g, '.').replace(/^\.+/, '');
139
+ }
140
+ function buildPropertyCommand(repoPath, target) {
141
+ const outDir = path.join(repoPath, '.swarm', 'property-tests');
142
+ fs.mkdirSync(outDir, { recursive: true });
143
+ const base = `${safeName(target.filePath)}-${safeName(target.functionName)}`;
144
+ if (target.language === 'python') {
145
+ const harness = path.join(outDir, `${base}.py`);
146
+ fs.writeFileSync(harness, pythonHarness(target, pythonModuleName(target.filePath)), 'utf8');
147
+ return `python ${path.relative(repoPath, harness)}`;
148
+ }
149
+ const extension = target.language === 'typescript' ? '.ts' : '.js';
150
+ const harness = path.join(outDir, `${base}${extension}`);
151
+ const targetPath = path.join(repoPath, target.filePath);
152
+ const importRel = path.relative(path.dirname(harness), targetPath).replace(/\\/g, '/');
153
+ fs.writeFileSync(harness, jsHarness(target, importRel), 'utf8');
154
+ return target.language === 'typescript'
155
+ ? `npx tsx ${path.relative(repoPath, harness)}`
156
+ : `node ${path.relative(repoPath, harness)}`;
157
+ }
158
+ function extractCounterexample(output) {
159
+ const match = output.match(/Counterexample:\s*([^\n]+)/i);
160
+ return match?.[1]?.trim();
161
+ }
162
+ /**
163
+ * Run generated property-based tests for modified functions.
164
+ *
165
+ * Untyped JavaScript is intentionally advisory-only because generators cannot
166
+ * be type-directed; the gate still fuzzes with generic values and reports a
167
+ * reduced-confidence finding when a counterexample appears.
168
+ *
169
+ * @param input - Target repo, changed files, timeout, and optional runner.
170
+ * @returns Advisory property-testing result.
171
+ */
172
+ async function runPropertyGate(input) {
173
+ const targets = discoverPropertyTargets(input.targetRepoPath, input.changedFiles);
174
+ if (targets.length === 0) {
175
+ return { status: 'SKIP', score: 1, targets: [], findings: [] };
176
+ }
177
+ const runner = input.commandRunner ?? command_runner_1.runVerificationCommand;
178
+ const findings = [];
179
+ for (const target of targets) {
180
+ const command = buildPropertyCommand(input.targetRepoPath, target);
181
+ const result = await runner(command, input.targetRepoPath, input.timeoutMsPerFunction ?? 60_000);
182
+ if (result.exitCode !== 0) {
183
+ const output = `${result.stdout}\n${result.stderr}`;
184
+ const counterexample = extractCounterexample(output);
185
+ findings.push({
186
+ filePath: target.filePath,
187
+ functionName: target.functionName,
188
+ ...(counterexample ? { counterexample } : {}),
189
+ explanation: target.advisoryOnly
190
+ ? 'generic advisory fuzzing found a failure or the property tool could not run'
191
+ : 'property-based test found a counterexample or tool failure',
192
+ });
193
+ }
194
+ }
195
+ return {
196
+ status: findings.length > 0 ? 'ADVISORY' : 'PASS',
197
+ score: Math.max(0, Number((1 - findings.length * 0.25).toFixed(3))),
198
+ targets,
199
+ findings,
200
+ };
201
+ }
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.validateSynthesizedTestCandidate = validateSynthesizedTestCandidate;
37
+ exports.synthesizeRegressionTest = synthesizeRegressionTest;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const claude_code_adapter_1 = require("../adapters/claude-code-adapter");
41
+ const command_runner_1 = require("./command-runner");
42
+ const DEFAULT_MODEL = 'claude-sonnet-4.6';
43
+ const SYNTH_TEST_DIR = '.swarm/synthesized-tests';
44
+ function isRecord(value) {
45
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
46
+ }
47
+ function extractJsonObject(text) {
48
+ const fenced = text.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
49
+ const raw = fenced?.[1] ?? text.slice(text.indexOf('{'), text.lastIndexOf('}') + 1);
50
+ if (!raw || raw.trim() === '') {
51
+ throw new Error('adapter output did not contain a JSON object');
52
+ }
53
+ return JSON.parse(raw);
54
+ }
55
+ function parseCandidate(stdout) {
56
+ const parsed = extractJsonObject(stdout);
57
+ if (!isRecord(parsed)) {
58
+ throw new Error('candidate JSON must be an object');
59
+ }
60
+ const testFilePath = parsed.testFilePath;
61
+ const testCommand = parsed.testCommand;
62
+ const testSource = parsed.testSource;
63
+ if (typeof testCommand !== 'string' || testCommand.trim() === '') {
64
+ throw new Error('candidate JSON must include non-empty testCommand');
65
+ }
66
+ if (typeof testSource !== 'string' || testSource.trim() === '') {
67
+ throw new Error('candidate JSON must include non-empty testSource');
68
+ }
69
+ return {
70
+ testFilePath: typeof testFilePath === 'string' && testFilePath.trim() !== ''
71
+ ? testFilePath
72
+ : 'synthesized-regression.test.js',
73
+ testCommand,
74
+ testSource,
75
+ };
76
+ }
77
+ /**
78
+ * Validate that a generated test has meaningful assertions.
79
+ *
80
+ * @param candidate - Generated test candidate.
81
+ * @returns Rejection reason, or undefined when the test is structurally usable.
82
+ */
83
+ function validateSynthesizedTestCandidate(candidate) {
84
+ const source = candidate.testSource;
85
+ const assertionPatterns = [
86
+ /\bassert\.(?:strictEqual|deepStrictEqual|notStrictEqual|throws|rejects|ok)\s*\(/,
87
+ /\bexpect\s*\(/,
88
+ /\bshould\b/,
89
+ ];
90
+ const weakPatterns = [
91
+ /\bassert\.ok\s*\(\s*true\s*\)/,
92
+ /\bexpect\s*\(\s*true\s*\)/,
93
+ ];
94
+ if (!assertionPatterns.some(pattern => pattern.test(source))) {
95
+ return 'generated test has no clear assertion or explicit failure mode';
96
+ }
97
+ if (weakPatterns.some(pattern => pattern.test(source))) {
98
+ return 'generated test only asserts an unconditional truth';
99
+ }
100
+ return undefined;
101
+ }
102
+ function safeOutputPath(repoPath, candidatePath, attemptNumber) {
103
+ const safeName = path.basename(candidatePath).replace(/[^a-zA-Z0-9._-]/g, '-')
104
+ || 'synthesized-regression.test.js';
105
+ return path.join(repoPath, SYNTH_TEST_DIR, `attempt-${attemptNumber}-${safeName}`);
106
+ }
107
+ function writeCandidate(repoPath, candidate, attemptNumber) {
108
+ const absolutePath = safeOutputPath(repoPath, candidate.testFilePath, attemptNumber);
109
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
110
+ fs.writeFileSync(absolutePath, candidate.testSource, 'utf8');
111
+ const relativePath = path.relative(repoPath, absolutePath);
112
+ const command = candidate.testCommand.includes('{{TEST_FILE}}')
113
+ ? candidate.testCommand.replace(/\{\{TEST_FILE\}\}/g, relativePath)
114
+ : candidate.testCommand.replace(candidate.testFilePath, relativePath);
115
+ return { absolutePath, relativePath, command };
116
+ }
117
+ function readRelevantFiles(repoPath, relevantFiles) {
118
+ if (!relevantFiles || relevantFiles.length === 0)
119
+ return 'No relevant files were supplied.';
120
+ const sections = [];
121
+ for (const rel of relevantFiles.slice(0, 8)) {
122
+ if (rel.startsWith('/') || path.normalize(rel).startsWith('..'))
123
+ continue;
124
+ const full = path.join(repoPath, rel);
125
+ if (!fs.existsSync(full) || !fs.statSync(full).isFile())
126
+ continue;
127
+ const body = fs.readFileSync(full, 'utf8').slice(0, 4_000);
128
+ sections.push(`FILE: ${rel}\n\`\`\`\n${body}\n\`\`\``);
129
+ }
130
+ return sections.length > 0 ? sections.join('\n\n') : 'No readable relevant files were found.';
131
+ }
132
+ function buildPrompt(input, feedback) {
133
+ return `Generate one regression test for this goal:
134
+
135
+ ${input.goalText}
136
+
137
+ Relevant source context:
138
+ ${readRelevantFiles(input.targetRepoPath, input.relevantFiles)}
139
+
140
+ Return ONLY JSON with this schema:
141
+ {
142
+ "testFilePath": "relative/path/to/regression.test.js",
143
+ "testCommand": "command that runs only this test; use {{TEST_FILE}} for the generated file path",
144
+ "testSource": "complete test source"
145
+ }
146
+
147
+ The test must contain clear assertions, fail against the current codebase, and pass once the goal is correctly implemented.
148
+ ${feedback ? `Previous attempt feedback: ${feedback}` : ''}`;
149
+ }
150
+ /**
151
+ * Generate a user-goal regression test and prove it fails against the base repo.
152
+ *
153
+ * @param input - Goal, repo path, adapter, and retry settings.
154
+ * @returns Synthesized test path and command, or an ambiguous/failed status.
155
+ */
156
+ async function synthesizeRegressionTest(input) {
157
+ const adapter = input.adapter ?? new claude_code_adapter_1.ClaudeCodeAdapter();
158
+ const maxAttempts = input.maxAttempts ?? 3;
159
+ const timeoutMs = input.timeoutMs ?? 120_000;
160
+ const attempts = [];
161
+ let feedback;
162
+ for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
163
+ const adapterResult = await adapter.spawn({
164
+ prompt: buildPrompt(input, feedback),
165
+ workdir: input.targetRepoPath,
166
+ model: input.model ?? DEFAULT_MODEL,
167
+ timeout: timeoutMs,
168
+ });
169
+ if (adapterResult.exitCode !== 0) {
170
+ feedback = adapterResult.stderr || 'adapter exited non-zero';
171
+ attempts.push({
172
+ attemptNumber,
173
+ adapterExitCode: adapterResult.exitCode,
174
+ validation: 'rejected',
175
+ rejectionReason: feedback,
176
+ });
177
+ continue;
178
+ }
179
+ let candidate;
180
+ try {
181
+ candidate = parseCandidate(adapterResult.stdout);
182
+ }
183
+ catch (err) {
184
+ feedback = err instanceof Error ? err.message : String(err);
185
+ attempts.push({
186
+ attemptNumber,
187
+ adapterExitCode: adapterResult.exitCode,
188
+ validation: 'rejected',
189
+ rejectionReason: feedback,
190
+ });
191
+ continue;
192
+ }
193
+ const structuralRejection = validateSynthesizedTestCandidate(candidate);
194
+ if (structuralRejection) {
195
+ feedback = structuralRejection;
196
+ attempts.push({
197
+ attemptNumber,
198
+ adapterExitCode: adapterResult.exitCode,
199
+ validation: 'rejected',
200
+ rejectionReason: structuralRejection,
201
+ candidate,
202
+ });
203
+ continue;
204
+ }
205
+ const written = writeCandidate(input.targetRepoPath, candidate, attemptNumber);
206
+ const commandResult = await (0, command_runner_1.runVerificationCommand)(written.command, input.targetRepoPath, timeoutMs);
207
+ if (commandResult.exitCode !== 0) {
208
+ const accepted = {
209
+ ...candidate,
210
+ testFilePath: written.absolutePath,
211
+ testCommand: written.command,
212
+ };
213
+ attempts.push({
214
+ attemptNumber,
215
+ adapterExitCode: adapterResult.exitCode,
216
+ validation: 'accepted',
217
+ candidate: accepted,
218
+ commandResult,
219
+ });
220
+ return {
221
+ status: 'GENERATED',
222
+ reason: 'generated test fails against the base codebase',
223
+ attempts,
224
+ testFilePath: written.absolutePath,
225
+ testCommand: written.command,
226
+ };
227
+ }
228
+ feedback = 'generated test passed against the base codebase; make it expose the bug or missing behavior';
229
+ attempts.push({
230
+ attemptNumber,
231
+ adapterExitCode: adapterResult.exitCode,
232
+ validation: 'rejected',
233
+ rejectionReason: feedback,
234
+ candidate,
235
+ commandResult,
236
+ });
237
+ }
238
+ return {
239
+ status: attempts.some(attempt => attempt.adapterExitCode === 0) ? 'AMBIGUOUS_GOAL' : 'GENERATION_FAILED',
240
+ reason: feedback ?? 'test synthesizer could not produce a failing regression test',
241
+ attempts,
242
+ };
243
+ }