agile-context-engineering 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +7 -1
- package/README.md +16 -12
- package/agents/ace-code-discovery-analyst.md +245 -245
- package/agents/ace-code-integration-analyst.md +248 -248
- package/agents/ace-code-reviewer.md +375 -375
- package/agents/ace-product-owner.md +365 -361
- package/agents/ace-project-researcher.md +606 -606
- package/agents/ace-technical-application-architect.md +315 -315
- package/bin/install.js +587 -173
- package/hooks/ace-check-update.js +15 -14
- package/hooks/ace-statusline.js +30 -12
- package/hooks/hooks.json +14 -0
- package/package.json +3 -2
- package/shared/lib/ace-core.js +53 -0
- package/shared/lib/ace-core.test.js +308 -308
- package/shared/lib/ace-story.test.js +250 -250
- package/skills/execute-story/SKILL.md +116 -110
- package/skills/execute-story/script.js +13 -27
- package/skills/execute-story/script.test.js +261 -261
- package/skills/execute-story/story-template.xml +451 -451
- package/skills/execute-story/workflow.xml +3 -1
- package/skills/help/SKILL.md +71 -69
- package/skills/help/script.js +32 -35
- package/skills/help/script.test.js +183 -183
- package/skills/help/workflow.xml +14 -3
- package/skills/init-coding-standards/SKILL.md +91 -72
- package/skills/init-coding-standards/coding-standards-template.xml +531 -531
- package/skills/init-coding-standards/script.js +50 -59
- package/skills/init-coding-standards/script.test.js +70 -70
- package/skills/init-coding-standards/workflow.xml +1 -1
- package/skills/map-cross-cutting/SKILL.md +126 -89
- package/skills/map-cross-cutting/workflow.xml +1 -1
- package/skills/map-guide/SKILL.md +126 -89
- package/skills/map-guide/workflow.xml +1 -1
- package/skills/map-pattern/SKILL.md +125 -89
- package/skills/map-pattern/workflow.xml +1 -1
- package/skills/map-story/SKILL.md +180 -127
- package/skills/map-story/templates/tech-debt-index.xml +125 -125
- package/skills/map-story/workflow.xml +2 -2
- package/skills/map-subsystem/SKILL.md +155 -111
- package/skills/map-subsystem/script.js +51 -60
- package/skills/map-subsystem/script.test.js +68 -68
- package/skills/map-subsystem/templates/subsystem-architecture.xml +343 -343
- package/skills/map-subsystem/templates/subsystem-structure.xml +234 -234
- package/skills/map-subsystem/workflow.xml +1173 -1173
- package/skills/map-sys-doc/SKILL.md +125 -90
- package/skills/map-sys-doc/workflow.xml +1 -1
- package/skills/map-system/SKILL.md +103 -85
- package/skills/map-system/script.js +75 -84
- package/skills/map-system/script.test.js +73 -73
- package/skills/map-system/templates/system-structure.xml +177 -177
- package/skills/map-system/templates/testing-framework.xml +283 -283
- package/skills/map-system/workflow.xml +667 -667
- package/skills/map-walkthrough/SKILL.md +140 -92
- package/skills/map-walkthrough/workflow.xml +457 -457
- package/skills/plan-backlog/SKILL.md +93 -75
- package/skills/plan-backlog/script.js +121 -136
- package/skills/plan-backlog/script.test.js +83 -83
- package/skills/plan-backlog/workflow.xml +1348 -1348
- package/skills/plan-feature/SKILL.md +99 -76
- package/skills/plan-feature/feature-template.xml +361 -361
- package/skills/plan-feature/script.js +131 -148
- package/skills/plan-feature/script.test.js +80 -80
- package/skills/plan-feature/workflow.xml +1 -1
- package/skills/plan-product-vision/SKILL.md +91 -75
- package/skills/plan-product-vision/product-vision-template.xml +227 -227
- package/skills/plan-product-vision/script.js +51 -60
- package/skills/plan-product-vision/script.test.js +69 -69
- package/skills/plan-product-vision/workflow.xml +337 -337
- package/skills/plan-story/SKILL.md +125 -102
- package/skills/plan-story/script.js +18 -49
- package/skills/plan-story/story-template.xml +8 -1
- package/skills/plan-story/workflow.xml +17 -1
- package/skills/research-external-solution/SKILL.md +120 -107
- package/skills/research-external-solution/external-solution-template.xml +832 -832
- package/skills/research-external-solution/script.js +229 -238
- package/skills/research-external-solution/script.test.js +134 -134
- package/skills/research-external-solution/workflow.xml +657 -657
- package/skills/research-integration-solution/SKILL.md +121 -98
- package/skills/research-integration-solution/integration-solution-template.xml +1015 -1015
- package/skills/research-integration-solution/script.js +223 -231
- package/skills/research-integration-solution/script.test.js +134 -134
- package/skills/research-integration-solution/workflow.xml +711 -711
- package/skills/research-story-wiki/SKILL.md +101 -92
- package/skills/research-story-wiki/script.js +223 -231
- package/skills/research-story-wiki/script.test.js +138 -138
- package/skills/research-story-wiki/story-wiki-template.xml +194 -194
- package/skills/research-story-wiki/workflow.xml +473 -473
- package/skills/research-technical-solution/SKILL.md +131 -103
- package/skills/research-technical-solution/script.js +223 -231
- package/skills/research-technical-solution/script.test.js +134 -134
- package/skills/research-technical-solution/technical-solution-template.xml +1025 -1025
- package/skills/research-technical-solution/workflow.xml +761 -761
- package/skills/review-story/SKILL.md +99 -100
- package/skills/review-story/script.js +8 -16
- package/skills/review-story/script.test.js +169 -169
- package/skills/review-story/story-template.xml +451 -451
- package/skills/review-story/workflow.xml +1 -1
- package/skills/update/SKILL.md +65 -53
- package/skills/update/workflow.xml +21 -5
|
@@ -1,261 +1,261 @@
|
|
|
1
|
-
const { describe, it, before, after } = require('node:test');
|
|
2
|
-
const assert = require('node:assert');
|
|
3
|
-
const { execSync } = require('child_process');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
|
|
8
|
-
const SCRIPT = path.join(__dirname, 'script.js');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Create a minimal ACE project structure in a temp directory.
|
|
12
|
-
*/
|
|
13
|
-
function createTestProject() {
|
|
14
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ace-test-'));
|
|
15
|
-
|
|
16
|
-
// .ace/config.json
|
|
17
|
-
const aceDir = path.join(tmpDir, '.ace');
|
|
18
|
-
fs.mkdirSync(aceDir, { recursive: true });
|
|
19
|
-
fs.writeFileSync(path.join(aceDir, 'config.json'), JSON.stringify({
|
|
20
|
-
version: '0.1.0',
|
|
21
|
-
projectName: 'test-project',
|
|
22
|
-
model_profile: 'quality',
|
|
23
|
-
commit_docs: true,
|
|
24
|
-
github: { enabled: false },
|
|
25
|
-
}, null, 2));
|
|
26
|
-
|
|
27
|
-
// .ace/settings.json
|
|
28
|
-
fs.writeFileSync(path.join(aceDir, 'settings.json'), JSON.stringify({
|
|
29
|
-
model_profile: 'quality',
|
|
30
|
-
commit_docs: true,
|
|
31
|
-
agent_teams: false,
|
|
32
|
-
github_project: { enabled: false, gh_installed: false, repo: '', project_number: null, owner: '' },
|
|
33
|
-
}, null, 2));
|
|
34
|
-
|
|
35
|
-
return tmpDir;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Create a story file in the test project.
|
|
40
|
-
*/
|
|
41
|
-
function createStoryFile(tmpDir, relPath, content) {
|
|
42
|
-
const fullPath = path.join(tmpDir, relPath);
|
|
43
|
-
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
44
|
-
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
45
|
-
return relPath;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function runScript(subcommand, args, cwd) {
|
|
49
|
-
return execSync(`node "${SCRIPT}" ${subcommand} ${args}`, {
|
|
50
|
-
cwd,
|
|
51
|
-
encoding: 'utf-8',
|
|
52
|
-
timeout: 10000,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function cleanup(tmpDir) {
|
|
57
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
describe('execute-story script', () => {
|
|
63
|
-
|
|
64
|
-
describe('init', () => {
|
|
65
|
-
let tmpDir;
|
|
66
|
-
|
|
67
|
-
before(() => { tmpDir = createTestProject(); });
|
|
68
|
-
after(() => { cleanup(tmpDir); });
|
|
69
|
-
|
|
70
|
-
it('returns valid JSON with environment detection for a story file', () => {
|
|
71
|
-
const storyContent = [
|
|
72
|
-
'# S1: Add Login Button',
|
|
73
|
-
'**Feature**: F1 User Auth | **Epic**: E1 Platform',
|
|
74
|
-
'**Status**: Refined | **Size**: 3 | **Sprint**: — | **Link**: —',
|
|
75
|
-
'',
|
|
76
|
-
'## User Story',
|
|
77
|
-
'',
|
|
78
|
-
'> As a user,',
|
|
79
|
-
'> I want to click a login button,',
|
|
80
|
-
'> so that I can access my account.',
|
|
81
|
-
'',
|
|
82
|
-
'## Description',
|
|
83
|
-
'',
|
|
84
|
-
'Adds a login button to the header.',
|
|
85
|
-
'',
|
|
86
|
-
'## Acceptance Criteria',
|
|
87
|
-
'',
|
|
88
|
-
'### Scenario: Click login button',
|
|
89
|
-
'',
|
|
90
|
-
'**Given** the user is on the homepage',
|
|
91
|
-
'**When** they click "Login"',
|
|
92
|
-
'**Then** they see the login form',
|
|
93
|
-
'',
|
|
94
|
-
'## Technical Solution',
|
|
95
|
-
'',
|
|
96
|
-
'### Architecture',
|
|
97
|
-
'',
|
|
98
|
-
'Simple button component in the header.',
|
|
99
|
-
'',
|
|
100
|
-
'### Implementation Plan',
|
|
101
|
-
'',
|
|
102
|
-
'1. Create LoginButton component',
|
|
103
|
-
'2. Add to Header',
|
|
104
|
-
].join('\n');
|
|
105
|
-
|
|
106
|
-
const storyPath = createStoryFile(
|
|
107
|
-
tmpDir,
|
|
108
|
-
'.ace/artifacts/product/e1-platform/f1-user-auth/s1-add-login-button/s1-add-login-button.md',
|
|
109
|
-
storyContent
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
const result = JSON.parse(runScript('init', storyPath, tmpDir));
|
|
113
|
-
|
|
114
|
-
assert.ok(result.executor_model, 'should have executor_model');
|
|
115
|
-
assert.ok(result.reviewer_model, 'should have reviewer_model');
|
|
116
|
-
assert.strictEqual(result.story_valid, true, 'story should be valid');
|
|
117
|
-
assert.strictEqual(result.story_source, 'file');
|
|
118
|
-
assert.strictEqual(result.story.id, 'S1');
|
|
119
|
-
assert.strictEqual(result.story.title, 'Add Login Button');
|
|
120
|
-
assert.strictEqual(result.story.status, 'Refined');
|
|
121
|
-
assert.strictEqual(result.has_acceptance_criteria, true);
|
|
122
|
-
assert.strictEqual(result.acceptance_criteria_count, 1);
|
|
123
|
-
assert.strictEqual(result.has_technical_solution, true);
|
|
124
|
-
assert.ok(result.paths, 'should have computed paths');
|
|
125
|
-
assert.ok(result.paths.story_file.includes('s1-add-login-button'));
|
|
126
|
-
assert.ok(result.paths.product_backlog);
|
|
127
|
-
assert.ok(result.paths.coding_standards);
|
|
128
|
-
assert.strictEqual(result.has_story_file, true);
|
|
129
|
-
assert.strictEqual(typeof result.commit_docs, 'boolean');
|
|
130
|
-
assert.strictEqual(typeof result.has_git, 'boolean');
|
|
131
|
-
assert.strictEqual(typeof result.agent_teams, 'boolean');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('returns invalid when story has no AC', () => {
|
|
135
|
-
const storyContent = [
|
|
136
|
-
'# S2: No AC Story',
|
|
137
|
-
'**Feature**: F1 Test Feature | **Epic**: E1 Test Epic',
|
|
138
|
-
'**Status**: Todo | **Size**: 3 | **Sprint**: — | **Link**: —',
|
|
139
|
-
'',
|
|
140
|
-
'## Description',
|
|
141
|
-
'',
|
|
142
|
-
'A story without acceptance criteria.',
|
|
143
|
-
].join('\n');
|
|
144
|
-
|
|
145
|
-
const storyPath = createStoryFile(
|
|
146
|
-
tmpDir,
|
|
147
|
-
'.ace/artifacts/product/e1-test-epic/f1-test-feature/s2-no-ac/s2-no-ac.md',
|
|
148
|
-
storyContent
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const result = JSON.parse(runScript('init', storyPath, tmpDir));
|
|
152
|
-
|
|
153
|
-
assert.strictEqual(result.story_valid, true);
|
|
154
|
-
assert.strictEqual(result.has_acceptance_criteria, false);
|
|
155
|
-
assert.strictEqual(result.acceptance_criteria_count, 0);
|
|
156
|
-
assert.strictEqual(result.has_technical_solution, false);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('handles non-existent story file gracefully', () => {
|
|
160
|
-
const result = JSON.parse(runScript('init', 'nonexistent/story.md', tmpDir));
|
|
161
|
-
|
|
162
|
-
assert.strictEqual(result.story_valid, false);
|
|
163
|
-
assert.ok(result.story_error.includes('not found'));
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('returns invalid with no story parameter', () => {
|
|
167
|
-
const result = JSON.parse(runScript('init', '', tmpDir));
|
|
168
|
-
|
|
169
|
-
assert.strictEqual(result.story_valid, false);
|
|
170
|
-
assert.ok(result.story_error);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
describe('resolve-model', () => {
|
|
175
|
-
let tmpDir;
|
|
176
|
-
|
|
177
|
-
before(() => { tmpDir = createTestProject(); });
|
|
178
|
-
after(() => { cleanup(tmpDir); });
|
|
179
|
-
|
|
180
|
-
it('returns a model string with --raw', () => {
|
|
181
|
-
const result = runScript('resolve-model', 'ace-executor --raw', tmpDir).trim();
|
|
182
|
-
assert.match(result, /^(opus|sonnet|haiku)$/);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('returns JSON without --raw', () => {
|
|
186
|
-
const result = JSON.parse(runScript('resolve-model', 'ace-executor', tmpDir));
|
|
187
|
-
assert.ok(result.model);
|
|
188
|
-
assert.strictEqual(result.agent, 'ace-executor');
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('returns correct model for reviewer', () => {
|
|
192
|
-
const result = runScript('resolve-model', 'ace-code-reviewer --raw', tmpDir).trim();
|
|
193
|
-
assert.match(result, /^(opus|sonnet|haiku)$/);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('update-state', () => {
|
|
198
|
-
let tmpDir;
|
|
199
|
-
|
|
200
|
-
before(() => { tmpDir = createTestProject(); });
|
|
201
|
-
after(() => { cleanup(tmpDir); });
|
|
202
|
-
|
|
203
|
-
it('updates story status in the story file', () => {
|
|
204
|
-
const storyContent = [
|
|
205
|
-
'# S1: Test Story',
|
|
206
|
-
'**Feature**: F1 Test Feature | **Epic**: E1 Test Epic',
|
|
207
|
-
'**Status**: Refined | **Size**: 3 | **Sprint**: — | **Link**: —',
|
|
208
|
-
].join('\n');
|
|
209
|
-
|
|
210
|
-
const storyPath = createStoryFile(
|
|
211
|
-
tmpDir,
|
|
212
|
-
'.ace/artifacts/product/e1-test-epic/f1-test-feature/s1-test-story/s1-test-story.md',
|
|
213
|
-
storyContent
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
const result = JSON.parse(runScript('update-state', `story=${storyPath} status=Done`, tmpDir));
|
|
217
|
-
|
|
218
|
-
assert.strictEqual(result.story_updated, true);
|
|
219
|
-
assert.strictEqual(result.new_status, 'Done');
|
|
220
|
-
|
|
221
|
-
// Verify file was actually updated
|
|
222
|
-
const updated = fs.readFileSync(path.join(tmpDir, storyPath), 'utf-8');
|
|
223
|
-
assert.ok(updated.includes('**Status**: Done'));
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('normalizes InProgress to "In Progress"', () => {
|
|
227
|
-
const storyContent = [
|
|
228
|
-
'# S2: Another Story',
|
|
229
|
-
'**Feature**: F1 Test Feature | **Epic**: E1 Test Epic',
|
|
230
|
-
'**Status**: Refined | **Size**: 2 | **Sprint**: — | **Link**: —',
|
|
231
|
-
].join('\n');
|
|
232
|
-
|
|
233
|
-
const storyPath = createStoryFile(
|
|
234
|
-
tmpDir,
|
|
235
|
-
'.ace/artifacts/product/e1-test-epic/f1-test-feature/s2-another-story/s2-another-story.md',
|
|
236
|
-
storyContent
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
const result = JSON.parse(runScript('update-state', `story=${storyPath} status=InProgress`, tmpDir));
|
|
240
|
-
|
|
241
|
-
assert.strictEqual(result.new_status, 'In Progress');
|
|
242
|
-
|
|
243
|
-
const updated = fs.readFileSync(path.join(tmpDir, storyPath), 'utf-8');
|
|
244
|
-
assert.ok(updated.includes('**Status**: In Progress'));
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe('error handling', () => {
|
|
249
|
-
it('errors on unknown command', () => {
|
|
250
|
-
assert.throws(() => {
|
|
251
|
-
execSync(`node "${SCRIPT}" bogus`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('errors on resolve-model without agent type', () => {
|
|
256
|
-
assert.throws(() => {
|
|
257
|
-
execSync(`node "${SCRIPT}" resolve-model`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
});
|
|
1
|
+
const { describe, it, before, after } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
const SCRIPT = path.join(__dirname, 'script.js');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a minimal ACE project structure in a temp directory.
|
|
12
|
+
*/
|
|
13
|
+
function createTestProject() {
|
|
14
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ace-test-'));
|
|
15
|
+
|
|
16
|
+
// .ace/config.json
|
|
17
|
+
const aceDir = path.join(tmpDir, '.ace');
|
|
18
|
+
fs.mkdirSync(aceDir, { recursive: true });
|
|
19
|
+
fs.writeFileSync(path.join(aceDir, 'config.json'), JSON.stringify({
|
|
20
|
+
version: '0.1.0',
|
|
21
|
+
projectName: 'test-project',
|
|
22
|
+
model_profile: 'quality',
|
|
23
|
+
commit_docs: true,
|
|
24
|
+
github: { enabled: false },
|
|
25
|
+
}, null, 2));
|
|
26
|
+
|
|
27
|
+
// .ace/settings.json
|
|
28
|
+
fs.writeFileSync(path.join(aceDir, 'settings.json'), JSON.stringify({
|
|
29
|
+
model_profile: 'quality',
|
|
30
|
+
commit_docs: true,
|
|
31
|
+
agent_teams: false,
|
|
32
|
+
github_project: { enabled: false, gh_installed: false, repo: '', project_number: null, owner: '' },
|
|
33
|
+
}, null, 2));
|
|
34
|
+
|
|
35
|
+
return tmpDir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a story file in the test project.
|
|
40
|
+
*/
|
|
41
|
+
function createStoryFile(tmpDir, relPath, content) {
|
|
42
|
+
const fullPath = path.join(tmpDir, relPath);
|
|
43
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
44
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
45
|
+
return relPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function runScript(subcommand, args, cwd) {
|
|
49
|
+
return execSync(`node "${SCRIPT}" ${subcommand} ${args}`, {
|
|
50
|
+
cwd,
|
|
51
|
+
encoding: 'utf-8',
|
|
52
|
+
timeout: 10000,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function cleanup(tmpDir) {
|
|
57
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
describe('execute-story script', () => {
|
|
63
|
+
|
|
64
|
+
describe('init', () => {
|
|
65
|
+
let tmpDir;
|
|
66
|
+
|
|
67
|
+
before(() => { tmpDir = createTestProject(); });
|
|
68
|
+
after(() => { cleanup(tmpDir); });
|
|
69
|
+
|
|
70
|
+
it('returns valid JSON with environment detection for a story file', () => {
|
|
71
|
+
const storyContent = [
|
|
72
|
+
'# S1: Add Login Button',
|
|
73
|
+
'**Feature**: F1 User Auth | **Epic**: E1 Platform',
|
|
74
|
+
'**Status**: Refined | **Size**: 3 | **Sprint**: — | **Link**: —',
|
|
75
|
+
'',
|
|
76
|
+
'## User Story',
|
|
77
|
+
'',
|
|
78
|
+
'> As a user,',
|
|
79
|
+
'> I want to click a login button,',
|
|
80
|
+
'> so that I can access my account.',
|
|
81
|
+
'',
|
|
82
|
+
'## Description',
|
|
83
|
+
'',
|
|
84
|
+
'Adds a login button to the header.',
|
|
85
|
+
'',
|
|
86
|
+
'## Acceptance Criteria',
|
|
87
|
+
'',
|
|
88
|
+
'### Scenario: Click login button',
|
|
89
|
+
'',
|
|
90
|
+
'**Given** the user is on the homepage',
|
|
91
|
+
'**When** they click "Login"',
|
|
92
|
+
'**Then** they see the login form',
|
|
93
|
+
'',
|
|
94
|
+
'## Technical Solution',
|
|
95
|
+
'',
|
|
96
|
+
'### Architecture',
|
|
97
|
+
'',
|
|
98
|
+
'Simple button component in the header.',
|
|
99
|
+
'',
|
|
100
|
+
'### Implementation Plan',
|
|
101
|
+
'',
|
|
102
|
+
'1. Create LoginButton component',
|
|
103
|
+
'2. Add to Header',
|
|
104
|
+
].join('\n');
|
|
105
|
+
|
|
106
|
+
const storyPath = createStoryFile(
|
|
107
|
+
tmpDir,
|
|
108
|
+
'.ace/artifacts/product/e1-platform/f1-user-auth/s1-add-login-button/s1-add-login-button.md',
|
|
109
|
+
storyContent
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const result = JSON.parse(runScript('init', storyPath, tmpDir));
|
|
113
|
+
|
|
114
|
+
assert.ok(result.executor_model, 'should have executor_model');
|
|
115
|
+
assert.ok(result.reviewer_model, 'should have reviewer_model');
|
|
116
|
+
assert.strictEqual(result.story_valid, true, 'story should be valid');
|
|
117
|
+
assert.strictEqual(result.story_source, 'file');
|
|
118
|
+
assert.strictEqual(result.story.id, 'S1');
|
|
119
|
+
assert.strictEqual(result.story.title, 'Add Login Button');
|
|
120
|
+
assert.strictEqual(result.story.status, 'Refined');
|
|
121
|
+
assert.strictEqual(result.has_acceptance_criteria, true);
|
|
122
|
+
assert.strictEqual(result.acceptance_criteria_count, 1);
|
|
123
|
+
assert.strictEqual(result.has_technical_solution, true);
|
|
124
|
+
assert.ok(result.paths, 'should have computed paths');
|
|
125
|
+
assert.ok(result.paths.story_file.includes('s1-add-login-button'));
|
|
126
|
+
assert.ok(result.paths.product_backlog);
|
|
127
|
+
assert.ok(result.paths.coding_standards);
|
|
128
|
+
assert.strictEqual(result.has_story_file, true);
|
|
129
|
+
assert.strictEqual(typeof result.commit_docs, 'boolean');
|
|
130
|
+
assert.strictEqual(typeof result.has_git, 'boolean');
|
|
131
|
+
assert.strictEqual(typeof result.agent_teams, 'boolean');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns invalid when story has no AC', () => {
|
|
135
|
+
const storyContent = [
|
|
136
|
+
'# S2: No AC Story',
|
|
137
|
+
'**Feature**: F1 Test Feature | **Epic**: E1 Test Epic',
|
|
138
|
+
'**Status**: Todo | **Size**: 3 | **Sprint**: — | **Link**: —',
|
|
139
|
+
'',
|
|
140
|
+
'## Description',
|
|
141
|
+
'',
|
|
142
|
+
'A story without acceptance criteria.',
|
|
143
|
+
].join('\n');
|
|
144
|
+
|
|
145
|
+
const storyPath = createStoryFile(
|
|
146
|
+
tmpDir,
|
|
147
|
+
'.ace/artifacts/product/e1-test-epic/f1-test-feature/s2-no-ac/s2-no-ac.md',
|
|
148
|
+
storyContent
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const result = JSON.parse(runScript('init', storyPath, tmpDir));
|
|
152
|
+
|
|
153
|
+
assert.strictEqual(result.story_valid, true);
|
|
154
|
+
assert.strictEqual(result.has_acceptance_criteria, false);
|
|
155
|
+
assert.strictEqual(result.acceptance_criteria_count, 0);
|
|
156
|
+
assert.strictEqual(result.has_technical_solution, false);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('handles non-existent story file gracefully', () => {
|
|
160
|
+
const result = JSON.parse(runScript('init', 'nonexistent/story.md', tmpDir));
|
|
161
|
+
|
|
162
|
+
assert.strictEqual(result.story_valid, false);
|
|
163
|
+
assert.ok(result.story_error.includes('not found'));
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('returns invalid with no story parameter', () => {
|
|
167
|
+
const result = JSON.parse(runScript('init', '', tmpDir));
|
|
168
|
+
|
|
169
|
+
assert.strictEqual(result.story_valid, false);
|
|
170
|
+
assert.ok(result.story_error);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('resolve-model', () => {
|
|
175
|
+
let tmpDir;
|
|
176
|
+
|
|
177
|
+
before(() => { tmpDir = createTestProject(); });
|
|
178
|
+
after(() => { cleanup(tmpDir); });
|
|
179
|
+
|
|
180
|
+
it('returns a model string with --raw', () => {
|
|
181
|
+
const result = runScript('resolve-model', 'ace-executor --raw', tmpDir).trim();
|
|
182
|
+
assert.match(result, /^(opus|sonnet|haiku)$/);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('returns JSON without --raw', () => {
|
|
186
|
+
const result = JSON.parse(runScript('resolve-model', 'ace-executor', tmpDir));
|
|
187
|
+
assert.ok(result.model);
|
|
188
|
+
assert.strictEqual(result.agent, 'ace-executor');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('returns correct model for reviewer', () => {
|
|
192
|
+
const result = runScript('resolve-model', 'ace-code-reviewer --raw', tmpDir).trim();
|
|
193
|
+
assert.match(result, /^(opus|sonnet|haiku)$/);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('update-state', () => {
|
|
198
|
+
let tmpDir;
|
|
199
|
+
|
|
200
|
+
before(() => { tmpDir = createTestProject(); });
|
|
201
|
+
after(() => { cleanup(tmpDir); });
|
|
202
|
+
|
|
203
|
+
it('updates story status in the story file', () => {
|
|
204
|
+
const storyContent = [
|
|
205
|
+
'# S1: Test Story',
|
|
206
|
+
'**Feature**: F1 Test Feature | **Epic**: E1 Test Epic',
|
|
207
|
+
'**Status**: Refined | **Size**: 3 | **Sprint**: — | **Link**: —',
|
|
208
|
+
].join('\n');
|
|
209
|
+
|
|
210
|
+
const storyPath = createStoryFile(
|
|
211
|
+
tmpDir,
|
|
212
|
+
'.ace/artifacts/product/e1-test-epic/f1-test-feature/s1-test-story/s1-test-story.md',
|
|
213
|
+
storyContent
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const result = JSON.parse(runScript('update-state', `story=${storyPath} status=Done`, tmpDir));
|
|
217
|
+
|
|
218
|
+
assert.strictEqual(result.story_updated, true);
|
|
219
|
+
assert.strictEqual(result.new_status, 'Done');
|
|
220
|
+
|
|
221
|
+
// Verify file was actually updated
|
|
222
|
+
const updated = fs.readFileSync(path.join(tmpDir, storyPath), 'utf-8');
|
|
223
|
+
assert.ok(updated.includes('**Status**: Done'));
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('normalizes InProgress to "In Progress"', () => {
|
|
227
|
+
const storyContent = [
|
|
228
|
+
'# S2: Another Story',
|
|
229
|
+
'**Feature**: F1 Test Feature | **Epic**: E1 Test Epic',
|
|
230
|
+
'**Status**: Refined | **Size**: 2 | **Sprint**: — | **Link**: —',
|
|
231
|
+
].join('\n');
|
|
232
|
+
|
|
233
|
+
const storyPath = createStoryFile(
|
|
234
|
+
tmpDir,
|
|
235
|
+
'.ace/artifacts/product/e1-test-epic/f1-test-feature/s2-another-story/s2-another-story.md',
|
|
236
|
+
storyContent
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const result = JSON.parse(runScript('update-state', `story=${storyPath} status=InProgress`, tmpDir));
|
|
240
|
+
|
|
241
|
+
assert.strictEqual(result.new_status, 'In Progress');
|
|
242
|
+
|
|
243
|
+
const updated = fs.readFileSync(path.join(tmpDir, storyPath), 'utf-8');
|
|
244
|
+
assert.ok(updated.includes('**Status**: In Progress'));
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('error handling', () => {
|
|
249
|
+
it('errors on unknown command', () => {
|
|
250
|
+
assert.throws(() => {
|
|
251
|
+
execSync(`node "${SCRIPT}" bogus`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('errors on resolve-model without agent type', () => {
|
|
256
|
+
assert.throws(() => {
|
|
257
|
+
execSync(`node "${SCRIPT}" resolve-model`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
});
|