Skip to content

Merge pstack-claude 0.9.69 into the Herdr fork (0.9.70) - #19

Merged
TiewKH merged 27 commits into
mainfrom
sync/pstack-claude-0.9.69
Oct 5, 2026
Merged

TiewKH merged 27 commits into
mainfrom
sync/pstack-claude-0.9.69

Conversation

@TiewKH

@TiewKH TiewKH commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Merges michael-denyer/pstack-claude 0.9.58 through 0.9.69 into the fork.

Merge with a merge commit, not a squash, so the next sync starts from upstream's ad396c2.

From upstream

  • The Pi package and extension
  • The /correct skill, and the cursor/plugins pin moved to e43c7ee (v0.15.9)
  • A Windows Codex SessionStart hook
  • Hook validation that requires a command
  • Plan, playbook, and sheet readers that tolerate a byte-order mark
  • Codex sessions in worktree-audit
  • Child processes that no longer inherit the whole environment

Keeping Herdr working

  • Upstream didn't touch herdr-dispatch.ts, --next, the Agent gate, routing, or herdr-tools.md. /correct doesn't delegate, so it needs no Herdr wiring.
  • models.json keeps the fork's herdr menu next to upstream's pi block. poteto-mode's Platform Adaptation keeps the HERDR_ENV=1 pointer next to the new Pi pointer.
  • CI keeps the real Herdr dispatch job and adds upstream's Windows hooks and fork registry jobs, each with a 10-minute limit. The actionlint, lychee, and OSV mounts move from $PWD to $GITHUB_WORKSPACE, as upstream did, and stay read-only.
  • tools/forks.json declares the Herdr pointer lines in arena, automate-me, how, interrogate, maintain-verification-skill, no-comments, swarm, and why. Without these entries the new registry dry-run fails.
  • The Claude plugin.json drops icon, as upstream did. Claude Code ignores it.
  • The README adds Pi install from this fork and updates the skill counts and upstream pin.
  • Both lines had already released their own 0.9.58 and 0.9.59, so the version moves to 0.9.70.

Verification (local)

  • bun tools/generate.mjs --check
  • node tests/herdr-runtime.test.mjs
  • bun test tests/: 635 pass, 0 fail
  • Scripts: bun run typecheck, bun test orch watch-pr herdr (273 pass), prettier
  • markdownlint, actionlint
  • Fork registry dry-run sync (tools/sync.mjs ... --dry-run) exits 0
  • tests/herdr-e2e.sh: Linux only, runs in CI

🤖 Generated with Claude Code

michael-denyer and others added 27 commits October 2, 2026 21:08
…el-denyer#157)

The registry check only ran during an upstream sync, so a PR could edit a skill away from upstream without a tools/forks.json entry and still pass CI. A dry-run sync to the pinned SHA runs the same check on every PR. A second step annotates each skill file a PR touches.
…michael-denyer#158)

* feat(reflect): read Pi sessions in find-transcript and worktree-audit

Pi writes one JSONL file per session and branches in place, so the first
user message in file order is not always the opening prompt.
find-transcript.mjs now recognises a Pi session by its header line and
follows the active branch from the last entry to its root. It also splits
lines on newlines only, because readline breaks at U+2028 and U+2029 and
skipped any prompt that contained one.

worktree-audit.mjs takes several transcript roots and defaults to every
runtime directory that exists, so a worktree whose last chat ran in Pi is
no longer classed as safe to prune.

These two scripts are port-only and need nothing else from the Pi work,
so they land first.

Co-authored-by: Younes <git@younes.io>

* refactor(worktree-audit): name every missing transcripts root

The root check read a Fact's value without checking it was known and warned from inside an every() predicate, so only the first missing root was reported. It now yields one Fact and warns for each missing root.

---------

Co-authored-by: Younes <git@younes.io>
…hael-denyer#159)

* feat(reflect): read Pi sessions in find-transcript and worktree-audit

