Skip to content
Merged
Show file tree
Hide file tree
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
8 changes: 4 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ developer --types--> commands: /surface-plan /surface-execute /surface-status
plan folder: journal.jsonl + documents git: branch, merge base, other branches

install.py --copies--> host .claude/skills/surface-*/ and .claude/agents/surface-*.md
host CI --runs----> surface-status check --require conformant
```

- **Commands** are three skills the developer types. `/surface-plan` explores, interviews, has the plan drafted and writes it with its gates, has the blueprint drawn and cross-checked, then commits, pushes and opens a draft pull request. It then stays in the conversation and takes amendments there, as it does after a block during planning, since a reply that approves nothing needs no new launch. `/surface-execute` approves the drafted revision by being launched, then dispatches slices, gate runs, reviews and fixes. It puts a plan change proposal and the ceiling to the developer in the conversation: a refusal or a resumption goes on in the session, while an accepted change or an amendment goes to `/surface-plan`, since this command cannot load it and never writes the plan. `/surface-status` reports, runs the conformity check, and abandons a plan on confirmation.
- **Agents** are four roles of the chain, and one agent built into Claude Code. The `Plan` agent drafts the plan, in the form it chooses: a good plan is what it already does well, so the chain ships no definition for it and holds the plan only to the minimum it reads, the numbered acceptance criteria, the slices behind their markers, each enough for an agent that starts fresh, and the `gates` block ([ADR 0035](docs/adr/0035-plan-drafted-by-the-built-in-plan-agent.md)). It has no write tool: it returns the plan, and `/surface-plan` writes `plan.md` as returned, but for two repairs it makes without a word to the developer, who never reads the plan: a return that arrived escaped, whose markers the script would not read, and a path of the machine. That agent cannot ask the developer anything: a rule a user of the feature would see applied, which neither the specs, an answer of the developer nor the code settles, it settles and marks as an assumption of the plan, and the extractor shows it on the blueprint as an assumption that approving the page confirms, never as settled. Planning gains no step for it: a question sent back would cost the developer a turn. The extractor draws `blueprint.md` from `plan.md`, as long as the feature needs, since the developer reads all of it. A fact is said once in prose on the whole page: the criteria state the rules, and the body shows what no criterion states, as behavior and not as code, since a page that tells a rule three times is a page the developer skims. So an aspect the plan changes is shown by a criterion or in the body, and the checker reads both. Neither the plan nor the blueprint says of its own how a commit is written or signed: a slice of the plan carries what the host's conventions or the developer ask, and the blueprint only what the developer asked, since a commit is written by another agent than the one that drafts the plan, after the blueprint is frozen, and a page that promised its message or its author would turn a commit written another way into a contract break. A blueprint has a fixed frame and a free body: it opens with the idea, the acceptance criteria and the scope, and closes with the sensitive zones, while the extractor cuts the body by what the developer decides separately, and keeps the cut of the previous revision, so that a revision is read against the one before. Inside a section too long to take in at a glance, a title in bold opens each thing the developer would come back for on its own, since a rule with no title over it is found only by reading the section through. No heading carries a number: a section is cited by its title, which an amendment does not move, where a number would shift and send a report to another section. Five aspects are gone through whatever the cut, the data schema, the boundaries, the sequences, the state machines and the algorithms, and one closing line names those the plan leaves alone, since silence must not read as "unchanged". A diagram is drawn where what it shows has a shape that prose flattens, with the existing elements the change attaches to. The checker looks for what the plan does and the blueprint does not show, never for a cut, a short section or a diagram that is not there. The executor carries out a slice or a fix. The reviewer judges the branch against the approved blueprint, writes `conformity.md` when it finds nothing, with the changed files of the critical zones, judges a break an executor suspects, and corrects that list of files when the script refuses it.
- **The state script** derives the state, refuses illegal steps, runs the gates, and answers the skills and the host's CI.
- **The state script** derives the state, refuses illegal steps, runs the gates, and answers the skills.
- **A plan folder**, `docs/plans/<date>-<slug>/` by default, holds the specs, the exploration, the interview, the plan, the blueprint, the reports (`checks/`, `reviews/`, `plan-changes/`, `gates/`), `conformity.md` and `journal.jsonl`. It is the whole state of a plan.
- **The installer** copies the chain into the host, updates it, and checks it for drift.

Expand Down Expand Up @@ -61,7 +60,8 @@ host CI --runs----> surface-status check --require conformant
- Every command sets `disable-model-invocation: true`: launching `/surface-execute` is an approval, which only the developer gives, and no sentence of a conversation approves a revision.
- Each step goes to a fresh agent that reads files, never a conversation, one agent at a time.
- Prompts run every command from the repository root, with paths from there, since the dispatcher and its agents share one shell. They ban neither `cd` nor `git -C`, since a ban gave a session a breach to report to a developer who could do nothing with it, and say nothing else of how an agent writes a command, nor of the tool it changes a file with: a rule that general is not the chain's, the agent judges, and what stops for an approval is the permission mode's call ([ADR 0032](docs/adr/0032-permissions-left-to-the-mode-no-tests-on-the-readme.md)).
- The chain never marks a pull request ready: that triggers the host's CI, so the developer does it, when they want, once the plan is conformant. `conformity.md` is the audit trail the `conformant` event cites, never a required reading.
- The chain acts on nothing of the host's CI: it reads its configuration for the commands that check the project and their order, reads no trigger, asks nothing before a push, and gives the host no job to run, since each would bind the chain to what every host does its own way, and a check prescribed at a moment would decide when a finished plan may leave the plans in progress ([ADR 0037](docs/adr/0037-the-host-ci-left-to-the-host.md)). `check --require conformant` is the developer's command before merging, which a CI can call.
- The chain never marks a pull request ready: what the host runs then is the developer's to start, so the developer does it, when they want, once the plan is conformant. `conformity.md` is the audit trail the `conformant` event cites, never a required reading.
- No test asserts the prose of the README or of these documents ([ADR 0032](docs/adr/0032-permissions-left-to-the-mode-no-tests-on-the-readme.md)).
- No file holds an em dash, and no commit message carries an attribution.

Expand Down Expand Up @@ -89,7 +89,7 @@ The developer reads the blueprint and not the code, except where a mistake would

### Command line contract

Exit code 0 means accepted or passed, 1 refused or failed, 2 a wrong call or an unreadable journal. With `--json`, each answer is one object with `"v": 1`; a field is added without a new version, and renamed or removed only with `"v": 2`. The skills and the host's CI depend on both, and `tests/fixtures/cli/` pins the shapes.
Exit code 0 means accepted or passed, 1 refused or failed, 2 a wrong call or an unreadable journal. With `--json`, each answer is one object with `"v": 1`; a field is added without a new version, and renamed or removed only with `"v": 2`. The skills depend on both, as does a host that calls the check from its CI, and `tests/fixtures/cli/` pins the shapes.

### Install and drift

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Ownership is by namespace: the installer owns `.claude/skills/surface-*/` and `.

