Merge remote-tracking branch 'origin/main' into gsta-114-autoplan-gates

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
garrytan committed 2026-10-27 06:39:33 +00:00
commit 624f0eedec
20 files changed
+973 -6

No files matched your search

+30
View File
@@ -172,3 +172,33 @@ Option C (this section) keeps secret-distribution scope tight.
If the user asks you to skip the move (e.g., "just leave it as a fork PR"),
respect that — eval CI will fail with empty-env auth, but check-freshness,
workflow-lint, and windows-tests will still pass on the fork PR.
## Auditing Conductor worktrees
Conductor workspaces are git worktrees, so after a few weeks of parallel
lanes the checkout carries worktrees nobody remembers. `bin/gstack-worktree-audit
[-b origin/<base>] [--no-fetch] [repo-path]` prints one TSV row per worktree
beside the main one, largest first: size, age of the last commit, whether its
head is merged into the base, `clean` / `clean,ignored:<n>` (only gitignored
entries such as `.env` or build output) / `wip:<n>` (tracked edits) /
`scratch:<n>` (untracked only) / `missing` (registered, directory gone) /
`broken` (below) / `unknown` (`git status` failed), `pushed` / `ahead<n>` / `no-remote` /
`detached` (against the local `origin/<branch>` tracking ref as last fetched),
the PR on its branch through `gh` (`#n/open|merged|closed`, or `-`; a fork's
PR on a same-named branch is ignored, so in a fork workflow where `origin` is
your fork the column is `-`, and an open PR wins over an older closed one on a
reused branch name), the date of the newest Claude Code chat that ran in it,
and a bucket: `prunable` (registered, directory gone, not locked), then
`review` for a locked entry whose directory is gone or a `broken` worktree
whose own `.git` link is missing, then `hold-wip`, then `hold-open-pr`, then
`verify-recent-chat` (a chat in the last four days), then `safe`, else
`review`. `safe` means nothing tracked or untracked exists only in that
worktree: a clean tree whose head is merged into the base, or whose PR is
closed or merged with every commit pushed; an unpushed commit or an untracked
file on a merged branch is `review`. Ignored entries do not change the bucket,
but removing the worktree removes them too, so read the `ignored:<n>` count
before you do. A squash-merged branch is never an ancestor of the base, so read
`MERGED` together with `PR`. The audit only reads, apart from a best-effort fetch
of the base ref that `--no-fetch` skips; removing a worktree
(`git worktree remove <path>`) stays your call after the table. `/landing-report` points at it when the sibling table shows
stale worktrees.
+2
View File
@@ -12,6 +12,8 @@ Beyond the slash-command skills, gstack ships standalone CLIs for workflows that
| `gstack-code-intelligence` | **Code-intelligence provider picker** — wraps GBrain, Sourcebot, and Graphify behind one interface: `options`/`status` to see what's available, `select` to pick one, `index`/`search` to use it, `suggest` to check whether the one-time indexing offer should fire here. The offer triggers on large repos (1,000+ tracked files; a decline is persisted). Non-local providers refuse to index *or search* until you record per-repo consent (`consent <repo> yes\|no` — the query text is repo-derived content), the per-repo trust policy's deny and read-only tiers veto write-class operations regardless of consent, and every off-machine send writes an egress receipt. Fully optional — with nothing selected, gstack falls back to grep. |
| `gstack-verify-gate` | **Verification stop hook (opt-in)** — blocks a Claude Code turn from ending until the project's declared verify command passes (after 3 blocked re-entries it yields with a loud still-RED warning instead of looping forever). Declare it on one line in CLAUDE.md: `<!-- gstack:verify: bun test -->`. Hooks bypass the permission system, so a declared command never runs until you trust it once per repo (`gstack-verify-gate --trust`); editing the command invalidates trust until re-granted, and every grant is audit-logged. `./setup` never registers it for you — opt in with `gstack-settings-hook add-event --event Stop --command ~/.claude/skills/gstack/bin/gstack-verify-gate --source verify-gate`, remove with `gstack-settings-hook remove-source --source verify-gate`. |
| `gstack-memorable` | **Memorable recall bridge (opt-in, third party, Claude Code only)** — connects Claude Code to the external [Memorable](https://memorable.sh) CLI *through gstack* instead of the vendor's own installer, so the hook gets gstack's guarantees: an explicit consent key (`memorable_recall`, off by default, listed by `gstack-egress grants`), a fail-closed egress receipt for every prompt handed over (`gstack-egress list --sink memorable-recall`), a HIGH-tier secret pre-scan, a trust envelope and 8 KiB cap on whatever comes back, an allowlisted environment and process-group containment for the vendor process, and clean removal. `enable` registers the hook at the stable install with a 5 s timeout and never runs the vendor's own consent command; `disable` revokes the gate first and removes the entry by identity even after Claude Code strips the tag; `status` is read-only. gstack never installs Memorable, and what its binary sends is the vendor's claim, not gstack's. Not available on Windows yet. [Full guide](memorable-workflow-memory.md). |
| `gstack-decision-trail` | **Decision trail** — append one row (ts, phase, decision, why, evidence, result) to a run's TSV trail so a reviewer reads what was chosen and why without the transcript. Header once, append-only, cells flattened to one line, and a cell starting with `=`, `+`, `-` or `@` quoted so a spreadsheet cannot run it. `start <file> <run-id>` opens a run's stretch of a shared trail (idempotent; `start` is reserved as a phase name), and `--run <id>` refuses an append until that start row is the newest. Ported from pstack's `log.sh` (MIT, NOTICE.md). [Rule](unattended.md#the-decision-trail). |
| `gstack-worktree-audit` | **Worktree audit** — read-only table of every git worktree beside the main one, largest first: size, age, merged into the base or not, `clean`/`clean,ignored:<n>`/`wip:<n>`/`scratch:<n>`, `pushed`/`ahead<n>`/`no-remote`/`detached`, the PR on its branch through `gh` (fork PRs ignored), the newest Claude Code chat in it, and a bucket (`prunable` for an unlocked registered worktree whose directory is gone, `review` for a locked or `broken` one, `hold-wip`, `hold-open-pr`, `verify-recent-chat`, `safe` only for a clean tree that is merged into the base or whose PR is closed or merged with every commit pushed, else `review`). `-b origin/<branch>` sets the base, `--no-fetch` skips the best-effort fetch of the base ref (its one write). Never deletes. Ported from pstack's `worktree-audit.sh` (MIT, NOTICE.md). [Guide](CONTRIBUTOR_REFERENCE.md#auditing-conductor-worktrees). |
| `gstack-wtree` | **Working-tree fingerprint** — prints a content hash of what's actually on disk (temp index seeded from the stat cache, ~40x cheaper than a full re-hash; untracked source counts, gitignored scratch doesn't). Identical content fingerprints identically through commits, rebases, amends, and squashes — it's what binds reviews and test evidence to content instead of commit SHAs. |
| `gstack-review-log` | **Review-pass receipts** — `--start <skill>` captures the working-tree fingerprint before a diff review; `'<JSON>' --finish <token>` consumes that single-use, repository/branch/skill-scoped receipt. Binding requires matching start/end content and reviewer-reported `completed:true` and `converged:true`; it is not independent proof that a model read the source. `--check-shared-libs <token>` reads a current finding as JSON on stdin and checks prior Skip decisions against the actual branch, capture and eligible raw source blobs. It returns `reusable:false` when proof is missing or unsafe, including older records without logger-versioned coverage. Final review logging computes shared-code fingerprints and coverage rather than trusting supplied proof. |
| `gstack-review-read` | **Review freshness** — emits review records with computed `review_freshness.status` and `reason`: CURRENT, STALE, or UNVERIFIED for diff reviews. `/ship` and `/land-and-deploy` use the same grade; a matching commit alone never certifies a diff review. [Dashboard rules](skills.md#review-readiness-dashboard). |
+30
View File
@@ -4876,3 +4876,33 @@ final summary. Rename or move yours, or switch modes (`./setup --prefix` /
a generated gstack SKILL.md and then edited it, that file is moved to
`~/.gstack/backups/skills/<timestamp>/<skill>/SKILL.md` before gstack's is
linked in, never deleted.
## gstack-decision-trail and gstack-worktree-audit
<a id="decision-trail-start-required"></a>
### `gstack-decision-trail: DECISION_TRAIL_START_REQUIRED: <file> has <n> rows and run <id> has no start row after the newest one`
You appended with `--run <id>` to a trail that other runs already wrote, and
the newest `start` row in it is not this run's. A run (one agent
conversation, with its later turns) that joins a trail opens with its own
`start` row so a reviewer can tell whose rows are whose. Run
`gstack-decision-trail start <file> <id>` once and append again; the start
command is a no-op while this run's start row is still the newest, so a rerun
of the same script is safe. Nothing was written. See
[docs/unattended.md](unattended.md#the-decision-trail).
### `gstack-worktree-audit` prints `warn: no -b given; MERGED checks against origin/HEAD` or every row says `MERGED no`
Without `-b` the audit compares each worktree's head against the remote's
default branch (then `origin/main`, then `origin/master`), which is not
always the branch PRs merge into; pass `-b origin/<branch>`. A squash-merged
branch is never an ancestor of the base, so `MERGED no` beside `PR #n/merged`
is normal and the bucket is still `safe` while the tree is clean and every
commit is pushed; an unpushed commit or an untracked file on that branch makes
it `review`, because deleting the worktree would lose it. The audit only reads
(its one write is the base fetch, which `--no-fetch` skips);
a worktree it calls `safe` is still yours to remove, and a `prunable` row names
a registered, unlocked worktree whose directory is already gone (`git worktree
prune`); a locked one, or a `broken` row whose `.git` link is missing, reads
`review` instead. `clean,ignored:<n>` means the tree is clean but holds
gitignored entries (`.env`, build output) that go with the worktree.
+38
View File
@@ -482,3 +482,41 @@ fix. `GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1` admits anyway (`INSTALL_VERDICT:
overridden`), recorded as a deviation. TypeScript entrypoints use
`checkInstallAdmission` from `lib/install-verdict.ts`; the Bash side is
`bin/gstack-install-verdict.sh`. See [capy-install.md](capy-install.md#the-launch-line).
## The decision trail
A long or unattended run keeps one tab-separated trail of its decisions at
`<run dir>/decisions.tsv`, written through `gstack-decision-trail <file>
<phase> <decision> <why> <evidence> <result>`, so the parent reads what the run
chose and why without the transcript. The helper stamps the time, writes the
header once, flattens each cell to one line and quotes a cell that starts
with `=`, `+`, `-` or `@`, so evidence lifted from a PR title or a filename
cannot run as a formula when a reviewer opens the file in a spreadsheet.
Log the forks, not the keystrokes: an approach chosen or dropped, a unit done
with its verification result, a revert and what triggered it, a blocker, one
row per iteration of a loop. Evidence is a pointer that resolves (a commit, a
PR, `file:line`, an artifact path), never prose. A run that joins a trail
other runs wrote (a pickup after a crash, a replacement machine, a later turn
of the same thread) first runs `gstack-decision-trail start <file> <run id>`,
which appends a `start` row naming the earlier rows and the run; appends with
`--run <id>` refuse (`DECISION_TRAIL_START_REQUIRED`) until that row is the
newest start row, and `start` is a no-op when it already is, so a rerun of the
same step writes nothing twice.
Before it hands back, the run audits the trail against what it did: every row
of its own stretch (from its `start` row to the next run's) maps to a real
decision or action, every evidence pointer resolves and shows what the row
claims, and a fork or abandoned approach that shaped the result but is not
logged gets a row. The audit corrects the log, never the story: no row is
edited or removed, an invented or wrong row stays and a new row supersedes it
with what actually happened. Then a reviewer on a different model family from
the one that did the work (a Capy subagent on another family, or the outside
voice) reads the trail and the transcript and flags what the owner should look
at: decisions with weak or missing evidence, verification claimed without
proof, choices that look risky in hindsight, gaps a skim would miss. The run's
final report ends with an `Attention` section that opens with `reviewed by
<model>` on its own line and lists each flag against its rows; `No flags` is a
valid body, a missing model line is not. Self-review does not stand in for
this. `decisions.jsonl` stays the typed record `gstack-artifact` validates; the
trail is the plain one a person reads, and `column -s$'\t' -t decisions.tsv`
renders it in a terminal.