agile-context-engineering 0.3.0 → 0.5.0

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.
Files changed (147) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/LICENSE +51 -51
  3. package/README.md +332 -324
  4. package/agents/ace-product-owner.md +1 -1
  5. package/agents/ace-research-synthesizer.md +228 -228
  6. package/agents/ace-wiki-mapper.md +449 -445
  7. package/bin/install.js +60 -64
  8. package/hooks/ace-check-update.js +70 -62
  9. package/hooks/ace-statusline.js +89 -89
  10. package/package.json +5 -4
  11. package/shared/lib/ace-core.js +308 -0
  12. package/shared/lib/ace-core.test.js +308 -0
  13. package/shared/lib/ace-github.js +753 -0
  14. package/shared/lib/ace-story.js +400 -0
  15. package/shared/lib/ace-story.test.js +250 -0
  16. package/{agile-context-engineering → shared}/utils/questioning.xml +110 -110
  17. package/{agile-context-engineering → shared}/utils/ui-formatting.md +299 -299
  18. package/skills/execute-story/SKILL.md +110 -0
  19. package/skills/execute-story/script.js +305 -0
  20. package/skills/execute-story/script.test.js +261 -0
  21. package/skills/execute-story/walkthrough-template.xml +255 -0
  22. package/{agile-context-engineering/workflows/execute-story.xml → skills/execute-story/workflow.xml} +1219 -1219
  23. package/skills/help/SKILL.md +69 -0
  24. package/skills/help/script.js +318 -0
  25. package/skills/help/script.test.js +183 -0
  26. package/{agile-context-engineering/workflows/help.xml → skills/help/workflow.xml} +540 -540
  27. package/skills/init-coding-standards/SKILL.md +72 -0
  28. package/skills/init-coding-standards/script.js +59 -0
  29. package/skills/init-coding-standards/script.test.js +70 -0
  30. package/{agile-context-engineering/workflows/init-coding-standards.xml → skills/init-coding-standards/workflow.xml} +381 -386
  31. package/skills/map-cross-cutting/SKILL.md +89 -0
  32. package/{agile-context-engineering/templates/wiki → skills/map-cross-cutting}/system-cross-cutting.xml +197 -197
  33. package/skills/map-cross-cutting/workflow.xml +330 -0
  34. package/skills/map-guide/SKILL.md +89 -0
  35. package/{agile-context-engineering/templates/wiki → skills/map-guide}/guide.xml +137 -137
  36. package/skills/map-guide/workflow.xml +320 -0
  37. package/skills/map-pattern/SKILL.md +89 -0
  38. package/{agile-context-engineering/templates/wiki → skills/map-pattern}/pattern.xml +159 -159
  39. package/skills/map-pattern/workflow.xml +331 -0
  40. package/skills/map-story/SKILL.md +127 -0
  41. package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/decizions.xml +115 -115
  42. package/skills/map-story/templates/guide.xml +137 -0
  43. package/skills/map-story/templates/pattern.xml +159 -0
  44. package/skills/map-story/templates/system-cross-cutting.xml +197 -0
  45. package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/system.xml +381 -381
  46. package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/walkthrough.xml +255 -255
  47. package/{agile-context-engineering/workflows/map-story.xml → skills/map-story/workflow.xml} +1046 -1046
  48. package/skills/map-subsystem/SKILL.md +111 -0
  49. package/skills/map-subsystem/script.js +60 -0
  50. package/skills/map-subsystem/script.test.js +68 -0
  51. package/skills/map-subsystem/templates/decizions.xml +115 -0
  52. package/skills/map-subsystem/templates/guide.xml +137 -0
  53. package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/module-discovery.xml +174 -174
  54. package/skills/map-subsystem/templates/pattern.xml +159 -0
  55. package/skills/map-subsystem/templates/system-cross-cutting.xml +197 -0
  56. package/skills/map-subsystem/templates/system.xml +381 -0
  57. package/skills/map-subsystem/templates/walkthrough.xml +255 -0
  58. package/{agile-context-engineering/workflows/map-subsystem.xml → skills/map-subsystem/workflow.xml} +15 -20
  59. package/skills/map-sys-doc/SKILL.md +90 -0
  60. package/skills/map-sys-doc/system.xml +381 -0
  61. package/skills/map-sys-doc/workflow.xml +336 -0
  62. package/skills/map-system/SKILL.md +85 -0
  63. package/skills/map-system/script.js +84 -0
  64. package/skills/map-system/script.test.js +73 -0
  65. package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/system-architecture.xml +254 -254
  66. package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/wiki-readme.xml +296 -296
  67. package/{agile-context-engineering/workflows/map-system.xml → skills/map-system/workflow.xml} +11 -16
  68. package/skills/map-walkthrough/SKILL.md +92 -0
  69. package/skills/map-walkthrough/walkthrough.xml +255 -0
  70. package/skills/plan-backlog/SKILL.md +75 -0
  71. package/{agile-context-engineering/templates/product/product-backlog.xml → skills/plan-backlog/product-backlog-template.xml} +231 -231
  72. package/skills/plan-backlog/script.js +136 -0
  73. package/skills/plan-backlog/script.test.js +83 -0
  74. package/{agile-context-engineering/workflows/plan-backlog.xml → skills/plan-backlog/workflow.xml} +13 -21
  75. package/skills/plan-feature/SKILL.md +76 -0
  76. package/skills/plan-feature/script.js +148 -0
  77. package/skills/plan-feature/script.test.js +80 -0
  78. package/{agile-context-engineering/workflows/plan-feature.xml → skills/plan-feature/workflow.xml} +1487 -1495
  79. package/skills/plan-product-vision/SKILL.md +75 -0
  80. package/skills/plan-product-vision/script.js +60 -0
  81. package/skills/plan-product-vision/script.test.js +69 -0
  82. package/{agile-context-engineering/workflows/plan-product-vision.xml → skills/plan-product-vision/workflow.xml} +4 -9
  83. package/skills/plan-story/SKILL.md +116 -0
  84. package/skills/plan-story/script.js +326 -0
  85. package/skills/plan-story/script.test.js +240 -0
  86. package/skills/plan-story/story-template.xml +451 -0
  87. package/{agile-context-engineering/workflows/plan-story.xml → skills/plan-story/workflow.xml} +1285 -944
  88. package/skills/research-external-solution/SKILL.md +107 -0
  89. package/skills/research-external-solution/script.js +238 -0
  90. package/skills/research-external-solution/script.test.js +134 -0
  91. package/{agile-context-engineering/workflows/research-external-solution.xml → skills/research-external-solution/workflow.xml} +4 -6
  92. package/skills/research-integration-solution/SKILL.md +98 -0
  93. package/skills/research-integration-solution/script.js +231 -0
  94. package/skills/research-integration-solution/script.test.js +134 -0
  95. package/{agile-context-engineering/workflows/research-integration-solution.xml → skills/research-integration-solution/workflow.xml} +3 -5
  96. package/skills/research-story-wiki/SKILL.md +92 -0
  97. package/skills/research-story-wiki/script.js +231 -0
  98. package/skills/research-story-wiki/script.test.js +138 -0
  99. package/{agile-context-engineering/workflows/research-story-wiki.xml → skills/research-story-wiki/workflow.xml} +3 -5
  100. package/skills/research-technical-solution/SKILL.md +103 -0
  101. package/skills/research-technical-solution/script.js +231 -0
  102. package/skills/research-technical-solution/script.test.js +134 -0
  103. package/{agile-context-engineering/workflows/research-technical-solution.xml → skills/research-technical-solution/workflow.xml} +3 -5
  104. package/skills/review-story/SKILL.md +100 -0
  105. package/skills/review-story/script.js +257 -0
  106. package/skills/review-story/script.test.js +169 -0
  107. package/skills/review-story/story-template.xml +451 -0
  108. package/{agile-context-engineering/workflows/review-story.xml → skills/review-story/workflow.xml} +279 -281
  109. package/skills/update/SKILL.md +53 -0
  110. package/{agile-context-engineering/workflows/update.xml → skills/update/workflow.xml} +12 -13
  111. package/agile-context-engineering/src/ace-tools.js +0 -2881
  112. package/agile-context-engineering/src/ace-tools.test.js +0 -1089
  113. package/agile-context-engineering/templates/_command.md +0 -54
  114. package/agile-context-engineering/templates/_workflow.xml +0 -17
  115. package/agile-context-engineering/templates/config.json +0 -0
  116. package/agile-context-engineering/templates/product/integration-solution.xml +0 -0
  117. package/commands/ace/execute-story.md +0 -138
  118. package/commands/ace/help.md +0 -93
  119. package/commands/ace/init-coding-standards.md +0 -83
  120. package/commands/ace/map-story.md +0 -165
  121. package/commands/ace/map-subsystem.md +0 -140
  122. package/commands/ace/map-system.md +0 -92
  123. package/commands/ace/map-walkthrough.md +0 -127
  124. package/commands/ace/plan-backlog.md +0 -83
  125. package/commands/ace/plan-feature.md +0 -89
  126. package/commands/ace/plan-product-vision.md +0 -81
  127. package/commands/ace/plan-story.md +0 -159
  128. package/commands/ace/research-external-solution.md +0 -138
  129. package/commands/ace/research-integration-solution.md +0 -135
  130. package/commands/ace/research-story-wiki.md +0 -116
  131. package/commands/ace/research-technical-solution.md +0 -147
  132. package/commands/ace/review-story.md +0 -109
  133. package/commands/ace/update.md +0 -56
  134. /package/{agile-context-engineering/templates/product/story.xml → skills/execute-story/story-template.xml} +0 -0
  135. /package/{agile-context-engineering/templates/wiki/coding-standards.xml → skills/init-coding-standards/coding-standards-template.xml} +0 -0
  136. /package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/tech-debt-index.xml +0 -0
  137. /package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/subsystem-architecture.xml +0 -0
  138. /package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/subsystem-structure.xml +0 -0
  139. /package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/system-structure.xml +0 -0
  140. /package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/testing-framework.xml +0 -0
  141. /package/{agile-context-engineering/workflows/map-walkthrough.xml → skills/map-walkthrough/workflow.xml} +0 -0
  142. /package/{agile-context-engineering/templates/product/feature.xml → skills/plan-feature/feature-template.xml} +0 -0
  143. /package/{agile-context-engineering/templates/product/product-vision.xml → skills/plan-product-vision/product-vision-template.xml} +0 -0
  144. /package/{agile-context-engineering/templates/product/external-solution.xml → skills/research-external-solution/external-solution-template.xml} +0 -0
  145. /package/{agile-context-engineering/templates/product/story-integration-solution.xml → skills/research-integration-solution/integration-solution-template.xml} +0 -0
  146. /package/{agile-context-engineering/templates/product/story-wiki.xml → skills/research-story-wiki/story-wiki-template.xml} +0 -0
  147. /package/{agile-context-engineering/templates/product/story-technical-solution.xml → skills/research-technical-solution/technical-solution-template.xml} +0 -0
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * review-story skill script — Entry point for ace-tools operations
5
+ * needed by the review-story skill.
6
+ *
7
+ * Subcommands:
8
+ * init [story-param] Environment detection for review-story workflow
9
+ *
10
+ * Usage: node script.js <subcommand> [args] [--raw]
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const {
17
+ loadConfig, pathExists, safeReadFile, loadSettings, resolveModel,
18
+ execCommand, output, error,
19
+ } = require('../../shared/lib/ace-core');
20
+
21
+ const {
22
+ classifyStoryParam, extractStoryMetadata, extractStoryRequirements,
23
+ extractMarkdownSection, extractIssueNumber, extractIssueNumberFromFile,
24
+ computeStoryPaths,
25
+ } = require('../../shared/lib/ace-story');
26
+
27
+ // ─── CLI Dispatch ────────────────────────────────────────────────────────────
28
+
29
+ const cwd = process.cwd();
30
+ const args = process.argv.slice(2);
31
+ const raw = args.includes('--raw');
32
+ const cmd = args[0];
33
+
34
+ switch (cmd) {
35
+ case 'init':
36
+ cmdInit(cwd, raw, args.slice(1).filter(a => a !== '--raw').join(' ') || undefined);
37
+ break;
38
+ default:
39
+ error(`Unknown command: ${cmd}\nAvailable: init`);
40
+ }
41
+
42
+ // ─── Init: Execute/Review Story ─────────────────────────────────────────────
43
+
44
+ /**
45
+ * Environment detection for the review-story workflow.
46
+ *
47
+ * Replicates cmdInitExecuteStory from ace-tools.js:
48
+ * 1. loadConfig, detect git/gh CLI/settings/agent_teams
49
+ * 2. classifyStoryParam
50
+ * 3. Load story content
51
+ * 4. extractStoryMetadata, extractStoryRequirements
52
+ * 5. Detect key sections (AC, technical solution, wiki refs, coding standards)
53
+ * 6. computeStoryPaths
54
+ * 7. Output JSON with all data
55
+ */
56
+ function cmdInit(cwd, raw, storyParam) {
57
+ const config = loadConfig(cwd);
58
+
59
+ // ── Environment detection ──
60
+ const has_git = pathExists(cwd, '.git');
61
+ const has_gh_cli = (() => {
62
+ try {
63
+ const { execSync } = require('child_process');
64
+ execSync('gh --version', { stdio: 'pipe' });
65
+ return true;
66
+ } catch { return false; }
67
+ })();
68
+ const settings = loadSettings(cwd);
69
+ const github_project = settings.github_project;
70
+
71
+ // ── Agent teams detection (sync from runtime settings) ──
72
+ // RUNTIME_CONFIG_DIR is not in shared libs — detect inline
73
+ // Try .claude first, then .opencode
74
+ let agent_teams = settings.agent_teams || false;
75
+ for (const configDir of ['.claude', '.opencode']) {
76
+ const claudeSettingsPath = path.join(cwd, configDir, 'settings.json');
77
+ try {
78
+ const claudeRaw = fs.readFileSync(claudeSettingsPath, 'utf-8');
79
+ const claudeSettings = JSON.parse(claudeRaw);
80
+ const val = claudeSettings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
81
+ if (val === '1' || val === 'true') {
82
+ agent_teams = true;
83
+ }
84
+ break; // found a settings file, stop looking
85
+ } catch {}
86
+ }
87
+
88
+ // ── Classify the story parameter ──
89
+ const classified = classifyStoryParam(storyParam);
90
+
91
+ // Early exit if invalid
92
+ if (classified.type === null || classified.type === 'invalid') {
93
+ output({
94
+ executor_model: resolveModel(cwd, 'ace-executor'),
95
+ reviewer_model: resolveModel(cwd, 'ace-code-reviewer'),
96
+ commit_docs: config.commit_docs,
97
+ has_git, has_gh_cli, github_project, agent_teams,
98
+ story_source: null,
99
+ story_valid: false,
100
+ story_error: classified.reason || 'No story parameter provided',
101
+ story_content: null,
102
+ story: { id: null, title: null, status: null, size: null },
103
+ feature: { id: null, title: null },
104
+ epic: { id: null, title: null },
105
+ has_acceptance_criteria: false,
106
+ acceptance_criteria_count: 0,
107
+ has_technical_solution: false,
108
+ has_wiki_refs: false,
109
+ has_coding_standards: false,
110
+ paths: null,
111
+ }, raw);
112
+ return;
113
+ }
114
+
115
+ // ── Load story content ──
116
+ let storyContent = null;
117
+ let storySource = classified.type === 'file' ? 'file' : 'github';
118
+ let storyError = null;
119
+ let storyFilePath = null;
120
+
121
+ if (classified.type === 'file') {
122
+ const resolvedPath = path.isAbsolute(classified.filePath)
123
+ ? classified.filePath
124
+ : path.join(cwd, classified.filePath);
125
+ if (!pathExists(cwd, classified.filePath)) {
126
+ storyError = `Story file not found: ${classified.filePath}`;
127
+ } else {
128
+ storyContent = safeReadFile(resolvedPath);
129
+ storyFilePath = classified.filePath;
130
+ if (!storyContent) storyError = `Could not read story file: ${classified.filePath}`;
131
+ }
132
+ } else {
133
+ // github-url or issue-number
134
+ if (!has_gh_cli) {
135
+ storyError = 'GitHub CLI (gh) not installed. Cannot fetch GitHub issues.';
136
+ } else {
137
+ const repo = classified.repo || (github_project.repo || null);
138
+ if (!repo) {
139
+ storyError = 'No repository configured. Provide a full GitHub URL or configure github_project.repo in settings.';
140
+ } else {
141
+ const ghResult = execCommand(
142
+ `gh issue view ${classified.issueNumber} --repo ${repo} --json title,body,labels,state`,
143
+ cwd
144
+ );
145
+ if (!ghResult) {
146
+ storyError = `Could not fetch GitHub issue #${classified.issueNumber} from ${repo}.`;
147
+ } else {
148
+ try {
149
+ const issue = JSON.parse(ghResult);
150
+ storyContent = issue.body || '';
151
+ if (storyContent && !storyContent.match(/^#\s+/m)) {
152
+ storyContent = `# ${issue.title}\n\n${storyContent}`;
153
+ }
154
+ } catch {
155
+ storyError = `Failed to parse GitHub issue response for #${classified.issueNumber}.`;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ // ── Extract metadata & requirements ──
163
+ const metadata = extractStoryMetadata(storyContent);
164
+ const requirements = extractStoryRequirements(storyContent);
165
+
166
+ // ── Detect key sections ──
167
+ const has_acceptance_criteria = requirements.acceptance_criteria_count > 0;
168
+ const has_technical_solution = storyContent
169
+ ? !!extractMarkdownSection(storyContent, 'Technical Solution', 2)
170
+ : false;
171
+ const has_wiki_refs = storyContent
172
+ ? !!extractMarkdownSection(storyContent, 'Relevant Wiki', 2)
173
+ : false;
174
+ const has_coding_standards = pathExists(cwd, '.docs/wiki/system-wide/coding-standards.md');
175
+
176
+ // ── Compute paths ──
177
+ let paths = null;
178
+ let has_story_file = false;
179
+
180
+ if (storyFilePath) {
181
+ const resolvedPath = path.isAbsolute(storyFilePath)
182
+ ? storyFilePath
183
+ : path.join(cwd, storyFilePath);
184
+ const storyDir = path.dirname(resolvedPath);
185
+ const relStoryDir = path.relative(cwd, storyDir).replace(/\\/g, '/');
186
+ const storySlug = path.basename(storyDir);
187
+ const featureDir = path.dirname(storyDir);
188
+ const relFeatureDir = path.relative(cwd, featureDir).replace(/\\/g, '/');
189
+ const featureSlug = path.basename(featureDir);
190
+
191
+ paths = {
192
+ epic_slug: null,
193
+ feature_slug: featureSlug,
194
+ story_slug: storySlug,
195
+ story_dir: relStoryDir,
196
+ story_file: storyFilePath.replace(/\\/g, '/'),
197
+ external_analysis_file: `${relStoryDir}/external-analysis.md`,
198
+ integration_analysis_file: `${relStoryDir}/integration-analysis.md`,
199
+ feature_dir: relFeatureDir,
200
+ feature_file: `${relFeatureDir}/${featureSlug}.md`,
201
+ product_backlog: '.ace/artifacts/product/product-backlog.md',
202
+ coding_standards: '.docs/wiki/system-wide/coding-standards.md',
203
+ };
204
+ has_story_file = true;
205
+ } else if (metadata.epic.id && metadata.feature.id && metadata.id) {
206
+ const computed = computeStoryPaths(
207
+ metadata.epic.id, metadata.epic.title || '',
208
+ metadata.feature.id, metadata.feature.title || '',
209
+ metadata.id, metadata.title || ''
210
+ );
211
+ if (computed) {
212
+ paths = {
213
+ ...computed,
214
+ product_backlog: '.ace/artifacts/product/product-backlog.md',
215
+ coding_standards: '.docs/wiki/system-wide/coding-standards.md',
216
+ };
217
+ has_story_file = pathExists(cwd, paths.story_file);
218
+ }
219
+ }
220
+
221
+ // ── Extract GitHub issue numbers ──
222
+ const storyIssueNumber = extractIssueNumber(metadata.link);
223
+ const featureIssueNumber = paths ? extractIssueNumberFromFile(cwd, paths.feature_file) : null;
224
+
225
+ // ── Build result ──
226
+ const result = {
227
+ executor_model: resolveModel(cwd, 'ace-executor'),
228
+ reviewer_model: resolveModel(cwd, 'ace-code-reviewer'),
229
+ commit_docs: config.commit_docs,
230
+ has_git, has_gh_cli, github_project, agent_teams,
231
+ story_source: storySource,
232
+ story_valid: storyContent !== null && storyError === null,
233
+ story_error: storyError,
234
+ story_content: storyContent,
235
+ story: {
236
+ id: metadata.id,
237
+ title: metadata.title,
238
+ status: metadata.status,
239
+ size: metadata.size,
240
+ issue_number: storyIssueNumber,
241
+ },
242
+ feature: {
243
+ ...metadata.feature,
244
+ issue_number: featureIssueNumber,
245
+ },
246
+ epic: metadata.epic,
247
+ has_acceptance_criteria,
248
+ acceptance_criteria_count: requirements.acceptance_criteria_count,
249
+ has_technical_solution,
250
+ has_wiki_refs,
251
+ has_coding_standards,
252
+ paths,
253
+ has_story_file,
254
+ };
255
+
256
+ output(result, raw);
257
+ }
@@ -0,0 +1,169 @@
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('review-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**: Todo | **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
+ 'Add a LoginButton component to the header.',
97
+ ].join('\n');
98
+
99
+ const storyPath = createStoryFile(
100
+ tmpDir,
101
+ '.ace/artifacts/product/e1-platform/f1-user-auth/s1-add-login-button/s1-add-login-button.md',
102
+ storyContent
103
+ );
104
+
105
+ const result = JSON.parse(runScript('init', storyPath, tmpDir));
106
+
107
+ assert.ok(result.executor_model, 'should have executor_model');
108
+ assert.ok(result.reviewer_model, 'should have reviewer_model');
109
+ assert.strictEqual(result.story_valid, true, 'story should be valid');
110
+ assert.strictEqual(result.story_source, 'file');
111
+ assert.strictEqual(result.story.id, 'S1');
112
+ assert.strictEqual(result.story.title, 'Add Login Button');
113
+ assert.strictEqual(result.story.status, 'Todo');
114
+ assert.strictEqual(result.has_acceptance_criteria, true);
115
+ assert.strictEqual(result.acceptance_criteria_count, 1);
116
+ assert.strictEqual(result.has_technical_solution, true);
117
+ assert.ok(result.paths, 'should have computed paths');
118
+ assert.ok(result.paths.story_file.includes('s1-add-login-button'));
119
+ assert.strictEqual(result.has_story_file, true);
120
+ assert.strictEqual(typeof result.commit_docs, 'boolean');
121
+ assert.strictEqual(typeof result.has_git, 'boolean');
122
+ assert.strictEqual(typeof result.agent_teams, 'boolean');
123
+ assert.ok(result.story_content, 'should include story_content');
124
+ });
125
+
126
+ it('errors on init without story param', () => {
127
+ const result = JSON.parse(runScript('init', '', tmpDir));
128
+ assert.strictEqual(result.story_valid, false);
129
+ });
130
+
131
+ it('handles non-existent story file gracefully', () => {
132
+ const result = JSON.parse(runScript('init', 'nonexistent/story.md', tmpDir));
133
+ assert.strictEqual(result.story_valid, false);
134
+ assert.ok(result.story_error.includes('not found'));
135
+ });
136
+
137
+ it('detects missing technical solution section', () => {
138
+ const storyContent = [
139
+ '# S2: Simple Story',
140
+ '**Feature**: F1 Test | **Epic**: E1 Test',
141
+ '**Status**: Todo | **Size**: 1 | **Sprint**: — | **Link**: —',
142
+ '',
143
+ '## Description',
144
+ '',
145
+ 'A simple story without technical solution.',
146
+ ].join('\n');
147
+
148
+ const storyPath = createStoryFile(
149
+ tmpDir,
150
+ '.ace/artifacts/product/e1-test/f1-test/s2-simple-story/s2-simple-story.md',
151
+ storyContent
152
+ );
153
+
154
+ const result = JSON.parse(runScript('init', storyPath, tmpDir));
155
+
156
+ assert.strictEqual(result.story_valid, true);
157
+ assert.strictEqual(result.has_technical_solution, false);
158
+ assert.strictEqual(result.has_acceptance_criteria, false);
159
+ });
160
+ });
161
+
162
+ describe('error handling', () => {
163
+ it('errors on unknown command', () => {
164
+ assert.throws(() => {
165
+ execSync(`node "${SCRIPT}" bogus`, { encoding: 'utf-8', stdio: 'pipe' });
166
+ });
167
+ });
168
+ });
169
+ });