Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5417a05
release: v0.49.1
matthewevans Aug 10, 2026
907d219
fix(coverage): traverse direct effect payloads (#7137)
matthewevans Aug 10, 2026
d7d5895
docs(skill): review non-frontier PRs on merit (#7138)
matthewevans Aug 10, 2026
0880698
ship/sealed pack opening (#7149)
matthewevans Aug 10, 2026
82318cc
fix(parser): keep shared-event subject disjunctions as one trigger (#…
JacobWoodson Aug 10, 2026
55eb20b
fix(ai): stop proposing strategically vacuous loop-shortcut Shortens …
lgray Aug 10, 2026
6b432b5
chore: refresh metagame feeds (#7203)
matthewevans Aug 10, 2026
ee76bc5
release: v0.50.0
Aug 10, 2026
9dc3970
Add Exuberant Wolfbear (#7204)
keloide Aug 10, 2026
6bad30e
docs: name the phase-engine package in every cargo invocation (#7205)
lgray Aug 10, 2026
231b016
ci: split Rust test job into four nextest shards (#7206)
lgray Aug 10, 2026
fb15645
docs(contributing): stop rejecting frontier models with opaque model …
matthewevans Aug 10, 2026
58d7043
Fix Emperor of Bones (#7200)
traemyn Aug 10, 2026
1bb6c1d
chore(card-data): refresh MTGJSON token & subtype catalogs (#7209)
matthewevans Aug 10, 2026
97f7c77
fix(triage): drain unpublished backlog by default (#7214)
matthewevans Aug 10, 2026
7f86377
Add Magmatic Scorchwing (#7210)
keloide Aug 10, 2026
58b3d35
Fix Nadu trigger cap across granted abilities
traemyn Aug 10, 2026
68e5adb
Preserve trigger cap provenance across state restore
traemyn Aug 10, 2026
74446c5
Retire trigger grants across zone changes
traemyn Aug 10, 2026
3c78962
Fix grant lifecycle replay boundaries
traemyn Aug 11, 2026
38a863f
Keep ledger helper before tests
traemyn Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 92 additions & 14 deletions .claude/skills/bug-triage/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ bun scripts/sync-bug-reports.ts extract
bun scripts/sync-bug-reports.ts triage # also emits triage/triage-delta.jsonl
bun scripts/sync-bug-reports.ts render

# Review ONLY the delta — the reports new since the last fetch. NEVER scan the
# full triage-items.jsonl looking for "what's new"; that is how reports get
# missed. `triage` prints the delta + a "reports to resolve" list (every
# non-skip item).
# Full-drain queue — REQUIRED unless the user explicitly asks for latest-delta-only.
# `pending` is the durable unpublished backlog; it is not a diagnostic optional extra.
bun scripts/sync-bug-reports.ts pending --limit=500

# Review the delta as the authoritative latest-fetch slice. Never infer
# latest work from the full triage-items.jsonl; this review is in addition to
# the default `pending` backlog drain.
bun scripts/sync-bug-reports.ts delta # re-emit delta without re-classifying

# CRITICAL — the script does NOT dedup against GitHub and does NOT pre-judge
Expand Down Expand Up @@ -70,6 +73,66 @@ gh issue view <N> --repo phase-rs/phase --json subIssues,title,body
gh issue list --repo phase-rs/phase --label "collector" --state closed --limit 50 --json number,title,closedAt
```

## Publish Scope — Default Is Full Backlog Drain

**Default scope.** When the user asks to run bug triage, publish GitHub issues,
or otherwise does not explicitly restrict the request, resolve both the latest
delta **and** every thread from `pending`. Delta-only is permitted only for an
explicit request such as “triage the latest fetch only.”

The delta prevents historical reports from being reprocessed; `pending` finds
the distinct failure mode where a real report was never recorded in
`published_threads`. Do not treat a clean delta as a terminal condition.

1. Run fetch → extract → triage → render, then snapshot `triage-delta.jsonl`.
2. Run `bun scripts/sync-bug-reports.ts pending --limit=500` and record the
total. For every listed thread, read its live Discord history, apply the
Handled-tag gate, deduplicate, and publish/link/mark it in the same manner
as a delta thread.
3. Work in small, sequential publish batches. `publish` rechecks the live
`Handled` tag, so use it for any new issue after the operator decides it is
not a duplicate.
4. Finish only when `pending` prints `pending (shown): 0 of 0`.

At session start, fetch one paginated, all-state GitHub issue inventory with
`number`, `title`, `body`, `url`, and `state`; build local lookups by
`phase-discord-thread-id` and `report_id`. Consult that inventory for every
thread, including closed issues. Use GitHub Search only for the remaining
plausible semantic duplicates, reuse results across threads, and never make
one Search API request per backlog thread.

Create the delta snapshot before performing any delta audit:

```bash
DELTA_SNAPSHOT=$(mktemp /tmp/bug-triage-delta.XXXXXX)
cp triage/triage-delta.jsonl "$DELTA_SNAPSHOT"
shasum -a 256 "$DELTA_SNAPSHOT"
jq -s '[.[] | select(.proposed_action != "skip") | .thread_id] | unique | length' \
"$DELTA_SNAPSHOT"
```

### Classifier False-Negative Gate

Before publishing a delta snapshot, audit its `skip` items grouped by thread.
A short starter message, attachment-only message, or follow-up classification
does not make the **thread** non-bug. Read the complete thread whenever its
title or combined messages describe a failure. Reclassify that thread inline
as NEW, DUP-OF, APPEND-TO, or MARK-HANDLED; do not silently leave it outside
the cycle because one message was labelled `skip`.

Add every skip-classified thread reclassified as NEW, DUP-OF, or APPEND-TO to
the cycle resolution set. Final close-out and counts use the union of snapshot
non-skip threads and these reclassified false negatives.

```bash
# Show the complete context for each skip-classified delta thread before
# accepting the classifier's verdict.
jq -r 'select(.proposed_action == "skip") | .thread_id' \
"$DELTA_SNAPSHOT" | sort -u | while read tid; do
bun scripts/sync-bug-reports.ts read --thread="$tid"
done
```

## Delta Completion Invariant — Every Non-Skip Item, Same Cycle

**Hard rule.** A fetch cycle is NOT done until *every* delta item with a
Expand Down Expand Up @@ -158,9 +221,10 @@ are also regenerated by other operators, so never audit a moving
`triage-delta.jsonl` and then report the result as final.

At the start of every publish pass, copy the delta to a session-local snapshot
and record its SHA-256 and its non-skip unique-thread count. Use **that
snapshot** for all selections and close-out checks. Immediately before calling
the cycle done, re-hash the live delta; if it changed, take a new snapshot and
and record its SHA-256 and its non-skip unique-thread count. Set
`DELTA_SNAPSHOT` to that path; use **that snapshot** for all selections,
false-negative audits, and close-out checks. Immediately before calling the
cycle done, re-hash the live delta; if it changed, take a new snapshot and
repeat the audit for the new reports.

For every non-skip thread in the snapshot with `issue_number > 0`, independently
Expand All @@ -170,11 +234,19 @@ body without that metadata, or a mapping to a PR is an orphan: deduplicate it
against GitHub and then file/link it with the required Discord write-back. Do
not use a count of `published_threads` entries as proof.

The final operator report must state all four counts: snapshot non-skip threads,
new GitHub issues, reconciled/mark-handled threads, and GitHub-metadata
mismatches. A successful pass has **zero** orphan threads and **zero** metadata
mismatches (except a documented archived/deleted Discord thread, where the
GitHub issue remains valid but Discord write-back is impossible).
The final operator report must state all five counts: snapshot non-skip threads,
backlog threads discovered by `pending`, new GitHub issues,
reconciled/mark-handled threads, and GitHub-metadata mismatches. A successful
pass always ends with **zero** orphan threads, **zero** metadata mismatches,
and `pending (shown): 0 of 0`. Archived/deleted threads are exceptions only
to Discord write-back: independently validate the existing issue, then run:

```bash
bun scripts/sync-bug-reports.ts mark-handled --thread=<id> \
--notes='tracked by #N; Discord archived/deleted, write-back impossible'
```

Document the exception in the operator report.

### Discord `Handled` Tag — Hard Pre-Publish Gate

Expand Down Expand Up @@ -741,10 +813,16 @@ Also at this step: audit open `collector` trackers. When a resync pass closes ch
```bash
bun scripts/sync-bug-reports.ts fetch
```
If new messages exist, re-run extract → triage → render. Then review **`triage/triage-delta.jsonl`** — and ONLY that file. It contains exactly the triage items from the latest fetch window (messages with `fetched_at > prev_fetch_at`). Do not re-process every historical Discord thread as new work, and do not hand-filter `triage-items.jsonl` by snowflake/timestamp guesses — that is how orphaned reports get missed. The raw store and dashboards regenerate from the full message archive for determinism, but GitHub issue work is delta-based:
If new messages exist, re-run extract → triage → render and review
**`triage/triage-delta.jsonl`** for the latest-fetch window; do not hand-filter
`triage-items.jsonl` by snowflake/timestamp guesses. Regardless of whether the
fetch found new messages, unless the user explicitly requested delta-only
triage, drain `pending` to zero as required by *Publish Scope — Default Is Full
Backlog Drain*. The raw store and dashboards regenerate from the full message
archive for determinism:
- The `triage` command prints the delta breakdown + a **"reports to resolve" list**: every non-skip delta item. Each must be filed (`publish --thread=`), linked/deduped to an existing issue, or `mark-handled`. Never ignore one.
- Use Discord cursors in `triage/sync-state.json` and the `fetch` command's "New messages fetched" count to decide whether there is new Discord input.
- Treat `report_id` (`discord:<thread_id>:<message_id>:<item_index>`) as the stable idempotency key. The script does NOT dedup against GitHub — **you** are the arbiter. Before creating work, search GitHub issues/comments for that report id or thread/message URL.
- Treat `report_id` (`discord:<thread_id>:<message_id>:<item_index>`) as the stable idempotency key. The script does NOT dedup against GitHub — **you** are the arbiter. Consult the session's all-state local GitHub inventory for the report id or thread/message URL; use Search only for unresolved plausible semantic duplicates.
- Your manual dedupe checks MUST include closed issues: use `--state all`, not `--state open`. Closed `status:fixed-unreleased`, `stale`, `duplicate`, and `wont-fix` issues are still authoritative triage records and must prevent duplicate creation.
- When you confirm a delta report already has a GitHub issue (open or closed), do not refile it — `mark-handled --notes="dup of #N"` (closed) or link/comment it (open). Recreate only if the Discord thread contains a newer unmatched `report_id`.
- Existing GitHub issues, comments, labels, and sub-issue parentage are the persistent triage state. Update those records instead of rediscovering or refiling old reports.
Expand Down
8 changes: 7 additions & 1 deletion .claude/skills/pr-review-loop/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ At sweep start, read `.agents/pr-review/campaign-hotspots.toml` if present (lead
**Public-disposition completion gate:** local events and a zero `review` count are necessary but insufficient. For every PR inspected or reviewed this sweep, live-check GitHub before declaring it processed. A substantive blocker must have a current-head formal `CHANGES_REQUESTED` review, unless an existing current-head requested-changes review already states the same unresolved finding. A non-substantive hold must have a current-head maintainer comment explaining the exact external condition and next step. An approval/enqueue must be live-verified as `APPROVED` plus the expected queue/auto-merge state. Do not let a local `blocked`/`held` event substitute for a visible maintainer response.
3. Every packet (and `recommend` output) carries an advisory `contributor` block — standing, scrutiny, `scrutiny_reasons`, `recurrence`, `first_contribution` — derived from the local event log plus `contributor_standing` overrides; it is `null` only when the PR has no author login. Scale review depth by it: `first_contribution` → full evidence bar, and point the author at the `docs/AI-CONTRIBUTOR.md` gates in the first review comment; `elevated` → dig specifically into the recurring signals named in `scrutiny_reasons`; `maintainer_attention` → include the contributor in the sweep report for the maintainer. `light_touch_eligible` permits a lighter pass only while scrutiny is `normal`.

**Model tier is a hard gate; the agentic harness is a scrutiny signal.** `docs/AI-CONTRIBUTOR.md` §0.1.1 accepts **Frontier tier only**. A PR whose canonical `Model:` line names a non-Frontier model (`claude-sonnet-*`, `claude-haiku-*`, `composer-*`, `gpt-5-4` or below, `codex-5-4` or below), or whose commits show one, is closed as out-of-policy without an implementation review. Separately, a `Co-authored-by: Cursor <cursoragent@cursor.com>` trailer is **not** grounds to close on its own — it raises scrutiny to the full evidence bar and forfeits light-touch, because the observed failure mode is CI-as-correction-loop rather than the harness itself. Judge the PR on its merits; good work under Cursor is still good work.
**Model declarations route review depth; they do not authorize an automatic close.** Frontier remains the requested contribution floor in `docs/AI-CONTRIBUTOR.md`, but a non-Frontier declaration or trailer is an elevated-scrutiny signal, never a `decline` by itself. Do not close a PR merely because its stated model falls below that floor.

For a non-Frontier PR, require the full evidence bar: accurate model declaration, concrete `/engine-implementer` (or a specific `not-applicable`) method record, current-head Gate A and final `review-impl` evidence when applicable, relevant anchors, required verification, and a manual implementation review. A PR with that evidence must be reviewed on its merits. If the evidence is incomplete, request the missing proof or hold it; do not replace the review with a model-policy closure. An explicit maintainer message inviting continued review or asking the contributor to proceed also routes the current head to review, even if older prose describes a model-tier close.

A `Co-authored-by: Cursor <cursoragent@cursor.com>` trailer remains a scrutiny signal, not a closure reason. The behavioural CI-farming pattern — pushing unverified diagnostic commits or deleting passing assertions to obtain green CI — is review evidence on the current diff, not account-level or batch-close evidence. This maintainer-review rule intentionally overrides older automatic-close wording in `docs/AI-CONTRIBUTOR.md` for this workflow.

Check both surfaces, and read what actually matched before acting — a bare `rg cursor` hits `WordCursor` and other identifiers:

Expand Down Expand Up @@ -253,6 +257,8 @@ When the user explicitly authorizes maintainer actions, the loop may pass clean

When delegating labeling, require the handler to classify by the actual diff: ordinary additive engine, parser, or tooling capabilities are **enhancement** by default, even when they touch several files. Reserve **feature** for a genuinely broad mechanic or product change spanning distinct subsystems (for example, a combo workflow that jointly changes engine rules, priority handling, UI, and AI). Never infer `feature` from a `feat:` title, file count, or author identity.

When delegating a PR with a non-Frontier declaration, state explicitly that the declaration is an elevated-scrutiny signal, **not** a decline authorization. The handler must review the current head against the evidence bar above and may only close for a diff-based or independently enforced reason.

Do not perform GitHub mutations from this skill except ordinary review/comment actions explicitly required by the current sweep and policy-configured deferral labels. Approval, queue, update-branch, dequeue, and merge execution still belongs to `pr-contribution-handler`.

## Drift Rule
Expand Down
8 changes: 6 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@

## LLM

Model: <actual Frontier model identifier | not-applicable (no LLM)>
Model: <actual Frontier model identifier or reported name | not-applicable (no LLM)>
Tier: <Frontier | not-applicable (no LLM)>
Thinking: <high | max | not-applicable (no LLM)>

<!-- Keep Model and Tier as exact, standalone canonical lines. AI-assisted PRs
must report their actual Frontier model, Tier: Frontier, and thinking level. -->
must report their actual Frontier model, Tier: Frontier, and thinking level.
If your harness does not expose an exact model identifier, report the name it
does give you — e.g. "Model: gpt-5.6-sol (via GitHub Copilot; canonical id not
exposed)" — rather than guessing an identifier. See AI-CONTRIBUTOR.md
§0.1.1. -->

## Implementation method (required)

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ jobs:
./scripts/check-test-card-data-load.sh "$BASE"

rust-test:
name: Rust tests (shard ${{ matrix.shard }}/2)
name: Rust tests (shard ${{ matrix.shard }}/4)
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard: [1, 2]
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -174,7 +174,7 @@ jobs:
# caught before it lands on the branch that feeds staging/preview.
env:
PROPTEST_CASES: ${{ github.event_name == 'pull_request' && '32' || '256' }}
run: cargo nextest run --profile ci --partition count:${{ matrix.shard }}/2 --workspace --exclude phase-tauri --exclude mtgish-import --features engine/proptest --status-level fail --final-status-level fail
run: cargo nextest run --profile ci --partition count:${{ matrix.shard }}/4 --workspace --exclude phase-tauri --exclude mtgish-import --features engine/proptest --status-level fail --final-status-level fail

card-data-gate:
name: Card data (generate, validate, coverage)
Expand Down
Loading
Loading