@miraland-labs/conduit-bridge 0.16.102 → 0.16.104
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/brief.js +27 -4
- package/dist/ensure-test-evidence.js +32 -5
- package/dist/execution-class.js +1 -1
- package/dist/execution-facts.js +5 -1
- package/dist/execution.js +3 -2
- package/package.json +1 -1
package/dist/brief.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
1
|
+
import { access, readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { promisify } from "node:util";
|
|
@@ -8,18 +8,28 @@ const execFileAsync = promisify(execFile);
|
|
|
8
8
|
const MANIFESTS = [
|
|
9
9
|
"package.json", "wrangler.jsonc", "wrangler.toml", "tsconfig.json",
|
|
10
10
|
"Cargo.toml", "pyproject.toml", "pytest.ini", "setup.cfg", "go.mod",
|
|
11
|
-
"Makefile", "build.gradle", "build.gradle.kts", "gradlew",
|
|
11
|
+
"Makefile", "build.gradle", "build.gradle.kts", "gradlew", "pom.xml", "mvnw",
|
|
12
|
+
"DESCRIPTION", "renv.lock", "tests/testthat.R",
|
|
12
13
|
];
|
|
13
14
|
/** Prefer test before typecheck/lint so discovery order matches what pickVerificationCommand wants. */
|
|
14
15
|
const VERIFICATION_SCRIPTS = ["test", "verify", "typecheck", "lint", "build"];
|
|
15
16
|
export const MAKE_VERIFICATION_TARGETS = ["test", "check", "verify", "replay", "typecheck", "lint", "build"];
|
|
16
17
|
const EXCLUDED_DIRECTORIES = new Set(["node_modules", "dist", "build", "target", "coverage", ".venv", "venv"]);
|
|
18
|
+
function rDescriptionUsesTestthat(text) {
|
|
19
|
+
return [...text.matchAll(/^(?:Depends|Imports|Suggests):[^\n]*(?:\n[ \t]+[^\n]*)*/gmi)]
|
|
20
|
+
.some((field) => /\btestthat\b/i.test(field[0]));
|
|
21
|
+
}
|
|
17
22
|
export async function buildWorkspaceBrief(workspace) {
|
|
18
23
|
const entries = await readdir(workspace, { withFileTypes: true });
|
|
19
24
|
const modules = entries
|
|
20
25
|
.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && !EXCLUDED_DIRECTORIES.has(entry.name))
|
|
21
26
|
.map((entry) => entry.name).sort().slice(0, 30);
|
|
22
27
|
const files = new Set(entries.filter((entry) => entry.isFile()).map((entry) => entry.name));
|
|
28
|
+
try {
|
|
29
|
+
await access(join(workspace, "tests", "testthat.R"));
|
|
30
|
+
files.add("tests/testthat.R");
|
|
31
|
+
}
|
|
32
|
+
catch { /* optional nested R test entrypoint */ }
|
|
23
33
|
const manifests = MANIFESTS.filter((name) => files.has(name));
|
|
24
34
|
const declared = await readDeclaredVerification(workspace);
|
|
25
35
|
return {
|
|
@@ -258,8 +268,21 @@ export async function discoverVerificationCommands(workspace, files) {
|
|
|
258
268
|
}
|
|
259
269
|
}
|
|
260
270
|
if (files.has("build.gradle") || files.has("build.gradle.kts")) {
|
|
261
|
-
|
|
262
|
-
|
|
271
|
+
commands.push(files.has("gradlew") ? "./gradlew test" : "gradle test");
|
|
272
|
+
}
|
|
273
|
+
if (files.has("pom.xml")) {
|
|
274
|
+
commands.push(files.has("mvnw") ? "./mvnw test" : "mvn test");
|
|
275
|
+
}
|
|
276
|
+
if (files.has("DESCRIPTION")) {
|
|
277
|
+
try {
|
|
278
|
+
const description = await readFile(join(workspace, "DESCRIPTION"), "utf8");
|
|
279
|
+
if (rDescriptionUsesTestthat(description))
|
|
280
|
+
commands.push("Rscript -e testthat::test_local()");
|
|
281
|
+
}
|
|
282
|
+
catch { /* unreadable manifests do not broaden execution */ }
|
|
283
|
+
}
|
|
284
|
+
else if (files.has("tests/testthat.R")) {
|
|
285
|
+
commands.push("Rscript tests/testthat.R");
|
|
263
286
|
}
|
|
264
287
|
// The heartbeat schema caps `verification` at 10. A gate-rich checkout discovers more than that,
|
|
265
288
|
// and the whole heartbeat was then refused: the computer went dark with no reason on any card.
|
|
@@ -29,7 +29,7 @@ export function needsTestEvidence(spec, grants) {
|
|
|
29
29
|
* is not satisfied by a silent `npm run typecheck`.
|
|
30
30
|
*/
|
|
31
31
|
export function isPreferentialTestCommand(command) {
|
|
32
|
-
return /^(npm run test|pnpm test|yarn test|cargo test|go test(?: \.\/\.\.\.)?|make test|python3? -m (?:pytest|unittest)(?: [\w./=:-]+)*|pytest(?: [\w./=-]+)
|
|
32
|
+
return /^(npm run test|pnpm test|yarn test|cargo test|go test(?: \.\/\.\.\.)?|make test|python3? -m (?:pytest|unittest)(?: [\w./=:-]+)*|pytest(?: [\w./=-]+)?|(?:\.\/gradlew|gradle|\.\/mvnw|mvn) test|Rscript tests\/testthat\.R|Rscript -e testthat::test_local\(\))$/.test(command.trim());
|
|
33
33
|
}
|
|
34
34
|
export function pickVerificationCommand(commands) {
|
|
35
35
|
const bounded = commands
|
|
@@ -45,14 +45,24 @@ export function pickVerificationCommand(commands) {
|
|
|
45
45
|
* a declared gate neither dry-run nor witnessed unless an acceptance criterion happened to name it.
|
|
46
46
|
* So the declared gates run beside the aggregate, in declared order, de-duplicated.
|
|
47
47
|
*/
|
|
48
|
-
export function verificationEvidenceCommands(commands, declared = []) {
|
|
48
|
+
export function verificationEvidenceCommands(commands, declared = [], acceptance = null) {
|
|
49
49
|
const bounded = [...new Set(commands
|
|
50
50
|
.map((command) => command.trim())
|
|
51
51
|
.filter((command) => command && isRunnableVerificationCommand(command)))];
|
|
52
|
+
const declaredGates = bounded.filter((command) => declared.some((line) => line.trim() === command));
|
|
53
|
+
// T191: the gate is what the project declared for itself plus what the contract will be judged
|
|
54
|
+
// on — the commands its acceptance criteria name. Every other discovered script (this repository's
|
|
55
|
+
// ten-minute `npm run verify` for a docs change judged on `npm run typecheck`) is a candidate the
|
|
56
|
+
// planner may name, not a gate the Bridge runs unasked. When the criteria name no command the
|
|
57
|
+
// discovered rule below decides, as before (T31: a declared gate runs beside the aggregate).
|
|
58
|
+
if (acceptance !== null) {
|
|
59
|
+
const named = acceptanceVerificationCommands([...acceptance], commands).commands;
|
|
60
|
+
if (named.length)
|
|
61
|
+
return [...new Set([...declaredGates, ...named])];
|
|
62
|
+
}
|
|
52
63
|
const aggregate = bounded.find((command) => /^(?:npm run|pnpm|yarn) verify$|^make check$/.test(command));
|
|
53
64
|
if (!aggregate)
|
|
54
65
|
return bounded;
|
|
55
|
-
const declaredGates = bounded.filter((command) => declared.some((line) => line.trim() === command));
|
|
56
66
|
return [...new Set([...declaredGates, aggregate])];
|
|
57
67
|
}
|
|
58
68
|
/**
|
|
@@ -91,7 +101,7 @@ export function verificationCommandsMentioned(text, offered = []) {
|
|
|
91
101
|
const source = text.slice(0, 20_000);
|
|
92
102
|
const verbatim = [...new Set(offered.map((command) => command.trim()))]
|
|
93
103
|
.filter((command) => isBoundedVerificationCommand(command) && textNamesCommand(source, command));
|
|
94
|
-
const pattern = /(?:^|[\s("'`])((?:npm run [A-Za-z0-9][A-Za-z0-9._:-]*|pnpm [A-Za-z0-9][A-Za-z0-9._:-]*|yarn [A-Za-z0-9][A-Za-z0-9._:-]*|cargo (?:test|check)|go test(?: \.\/\.\.\.)?|make [A-Za-z0-9][A-Za-z0-9._-]*|python3? -m [A-Za-z0-9_][\w.]*(?: [\w./=:-]*[./=-][\w./=:-]*)*|pytest(?: [\w./=-]*[./=-][\w./=-]*)?|python3? [\w./-]+\.py(?: [\w./=:-]*[./=-][\w./=:-]*)
|
|
104
|
+
const pattern = /(?:^|[\s("'`])((?:npm run [A-Za-z0-9][A-Za-z0-9._:-]*|pnpm [A-Za-z0-9][A-Za-z0-9._:-]*|yarn [A-Za-z0-9][A-Za-z0-9._:-]*|cargo (?:test|check)|go test(?: \.\/\.\.\.)?|make [A-Za-z0-9][A-Za-z0-9._-]*|python3? -m [A-Za-z0-9_][\w.]*(?: [\w./=:-]*[./=-][\w./=:-]*)*|pytest(?: [\w./=-]*[./=-][\w./=-]*)?|python3? [\w./-]+\.py(?: [\w./=:-]*[./=-][\w./=:-]*)*|(?:\.\/gradlew|gradle|\.\/mvnw|mvn) test|Rscript tests\/testthat\.R|Rscript -e testthat::test_local\(\)))(?=$|[\s"'`),.;:])/g;
|
|
95
105
|
const mentions = [...new Set([...source.matchAll(pattern)].map((match) => match[1]))]
|
|
96
106
|
.filter((mention) => isBoundedVerificationCommand(mention)
|
|
97
107
|
&& !verbatim.some((command) => command === mention || command.startsWith(`${mention} `)));
|
|
@@ -423,6 +433,23 @@ export function detectGateRanNothing(command, stdout, stderr, code) {
|
|
|
423
433
|
if (/^go test\b/.test(trimmed) && code === 0 && /\[no test files\]/.test(combined) && !/^ok\s/m.test(combined)) {
|
|
424
434
|
return "gate ran no tests";
|
|
425
435
|
}
|
|
436
|
+
if (/^(?:\.\/mvnw|mvn) test$/.test(trimmed)
|
|
437
|
+
&& /\bNo tests to run\b/i.test(combined)
|
|
438
|
+
&& !/\bTests run:\s*[1-9]\d*/i.test(combined)) {
|
|
439
|
+
return "gate ran no tests";
|
|
440
|
+
}
|
|
441
|
+
if (/^(?:\.\/gradlew|gradle) test$/.test(trimmed)) {
|
|
442
|
+
const testTasks = combined.split(/\r?\n/)
|
|
443
|
+
.map((line) => line.trim().replace(/^> Task /, ""))
|
|
444
|
+
.filter((line) => /^:(?:[^:\s]+:)*test(?:\s|$)/.test(line));
|
|
445
|
+
if (testTasks.length > 0 && testTasks.every((line) => /\sNO-SOURCE\b/.test(line))) {
|
|
446
|
+
return "gate ran no tests";
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (/^Rscript (?:tests\/testthat\.R|-e testthat::test_local\(\))$/.test(trimmed)
|
|
450
|
+
&& /\bNo test files found\b/i.test(combined)) {
|
|
451
|
+
return "gate ran no tests";
|
|
452
|
+
}
|
|
426
453
|
const firstLine = combined.trim().split("\n")[0] ?? "";
|
|
427
454
|
if ((code === 1 || code === 2) && /^usage:/i.test(firstLine)) {
|
|
428
455
|
return "gate printed usage";
|
|
@@ -458,7 +485,7 @@ export async function ensureTestEvidence(input) {
|
|
|
458
485
|
// The project's gate first, then every bounded command the criteria name that the workspace
|
|
459
486
|
// authorizes. Each is witnessed on its own, so the reviewer sees the criterion's own proof.
|
|
460
487
|
const commands = [...new Set([
|
|
461
|
-
...verificationEvidenceCommands(input.verificationCommands, input.declaredVerificationCommands ?? []),
|
|
488
|
+
...verificationEvidenceCommands(input.verificationCommands, input.declaredVerificationCommands ?? [], acceptance),
|
|
462
489
|
...named.commands,
|
|
463
490
|
])];
|
|
464
491
|
if (commands.length === 0) {
|
package/dist/execution-class.js
CHANGED
|
@@ -13,7 +13,7 @@ export function isBoundedVerificationCommand(command) {
|
|
|
13
13
|
// A `*` is a legal character in a Python argument (`-p test_*.py` is how a repository spells its
|
|
14
14
|
// own gate). Bridge starts a gate with execFile and no shell, so the star stays literal and no
|
|
15
15
|
// expansion can happen. Refusing it only dropped the declaration, with no reason given.
|
|
16
|
-
return /^(npm run [A-Za-z0-9][A-Za-z0-9._:-]*|pnpm [A-Za-z0-9][A-Za-z0-9._:-]*|yarn [A-Za-z0-9][A-Za-z0-9._:-]*|cargo (test|check)|go test(?: \.\/\.\.\.)?|make [A-Za-z0-9][A-Za-z0-9._-]*|python3? -m [A-Za-z0-9_][\w.]*(?: [\w./=:*-]+)*|pytest(?: [\w./=*-]+)?|python3? [\w./-]+\.py(?: [\w./=:-]+)
|
|
16
|
+
return /^(npm run [A-Za-z0-9][A-Za-z0-9._:-]*|pnpm [A-Za-z0-9][A-Za-z0-9._:-]*|yarn [A-Za-z0-9][A-Za-z0-9._:-]*|cargo (test|check)|go test(?: \.\/\.\.\.)?|make [A-Za-z0-9][A-Za-z0-9._-]*|python3? -m [A-Za-z0-9_][\w.]*(?: [\w./=:*-]+)*|pytest(?: [\w./=*-]+)?|python3? [\w./-]+\.py(?: [\w./=:-]+)*|(?:\.\/gradlew|gradle|\.\/mvnw|mvn) test|Rscript tests\/testthat\.R|Rscript -e testthat::test_local\(\))$/.test(command);
|
|
17
17
|
}
|
|
18
18
|
/** Shell commands each grant authorizes — mapped per driver so they cannot drift. */
|
|
19
19
|
export const branchCreateCommands = [
|
package/dist/execution-facts.js
CHANGED
|
@@ -15,6 +15,10 @@ export const BOOTSTRAP_RESULTS = ["not_applicable", "not_run", "installed", "fai
|
|
|
15
15
|
const LOCKFILE_NAMES = [
|
|
16
16
|
"package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml",
|
|
17
17
|
"Cargo.lock", "poetry.lock", "Pipfile.lock", "go.sum", "Gemfile.lock", "composer.lock",
|
|
18
|
+
"gradle.lockfile", "renv.lock",
|
|
19
|
+
];
|
|
20
|
+
const JAVASCRIPT_LOCKFILE_NAMES = [
|
|
21
|
+
"package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml",
|
|
18
22
|
];
|
|
19
23
|
/**
|
|
20
24
|
* The facts the control plane refuses to see change once it holds them, in the order it compares
|
|
@@ -114,7 +118,7 @@ export async function bootstrapResultForWorkspace(workspace) {
|
|
|
114
118
|
return "not_applicable";
|
|
115
119
|
}
|
|
116
120
|
try {
|
|
117
|
-
const lockfile = await Promise.any(
|
|
121
|
+
const lockfile = await Promise.any(JAVASCRIPT_LOCKFILE_NAMES.map(async (name) => {
|
|
118
122
|
await access(join(workspace, name));
|
|
119
123
|
return true;
|
|
120
124
|
}));
|
package/dist/execution.js
CHANGED
|
@@ -1285,7 +1285,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1285
1285
|
await removeReleasedDiagnosticWorktree(response);
|
|
1286
1286
|
return;
|
|
1287
1287
|
}
|
|
1288
|
-
const evidenceCommands = verificationEvidenceCommands(attemptBrief?.verification ?? [], attemptBrief?.declared_verification ?? []);
|
|
1288
|
+
const evidenceCommands = verificationEvidenceCommands(attemptBrief?.verification ?? [], attemptBrief?.declared_verification ?? [], spec.acceptance ?? []);
|
|
1289
1289
|
// Ahead of the dry run and of the model: the contract requires test evidence and this worktree
|
|
1290
1290
|
// offers no command that can witness it. Every fact is known here — the required evidence, the
|
|
1291
1291
|
// test_run grant, the attempt worktree's own gates — and the same question was asked only after
|
|
@@ -1323,6 +1323,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1323
1323
|
workspace: attemptWorkspace,
|
|
1324
1324
|
verificationCommands: attemptBrief?.verification ?? [],
|
|
1325
1325
|
declaredVerificationCommands: attemptBrief?.declared_verification ?? [],
|
|
1326
|
+
acceptance: spec.acceptance ?? [],
|
|
1326
1327
|
changeScope: spec.change_scope ?? [],
|
|
1327
1328
|
});
|
|
1328
1329
|
if (dryRun.ranNothing) {
|
|
@@ -2791,7 +2792,7 @@ export function verificationScopeConflictDetail(paths, changeScope) {
|
|
|
2791
2792
|
* discarded before the agent starts, so the agent always begins in the tree of the base commit.
|
|
2792
2793
|
*/
|
|
2793
2794
|
export async function verificationScopeDryRun(input) {
|
|
2794
|
-
const commands = verificationEvidenceCommands(input.verificationCommands, input.declaredVerificationCommands ?? []);
|
|
2795
|
+
const commands = verificationEvidenceCommands(input.verificationCommands, input.declaredVerificationCommands ?? [], input.acceptance ?? null);
|
|
2795
2796
|
if (!commands.length)
|
|
2796
2797
|
return { ranNothing: null, redBase: null, outsideScope: [] };
|
|
2797
2798
|
const run = input.runCommand ?? runBoundedVerificationCommand;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.104",
|
|
4
4
|
"description": "Conduit Bridge CLI \u2014 join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|