diff --git a/.claude/skills/verify-open-pstack/features/registry.json b/.claude/skills/verify-open-pstack/features/registry.json index 88ad664d..dcacfbfd 100644 --- a/.claude/skills/verify-open-pstack/features/registry.json +++ b/.claude/skills/verify-open-pstack/features/registry.json @@ -79,7 +79,7 @@ "nonRuntime": [ "README.md", "REVIEW.md", "CHANGES.md", "LICENSE", "LICENSE.md", "UPSTREAM.md", "NOTICE.md", "README-UPSTREAM.md", "LICENSE-cursor-team-kit", "LICENSE-superpowers", - ".gitignore", ".mergify.yml", "docs/**", "assets/**", + ".gitignore", ".mergify.yml", ".github/workflows/unfret-threads.yml", "docs/**", "assets/**", "plugins/pstack/skills/poteto-mode/scripts/bootstrap.test.ts", "plugins/pstack/skills/poteto-mode/scripts/check-plan.test.ts", "plugins/pstack/skills/poteto-mode/scripts/resolve-codex-transcript.test.ts", "scripts/release.py", "scripts/release_test.py" diff --git a/.github/workflows/unfret-threads.yml b/.github/workflows/unfret-threads.yml new file mode 100644 index 00000000..70a0902b --- /dev/null +++ b/.github/workflows/unfret-threads.yml @@ -0,0 +1,44 @@ +name: Unfret threads + +on: + pull_request_review: + types: [submitted] + +permissions: + # GitHub refuses resolveReviewThread to the Actions token without contents: write. + contents: write + pull-requests: write + +jobs: + resolve: + if: github.event.review.user.login == 'unfret-eal[bot]' + runs-on: ubuntu-latest + steps: + - name: Resolve the threads Unfret started + shell: bash + env: + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + run: | + gh api graphql --paginate \ + -f owner="${REPOSITORY%/*}" -f name="${REPOSITORY#*/}" -F number="$PR" \ + -f query='query($owner: String!, $name: String!, $number: Int!, $endCursor: String) { + repository(owner: $owner, name: $name) { + pullRequest(number: $number) { + reviewThreads(first: 100, after: $endCursor) { + pageInfo { hasNextPage endCursor } + nodes { id isResolved comments(first: 1) { nodes { author { login } } } } + } + } + } + }' \ + --jq '.data.repository.pullRequest.reviewThreads.nodes[] + | select(.isResolved | not) + | select(.comments.nodes[0].author.login == "unfret-eal") + | .id' | + while read -r thread; do + gh api graphql -f thread="$thread" \ + -f query='mutation($thread: ID!) { resolveReviewThread(input: {threadId: $thread}) { thread { id } } }' \ + > /dev/null + done diff --git a/.mergify.yml b/.mergify.yml index a7d6e5ad..a09ce350 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -9,7 +9,6 @@ queue_rules: queue_conditions: - -draft - check-success=verify - - check-success=Unfret - check-success=live-gate - "#review-threads-unresolved = 0" merge_conditions: [] @@ -21,15 +20,21 @@ merge_protections_settings: merge_protections: - name: Queue ready pull requests - description: Automatically queue non-draft pull requests targeting main after verify, Unfret, and live-gate pass on the head and every review thread is resolved. + description: Automatically queue non-draft pull requests targeting main after verify and live-gate pass on the head and every review thread is resolved. if: - base = main success_conditions: - -draft - check-success=verify - - check-success=Unfret - check-success=live-gate - "#review-threads-unresolved = 0" + - name: Unfret on risky paths + description: Pull requests that touch .github/ or the Mergify config also wait for Unfret. + if: + - base = main + - files ~= ^(\.github/|\.mergify(\.ya?ml\Z|/)) + success_conditions: + - check-success = @unfret-eal/Unfret commands_restrictions: requeue: diff --git a/AGENTS.md b/AGENTS.md index 02abcc79..5903f99d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,8 +8,14 @@ Code this repository owns explains itself. `UPSTREAM.md`, `CHANGES.md` and `NOTI Before opening a pull request, run the Bun tests, strict typecheck, static invariants, and plugin validation. -Nothing merges, tags, releases, or rolls out until the exact candidate is installed and the changed behavior passes a live test from the real user surface in every affected harness. Unit tests, validators, source inspection, and self-reports do not satisfy this gate. The record is the `live-gate` commit status on that exact head, which links an evidence comment naming the installed version, surface, action, and observed result. Mergify does not queue a pull request without it. A pull request may be ready for review before its live test, but every new head, including a queue-created head, needs its own. +Nothing merges, tags, releases, or rolls out until the exact candidate is installed and the changed behavior passes a live test from the real user surface in every affected harness. Unit tests, validators, source inspection, and self-reports do not satisfy this gate. The live test covers the changed behavior, not every changed file or branch. `verify-open-pstack`'s rule picks the features and never defaults to a full sweep. A representative selection is the default, not a coverage gap to escalate. One stand-in covers a sentence repeated verbatim across many skills: the cheapest skill that carries it, never one that runs several models at high effort, such as `interrogate`, `swarm` or `arena`. The record is the `live-gate` commit status on that exact head, which links an evidence comment naming the installed version, surface, action, and observed result. Mergify does not queue a pull request without it. A pull request may be ready for review before its live test, but every new head, including a queue-created head, needs its own. For Open Pstack PRs, use the shared repository-local `.claude/skills/verify-open-pstack/` skill (Codex: `.agents/skills/verify-open-pstack`). Verifier-changing PRs run from a checkout of their reviewed candidate head, with the publisher revision equal to the candidate SHA; all other PRs run from trusted `main`. Record the publisher revision separately from the candidate SHA. Reuse the operator's existing logins without credential source flags, extra logins, or credential reading/copying. Claude uses normal operator config with session-only `--plugin-dir /plugins/pstack --settings '{"enabledPlugins":{"pstack@open-pstack":false}}'`. Codex installs the exact-head candidate in a run-owned `CODEX_HOME`, with `auth.json` symlinked to the operator's `${CODEX_HOME:-$HOME/.codex}/auth.json`; absent file-based auth fails closed. Removing the run home removes only the symlink, never its target. Setup alone snapshots real `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/pstack-models.md` and `CLAUDE.md`, restores their exact bytes or original absence afterwards, and verifies restoration before any publication; restoration failure publishes nothing. Preserve private mode-0700 raw evidence through merge. Publication is one structured evidence comment plus exact-SHA `live-gate`; no credential parsing, token registration, or exact-secret comment checks. No PR-body, ready/draft, reversal, or compensation writes, publisher-code attestation, or OS filesystem-denial claims are permitted. Builders do not publish the gate, queue, or merge. +Merge policy: land on green + +Mergify merges a pull request once `verify` and `live-gate` pass on its head and no review thread is unresolved. Unfret reviews every head in parallel and usually publishes its verdict after the merge. `.github/workflows/unfret-threads.yml` resolves each thread Unfret starts, so only other reviewers' threads hold a merge. A pull request that touches `.github/**` or the Mergify config also waits for `Unfret`; the risky-path rule in `.mergify.yml` lists those paths, and the verifier is not one of them. A pull request that touches `.github/**` also needs an operator to queue it. A material Unfret finding on a merged head gets a fix-forward pull request: a new, small pull request under the same flow, whose issue carries `Introduced by: `. Mergify reads no `hold` label here: to keep a pull request from landing, keep it a draft. + +Publish a release only when every pull request merged since the previous release has an Unfret verdict on its merged head and each material finding among them has a merged fix-forward pull request. Then run `python3 scripts/release.py publish` from a clean checkout of `main`. + Do not add an implicit runtime timeout or a weaker-model fallback. diff --git a/CHANGES.md b/CHANGES.md index c9d9a4da..30240cba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # CHANGES — applied substitutions -Mergify auto-queue is live: ready PRs to main require passing verify, Unfret and exact-head live-gate, and no unresolved review thread; workflow changes require operator queueing. +Open Pstack lands on green: Mergify merges a ready PR to main once verify and exact-head live-gate pass and no review thread is unresolved, and a workflow resolves Unfret's own threads. A PR that touches `.github/` or the Mergify config also waits for Unfret, and workflow changes require operator queueing. This port applies the Cursor → Claude Code substitutions in skill bodies. Earlier drafts left them flagged; this revision resolves them. A later pass added a Codex build that shares the same skills; see [Codex port](#codex-port) below. ## 1.16.0 syncs to Cursor pstack 0.15.29 diff --git a/REVIEW.md b/REVIEW.md index ba1ff664..c945240e 100644 --- a/REVIEW.md +++ b/REVIEW.md @@ -31,7 +31,8 @@ This repository has two surfaces. Shipped plugin code, `plugins/pstack/**`, runs - Contract (`.claude/skills/verify-open-pstack/scripts/harness.ts`, `features/recipes.ts`): the verifier names each fixture directory `state//fixtures/` inside the run root, and generates per-run fixture markers with `crypto.randomUUID()`. - Contract (`plugins/pstack/skills/poteto-mode/references/provider-dispatch.md`): it names no CLI version range. A runner lane requires the assigned provider CLI installed and authenticated as the launcher's preflight checks, a parent sandbox that lets that CLI reach its network and state, and, on Linux, Landlock and `bwrap` for Grok's bounded profiles. A lane that lacks one of these ends as a named dropout or a failed receipt, never a fallback. - Contract (`docs/reference.md`, Dependencies): shipped tooling uses `gh` (or Origin's `origin`), `bun`, `node`, and the provider CLIs in the user's model sheet; `gt` only for the Orchestrate playbook; `jq` and `rg` only for `worktree-audit.sh`, which warns without them. -- Contract (`.claude/skills/verify-open-pstack/features/recipes.ts`, runner and Reflect assertions): the live gate's evidence is the harness-recorded command naming the installed tool, plus a receipt or output that carries this run's paths, markers or results. It guards against model claims; it does not attest that a file executed. Operator policy (2026-10-11, PR #151 step-back decision): this is the gate's evidence standard. A false gate decision that an honest run can reach stays material. +- Contract (`.claude/skills/verify-open-pstack/features/recipes.ts`, runner and Reflect assertions): the live gate's evidence is the harness-recorded command naming the installed tool, plus a receipt or output that carries this run's paths, markers or results. It guards against model claims; it does not attest that a file executed. Operator policy (2026-10-11, PR #151 step-back decision): this is the gate's evidence standard. Operator policy (2026-10-21, #240): an assertion the live gate publishes that can pass without the behavior it names is material, and its correction is to delete the assertion and list the behavior under the PR's Not verified. A behavior no recipe asserts is a coverage gap: an advisory listed under Not verified. +- Contract (`.mergify.yml`; operator policy 2026-10-21, #241): this repository lands on green. Only a pull request that touches `.github/**` or the Mergify config waits for `Unfret` before it merges, and the verifier, `.claude/skills/verify-open-pstack/**`, is not a risky path. Unfret reviews every other head in parallel, usually after the merge, and a material finding on a merged head gets a fix-forward pull request. ## Entry-point scope (read by the review panel as a mandate) @@ -41,7 +42,7 @@ Each scoped sentence in this section names a concrete entry point or bounded sur - `verify.sh doctor` and `verify.sh run` serve only the operator's Mac, which `doctor.ts` and `harness.ts` enforce as Darwin; a live-gate failure that needs a non-Darwin host is out of scope, but the verifier's unit tests and typecheck in Ubuntu CI stay in scope. - The verifier's `--output` run root is `$SESSION/live` in a directory the documented launch recipe makes with `mktemp -d` under the operator's `TMPDIR`, and the verifier refuses an existing path; a failure that needs a hand-named run root is out of scope, but any path an inherited `TMPDIR` gives stays in scope. - Fixture directories and per-run markers in `.claude/skills/verify-open-pstack/**` are generated by `harness.ts` and `features/recipes.ts`; a failure that needs a fixture or marker name chosen by hand is out of scope. -- The live gate in `.claude/skills/verify-open-pstack/**` must reject model claims and reach a correct gate decision on every honest run; a failure that needs the model under test to deliberately forge a tool's output, such as rebuilding a runner or resolver result with other commands, is out of scope. +- The live gate in `.claude/skills/verify-open-pstack/**` must reject model claims, and every assertion it publishes must be backed by its retained evidence; a failure that needs the model under test to deliberately forge a tool's output, such as rebuilding a runner or resolver result with other commands, is out of scope. - In `.claude/skills/verify-open-pstack/**`, a failure that needs the model under test to deliberately evade a recipe, such as a crafted shell spelling, a value hidden in a variable or a report echoed without its lookup, is out of scope even when it would change the gate decision; a wrong decision on an honest run stays in scope. - In `plugins/pstack/**`, a failure that needs a host missing a prerequisite named in `docs/reference.md` (Dependencies) or `provider-dispatch.md` (Host and parent prerequisites) is out of scope, except for the behavior those files promise on such a host. - On such a host, `plugins/pstack/**` must keep the promised behavior: `worktree-audit.sh` warns without `jq` or `rg`, a blocked external CLI is a loud dropout, and a Grok lane without Landlock or `bwrap` ends `child-failed` with Grok's refusal in its receipt. diff --git a/UPSTREAM.md b/UPSTREAM.md index 30e5de1f..6740a51e 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -49,7 +49,7 @@ The table above is the current Cursor sync point. Open Pstack 1.16.0 imported th - **#157 — role pointers**: Cursor names model descriptors in the Arena, Architect, How, Interrogate, and Swarm skills, the bug-fix, feature, hillclimb, perf-issue, and refactoring playbooks, poteto-mode's delegation defaults, and setup's probe table and first-run sheet. The port replaces each with a role pointer: the role's backticked sheet label and a link to `plugins/pstack/skills/poteto-mode/references/model-catalog.md#role-defaults`, which `registry/render.ts` generates from `plugins/pstack/skills/poteto-mode/scripts/runner/registry/models.ts`. Interrogate's reviewer table and setup's probe table and example sheet are deleted. When an upstream sync changes a model slug on one of these lines, keep the port's line and put the model change in `registry/models.ts`. `runner/registry.test.ts` enforces the rule: `ROLE_POINTERS` names each file's roles, and its text scan rejects any descriptor or versioned model name in `plugins/pstack/**/*.md`, `README.md`, and `docs/reference.md` outside the generated catalog and agents. -- **#46 — quiet setup reruns**: Upstream's setup has no keep-roles, per-family effort, or write-confirmation questions, so this is a port patch. `setup-pstack/SKILL.md` states one qualifying-rerun exception before step 2's keep-roles question. On a rerun whose sheet loaded with one valid setting line, with no change named and no inconsistency or kept-predecessor proposal that needs a choice, setup validates in step 3, keeps and prints the roles and efforts instead of asking steps 2 and 4, and writes without step 7's confirmation when every step 5 probe passes. Steps 4, 7 and 8 and the offers in steps 10 and 11 each name the exception. It skips no validation, permission check, snapshot, readback, or the step 9 smoke test, and a failed probe still stops without writing. `poteto-help` says the same for reruns. The verifier's `keep-setting` case gives no answers and requires the sheet and Codex mirror to keep their bytes with no `AskUserQuestion` call. Preserve this divergence during upstream syncs. +- **#46 — quiet setup reruns**: Upstream's setup has no keep-roles, per-family effort, or write-confirmation questions, so this is a port patch. `setup-pstack/SKILL.md` states one qualifying-rerun exception before step 2's keep-roles question. On a rerun whose sheet loaded with one valid setting line, with no change named and no inconsistency or kept-predecessor proposal that needs a choice, setup validates in step 3, keeps and prints the roles and efforts instead of asking steps 2 and 4, and writes without step 7's confirmation when every step 5 probe passes. Steps 4, 7 and 8 and the offers in steps 10 and 11 each name the exception. It skips no validation, permission check, snapshot, readback, or the step 9 smoke test, and a failed probe still stops without writing. `poteto-help` says the same for reruns. Preserve this divergence during upstream syncs. - **#191 — landing playbooks and per-spawn effort**: `ce649c0` cuts Shipping to six steps and both autopilots to principles. The port takes the cut's rules and reasons but keeps the landing procedure from 1.3.0: Shipping keeps steps 1 to 9, Autopilot-full step 2 holds the guarded rebase and push, and Autopilot-stack step 2 holds the fork and topology rules. Those steps disarm auto-merge and merge-queue entries before verification and before any rewrite, keep the verdict head and base apart from the landing head and base, resolve the base fetch remote apart from the head push URL, bind each push to a captured SHA, and merge only through a server-enforced expected head. The named landing invariants in `tests/skill-collision-repro.sh` check those rules by step and phrase, so a sync that moves them updates the invariants in the same change. `ce649c0`'s Match the model to the job rule is taken with port adaptations: the starting level is the role's configured effort, an unlisted effort moves to the nearest effort the catalog row lists, a configured `ultra` stays, and the fast and standard speed sentences are dropped because the port's descriptors carry no speed selector. @@ -87,9 +87,9 @@ No output means the tracked pstack tree has not changed. This comparison does no The merge checks out files the port never changed, merges the rest three ways, and prints counts of verbatim, clean, review and unmapped paths. It exits 1 when any path needs review. 4. Resolve each review row by hand. A conflict hunk keeps the port's adaptation and takes upstream's new intent beside it. A change to an excluded path stays out and extends that exclusion's line in this file. Each unmapped path, such as a Cursor manifest or the guide, is excluded or mapped by a decision in step 2. Record every hand resolution and every new adaptation or exclusion in this file. 5. Keep one shared `plugins/pstack/skills/` tree. Put harness translation in the existing `codex-tools.md` and provider routing in `provider-dispatch.md`; do not fork a skill per harness. -6. Add every new skill to the `skills` list in `.claude/skills/verify-open-pstack/features/registry.json`. When the sync changes what a skill with its own recipe does, such as `setup-pstack`, update that recipe in `features/recipes.ts`, its unit tests under `scripts/`, and its feature page. +6. Add every new skill to the `skills` list in `.claude/skills/verify-open-pstack/features/registry.json`. 7. Update the commit and version in this file, add a provenance row to `NOTICE.md`, and take `README-UPSTREAM.md` when upstream changes its README. 8. Run CI-equivalent checks locally, then run the installed Claude Code and Codex behavioral lanes required by the changed surface with `verify-open-pstack`. A sync that edits the verifier runs from the candidate head with `--self-test`. A run above the session limit needs `--wide ""`. Unit tests alone are not a release gate. -9. Merge the reviewed PR before tagging the next open-pstack release. Publish the release with `python3 scripts/release.py publish` from a clean checkout of `main`. +9. Merge the reviewed PR before tagging the next open-pstack release. Publish a release only when every PR merged since the previous release has an Unfret verdict on its merged head and each material finding among them has a merged fix-forward PR. Publish it with `python3 scripts/release.py publish` from a clean checkout of `main`. Cursor's version and open-pstack's version are independent. Cursor's version identifies the imported content; open-pstack's version identifies the cross-harness distribution.