bmad-module-ultracode-goal 0.1.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 (47) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.gitattributes +16 -0
  3. package/.nvmrc +1 -0
  4. package/LICENSE +27 -0
  5. package/README.md +200 -0
  6. package/docs/_internal/RELEASING.md +101 -0
  7. package/docs/_internal/STABILITY.md +62 -0
  8. package/docs/architecture.md +77 -0
  9. package/docs/assets/ucg-logo.svg +73 -0
  10. package/docs/gate-model.md +109 -0
  11. package/docs/getting-started.md +51 -0
  12. package/docs/health-check.md +61 -0
  13. package/docs/how-it-works.md +73 -0
  14. package/docs/index.md +25 -0
  15. package/docs/parallel-mode.md +33 -0
  16. package/docs/troubleshooting.md +56 -0
  17. package/docs/why-ultracode-goal.md +34 -0
  18. package/package.json +100 -0
  19. package/skills/.gitkeep +0 -0
  20. package/skills/module.yaml +12 -0
  21. package/skills/ultracode-goal/SKILL.md +75 -0
  22. package/skills/ultracode-goal/assets/execute-epic.workflow.js +208 -0
  23. package/skills/ultracode-goal/customize.toml +47 -0
  24. package/skills/ultracode-goal/references/define-done.md +41 -0
  25. package/skills/ultracode-goal/references/execute.md +90 -0
  26. package/skills/ultracode-goal/references/finalize.md +64 -0
  27. package/skills/ultracode-goal/references/gate.md +70 -0
  28. package/skills/ultracode-goal/references/health-check.md +332 -0
  29. package/skills/ultracode-goal/references/ingest-and-scope.md +46 -0
  30. package/skills/ultracode-goal/references/preflight.md +100 -0
  31. package/skills/ultracode-goal/scripts/gate_eval.py +280 -0
  32. package/skills/ultracode-goal/scripts/health_check_fp.py +196 -0
  33. package/skills/ultracode-goal/scripts/hooks/budget_stop.py +162 -0
  34. package/skills/ultracode-goal/scripts/hooks/guard_pretooluse.py +174 -0
  35. package/skills/ultracode-goal/scripts/preflight_check.py +399 -0
  36. package/tools/cli/commands/install.js +36 -0
  37. package/tools/cli/commands/status.js +161 -0
  38. package/tools/cli/commands/uninstall.js +175 -0
  39. package/tools/cli/commands/update.js +65 -0
  40. package/tools/cli/lib/ide-skills.js +218 -0
  41. package/tools/cli/lib/installer.js +226 -0
  42. package/tools/cli/lib/manifest.js +100 -0
  43. package/tools/cli/lib/platform-codes.yaml +223 -0
  44. package/tools/cli/lib/ui.js +353 -0
  45. package/tools/cli/lib/version-check.js +86 -0
  46. package/tools/cli/ucg-cli.js +45 -0
  47. package/tools/ucg-npx-wrapper.js +36 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * UCG Installer - Core orchestrator
