baro-ai 0.118.4 → 0.118.7
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/bin/baro +14 -2
- package/bin/self-update.mjs +122 -0
- package/dist/cli.mjs +124 -5
- package/dist/cli.mjs.map +1 -1
- package/dist/runner.mjs +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +11 -2
package/bin/baro
CHANGED
|
@@ -3,9 +3,21 @@
|
|
|
3
3
|
// shebang, and a #!/bin/sh bin makes the Windows shim invoke sh.exe (absent on
|
|
4
4
|
// Windows). A node shebang yields correct cmd/ps1/sh shims on every platform.
|
|
5
5
|
import { spawnSync } from "node:child_process"
|
|
6
|
-
import { existsSync } from "node:fs"
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
7
7
|
import { homedir } from "node:os"
|
|
8
8
|
import path from "node:path"
|
|
9
|
+
import { fileURLToPath } from "node:url"
|
|
10
|
+
|
|
11
|
+
import { selfUpdate } from "./self-update.mjs"
|
|
12
|
+
|
|
13
|
+
const argv = process.argv.slice(2)
|
|
14
|
+
try {
|
|
15
|
+
const packageJson = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json")
|
|
16
|
+
const packageVersion = JSON.parse(readFileSync(packageJson, "utf8")).version
|
|
17
|
+
await selfUpdate({ packageVersion, argv })
|
|
18
|
+
} catch {
|
|
19
|
+
// An update that cannot be attempted never stops baro from starting.
|
|
20
|
+
}
|
|
9
21
|
|
|
10
22
|
const bin = path.join(
|
|
11
23
|
homedir(),
|
|
@@ -19,7 +31,7 @@ if (!existsSync(bin)) {
|
|
|
19
31
|
process.exit(1)
|
|
20
32
|
}
|
|
21
33
|
|
|
22
|
-
const result = spawnSync(bin,
|
|
34
|
+
const result = spawnSync(bin, argv, { stdio: "inherit" })
|
|
23
35
|
if (result.error) {
|
|
24
36
|
console.error(`baro: failed to launch binary: ${result.error.message}`)
|
|
25
37
|
process.exit(1)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// A hand-started baro brings itself up to date before it runs. The runner
|
|
2
|
+
// already did this for services; a person running `baro` stayed on whatever
|
|
3
|
+
// they had installed until they noticed the banner.
|
|
4
|
+
import { spawnSync } from "node:child_process"
|
|
5
|
+
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"
|
|
6
|
+
import { homedir } from "node:os"
|
|
7
|
+
import path from "node:path"
|
|
8
|
+
|
|
9
|
+
const CACHE_TTL_MS = 3 * 3600_000
|
|
10
|
+
const FETCH_TIMEOUT_MS = 2500
|
|
11
|
+
const QUICK_COMMANDS = new Set(["--version", "-V", "--help", "-h", "connect"])
|
|
12
|
+
|
|
13
|
+
export function semverLt(a, b) {
|
|
14
|
+
const parts = (v) => String(v).split(".").map((x) => Number.parseInt(x, 10) || 0)
|
|
15
|
+
const [pa, pb] = [parts(a), parts(b)]
|
|
16
|
+
for (let i = 0; i < 3; i++) {
|
|
17
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) < (pb[i] ?? 0)
|
|
18
|
+
}
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Why this invocation must not update, or null when it may. Automation keeps
|
|
24
|
+
* the version it was given; the runner updates itself between runs; and the
|
|
25
|
+
* staged bundles are shared, so replacing them under a live run breaks it.
|
|
26
|
+
*/
|
|
27
|
+
export function skipReason({ argv, env, isTTY, liveRuns }) {
|
|
28
|
+
if (env.BARO_NO_SELF_UPDATE === "1") return "BARO_NO_SELF_UPDATE=1"
|
|
29
|
+
if (env.CI) return "CI"
|
|
30
|
+
if (!isTTY) return "not a terminal"
|
|
31
|
+
if (argv.includes("--headless")) return "headless"
|
|
32
|
+
if (argv.some((arg) => QUICK_COMMANDS.has(arg))) return "quick command"
|
|
33
|
+
if (liveRuns > 0) return "another baro run is active"
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function baroHome() {
|
|
38
|
+
return path.join(homedir(), ".baro")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readJson(file) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(readFileSync(file, "utf8"))
|
|
44
|
+
} catch {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The version the binary and its bundles are actually staged at. */
|
|
50
|
+
export function installedVersion(packageVersion) {
|
|
51
|
+
return readJson(path.join(baroHome(), "bin", "bundle-version.json"))?.version ?? packageVersion
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function liveRunCount() {
|
|
55
|
+
try {
|
|
56
|
+
const dir = path.join(baroHome(), "live")
|
|
57
|
+
return readdirSync(dir).filter((name) => {
|
|
58
|
+
const pid = readJson(path.join(dir, name))?.pid
|
|
59
|
+
if (!Number.isInteger(pid)) return false
|
|
60
|
+
try {
|
|
61
|
+
process.kill(pid, 0)
|
|
62
|
+
return true
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return error.code === "EPERM"
|
|
65
|
+
}
|
|
66
|
+
}).length
|
|
67
|
+
} catch {
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function latestVersion(now = Date.now()) {
|
|
73
|
+
const cachePath = path.join(baroHome(), "update-check.json")
|
|
74
|
+
const cached = readJson(cachePath)
|
|
75
|
+
if (cached?.latest && cached.checkedAt && now - cached.checkedAt < CACHE_TTL_MS) {
|
|
76
|
+
return cached.latest
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch("https://registry.npmjs.org/baro-ai/latest", {
|
|
80
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
81
|
+
})
|
|
82
|
+
const latest = (await response.json())?.version
|
|
83
|
+
if (typeof latest !== "string") return null
|
|
84
|
+
try {
|
|
85
|
+
mkdirSync(baroHome(), { recursive: true })
|
|
86
|
+
writeFileSync(cachePath, JSON.stringify({ latest, checkedAt: now }))
|
|
87
|
+
} catch {
|
|
88
|
+
// A read-only home only costs the next start another lookup.
|
|
89
|
+
}
|
|
90
|
+
return latest
|
|
91
|
+
} catch {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Update when a newer baro is published. Never throws and never blocks a run
|
|
98
|
+
* for long: offline, a failed install or a root-owned global prefix all fall
|
|
99
|
+
* through to the version already installed.
|
|
100
|
+
*/
|
|
101
|
+
export async function selfUpdate({ packageVersion, argv, env = process.env, isTTY = process.stdin.isTTY }) {
|
|
102
|
+
const reason = skipReason({ argv, env, isTTY: Boolean(isTTY), liveRuns: 0 })
|
|
103
|
+
if (reason) return { updated: false, reason }
|
|
104
|
+
const current = installedVersion(packageVersion)
|
|
105
|
+
const latest = await latestVersion()
|
|
106
|
+
if (!latest || !semverLt(current, latest)) return { updated: false, reason: "up to date" }
|
|
107
|
+
if (liveRunCount() > 0) {
|
|
108
|
+
console.error(`baro ${latest} is available; not updating while another baro run is active.`)
|
|
109
|
+
return { updated: false, reason: "another baro run is active" }
|
|
110
|
+
}
|
|
111
|
+
console.error(`Updating baro ${current} → ${latest}…`)
|
|
112
|
+
const result = spawnSync("npm", ["install", "-g", `baro-ai@${latest}`], {
|
|
113
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
114
|
+
shell: process.platform === "win32",
|
|
115
|
+
})
|
|
116
|
+
if (result.status !== 0 || installedVersion(packageVersion) !== latest) {
|
|
117
|
+
console.error(`Could not update baro automatically. Update with: npm i -g baro-ai@latest`)
|
|
118
|
+
return { updated: false, reason: "install failed" }
|
|
119
|
+
}
|
|
120
|
+
console.error(`baro ${latest} installed.`)
|
|
121
|
+
return { updated: true, reason: null }
|
|
122
|
+
}
|
package/dist/cli.mjs
CHANGED
|
@@ -44719,6 +44719,18 @@ function javaRuns(env) {
|
|
|
44719
44719
|
return false;
|
|
44720
44720
|
}
|
|
44721
44721
|
}
|
|
44722
|
+
function resolveJvmBuildTool(cwd, kind, probe2 = {}) {
|
|
44723
|
+
const env = probe2.env ?? process.env;
|
|
44724
|
+
const windows = (probe2.platform ?? process.platform) === "win32";
|
|
44725
|
+
if (!(probe2.javaRuns ?? javaRuns)(env)) return null;
|
|
44726
|
+
return kind === "maven" ? wrapperOrTool(cwd, windows ? "mvnw.cmd" : "mvnw", "mvn", env, windows) : wrapperOrTool(cwd, windows ? "gradlew.bat" : "gradlew", "gradle", env, windows);
|
|
44727
|
+
}
|
|
44728
|
+
function isMavenRepository(cwd) {
|
|
44729
|
+
return MAVEN_FILES.some((name) => existsSync4(join7(cwd, name)));
|
|
44730
|
+
}
|
|
44731
|
+
function isGradleRepository(cwd) {
|
|
44732
|
+
return GRADLE_FILES.some((name) => existsSync4(join7(cwd, name)));
|
|
44733
|
+
}
|
|
44722
44734
|
function wrapperOrTool(cwd, wrapper, tool, env, windows) {
|
|
44723
44735
|
if (isFile(join7(cwd, wrapper))) return windows ? `.\\${wrapper}` : `./${wrapper}`;
|
|
44724
44736
|
return onPath(tool, env, windows) ? tool : null;
|
|
@@ -45011,6 +45023,10 @@ function dispatchDeclared(cwd, requirement, parsed, packageManagers, insideDdev)
|
|
|
45011
45023
|
args: ["diff", "--no-ext-diff", "--no-textconv", "--check"]
|
|
45012
45024
|
};
|
|
45013
45025
|
}
|
|
45026
|
+
if (tool === "mvn" || tool === "./mvnw") return translateMaven(cwd, requirement, parsed);
|
|
45027
|
+
if (tool === "gradle" || tool === "./gradlew") {
|
|
45028
|
+
return translateGradle(cwd, requirement, parsed);
|
|
45029
|
+
}
|
|
45014
45030
|
if (tool === "composer") return translateComposer(cwd, requirement, parsed);
|
|
45015
45031
|
if (tool === "vendor/bin/phpunit") {
|
|
45016
45032
|
return translatePhpunit(cwd, requirement, parsed);
|
|
@@ -45026,7 +45042,7 @@ function dispatchDeclared(cwd, requirement, parsed, packageManagers, insideDdev)
|
|
|
45026
45042
|
}
|
|
45027
45043
|
return incomplete(
|
|
45028
45044
|
requirement,
|
|
45029
|
-
"unsupported declared test; allowed tools are npm/pnpm/yarn, exact npx rstest run paths, cargo, node, git diff --check, composer, vendor/bin/phpunit, and ddev exec"
|
|
45045
|
+
"unsupported declared test; allowed tools are npm/pnpm/yarn, exact npx rstest run paths, cargo, node, mvn, gradle, git diff --check, composer, vendor/bin/phpunit, and ddev exec"
|
|
45030
45046
|
);
|
|
45031
45047
|
}
|
|
45032
45048
|
var CD_PREFIX = /^cd\s+([^\s&]+)\s*&&\s*([^]*)$/;
|
|
@@ -45639,6 +45655,95 @@ function translateCargo(cwd, requirement, parsed) {
|
|
|
45639
45655
|
args
|
|
45640
45656
|
};
|
|
45641
45657
|
}
|
|
45658
|
+
var MAVEN_GOALS = /* @__PURE__ */ new Set(["test", "verify", "package", "compile", "test-compile"]);
|
|
45659
|
+
var MAVEN_FLAGS = /* @__PURE__ */ new Set([
|
|
45660
|
+
"-B",
|
|
45661
|
+
"--batch-mode",
|
|
45662
|
+
"-ntp",
|
|
45663
|
+
"--no-transfer-progress",
|
|
45664
|
+
"-q",
|
|
45665
|
+
"--quiet",
|
|
45666
|
+
"-o",
|
|
45667
|
+
"--offline",
|
|
45668
|
+
"-e",
|
|
45669
|
+
"-am",
|
|
45670
|
+
"--also-make"
|
|
45671
|
+
]);
|
|
45672
|
+
var MAVEN_PROPERTY = /^-D(?:test|failIfNoTests|surefire\.failIfNoSpecifiedTests|skipITs)=[A-Za-z0-9_.,*#+!$-]+$/;
|
|
45673
|
+
var JVM_MODULE = /^[A-Za-z0-9_./:-]+$/;
|
|
45674
|
+
function translateMaven(cwd, requirement, parsed) {
|
|
45675
|
+
if (!isMavenRepository(cwd)) {
|
|
45676
|
+
return incomplete(requirement, "declared mvn test requires pom.xml at the repository root");
|
|
45677
|
+
}
|
|
45678
|
+
const args = parsed.tokens.slice(1);
|
|
45679
|
+
let goals = 0;
|
|
45680
|
+
for (let index = 0; index < args.length; index++) {
|
|
45681
|
+
const token = args[index];
|
|
45682
|
+
if (MAVEN_GOALS.has(token)) {
|
|
45683
|
+
goals++;
|
|
45684
|
+
} else if (token === "-pl" || token === "--projects") {
|
|
45685
|
+
const module = args[++index];
|
|
45686
|
+
if (!module || !JVM_MODULE.test(module) || hasParentTraversal(module)) {
|
|
45687
|
+
return incomplete(requirement, "mvn -pl needs a module path inside the repository");
|
|
45688
|
+
}
|
|
45689
|
+
} else if (!MAVEN_FLAGS.has(token) && !MAVEN_PROPERTY.test(token)) {
|
|
45690
|
+
return incomplete(
|
|
45691
|
+
requirement,
|
|
45692
|
+
`mvn declarations are limited to test, verify, package, compile and test-compile with batch/offline flags, -pl and -Dtest; unsupported '${token}'`
|
|
45693
|
+
);
|
|
45694
|
+
}
|
|
45695
|
+
}
|
|
45696
|
+
if (goals !== 1) {
|
|
45697
|
+
return incomplete(requirement, "mvn declaration must name exactly one goal");
|
|
45698
|
+
}
|
|
45699
|
+
const tool = resolveJvmBuildTool(cwd, "maven");
|
|
45700
|
+
if (!tool) {
|
|
45701
|
+
return incomplete(requirement, "this host has no working JDK and Maven (or ./mvnw) to run the declared test");
|
|
45702
|
+
}
|
|
45703
|
+
return { label: ["mvn", ...args].join(" "), tool, args };
|
|
45704
|
+
}
|
|
45705
|
+
var GRADLE_TASK = /^(?::[A-Za-z0-9_-]+)*:?(?:test|check|build|assemble)$/;
|
|
45706
|
+
var GRADLE_FLAGS = /* @__PURE__ */ new Set([
|
|
45707
|
+
"--console=plain",
|
|
45708
|
+
"--offline",
|
|
45709
|
+
"--no-daemon",
|
|
45710
|
+
"-q",
|
|
45711
|
+
"--quiet",
|
|
45712
|
+
"--stacktrace",
|
|
45713
|
+
"--continue"
|
|
45714
|
+
]);
|
|
45715
|
+
var GRADLE_TEST_FILTER = /^[A-Za-z0-9_.*$]+$/;
|
|
45716
|
+
function translateGradle(cwd, requirement, parsed) {
|
|
45717
|
+
if (!isGradleRepository(cwd)) {
|
|
45718
|
+
return incomplete(requirement, "declared gradle test requires a Gradle build at the repository root");
|
|
45719
|
+
}
|
|
45720
|
+
const args = parsed.tokens.slice(1);
|
|
45721
|
+
let tasks = 0;
|
|
45722
|
+
for (let index = 0; index < args.length; index++) {
|
|
45723
|
+
const token = args[index];
|
|
45724
|
+
if (GRADLE_TASK.test(token)) {
|
|
45725
|
+
tasks++;
|
|
45726
|
+
} else if (token === "--tests") {
|
|
45727
|
+
const filter = args[++index];
|
|
45728
|
+
if (!filter || !GRADLE_TEST_FILTER.test(filter)) {
|
|
45729
|
+
return incomplete(requirement, "gradle --tests needs a class or method filter");
|
|
45730
|
+
}
|
|
45731
|
+
} else if (!GRADLE_FLAGS.has(token)) {
|
|
45732
|
+
return incomplete(
|
|
45733
|
+
requirement,
|
|
45734
|
+
`gradle declarations are limited to test, check, build and assemble tasks with --tests; unsupported '${token}'`
|
|
45735
|
+
);
|
|
45736
|
+
}
|
|
45737
|
+
}
|
|
45738
|
+
if (tasks === 0) {
|
|
45739
|
+
return incomplete(requirement, "gradle declaration must name a test, check, build or assemble task");
|
|
45740
|
+
}
|
|
45741
|
+
const tool = resolveJvmBuildTool(cwd, "gradle");
|
|
45742
|
+
if (!tool) {
|
|
45743
|
+
return incomplete(requirement, "this host has no working JDK and Gradle (or ./gradlew) to run the declared test");
|
|
45744
|
+
}
|
|
45745
|
+
return { label: ["gradle", ...args].join(" "), tool, args };
|
|
45746
|
+
}
|
|
45642
45747
|
function containedPath(cwd, candidate, requireFile) {
|
|
45643
45748
|
if (candidate === "" || candidate.startsWith("-") || candidate.startsWith("@") || isAbsolute3(candidate) || /^[A-Za-z]:\//.test(candidate) || hasParentTraversal(candidate)) return { reason: `unsafe or escaping path '${candidate}'` };
|
|
45644
45749
|
const root = resolve5(cwd);
|
|
@@ -70765,8 +70870,13 @@ var Finalizer = class extends BaseObserver {
|
|
|
70765
70870
|
hostRepoRoot: this.opts.hostRepoRoot
|
|
70766
70871
|
});
|
|
70767
70872
|
}
|
|
70768
|
-
const
|
|
70769
|
-
|
|
70873
|
+
const hygieneOnly = verify.ok && provesOnlyHygiene(
|
|
70874
|
+
verify.commands.filter((command) => command.command !== VERDICT_COMMAND)
|
|
70875
|
+
);
|
|
70876
|
+
const verificationIncomplete = !hygieneOnly && (!verify.ran || verify.commands.some((command) => command.status === "skipped"));
|
|
70877
|
+
if (hygieneOnly) {
|
|
70878
|
+
this.log("[finalizer] not test-verified: only git diff --check ran on the merged result");
|
|
70879
|
+
} else if (!verify.ok) {
|
|
70770
70880
|
this.log(`[finalizer] \u26A0 verification failed: ${verify.failures[0]?.cmd ?? "build/test"}`);
|
|
70771
70881
|
} else if (!verify.ran) {
|
|
70772
70882
|
this.log("[finalizer] nothing to verify (no build/test)");
|
|
@@ -70783,6 +70893,7 @@ var Finalizer = class extends BaseObserver {
|
|
|
70783
70893
|
prd,
|
|
70784
70894
|
run,
|
|
70785
70895
|
checkpoint,
|
|
70896
|
+
hygieneOnly,
|
|
70786
70897
|
verify,
|
|
70787
70898
|
orderedStories,
|
|
70788
70899
|
passed,
|
|
@@ -71075,7 +71186,14 @@ ${BARO_COAUTHOR_TRAILER}`,
|
|
|
71075
71186
|
const skippedVerification = args.verify.commands.filter(
|
|
71076
71187
|
(command) => command.status === "skipped"
|
|
71077
71188
|
);
|
|
71078
|
-
if (
|
|
71189
|
+
if (args.hygieneOnly) {
|
|
71190
|
+
lines.push("## Not test-verified");
|
|
71191
|
+
lines.push("");
|
|
71192
|
+
lines.push(
|
|
71193
|
+
"The only check run on the merged branch was `git diff --check`. No build or test command verified it; run the project's tests before merging."
|
|
71194
|
+
);
|
|
71195
|
+
lines.push("");
|
|
71196
|
+
} else if (!args.verify.ran || skippedVerification.length > 0) {
|
|
71079
71197
|
lines.push("## \u26A0 Build/test verification incomplete");
|
|
71080
71198
|
lines.push("");
|
|
71081
71199
|
lines.push(
|
|
@@ -71207,11 +71325,12 @@ function bindAuthority2(current, authority, label) {
|
|
|
71207
71325
|
}
|
|
71208
71326
|
return authority;
|
|
71209
71327
|
}
|
|
71328
|
+
var VERDICT_COMMAND = "objective verification verdict";
|
|
71210
71329
|
function verificationResult(evidence) {
|
|
71211
71330
|
const commands = evidence.commands.map((command) => ({ ...command }));
|
|
71212
71331
|
if (evidence.status === "skipped" && !commands.some((command) => command.status === "skipped")) {
|
|
71213
71332
|
commands.push({
|
|
71214
|
-
command:
|
|
71333
|
+
command: VERDICT_COMMAND,
|
|
71215
71334
|
status: "skipped",
|
|
71216
71335
|
durationMs: 0,
|
|
71217
71336
|
tail: "verifier reported an incomplete/skipped objective gate"
|