Skip to content
Merged
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ The proposal states which of these the change is, and why. Decide it while writi

The deciding question between forward and down is only this: **can each unit reach production on its own without breaking anything?** If landing unit 1 alone would leave `check` broken, tests failing, or a migration half-applied, the answer is no and the stack merges down. Do not assume forward because it is tidier. Verify it, since "each unit is safe" is a claim about behavior, not intent.

Note how this interacts with archiving: a change is archived exactly once, on whichever PR is the tip. **No check asks about that anywhere any more.** A PR-time gate could only guess at stack position, and guessed wrong often enough to be ignored; a `main`-only gate replaced it and turned `main` red for the entire time a forward-merging stack was draining, which is a red that means "work is in progress" rather than "something is wrong". A signal that is expected to be red is not a signal. Archiving is now a step you perform on the tip, and the thing worth detecting (work that stalled and was abandoned) is not what either gate measured.
Note how this interacts with archiving: a change is archived exactly once, on whichever PR is the tip. **No check gates that anywhere any more, though the state is reported.** A PR-time gate could only guess at stack position, and guessed wrong often enough to be ignored; a `main`-only gate replaced it and turned `main` red for the entire time a forward-merging stack was draining, which is a red that means "work is in progress" rather than "something is wrong". A signal that is expected to be red is not a signal. Archiving is a step you perform on the tip, and what replaced the gates reports instead of failing: an `Open OpenSpec` label and a tip warning on pull requests, and on `main` a tracking issue and a stale-work sweep, which is the abandoned work neither gate measured.

### One changeset, at the bottom of the stack, grown as the stack grows

Expand Down Expand Up @@ -148,7 +148,7 @@ This is not hypothetical. `@taskless/cli/reference.json` had its `tests` field c
Two rules follow, and they pull in opposite directions:

- **A workflow that must run everywhere carries no `branches:` filter at all.** Lint, typecheck, and tests have no interest in where a PR eventually merges. `validate.yml`, `changeset.yml`, and `stack-breadcrumb.yml` all carry no filter, which is why they kept running on #103. If you add such a workflow, also name `ready_for_review` in `types:`. It is not in the default set (`opened`/`synchronize`/`reopened`), so without it a draft marked ready gets no fresh run until someone happens to push again.
- **A workflow whose correctness depends on "is this the PR that merges to `main`" must determine that itself**, from the base ref or by resolving stack position, and cannot lean on the `on:` filter to scope it. Better still, ask a question that does not depend on stack position at all: `changeset.yml` diffs against `main` rather than against its base. The archive check tried the other route, moving off pull requests onto `main`, and was removed instead, because on `main` it reported an in-flight stack as a fault.
- **A workflow whose correctness depends on "is this the PR that merges to `main`" must determine that itself**, from the base ref or by resolving stack position, and cannot lean on the `on:` filter to scope it. Better still, ask a question that does not depend on stack position at all: `changeset.yml` diffs against `main` rather than against its base. The archive check tried the other route, moving off pull requests onto `main`, and was removed instead, because on `main` it reported an in-flight stack as a fault. What replaced it on `main`, `openspec-tracking.yml`, kept the position and changed the question: a change is reported only when no open pull request's diff touches its directory, which is a file-path question about open pull requests rather than a reconstruction of stack lineage. On the pull-request side, `openspec-label.yml` takes the `changeset.yml` route instead: it reads the head tree and asks nothing about stack position at all.

The shared point: the `on:` filter is not a reliable answer to "where does this PR land." Let the workflow run, and decide inside it.

Expand Down Expand Up @@ -286,7 +286,7 @@ This happens when the **parent** PR is merged with `--delete-branch`: deleting t

- **Projects-classic deprecation** breaks some GraphQL-backed `gh` commands (e.g. `gh pr reopen`). Workaround: use the REST API for PR state changes (`gh api --method PATCH .../pulls/<n> -f state=open`).
- **`gh pr update-branch` may not exist** in the installed `gh`; update locally (`git merge origin/main` on the up-to-date remote branch) and push.
- **No OpenSpec archive check at all.** An unarchived change directory is expected on a pull request AND on `main` while a forward-merging stack drains. Neither is a failure, and nothing reports it. Archive the change on the tip slice; a replacement that measures abandoned work rather than in-progress work is a separate piece of design.
- **Nothing fails over an unarchived change, but it is reported.** An unarchived change directory is expected on a pull request AND on `main` while a forward-merging stack drains. Neither is a failure: no check status reports it, nothing blocks a merge, and `openspec-label.yml` is deliberately not a step in `Validate`, so an unarchived change cannot gate a nightly publish. It is still reported. That workflow reads the head tree on every pull request, asking only whether any directory other than `archive/` exists under `openspec/changes/`, and applies 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 once a branch archives the change. On the tip, where no open PR bases on the head branch, it adds a `::warning::` annotation and a job summary naming the `pnpm openspec archive` line to run; read the `OpenSpec Label` job for it, since nothing is posted to the conversation. On `main`, `openspec-tracking.yml` opens a tracking issue for an unarchived change no open pull request is working on, and `openspec-sweep.yml` escalates it once the directory has gone seven days without activity. Archive the change on the tip slice, and treat the tip warning as feedback to act on rather than as a gate.
- **Clean up local branches** once the stack lands: `git fetch --prune`, then delete the branches that merged (`git branch --merged main`).

## A spec delta REPLACES a requirement, it does not patch it
Expand Down
Loading