From 60e9b26b17825c5042eec5d2dfcd4f0299c14bea Mon Sep 17 00:00:00 2001 From: MGrin Date: Tue, 18 Aug 2026 22:14:24 +0800 Subject: [PATCH] feat(build): report which commit this process was actually loaded from MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bb bundles a `path:` plugin FROM SOURCE at reload, so a revision read at module load is by construction the code now executing. Nothing else on the machine can answer it: `bb plugin list` prints `running` and the source path but no revision, `bb plugin source` has none to record for a path: source, and dist/ is not the loaded artifact — its mtime was measured lying by fifteen minutes. This plugin is a UI surface and registered only `bb.rpc`, with no CLI at all, so `bb memory-ui` really was "unknown command" — verified six times rather than concluded once, because that message is also this machine's classic transient false negative. That absence made it the one plugin whose running revision could not be asked for from a shell, and so the one the loaded-revision check in dotfiles could never cover. Registers a single introspection command rather than a whole surface. `build` is not a feature; it is the answer to "is what I merged what is running". Co-Authored-By: Claude Opus 5 (1M context) --- server.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/server.ts b/server.ts index 6ffe289..06cc0c9 100644 --- a/server.ts +++ b/server.ts @@ -29,7 +29,7 @@ // dependency to rebuild against bb's embedded runtime on every app update). // Writes go through `bb memory` — the public surface — so content guards, // version history and optimistic concurrency apply exactly as they do to agents. -import { execFile } from "node:child_process"; +import { execFile, execFileSync } from "node:child_process"; import os from "node:os"; import { promisify } from "node:util"; import { defineRpcContract, type BbPluginApi } from "@bb/plugin-sdk"; @@ -351,7 +351,72 @@ async function bbMemory(args: string[], projectId: string | null) { } } +/** + * Which commit is this PROCESS running? (MX-139/MX-141) + * + * bb bundles a `path:` plugin FROM SOURCE at reload, so a revision read here — at module + * load, the same moment — is by construction the code now executing. Nothing else can say: + * `bb plugin list` prints `running` and the source path but no revision, `bb plugin source` + * has none to record for a path: source, and dist/ is NOT the loaded artifact (its mtime was + * measured lying by 15 minutes). So a checkout can sit clean on main, every drift check + * green, while the process runs something older. + * + * Synchronous on purpose: the value must be fixed before anything can observe it, and it is + * one git call per load. Failure yields rev: null rather than a guess — a tarball install has + * no git dir, and that must stay distinguishable from a real mismatch so a checker reports + * UNKNOWN rather than OK. `dirty` rides along because a bundle built from an edited tree + * matches NO commit, and comparing revisions alone would call that a match. + */ +const BUILD_STAMP: { rev: string | null; dirty: boolean | null; sourceDir: string; loadedAt: string; why: string | null } = (() => { + const here = import.meta.dirname; + const loadedAt = new Date().toISOString(); + try { + const git = (args: string[]): string => + execFileSync("git", ["-C", here, ...args], { encoding: "utf8", timeout: 5000 }).trim(); + return { + rev: git(["rev-parse", "HEAD"]), + dirty: git(["status", "--porcelain"]).length > 0, + sourceDir: git(["rev-parse", "--show-toplevel"]), + loadedAt, + why: null, + }; + } catch (e) { + return { rev: null, dirty: null, sourceDir: here, loadedAt, why: e instanceof Error ? e.message : String(e) }; + } +})(); + export default async function plugin(bb: BbPluginApi) { + // This plugin is a UI surface — until now it registered only `bb.rpc` and had no CLI at + // all, so `bb memory-ui` was genuinely "unknown command". That also made it the one + // plugin whose RUNNING revision could not be asked for from a shell, and so the one the + // loaded-revision check in dotfiles could never cover. + // + // Registering a single introspection command rather than a whole surface: `build` is not + // a feature, it is the answer to "is what I merged what is running". + bb.cli.register({ + name: "memory-ui", + summary: "The memory browser UI (no interactive commands — this surface is introspection only)", + commands: [ + { + name: "build", + summary: "Which commit this RUNNING process was loaded from (not the checkout)", + usage: "bb memory-ui build [--json]", + }, + ], + async run(argv) { + if (argv[0] === "build" || argv.length === 0) { + if (argv.includes("--json")) return { exitCode: 0, stdout: JSON.stringify(BUILD_STAMP) }; + const dirty = BUILD_STAMP.dirty === null ? "" : BUILD_STAMP.dirty ? " +dirty" : ""; + const why = BUILD_STAMP.why ? ` (${BUILD_STAMP.why})` : ""; + return { + exitCode: 0, + stdout: `loaded ${BUILD_STAMP.rev ?? "unknown"}${dirty} from ${BUILD_STAMP.sourceDir} at ${BUILD_STAMP.loadedAt}${why}`, + }; + } + return { exitCode: 1, stderr: `unknown subcommand: ${argv[0]} — only 'build' exists` }; + }, + }); + const titleCache = new Map(); const viewClause = (view: string) =>