@agentbridge1/cli 0.0.10 → 0.0.11
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/dist/build-info.json +3 -3
- package/dist/commands/watch.js +116 -9
- package/dist/contract-intelligence.js +45 -18
- package/dist/contract-verdict.js +9 -2
- package/dist/domain-keywords.js +119 -0
- package/dist/index.js +4 -0
- package/dist/intent-parser.js +8 -20
- package/dist/mcp/agentbridge-mcp.js +124 -26
- package/dist/mcp/agentbridge-mcp.js.map +4 -4
- package/dist/proof-parser.js +24 -25
- package/dist/test-runner.js +134 -46
- package/package.json +1 -1
package/dist/proof-parser.js
CHANGED
|
@@ -6,15 +6,7 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.parseProofOutput = parseProofOutput;
|
|
8
8
|
exports.proofEvidenceString = proofEvidenceString;
|
|
9
|
-
const
|
|
10
|
-
auth: ["auth", "login", "logout", "session", "token", "password", "oauth", "signin", "signup"],
|
|
11
|
-
database: ["db", "database", "prisma", "migration", "schema", "query", "model"],
|
|
12
|
-
api: ["api", "route", "endpoint", "handler", "request", "response"],
|
|
13
|
-
payments: ["payment", "billing", "stripe", "invoice", "webhook", "subscription"],
|
|
14
|
-
mcp: ["mcp", "setup", "rules", "config", "agentbridge"],
|
|
15
|
-
ui: ["component", "render", "view", "style", "page", "layout"],
|
|
16
|
-
tests: ["test", "spec"],
|
|
17
|
-
};
|
|
9
|
+
const domain_keywords_1 = require("./domain-keywords");
|
|
18
10
|
// Vitest / Jest summary: "12 passed", "Tests: 5 passed", "5 failed"
|
|
19
11
|
const PASSED_RE = /(\d+)\s+passed/i;
|
|
20
12
|
const FAILED_RE = /(\d+)\s+failed/i;
|
|
@@ -41,24 +33,30 @@ function detectRunner(stdout) {
|
|
|
41
33
|
}
|
|
42
34
|
function extractDomainKeywords(testNames) {
|
|
43
35
|
const found = new Set();
|
|
44
|
-
const
|
|
45
|
-
for (const
|
|
46
|
-
if (
|
|
36
|
+
const text = testNames.join(" ");
|
|
37
|
+
for (const domain of Object.keys(domain_keywords_1.DOMAIN_KEYWORDS)) {
|
|
38
|
+
if ((0, domain_keywords_1.matchesDomainKeywords)(text, domain))
|
|
47
39
|
found.add(domain);
|
|
48
40
|
}
|
|
49
41
|
return [...found];
|
|
50
42
|
}
|
|
43
|
+
function normalizeCoverageDomain(domain) {
|
|
44
|
+
const lower = domain.trim().toLowerCase();
|
|
45
|
+
if (lower in domain_keywords_1.DOMAIN_KEYWORDS)
|
|
46
|
+
return lower;
|
|
47
|
+
if (lower === "db")
|
|
48
|
+
return "database";
|
|
49
|
+
if (lower === "payment")
|
|
50
|
+
return "payments";
|
|
51
|
+
if (lower === "test")
|
|
52
|
+
return "tests";
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
51
55
|
function hasDomainCoverage(domain, domainKeywordsInPassed) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
payments: ["payments"],
|
|
57
|
-
mcp: ["mcp"],
|
|
58
|
-
ui: ["ui"],
|
|
59
|
-
};
|
|
60
|
-
const check = aliases[domain] ?? [domain];
|
|
61
|
-
return check.some((alias) => domainKeywordsInPassed.includes(alias));
|
|
56
|
+
const canonical = normalizeCoverageDomain(domain);
|
|
57
|
+
if (!canonical)
|
|
58
|
+
return false;
|
|
59
|
+
return domainKeywordsInPassed.includes(canonical);
|
|
62
60
|
}
|
|
63
61
|
function parseProofOutput(proofRun) {
|
|
64
62
|
const stdout = proofRun.stdoutExcerpt ?? "";
|
|
@@ -106,12 +104,13 @@ function parseProofOutput(proofRun) {
|
|
|
106
104
|
function proofEvidenceString(parsed, domain) {
|
|
107
105
|
const total = parsed.passed + parsed.failed + parsed.skipped;
|
|
108
106
|
const base = `Proof run ${parsed.failed > 0 ? "failed" : "passed"} (${parsed.passed}/${total} tests passed).`;
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
const canonicalDomain = domain ? normalizeCoverageDomain(domain) : null;
|
|
108
|
+
if (canonicalDomain && parsed.passed > 0) {
|
|
109
|
+
if (parsed.has_domain_coverage(canonicalDomain)) {
|
|
111
110
|
const covering = parsed.domain_keywords_in_passed.join(", ");
|
|
112
111
|
return `${base} Tests covering: ${covering}.`;
|
|
113
112
|
}
|
|
114
|
-
const keywords =
|
|
113
|
+
const keywords = domain_keywords_1.DOMAIN_KEYWORDS[canonicalDomain]?.slice(0, 3).join(", ") ?? canonicalDomain;
|
|
115
114
|
return `${base} No passed tests mention ${keywords} — domain coverage unconfirmed.`;
|
|
116
115
|
}
|
|
117
116
|
return base;
|
package/dist/test-runner.js
CHANGED
|
@@ -10,6 +10,96 @@ exports.analyzeCoherence = analyzeCoherence;
|
|
|
10
10
|
const node_fs_1 = require("node:fs");
|
|
11
11
|
const node_path_1 = require("node:path");
|
|
12
12
|
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
function detectRunnerFromCommand(command) {
|
|
14
|
+
const lower = command.toLowerCase();
|
|
15
|
+
if (/\bvitest\b/.test(lower))
|
|
16
|
+
return "vitest";
|
|
17
|
+
if (/\bjest\b/.test(lower))
|
|
18
|
+
return "jest";
|
|
19
|
+
if (/\bpytest\b/.test(lower))
|
|
20
|
+
return "pytest";
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
function quoteArg(value) {
|
|
24
|
+
return `"${value.replaceAll(`"`, `\\"`)}"`;
|
|
25
|
+
}
|
|
26
|
+
function classifyFailure(stdout, stderr, timedOut) {
|
|
27
|
+
if (timedOut)
|
|
28
|
+
return "timeout";
|
|
29
|
+
const combined = `${stdout}\n${stderr}`.toLowerCase();
|
|
30
|
+
if (combined.includes("unknown option") ||
|
|
31
|
+
combined.includes("unknown argument") ||
|
|
32
|
+
combined.includes("unrecognized option") ||
|
|
33
|
+
combined.includes("cacerror")) {
|
|
34
|
+
return "command_invalid";
|
|
35
|
+
}
|
|
36
|
+
if (combined.includes("command not found") ||
|
|
37
|
+
combined.includes("enoent") ||
|
|
38
|
+
combined.includes("cannot find module") ||
|
|
39
|
+
combined.includes("is not recognized as an internal or external command")) {
|
|
40
|
+
return "tooling_missing";
|
|
41
|
+
}
|
|
42
|
+
if (extractFailedTestNames(`${stdout}\n${stderr}`).length > 0) {
|
|
43
|
+
return "test_failures";
|
|
44
|
+
}
|
|
45
|
+
if (combined.includes("failed to load config") ||
|
|
46
|
+
combined.includes("syntaxerror") ||
|
|
47
|
+
combined.includes("typeerror") ||
|
|
48
|
+
combined.includes("referenceerror")) {
|
|
49
|
+
return "runtime_error";
|
|
50
|
+
}
|
|
51
|
+
return "test_failures";
|
|
52
|
+
}
|
|
53
|
+
async function runSingleCommand(command, options) {
|
|
54
|
+
const started = Date.now();
|
|
55
|
+
return new Promise((resolveResult) => {
|
|
56
|
+
const child = (0, node_child_process_1.spawn)(command, {
|
|
57
|
+
shell: true,
|
|
58
|
+
cwd: options.cwd,
|
|
59
|
+
env: process.env,
|
|
60
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
61
|
+
});
|
|
62
|
+
let stdout = "";
|
|
63
|
+
let stderr = "";
|
|
64
|
+
let didTimeout = false;
|
|
65
|
+
child.stdout?.on("data", (chunk) => {
|
|
66
|
+
stdout += chunk.toString();
|
|
67
|
+
});
|
|
68
|
+
child.stderr?.on("data", (chunk) => {
|
|
69
|
+
stderr += chunk.toString();
|
|
70
|
+
});
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
didTimeout = true;
|
|
73
|
+
child.kill("SIGTERM");
|
|
74
|
+
const durationMs = Date.now() - started;
|
|
75
|
+
resolveResult({
|
|
76
|
+
command,
|
|
77
|
+
passed: false,
|
|
78
|
+
stdout,
|
|
79
|
+
stderr: `${stderr}\nTimed out after ${options.timeoutMs}ms`,
|
|
80
|
+
durationMs,
|
|
81
|
+
timedOut: true,
|
|
82
|
+
failureType: "timeout",
|
|
83
|
+
});
|
|
84
|
+
}, options.timeoutMs);
|
|
85
|
+
child.on("close", (code) => {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
if (didTimeout)
|
|
88
|
+
return; // already resolved
|
|
89
|
+
const durationMs = Date.now() - started;
|
|
90
|
+
const passed = code === 0;
|
|
91
|
+
resolveResult({
|
|
92
|
+
command,
|
|
93
|
+
passed,
|
|
94
|
+
stdout,
|
|
95
|
+
stderr,
|
|
96
|
+
durationMs,
|
|
97
|
+
timedOut: false,
|
|
98
|
+
failureType: passed ? undefined : classifyFailure(stdout, stderr, false),
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
13
103
|
/**
|
|
14
104
|
* Detect the appropriate test command for the project rooted at `cwd`.
|
|
15
105
|
* Checks config override first, then language-specific conventions.
|
|
@@ -75,16 +165,25 @@ function detectTestCommand(cwd = process.cwd()) {
|
|
|
75
165
|
function buildScopedCommand(base, relatedFiles) {
|
|
76
166
|
if (!relatedFiles.length)
|
|
77
167
|
return base;
|
|
78
|
-
|
|
79
|
-
|
|
168
|
+
const runner = detectRunnerFromCommand(base);
|
|
169
|
+
// Jest supports --testPathPattern (regex)
|
|
170
|
+
if (runner === "jest") {
|
|
80
171
|
// Build a pattern that matches the test files corresponding to the changed source files.
|
|
81
172
|
// e.g. "src/foo.ts" → look for "src/foo" anywhere in test paths.
|
|
82
173
|
const stems = relatedFiles.map((f) => f.replace(/\.[^/.]+$/, "").replace(/\//g, "\\/"));
|
|
83
174
|
const pattern = stems.join("|");
|
|
84
175
|
return `${base} --testPathPattern="${pattern}"`;
|
|
85
176
|
}
|
|
177
|
+
// Vitest supports positional filters; avoid jest-only --testPathPattern.
|
|
178
|
+
if (runner === "vitest") {
|
|
179
|
+
const stems = relatedFiles.map((f) => f.replace(/\.[^/.]+$/, "").replaceAll("\\", "/"));
|
|
180
|
+
const unique = [...new Set(stems)].filter(Boolean).slice(0, 10);
|
|
181
|
+
if (!unique.length)
|
|
182
|
+
return base;
|
|
183
|
+
return `${base} ${unique.map(quoteArg).join(" ")}`;
|
|
184
|
+
}
|
|
86
185
|
// pytest supports positional file/dir args (best effort)
|
|
87
|
-
if (
|
|
186
|
+
if (runner === "pytest") {
|
|
88
187
|
const pyFiles = relatedFiles
|
|
89
188
|
.map((f) => f.replace(/\.ts$/, ".py"))
|
|
90
189
|
.filter((f) => (0, node_fs_1.existsSync)(f));
|
|
@@ -125,52 +224,41 @@ async function runDetectedTests(optionsOrTimeout = {}) {
|
|
|
125
224
|
const baseCommand = detectTestCommand(cwd);
|
|
126
225
|
if (!baseCommand)
|
|
127
226
|
return null;
|
|
128
|
-
const
|
|
227
|
+
const scopedCommand = options.relatedFiles?.length
|
|
129
228
|
? buildScopedCommand(baseCommand, options.relatedFiles)
|
|
130
229
|
: baseCommand;
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
230
|
+
const scopedResult = await runSingleCommand(scopedCommand, { cwd, timeoutMs });
|
|
231
|
+
const attempts = [
|
|
232
|
+
{
|
|
233
|
+
command: scopedResult.command,
|
|
234
|
+
passed: scopedResult.passed,
|
|
235
|
+
durationMs: scopedResult.durationMs,
|
|
236
|
+
timedOut: Boolean(scopedResult.timedOut),
|
|
237
|
+
failureType: scopedResult.failureType,
|
|
238
|
+
},
|
|
239
|
+
];
|
|
240
|
+
if (scopedCommand !== baseCommand &&
|
|
241
|
+
!scopedResult.passed &&
|
|
242
|
+
(scopedResult.failureType === "command_invalid" || scopedResult.failureType === "tooling_missing")) {
|
|
243
|
+
const fallbackResult = await runSingleCommand(baseCommand, { cwd, timeoutMs });
|
|
244
|
+
attempts.push({
|
|
245
|
+
command: fallbackResult.command,
|
|
246
|
+
passed: fallbackResult.passed,
|
|
247
|
+
durationMs: fallbackResult.durationMs,
|
|
248
|
+
timedOut: Boolean(fallbackResult.timedOut),
|
|
249
|
+
failureType: fallbackResult.failureType,
|
|
144
250
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
durationMs: Date.now() - started,
|
|
157
|
-
timedOut: true,
|
|
158
|
-
});
|
|
159
|
-
}, timeoutMs);
|
|
160
|
-
child.on("close", (code) => {
|
|
161
|
-
clearTimeout(timer);
|
|
162
|
-
if (didTimeout)
|
|
163
|
-
return; // already resolved
|
|
164
|
-
resolveResult({
|
|
165
|
-
command,
|
|
166
|
-
passed: code === 0,
|
|
167
|
-
stdout,
|
|
168
|
-
stderr,
|
|
169
|
-
durationMs: Date.now() - started,
|
|
170
|
-
timedOut: false,
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
});
|
|
251
|
+
return {
|
|
252
|
+
...fallbackResult,
|
|
253
|
+
attempts,
|
|
254
|
+
fallbackUsed: true,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
...scopedResult,
|
|
259
|
+
attempts,
|
|
260
|
+
fallbackUsed: false,
|
|
261
|
+
};
|
|
174
262
|
}
|
|
175
263
|
/** Common English stop words to strip from intent before keyword extraction. */
|
|
176
264
|
const STOP_WORDS = new Set([
|