3
+ * Copies the ultracode-goal skill, installs it to IDEs, writes config + manifest.
4
+ */
5
+
6
+ const path = require('node:path');
7
+ const fs = require('fs-extra');
8
+ const { spinner } = require('@clack/prompts');
9
+ const yaml = require('js-yaml');
10
+ const { installSkillsToIdes } = require('./ide-skills');
11
+ const { writeManifest } = require('./manifest');
12
+
13
+ // Dev-only artifacts never shipped into a user's project
14
+ const DEV_ARTIFACTS = new Set(['.analysis', '.decision-log.md', '__pycache__', '.pytest_cache', '.DS_Store', 'Thumbs.db']);
15
+
16
+ class Installer {
17
+ constructor() {
18
+ // Resolve directories relative to this file (tools/cli/lib/ -> up 3 levels)
19
+ const repoRoot = path.resolve(__dirname, '..', '..', '..');
20
+ this.srcDir = path.join(repoRoot, 'skills');
21
+ this.docsDir = path.join(repoRoot, 'docs');
22
+ }
23
+
24
+ async install(config) {
25
+ const { projectDir, ucgFolder } = config;
26
+ const ucgDir = path.join(projectDir, ucgFolder);
27
+ const action = config._action || 'fresh';
28
+ const s = spinner();
29
+
30
+ // Handle update vs fresh for existing installation
31
+ if (action === 'update' && (await fs.pathExists(ucgDir))) {
32
+ const configPath = path.join(ucgDir, 'config.yaml');
33
+ if (!config._savedConfigYaml && (await fs.pathExists(configPath))) {
34
+ config._savedConfigYaml = await fs.readFile(configPath, 'utf8');
35
+ }
36
+
37
+ // On update, extract settings from saved config
38
+ if (config._savedConfigYaml) {
39
+ try {
40
+ const savedData = yaml.load(config._savedConfigYaml);
41
+ if (!config.ides && savedData.ides) config.ides = savedData.ides;
42
+ if (config.install_learning == null && savedData.install_learning != null) config.install_learning = savedData.install_learning;
43
+ } catch {
44
+ /* ignore parse errors, defaults will apply */
45
+ }
46
+ }
47
+
48
+ s.start('Updating UCG files...');
49
+ await fs.remove(ucgDir);
50
+ s.stop('Old files cleared');
51
+ } else if (action === 'fresh' && (await fs.pathExists(ucgDir))) {
52
+ s.start('Removing existing UCG installation...');
53
+ await fs.remove(ucgDir);
54
+ s.stop('Old installation removed');
55
+ }
56
+
57
+ // Ensure parent directory exists (for _bmad/ucg/)
58
+ await fs.ensureDir(path.dirname(ucgDir));
59
+
60
+ // Step 1: Copy source files
61
+ s.start('Copying UCG files...');
62
+ try {
63
+ await this.copySrcFiles(ucgDir);
64
+ s.stop('UCG files copied');
65
+ } catch (error) {
66
+ s.stop('Failed to copy UCG files');
67
+ throw error;
68
+ }
69
+
70
+ // Step 2: Update .gitignore (the skill merges hooks into .claude/settings.local.json
71
+ // at preflight — that file must never be committed)
72
+ await this.updateGitignore(projectDir);
73
+
74
+ // Step 3: Write config.yaml
75
+ s.start('Writing configuration...');
76
+ try {
77
+ await this.writeConfig(ucgDir, config);
78
+ s.stop('Configuration saved');
79
+ } catch (error) {
80
+ s.stop('Failed to write configuration');
81
+ throw error;
82
+ }
83
+
84
+ // Step 4: Copy learning material (optional)
85
+ if (config.install_learning !== false) {
86
+ s.start('Copying learning & reference material...');
87
+ try {
88
+ await this.copyLearningMaterial(projectDir);
89
+ s.stop('Learning material added to _ucg-learn/');
90
+ } catch (error) {
91
+ s.stop('Failed to copy learning material');
92
+ throw error;
93
+ }
94
+ }
95
+
96
+ // Step 5: Install the skill to selected IDEs
97
+ let ideDirectories = [];
98
+ const selectedIdes = config.ides || [];
99
+ if (selectedIdes.length > 0) {
100
+ s.start('Installing the skill to IDEs...');
101
+ try {
102
+ const ideResult = await installSkillsToIdes(projectDir, ucgDir, selectedIdes);
103
+ ideDirectories = ideResult.directories || [];
104
+ if (ideResult.installed > 0) {
105
+ s.stop(`Skill installed for ${ideResult.ides.join(', ')}`);
106
+ } else {
107
+ s.stop('No IDE skill installation needed');
108
+ }
109
+ } catch (error) {
110
+ s.stop('Failed to install the skill to IDEs');
111
+ throw error;
112
+ }
113
+ }
114
+
115
+ // Step 6: Write installation manifest
116
+ s.start('Writing manifest...');
117
+ try {
118
+ const packageJson = require('../../../package.json');
119
+ await writeManifest(projectDir, config, {
120
+ version: packageJson.version,
121
+ ideDirectories,
122
+ });
123
+ s.stop('Installation manifest saved');
124
+ } catch (error) {
125
+ s.stop('Failed to write manifest');
126
+ throw error;
127
+ }
128
+
129
+ return { success: true, ucgDir, projectDir };
130
+ }
131
+
132
+ /**
133
+ * Copy skills/ content into the target UCG directory.
134
+ * The single ultracode-goal skill directory is copied alongside module.yaml,
135
+ * filtering out dev/test artifacts that have no place in a user's project.
136
+ */
137
+ async copySrcFiles(ucgDir) {
138
+ const copyFilter = (src) => {
139
+ const base = path.basename(src);
140
+ if (DEV_ARTIFACTS.has(base)) return false;
141
+ // Skip pytest suites — dev-only, not needed at runtime
142
+ if (base === 'tests' && path.basename(path.dirname(src)) === 'scripts') return false;
143
+ return true;
144
+ };
145
+
146
+ // Copy skill directories — each is a self-contained skill
147
+ const srcEntries = await fs.readdir(this.srcDir, { withFileTypes: true });
148
+ for (const entry of srcEntries) {
149
+ if (entry.isDirectory()) {
150
+ await fs.copy(path.join(this.srcDir, entry.name), path.join(ucgDir, entry.name), { filter: copyFilter });
151
+ }
152
+ }
153
+
154
+ // Copy the module manifest
155
+ const moduleYaml = path.join(this.srcDir, 'module.yaml');
156
+ if (await fs.pathExists(moduleYaml)) {
157
+ await fs.copy(moduleYaml, path.join(ucgDir, 'module.yaml'));
158
+ }
159
+
160
+ // Write VERSION file for UCG version resolution in installed projects
161
+ const packageJson = require('../../../package.json');
162
+ await fs.writeFile(path.join(ucgDir, 'VERSION'), packageJson.version, 'utf8');
163
+ }
164
+
165
+ async writeConfig(ucgDir, config) {
166
+ // On update, restore the user's existing config
167
+ if (config._savedConfigYaml) {
168
+ await fs.writeFile(path.join(ucgDir, 'config.yaml'), config._savedConfigYaml, 'utf8');
169
+ return;
170
+ }
171
+
172
+ // Get user name from git or system
173
+ const getUserName = () => {
174
+ try {
175
+ const { execSync } = require('node:child_process');
176
+ return execSync('git config user.name', { encoding: 'utf8' }).trim() || 'Developer';
177
+ } catch {
178
+ return 'Developer';
179
+ }
180
+ };
181
+
182
+ const configData = {
183
+ user_name: getUserName(),
184
+ project_name: config.project_name || 'Untitled Project',
185
+ communication_language: 'en',
186
+ document_output_language: 'en',
187
+ output_folder: config.output_folder || '_bmad-output',
188
+ ucg_folder: config.ucgFolder,
189
+ ides: config.ides || [],
190
+ install_learning: config.install_learning !== false,
191
+ };
192
+
193
+ const yamlStr = yaml.dump(configData, { lineWidth: -1 });
194
+ await fs.writeFile(path.join(ucgDir, 'config.yaml'), `# UCG Configuration - Generated by installer\n${yamlStr}`, 'utf8');
195
+ }
196
+
197
+ async updateGitignore(projectDir) {
198
+ const gitignorePath = path.join(projectDir, '.gitignore');
199
+ const entry = '.claude/settings.local.json';
200
+
201
+ try {
202
+ if (await fs.pathExists(gitignorePath)) {
203
+ const content = await fs.readFile(gitignorePath, 'utf8');
204
+ // Check if entry already present (exact line match)
205
+ const lines = content.split('\n');
206
+ if (lines.some((line) => line.trim() === entry)) return;
207
+ // Append with preceding newline if file doesn't end with one
208
+ const prefix = content.endsWith('\n') ? '' : '\n';
209
+ await fs.appendFile(gitignorePath, `${prefix}${entry}\n`, 'utf8');
210
+ } else {
211
+ await fs.writeFile(gitignorePath, `${entry}\n`, 'utf8');
212
+ }
213
+ } catch {
214
+ // Non-critical — don't fail the install over .gitignore
215
+ }
216
+ }
217
+
218
+ async copyLearningMaterial(projectDir) {
219
+ const learnDir = path.join(projectDir, '_ucg-learn');
220
+ if (await fs.pathExists(this.docsDir)) {
221
+ await fs.copy(this.docsDir, learnDir);
222
+ }
223
+ }
224
+ }
225
+
226
+ module.exports = { Installer };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * UCG Manifest - Tracks installed files for uninstall and smart updates.
3
+ * Writes _bmad/_config/ucg-manifest.yaml after each install.
4
+ */
5
+
6
+ const path = require('node:path');
7
+ const fs = require('fs-extra');
8
+ const yaml = require('js-yaml');
9
+
10
+ const MANIFEST_DIR = '_bmad/_config';
11
+ const MANIFEST_FILE = 'ucg-manifest.yaml';
12
+
13
+ /**
14
+ * Recursively collect all file paths under a directory,
15
+ * returned as paths relative to projectDir.
16
+ */
17
+ async function collectFiles(dir, projectDir) {
18
+ const files = [];
19
+ if (!(await fs.pathExists(dir))) return files;
20
+
21
+ const entries = await fs.readdir(dir, { withFileTypes: true });
22
+ for (const entry of entries) {
23
+ const fullPath = path.join(dir, entry.name);
24
+ if (entry.isDirectory()) {
25
+ const subFiles = await collectFiles(fullPath, projectDir);
26
+ files.push(...subFiles);
27
+ } else {
28
+ files.push(path.relative(projectDir, fullPath));
29
+ }
30
+ }
31
+ return files;
32
+ }
33
+
34
+ /**
35
+ * Build and write the manifest after installation.
36
+ *
37
+ * @param {string} projectDir - Project root
38
+ * @param {object} config - Install config (ucgFolder, ides, etc.)
39
+ * @param {object} options - Additional info (version, ideDirectories)
40
+ */
41
+ async function writeManifest(projectDir, config, options = {}) {
42
+ const ucgFolder = config.ucgFolder || '_bmad/ucg';
43
+ const ucgDir = path.join(projectDir, ucgFolder);
44
+
45
+ // Collect all installed files
46
+ const ucgFiles = await collectFiles(ucgDir, projectDir);
47
+
48
+ // Collect IDE skill directory files (installed to each IDE's skills/ dir)
49
+ const ideDirectories = options.ideDirectories || [];
50
+ const ideSkillFiles = [];
51
+ for (const ideDir of ideDirectories) {
52
+ const fullDir = path.join(projectDir, ideDir);
53
+ const files = await collectFiles(fullDir, projectDir);
54
+ ideSkillFiles.push(...files);
55
+ }
56
+
57
+ // Collect learning material files
58
+ const learnDir = path.join(projectDir, '_ucg-learn');
59
+ const learnFiles = await collectFiles(learnDir, projectDir);
60
+
61
+ const manifest = {
62
+ version: options.version || 'unknown',
63
+ installed_at: new Date().toISOString(),
64
+ action: config._action || 'fresh',
65
+ module: 'ucg',
66
+ ucg_folder: ucgFolder,
67
+ ides: config.ides || [],
68
+ directories: [ucgFolder, ...ideDirectories, ...(learnFiles.length > 0 ? ['_ucg-learn'] : [])],
69
+ files: {
70
+ ucg: ucgFiles,
71
+ ide_skills: ideSkillFiles,
72
+ learning: learnFiles,
73
+ },
74
+ };
75
+
76
+ const manifestDir = path.join(projectDir, MANIFEST_DIR);
77
+ await fs.ensureDir(manifestDir);
78
+
79
+ const yamlStr = yaml.dump(manifest, { lineWidth: -1, sortKeys: false });
80
+ await fs.writeFile(
81
+ path.join(manifestDir, MANIFEST_FILE),
82
+ `# UCG Installation Manifest - Generated by installer\n# Do not edit manually\n${yamlStr}`,
83
+ 'utf8',
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Read an existing manifest, or return null if none exists.
89
+ */
90
+ async function readManifest(projectDir) {
91
+ const manifestPath = path.join(projectDir, MANIFEST_DIR, MANIFEST_FILE);
92
+ try {
93
+ const content = await fs.readFile(manifestPath, 'utf8');
94
+ return yaml.load(content) || null;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+
100
+ module.exports = { writeManifest, readManifest, MANIFEST_DIR, MANIFEST_FILE };
@@ -0,0 +1,223 @@
1
+ # BMAD Platform Codes Configuration
2
+ #
3
+ # Each platform entry has:
4
+ # name: Display name shown to users
5
+ # preferred: Whether shown as a recommended option on install
6
+ # suspended: (optional) Message explaining why install is blocked
7
+ # skill_invocation_prefix: (optional) Prefix users type to invoke a named
8
+ # skill in this IDE's chat. Examples:
9
+ # "/" — slash command, e.g. /ultracode-goal (most IDEs)
10
+ # "$" — dollar mention, e.g. $ultracode-goal (Codex)
11
+ # "/skill:" — namespaced slash, e.g. /skill:ultracode-goal (Pi)
12
+ # Omit when the IDE only auto-invokes skills from descriptions and has
13
+ # no manual invocation form (Cline, GitHub Copilot, Junie, etc.).
14
+ # installer:
15
+ # target_dir: Directory where skill directories are installed
16
+ # legacy_targets: (optional) Old target dirs to clean up on reinstall
17
+ platforms:
18
+ antigravity:
19
+ name: "Google Antigravity"
20
+ preferred: false
21
+ skill_invocation_prefix: "/"
22
+ installer:
23
+ detection_marker: .agent/antigravity
24
+ legacy_targets:
25
+ - .agent/workflows
26
+ target_dir: .agent/skills
27
+
28
+ auggie:
29
+ name: "Auggie"
30
+ preferred: false
31
+ skill_invocation_prefix: "/"
32
+ installer:
33
+ legacy_targets:
34
+ - .augment/commands
35
+ target_dir: .augment/skills
36
+
37
+ claude-code:
38
+ name: "Claude Code"
39
+ preferred: true
40
+ skill_invocation_prefix: "/"
41
+ installer:
42
+ legacy_targets:
43
+ - .claude/commands
44
+ target_dir: .claude/skills
45
+
46
+ cline:
47
+ name: "Cline"
48
+ preferred: false
49
+ # Auto-invoked from descriptions; no manual prefix
50
+ installer:
51
+ detection_marker: .clinerules
52
+ legacy_targets:
53
+ - .clinerules/workflows
54
+ target_dir: .cline/skills
55
+
56
+ codex:
57
+ name: "Codex"
58
+ preferred: false
59
+ skill_invocation_prefix: "$"
60
+ installer:
61
+ detection_marker: .codex
62
+ legacy_targets:
63
+ - .codex/prompts
64
+ target_dir: .agents/skills
65
+
66
+ codebuddy:
67
+ name: "CodeBuddy"
68
+ preferred: false
69
+ skill_invocation_prefix: "/"
70
+ installer:
71
+ legacy_targets:
72
+ - .codebuddy/commands
73
+ target_dir: .codebuddy/skills
74
+
75
+ crush:
76
+ name: "Crush"
77
+ preferred: false
78
+ # Auto-invoked from descriptions; no manual prefix
79
+ installer:
80
+ legacy_targets:
81
+ - .crush/commands
82
+ target_dir: .crush/skills
83
+
84
+ cursor:
85
+ name: "Cursor"
86
+ preferred: true
87
+ skill_invocation_prefix: "/"
88
+ installer:
89
+ legacy_targets:
90
+ - .cursor/commands
91
+ target_dir: .cursor/skills
92
+
93
+ gemini:
94
+ name: "Gemini CLI"
95
+ preferred: false
96
+ skill_invocation_prefix: "/"
97
+ installer:
98
+ legacy_targets:
99
+ - .gemini/commands
100
+ target_dir: .gemini/skills
101
+
102
+ github-copilot:
103
+ name: "GitHub Copilot"
104
+ preferred: false
105
+ # Auto-invoked from descriptions; no manual prefix
106
+ installer:
107
+ detection_marker: .github/copilot-instructions.md
108
+ legacy_targets:
109
+ - .github/agents
110
+ - .github/prompts
111
+ target_dir: .github/skills
112
+
113
+ iflow:
114
+ name: "iFlow"
115
+ preferred: false
116
+ skill_invocation_prefix: "/"
117
+ installer:
118
+ legacy_targets:
119
+ - .iflow/commands
120
+ target_dir: .iflow/skills
121
+
122
+ junie:
123
+ name: "Junie"
124
+ preferred: false
125
+ # Auto-invoked from descriptions; no manual prefix
126
+ installer:
127
+ target_dir: .junie/skills
128
+
129
+ kilo:
130
+ name: "KiloCoder"
131
+ preferred: false
132
+ # Auto-invoked from descriptions; no manual prefix
133
+ installer:
134
+ legacy_targets:
135
+ - .kilocode/workflows
136
+ target_dir: .kilocode/skills
137
+
138
+ kiro:
139
+ name: "Kiro"
140
+ preferred: false
141
+ # Auto-invoked from descriptions; no manual prefix
142
+ installer:
143
+ legacy_targets:
144
+ - .kiro/steering
145
+ target_dir: .kiro/skills
146
+
147
+ ona:
148
+ name: "Ona"
149
+ preferred: false
150
+ skill_invocation_prefix: "/"
151
+ installer:
152
+ target_dir: .ona/skills
153
+
154
+ opencode:
155
+ name: "OpenCode"
156
+ preferred: false
157
+ skill_invocation_prefix: "/"
158
+ installer:
159
+ legacy_targets:
160
+ - .opencode/agents
161
+ - .opencode/commands
162
+ - .opencode/agent
163
+ - .opencode/command
164
+ target_dir: .opencode/skills
165
+
166
+ pi:
167
+ name: "Pi"
168
+ preferred: false
169
+ skill_invocation_prefix: "/skill:"
170
+ installer:
171
+ target_dir: .pi/skills
172
+
173
+ qoder:
174
+ name: "Qoder"
175
+ preferred: false
176
+ skill_invocation_prefix: "/"
177
+ installer:
178
+ target_dir: .qoder/skills
179
+
180
+ qwen:
181
+ name: "QwenCoder"
182
+ preferred: false
183
+ skill_invocation_prefix: "/"
184
+ installer:
185
+ legacy_targets:
186
+ - .qwen/commands
187
+ target_dir: .qwen/skills
188
+
189
+ roo:
190
+ name: "Roo Code"
191
+ preferred: false
192
+ skill_invocation_prefix: "/"
193
+ installer:
194
+ legacy_targets:
195
+ - .roo/commands
196
+ target_dir: .roo/skills
197
+
198
+ rovo-dev:
199
+ name: "Rovo Dev"
200
+ preferred: false
201
+ # Auto-invoked from descriptions; no manual prefix
202
+ installer:
203
+ legacy_targets:
204
+ - .rovodev/workflows
205
+ target_dir: .rovodev/skills
206
+
207
+ trae:
208
+ name: "Trae"
209
+ preferred: false
210
+ # Auto-invoked from descriptions; no manual prefix
211
+ installer:
212
+ legacy_targets:
213
+ - .trae/rules
214
+ target_dir: .trae/skills
215
+
216
+ windsurf:
217
+ name: "Windsurf"
218
+ preferred: false
219
+ skill_invocation_prefix: "/"
220
+ installer:
221
+ legacy_targets:
222
+ - .windsurf/workflows
223
+ target_dir: .windsurf/skills