diff --git a/packages/agent-data/DECISIONS.md b/packages/agent-data/DECISIONS.md index d367e4661..01f40b214 100644 --- a/packages/agent-data/DECISIONS.md +++ b/packages/agent-data/DECISIONS.md @@ -1,11 +1,15 @@ Non-obvious decisions only, grouped by business-logic flow. Anything not listed is left to the implementer's judgment. Flag conflicts instead of silently deviating. +A bullet is a person's pick, and says what it was picked over. What the code does belongs +in SPEC.md; a choice made while implementing is the implementer's judgment, not a +decision. An AI proposes a bullet and asks; it never adds or rewrites one. + ## The package -- A **library, not a skill**: no `SKILL.md`, no command. Skills import it; they never import - each other. -- Named for the `agent-data` branch, but no function hardcodes it: every one that touches a - branch takes it as an argument. +- A **library, not a skill**: no `SKILL.md`, no command. Skills import it; they never + import each other. +- Named for the `agent-data` branch, but no function hardcodes it: every one that touches + a branch takes it as an argument. - `.branches/` holds a project's persistent checkouts, one per branch: the agents' own (made by other packages) and the data branch's (made here). The directory name is exported. Dotted so a `*` glob skips it: each checkout is a full copy, so a tool that @@ -13,36 +17,27 @@ to the implementer's judgment. Flag conflicts instead of silently deviating. - Hidden through the common git dir's `info/exclude`, never a committed `.gitignore`: the library must not touch tracked files; a per-worktree `info/exclude` is never read, and one line there, written once, covers every checkout. Best-effort: the checkout stands - even when the rule could not be written. Prune before adding: a hand-deleted checkout - leaves a registration that fails the add. -- Every git call has a time budget: a listed read 10s, network and `worktree add` 120s, - other writes 30s. `worktree` goes by its second word (`add` slow, `list` a read, the rest - a write), `branch` by its flags (bare or a listing flag reads; `-D`, `-m` or a new name - writes); an unlisted subcommand gets the write budget, so a write is never cut short at - the read budget. A killed `push` may have half landed, so a git call that outruns its - budget fails as a timeout, its own error kind, never as a plain failure. git's output - buffer is 16 MB (a large checkout's file listing); an overrun is not a timeout. + even when the rule could not be written. +- Every git call has a time budget; a call that outruns it fails as a timeout, its own + error kind, never as a plain failure: a killed `push` may have half landed. ## The branch - A branch of the project's repository holds the agents' data (tickets, the queue) the way - `gh-pages` holds a site; code branches hold only code. Every write that changes something - pushes, and a pull is a cycle of its own, so a machine that writes nothing still gets what - the others pushed. + `gh-pages` holds a site; code branches hold only code. Every write that changes + something pushes, and a pull is a cycle of its own, so a machine that writes nothing + still gets what the others pushed. - One branch for all skills, each with its own folder or file. Not one per skill: every extra branch needs its own checkout and its own sync failure to report. -- Missing locally, it is adopted from origin's copy; missing there too, it is born an orphan - branch, so no code commit is ever in its history. +- Missing locally, it is adopted from origin's copy; missing there too, it is born an + orphan branch, so no code commit is ever in its history. - The name is written once, in `names`, as `DATA_BRANCH`, and imported everywhere else. `names` is its own entry point with no node imports, so browser code can import it. - A read works from anywhere in the repository, an agent's worktree included: the checkout - is looked for beside the real `.git`. A file is looked for in that checkout when there is - one, then on the local branch, then on origin's copy; a directory listing always comes off - a ref. A read can ask for a fresh copy: fetch, then origin's ref instead of the checkout, - for a reader whose checkout may trail. A one-shot command opens the branch once: one fetch - when there is an origin, every read off origin's copy (the local branch when there is no - `origin/`), because its own writes go straight to the remote and never move the - local branch. A read never fails: a missing file, a missing branch and a git that could - not run all read as absent. + is looked for beside the real `.git`. A one-shot command opens the branch once: one + fetch when there is an origin, every read off origin's copy (the local branch when there + is no `origin/`), because its own writes go straight to the remote and never + move the local branch. A read never fails: a missing file, a missing branch and a git + that could not run all read as absent. ## Flow: a write Fetch what others pushed → make the change → commit → push. @@ -57,20 +52,18 @@ Fetch what others pushed → make the change → commit → push. resets it, new files included. Both try the push twice; the command's write then throws with nothing left to retry, the process's never throws: its callers are background ticks. -- A write is a re-runnable function, not a finished commit: a lost race winds the attempt's - commit back and runs the function again on the new files, so the change lands once. The - message is the caller's: fixed, or a function run after the change, since a batch only - knows what it did once done; the library's own are `create the branch` for the - birth and `sync` for the pull. Never a force push. After two failed pushes the process's - write reports the failure and the commit stays local in its checkout; the next write or - pull rebases it onto the remote and pushes it with the new one. When that rebase conflicts - the checkout is reset to origin's tip: the remote wins, every unpushed commit is dropped - unreported, only the current change runs again. -- An op is handed a directory and writes into it. `BranchFileFs` is the file seam an op can - take instead of the disk, for tests (type and node implementation ship here, the op - injects it); it creates parent directories: git keeps no empty directory, so a skill's - folder vanishes with its last file and is absent on a new branch. +- A write is a re-runnable function, not a finished commit: a lost race winds the + attempt's commit back and runs the function again on the new files, so the change lands + once. The message is the caller's: fixed, or a function run after the change, since a + batch only knows what it did once done. Never a force push. After two failed pushes the + process's write reports the failure and the commit stays local in its checkout; the next + write or pull rebases it onto the remote and pushes it with the new one. When that + rebase conflicts the checkout is reset to origin's tip: the remote wins, every unpushed + commit is dropped unreported, only the current change runs again. +- An op is handed a directory and writes into it. Parent directories are created: git + keeps no empty directory, so a skill's folder vanishes with its last file and is absent + on a new branch. - The remote is always `origin`; a repository without one is remote-less whatever other - remotes it has. Then the process's write commits locally and reports no error, a command's - write refuses (an outcome, not a throw), and the pull reports an error: nothing to - converge with. + remotes it has. Then the process's write commits locally and reports no error, a + command's write refuses (an outcome, not a throw), and the pull reports an error: + nothing to converge with. diff --git a/packages/skill-branches/DECISIONS.md b/packages/skill-branches/DECISIONS.md index 3ed3684a0..1925933b7 100644 --- a/packages/skill-branches/DECISIONS.md +++ b/packages/skill-branches/DECISIONS.md @@ -1,6 +1,10 @@ Non-obvious decisions only, grouped by business-logic flow. Anything not listed is left to the implementer's judgment. Flag conflicts instead of silently deviating. +A bullet is a person's pick, and says what it was picked over. What the code does belongs +in SPEC.md; a choice made while implementing is the implementer's judgment, not a +decision. An AI proposes a bullet and asks; it never adds or rewrites one. + ## The checkout - One checkout per agent, a git worktree of the user's repository under `.branches/`, branched from the project's head unless the caller names a base. Agents run in parallel, @@ -11,33 +15,25 @@ to the implementer's judgment. Flag conflicts instead of silently deviating. `.gitignore` is tracked. - A checkout starts as branch `agent-` in folder `.branches/agent-/`; `` comes from the program that starts the agent, and must match `[A-Za-z0-9_-]+`, so no id can - build a path outside `.branches/`. `npx branches name ` (`[a-z0-9-]+`; the skill - asks for a leading letter or digit: at the command line a leading `-` reads as a flag, a - usage error, and the name check itself does not mind it) renames the branch to - `agent-`: a rename, not a new branch, so nothing is left behind; the folder keeps - the id, since the agent is running inside it. A checkout on no branch is neither renamed - nor reclaimed; `status` answers it without a `branch`. + build a path outside `.branches/`. `npx branches name ` (`[a-z0-9-]+`) renames the + branch to `agent-`: a rename, not a new branch, so nothing is left behind; the + folder keeps the id, since the agent is running inside it. A checkout on no branch is + neither renamed nor reclaimed; `status` answers it without a `branch`. - After a checkout is made, named or removed, each checkout whose branch differs from its folder name gets a sibling link `.branches/` to its folder, relative; a detached checkout or a slashed branch gets none. A link whose target is an `agent-*` name, `agent-data` aside, is the package's to remove, whatever it is called, and whether or - not the target exists; anything else at a link's path is left alone. `list` and `prune` - see directories only, so a link is never a checkout; a session name passes as an id, so - `remove ` follows the link `.branches/agent-` to the checkout. + not the target exists; anything else at a link's path is left alone. - No name the package mints holds a `/`: a folder and a link are named after a branch, and a cloud session (a hosted agent run, started on a branch) cannot start on a slashed ref. The package renames and deletes only `agent-*` branches. - `agent-data` is `@gemstack/agent-data`'s data branch, checked out as `.branches/agent-data` by the program that keeps it, not by this package. Never listed, renamed or deleted; `data` is refused as an id, and an agent naming itself `data` gets - `agent-data-2`; `attach` guards the id only, never the branch it is given. + `agent-data-2`. - A taken name gets `-2`, `-3`, … instead of a refusal: the agent asked for a name and reads back the one it got. Taken means any local or remote-tracking branch, so the later - push cannot land on someone else's branch. The branch the checkout carries right now, - suffix included, is not counted: a checkout already on `agent--2` that asks for - `` again keeps `-2` while `agent-` is still taken, and takes `agent-` - once it is free. Two agents naming the same thing at once race on the rename; the loser - takes the next free suffix, in at most three tries, then `git-failed`. + push cannot land on someone else's branch. - Continuing an agent puts it back on the branch its work is on, even one the package did not make; a branch gone locally comes back from origin's copy, and one gone everywhere is recreated from the project's head: every branch the package deletes held nothing the @@ -45,20 +41,11 @@ to the implementer's judgment. Flag conflicts instead of silently deviating. - The user's installed dependencies are linked into the checkout, not copied or reinstalled: one link per entry of the folder, absolute, so an install in the checkout writes into the checkout (a scope like `@acme` is one entry, so a scoped install still - writes into the user's folder: a known limit). Every dependency folder down to two - levels under the root (not under `node_modules`, `dist`, `build`, `coverage` or a - dot-directory) is linked, so a workspace package's own dependencies are there too; a - tree already in the checkout is left alone. Of the dot-entries only `.bin` is linked, so - the agent runs the project's tools; the others (`.pnpm`, `.modules.yaml`) would tell the - package manager the checkout's tree was installed there, which it was not. The packages - still resolve: a link to a link resolves where the target lives. + writes into the user's folder: a known limit). Of the dot-entries only `.bin` is linked, + so the agent runs the project's tools; the others (`.pnpm`, `.modules.yaml`) would tell + the package manager the checkout's tree was installed there, which it was not. - Everything after the worktree is best-effort: a checkout missing any of it is a worse run, not a failed one. -- `create` or `attach` for an id that already has a checkout fails as `git-failed` with - git's own error, and so does `create` when the branch exists without one: `attach` is - the way then. `create` and `attach` each answer the path and the branch; `list` answers - one row per checkout in directory order: the id, the path, the branch and, asked for, - the size. Only a directory named `agent-` counts as a checkout. ## Flow: reclaim Deleting an agent's checkout to free disk, only after the remote has everything in it. It @@ -69,15 +56,14 @@ push. files included, is kept until a person commits or deletes it, and nothing of it is pushed. - An `agent-*` branch whose tip is reachable from another name's remote-tracking ref, on - any remote, holds nothing of its own (the holds-nothing rule): it goes with its checkout - unpushed, deleted with `-D`: git's own merged test asks the wrong question. Its own - copy, under its current name or its upstream's, does not count. Pushed means on - `origin`, the only remote the package pushes to. Both reads take the local - remote-tracking refs, never a fetch: the push that put a tip there wrote them. + any remote, holds nothing of its own: it goes with its checkout unpushed, deleted with + `-D`: git's own merged test asks the wrong question. Its own copy, under its current + name or its upstream's, does not count. Pushed means on `origin`, the only remote the + package pushes to. Both reads take the local remote-tracking refs, never a fetch: the + push that put a tip there wrote them. - The caller may name a pushed commit through the library, not from the command line: the commit a cloud session pushed on the agent's behalf. A checkout whose tip is an ancestor - of it goes without a push and keeps its branch, even one the holds-nothing rule would - delete. + of it goes without a push and keeps its branch, even one the rule above would delete. - An agent that switched to another branch leaves `agent-` behind; it goes with the checkout once the branch the agent ended on contains it. - A folder under `.branches/` that git no longer knows as a worktree is left alone, and @@ -85,12 +71,9 @@ push. user's own checkout. - A removal git refuses as unclean after the clean check passed is forced, and says so on stderr: an ignored build artifact must not strand a checkout for good. -- `remove` and `prune` push by default; `--no-push` opts out. `remove` of a missing - checkout is a refusal, `no-checkout`; a removal judges the birth branch before anything - goes, then removes the checkout, then the branches, since git will not delete a branch a - worktree has out. `remove` names the branches that went with it, absent when none. - `prune` lists only the ids it removed, and the checkouts it kept, each with its reason, - in its result, nothing on stderr but the forced-removal line, and exits 0. +- `remove` and `prune` push by default; `--no-push` opts out. A removal judges the birth + branch before anything goes, then removes the checkout, then the branches, since git + will not delete a branch a worktree has out. - The package reads no configuration and never asks whether an agent still runs: the caller says whether it may push, and may pass a hook that runs just before the checkout goes, to stop whatever serves the tree; the command line passes no hook. @@ -110,30 +93,15 @@ push. refusal (a rule saying no) adds one line for a person on stderr and exits 1. A malformed command line (an unknown flag, the wrong argument count) never gets that far: the usage on stderr, nothing on stdout, exit 2. An id the charset rejects is a refusal, - `invalid-id`, not a usage error; one starting with `-` reads as a flag, a usage error, - unless the arguments follow `--`. -- A command that throws is reported like a refusal, reason `git-failed`, the error's own - line as `detail` on stdout and on stderr. + `invalid-id`, not a usage error. A command that throws is reported like a refusal, + reason `git-failed`, the error's own line as `detail` on stdout and on stderr. - `create`, `attach`, `list`, `remove` and `prune` act on the project, found from the `.branches/` layout even from inside a checkout; `name` and `status` act on the checkout - the command runs in, found from anywhere inside it. `status` also takes the path of a - checkout root. `status` answers the path, the branch, whether the tree is clean, and - whether the tip is on the remote. -- The keys: `agentId`, `path`, `branch`, `clean`, `onRemote`, `sizeBytes`, `detail`, - `removed`, `skipped` (each with `agentId`, `reason`, and the person's line as `detail`), - `branchesDeleted`. + the command runs in, found from anywhere inside it. - `list` answers with a bare JSON array; every other result and every refusal is an object whose `ok` tells the two apart. - Outside a repository, a command that needs one refuses with `not-a-repo`: only git's own - "not a git repository" reads as that; every other failure stays `git-failed`. An id is - checked before the repository, a session name after it, and `status ` skips the - repository check, so outside one it answers `not-a-worktree`. -- A refusal names its subject: `invalid-id` and `no-checkout` the id, `status`'s - `not-a-worktree` the path, `dirty` and `not-on-remote` the branch, `not-on-remote` also - git's reason when a push was tried; `name`'s refusals, `not-a-repo`, and - `not-a-worktree` and `no-branch` from `remove`, carry the reason alone. The refusals: - `invalid-id`, `invalid-name`, `not-a-worktree`, `no-branch`, `not-an-agent-branch`, - `no-checkout`, `dirty`, `not-on-remote`, `not-a-repo`, `git-failed`. + "not a git repository" reads as that; every other failure stays `git-failed`. - The skill tells the agent where it is: on `agent-*` the checkout is its whole workspace, and the dependency files and skill folders in it are links to the user's copies, never edited; on any other branch under `.branches/` it was continued on that branch on @@ -144,7 +112,6 @@ push. - Each agent tool (Claude Code, Codex) looks for skills in its own folder at the checkout root: `.claude/skills`, `.agents/skills`. In every checkout it makes, the package links its own folder, which holds `SKILL.md`, into both as `branches`, hidden through the - repository's exclude, whose entry also hides an untracked project file at that path. An - entry already there, a committed skill say, is left alone. A caller may name further - skills to link in beside it, each under its own name, not from the command line; - temporary, until the project commits its own skill files. + repository's exclude. An entry already there, a committed skill say, is left alone. A + caller may name further skills to link in beside it, each under its own name, not from + the command line; temporary, until the project commits its own skill files. diff --git a/packages/skill-tickets/DECISIONS.md b/packages/skill-tickets/DECISIONS.md index f8b618783..3c39bf809 100644 --- a/packages/skill-tickets/DECISIONS.md +++ b/packages/skill-tickets/DECISIONS.md @@ -1,64 +1,37 @@ Non-obvious decisions only, grouped by business-logic flow. Anything not listed is left to the implementer's judgment. Flag conflicts instead of silently deviating. +A bullet is a person's pick, and says what it was picked over. What the code does belongs +in SPEC.md; a choice made while implementing is the implementer's judgment, not a +decision. An AI proposes a bullet and asks; it never adds or rewrites one. + ## The tickets - Two callers: the command an agent runs, and a long-lived program that keeps the branch checked out, starts agents through the library, and imports issues with its own code, stamping `meta.json`. The executable is `tickets`. The package ships `SKILL.md`, the - agent's instructions: where the files live, never to write through the root link, - install, then `npx tickets`. It gives the commands and what they answer; claim before - planning or working, release before stopping unless closed, close once the work is - merged; and the ticket, plan and queue formats, which parts of them the code parses, - with the filename convention `_.md`, the plan's optional sections, its - `Outdated:` key and its rubric for rating uncertainty. + agent's instructions. - A ticket is a markdown file in `tickets/`. Its plan and its claim sit beside it: `.plan.md` and `.lock.md`, `` the filename without `.md`. - Tickets live on `agent-data`, the branch `@gemstack/agent-data` names, never on a code - branch. The program's sync runs three steps in order. It seeds an empty `TODO_AGENTS.md` - when the branch has none, so the queue exists before its first entry. It links `tickets` - at the project root to the relative target `.branches/agent-data/tickets`, only when - nothing of that name sits at the root; the target may not exist yet, so the link dangles - until the first ticket lands. Then it converges the checkout with origin. A seed that - cannot commit stops before the link; a link that cannot be made is ignored, and the sync - still returns the pull's result. The link is hidden by two rules in `.git/info/exclude`, - `/tickets` then `!/tickets/`, written on the run that creates the link even when the - symlink fails. `/tickets` hides the link, `!/tickets/` re-admits directories, which a - symlink is not, so the persistent checkout still commits its own `tickets/` under the - same repo-wide exclude. + branch. The program links `tickets` at the project root to the relative target + `.branches/agent-data/tickets`, only when nothing of that name sits at the root; the + target may not exist yet, so the link dangles until the first ticket lands. The link is + hidden by two rules in `.git/info/exclude`, `/tickets` then `!/tickets/`. `/tickets` + hides the link, `!/tickets/` re-admits directories, which a symlink is not, so the + persistent checkout still commits its own `tickets/` under the same repo-wide exclude. - Closing a ticket deletes it, its plan and its claim, and nothing else; a queue entry linking it stays until `queue done`. -- `list` sorts newest first by the filename's leading `yyyy-mm-dd_`, ties by filename - ascending; the row's `date` is that day at `T00:00:00.000Z`, unvalidated. A filename - with no date takes the file's modification time, or, when read from git, the epoch, - which sorts last. - A ticket's row, the same fields in `list` and `show`: the title from its `# ` line, the summary from the first prose line after `## TLDR`, else after the title, scanning past - headings to the end of the file (a `Source:` line is skipped in either scan, matched - case-sensitively unlike every key: a trailer on imported tickets, not a field), `Topics: - [a, b]` split into tags, `GitHub:` into label and url when it is a full markdown link, - `Priority:` verbatim. Plus whether a plan sits beside it, whether a lock does (`locked`, - present only when true), whom the lock names, and the plan's `Effort:` and - `Uncertainty:`, absent unless a whole number 0-10. Keys and headings match in any case, - `## TLDR` as the whole line; a ticket's keys are read only above the `# ` title, a - plan's above its title or, when it has none, anywhere in its first 4000 characters. - `list` reads the first 4000 characters of a ticket, `show` all of it, so the two can - differ on a long ticket. The library also reads a ticket's issue reference, scanning the - whole file for a `GitHub:` line: the number from the link's URL, `/issues/` or - `/pull/`, a bare `#42` as the fallback. The row's keys, `summary` always present, - empty when there is no prose: `file`, `title`, `summary`, `priority`, `topics`, `github` - (`label`, `url`), `date`, `planned`, `locked`, `lockedBy`, `effort`, `uncertainty`; - `show` nests the row as `ticket` with its text as `content`. - + headings to the end of the file, `Priority:` verbatim. ## Flow: a claim - A claim is a committed file holding one line, `CLAIMED: `, so agents on other machines see it. - One claim per ticket; it never expires: it lifts when the ticket is released or closed, otherwise only by hand on the branch. The command lifts only its own lock, and closes - only when the ticket has no lock or its own, `not-holder` otherwise. The program that - started an agent releases what the agent left claimed, naming the holder it expects, or - none to free whoever holds the lock. -- Releasing an unclaimed ticket is a refusal. + only when the ticket has no lock or its own, `not-holder` otherwise. Releasing an + unclaimed ticket is a refusal. - A lock is written only by a claim; someone else's `claim` is refused while it exists. `put` ignores it, so an import can refresh a ticket someone holds. - A claim the program committed but could not push still counts: the commit already guards @@ -73,62 +46,34 @@ to the implementer's judgment. Flag conflicts instead of silently deviating. folder name is not read: the layout is the caller's. - The program says whether a claim is for planning or implementing: a claim for planning is skipped, no lock written, when the ticket already has a plan, unless the lock is - already this holder's: the lock is checked before the plan; a claim for implementing - ignores the plan; only someone else's lock stands in its way. The command always claims - to implement. The program's claim writes the lock without reading the ticket, so a - ticket closed under it gets an orphan lock. + already this holder's; a claim for implementing ignores the plan; only someone else's + lock stands in its way. The command always claims to implement. - The lock's existence is the claim: the holder it names decides who may close or release, - not whether the ticket counts as locked. A lock whose line does not parse still holds - the ticket, and no command lifts it: only the program's release naming no holder, or a - hand edit on the branch. A lock file that cannot be read at all counts as no lock to - `claim`, `close` and `release`, though `list` still shows it locked, since `locked` - comes from the directory listing, not from reading the lock. + not whether the ticket counts as locked. ## The queue - The queue is one markdown file on the branch, `TODO_AGENTS.md`: sections `## Priority - 10` down to `## Priority 0`, any `## Priority N` counts, in any case, N one or two - digits ending at a word boundary; any `-`, `*` or `N.` list item with text is an entry, - wherever it sits. Entries are placed to keep the file sorted high to low; nothing - re-sorts on read. + 10` down to `## Priority 0`, any `## Priority N` counts, in any case; any `-`, `*` or + `N.` list item with text is an entry, wherever it sits. Entries are placed to keep the + file sorted high to low; nothing re-sorts on read. - An entry is plain trimmed text: the task a future agent is started with. `--ticket` - writes the entry as a markdown link to the ticket, the given text as its label, - unescaped. The ticket is read off the fetched branch before the write, for its priority - and to refuse an entry pointing at no ticket; the program reads the link back to claim - the ticket for the agent it starts. + writes the entry as a markdown link to the ticket, the given text as its label; the + program reads the link back to claim the ticket for the agent it starts. - `queue add` creates the queue file when the branch has none. An entry with no priority goes at the end of the file, in whatever section ends it; one linked to a ticket takes the ticket's priority unless `--priority` was given; a ticket whose `Priority:` is missing or unreadable counts as 5, never 10 or 0: those ends (act immediately, only if - capacity) are deliberate picks. A priority the file has no section for gets one, before - the first lower section, or after the last priority section when none is lower; a file - with no priority section gets it above its first `## ` section, so an unranked section - cannot bury a deliberate one; a file with no `## ` section gets it appended. An entry - joins its section at the end, before the blank lines; only a `## ` heading ends a - section. + capacity) are deliberate picks. - Done means deleted, never checked off: a `- [x]` or `- [X]` line is not an open entry; a `- [ ]` line is, printed without its box and deleted whole. ## Flow: the command -- The command reads with `list`, `show` and `queue`, and writes with `put` (a ticket, a - plan, or `meta.json`, all inside `tickets/`; the bytes as given, unparsed), `close`, - `claim`, `release`, `queue add` and `queue done`. -- `show`, `claim` and `close` refuse a missing ticket with `no-ticket`. Every write - refuses `no-remote` without an origin. For `claim` and `close` the refusals come in this - order: `invalid-path`, `not-a-repo`, `no-identity` (a checkout on no branch), - `no-remote`, then `no-ticket`, since the ticket is checked inside the write; `release` - the same without `no-ticket`, never looking at the ticket; `queue add --ticket` checks - the ticket before the write but after the repository, so there `no-ticket` comes before - `no-remote` and `not-a-repo` before `invalid-path`. `release` looks only at the lock, so - an orphan lock naming you lifts. `put` checks only the name, before reading stdin, so a - plan can be written for a ticket that does not exist, and writes whatever stdin gives, - an empty file included. - Every command names a ticket by its bare filename or its `tickets/` path, so a queue entry's link target can be pasted in as is; a sibling's name (`.plan.md`, `.lock.md`) is `invalid-path` to every command, `put` taking `.plan.md` the one exception. - No command reads `meta.json`: only the importing program does, for its one key - `lastImportedAt`; an unparsable file, or a value that is not a date string, reads as no - stamp, and only the first 10000 characters are parsed. + `lastImportedAt`. - A read fetches origin once and reads everything from that copy (the library's queue read fetches only when asked): only origin has every writer's pushes, this command's own included. With no origin the local branch is read: writes are refused there, so nobody @@ -137,28 +82,15 @@ to the implementer's judgment. Flag conflicts instead of silently deviating. also puts one line on stderr and exits 1. A malformed command line (an unknown flag, the wrong argument count, an empty `queue add` text, a `--priority` off the 0-10 scale) is rejected first: the usage on stderr, nothing on stdout, exit 2. A file no command may - touch refuses with `invalid-path`. -- Anything a command throws refuses with `git-failed`. + touch refuses with `invalid-path`. Anything a command throws refuses with `git-failed`. + Outside a repository a command refuses `not-a-repo`; only git's own "not a git + repository" reads as that. - `list` and a bare `queue` answer with a JSON array; every other result and every refusal is an object with `ok`. -- Outside a repository a command refuses `not-a-repo`; only git's own "not a git - repository" reads as that. -- A write's result echoes the file as `tickets/`, a row's `file` is bare; `claim` - and `release` also answer the holder, `queue add` and `queue done` the entry, `queue - add` its priority when placed, `show` the plan and the holder when there are any; a - refusal names the bare file or the entry it was given, `invalid-path` the argument as - typed (`put`'s with the `tickets/` prefix stripped), `git-failed` its detail, and - `no-remote`, `no-identity` and `not-a-repo` name nothing. The refusals: `no-ticket`, - `claimed` and `not-holder` (both with the holder, when readable), `no-lock`, `no-entry`, - `no-identity`, `no-remote`, `invalid-path`, `not-a-repo`, `git-failed`. -- The command's write is one commit per command (`put tickets/`, `close - tickets/`, `claim tickets/`, `release tickets/`, `queue add: `, - `queue done: `), pushed straight to origin through a throwaway worktree at - origin's tip; a push that loses a race is re-applied on the new tip by - `@gemstack/agent-data`. The program's writes go through its persistent checkout's cycle - instead, its seed as `seed the queue`, its batch claim as one commit, `claim - tickets` for the n it locked, or `claim tickets/` when n is one; its `queue done` - of an entry already gone succeeds, changing nothing. +- The command's write is one commit per command, pushed straight to origin through a + throwaway worktree at origin's tip; a push that loses a race is re-applied on the new + tip by `@gemstack/agent-data`. The program's writes go through its persistent checkout's + cycle instead; its `queue done` of an entry already gone succeeds, changing nothing. - `queue done` takes the entry as `queue` printed it, trimmed, removes the first such line, and refuses a line the queue does not have, an empty one included, decided inside the write.