Pi writes one JSONL file per session and branches in place, so the first
user message in file order is not always the opening prompt.
find-transcript.mjs now recognises a Pi session by its header line and
follows the active branch from the last entry to its root. It also splits
lines on newlines only, because readline breaks at U+2028 and U+2029 and
skipped any prompt that contained one.

worktree-audit.mjs takes several transcript roots and defaults to every
runtime directory that exists, so a worktree whose last chat ran in Pi is
no longer classed as safe to prune.

These two scripts are port-only and need nothing else from the Pi work,
so they land first.

Co-authored-by: Younes <git@younes.io>

* refactor(generate): move the runtime table to tools/runtimes.mjs

The generator named Codex in constants spread through generate.mjs: the
mapping file, the notes-table header, the preamble, the prompts
directory, and the models.json check. A second runtime would have meant
a second copy of each.

tools/runtimes.mjs now holds a RUNTIMES table with one Codex row, the
Model names renderer and models.json check that row points at, and the
Codex marketplace validator. generate.mjs derives regions, lead lines,
prompt stubs, and owned directories from the rows. codexNoteSkills
becomes noteSkills(runtime, markdown).

Generated output is unchanged: `bun tools/generate.mjs --check` reports
the same 69 files current with no committed file touched. Tests change
only where an import or a signature moved.

Co-authored-by: Younes <git@younes.io>

* refactor(worktree-audit): name every missing transcripts root

The root check read a Fact's value without checking it was known and warned from inside an every() predicate, so only the first missing root was reported. It now yields one Fact and warns for each missing root.

* refactor(generate): keep a runtime's prompts directory repo-relative on its row

The row held a plugin-relative path, so generate.mjs carried a promptsDir helper and repeated the prompts filter. The row now holds the repo-relative path, like its mapping file, and one PROMPT_RUNTIMES constant replaces both filters.

---------

Co-authored-by: Younes <git@younes.io>
* feat(reflect): read Pi sessions in find-transcript and worktree-audit

Pi writes one JSONL file per session and branches in place, so the first
user message in file order is not always the opening prompt.
find-transcript.mjs now recognises a Pi session by its header line and
follows the active branch from the last entry to its root. It also splits
lines on newlines only, because readline breaks at U+2028 and U+2029 and
skipped any prompt that contained one.

worktree-audit.mjs takes several transcript roots and defaults to every
runtime directory that exists, so a worktree whose last chat ran in Pi is
no longer classed as safe to prune.

These two scripts are port-only and need nothing else from the Pi work,
so they land first.

Co-authored-by: Younes <git@younes.io>

* refactor(generate): move the runtime table to tools/runtimes.mjs

The generator named Codex in constants spread through generate.mjs: the
mapping file, the notes-table header, the preamble, the prompts
directory, and the models.json check. A second runtime would have meant
a second copy of each.

tools/runtimes.mjs now holds a RUNTIMES table with one Codex row, the
Model names renderer and models.json check that row points at, and the
Codex marketplace validator. generate.mjs derives regions, lead lines,
prompt stubs, and owned directories from the rows. codexNoteSkills
becomes noteSkills(runtime, markdown).

Generated output is unchanged: `bun tools/generate.mjs --check` reports
the same 69 files current with no committed file touched. Tests change
only where an import or a signature moved.

Co-authored-by: Younes <git@younes.io>

* feat(pi): run pstack on Pi 1.0

Pi discovers the shared skills tree but has no subagent, question, or
wake-up tools, so the skills that delegate could not run there.

The repository root is now a Pi package. Its extension in
plugins/pstack/pi/ supplies `agent`, `send_message`, `list_agents`,
`stop_agent`, `ask_user_question`, `schedule_wakeup`, and `/loop` under
the Claude Code names the skills already use, and adds the routing
instruction and the override sheet to the system prompt at every agent
start. Each subagent is a child `pi --mode rpc` process.