## State script

`surface-status` is the only writer of each plan's `journal.jsonl`. State is derived from the journal, never stored, and the script refuses any transition its table does not allow, and any event whose guard fails. Its subcommands (`show`, `resolve`, `record`, `gate`, `abandon`, `commits`, `pr-body`, `check`) serve the developer, the skills and the host project's CI; `--json` is for the skills.
`surface-status` is the only writer of each plan's `journal.jsonl`. State is derived from the journal, never stored, and the script refuses any transition its table does not allow, and any event whose guard fails. Its subcommands (`show`, `resolve`, `record`, `gate`, `abandon`, `commits`, `pr-body`, `check`) serve the developer and the skills, and a host may call `check` from its CI; `--json` is for the skills.

## Text rules

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Two commands, and three moments when the loop hands back to you: the blueprint w

## Going further

- [Guide](docs/guide.md): every step in detail, permissions, the conformity check in your CI, settings, updates.
- [Guide](docs/guide.md): every step in detail, permissions, the conformity check, which your CI can call, settings, updates.
- [ARCHITECTURE.md](ARCHITECTURE.md): how the chain is built, and its invariants.
- [`docs/adr/`](docs/adr/README.md): the decisions whose history matters.
- [CONTRIBUTING.md](CONTRIBUTING.md): to work on the chain itself.
Expand Down
22 changes: 22 additions & 0 deletions docs/adr/0037-the-host-ci-left-to-the-host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 0037. The host's CI left to the host

Status: accepted
Date: 2026-10-08

## Context

The chain had grown three holds on the CI of the host. Before the first push of a branch, `/surface-plan` read what triggers the CI, warned the developer and waited for their agreement when a push or a new pull request would start a run. The guide gave a GitHub Actions workflow that ran the conformity check once the pull request was marked ready, with the depth its checkout needs. And the hand back at conformity said that marking the pull request ready "triggers your CI".

Each bound the chain to what every host does its own way: a CI system, its triggers, its checkout. The warning had to read the configuration of any CI. The workflow was one host's shape, had never run on a hosted repository, and named an action to keep current. The sentence at conformity was false in a host with no CI. And a check prescribed at the moment a pull request is marked ready decided when a finished plan may leave the plans in progress: its folder had to stay on the branch, readable, until the merge.

