Repository navigation
feat: release PAACM 3.5.0 with finishing through the runtime, worktrees and shared machines - #9
Merged
Merged
Conversation
… sessions - lib/project.mjs: one project-root resolver for the CLI and the hooks. It never climbs out of a worktree, passes through submodules, and compares repositories by their common git directory. - Hooks anchor the project on the IDE's project directory and follow `cwd` into worktrees of the same repository (Claude Code keeps CLAUDE_PROJECT_DIR at the original root in a worktree session). - protect and post-edit use the edited file's own worktree, configuration and task: sibling worktrees are protected, nested ones are no longer treated as the main checkout's .claude/ folder, and a session started in a subdirectory still protects approved tests. - Session start prints the worktree's own runtime command. - Test helper: CLAUDE_PROJECT_DIR is now actually passed to hook processes.
Baselines lived in a shared os.tmpdir()/paacm-sessions. On a machine several accounts use, the second account's session start failed with EACCES and the agent lost the whole session context; other accounts could also read or forge the baselines. They now live in <git dir>/paacm-sessions (per worktree, 0700/0600, pruned after 30 days). A baseline that cannot be written is a warning in the session context, never a failed hook.
…lobal options The shell guard allowed git commit --no-verify, -n, git -c core.hooksPath=, git config core.hooksPath and HUSKY=0/SKIP=/LEFTHOOK=0, which skip the project's pre-commit and pre-push checks. A new ask rule, skip-git-hooks, covers them. publish-deploy now also matches terraform -chdir=… apply, kubectl --context … apply, helm and docker with global options, and docker image push.
…y default .github/actions/** runs in CI like the workflows, and .husky/**, .pre-commit-config.yaml and the lefthook files decide what runs on commit. They join protect.config, so an agent cannot weaken them without the human's approval.
…n files approve-tests could be run again after an approved test (or the check manifest, or .agent/paacm.json) was changed through the shell and committed, which moved the baseline past the change without the human. It now refuses while the audit reports such a change and names reopen-tests, which the human confirms. It also moves a task from testing to implementing, so fixes made after reopen-tests are gated again. Protect, audit and approve-tests share one case-aware matcher.
… read next Agents stopped with the task still triaged, without REPORT.md and without `paacm task done`, because the stop gate let an open task stop whenever the gate was green and nothing pointed to the later phases. - `task done` also requires the last phase (review for STANDARD and DEEP, verify for FAST), approved tests, a passing gate at the route's tier, and a REPORT.md whose OUTCOME is done; each refusal names the reference to open. - The stop gate nudges once (never on a continuation, and not twice for the same stop) when the gate is green but the task is open, and when a STANDARD or DEEP task changed files while still triaged. A gate below the route's tier no longer counts. - `task start`, `task phase` and a passing `gate` print the reference to read next, found next to the runtime in project and plugin installs.
…, and missing tools named Agents piped or redirected the gate's output to keep it short, and IDE allow rules refuse such compound commands; a check whose tool was missing (exit 127) was reported like a code failure and counted toward the retry cap. - `paacm gate --quiet` and `paacm check --quiet` print only the RUN/PASS/FAIL lines, the summary, the last 30 lines of a failing check and the exit line. - With a task, every gate run saves its whole output to .agent/tasks/<task>/gate.log (capped at 8 MiB) and names it. - A check that exits 127 (9009 on Windows) gets a hint: this is the environment, not the code; the stop gate says to ask for the tool or block with trigger 2.
…to the evidence - The diff is built in a temporary index, so new untracked files are reviewed with their content; the real index is never touched. - Diffs of files marked linguist-generated, of files whose diff exceeds 64 KiB, and then of the largest files until the rest fits 512 KiB, go to review-diffs/ and are listed with their stats; a diff above git's buffer is an explicit error, never an empty diff. - The fence is longer than any backtick run in the diff. - Evidence lists the gate log, the protected-configuration warnings and the files the writer saved in the task folder for reviewers (who have no shell). - A "## " line inside a fenced code block no longer ends the reviewer checklist. - paacm-review writes its input into a new private temporary folder.
…he task's own changes After a task merged its base branch, the audit listed every protected file the merge changed as "changed in this task", so a report could claim approvals for changes the task never made. Each warning now has an origin: "task" (own first-parent commits, a merge's conflict resolutions, uncommitted edits) or "merge" (arrived only from the merged branch), printed as WARN or NOTE in the audit, the gate and the review input. Violations stay strict: a merge cannot bring in a change to an approved test or to the gate configuration.
`--ref` takes an issue number (123, #123), a tracker key (PROJ-123), a GitHub reference (owner/repo#123) or an https URL, at most 200 printable ASCII characters. It is kept in state.json and shown by task status, the session context and the review input; the report template has a WORK ITEM line.
…missing reviewer checklist - For each check in .agent/checks.conf, doctor reads the programs the command starts (the first word of each command after assignments and wrappers, following a plain cd for script paths) and warns about one that is not on PATH or a script that does not exist: the gate would exit 127 there. - When .agent/extensions/08-review.md has no "## Reviewer checklist" section, doctor says the reviewer gets no project checklist; otherwise it names the other sections, which stay with the writer.
… missing tools and --ref - The core block and the skill description cover any change to the repository's files, documentation included; a new always-on rule: record each phase, read the reference it names, finish with REPORT.md and `paacm task done` or BLOCKED:, and run each runtime command on its own. - context.md no longer recommends a compound command; the gate's --quiet and gate.log replace piping, and other redirects are noted as needing approval. - 05-tests-first: run fixers on the tests and update the lists the change makes wrong before they freeze; re-approval needs reopen-tests. - 07-verify, 08-review, 09-deliver, 01-intake-triage, runtime.md, paacm-verify and the REPORT template describe the new behaviour.
…hangelog - ADR 0021 records why tasks finish through the runtime and why state and protection follow the worktree; ALGORITHM.md section 4, phases 5, 7, 8 and 9 and the IDE mapping follow it. - Setup guide: running the checks in CI, worktrees and per-person state on a shared machine. Troubleshooting: task done refusals, the stop gate's nudges, exit 127, the baseline warning, worktrees; the reopen-tests path for an approved test change. Extending and user guides updated. - Requirements: release 3.5.0 with its evidence. Version 3.5.0.
…-tests names the next step A headless Claude Code run showed "Stop hook error occurred" for the finish nudge, because it was sent as decision "block". Claude Code documents hookSpecificOutput.additionalContext for a Stop hook that gives guidance: it continues the turn under the same stop_hook_active protection and is shown as stop hook feedback. The nudges (no task, still triaged, finish, an unconfigured tier on the first stop) now use it; a failing gate still blocks. approve-tests now prints that the task moved to implementing and the reference to read, so the agent does not record the phase again.
Review finding: after running the gate, the stop hook's nudge wrote back the task state it had read before the gate, so the new lastGate and the reset failure count were lost and task done then refused a passing gate. The nudge now reads the state again before recording itself.
…outs below a non-git session folder are protected Review findings: - The hooks followed the agent's cwd into any sub-folder with its own .agent/paacm.json, and into a worktree without a task, so the stop gate stopped checking the session's failing task. cwd now moves the project only into another worktree of the same repository, and the stop gate judges both that worktree and the session's own project. - With the session folder outside git (a folder holding several checkouts), no edit below it was protected. Each file is now judged by its own checkout, and cwd picks the checkout for the other hooks. - The phases nudge counted the human's uncommitted work from before the session; with a session baseline it now counts only the session's changes.
…LOCKED trigger 2 Review findings: - A FAST task stays triaged after approve-tests, so "Next: phase 9 … task done" led to a refusal. A passing gate and the finish nudge now say to record phase 7 first; 05-tests-first says only a task in testing moves to implementing. - The missing-tool message offered "ask the human to install the tool", which an agent cannot do by stopping while the gate fails. The runner hint, the stop gate, 07-verify, paacm-verify and troubleshooting now say: list the check under CHECKS NOT RUN and stop with BLOCKED trigger 2.
Review finding: any change off the first-parent line counted as "merge", so the writer could branch, commit protected configuration, merge it and have it reported as a NOTE instead of a WARN. A change now counts as merged only when it came from commits a remote-tracking branch already has (the base branch); everything else, including a locally merged branch, is the task's own.
…ged frozen files Review finding: after approve-tests refused a changed approved test, the writer could block the task and start a new one, whose first approve-tests accepted the weakened test. task start now refuses while the latest task is blocked and files frozen by its approved tests changed since; the change is settled in that task (resume, reopen-tests with the human) or undone.
Review findings: - `git add -A` stopped on an untracked nested repository without commits and the whole review input failed. It now runs with --ignore-errors; the paths git could not add are listed in the review input. - The temporary index was built with read-tree, so every tracked file was hashed again (clean filters and LFS ran on each) and a skip-worktree file's local content (for example a secret) entered the review. It now starts from a copy of the real index: with a settled index no clean filter runs (0 against 50 in a 50-file check), and skip-worktree files stay out, as in git status. - A file whose type changed has two diff sections and a submodule shown as a log has none, which disabled the per-file split or paired sections with the wrong files. Sections are now matched to files by the path in their header (fixed prefixes, unescaped names, quoted names decoded), and submodules are shown as commit lines. - The longest backtick run is found with a loop; spreading 150 000 runs into Math.max overflowed the stack.
Review finding: git accepts --no-veri for --no-verify; 'export HUSKY=0;' before git, GIT_CONFIG_COUNT/KEY/PARAMETERS and a global option before 'commit -n' were allowed. They now ask. 'git log --grep=commit -n 3' no longer asks: the -n rule needs commit to be git's subcommand.
Review finding: '(cd a && ./run.sh)' kept the closing parenthesis, wrapper
options with a value ('timeout -s KILL 60', 'nice -n 10') were read as the
program, and a script after a quoted cd was checked at the project root. On
Windows, a program written with its extension or found in the project folder,
as cmd.exe finds it, was reported missing.
…e gate log in memory Review findings (reasoned, then checked): lefthook also reads .lefthook.yaml, toml, json and jsonc files, also under .config/, and lefthook-local overrides in every format (lefthook.dev/configuration); the defaults now cover them with four globs. The gate kept every check's whole output in memory before cutting the log to its last 8 Mi characters; it now keeps only that much while running.
User audit findings: 'git commit -n&&…', '-n;', '-n>out', '-nm"msg"', a quoted '-c core.hooksPath=…' or config key, and HUSKY='0' / LEFTHOOK='0' passed the skip-git-hooks rule although git honours them; 'docker build --push' passed publish-deploy. All ask now. 'git update-ref' joins git-destructive: it moves a ref directly (and could forge a remote-tracking ref).
…evidence list User audit finding: the review input listed the files saved for reviewers with the kit-content walker, which throws on a symbolic link, so one link replaced the whole list with an error line. The task folder now has its own walker, which skips links and unreadable folders.
…the report accounts for User audit finding: the merge origin came from remote-tracking refs, which the writer can create itself (update-ref, a fetch refspec, a local remote), so its own protected-configuration edit could be reported as a note 'reviewed there'. The origin is now context, never an exemption: every configuration change is a WARN, marked 'through a merge' where it applies, and the report says who approved it or where a merged one was reviewed. The review input no longer claims a merged change was reviewed elsewhere.
…ct rules User audit finding: the protect hook judged a file by the nearest .agent/paacm.json, so writing sub/.agent/paacm.json with empty patterns made the approved tests under sub/ editable (the root gate still caught it later). Projects are now found by worktree: in the session's worktree a file is judged by the session project; in another worktree of the repository by the project at the same place. A .agent/paacm.json or .agent/checks.conf at any depth is protected configuration by default.
User audit finding: with `cwd` in one checkout, the protect hook judged every file by that checkout, so a sibling checkout's approved tests could be edited, and the stop hook never judged the sibling, so its failing gate did not hold the stop. - protect and post-edit pass the session folder to editLocation, so a file is judged by the checkout it is in. - post-edit marks the edited checkout (in its git directory) when the session folder is not a git repository; the stop hook judges each checkout directly inside that folder that the session marked. Checkouts the session never edited are not judged, so other agents' tasks there do not hold its stop.
…ee that has them User audit finding: the blocked-task refusal read only the .agent/tasks folder of the worktree `task start` ran in, so `git worktree add` from the same commit started a task, approved the weakened test and passed the gate. - `task start` checks the latest task of every worktree of the repository; for another worktree, only when this HEAD contains that task's approved test commit, so an unrelated branch is not held. - `approve-tests` checks the other worktrees too, which covers a task started there before the change was merged.
Non-git session folders (protect at any depth, stop for the checkouts directly inside that the session edited), the blocked-task refusal across worktrees, git update-ref and docker build --push in the shell guard, private file modes on Linux and macOS only, and the audit's findings in the release evidence.
Windows CI: `cmd /c` exits 1, not 9009, for a command it cannot find, so the gate reported a missing tool as failing code. A check that exits 1 with cmd's "is not recognized as an internal or external command" message is now a missing tool; 9009 (what a batch file sees) still is. The docs no longer promise 9009.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this release does
Running PAACM 3.4.1 on a team project — several people on one server, linked worktrees, headless agent runs — showed where the runtime enforced less than the method asks. This release closes those gaps. Every fix has tests that failed on 3.4.1 and pass now. The design is recorded in ADR 0021, and
ALGORITHM.mdfollows it.triaged, withoutREPORT.mdortask done. The stop gate let any task stop once the gate was green.task doneneeds the route's last phase, approved tests, a gate at the route's tier andREPORT.mdwithOUTCOME: done. The stop gate nudges once: never on a continuation, never twice for the same stop.task start,task phaseand a passinggatename the reference to read next..claude/worktrees/x/needed approval as agent configuration. The CLI could climb into the outer checkout.cwdinto worktrees of the same repository. The protect hook judges each file by its own worktree's configuration and task. In a session folder that is not a git repository, each file is judged by its own checkout wherevercwdis, and the stop gate also judges the checkouts directly inside that the session edited. A nested.agent/paacm.jsonis not a second set of rules./tmp/paacm-sessions: the second account's session start failed withEACCESand lost all context.approve-testscould take a new baseline over a test changed through the shell.reopen-tests(human-confirmed). While a blocked task has such a change, no task starts over it in any worktree whose commit has it, and other worktrees cannot approve tests over it. Approving moves the task from testing to implementing.```inside the diff broke the fence. One line of evidence.review-diffs/. The fence is always long enough. A too-large diff is an explicit error. Evidence listsgate.log, configuration warnings and files saved for the reviewer.--quietplusgate.log. A missing tool is named as an environment problem (trigger 2).WARNthe report accounts for; one that arrived with commits a remote-tracking branch has is markedthrough a merge. Violations stay strict.--no-verify,core.hooksPath,HUSKY=0,git update-ref,docker build --pushandterraform -chdir=… applywere allowed..github/actions/**and git-hook manager files are protected by default.task start --ref, shown in the status, the session context, the review input and the report.## Reviewer checklist.context.mdrecommended a compound command.Risk
task donerefuses tasks that skipped phases, tests or the report.reopen-tests.task startin every worktree whose commit has the change, andapprove-testsin the other worktrees.paacm.jsonfiles must add the new protected defaults by hand (see Upgrading in the CHANGELOG).additionalContext, which Claude Code documents for Stop hooks that give guidance. A Claude Code version that ignored it would simply let the agent stop, which is the 3.4.1 behaviour for these cases. Failing gates still usedecision: "block".git checkout <commit> -- <file>. A second clone is outside what the runtime can see.doctor's program lookup is reasoned, not run.Evidence
npm test160/160;validate;build --check(61 files up to date); markdownlint 0 issues;claude plugin validate plugins/paacm --strictandplugin validate .both passed;node bin/paacm.mjs check --tier quickexit 0.finishing,worktrees,sessions,review,gateandaudit;hardening,tools,runtimeandcontent.CLAUDE_PROJECT_DIRto hook processes; before, it dropped it.paacm.json, an extension and the team's AGENTS.md text are kept;paacm.jsonreceives the new defaults;doctorreports all installed files intact.EACCESand emits no context;0700and owned by its account.gate --quiet, phase 7, REPORT,task done;additionalContextwith no hook error shown, then let through;docs/project/requirements.mdunder Release 3.5.0.df0d83e: 7 confirmed findings and 1 plausible one. Each confirmed one is fixed with a test that failed first, and the plausible one (file modes on Windows) is documented. The claims it found stronger than the code are corrected in the CHANGELOG, ADR 0021,ALGORITHM.md, the guides and the requirements. The audit's reproduction script, run against this head, reports none of its findings as confirmed. The one stop-gate case it builds (a sibling checkout the session never edited) is now judged by design only once the session edits there.After merge: tag
v3.5.0and publish the release (on the maintainer's go-ahead).