Merge origin/main (934221b) into #3379 for train slot; stamp v1.91.196.0

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-25 05:31:32 +00:00
commit 4e9f52f29b
76 files changed
+2418 -297

No files matched your search

+3 -1
View File
@@ -152,7 +152,9 @@ gstack itself uses this: `/ship` Step 14.5 dispatches the `/document-release`
subagent with this prefix before final commit, verification and publication.
Its ship-owned scope overrides generic spawned auto-choice: risky or uncertain
documentation changes return as blockers for the parent, without interactive
questions or automatic approval. Deliberately narrow: only `spawned` is honored — `headless`
questions or automatic approval. Deliberately narrow: only `spawned` and `unattended` are honored
(`unattended` is a separate, explicitly selected contract for a parent that
reads a run's artifacts; see [unattended.md](unattended.md)) — `headless`
already has `GSTACK_HEADLESS`, and letting an env var force `interactive`
over CI markers would be a misclassification footgun. Empty or other values
are reserved and ignored (fall through to ambient detection). Note that hook
+2 -2
View File
@@ -141,7 +141,7 @@ session holds no lease) and a reclaim keeps the lease. `bin/gstack-lease list` s
packageManager, `.tool-versions`, `.bun-version` and CI `bun-version:` lanes
for this platform). CI exact pins for this platform are alternatives: a
repository may test several Bun versions (gstack's own CI pins 1.4.2 and
runs one job on 1.4.3), so a Bun equal to any one of them satisfies CI;
runs one job on 1.4.4), so a Bun equal to any one of them satisfies CI;
CI ranges and every non-CI pin stay required. One resolver picks the Bun (`gstack_bun_resolve` in
`bin/gstack-bun-version.sh`; `resolveBun` in `lib/pregate/runtime.ts` is
its TypeScript twin), and the installer, `--check`, the doctor, the bun
@@ -160,7 +160,7 @@ session holds no lease) and a reclaim keeps the lease. `bin/gstack-lease list` s
exact pin when gstack carries a verified archive for it
(`GSTACK_BUN_VERIFIED`: 1.4.2, 1.4.3, 1.4.4); with only CI exact pins, the install
default when it is one of them, else the highest verified one; else the
install default `GSTACK_BUN_DEFAULT` (1.4.3, above the 1.4.2 minimum; one
install default `GSTACK_BUN_DEFAULT` (1.4.4, above the 1.4.2 minimum; one
CI job runs the quick free tier on it).
`GSTACK_BUN_INSTALL_VERSION=<v>` overrides the default; a version without a
verified archive or below the minimum stops the install
+34 -5
View File
@@ -94,10 +94,11 @@ It refuses the hand-off, with one rate-limited line in
- the prompt carries a HIGH-tier credential shape (checked on the raw bytes
and on the decoded string values, so a JSON-escaped key does not slip by),
or is larger than 1 MiB;
- the repo's per-remote trust policy is `deny` or `read-only` (judged by the
session's working directory, so a session that touches other repositories
is not covered), or that policy could not be looked up at all (git could
not read the repository, the store is unreadable): the lookup fails closed;
- the repo's per-remote trust policy is `deny`, or `read-only` without the
recall opt-in below (judged by the session's working directory, so a
session that touches other repositories is not covered), or that policy
could not be looked up at all (git could not read the repository, the store
is unreadable or corrupt): the lookup fails closed;
- the receipt cannot be written, or the hook's 4.5 s budget cannot afford the
next step (the secret scan of a very large prompt, or the vendor spawn).
@@ -163,6 +164,34 @@ whatever it stored are untouched: `memorable disable` stops capture,
The entry also comes out with `gstack-uninstall` (named in its summary) and
survives `./setup --no-team`, which only tears down team-mode hooks.
## Recall in a read-only repo (per-remote opt-in)
`read-only` keeps a repo's content out of the brain, and by default it also
keeps that repo's prompts away from Memorable. If you chose `read-only` only
to stop code ingest, and you have no objection to Memorable seeing prompts
from that repo, opt that one remote in:
```bash
gstack-gbrain-repo-policy memorable set github.com/acme/client-a allow
gstack-gbrain-repo-policy memorable get github.com/acme/client-a # read-only allow
gstack-gbrain-repo-policy memorable set github.com/acme/client-a off
```
The opt-in is off by default and covers only the remote you name (the SSH and
HTTPS spellings of it are one key). It is refused on a `deny` remote, and a
`deny` vetoes recall even if an opt-in was written into the store by hand.
Changing the remote's tier clears it, so moving a repo back to `read-only`
later never silently re-opens recall. When `~/.gstack` also makes the remote
read-only (see [state roots](state-root.md)), that store has to opt it in too.
A corrupt, empty or unreadable policy store (or a dangling symlink in its
place) skips recall for every repo until you fix it; the hook never resets it
to "no policy" and never rewrites it. The lookup checks every configured
`remote.origin.url`, not the URL after `url.*.insteadOf` rewriting, and any
denied or not-opted-in read-only one skips recall. `gstack-gbrain-repo-policy list` shows
opted-in remotes with a `memorable=allow` column. Nothing about gbrain
ingest changes: the repo stays `read-only` for `/sync-gbrain` and every
other gbrain writer.
## If you run gbrain
Memorable can keep its procedures in your own gbrain database instead of its
@@ -189,7 +218,7 @@ and `/sync-gbrain` are unrelated to it.
(roughly 4 seconds after gstack's own work). That is usually network.
- **`hook-errors.log` names a refusal.** `refused:redaction-high` means a
credential shape was in the prompt; `trust policy ... deny or read-only`
means the repo is protected; `receipt-unwritable` means
means the repo is protected (see the per-remote opt-in above); `receipt-unwritable` means
`~/.gstack/security` is not writable.
- **`enable` says the stable install predates this bridge.** Run `./setup`
(or `/gstack-upgrade`) so the hook registered at `~/.claude/skills/gstack`
+4
View File
@@ -137,6 +137,10 @@ Two stages with separate clocks, both printed:
included, are source code, so a `dependencies` `[]` declaration clears them and `empty_lanes`
does not), transitively through
non-test files; deleted and renamed files select the tests that named them.
An extensionless JS/TS import (relative, a tsconfig/jsconfig `paths` alias or
`baseUrl`) tries the exact path, then `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`,
`.cjs`, `.mts`, `.cts`, then `index.ts`, `index.tsx`, `index.js`, `index.jsx`
and `index.mjs` inside the directory; the first file that exists wins.
A touched source file with no lane is `FAIL lanes — <file> has no test lane`,
never a pass. Markdown that no test depends on is a docs/no-tests empty lane
instead (`docs/no-tests: N documentation file(s)`), including when the base
+13 -6
View File
@@ -337,7 +337,9 @@ gstack-ship-queue train build --prs 41@<sha>,42@<sha>,43@<sha>
It prints `ORDER:` (the slot order to announce: screened PRs first in READY
order, then unscreened; when `max_size` or more unscreened PRs wait they form
their own train and `HELD:` lists them), one `PREFIX: T<i> <commit>
their own train and `HELD:` lists them; `--order <pr>,<pr>,...` naming every
pinned PR once replaces that rule, for a PR that must merge before another,
and the train's later `NEXT:` rebuilds keep it), one `PREFIX: T<i> <commit>
tree=<tree>` per prefix and one `SLOT:` per PR. `--dry-run` adds the
`ESTIMATE:` line and writes, pushes and dispatches nothing. Otherwise it
writes `train.json`, pushes `train/<nonce>`, dispatches `free_workflow` there
@@ -355,7 +357,11 @@ merging #<a>,#<b>; nothing dispatched, no branch pushed; fix: …` line names
the build without that PR) and `QUEUE_TRAIN_FREE_RED` (free tests red,
missing, cancelled or on another SHA). In a merge, version files take the
prefix's side and the CHANGELOG merges by section, since the stamp at merge
rewrites both.
rewrites both; a PR whose CHANGELOG drops or renames a released heading (or
carries a released entry's text in a section of its own) is a CHANGELOG
conflict, never merged with the damage. READY receipts and train records are
keyed on origin's GitHub owner/repo, so lanes whose origin uses another URL
form (git.capy.ai, ssh, a token URL) share the coordinator's queue.
**3. Coordinator: status and verdict.**
@@ -406,13 +412,13 @@ EVAL_POLICY records it. Bisect results never turn a train green.
```bash
git fetch origin main
git merge --no-ff --no-commit origin/main # expect a CHANGELOG.md conflict only
bun --no-env-file --config=/dev/null -e "import { mergeChangelogs } from './lib/changelog-check'; import { spawnSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; const s = (n) => spawnSync('git', ['show', ':' + n + ':CHANGELOG.md'], { encoding: 'utf8', maxBuffer: 1 << 26, timeout: 30000 }).stdout; const m = mergeChangelogs(s(2), s(3)); if (!m) { console.error('CHANGELOG needs a hand merge'); process.exit(1); } writeFileSync('CHANGELOG.md', m);"
bun --no-env-file --config=/dev/null -e "import { mergeChangelogs } from './lib/changelog-check'; import { spawnSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; const s = (n) => spawnSync('git', ['show', ':' + n + ':CHANGELOG.md'], { encoding: 'utf8', maxBuffer: 1 << 26, timeout: 30000 }).stdout; const m = mergeChangelogs(s(2), s(3), s(1)); if (!m) { console.error('CHANGELOG needs a hand merge'); process.exit(1); } writeFileSync('CHANGELOG.md', m);"
git add CHANGELOG.md && git commit --no-edit
gstack-restamp --next --base main --pr 42 --expect-base <main sha> --expect-order <order> --allow-repo-commands
git add CHANGELOG.md VERSION package.json <release outputs> && git commit -m "v<X> stamp" && git push
```
A conflict in any file other than CHANGELOG.md means the merge isn't the train's prefix. Stop and report it. `verify` accepts the resulting head because its tree equals T<i> modulo stamps; the `head:` line says "T<i> with main merged in".
A conflict in any file other than CHANGELOG.md means the merge isn't the train's prefix. Stop and report it. When the helper says the CHANGELOG needs a hand merge, take main's CHANGELOG unchanged and put your own entry above it. `verify` accepts the resulting head because it contains main and its tree equals T<i> modulo stamps, with its CHANGELOG judged as main's plus lines your pin added, however the conflict was resolved; the `head:` line says "T<i> with main merged in".
**5. Coordinator: verify, merge, verify again.** For each slot in order:
@@ -423,8 +429,9 @@ gstack-ship-queue verify --pr 41 --train <nonce> # VERIFY_TRAIN: #41 merged; m
```
Before a merge, main's tree must equal the prefix before the PR modulo stamps
and the PR must be at its pin (stamps aside) or at T<i> modulo stamps (its pin
with main merged in); after it, main's tree
and the PR must be at its pin (stamps aside) or contain main and be T<i>
modulo stamps (its pin with main merged in; its CHANGELOG must be main's with
only lines from the pin's own CHANGELOG diff above it); after it, main's tree
must equal the prefix after it. Anything else is `QUEUE_TRAIN_TREE_MISMATCH`
(or `QUEUE_TRAIN_MOVED`). When nothing of the train has merged, that is a plain
refusal: build a new train on current main. When earlier slots already
+4 -4
View File
@@ -1590,7 +1590,7 @@ next `--check` passes.
### `gstack-capy-install: GSTACK_BUN_INSTALL_VERSION=<v> is not a verified Bun at or above gstack's supported minimum ...; fix: ... (BUN_INSTALL_VERSION_INVALID)`
**Meaning.** `GSTACK_BUN_INSTALL_VERSION` overrides the Bun the installer
downloads when no exact project pin decides (`GSTACK_BUN_DEFAULT`, 1.4.3). It
downloads when no exact project pin decides (`GSTACK_BUN_DEFAULT`, 1.4.4). It
must be listed in `GSTACK_BUN_VERIFIED` (a SHA-256-verified archive) and sit at
or above the supported minimum (`GSTACK_BUN_TESTED`). Nothing was installed.
@@ -2440,9 +2440,9 @@ reply with the printed `gate_rev`.
**Meaning.** Part of the reply matched no gate item or option. Nothing was
applied; the unparsed tokens are listed.
**Fix.** Reply with `all`, with `<id><option>` tokens (`d3b uc1a`), or with
`all except <tokens>`. A bare `yes` or `no` is ambiguous across multi-option
items and is never guessed.
**Fix.** Reply with `rec` (alias `all`), with `rec except <tokens>`, with
`<id><option>` tokens (`d3b uc1a`), or with `as-is` (the plan as written). A
bare `yes` or `no` is ambiguous across multi-option items and is never guessed.
<a id="answer-by-required"></a>
### `ANSWER_BY_REQUIRED`
+27 -2
View File
@@ -57,18 +57,41 @@ An unknown `GSTACK_SESSION_KIND` value coerces to `interactive` and says so:
## The terminal line
Every workflow skill ends an unattended run with one line the parent greps:
A run that writes a record ends with one line the parent greps:
```
GSTACK_RESULT: skill=<name> status=<status> run=<dir>
```
`status` is one of `complete`, `gate_pending`, `incomplete`, `interrupted` or
`refused`; `run` is the directory that holds the artifacts below. `/autoplan`
`refused`; `run` is where the run's record is. `/autoplan`
always ends `gate_pending` when it reaches the final gate, because the gate is
written and never approved. Pure queries (`gstack-artifact schema`,
`gstack-gate render`) print no result line.
Not every workflow writes this line or a run directory today:
- **`/autoplan`** writes the full run directory below through `gstack-autoplan`,
and `gstack-artifact validate` checks it. It is the only skill with steps
and helpers that write `run.json`, `decisions.jsonl` and `gate.json`.
- **Workflow commands** `gstack-pregate`, `gstack-regen`, `gstack-restamp` and
`gstack-ship-queue` print their own line, with `run=` naming their own record
(the pregate receipt, the regen worktree, the restamp journal, the queue
record), not a run directory.
- **A refused launch.** Any skill whose install check refuses it at startup
prints `GSTACK_RESULT: skill=<name> status=refused run=-` and stops.
- **Every other workflow skill** (`/ship`, `/review`, `/qa`, `/design-review`,
`/spec`, `/land-and-deploy` and the rest) gets the session rules above (no
prompts, approvals stay pending, nothing published), but has no step that
writes a run directory or prints the line, so a parent should not wait for
either. `/eval-plan` asks for a `gate_pending` line at its spend gate, but no
helper writes its `gate.json` yet (`gstack-gate` renders and parses a gate
list; it does not create one). `/cso` skips the shared startup, so these
session rules do not reach it at all. Read each skill's own record instead:
the ship receipt in the PR body (`gstack-ship-receipt read --pr <n> --json`)
for `/ship`, the review-log rows (`gstack-review-read`) for `/review`, and the
`qa-report-*.md` file for `/qa`.
Every workflow command shares one exit table: `0` ok, `1` fail, `2` usage,
`3` refused or needs a flag. Error lines end with a result code in parentheses,
for example `findings.jsonl:12: ... (ARTIFACT_SCHEMA)`; each code has an anchor
@@ -77,6 +100,8 @@ a `fix:` clause. Grep the code, not the prose.
## The run directory
`/autoplan` writes these files; the next section shows how a parent reads them.
| File | Schema | Contents |
|------|--------|----------|
| `run.json` | `run` v1 | The manifest: `run` id, `status`, `required_phases`, each phase's `native` and `outside` outcome (`status`, `model`, `provider`), input `snapshot` hashes, `counts`, every artifact's `path` (relative to the run directory) and `sha256`, `session_kind`, `gate_rev`, `deviations`, `consumed_by`. |