## Decision

The chain acts on nothing of the host's CI.

`/surface-plan` still reads the CI configuration for what it says of the project, the commands that check it and their order, as it reads a manifest. It reads no trigger, warns of nothing and asks nothing before a push. No prompt says what a push or a ready mark starts. The chain still never marks a pull request ready: what the host runs then is the developer's to start.

`surface-status check --require conformant` stays, as a command the developer runs before merging. The guide says that a CI can call it, with its exit codes and the full history it needs, and gives no workflow. The script goes on answering in the detached checkout a CI makes, and refusing one too shallow to find where the branch left the main branch, which `tests/ci` holds.

## Consequences

In a host whose CI runs on every push, the first push of a plan starts a run nobody announced: that is a configuration of its CI, not a thing the chain guesses. Whether the conformity check runs in a CI, and at which moment, is the developer's call, and so is the moment a finished plan leaves the plans in progress. A plan whose developer declined its first push, when the chain still asked, is pushed by the next launch that finishes its draft: the answer kept in its `interview.md` is no longer read. The end to end tests keep one project with a CI, to hold that planning pushes there without a question, and nothing of the chain is left to exercise on a hosted CI.
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ The current architecture is described in [ARCHITECTURE.md](../../ARCHITECTURE.md
| 0034 | [Gates named by the plan, run by the script](0034-gates-named-by-the-plan-run-by-the-script.md) |
| 0035 | [The plan is drafted by Claude Code's built-in Plan agent, held to the minimum the chain reads](0035-plan-drafted-by-the-built-in-plan-agent.md) |
| 0036 | [Evaluations on real sessions, with a model as the developer and a judge that is checked](0036-evaluations-on-real-sessions.md) |
| 0037 | [The host's CI left to the host](0037-the-host-ci-left-to-the-host.md) |
26 changes: 3 additions & 23 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ In a Claude Code session of your project, run `/surface-plan` with a short descr
- It explores the code the feature touches, then asks you its questions one at a time. Each comes with its options and the one it would pick. Your answers are written down as you give them, and it never invents a business rule to fill a gap. A rule a user of the feature would see applied, an order, a number, a limit, is yours to decide: it asks unless your specs or your code already settle it, and settles alone only what is a matter of implementation.
- It finds the commands that check your project, tests, lint, type checks, where your project states them, and writes them in the plan as its gates. It asks you only if it finds none.
- It has a detailed plan drafted by the `Plan` agent built into Claude Code, then the blueprint drawn from it and cross-checked, so that the blueprint hides nothing the plan does.
- Before the first push, if your CI reacts to a push or to a new PR, it warns you and waits for your agreement.
- It commits, pushes, opens a draft PR that links the blueprint and the plan, and tells you where to read the blueprint and which gates the loop will run.

Everything lands in one folder per plan, `docs/plans/<date>-<slug>/` by default.
Expand Down Expand Up @@ -81,34 +80,15 @@ Conformant means the last review proved every acceptance criterion of the bluepr

The PR description lists, one line each and for information, the decisions the agents took within the contract that you did not see go by: the plan amendments that keep the blueprint true, and the suspected breaks a reviewer dismissed. It lists nothing when there is nothing to list.

When you want, you mark the PR ready for review, which triggers your CI, and you merge. The chain never does it for you. The conformity check belongs in your CI when the PR is marked ready, and in your hands before merging:
When you want, you mark the PR ready for review, and you merge. The chain never does it for you. Before merging, the conformity check is in your hands:

```sh
.claude/skills/surface-status/scripts/surface-status check --require conformant
```

It fails as long as a plan of the branch is neither conformant nor abandoned before its approval, so keep it out of the gates your plans name.

In a GitHub Actions workflow, the check needs the full history of the repository to find where your branch left the main branch. A checkout at depth 1, the default, makes it exit 2 and say so:

```yaml
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

jobs:
conformity:
# A draft pull request waits until it is marked ready.
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: .claude/skills/surface-status/scripts/surface-status check --require conformant
```
It fails as long as a plan of the branch is neither conformant nor abandoned before its approval, so keep it out of the gates your plans name. Exit code 0 means every plan of the branch is conformant, 1 that one is not, 2 that the check could not run.

Exit code 0 means every plan of the branch is conformant, 1 that one is not, 2 that the check could not run.
Your CI can call it too: whether it does, and when, is yours to decide. It needs the full history of the repository, main branch included, to find where your branch left it: in a checkout that is shallow, or that holds your branch alone, it exits with 2.

### At any time: `/surface-status`

Expand Down
Loading
Loading