A Pi row in the runtime table stamps the Model names section of
poteto-mode/references/pi-tools.md from a new `pi` block in models.json,
which maps each Claude family name to a model ID per Pi provider. The
generator also writes the package version and validates the package
manifest.

docs/pi-equivalence.md lists each Claude Code mechanism pstack depends
on and how Pi provides it, and a test fails when a row cites a test that
does not exist. CI installs the root dependencies before the tests and
typechecks the extension against the pinned Pi release.

Releases 0.9.58.

Co-authored-by: Younes <git@younes.io>

* ci(pi): mark the pinned Pi install as a reviewed ad-hoc package

zizmor's adhoc-packages audit rejects the global install in the Pi extension types job. The version is pinned, Pi ships its own shrinkwrap, and install scripts are off, so the step carries a scoped ignore with that reason.

Co-authored-by: Younes <git@younes.io>

* refactor(worktree-audit): name every missing transcripts root

The root check read a Fact's value without checking it was known and warned from inside an every() predicate, so only the first missing root was reported. It now yields one Fact and warns for each missing root.

* refactor(generate): keep a runtime's prompts directory repo-relative on its row

The row held a plugin-relative path, so generate.mjs carried a promptsDir helper and repeated the prompts filter. The row now holds the repo-relative path, like its mapping file, and one PROMPT_RUNTIMES constant replaces both filters.

* refactor(pi): end agents atomically, decide steers at response time, split the agent tests

Extension: finish() and launch() update the record and the run table together before any write that can fail, so an agent whose output file cannot be saved still ends and reports instead of staying 'running under another pi process'. stop() uses one SIGTERM-then-SIGKILL ladder on PiChild and returns when the child exits instead of polling its group for the whole kill grace. A steer counts as taken only if the run had not settled when its response was read; the check ran after an await, so a settle in the same output chunk resent the message as a resume. Final text is capped once, wait() and stop() return ended records, general-purpose is a row in the agent type table, and the agent listing lives with the other model-facing text.

Generator: validatePiPackage always requires the extension entry, and every runtime's Per-skill notes table must name real skills.

Tests: agents.test.mjs (950 lines) is split by concern, shared setup and probes move into the harness, cleanup kills every process a test started, the fake pi rejects unknown step keys, assertions that could not fail are removed or repaired, and CI runs the catalog check against the pinned Pi.

---------

