From 68180393b2051c78a5f220d6722f52ca45e2a222 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Tue, 22 Sep 2026 10:12:38 -0700 Subject: [PATCH 1/2] docs(iterate-pr): describe the OpenSpec label and tip warning The section predated openspec-label.yml, openspec-tracking.yml and openspec-sweep.yml and told an agent that nothing reports on an unarchived change. It now describes the label predicate, how the tip warning is decided and where it is emitted, what to do on a tip PR versus a non-tip PR, and why 'archive on landing' is not available. --- .agents/skills/iterate-pr/SKILL.md | 58 +++++++++++++++++++----------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/.agents/skills/iterate-pr/SKILL.md b/.agents/skills/iterate-pr/SKILL.md index 9ca381f3..d626c05e 100644 --- a/.agents/skills/iterate-pr/SKILL.md +++ b/.agents/skills/iterate-pr/SKILL.md @@ -281,26 +281,44 @@ Run `${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.cjs` to get structured failure This applies even when the check itself has already concluded. A review bot can report its check `success` while its comment is still the placeholder it posted on trigger — check `summary.review_in_progress` from `fetch_pr_feedback.cjs` (step 2), not the body text or the check's conclusion, and wait for it to drop to 0 before treating that bot's feedback as final. -#### No PR check asks whether the OpenSpec change is archived - -A change is archived exactly once, at the END of the work, so an unarchived -directory under `openspec/changes/` is the normal state of a pull request. -There is no gate for it, in either direction. - -There were two, and both measured the wrong thing. A PR-time gate had to infer -stack position to avoid firing on in-flight work, and a check that is -expected-red on most of a stack teaches people to ignore red. Moving it to -`main` fixed the false positives and introduced a worse problem: a -forward-merging stack leaves its change directory on `main` until the final -slice, so `main` ran red for the whole time the stack was draining. A signal -that is expected to be red is not a signal. - -What is worth detecting is work that stalled and was abandoned. Neither gate -measured that; both measured "work is in progress". A replacement is a separate -piece of design. - -Practically, on a PR: archive when the PR is the last in the chain, and leave -the change directory alone otherwise. Nothing will fail either way. +#### The OpenSpec archive signal is a label and a warning, not a check + +`openspec-label.yml` runs on every pull request with no `branches:` filter. It +reads the head tree and asks one question: does any directory other than +`archive/` exist under `openspec/changes/`? If one does, the PR gets the +`Open OpenSpec` label. The predicate asks nothing about stack position, so +every PR in a stack gets the same answer, and the label clears when a branch in +the stack archives the change. + +The tip gets more. After labelling, the workflow runs +`gh pr list --state open --base `; a count of zero means nothing is +stacked above, so this is the last branch that can archive before the change +reaches `main`. It then emits a `::warning::` annotation and a job-summary block +listing one `pnpm openspec archive ` line per change. That is an +annotation and a summary, not a PR comment, so read the `OpenSpec Label` job +rather than waiting for something to appear in the conversation. A fork PR gets +neither the label nor the tip warning: the token is read-only and the fork's +head branch cannot be a base here, which would report every fork PR as the tip. + +**On a tip PR, treat that warning as feedback to act on before merge.** Sync the +deltas into `openspec/specs/`, archive, push. "Archive on landing" is not an +option, because `main` only takes PRs: a change that lands unarchived needs a +second PR to correct it. On `taskless/marketing`, #50 and #51 landed unarchived +and the fix could not be pushed directly, since the `main` ruleset has no bypass +actors, so #52 was a third PR to do what the tip should have done. + +**On a non-tip PR, leave the change directory alone and let the label stand.** +Archiving mid-stack rewrites `openspec/specs/` before the work it describes has +landed. + +Nothing fails either way, and that is deliberate: a gate here was expected-red +for as long as a forward-merging stack took to drain, and a signal expected to +be red is not a signal. The channel is the label and an issue. Once no open PR's +diff touches the change directory, the next push to `main` has +`openspec-tracking.yml` open `OpenSpec: is unarchived on main`, labelled +`Open OpenSpec`, which closes itself when the change reaches +`openspec/changes/archive/`. `openspec-sweep.yml` escalates on that issue daily +once the directory has gone seven days without git activity. #### Stacked PRs: two other check behaviours worth knowing From 923c84ad898fd427f0f6f765e68592a5a6ff88bd Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Tue, 22 Sep 2026 13:39:14 -0700 Subject: [PATCH 2/2] docs(iterate-pr): correct the sweep escalation cadence The section said openspec-sweep.yml escalates daily once a change directory has gone seven days without git activity. The cron is daily, but openspec-tracking.cjs throttles on the issue's own idle time (idleDays < staleDays continues), so an escalation lands at most once per seven-day window and any comment on the thread defers the next one. --- .agents/skills/iterate-pr/SKILL.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.agents/skills/iterate-pr/SKILL.md b/.agents/skills/iterate-pr/SKILL.md index d626c05e..6e3ccc68 100644 --- a/.agents/skills/iterate-pr/SKILL.md +++ b/.agents/skills/iterate-pr/SKILL.md @@ -317,8 +317,11 @@ be red is not a signal. The channel is the label and an issue. Once no open PR's diff touches the change directory, the next push to `main` has `openspec-tracking.yml` open `OpenSpec: is unarchived on main`, labelled `Open OpenSpec`, which closes itself when the change reaches -`openspec/changes/archive/`. `openspec-sweep.yml` escalates on that issue daily -once the directory has gone seven days without git activity. +`openspec/changes/archive/`. `openspec-sweep.yml` runs on a daily cron but +escalates on that issue at most once per seven-day window: it acts only once the +directory has gone seven days without git activity, and then only if the issue +itself has been idle that long, so any comment on the thread defers the next +escalation by another window. #### Stacked PRs: two other check behaviours worth knowing