@miraland-labs/conduit-bridge 0.16.102 → 0.16.103

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 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
- if (files.has("gradlew"))
262
- commands.push("./gradlew test");
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./=-]+)?|\.\/gradlew test)$/.test(command.trim());
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
@@ -91,7 +91,7 @@ export function verificationCommandsMentioned(text, offered = []) {
91
91
  const source = text.slice(0, 20_000);
92
92
  const verbatim = [...new Set(offered.map((command) => command.trim()))]
93
93
  .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./=:-]*)*|\.\/gradlew test))(?=$|[\s"'`),.;:])/g;
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./=:-]*)*|(?:\.\/gradlew|gradle|\.\/mvnw|mvn) test|Rscript tests\/testthat\.R|Rscript -e testthat::test_local\(\)))(?=$|[\s"'`),.;:])/g;
95
95
  const mentions = [...new Set([...source.matchAll(pattern)].map((match) => match[1]))]
96
96
  .filter((mention) => isBoundedVerificationCommand(mention)
97
97
  && !verbatim.some((command) => command === mention || command.startsWith(`${mention} `)));
@@ -423,6 +423,23 @@ export function detectGateRanNothing(command, stdout, stderr, code) {
423
423
  if (/^go test\b/.test(trimmed) && code === 0 && /\[no test files\]/.test(combined) && !/^ok\s/m.test(combined)) {
424
424
  return "gate ran no tests";
425
425
  }
426
+ if (/^(?:\.\/mvnw|mvn) test$/.test(trimmed)
427
+ && /\bNo tests to run\b/i.test(combined)
428
+ && !/\bTests run:\s*[1-9]\d*/i.test(combined)) {
429
+ return "gate ran no tests";
430
+ }
431
+ if (/^(?:\.\/gradlew|gradle) test$/.test(trimmed)) {
432
+ const testTasks = combined.split(/\r?\n/)
433
+ .map((line) => line.trim().replace(/^> Task /, ""))
434
+ .filter((line) => /^:(?:[^:\s]+:)*test(?:\s|$)/.test(line));
435
+ if (testTasks.length > 0 && testTasks.every((line) => /\sNO-SOURCE\b/.test(line))) {
436
+ return "gate ran no tests";
437
+ }
438
+ }
439
+ if (/^Rscript (?:tests\/testthat\.R|-e testthat::test_local\(\))$/.test(trimmed)
440
+ && /\bNo test files found\b/i.test(combined)) {
441
+ return "gate ran no tests";
442
+ }
426
443
  const firstLine = combined.trim().split("\n")[0] ?? "";
427
444
  if ((code === 1 || code === 2) && /^usage:/i.test(firstLine)) {
428
445
  return "gate printed usage";
@@ -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./=:-]+)*|\.\/gradlew test)$/.test(command);
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 = [
@@ -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(LOCKFILE_NAMES.map(async (name) => {
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.102",
3
+ "version": "0.16.103",
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": {