You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
qargi local prepare --task-file FILE --done COMMAND [--scope PATH]... writes the whole task block to RUN/task.md for every runtime (before, only a command runtime had one, and the lead assembled it by hand): ROLE with its class, ABILITY (the role's text), TASK (the file), DONE, PROFILE (the text of the repository's profile, ~/.qargi/config/profiles/<repository>.md, when there is one), BASE, BASE_SHA, one SCOPE: line (the paths comma separated, or none), the run's paths, and SUBMODULES and MEMORY when there are any; prepare prints TASK_FILE=RUN/task.md. It refuses, and makes no run, a task file that is empty, a link, not a regular file or over 1 MiB, or has a line of its own that starts SCOPE:; a DONE that is plainly not a command line (empty, several lines, or text bash -n cannot parse; for a simple command with no operator, a first word, after NAME=value words, that is not an executable, a shell builtin or keyword, or a script: a path is looked up at BASE in the object store, not in the lead's checkout, and a file under a --scope path counts because the task may create it; a line with ;, &&, ||, |, (, $( or a redirection is accepted when bash parses it, so only its syntax is checked, which lets the isolated-suite command line T=$(mktemp -d -p /dev/shm); mkdir $T/h; HOME=$T/h bash tests/run.sh through); a missing --done; and a --scope that is absolute, empty or holds a space, a comma or ... Without the flags prepare is unchanged. tests/test_taskblock.sh compares the block of a build run and of a review run with tests/golden/taskblock-build.md and taskblock-review.md
qargi review check fails a patch series that touches a path outside SCOPE: the SCOPE: line of the run's task block (RUN/task.md, which a member cannot write) when it records one, else the manifest's scope[]; a rename's new name and the manifest's touched_paths[] count too. The contract tells a member to compare git diff --name-only BASE_SHA with SCOPE before each commit, to report BLOCKED naming the path when DONE needs a change outside it, and to put anything else under noticed_not_acted_on; the "Needs from the lead" instruction is gone. contract/output-format.md names the manifest's required keys (status, base, done, gates, commits, scope, touched_paths) and contract/manifest.example.json shows a complete manifest. The help of qargi review check, the manual page and the review role describe the rule as it is: a report-only run needs its manifest.json and a report.md whose last non-empty line is verdict: accept or verdict: revise, a patch outside SCOPE fails, and commits dated within two minutes of each other warn
qargi review save RUN [--force]: stores the final message of a read-only run (standard input) as RUN/out/report.md, and writes a minimal manifest.json (status, base, done, gates, commits: [], touched_paths, the report in artifacts) when OUT has none. It is the report path with no shell text: a reviewer whose file tool is refused returns its report as its final message and the lead stores it, instead of a heredoc that the guard reads line by line. It refuses a directory that is not a run of the lead's registry, a run that is not read-only, a run directory or OUT that is a link, an empty message and an existing report.md (without --force); qargi review check RUN/out then decides whether the report has the required form. The verb is the lead's: the guard refuses it from a member, as it refuses review apply.
docs/development/architecture.md: where each kind of record lives (configuration and state), the layers of the code, the life of a local run, what the guard advises and what the checks verify, and which parts are data and which are per platform. docs/development/README.md says in a sentence what each of its links is for.
Changed
qargi __guard, the hook command a member runs before every tool call, loads lib/guard.py as an imported module instead of running it as a script, so python reuses its compiled form (a script is compiled on every run: the 235 KB guard took more than half of each call). A harmless ls call takes a median of 48 ms instead of 105 ms, a Write call 60 ms instead of 118 ms (20 runs, minimal environment). The exit status, standard output and standard error, the options and the refusal messages are unchanged. install.sh --user compiles the tool's lib/ as you (compileall), and qargi upgrade compiles the root-owned checkout through QARGI_SUDO before it relinks; nothing is written outside the tool's own __pycache__ directories. A tool you cannot write to and that nobody compiled (a first sudo git clone install) works as before, only slower: install.sh --user says so and prints the one command that compiles it.
tests/startup-budget.txt has a ceiling for qargi __guard (120 ms, the median of 20 runs on a harmless call in a compiled tool, about twice the 48-60 ms measured), and unlike the role list ceiling, which only warns, it fails: a line marked enforced fails the test when the median is over its ceiling in three measurements in a row, so a loaded machine does not.
paths.installed_tool_root() (asked dozens of times in one guarded call) reads install.json again only when the file changed (inode, size, modification and change time), not on every call; what it returns is unchanged, and a record that is rewritten, replaced or removed, or a configuration directory that moves, is seen at once. 28 calls take 0.4 ms instead of 1.2 ms.
The build and write role texts point at the reference store only when the task block has a MEMORY section or a store is mounted (a local member has none; qargi role prompt prints these texts to it), and the contract says a member without one follows the task block, the contract and the repository. The six-step order of work (orient, baseline, implement, check, commit as you go, report) is a numbered list in the contract, which every member loads, and no longer only in the build seed's workflow note
The advisor sign-off of the qargi-flow skill is a run: write the combined diff to a scratch file, prepare a review run with qargi local prepare --role review --profile advisor, dispatch it with a full task block (TASK, the diff path, and as its DONE a command, qargi review check ../out, which the member runs from its clone: a sentence is not a DONE, local prepare refuses it), take it in with qargi review check OUT and close it with qargi local clean; before, the advisor was spawned on a bare file with no task block and no output directory
One review vocabulary. A read-only role returns report.md and manifest.json: # Findings (each finding with severity high, medium or low, file:line, what is wrong, the evidence, a suggested fix as a fenced diff), then # Checked (what was run with its exit status; what was only read), and a last line verdict: accept or verdict: revise. The output format, the review role, the review-findings pack and the review role seed's findings format and workflow use these words; CONFIRMED, PLAUSIBLE and "not ready, ready with changes, ready" are gone. qargi review check now fails a report-only run without its manifest.json (PREFIXmanifest.json beside PREFIXreport.md) or whose last non-empty line is not the verdict line, and warns when there is no # Checked section
The member contract says to record each check in log.txt when it runs (command, exit status, last lines), to report only what was run in this run, and that a check reasoned about but not run is not run with the reason, never pass; and to commit as you go, each change once its checks pass, a series committed all at once at the end being sent back. qargi review check raises its note on commits all dated within two minutes to a warning
The member contract no longer says "never stash: use a WIP commit", which collided with the rules against rewriting history and for commits that pass alone. It says to check BASE out beside the work with git worktree add to see how BASE behaves, to git restore an experiment, and that a one-task member has nothing to set aside; the gitflow-member and qargi-contributing packs drop their copies
The member contract says once how to write a report and a commit message: with the file-writing tool, never with shell text (echo, heredoc), and a commit with git commit -F FILE; a read-only role whose file tool is refused puts the whole report in its final message, and a guard refusal is reported, not rephrased. The shell-heredoc fallback and the copies of the commit-message rule are gone from the contract, the gitflow-member and qargi-contributing packs, the Claude Code addendum and its member-out note; qargi-verify tells the lead to store such a report with qargi review save
The qargi-task skill has the lead write the task to a file and call qargi local prepare ... --task-file F --done CMD [--scope PATH]..., then hand the member the path of RUN/task.md ("read it and follow it", with CLONE, OUT, GIT and BASE_SHA when the runtime needs them); the list of sections to assemble by hand and the step that pastes prepare's output into the block are gone. qargi-verify compares the manifest's done.command and the DONE check it re-runs with the DONE of RUN/task.md, and review check applies its SCOPE
make bootstrap installs and make doctor checks ssh-keygen (OpenSSH: openssh on Arch, openssh-client on Debian and Ubuntu, openssh-clients on Fedora), which the marshal's signed records and the test suite need; AGENTS.md lists it
A plan token is checked by default. need_plan (lib/lib.sh) no longer reads QARGI_NESTED: it always compares the token, so a verb that forgot to clear the variable can no longer skip it. need_plan_nested is the one variant that honours the variable, and qargi canon apply (which provisioning runs nested after its own plan check) is the only caller; tests/test_nested_plan.sh fails when it appears anywhere else. The QARGI_NESTED='' prefixes and unset QARGI_NESTED lines on the other verbs are gone. The nested field of an audit line is true only when a call skipped its check through the variable, and no longer when the variable merely was set. qargi help guard and the manual page say that only canon apply honours QARGI_NESTED, where they said it "skips a plan token check".
Documentation and help say what the guard is and no more. The README, the introduction of qargi help, the members page, the marshal page, the local-session page, the qargi-task skill and the member prompt (agents/member.md, agents/member.body.md) no longer say "sandboxed agents", that a member "cannot" do what the guard refuses, or that the guard "enforces" a class. They say it is a best-effort text check of the ordinary ways, not a sandbox; a local member runs as the lead's operating-system user and a real sandbox is not yet built; qargi local verify and qargi review check are the backstop. qargi help guard opens with that. Run qargi agent refresh to render the new member prompt into existing members.
qargi help memory shows the help of the memory command, not the topic of the same name. The topic is now memory-stores (qargi help memory-stores, and the MEMORY-STORES section of the manual page), and the command's help names it.
Documentation brought in step with the code: the local-session page describes the review-findings pack (severity, file:line, the closing verdict: line) and the qargi-contributing pack as they are now, CONTRIBUTING lists every rule of the hermetic lint (absent included) and the legacy lint, and the members page and step 2 of the git-flow page (each one long paragraph or list item) are split into short paragraphs and list items with the same meaning.
The documentation and qargi help methodology say that methodologies are not pluggable in behaviour yet: a methodology file sets the scaffold (files and parameters), the integration branch project attach looks for, the release and hotfix branch patterns and the after_release steps, while the feature, release and branch verbs name develop, master and the feature/ and agent/ prefixes themselves. The README states the requirements: Linux, bash, GNU coreutils and findutils, git, jq, python3 3.11 or newer, flock and ssh-keygen, and that nothing checks the platform before a verb runs.
lib/local.sh no longer clears QARGI_NESTED before the plan check of local clean --orphans (need_plan ignores the variable, so the prefix did nothing), and run_class is called with the run alone (the second argument was ignored). tests/test_nested_plan.sh now covers lib/local.sh in its static check of redundant clears, and fails on a run_class call with a second argument.
The note that qargi local verify and qargi local clean print when OUT holds no report no longer sends the member to the shell and a heredoc. It says the member writes its OUT files with its file tool, and that a read-only member whose file tool is refused returns the report as its final message, which the lead stores with qargi review save RUN < MESSAGE.
The DONE that qargi team brief gives the review member is what qargi review check requires of a report-only run, where it asked for a Findings heading alone: the report has a Findings heading and its last non-empty line is verdict: accept or verdict: revise, and the prefixed manifest (review--manifest.json) is a JSON file. It is plain grep, sed and python3 -m json.tool, which a hosted member has (it has no qargi); tests/test_team.sh runs it on files and requires qargi review check to agree on each case.
The contract directory of a local run holds manifest.example.json beside contract.md and output-format.md (qargi local prepare copied only the .md files, so the example the output format refers to was out of the member's reach), and the output format says in one sentence to see it beside the file. contract/output-format.md is five words longer (the ceiling in tests/skill-budget.txt is 323). The prompt of a cloud agent is the same texts without the file, so a deployed agent shows as stale until qargi instances refresh-prompt.
The member contract asks for qargi review check to report no failure (0 fail, or OK report-only run) and says to explain a warn in the report, where it asked for 0 warn: the warning for commits made within two minutes of each other cannot be cleared without rewriting history, which the contract forbids. The check is unchanged. The ceiling of contract/contract.md in tests/skill-budget.txt is 982.
qargi release finish (and --hotfix) merges the tagged master merge commit into develop, or into the open release branch for a hotfix, instead of merging the release branch a second time (Merge tag 'VERSION' into develop, --no-ff). Master and develop used to differ by one merge commit per release (GitHub showed develop "N behind master" with equal trees, and git describe on develop did not reach the release tags); now the release's commits and every merge commit master holds arrive in develop with the one merge, so git rev-list develop..master is empty after a finish (not after a hotfix finished into an open release branch, which develop gets with that branch) and git describe --tags develop names the tag. release start's list of the features merged since the previous tag works on develop for the same reason. History already on master is not rewritten: the first finish of this shape brings the earlier master-only merge commits into develop. The plan reads "merge tag VERSION (the master merge commit) into develop"; the intent record, the resume after a death following the push, the refusals and the exit 3 on a conflict are unchanged, and the conflict worktree now holds a merge of the master commit (kept beside it, so a run with --resolved uses the same commit). The qargi help release finish text, the manual page, the git-flow page and the qargi-flow skill (still 1145 words) say so. tests/test_release_master_merge.sh is the test.
Removed
Code and text that served records and layouts of earlier versions (qargi keeps no backwards compatibility). install.sh --uninstall with no install manifest removes nothing and says so (exit status 1), with or without --force; it no longer guesses at the fixed paths of an install that predates the manifest. qargi upgrade no longer warns about an agents.json of another shape (the agent verbs still refuse one, exit 2, and agent add still starts a fresh file). The class of a local run is the registry's alone (a record with no class counts as a run of work; a class file in the run directory is never read), the answers of a run are its registry snapshot alone (a record with none has none, and the stored project's file is not read in its place), and the project of a run is the one prepare recorded. memplan.migrate(store, to_version, ...) is memplan.canon_plan(store, ...) (the version argument and its check are gone), and the notes canon plan adds a missing Sources to say carried over by the canon 2 plan, not migration. The messages for a canon version other than 2, and for an archive manifest without file modes, no longer speak of migration or an older qargi; the report's limits no longer mention runs made before the registry existed; the migration prose is gone from the models page.
tests/test_legacy_lint.py fails when lib/ holds the phrases older record, migrat or earlier version outside tests/legacy-allowlist.txt, a list with a reason on every line that can only shrink (a line that matches nothing fails). The list is empty.
The reading of an acceptance record that names no provider as the project's provider's (lib/packs_audit.py) and its line in tests/legacy-allowlist.txt. A skill acceptance counts only when its record names both the resource (skills) and the project's provider, as the verbs write it. A record that lacks either is nobody's: the audit shows the skill as not accepted (unmanaged), acceptance and unaccept say it is not accepted and change nothing, and a new accept is stored under skills/PROVIDER/NAME beside the old record, which is not rewritten. Accept such a skill again with qargi skill accept.
Fixed
The guard refuses a hook call whose input is empty or is not JSON (a hook cut off on the way, a runtime that sent nothing) instead of letting it through: exit status 2 and one line on standard error, the hook input is empty or the hook input is not valid JSON (cause), so the guard cannot read the call; refusing it. Every protocol the guard supports sends JSON, so no genuine call is affected.
The guard refuses a call when the class or ability file of the run it works in exists and cannot be read (a directory in its place, a mode that bars it, a loop of links), instead of reading it as a run that is not read-only: one line, the class file of the run RUN cannot be read (cause), so the guard cannot tell whether the run is read-only; refusing the call until the run directory is fixed. A run with no such file is still a write run, and the --class of the hook still decides first.
The guard refuses a call that needs the protected directories when a provider definition of the library does not load (unreadable, a name clash, a name that disagrees with its directory), instead of skipping the definition with a note and leaving its skills and agents directories unprotected: one line, provider definitions that do not load: NAME (cause); ... refusing the call until the library is fixed (qargi provider check NAME, or remove the definition). A call that writes nowhere and reads no protected place (ls, git status) is not asked and still runs.
The member's guard hook covers the Monitor tool, which a member has and which runs a shell command in the background: a Monitor call carrying git push (or any command the guard refuses from Bash) was let through because the hook matcher omitted it. The matcher is now Bash|Monitor|Edit|Write|NotebookEdit|Read|Grep in the Claude Code definition and the member template, Monitor is a shell tool in the hook data, and its command is judged by the same code as a Bash command. A member made before this change keeps the old matcher until qargi agent refresh (it shows as STALE). The members page lists exactly the tools the matcher has, and a test keeps the four places in step.
qargi credential setup lead-push no longer puts the GitHub write token in the argument list of the jq call that builds the vault credential, where any user of the machine could read it with ps; the token goes to jq on standard input, as the file header already said it would. The body sent to the vault is unchanged, and tests/test_leadcred.sh records the arguments of every jq call of an apply and fails when the token is among them.
qargi feature finish and qargi release finish can be run again after a death that came after their push. They pushed origin first and moved the local branches after it, so a process that died in between left origin finished and the local refs where they were, and the re-run died at "develop is behind or differs from origin/develop" or "the tag exists already". Both now write an intent record under the state (finish-intent/PROJECT-release-VERSION.json, -hotfix-, -feature-NAME) before they tag or push: the verb, the local and origin values of each ref before and the value the finish gives it, and the steps done. A re-run that finds it asks origin live: when origin holds exactly the intended refs it prints what is left (move the branches, delete the release or feature branch and the one on origin, the audit line, the after-steps) with a plan token, and --plan TOKEN --approved "..." does only those steps, each marked in the record so a second death repeats nothing; when origin still has the old values the push never happened, so the tag and the record are dropped and the finish starts again (by the apply, after its plan token is checked: a plan alone says they will be removed and removes nothing); when origin holds anything else, or only some of the refs, or cannot be asked, it refuses, names the ref and changes nothing, and says how to give the record up: the rm of the record and, for a release, the git tag -d of the tag the dead run made here, which would otherwise stop the next finish at "the tag exists already". A finish holds a pid lock (finish-intent/PROJECT-KEY.json.run) while it runs, so a second finish of the same name is refused instead of mistaking the record of a running one for a cut-off one; a lock whose process is gone is taken over, and of several verbs that find it at the same moment exactly one does (the check and the takeover are one step under a short flock). A push that exits non-zero keeps the record (and the release's local tag) and says that origin may hold the result anyway, since a lost answer looks the same as a refused push: the re-run asks origin and completes the finish, or starts it over. The record is removed when the last step is done. tests/test_resume.sh kills the verb right after the push with a git shim, and tests/test_resume_push.sh makes the push exit 1 after it reached origin and without reaching it, and each checks these.
qargi review apply keeps its record of the apply in the lead's state (apply/RUN-ID.json), made before the branch is, and no longer relies on the applied file of the run directory (which a member can write; it is still written, because local reads it): a run whose file was removed is still refused as applied. A death after the branch was made used to leave a run that could not be applied again until the branch was deleted by hand; the re-run now removes a branch that is the base plus some of the patches and applies the series again (status started), or, when the branch was built (status built, with its tip), finishes the journal line, audit line, applied file and snapshot without applying the patches again, and refuses with what differs when the branch is no longer at the recorded tip. tests/test_review.sh covers the three cases. Two applies of one run cannot run at once: an apply holds a pid lock (apply/RUN-ID.json.run) and writes its pid and start token in the record, a second apply is refused while the first's process is alive and leaves its worktree, branch and record alone (it used to remove them, and the first apply's failure path then removed the second's), and only a record whose process is gone (or whose pid now belongs to another process) is resumed.
A usage error exits 2, as the manual page says, where these verbs exited 1: every usage error of qargi release (no version, a version that is not one, an unknown option, an option that belongs to another verb, release step with no name or a blank --evidence), an unknown option of qargi lead usage and compact-check, qargi feature merge with no branch in a detached feature, qargi mark with a bad state, and qargi discover show and install with no ID. tests/test_valued_opts.sh no longer exempts release.sh.
The guard protects the finish-intent/ and apply/ directories of the state, where the finishing verbs of a feature and a release and qargi review apply keep the records a re-run completes from, like scrub_ok/ and runs/: a member's file tool or shell can no longer write, replace or remove one (it could have named the branch an apply makes or the tip it believes was built); reading stays allowed. The refusal names both directories.
qargi local clean RUN also removes the run's apply record (apply/RUN-ID.json in the state, with its locks) and the temporary directories and worktree a qargi review apply of the run left when it was killed; a worktree is pruned from its repository, so the branch it held is free again, and a link in the name of such a directory is left alone and said. The two directories of an apply are now named with the run's id (apply-in.RUN-ID.XXXXXX holds the copy of the patches, apply-wt.RUN-ID.XXXXXX the worktree), which is how clean attributes them. qargi local clean --orphans also lists the apply-in.* and apply-wt.* directories of the state that no run of the registry accounts for (the run is cleaned or unknown, or the name carries no run id), under the same age rule and behind the same plan token and the lead's words as the qargi-run.* ones; a run still in the registry keeps its own, for clean RUN to remove.
The provider adapters (lib/providers/claude-code/adapter, claude-platform/adapter), the guard command the claude-code adapter runs, lib/conformance/cases.py, lib/env_apply.py (env check) and lib/tiers.py (the canon check) start python again with -I and now add -B when the process that starts them writes no bytecode (sys.dont_write_bytecode), as the tests' python3 wrapper does. The conformance suite wrote lib/__pycache__ in a test run because -I ignores PYTHONDONTWRITEBYTECODE; tests/test_hermetic.sh now runs the suite of both shipped runtimes on a copy of the tool and fails when a .pyc appears under lib/. A normal run (nothing asked to skip bytecode) is unchanged.
The copy of a passed run that qargi local verify saves in the lead's state (runs/RUN-ID/) includes task.md, so qargi review apply --from-saved applies the SCOPE of the run's task block, as the apply of the run directory does, instead of falling back to the member's own scope[] (a run whose directory was lost could be applied with paths the task forbade).