agile-context-engineering 0.3.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 +10 -0
- package/CHANGELOG.md +7 -1
- package/LICENSE +51 -51
- package/README.md +330 -318
- 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-research-synthesizer.md +228 -228
- package/agents/ace-technical-application-architect.md +315 -315
- package/agents/ace-wiki-mapper.md +449 -445
- package/bin/install.js +605 -195
- package/hooks/ace-check-update.js +71 -62
- package/hooks/ace-statusline.js +107 -89
- package/hooks/hooks.json +14 -0
- package/package.json +7 -5
- package/shared/lib/ace-core.js +361 -0
- package/shared/lib/ace-core.test.js +308 -0
- package/shared/lib/ace-github.js +753 -0
- package/shared/lib/ace-story.js +400 -0
- package/shared/lib/ace-story.test.js +250 -0
- package/{agile-context-engineering → shared}/utils/questioning.xml +110 -110
- package/{agile-context-engineering → shared}/utils/ui-formatting.md +299 -299
- package/{commands/ace/execute-story.md → skills/execute-story/SKILL.md} +116 -138
- package/skills/execute-story/script.js +291 -0
- package/skills/execute-story/script.test.js +261 -0
- package/{agile-context-engineering/templates/product/story.xml → skills/execute-story/story-template.xml} +451 -451
- package/skills/execute-story/walkthrough-template.xml +255 -0
- package/{agile-context-engineering/workflows/execute-story.xml → skills/execute-story/workflow.xml} +1221 -1219
- package/skills/help/SKILL.md +71 -0
- package/skills/help/script.js +315 -0
- package/skills/help/script.test.js +183 -0
- package/{agile-context-engineering/workflows/help.xml → skills/help/workflow.xml} +544 -533
- package/{commands/ace/init-coding-standards.md → skills/init-coding-standards/SKILL.md} +91 -83
- package/{agile-context-engineering/templates/wiki/coding-standards.xml → skills/init-coding-standards/coding-standards-template.xml} +531 -531
- package/skills/init-coding-standards/script.js +50 -0
- package/skills/init-coding-standards/script.test.js +70 -0
- package/{agile-context-engineering/workflows/init-coding-standards.xml → skills/init-coding-standards/workflow.xml} +381 -386
- package/skills/map-cross-cutting/SKILL.md +126 -0
- package/{agile-context-engineering/templates/wiki → skills/map-cross-cutting}/system-cross-cutting.xml +197 -197
- package/skills/map-cross-cutting/workflow.xml +330 -0
- package/skills/map-guide/SKILL.md +126 -0
- package/{agile-context-engineering/templates/wiki → skills/map-guide}/guide.xml +137 -137
- package/skills/map-guide/workflow.xml +320 -0
- package/skills/map-pattern/SKILL.md +125 -0
- package/{agile-context-engineering/templates/wiki → skills/map-pattern}/pattern.xml +159 -159
- package/skills/map-pattern/workflow.xml +331 -0
- package/{commands/ace/map-story.md → skills/map-story/SKILL.md} +180 -165
- package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/decizions.xml +115 -115
- package/skills/map-story/templates/guide.xml +137 -0
- package/skills/map-story/templates/pattern.xml +159 -0
- package/skills/map-story/templates/system-cross-cutting.xml +197 -0
- package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/system.xml +381 -381
- package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/tech-debt-index.xml +125 -125
- package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/walkthrough.xml +255 -255
- package/{agile-context-engineering/workflows/map-story.xml → skills/map-story/workflow.xml} +1046 -1046
- package/{commands/ace/map-subsystem.md → skills/map-subsystem/SKILL.md} +155 -140
- package/skills/map-subsystem/script.js +51 -0
- package/skills/map-subsystem/script.test.js +68 -0
- package/skills/map-subsystem/templates/decizions.xml +115 -0
- package/skills/map-subsystem/templates/guide.xml +137 -0
- package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/module-discovery.xml +174 -174
- package/skills/map-subsystem/templates/pattern.xml +159 -0
- package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/subsystem-architecture.xml +343 -343
- package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/subsystem-structure.xml +234 -234
- package/skills/map-subsystem/templates/system-cross-cutting.xml +197 -0
- package/skills/map-subsystem/templates/system.xml +381 -0
- package/skills/map-subsystem/templates/walkthrough.xml +255 -0
- package/{agile-context-engineering/workflows/map-subsystem.xml → skills/map-subsystem/workflow.xml} +1173 -1178
- package/skills/map-sys-doc/SKILL.md +125 -0
- package/skills/map-sys-doc/system.xml +381 -0
- package/skills/map-sys-doc/workflow.xml +336 -0
- package/{commands/ace/map-system.md → skills/map-system/SKILL.md} +103 -92
- package/skills/map-system/script.js +75 -0
- package/skills/map-system/script.test.js +73 -0
- package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/system-architecture.xml +254 -254
- package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/system-structure.xml +177 -177
- package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/testing-framework.xml +283 -283
- package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/wiki-readme.xml +296 -296
- package/{agile-context-engineering/workflows/map-system.xml → skills/map-system/workflow.xml} +667 -672
- package/{commands/ace/map-walkthrough.md → skills/map-walkthrough/SKILL.md} +140 -127
- package/skills/map-walkthrough/walkthrough.xml +255 -0
- package/{agile-context-engineering/workflows/map-walkthrough.xml → skills/map-walkthrough/workflow.xml} +457 -457
- package/{commands/ace/plan-backlog.md → skills/plan-backlog/SKILL.md} +93 -83
- package/{agile-context-engineering/templates/product/product-backlog.xml → skills/plan-backlog/product-backlog-template.xml} +231 -231
- package/skills/plan-backlog/script.js +121 -0
- package/skills/plan-backlog/script.test.js +83 -0
- package/{agile-context-engineering/workflows/plan-backlog.xml → skills/plan-backlog/workflow.xml} +1348 -1356
- package/{commands/ace/plan-feature.md → skills/plan-feature/SKILL.md} +99 -89
- package/{agile-context-engineering/templates/product/feature.xml → skills/plan-feature/feature-template.xml} +361 -361
- package/skills/plan-feature/script.js +131 -0
- package/skills/plan-feature/script.test.js +80 -0
- package/{agile-context-engineering/workflows/plan-feature.xml → skills/plan-feature/workflow.xml} +1487 -1495
- package/{commands/ace/plan-product-vision.md → skills/plan-product-vision/SKILL.md} +91 -81
- package/{agile-context-engineering/templates/product/product-vision.xml → skills/plan-product-vision/product-vision-template.xml} +227 -227
- package/skills/plan-product-vision/script.js +51 -0
- package/skills/plan-product-vision/script.test.js +69 -0
- package/{agile-context-engineering/workflows/plan-product-vision.xml → skills/plan-product-vision/workflow.xml} +337 -342
- package/{commands/ace/plan-story.md → skills/plan-story/SKILL.md} +139 -159
- package/skills/plan-story/script.js +295 -0
- package/skills/plan-story/script.test.js +240 -0
- package/skills/plan-story/story-template.xml +458 -0
- package/{agile-context-engineering/workflows/plan-story.xml → skills/plan-story/workflow.xml} +1301 -944
- package/{commands/ace/research-external-solution.md → skills/research-external-solution/SKILL.md} +120 -138
- package/{agile-context-engineering/templates/product/external-solution.xml → skills/research-external-solution/external-solution-template.xml} +832 -832
- package/skills/research-external-solution/script.js +229 -0
- package/skills/research-external-solution/script.test.js +134 -0
- package/{agile-context-engineering/workflows/research-external-solution.xml → skills/research-external-solution/workflow.xml} +657 -659
- package/{commands/ace/research-integration-solution.md → skills/research-integration-solution/SKILL.md} +121 -135
- package/{agile-context-engineering/templates/product/story-integration-solution.xml → skills/research-integration-solution/integration-solution-template.xml} +1015 -1015
- package/skills/research-integration-solution/script.js +223 -0
- package/skills/research-integration-solution/script.test.js +134 -0
- package/{agile-context-engineering/workflows/research-integration-solution.xml → skills/research-integration-solution/workflow.xml} +711 -713
- package/{commands/ace/research-story-wiki.md → skills/research-story-wiki/SKILL.md} +101 -116
- package/skills/research-story-wiki/script.js +223 -0
- package/skills/research-story-wiki/script.test.js +138 -0
- package/{agile-context-engineering/templates/product/story-wiki.xml → skills/research-story-wiki/story-wiki-template.xml} +194 -194
- package/{agile-context-engineering/workflows/research-story-wiki.xml → skills/research-story-wiki/workflow.xml} +473 -475
- package/{commands/ace/research-technical-solution.md → skills/research-technical-solution/SKILL.md} +131 -147
- package/skills/research-technical-solution/script.js +223 -0
- package/skills/research-technical-solution/script.test.js +134 -0
- package/{agile-context-engineering/templates/product/story-technical-solution.xml → skills/research-technical-solution/technical-solution-template.xml} +1025 -1025
- package/{agile-context-engineering/workflows/research-technical-solution.xml → skills/research-technical-solution/workflow.xml} +761 -763
- package/{commands/ace/review-story.md → skills/review-story/SKILL.md} +99 -109
- package/skills/review-story/script.js +249 -0
- package/skills/review-story/script.test.js +169 -0
- package/skills/review-story/story-template.xml +451 -0
- package/{agile-context-engineering/workflows/review-story.xml → skills/review-story/workflow.xml} +279 -281
- package/{commands/ace/update.md → skills/update/SKILL.md} +65 -56
- package/{agile-context-engineering/workflows/update.xml → skills/update/workflow.xml} +33 -18
- package/agile-context-engineering/src/ace-tools.js +0 -2881
- package/agile-context-engineering/src/ace-tools.test.js +0 -1089
- package/agile-context-engineering/templates/_command.md +0 -54
- package/agile-context-engineering/templates/_workflow.xml +0 -17
- package/agile-context-engineering/templates/config.json +0 -0
- package/agile-context-engineering/templates/product/integration-solution.xml +0 -0
- package/commands/ace/help.md +0 -93
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: help
|
|
3
|
+
description: Check project initialization status and suggest next steps
|
|
4
|
+
argument-hint: ""
|
|
5
|
+
disable-model-invocation: false
|
|
6
|
+
allowed-tools: Read, Bash, Write, AskUserQuestion
|
|
7
|
+
model: sonnet
|
|
8
|
+
effort: medium
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Supporting Resources (auto-loaded)
|
|
12
|
+
|
|
13
|
+
!`cat "${CLAUDE_SKILL_DIR}/workflow.xml"`
|
|
14
|
+
|
|
15
|
+
!`cat "${CLAUDE_SKILL_DIR}/../../shared/utils/questioning.xml"`
|
|
16
|
+
|
|
17
|
+
!`cat "${CLAUDE_SKILL_DIR}/../../shared/utils/ui-formatting.md"`
|
|
18
|
+
|
|
19
|
+
```xml
|
|
20
|
+
<command>
|
|
21
|
+
|
|
22
|
+
<execution-time>
|
|
23
|
+
<runs-after>
|
|
24
|
+
<trigger>At any time — to check which ACE documents exist and what to do next</trigger>
|
|
25
|
+
<trigger>At the start of a new project — to see the initialization checklist</trigger>
|
|
26
|
+
</runs-after>
|
|
27
|
+
<use-when>
|
|
28
|
+
<condition>Starting a new project and want to see what needs to be set up</condition>
|
|
29
|
+
<condition>Returning to a project and want to check initialization status</condition>
|
|
30
|
+
<condition>Unsure which ACE command to run next</condition>
|
|
31
|
+
</use-when>
|
|
32
|
+
</execution-time>
|
|
33
|
+
|
|
34
|
+
<input>
|
|
35
|
+
<flags>
|
|
36
|
+
</flags>
|
|
37
|
+
|
|
38
|
+
<parameters>
|
|
39
|
+
<required>
|
|
40
|
+
</required>
|
|
41
|
+
|
|
42
|
+
<optional>
|
|
43
|
+
</optional>
|
|
44
|
+
</parameters>
|
|
45
|
+
</input>
|
|
46
|
+
|
|
47
|
+
<execution-context>
|
|
48
|
+
<!-- All supporting files are auto-loaded in the Supporting Resources section above.
|
|
49
|
+
The model does NOT need to Read these files — they are already in context. -->
|
|
50
|
+
</execution-context>
|
|
51
|
+
|
|
52
|
+
<output>
|
|
53
|
+
<objective>
|
|
54
|
+
Detect which ACE documents exist (product vision, system architecture, system structure,
|
|
55
|
+
coding standards, testing framework). Display a status dashboard showing what's done
|
|
56
|
+
and what's missing. Suggest the next command to run based on gaps.
|
|
57
|
+
</objective>
|
|
58
|
+
|
|
59
|
+
<artifacts>
|
|
60
|
+
- .ace/settings.json (created on first run if missing)
|
|
61
|
+
</artifacts>
|
|
62
|
+
</output>
|
|
63
|
+
|
|
64
|
+
<process>
|
|
65
|
+
Execute the help workflow from
|
|
66
|
+
`workflow.xml` end-to-end.
|
|
67
|
+
This is a lightweight state-check and routing workflow.
|
|
68
|
+
|
|
69
|
+
CRITICAL MANDATORY STEP — DO NOT SKIP:
|
|
70
|
+
Before displaying the status dashboard, you MUST run:
|
|
71
|
+
```
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* help skill script — Entry point for all ace-tools operations
|
|
5
|
+
* needed by the help skill.
|
|
6
|
+
*
|
|
7
|
+
* Subcommands:
|
|
8
|
+
* init Environment detection for help workflow
|
|
9
|
+
* ensure-settings Create .ace/settings.json with defaults if missing
|
|
10
|
+
* setup-github Detect gh CLI, repo, and list GitHub Projects
|
|
11
|
+
* write-github-settings Write GitHub Project settings (key=value args)
|
|
12
|
+
* sync-agent-teams Sync agent_teams from runtime settings to .ace/settings.json
|
|
13
|
+
* write-agent-teams <true|false> Enable/disable agent teams in ACE + runtime settings
|
|
14
|
+
* verify-path-exists <path> Check file/directory existence
|
|
15
|
+
*
|
|
16
|
+
* Usage: node script.js <subcommand> [args] [--raw]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
loadConfig, pathExists, resolveModel,
|
|
24
|
+
detectBrownfieldStatus, loadSettings, writeSettings,
|
|
25
|
+
output, error, runSkillScript,
|
|
26
|
+
} = require('../../shared/lib/ace-core');
|
|
27
|
+
|
|
28
|
+
// ─── Runtime Config Dir ─────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Detect the runtime config directory name.
|
|
32
|
+
* In the plugin context, the script lives at:
|
|
33
|
+
* <base>/<config-dir>/skills/help/script.js
|
|
34
|
+
* Default to '.claude' for backwards compatibility.
|
|
35
|
+
*/
|
|
36
|
+
function getRuntimeConfigDirName() {
|
|
37
|
+
try {
|
|
38
|
+
const skillDir = __dirname; // <base>/<config-dir>/skills/help
|
|
39
|
+
const skillsDir = path.dirname(skillDir); // <base>/<config-dir>/skills
|
|
40
|
+
const configDir = path.dirname(skillsDir); // <base>/<config-dir>
|
|
41
|
+
const dirName = path.basename(configDir);
|
|
42
|
+
if (dirName === '.opencode' || dirName === '.codex' || dirName === '.claude') {
|
|
43
|
+
return dirName;
|
|
44
|
+
}
|
|
45
|
+
} catch {}
|
|
46
|
+
return '.claude';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const RUNTIME_CONFIG_DIR = getRuntimeConfigDirName();
|
|
50
|
+
|
|
51
|
+
// ─── CLI Dispatch ────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
runSkillScript({
|
|
54
|
+
init: cmdInit,
|
|
55
|
+
'ensure-settings': (cwd, raw) => cmdEnsureSettings(cwd, raw),
|
|
56
|
+
'setup-github': (cwd, raw) => cmdSetupGithubProject(cwd, raw),
|
|
57
|
+
'write-github-settings': (cwd, raw, args) => cmdWriteGithubSettings(cwd, raw, args),
|
|
58
|
+
'sync-agent-teams': (cwd, raw) => cmdSyncAgentTeams(cwd, raw),
|
|
59
|
+
'write-agent-teams': (cwd, raw, args) => cmdWriteAgentTeamsSetting(cwd, raw, args),
|
|
60
|
+
'verify-path-exists': (cwd, raw, args, parsed) => {
|
|
61
|
+
const targetPath = parsed._positional || args[0];
|
|
62
|
+
if (!targetPath) error('path required for verify-path-exists');
|
|
63
|
+
const exists = pathExists(cwd, targetPath);
|
|
64
|
+
output({ exists, path: targetPath }, raw, exists ? 'true' : 'false');
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ─── Init: Help ─────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Environment detection for the help workflow (project status dashboard).
|
|
72
|
+
* Detects: git, gh CLI, brownfield status, wiki state, product artifacts.
|
|
73
|
+
*/
|
|
74
|
+
function cmdInit(cwd, raw) {
|
|
75
|
+
const config = loadConfig(cwd);
|
|
76
|
+
const brownfield = detectBrownfieldStatus(cwd);
|
|
77
|
+
|
|
78
|
+
const result = {
|
|
79
|
+
// Models (pre-resolved so workflows know which model to spawn each agent with)
|
|
80
|
+
product_owner_model: resolveModel(cwd, 'ace-product-owner'),
|
|
81
|
+
researcher_model: resolveModel(cwd, 'ace-project-researcher'),
|
|
82
|
+
synthesizer_model: resolveModel(cwd, 'ace-research-synthesizer'),
|
|
83
|
+
|
|
84
|
+
// Config
|
|
85
|
+
commit_docs: config.commit_docs,
|
|
86
|
+
runtime_config_dir: RUNTIME_CONFIG_DIR,
|
|
87
|
+
|
|
88
|
+
// Existing state
|
|
89
|
+
has_product_vision: pathExists(cwd, '.docs/product/product-vision.md'),
|
|
90
|
+
has_system_architecture: pathExists(cwd, '.docs/wiki/system-wide/system-architecture.md'),
|
|
91
|
+
has_system_structure: pathExists(cwd, '.docs/wiki/system-wide/system-structure.md'),
|
|
92
|
+
has_coding_standards: pathExists(cwd, '.docs/wiki/system-wide/coding-standards.md'),
|
|
93
|
+
has_testing_framework: pathExists(cwd, '.docs/wiki/system-wide/testing-framework.md'),
|
|
94
|
+
project_exists: pathExists(cwd, '.docs/product/product-vision.md'),
|
|
95
|
+
has_codebase_map: pathExists(cwd, '.ace/codebase'),
|
|
96
|
+
planning_exists: pathExists(cwd, '.ace'),
|
|
97
|
+
|
|
98
|
+
// Brownfield detection
|
|
99
|
+
...brownfield,
|
|
100
|
+
needs_codebase_map: brownfield.is_brownfield && !pathExists(cwd, '.ace/codebase'),
|
|
101
|
+
|
|
102
|
+
// Git state
|
|
103
|
+
has_git: pathExists(cwd, '.git'),
|
|
104
|
+
|
|
105
|
+
// GitHub CLI
|
|
106
|
+
has_gh_cli: (() => {
|
|
107
|
+
try {
|
|
108
|
+
const { execSync } = require('child_process');
|
|
109
|
+
execSync('gh --version', { stdio: 'pipe' });
|
|
110
|
+
return true;
|
|
111
|
+
} catch { return false; }
|
|
112
|
+
})(),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
output(result, raw);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Ensure Settings ────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
function cmdEnsureSettings(cwd, raw) {
|
|
121
|
+
const settingsPath = path.join(cwd, '.ace', 'settings.json');
|
|
122
|
+
const alreadyExists = pathExists(cwd, '.ace/settings.json');
|
|
123
|
+
|
|
124
|
+
if (!alreadyExists) {
|
|
125
|
+
const SETTINGS_DEFAULTS = {
|
|
126
|
+
model_profile: 'balanced',
|
|
127
|
+
commit_docs: true,
|
|
128
|
+
agent_teams: false,
|
|
129
|
+
github_project: {
|
|
130
|
+
enabled: false,
|
|
131
|
+
gh_installed: false,
|
|
132
|
+
repo: '',
|
|
133
|
+
project_number: null,
|
|
134
|
+
owner: '',
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
const defaults = JSON.parse(JSON.stringify(SETTINGS_DEFAULTS));
|
|
138
|
+
writeSettings(cwd, defaults);
|
|
139
|
+
output({ created: true, path: settingsPath, settings: defaults }, raw);
|
|
140
|
+
} else {
|
|
141
|
+
const settings = loadSettings(cwd);
|
|
142
|
+
output({ created: false, path: settingsPath, settings }, raw);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Setup GitHub Project ───────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
function cmdSetupGithubProject(cwd, raw) {
|
|
149
|
+
const { execSync } = require('child_process');
|
|
150
|
+
const settings = loadSettings(cwd);
|
|
151
|
+
|
|
152
|
+
// Detect gh CLI
|
|
153
|
+
let ghInstalled = false;
|
|
154
|
+
try {
|
|
155
|
+
execSync('gh --version', { stdio: 'pipe' });
|
|
156
|
+
ghInstalled = true;
|
|
157
|
+
} catch {}
|
|
158
|
+
|
|
159
|
+
// Detect repo
|
|
160
|
+
let repo = '';
|
|
161
|
+
let owner = '';
|
|
162
|
+
if (ghInstalled) {
|
|
163
|
+
try {
|
|
164
|
+
repo = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', {
|
|
165
|
+
cwd,
|
|
166
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
167
|
+
encoding: 'utf-8',
|
|
168
|
+
}).trim();
|
|
169
|
+
owner = repo.split('/')[0] || '';
|
|
170
|
+
} catch {}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// List projects
|
|
174
|
+
let projects = [];
|
|
175
|
+
if (ghInstalled && owner) {
|
|
176
|
+
try {
|
|
177
|
+
const projectsJson = execSync(`gh project list --owner ${owner} --limit 10 --format json`, {
|
|
178
|
+
cwd,
|
|
179
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
180
|
+
encoding: 'utf-8',
|
|
181
|
+
}).trim();
|
|
182
|
+
const parsed = JSON.parse(projectsJson);
|
|
183
|
+
projects = (parsed.projects || parsed || []).map(p => ({
|
|
184
|
+
number: p.number,
|
|
185
|
+
title: p.title,
|
|
186
|
+
}));
|
|
187
|
+
} catch {}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
output({
|
|
191
|
+
gh_installed: ghInstalled,
|
|
192
|
+
repo,
|
|
193
|
+
owner,
|
|
194
|
+
projects,
|
|
195
|
+
current_settings: settings.github_project,
|
|
196
|
+
}, raw);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ─── Write GitHub Settings ──────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
function cmdWriteGithubSettings(cwd, raw, extraArgs) {
|
|
202
|
+
const settings = loadSettings(cwd);
|
|
203
|
+
|
|
204
|
+
for (const arg of extraArgs) {
|
|
205
|
+
const eqIndex = arg.indexOf('=');
|
|
206
|
+
if (eqIndex === -1) continue;
|
|
207
|
+
const key = arg.substring(0, eqIndex);
|
|
208
|
+
const value = arg.substring(eqIndex + 1);
|
|
209
|
+
|
|
210
|
+
switch (key) {
|
|
211
|
+
case 'enabled':
|
|
212
|
+
settings.github_project.enabled = value === 'true';
|
|
213
|
+
break;
|
|
214
|
+
case 'gh_installed':
|
|
215
|
+
settings.github_project.gh_installed = value === 'true';
|
|
216
|
+
break;
|
|
217
|
+
case 'repo':
|
|
218
|
+
settings.github_project.repo = value;
|
|
219
|
+
break;
|
|
220
|
+
case 'project_number':
|
|
221
|
+
settings.github_project.project_number = value === 'null' ? null : parseInt(value, 10);
|
|
222
|
+
break;
|
|
223
|
+
case 'owner':
|
|
224
|
+
settings.github_project.owner = value;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
writeSettings(cwd, settings);
|
|
230
|
+
output({ written: true, settings }, raw);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ─── Sync Agent Teams ───────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
function cmdSyncAgentTeams(cwd, raw) {
|
|
236
|
+
if (RUNTIME_CONFIG_DIR === '.codex') {
|
|
237
|
+
const settings = loadSettings(cwd);
|
|
238
|
+
const wasDifferent = settings.agent_teams !== false;
|
|
239
|
+
if (wasDifferent) {
|
|
240
|
+
settings.agent_teams = false;
|
|
241
|
+
writeSettings(cwd, settings);
|
|
242
|
+
}
|
|
243
|
+
output({ agent_teams: false, synced: wasDifferent, runtime: 'codex' }, raw);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Source of truth: runtime settings.json env var (e.g. .claude/settings.json)
|
|
248
|
+
const claudeSettingsPath = path.join(cwd, RUNTIME_CONFIG_DIR, 'settings.json');
|
|
249
|
+
let claudeEnabled = false;
|
|
250
|
+
try {
|
|
251
|
+
const claudeRaw = fs.readFileSync(claudeSettingsPath, 'utf-8');
|
|
252
|
+
const claudeSettings = JSON.parse(claudeRaw);
|
|
253
|
+
const val = claudeSettings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
254
|
+
claudeEnabled = val === '1' || val === 'true';
|
|
255
|
+
} catch {
|
|
256
|
+
// File doesn't exist or is invalid — treat as disabled
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Sync ACE settings to match Claude's source of truth
|
|
260
|
+
const settings = loadSettings(cwd);
|
|
261
|
+
const wasDifferent = settings.agent_teams !== claudeEnabled;
|
|
262
|
+
if (wasDifferent) {
|
|
263
|
+
settings.agent_teams = claudeEnabled;
|
|
264
|
+
writeSettings(cwd, settings);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
output({ agent_teams: claudeEnabled, synced: wasDifferent }, raw);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ─── Write Agent Teams Setting ──────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
function cmdWriteAgentTeamsSetting(cwd, raw, extraArgs) {
|
|
273
|
+
const enabled = extraArgs[0] === 'true';
|
|
274
|
+
const settings = loadSettings(cwd);
|
|
275
|
+
settings.agent_teams = RUNTIME_CONFIG_DIR === '.codex' ? false : enabled;
|
|
276
|
+
writeSettings(cwd, settings);
|
|
277
|
+
|
|
278
|
+
if (RUNTIME_CONFIG_DIR === '.codex') {
|
|
279
|
+
output({ written: true, agent_teams: false, settings, runtime: 'codex' }, raw);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Also update the project's runtime settings.json (e.g. .claude/)
|
|
284
|
+
const claudeDir = path.join(cwd, RUNTIME_CONFIG_DIR);
|
|
285
|
+
const claudeSettingsPath = path.join(claudeDir, 'settings.json');
|
|
286
|
+
|
|
287
|
+
let claudeSettings = {};
|
|
288
|
+
try {
|
|
289
|
+
const existing = fs.readFileSync(claudeSettingsPath, 'utf-8');
|
|
290
|
+
claudeSettings = JSON.parse(existing);
|
|
291
|
+
} catch {
|
|
292
|
+
// File doesn't exist or is invalid — start fresh
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!claudeSettings.env) {
|
|
296
|
+
claudeSettings.env = {};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (enabled) {
|
|
300
|
+
claudeSettings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
|
|
301
|
+
} else {
|
|
302
|
+
delete claudeSettings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
303
|
+
// Clean up empty env object
|
|
304
|
+
if (Object.keys(claudeSettings.env).length === 0) {
|
|
305
|
+
delete claudeSettings.env;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!fs.existsSync(claudeDir)) {
|
|
310
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
311
|
+
}
|
|
312
|
+
fs.writeFileSync(claudeSettingsPath, JSON.stringify(claudeSettings, null, 2) + '\n', 'utf-8');
|
|
313
|
+
|
|
314
|
+
output({ written: true, agent_teams: enabled, settings, claude_settings: claudeSettings }, raw);
|
|
315
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
function runScript(subcommand, args, cwd) {
|
|
39
|
+
return execSync(`node "${SCRIPT}" ${subcommand} ${args}`, {
|
|
40
|
+
cwd,
|
|
41
|
+
encoding: 'utf-8',
|
|
42
|
+
timeout: 10000,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function cleanup(tmpDir) {
|
|
47
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
describe('help script', () => {
|
|
53
|
+
|
|
54
|
+
describe('init', () => {
|
|
55
|
+
let tmpDir;
|
|
56
|
+
|
|
57
|
+
before(() => { tmpDir = createTestProject(); });
|
|
58
|
+
after(() => { cleanup(tmpDir); });
|
|
59
|
+
|
|
60
|
+
it('returns valid JSON with environment detection', () => {
|
|
61
|
+
const result = JSON.parse(runScript('init', '', tmpDir));
|
|
62
|
+
|
|
63
|
+
assert.strictEqual(typeof result.commit_docs, 'boolean');
|
|
64
|
+
assert.strictEqual(typeof result.has_git, 'boolean');
|
|
65
|
+
assert.strictEqual(typeof result.has_product_vision, 'boolean');
|
|
66
|
+
assert.strictEqual(typeof result.has_system_architecture, 'boolean');
|
|
67
|
+
assert.strictEqual(typeof result.has_system_structure, 'boolean');
|
|
68
|
+
assert.strictEqual(typeof result.has_coding_standards, 'boolean');
|
|
69
|
+
assert.strictEqual(typeof result.has_testing_framework, 'boolean');
|
|
70
|
+
assert.strictEqual(typeof result.is_brownfield, 'boolean');
|
|
71
|
+
assert.strictEqual(typeof result.is_greenfield, 'boolean');
|
|
72
|
+
assert.ok(result.product_owner_model, 'should have product_owner_model');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('detects brownfield vs greenfield correctly', () => {
|
|
76
|
+
const result = JSON.parse(runScript('init', '', tmpDir));
|
|
77
|
+
assert.strictEqual(result.is_brownfield, !result.is_greenfield);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('ensure-settings', () => {
|
|
82
|
+
let tmpDir;
|
|
83
|
+
|
|
84
|
+
before(() => { tmpDir = createTestProject(); });
|
|
85
|
+
after(() => { cleanup(tmpDir); });
|
|
86
|
+
|
|
87
|
+
it('reports settings already exist when they do', () => {
|
|
88
|
+
const result = JSON.parse(runScript('ensure-settings', '', tmpDir));
|
|
89
|
+
assert.strictEqual(result.created, false);
|
|
90
|
+
assert.ok(result.settings);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('creates settings when they do not exist', () => {
|
|
94
|
+
const freshDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ace-test-'));
|
|
95
|
+
const aceDir = path.join(freshDir, '.ace');
|
|
96
|
+
fs.mkdirSync(aceDir, { recursive: true });
|
|
97
|
+
fs.writeFileSync(path.join(aceDir, 'config.json'), JSON.stringify({
|
|
98
|
+
version: '0.1.0',
|
|
99
|
+
projectName: 'test',
|
|
100
|
+
model_profile: 'balanced',
|
|
101
|
+
commit_docs: true,
|
|
102
|
+
github: { enabled: false },
|
|
103
|
+
}, null, 2));
|
|
104
|
+
|
|
105
|
+
const result = JSON.parse(runScript('ensure-settings', '', freshDir));
|
|
106
|
+
assert.strictEqual(result.created, true);
|
|
107
|
+
assert.ok(result.settings);
|
|
108
|
+
assert.strictEqual(result.settings.model_profile, 'balanced');
|
|
109
|
+
|
|
110
|
+
cleanup(freshDir);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('sync-agent-teams', () => {
|
|
115
|
+
let tmpDir;
|
|
116
|
+
|
|
117
|
+
before(() => { tmpDir = createTestProject(); });
|
|
118
|
+
after(() => { cleanup(tmpDir); });
|
|
119
|
+
|
|
120
|
+
it('returns agent_teams boolean', () => {
|
|
121
|
+
const result = JSON.parse(runScript('sync-agent-teams', '', tmpDir));
|
|
122
|
+
assert.strictEqual(typeof result.agent_teams, 'boolean');
|
|
123
|
+
assert.strictEqual(typeof result.synced, 'boolean');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('write-agent-teams', () => {
|
|
128
|
+
let tmpDir;
|
|
129
|
+
|
|
130
|
+
before(() => { tmpDir = createTestProject(); });
|
|
131
|
+
after(() => { cleanup(tmpDir); });
|
|
132
|
+
|
|
133
|
+
it('enables agent teams', () => {
|
|
134
|
+
const result = JSON.parse(runScript('write-agent-teams', 'true', tmpDir));
|
|
135
|
+
assert.strictEqual(result.written, true);
|
|
136
|
+
assert.strictEqual(result.agent_teams, true);
|
|
137
|
+
|
|
138
|
+
// Verify .ace/settings.json updated
|
|
139
|
+
const settings = JSON.parse(fs.readFileSync(path.join(tmpDir, '.ace', 'settings.json'), 'utf-8'));
|
|
140
|
+
assert.strictEqual(settings.agent_teams, true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('disables agent teams', () => {
|
|
144
|
+
const result = JSON.parse(runScript('write-agent-teams', 'false', tmpDir));
|
|
145
|
+
assert.strictEqual(result.written, true);
|
|
146
|
+
assert.strictEqual(result.agent_teams, false);
|
|
147
|
+
|
|
148
|
+
const settings = JSON.parse(fs.readFileSync(path.join(tmpDir, '.ace', 'settings.json'), 'utf-8'));
|
|
149
|
+
assert.strictEqual(settings.agent_teams, false);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('verify-path-exists', () => {
|
|
154
|
+
let tmpDir;
|
|
155
|
+
|
|
156
|
+
before(() => { tmpDir = createTestProject(); });
|
|
157
|
+
after(() => { cleanup(tmpDir); });
|
|
158
|
+
|
|
159
|
+
it('returns true for existing path', () => {
|
|
160
|
+
const result = runScript('verify-path-exists', '.ace/config.json --raw', tmpDir).trim();
|
|
161
|
+
assert.strictEqual(result, 'true');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('returns false for non-existing path', () => {
|
|
165
|
+
const result = runScript('verify-path-exists', 'nonexistent/file.md --raw', tmpDir).trim();
|
|
166
|
+
assert.strictEqual(result, 'false');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('error handling', () => {
|
|
171
|
+
it('errors on unknown command', () => {
|
|
172
|
+
assert.throws(() => {
|
|
173
|
+
execSync(`node "${SCRIPT}" bogus`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('errors on verify-path-exists without path', () => {
|
|
178
|
+
assert.throws(() => {
|
|
179
|
+
execSync(`node "${SCRIPT}" verify-path-exists`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|