@tangle-network/agent-app 0.45.27 → 0.45.28

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.
Files changed (46) hide show
  1. package/dist/assistant/index.d.ts +1 -0
  2. package/dist/assistant/index.js +1 -1
  3. package/dist/chunk-C3CAYGGQ.js +18 -0
  4. package/dist/chunk-C3CAYGGQ.js.map +1 -0
  5. package/dist/chunk-EMLGWEHV.js +33 -0
  6. package/dist/chunk-EMLGWEHV.js.map +1 -0
  7. package/dist/{chunk-XEA5EG6P.js → chunk-EPZYYWCT.js} +8 -30
  8. package/dist/chunk-EPZYYWCT.js.map +1 -0
  9. package/dist/{chunk-WD2B6HGY.js → chunk-K4RA5T3A.js} +11 -32
  10. package/dist/chunk-K4RA5T3A.js.map +1 -0
  11. package/dist/{chunk-RT5RKAFX.js → chunk-QGSVF6DZ.js} +8 -18
  12. package/dist/chunk-QGSVF6DZ.js.map +1 -0
  13. package/dist/{chunk-ODYE4A7L.js → chunk-WC43OQMR.js} +40 -29
  14. package/dist/chunk-WC43OQMR.js.map +1 -0
  15. package/dist/{chunk-N3G3FSM4.js → chunk-YXA3U4JZ.js} +9 -47
  16. package/dist/chunk-YXA3U4JZ.js.map +1 -0
  17. package/dist/legibility/cli.js +14 -8
  18. package/dist/legibility/cli.js.map +1 -1
  19. package/dist/legibility/index.js +2 -1
  20. package/dist/peer-floors/cli.js +14 -8
  21. package/dist/peer-floors/cli.js.map +1 -1
  22. package/dist/preflight/cli.js +4 -2
  23. package/dist/preflight/cli.js.map +1 -1
  24. package/dist/{run-tEsZUhAf.d.ts → run-D80hoLKh.d.ts} +1 -1
  25. package/dist/signoff/cli.d.ts +2 -2
  26. package/dist/signoff/cli.js +6 -18
  27. package/dist/signoff/cli.js.map +1 -1
  28. package/dist/signoff/index.d.ts +4 -420
  29. package/dist/signoff/index.js +3 -55
  30. package/dist/signoff/proof-cli.js +11 -6
  31. package/dist/signoff/proof-cli.js.map +1 -1
  32. package/dist/signoff/proof.d.ts +70 -271
  33. package/dist/signoff/proof.js +3 -73
  34. package/dist/signoff/proof.js.map +1 -1
  35. package/dist/theme-contract/cli.js +6 -2
  36. package/dist/theme-contract/cli.js.map +1 -1
  37. package/dist/theme-contract/index.js +2 -1
  38. package/dist/{types-U7Nz-txa.d.ts → types-Bd6uW9vQ.d.ts} +1 -1
  39. package/dist/web-react/index.d.ts +28 -40
  40. package/dist/web-react/index.js +1 -1
  41. package/package.json +1 -1
  42. package/dist/chunk-N3G3FSM4.js.map +0 -1
  43. package/dist/chunk-ODYE4A7L.js.map +0 -1
  44. package/dist/chunk-RT5RKAFX.js.map +0 -1
  45. package/dist/chunk-WD2B6HGY.js.map +0 -1
  46. package/dist/chunk-XEA5EG6P.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/signoff/config.ts","../src/signoff/run.ts","../src/signoff/node-version.ts","../src/signoff/workflow-pin.ts","../src/signoff/exec.ts","../src/signoff/schedule.ts","../src/signoff/seeds.ts","../src/signoff/store.ts","../src/signoff/workspace.ts","../src/signoff/report.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { z } from 'zod'\nimport type { LoadedSignoffConfig, SignoffConfig, SignoffStepSpec } from './types'\n\n/**\n * Where the step list comes from.\n *\n * It is declared by the repo, never hardcoded here, because the three workflows\n * this has to reproduce do not share a shape: tax-agent filters its install to\n * one workspace package and runs a Python toolkit suite; legal-agent runs a\n * react-router typegen and a Worker startup check; agent-app runs a named\n * incident-class gate, a generated-project test and knip. A checker that\n * hardcodes any one of those is wrong for the other two.\n *\n * Three sources, in order:\n * 1. `signoff.config.mjs` (or `.js`) — the full surface, including per-step\n * `env`, `needs` and shuffle specs.\n * 2. a `signoff` key in `package.json` — the same shape, JSON-only.\n * 3. derived from the repo's own `scripts`, so a repo gets a real gate before\n * anyone writes a config. The derivation is documented below and its origin\n * is stamped into the proof, because a derived run is a weaker claim than a\n * declared one.\n */\n\nconst SIGNOFF_CONFIG_FILES: readonly string[] = ['signoff.config.mjs', 'signoff.config.js']\n\nconst shuffleSchema = z.object({\n runs: z.number().int().positive().optional(),\n seeds: z.array(z.number().int()).optional(),\n args: z.array(z.string()).optional(),\n})\n\nconst stepSchema = z.object({\n name: z.string().min(1),\n run: z.string().min(1),\n cwd: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n needs: z.array(z.string()).optional(),\n timeoutMs: z.number().int().positive().optional(),\n shuffle: z.union([z.boolean(), shuffleSchema]).optional(),\n})\n\nconst configSchema = z.object({\n install: z\n .object({\n run: z.string().min(1).optional(),\n storeDirFlag: z.string().nullable().optional(),\n storeEnv: z.string().nullable().optional(),\n cwd: z.string().optional(),\n timeoutMs: z.number().int().positive().optional(),\n env: z.record(z.string(), z.string()).optional(),\n })\n .optional(),\n steps: z.array(stepSchema).min(1),\n maxParallel: z.number().int().positive().optional(),\n env: z.record(z.string(), z.string()).optional(),\n nodeVersion: z.string().min(1).optional(),\n carryFiles: z.array(z.string()).optional(),\n cacheDir: z.string().optional(),\n storeGenerations: z.number().int().positive().optional(),\n})\n\nfunction describeIssues(error: z.ZodError, where: string): string {\n const lines = error.issues.map((issue) => ` ${issue.path.join('.') || '(root)'}: ${issue.message}`)\n return `signoff: ${where} is not a valid config:\\n${lines.join('\\n')}`\n}\n\nexport function parseSignoffConfig(value: unknown, where: string): SignoffConfig {\n const result = configSchema.safeParse(value)\n if (!result.success) throw new Error(describeIssues(result.error, where))\n return result.data as SignoffConfig\n}\n\n/**\n * The script → step derivation, and the dependency edges it asserts.\n *\n * The edges are the speed lever, so each one is a claim about the repo:\n * typecheck, the suite, the build and knip are ASSUMED to read source and not\n * each other's output, so they run concurrently. Only `test:generated`\n * genuinely consumes a build artifact, so only it declares a dependency.\n *\n * **That assumption is a guess, and it is wrong for at least one real repo.**\n * agent-app's own suite copies `dist/` into a generated project while `tsup`\n * (`clean: true`) is deleting it, which fails loudly as \"dist/ not built\" and\n * quietly as a half-copied package. A repo whose suite reads build output MUST\n * declare `needs: ['build']` on it in a `signoff.config.mjs`; the derivation\n * cannot see that from a script name, which is one more reason a derived run is\n * a weaker claim than a declared one.\n *\n * `build:check` supersedes `build` when both exist (legal-agent's `build:check`\n * is `pnpm build && …`, so running both builds twice for one verdict).\n */\nconst DERIVED_STEPS: readonly {\n readonly script: string\n readonly name: string\n readonly shuffle?: boolean\n readonly supersedes?: string\n readonly needsBuild?: boolean\n}[] = [\n { script: 'peer-check', name: 'peer floors' },\n { script: 'typecheck', name: 'typecheck' },\n { script: 'test:gates', name: 'incident-class gates' },\n { script: 'test', name: 'unit tests', shuffle: true },\n { script: 'build', name: 'build' },\n { script: 'build:check', name: 'build + worker checks', supersedes: 'build' },\n { script: 'test:generated', name: 'generated projects', needsBuild: true },\n { script: 'knip', name: 'dead-surface (knip)' },\n]\n\nexport function deriveSignoffConfig(scripts: Readonly<Record<string, string>>): {\n readonly config: SignoffConfig\n readonly used: readonly string[]\n} {\n const present = DERIVED_STEPS.filter((candidate) => scripts[candidate.script] !== undefined)\n const superseded = new Set(present.map((candidate) => candidate.supersedes).filter((name): name is string => !!name))\n const kept = present.filter((candidate) => !superseded.has(candidate.script))\n const buildStep = kept.find((candidate) => candidate.script === 'build' || candidate.script === 'build:check')\n\n const steps: SignoffStepSpec[] = kept.map((candidate) => ({\n name: candidate.name,\n run: `pnpm run ${candidate.script}`,\n ...(candidate.shuffle ? { shuffle: true as const } : {}),\n ...(candidate.needsBuild && buildStep ? { needs: [buildStep.name] } : {}),\n }))\n\n if (steps.length === 0) {\n throw new Error(\n 'signoff: no config and no recognizable scripts. Add a `signoff.config.mjs` naming the steps ' +\n `this repo's CI runs, or a package.json \"signoff\" key. Recognized script names: ` +\n `${DERIVED_STEPS.map((candidate) => candidate.script).join(', ')}.`,\n )\n }\n return { config: { steps }, used: kept.map((candidate) => candidate.script) }\n}\n\nexport interface LoadSignoffConfigOptions {\n readonly repoRoot: string\n /** Explicit config path. Missing file is an error, never a silent fallback. */\n readonly configPath?: string\n}\n\nexport async function loadSignoffConfig(options: LoadSignoffConfigOptions): Promise<LoadedSignoffConfig> {\n const { repoRoot, configPath } = options\n\n if (configPath !== undefined) {\n const abs = resolve(repoRoot, configPath)\n if (!existsSync(abs)) throw new Error(`signoff: no config at ${abs}`)\n return { config: await importConfig(abs), origin: { kind: 'file', path: abs } }\n }\n\n for (const candidate of SIGNOFF_CONFIG_FILES) {\n const abs = join(repoRoot, candidate)\n if (existsSync(abs)) return { config: await importConfig(abs), origin: { kind: 'file', path: abs } }\n }\n\n const pkgPath = join(repoRoot, 'package.json')\n if (!existsSync(pkgPath)) {\n throw new Error(`signoff: ${repoRoot} has no package.json, no signoff.config.mjs, and nothing to derive from.`)\n }\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as {\n signoff?: unknown\n scripts?: Record<string, string>\n }\n if (pkg.signoff !== undefined) {\n return { config: parseSignoffConfig(pkg.signoff, `${pkgPath} \"signoff\"`), origin: { kind: 'package-json', path: pkgPath } }\n }\n\n const derived = deriveSignoffConfig(pkg.scripts ?? {})\n return { config: derived.config, origin: { kind: 'derived', path: pkgPath, scripts: derived.used } }\n}\n\nasync function importConfig(abs: string): Promise<SignoffConfig> {\n const mod: unknown = await import(pathToFileURL(abs).href)\n const value = (mod as { default?: unknown }).default\n if (value === undefined) throw new Error(`signoff: ${abs} must have a default export`)\n return parseSignoffConfig(value, abs)\n}\n","import { spawnSync } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { availableParallelism, homedir } from 'node:os'\nimport { basename, join, resolve } from 'node:path'\nimport { loadSignoffConfig } from './config'\nimport { assertNodeVersion, resolveNodeRequirement, type NodeVersionRequirement } from './node-version'\nimport { runCommand, type CommandResult } from './exec'\nimport { runGraph, validateGraph, type TaskOutcome } from './schedule'\nimport { assertShuffleArgsReachTheRunner, newSeedBase, planAttempts } from './seeds'\nimport { resolveStore } from './store'\nimport { materializeCleanTree, removeCleanTree, repoRootOf, type CleanTree } from './workspace'\nimport type {\n SignoffAttempt,\n SignoffEvent,\n SignoffHostFacts,\n SignoffInstallResult,\n SignoffReport,\n SignoffSource,\n SignoffStepResult,\n SignoffStepSpec,\n} from './types'\n\n/**\n * The sign-off run: reproduce a clean CI environment, then go further.\n *\n * Order matters and each stage exists for a failure this fleet actually paid\n * for:\n * 1. a pristine checkout (`workspace.ts`) — no warm `node_modules`, no Vite\n * cache, because that is the entire mechanical difference between \"green\n * locally\" and \"red in CI\";\n * 2. `--frozen-lockfile` into a store keyed on the lockfile (`store.ts`) — the\n * clean install CI does, without paying the download twice;\n * 3. the repo's declared steps (`config.ts`), run as a graph rather than a line\n * (`schedule.ts`), with the suite re-run under randomized file order and\n * recorded seeds (`seeds.ts`) — the part CI does not do at all.\n *\n * Nothing about a failure is inferred. Every step reports its command, exit\n * code, duration, seed and captured output.\n */\n\nexport interface RunSignoffOptions {\n /** Any directory inside the repo. Defaults to the process cwd. */\n readonly repoDir?: string\n readonly configPath?: string\n /** Default `working-tree` — verify what you are about to commit. */\n readonly source?: SignoffSource\n /** Run every step even after one fails. Default false. */\n readonly keepGoing?: boolean\n /** Base seed. Pass a previous run's to reproduce it exactly. */\n readonly seed?: number\n readonly maxParallel?: number\n readonly cacheDir?: string\n /** Override every shuffled step's run count. */\n readonly shuffleRuns?: number\n /** Keep the clean tree for inspection instead of removing it. */\n readonly keepWorkspace?: boolean\n readonly onEvent?: (event: SignoffEvent) => void\n}\n\nconst DEFAULT_CACHE_DIR = join(homedir(), '.cache', 'agent-app-signoff')\n\nfunction hostFacts(treePath: string, requirement: NodeVersionRequirement | null): SignoffHostFacts {\n const pm = spawnSync('pnpm', ['--version'], { cwd: treePath, encoding: 'utf8' })\n return {\n node: process.version,\n nodePinned: requirement?.declared ?? null,\n nodePinSource: requirement?.source ?? null,\n packageManager: pm.status === 0 ? `pnpm ${pm.stdout.trim()}` : 'pnpm (not resolvable)',\n platform: process.platform,\n arch: process.arch,\n cpus: availableParallelism(),\n }\n}\n\n/** Successful steps keep only a tail; a passing 3,000-test run is not evidence. */\nconst SUCCESS_OUTPUT_TAIL = 4_000\n\nfunction toAttempt(result: CommandResult, seed: number | null, ok: boolean): SignoffAttempt {\n return {\n command: result.command,\n seed,\n exitCode: result.exitCode,\n signal: result.signal,\n durationMs: result.durationMs,\n timedOut: result.timedOut,\n output: ok ? result.output.slice(-SUCCESS_OUTPUT_TAIL) : result.output,\n outputTruncated: result.truncated || (ok && result.output.length > SUCCESS_OUTPUT_TAIL),\n }\n}\n\nfunction buildEnv(\n base: Readonly<Record<string, string | undefined>>,\n layers: readonly (Readonly<Record<string, string>> | undefined)[],\n): Record<string, string | undefined> {\n const env: Record<string, string | undefined> = { ...base }\n for (const layer of layers) {\n if (layer) Object.assign(env, layer)\n }\n return env\n}\n\n/** Where the store flag goes: after the subcommand, so `pnpm install X --store-dir Y` stays valid. */\nfunction withStoreDir(command: string, flag: string | null | undefined, storeDir: string): string {\n if (flag === null) return command\n return `${command} ${flag ?? '--store-dir'} ${JSON.stringify(storeDir)}`\n}\n\nexport async function runSignoff(options: RunSignoffOptions = {}): Promise<SignoffReport> {\n const startedAt = new Date()\n const wallStart = Date.now()\n const repoDir = resolve(options.repoDir ?? process.cwd())\n const repoRoot = repoRootOf(repoDir)\n const { config, origin } = await loadSignoffConfig({ repoRoot, configPath: options.configPath })\n\n // Fail before materializing anything: a bad graph is a config error, and\n // paying for an install to learn that is a waste of the speed this exists for.\n validateGraph(config.steps.map((step) => ({ name: step.name, needs: step.needs })))\n assertShuffleArgsReachTheRunner(config.steps)\n const nodeRequirement = resolveNodeRequirement(repoRoot, config.nodeVersion)\n assertNodeVersion(nodeRequirement)\n\n const source = options.source ?? 'working-tree'\n const cacheDir = resolve(options.cacheDir ?? config.cacheDir ?? DEFAULT_CACHE_DIR)\n // The tree lives under the same root as the stores on purpose: pnpm hardlinks\n // from the store into `node_modules`, and a store on another filesystem\n // silently degrades to a full copy.\n const treePath = join(cacheDir, 'trees', `${basename(repoRoot)}-${process.pid}`)\n\n let tree: CleanTree | null = null\n try {\n tree = materializeCleanTree({ repoDir: repoRoot, dest: treePath, source, carryFiles: config.carryFiles })\n options.onEvent?.({ kind: 'tree', path: tree.path, head: tree.head, dirty: tree.dirty })\n\n const store = resolveStore({ treePath: tree.path, cacheDir, generations: config.storeGenerations })\n options.onEvent?.({ kind: 'store', storeDir: store.storeDir, cacheHit: store.hit, cacheKey: store.cacheKey })\n\n const installSpec = config.install ?? {}\n const installCwd = join(tree.path, installSpec.cwd ?? '.')\n const installCommand = withStoreDir(\n installSpec.run ?? 'pnpm install --frozen-lockfile',\n installSpec.storeDirFlag,\n store.storeDir,\n )\n const storeEnvName = installSpec.storeEnv === null ? null : installSpec.storeEnv ?? 'NPM_CONFIG_STORE_DIR'\n const sharedEnv = buildEnv(process.env, [\n // Parity with CI: a runner that behaves differently under `CI` (vitest's\n // reporter, wrangler's prompts) must behave that way here too.\n { CI: 'true' },\n config.env,\n storeEnvName === null ? undefined : { [storeEnvName]: store.storeDir },\n ])\n\n options.onEvent?.({ kind: 'install-start', command: installCommand })\n const installResult = await runCommand({\n command: installCommand,\n cwd: installCwd,\n env: buildEnv(sharedEnv, [installSpec.env]),\n timeoutMs: installSpec.timeoutMs,\n })\n options.onEvent?.({ kind: 'install-end', exitCode: installResult.exitCode, durationMs: installResult.durationMs })\n\n const install: SignoffInstallResult = {\n command: installCommand,\n storeDir: store.storeDir,\n cacheKey: store.cacheKey,\n cacheHit: store.hit,\n keyedOn: store.keyedOn,\n exitCode: installResult.exitCode,\n durationMs: installResult.durationMs,\n output: installResult.exitCode === 0 ? installResult.output.slice(-SUCCESS_OUTPUT_TAIL) : installResult.output,\n outputTruncated: installResult.truncated,\n }\n\n const host = hostFacts(tree.path, nodeRequirement)\n const seedBase = options.seed ?? newSeedBase()\n\n if (installResult.exitCode !== 0) {\n // Every step is unrunnable, and saying so is the honest report. A gate\n // that reports \"0 failures\" because it never ran anything is the failure\n // mode this whole module exists to prevent.\n return finish({\n ok: false,\n startedAt,\n wallStart,\n tree,\n origin,\n host,\n install,\n steps: config.steps.map(\n (step): SignoffStepResult => ({\n name: step.name,\n status: 'skipped',\n attempts: [],\n durationMs: 0,\n startedAtMs: null,\n finishedAtMs: null,\n }),\n ),\n seedBase,\n keepGoing: options.keepGoing ?? false,\n workspaceRetained: options.keepWorkspace ?? false,\n source,\n options,\n })\n }\n\n const treeRoot = tree.path\n const outcomes = await runGraph<SignoffStepSpec, readonly SignoffAttempt[]>({\n nodes: config.steps,\n maxParallel: options.maxParallel ?? config.maxParallel ?? availableParallelism(),\n keepGoing: options.keepGoing ?? false,\n run: async (step, signal) => {\n const attempts: SignoffAttempt[] = []\n for (const plan of planAttempts(step, seedBase, options.shuffleRuns)) {\n options.onEvent?.({ kind: 'step-start', name: step.name, command: plan.command, seed: plan.seed })\n const result = await runCommand({\n command: plan.command,\n cwd: join(treeRoot, step.cwd ?? '.'),\n env: buildEnv(sharedEnv, [step.env]),\n timeoutMs: step.timeoutMs,\n signal,\n })\n const ok = result.exitCode === 0\n attempts.push(toAttempt(result, plan.seed, ok))\n // Stop at the first failing order: the remaining seeds would report\n // the same defect, and the seed that found it is already recorded.\n if (!ok) {\n emitStepEnd(options, step.name, 'failed', attempts)\n return { ok: false, value: attempts }\n }\n }\n emitStepEnd(options, step.name, 'passed', attempts)\n return { ok: true, value: attempts }\n },\n })\n\n const steps = outcomes.map(toStepResult)\n return finish({\n ok: steps.every((step) => step.status === 'passed'),\n startedAt,\n wallStart,\n tree,\n origin,\n host,\n install,\n steps,\n seedBase,\n keepGoing: options.keepGoing ?? false,\n workspaceRetained: options.keepWorkspace ?? false,\n source,\n options,\n })\n } finally {\n if (tree && !options.keepWorkspace && existsSync(tree.path)) removeCleanTree(tree)\n }\n}\n\n/** Emitted from inside the step, so a watching CLI sees a completion when it\n * happens rather than every completion at the end of the run. */\nfunction emitStepEnd(\n options: RunSignoffOptions,\n name: string,\n status: SignoffStepResult['status'],\n attempts: readonly SignoffAttempt[],\n): void {\n options.onEvent?.({\n kind: 'step-end',\n name,\n status,\n durationMs: attempts.reduce((total, attempt) => total + attempt.durationMs, 0),\n })\n}\n\nfunction toStepResult(outcome: TaskOutcome<readonly SignoffAttempt[]>): SignoffStepResult {\n const attempts = outcome.value ?? []\n const durationMs = attempts.reduce((total, attempt) => total + attempt.durationMs, 0)\n return {\n name: outcome.name,\n status: outcome.status,\n attempts,\n durationMs,\n startedAtMs: outcome.startedAtMs,\n finishedAtMs: outcome.finishedAtMs,\n }\n}\n\ninterface FinishInput {\n readonly ok: boolean\n readonly startedAt: Date\n readonly wallStart: number\n readonly tree: CleanTree\n readonly origin: SignoffReport['configOrigin']\n readonly host: SignoffHostFacts\n readonly install: SignoffInstallResult\n readonly steps: readonly SignoffStepResult[]\n readonly seedBase: number\n readonly keepGoing: boolean\n readonly workspaceRetained: boolean\n readonly source: SignoffSource\n readonly options: RunSignoffOptions\n}\n\nfunction finish(input: FinishInput): SignoffReport {\n const serialMs = input.install.durationMs + input.steps.reduce((total, step) => total + step.durationMs, 0)\n const flags = [\n `--source ${input.source}`,\n `--seed ${input.seedBase}`,\n ...(input.keepGoing ? ['--keep-going'] : []),\n ]\n return {\n ok: input.ok,\n startedAt: input.startedAt.toISOString(),\n repo: {\n root: input.tree.root,\n head: input.tree.head,\n branch: input.tree.branch,\n source: input.source,\n dirty: input.tree.dirty,\n diffSha256: input.tree.diffSha256,\n untrackedFiles: input.tree.untrackedFiles,\n carriedFiles: input.tree.carriedFiles,\n },\n configOrigin: input.origin,\n workspace: input.tree.path,\n workspaceRetained: input.workspaceRetained,\n host: input.host,\n install: input.install,\n steps: input.steps,\n seedBase: input.seedBase,\n wallClockMs: Date.now() - input.wallStart,\n serialMs,\n keepGoing: input.keepGoing,\n reproduce: `agent-app-signoff ${input.tree.root} ${flags.join(' ')}`,\n }\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { resolveWorkflowNodePin } from './workflow-pin'\n\n/**\n * Refuse to sign off on a runtime the product does not ship.\n *\n * CI pins its Node version (`actions/setup-node` with `node-version: 22` in all\n * three fleet workflows). A developer's shell does not — this host runs 24. A\n * local gate that inherits whatever is on `PATH` therefore verifies a runtime\n * nobody deploys, and reports it as the runtime that ships. That is the same\n * failure shape as a number measured in a narrower context than it is\n * presented in, and it is not acceptable in the thing that replaces the merge\n * gate.\n *\n * So the pin is read from the repo and enforced, or it is absent and the report\n * says so. Three sources, in order:\n *\n * - `signoff.config.mjs`'s `nodeVersion` — the explicit declaration.\n * - `.nvmrc` — the pin a repo already keeps for humans.\n * - the `pull_request` workflows' `node-version` (`./workflow-pin`) — the pin\n * CI itself runs on.\n *\n * **The third source was added because the first two were empty on the entire\n * fleet, and that produced a false PASS.** legal-agent `4c0d688` failed CI on\n * `Cannot bundle Node.js built-in \"node:sqlite\"` and this gate signed it off.\n * Reproduced in one installed tree, same bytes: Node 22 fails both files, Node\n * 24 passes both, every time. No `.nvmrc` exists in tax-agent, legal-agent or\n * agent-app; all three pin `node-version: 22` in the workflow being replaced.\n * A gate that replaces a workflow has to read the runtime that workflow pins.\n *\n * A `.nvmrc` and a workflow that disagree is not resolved by preference — it is\n * a refusal, because it means the local gate and CI verify different runtimes,\n * which is the exact defect this module exists to prevent.\n *\n * **`engines.node` is deliberately NOT read.** It is a floor (`\">=18\"`), not a\n * pin, so treating it as one manufactures refusals on every version above the\n * floor — and an unsatisfiable gate does not stop bad work, it gets waived.\n */\n\nexport interface NodeVersionRequirement {\n /** The declared major, e.g. `22`. */\n readonly major: number\n readonly declared: string\n /** Where it came from, for the proof. */\n readonly source: string\n}\n\n/** Leading major from a pin like `22`, `v22.22.3`, `22.22`, `lts/iron` (none). */\nfunction majorOf(raw: string): number | null {\n const match = /^v?(\\d+)(?:\\.|$)/.exec(raw.trim())\n return match?.[1] === undefined ? null : Number.parseInt(match[1], 10)\n}\n\nexport function resolveNodeRequirement(\n repoRoot: string,\n configured?: string,\n): NodeVersionRequirement | null {\n if (configured !== undefined) {\n const major = majorOf(configured)\n if (major === null) {\n throw new Error(\n `signoff: nodeVersion \"${configured}\" does not start with a major version. ` +\n 'Declare a pin like \"22\" or \"22.22.3\".',\n )\n }\n return { major, declared: configured.trim(), source: 'signoff config `nodeVersion`' }\n }\n\n let fromNvmrc: NodeVersionRequirement | null = null\n const nvmrc = join(repoRoot, '.nvmrc')\n if (existsSync(nvmrc)) {\n const raw = readFileSync(nvmrc, 'utf8').trim()\n const major = majorOf(raw)\n // An alias (`lts/iron`) is a pin this module cannot resolve without a\n // network call, so it is reported as no requirement rather than guessed.\n if (major !== null) fromNvmrc = { major, declared: raw, source: '.nvmrc' }\n }\n\n const fromWorkflow = resolveWorkflowNodePin(repoRoot, majorOf)\n if (fromNvmrc && fromWorkflow && fromNvmrc.major !== fromWorkflow.major) {\n throw new Error(\n `signoff: .nvmrc pins Node ${fromNvmrc.declared} and ${fromWorkflow.source} pins ` +\n `${fromWorkflow.declared}. A sign-off that replaces CI cannot verify two runtimes, and picking ` +\n 'one silently would sign off a runtime the other half of the repo says is wrong. Make them agree, ' +\n 'or declare `nodeVersion` in the signoff config.',\n )\n }\n if (fromNvmrc) return fromNvmrc\n if (fromWorkflow) return { ...fromWorkflow, source: fromWorkflow.source }\n return null\n}\n\n/**\n * Throw when the running Node cannot stand in for the declared one.\n *\n * Major-only: a patch difference is not a different runtime, and demanding an\n * exact patch would refuse every host that has not just re-installed.\n */\nexport function assertNodeVersion(requirement: NodeVersionRequirement | null, running = process.version): void {\n if (!requirement) return\n const runningMajor = majorOf(running)\n if (runningMajor === requirement.major) return\n throw new Error(\n `signoff: this repo pins Node ${requirement.declared} (${requirement.source}) and you are running ${running}. ` +\n 'A sign-off that replaces CI has to verify the runtime the product ships, so this refuses rather than ' +\n `reporting a pass it did not earn. Switch with \\`nvm use ${requirement.major}\\`, or change the pin if the ` +\n 'product really has moved.',\n )\n}\n","import { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/**\n * Read the Node pin out of the workflows that gate a merge.\n *\n * This exists because of a measured false PASS. legal-agent `4c0d688` was red\n * in CI on two test files — `Cannot bundle Node.js built-in \"node:sqlite\"` —\n * and the sign-off gate signed it off. Same bytes, same lockfile, same\n * hermetic tree: on Node 22 both files fail, on Node 24 both pass, 100% of the\n * time. The runtime was the whole difference, and the gate never saw it because\n * it looked for a pin in exactly two places the fleet does not keep one.\n *\n * None of tax-agent, legal-agent or agent-app has a `.nvmrc`. All three pin\n * `node-version: 22` in the workflow that IS the merge gate. So the pin the\n * gate is replacing was sitting in the file it is replacing, unread.\n *\n * Scope is deliberate: only workflows that trigger on `pull_request`. Those are\n * the merge gate. agent-app's `publish.yml` runs Node 24.18.0 for its release\n * jobs and triggers on push, and reading it would put the gate in permanent\n * conflict with `ci.yml`'s Node 22.\n *\n * Nothing here guesses. An expression (`${{ matrix.node }}`) is not a version,\n * a `node-version-file` that does not exist is not a pin, and two merge-gate\n * workflows on different majors is a question this module cannot answer — each\n * one refuses and names the config key that settles it.\n */\n\nexport interface WorkflowNodePin {\n /** Repo-relative workflow path, for the proof. */\n readonly file: string\n /** The version as written: `22`, `24.18.0`, `v20`. */\n readonly value: string\n /** `node-version`, or the `node-version-file` path it was read through. */\n readonly via: string\n}\n\nconst WORKFLOW_DIR = join('.github', 'workflows')\n\n/** Everything after an unquoted ` #`, plus surrounding quotes, is not the value. */\nfunction scalarValue(raw: string): string {\n const withoutComment = raw.replace(/\\s+#.*$/, '').trim()\n const quoted = /^(['\"])(.*)\\1$/.exec(withoutComment)\n return (quoted?.[2] ?? withoutComment).trim()\n}\n\nfunction indentOf(line: string): number {\n return line.length - line.trimStart().length\n}\n\nfunction isBlank(line: string): boolean {\n const trimmed = line.trim()\n return trimmed.length === 0 || trimmed.startsWith('#')\n}\n\n/**\n * Does this workflow run on `pull_request`?\n *\n * Both spellings the fleet uses are handled: the block form every workflow here\n * writes, and the inline form (`on: [push, pull_request]`). `on` is quoted in\n * some repos because YAML 1.1 reads a bare `on` as the boolean true, so the\n * quoted keys are matched too.\n */\nexport function triggersOnPullRequest(source: string): boolean {\n const lines = source.split('\\n')\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index] as string\n const header = /^(?:on|\"on\"|'on')\\s*:(.*)$/.exec(line)\n if (!header) continue\n\n const inline = scalarValue(header[1] ?? '')\n if (inline.length > 0) {\n return inline\n .replace(/^\\[|\\]$/g, '')\n .split(',')\n .map((token) => token.trim())\n .includes('pull_request')\n }\n\n // Block form: the trigger names sit at the first nesting level under `on:`.\n // Anything deeper belongs to a trigger's own options (`branches`, `paths`).\n let nesting: number | null = null\n for (let cursor = index + 1; cursor < lines.length; cursor += 1) {\n const body = lines[cursor] as string\n if (isBlank(body)) continue\n const bodyIndent = indentOf(body)\n if (bodyIndent === 0) break\n if (nesting === null) nesting = bodyIndent\n if (bodyIndent !== nesting) continue\n const key = /^\\s*(?:-\\s*)?([A-Za-z_][\\w-]*)\\s*:?\\s*$/.exec(body)\n if (key?.[1] === 'pull_request') return true\n }\n return false\n }\n return false\n}\n\n/** Every Node pin an `actions/setup-node` step declares in one workflow. */\nfunction pinsInWorkflow(repoRoot: string, file: string, source: string): WorkflowNodePin[] {\n const pins: WorkflowNodePin[] = []\n for (const line of source.split('\\n')) {\n const match = /^\\s*(node-version|node-version-file)\\s*:\\s*(\\S.*)$/.exec(line)\n if (!match) continue\n const key = match[1] as string\n const value = scalarValue(match[2] as string)\n // A matrix expression is several runtimes, not one. Refusing to guess is\n // the point; `resolveWorkflowNodePin` turns an all-expression repo into a\n // named refusal rather than a silent \"unpinned\".\n if (value.includes('${{')) continue\n\n if (key === 'node-version') {\n pins.push({ file, value, via: 'node-version' })\n continue\n }\n\n const target = join(repoRoot, value)\n if (!existsSync(target)) {\n throw new Error(\n `signoff: ${file} reads its Node pin from \"${value}\" (node-version-file) and that file does not exist. ` +\n 'The workflow this gate replaces cannot itself run, so there is nothing to verify against.',\n )\n }\n const declared = readFileSync(target, 'utf8')\n .split('\\n')\n .map((entry) => entry.trim())\n .find((entry) => entry.length > 0 && !entry.startsWith('#'))\n if (declared !== undefined) pins.push({ file, value: declared, via: `node-version-file ${value}` })\n }\n return pins\n}\n\n/** Node pins declared by every workflow that gates a merge, in filename order. */\nexport function scanMergeGateNodePins(repoRoot: string): WorkflowNodePin[] {\n const dir = join(repoRoot, WORKFLOW_DIR)\n if (!existsSync(dir)) return []\n\n const pins: WorkflowNodePin[] = []\n const files = readdirSync(dir)\n .filter((name) => name.endsWith('.yml') || name.endsWith('.yaml'))\n .sort()\n for (const name of files) {\n const source = readFileSync(join(dir, name), 'utf8')\n if (!triggersOnPullRequest(source)) continue\n pins.push(...pinsInWorkflow(repoRoot, `${WORKFLOW_DIR}/${name}`, source))\n }\n return pins\n}\n\nexport interface ResolvedWorkflowPin {\n readonly major: number\n readonly declared: string\n readonly source: string\n}\n\n/**\n * One Node major for the whole merge gate, or a refusal that names why not.\n *\n * `majorOf` is injected rather than imported to keep the direction of the\n * dependency one-way: `node-version.ts` owns what a version string means, this\n * module owns where the string lives.\n */\nexport function resolveWorkflowNodePin(\n repoRoot: string,\n majorOf: (raw: string) => number | null,\n): ResolvedWorkflowPin | null {\n const pins = scanMergeGateNodePins(repoRoot)\n if (pins.length === 0) return null\n\n const byMajor = new Map<number, WorkflowNodePin[]>()\n for (const pin of pins) {\n const major = majorOf(pin.value)\n // An alias (`lts/*`) is a pin this module cannot resolve without a network\n // call — the same treatment `.nvmrc` gives one.\n if (major === null) continue\n const bucket = byMajor.get(major)\n if (bucket) bucket.push(pin)\n else byMajor.set(major, [pin])\n }\n\n if (byMajor.size === 0) return null\n if (byMajor.size > 1) {\n const detail = [...byMajor.values()]\n .flat()\n .map((pin) => ` ${pin.file} (${pin.via}): ${pin.value}`)\n .join('\\n')\n throw new Error(\n 'signoff: the workflows that gate a merge here pin different Node majors, so there is no single ' +\n `runtime to verify:\\n${detail}\\n` +\n 'Declare `nodeVersion` in the signoff config to say which one a sign-off means.',\n )\n }\n\n const [entry] = [...byMajor.entries()]\n if (entry === undefined) return null\n const [major, matched] = entry\n const first = matched[0] as WorkflowNodePin\n const files = [...new Set(matched.map((pin: WorkflowNodePin) => pin.file))].join(', ')\n return { major, declared: first.value, source: `${files} (${first.via})` }\n}\n","import { spawn } from 'node:child_process'\n\n/**\n * The subprocess primitive every sign-off step runs through.\n *\n * Three properties the gate depends on, none of which `execSync` gives:\n *\n * 1. **Cancellable.** Under fail-fast, a build still running when typecheck\n * fails is killed rather than waited out — otherwise the \"faster than CI\"\n * claim is spent waiting for work whose verdict no longer matters.\n * 2. **Process-group kill.** Steps run through `sh -c`, so killing the shell\n * leaves `vitest`'s forks and `tsup`'s dts worker orphaned and holding CPU.\n * The child is spawned as a group leader and the whole group is signalled.\n * 3. **Bounded, non-silent capture.** Output is capped, and when the cap is hit\n * the elision is stated in the captured text. A gate that quietly drops the\n * middle of a failure log is a gate that hides the failure.\n */\n\nexport interface CommandResult {\n readonly command: string\n readonly cwd: string\n readonly exitCode: number\n readonly signal: string | null\n readonly durationMs: number\n readonly output: string\n readonly truncated: boolean\n readonly timedOut: boolean\n}\n\nexport interface RunCommandOptions {\n readonly command: string\n readonly cwd: string\n readonly env?: Readonly<Record<string, string | undefined>>\n readonly timeoutMs?: number\n readonly signal?: AbortSignal\n /** Retained bytes before elision. Default 2 MiB. */\n readonly maxOutputBytes?: number\n /** Grace between SIGTERM and SIGKILL. Default 5 s. */\n readonly killGraceMs?: number\n readonly onData?: (chunk: string) => void\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 2 * 1024 * 1024\nconst DEFAULT_KILL_GRACE_MS = 5_000\n/** Fraction of the budget kept from the head; the rest is the tail. The head\n * carries the command echo and the first error, the tail carries the summary. */\nconst HEAD_SHARE = 0.25\n\n/**\n * Ring-ish buffer that keeps a head window and a tail window, so both the first\n * error and the final summary survive a very long log.\n */\nclass BoundedOutput {\n private head = ''\n private tail = ''\n private total = 0\n private readonly headBudget: number\n private readonly tailBudget: number\n\n constructor(private readonly budget: number) {\n this.headBudget = Math.floor(budget * HEAD_SHARE)\n this.tailBudget = budget - this.headBudget\n }\n\n push(chunk: string): void {\n this.total += chunk.length\n if (this.head.length < this.headBudget) {\n const room = this.headBudget - this.head.length\n this.head += chunk.slice(0, room)\n chunk = chunk.slice(room)\n if (chunk.length === 0) return\n }\n this.tail = (this.tail + chunk).slice(-this.tailBudget)\n }\n\n get truncated(): boolean {\n return this.total > this.budget\n }\n\n text(): string {\n if (!this.truncated) return this.head + this.tail\n const elided = this.total - this.head.length - this.tail.length\n return `${this.head}\\n\\n[signoff] ${elided} bytes elided (output exceeded ${this.budget} bytes)\\n\\n${this.tail}`\n }\n}\n\n/** Signal a whole process group, tolerating the race where it already exited. */\nfunction killGroup(pid: number, signal: NodeJS.Signals): void {\n try {\n process.kill(-pid, signal)\n } catch (err) {\n // ESRCH means the group is already gone, which is the outcome we wanted.\n // Anything else is a real failure to signal and must not be swallowed.\n if ((err as NodeJS.ErrnoException).code !== 'ESRCH') throw err\n }\n}\n\n/**\n * Run one shell command to completion and report what happened.\n *\n * Never throws on a non-zero exit — a failing step is data, not an exception.\n * It throws only when the process could not be started at all.\n */\nexport function runCommand(options: RunCommandOptions): Promise<CommandResult> {\n const {\n command,\n cwd,\n env,\n timeoutMs,\n signal,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n killGraceMs = DEFAULT_KILL_GRACE_MS,\n onData,\n } = options\n\n return new Promise<CommandResult>((resolve, reject) => {\n const startedAt = Date.now()\n const buffer = new BoundedOutput(maxOutputBytes)\n let timedOut = false\n let killTimer: NodeJS.Timeout | undefined\n let graceTimer: NodeJS.Timeout | undefined\n\n const child = spawn(command, {\n cwd,\n env: env as NodeJS.ProcessEnv | undefined,\n shell: true,\n // Group leader: lets one signal reach `sh` and everything it spawned.\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n\n const pid = child.pid\n const terminate = (): void => {\n if (pid === undefined || child.exitCode !== null || child.signalCode !== null) return\n killGroup(pid, 'SIGTERM')\n graceTimer = setTimeout(() => {\n if (child.exitCode === null && child.signalCode === null) killGroup(pid, 'SIGKILL')\n }, killGraceMs)\n graceTimer.unref()\n }\n\n const onAbort = (): void => terminate()\n signal?.addEventListener('abort', onAbort, { once: true })\n\n if (timeoutMs !== undefined) {\n killTimer = setTimeout(() => {\n timedOut = true\n terminate()\n }, timeoutMs)\n killTimer.unref()\n }\n\n const collect = (chunk: Buffer): void => {\n const text = chunk.toString('utf8')\n buffer.push(text)\n onData?.(text)\n }\n child.stdout.on('data', collect)\n child.stderr.on('data', collect)\n\n const cleanup = (): void => {\n if (killTimer) clearTimeout(killTimer)\n if (graceTimer) clearTimeout(graceTimer)\n signal?.removeEventListener('abort', onAbort)\n }\n\n child.on('error', (err) => {\n cleanup()\n reject(new Error(`signoff: could not start \\`${command}\\` in ${cwd}: ${err.message}`))\n })\n\n child.on('close', (code, sig) => {\n cleanup()\n resolve({\n command,\n cwd,\n // A signalled process reports code `null`; 128+n is the shell's own\n // convention and keeps the field a number a caller can compare.\n exitCode: code ?? (sig === 'SIGKILL' ? 137 : 143),\n signal: sig,\n durationMs: Date.now() - startedAt,\n output: buffer.text(),\n truncated: buffer.truncated,\n timedOut,\n })\n })\n })\n}\n","/**\n * The dependency graph, and running it as wide as the graph allows.\n *\n * CI runs these steps in a line because a YAML `steps:` list is a line. Nothing\n * about the work requires that: typecheck, the suite, the build and knip all\n * read source and none reads another's output. On a 32-core host, running them\n * concurrently is free wall-clock, and it is the only reason a strictly larger\n * verification can still finish faster than the CI it replaces.\n *\n * The graph is declared per step (`needs`), validated here, and the schedule is\n * derived from it — so an omitted edge is a correctness bug the repo owns, and\n * a cycle is named rather than deadlocking.\n */\n\nexport interface GraphNode {\n readonly name: string\n readonly needs?: readonly string[]\n}\n\n/** Throws on a duplicate name, a dangling `needs`, or a cycle. */\nexport function validateGraph(nodes: readonly GraphNode[]): void {\n const seen = new Set<string>()\n for (const node of nodes) {\n if (seen.has(node.name)) throw new Error(`signoff: two steps are both named \"${node.name}\"; names must be unique`)\n seen.add(node.name)\n }\n for (const node of nodes) {\n for (const need of node.needs ?? []) {\n if (!seen.has(need)) {\n throw new Error(`signoff: step \"${node.name}\" needs \"${need}\", which is not a step in this config`)\n }\n }\n }\n\n const byName = new Map(nodes.map((node) => [node.name, node]))\n const state = new Map<string, 'visiting' | 'done'>()\n const walk = (name: string, path: readonly string[]): void => {\n const status = state.get(name)\n if (status === 'done') return\n if (status === 'visiting') {\n const cycle = [...path.slice(path.indexOf(name)), name].join(' -> ')\n throw new Error(`signoff: dependency cycle among steps: ${cycle}`)\n }\n state.set(name, 'visiting')\n for (const need of byName.get(name)?.needs ?? []) walk(need, [...path, name])\n state.set(name, 'done')\n }\n for (const node of nodes) walk(node.name, [])\n}\n\ntype TaskStatus = 'passed' | 'failed' | 'skipped' | 'cancelled' | 'blocked'\n\nexport interface TaskOutcome<T> {\n readonly name: string\n readonly status: TaskStatus\n /** Present for anything that actually ran, including a cancelled task. */\n readonly value: T | null\n readonly startedAtMs: number | null\n readonly finishedAtMs: number | null\n}\n\nexport interface RunGraphOptions<TNode extends GraphNode, TValue> {\n readonly nodes: readonly TNode[]\n readonly maxParallel: number\n /** `false` stops scheduling after the first failure and kills what is running. */\n readonly keepGoing: boolean\n /** Runs one node. Resolves `{ ok }` for pass/fail; must not throw for a\n * failing command — a non-zero exit is data. */\n readonly run: (node: TNode, signal: AbortSignal) => Promise<{ readonly ok: boolean; readonly value: TValue }>\n /** Wall-clock origin, so outcomes are comparable across the whole run. */\n readonly now?: () => number\n}\n\n/**\n * Run the graph, honouring dependencies and the parallelism cap.\n *\n * Failure semantics, stated because they are the difference between a gate you\n * trust and one you argue with:\n * - fail-fast (default): nothing new is scheduled, everything in flight is\n * aborted and reported as `cancelled`, everything unstarted as `skipped`.\n * - `keepGoing`: independent work continues, but a step whose dependency\n * failed is reported `blocked`. It is never reported as passed, and never\n * silently omitted.\n */\nexport async function runGraph<TNode extends GraphNode, TValue>(\n options: RunGraphOptions<TNode, TValue>,\n): Promise<TaskOutcome<TValue>[]> {\n const { nodes, maxParallel, keepGoing, run, now = () => Date.now() } = options\n validateGraph(nodes)\n\n const origin = now()\n const outcomes = new Map<string, TaskOutcome<TValue>>()\n const pending = new Map(nodes.map((node) => [node.name, node]))\n const running = new Map<string, { readonly promise: Promise<void>; readonly controller: AbortController }>()\n let aborted = false\n\n const failedNames = new Set<string>()\n const passedNames = new Set<string>()\n\n const blockedBy = (node: TNode): boolean => (node.needs ?? []).some((need) => failedNames.has(need))\n const ready = (node: TNode): boolean => (node.needs ?? []).every((need) => passedNames.has(need))\n\n const settle = (name: string, outcome: TaskOutcome<TValue>): void => {\n outcomes.set(name, outcome)\n if (outcome.status === 'passed') passedNames.add(name)\n else failedNames.add(name)\n }\n\n const start = (node: TNode): void => {\n pending.delete(node.name)\n const controller = new AbortController()\n const startedAtMs = now() - origin\n const promise = run(node, controller.signal).then((result) => {\n const finishedAtMs = now() - origin\n const cancelled = controller.signal.aborted && !result.ok\n settle(node.name, {\n name: node.name,\n status: cancelled ? 'cancelled' : result.ok ? 'passed' : 'failed',\n value: result.value,\n startedAtMs,\n finishedAtMs,\n })\n running.delete(node.name)\n })\n running.set(node.name, { promise, controller })\n }\n\n for (;;) {\n if (!aborted) {\n // Drain everything the graph currently permits, up to the cap.\n for (const node of [...pending.values()]) {\n if (running.size >= maxParallel) break\n if (blockedBy(node)) {\n pending.delete(node.name)\n settle(node.name, { name: node.name, status: 'blocked', value: null, startedAtMs: null, finishedAtMs: null })\n continue\n }\n if (ready(node)) start(node)\n }\n }\n\n if (running.size === 0) {\n // Nothing running: either everything settled, or the rest is unreachable.\n if (pending.size === 0) break\n if (aborted) break\n // A pending node whose dependency neither passed nor failed cannot happen\n // after validateGraph, so anything left here is blocked or ready-next.\n const progressed = [...pending.values()].some((node) => ready(node) || blockedBy(node))\n if (!progressed) break\n continue\n }\n\n await Promise.race([...running.values()].map((entry) => entry.promise))\n\n if (!keepGoing && failedNames.size > 0 && !aborted) {\n aborted = true\n for (const entry of running.values()) entry.controller.abort()\n }\n }\n\n for (const node of pending.values()) {\n settle(node.name, {\n name: node.name,\n status: blockedBy(node) ? 'blocked' : 'skipped',\n value: null,\n startedAtMs: null,\n finishedAtMs: null,\n })\n }\n\n return nodes.map((node) => {\n const outcome = outcomes.get(node.name)\n if (!outcome) throw new Error(`signoff: step \"${node.name}\" produced no outcome — scheduler bug`)\n return outcome\n })\n}\n","import { createHash, randomInt } from 'node:crypto'\nimport type { SignoffShuffleSpec, SignoffStepSpec } from './types'\n\n/**\n * Suite-order randomization — the half of this gate that CI does not have.\n *\n * CI runs one arbitrary file order, so a scheduling-dependent failure is a coin\n * flip it happens to win or lose. The `node:sqlite` bundling failure that\n * started this was exactly that shape: it reproduced under CI's clean install\n * and worker sharding and not under a warm local run, and a single fixed order\n * could have missed it in either direction.\n *\n * Two rules make randomization useful rather than merely noisy:\n *\n * 1. **Every seed is recorded.** A shuffled failure that cannot be replayed is\n * a rumour. The report carries the base seed and every derived seed, and the\n * derivation is a pure function of `(base, step, index)` — so `--seed <base>`\n * reproduces the whole run, and a single seed replays one step.\n * 2. **Files are shuffled; tests inside a file are not.** File order is what\n * module-graph and worker-scheduling failures depend on. Shuffling the tests\n * within a file mostly finds intentional ordering in a `describe` block,\n * which is a false alarm, and a gate that cries wolf gets waived.\n */\n\n/**\n * Vitest file-order shuffle. `{seed}` is substituted per attempt.\n *\n * **No `--` separator, and that is measured, not assumed.** The habit is to\n * write `pnpm run test -- --flag`, and it silently breaks this: pnpm forwards\n * script arguments verbatim without needing the separator, so the `--` reaches\n * vitest, whose CLI treats it as end-of-options and drops everything after it.\n * Measured on this repo, four test files, `--reporter=verbose`:\n *\n * | invocation | seed 1 order | seed 2 order |\n * |---|---|---|\n * | `vitest run -- --sequence.shuffle.files=true --sequence.seed=N` | schedule, store, seeds, config | schedule, store, seeds, config |\n * | `vitest run --sequence.shuffle.files=true --sequence.seed=N` | schedule, config, store, seeds | seeds, schedule, config, store |\n *\n * The first row is the failure this gate exists to prevent, applied to itself:\n * a run that reports \"2 orders, seeds recorded\" while running one fixed order\n * twice. If a runner ever does need a separator, its config declares its own\n * `shuffle.args`.\n */\nconst DEFAULT_SHUFFLE_ARGS: readonly string[] = [\n '--sequence.shuffle.files=true',\n '--sequence.seed={seed}',\n]\n\nexport const DEFAULT_SHUFFLE_RUNS = 2\n\n/** A fresh base seed. Random, then recorded — never a fixed constant, or the\n * \"randomized\" order is one more fixed order. */\nexport function newSeedBase(): number {\n return randomInt(0, 2 ** 31 - 1)\n}\n\n/**\n * Derive a step's Nth seed from the base.\n *\n * A hash rather than `base + n`: adjacent seeds produce correlated orders in\n * some runners, and the point is independent samples of the order space.\n */\nexport function deriveSeed(base: number, stepName: string, index: number): number {\n const digest = createHash('sha256').update(`${base}:${stepName}:${index}`).digest()\n return digest.readUInt32BE(0) % 2 ** 31\n}\n\n/**\n * Refuse a shuffled step whose command cannot receive the appended flags.\n *\n * pnpm only forwards extra arguments to a script through `run`, `exec` or\n * `dlx`. The shorthand form puts pnpm's own option parser in front of them, and\n * what happens next is a version lottery. Measured on this host, appending\n * `--sequence.shuffle.files=true --sequence.seed=7` to a script that prints its\n * `process.argv`:\n *\n * | command | pnpm 9.15.9 | pnpm 10.22.0 |\n * |---|---|---|\n * | `pnpm run t <flags>` | forwarded, exit 0 | forwarded, exit 0 |\n * | `pnpm exec node probe.mjs <flags>` | forwarded, exit 0 | forwarded, exit 0 |\n * | `pnpm t <flags>` | `Unknown options`, exit 1 | exit 254, script never ran |\n * | `pnpm --filter web t <flags>` | `Unknown options`, exit 1 | **exit 0, script never ran** |\n *\n * That last cell is the reason this is a refusal and not a note. tax-agent's CI\n * runs `pnpm --filter web test`, and a config that copied the line verbatim\n * would, on pnpm 10, report a green \"unit tests\" step that executed zero tests.\n * A gate reporting safety it did not provide is the exact failure this module\n * exists to prevent, and the shorthand makes it silent.\n *\n * This is a static approximation — it checks the invocation SHAPE, not what the\n * runner received — so it is deliberately narrow: it fires only on commands\n * that invoke pnpm, and only on steps that get arguments appended.\n */\nexport function assertShuffleArgsReachTheRunner(steps: readonly SignoffStepSpec[]): void {\n for (const step of steps) {\n if (!normalizeShuffle(step.shuffle)) continue\n const tokens = step.run.split(/\\s+/).filter((token) => token.length > 0)\n const pnpmAt = tokens.findIndex((token) => token === 'pnpm' || token.endsWith('/pnpm'))\n if (pnpmAt === -1) continue\n if (tokens.slice(pnpmAt + 1).some((token) => token === 'run' || token === 'exec' || token === 'dlx')) continue\n throw new Error(\n `signoff: step \"${step.name}\" runs \\`${step.run}\\` and is shuffled, but pnpm only forwards appended ` +\n 'arguments to a script through `run`, `exec` or `dlx`. In the shorthand form pnpm 9 errors and pnpm 10 ' +\n 'exits 0 having run nothing, which would report a passing suite that never executed. ' +\n `Write it as \\`${step.run.replace(/\\s(\\S+)$/, ' run $1')}\\`.`,\n )\n }\n}\n\nexport interface StepAttemptPlan {\n readonly command: string\n readonly seed: number | null\n}\n\nfunction normalizeShuffle(shuffle: boolean | SignoffShuffleSpec | undefined): SignoffShuffleSpec | null {\n if (shuffle === undefined || shuffle === false) return null\n return shuffle === true ? {} : shuffle\n}\n\n/**\n * Expand one step into the commands that will actually run.\n *\n * An unshuffled step is one attempt with no seed. A shuffled step is one\n * attempt per seed, each with the seed substituted into the appended arguments.\n */\nexport function planAttempts(\n step: SignoffStepSpec,\n seedBase: number,\n overrideRuns?: number,\n): StepAttemptPlan[] {\n const spec = normalizeShuffle(step.shuffle)\n if (!spec) return [{ command: step.run, seed: null }]\n\n const args = spec.args ?? DEFAULT_SHUFFLE_ARGS\n const seeds =\n spec.seeds && spec.seeds.length > 0\n ? [...spec.seeds]\n : Array.from({ length: overrideRuns ?? spec.runs ?? DEFAULT_SHUFFLE_RUNS }, (_unused, index) =>\n deriveSeed(seedBase, step.name, index),\n )\n\n return seeds.map((seed) => ({\n command: `${step.run} ${args.map((arg) => arg.replaceAll('{seed}', String(seed))).join(' ')}`,\n seed,\n }))\n}\n","import { createHash } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs'\nimport { join, relative, sep } from 'node:path'\n\n/**\n * The pristine package store, cached on the lockfile so speed is not paid for\n * with a stale module graph.\n *\n * The whole mechanical difference between \"green locally\" and \"red in CI\" is\n * that CI installs `--frozen-lockfile` into an isolated store with no\n * `node_modules` and no framework cache, and a local run reuses both. Only one\n * of those two is safe to keep:\n *\n * - `node_modules` and any build/framework cache are **never** reused. They are\n * what masked the failure this gate exists to catch, and they are recreated\n * for every run inside a fresh `git worktree`.\n * - The **store** is reused, keyed on the bytes that decide what gets installed.\n * A pnpm store is content-addressed: every entry is named by the hash of what\n * is in it, so a reused entry cannot be a different version of a package than\n * the lockfile asked for. Reusing it skips downloads, not resolution.\n *\n * The key covers every manifest in the tree — lockfile, workspace file, every\n * `package.json`, `.npmrc`. Change any of them and the key changes, so the next\n * run installs into an empty store and pays the honest cold cost. Generations\n * are kept (default 4) rather than one, so moving between a branch and `main`\n * finds both warm instead of thrashing.\n */\n\n/** Basenames whose bytes decide what an install resolves to. */\nconst MANIFEST_FILES: readonly string[] = [\n 'pnpm-lock.yaml',\n 'pnpm-workspace.yaml',\n 'package.json',\n '.npmrc',\n '.nvmrc',\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n 'yarn.lock',\n]\n\n/** Never descended into when hunting manifests. */\nconst SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.wrangler', '.react-router'])\n\nexport interface StoreResolution {\n readonly storeDir: string\n readonly cacheKey: string\n /** True when a store for this key already had content. */\n readonly hit: boolean\n /** Repo-relative manifest paths, sorted — the inputs to `cacheKey`. */\n readonly keyedOn: readonly string[]\n /** Store directories removed by the generation cap. */\n readonly pruned: readonly string[]\n}\n\nexport interface ResolveStoreOptions {\n /** The clean tree whose manifests are hashed. */\n readonly treePath: string\n readonly cacheDir: string\n /** Generations to keep. Default 4. */\n readonly generations?: number\n}\n\nfunction collectManifests(dir: string, root: string, out: string[]): void {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue\n collectManifests(join(dir, entry.name), root, out)\n } else if (MANIFEST_FILES.includes(entry.name)) {\n out.push(relative(root, join(dir, entry.name)).split(sep).join('/'))\n }\n }\n}\n\n/** Every manifest in the tree, repo-relative and sorted. */\nexport function manifestFiles(treePath: string): string[] {\n const found: string[] = []\n collectManifests(treePath, treePath, found)\n return found.sort()\n}\n\n/** sha256 over each manifest's path and content — order-independent by sorting. */\nexport function manifestCacheKey(treePath: string, files: readonly string[]): string {\n const hash = createHash('sha256')\n for (const rel of files) {\n hash.update(rel)\n hash.update('\\0')\n hash.update(createHash('sha256').update(readFileSync(join(treePath, rel))).digest('hex'))\n hash.update('\\n')\n }\n return hash.digest('hex')\n}\n\n/**\n * Prune all but the `keep` most recently used store generations.\n *\n * Recency is the directory's mtime, stamped on every use, so the store a run\n * just used is never the one evicted.\n */\nfunction pruneStores(storesRoot: string, keep: number): string[] {\n if (!existsSync(storesRoot)) return []\n const entries = readdirSync(storesRoot, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => {\n const full = join(storesRoot, entry.name)\n return { full, mtimeMs: statSync(full).mtimeMs }\n })\n .sort((a, b) => b.mtimeMs - a.mtimeMs)\n\n const pruned: string[] = []\n for (const stale of entries.slice(keep)) {\n rmSync(stale.full, { recursive: true, force: true })\n pruned.push(stale.full)\n }\n return pruned\n}\n\nexport function resolveStore(options: ResolveStoreOptions): StoreResolution {\n const { treePath, cacheDir, generations = 4 } = options\n const files = manifestFiles(treePath)\n if (files.length === 0) {\n throw new Error(\n `signoff: no package manifest under ${treePath}. A sign-off run installs from a lockfile; ` +\n 'there is nothing here to install.',\n )\n }\n\n const cacheKey = manifestCacheKey(treePath, files)\n const storesRoot = join(cacheDir, 'stores')\n const storeDir = join(storesRoot, cacheKey)\n // A store directory that exists but holds only the marker is a previous run\n // that died before installing; treat it as a miss so the report does not\n // claim a warm store it does not have.\n const marker = join(storeDir, '.signoff-store.json')\n const hit = existsSync(storeDir) && readdirSync(storeDir).some((entry) => entry !== '.signoff-store.json')\n\n mkdirSync(storeDir, { recursive: true })\n writeFileSync(marker, `${JSON.stringify({ cacheKey, keyedOn: files, usedAt: new Date().toISOString() }, null, 2)}\\n`)\n const now = new Date()\n utimesSync(storeDir, now, now)\n\n return { storeDir, cacheKey, hit, keyedOn: files, pruned: pruneStores(storesRoot, generations) }\n}\n","import { spawnSync } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { copyFileSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\nimport type { SignoffRepoFacts, SignoffSource } from './types'\n\n/**\n * Materialize the pristine checkout every step runs against.\n *\n * **The decision, and the measurement behind it: a `git worktree` of HEAD, not\n * a filtered copy.** Both were timed on two real repos on this host:\n *\n * | repo | `git worktree add --detach` | `rsync -a --exclude node_modules --exclude .git` |\n * |---|---|---|\n * | agent-app (931 tracked files) | **0.04 s / 11 MB** | 0.12 s / 25 MB |\n * | legal-agent (753 tracked files) | **0.06 s / 24 MB** | **3.97 s / 2.7 GB** |\n *\n * The 2.7 GB is the argument. The copy carried `build/`, `.react-router/` and\n * `.wrangler/` — generated output and framework caches — because an exclude\n * list is a hand-maintained enumeration of things to leave behind, and it is\n * never complete. A warm Vite cache is precisely what made the `node:sqlite`\n * bundling failure invisible locally while CI saw it, so a materializer that\n * can leak one has defeated its own purpose. `git` already knows what is source\n * and what is generated, and `.gitignore` is that list, maintained by the repo.\n *\n * The overlay on top is what keeps the gate usable before you commit:\n * `source: 'working-tree'` applies `git diff HEAD` (staged and unstaged) as a\n * patch and copies untracked, non-ignored files in. `source: 'head'` verifies\n * exactly the commit that would merge.\n */\n\nexport interface MaterializeOptions {\n readonly repoDir: string\n readonly dest: string\n readonly source: SignoffSource\n /** Gitignored files the run genuinely needs. Missing ones abort. */\n readonly carryFiles?: readonly string[]\n}\n\nexport interface CleanTree extends SignoffRepoFacts {\n /** Absolute path of the materialized checkout. */\n readonly path: string\n}\n\nfunction git(args: readonly string[], cwd: string): string {\n const result = spawnSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 })\n if (result.error) throw new Error(`signoff: git ${args.join(' ')} failed to start: ${result.error.message}`)\n if (result.status !== 0) {\n throw new Error(`signoff: git ${args.join(' ')} exited ${result.status}\\n${result.stderr.trim()}`)\n }\n return result.stdout\n}\n\n/** Split a `-z` separated git list into entries. */\nfunction zsplit(out: string): string[] {\n return out.split('\\0').filter((entry) => entry.length > 0)\n}\n\nexport function repoRootOf(dir: string): string {\n return git(['rev-parse', '--show-toplevel'], dir).trim()\n}\n\nexport function materializeCleanTree(options: MaterializeOptions): CleanTree {\n const { repoDir, dest, source, carryFiles = [] } = options\n const root = repoRootOf(repoDir)\n const head = git(['rev-parse', 'HEAD'], root).trim()\n const branch = git(['rev-parse', '--abbrev-ref', 'HEAD'], root).trim()\n\n // A crashed earlier run leaves a registered worktree whose directory is gone;\n // `add` then refuses on a name collision. Pruning first is what makes the gate\n // re-runnable after a kill.\n git(['worktree', 'prune'], root)\n mkdirSync(dirname(dest), { recursive: true })\n if (existsSync(dest)) rmSync(dest, { recursive: true, force: true })\n git(['worktree', 'add', '--detach', '--quiet', dest, head], root)\n\n let diffSha256: string | null = null\n let untrackedFiles: string[] = []\n\n if (source === 'working-tree') {\n // `git diff HEAD` covers staged and unstaged changes to tracked files in one\n // patch, including deletions and renames. `--binary` keeps a changed image\n // or lockfile-adjacent binary from silently dropping out of the patch.\n const patch = git(['diff', 'HEAD', '--binary', '--no-color', '--no-ext-diff'], root)\n if (patch.length > 0) {\n diffSha256 = createHash('sha256').update(patch).digest('hex')\n const patchFile = join(dirname(dest), `${dest.split('/').pop() ?? 'tree'}.patch`)\n writeFileSync(patchFile, patch)\n // Fail loud: a patch that does not apply means the tree we would verify is\n // not the tree the developer has, and verifying the wrong bytes is worse\n // than not verifying.\n git(['apply', '--binary', '--whitespace=nowarn', patchFile], dest)\n rmSync(patchFile, { force: true })\n }\n\n untrackedFiles = zsplit(git(['ls-files', '--others', '--exclude-standard', '-z'], root))\n for (const rel of untrackedFiles) {\n const target = join(dest, rel)\n mkdirSync(dirname(target), { recursive: true })\n copyFileSync(join(root, rel), target)\n }\n }\n\n const carried: string[] = []\n for (const rel of carryFiles) {\n if (isAbsolute(rel)) throw new Error(`signoff: carryFiles must be repo-relative; got \"${rel}\"`)\n const from = resolve(root, rel)\n if (!existsSync(from)) {\n throw new Error(\n `signoff: carryFiles names \"${rel}\", which does not exist at ${from}. ` +\n 'Remove it from the config or create the file — installing without it would resolve ' +\n 'against a different registry than the one you think you are verifying.',\n )\n }\n const target = join(dest, rel)\n mkdirSync(dirname(target), { recursive: true })\n copyFileSync(from, target)\n carried.push(rel)\n }\n\n return {\n path: dest,\n root,\n head,\n branch,\n source,\n dirty: diffSha256 !== null || untrackedFiles.length > 0,\n diffSha256,\n untrackedFiles,\n carriedFiles: carried,\n }\n}\n\n/** Unregister and delete a materialized tree. */\nexport function removeCleanTree(tree: CleanTree): void {\n git(['worktree', 'remove', '--force', tree.path], tree.root)\n}\n","import type { SignoffReport, SignoffStepResult } from './types'\n\n/**\n * The proof a human reads before merging.\n *\n * A sign-off gate is only worth the CI it replaces if its output answers the\n * question CI's green tick answers and the ones it does not: what bytes were\n * verified, in what environment, under which suite orders, and how to reproduce\n * it. So the report leads with the verdict, states the subject (commit + patch\n * digest) and the environment (clean tree, store cache state), lists every step\n * with its seeds, and ends with the exact command that runs it again.\n *\n * Failure output is printed in full, under the step's name. Naming the step is\n * the difference between \"CI is red\" and a fix.\n */\n\nconst BAR = '─'.repeat(72)\n\nfunction ms(value: number): string {\n return value >= 10_000 ? `${(value / 1000).toFixed(1)}s` : `${value}ms`\n}\n\nfunction statusMark(status: SignoffStepResult['status']): string {\n switch (status) {\n case 'passed': return 'ok '\n case 'failed': return 'FAIL'\n case 'cancelled': return 'kill'\n case 'blocked': return 'blkd'\n case 'skipped': return '-- '\n }\n}\n\nfunction seedList(step: SignoffStepResult): string {\n const seeds = step.attempts.map((attempt) => attempt.seed).filter((seed): seed is number => seed !== null)\n return seeds.length === 0 ? '' : ` seeds ${seeds.join(', ')}`\n}\n\n/** Concurrency actually achieved, measured from the step windows rather than\n * asserted from the config. A claimed speedup nobody measured is a wish. */\nexport function peakConcurrency(steps: readonly SignoffStepResult[]): number {\n const events: { at: number; delta: number }[] = []\n for (const step of steps) {\n if (step.startedAtMs === null || step.finishedAtMs === null) continue\n events.push({ at: step.startedAtMs, delta: 1 }, { at: step.finishedAtMs, delta: -1 })\n }\n events.sort((a, b) => a.at - b.at || a.delta - b.delta)\n let current = 0\n let peak = 0\n for (const event of events) {\n current += event.delta\n peak = Math.max(peak, current)\n }\n return peak\n}\n\nexport function formatSignoffReport(report: SignoffReport): string {\n const lines: string[] = []\n const verdict = report.ok ? 'SIGN-OFF PASSED' : 'SIGN-OFF FAILED'\n lines.push(BAR, `${verdict} — ${report.repo.branch} @ ${report.repo.head.slice(0, 12)}`, BAR, '')\n\n lines.push('subject')\n lines.push(` repo ${report.repo.root}`)\n lines.push(` source ${report.repo.source}${report.repo.dirty ? ' (working tree carries uncommitted work)' : ''}`)\n if (report.repo.diffSha256) lines.push(` patch sha256:${report.repo.diffSha256.slice(0, 16)}`)\n if (report.repo.untrackedFiles.length > 0) {\n lines.push(` untracked ${report.repo.untrackedFiles.length} file(s) copied in`)\n }\n if (report.repo.carriedFiles.length > 0) lines.push(` carried ${report.repo.carriedFiles.join(', ')}`)\n lines.push('')\n\n lines.push('environment')\n lines.push(` clean tree ${report.workspace}${report.workspaceRetained ? ' (retained)' : ' (removed)'}`)\n lines.push(` install ${report.install.command}`)\n lines.push(\n ` store ${report.install.cacheHit ? 'warm' : 'cold'} — ${report.install.cacheKey.slice(0, 16)} ` +\n `(keyed on ${report.install.keyedOn.length} manifest file(s))`,\n )\n lines.push(\n ` host ${report.host.node} · ${report.host.packageManager} · ${report.host.cpus} cpus` +\n (report.host.nodePinned === null\n ? ' · node UNPINNED by this repo'\n : ` · pinned ${report.host.nodePinned} (${report.host.nodePinSource})`),\n )\n lines.push(\n ` config ${report.configOrigin.kind === 'derived'\n ? `derived from scripts: ${report.configOrigin.scripts.join(', ')}`\n : report.configOrigin.path}`,\n )\n lines.push('')\n\n const width = Math.max(...report.steps.map((step) => step.name.length), 'install'.length)\n lines.push('steps')\n lines.push(\n ` ${report.install.exitCode === 0 ? 'ok ' : 'FAIL'} ${'install'.padEnd(width)} ${ms(report.install.durationMs).padStart(8)}`,\n )\n for (const step of report.steps) {\n const window =\n step.startedAtMs === null || step.finishedAtMs === null\n ? ''\n : ` [${ms(step.startedAtMs)} → ${ms(step.finishedAtMs)}]`\n lines.push(\n ` ${statusMark(step.status)} ${step.name.padEnd(width)} ${ms(step.durationMs).padStart(8)}` +\n ` ${step.attempts.length} run(s)${window}${seedList(step)}`,\n )\n }\n lines.push('')\n\n const peak = peakConcurrency(report.steps)\n const saved = report.serialMs - report.wallClockMs\n lines.push('timing')\n lines.push(` wall clock ${ms(report.wallClockMs)}`)\n lines.push(` serial sum ${ms(report.serialMs)} (install + every step, one after another)`)\n lines.push(\n ` parallel peak ${peak} step(s) at once — ` +\n (saved > 0 ? `${ms(saved)} saved, ${(report.serialMs / report.wallClockMs).toFixed(2)}x` : 'no overlap available'),\n )\n lines.push('')\n\n const failures = report.steps.filter((step) => step.status === 'failed' || step.status === 'cancelled')\n if (report.install.exitCode !== 0) {\n lines.push(BAR, 'install FAILED — no step could run', BAR, report.install.output.trimEnd(), '')\n }\n for (const step of failures) {\n const last = step.attempts[step.attempts.length - 1]\n lines.push(BAR)\n lines.push(`${step.status === 'cancelled' ? 'CANCELLED' : 'FAILED'}: ${step.name}`)\n if (last) {\n lines.push(` command ${last.command}`)\n lines.push(` exit ${last.exitCode}${last.signal ? ` (${last.signal})` : ''}${last.timedOut ? ' — TIMED OUT' : ''}`)\n if (last.seed !== null) {\n lines.push(` seed ${last.seed} — replay this order alone with the same seed`)\n }\n lines.push(BAR, last.output.trimEnd(), '')\n }\n }\n\n const blocked = report.steps.filter((step) => step.status === 'blocked' || step.status === 'skipped')\n if (blocked.length > 0) {\n lines.push(`not judged: ${blocked.map((step) => `${step.name} (${step.status})`).join(', ')}`)\n lines.push('')\n }\n\n lines.push(`reproduce: ${report.reproduce}`)\n return lines.join('\\n')\n}\n\n/** One line for a commit message, a PR comment, or a chat handoff. */\nexport function formatSignoffLine(report: SignoffReport): string {\n const passed = report.steps.filter((step) => step.status === 'passed').length\n return (\n `${report.ok ? 'signoff PASS' : 'signoff FAIL'} ${report.repo.head.slice(0, 12)} — ` +\n `${passed}/${report.steps.length} steps, ${ms(report.wallClockMs)} wall ` +\n `(${ms(report.serialMs)} serial), seed ${report.seedBase}, ` +\n `${report.install.cacheHit ? 'warm' : 'cold'} store, clean install`\n )\n}\n"],"mappings":"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","names":["spawnSync","existsSync","join","resolve","existsSync","readFileSync","join","existsSync","readFileSync","join","majorOf","major","join","existsSync","readFileSync","resolve","createHash","existsSync","readFileSync","readdirSync","join","createHash","existsSync","mkdirSync","rmSync","writeFileSync","join","resolve","join","spawnSync","resolve","existsSync"]}
@@ -1,9 +1,11 @@
1
+ import {
2
+ walkSources
3
+ } from "./chunk-EMLGWEHV.js";
4
+
1
5
  // src/theme-contract/index.ts
2
- import { existsSync, readFileSync, readdirSync } from "fs";
3
- import { join, relative } from "path";
6
+ import { existsSync, readFileSync } from "fs";
7
+ import { relative } from "path";
4
8
  import { fileURLToPath } from "url";
5
- var SOURCE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
6
- var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage"]);
7
9
  var DANGEROUS_UTILITIES = [
8
10
  { suffix: "surface-container-highest", varName: "--secondary" },
9
11
  { suffix: "surface-container-high", varName: "--popover" },
@@ -17,18 +19,6 @@ var UTILITY_PREFIXES = "bg|text|border|ring|fill|stroke";
17
19
  function buildUtilityRe(suffix) {
18
20
  return new RegExp(`(?<![\\w-])(?:${UTILITY_PREFIXES})-${suffix}(?![\\w-])`, "g");
19
21
  }
20
- function walkSources(dir) {
21
- let entries;
22
- try {
23
- entries = readdirSync(dir, { withFileTypes: true });
24
- } catch {
25
- return [];
26
- }
27
- return entries.flatMap((e) => {
28
- if (e.isDirectory()) return SKIP_DIRS.has(e.name) ? [] : walkSources(join(dir, e.name));
29
- return SOURCE_RE.test(e.name) && !e.name.endsWith(".d.ts") ? [join(dir, e.name)] : [];
30
- });
31
- }
32
22
  function definedVars(cssFiles) {
33
23
  const defs = /* @__PURE__ */ new Set();
34
24
  for (const file of cssFiles) {
@@ -53,7 +43,7 @@ function checkThemeContract(opts) {
53
43
  const defined = definedVars([tokensCss, ...opts.extraTokensCss ?? []]);
54
44
  const allow = new Set(opts.allowlist ?? []);
55
45
  const isDefined = (name) => defined.has(name) || allow.has(name);
56
- const files = opts.srcDirs.flatMap(walkSources);
46
+ const files = opts.srcDirs.flatMap((dir) => walkSources(dir, [".d.ts"]));
57
47
  const utilityMatchers = DANGEROUS_UTILITIES.map((u) => ({ ...u, re: buildUtilityRe(u.suffix) }));
58
48
  const seenVar = /* @__PURE__ */ new Map();
59
49
  const seenUtility = /* @__PURE__ */ new Map();
@@ -96,4 +86,4 @@ function displayPath(file) {
96
86
  export {
97
87
  checkThemeContract
98
88
  };
99
- //# sourceMappingURL=chunk-RT5RKAFX.js.map
89
+ //# sourceMappingURL=chunk-QGSVF6DZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theme-contract/index.ts"],"sourcesContent":["/**\n * Exportable theme-token contract checker — the incident guard for the\n * invisible-popover class of bugs.\n *\n * The failure mode (tax-agent's transparent model dropdown; the whole\n * `bg-surface-container-*` family): a consumer app ships a component that\n * references a theme token — either as `var(--popover)` or as a Tailwind class\n * like `bg-surface-container-high` that the agent-app preset maps to\n * `hsl(var(--popover))` — but the app's OWN build never emits that custom\n * property (it forgot `import '@tangle-network/agent-app/styles'`, or dropped a\n * token in its local tokens.css). CSS resolves the missing var to nothing, the\n * surface paints transparent, and NOTHING errors. It ships invisible.\n *\n * `tests/theme/tokens-contract.test.ts` guards agent-app's OWN components. This\n * module lifts that walking logic into a function every CONSUMER app can run\n * against ITS OWN source in CI, comparing references to the tokens.css agent-app\n * ships plus any extra CSS the app defines.\n *\n * ── What each check covers (scope is deliberately honest) ────────────────────\n *\n * 1. var(--…) check — COMPLETE. Every `var(--name)` literal in the scanned\n * source (inline styles, `bg-[var(--name)]` arbitrary Tailwind values, CSS\n * template strings) is matched and compared against the defined token set.\n * This is exact: a `var(--x)` reference is unambiguous. It is a raw-text\n * scan (no AST), so a `var(--x)` written inside a comment or string literal\n * counts too — deliberate: it keeps the single-source logic identical to the\n * agent-app self-test, and a dangling `var(--x)` in a comment is a smell\n * worth surfacing. Suppress a deliberate one with `allowlist`.\n *\n * 2. Tailwind-utility check — INTENTIONALLY PARTIAL. Bare classes like\n * `bg-card` carry no `var(--)` and so are invisible to check 1; Tailwind\n * resolves them to `hsl(var(--card))` at build via the preset. Fully\n * resolving arbitrary Tailwind config is out of scope (it would mean\n * re-implementing Tailwind). Instead we check the SPECIFIC known-dangerous\n * families that have actually shipped invisible: the MD3 surface ladder\n * (`surface-container` / `-high` / `-highest`) and the `card` / `popover`\n * elevation pairs — exactly the utilities the agent-app tailwind-preset\n * registers onto elevation tokens (see src/theme/tailwind-preset.ts, the\n * source of truth for this mapping). The canvas/sequence aliases\n * (`--bg-input`, `--text-primary`, …) are consumed as `bg-[var(--…)]`\n * arbitrary values and so are already covered fully by check 1 — they need\n * no entry here.\n *\n * Node-only (reads the filesystem) → this lives in the `./theme-contract`\n * subpath, NOT `./theme`, which must stay browser-clean (it's in the\n * browser-safe manifest test).\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { walkSources } from '../legibility/walk-sources'\n\n/** Define options for scanning source directories and CSS token files in a theme contract */\nexport interface ThemeContractOptions {\n /** Consumer source directories to scan for token references (recursively). */\n srcDirs: string[]\n /**\n * Path to the base tokens.css whose `--name:` definitions are the ground\n * truth. Defaults to the tokens.css agent-app ships (`./styles`) — the set a\n * consumer gets from `import '@tangle-network/agent-app/styles'`.\n */\n tokensCss?: string\n /**\n * Additional CSS files whose `--name:` definitions also count as defined —\n * the app's own overrides/extensions layered on top of the base tokens.\n */\n extraTokensCss?: string[]\n /**\n * Token names (e.g. `--my-app-accent`) to treat as always-defined, suppressing\n * them from the missing list. For app-specific vars defined outside any CSS\n * the checker can see (injected at runtime, from a third-party stylesheet, …).\n */\n allowlist?: string[]\n}\n\n/** Describe a missing theme contract variable and where it was referenced */\nexport interface ThemeContractMiss {\n /** The undefined custom property, e.g. `--popover`. */\n varName: string\n /**\n * Where it was referenced: `path/to/file.tsx`, or\n * `path/to/file.tsx (via bg-surface-container-high)` when the reference is a\n * Tailwind utility that resolves to the token rather than a literal var().\n */\n referencedIn: string\n}\n\n/** Describe the result of validating a theme contract including success status and missing items */\nexport interface ThemeContractResult {\n ok: boolean\n missing: ThemeContractMiss[]\n}\n\n/**\n * Known-dangerous Tailwind utility families and the elevation token each\n * resolves to, mirroring src/theme/tailwind-preset.ts. Ordered longest-suffix\n * first so `surface-container-highest` is matched before `surface-container`.\n * The negative look-around in {@link buildUtilityRe} makes ordering belt-and-\n * suspenders rather than load-bearing.\n */\nconst DANGEROUS_UTILITIES: ReadonlyArray<{ suffix: string; varName: string }> = [\n { suffix: 'surface-container-highest', varName: '--secondary' },\n { suffix: 'surface-container-high', varName: '--popover' },\n { suffix: 'surface-container', varName: '--card' },\n { suffix: 'card-foreground', varName: '--card-foreground' },\n { suffix: 'popover-foreground', varName: '--popover-foreground' },\n { suffix: 'card', varName: '--card' },\n { suffix: 'popover', varName: '--popover' },\n]\n\n/** Tailwind color-utility prefixes that can carry a background/text/border color. */\nconst UTILITY_PREFIXES = 'bg|text|border|ring|fill|stroke'\n\n/**\n * Match a whole utility class for `suffix`, tolerant of variants (`hover:`,\n * `dark:`) and opacity (`/95`) but not of longer siblings: the trailing\n * `(?![\\w-])` stops `bg-surface-container` from matching inside\n * `bg-surface-container-high`, and `bg-card` from matching inside\n * `bg-card-foreground`.\n */\nfunction buildUtilityRe(suffix: string): RegExp {\n return new RegExp(`(?<![\\\\w-])(?:${UTILITY_PREFIXES})-${suffix}(?![\\\\w-])`, 'g')\n}\n\n/**\n * Every `--name:` DEFINITION across the given CSS files. A definition is\n * `--name:` at the start of a (trimmed) line; RHS references like\n * `hsl(var(--card))` are mid-line and are never counted as definitions.\n */\nfunction definedVars(cssFiles: string[]): Set<string> {\n const defs = new Set<string>()\n for (const file of cssFiles) {\n let css: string\n try {\n css = readFileSync(file, 'utf8')\n } catch {\n continue\n }\n for (const m of css.matchAll(/^\\s*(--[a-z0-9-]+)\\s*:/gim)) if (m[1]) defs.add(m[1])\n }\n return defs\n}\n\n/**\n * Default tokens.css: the one agent-app ships as `./styles`. Resolved relative\n * to this module's URL, but tolerant of where the bundler lands the running\n * code — tsup code-splits shared logic into a chunk at the dist ROOT, so the\n * tokens.css sits one directory DIFFERENTLY depending on layout:\n * - source (src/theme-contract/index.ts) → ../theme/tokens.css (src/theme)\n * - split chunk (dist/contract-*.js) → ./theme/tokens.css (dist/theme)\n * - unsplit entry (dist/theme-contract/index.js) → ../theme/tokens.css\n * Probe both and return the one that exists; fall back to the first for a\n * sensible error path if neither is present.\n */\nfunction defaultTokensCss(): string {\n const candidates = ['../theme/tokens.css', './theme/tokens.css'].map((rel) =>\n fileURLToPath(new URL(rel, import.meta.url)),\n )\n return candidates.find((p) => existsSync(p)) ?? candidates[0]!\n}\n\n/**\n * Check that every theme token a consumer's source references is actually\n * defined in the CSS that consumer ships. Returns the full missing set; the\n * caller decides how to fail (the bin exits non-zero on any miss).\n */\nexport function checkThemeContract(opts: ThemeContractOptions): ThemeContractResult {\n const tokensCss = opts.tokensCss ?? defaultTokensCss()\n const defined = definedVars([tokensCss, ...(opts.extraTokensCss ?? [])])\n const allow = new Set(opts.allowlist ?? [])\n const isDefined = (name: string) => defined.has(name) || allow.has(name)\n\n const files = opts.srcDirs.flatMap((dir) => walkSources(dir, ['.d.ts']))\n const utilityMatchers = DANGEROUS_UTILITIES.map((u) => ({ ...u, re: buildUtilityRe(u.suffix) }))\n\n // Dedupe by varName (literal check) and by varName+utility (utility check),\n // keeping the FIRST referencing file — enough to locate the offender without\n // drowning the report when one token is referenced across many files.\n const seenVar = new Map<string, string>()\n const seenUtility = new Map<string, string>()\n\n for (const file of files) {\n let text: string\n try {\n text = readFileSync(file, 'utf8')\n } catch {\n continue\n }\n const where = displayPath(file)\n\n // Check 1 — literal var(--…) references.\n for (const m of text.matchAll(/var\\(\\s*(--[a-z0-9-]+)/gi)) {\n const name = m[1]\n if (!name || isDefined(name) || seenVar.has(name)) continue\n seenVar.set(name, where)\n }\n\n // Check 2 — known-dangerous Tailwind utility classes.\n for (const u of utilityMatchers) {\n if (isDefined(u.varName)) continue\n const key = `${u.varName}::${u.suffix}`\n if (seenUtility.has(key)) continue\n u.re.lastIndex = 0\n if (u.re.test(text)) seenUtility.set(key, `${where} (via ${firstUtilityHit(text, u.suffix)})`)\n }\n }\n\n const missing: ThemeContractMiss[] = [\n ...[...seenVar].map(([varName, referencedIn]) => ({ varName, referencedIn })),\n ...[...seenUtility].map(([key, referencedIn]) => ({ varName: key.split('::')[0]!, referencedIn })),\n ]\n return { ok: missing.length === 0, missing }\n}\n\n/** The literal utility class (with prefix) first seen in `text` for `suffix`, for the report. */\nfunction firstUtilityHit(text: string, suffix: string): string {\n const m = buildUtilityRe(suffix).exec(text)\n return m?.[0] ?? `<utility>-${suffix}`\n}\n\n/** Path relative to cwd when it stays inside it, else the path as given — for readable reports. */\nfunction displayPath(file: string): string {\n const rel = relative(process.cwd(), file)\n return rel && !rel.startsWith('..') ? rel : file\n}\n"],"mappings":"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","names":[]}
@@ -307,7 +307,8 @@ function ModelPicker({ value, onChange, models, loading, renderProviderBadge, re
307
307
  filtered.length === 0 && /* @__PURE__ */ jsx2("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "No models match your search" }),
308
308
  filtered.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
309
309
  ] }),
310
- !loading && !filtered && /* @__PURE__ */ jsxs2(Fragment, { children: [
310
+ !loading && !filtered && models.length === 0 && /* @__PURE__ */ jsx2("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "No models available" }),
311
+ !loading && !filtered && models.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
311
312
  priorityGroup && sections.priority.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
312
313
  /* @__PURE__ */ jsx2(SectionHeader, { children: priorityGroup.label }),
313
314
  sections.priority.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
@@ -2773,20 +2774,20 @@ function ActivityRow({
2773
2774
  function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent activity", emptyLabel = "No agent runs yet." }) {
2774
2775
  const [rows, setRows] = useState11([]);
2775
2776
  const [cursor, setCursor] = useState11(void 0);
2776
- const [loading, setLoading] = useState11(false);
2777
+ const [status, setStatus] = useState11("loading");
2777
2778
  const [error, setError] = useState11(null);
2778
2779
  const load = useCallback6(
2779
2780
  async (from) => {
2780
- setLoading(true);
2781
+ setStatus("loading");
2781
2782
  setError(null);
2782
2783
  try {
2783
2784
  const page = await fetchActivity(from);
2784
2785
  setRows((prev) => mergeActivityPages(from === void 0 ? [] : prev, page.items));
2785
2786
  setCursor(page.nextCursor);
2787
+ setStatus("ready");
2786
2788
  } catch (e) {
2787
2789
  setError(e instanceof Error ? e.message : String(e));
2788
- } finally {
2789
- setLoading(false);
2790
+ setStatus("error");
2790
2791
  }
2791
2792
  },
2792
2793
  [fetchActivity]
@@ -2794,6 +2795,7 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
2794
2795
  useEffect8(() => {
2795
2796
  void load();
2796
2797
  }, [load]);
2798
+ const loading = status === "loading";
2797
2799
  return /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
2798
2800
  /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
2799
2801
  /* @__PURE__ */ jsx10("h2", { className: "flex-1 text-sm font-semibold", children: title }),
@@ -2809,8 +2811,8 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
2809
2811
  }
2810
2812
  )
2811
2813
  ] }),
2812
- error && /* @__PURE__ */ jsx10("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: error }),
2813
- !error && rows.length === 0 && !loading && /* @__PURE__ */ jsx10("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
2814
+ status === "error" && /* @__PURE__ */ jsx10("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: error }),
2815
+ status === "ready" && rows.length === 0 && /* @__PURE__ */ jsx10("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
2814
2816
  /* @__PURE__ */ jsx10("span", { role: "status", "aria-live": "polite", "aria-busy": loading, className: "sr-only", children: loading ? "Loading activity\u2026" : "" }),
2815
2817
  /* @__PURE__ */ jsx10("div", { className: "space-y-1.5", "aria-busy": loading, children: rows.map((record) => /* @__PURE__ */ jsx10(ActivityRow, { record, renderMissionRef }, record.taskId)) }),
2816
2818
  cursor && /* @__PURE__ */ jsx10(
@@ -4175,6 +4177,7 @@ function SessionRow({
4175
4177
  // src/web-react/record-grid.tsx
4176
4178
  import {
4177
4179
  Fragment as Fragment7,
4180
+ isValidElement,
4178
4181
  useCallback as useCallback9,
4179
4182
  useEffect as useEffect11,
4180
4183
  useId as useId2,
@@ -4511,6 +4514,7 @@ function pruneRecordGridOverlay(rows, overlay) {
4511
4514
 
4512
4515
  // src/web-react/record-grid.tsx
4513
4516
  import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
4517
+ var EMPTY_RECORD_GRID_ROWS = [];
4514
4518
  var CELL_KEY_SEPARATOR = "\0";
4515
4519
  function cellKey(rowId, columnId) {
4516
4520
  return `${rowId}${CELL_KEY_SEPARATOR}${columnId}`;
@@ -4537,22 +4541,21 @@ function groupColumns(columns) {
4537
4541
  }
4538
4542
  var INPUT_CLASS = "w-full rounded-md border border-border bg-background px-2 py-1 text-sm text-foreground outline-none focus:border-primary/60 focus:ring-1 focus:ring-primary/40";
4539
4543
  var BASIS_TONES2 = {
4540
- source: "border-primary/60 text-primary",
4541
- confirmed: "border-success/60 text-success",
4542
- derived: "border-border text-muted-foreground"
4544
+ extracted: "border-primary/60 text-primary",
4545
+ entered: "border-success/60 text-success",
4546
+ computed: "border-border text-muted-foreground",
4547
+ asserted: "border-warning/60 text-warning"
4543
4548
  };
4544
4549
  var BASIS_TITLES = {
4545
- source: "Extracted from a source document",
4546
- confirmed: "Confirmed by a person",
4547
- derived: "Computed from other values"
4550
+ extracted: "Extracted from a source document",
4551
+ entered: "Confirmed by a person",
4552
+ computed: "Computed from other values",
4553
+ asserted: "Agent, unverified \u2014 no source recorded"
4548
4554
  };
4549
4555
  function RecordGrid({
4550
4556
  columns,
4551
- rows,
4552
4557
  caption,
4553
- state = "ready",
4554
- error,
4555
- onRetry,
4558
+ state,
4556
4559
  empty,
4557
4560
  onCreate,
4558
4561
  onUpdate,
@@ -4586,10 +4589,11 @@ function RecordGrid({
4586
4589
  editingRef.current = next;
4587
4590
  setEditingState(next);
4588
4591
  }, []);
4592
+ const callerRows = state.status === "ready" || state.status === "empty" ? state.value : EMPTY_RECORD_GRID_ROWS;
4589
4593
  useEffect11(() => {
4590
- setOverlay((current) => pruneRecordGridOverlay(rows, current));
4591
- }, [rows]);
4592
- const visibleRows = useMemo7(() => projectRecordGridRows(rows, overlay), [rows, overlay]);
4594
+ setOverlay((current) => pruneRecordGridOverlay(callerRows, current));
4595
+ }, [callerRows]);
4596
+ const visibleRows = useMemo7(() => projectRecordGridRows(callerRows, overlay), [callerRows, overlay]);
4593
4597
  const activeFocus = useMemo7(() => {
4594
4598
  if (focus === null) return null;
4595
4599
  if (!visibleRows.some((row) => row.id === focus.rowId)) return null;
@@ -4798,8 +4802,7 @@ function RecordGrid({
4798
4802
  },
4799
4803
  [beginEdit, columns, editing, focusCell, onUpdate, visibleRows]
4800
4804
  );
4801
- const dataState = typeof error === "string" && error !== "" ? "error" : state;
4802
- if (dataState === "loading") {
4805
+ if (state.status === "idle" || state.status === "loading") {
4803
4806
  return /* @__PURE__ */ jsxs12("div", { className: `space-y-3 ${className ?? ""}`, children: [
4804
4807
  toolbar,
4805
4808
  /* @__PURE__ */ jsxs12(
@@ -4820,16 +4823,16 @@ function RecordGrid({
4820
4823
  )
4821
4824
  ] });
4822
4825
  }
4823
- if (dataState === "error") {
4826
+ if (state.status === "error") {
4824
4827
  return /* @__PURE__ */ jsxs12("div", { className: `space-y-3 ${className ?? ""}`, children: [
4825
4828
  toolbar,
4826
4829
  /* @__PURE__ */ jsxs12("div", { role: "alert", className: "rounded-xl border border-destructive/40 bg-destructive/10 px-4 py-4", children: [
4827
- /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-destructive", children: typeof error === "string" && error !== "" ? error : `${caption} could not be loaded.` }),
4828
- onRetry && /* @__PURE__ */ jsx14(
4830
+ /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-destructive", children: state.message }),
4831
+ /* @__PURE__ */ jsx14(
4829
4832
  "button",
4830
4833
  {
4831
4834
  type: "button",
4832
- onClick: onRetry,
4835
+ onClick: state.retry,
4833
4836
  className: "mt-3 rounded-md border border-destructive/40 px-3 py-1.5 text-xs font-medium text-destructive transition hover:bg-destructive/10",
4834
4837
  children: "Try again"
4835
4838
  }
@@ -4862,7 +4865,15 @@ function RecordGrid({
4862
4865
  /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-foreground", children: empty.title }),
4863
4866
  empty.description && /* @__PURE__ */ jsx14("p", { className: "mx-auto mt-1 max-w-md text-sm text-muted-foreground", children: empty.description }),
4864
4867
  /* @__PURE__ */ jsxs12("div", { className: "mt-4 flex flex-wrap items-center justify-center gap-2", children: [
4865
- empty.action,
4868
+ empty.action && (isValidElement(empty.action) ? empty.action : /* @__PURE__ */ jsx14(
4869
+ "button",
4870
+ {
4871
+ type: "button",
4872
+ onClick: empty.action.onClick,
4873
+ className: "rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30",
4874
+ children: empty.action.label
4875
+ }
4876
+ )),
4866
4877
  onCreate && /* @__PURE__ */ jsx14(
4867
4878
  "button",
4868
4879
  {
@@ -5158,7 +5169,7 @@ function CellEditor({ column, rowLabel, text, invalid, describedBy, onText, onCo
5158
5169
  );
5159
5170
  }
5160
5171
  function SourceMarker({ panelId, columnHeader, rowLabel, source, open, onToggle }) {
5161
- const basis = source.basis ?? "source";
5172
+ const basis = source.basis ?? "asserted";
5162
5173
  return /* @__PURE__ */ jsxs12("span", { className: "relative inline-flex", children: [
5163
5174
  /* @__PURE__ */ jsx14(
5164
5175
  "button",
@@ -6364,4 +6375,4 @@ export {
6364
6375
  useThinkingSeconds,
6365
6376
  ChatMessages
6366
6377
  };
6367
- //# sourceMappingURL=chunk-ODYE4A7L.js.map
6378
+ //# sourceMappingURL=chunk-WC43OQMR.js.map