Co-authored-by: Younes <git@younes.io>
…as, split ask from the scheduler (michael-denyer#161)

* feat(reflect): read Pi sessions in find-transcript and worktree-audit

Pi writes one JSONL file per session and branches in place, so the first
user message in file order is not always the opening prompt.
find-transcript.mjs now recognises a Pi session by its header line and
follows the active branch from the last entry to its root. It also splits
lines on newlines only, because readline breaks at U+2028 and U+2029 and
skipped any prompt that contained one.

worktree-audit.mjs takes several transcript roots and defaults to every
runtime directory that exists, so a worktree whose last chat ran in Pi is
no longer classed as safe to prune.

These two scripts are port-only and need nothing else from the Pi work,
so they land first.

Co-authored-by: Younes <git@younes.io>

* refactor(generate): move the runtime table to tools/runtimes.mjs

The generator named Codex in constants spread through generate.mjs: the
mapping file, the notes-table header, the preamble, the prompts
directory, and the models.json check. A second runtime would have meant
a second copy of each.

tools/runtimes.mjs now holds a RUNTIMES table with one Codex row, the
Model names renderer and models.json check that row points at, and the
Codex marketplace validator. generate.mjs derives regions, lead lines,
prompt stubs, and owned directories from the rows. codexNoteSkills
becomes noteSkills(runtime, markdown).

Generated output is unchanged: `bun tools/generate.mjs --check` reports
the same 69 files current with no committed file touched. Tests change
only where an import or a signature moved.

Co-authored-by: Younes <git@younes.io>

* feat(pi): run pstack on Pi 1.0

Pi discovers the shared skills tree but has no subagent, question, or
wake-up tools, so the skills that delegate could not run there.

The repository root is now a Pi package. Its extension in
plugins/pstack/pi/ supplies `agent`, `send_message`, `list_agents`,
`stop_agent`, `ask_user_question`, `schedule_wakeup`, and `/loop` under
the Claude Code names the skills already use, and adds the routing
instruction and the override sheet to the system prompt at every agent
start. Each subagent is a child `pi --mode rpc` process.

A Pi row in the runtime table stamps the Model names section of
poteto-mode/references/pi-tools.md from a new `pi` block in models.json,
which maps each Claude family name to a model ID per Pi provider. The
generator also writes the package version and validates the package
manifest.

docs/pi-equivalence.md lists each Claude Code mechanism pstack depends
on and how Pi provides it, and a test fails when a row cites a test that
does not exist. CI installs the root dependencies before the tests and
typechecks the extension against the pinned Pi release.

Releases 0.9.58.

Co-authored-by: Younes <git@younes.io>

* ci(pi): mark the pinned Pi install as a reviewed ad-hoc package

zizmor's adhoc-packages audit rejects the global install in the Pi extension types job. The version is pinned, Pi ships its own shrinkwrap, and install scripts are off, so the step carries a scoped ignore with that reason.

Co-authored-by: Younes <git@younes.io>

* refactor(worktree-audit): name every missing transcripts root

The root check read a Fact's value without checking it was known and warned from inside an every() predicate, so only the first missing root was reported. It now yields one Fact and warns for each missing root.

* refactor(generate): keep a runtime's prompts directory repo-relative on its row

The row held a plugin-relative path, so generate.mjs carried a promptsDir helper and repeated the prompts filter. The row now holds the repo-relative path, like its mapping file, and one PROMPT_RUNTIMES constant replaces both filters.

* refactor(pi): end agents atomically, decide steers at response time, split the agent tests

Extension: finish() and launch() update the record and the run table together before any write that can fail, so an agent whose output file cannot be saved still ends and reports instead of staying 'running under another pi process'. stop() uses one SIGTERM-then-SIGKILL ladder on PiChild and returns when the child exits instead of polling its group for the whole kill grace. A steer counts as taken only if the run had not settled when its response was read; the check ran after an await, so a settle in the same output chunk resent the message as a resume. Final text is capped once, wait() and stop() return ended records, general-purpose is a row in the agent type table, and the agent listing lives with the other model-facing text.

Generator: validatePiPackage always requires the extension entry, and every runtime's Per-skill notes table must name real skills.

Tests: agents.test.mjs (950 lines) is split by concern, shared setup and probes move into the harness, cleanup kills every process a test started, the fake pi rejects unknown step keys, assertions that could not fail are removed or repaired, and CI runs the catalog check against the pinned Pi.

* refactor(pi): nest an agent's identity inside its record

A record was a flat union, so a resume re-derived the identity fields through a key list and two casts. The record is now { agent, status, ... }: a resume passes record.agent, and identityOf is gone. The persisted entry shape changes with it; a flat entry no longer loads.

* refactor(pi): check the child's output lines against schemas

onLine checked shapes by hand and cast the result to Pi's RpcResponse after testing one field. Each line, and each part a field is read from, is now checked against a typebox schema, as the record and models.json already are, and a command resolves to the two fields the extension reads.

* refactor(pi): split ask_user_question from the scheduler

interaction.ts held ask_user_question beside schedule_wakeup and /loop, which share a Scheduler and nothing with it. The question tool moves to ask.ts and the rest becomes schedule.ts.

* test(pi): wait for a stopped agent's running command to die

A child pi kills the command it has running as it exits and does not wait for it, so the command can outlive the stop by a moment. Two tests asserted it was dead the instant the stop returned and failed on a slow runner. They now wait for it. Also drops interaction.ts, which the merge of main brought back after its rename to schedule.ts.

---------

Co-authored-by: Younes <git@younes.io>
…ite (michael-denyer#162)

Two live assertions still matched model, subagentType, and thinking at the top level of a persisted record. Those fields moved under agent, so both assertions failed against real Pi.
…te (michael-denyer#163)

The print-mode test still matched id at the top level of a persisted record. It moved under agent, so the assertion failed against real Pi.
….9.59) (michael-denyer#165)

* Replace $PWD so the plugin directory stops holding pstack (0.9.59)

The directory validator reads $PWD as a credential-named variable and holds a plugin when a file naming it also names a remote URL (MCP_FORWARDS_CREDENTIAL_ENV). docs/reference.md had both. Same change as agent-formal-verify #12.

* Drop the listing fields from the Claude plugin.json

Claude Code ignores icon, documentationUrl, supportUrl, privacyPolicyUrl, and termsOfServiceUrl at load time, and the directory validator reports each as a finding. agent-formal-verify published without them and added them afterwards (#15).
… watch-pr tests (0.9.60) (michael-denyer#166)

The plugin directory holds pstack with MCP_FORWARDS_CREDENTIAL_ENV. The orch store passed a copy of process.env to gt and git, and three test files did the same for their child processes. gt and git now inherit the environment; gt output has colour codes stripped where NO_COLOR was set before. openStore takes a gt executable path so the tests can run a fake gt, since Bun resolves a command with no env option against its startup PATH. Adds .claude-plugin/icon.png for the ICON_MISSING warning.
…ronment (0.9.61) (michael-denyer#167)

The plugin directory still holds 0.9.60 with MCP_FORWARDS_CREDENTIAL_ENV, and pi/config.ts was the last place the plugin handed a copy of the whole environment to a child process. Agents now inherit the environment unchanged; the extension registers --pstack-depth, the parent passes depth + 1, and settings.depth reads the flag on each use because pi parses extension flags after loading the extension.
Adds the benchmark-checklist skill and the explain-the-number principle, fresh subagents by default, an hourly autopilot audit tick, and PR size and stack guidance. Five conflicting poteto-mode files resolved by hand, keeping the operator-merges policy.
Adds the /correct skill, agent-proof design red flags in architect, and the performance mantras in the Perf issue and Hillclimb playbooks. The three-way sync merged with no conflicts.
…-denyer#172)

* test(codex): reproduce Windows SessionStart failures

* fix(codex): run SessionStart hooks natively on Windows

* Fix CRLF off-switch parity and harden the Windows hook CI job (0.9.66)

Share one table of off-switch sheets across session-start.sh, session-start.ps1 and Pi's parseSheet; session-start.sh now treats a CRLF 'session hook: off' line as off. The Windows CI job drops its -t filter and fails instead of skipping via PSTACK_REQUIRE_WINDOWS_HOOK. session-start.ps1 reads its context file from $PSScriptRoot. Document the Group Policy execution-policy limit.

* Measure Windows hook test durations with a 60s timeout

* Pass PowerShell's cache and module paths to the Windows hook test

---------

Co-authored-by: Michael Denyer <97485362+michael-denyer@users.noreply.github.com>
A dry-run sync to the pinned upstream e43c7ee warns that tools/forks.json
declares watch-pr/transport.test.ts, but upstream carries no file at that
path, so the sync now lists it as port-only rather than forked. The sync
says to delete the entry, and the file itself stays.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A project playbook saved with a UTF-8 byte-order mark (Notepad and some
Windows editors add one) failed check-playbooks.mjs: the frontmatter
regex anchors on a leading `---`, so the BOM hid both `when:` and
`extends:`. The script reported a missing `when:` line and flagged every
anchored change as absent from the playbooks it extends.

Strip a leading BOM before parsing, matching how 0.9.67 made the session
hooks read BOM-led sheets.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A plan saved with a UTF-8 byte-order mark failed check-plan.mjs. With no
frontmatter, the BOM sat in front of the H1 and the check reported "no H1
title". With frontmatter, the `---` test missed the opening delimiter, so
the frontmatter lines were linted as prose and tripped "mid-sentence
colon".

Strip a leading BOM before splitting the file into lines, the same way
the session hooks and check-playbooks read BOM-led files.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…el-denyer#180)

* fix(pi): cancel stale wakeups when replacing a self-paced loop

* Stop the previous loop the same way in both /loop modes

The fixed arm now calls stopAll like the self-paced arm, so stopLoop has no caller outside Scheduler and becomes private. A new test pins that a fixed loop cancels a pending wakeup.

---------

Co-authored-by: Michael Denyer <97485362+michael-denyer@users.noreply.github.com>
…chael-denyer#178)

* fix(pi): verify linked worktree identity before reuse and cleanup

Reject replacement directories, unrelated repositories and checkout links before an isolated Pi child starts or cleanup runs. Keep valid retained, recreated and detached-HEAD worktrees supported.

Add twelve focused regression tests. Full Bun/Pi/Windows gates remain pending.

* Fold the worktree identity tests into the worktree suite

The cases now use the harness world and gitRepo fixture and bun:test expect, like the rest of tests/pi/worktree.test.mjs. The accept-twice and detached-HEAD resume checks join the existing tests they duplicated.

---------

Co-authored-by: Michael Denyer <97485362+michael-denyer@users.noreply.github.com>
…nyer#179)

* fix(pi): keep defaults when the model sheet is unreadable

* Put the unreadable-sheet case in the shared sheet table

A sheet file without read permission joins tests/session-hook-sheets.mjs, so the POSIX hook, the PowerShell hook and Pi are held to it together. The table includes the row only where chmod can deny a read. The Pi-only directory and model-fallback checks move into config.test.mjs and agents-launch.test.mjs.

---------

Co-authored-by: Michael Denyer <97485362+michael-denyer@users.noreply.github.com>
…ths (michael-denyer#177)

* fix(worktree-audit): include Codex sessions and match JSON-escaped paths

Discover active and archived Codex sessions, honor runtime home overrides, and recognize JSON-escaped/Windows worktree paths without matching sibling prefixes.

Add seventeen regression tests, including real Git audit fixtures. Full Bun and native Windows gates remain pending.

* Search transcripts only for JSON spellings of a worktree path

The audit scans only .jsonl files, so a path never appears unescaped; the raw spellings and the Set that merged them are gone. The new cases join tests/worktree-audit.test.mjs on its existing fixtures, and codex-tools.md and NOTICE.md now name the Codex and Pi transcript directories.

---------

Co-authored-by: Michael Denyer <97485362+michael-denyer@users.noreply.github.com>
…chael-denyer#181)

* fix(resume): accept angle-bracket artifact links

* Widen the 0.9.68 entry to cover every change merged since 0.9.67

---------

Co-authored-by: Michael Denyer <97485362+michael-denyer@users.noreply.github.com>
…r#185)

The commandWindows loop filtered out an undefined command, so a hook with no command, or only a Windows override, passed validation unchecked. Before 0.9.66 the same input crashed on hook.command.matchAll.
Takes upstream's Pi package and extension, the /correct skill and the
cursor/plugins pin at e43c7ee (v0.15.9), the Windows Codex SessionStart
hook, hook command validation, BOM-tolerant readers, Codex sessions in
worktree-audit, and narrower child-process environments. Keeps the
fork's Herdr dispatcher, --next, Agent gate, routes, and the real Herdr
CI job, with read-only mounts now on $GITHUB_WORKSPACE. Declares the
Herdr pointer lines in tools/forks.json so the new fork registry check
passes. Both lines released their own 0.9.58 and 0.9.59, so the
version moves to 0.9.70.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TiewKH
TiewKH merged commit 2533dc3 into main Oct 5, 2026
13 checks passed
@TiewKH
TiewKH deleted the sync/pstack-claude-0.9.69 branch October 5, 2026 14:37
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.

5 participants