Repository navigation
Releases
Releases · rett/qargi
qargi 0.3.0
Added
- Graph timestamps restrict hours to 00 through 23 consistently across Python runtimes. Direct-reader tests suppress bytecode writes, and the reader contract documents import-cache behavior and unresolved paths in invalid reports.
qargi backlog graph check|show|why --graph FILEinspects a closed native v1 graph against the configured open backlog. It validates declared relationships, pins reported evidence to parser source revisions, propagates unknown prerequisites conservatively, and explains proposed needs and stale evidence with deterministic reports. Complete explanation paths can have quadratic output size. Contains edges describe structure. Archives and feature lifecycle have unknown observation state. Evaluation and execution follow existing governance. The public contract indocs/reference/backlog-graph.mddocuments manual editing, the current lead-only guard boundary and compatibility limits.- Backlog graph reports malformed input paths, including configured
backlog.filevalues containing NUL or unencodable Unicode, asinput-unreadablefindings withvalid:falseJSON and exit 1 instead of a traceback. Unread inputs retain null digests, and ordinary filesystem errors retain their diagnostics. Text reports escape non-printable controls and characters that the output encoding cannot represent while preserving printable Unicode. - Graph source paths use a literal leading-tilde replacement with raw
HOMEfor repository and backlog paths. The public contract documents bounded differences from Bash replacement of&and shell stripping of NUL/trailing newlines. Source and graph paths retain symlink and parent segments so the kernel selects the intended bytes. Empty and false section values are unset, and the read-only graph can observe an absolute backlog after its checkout has gone. Confirmed structure errors name participating nodes and relations, and the public contract specifies report reasons and first-error behavior. The public graph command remains lead-only with a precise guard denial; members may run the pure Python reader directly. Fixed ISO-date cases run on the current tested runtime without certifying a Python 3.11 runtime. - Fast plans using
--changed-filesadd the full-tier strict literal converter subset whenCHANGELOG.mdchanges. An empty changed-file list excludes this subset from fast selection. Whole runs retain both the subset and the complete site step.
Changed
- Shared project inference introduces a breaking change to leading-tilde repository matching. It treats any leading tilde in
repo.path, including~name, as the rawHOMEprefix plus the remaining text, rather than expanding a named user's home. WithHOMEunset it uses an empty prefix in place of the former password-database fallback. This follows ordinary backlog prefix rules with the documented Bash&exception; explicit project arguments andQARGI_PROJECTretain precedence. - The graph v1 input contract requires Unicode scalar values in free-text
by,basisandreffields. Escaped lone surrogates produce invalid provenance reports; valid surrogate pairs and printable Unicode remain supported. This does not establish general JSON security.
Fixed
- Graph inputs preserve terminal slash and
/./kernel semantics instead of silently opening a regular file after path normalization. Deeply nested unrelated registry JSON is skipped by shared inference, and a selected deep project configuration yields an invalid graph report. Relative graph paths from an unavailable current directory produceinput-unreadable; absolute inputs remain usable with an accessible configuration and explicit project. The public shell entrypoints may still print getcwd startup warnings.backlog list,addanddonerequire a repository marker even though graph observation of an absolute backlog does not.
qargi 0.2.14
Fixed
- The agent test runs serially after the parallel pool following checkpoint2 8a7a675's lone timeout at 678s without assertion failures (137 steps passed; suite total 2554s). Its agent code and test match 408b8ae, whose lead DONE passed in 440.82s. This scheduling mitigation lengthens the serial tail without changing cases, limits, budgets or recorded durations; it establishes neither a root cause nor a performance guarantee.
- Conformance case 15 checks prepare's original-root plain-path diagnostics and unchanged scratch/TMP parent entries; runtime coverage is the symlink real-path branch only, not runtime plain paths or prepare real paths. Redirect fixtures read TMP through the trusted registry reader and stop on unknown labels or move/link failures, with focused negative probes. These are fixture checks, not product containment or qualification evidence.
- Signal cancellation tests exercise regular and symlinked runtime executables using dispatch's real path, failing promptly on an executable-path mismatch in the timed exec-child wait while retaining readiness, PID and group assertions without changing limits.
- Both guard test selections and the local-run workflow test run serially after the suite's parallel pool to mitigate contention observed at checkpoint1 e77c682. Reduced-concurrency reproductions passed; this scheduling mitigation increases the serial tail without changing cases, limits, budgets or recorded durations, and does not establish a root cause or guarantee performance.
- Codex spawn guidance fits the existing 393-word provider-notes budget while retaining prepared defaults, override and identity qualifications, drift refusal and sandbox guidance.
- The running-signal lifecycle test targets the runtime child's wait after proving its readiness and PID, so prepared-profile resolution subprocesses cannot receive the injection; cancellation and process-group shutdown remain required.
- Provider acceptance tests distinguish logical command profiles from stale in-session member rows and verify prepared high effort, unsupported override refusals, policy drift and supported explicit tier/effort overrides.
- Command conformance uses one bound provider definition for prepare and run, retaining private request authority. Hidden-path coverage checks prepare's refusal and runtime's independent recheck after member paths are redirected into a hidden place.
local runretrieves the prepared-command marker with the required registry filter when its configured provider becomes unavailable, preserving re-prepare guidance without an unbound-variable diagnostic. Refusal leaves the private request intact and starts no member.local run --modeluses the provider's established pattern search semantics, so repeating a prepared out-of-tier model accepted by a prefix pattern works without override capability verification.- Command-mode
agent modelstext and JSON now use the same profile resolver asagent for, showing logical bases, selected model IDs or aliases, requested effort levels and sources without member registration warnings or legacyroles/leadselections. JSON retains existing keys and profile metadata and adds command mode,selected,requested_effortand logical-base markers; nativeeffort_controlremains separate from the requested level. Refused entries remain visible with their reasons. The intentional never-dispatched lead row is informational, without a refusal or warning;agent for --profile leadstill refuses. Empty tier aliases fall back to the ID, as command dispatch does. Member registration and alias flags apply to in-session rows. Row flags do not fail the display, but command-mode extension composition or load errors may fail the command. local prepareresolves explicit and implicit command profiles before making run artifacts and retains the request in lead-owned private lifecycle authority.local runuses its requested model and effort when each option is omitted, rejects runtime/mode/catalog/profile/policy drift with reprepare guidance, and permits explicit changes only after current catalog and effective capability checks. Null effort controls remain omitted with a note; requests do not claim observed identity. RUN and saved request files are inspection mirrors. Existing records without requests retain their documented behavior without adopting member files.agent forselects dispatch mode without composing extensions, preserving in-session resolution's existing handling of extension errors. Command runtimes still validate the project's enabled extensions.- Command-mode
agent forrefuses malformed non-nullprofilescontainers and selected entries before normalization can inherit shipped defaults or choose a class fallback. An absent or nullprofilescontainer uses shipped defaults, consistent with project validation. Present profile fields follow the project rules for bases, known-role ability lists, purpose, allowed keys, model, tier and effort; null selected entries and null field values are refused. Unrelated profile entries do not block resolution. agent forresolves command-runtime profiles to logical worker/reviewer bases without member files or registration, using the runtime's catalog and ignoring legacy member model/effort answers. Invalid profile models, tiers and effort levels are refused. Text output and JSON'smodel // model_idselect the alias only when the catalog reference saysuse: aliasand the tier has one; inline models anduse: idselect the ID. JSON marks command mode. Effort remains the requested level, including when the runtime has no native control.local closeagain names the applied branch and rewrite remedy when refusing a completed authoritative apply; unfinished applies still refuse early.- Help, the manual and verification guidance qualify discard ownership and quiescence guarantees as lifecycle protections; legacy recovery remains explicitly weaker.
- Lifecycle cleanup completes bookkeeping after an already removed work directory’s parent changes or its tombstone is recreated, preserving foreign entries. Replacements are reported only when observed through the original parent.
- A process reaped while
/proc/PID/statis open can returnESRCH; lifecycle probing now treats it as PID absence, like a missing file, only while/procremains available. Other evidence errors stay unknown and process-group completion still requires the kernel absence check. - Interrupted lifecycle cleanup preserves and reports replacements at original RUN/TMP names after staging or deletion, then completes original tombstone deletion and exact registry bookkeeping. Owned tombstones recover crashes before phase commit; pre-stage replacements and foreign/unowned tombstones still refuse. Internal TMP completion is recorded before RUN staging, so recovery never traverses a replacement RUN or its symlink target to inspect old TMP paths.
- Genuinely unmarked registered legacy runs again support warned
local clean --discard, and ordinary clean where the released checks permit it, including applied/stale batches. Registry-sourced TMP confinement and one-filesystem deletion remain; this weaker recovery proves no process quiescence and creates no lifecycle authority. Missing/corrupt marked state and stripped markers with private lifecycle remnants still refuse. local prepareagain accepts symlinked, relative and..-containingQARGI_RUN_TMPparents. It resolves the parent before creating directories and consistently records and displays the canonical TMP path; repointing the alias later does not redirect cleanup. Hidden-path checks still inspect actual paths.
Changed
- Clean authorizes retained evidence before deleting TMP or RUN. Prepare records private directory identities; interrupted cleanup stages owned tombstones and refuses source replacements before staging. Missing ownership evidence for marked runs requires manual recovery.
- Process-group completion requires a kernel absence report. A changing process listing or zombie-only sample cannot authorize retry or cleanup; runner diagnostics identify busy or unknown groups without claiming live survivors.
- A full verification history refuses before publishing a new snapshot; identical observations still consume no history.
- Lifecycle safety guidance is generated from the canonical help source, keeping the manual and command help consistent.
- Structural review can inspect unsuccessful attempts as diagnostic evidence; it explicitly says that inspection is not acceptance, and verification and apply still refuse their output.
- A signal received after a local member finishes no longer marks its private attempt cancelled, so completed output can be verified.
- Cleanup diagnostics explicitly name retained cleaned registry evidence; covering shell expectations retain branch, worktree, scrub and registry assertions under the conservative lifecycle policy.
- Retry epochs precede ref collection, including slow remote queries; lead-written report manifests take their base commit from private prepared authority.
- Lifecycle clean records owned atomic tombstones before deletion and keeps its cleaned registry row. Repeating clean resumes interruptions before or after registry and lifecycle-state removal without adopting orphan paths or implicitly discarding retained evidence.
- Save staging is limited to one privately recorded directory per run and recovered after owner death without leaving a copy child. Unknown or replaced staging refuses cleanup; retained-copy limits count directories, not bytes.
- Unfinished lifecycle applies refuse resume and cleanup after their parent dies because Git-child termination is unknown. Completed Git work marked built retains supported bookkeeping recovery; hooks still receive no lifecycle lock.
- Dispatch installs signal handling before committing begin, records caught prelaunch cancellation as not-started, and refuses unknown pr...
qargi 0.2.13
Changed
- Member tasks can reuse lead-supplied passing baseline evidence for the exact BASE_SHA when relevant input and check definitions match, recording its reference, SHA and provenance in
deviations[]rather than claiming a run or gates pass. They run focused checks and applicable gates for each change, then DONE on final HEAD after all outcomes and again after later changes; profile gates and the lead's whole-suite checkpoint remain in force. qargi feature parkand automatic parking follow allow nonconflicting untracked runtime files while refusing tracked changes, parking-branch commits absent from develop, and collisions with untracked or ignored files. A failed automatic follow leaves the parking branch behind without undoing a successful integration.- Installation examples distinguish user and project choices, and the memory tiers example aligns its columns.
- Documentation examples and explanatory tables fit narrower GitHub views more readily.
- Ordinary clean retains the read-only product refusal independently of refs-only observation: unexpected patches or a bundle preserve the run even with zero extra clone commits. An unchanged internal refs verdict grants neither verification nor artifact acceptance; explicit discard deliberately recovers it and reports actual refs.
- Inspection failures now show sanitized reasons in run, verify, review check and clean. The 0.2.13 limits stay fixed: 512 MiB copied metadata (the whole object store included), 1 MiB config, 100,000 traversal/read steps, 64 directory levels and 60 seconds for copy traversal and each Git subprocess. Ordinary prepared clones are supported only within those bounds. Ordinary clean preserves every artifact when worktree state is unknown, including when commit inspection succeeds; explicit discard remains deliberate recovery after preserving wanted work.
- Inspection disables replacement objects, preserves the difference between valueless (true) and explicit empty (false) Git booleans, and uses a minimal format-0 config for SHA-1 while keeping explicit SHA-256 format support. Regressions include replacement refs and a same-length, restored-mtime racy-index case with a negative control.
- When the run directory still exists, classless registered legacy discard reports the authoritative refs verdict, including unexplained real ref changes. Its internal refs-only observation neither verifies the run nor saves accepted output or grants class permissions; ordinary unknown-class refusal remains.
- Unexpected runner failures retain nonsecret start/process and home metadata, cleanup status and refreshed-file residue for list and verify. Retry checks the original group for every dispatch outcome and refuses known or unknown survivors until quiescence can be established, without signalling a potentially recycled PID. A partial home setup whose cleanup also fails keeps an explicit cleanup note alongside the planned link names.
feature finishvalidates project identity/answers and strictly parses and enumerates the same registry snapshot. Missing/malformed answers, dangling/unreadable/malformed registries and reader failures refuse; empty exit 1 and traceback output are distinguished from valid open-run rows. General diagnostic readers remain tolerant. This removes the double-read path but provides no coordination of new preparation through the eventual merge.- Local runs stop remaining members of the original process group before removing authentication links and inspecting output, including on normal exit and unexpected runner errors; surviving members fail an otherwise successful run. Unexpected errors retain the original exception even if cleanup also fails, and an unreadable survivor count stays unknown.
- The lead verbs and guard MCP policy validate the decoded JSON class against the exact supported registry strings before shell decoding and preserve malformed runs for investigation. Explicit
clean --discardremains a recovery route for known registered legacy runs without a class; it intentionally drops evidence and cannot discard unregistered runs. A member'sreview checkrequires its marker to resolve to the inspected run, then checks structure without lead registry authority, verification, scrub marks or cache. Lead review checks validate class before renewing the lease. Pre-existing guard shell/write tightening still consults the member class file; those paths and their heuristics are unchanged. - Clone inspection reads a bounded private metadata copy in trusted lead state with a minimal configuration and an absolute Git resolved once from the lead's controlled PATH, so even a newly inserted filter cannot execute during inspection. The copy preserves index timestamps for same-length write detection and supports SHA-1 and SHA-256. Unsupported metadata layouts and inspection failures stay unknown; ordinary clean preserves unknown commit state. Copies may combine concurrent states and do not contain escaped sessions.
- Expected setup refusals retain planned sign-in link names if partial cleanup fails, so list and verify can locate residue. Unreadable process-group probes no longer prevent TERM-to-KILL escalation; an unreadable final count remains unknown. Cleanup marks the run cleaned without granting verification, including when no prior verify was run.
- Feature finish requires repository identity for any run path that exists or cannot be inspected. Structurally valid records for confirmed-absent run paths do not block finish solely for missing identity; the records are not deleted, rewritten or granted authority.
qargi 0.2.12
Added
- The model catalog of the family
openai(lib/providers/catalogs/openai.json), the table a runtime of that family names withmodels.catalog: the tiers fastgpt-6-luna, balancedgpt-6.1-terra, deepgpt-6.1-soland frontiergpt-6-astra(ids of the pattern^gpt-[a-z0-9.-]+$, passed as ids, with no alias), and the effort levelslowtomaxunder the same names. No tier is verified yet (verified_onis null until a real run uses it); the vendor's sixth effort level,ultra, has no qargi level and is not mapped. - The local runtime
codex(lib/providers/codex.json, data only, no code of its own): a project whose local provider iscodexruns its members withqargi local run, which startscodex exec --json --ephemeral --ignore-rules --strict-config -C CLONE [-m MODEL] [-c model_reasoning_effort=EFFORT] -o FINALin the run's clone with the prompt on standard input, adding--sandbox workspace-write --add-dir OUT --add-dir CLONE/.gitfor a write run and--sandbox read-onlyfor a read-only one (--dry-runprints it). The clone's.gitis added because workspace-write keeps it read-only, sogit addfailed onindex.lockand a build member could not commit (a live check with codex-cli 0.167.1 showedgit addandgit commitworking with it). The cost of that grant: a write member can change its own clone's git configuration and hooks. The clone's only remote is the run's own bare copy, with its push URL disabled, the sandbox has no network, the lead takes the work from the patches in OUT, andqargi local verifycompares the real repository's refs; a read-only run never gets the grant. A path of the run in a definition's argv ({clone},{out_dir},{run_dir}, alone or with a path after it, such as{clone}/.git) must stay inside its directory and pass through no link, orqargi local runrefuses the run: a.gitan earlier member left as a link would otherwise point the sandbox's writable directory anywhere. The read-only barrier is that sandbox alone (barrier.kindsandbox-args, claimedpartial): it holds a member's shell commands, not its reads, and no guard hook runs. Headless dispatch, the read-only barrier (partial) and the token report are verified by live checks with codex-cli 0.167.1 on 2026-11-11 (verified_onandsourcein the definition name the evidence), so a review role oncodexruns with the barrier's note and needs noprovider accept; the model and effort overrides (accepted in a run, but no event names the model or effort used), skills and MCP stay unverified and count as not supported. The member's last message goes to a new file of the run (FINAL, below) and becomesOUT/report.md, and the run's contract addenda (lib/providers/codex/local-run.md) say so.--ignore-ruleskeeps every Codex execpolicy rules file out of a run (a rule can let a command run outside the sandbox), and--strict-configmakes Codex refuse a key of the run's configuration file it does not know, so a baseline key a later CLI renames stops the run instead of being ignored. A Codex member gets the MCP tools of its role and the runtime's baseline in the configuration file of its run home (below), and no resumed session. It gets no qargi skill: itsCODEX_HOMEandHOMEare the run home, which has no skills directory, its clone carries only the skills the repository commits under.agents/skills(a session's untracked links do not travel to a clone), and the baseline turns Codex's bundled skills off, so a member receives the contract and the task, and never a lead-management skill (a test runs a member withqargi-flow,qargi-verifyandqargi-tasklinked where a Codex lead finds them and checks that it sees none).qargi provider notesanswersspawn,session-model,exploration,skills-dir,identityandcommandsfor it. - A command runtime is told where to write the member's last message with the placeholder
{final_message}: a file in a directory of the run thatqargi local runmakes new for each run (RUN/final.XXXXXXXXXXXX/message.md, mode 700), never a file of OUT, so the runtime never writes into OUT itself. When the process has ended the file is taken only as a regular file with one name, of UTF-8 text, at most 1 MiB, opened without following a link or waiting on a pipe; it is published asOUT/report.mdby renaming a new file (mode 644) over that name, so a link, a pipe or a second name a member left there is replaced and never written through. A link, a pipe or a second name at the private file is removed (what it points to is untouched), a directory there is left, and a file that is too large or not text is kept for you to read; none of them is published. The output says what became of it (REPORT=OUT/report.md (N bytes ...)orREPORT=none: WHY), andRUN/dispatch.jsonrecords it (report:state,why,bytes,kept,previous). On--againthe report of the earlier run is set aside asOUT/report.md.previousjust before the member starts: a run that publishes a report replaces it, or one an earlier rerun set aside (PREVIOUS_REPORT=replaced), and a run that publishes none, a failed or refused one included, leaves it there (PREVIOUS_REPORT=kept as ...), where neitherreview checknorlocal verifytakes it for this run's report. Areport.mdthat is not a plain file of its own (a link, a pipe, a directory, a second name) refuses the run before anything starts.--dry-runprintsFINAL_MESSAGE=. Thecodexruntime passes it as-o {final_message}in place of-o OUT/report.md, which let the vendor CLI write through whatever an earlier member had left at that name. The codex stub of the conformance suite now writes a last message where-osays, as the CLI does. - A read-only run whose report is the runtime's last message (
{final_message}, thecodexruntime's-o) gets its manifest from the lead:qargi local runwritesOUT/manifest.jsonin the minimal report-only format ofqargi review save(status,base,commits: [], the report inartifacts) with arunobject of what the lead recorded (the run's id, role, runtime, class, the model and effort qargi passed, start, seconds, exit, outcome andpackaged_by), taken from the lead's registry and fromRUN/refs.beforeas read before the member started, never from the member. A Codex reviewer, which may write nothing, OUT included, had a report and no manifest, soqargi review checkfailed every one of its runs. The manifest is written only when the run's outcome is ok, its report was published and OUT holds no patch; otherwiseMANIFEST=none: WHYsays why, and a missing report, a report without a Findings heading or a valid last verdict line, and patches in a read-only OUT failreview checkandlocal verifyas before. It claims no test and no verdict (done.exit_codenull, no gates); its status isBLOCKEDwhen the first line of the report saysBLOCKED, also under Markdown marks or asStatus: BLOCKED(**BLOCKED**,# BLOCKED,**Status:** BLOCKED). The manifest of an earlier run is set aside asOUT/manifest.json.previouswith the report (PREVIOUS_MANIFEST=), and amanifest.jsonthere that is not a plain file of its own refuses a read-only run before anything starts. A write run's manifest stays the member's.RUN/dispatch.jsonrecordsmanifest(state,why,previous). The Codex addenda and the providers page say so. - A runtime started by
qargi local runcan have a home directory of its own (dispatch.homeinlib/providers/schema-2.json:{env, dir, link: [{from, to}]}): the member getsRUN/home/DIRin the variableenv, and each filefromof your home is linked into it astowhen the run starts and unlinked when the run ends, also when it fails or times out. The file is never copied;--dry-runprintsHOME_ENV=,HOME_DIR=andHOME_LINKS=with the link names only, anddispatch.jsonrecords the names, never a source path or a value. A source that does not exist is not linked (a note says so). What the runtime does to the link during the run (a sign-in it refreshes) loses nothing, and qargi never writes the source: written through the link, it lands in the source, which qargi cannot prevent (a note); the link deleted, the source is untouched (a note); the link replaced by a file of its own (an atomic replace), that file is kept asNAME.from-run(mode 600) in the run home, never written over the source (another run, a session of the runtime or a sign-in of yours may have written it meanwhile), named in aHOME_RESIDUE=line and inhome.residueofdispatch.json, with a note that says whether the source is still the file it was when the run started (the same real path and the same sha256, taken in memory then and never printed or recorded); you reconcile it yourself (docs/members-and-providers/codex.mdsays how),qargi local listandverifyname it andqargi local cleanremoves it. No note or record holds a value. A variable of the run home (CODEX_HOME) set in your environment to another directory is a note, without its value: the run links the sources the definition names. The directory is made and the links removed without following a link a member may have put in the run directory.provider check,provider importandlocal runrefuse a variable that qargi sets or never hands on (HOME,PATH,GIT_*,QARGI_*,XDG_*, the loader variables and the othersenv_passrefuses, andLANG,LC_ALL,TZ,TERM) or thatenv_passalso names, adiroutside the run home, afromthat is not a plain path under your home or that lies in qargi's directory, a credential store of your other tools (ssh, gpg, git, forges, clouds, keyrings), a startup file of your shell (~/.bashrc,~/.profileand their kin), what your desktop or service manager starts (~/.config/autostart,~/.config/systemd,~/.config/environment.d) or~/.local/bin(checked again where the...
qargi 0.2.11
Added
qargi tool import SOURCE --tool-list FILEandqargi tool seal NAME --tool-list FILEjudge the tools of a tool pack by the server's own tool listing: FILE is the JSON result of the server's MCPtools/list(or the response that holds it asresult), saved to a file; qargi reads the file and never contacts a server. A tool that the listing does not annotatereadOnlyHint: true, or does not name, may not bealways_alloworautounless the pack sets"write_ok": true; a tool annotateddestructiveHint: trueand a tool the listing does not name are reported in the review asNOTElines; the check of the tool's name still applies. The pack's record keeps the sha256 of the listing and what it says of each tool of the pack (tool_list:read-only,writeordestructiveper tool;qargi tool show NAMEprints it).tool_listis part of the content hash and of the plan token, so it is approved with the pack and a change of it is drift; a pack without one hashes as before. A latersealwithout the option keeps the record's listing, and a tool added to the pack since then counts as not listed. A file that is no listing ends the verb with exit 1, and the option with any other verb is a usage error (exit 2). Without a listing the review is the name check alone, as before.
Fixed
- The review of a tool pack finds a write-like tool however the MCP server spells its name. The check only saw a write word at the start of a name or after
_, soplatform.delete_knowledge,repo/push,issue-closeanddeleteItempassed asalways_alloworautowithoutwrite_ok. A name is now cut into words at every character that is not a letter (_,.,-,/, digits) and at a camelCase boundary (deleteItem,HTTPPost), and a word that starts with a write word (create, update, delete, remove, post, push, write, merge, send, set, add, edit, close, comment) makes the tool write-like, also in a name with no separator (deleteall,sendmail). A write word in the middle or at the end of a word does not count (offset,dataset), and the wordssettings,setting,address,addresses,postal,postcode,postcodes,additionalandclosestare taken for the names of things that are read, sosettings_view,address_listandpostal_code_lookup, which the old check refused, pass. The rule is written once, inlib/packs_check.py, and every review asks it (tool import,seal,show,link,check, the renders). A pack in your library that has such a tool unattended now failsqargi tool checkand is not rendered until the tool isalways_askor the pack sets"write_ok": true, and you seal it again. qargi packs apply --target claude-code PROJECTwrites the permission rule of a tool under the name Claude Code gives the tool:mcp__SERVER__TOOLwith every character of a name other than a letter, a digit,_and-written as_, so the toolplatform.list_itemsof the packkbgets the rulemcp__kb__platform_list_items. The rule held the name as the pack writes it, which matched no tool when the name had a dot, and a name with a slash was refused. How a runtime writes a name is data of its definition (resources.mcp.name_keyinlib/providers/schema-2.json:nonword-underscore, or absent for a name left as written; a runtime without it still takes only plain names). Two tools that would get the same rule (a.banda_bof one server) are refused by the plan, since a rule for one would hold for the other. A render that holds a tool pack with a dotted name differs from whatpacks checknow expects: apply it again.- The review of a tool pack checks the names in its
toolsmap: letters, digits and_ . - /, 1 to 128 characters, the first a letter, a digit or_, so the dotted and slashed names that servers publish are accepted and a name with a blank, a control character, a quote or a wildcard fails with the name written as a JSON string. A tool pack that is not one JSON object, or whosetoolsis not an object (a list), fails its review; both passed before, because the check ended in an error and said nothing. The name of the pack keeps its rule (lower-case letters, digits,-). - A member's calls of MCP tools pass its guard, and a member has the MCP tools of its roles' tool packs and no others. The hook of a member matched
Bash,Monitor,Edit,Write,NotebookEdit,ReadandGrep, and the member definition listed no MCP tool: a call of an MCP tool was not judged, so a read-only (review) run that reached a tool pack could have called a write tool. The hook matcher now also coversmcp__.*, and the guard judges a call by the policy the tool packs of the role give that tool. It refuses a tool that is in no tool pack linked to the role, in every run; a tool whose pack is missing, changed since its approval or fails its review; analways_asktool, since a member runs unattended; a name that stands for two tools of the role; and, in a read-only run, every tool that is not a declared read (always_allow, a name that is not write-like and, where the pack was approved with the server's tool listing,readOnlyHint: true). The role is the one of the run, as the lead's registry of local runs records it; the run is found from the member mark ofqargi local runor from the directory of the call.qargi agent addandagent refreshwrite into a member's tool list the tools of the packs that reach its roles, under the names the runtime gives them (mcp__kb__platform_list_items), without thealways_askones and, for a member of read-only roles alone, the declared reads only. The hook command of such a member names its project and roles (qargi __guard --project P --roles build,review), because an MCP call names no path and a member a session starts works outside its run directory: a call made where no run is found must be allowed for every one of those roles, each held to its class, and with neither a run nor those names the call is refused. Every member isSTALEafter this release, since the hook matcher changed, and again whenever a link, an unlink or a seal changes the tool packs of its roles:qargi agent refresh NAME --project Prenders it. A runtime other than Claude Code is judged by the names its definition gives (resources.mcp.permission_ruleandname_key), and one that gives none has no MCP calls to judge.
qargi 0.2.10
Added
qargi local report RUN [--findings] [--from FILE] [--force]: a read-only member's own way to put its report into OUT when its runtime refuses the file tool forreport.md. The member writes the text under another name in OUT with its file tool and runs the verb with--from NAME, or gives it on standard input; the verb writesOUT/report.md(--findings:OUT/findings.md, whichreview checkdoes not read as a report) and, when OUT has none, a minimalmanifest.jsonthat names it. It reads only standard input or a regular file under OUT (a link, a path that leaves OUT, a file over one megabyte and an empty report are refused), writes only in the OUT of a read-only run that the lead's registry names, keeps a report that is there unless--force, refuses a build run and, with the member mark set, any run but the member's own. It is notreview save(the lead's, which stores a final message): the guard needs no change, becauselocal reportis on none of its lists of the lead's verbs. The member contract says to use it when the file tool is refused forreport.md.- The CI scan of
qargi release verifycan be told which jobs are not unmatched. A gate may carryci_jobs, a list of CI job names or ids that run its check under another name: the scan reports each as a note,covered by gate NAME, and does not warn. The new answerci.not_gatesis a list of job names or ids that are aggregate or publish jobs and not gates; the scan leaves them out. A name listed in either that no workflow has is a warning, unless a workflow or a job name could not be read.--strictcounts neither covered nor listed jobs.project validateandproject setcheck both lists (non-empty names, each once). - A feature gate may carry
covered_by: NAME, NAME being a release-tier gate whose command runs the same check.qargi release verifylists the gate ascovered by NAMEand does not run it; its result in the run record and thegates:line iscovered by NAMEwhen NAME passed, andnot runwhen NAME failed or was not reached.qargi feature verifystill runs it.project validateandproject setrefuse acovered_bythat names no release gate (or that is on a release gate), naming the entry. qargi project adopt NAME --showprints, with the plan, everything the plan token covers: the refs it starts from, the branches to create, and the full content of each file to write (the sha256 in the basis is that of the content shown). It changes nothing and takes neither--plannor--approved.qargi project adoptlists the repository's own agent instructions (CLAUDE.md,AGENTS.md) and what it does about each, in the plan (instructionsin the plan JSON): it edits neither; with aCLAUDE.mdand noAGENTS.mdit writes anAGENTS.mdthat holds the Development environment section only, and says that the@AGENTS.mdimport line inCLAUDE.mdis for the owner to add.- The gitflow scaffold has the parameters
unreleased_headingandchangelog_entry. - The answer
methodology_accepted, a list of{gap, reason, date}(the gap written as attach prints it, for examplefile: CONTRIBUTING.md), records the deviations from the methodology the lead accepts. The lead sets it withqargi project set; it is validated, andproject setrefuses it in a member run.project attach,project adoptandmarshal handoff(a== methodology deviationssection, anddeviationsin--json) print the accepted deviations apart from the open gaps; adopt writes no accepted file and creates no accepted branch; an accepted deviation whose gap is gone reads "accepted, no longer applies". (The key is top-level becausemethodologyis a plain string.) qargi release plan VERSION [--hotfix] [--date D] [--keep] [--allow-empty-notes] [--again]: one plan and one approval for the whole release cut. It prints every step with what moves (start, push when the repository has an origin, finish, site whensite.enabled) and ONE plan token (publish is not in the cut: it has its own plan); the start step is planned byrelease startitself, so its refusals come before a token does.release plan VERSION --plan TOKEN --approved "<words>"records the approval once (the words, the plan, the date, an audit linerelease:cut) inSTATE/release/PROJECT-VERSION.cut.json, lead only. Thenrelease start,push,finishandsiteeach take--plan TOKENalone: the words are the record's and are on that step's audit line, every step records that it ran (planagain andrelease statusshow the steps and the next command, so a cut resumes after a refusal or a death), and each step runs only while what the plan fixed holds:startfinds its own plan as it was frozen,pushthe branch wherestartleft it,finishthe branch wherepushleft it (the head the checks ran on) with master, the second target and--keepas planned,sitethe tag on the commitfinishtagged.release publishrefuses the cut's token (run it with its own plan and approval): the notes and the release files it creates, whose hashes exist only after the tag, are shown by its own plan. A step under the cut is the lead's as approving it is, and is refused in a member run and whenQARGI_NESTEDis set; a--planthat is not the recorded cut's token, without--approved, is refused naming that a cut is recorded (it does not print a plan of its own). A step that no longer matches is refused before anything moves and is run with its own plan and approval, which the cut records; the single verbs and their own tokens work as before.finishstays the push and tag gate: the approval words are the owner's, and--ignore-checks WORDSat the call is recorded as before. Theqargi-flowskill,docs/flow/git-flow.md, the help and the manual page describe it.qargi project unset NAME PATHremoves one answer of a project. It refuses whatproject setrefuses (the marshal backend;models.extensionsandmodels.accept_no_barrier, also inside a wholemodelsobject, with exit 2 and the verb that takes a plan token), refuses a key that is not set, refuses the removal of an answer the project needs (validate runs first and the file is not touched), and is refused in a member run (the run directory or the member mark in the environment) with exit 2.- The answers
backlog.section(a heading of the backlog file: only the list under it, up to the next heading of the same or a higher level, holds items) andbacklog.archive(a directory that receivesNAME-archive-YYYY-MM.md, or a path ending in.mdfor one archive file; by default the archive is beside the file) shapeqargi backlog listanddone. A heading the file lacks is an error.lib/backlog.pytakes them as--sectionand--archive. qargi backlog add ID "text" [--detail "..."]appends the item- [ID] text(the detail as indented lines) at the end of the list, of thebacklog.sectionlist when that is set, with an audit line. It refuses an id that is not a slug and an id already used anywhere in the file, and then leaves the file alone.qargi backlog done ID [ID...] --evidence TEXTtakes several ids as one batch: one plan, one token, the same evidence archived with each id, applied together (the archive is written once, then the backlog); a missing, ambiguous or repeated id refuses the whole batch before a plan is printed. Both are the lead's, asdonewas: the guard refuses every backlog verb butlist.- The answer
review.reject_trailers, a list of trailer keys (letters, digits and hyphens,*for any run of them) refused besides the built-inCo-Authored-ByandGenerated-withunderreview.trailer_policyreject;project validaterefuses it while the policy is notreject, and a member's guard refuses setting it. The scrub verdict for a rejected trailer names the commit, the line of its message and what matched (review.trailer_policy is rejectorreview.reject_trailers names KEY), and an entry that is no trailer key fails the scrub. The scrub reads the list from the run's copy of the answers in the lead's registry, as it doesreview.scrub. qargi doctorhas aproject-envcheck:last env check <verdict> at <time>from the recordqargi env checkkeeps instate/env/PROJECT.json(a warning when the verdict is notENV OK), orunknown never runwhen none was recorded. It only reads the record and never runs the project's code; the help ofdoctorand ofenv checkname each other.qargi memory init repo --worktree DIR(andmemory movewith the repo tier at one end) writes the repo tier into DIR, another worktree of the project's repository, at the same path inside it, instead of the main checkout. DIR must be the top of a work tree of that repository; the option is refused for any other tier and for a move that has no repo tier. The plan (and its token) are of that worktree.- Tab completion of
qargi project setoffers the answersci.not_gates,methodology_accepted,review.reject_trailers,backlog.archiveandbacklog.section. qargi doctorhas atoolscheck:flock(the locks of the finishing verbs and the ref journal) andtimeout(the bound on a question put to a remote) must be on PATH, and a missing one warns, naming what it is for and the package that has it.flockandtimeoutare in the MakefileTOOLSlist (make doctorhas a line for each), in the bootstrap lines (util-linux,coreutils) and in the toolsAGENTS.mdlists.- The answer
release.hotfix_preflight, a command line or a command as a list of words ({version}replaced,QARGI_RELEASE_VERSIONset):qargi release pushruns it for a hotfix, in a copy of the branch before anything is pushed, instead ofrelease.preflight, for a project whose stored preflight needs content only the fix has or does not suit a hotfix. The plans ofrelease start --hotfixandrelease pushname the answer that runs. A release keeps `release.p...
qargi 0.2.9
Added
qargi local prepare --task-file FILE --done COMMAND [--scope PATH]...writes the whole task block toRUN/task.mdfor 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, oneSCOPE:line (the paths comma separated, ornone), the run's paths, and SUBMODULES and MEMORY when there are any; prepare printsTASK_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 startsSCOPE:; a DONE that is plainly not a command line (empty, several lines, or textbash -ncannot parse; for a simple command with no operator, a first word, afterNAME=valuewords, 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--scopepath 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 lineT=$(mktemp -d -p /dev/shm); mkdir $T/h; HOME=$T/h bash tests/run.shthrough); a missing--done; and a--scopethat is absolute, empty or holds a space, a comma or... Without the flags prepare is unchanged.tests/test_taskblock.shcompares the block of a build run and of a review run withtests/golden/taskblock-build.mdandtaskblock-review.mdqargi review checkfails a patch series that touches a path outside SCOPE: theSCOPE:line of the run's task block (RUN/task.md, which a member cannot write) when it records one, else the manifest'sscope[]; a rename's new name and the manifest'stouched_paths[]count too. The contract tells a member to comparegit diff --name-only BASE_SHAwith SCOPE before each commit, to report BLOCKED naming the path when DONE needs a change outside it, and to put anything else undernoticed_not_acted_on; the "Needs from the lead" instruction is gone.contract/output-format.mdnames the manifest's required keys (status, base, done, gates, commits, scope, touched_paths) andcontract/manifest.example.jsonshows a complete manifest. The help ofqargi review check, the manual page and the review role describe the rule as it is: a report-only run needs itsmanifest.jsonand areport.mdwhose last non-empty line isverdict: acceptorverdict: revise, a patch outside SCOPE fails, and commits dated within two minutes of each other warnqargi review save RUN [--force]: stores the final message of a read-only run (standard input) asRUN/out/report.md, and writes a minimalmanifest.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 existingreport.md(without--force);qargi review check RUN/outthen decides whether the report has the required form. The verb is the lead's: the guard refuses it from a member, as it refusesreview 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.mdsays in a sentence what each of its links is for.
Changed
qargi __guard, the hook command a member runs before every tool call, loadslib/guard.pyas 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 harmlesslscall takes a median of 48 ms instead of 105 ms, aWritecall 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 --usercompiles the tool'slib/as you (compileall), andqargi upgradecompiles the root-owned checkout throughQARGI_SUDObefore it relinks; nothing is written outside the tool's own__pycache__directories. A tool you cannot write to and that nobody compiled (a firstsudo git cloneinstall) works as before, only slower:install.sh --usersays so and prints the one command that compiles it.tests/startup-budget.txthas a ceiling forqargi __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 therole listceiling, which only warns, it fails: a line markedenforcedfails 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) readsinstall.jsonagain 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
buildandwriterole texts point at the reference store only when the task block has aMEMORYsection or a store is mounted (a local member has none;qargi role promptprints 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-flowskill is a run: write the combined diff to a scratch file, prepare a review run withqargi 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 preparerefuses it), take it in withqargi review check OUTand close it withqargi 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.mdandmanifest.json:# Findings(each finding with severityhigh,mediumorlow,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 lineverdict: acceptorverdict: revise. The output format, the review role, thereview-findingspack and the review role seed's findings format and workflow use these words;CONFIRMED,PLAUSIBLEand "not ready, ready with changes, ready" are gone.qargi review checknow fails a report-only run without itsmanifest.json(PREFIXmanifest.jsonbesidePREFIXreport.md) or whose last non-empty line is not the verdict line, and warns when there is no# Checkedsection - The member contract says to record each check in
log.txtwhen 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 isnot runwith the reason, neverpass; 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 checkraises 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 addto see how BASE behaves, togit restorean experiment, and that a one-task member has nothing to set aside; thegitflow-memberandqargi-contributingpacks 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, thegitflow-memberandqargi-contributingpacks, the Claude Code addendum and itsmember-outnote;qargi-verifytells the lead to store such a report withqargi review save - The
qargi-taskskill has the lead write the task to a file and callqargi local prepare ... --task-file F --done CMD [--scope PATH]..., then hand the member the path ofRUN/task.md("read it and follow it", withCLONE,OUT,GITandBASE_SHAwhen the runtime needs them); the list of sections to assemble by hand and the step that pastesprepare's output into the block are gone.qargi-verifycompares the manifest'sdone.commandand the DONE check it re-runs with the DONE ofRUN/task.md, andreview checkapplies its SCOPE make bootstrapinstalls andmake doctorchecksssh-keygen(OpenSSH:opensshon Arch,openssh-clienton Debian and Ubuntu,openssh-clientson Fedora), which the marshal's signed records and the test suite need;AGENTS.mdlists it- A plan token is checked by default.
need_plan(lib/lib.sh) no longer readsQARGI_NESTED: it always compares the token, so a verb that forgot to clear the variable can no longer skip it.need_plan_nestedis the one variant that honours the variable, andqargi canon apply(which provisioning runs ne...
qargi 0.2.8
Fixed
qargi local clean RUNalso removes the saved scrub record of the run (scrub_ok/RUN-IDin the state, written byqargi review check), so a run that was checked and never applied leaves no record behind.qargi release steptells a failed write of the step record in plain words instead of printing rawjqtext: when another command marked the step first, it says so and to run again with--again; otherwise it says the record cannot be written and to check that it and its directory can be written and that the record is valid JSON (commands that write the record wait for each other, so another command is not named as the cause). The exit status is unchanged.
Changed
qargi project attachandqargi project showname the declaredgateswhere they showed only the test command: attach lists them (tier, name, command, feature tier first) after thetest command:line of a stored project and says they replace it;project showkeeps the answers as JSON on standard output and prints the same list on standard error. Without gates nothing changes.qargi marshal handofflists the declared gates in a== gatessection after the features (tier, name, command; they replacetest_command), and--jsoncarries them asgates(an empty list without gates). Without gates the note is as before.qargi helpand the manual page describe this output, that ofproject showandproject attachabove, and the--param gates=JSONofproject new.- The gitflow scaffold (
qargi methodology files, and the filesqargi project adoptwrites) renders the declared gates where it rendered only{{test_command}}:README.md,AGENTS.md,CLAUDE.mdandCONTRIBUTING.mdlist them (name, tier, command) and.github/workflows/test.ymlhas one job for each gate, named after it (the name is a YAML single-quoted scalar, so the scan reads any name back; a feature-tier gate's job runs on every pull request, a release-tier gate's job only on a pull request from arelease/orhotfix/branch or when started by hand; the jobs run in parallel whileverifyruns the gates in order, andCONTRIBUTING.mdsays so), so the CI scan ofrelease verifyfinds every job matched. The markdown files write a name and a command as inline code in a fence of backticks longer than any run in them ({{gate_name_code}},{{gate_command_code}}), so a backtick in a gate does not end the span. The new optional methodology parametergates(one line of JSON) carries them; a template marks its parts with{{#gates}},{{^gates}}and a{{#gate}}loop, each alone on its line.qargi project newwith--urland--param gates=JSONstores the same gates in the project's answers, so the files,feature verifyand the CI scan agree (without--urlnothing is stored, as for every answer). A gate name or command, and any methodology parameter, may not hold a character that ends a line (U+2028, U+2029, U+0085, a vertical tab and the like, besides a newline), which would break the YAML and the markdown list. Without gates the files are byte for byte what they were. - The
qargi-flowskill and thegitflow-memberpack say that a project may declare gates, thatfeature verifyandrelease verifyrun them in order and stop at the first failure, and that a member runs only the checks of its own change (the pack's line about the checks of the touched paths calls them the PROFILE checks, so the word gates means only the declared ones). Their word ceilings intests/skill-budget.txtare raised by 35 and 22 words, with the reason on the line. - Every plan-token check except canon apply's now ignores
QARGI_NESTED: the checks of branch prune, credential setup lead-push, the marshal owner verbs, project new, attach and adopt, provider accept, provision apply and the cloud instances verbs (agent update, normalize, refresh-prompt) are writtenQARGI_NESTED='' need_plan, as those of release, local, feature, upstream and backlog already were, so a nested environment can no longer apply a plan without its token. The verbs that clear the variable themselves (memory, tier writes, packs, project link) are unchanged, and canon apply still honours it because provisioning runs it that way. A new test,tests/test_nested_plan.sh, covers one verb of each file and checks statically that everyneed_plancall inlib/is prefixed or on a short list with its reason (canon apply, and the verbs that unset the variable first).
qargi 0.2.7
Added
qargi doctor [PROJECT] [--json] [--strict]: a read-only check of the host, first slice. One line per check,ok|warn|unknown name: detail, and under a warning the lines that say
what you can do (nothing is run for you). The exit status is 0 whatever it finds, or 1 with--strictwhen a check warns. Four checks:remove-ipcwarns when logind has
RemoveIPCon (read withbusctl,QARGI_BUSCTL) and the user has no linger (loginctl,QARGI_LOGINCTL), because logind then removes what lives in/dev/shmwhen the last
session ends, and prints the drop-in/etc/systemd/logind.conf.d/remove-ipc.conforloginctl enable-linger;guardwarns whenqargi __guardis not onPATHor does not
answer as the guard (and when a member of the project is missing, stale or edited);shmwarns on less than 10% free under/dev/shm(QARGI_RUN_TMP) or on an orphanedqargi-run.*
directory;window-sourcenames where the context-window table of the project's runtime comes from and warns on a model id of the project's roles that is not in it. Without
systemd or a tool a check saysunknown. A member may run it. The orphan listing ofqargi local clean --orphansmoved tolib/orphans.shso both read the same code;clean --orphansprints what it printed.qargi backlog: a generic backlog kept in a markdown file that the new project answerbacklog.filenames (a path,~/..., or relative to the repository; it may be outside the
repository, a memory store for instance;project setandproject validatecheck it). An item is a top-level list line- [ID] text(ID a lowercase slug of two characters or
more) with its indented lines; any other top-level list line is listed as unnumbered.qargi backlog list [--json]shows the open items and flags a duplicated id.
qargi backlog done ID --evidence TEXTprints a plan with a token, and with--planand--approvedappends the item, the date and the evidence toNAME-archive-YYYY-MM.md
beside the backlog file (created when absent) and removes it from the backlog, with an audit log line. The plans offeature finishandrelease finishadd the line
backlog items named in these commits: ID, ...for the open items whose id the merged commit messages name (information only, outside the token; nothing when the answer is
unset or the file is missing). A member's guard refusesbacklog doneandproject setofbacklog.file;backlog listonly reads.- Declared gates: the project answer
gatesis an ordered list of{name, command, tier}(commanda command line or a list of words, astest_command;tierfeatureor
release; names unique and non-empty).qargi project setandproject validatecheck it and name the entry that is wrong.qargi feature verifythen runs the gates of tier
featurein order and stops at the first failure;qargi release verifyruns the feature gates, then the release gates. Declared gates replacetest_commandand
release.test_command(a note says when one of them is not the command of a gate); the verdict line is stillfull suite: pass|fail at SHA, sofeature finishand the
release steps work as before. The run record lists each gate with its result (pass,fail,not run),feature statusandrelease statusshow agates:line, and a failed
run names the failing gate. Without the answer nothing changes. A member'sproject setofgatesis refused by the guard, as it is fortest_command. qargi release verifyreads.github/workflows/*.ymland*.yamlof the tree it verifies (standard library only, no YAML dependency) and warns on standard error, naming each
CI job whose name and id match no declared gate name; a job whose name cannot be read (an expression, a block scalar, a flow mapping) is a note and never a failure.
release verify --strictrefuses, before anything runs, on any such job. With no workflow it prints nothing new; with workflows and no gate declared every job is named.- A test (
tests/test_release_unrelated.sh) covers the fallback line ofrelease finish --hotfixwhen the hotfix branch has no merge base with develop but develop and master are
related (master merges two histories): finish refuses with a line about the branch and develop only, without the advice for an unrelated develop and master.
Changed
docs/flow/git-flow.mdsays thatqargi review applyruns the take-in scrub too, unless a passingreview checkrecorded the same series (then it prints
scrub: skipped (review check passed it for this series)), and namesqargi local clean --orphansbeside the otherlocal cleanforms.
Fixed
QARGI_NESTEDno longer skips the plan token ofqargi feature finish,qargi feature mergeinto a detached branch andqargi upstream sync: as for the release verbs and
local clean --orphans, a caller that is itself approved has not approved this plan, so the token is always needed. Tests cover all three.qargi backlog doneneeds its plan token underQARGI_NESTEDtoo, as the verbs above: with the variable set it archived and removed the item with no--planor with a wrong
one. Tests cover both and check that the backlog and the archive stay as they were.qargi marshal handoffjudges the whole-suite record of a project with declared gates by the gates: the record's command is thengates: NAME=COMMAND; ..., which the note compared
withtest_command, so every feature showed as "another test command" even right after a passingfeature verifyat the tip. The identity is now built in one place,
lib/gates.py(gates.py identity ANSWERS TIER..., whichfeature verifyand the note both use), and the help ofmarshal handoffsays what makes a run apply. Tests cover a
gated project with a run at the tip and with a changed gate.qargi doctorchanges nothing on the host, as it says. It ranlib/orphans.shandqargi agent list, and loadinglib/lib.shmade the config and state directories, set them
to mode 700 (and~/.qargi), and createdinstances.json,sessions.jsonandagents.json: a host that had never run qargi got them from its first doctor.lib/lib.shnow skips
that setup whenDA_NOSETUPis set; the orphan scan sets it when run as a program, and doctor sets it foragent listand skips the member check whenagents.json
does not exist (see the next entry). The test starts with the data directories absent, and snapshots them,HOMEand the directories of a host that has them (types, modes, sizes, times) before and
after.qargi doctor PROJECTon a host with no members file saysok ... no member is recorded on this host, so there is none to be stalein its guard check. It saidunknown ... the members of project PROJECT could not be read (qargi agent list), which read like a failure;unknownstays for aqargi agent listthat fails.- The gate results of a whole-suite run are kept per run, beside its log (
STATE/suite-logs/PROJECT.feature-NAME.gates.jsonl,PROJECT.release-VERSION.gates.jsonl), and not in one
file per project: two verifies at once truncated and appended to the same file, so a record could list a gate twice or the gates of the other run. The verdict line was never
affected. A test runs two verifies at once and checks each record. Two verifies of the same feature still share a log and a results file (the warning about other runs stays). - The member guard no longer refuses a script written with a heredoc because of what its text mentions. A body that
cat,tee,greporgit commitreceives is
now data in a line with other commands too (cat > run.sh <<'E' ... Ethenchmod +x run.sh), as it already was when every command of the line was one of those: a body
withproject set $Xin a sentence was refused as theprojectverb with an expansion. The other commands of the line must only look at files, make, move or remove
them (chmod,mkdir,ls,mv,cp,rm...): a shell,make,source, a script, an alias or anexportin the line keeps the plain reading, since the file could be
run there. The lines of such a body that start a lead-only verb (qargi ..., also behindsudo,env, a shell, a keyword, a redirection or a continuation, the word
project,QARGI_LEAD,QARGI_NESTED,QARGI_MEMBER_RUN,marshal_,$'..') stay refused, and so do the lines an unquoted delimiter lets the shell expand. A body a
shell reads (bash <<E,source /dev/stdin) or a pipe carries on (cat <<E | sh) is read whole. Tests cover the data, the nearest real violations and the linear time. - The member guard no longer reads a parameter expansion with a subscript or an operator as a glob on the signing key:
${PIPESTATUS[0]},${arr[@]+...},
"${files[@]}",${!a[@]}and${#a[@]}(so a loop over an array next to the wordmarshal, which was refused as a read ofcredentials/marshal_*) are allowed.
A name that starts withmarshal_, a glob in a default word (${X:-*}) and a recursive reader still are refused. - The member guard reads the elements of an array literal as it reads the words of
for x in WORDS: a glob insideNAME=(...),NAME+=(...)or
declare|typeset|local|readonly [-aA] NAME=(...)(also[k]=entries, and a relative glob after acdinto the credentials directory) that reaches
credentials/marshal_*is refused when the array literal names the credentials directory, in plain words or through a variable the line sets (the entry below). Trimming
the subscript off${a[@]}(the entry above) had let that through. A word with a variable after the path of the credentials directory (credentials/${X:-ma}rshal_ed25519,
credentials/m${X}shal_ed25519,cd credentials; cat $X) is refused too, as the name the variable builds could be the key; the entry below widens this to a glob in the path. - The member guard reads the signing key through a name it did n...
qargi 0.2.6
Added
- A test (
tests/test_release_snapshot_cycle.sh) runs a whole release through plan tokens on a repository whose master is an unrelated snapshot commit, with a local bare
origin that refuses non-fast-forward pushes:release start,push(which joins the history of master) andfinishmerge and tag master, merge develop, push
by fast-forward only, and leave every earlier commit reachable. It found no bug. qargi local clean --orphans [--days N]lists theqargi-run.XXXXXXdirectories under/dev/shm(orQARGI_RUN_TMP) that no run of the lead's registry names, each
with its age and whether it is named (a run's own directory, live or gone, is named; a link and a file are named and left). It prints a plan and a token and removes
nothing;--plan TOKEN --approved WORDSremoves only the unnamed directories older than N days (default 2, at least 1) with nothing newer inside, each checked again
just before it goes, and logs the removal. The existing--stale DAYS --scratch DIR(finished runs) is unchanged.
Changed
qargi project adoptnames the next step when two long-lived branches share no history (a master made as a snapshot, say), not only the problem: the first release
joins them (qargi release start VERSION, thenrelease push VERSION, whose plan joins the history of the first branch into the release branch, thenrelease finish VERSION), or one merge by hand (git switch develop && git merge -s ours --allow-unrelated-histories master, which keeps develop's tree). Adopt still changes no branch.qargi release start --hotfix(its plan, andrelease status) and the refusal ofrelease finish --hotfixsay what to do when develop shares no history with master
and no release is open: the hotfix could not be merged into develop. The line names the way out, the next release (qargi release start VERSION,pushand
finish: the push joins the history of master) or one merge on develop by hand (git switch develop && git merge -s ours --allow-unrelated-histories master), then
qargi release finish VERSION --hotfix. It was a refusal atfinishthat only said to join by hand.qargi review applyruns the declared take-in scrub (the answersreview.scrubandreview.trailer_policy) before it makes anything, asreview checkdoes, so a lead
cannot apply a series the scrub refuses: a failing scrub printsscrub: FAIL ..., refuses the apply and changes nothing. It skips the scrub, with the one line
scrub: skipped (review check passed it for this series), when a passingreview checkrecorded the same series:review checkwrites, in the lead's state directory
(scrub_ok/RUN-ID, not in the member's run directory), a hash of the patches in order and of the declared scrub, and apply compares it; a series (or a scrub answer)
changed since the check is scrubbed again, and a check that failed leaves no record.--from-savedfollows the same rule. A member's apply does not run the scrub
(scrub: not run), as its check does not.qargi local cleanandqargi local close --applied-by-am, when they refuse a run whose applied branch the lead rewrote by hand (the branch is not at the tip that
review applyjournaled, the two trees are the same and no rewrite is recorded), also name the fix with the real branch and tips:git branch -f BRANCH OLD, then
qargi ref record --rewrite BRANCH OLD NEW --why .... The other refusals keep their text.- Where the runtime refuses a member's write tool for a report file, OUT never gets the report. The contract and the runtime's addendum now say to write OUT files
with the shell (a heredoc) then, the runtime's notes have amember-outtopic, andqargi local verify(an uncollected run, or a read-only run whose OUT has no
report, as a note) andqargi local clean(an OUT with no manifest and no report) name that cause. A test shows the guard lets a build member write OUT files with
a heredoc.
Fixed
- The session-start hook's read of the open features (
qargi marshal status --all --brief --no-fetch, 5 s) takesQARGI_HOOK_MARSHAL_SECS, a whole number of seconds from 1
to 9999 (anything else is ignored; the default stays 5 and the hook stays bounded).tests/test_marshal_refs.shfailed once in a parallel suite run because that call,
which takes 1.3 s alone on its hostile-records fixture, took 4 to 6 s under load: the read was cut off, the marshal block was empty, and with no tier files so was the
context. The test now gives the hook 60 s and, when the hook's output is wrong, prints how long the status call took. - The guard refuses input it cannot decode. A hook input that is not valid UTF-8 was taken for "not JSON" and let through; it is now refused (exit 2, one line on
stderr). Input that decodes but is not JSON is still no call to judge and is allowed, as before. qargi lead grantandqargi lead revokeread each step's plan text and token from the step's--jsonoutput (theplanandtokenfields) and no longer parse the token out of text with sed. The steps run throughbin/qargi; what is shown and applied is as before.qargi project setadds "(applies to the next provision ...)" only for the answers a provision reads (the list isprovision_readsinlib/project.sh: repo.url and repo.long_lived, teams, roles, profiles, budget_cents, memory stores, models.provider, lead model, effort and push, packs, cloud). Every other answer (runs.owned_refs,lead.context_window,release.*,review.*,deployment,test_command, ...) prints only what was set.deploymentno longer keeps the note for a value that names a cloud deployment: no provision reads it.qargi release start VERSION [--hotfix]is behind a plan token likerelease push,finish,fix,publishandstep. Without--approvedit changes nothing:
it prints the plan (the branch, the base it is cut from and that base's head, the one commit that bumps the version, the files the commit changes, the date)
and a token.--plan TOKEN --approved "..."creates the branch and writes therelease:startline of the audit log, whichstartnever wrote. The checks
(the suite verdict, the preflight, the changelog and man page) run for the plan too, so a refusal comes before a token does; the token covers the head of
the base and the date. The apply line it prints repeats--dateand--allow-empty-notes. The tests, the skill, the manual page and the docs that start a release pass a plan.- Two
qargi release stepcalls at the same moment (or a step andrelease finish) no longer lose a step: the after-steps record was written through one
fixed temporary file with no lock, so the slower call overwrote the other. The record is now read, changed and replaced under a lock (the one the registry of
local runs uses) through a temporary file of its own name; a step another call marked done in the meantime is refused unless--again. - The guard protects
scrub_ok/of the state directory likefeatures/,runs/andhandoff/: a write, redirect, heredoc,cp,mv,teeorlninto it is refused,
reading stays allowed. The recordreview checkkeeps there (the hash of a series that passed its declared scrub, whichreview applytakes as the scrub done) was
writable by a member running as the same user, who could compute the hash and make the apply skip the scrub. qargi review applycopiesOUT/patches/*.patchonce into a directory of its own in the lead's state directory and hashes, scrubs and applies (git am) that copy.
It read the patches three times from the run directory, so a member process still alive could change a patch after it was scrubbed and before it was applied. A
patch that stops being a regular file while it is copied refuses the apply; the copy is removed however the verb ends.qargi release start|push|finish|publish|site ... --approved WORDSandqargi local clean --orphans --approved WORDSneed the plan token even whenQARGI_NESTEDis
set.need_planreturns without a check when that variable is set, sorelease start 1.2.3 --approved xcreated the branch, andlocal clean --orphans --days 2 --approved xremoved the directories it listed, with no token. A caller that is itself approved has not approved this plan; the packs, memory and tier-write verbs
already refused to honour it. The lead-only verbs (assets,sign,site) still refuse a nested call as before.