agile-context-engineering 0.2.2 → 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 (146) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/CHANGELOG.md +82 -0
  3. package/README.md +27 -18
  4. package/agents/ace-product-owner.md +1 -1
  5. package/agents/ace-technical-application-architect.md +28 -0
  6. package/agents/ace-wiki-mapper.md +144 -29
  7. package/bin/install.js +67 -63
  8. package/hooks/ace-check-update.js +17 -9
  9. package/package.json +7 -5
  10. package/shared/lib/ace-core.js +308 -0
  11. package/shared/lib/ace-core.test.js +308 -0
  12. package/shared/lib/ace-github.js +753 -0
  13. package/shared/lib/ace-story.js +400 -0
  14. package/shared/lib/ace-story.test.js +250 -0
  15. package/{agile-context-engineering → shared}/utils/ui-formatting.md +299 -299
  16. package/skills/execute-story/SKILL.md +110 -0
  17. package/skills/execute-story/script.js +305 -0
  18. package/skills/execute-story/script.test.js +261 -0
  19. package/skills/execute-story/walkthrough-template.xml +255 -0
  20. package/{agile-context-engineering/workflows/execute-story.xml → skills/execute-story/workflow.xml} +83 -9
  21. package/skills/help/SKILL.md +69 -0
  22. package/skills/help/script.js +318 -0
  23. package/skills/help/script.test.js +183 -0
  24. package/{agile-context-engineering/workflows/help.xml → skills/help/workflow.xml} +8 -8
  25. package/skills/init-coding-standards/SKILL.md +72 -0
  26. package/{agile-context-engineering/templates/wiki/coding-standards.xml → skills/init-coding-standards/coding-standards-template.xml} +38 -0
  27. package/skills/init-coding-standards/script.js +59 -0
  28. package/skills/init-coding-standards/script.test.js +70 -0
  29. package/{agile-context-engineering/workflows/init-coding-standards.xml → skills/init-coding-standards/workflow.xml} +4 -9
  30. package/skills/map-cross-cutting/SKILL.md +89 -0
  31. package/skills/map-cross-cutting/workflow.xml +330 -0
  32. package/skills/map-guide/SKILL.md +89 -0
  33. package/skills/map-guide/workflow.xml +320 -0
  34. package/skills/map-pattern/SKILL.md +89 -0
  35. package/skills/map-pattern/workflow.xml +331 -0
  36. package/skills/map-story/SKILL.md +127 -0
  37. package/skills/map-story/templates/guide.xml +137 -0
  38. package/skills/map-story/templates/pattern.xml +159 -0
  39. package/skills/map-story/templates/system-cross-cutting.xml +197 -0
  40. package/skills/map-story/templates/walkthrough.xml +255 -0
  41. package/{agile-context-engineering/workflows/map-story.xml → skills/map-story/workflow.xml} +258 -9
  42. package/skills/map-subsystem/SKILL.md +111 -0
  43. package/skills/map-subsystem/script.js +60 -0
  44. package/skills/map-subsystem/script.test.js +68 -0
  45. package/skills/map-subsystem/templates/decizions.xml +115 -0
  46. package/skills/map-subsystem/templates/guide.xml +137 -0
  47. package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/module-discovery.xml +3 -3
  48. package/skills/map-subsystem/templates/pattern.xml +159 -0
  49. package/skills/map-subsystem/templates/system-cross-cutting.xml +197 -0
  50. package/skills/map-subsystem/templates/system.xml +381 -0
  51. package/skills/map-subsystem/templates/walkthrough.xml +255 -0
  52. package/{agile-context-engineering/workflows/map-subsystem.xml → skills/map-subsystem/workflow.xml} +17 -21
  53. package/skills/map-sys-doc/SKILL.md +90 -0
  54. package/skills/map-sys-doc/system.xml +381 -0
  55. package/skills/map-sys-doc/workflow.xml +336 -0
  56. package/skills/map-system/SKILL.md +85 -0
  57. package/skills/map-system/script.js +84 -0
  58. package/skills/map-system/script.test.js +73 -0
  59. package/skills/map-system/templates/wiki-readme.xml +297 -0
  60. package/{agile-context-engineering/workflows/map-system.xml → skills/map-system/workflow.xml} +11 -16
  61. package/skills/map-walkthrough/SKILL.md +92 -0
  62. package/skills/map-walkthrough/walkthrough.xml +255 -0
  63. package/skills/map-walkthrough/workflow.xml +457 -0
  64. package/skills/plan-backlog/SKILL.md +75 -0
  65. package/skills/plan-backlog/script.js +136 -0
  66. package/skills/plan-backlog/script.test.js +83 -0
  67. package/{agile-context-engineering/workflows/plan-backlog.xml → skills/plan-backlog/workflow.xml} +13 -21
  68. package/skills/plan-feature/SKILL.md +76 -0
  69. package/skills/plan-feature/script.js +148 -0
  70. package/skills/plan-feature/script.test.js +80 -0
  71. package/{agile-context-engineering/workflows/plan-feature.xml → skills/plan-feature/workflow.xml} +21 -29
  72. package/skills/plan-product-vision/SKILL.md +75 -0
  73. package/skills/plan-product-vision/script.js +60 -0
  74. package/skills/plan-product-vision/script.test.js +69 -0
  75. package/{agile-context-engineering/workflows/plan-product-vision.xml → skills/plan-product-vision/workflow.xml} +4 -9
  76. package/skills/plan-story/SKILL.md +116 -0
  77. package/skills/plan-story/script.js +326 -0
  78. package/skills/plan-story/script.test.js +240 -0
  79. package/skills/plan-story/story-template.xml +451 -0
  80. package/{agile-context-engineering/workflows/plan-story.xml → skills/plan-story/workflow.xml} +1285 -909
  81. package/skills/research-external-solution/SKILL.md +107 -0
  82. package/skills/research-external-solution/script.js +238 -0
  83. package/skills/research-external-solution/script.test.js +134 -0
  84. package/{agile-context-engineering/workflows/research-external-solution.xml → skills/research-external-solution/workflow.xml} +4 -6
  85. package/skills/research-integration-solution/SKILL.md +98 -0
  86. package/{agile-context-engineering/templates/product/story-integration-solution.xml → skills/research-integration-solution/integration-solution-template.xml} +1 -0
  87. package/skills/research-integration-solution/script.js +231 -0
  88. package/skills/research-integration-solution/script.test.js +134 -0
  89. package/{agile-context-engineering/workflows/research-integration-solution.xml → skills/research-integration-solution/workflow.xml} +4 -5
  90. package/skills/research-story-wiki/SKILL.md +92 -0
  91. package/skills/research-story-wiki/script.js +231 -0
  92. package/skills/research-story-wiki/script.test.js +138 -0
  93. package/{agile-context-engineering/templates/product/story-wiki.xml → skills/research-story-wiki/story-wiki-template.xml} +4 -0
  94. package/{agile-context-engineering/workflows/research-story-wiki.xml → skills/research-story-wiki/workflow.xml} +5 -6
  95. package/skills/research-technical-solution/SKILL.md +103 -0
  96. package/skills/research-technical-solution/script.js +231 -0
  97. package/skills/research-technical-solution/script.test.js +134 -0
  98. package/{agile-context-engineering/workflows/research-technical-solution.xml → skills/research-technical-solution/workflow.xml} +4 -5
  99. package/skills/review-story/SKILL.md +100 -0
  100. package/skills/review-story/script.js +257 -0
  101. package/skills/review-story/script.test.js +169 -0
  102. package/skills/review-story/story-template.xml +451 -0
  103. package/{agile-context-engineering/workflows/review-story.xml → skills/review-story/workflow.xml} +1 -3
  104. package/skills/update/SKILL.md +53 -0
  105. package/{agile-context-engineering/workflows/update.xml → skills/update/workflow.xml} +237 -207
  106. package/agile-context-engineering/src/ace-tools.js +0 -2881
  107. package/agile-context-engineering/src/ace-tools.test.js +0 -1089
  108. package/agile-context-engineering/templates/_command.md +0 -54
  109. package/agile-context-engineering/templates/_workflow.xml +0 -17
  110. package/agile-context-engineering/templates/config.json +0 -0
  111. package/agile-context-engineering/templates/product/integration-solution.xml +0 -0
  112. package/agile-context-engineering/templates/wiki/wiki-readme.xml +0 -276
  113. package/commands/ace/execute-story.md +0 -137
  114. package/commands/ace/help.md +0 -93
  115. package/commands/ace/init-coding-standards.md +0 -83
  116. package/commands/ace/map-story.md +0 -156
  117. package/commands/ace/map-subsystem.md +0 -138
  118. package/commands/ace/map-system.md +0 -92
  119. package/commands/ace/plan-backlog.md +0 -83
  120. package/commands/ace/plan-feature.md +0 -89
  121. package/commands/ace/plan-product-vision.md +0 -81
  122. package/commands/ace/plan-story.md +0 -145
  123. package/commands/ace/research-external-solution.md +0 -138
  124. package/commands/ace/research-integration-solution.md +0 -135
  125. package/commands/ace/research-story-wiki.md +0 -116
  126. package/commands/ace/research-technical-solution.md +0 -147
  127. package/commands/ace/review-story.md +0 -109
  128. package/commands/ace/update.md +0 -54
  129. /package/{agile-context-engineering → shared}/utils/questioning.xml +0 -0
  130. /package/{agile-context-engineering/templates/product/story.xml → skills/execute-story/story-template.xml} +0 -0
  131. /package/{agile-context-engineering/templates/wiki → skills/map-cross-cutting}/system-cross-cutting.xml +0 -0
  132. /package/{agile-context-engineering/templates/wiki → skills/map-guide}/guide.xml +0 -0
  133. /package/{agile-context-engineering/templates/wiki → skills/map-pattern}/pattern.xml +0 -0
  134. /package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/decizions.xml +0 -0
  135. /package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/system.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-architecture.xml +0 -0
  140. /package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/system-structure.xml +0 -0
  141. /package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/testing-framework.xml +0 -0
  142. /package/{agile-context-engineering/templates/product/product-backlog.xml → skills/plan-backlog/product-backlog-template.xml} +0 -0
  143. /package/{agile-context-engineering/templates/product/feature.xml → skills/plan-feature/feature-template.xml} +0 -0
  144. /package/{agile-context-engineering/templates/product/product-vision.xml → skills/plan-product-vision/product-vision-template.xml} +0 -0
  145. /package/{agile-context-engineering/templates/product/external-solution.xml → skills/research-external-solution/external-solution-template.xml} +0 -0
  146. /package/{agile-context-engineering/templates/product/story-technical-solution.xml → skills/research-technical-solution/technical-solution-template.xml} +0 -0
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: execute-story
3
+ description: Execute a fully-planned story -- loads AC + Technical Solution, creates execution plan via Plan Mode, implements (solo or agent teams), runs code review, updates state, and triggers wiki mapping
4
+ argument-hint: "story=<file-path|github-url> [--agent-teams-off]"
5
+ disable-model-invocation: true
6
+ allowed-tools: Read, Bash, Write, Edit, AskUserQuestion, Glob, Grep, Agent, EnterPlanMode, ExitPlanMode
7
+ model: opus
8
+ effort: max
9
+ ---
10
+
11
+ # Execute Story
12
+
13
+ Orchestrate story execution: load a fully-planned story (with AC + Technical Solution), create an execution plan via Claude Code Plan Mode, execute the plan (solo or agent teams), run code review, present results for user verification, update state across all ACE artifacts, and trigger wiki mapping.
14
+
15
+ ## When to Use
16
+
17
+ - After `/ace:plan-story` -- when a story has AC + Technical Solution
18
+ - Anytime -- to execute a fully-planned story specification
19
+ - When a story has both Acceptance Criteria AND Technical Solution sections
20
+ - When story status is "Refined" (ready for implementation)
21
+
22
+ ## Input
23
+
24
+ ### Required
25
+
26
+ - **`story`** -- Story source:
27
+ - **File path**: Path to a fully-planned story markdown file (must have AC + Technical Solution sections)
28
+ - **GitHub URL or issue number**: GitHub story reference
29
+ - Must be a valid, accessible file or GitHub issue.
30
+ - The story MUST have been through `/ace:plan-story` (has AC + Technical Solution).
31
+
32
+ ### Flags
33
+
34
+ - **`--agent-teams-off`** -- Force solo execution mode regardless of agent_teams setting. Overrides the agent_teams flag in settings.json. Use when you want single-context execution even if teams are enabled.
35
+
36
+ ## Environment Context (preprocessed)
37
+
38
+ !`node "${CLAUDE_SKILL_DIR}/script.js" init "$ARGUMENTS" 2>/dev/null`
39
+
40
+ ## Supporting Resources
41
+
42
+ Read ALL of these before starting the workflow:
43
+
44
+ - **Workflow**: Read [workflow.xml](workflow.xml) -- complete orchestration process with all steps
45
+ - **Story template**: Read [story-template.xml](story-template.xml) -- output format for the story specification
46
+ - **Walkthrough template**: Read [walkthrough-template.xml](walkthrough-template.xml) -- walkthrough format for wiki docs
47
+ - **Questioning guide**: Read `${CLAUDE_SKILL_DIR}/../../shared/utils/questioning.xml` -- deep questioning techniques
48
+ - **UI formatting**: Read `${CLAUDE_SKILL_DIR}/../../shared/utils/ui-formatting.md` -- ACE output formatting rules
49
+
50
+ ## Process
51
+
52
+ **STRICT WORKFLOW EXECUTION -- Follow the execute-story workflow STEP BY STEP.
53
+ Do NOT skip steps. Do NOT improvise. Do NOT start reading code or planning
54
+ until step 1 (init & validate) is fully complete with the init command output parsed.**
55
+
56
+ Execute the execute-story workflow from [workflow.xml](workflow.xml) end-to-end.
57
+
58
+ The Environment Context above contains the preprocessed INIT JSON -- use it directly instead of running the init script manually. The workflow's step 1 setup can skip the init bash call since that data is already available.
59
+
60
+ **MANDATORY FIRST ACTION: Parse the preprocessed INIT JSON from Environment Context BEFORE doing anything else.
61
+ Do NOT read the story file manually. Do NOT explore the codebase. Do NOT start planning.
62
+ The init output validates the story and provides all paths and context needed.**
63
+
64
+ **CRITICAL REQUIREMENTS:**
65
+ - Story MUST have Acceptance Criteria -- STOP if missing
66
+ - Story MUST have Technical Solution -- STOP if missing
67
+ - NO intermediary commits during implementation
68
+ - ONE single commit per story after user approval (code + state + docs)
69
+ - Code review is MANDATORY -- blockers must be fixed before approval
70
+ - Coding standards violations are BLOCKERS, not warnings
71
+ - Dead code and backwards-compatible shims must be DELETED
72
+
73
+ Two execution modes:
74
+ - **Solo Mode** (default or --agent-teams-off): Single context, plan mode then execute
75
+ - **Agent Teams Mode** (when enabled + plan recommends): Lead + teammates for parallel work
76
+
77
+ ## Artifacts
78
+
79
+ Story file updated with Summary & State section and Wiki Updates section.
80
+ Feature file updated with story status.
81
+ Product backlog updated with story (and possibly feature) status.
82
+ Wiki documents updated/created based on implementation changes.
83
+
84
+ ## Example Usage
85
+
86
+ ```
87
+ # Execute a story from a file path
88
+ /ace:execute-story \
89
+ story=.ace/artifacts/product/e1-auth/f3-oauth/s1-buttons/s1-buttons.md
90
+
91
+ # Execute from a GitHub issue
92
+ /ace:execute-story \
93
+ story=https://github.com/owner/repo/issues/95
94
+
95
+ # Force solo mode (no agent teams)
96
+ /ace:execute-story \
97
+ story=.ace/artifacts/product/e1-auth/f3-oauth/s1-buttons/s1-buttons.md \
98
+ --agent-teams-off
99
+
100
+ # With just an issue number
101
+ /ace:execute-story story=#95
102
+ ```
103
+
104
+ ## Next Steps
105
+
106
+ **After this command:**
107
+ - `/ace:execute-story story=...` -- Execute the next story in the feature
108
+ - `/ace:review-story story=...` -- Re-run code review (standalone)
109
+ - `/ace:plan-story story=...` -- Plan the next story
110
+ - `/ace:help` -- Check project status
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * execute-story skill script — Entry point for all ace-tools operations
5
+ * needed by the execute-story skill.
6
+ *
7
+ * Subcommands:
8
+ * init [story-param] Environment detection for execute-story workflow
9
+ * update-state story=X status=Y Update story status across files
10
+ * sync-github repo=X story_file=Y Sync story/feature to GitHub
11
+ * resolve-model <agent-type> Get model for agent based on profile
12
+ *
13
+ * Usage: node script.js <subcommand> [args] [--raw]
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const {
20
+ loadConfig, pathExists, safeReadFile, resolveModel,
21
+ loadSettings, execCommand, output, error,
22
+ } = require('../../shared/lib/ace-core');
23
+
24
+ const {
25
+ classifyStoryParam, extractStoryMetadata, extractStoryRequirements,
26
+ extractMarkdownSection, extractIssueNumber, extractIssueNumberFromFile,
27
+ computeStoryPaths, updateState,
28
+ } = require('../../shared/lib/ace-story');
29
+
30
+ const { syncStory } = require('../../shared/lib/ace-github');
31
+
32
+ // ─── Runtime Config Dir ─────────────────────────────────────────────────────
33
+
34
+ /**
35
+ * Detect the runtime config directory name.
36
+ * In the plugin context, the script lives at:
37
+ * <base>/<config-dir>/skills/execute-story/script.js
38
+ * Default to '.claude' since the plugin always runs in Claude Code.
39
+ */
40
+ function getRuntimeConfigDirName() {
41
+ try {
42
+ const skillDir = __dirname; // <base>/<config-dir>/skills/execute-story
43
+ const skillsDir = path.dirname(skillDir); // <base>/<config-dir>/skills
44
+ const configDir = path.dirname(skillsDir); // <base>/<config-dir>
45
+ const dirName = path.basename(configDir);
46
+ if (dirName === '.opencode' || dirName === '.claude') {
47
+ return dirName;
48
+ }
49
+ } catch {}
50
+ return '.claude';
51
+ }
52
+
53
+ const RUNTIME_CONFIG_DIR = getRuntimeConfigDirName();
54
+
55
+ // ─── CLI Dispatch ────────────────────────────────────────────────────────────
56
+
57
+ const cwd = process.cwd();
58
+ const args = process.argv.slice(2);
59
+ const raw = args.includes('--raw');
60
+ const cmd = args[0];
61
+
62
+ switch (cmd) {
63
+ case 'init':
64
+ cmdInit(cwd, raw, args.slice(1).filter(a => a !== '--raw'));
65
+ break;
66
+ case 'update-state':
67
+ updateState(cwd, raw, args.slice(1).filter(a => a !== '--raw'));
68
+ break;
69
+ case 'sync-github':
70
+ syncStory(cwd, raw, args.slice(1).filter(a => a !== '--raw'));
71
+ break;
72
+ case 'resolve-model': {
73
+ const agentType = args[1];
74
+ if (!agentType) error('resolve-model requires agent-type argument');
75
+ const model = resolveModel(cwd, agentType);
76
+ output({ model, agent: agentType }, raw, model);
77
+ break;
78
+ }
79
+ default:
80
+ error(`Unknown command: ${cmd}\nAvailable: init, update-state, sync-github, resolve-model`);
81
+ }
82
+
83
+ // ─── Init: Execute Story ────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * Environment detection for the execute-story workflow.
87
+ *
88
+ * Detects: git, gh CLI, GitHub project, agent teams, story source/content/metadata,
89
+ * acceptance criteria, technical solution, wiki refs, coding standards, computed paths.
90
+ */
91
+ function cmdInit(cwd, raw, initArgs) {
92
+ const config = loadConfig(cwd);
93
+
94
+ // ── Environment detection ──
95
+ const has_git = pathExists(cwd, '.git');
96
+ const has_gh_cli = (() => {
97
+ try {
98
+ const { execSync } = require('child_process');
99
+ execSync('gh --version', { stdio: 'pipe' });
100
+ return true;
101
+ } catch { return false; }
102
+ })();
103
+ const settings = loadSettings(cwd);
104
+ const github_project = settings.github_project;
105
+
106
+ // ── Agent teams detection (sync from runtime settings) ──
107
+ const claudeSettingsPath = path.join(cwd, RUNTIME_CONFIG_DIR, 'settings.json');
108
+ let agent_teams = settings.agent_teams || false;
109
+ try {
110
+ const claudeRaw = fs.readFileSync(claudeSettingsPath, 'utf-8');
111
+ const claudeSettings = JSON.parse(claudeRaw);
112
+ const val = claudeSettings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
113
+ agent_teams = val === '1' || val === 'true';
114
+ } catch {}
115
+
116
+ // ── Parse story param ──
117
+ const storyParam = initArgs.join(' ').trim() || null;
118
+
119
+ // ── Classify the story parameter ──
120
+ const classified = classifyStoryParam(storyParam);
121
+
122
+ // Early exit if invalid
123
+ if (classified.type === null || classified.type === 'invalid') {
124
+ output({
125
+ executor_model: resolveModel(cwd, 'ace-executor'),
126
+ reviewer_model: resolveModel(cwd, 'ace-code-reviewer'),
127
+ commit_docs: config.commit_docs,
128
+ has_git, has_gh_cli, github_project, agent_teams,
129
+ story_source: null,
130
+ story_valid: false,
131
+ story_error: classified.reason || 'No story parameter provided',
132
+ story_content: null,
133
+ story: { id: null, title: null, status: null, size: null },
134
+ feature: { id: null, title: null },
135
+ epic: { id: null, title: null },
136
+ has_acceptance_criteria: false,
137
+ acceptance_criteria_count: 0,
138
+ has_technical_solution: false,
139
+ has_wiki_refs: false,
140
+ has_coding_standards: false,
141
+ paths: null,
142
+ }, raw);
143
+ return;
144
+ }
145
+
146
+ // ── Load story content ──
147
+ let storyContent = null;
148
+ let storySource = classified.type === 'file' ? 'file' : 'github';
149
+ let storyError = null;
150
+ let storyFilePath = null;
151
+
152
+ if (classified.type === 'file') {
153
+ const resolvedPath = path.isAbsolute(classified.filePath)
154
+ ? classified.filePath
155
+ : path.join(cwd, classified.filePath);
156
+ if (!pathExists(cwd, classified.filePath)) {
157
+ storyError = `Story file not found: ${classified.filePath}`;
158
+ } else {
159
+ storyContent = safeReadFile(resolvedPath);
160
+ storyFilePath = classified.filePath;
161
+ if (!storyContent) storyError = `Could not read story file: ${classified.filePath}`;
162
+ }
163
+ } else {
164
+ // github-url or issue-number
165
+ if (!has_gh_cli) {
166
+ storyError = 'GitHub CLI (gh) not installed. Cannot fetch GitHub issues.';
167
+ } else {
168
+ const repo = classified.repo || (github_project.repo || null);
169
+ if (!repo) {
170
+ storyError = 'No repository configured. Provide a full GitHub URL or configure github_project.repo in settings.';
171
+ } else {
172
+ const ghResult = execCommand(
173
+ `gh issue view ${classified.issueNumber} --repo ${repo} --json title,body,labels,state`,
174
+ cwd
175
+ );
176
+ if (!ghResult) {
177
+ storyError = `Could not fetch GitHub issue #${classified.issueNumber} from ${repo}.`;
178
+ } else {
179
+ try {
180
+ const issue = JSON.parse(ghResult);
181
+ storyContent = issue.body || '';
182
+ if (storyContent && !storyContent.match(/^#\s+/m)) {
183
+ storyContent = `# ${issue.title}\n\n${storyContent}`;
184
+ }
185
+ } catch {
186
+ storyError = `Failed to parse GitHub issue response for #${classified.issueNumber}.`;
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ // ── Extract metadata & requirements ──
194
+ const metadata = extractStoryMetadata(storyContent);
195
+ const requirements = extractStoryRequirements(storyContent);
196
+
197
+ // ── Detect key sections ──
198
+ const has_acceptance_criteria = requirements.acceptance_criteria_count > 0;
199
+ const has_technical_solution = storyContent
200
+ ? !!extractMarkdownSection(storyContent, 'Technical Solution', 2)
201
+ : false;
202
+ const has_wiki_refs = storyContent
203
+ ? !!extractMarkdownSection(storyContent, 'Relevant Wiki', 2)
204
+ : false;
205
+ const has_coding_standards = pathExists(cwd, '.docs/wiki/system-wide/coding-standards.md');
206
+
207
+ // ── Compute paths ──
208
+ let paths = null;
209
+ let has_story_file = false;
210
+
211
+ if (storyFilePath) {
212
+ const resolvedPath = path.isAbsolute(storyFilePath)
213
+ ? storyFilePath
214
+ : path.join(cwd, storyFilePath);
215
+ const storyDir = path.dirname(resolvedPath);
216
+ const relStoryDir = path.relative(cwd, storyDir).replace(/\\/g, '/');
217
+ const storySlug = path.basename(storyDir);
218
+ const featureDir = path.dirname(storyDir);
219
+ const relFeatureDir = path.relative(cwd, featureDir).replace(/\\/g, '/');
220
+ const featureSlug = path.basename(featureDir);
221
+
222
+ paths = {
223
+ epic_slug: null,
224
+ feature_slug: featureSlug,
225
+ story_slug: storySlug,
226
+ story_dir: relStoryDir,
227
+ story_file: storyFilePath.replace(/\\/g, '/'),
228
+ external_analysis_file: `${relStoryDir}/external-analysis.md`,
229
+ integration_analysis_file: `${relStoryDir}/integration-analysis.md`,
230
+ feature_dir: relFeatureDir,
231
+ feature_file: `${relFeatureDir}/${featureSlug}.md`,
232
+ product_backlog: '.ace/artifacts/product/product-backlog.md',
233
+ coding_standards: '.docs/wiki/system-wide/coding-standards.md',
234
+ };
235
+ has_story_file = true;
236
+ } else if (metadata.epic.id && metadata.feature.id && metadata.id) {
237
+ const computed = computeStoryPaths(
238
+ metadata.epic.id, metadata.epic.title || '',
239
+ metadata.feature.id, metadata.feature.title || '',
240
+ metadata.id, metadata.title || ''
241
+ );
242
+ if (computed) {
243
+ paths = {
244
+ ...computed,
245
+ product_backlog: '.ace/artifacts/product/product-backlog.md',
246
+ coding_standards: '.docs/wiki/system-wide/coding-standards.md',
247
+ };
248
+ has_story_file = pathExists(cwd, paths.story_file);
249
+ }
250
+ }
251
+
252
+ // ── Extract GitHub issue numbers ──
253
+ const storyIssueNumber = extractIssueNumber(metadata.link);
254
+ const featureIssueNumber = paths ? extractIssueNumberFromFile(cwd, paths.feature_file) : null;
255
+
256
+ // ── Build result ──
257
+ const result = {
258
+ // Models
259
+ executor_model: resolveModel(cwd, 'ace-executor'),
260
+ reviewer_model: resolveModel(cwd, 'ace-code-reviewer'),
261
+
262
+ // Config
263
+ commit_docs: config.commit_docs,
264
+
265
+ // Environment
266
+ has_git, has_gh_cli, github_project, agent_teams,
267
+
268
+ // Story source
269
+ story_source: storySource,
270
+ story_valid: storyContent !== null && storyError === null,
271
+ story_error: storyError,
272
+
273
+ // Raw story content
274
+ story_content: storyContent,
275
+
276
+ // Story metadata
277
+ story: {
278
+ id: metadata.id,
279
+ title: metadata.title,
280
+ status: metadata.status,
281
+ size: metadata.size,
282
+ issue_number: storyIssueNumber,
283
+ },
284
+ feature: {
285
+ ...metadata.feature,
286
+ issue_number: featureIssueNumber,
287
+ },
288
+ epic: metadata.epic,
289
+
290
+ // Section detection
291
+ has_acceptance_criteria,
292
+ acceptance_criteria_count: requirements.acceptance_criteria_count,
293
+ has_technical_solution,
294
+ has_wiki_refs,
295
+ has_coding_standards,
296
+
297
+ // Computed paths
298
+ paths,
299
+
300
+ // Artifact existence
301
+ has_story_file,
302
+ };
303
+
304
+ output(result, raw);
305
+ }
@@ -0,0 +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
+ });