diff --git a/docs/install-wizard.md b/docs/install-wizard.md index 170ce3405..8d7bff20d 100644 --- a/docs/install-wizard.md +++ b/docs/install-wizard.md @@ -787,9 +787,13 @@ publishes main builds, so main is built on this computer Gentleman-Programming/gentle-ai (GitHub API, raw SHA), runs `go install github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@` in a sealed Go environment (`GOPROXY=https://proxy.golang.org`, - `GOSUMDB=sum.golang.org`, `GOTOOLCHAIN=local`, `GOFLAGS=-modcacherw`), requires - the binary's version to end in that commit's first 12 characters, keeps it - under `/main/gentle-ai//` and registers it as the + `GOSUMDB=sum.golang.org`, `GOTOOLCHAIN=local`, `GOFLAGS=-modcacherw`), + asks Go (`go list -m -json`, same environment) which module version that + commit is (a release tag such as `v4.2.0`, or a pseudo-version) and requires + its origin to be that exact commit, requires the binary to embed that module + version (`go version -m`) and to report it, all in the `.build` work + folder, and only then moves it to `/main/gentle-ai//` + (a failed rebuild leaves an already registered build in place) and registers it as the `gentle-pi.dev-binary/v1` override, which never falls back to the pinned binary silently. 2. `install-shell-main`: downloads the latest `main` commit's source tarball, diff --git a/scripts/main-channel.mjs b/scripts/main-channel.mjs index 88e2d9d66..8836b288f 100644 --- a/scripts/main-channel.mjs +++ b/scripts/main-channel.mjs @@ -4,9 +4,11 @@ // an exact commit SHA resolved at install or upgrade time: // - Gentle AI: `go install @` in a sealed Go environment, so the // module source is verified by Go's checksum database (sum.golang.org), as -// the Windows release build already is. The binary must report a version -// naming that commit, then it is activated through the existing dev-binary -// override, which never falls back to the pinned binary silently. +// the Windows release build already is. Go must resolve the commit to a +// module version (a tag or a pseudo-version) whose origin is that commit, +// and the binary must embed that version and report it, then it is +// activated through the existing dev-binary override, which never falls +// back to the pinned binary silently. // - Gentle Shell: the exact commit's source tarball, versioned // `-main.` and packed without `prepack` (which runs the full // test suite), then installed globally like any local tarball. @@ -18,13 +20,14 @@ import { goAcquisition, pnpmGlobalBin, requirements } from "./installer-prefligh export const SHELL_REPOSITORY = "Gentleman-Programming/gentle-shell"; export const GENTLE_AI_REPOSITORY = "Gentleman-Programming/gentle-ai"; +export const GENTLE_AI_MODULE = "github.com/gentleman-programming/gentle-ai/v4"; export const GENTLE_AI_MAIN_PACKAGE = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai"; export const CHANNEL_SCHEMA = "gentle-shell.channel/v1"; export const CHANNELS = Object.freeze(["release", "main"]); const SHA = /^[0-9a-f]{40}$/; const MINUTE = 60_000; -const deadlines = Object.freeze({ build: 15 * MINUTE, version: 30_000, extract: 2 * MINUTE, pack: 5 * MINUTE }); +const deadlines = Object.freeze({ build: 15 * MINUTE, resolve: 2 * MINUTE, version: 30_000, extract: 2 * MINUTE, pack: 5 * MINUTE }); const MAX_SOURCE_BYTES = 100 * 1024 * 1024; export class MainChannelError extends Error { @@ -136,23 +139,57 @@ export async function buildMainGentleAi({ commit, ctx, platform, goPath, run, fs const executable = platform === "win32" ? "gentle-ai.exe" : "gentle-ai"; const directory = join(configHome(ctx), "main", "gentle-ai", commit); const buildDirectory = join(configHome(ctx), "main", ".build"); + // Verified in the work folder before it replaces anything, so a failed rebuild of + // an already registered commit never touches the override; the final move stays + // on one filesystem, and uninstall already accepts a leftover .build. + const staging = join(buildDirectory, commit); await fs.rm(buildDirectory, { recursive: true, force: true }); const env = sealedGoEnvironment(goPath, buildDirectory, platform); for (const path of [env.GOBIN, env.GOPATH, env.GOMODCACHE, env.GOCACHE, join(buildDirectory, "tmp")]) await fs.mkdir(path, { recursive: true }); try { const install = await run(goPath, ["install", `${GENTLE_AI_MAIN_PACKAGE}@${commit}`], { env, cwd: buildDirectory, deadlineMs: deadlines.build, stderrTail: 4096 }); if (!succeeded(install)) throw new MainChannelError("main-gentle-ai-build-failed", `go install ${GENTLE_AI_MAIN_PACKAGE}@${commit} failed`, stderrCause(install)); - await fs.mkdir(directory, { recursive: true }); - const binaryPath = join(directory, executable); - await fs.copyFile(join(env.GOBIN, executable), binaryPath); - if (platform !== "win32") await fs.chmod(binaryPath, 0o755); - // Go stamps the module pseudo-version, whose suffix is the commit's first 12 characters. - const probe = await run(binaryPath, ["version"], { env: { PATH: env.PATH }, deadlineMs: deadlines.version }); + await fs.mkdir(staging, { recursive: true }); + const stagedPath = join(staging, executable); + await fs.copyFile(join(env.GOBIN, executable), stagedPath); + if (platform !== "win32") await fs.chmod(stagedPath, 0o755); + const reject = async (message, cause) => { + throw new MainChannelError("main-gentle-ai-unverified", message, cause); + }; + const short = commit.slice(0, 12); + const goOptions = { env, cwd: buildDirectory, deadlineMs: deadlines.version, stderrTail: 4096 }; + // Go resolves the commit through the proxy and checksum database: a tag commit is + // stamped with its tag, any other with a pseudo-version, so the version is not guessed. + // Resolving may reach the proxy, so it gets a network deadline, not the local one. + const listed = await run(goPath, ["list", "-m", "-json", `${GENTLE_AI_MODULE}@${commit}`], { ...goOptions, deadlineMs: deadlines.resolve }); + if (!succeeded(listed)) return await reject(`Go could not resolve Gentle AI commit ${short}`, stderrCause(listed)); + let resolved = null; + try { + resolved = JSON.parse(String(listed.stdout ?? "")); + } catch { + return await reject(`Go returned unreadable module information for Gentle AI commit ${short}`); + } + const resolvedVersion = typeof resolved?.Version === "string" ? resolved.Version : ""; + const originHash = resolved?.Origin?.Hash; + if (resolvedVersion === "") return await reject(`Go resolved Gentle AI commit ${short} to no module version`); + if (originHash !== commit) { + return await reject(`Go resolved Gentle AI commit ${short} to ${originHash ? `commit ${originHash}` : "no commit"}, not the requested commit`); + } + const embedded = await run(goPath, ["version", "-m", stagedPath], goOptions); + if (!succeeded(embedded)) return await reject("the built Gentle AI's embedded module version could not be read", stderrCause(embedded)); + const modLine = String(embedded.stdout ?? "").split(/\r?\n/).map((line) => line.trim().split(/\s+/)).find((fields) => fields[0] === "mod" && fields[1] === GENTLE_AI_MODULE); + if (modLine?.[2] !== resolvedVersion) { + return await reject(`the built Gentle AI embeds module version ${modLine?.[2] ?? "none"}, expected ${resolvedVersion} for commit ${short}`); + } + const probe = await run(stagedPath, ["version"], { env: { PATH: env.PATH }, deadlineMs: deadlines.version }); const match = succeeded(probe) && /^gentle-ai (\S+)$/m.exec(String(probe.stdout ?? "")); - if (!match || !match[1].endsWith(`-${commit.slice(0, 12)}`)) { - await fs.rm(directory, { recursive: true, force: true }); - throw new MainChannelError("main-gentle-ai-unverified", `the built Gentle AI does not report commit ${commit.slice(0, 12)}`); + const expectedVersion = resolvedVersion.replace(/^v/, ""); + if (!match || match[1] !== expectedVersion) { + return await reject(`the built Gentle AI reports version ${match ? match[1] : "none"}, expected ${expectedVersion} for commit ${short}`); } + await fs.mkdir(directory, { recursive: true }); + const binaryPath = join(directory, executable); + await fs.rename(stagedPath, binaryPath); registerGentleAiDevBinary(binaryPath, ctx, platform); return { binaryPath, version: match[1] }; } finally { diff --git a/tests/main-channel-upgrade.test.ts b/tests/main-channel-upgrade.test.ts index a763bf440..7a1aa55f1 100644 --- a/tests/main-channel-upgrade.test.ts +++ b/tests/main-channel-upgrade.test.ts @@ -4,7 +4,7 @@ import * as fsPromises from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; -import { CHANNEL_SCHEMA, MainChannelError, ownerManager, readChannel, removeGlobalShell, runUpgrade, writeChannel } from "../scripts/main-channel.mjs"; +import { CHANNEL_SCHEMA, GENTLE_AI_MODULE, MainChannelError, ownerManager, readChannel, removeGlobalShell, runUpgrade, writeChannel } from "../scripts/main-channel.mjs"; import { requirements } from "../scripts/installer-preflight.mjs"; const AI_SHA = "1f9d5e6423e37f7d2316859045f379ba9b5d8c3a"; @@ -55,8 +55,16 @@ function world({ owner = "pnpm", latest = "4.1.0", commits = { ai: AI_SHA, shell return { code: 0, stdout: "" }; } if (argv[0] === "version" && command === `${bin}/go`) { + if (argv[1] === "-m") { + const commit = String(argv[2] ?? "").split("/").at(-2) ?? ""; + return { code: 0, stdout: `${command} (binary)\n\tmod\t${GENTLE_AI_MODULE}\tv4.0.1-0.20261008202137-${commit.slice(0, 12)}\th1:J8EluS7QrfM+m3vmQlF26B/shZRjD6rBfsKtrABL5mc=\n` }; + } return { code: 0, stdout: `go version go${requirements.go} ${platform}/amd64\n` }; } + if (argv[0] === "list" && command === `${bin}/go`) { + const commit = String(argv[3] ?? "").slice(`${GENTLE_AI_MODULE}@`.length); + return { code: 0, stdout: JSON.stringify({ Path: GENTLE_AI_MODULE, Version: `v4.0.1-0.20261008202137-${commit.slice(0, 12)}`, Origin: { VCS: "git", URL: "https://github.com/gentleman-programming/gentle-ai", Hash: commit } }) }; + } if (argv[0] === "version") { const sha = command.split("/").at(-2) ?? ""; return { code: 0, stdout: `gentle-ai 4.0.1-0.20261008202137-${sha.slice(0, 12)}\n` }; diff --git a/tests/main-channel.test.ts b/tests/main-channel.test.ts index 61c3def0f..e415e8336 100644 --- a/tests/main-channel.test.ts +++ b/tests/main-channel.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; +import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import * as fsPromises from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, win32 } from "node:path"; @@ -7,6 +7,7 @@ import test from "node:test"; import { CHANNEL_SCHEMA, GENTLE_AI_MAIN_PACKAGE, + GENTLE_AI_MODULE, MainChannelError, buildMainGentleAi, channelStatePath, @@ -23,6 +24,8 @@ import { artifactFor } from "../scripts/installer-downloads.mjs"; const SHELL_SHA = "6e7e3a18f794223396527a54c7c36d19c7d236c6"; const AI_SHA = "1f9d5e6423e37f7d2316859045f379ba9b5d8c3a"; +const PSEUDO = "v4.0.1-0.20261008202137-1f9d5e6423e3"; +const LIST_FAILURE = "go: github.com/gentleman-programming/gentle-ai/v4@v4.0.1-0.20261008202137-1f9d5e6423e3: module lookup failed"; const posixOnly = { skip: process.platform === "win32" }; function sandbox() { @@ -33,6 +36,38 @@ function sandbox() { } type Call = { command: string; argv: string[]; options: { env?: Record; cwd?: string } }; +// Emulates the Go and binary invocations buildMainGentleAi makes, each driven by +// what `go list -m -json` resolves, what the binary embeds, and what it prints. +function gentleAiBuildRun(world: { + commit: string; + listVersion?: string; + originHash?: string; + embeddedVersion?: string; + probeVersion?: string; + listFails?: boolean; +}) { + const { commit, listVersion = "v4.2.0", originHash = commit, embeddedVersion = listVersion, probeVersion = listVersion.replace(/^v/, ""), listFails = false } = world; + const calls: Call[] = []; + const run = async (command: string, argv: string[], options: Call["options"]) => { + calls.push({ command, argv, options }); + if (argv[0] === "install") { + mkdirSync(options.env!.GOBIN, { recursive: true }); + writeFileSync(join(options.env!.GOBIN, "gentle-ai"), "#!/bin/sh\n", { mode: 0o755 }); + return { code: 0, stdout: "" }; + } + if (argv[0] === "list") { + if (listFails) return { code: 1, stdout: "", stderrTail: `${LIST_FAILURE}\n` }; + return { code: 0, stdout: JSON.stringify({ Path: GENTLE_AI_MODULE, Version: listVersion, + Origin: { VCS: "git", URL: "https://github.com/gentleman-programming/gentle-ai", Hash: originHash, Ref: "refs/tags/v4.2.0" } }) }; + } + if (argv[0] === "version" && argv[1] === "-m") { + return { code: 0, stdout: `${command} (binary)\n\tmod\t${GENTLE_AI_MODULE}\t${embeddedVersion}\th1:J8EluS7QrfM+m3vmQlF26B/shZRjD6rBfsKtrABL5mc=\n` }; + } + return { code: 0, stdout: `gentle-ai ${probeVersion}\n` }; + }; + return { calls, run }; +} + test("channel state defaults to release and round-trips a main record", async () => { const s = sandbox(); try { @@ -169,18 +204,7 @@ test("Gentle AI main is built from the exact commit through Go's checksum databa const s = sandbox(); try { const goPath = join(s.root, "go", "bin", "go"); - const calls: Call[] = []; - const run = async (command: string, argv: string[], options: Call["options"]) => { - calls.push({ command, argv, options }); - if (argv[0] === "install") { - mkdirSync(options.env!.GOBIN, { recursive: true }); - const binary = join(options.env!.GOBIN, "gentle-ai"); - writeFileSync(binary, "#!/bin/sh\n"); - chmodSync(binary, 0o755); - return { code: 0, stdout: "" }; - } - return { code: 0, stdout: "gentle-ai 4.0.1-0.20261008202137-1f9d5e6423e3\n" }; - }; + const { calls, run } = gentleAiBuildRun({ commit: AI_SHA, listVersion: PSEUDO }); const built = await buildMainGentleAi({ commit: AI_SHA, ctx: s.ctx, platform: "darwin", goPath, run, fs: fsPromises }); const install = calls[0]; assert.equal(install.command, goPath); @@ -194,19 +218,107 @@ test("Gentle AI main is built from the exact commit through Go's checksum databa } finally { s.cleanup(); } }); -test("a Gentle AI build whose version names another commit is rejected and never registered", posixOnly, async () => { +// #2301: when main's HEAD is exactly a tag commit, Go stamps the tag (v4.2.0), not a +// pseudo-version, so the suffix check alone rejects every tagged release. +test("a main commit that is exactly a release tag is accepted when Go resolves that tag to the commit", posixOnly, async () => { const s = sandbox(); try { - const run = async (_command: string, argv: string[], options: Call["options"]) => { - if (argv[0] === "install") { - mkdirSync(options.env!.GOBIN, { recursive: true }); - writeFileSync(join(options.env!.GOBIN, "gentle-ai"), "#!/bin/sh\n", { mode: 0o755 }); - } - return { code: 0, stdout: "gentle-ai 4.0.1-0.20261008202137-aaaaaaaaaaaa\n" }; - }; + const TAGGED = "4acaf4241beae1e0c977c92dfe406a270f8d40de"; + const { run } = gentleAiBuildRun({ commit: TAGGED, listVersion: "v4.2.0", originHash: TAGGED }); + const built = await buildMainGentleAi({ commit: TAGGED, ctx: s.ctx, platform: "darwin", goPath: "/go/bin/go", run, fs: fsPromises }); + assert.equal(built.binaryPath, join(s.home, ".pi", "gentle-ai", "main", "gentle-ai", TAGGED, "gentle-ai")); + assert.equal(built.version, "4.2.0"); + assert.deepEqual(JSON.parse(readFileSync(join(s.home, ".pi", "gentle-ai", "dev-binary.json"), "utf8")), + { schema: "gentle-pi.dev-binary/v1", path: built.binaryPath }); + } finally { s.cleanup(); } +}); + +test("a go list whose Origin.Hash names another commit is rejected, and the build is removed", posixOnly, async () => { + const s = sandbox(); + try { + const OTHER = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const { run } = gentleAiBuildRun({ commit: AI_SHA, listVersion: PSEUDO, originHash: OTHER }); + await assert.rejects(buildMainGentleAi({ commit: AI_SHA, ctx: s.ctx, platform: "darwin", goPath: "/go/bin/go", run, fs: fsPromises }), + (error: MainChannelError) => error.code === "main-gentle-ai-unverified" && error.message.includes(OTHER) && error.message.includes(AI_SHA.slice(0, 12))); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "dev-binary.json")), false); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "main", "gentle-ai", AI_SHA)), false); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "main", ".build")), false); + } finally { s.cleanup(); } +}); + +test("a binary whose embedded module version differs from what Go resolved is rejected and never registered", posixOnly, async () => { + const s = sandbox(); + try { + const { run } = gentleAiBuildRun({ commit: AI_SHA, listVersion: PSEUDO, embeddedVersion: "v4.1.2" }); + await assert.rejects(buildMainGentleAi({ commit: AI_SHA, ctx: s.ctx, platform: "darwin", goPath: "/go/bin/go", run, fs: fsPromises }), + (error: MainChannelError) => error.code === "main-gentle-ai-unverified"); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "dev-binary.json")), false); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "main", "gentle-ai", AI_SHA)), false); + } finally { s.cleanup(); } +}); + +test("a failed go list is rejected as unverified, with its stderr tail as the cause, and nothing is registered", posixOnly, async () => { + const s = sandbox(); + try { + const { run } = gentleAiBuildRun({ commit: AI_SHA, listVersion: PSEUDO, listFails: true }); + await assert.rejects(buildMainGentleAi({ commit: AI_SHA, ctx: s.ctx, platform: "darwin", goPath: "/go/bin/go", run, fs: fsPromises }), + (error: MainChannelError) => error.code === "main-gentle-ai-unverified" && error.cause instanceof Error && error.cause.message === LIST_FAILURE); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "dev-binary.json")), false); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "main", "gentle-ai", AI_SHA)), false); + } finally { s.cleanup(); } +}); + +// Rebuilding a commit that is already registered (a rerun wizard step, a lost +// channel record) must not delete the working override when verification fails. +for (const [name, failure] of Object.entries({ + "go list fails": { listFails: true }, + "the embedded version differs": { embeddedVersion: "v4.1.2" }, + "the reported version differs": { probeVersion: "4.0.1-0.20261008202137-aaaaaaaaaaaa" }, +})) test(`a registered build of the same commit survives a rebuild whose verification fails because ${name}`, posixOnly, async () => { + const s = sandbox(); + try { + const directory = join(s.home, ".pi", "gentle-ai", "main", "gentle-ai", AI_SHA); + const binary = join(directory, "gentle-ai"); + const registration = join(s.home, ".pi", "gentle-ai", "dev-binary.json"); + mkdirSync(directory, { recursive: true }); + writeFileSync(binary, "#!/bin/sh\n# registered\n", { mode: 0o755 }); + const record = JSON.stringify({ schema: "gentle-pi.dev-binary/v1", path: binary }); + writeFileSync(registration, record); + const { run } = gentleAiBuildRun({ commit: AI_SHA, listVersion: PSEUDO, ...failure }); + await assert.rejects(buildMainGentleAi({ commit: AI_SHA, ctx: s.ctx, platform: "darwin", goPath: "/go/bin/go", run, fs: fsPromises }), + (error: MainChannelError) => error.code === "main-gentle-ai-unverified"); + assert.equal(readFileSync(binary, "utf8"), "#!/bin/sh\n# registered\n"); + assert.equal(readFileSync(registration, "utf8"), record); + assert.deepEqual(readdirSync(join(s.home, ".pi", "gentle-ai", "main", "gentle-ai")), [AI_SHA]); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "main", ".build")), false); + } finally { s.cleanup(); } +}); + +test("go list and go version -m run with the same sealed environment and cwd as go install", posixOnly, async () => { + const s = sandbox(); + try { + const goPath = join(s.root, "go", "bin", "go"); + const { calls, run } = gentleAiBuildRun({ commit: AI_SHA, listVersion: PSEUDO }); + await buildMainGentleAi({ commit: AI_SHA, ctx: s.ctx, platform: "darwin", goPath, run, fs: fsPromises }); + const sealed = { GOPROXY: "https://proxy.golang.org", GOSUMDB: "sum.golang.org", GOTOOLCHAIN: "local", + GOFLAGS: "-modcacherw", GONOSUMDB: "", GOPRIVATE: "", GOINSECURE: "", GOENV: "off", GOWORK: "off" }; + const goCalls = calls.filter((call) => call.command === goPath); + assert.deepEqual(goCalls.map((call) => call.argv[0]), ["install", "list", "version"]); + for (const call of goCalls) { + assert.equal(call.options.cwd, join(s.home, ".pi", "gentle-ai", "main", ".build"), call.argv.join(" ")); + for (const [key, value] of Object.entries(sealed)) assert.equal(call.options.env![key], value, `${key} for ${call.argv.join(" ")}`); + } + } finally { s.cleanup(); } +}); + +test("a Gentle AI build whose reported version differs from the resolved module version is rejected and never registered", posixOnly, async () => { + const s = sandbox(); + try { + const { run } = gentleAiBuildRun({ commit: AI_SHA, listVersion: PSEUDO, probeVersion: "4.0.1-0.20261008202137-aaaaaaaaaaaa" }); await assert.rejects(buildMainGentleAi({ commit: AI_SHA, ctx: s.ctx, platform: "darwin", goPath: "/go/bin/go", run, fs: fsPromises }), (error: MainChannelError) => error.code === "main-gentle-ai-unverified"); assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "dev-binary.json")), false); + assert.equal(existsSync(join(s.home, ".pi", "gentle-ai", "main", "gentle-ai", AI_SHA)), false); } finally { s.cleanup(); } }); @@ -278,6 +390,13 @@ async function upgradeWorld({ userGo = null as string | null, pinned = false, ac const fetch = async (url: string) => ({ ok: true, status: 200, text: async () => (url.endsWith("/gentle-ai/commits/main") ? NEW_AI : SHELL_SHA) }); const run = async (command: string, argv: string[], options: Call["options"]) => { calls.push({ command, argv, options }); + const isGo = command === "/usr/bin/go" || command === pinnedGo; + const pseudo = (commit: string) => `v4.0.1-0.20261008202137-${commit.slice(0, 12)}`; + if (isGo && argv[0] === "version" && argv[1] === "-m") return { code: 0, stdout: `${command} (binary)\n\tmod\t${GENTLE_AI_MODULE}\t${pseudo(NEW_AI)}\th1:J8EluS7QrfM+m3vmQlF26B/shZRjD6rBfsKtrABL5mc=\n` }; + if (isGo && argv[0] === "list") { + const requested = argv[3].slice(`${GENTLE_AI_MODULE}@`.length); + return { code: 0, stdout: JSON.stringify({ Path: GENTLE_AI_MODULE, Version: pseudo(requested), Origin: { VCS: "git", URL: "https://github.com/gentleman-programming/gentle-ai", Hash: requested } }) }; + } if (command === "/usr/bin/go" && argv[0] === "version") return { code: 0, stdout: `go version ${userGo === "devel" ? "devel go1.26-abc" : `go${userGo}`} darwin/arm64\n` }; if (argv[0] === "install") { mkdirSync(options.env!.GOBIN, { recursive: true });