Skip to content

Commit e6b8be0

Browse files
Codex, phases 6 and 7: install.sh and install.ps1 wire Codex when it is chosen (Codex closed on every system) (#56)
* codex phase 6: the night check says when Codex's hooks never run _agents.py gains what install.sh needs to wire Codex: `ouvert` (can Codex be chosen on this system), `codex-home` (the CODEX_HOME agents.json records, when it lists Codex and is trusted; written as bytes, whatever the locale) and `pose-hooks`, which notes when the installer wired the kit's hooks in Codex's hooks.json (~/.brain-in-a-box/codex-hooks-poses.json, 0600, written in one step as the sign of life is). The line `choisir` prints is unchanged: install.ps1 reads it. hooks_muets() reads that record against the sign of life of phase 5: Codex kept a session (a rollout, compressed or not, regular files only) since the install, and none of the kit's hooks has run since. The vault's report then says, in orange: "Les hooks Codex ne tournent pas : ouvre Codex, tape /hooks et approuve-les." Only when agents.json lists Codex: Claude alone reads as before, and Codex is still closed on every system. * codex phase 6: install.sh wires Codex when it is chosen (Codex still closed) When agents.json lists Codex, install.sh now: - installs Codex if it is missing, from OpenAI's official installer as published with the rust-v0.162.1 release, checked against a SHA-256 pinned in install.sh before it runs (it then checks the binary's own digest), with the user's PATH and without VOYAGE_API_KEY; - appends the kit's block to the global AGENTS.md Codex reads (AGENTS.override.md when that one has text), drawn from CLAUDE.global.template.md, which stays as it is for install.ps1; - merges the six hooks into Codex's hooks.json (--agent codex, a group of the kit's own at the end, previous version kept in 0600; one that does not parse stops the install at preflight); - approves them for the user and says so: new [hooks.state."..."] tables in config.toml, never a table rewritten, refused over hooks in any other form, checked by `codex features list` before and after (put back as it was otherwise); - allows gbq query and gbq search without asking in a rules file of its own, with host_executable: without it Codex matches any program named gbq, ./gbq included, which a session can write; - places an AGENTS.md in the vault when it has none, installs the skills in ~/.agents/skills, says `codex login`, and opens the preferred agent for the onboarding. A new install at a terminal asks which AI only where Codex can be chosen: never while it is closed. An agent no longer chosen keeps the kit's files, and the installer says how to remove them. Claude alone is unchanged: compared with master (same stand-ins, nothing chosen, --agents claude, a Codex of the user's own beside it), the output, the calls and install.log are the same, and nothing is written in ~/.codex or ~/.agents. test-hooks.sh §15bis tests each function against stand-ins; §16 runs install.sh end to end with Codex open in a copy of the kit (alone, both, both then Claude then both, a config.toml it will not add to, at a terminal). SECURITY.md says that the installer approves the kit's Codex hooks and allows gbq's two reads. * CI: codex-mock runs install.sh with Codex chosen, and the real Codex runs the hooks it approved On macOS and Linux, codex_mock.py runs install.sh --agents codex from a copy of the kit where Codex is open, in a home of its own, with stand-ins for what would schedule jobs or reach gbrain: Codex comes from its pinned installer, for real. The real Codex then runs, in the vault, the six hooks from the hooks.json install.sh wrote, approved by the tables it wrote, no trust bypassed (control: without them, none runs), and reads the kit's block in the global AGENTS.md, the vault's AGENTS.md and the skills in ~/.agents/skills. `codex execpolicy check` shows gbq's rules allow the kit's gbq only (control: without host_executable, ./gbq query runs without asking). The job runs when install.sh, engine/codex, the global template, the skills installer or a skill changes. * codex-mock: the kit's copy has its folder, and the six hooks are read over three sessions at most install_sh_wires_codex copied install.sh into a folder it had not made (FileNotFoundError on macOS and Linux, the first run). Codex also runs one event's hooks together (three at Stop, two at SessionStart), and a hook may miss another's line in the sign of life once (_agents.vu): the scenario now takes what each session showed, over three sessions at most, before saying a hook did not run. The control (without the tables install.sh wrote, none runs) stays a single session. * terminal.py answers a question half a second after it appears, as a person would Answered at once, the reply could reach the terminal before `read -rs` turned its echo off: the echoed Enter added a blank line now and then (macOS CI, test-hooks.sh §16, "a re-run asked more or said other things": 15a16 >). Reproduced here with /bin/bash 3.2 under load, the prompt read by `read -rs -p " Voyage key: "`: 5 runs in 40 with the extra line before, 0 in 40 after. * test-hooks.sh §16: a hooks.json of Codex's that does not parse stops install.sh at preflight Each of step 6's protections taken out one at a time: removing the preflight check of Codex's hooks.json left every test green (the merge step then only warned, and the install went on). The whole install.sh, Codex chosen over a hooks.json that does not parse: it stops before anything, the file left as it was, no install.log, no stand-in called; only the choice is recorded. * Codex plan: Codex stays closed after steps 6 and 7, and step 6, done and its deviations Two dated sections. "Changement au plan": steps 6 (install.sh) and 7 (install.ps1) build and test the whole Codex path of the installers but do not open Codex; opening it will be a one-line PR per system after a manual vrai-codex run. "Étape 6 — fait et écarts": what install.sh does when Codex is chosen, the twelve deviations from the plan (pinned installer from the release, the global block drawn from the template left as it is, the vault's AGENTS.md from engine/codex and none in the team vault, host_executable for gbq's rules, the question only where Codex is open...), the proof that Claude alone is unchanged, the tests, the mutations and the CI runs. * codex phase 7: the launcher pauses Codex when %ProgramData%\OpenAI\Codex could be changed by another account Step 3's review left a blocking point for Windows: a standard account can create %ProgramData%\OpenAI\Codex when it is missing, and Codex loads it all the same (its own probe, config/src/loader/windows.rs, only measures). check_system_settings() runs before any probe and again just before the question. ProgramData comes from the Known Folder, as Codex finds it, never from the variable (_managed_files() too). OpenAI and OpenAI\Codex, when there, and the files Codex reads in them: no link or reparse point, the right kind, owned by SYSTEM, the Administrators or TrustedInstaller, and no entry letting any other account change them, or (for Codex) the files it gets. Stricter than Codex's probe, which looks at Users, Authenticated Users and Everyone only. Otherwise Codex pauses (managed). Tests: the decision on access lists written by hand (every system), the check over stood-in descriptions, its place in the call; on Windows, real folders whose owner and access icacls sets, and a junction. * codex phase 7: install.sh's Codex merges move to engine/codex/wire.py, which install.ps1 shares The Python install.sh ran from a heredoc (check, merge, approve, restore, global) is now engine/codex/wire.py: install.sh calls it as before, with the same output (test-hooks.sh 217/217 unchanged); install.ps1 will call it with --ps1 <python.exe>: its lines in French ASCII, and the hooks in the form Codex runs on Windows, & '<python.exe>' '<hook>' --agent codex, in commandWindows and command alike, single-quoted (PowerShell would expand $ and read backticks inside double quotes; quotes, typographic ones too, written twice). Their approval is hashed on that command, which Codex hashes on Windows (discovery.rs, command_windows.unwrap_or(command)); an entry of the kit's is recognised by its \.claude\hooks\brain\ path, any case. engine/codex/ joins CODEOWNERS and "Never merge": it writes the hooks' approval into the user's config.toml, and its rules file lets gbq run outside Codex's sandbox. Tests: test_codex_wire.py; on Windows, the command run by pwsh and Windows PowerShell with a path holding ', $HOME, a backtick, a typographic quote, é, & and ;. * codex phase 7: install.ps1 wires Codex when it is chosen (Codex still closed) As install.sh does since phase 6, in a section "Codex" before the vault's history: Codex from the install.ps1 published with the rust-v0.162.1 release, checked against a pinned SHA-256 before it runs in a PowerShell of its own (CODEX_RELEASE, no question, the chosen CODEX_HOME, none of its own variables nor VOYAGE_API_KEY), an .exe only; the kit's block in the global AGENTS.md; the hooks in hooks.json and their approval, said on screen (decision 2); gbq query and gbq search allowed at the kit's gbq.cmd only; the vault's AGENTS.md when missing; the skills in ~\.agents\skills. A hooks.json it cannot read stops it at preflight, before anything. The question at a console, never while Codex is closed; codex login, the preferred agent's onboarding and an agent no longer chosen said at the end. Claude alone: nothing more runs nor is said. Bibo announces one more section, "Je branche Codex" (steps.rs), so its count is out of 14. .github/ci/install-ps1-codex.ps1 runs the block's functions against stand-ins under pwsh, on macOS, Linux and Windows (test-hooks.sh section 15bis's counterpart). * CI: install.ps1 with Codex chosen, and Claude alone the same as master's install.ps1 test.yml: install.ps1's Codex functions against stand-ins in the hooks, linux and windows jobs. The windows job puts a Codex of the user's own in place before the first install, then runs .github/ci/same-as-master.ps1 twice (without agents.json, then with Claude chosen): master's install.ps1 then this one's over the install in place, the same lines, the same install.log commands, the same files but the copies of the sources the branch touches, ~\.codex untouched, no ~\.agents. Then, from a copy of the kit where Codex is open: Codex beside Claude, a second run that rewrites nothing, a hooks.json that does not parse, back to Claude alone. codex-mock: on Windows, %ProgramData%\OpenAI\Codex as a folder made there is by default pauses Codex, found through the Known Folder whatever %ProgramData% says (control: codex exec loads it); the system settings the other scenarios write go in folders locked as an administrator does it. Then install.ps1 -Agents codex: Codex from its pinned installer, for real, and the real Codex runs the six hooks it wired, through PowerShell, approved, no trust bypassed (control: without the tables, none runs); the global and vault AGENTS.md and the skills read; execpolicy: the kit's gbq.cmd only. The job also runs when install.ps1 changes; 30 minutes. * Codex plan: step 7, done and its deviations A dated section, "Étape 7 — fait et écarts": install.ps1's Codex path, the shared wire.py, the single-quoted PowerShell form of the hooks, the pinned installer, gbq.cmd's rules, the %ProgramData% check (Known Folder, stricter than Codex's probe), Bibo's one more section, the question, the proof that Claude alone is unchanged on Windows, and the tests. * CI: when Codex's installer leaves no codex.exe, its lines in install.log are shown codex-mock (Windows) and the windows job print install.log from Codex's installer on: the first run of install.ps1 with Codex chosen ended with "Codex ne s'est pas installe", the reason only in a log the runner then dropped. * install.ps1: Codex's installer runs with Windows' own tar first on its PATH codex-mock (Windows) ran install.ps1 under Git Bash's PATH, Git's Unix tools first, as a user who chose them when installing Git has it: Codex's official installer ran GNU tar, which reads "C:\..." as a remote host ("tar (child): Cannot connect to C: resolve failed"), and left no codex.exe. The installer now starts with Windows' system folder (bsdtar) ahead of the PATH, put back afterwards; the stand-in installer of .github/ci/install-ps1-codex.ps1 checks it on Windows. * test_headless_codex: on Windows, a folder its users may add to, as one made in ProgramData The Windows job showed the test's premise wrong: a folder made in the runner's temporary folder is the Administrators' and only theirs and SYSTEM's, so nothing pauses there. The test now gives it what a folder made in C:\ProgramData inherits (Users may add files and folders, seen on the runner by codex-mock), and the launcher pauses Codex, naming them. * CI (windows): install.ps1's Codex functions run with the Python the installer set up poste-neuf.ps1 leaves only the Store stub on the job's PATH, so the script's Python lookup found python.exe in WindowsApps ("Python was not found") and stopped on an empty path. The step now puts the Python of the installed hooks first, as the unit-test step uses it, and the script refuses the Store stub by name instead of failing further down. * install-ps1-codex.ps1: Windows' tar is checked past pwsh's own folder, ahead of a GNU tar's The Windows job's first full run showed the check wrong, not install.ps1: the stand-in installer runs under pwsh, which puts $PSHOME first on its PATH as it starts (powershell.exe does not, and $PSHOME has no tar). The check now skips that entry and wants the system folder first, then a folder the test puts ahead on the PATH as Git's Unix tools are on some PCs. Also checked: a codex.cmd (npm's) on the PATH is not taken for Codex; nothing turned it red before. * test_codex_wire: a hook of the kit's named only in commandWindows or command_windows is not added twice Taking out the reading of those two keys left every test green: the kit writes the same command in both, so only an entry the user changed shows it. * same-as-master.ps1: the snapshot leaves install.log out after a run too Run() kept the new part of install.log in $log, and Snap, called from it, read that as $Log (PowerShell's names ignore case, and a function sees its caller's variables): install.log was compared after the branch's run and not before, so the check failed on the log itself. Screen and commands had already matched master's. * Codex plan: step 7, what is left open, its protections taken out one at a time, local checks and CI The ProgramData folder can still appear between the last check and codex exec starting; 39 of the step's protections taken out one at a time (36 red on macOS, two given a test, one Windows-only); the green run, and what the earlier runs found: one fact in the kit (GNU tar), three in the test bench (the Store's python, pwsh's own folder first on its PATH, $log read as $Log). * Benches open Codex in their copies whatever the gate says test-hooks.sh, codex_mock.py, test_codex_hooks.py, install-ps1-codex.ps1 and the windows job replaced the closed line, CODEX_OUVERT = frozenset(), exactly: the change that opens one system (frozenset({"darwin", "linux"})) would have broken them all, §16 stopping on "harness not isolated". They now replace the gate's line whatever it says, then check it is open, so that only the tests that pin the closed gate change with the opening. * gbq: its reads start gbrain from a folder no session writes to Codex runs gbq query and gbq search without asking, outside its sandbox, from a session's folder, and gbq started gbrain there: Bun reads a bunfig.toml (whose preload runs before gbrain), a .env and a tsconfig.json in the folder it starts in, and the sandbox lets a session write them. engine/bin/gbq now starts its reads (query, search, ask, graph-query) from /, gbq.py (Windows) from its own folder; a write keeps the caller's folder (gbq import ./notes). Tests: test-hooks.sh §10, the folder each command starts gbrain from (red before); the linux and windows jobs, the real Bun and gbrain: a bunfig.toml whose preload runs when gbrain starts from that folder (the control), and never through gbq query or gbq search, which still find the vault's note. * install.ps1: no gbq rule in Codex on Windows, where gbq.cmd goes through cmd.exe The rule let Codex run gbq query and gbq search without asking, outside its sandbox, at ~\.local\bin\gbq.cmd. Codex 0.162.1 reads 'x&calc' as one word, the rule allowed it, and PowerShell hands it to cmd.exe, which reads the line again: the "&" starts a second command, unasked and unsandboxed (BatBadBut). Until gbq has an entry point that is not a batch file, install.ps1 puts no rule, says that Codex asks before every gbq, and the rules file is install.sh's alone (its comment says why, and that gbq's reads start from /). Tests: install-ps1-codex.ps1 (no rules folder, said; red before); the windows job; codex-mock on Windows: no rule in Codex's home, and two controls on the real Codex and the runner: install.sh's rule at this gbq.cmd would allow gbq query 'x&cd.>w', and run through PowerShell, the "&" starts the second command. * wire.py: a hook turned off in /hooks stays off once moved; Windows' hooks follow a new Python Codex knows a hook by its place in hooks.json. Turned off in /hooks, then moved by a hook of the user's put before it, the kit's hook got a new table at its new place on the next install (the update path): Codex ran it again, and nothing was said. approve() now looks at the event's other tables: one that turns this hook off (its hash and something else, or "enabled = false" without a hash) keeps it as the user set it, and it is said. On Windows the hooks name python.exe. Another Python since the last install, merge() recognised the kit's entries by their path and left the old python.exe, and approve() kept the old tables as the user's: Codex would run a Python since removed, and the night's report said to approve hooks that could not run. merge() now moves an entry exactly in the kit's form to this install's Python (as settings.json's for Claude), after a backup, and says so; an entry the user changed stays. approve() writes the command each table approves as a comment under it, and replaces a table of its own whose hash is that of the kit's command it names; any other table stays as it is. Tests: test_codex_wire.py (NewPython, Moved, both forms), test-hooks.sh §15bis, install-ps1-codex.ps1; each red before. * Claude alone: the end of the install reads Codex's hooks.json only once the kit wired Codex To say "Codex is not chosen any more", both installers read ~/.codex/hooks.json (or $CODEX_HOME's) for anyone who had not chosen Codex, which master never does. On Windows, a CODEX_HOME on a drive that is not there made Join-Path throw and install.ps1 exit 1 at its very end, for every Claude alone install (Bibo then shows the brain's step as failed); and a hooks.json naming one of the kit's hooks by hand got the message without Codex ever chosen. The message now needs the kit's own record that it wired Codex (codex-hooks-poses.json); install.ps1 builds the path with [IO.Path]::Combine and never lets that read stop it. Tests: the windows job runs same-as-master.ps1 with CODEX_HOME on a drive that is not there, then with a Codex whose hooks.json names the kit's hook (red before); test-hooks.sh §16, Claude alone beside such a Codex: the default's output (red before). * test-hooks.sh §16: Claude alone, install.sh compared with master's Nothing compared install.sh with master's on macOS and Linux: §16 compared Claude alone with this branch's own default, so a line or a file added to every install left it green (217/217 with both, checked by mutation). §16 now runs master's install.sh (origin/master, which the hooks and linux jobs fetch) and this one's, each in a new home beside a Codex of the user's own, then both again over that install, without a choice and with --agents claude: the same output, stub calls and install.log commands, the same files but the kit's copies of the sources this change touches and installed.json, this one's re-run leaving the install as it found it, ~/.codex untouched. Without origin/master: a failure in CI, said and skipped elsewhere. Mutations, each caught: a line on screen, a file written, an extra logged command, the hooks.json read of the previous commit undone. * Bibo: install.ps1's Codex section said without a count, Claude alone still "sur 13" Its section in SECTIONS made every Bibo user (Claude alone: Bibo chooses no agent before phase 10b) see "sur 14" and the number 9 skipped, where master says "sur 13". Codex's section is now in CHOSEN_SECTIONS: said as "▸ Je branche Codex…", without a count, and the other thirteen counted as on master. app/dev/mock.html is master's again; the app-windows-install step looks for "sur 13" again. * SECURITY.md: gbq's reads from /, no gbq rule on Windows, a hook turned off stays off What the previous commits change for Codex users: the approval keeps a hook turned off in /hooks, and follows a new Python on Windows; gbq's two reads run outside Codex's sandbox on macOS and Linux only, started from /, and not with the home folder as Codex's workspace (a session could then rewrite gbq itself); on Windows Codex asks before every gbq. * The comparisons with master: mktemp's names, and ~\.brain-search's copies Two defects of the benches, found by the first run, nothing to fix in the kit: test-hooks.sh §16 compared crontab's stand-in calls with the file mktemp names for it (Linux), a new name each run; same-as-master.ps1 counted the copies the change touches next to the hooks only, and gbq.py, which install.ps1 copies into ~\.brain-search, is one now. Both runs said and ran the same; the files differed by gbq.py alone. * CI (linux): the bunfig.toml control starts gbrain with Bun on the PATH, as gbq does gbrain is a `#!/usr/bin/env bun` script, and the job's PATH has no ~/.bun/bin: the control's gbrain never started, so its preload never ran. Its output is now shown when the control fails. * Codex plan: steps 6 and 7, review and corrections Eight findings, each verified and fixed with a test red before: gbq's reads and the session's folder, no gbq rule on Windows, a hook turned off staying off once moved, Claude alone compared with master's install.sh, ~/.codex read for Claude alone, a new Python on Windows, Bibo's count, and what the opening change will touch. The run cited for step 6 corrected; local checks and CI. --------- Co-authored-by: Chad El Kurdi <162061136+El-Kurdi-Chad@users.noreply.github.com>
1 parent 4038759 commit e6b8be0

27 files changed

Lines changed: 3433 additions & 57 deletions

‎.github/CODEOWNERS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
/engine/hooks/_headless_codex.py @El-Kurdi-Chad
1414
/engine/hooks/_headless.py @El-Kurdi-Chad
1515
/engine/hooks/_agents.py @El-Kurdi-Chad
16+
/engine/codex/ @El-Kurdi-Chad
1617
/engine/hooks/_redact.py @El-Kurdi-Chad
1718
/engine/tests/terrain.py @El-Kurdi-Chad
1819
/engine/tests/terrain_app.py @El-Kurdi-Chad

‎.github/ci/install-ps1-codex.ps1‎

Lines changed: 341 additions & 0 deletions
Large diffs are not rendered by default.

‎.github/ci/same-as-master.ps1‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Claude alone, install.ps1 says and does what master's does. Both re-run over the install in place,
2+
# master's first, then this one's: on screen the same lines, in install.log the same commands, on disk
3+
# the same files, but for the kit's copies of the sources this change touches (each run puts its own
4+
# version back, backs the other up and says so, with the same names both ways) and their hashes in
5+
# installed.json. This one's run leaves everything as it found it. The user's own Codex (~\.codex, made
6+
# before the first install) is untouched, and nothing goes into ~\.agents.
7+
# Used by test.yml (job windows), without agents.json and with Claude chosen, from the repository's root:
8+
# & .github/ci/same-as-master.ps1
9+
$ErrorActionPreference = "Stop"
10+
$H = $env:USERPROFILE
11+
$Mine = (Resolve-Path .).Path
12+
$T = Join-Path $env:RUNNER_TEMP "vs-master"
13+
if (-not (Test-Path -LiteralPath "$T\master\install.ps1")) {
14+
New-Item -ItemType Directory $T -Force | Out-Null
15+
git fetch -q --depth 1 origin master
16+
if ($LASTEXITCODE) { throw "git fetch of master failed" }
17+
git archive --format=zip -o "$T\master.zip" FETCH_HEAD
18+
if ($LASTEXITCODE) { throw "git archive of master failed" }
19+
Add-Type -AssemblyName System.IO.Compression.FileSystem
20+
[IO.Compression.ZipFile]::ExtractToDirectory("$T\master.zip", "$T\master")
21+
}
22+
$Log = "$H\.brain-in-a-box\install.log"
23+
# What the kit installs, apart from gbrain's own state and the search index, which every run rebuilds.
24+
function Snap {
25+
$m = [ordered]@{}
26+
foreach ($r in "$H\.claude", "$H\.brain-in-a-box", "$H\.local\bin", "$H\.codex", "$H\.agents", "$H\Documents\Brain") {
27+
if (-not (Test-Path -LiteralPath $r)) { continue }
28+
Get-ChildItem -LiteralPath $r -Recurse -File -Force | Where-Object {
29+
$_.FullName -notlike "$H\.brain-in-a-box\backups\*" -and $_.FullName -ne $Log -and $_.FullName -notlike "$H\Documents\Brain\.git\*"
30+
} | Sort-Object FullName | ForEach-Object { $m[$_.FullName] = (Get-FileHash -LiteralPath $_.FullName).Hash }
31+
}
32+
foreach ($f in Get-ChildItem -LiteralPath "$H\.brain-search" -File -Filter *.py) { $m[$f.FullName] = (Get-FileHash -LiteralPath $f.FullName).Hash }
33+
foreach ($t in Get-ScheduledTask -TaskName BrainNightly, BrainReflection -ErrorAction SilentlyContinue) {
34+
$m["task $($t.TaskName)"] = "$($t.Actions.Execute) $($t.Actions.Arguments)"
35+
}
36+
return $m
37+
}
38+
function Run([string]$Repo) {
39+
$from = (Get-Item -LiteralPath $Log).Length
40+
$out = (powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$Repo\install.ps1" 2>&1 | Out-String)
41+
if ($LASTEXITCODE) { Write-Host $out; throw "install.ps1 of $Repo failed ($LASTEXITCODE)" }
42+
$all = [IO.File]::ReadAllBytes($Log)
43+
# Not $log: PowerShell's names ignore case, and Snap, called from here, would read it as $Log.
44+
$added = [Text.Encoding]::UTF8.GetString($all, [int]$from, $all.Length - [int]$from)
45+
$norm = { param($s) ($s.Replace($Repo, "<kit>") -replace '\d{4}-\d\d-\d\d \d\d:\d\d:\d\d', '<date>') -replace 'backups\\\d{8}-\d{6}', 'backups\<date>' }
46+
return @{ Out = (& $norm $out); Calls = @((& $norm $added) -split "`r?`n" | Where-Object { $_ -like '> *' }) -join "`n"; Snap = (Snap) }
47+
}
48+
function Differ($a, $b) { return @(@($a.Keys) + @($b.Keys) | Sort-Object -Unique | Where-Object { $a[$_] -ne $b[$_] }) }
49+
50+
$backups = @(Get-ChildItem -LiteralPath "$H\.brain-in-a-box\backups" -Directory -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName })
51+
$before = Snap
52+
$m = Run "$T\master"
53+
$b = Run $Mine
54+
# The sources this change touches, among those install.ps1 copies next to the hooks, and into ~\.brain-search.
55+
$touched = @(foreach ($f in @(Get-ChildItem "$Mine\engine\hooks" -Filter *.py -File) + @(Get-Item "$Mine\engine\nightly\windows-nightly.py") +
56+
@(Get-ChildItem "$Mine\engine\search" -Filter *.py -File)) {
57+
$theirs = Join-Path "$T\master" ($f.FullName.Substring($Mine.Length + 1))
58+
if (-not (Test-Path -LiteralPath $theirs) -or (Get-FileHash $theirs).Hash -ne (Get-FileHash $f.FullName).Hash) {
59+
if ($f.Directory.Name -eq "search") { "$H\.brain-search\$($f.Name)" } else { "$H\.claude\hooks\brain\$($f.Name)" }
60+
}
61+
})
62+
Write-Host "the kit's copies this change touches: $($touched -join ', ')"
63+
if ($m.Out -cne $b.Out) {
64+
Compare-Object ($m.Out -split "`r?`n") ($b.Out -split "`r?`n") | Format-Table -AutoSize -Wrap | Out-String -Width 400 | Write-Host
65+
throw "Claude alone: install.ps1 does not say what master's says (<= master, => this change)"
66+
}
67+
if ($m.Calls -cne $b.Calls) {
68+
Compare-Object ($m.Calls -split "`n") ($b.Calls -split "`n") | Format-Table -AutoSize -Wrap | Out-String -Width 400 | Write-Host
69+
throw "Claude alone: install.ps1 does not run what master's runs (install.log)"
70+
}
71+
$again = Differ $before $b.Snap
72+
if ($again.Count) { throw "Claude alone: this change's run did not leave the install as it found it: $($again -join ', ')" }
73+
$apart = Differ $m.Snap $b.Snap
74+
$want = @($touched | Where-Object { Test-Path -LiteralPath $_ }) + @(if ($touched.Count) { "$H\.brain-in-a-box\installed.json" })
75+
if ((($apart | Sort-Object) -join "`n") -cne (($want | Sort-Object) -join "`n")) {
76+
throw "Claude alone: master's files and this change's differ in $($apart -join ', '), not just in $($want -join ', ')"
77+
}
78+
Write-Host "Claude alone: the same lines on screen, the same $(@($b.Calls -split "`n").Count) commands in install.log, the same $($b.Snap.Count) files but the $($touched.Count) touched"
79+
$now = @(Get-ChildItem "$H\.codex" -Recurse -File -Force | ForEach-Object { "$($_.FullName) $((Get-FileHash $_.FullName).Hash)" } | Sort-Object)
80+
if (($now -join "`n") -cne ((Get-Content "$env:RUNNER_TEMP\user-codex.txt") -join "`n")) { throw "Claude alone wrote into the user's own ~\.codex" }
81+
if (Test-Path -LiteralPath "$H\.agents") { throw "Claude alone wrote ~\.agents" }
82+
# These two runs' backups go: later steps count the ones they expect.
83+
Get-ChildItem -LiteralPath "$H\.brain-in-a-box\backups" -Directory -ErrorAction SilentlyContinue | Where-Object { $backups -notcontains $_.FullName } |
84+
Remove-Item -Recurse -Force

‎.github/ci/terminal.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ def main(argv=None):
8686
code = 3
8787
break
8888
used.add(match)
89+
# A person reads the question before typing. Answered at once, the reply can reach the
90+
# terminal before `read -s` turns its echo off: an echoed Enter adds a blank line, now and then.
91+
time.sleep(0.5)
8992
os.write(fd, (a.answers[match][1] + "\n").encode("utf-8"))
9093
print("\nterminal.py: answered %r to %r" % (a.answers[match][1], line.strip()), flush=True)
9194
line = ""

0 commit comments

Comments
 (0)