Skip to content

Commit 18ee59b

Browse files
FasterPHPclaude
andcommitted
feat: add checkpoint discipline proposal for apply-change skill
Adds an OpenSpec change proposal to strengthen the apply-change skill's implementation loop with checkpoint discipline for crash recovery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a0608d0 commit 18ee59b

5 files changed

Lines changed: 224 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-02-22
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
## Context
2+
3+
The apply-change skill instructions originate from a single TypeScript source file:
4+
5+
**`src/core/templates/workflows/apply-change.ts`** — contains two functions:
6+
- `getApplyChangeSkillTemplate()` — generates SKILL.md files for all tools (Claude, Cursor, Windsurf, etc.)
7+
- `getOpsxApplyCommandTemplate()` — generates tool-specific command files (e.g., `.claude/commands/opsx/apply.md`)
8+
9+
Both functions contain identical instruction text for steps 1–7, output formats, and guardrails. The command template has minor wording differences (e.g., `/opsx:continue` instead of `openspec-continue-change`) but the same step 6 and guardrails.
10+
11+
The current step 6 is a flat bullet list: show task → make changes → mark complete → continue. The checkpoint instruction exists only as guardrail #6: "Update task checkbox immediately after completing each task." In practice, agents ignore this because stronger competing signals (system-level parallelism prompts, the "keep going" guardrail) override it.
12+
13+
## Goals / Non-Goals
14+
15+
**Goals:**
16+
17+
- Make checkpoint-after-task the dominant behavioural signal in step 6, not a buried guardrail
18+
- Include the recovery rationale inline so agents understand *why*, not just *what*
19+
- Add git commit as part of the checkpoint so recovery survives process crashes (not just context window loss)
20+
- Define tightly-coupled grouping clearly enough that agents can apply it without ambiguity
21+
- Promote the checkpoint guardrail to first position with anti-batching language
22+
- Address the PR noise problem by noting that per-task commits should be bundled for review
23+
24+
**Non-Goals:**
25+
26+
- Changing the schema, artifact graph, or CLI commands
27+
- Adding new CLI flags or configuration options for checkpoint behaviour
28+
- Modifying other skills (archive, verify, sync, etc.)
29+
- Enforcing checkpoint discipline programmatically (e.g., via hooks or validation) — this change relies on instruction strength only
30+
- Changing commit message conventions beyond the task-referencing format
31+
32+
## Decisions
33+
34+
### 1. Replace step 6 rather than adding a new step
35+
36+
**Decision:** Rewrite step 6 in-place with the checkpoint loop structure (announce → implement → mark complete → commit → confirm).
37+
38+
**Rationale:** Adding a separate "checkpoint" step would break the existing step numbering and create ambiguity about whether the old loop or new checkpoint step takes precedence. Replacing step 6 keeps the structure clean and makes the checkpoint integral to the loop, not an afterthought.
39+
40+
**Alternative considered:** Adding a step 6b or inserting between steps 6 and 7. Rejected because it fragments the loop logic across multiple steps.
41+
42+
### 2. Use sub-steps (a–e) within step 6
43+
44+
**Decision:** Structure the checkpoint loop as lettered sub-steps: a. Announce, b. Implement, c. Mark complete, d. Commit, e. Confirm.
45+
46+
**Rationale:** The sequential lettering makes the order unambiguous. Agents can reference specific sub-steps. The hard gate ("steps c and d must happen before starting the next task") is clear when the steps are named.
47+
48+
**Alternative considered:** Keeping the bullet-list format. Rejected because bullets imply optional/unordered items, which is exactly the misinterpretation we're fixing.
49+
50+
### 3. Define "tightly coupled" by coherence, not by file proximity
51+
52+
**Decision:** Define tightly-coupled tasks as "changes that would be incoherent if split" with the example of a class change and its corresponding test.
53+
54+
**Rationale:** File-based rules (e.g., "tasks touching the same file") are too brittle — a refactoring task and a feature task might touch the same file but be logically independent. Coherence is the right criterion because it maps to what a reviewer would consider a single logical unit.
55+
56+
**Alternative considered:** Strict one-task-per-checkpoint with no grouping. Rejected as impractical — splitting a function change from its test would create commits that fail tests, which is worse for recovery.
57+
58+
### 4. Cap grouped tasks at 2–3
59+
60+
**Decision:** "Never let more than 2–3 tasks accumulate without a checkpoint."
61+
62+
**Rationale:** This is deliberately imprecise. A hard cap of exactly 2 or exactly 3 would invite rules-lawyering. The range signals "this should be rare and small" while leaving room for agent judgment. The typical case is 1 task per checkpoint; grouping is the exception.
63+
64+
### 5. Include git commit in the checkpoint, not just file update
65+
66+
**Decision:** The checkpoint includes `git add -A && git commit -m "task N: <short description>"`.
67+
68+
**Rationale:** Marking `[x]` in the tasks file without committing only protects against context window loss (the agent can re-read the file). It does not protect against process crashes, machine failures, or session timeouts — the primary failure modes we're addressing. A git commit makes progress durable on disk.
69+
70+
**Alternative considered:** Making the commit optional or configurable. Rejected because the whole point is crash recovery — without the commit, the checkpoint is incomplete.
71+
72+
### 6. Update both template functions in apply-change.ts
73+
74+
**Decision:** Edit `getApplyChangeSkillTemplate()` and `getOpsxApplyCommandTemplate()` in `apply-change.ts` with matching changes.
75+
76+
**Rationale:** Both functions contain the same step 6 and guardrails text. They must stay in sync — the skill template drives SKILL.md generation and the command template drives slash command generation.
77+
78+
### 7. Add MR bundling guidance to the guardrails
79+
80+
**Decision:** Add a guardrail note that per-task commits should be bundled into a single merge request by default, with user confirmation.
81+
82+
**Rationale:** Per-task commits are a recovery mechanism, not a review unit. Without this guidance, the granular commit history could produce noisy PRs. The user confirmation step ensures the agent doesn't automatically submit without the user's awareness.
83+
84+
**Alternative considered:** Squashing commits automatically. Rejected because squashing destroys the recovery trail before the user has confirmed the work is complete, and some users may prefer the granular history.
85+
86+
## Risks / Trade-offs
87+
88+
**[Agents may still batch despite stronger instructions]** → The instructions are the strongest lever available without programmatic enforcement. The combination of rationale, sub-step structure, hard gate language, and first-position guardrail is significantly stronger than the current single bullet. If agents still batch, the next step would be hooks or validation — but that's a separate change.
89+
90+
**[Per-task commits add overhead]** → Each checkpoint adds a git commit, which takes time and creates history. This is intentional — the overhead is the cost of recoverability. The tightly-coupled grouping rule mitigates this by allowing natural units to be committed together.
91+
92+
**[`git add -A` may stage unintended files]** → This matches the existing convention in the upstream proposal. Projects with sensitive files should have `.gitignore` configured appropriately. Changing to explicit file staging would require the agent to track which files each task modified, adding complexity disproportionate to the risk.
93+
94+
**[Commit message format is prescriptive]** → The `task N: <short description>` format is simple and traceable. Projects with strict commit conventions can override this via project config rules.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Why
2+
3+
The apply-change skill's task loop is the only point where implementation progress becomes durable. If a session crashes mid-implementation, only tasks marked `[x]` and committed to git can be recovered. The current instructions tell agents to "update task checkbox immediately after completing each task," but in practice agents routinely batch multiple tasks and mark them all complete at the end — meaning a crash loses all progress.
4+
5+
This happens because the instruction competes with stronger signals: coding agents are prompted to maximise parallelism and throughput, no rationale is given for *why* checkpointing matters, no anti-pattern is stated, and the "keep going through tasks until done" guardrail reinforces batching over discipline. The fix is to make checkpoint semantics explicit, explain the recovery purpose, and structure the loop as a hard gate rather than a soft preference.
6+
7+
## What Changes
8+
9+
- **Restructure step 6** of the apply-change skill from a simple loop into a checkpoint-disciplined loop with announce → implement → mark complete → commit → confirm stages
10+
- **Explain the "why"** — frame the tasks file as a recovery log so agents understand the purpose of per-task updates
11+
- **Add git commit to the checkpoint** — marking `[x]` without committing still loses progress on crash; committing makes recovery durable
12+
- **Allow pragmatic grouping** — tightly-coupled tasks (e.g., a class change and its test) can be checkpointed together, but unrelated tasks must not be batched
13+
- **Default to squashing per-task commits in merge requests** — per-task commits are a recovery mechanism, not a review unit. When creating a merge request for a change, all per-task commits should be included in a single PR by default, with the user prompted to confirm before submission
14+
- **Reorder and strengthen guardrails** — move the checkpoint rule to the top, add an explicit anti-batching statement, remove the weaker "update task checkbox immediately" bullet it replaces
15+
16+
## Capabilities
17+
18+
### New Capabilities
19+
20+
- `apply-checkpoint-discipline`: Defines the checkpoint loop contract for the apply-change skill — when to checkpoint, what a checkpoint includes (mark complete + commit), how tightly-coupled tasks may be grouped, and the maximum batch size
21+
22+
### Modified Capabilities
23+
24+
_(none — no existing spec requirements are changing)_
25+
26+
## Impact
27+
28+
- **Files changed:** `src/core/templates/workflows/apply-change.ts` — both `getApplyChangeSkillTemplate()` and `getOpsxApplyCommandTemplate()`. These are the source templates from which all tool-specific SKILL.md and command files are generated during `openspec init`.
29+
- **No breaking changes:** The checkpoint loop is a refinement of the existing loop, not a new workflow step
30+
- **Trade-off: resilience vs parallel efficiency.** Committing after each task adds overhead and prevents agents from parallelising unrelated tasks. This is intentional — the tasks file exists for recoverability, and that value is lost if progress isn't persisted. The tightly-coupled grouping rule (up to 2–3 tasks) provides a pragmatic escape valve so agents aren't forced to split changes that would be incoherent apart.
31+
- **Trade-off: granular commits vs reviewable PRs.** Per-task commits create a clean recovery trail but would produce noisy merge requests if submitted as-is. The default behaviour should be to bundle all per-task commits for a change into a single PR, with user confirmation before submission.
32+
- **No schema changes** — this modifies skill instructions only, not the artifact graph or schema definitions
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# apply-checkpoint-discipline Specification
2+
3+
## Purpose
4+
Define the checkpoint contract for the apply-change implementation loop, ensuring task progress is durable and recoverable after session crashes while allowing pragmatic grouping of tightly-coupled tasks.
5+
6+
## ADDED Requirements
7+
8+
### Requirement: Checkpoint after each task
9+
The apply-change skill SHALL checkpoint after completing each task (or small group of tightly-coupled tasks) before starting the next. A checkpoint consists of marking the task complete in the tasks file and committing the changes to git.
10+
11+
#### Scenario: Single task completion
12+
- **WHEN** an agent completes a single pending task
13+
- **THEN** it SHALL mark the task `[x]` in the tasks file
14+
- **AND** run `git add -A && git commit` with a message referencing the task number and a short description
15+
- **AND** output a confirmation (e.g., "Task N complete") before starting the next task
16+
17+
#### Scenario: Session crash after checkpoint
18+
- **WHEN** a session crashes after a task has been checkpointed (marked `[x]` and committed)
19+
- **THEN** the completed task's changes SHALL be recoverable from the git history
20+
- **AND** the tasks file SHALL accurately reflect which tasks were completed
21+
22+
#### Scenario: Session crash before checkpoint
23+
- **WHEN** a session crashes while a task is in progress but before the checkpoint
24+
- **THEN** only the current in-progress task's changes are lost
25+
- **AND** all previously checkpointed tasks remain recoverable
26+
27+
### Requirement: Recovery rationale in instructions
28+
The apply-change skill instructions SHALL explain that the tasks file serves as a recovery log and that progress is only durable once marked complete and committed. This rationale SHALL appear in the implementation step, not only in guardrails.
29+
30+
#### Scenario: Agent reads implementation step
31+
- **WHEN** an agent processes the apply-change skill's implementation step
32+
- **THEN** the step SHALL state that the tasks file is a recovery log
33+
- **AND** SHALL state that progress is only durable once tasks are marked complete and committed
34+
35+
### Requirement: Tightly-coupled task grouping
36+
The apply-change skill SHALL allow tightly-coupled tasks to be checkpointed together in a single commit, but SHALL prohibit batching unrelated tasks.
37+
38+
#### Scenario: Grouping a class change and its test
39+
- **WHEN** two tasks are tightly coupled (e.g., a class modification and its corresponding unit test)
40+
- **THEN** the agent MAY implement both before checkpointing
41+
- **AND** SHALL commit them together in a single checkpoint
42+
43+
#### Scenario: Unrelated tasks
44+
- **WHEN** two tasks are not tightly coupled (their changes would be coherent if split)
45+
- **THEN** the agent SHALL checkpoint each task separately
46+
- **AND** SHALL NOT batch them into a single commit
47+
48+
#### Scenario: Maximum batch size
49+
- **WHEN** an agent groups tightly-coupled tasks
50+
- **THEN** no more than 2–3 tasks SHALL accumulate without a checkpoint
51+
52+
### Requirement: Checkpoint guardrail prominence
53+
The checkpoint rule SHALL appear as the first guardrail in the apply-change skill and SHALL include an explicit anti-batching statement.
54+
55+
#### Scenario: Guardrail ordering
56+
- **WHEN** the apply-change skill's guardrails are rendered
57+
- **THEN** the checkpoint guardrail SHALL be the first item in the list
58+
59+
#### Scenario: Anti-batching statement
60+
- **WHEN** the checkpoint guardrail is rendered
61+
- **THEN** it SHALL explicitly state that large numbers of task completions MUST NOT be batched together
62+
63+
### Requirement: Per-task commits bundled in merge requests
64+
Per-task commits SHALL be bundled into a single merge request per change by default. The agent SHALL prompt the user for confirmation before creating the merge request.
65+
66+
#### Scenario: Creating a merge request after implementation
67+
- **WHEN** all tasks for a change are complete and the user requests a merge request
68+
- **THEN** the agent SHALL include all per-task commits for that change in a single merge request by default
69+
- **AND** SHALL prompt the user to confirm before submitting
70+
71+
#### Scenario: User opts for separate merge requests
72+
- **WHEN** the user declines the default bundling and requests separate merge requests
73+
- **THEN** the agent SHALL accommodate the user's preference
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## 1. Update step 6 in getApplyChangeSkillTemplate()
2+
3+
- [ ] 1.1 Replace step 6 heading from "Implement tasks (loop until done or blocked)" to "Implement tasks (checkpoint after each)"
4+
- [ ] 1.2 Add recovery rationale paragraph ("The tasks file is a recovery log...")
5+
- [ ] 1.3 Replace flat bullet list with lettered sub-steps: a. Announce, b. Implement, c. Mark complete, d. Commit, e. Confirm
6+
- [ ] 1.4 Add hard gate statement ("Steps c and d must happen before starting the next task")
7+
- [ ] 1.5 Add tightly-coupled definition and maximum batch size (2–3 tasks)
8+
9+
## 2. Update guardrails in getApplyChangeSkillTemplate()
10+
11+
- [ ] 2.1 Replace "Update task checkbox immediately after completing each task" with checkpoint guardrail as first item, including anti-batching statement
12+
- [ ] 2.2 Add MR bundling guardrail: per-task commits bundled into a single PR by default, with user confirmation
13+
- [ ] 2.3 Reorder remaining guardrails (keep going, read context files, etc.) after the checkpoint guardrail
14+
15+
## 3. Apply matching changes to getOpsxApplyCommandTemplate()
16+
17+
- [ ] 3.1 Replace step 6 with identical checkpoint loop (matching task group 1)
18+
- [ ] 3.2 Replace guardrails with identical updated guardrails (matching task group 2)
19+
20+
## 4. Verify
21+
22+
- [ ] 4.1 Run `pnpm run build` to confirm TypeScript compiles without errors
23+
- [ ] 4.2 Run `pnpm test` to confirm no test regressions

0 commit comments

Comments
 (0)