Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
13 changes: 10 additions & 3 deletions src/commands/workflow/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,11 @@ export async function generateApplyInstructions(

if (missingArtifacts.length > 0) {
state = 'blocked';
instruction = `Cannot apply this change yet. Missing artifacts: ${missingArtifacts.join(', ')}.\nUse the openspec-continue-change skill to create the missing artifacts first.`;
const specsHint =
missingArtifacts.includes('specs') && context.schemaName === 'spec-driven'
? '\nDelta specs must exist under changes/<name>/specs/ before implementation.'
: '';
instruction = `Cannot apply this change yet. Missing artifacts: ${missingArtifacts.join(', ')}.${specsHint}\nUse the openspec-continue-change skill to create the missing artifacts first.`;
} else if (tracksFile && !tracksFileExists) {
// Tracking file configured but doesn't exist yet
const tracksFilename = path.basename(tracksFile);
Expand Down Expand Up @@ -467,10 +471,13 @@ export async function applyInstructionsCommand(options: ApplyInstructionsOptions

if (options.json) {
console.log(JSON.stringify({ ...instructions, root: toRootOutput(root) }, null, 2));
return;
} else {
printApplyInstructionsText(instructions);
}

printApplyInstructionsText(instructions);
if (instructions.state === 'blocked') {
process.exitCode = 1;
}
} catch (error) {
spinner?.stop();
throw error;
Expand Down
19 changes: 16 additions & 3 deletions test/commands/artifact-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,11 +440,25 @@ describe('artifact-workflow CLI commands', () => {
const result = await runCLI(['instructions', 'apply', '--change', 'blocked-apply'], {
cwd: tempDir,
});
expect(result.exitCode).toBe(0);
expect(result.exitCode).toBe(1);
expect(result.stdout).toContain('Blocked');
expect(result.stdout).toContain('Missing artifacts: tasks');
});

it('does not block apply when tasks exist but delta specs are missing', async () => {
await createTestChange('missing-specs-apply', ['proposal', 'design', 'tasks']);

const result = await runCLI(
['instructions', 'apply', '--change', 'missing-specs-apply', '--json'],
{ cwd: tempDir }
);
expect(result.exitCode).toBe(0);

const json = JSON.parse(result.stdout);
expect(json.state).toBe('ready');
expect(json.missingArtifacts).toBeUndefined();
});

it('outputs JSON for apply instructions', async () => {
await createTestChange('json-apply', ['proposal', 'design', 'specs', 'tasks']);

Expand Down Expand Up @@ -578,7 +592,6 @@ apply:
expect(result.exitCode).toBe(0);

const json = JSON.parse(result.stdout);
// spec-driven schema has apply block with requires: [tasks], so should be ready
expect(json.schemaName).toBe('spec-driven');
expect(json.state).toBe('ready');
});
Expand Down Expand Up @@ -624,7 +637,7 @@ artifacts:
env: { XDG_DATA_HOME: userDataDir },
}
);
expect(result.exitCode).toBe(0);
expect(result.exitCode).toBe(1);

const json = JSON.parse(result.stdout);
// Without apply block, fallback requires ALL artifacts - second is missing
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/tmp-init/openspec/changes/c1/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 1. Implementation

- [ ] 1.1 Complete the change