Skip to content

feat(agents): wire project-brain into Pi via before_agent_start extension - #193

Merged
jinyeow merged 4 commits into
mainfrom
feat/187-pi-project-brain-sessionstart
Aug 23, 2026
Merged

feat(agents): wire project-brain into Pi via before_agent_start extension#193
jinyeow merged 4 commits into
mainfrom
feat/187-pi-project-brain-sessionstart

Conversation

@jinyeow

@jinyeow jinyeow commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Wires project-brain into Pi via a before_agent_start extension, mirroring Claude Code's and Codex's own SessionStart hooks (#186).

What changed

  • pi/extensions/project-brain-autoload.ts (new): injects the active project-brain initiative's core.md + STATUS.md once per session. session_start alone can't inject a message into the conversation, so injection happens on before_agent_start, gated by a flag reset on session_start (new/resume/fork/reload).
  • Reuses ai-agents/skills/project-brain/scripts/session-start.ps1, the same script Claude Code's and Codex's hooks run, rather than reimplementing the resolve-and-read procedure in TypeScript. Feeds it a SessionStart-shaped { cwd } JSON payload on stdin, forwards hookSpecificOutput.additionalContext from its stdout.
  • pi/README.md: documents the extension.
  • tests/pi-project-brain-autoload.Tests.ps1 (new): behavioral tests driving the real handler (via node --experimental-strip-types), not source-text regex matching.

Bugs found and fixed while implementing/reviewing

  • PowerShell 7 falls back to the legacy OEM codepage for stdin/stdout when pwsh is spawned without an attached console (as Node's execFile does). This silently mangles multi-byte characters both ways: a non-ASCII cwd going in, and content read from core.md/STATUS.md (e.g. "→") coming back out as invalid JSON. Fixed with explicit [Console]::InputEncoding/OutputEncoding UTF-8 preambles, scoped to this extension's own invocation only (not a session-start.ps1 change, so Claude Code's and Codex's hooks are unaffected).
  • The once-per-session gate was scoped to the extension factory's closure lifetime, which couldn't be confirmed from static source to reset per Pi session boundary. Fixed by resetting explicitly on session_start.
  • child.stdin had no error listener; an EPIPE/ENOENT on that stream could crash the host Pi process instead of failing safe (reproduced reliably). Fixed with a no-op error listener.

Review

Ran through review-fix-loop (2 cycles, fable + Codex sol): all above-floor findings fixed and verified, full test suite green, lint clean.

Closes #187

…sion

Adds pi/extensions/project-brain-autoload.ts: a before_agent_start
extension (session_start can't inject a message) that injects the
active project-brain initiative's core.md + STATUS.md once per
session, gated by a module-scope flag.

Reuses ai-agents/skills/project-brain/scripts/session-start.ps1 rather
than reimplementing the resolve-and-read procedure - the same script
Claude Code's and Codex's own SessionStart hooks run - by feeding it a
SessionStart-shaped JSON payload on stdin and forwarding
hookSpecificOutput.additionalContext from its stdout.

Found and fixed a real bug while verifying against the actual
installed Pi: PowerShell 7 falls back to the legacy OEM codepage for
stdout when spawned without an attached console (as Node's execFile
does), silently mangling multi-byte characters (e.g. an arrow read
from core.md) into invalid JSON. Fixed by running via -Command with
an explicit [Console]::OutputEncoding preamble, scoped to this
extension's own invocation so Claude Code's and Codex's hooks are
unaffected.

Closes #187

Refs: AB#187
…ss [#c1-f1][#c1-f2][#c1-f3]

pi/extensions/project-brain-autoload.ts:
- Reset the injected gate on Pi's session_start event (new/resume/fork/
  reload) instead of relying solely on the extension factory's closure
  lifetime, which review couldn't confirm resets per session boundary
  from static source alone.
- Attach a no-op error listener on child.stdin before writing: an
  unhandled 'error' event on that stream (spawn failure, or EPIPE from
  a child that exits before draining stdin) crashed the host Pi
  process instead of the fail-safe undefined every other path returns.
  Reproduced reliably via a fast-exiting fixture script with a large
  payload.
- Read SESSION_START_SCRIPT from PI_PROJECT_BRAIN_SESSION_START_SCRIPT
  when set, so tests can drive the real default-exported handler
  end-to-end against a fixture instead of this machine's actual Pi
  projection.

tests/pi-project-brain-autoload.Tests.ps1:
- Replace source-text regex assertions over the once-per-session gate,
  handler registration, and message shape with real behavioral tests:
  import the module (node --experimental-strip-types), register a
  fake pi object, and drive the actual before_agent_start/session_start
  handlers across a simulated session boundary.
- Add a regression test for the stdin EPIPE crash.

Refs: AB#187
…f1][#c2-f2]

pi/extensions/project-brain-autoload.ts: session-start.ps1 reads stdin
via [Console]::In.ReadToEnd(), which uses Console.InputEncoding - the
same no-attached-console OEM-codepage fallback that motivated the
OutputEncoding preamble applies symmetrically to input. A non-ASCII
cwd was silently mangled before session-start.ps1 ever saw it.
Reproduced directly with node's execFile outside Pester; fixed by
adding the InputEncoding preamble alongside OutputEncoding.

pi/README.md: updated the stale "gated to fire once via a module-scope
flag" description (predates the session_start reset added for the
prior fix commit) and documented the InputEncoding preamble.

Refs: AB#187
tests/pi-project-brain-autoload.Tests.ps1: the "resolveBrainContext
contract" and "session-start.ps1 stdin contract" tests regex-extracted
resolveBrainContext's source text and re-evaluated it, anchored on a
literal blank line (\n\}\n\nexport default) between the function's
closing brace and the next declaration. On CI (Windows runner, no
.gitattributes forcing LF) the repo checks out with CRLF line
endings, so the two bare \n's in that pattern never span the actual
\r\n\r\n blank line - the regex silently matched nothing and every
test in the file failed with "resolveBrainContext function not
found", despite passing locally.

Replaced the extraction with a real import (node
--experimental-strip-types, same mechanism the "extension handler
behavior" tests already use), driving before_agent_start end to end
instead of re-evaluating pulled-apart source text. No regex, no CRLF
fragility. Verified against both LF and CRLF checkouts of the
extension file locally before restoring LF.

Refs: AB#187
@jinyeow
jinyeow merged commit d87d568 into main Aug 23, 2026
5 checks passed
@jinyeow
jinyeow deleted the feat/187-pi-project-brain-sessionstart branch August 23, 2026 00:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(agents): wire project-brain into Pi via before_agent_start extension

1 participant