fix(apply): surface deferred scope instead of silently simplifying tasks (#1530)

* fix(apply): surface deferred scope instead of silently simplifying tasks

The /opsx:apply guidance told agents to keep going through tasks but never
told them what to do when a task turned out harder than the spec assumed.
Agents absorbed the extra scope silently — narrowing, deferring, or
declaring partial work done — and marked the task complete anyway (#1529).

Add a pause trigger and two guardrails to the shared apply instructions
(rendered identically by the skill and command surfaces): surface the added
scope and ask rather than simplify to fit, and mark a task complete only
when it is fully implemented as specified. Regenerate the static skill and
parity-hash pins. Guidance text only — no behavioral code paths change.

Fixes #1529

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(apply): anchor deferred-scope guidance to spec scope, not effort

Adversarial review flagged that "more complex than the spec assumed" could
be read as "takes more effort than I guessed," which would make an agent
pause on nearly every task. Retie the pause trigger and guardrail to a
change in scope — work beyond what the spec/tasks describe, or dropping /
narrowing / deferring specified behavior — so normal implementation effort
does not trip it. Regenerate the static skill and parity pins; update the
regression test and changeset to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(apply): name the "accept exceptions" pattern in deferred-scope guidance

Issue #1529's concrete example is an agent that found three exceptions to a
"zero writes on the main thread" task, declared them "accepted," and moved
on. Add "accept exceptions to" to the pause trigger's verb list so the
guidance names that exact failure mode, not just drop/narrow/defer. Behavior
is otherwise unchanged; regenerate the static skill and parity pins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(apply): assert the deferred-scope guidance requires pausing

CodeRabbit noted the guardrail test checked that added scope is surfaced but
not that the agent pauses, so it could pass if the workflow reported scope and
kept going. Assert the exact "surface the added scope and pause" phrasing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Clay Good
2026-08-11 20:53:53 +00:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 9ae75c86ef
commit bf5099e39f
5 changed files with 50 additions and 3 deletions
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---
Apply workflow now tells agents to surface unexpected scope instead of hiding it. When a task needs work beyond what the spec describes, the `/opsx:apply` skill and command guidance direct the agent to pause and report the added scope rather than silently narrowing, deferring, or simplifying away specified behavior, and to mark a task complete only when its specified behavior is fully implemented. Fixes #1529.
+3
View File
@@ -99,6 +99,7 @@ Implement tasks from an OpenSpec change.
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- A task needs work beyond what the spec and tasks describe, or you are tempted to drop, narrow, defer, or accept exceptions to specified behavior to make it fit → surface the added scope and ask; do not absorb it silently
- Error or blocker encountered → report and wait for guidance
- User interrupts
@@ -169,6 +170,8 @@ What would you like to do?
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- When a task needs work beyond what the spec describes, surface the added scope and pause - never silently narrow, defer, or simplify away specified behavior
- Only mark a task `- [x]` when its specified behavior is fully implemented, not when it is partially done or deferred
- Use contextFiles from CLI output, don't assume specific file names
- Do not use context or operation guidance as proof that a task is complete
- Apply relevant project context; report conflicts with controlling workflow inputs
@@ -107,6 +107,7 @@ ${STORE_SELECTION_GUIDANCE}
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- A task needs work beyond what the spec and tasks describe, or you are tempted to drop, narrow, defer, or accept exceptions to specified behavior to make it fit → surface the added scope and ask; do not absorb it silently
- Error or blocker encountered → report and wait for guidance
- User interrupts
@@ -177,6 +178,8 @@ What would you like to do?
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- When a task needs work beyond what the spec describes, surface the added scope and pause - never silently narrow, defer, or simplify away specified behavior
- Only mark a task \`- [x]\` when its specified behavior is fully implemented, not when it is partially done or deferred
- Use contextFiles from CLI output, don't assume specific file names
- Do not use context or operation guidance as proof that a task is complete
- Apply relevant project context; report conflicts with controlling workflow inputs
@@ -0,0 +1,36 @@
import { describe, it, expect } from 'vitest';
import {
getApplyInstructions,
getApplyChangeSkillTemplate,
getOpsxApplyCommandTemplate,
} from '../../../src/core/templates/workflows/apply-change.js';
// #1529: agents were silently simplifying or deferring work mid-apply and
// marking tasks done anyway. The apply instructions must tell the agent to
// surface unexpected scope instead of absorbing it, on both surfaces.
describe('apply instructions surface deferred scope (#1529)', () => {
const instructions = getApplyInstructions();
it('tells the agent to surface added scope rather than defer or simplify', () => {
expect(instructions).toContain('surface the added scope');
expect(instructions).toContain('beyond what the spec describes');
expect(instructions).toMatch(/never silently narrow, defer/);
});
it('requires pausing, not just reporting and continuing', () => {
// The agent must hand control back, not surface the scope and press on.
expect(instructions).toContain('surface the added scope and pause');
});
it('forbids marking a task complete when it is only partially done', () => {
expect(instructions).toMatch(
/Only mark a task .* when its specified behavior is fully implemented/
);
});
it('carries the same guidance on both the skill and command surfaces', () => {
const needle = 'surface the added scope';
expect(getApplyChangeSkillTemplate().instructions).toContain(needle);
expect(getOpsxApplyCommandTemplate().content).toContain(needle);
});
});
@@ -41,14 +41,14 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = {
getExploreSkillTemplate: 'fec38ba01c5c20695aca0ec7eff78c26e278ead21459cab8ec1562af51053427',
getNewChangeSkillTemplate: '935f6335e2d4b7d1bd4f0538c88386350c25e8b16e11b627556262229583ca51',
getContinueChangeSkillTemplate: 'ed41e2356af7aad6ef760f60fad19c6843cefe436d8f90084dcba4dbc6bf7272',
getApplyChangeSkillTemplate: '0de84d3e414c0bc72b21a47384257a1b3bc754336538e245db55af307d7eda99',
getApplyChangeSkillTemplate: 'a8d2529741849723ef160726648173e8ca8b42f5bb6f3d89ec547268adce2846',
getFfChangeSkillTemplate: 'fc2a45a08533ee9c7ab30fdab5f832b7d440070048e2a153f03db1620dc379bb',
getSyncSpecsSkillTemplate: 'd43b112a3c74bc951b094d220c8e75cca26bb00640d404b78af0752af1ff7bd9',
getOnboardSkillTemplate: 'a9f6134b187ec4f3a5aa6c7c181e51a15fec11b7ac1044a076fdfe79b47fbc80',
getOpsxExploreCommandTemplate: 'e2d470148708a9070675edddd1e783f1c71c96625d08cff4fe7a9994e0d292c0',
getOpsxNewCommandTemplate: '08e784e52ac2c146975a874257c589d88e93efbd83dc4d79253c8525f5c3064f',
getOpsxContinueCommandTemplate: 'ae964cd00f6ca332fd7f9428a577ade75be279f50431d5f60ece8172e8d1a4b1',
getOpsxApplyCommandTemplate: 'd27ad905657dd3797571eccee2b6416495fa9b39759d36b43a9871a301757979',
getOpsxApplyCommandTemplate: '860b55e4ffc055bb6f7339eeb65eaa695e09be7738dff2260726403647a57a18',
getOpsxFfCommandTemplate: '012610f85576a7055dfec2aaabba6bfc245454ce91fb6214587ae9316dc2b864',
getArchiveChangeSkillTemplate: '5ef19163f73997fdda1c69dc8bca710c16c50b052b481821d916f4084bb42a64',
getBulkArchiveChangeSkillTemplate: '03cc44a0ce9bdb3ba2668a9d43946596308901600aa29a728c4a71fc76e86de3',
@@ -69,7 +69,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = {
'openspec-explore': '80109dec3abf1505ab1037f7196baac4fcdf175ca954411e8d439e5da881bf62',
'openspec-new-change': '579d432771703f947a331a6ed288bf9c6660ca015fcd376d76f19b6ac7683082',
'openspec-continue-change': '5c34be8194cdb4c5158335e47aece71143e8a22bfb4179dba47fd8aaf436d395',
'openspec-apply-change': 'a1c79d1104255f7655df120d3ebf362cc14a2bb23ae6e857ba430dea2f8bc8bc',
'openspec-apply-change': '2709759f101b455dbd93779005f6692966a37c7c71336af9dc5752906046d4f8',
'openspec-ff-change': '19315644df7c582d920acfb67f3c500ca4e06fccc900265b3ac39621d85f7cdb',
'openspec-sync-specs': '6e85521de10858bb020885eb657aa843e5746b2f09c846aa44545694f456cda9',
'openspec-archive-change': '019d580a13eee5892cc9233a899919b572a3abfc6a05c1f0aabf9c4ba9bf3d4d',