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,396 @@
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.runOutcomeChecks = runOutcomeChecks;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const defaults_1 = require("../defaults");
41
+ function last20Lines(output) {
42
+ const lines = output.split('\n');
43
+ if (lines.length <= 20)
44
+ return output;
45
+ return '...\n' + lines.slice(-20).join('\n');
46
+ }
47
+ function runOutcomeChecks(opts, workingDir) {
48
+ const checks = [];
49
+ checks.push(checkGitDiff(opts.workdir, opts.baseSha));
50
+ if (opts.expectedFiles && opts.expectedFiles.length > 0) {
51
+ checks.push(checkFileExistence(opts.workdir, opts.expectedFiles));
52
+ }
53
+ const buildCheck = checkBuildExec(opts.workdir);
54
+ if (buildCheck) {
55
+ checks.push(buildCheck);
56
+ }
57
+ if (process.env.SWARM_SKIP_OUTCOME_TEST_EXEC !== '1') {
58
+ const testCheck = checkTestExec(opts.workdir, workingDir);
59
+ if (testCheck) {
60
+ checks.push(testCheck);
61
+ }
62
+ }
63
+ // Idempotency resolution: if the diff check failed (no new commits) but the
64
+ // build and tests both pass, the goal state is demonstrably achieved. Downgrade
65
+ // git_diff from a hard failure to an advisory warning so an agent that found
66
+ // its work already present does not block the step from merging.
67
+ // Requires at least one exec check to be present; absence of scripts is not
68
+ // treated as a pass since that would mask genuinely empty agent runs.
69
+ const diffCheck = checks.find((c) => c.type === 'git_diff');
70
+ if (diffCheck && !diffCheck.passed) {
71
+ const buildExecCheck = checks.find((c) => c.type === 'build_exec');
72
+ const testExecCheck = checks.find((c) => c.type === 'test_exec');
73
+ const hasExecEvidence = buildExecCheck !== undefined || testExecCheck !== undefined;
74
+ const buildPassed = buildExecCheck ? buildExecCheck.passed : true;
75
+ const testsPassed = testExecCheck ? testExecCheck.passed : true;
76
+ if (hasExecEvidence && buildPassed && testsPassed) {
77
+ diffCheck.required = false;
78
+ diffCheck.passed = true;
79
+ diffCheck.evidence =
80
+ `${diffCheck.reason ?? 'No new commits'} — build and tests confirm goal already achieved (idempotent)`;
81
+ delete diffCheck.reason;
82
+ }
83
+ }
84
+ return checks;
85
+ }
86
+ function checkGitDiff(workdir, baseSha) {
87
+ const timeout = defaults_1.DEFAULT_SIGKILL_DELAY_MS * 2;
88
+ try {
89
+ // Primary: committed changes since baseline
90
+ const committedDiff = (0, child_process_1.execSync)(`git diff --stat ${baseSha}..HEAD`, { cwd: workdir, encoding: 'utf8', timeout }).trim();
91
+ if (committedDiff) {
92
+ const lines = committedDiff.split('\n');
93
+ const summaryLine = lines[lines.length - 1] || '';
94
+ return {
95
+ type: 'git_diff',
96
+ description: 'Agent produced code changes',
97
+ required: true,
98
+ passed: true,
99
+ evidence: summaryLine.trim(),
100
+ };
101
+ }
102
+ // Secondary: uncommitted working-tree changes (agent wrote files but did not commit)
103
+ const unstagedDiff = (0, child_process_1.execSync)(`git diff --stat HEAD`, { cwd: workdir, encoding: 'utf8', timeout }).trim();
104
+ // Use -uno to exclude untracked files; transcript files and other orchestrator
105
+ // artifacts dropped into the worktree should not count as "agent made changes".
106
+ const statusOutput = (0, child_process_1.execSync)(`git status --porcelain -uno`, { cwd: workdir, encoding: 'utf8', timeout }).trim();
107
+ if (unstagedDiff || statusOutput) {
108
+ const fileCount = statusOutput ? statusOutput.split('\n').filter(Boolean).length : 0;
109
+ return {
110
+ type: 'git_diff',
111
+ description: 'Agent produced code changes',
112
+ required: true,
113
+ passed: true,
114
+ evidence: `${fileCount} file(s) modified in working tree (uncommitted) — agent completed work without committing`,
115
+ };
116
+ }
117
+ // No committed or uncommitted changes: record for idempotency resolution above
118
+ return {
119
+ type: 'git_diff',
120
+ description: 'Agent produced code changes',
121
+ required: true,
122
+ passed: false,
123
+ reason: `No changes detected since ${baseSha.slice(0, 8)}`,
124
+ };
125
+ }
126
+ catch (err) {
127
+ const msg = err instanceof Error ? err.message : String(err);
128
+ return {
129
+ type: 'git_diff',
130
+ description: 'Agent produced code changes',
131
+ required: true,
132
+ passed: false,
133
+ reason: `git diff failed: ${msg.split('\n')[0]}`,
134
+ };
135
+ }
136
+ }
137
+ function checkFileExistence(workdir, expectedFiles) {
138
+ const missing = [];
139
+ const present = [];
140
+ for (const file of expectedFiles) {
141
+ const fullPath = path.join(workdir, file);
142
+ if (fs.existsSync(fullPath)) {
143
+ present.push(file);
144
+ }
145
+ else {
146
+ missing.push(file);
147
+ }
148
+ }
149
+ if (missing.length > 0) {
150
+ return {
151
+ type: 'file_existence',
152
+ description: 'Expected files exist in worktree',
153
+ required: true,
154
+ passed: false,
155
+ reason: `Missing files: ${missing.join(', ')}`,
156
+ evidence: `${present.length}/${expectedFiles.length} present`,
157
+ };
158
+ }
159
+ return {
160
+ type: 'file_existence',
161
+ description: 'Expected files exist in worktree',
162
+ required: true,
163
+ passed: true,
164
+ evidence: `All ${expectedFiles.length} expected file(s) found`,
165
+ };
166
+ }
167
+ function checkBuildExec(workdir) {
168
+ const buildCmd = detectBuildCommand(workdir);
169
+ if (!buildCmd)
170
+ return null;
171
+ try {
172
+ (0, child_process_1.execSync)(buildCmd, {
173
+ cwd: workdir,
174
+ encoding: 'utf8',
175
+ timeout: defaults_1.DEFAULT_COMMAND_TIMEOUT_MS / 2,
176
+ stdio: ['pipe', 'pipe', 'pipe'],
177
+ });
178
+ return {
179
+ type: 'build_exec',
180
+ description: `Build succeeded (${buildCmd})`,
181
+ required: true,
182
+ passed: true,
183
+ evidence: `Ran "${buildCmd}" in worktree`,
184
+ };
185
+ }
186
+ catch (err) {
187
+ const output = extractCommandOutput(err);
188
+ return {
189
+ type: 'build_exec',
190
+ description: `Build failed (${buildCmd})`,
191
+ required: true,
192
+ passed: false,
193
+ reason: last20Lines(output),
194
+ };
195
+ }
196
+ }
197
+ function checkTestExec(workdir, workingDir) {
198
+ const testCmd = detectTestCommand(workdir, workingDir);
199
+ if (!testCmd)
200
+ return null;
201
+ try {
202
+ (0, child_process_1.execSync)(testCmd, {
203
+ cwd: workdir,
204
+ encoding: 'utf8',
205
+ timeout: defaults_1.DEFAULT_COMMAND_TIMEOUT_MS,
206
+ stdio: ['pipe', 'pipe', 'pipe'],
207
+ });
208
+ return {
209
+ type: 'test_exec',
210
+ description: `Tests passed (${testCmd})`,
211
+ required: true,
212
+ passed: true,
213
+ evidence: `Ran "${testCmd}" in worktree`,
214
+ };
215
+ }
216
+ catch (err) {
217
+ const output = extractCommandOutput(err);
218
+ if (testCmd === 'npm test' && output.includes('MODULE_NOT_FOUND')) {
219
+ const fallback = retryTestWithAutoDiscovery(workdir);
220
+ if (fallback)
221
+ return fallback;
222
+ }
223
+ return {
224
+ type: 'test_exec',
225
+ description: `Tests failed (${testCmd})`,
226
+ required: true,
227
+ passed: false,
228
+ reason: last20Lines(output),
229
+ };
230
+ }
231
+ }
232
+ function retryTestWithAutoDiscovery(workdir) {
233
+ const pkgPath = path.join(workdir, 'package.json');
234
+ try {
235
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
236
+ const testScript = pkg.scripts?.test || '';
237
+ if (!/node\s+--test\s+\S+/.test(testScript))
238
+ return null;
239
+ }
240
+ catch {
241
+ return null;
242
+ }
243
+ const fallbackCmd = 'node --test';
244
+ try {
245
+ (0, child_process_1.execSync)(fallbackCmd, {
246
+ cwd: workdir,
247
+ encoding: 'utf8',
248
+ timeout: defaults_1.DEFAULT_COMMAND_TIMEOUT_MS,
249
+ stdio: ['pipe', 'pipe', 'pipe'],
250
+ });
251
+ return {
252
+ type: 'test_exec',
253
+ description: `Tests passed (${fallbackCmd}, auto-discovery fallback)`,
254
+ required: true,
255
+ passed: true,
256
+ evidence: `Original npm test hit ESM directory bug; retried with "${fallbackCmd}"`,
257
+ };
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ }
263
+ function detectBuildCommand(workdir) {
264
+ const pkgPath = path.join(workdir, 'package.json');
265
+ if (fs.existsSync(pkgPath)) {
266
+ try {
267
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
268
+ if (pkg.scripts?.build)
269
+ return 'npm run build';
270
+ }
271
+ catch {
272
+ // malformed package.json; skip
273
+ }
274
+ }
275
+ const makefilePath = path.join(workdir, 'Makefile');
276
+ if (fs.existsSync(makefilePath)) {
277
+ const content = fs.readFileSync(makefilePath, 'utf8');
278
+ if (/^build\s*:/m.test(content))
279
+ return 'make build';
280
+ }
281
+ return null;
282
+ }
283
+ function resolvePythonBinary(workdir, workingDir) {
284
+ for (const venvDir of ['venv', '.venv', 'env', '.env']) {
285
+ const venvPython = path.join(workdir, venvDir, 'bin', 'python');
286
+ if (fs.existsSync(venvPython))
287
+ return venvPython;
288
+ }
289
+ if (workdir !== workingDir) {
290
+ for (const venvDir of ['venv', '.venv', 'env', '.env']) {
291
+ const venvPython = path.join(workingDir, venvDir, 'bin', 'python');
292
+ if (fs.existsSync(venvPython))
293
+ return venvPython;
294
+ }
295
+ }
296
+ try {
297
+ (0, child_process_1.execSync)('python3 --version', { stdio: 'pipe', timeout: defaults_1.DEFAULT_SIGKILL_DELAY_MS });
298
+ return 'python3';
299
+ }
300
+ catch {
301
+ return 'python';
302
+ }
303
+ }
304
+ function detectTestCommand(workdir, workingDir) {
305
+ const pkgPath = path.join(workdir, 'package.json');
306
+ if (fs.existsSync(pkgPath)) {
307
+ try {
308
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
309
+ const testScript = pkg.scripts?.test || '';
310
+ const isPlaceholder = /no test(s)? specified/i.test(testScript)
311
+ || /exit\s+1/.test(testScript) && testScript.includes('echo');
312
+ if (testScript && !isPlaceholder)
313
+ return 'npm test';
314
+ }
315
+ catch {
316
+ // malformed package.json; skip
317
+ }
318
+ }
319
+ const makefilePath = path.join(workdir, 'Makefile');
320
+ // pytest is invoked by the verifier, NOT the agent. When the agent runs
321
+ // tests inside its worktree, it does so via its own shell commands; those
322
+ // are transcribed and transcript-parsed by the verifyTests path. This pytest
323
+ // invocation is the outcome-check path, and it must be isolated from
324
+ // orchestrator-generated artifact trees:
325
+ // - --rootdir="<workdir>" scopes pytest's rootdir search to the repo root
326
+ // instead of walking up to a common ancestor with parent worktrees (the
327
+ // shape that caused sympy__sympy-12481 to fail on double-collection of
328
+ // conftest.py).
329
+ // - --ignore paths prevent pytest from descending into orchestrator
330
+ // artifact trees whose own conftest.py would re-register options.
331
+ const pytestCmd = () => {
332
+ const py = resolvePythonBinary(workdir, workingDir);
333
+ const runsDir = path.join(workdir, 'runs');
334
+ const swarmDir = path.join(workdir, '.swarm');
335
+ return (`${py} -m pytest --rootdir="${workdir}" ` +
336
+ `--ignore="${runsDir}" --ignore="${swarmDir}"`);
337
+ };
338
+ const pyprojectPath = path.join(workdir, 'pyproject.toml');
339
+ if (fs.existsSync(pyprojectPath)) {
340
+ const content = fs.readFileSync(pyprojectPath, 'utf8');
341
+ if (content.includes('[tool.pytest') || content.includes('pytest')) {
342
+ return pytestCmd();
343
+ }
344
+ }
345
+ const setupCfgPath = path.join(workdir, 'setup.cfg');
346
+ if (fs.existsSync(setupCfgPath)) {
347
+ const content = fs.readFileSync(setupCfgPath, 'utf8');
348
+ if (content.includes('[tool:pytest]')) {
349
+ return pytestCmd();
350
+ }
351
+ }
352
+ for (const reqFile of ['requirements.txt', 'requirements-dev.txt', 'requirements-test.txt']) {
353
+ const reqPath = path.join(workdir, reqFile);
354
+ if (fs.existsSync(reqPath)) {
355
+ const content = fs.readFileSync(reqPath, 'utf8');
356
+ if (/^pytest/m.test(content)) {
357
+ return pytestCmd();
358
+ }
359
+ }
360
+ }
361
+ if (fs.existsSync(makefilePath)) {
362
+ const content = fs.readFileSync(makefilePath, 'utf8');
363
+ if (/^test\s*:/m.test(content))
364
+ return 'make test';
365
+ }
366
+ for (const testDir of ['tests', 'test']) {
367
+ const testDirPath = path.join(workdir, testDir);
368
+ if (fs.existsSync(testDirPath) && fs.statSync(testDirPath).isDirectory()) {
369
+ try {
370
+ const entries = fs.readdirSync(testDirPath);
371
+ const hasPyTests = entries.some(f => f.startsWith('test_') && f.endsWith('.py'));
372
+ if (hasPyTests)
373
+ return pytestCmd();
374
+ }
375
+ catch {
376
+ // unreadable dir; skip
377
+ }
378
+ }
379
+ }
380
+ return null;
381
+ }
382
+ function extractCommandOutput(err) {
383
+ if (err && typeof err === 'object') {
384
+ const e = err;
385
+ const parts = [];
386
+ if (e.stdout)
387
+ parts.push(e.stdout);
388
+ if (e.stderr)
389
+ parts.push(e.stderr);
390
+ if (parts.length > 0)
391
+ return parts.join('\n');
392
+ if (e.message)
393
+ return e.message;
394
+ }
395
+ return String(err);
396
+ }
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSummary = buildSummary;
4
+ exports.verifyTests = verifyTests;
5
+ exports.verifyBuild = verifyBuild;
6
+ exports.verifyCommits = verifyCommits;
7
+ exports.verifyAllClaims = verifyAllClaims;
8
+ exports.crossReferenceEvidence = crossReferenceEvidence;
9
+ const hook_generator_1 = require("../hook-generator");
10
+ function buildSummary(checks, passed) {
11
+ const total = checks.length;
12
+ const passedCount = checks.filter(c => c.passed).length;
13
+ const requiredFailed = checks.filter(c => c.required && !c.passed).length;
14
+ if (passed) {
15
+ return `${passedCount}/${total} checks passed`;
16
+ }
17
+ return `${requiredFailed} required check(s) failed out of ${total} total`;
18
+ }
19
+ function verifyTests(shareIndex) {
20
+ const testsRun = shareIndex.testsRun;
21
+ if (testsRun.length === 0) {
22
+ return {
23
+ type: 'test',
24
+ description: 'Tests executed',
25
+ required: true,
26
+ passed: false,
27
+ reason: 'No test commands found in transcript'
28
+ };
29
+ }
30
+ const verifiedTests = testsRun.filter(t => t.verified);
31
+ if (verifiedTests.length === 0) {
32
+ return {
33
+ type: 'test',
34
+ description: 'Tests executed with output',
35
+ required: true,
36
+ passed: false,
37
+ reason: 'Test commands found but no test output detected',
38
+ evidence: `Commands: ${testsRun.map(t => t.command).join(', ')}`
39
+ };
40
+ }
41
+ return {
42
+ type: 'test',
43
+ description: 'Tests executed successfully',
44
+ required: true,
45
+ passed: true,
46
+ evidence: `${verifiedTests.length} test(s) verified: ${verifiedTests.map(t => t.command).join(', ')}`
47
+ };
48
+ }
49
+ function verifyBuild(shareIndex) {
50
+ const builds = shareIndex.buildOperations;
51
+ if (builds.length === 0) {
52
+ return {
53
+ type: 'build',
54
+ description: 'Build executed',
55
+ required: true,
56
+ passed: false,
57
+ reason: 'No build commands found in transcript'
58
+ };
59
+ }
60
+ const verifiedBuilds = builds.filter(b => b.verified);
61
+ if (verifiedBuilds.length === 0) {
62
+ return {
63
+ type: 'build',
64
+ description: 'Build succeeded',
65
+ required: true,
66
+ passed: false,
67
+ reason: 'Build commands found but no success output detected',
68
+ evidence: `Tools: ${builds.map(b => b.tool).join(', ')}`
69
+ };
70
+ }
71
+ return {
72
+ type: 'build',
73
+ description: 'Build succeeded',
74
+ required: true,
75
+ passed: true,
76
+ evidence: `Verified builds: ${verifiedBuilds.map(b => b.tool).join(', ')}`
77
+ };
78
+ }
79
+ function verifyCommits(shareIndex) {
80
+ const commits = shareIndex.gitCommits;
81
+ if (commits.length === 0) {
82
+ return {
83
+ type: 'commit',
84
+ description: 'Git commits made',
85
+ required: true,
86
+ passed: false,
87
+ reason: 'No git commits found in transcript'
88
+ };
89
+ }
90
+ const verifiedCommits = commits.filter(c => c.verified);
91
+ if (verifiedCommits.length === 0) {
92
+ return {
93
+ type: 'commit',
94
+ description: 'Git commits verified',
95
+ required: true,
96
+ passed: false,
97
+ reason: 'Commit messages found but not verified in output'
98
+ };
99
+ }
100
+ return {
101
+ type: 'commit',
102
+ description: 'Git commits verified',
103
+ required: true,
104
+ passed: true,
105
+ evidence: `${verifiedCommits.length} commit(s): ${verifiedCommits.map(c => c.message).slice(0, 3).join('; ')}${verifiedCommits.length > 3 ? '...' : ''}`
106
+ };
107
+ }
108
+ function verifyAllClaims(shareIndex) {
109
+ const checks = [];
110
+ const unverifiedClaims = [];
111
+ shareIndex.claims.forEach(claim => {
112
+ if (!claim.verified) {
113
+ checks.push({
114
+ type: 'claim',
115
+ description: `Verify claim: "${claim.claim.substring(0, 50)}..."`,
116
+ required: false,
117
+ passed: false,
118
+ reason: claim.evidence || 'No evidence found',
119
+ evidence: claim.claim
120
+ });
121
+ unverifiedClaims.push(claim.claim);
122
+ }
123
+ });
124
+ return { checks, unverifiedClaims };
125
+ }
126
+ function crossReferenceEvidence(shareIndex, evidenceLogPath) {
127
+ const fs = require('fs');
128
+ // Hook evidence is only produced by adapters that load hooks from
129
+ // <gitRoot>/.github/hooks/ (currently only Copilot CLI). If the file does
130
+ // not exist, the adapter is not wired for hooks; suppress the check
131
+ // instead of synthesizing a failed "should have but didn't" record that
132
+ // misleads users on claude-code, codex, or claude-code-teams runs.
133
+ if (!fs.existsSync(evidenceLogPath)) {
134
+ return [];
135
+ }
136
+ const hookGen = new hook_generator_1.HookGenerator();
137
+ const entries = hookGen.parseEvidenceLog(evidenceLogPath);
138
+ if (entries.length === 0) {
139
+ return [{
140
+ type: 'claim',
141
+ description: 'Hook evidence log exists and is non-empty',
142
+ required: false,
143
+ passed: false,
144
+ reason: `Hook evidence log at ${evidenceLogPath} is empty; hooks ran but recorded no events.`
145
+ }];
146
+ }
147
+ const checks = [];
148
+ const errorEntries = entries.filter(e => e.event === 'errorOccurred');
149
+ const transcriptHasErrors = shareIndex.testsRun.some(t => !t.verified)
150
+ || shareIndex.buildOperations.some(b => !b.verified)
151
+ || shareIndex.claims.some(c => !c.verified);
152
+ if (errorEntries.length > 0 && !transcriptHasErrors) {
153
+ checks.push({
154
+ type: 'claim',
155
+ description: 'Hook error evidence cross-references transcript',
156
+ required: true,
157
+ passed: false,
158
+ evidence: `Hooks captured ${errorEntries.length} error(s) but transcript reports none`,
159
+ reason: `Hook evidence contradicts transcript: ${errorEntries.length} error(s) captured by hooks were not mentioned in transcript`
160
+ });
161
+ }
162
+ else if (errorEntries.length > 0) {
163
+ checks.push({
164
+ type: 'claim',
165
+ description: 'Hook error evidence cross-references transcript',
166
+ required: false,
167
+ passed: true,
168
+ evidence: `Both hooks (${errorEntries.length}) and transcript acknowledge errors`
169
+ });
170
+ }
171
+ const hookFiles = new Set(entries
172
+ .filter(e => e.filePath)
173
+ .map(e => e.filePath));
174
+ const transcriptFiles = new Set(shareIndex.changedFiles || []);
175
+ const scopeViolations = entries.filter(e => e.event === 'scope_violation');
176
+ if (scopeViolations.length > 0) {
177
+ const violatedFiles = scopeViolations.map(v => v.filePath || 'unknown').join(', ');
178
+ const agents = [...new Set(scopeViolations.map(v => v.agentName || 'unknown'))];
179
+ const rules = [...new Set(scopeViolations.map(v => v.boundaryRule || 'unknown'))];
180
+ checks.push({
181
+ type: 'claim',
182
+ description: 'No scope violations detected during execution',
183
+ required: true,
184
+ passed: false,
185
+ evidence: `${scopeViolations.length} scope violation(s) logged by hooks`,
186
+ reason: `Agent ${agents.join(', ')} wrote to files outside its declared scope: ${violatedFiles}. Violated boundary rule(s): ${rules.join(', ')}`
187
+ });
188
+ }
189
+ const hookOnlyFiles = [...hookFiles].filter(f => !transcriptFiles.has(f));
190
+ if (hookOnlyFiles.length > 0) {
191
+ checks.push({
192
+ type: 'claim',
193
+ description: 'Hook file evidence has unmentioned files',
194
+ required: false,
195
+ passed: true,
196
+ evidence: `Hooks captured ${hookOnlyFiles.length} file(s) not mentioned in transcript: ${hookOnlyFiles.slice(0, 5).join(', ')}`
197
+ });
198
+ }
199
+ checks.push({
200
+ type: 'claim',
201
+ description: 'Hook evidence corroborates transcript',
202
+ required: false,
203
+ passed: true,
204
+ evidence: `${entries.length} hook evidence entries, ${hookFiles.size} files tracked`
205
+ });
206
+ return checks;
207
+ }