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,100 +1,99 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: review-story
|
|
3
|
-
description: Standalone code review -- performs 3-level artifact verification, anti-pattern detection, coding standards enforcement, and tech debt discovery against a story's implementation
|
|
4
|
-
argument-hint: "story=<file-path|github-url>"
|
|
5
|
-
disable-model-invocation: true
|
|
6
|
-
allowed-tools: Read, Bash, Glob, Grep
|
|
7
|
-
model: sonnet
|
|
8
|
-
effort: high
|
|
9
|
-
context: fork
|
|
10
|
-
agent: ace-code-reviewer
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
The
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
- `/ace:help` -- Check project status
|
|
1
|
+
---
|
|
2
|
+
name: review-story
|
|
3
|
+
description: Standalone code review -- performs 3-level artifact verification, anti-pattern detection, coding standards enforcement, and tech debt discovery against a story's implementation
|
|
4
|
+
argument-hint: "story=<file-path|github-url>"
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
allowed-tools: Read, Bash, Glob, Grep
|
|
7
|
+
model: sonnet
|
|
8
|
+
effort: high
|
|
9
|
+
context: fork
|
|
10
|
+
agent: ace-code-reviewer
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Environment Context (preprocessed)
|
|
14
|
+
|
|
15
|
+
!`node "${CLAUDE_SKILL_DIR}/script.js" init "$ARGUMENTS" 2>/dev/null`
|
|
16
|
+
|
|
17
|
+
## Supporting Resources (auto-loaded)
|
|
18
|
+
|
|
19
|
+
!`cat "${CLAUDE_SKILL_DIR}/workflow.xml"`
|
|
20
|
+
|
|
21
|
+
!`cat "${CLAUDE_SKILL_DIR}/story-template.xml"`
|
|
22
|
+
|
|
23
|
+
!`cat "${CLAUDE_SKILL_DIR}/../../shared/utils/ui-formatting.md"`
|
|
24
|
+
|
|
25
|
+
```xml
|
|
26
|
+
<command>
|
|
27
|
+
|
|
28
|
+
<execution-time>
|
|
29
|
+
<runs-after>
|
|
30
|
+
<trigger>After /ace:execute-story — to re-run code review after manual changes</trigger>
|
|
31
|
+
<trigger>Anytime — to review a story implementation standalone</trigger>
|
|
32
|
+
</runs-after>
|
|
33
|
+
<use-when>
|
|
34
|
+
<condition>A story has been implemented and needs code review</condition>
|
|
35
|
+
<condition>Re-checking after manual changes post-execution</condition>
|
|
36
|
+
<condition>Verifying stories implemented outside ACE</condition>
|
|
37
|
+
<condition>Pre-merge quality gate</condition>
|
|
38
|
+
</use-when>
|
|
39
|
+
</execution-time>
|
|
40
|
+
|
|
41
|
+
<input>
|
|
42
|
+
<flags>
|
|
43
|
+
</flags>
|
|
44
|
+
|
|
45
|
+
<parameters>
|
|
46
|
+
<required>
|
|
47
|
+
<param name="story" type="file | github-url">
|
|
48
|
+
Story source — can be either:
|
|
49
|
+
- **File path**: Path to a fully-planned story markdown file
|
|
50
|
+
(must have AC + Technical Solution sections)
|
|
51
|
+
- **GitHub URL or issue number**: GitHub story reference
|
|
52
|
+
Must be a valid, accessible file or GitHub issue.
|
|
53
|
+
The story MUST have Acceptance Criteria and Technical Solution.
|
|
54
|
+
</param>
|
|
55
|
+
</required>
|
|
56
|
+
</parameters>
|
|
57
|
+
</input>
|
|
58
|
+
|
|
59
|
+
<execution-context>
|
|
60
|
+
<!-- All supporting files are auto-loaded in the Supporting Resources section above.
|
|
61
|
+
The model does NOT need to Read these files — they are already in context. -->
|
|
62
|
+
</execution-context>
|
|
63
|
+
|
|
64
|
+
<output>
|
|
65
|
+
<objective>
|
|
66
|
+
Review a story's implementation directly (the session IS the reviewer):
|
|
67
|
+
1. Load story context (AC, Technical Solution, Out of Scope)
|
|
68
|
+
2. Identify changed/created files related to the story
|
|
69
|
+
3. Run 3-level artifact verification (EXISTS → SUBSTANTIVE → WIRED)
|
|
70
|
+
4. Detect anti-patterns (dead code, stubs, TODOs, hardcoded values)
|
|
71
|
+
5. Enforce coding standards (mandatory, blocker-level)
|
|
72
|
+
6. Check AC coverage (all Gherkin scenarios implemented)
|
|
73
|
+
7. Discover pre-existing tech debt in touched files
|
|
74
|
+
8. Report results with structured format
|
|
75
|
+
</objective>
|
|
76
|
+
|
|
77
|
+
<artifacts>
|
|
78
|
+
No files modified — this command is read-only.
|
|
79
|
+
Outputs a structured review report to the conversation.
|
|
80
|
+
</artifacts>
|
|
81
|
+
</output>
|
|
82
|
+
|
|
83
|
+
<process>
|
|
84
|
+
For this command use the `ace-code-reviewer` agent
|
|
85
|
+
that's specialized in code review.
|
|
86
|
+
|
|
87
|
+
Execute the review-story workflow from
|
|
88
|
+
`workflow.xml` end-to-end.
|
|
89
|
+
|
|
90
|
+
**CRITICAL REQUIREMENTS:**
|
|
91
|
+
- Story MUST have Acceptance Criteria — STOP if missing
|
|
92
|
+
- Story MUST have Technical Solution — STOP if missing
|
|
93
|
+
- This is a READ-ONLY command — do NOT modify any code
|
|
94
|
+
- Coding standards violations are BLOCKERS, not warnings
|
|
95
|
+
- Dead code and backwards-compatible shims must be flagged for DELETION
|
|
96
|
+
</process>
|
|
97
|
+
|
|
98
|
+
<example-usage>
|
|
99
|
+
```
|
|
@@ -15,7 +15,7 @@ const path = require('path');
|
|
|
15
15
|
|
|
16
16
|
const {
|
|
17
17
|
loadConfig, pathExists, safeReadFile, loadSettings, resolveModel,
|
|
18
|
-
execCommand, output, error,
|
|
18
|
+
execCommand, output, error, runSkillScript,
|
|
19
19
|
} = require('../../shared/lib/ace-core');
|
|
20
20
|
|
|
21
21
|
const {
|
|
@@ -26,18 +26,9 @@ const {
|
|
|
26
26
|
|
|
27
27
|
// ─── CLI Dispatch ────────────────────────────────────────────────────────────
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
}
|
|
29
|
+
runSkillScript({
|
|
30
|
+
init: cmdInit,
|
|
31
|
+
});
|
|
41
32
|
|
|
42
33
|
// ─── Init: Execute/Review Story ─────────────────────────────────────────────
|
|
43
34
|
|
|
@@ -53,7 +44,8 @@ switch (cmd) {
|
|
|
53
44
|
* 6. computeStoryPaths
|
|
54
45
|
* 7. Output JSON with all data
|
|
55
46
|
*/
|
|
56
|
-
function cmdInit(cwd, raw,
|
|
47
|
+
function cmdInit(cwd, raw, args, parsed) {
|
|
48
|
+
const storyParam = parsed.story || parsed._positional || null;
|
|
57
49
|
const config = loadConfig(cwd);
|
|
58
50
|
|
|
59
51
|
// ── Environment detection ──
|
|
@@ -70,9 +62,9 @@ function cmdInit(cwd, raw, storyParam) {
|
|
|
70
62
|
|
|
71
63
|
// ── Agent teams detection (sync from runtime settings) ──
|
|
72
64
|
// RUNTIME_CONFIG_DIR is not in shared libs — detect inline
|
|
73
|
-
// Try
|
|
65
|
+
// Try runtime settings files that can carry ACE agent-team state.
|
|
74
66
|
let agent_teams = settings.agent_teams || false;
|
|
75
|
-
for (const configDir of ['.claude', '.opencode']) {
|
|
67
|
+
for (const configDir of ['.claude', '.codex', '.opencode']) {
|
|
76
68
|
const claudeSettingsPath = path.join(cwd, configDir, 'settings.json');
|
|
77
69
|
try {
|
|
78
70
|
const claudeRaw = fs.readFileSync(claudeSettingsPath, 'utf-8');
|
|
@@ -1,169 +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
|
-
});
|
|
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
|
+
});
|