From bf5099e39fdb5d7bde2adc84f49ea93afd7463e9 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 11 Aug 2026 15:53:53 -0500 Subject: [PATCH] fix(apply): surface deferred scope instead of silently simplifying tasks (#1530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 * 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 * 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 --------- Co-authored-by: Claude Opus 4.8 --- .changeset/apply-surface-deferred-scope.md | 5 +++ skills/openspec-apply-change/SKILL.md | 3 ++ src/core/templates/workflows/apply-change.ts | 3 ++ .../templates/apply-defer-guardrail.test.ts | 36 +++++++++++++++++++ .../templates/skill-templates-parity.test.ts | 6 ++-- 5 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 .changeset/apply-surface-deferred-scope.md create mode 100644 test/core/templates/apply-defer-guardrail.test.ts diff --git a/.changeset/apply-surface-deferred-scope.md b/.changeset/apply-surface-deferred-scope.md new file mode 100644 index 00000000..daafa643 --- /dev/null +++ b/.changeset/apply-surface-deferred-scope.md @@ -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. diff --git a/skills/openspec-apply-change/SKILL.md b/skills/openspec-apply-change/SKILL.md index cd00a8b4..e9dc213b 100644 --- a/skills/openspec-apply-change/SKILL.md +++ b/skills/openspec-apply-change/SKILL.md @@ -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 diff --git a/src/core/templates/workflows/apply-change.ts b/src/core/templates/workflows/apply-change.ts index 931f6e7b..d7ebe2f4 100644 --- a/src/core/templates/workflows/apply-change.ts +++ b/src/core/templates/workflows/apply-change.ts @@ -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 diff --git a/test/core/templates/apply-defer-guardrail.test.ts b/test/core/templates/apply-defer-guardrail.test.ts new file mode 100644 index 00000000..51fc85f3 --- /dev/null +++ b/test/core/templates/apply-defer-guardrail.test.ts @@ -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); + }); +}); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index e0a19957..8c267c61 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -41,14 +41,14 @@ const EXPECTED_FUNCTION_HASHES: Record = { 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 = { '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',