mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
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:
commit
624f0eedec
20 files changed
+973
-6
No files matched your search
@@ -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.
|
||||
@@ -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). |
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user