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.
- package/LICENSE +15 -0
- package/README.md +199 -0
- package/agents/reviewer.agent.md +50 -0
- package/agents/worker.agent.md +41 -0
- package/config/default-agents.yaml +56 -0
- package/config/pm-agent.yaml +36 -0
- package/config/quality-gates.yaml +88 -0
- package/config/repair-agent.yaml +34 -0
- package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
- package/config/semgrep-rules/exception-swallowing.yaml +13 -0
- package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
- package/config/semgrep-rules/mock-mutation.yaml +6 -0
- package/config/semgrep-rules/test-modification.yaml +6 -0
- package/config/user-agents.yaml +23 -0
- package/dist/src/adapters/adapter-factory.js +29 -0
- package/dist/src/adapters/agent-adapter.js +32 -0
- package/dist/src/adapters/claude-code-adapter.js +126 -0
- package/dist/src/adapters/claude-code-teams.js +119 -0
- package/dist/src/adapters/codex-adapter.js +118 -0
- package/dist/src/adapters/copilot-adapter.js +224 -0
- package/dist/src/adapters/fatal-error-classifier.js +88 -0
- package/dist/src/adapters/index.js +44 -0
- package/dist/src/adapters/persistent-session.js +204 -0
- package/dist/src/adapters/process-supervisor.js +199 -0
- package/dist/src/agents-exporter.js +398 -0
- package/dist/src/analytics-log.js +161 -0
- package/dist/src/baseline-scanner.js +65 -0
- package/dist/src/bootstrap-evidence.js +88 -0
- package/dist/src/bootstrap-orchestrator.js +205 -0
- package/dist/src/bootstrap-types.js +5 -0
- package/dist/src/branch-merger.js +427 -0
- package/dist/src/cli/attest-handlers.js +44 -0
- package/dist/src/cli/cost-prompt.js +51 -0
- package/dist/src/cli/demo-handlers.js +274 -0
- package/dist/src/cli/flags.js +152 -0
- package/dist/src/cli/index.js +57 -0
- package/dist/src/cli/live-status.js +186 -0
- package/dist/src/cli/misc-handlers.js +242 -0
- package/dist/src/cli/plan-handlers.js +350 -0
- package/dist/src/cli/share-handlers.js +163 -0
- package/dist/src/cli/status-handlers.js +457 -0
- package/dist/src/cli/swarm-handlers.js +638 -0
- package/dist/src/cli/usage.js +72 -0
- package/dist/src/cli.js +217 -0
- package/dist/src/commit-pattern-detector.js +143 -0
- package/dist/src/commit-quality-analyzer.js +38 -0
- package/dist/src/config-loader.js +339 -0
- package/dist/src/context-broker.js +358 -0
- package/dist/src/copilot-cli-wrapper.js +258 -0
- package/dist/src/cost-estimator.js +241 -0
- package/dist/src/defaults.js +18 -0
- package/dist/src/demo-mode.js +180 -0
- package/dist/src/deployment-handler.js +78 -0
- package/dist/src/deployment-manager.js +228 -0
- package/dist/src/execution-queue.js +196 -0
- package/dist/src/external-tool-manager.js +184 -0
- package/dist/src/gate-prompt-builder.js +117 -0
- package/dist/src/gate-remediation.js +173 -0
- package/dist/src/github-issues-ingester.js +152 -0
- package/dist/src/github-mcp-integrator.js +164 -0
- package/dist/src/hook-generator.js +229 -0
- package/dist/src/index.js +48 -0
- package/dist/src/knowledge-base.js +300 -0
- package/dist/src/logger.js +111 -0
- package/dist/src/meta-analyzer.js +208 -0
- package/dist/src/metrics-collector.js +209 -0
- package/dist/src/metrics-types.js +5 -0
- package/dist/src/multi-repo-coordinator.js +179 -0
- package/dist/src/orchestrator/async-meta-analysis.js +84 -0
- package/dist/src/orchestrator/fatal-run-error.js +43 -0
- package/dist/src/orchestrator/final-gates-remediation.js +258 -0
- package/dist/src/orchestrator/git-state-utils.js +119 -0
- package/dist/src/orchestrator/pause-controller.js +55 -0
- package/dist/src/orchestrator/replan-runner.js +258 -0
- package/dist/src/orchestrator/step-executor.js +391 -0
- package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
- package/dist/src/owasp-mapper.js +153 -0
- package/dist/src/owasp-report-renderer.js +39 -0
- package/dist/src/plan-files.js +142 -0
- package/dist/src/plan-generator.js +1207 -0
- package/dist/src/pm-agent.js +296 -0
- package/dist/src/post-run-reporter.js +203 -0
- package/dist/src/pr-automation.js +103 -0
- package/dist/src/pr-manager.js +318 -0
- package/dist/src/presenter/index.js +209 -0
- package/dist/src/prompt-builder.js +173 -0
- package/dist/src/quality-gates/config-loader.js +196 -0
- package/dist/src/quality-gates/default-config.js +243 -0
- package/dist/src/quality-gates/file-utils.js +152 -0
- package/dist/src/quality-gates/gate-runner.js +171 -0
- package/dist/src/quality-gates/gates/accessibility.js +355 -0
- package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
- package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
- package/dist/src/quality-gates/gates/readme-claims.js +62 -0
- package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
- package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
- package/dist/src/quality-gates/gates/test-coverage.js +240 -0
- package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
- package/dist/src/quality-gates/gates/test-isolation.js +74 -0
- package/dist/src/quality-gates/index.js +17 -0
- package/dist/src/quality-gates/registry.js +205 -0
- package/dist/src/quality-gates/types.js +2 -0
- package/dist/src/quick-fix-mode.js +302 -0
- package/dist/src/recipe-loader.js +122 -0
- package/dist/src/repair-agent.js +396 -0
- package/dist/src/repo-analyzer.js +342 -0
- package/dist/src/report-generator.js +146 -0
- package/dist/src/report-renderer.js +90 -0
- package/dist/src/requirement-filter.js +52 -0
- package/dist/src/sarif-formatter.js +140 -0
- package/dist/src/scheduling/dependency-analyzer.js +178 -0
- package/dist/src/scheduling/work-stealing-queue.js +123 -0
- package/dist/src/secret-redactor.js +90 -0
- package/dist/src/session-executor.js +587 -0
- package/dist/src/session-manager.js +221 -0
- package/dist/src/share/transcript-verification.js +128 -0
- package/dist/src/share-parser.js +477 -0
- package/dist/src/spinner.js +146 -0
- package/dist/src/step-runner.js +365 -0
- package/dist/src/swarm-orchestrator.js +686 -0
- package/dist/src/task-classifier.js +134 -0
- package/dist/src/test-command-discovery.js +123 -0
- package/dist/src/text-similarity.js +24 -0
- package/dist/src/tier-maps.js +158 -0
- package/dist/src/types.js +5 -0
- package/dist/src/url-shortener.js +135 -0
- package/dist/src/verification/attestation.js +166 -0
- package/dist/src/verification/cheat-detector.js +240 -0
- package/dist/src/verification/command-runner.js +70 -0
- package/dist/src/verification/composite-score.js +157 -0
- package/dist/src/verification/cosign-attestation.js +137 -0
- package/dist/src/verification/diff-analysis.js +89 -0
- package/dist/src/verification/differential-gate.js +173 -0
- package/dist/src/verification/index.js +41 -0
- package/dist/src/verification/mutation-gate.js +242 -0
- package/dist/src/verification/property-gate.js +201 -0
- package/dist/src/verification/test-synthesizer.js +243 -0
- package/dist/src/verifier/outcome-checks.js +396 -0
- package/dist/src/verifier/transcript-checks.js +207 -0
- package/dist/src/verifier/verification-reporters.js +148 -0
- package/dist/src/verifier-engine.js +491 -0
- package/dist/src/wave-resizer.js +131 -0
- package/dist/src/wave-scheduler.js +44 -0
- package/dist/src/worktree-manager.js +368 -0
- package/dist/src/worktree-reserved-paths.js +97 -0
- package/package.json +80 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/backend_master.agent.md +72 -0
- package/plugin/agents/devops_pro.agent.md +45 -0
- package/plugin/agents/frontend_expert.agent.md +59 -0
- package/plugin/agents/integrator_finalizer.agent.md +44 -0
- package/plugin/agents/meta_reviewer.agent.md +44 -0
- package/plugin/agents/security_auditor.agent.md +43 -0
- package/plugin/agents/tester_elite.agent.md +43 -0
- package/plugin/hooks/hooks.json +19 -0
- package/plugin/hooks/post-tool-use.json +9 -0
- package/plugin/hooks/pre-tool-use.json +9 -0
- package/plugin/plugin.json +47 -0
- package/plugin/skills/swarm-gates/SKILL.md +53 -0
- package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
- package/plugin/skills/swarm-verify/SKILL.md +44 -0
- package/templates/README.md +35 -0
- package/templates/cli-tool.json +31 -0
- package/templates/fullstack.json +52 -0
- package/templates/library.json +38 -0
- package/templates/react-app.json +45 -0
- package/templates/recipes/add-api-docs.json +35 -0
- package/templates/recipes/add-auth.json +47 -0
- package/templates/recipes/add-ci.json +39 -0
- package/templates/recipes/add-tests.json +39 -0
- package/templates/recipes/migrate-to-ts.json +42 -0
- package/templates/recipes/refactor-modularize.json +44 -0
- package/templates/recipes/security-audit.json +35 -0
- package/templates/rest-api.json +38 -0
|
@@ -0,0 +1,148 @@
|
|
|
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.generateVerificationReport = generateVerificationReport;
|
|
37
|
+
exports.commitVerificationReport = commitVerificationReport;
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
async function runGitCommand(workingDir, args) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const proc = (0, child_process_1.spawn)('git', args, { cwd: workingDir });
|
|
44
|
+
let stdout = '';
|
|
45
|
+
let stderr = '';
|
|
46
|
+
proc.stdout?.on('data', (data) => {
|
|
47
|
+
stdout += data.toString();
|
|
48
|
+
});
|
|
49
|
+
proc.stderr?.on('data', (data) => {
|
|
50
|
+
stderr += data.toString();
|
|
51
|
+
});
|
|
52
|
+
proc.on('close', (code) => {
|
|
53
|
+
if (code === 0)
|
|
54
|
+
resolve(stdout);
|
|
55
|
+
else
|
|
56
|
+
reject(new Error(stderr || `Git command failed with code ${code}`));
|
|
57
|
+
});
|
|
58
|
+
proc.on('error', (err) => reject(err));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Render a VerificationResult as a Markdown report and persist it.
|
|
63
|
+
*/
|
|
64
|
+
async function generateVerificationReport(result, outputPath) {
|
|
65
|
+
const lines = [];
|
|
66
|
+
lines.push('# Verification Report');
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push(`**Step**: ${result.stepNumber}`);
|
|
69
|
+
lines.push(`**Agent**: ${result.agentName}`);
|
|
70
|
+
lines.push(`**Status**: ${result.passed ? '✅ PASSED' : '❌ FAILED'}`);
|
|
71
|
+
lines.push(`**Timestamp**: ${result.timestamp}`);
|
|
72
|
+
lines.push(`**Transcript**: ${result.transcriptPath}`);
|
|
73
|
+
lines.push('');
|
|
74
|
+
lines.push('## Verification Checks');
|
|
75
|
+
lines.push('');
|
|
76
|
+
result.checks.forEach((check) => {
|
|
77
|
+
const icon = check.passed ? '✅' : '❌';
|
|
78
|
+
const req = check.required ? '(required)' : '(optional)';
|
|
79
|
+
lines.push(`### ${icon} ${check.description} ${req}`);
|
|
80
|
+
lines.push('');
|
|
81
|
+
lines.push(`**Type**: ${check.type}`);
|
|
82
|
+
lines.push(`**Passed**: ${check.passed}`);
|
|
83
|
+
if (check.evidence)
|
|
84
|
+
lines.push(`**Evidence**: ${check.evidence}`);
|
|
85
|
+
if (check.reason)
|
|
86
|
+
lines.push(`**Reason**: ${check.reason}`);
|
|
87
|
+
lines.push('');
|
|
88
|
+
});
|
|
89
|
+
if (result.unverifiedClaims.length > 0) {
|
|
90
|
+
lines.push('## ⚠️ Unverified Claims (Drift Detection)');
|
|
91
|
+
lines.push('');
|
|
92
|
+
lines.push('The following claims were made without supporting evidence:');
|
|
93
|
+
lines.push('');
|
|
94
|
+
result.unverifiedClaims.forEach((claim) => {
|
|
95
|
+
lines.push(`- ${claim}`);
|
|
96
|
+
});
|
|
97
|
+
lines.push('');
|
|
98
|
+
}
|
|
99
|
+
lines.push('## Summary');
|
|
100
|
+
lines.push('');
|
|
101
|
+
const passedCount = result.checks.filter((c) => c.passed).length;
|
|
102
|
+
const totalCount = result.checks.length;
|
|
103
|
+
lines.push(`**Checks Passed**: ${passedCount}/${totalCount}`);
|
|
104
|
+
lines.push(`**Unverified Claims**: ${result.unverifiedClaims.length}`);
|
|
105
|
+
lines.push('');
|
|
106
|
+
if (!result.passed) {
|
|
107
|
+
lines.push('**Action Required**: This step failed verification. Review the issues above and retry.');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
lines.push('**Result**: All required checks passed. Step verified successfully.');
|
|
111
|
+
}
|
|
112
|
+
const reportContent = lines.join('\n');
|
|
113
|
+
const dir = path.dirname(outputPath);
|
|
114
|
+
if (!fs.existsSync(dir)) {
|
|
115
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
fs.writeFileSync(outputPath, reportContent, 'utf8');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Commit the verification report with a natural-language message.
|
|
121
|
+
* Skips silently when the report lives outside the target worktree
|
|
122
|
+
* (e.g. bootstrap mode writing into the orchestrator's own runs/).
|
|
123
|
+
*/
|
|
124
|
+
async function commitVerificationReport(reportPath, workingDir, stepNumber, agentName, passed, logger) {
|
|
125
|
+
const status = passed ? 'verified' : 'failed verification';
|
|
126
|
+
const messages = [
|
|
127
|
+
`verify step ${stepNumber} (${agentName}) - ${status}`,
|
|
128
|
+
`add verification report for step ${stepNumber}`,
|
|
129
|
+
`verification: step ${stepNumber} ${status}`,
|
|
130
|
+
`step ${stepNumber} verification ${passed ? 'passed' : 'failed'}`,
|
|
131
|
+
];
|
|
132
|
+
const message = messages[Math.floor(Math.random() * messages.length)];
|
|
133
|
+
const resolvedReport = path.resolve(reportPath);
|
|
134
|
+
const resolvedWorkDir = path.resolve(workingDir);
|
|
135
|
+
if (!resolvedReport.startsWith(resolvedWorkDir + path.sep)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
await runGitCommand(workingDir, ['add', '-f', reportPath]);
|
|
140
|
+
await runGitCommand(workingDir, ['commit', '-m', message]);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const error = err;
|
|
144
|
+
if (!error.message.includes('nothing to commit')) {
|
|
145
|
+
logger.warn(` ⚠️ Could not commit verification report: ${error.message.split('\n')[0]}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,491 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.VerifierEngine = void 0;
|
|
40
|
+
exports.last20Lines = last20Lines;
|
|
41
|
+
exports.buildFailureContext = buildFailureContext;
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const share_parser_1 = __importDefault(require("./share-parser"));
|
|
46
|
+
const defaults_1 = require("./defaults");
|
|
47
|
+
const outcome_checks_1 = require("./verifier/outcome-checks");
|
|
48
|
+
const transcript_checks_1 = require("./verifier/transcript-checks");
|
|
49
|
+
const verification_reporters_1 = require("./verifier/verification-reporters");
|
|
50
|
+
const logger_1 = require("./logger");
|
|
51
|
+
const logger = (0, logger_1.getLogger)('verifier-engine');
|
|
52
|
+
/**
|
|
53
|
+
* Truncate output to the last 20 lines for error display.
|
|
54
|
+
* Returns the full string if it has 20 lines or fewer.
|
|
55
|
+
*/
|
|
56
|
+
function last20Lines(output) {
|
|
57
|
+
const lines = output.split('\n');
|
|
58
|
+
if (lines.length <= 20)
|
|
59
|
+
return output;
|
|
60
|
+
return '...\n' + lines.slice(-20).join('\n');
|
|
61
|
+
}
|
|
62
|
+
// Ordering for failure context: more actionable types first
|
|
63
|
+
const FAILURE_TYPE_PRIORITY = {
|
|
64
|
+
file_existence: 0,
|
|
65
|
+
test_exec: 1,
|
|
66
|
+
build_exec: 2,
|
|
67
|
+
git_diff: 3,
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Build a structured failure context string from failed checks.
|
|
71
|
+
* Ordered by actionability and truncated to ~500 tokens (2000 chars).
|
|
72
|
+
*/
|
|
73
|
+
function buildFailureContext(checks) {
|
|
74
|
+
const failed = checks.filter(c => !c.passed);
|
|
75
|
+
if (failed.length === 0)
|
|
76
|
+
return '';
|
|
77
|
+
failed.sort((a, b) => {
|
|
78
|
+
const pa = FAILURE_TYPE_PRIORITY[a.type] ?? 10;
|
|
79
|
+
const pb = FAILURE_TYPE_PRIORITY[b.type] ?? 10;
|
|
80
|
+
return pa - pb;
|
|
81
|
+
});
|
|
82
|
+
const lines = failed.map(c => `- ${c.type}: ${c.reason || c.description}`);
|
|
83
|
+
const joined = lines.join('\n');
|
|
84
|
+
// ~500 tokens at 4 chars/token = 2000 chars
|
|
85
|
+
if (joined.length > defaults_1.DEFAULT_FAILURE_CONTEXT_CHARS) {
|
|
86
|
+
return joined.slice(0, defaults_1.DEFAULT_FAILURE_CONTEXT_CHARS) + '\n... (truncated)';
|
|
87
|
+
}
|
|
88
|
+
return joined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Verifier Engine - proactive real-time verification and drift prevention
|
|
92
|
+
* Validates agent claims against actual evidence in transcripts
|
|
93
|
+
*/
|
|
94
|
+
class VerifierEngine {
|
|
95
|
+
shareParser;
|
|
96
|
+
workingDir;
|
|
97
|
+
constructor(workingDir) {
|
|
98
|
+
this.workingDir = workingDir || process.cwd();
|
|
99
|
+
this.shareParser = new share_parser_1.default();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Verify a completed step by analyzing its transcript
|
|
103
|
+
*/
|
|
104
|
+
async verifyStep(stepNumber, agentName, transcriptPath, requirements, preParsedIndex, evidenceLogPath, outcomeOpts) {
|
|
105
|
+
const checks = [];
|
|
106
|
+
const unverifiedClaims = [];
|
|
107
|
+
// Read and parse transcript
|
|
108
|
+
if (!fs.existsSync(transcriptPath)) {
|
|
109
|
+
return {
|
|
110
|
+
stepNumber,
|
|
111
|
+
agentName,
|
|
112
|
+
passed: false,
|
|
113
|
+
checks: [{
|
|
114
|
+
type: 'claim',
|
|
115
|
+
description: 'Transcript file exists',
|
|
116
|
+
required: true,
|
|
117
|
+
passed: false,
|
|
118
|
+
reason: `Transcript not found: ${transcriptPath}`
|
|
119
|
+
}],
|
|
120
|
+
unverifiedClaims: [],
|
|
121
|
+
timestamp: new Date().toISOString(),
|
|
122
|
+
transcriptPath
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Skip re-parse when the caller already parsed the transcript
|
|
126
|
+
const shareIndex = preParsedIndex ?? (() => {
|
|
127
|
+
const transcriptContent = fs.readFileSync(transcriptPath, 'utf8');
|
|
128
|
+
return this.shareParser.parse(transcriptContent);
|
|
129
|
+
})();
|
|
130
|
+
// Verify tests if required
|
|
131
|
+
if (requirements?.requireTests) {
|
|
132
|
+
const testCheck = this.verifyTests(shareIndex);
|
|
133
|
+
checks.push(testCheck);
|
|
134
|
+
}
|
|
135
|
+
// Verify build if required
|
|
136
|
+
if (requirements?.requireBuild) {
|
|
137
|
+
const buildCheck = this.verifyBuild(shareIndex);
|
|
138
|
+
checks.push(buildCheck);
|
|
139
|
+
}
|
|
140
|
+
// Verify commits if required
|
|
141
|
+
if (requirements?.requireCommits) {
|
|
142
|
+
const commitCheck = this.verifyCommits(shareIndex);
|
|
143
|
+
checks.push(commitCheck);
|
|
144
|
+
}
|
|
145
|
+
// Check for unverified claims (drift detection) - these are warnings only
|
|
146
|
+
const claimCheck = this.verifyAllClaims(shareIndex);
|
|
147
|
+
checks.push(...claimCheck.checks);
|
|
148
|
+
unverifiedClaims.push(...claimCheck.unverifiedClaims);
|
|
149
|
+
// Cross-reference hook evidence against transcript claims (defense in depth)
|
|
150
|
+
if (evidenceLogPath) {
|
|
151
|
+
const hookEvidence = this.crossReferenceEvidence(shareIndex, evidenceLogPath);
|
|
152
|
+
checks.push(...hookEvidence);
|
|
153
|
+
}
|
|
154
|
+
// Outcome-based checks: run when the caller provides worktree info
|
|
155
|
+
if (outcomeOpts) {
|
|
156
|
+
const outcomeChecks = this.runOutcomeChecks(outcomeOpts);
|
|
157
|
+
checks.push(...outcomeChecks);
|
|
158
|
+
// Demote transcript-parsed test/build checks when real execution checks exist
|
|
159
|
+
const hasOutcomeExecChecks = checks.some(c => c.type === 'build_exec' || c.type === 'test_exec');
|
|
160
|
+
if (hasOutcomeExecChecks) {
|
|
161
|
+
for (const c of checks) {
|
|
162
|
+
if (c.type === 'build' || c.type === 'test') {
|
|
163
|
+
c.required = false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Overall pass/fail: every required check must pass (vacuously true when none are required)
|
|
169
|
+
const passed = checks.every(c => !c.required || c.passed);
|
|
170
|
+
// Build failure context for repair agent consumption
|
|
171
|
+
const failureContext = passed ? undefined : buildFailureContext(checks);
|
|
172
|
+
const summary = this.buildSummary(checks, passed);
|
|
173
|
+
// Apply graceful degradation if enabled and verification failed
|
|
174
|
+
let gracefulFailure = false;
|
|
175
|
+
let degradationReason;
|
|
176
|
+
if (!passed && requirements?.gracefulDegradation) {
|
|
177
|
+
gracefulFailure = true;
|
|
178
|
+
const failedRequired = checks.filter(c => c.required && !c.passed);
|
|
179
|
+
degradationReason = `Verification failed but graceful degradation enabled. Failed checks: ${failedRequired.map(c => c.type).join(', ')}`;
|
|
180
|
+
}
|
|
181
|
+
const result = {
|
|
182
|
+
stepNumber,
|
|
183
|
+
agentName,
|
|
184
|
+
passed: passed || gracefulFailure,
|
|
185
|
+
checks,
|
|
186
|
+
unverifiedClaims,
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
transcriptPath,
|
|
189
|
+
gracefulFailure,
|
|
190
|
+
degradationReason,
|
|
191
|
+
failureContext,
|
|
192
|
+
summary,
|
|
193
|
+
baseSha: outcomeOpts?.baseSha,
|
|
194
|
+
};
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
runOutcomeChecks(opts) {
|
|
198
|
+
return (0, outcome_checks_1.runOutcomeChecks)(opts, this.workingDir);
|
|
199
|
+
}
|
|
200
|
+
buildSummary(checks, passed) {
|
|
201
|
+
return (0, transcript_checks_1.buildSummary)(checks, passed);
|
|
202
|
+
}
|
|
203
|
+
verifyTests(shareIndex) {
|
|
204
|
+
return (0, transcript_checks_1.verifyTests)(shareIndex);
|
|
205
|
+
}
|
|
206
|
+
verifyBuild(shareIndex) {
|
|
207
|
+
return (0, transcript_checks_1.verifyBuild)(shareIndex);
|
|
208
|
+
}
|
|
209
|
+
verifyCommits(shareIndex) {
|
|
210
|
+
return (0, transcript_checks_1.verifyCommits)(shareIndex);
|
|
211
|
+
}
|
|
212
|
+
verifyAllClaims(shareIndex) {
|
|
213
|
+
return (0, transcript_checks_1.verifyAllClaims)(shareIndex);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Verify expected outputs by checking files and content in the worktree.
|
|
217
|
+
* Each expectedOutput string is parsed for file paths and key terms,
|
|
218
|
+
* then verified against the actual file system.
|
|
219
|
+
*/
|
|
220
|
+
verifyExpectedOutputs(stepNumber, agentName, expectedOutputs, worktreeDir) {
|
|
221
|
+
const checks = [];
|
|
222
|
+
// Regex to extract file paths from expected output descriptions
|
|
223
|
+
const filePathRegex = /\b((?:src|test|tests|server|config|public)\/[\w./-]+\.\w+|[\w-]+\.\w{2,4})\b/g;
|
|
224
|
+
// Key terms that can be grepped in source files
|
|
225
|
+
const termPatterns = [
|
|
226
|
+
{ term: 'aria-label', regex: /aria-label/i },
|
|
227
|
+
{ term: 'aria-live', regex: /aria-live/i },
|
|
228
|
+
{ term: 'role="alert"', regex: /role\s*=\s*["']alert["']/i },
|
|
229
|
+
{ term: 'focus-visible', regex: /:focus-visible/i },
|
|
230
|
+
{ term: 'skip-link', regex: /skip-link|skip.to.content|#main-content/i },
|
|
231
|
+
{ term: 'ErrorBoundary', regex: /ErrorBoundary/i },
|
|
232
|
+
{ term: 'AbortController', regex: /AbortController/i },
|
|
233
|
+
{ term: 'retry', regex: /retry|backoff/i },
|
|
234
|
+
{ term: 'responsive breakpoint', regex: /@media\s*\(/i },
|
|
235
|
+
{ term: 'heading hierarchy', regex: /<h[12]/i },
|
|
236
|
+
{ term: 'skeleton shimmer', regex: /skeleton|shimmer/i },
|
|
237
|
+
{ term: 'animation', regex: /animation\s*[:={]/i }
|
|
238
|
+
];
|
|
239
|
+
for (const expected of expectedOutputs) {
|
|
240
|
+
const filePaths = [];
|
|
241
|
+
let match;
|
|
242
|
+
const pathRegex = new RegExp(filePathRegex.source, 'g');
|
|
243
|
+
while ((match = pathRegex.exec(expected)) !== null) {
|
|
244
|
+
// Skip common false positives like version numbers
|
|
245
|
+
if (/^\d+\.\d+/.test(match[1]))
|
|
246
|
+
continue;
|
|
247
|
+
filePaths.push(match[1]);
|
|
248
|
+
}
|
|
249
|
+
if (filePaths.length > 0) {
|
|
250
|
+
// File-existence check: verify at least one mentioned file exists
|
|
251
|
+
const existingFiles = filePaths.filter(fp => {
|
|
252
|
+
const fullPath = path.join(worktreeDir, fp);
|
|
253
|
+
return fs.existsSync(fullPath);
|
|
254
|
+
});
|
|
255
|
+
if (existingFiles.length > 0) {
|
|
256
|
+
checks.push({
|
|
257
|
+
type: 'output',
|
|
258
|
+
description: `Expected files present: ${existingFiles.join(', ')}`,
|
|
259
|
+
required: false,
|
|
260
|
+
passed: true,
|
|
261
|
+
evidence: `${existingFiles.length}/${filePaths.length} expected files found in worktree`
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
checks.push({
|
|
266
|
+
type: 'output',
|
|
267
|
+
description: `Expected files: ${filePaths.join(', ')}`,
|
|
268
|
+
required: false,
|
|
269
|
+
passed: false,
|
|
270
|
+
reason: `None of the expected files found in ${worktreeDir}`,
|
|
271
|
+
evidence: `Looked for: ${filePaths.join(', ')}`
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
// Content check: grep existing files for key terms mentioned in expected output
|
|
275
|
+
const lowerExpected = expected.toLowerCase();
|
|
276
|
+
for (const { term, regex } of termPatterns) {
|
|
277
|
+
if (!lowerExpected.includes(term.toLowerCase().split(/[="]/)[0]))
|
|
278
|
+
continue;
|
|
279
|
+
const found = existingFiles.some(fp => {
|
|
280
|
+
try {
|
|
281
|
+
const content = fs.readFileSync(path.join(worktreeDir, fp), 'utf8');
|
|
282
|
+
return regex.test(content);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// File may be binary or permission-restricted; skip it
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
if (found) {
|
|
290
|
+
checks.push({
|
|
291
|
+
type: 'output',
|
|
292
|
+
description: `"${term}" found in output files`,
|
|
293
|
+
required: false,
|
|
294
|
+
passed: true,
|
|
295
|
+
evidence: `Matched ${term} pattern in generated code`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
// No file paths extracted; try to match key terms across all changed files
|
|
302
|
+
const lowerExpected = expected.toLowerCase();
|
|
303
|
+
const matchedTerms = [];
|
|
304
|
+
for (const { term, regex } of termPatterns) {
|
|
305
|
+
if (!lowerExpected.includes(term.toLowerCase().split(/[="]/)[0]))
|
|
306
|
+
continue;
|
|
307
|
+
// Search all non-binary files in worktree src/ and test/
|
|
308
|
+
const searchDirs = ['src', 'test', 'tests', 'server'].map(d => path.join(worktreeDir, d));
|
|
309
|
+
let found = false;
|
|
310
|
+
for (const dir of searchDirs) {
|
|
311
|
+
if (!fs.existsSync(dir))
|
|
312
|
+
continue;
|
|
313
|
+
try {
|
|
314
|
+
const files = fs.readdirSync(dir, { recursive: true, encoding: 'utf8' });
|
|
315
|
+
for (const file of files) {
|
|
316
|
+
const fullPath = path.join(dir, file);
|
|
317
|
+
try {
|
|
318
|
+
const stat = fs.statSync(fullPath);
|
|
319
|
+
if (!stat.isFile() || stat.size > 256 * 1024)
|
|
320
|
+
continue;
|
|
321
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
322
|
+
if (regex.test(content)) {
|
|
323
|
+
found = true;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Binary or permission-restricted file; skip
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
// Directory unreadable (permissions, broken symlink); skip
|
|
334
|
+
}
|
|
335
|
+
if (found)
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
if (found)
|
|
339
|
+
matchedTerms.push(term);
|
|
340
|
+
}
|
|
341
|
+
if (matchedTerms.length > 0) {
|
|
342
|
+
checks.push({
|
|
343
|
+
type: 'output',
|
|
344
|
+
description: `Expected features verified: ${matchedTerms.join(', ')}`,
|
|
345
|
+
required: false,
|
|
346
|
+
passed: true,
|
|
347
|
+
evidence: `Found evidence for: ${matchedTerms.join(', ')}`
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return checks;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Generate verification report and save to file
|
|
356
|
+
*/
|
|
357
|
+
async generateVerificationReport(result, outputPath) {
|
|
358
|
+
return (0, verification_reporters_1.generateVerificationReport)(result, outputPath);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Rollback changes from a failed step
|
|
362
|
+
*/
|
|
363
|
+
async rollback(stepNumber, branchName, filesChanged, baseBranch) {
|
|
364
|
+
const filesRestored = [];
|
|
365
|
+
try {
|
|
366
|
+
// If on a branch, switch back and delete the branch. Use the caller's
|
|
367
|
+
// explicit base branch when provided; otherwise resolve it from origin/HEAD
|
|
368
|
+
// so this works on `master` repos too, not just `main`. Never resolve to
|
|
369
|
+
// the branch we're about to delete — git refuses to delete the current
|
|
370
|
+
// branch, so the fallback must exclude it.
|
|
371
|
+
if (branchName) {
|
|
372
|
+
const target = baseBranch ?? this.resolveBaseBranch(branchName);
|
|
373
|
+
await this.runGitCommand(['checkout', target]);
|
|
374
|
+
await this.runGitCommand(['branch', '-D', branchName]);
|
|
375
|
+
}
|
|
376
|
+
// Reset any uncommitted changes
|
|
377
|
+
await this.runGitCommand(['reset', '--hard', 'HEAD']);
|
|
378
|
+
// If specific files were changed, restore them
|
|
379
|
+
if (filesChanged && filesChanged.length > 0) {
|
|
380
|
+
for (const file of filesChanged) {
|
|
381
|
+
try {
|
|
382
|
+
await this.runGitCommand(['checkout', 'HEAD', '--', file]);
|
|
383
|
+
filesRestored.push(file);
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
// File does not exist in HEAD (new file from the agent); nothing to restore
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
success: true,
|
|
392
|
+
filesRestored
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
const err = error;
|
|
397
|
+
return {
|
|
398
|
+
success: false,
|
|
399
|
+
filesRestored,
|
|
400
|
+
error: err.message
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Resolve the repo's integration branch synchronously. Mirrors
|
|
406
|
+
* WorktreeManager.resolveDefaultBranch's contract: origin/HEAD first, then
|
|
407
|
+
* the currently checked-out branch, then the literal "main" as a last resort.
|
|
408
|
+
*
|
|
409
|
+
* @param excludeBranch When rollback() is deleting a branch, the caller
|
|
410
|
+
* passes that branch here so the current-branch fallback never
|
|
411
|
+
* resolves to it (git refuses to delete the checked-out branch).
|
|
412
|
+
*/
|
|
413
|
+
resolveBaseBranch(excludeBranch) {
|
|
414
|
+
try {
|
|
415
|
+
const head = (0, child_process_1.execSync)('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
416
|
+
cwd: this.workingDir,
|
|
417
|
+
encoding: 'utf8',
|
|
418
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
419
|
+
}).trim();
|
|
420
|
+
const prefix = 'refs/remotes/origin/';
|
|
421
|
+
if (head.startsWith(prefix))
|
|
422
|
+
return head.slice(prefix.length);
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
// origin/HEAD not set; fall through
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
const current = (0, child_process_1.execSync)('git branch --show-current', {
|
|
429
|
+
cwd: this.workingDir,
|
|
430
|
+
encoding: 'utf8',
|
|
431
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
432
|
+
}).trim();
|
|
433
|
+
if (current && current !== excludeBranch)
|
|
434
|
+
return current;
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// fall through
|
|
438
|
+
}
|
|
439
|
+
return 'main';
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Run a git command
|
|
443
|
+
*/
|
|
444
|
+
async runGitCommand(args) {
|
|
445
|
+
return new Promise((resolve, reject) => {
|
|
446
|
+
const proc = (0, child_process_1.spawn)('git', args, {
|
|
447
|
+
cwd: this.workingDir
|
|
448
|
+
});
|
|
449
|
+
let stdout = '';
|
|
450
|
+
let stderr = '';
|
|
451
|
+
if (proc.stdout) {
|
|
452
|
+
proc.stdout.on('data', (data) => {
|
|
453
|
+
stdout += data.toString();
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (proc.stderr) {
|
|
457
|
+
proc.stderr.on('data', (data) => {
|
|
458
|
+
stderr += data.toString();
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
proc.on('close', (code) => {
|
|
462
|
+
if (code === 0) {
|
|
463
|
+
resolve(stdout);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
reject(new Error(stderr || `Git command failed with code ${code}`));
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
proc.on('error', (err) => {
|
|
470
|
+
reject(err);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Commit verification report with natural message
|
|
476
|
+
* Skips if file is in a gitignored path (e.g., runs/)
|
|
477
|
+
*/
|
|
478
|
+
async commitVerificationReport(reportPath, stepNumber, agentName, passed) {
|
|
479
|
+
return (0, verification_reporters_1.commitVerificationReport)(reportPath, this.workingDir, stepNumber, agentName, passed, logger);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Cross-reference hook-captured evidence with transcript-based claims.
|
|
483
|
+
* If hooks recorded errors or scope violations that the transcript does not
|
|
484
|
+
* mention, the step fails (defense in depth).
|
|
485
|
+
*/
|
|
486
|
+
crossReferenceEvidence(shareIndex, evidenceLogPath) {
|
|
487
|
+
return (0, transcript_checks_1.crossReferenceEvidence)(shareIndex, evidenceLogPath);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
exports.VerifierEngine = VerifierEngine;
|
|
491
|
+
exports.default = VerifierEngine;
|