convoke-agents 3.0.4 → 3.2.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.
- package/CHANGELOG.md +60 -0
- package/README.md +14 -13
- package/_bmad/bme/_artifacts/config.yaml +15 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-01-scope.md +138 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-02-dryrun.md +199 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-03-resolve.md +174 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-04-execute.md +213 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/workflow.md +85 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-01-scan.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-02-explore.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-03-recommend.md +149 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/workflow.md +78 -0
- package/_bmad/bme/_gyre/guides/GYRE-TEAM-GUIDE.md +506 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/workflow.md +74 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/workflow.md +42 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/workflow.md +61 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/workflow.md +43 -0
- package/_bmad/bme/_team-factory/agents/team-factory.md +128 -0
- package/_bmad/bme/_team-factory/config.yaml +13 -0
- package/_bmad/bme/_team-factory/lib/cascade-logic.js +184 -0
- package/_bmad/bme/_team-factory/lib/collision-detector.js +228 -0
- package/_bmad/bme/_team-factory/lib/manifest-tracker.js +214 -0
- package/_bmad/bme/_team-factory/lib/spec-differ.js +176 -0
- package/_bmad/bme/_team-factory/lib/spec-parser.js +201 -0
- package/_bmad/bme/_team-factory/lib/spec-writer.js +128 -0
- package/_bmad/bme/_team-factory/lib/types/factory-types.js +193 -0
- package/_bmad/bme/_team-factory/lib/utils/csv-utils.js +62 -0
- package/_bmad/bme/_team-factory/lib/utils/naming-utils.js +45 -0
- package/_bmad/bme/_team-factory/lib/validators/end-to-end-validator.js +898 -0
- package/_bmad/bme/_team-factory/lib/writers/activation-validator.js +175 -0
- package/_bmad/bme/_team-factory/lib/writers/config-appender.js +192 -0
- package/_bmad/bme/_team-factory/lib/writers/config-creator.js +215 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-appender.js +118 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-creator.js +190 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-appender.js +372 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-writer.js +409 -0
- package/_bmad/bme/_team-factory/module-help.csv +3 -0
- package/_bmad/bme/_team-factory/schemas/schema-independent.json +147 -0
- package/_bmad/bme/_team-factory/schemas/schema-sequential.json +242 -0
- package/_bmad/bme/_team-factory/templates/team-spec-template.yaml +86 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-01-scope.md +105 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-02-connect.md +110 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-03-review.md +116 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-04-generate.md +160 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md +146 -0
- package/_bmad/bme/_team-factory/workflows/step-00-route.md +76 -0
- package/_bmad/bme/_vortex/config.yaml +4 -4
- package/_bmad/bme/_vortex/guides/VORTEX-TEAM-GUIDE.md +441 -0
- package/package.json +17 -8
- package/scripts/archive.js +26 -45
- package/scripts/convoke-check.js +88 -0
- package/scripts/convoke-doctor.js +303 -4
- package/scripts/install-gyre-agents.js +0 -0
- package/scripts/lib/artifact-utils.js +2182 -0
- package/scripts/lib/portfolio/formatters/markdown-formatter.js +40 -0
- package/scripts/lib/portfolio/formatters/terminal-formatter.js +56 -0
- package/scripts/lib/portfolio/portfolio-engine.js +572 -0
- package/scripts/lib/portfolio/rules/artifact-chain-rule.js +156 -0
- package/scripts/lib/portfolio/rules/conflict-resolver.js +99 -0
- package/scripts/lib/portfolio/rules/frontmatter-rule.js +42 -0
- package/scripts/lib/portfolio/rules/git-recency-rule.js +69 -0
- package/scripts/lib/types.js +122 -0
- package/scripts/migrate-artifacts.js +439 -0
- package/scripts/portability/catalog-generator.js +353 -0
- package/scripts/portability/classify-skills.js +646 -0
- package/scripts/portability/convoke-export.js +522 -0
- package/scripts/portability/export-engine.js +1133 -0
- package/scripts/portability/generate-adapters.js +79 -0
- package/scripts/portability/manifest-csv.js +147 -0
- package/scripts/portability/seed-catalog-repo.js +427 -0
- package/scripts/portability/templates/canonical-example.md +102 -0
- package/scripts/portability/templates/canonical-format.md +218 -0
- package/scripts/portability/templates/readme-template.md +72 -0
- package/scripts/portability/test-constants.js +42 -0
- package/scripts/portability/validate-classification.js +529 -0
- package/scripts/portability/validate-exports.js +348 -0
- package/scripts/update/lib/agent-registry.js +35 -0
- package/scripts/update/lib/config-merger.js +140 -10
- package/scripts/update/lib/migration-runner.js +1 -1
- package/scripts/update/lib/refresh-installation.js +293 -8
- package/scripts/update/lib/taxonomy-merger.js +138 -0
- package/scripts/update/lib/utils.js +27 -1
- package/scripts/update/lib/validator.js +114 -4
- package/scripts/update/migrations/2.0.x-to-3.1.0.js +50 -0
- package/scripts/update/migrations/3.0.x-to-3.1.0.js +41 -0
- package/scripts/update/migrations/registry.js +14 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convoke Artifact Governance Migration CLI
|
|
5
|
+
*
|
|
6
|
+
* Dry-run by default — shows what the migration would do without changing anything.
|
|
7
|
+
* Use --apply to execute renames. Use --apply --force to skip confirmation.
|
|
8
|
+
*
|
|
9
|
+
* @module migrate-artifacts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs-extra');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
const { findProjectRoot } = require('./update/lib/utils');
|
|
16
|
+
const {
|
|
17
|
+
readTaxonomy,
|
|
18
|
+
generateManifest,
|
|
19
|
+
formatManifest,
|
|
20
|
+
ensureCleanTree,
|
|
21
|
+
executeRenames,
|
|
22
|
+
ArtifactMigrationError,
|
|
23
|
+
verifyHistoryChain,
|
|
24
|
+
executeInjections,
|
|
25
|
+
resolveAmbiguous,
|
|
26
|
+
loadResolutionMap,
|
|
27
|
+
detectMigrationState,
|
|
28
|
+
generateGovernanceADR,
|
|
29
|
+
supersedePreviousADR
|
|
30
|
+
} = require('./lib/artifact-utils');
|
|
31
|
+
|
|
32
|
+
// --- CLI Argument Parsing ---
|
|
33
|
+
|
|
34
|
+
const DEFAULT_INCLUDE_DIRS = Object.freeze(['planning-artifacts', 'vortex-artifacts', 'gyre-artifacts']);
|
|
35
|
+
|
|
36
|
+
/** Pattern for valid directory names: lowercase alphanumeric, dashes, underscores */
|
|
37
|
+
const VALID_DIR_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse CLI arguments from argv array.
|
|
41
|
+
*
|
|
42
|
+
* @param {string[]} argv - Arguments (typically process.argv.slice(2))
|
|
43
|
+
* @returns {{help: boolean, includeDirs: string[], apply: boolean, force: boolean, verbose: boolean, resolutionFile: string|null, resolutionFileError: string|null}}
|
|
44
|
+
*/
|
|
45
|
+
function parseArgs(argv) {
|
|
46
|
+
const help = argv.includes('--help') || argv.includes('-h');
|
|
47
|
+
const apply = argv.includes('--apply');
|
|
48
|
+
const force = argv.includes('--force');
|
|
49
|
+
const verbose = argv.includes('--verbose');
|
|
50
|
+
|
|
51
|
+
let includeDirs = [...DEFAULT_INCLUDE_DIRS];
|
|
52
|
+
const includeIdx = argv.indexOf('--include');
|
|
53
|
+
if (includeIdx !== -1) {
|
|
54
|
+
const nextArg = argv[includeIdx + 1];
|
|
55
|
+
// Skip if next arg is missing or is another flag
|
|
56
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
57
|
+
const parsed = nextArg.split(',').map(d => d.trim()).filter(Boolean);
|
|
58
|
+
// Validate: only simple directory names (no path traversal)
|
|
59
|
+
const valid = parsed.filter(d => VALID_DIR_PATTERN.test(d));
|
|
60
|
+
const invalid = parsed.filter(d => !VALID_DIR_PATTERN.test(d));
|
|
61
|
+
if (invalid.length > 0) {
|
|
62
|
+
console.warn(`Warning: Invalid directory names ignored: ${invalid.join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
if (valid.length > 0) {
|
|
65
|
+
includeDirs = valid;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Story 6.4: --resolution-file <path> — operator decisions for AMBIGUOUS entries.
|
|
71
|
+
// Loaded and validated in main() after the manifest is generated.
|
|
72
|
+
// Also accept the GNU --resolution-file=path form. Reject missing/empty/flag-like
|
|
73
|
+
// values loudly via parse error so operators don't accidentally run a destructive
|
|
74
|
+
// --apply --force without their overrides being honored.
|
|
75
|
+
let resolutionFile = null;
|
|
76
|
+
let resolutionFileError = null;
|
|
77
|
+
|
|
78
|
+
// Check the equals form first: --resolution-file=path
|
|
79
|
+
const eqArg = argv.find(a => a.startsWith('--resolution-file='));
|
|
80
|
+
if (eqArg) {
|
|
81
|
+
const value = eqArg.slice('--resolution-file='.length);
|
|
82
|
+
if (!value || value.startsWith('-')) {
|
|
83
|
+
resolutionFileError = `--resolution-file requires a non-empty path (got: '${value}')`;
|
|
84
|
+
} else {
|
|
85
|
+
resolutionFile = value;
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
const resolutionIdx = argv.indexOf('--resolution-file');
|
|
89
|
+
if (resolutionIdx !== -1) {
|
|
90
|
+
const nextArg = argv[resolutionIdx + 1];
|
|
91
|
+
// Reject: missing arg, empty arg, anything starting with `-` (covers --flags AND -singledash)
|
|
92
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
93
|
+
resolutionFileError = `--resolution-file requires a path argument (got: '${nextArg || '<missing>'}')`;
|
|
94
|
+
} else {
|
|
95
|
+
resolutionFile = nextArg;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { help, includeDirs, apply, force, verbose, resolutionFile, resolutionFileError };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// --- Help ---
|
|
104
|
+
|
|
105
|
+
function printHelp() {
|
|
106
|
+
console.log(`
|
|
107
|
+
Usage: convoke-migrate-artifacts [options]
|
|
108
|
+
|
|
109
|
+
Analyze artifact files and show what the governance migration would do.
|
|
110
|
+
Dry-run by default — no files are modified.
|
|
111
|
+
|
|
112
|
+
Options:
|
|
113
|
+
--include <dirs> Comma-separated directory names to scan (relative to _bmad-output/)
|
|
114
|
+
Default: planning-artifacts,vortex-artifacts,gyre-artifacts
|
|
115
|
+
--verbose Show cross-references for ambiguous files
|
|
116
|
+
--apply Execute the rename migration (commit 1: git mv)
|
|
117
|
+
--force Bypass confirmation prompt (use with --apply for automation)
|
|
118
|
+
--resolution-file <path> JSON file with operator decisions for ambiguous entries.
|
|
119
|
+
Combined with --force gives a fully non-interactive run.
|
|
120
|
+
Schema: { "schemaVersion": 1, "resolutions": { "dir/file.md": { "action": "rename", "initiative": "convoke" } } }
|
|
121
|
+
--help, -h Show this help
|
|
122
|
+
|
|
123
|
+
Examples:
|
|
124
|
+
convoke-migrate-artifacts Dry-run with default scope
|
|
125
|
+
convoke-migrate-artifacts --verbose Dry-run with cross-references
|
|
126
|
+
convoke-migrate-artifacts --include planning-artifacts Dry-run for one directory
|
|
127
|
+
convoke-migrate-artifacts --apply --force --resolution-file resolutions.json Non-interactive apply with operator decisions
|
|
128
|
+
`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- Taxonomy Bootstrap ---
|
|
132
|
+
|
|
133
|
+
const PLATFORM_INITIATIVES = ['vortex', 'gyre', 'bmm', 'forge', 'helm', 'enhance', 'loom', 'convoke'];
|
|
134
|
+
|
|
135
|
+
const DEFAULT_ARTIFACT_TYPES = [
|
|
136
|
+
'prd', 'epic', 'arch', 'adr', 'persona', 'lean-persona', 'empathy-map',
|
|
137
|
+
'problem-def', 'hypothesis', 'experiment', 'signal', 'decision', 'scope',
|
|
138
|
+
'pre-reg', 'sprint', 'brief', 'vision', 'report', 'research', 'story', 'spec'
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create taxonomy.yaml with platform defaults if it does not exist.
|
|
143
|
+
* Idempotent — never overwrites an existing file.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
146
|
+
* @returns {boolean} true if file was created, false if already existed
|
|
147
|
+
*/
|
|
148
|
+
function bootstrapTaxonomy(projectRoot) {
|
|
149
|
+
const configDir = path.join(projectRoot, '_bmad', '_config');
|
|
150
|
+
const configPath = path.join(configDir, 'taxonomy.yaml');
|
|
151
|
+
|
|
152
|
+
if (fs.existsSync(configPath)) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const defaults = {
|
|
157
|
+
initiatives: {
|
|
158
|
+
platform: PLATFORM_INITIATIVES,
|
|
159
|
+
user: []
|
|
160
|
+
},
|
|
161
|
+
artifact_types: DEFAULT_ARTIFACT_TYPES,
|
|
162
|
+
aliases: {}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const header = [
|
|
166
|
+
'# Artifact Governance Taxonomy Configuration',
|
|
167
|
+
'# Schema version: 1',
|
|
168
|
+
`# Created by: convoke-migrate-artifacts bootstrap`,
|
|
169
|
+
'#',
|
|
170
|
+
'# This file is the single source of truth for initiative IDs, artifact types,',
|
|
171
|
+
'# and historical name aliases used by the governance system.',
|
|
172
|
+
''
|
|
173
|
+
].join('\n');
|
|
174
|
+
|
|
175
|
+
fs.ensureDirSync(configDir);
|
|
176
|
+
fs.writeFileSync(configPath, header + yaml.dump(defaults, { lineWidth: -1 }), 'utf8');
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --- Interactive Prompt ---
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Prompt the operator to confirm migration apply.
|
|
184
|
+
* Exported for mocking in tests — tests should NEVER interact with real readline.
|
|
185
|
+
*
|
|
186
|
+
* @returns {Promise<boolean>} true if operator confirms
|
|
187
|
+
*/
|
|
188
|
+
async function confirmApply() {
|
|
189
|
+
const readline = require('readline');
|
|
190
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
191
|
+
return new Promise(resolve => {
|
|
192
|
+
rl.on('close', () => resolve(false)); // piped/closed stdin → reject
|
|
193
|
+
rl.question('Apply migration? [y/n] ', answer => {
|
|
194
|
+
rl.close();
|
|
195
|
+
resolve(typeof answer === 'string' && answer.trim().toLowerCase() === 'y');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// --- Main ---
|
|
201
|
+
|
|
202
|
+
async function main() {
|
|
203
|
+
const args = parseArgs(process.argv.slice(2));
|
|
204
|
+
|
|
205
|
+
if (args.help) {
|
|
206
|
+
printHelp();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Story 6.4: fail fast on a malformed --resolution-file flag so a typo can't
|
|
211
|
+
// silently turn into a destructive --apply --force run with no overrides applied.
|
|
212
|
+
if (args.resolutionFileError) {
|
|
213
|
+
console.error(`Error: ${args.resolutionFileError}`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (args.force && !args.apply) {
|
|
218
|
+
console.log('Warning: --force has no effect without --apply. Running dry-run instead.');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const projectRoot = findProjectRoot();
|
|
222
|
+
if (!projectRoot) {
|
|
223
|
+
console.error('Error: Not in a Convoke project. Could not find _bmad/ directory.');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Archive exclusion (FR50): always strip _archive from includeDirs
|
|
228
|
+
const excludeDirs = ['_archive'];
|
|
229
|
+
const filteredIncludeDirs = args.includeDirs.filter(d => {
|
|
230
|
+
if (d === '_archive') {
|
|
231
|
+
console.warn('Warning: _archive is always excluded from migration scope (FR50). Ignoring.');
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (filteredIncludeDirs.length === 0) {
|
|
238
|
+
console.error('Error: No directories to scan. All specified directories were excluded.');
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Taxonomy bootstrap (FR49): create if absent, never overwrite
|
|
243
|
+
const created = bootstrapTaxonomy(projectRoot);
|
|
244
|
+
if (created) {
|
|
245
|
+
console.log('Created _bmad/_config/taxonomy.yaml with platform defaults.');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Validate taxonomy (NFR22: graceful error, no stack traces)
|
|
249
|
+
try {
|
|
250
|
+
readTaxonomy(projectRoot);
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.error(`Error: Invalid taxonomy configuration.`);
|
|
253
|
+
console.error(` ${err.message}`);
|
|
254
|
+
console.error(` Fix the file at: _bmad/_config/taxonomy.yaml`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Generate manifest (shared by dry-run and apply)
|
|
259
|
+
const manifest = await generateManifest(projectRoot, {
|
|
260
|
+
includeDirs: filteredIncludeDirs,
|
|
261
|
+
excludeDirs,
|
|
262
|
+
verbose: args.verbose
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const output = formatManifest(manifest, { verbose: args.verbose });
|
|
266
|
+
console.log(output);
|
|
267
|
+
|
|
268
|
+
// Dry-run mode (default): just print manifest and exit
|
|
269
|
+
if (!args.apply) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// --- Apply mode ---
|
|
274
|
+
|
|
275
|
+
// Idempotent recovery detection
|
|
276
|
+
let migrationState = detectMigrationState(projectRoot);
|
|
277
|
+
if (migrationState === 'complete') {
|
|
278
|
+
// Secondary check: verify manifest confirms all files governed (catches new files added since migration)
|
|
279
|
+
const hasWork = manifest.entries.some(e => e.action === 'RENAME' || e.action === 'AMBIGUOUS');
|
|
280
|
+
if (!hasWork) {
|
|
281
|
+
console.log('\nNothing to migrate -- all files governed.');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
// New files found — proceed as fresh migration
|
|
285
|
+
console.log('\nPrevious migration detected, but new ungoverned files found. Proceeding with fresh migration.');
|
|
286
|
+
migrationState = 'fresh';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Load taxonomy for ambiguous resolution
|
|
290
|
+
const taxonomy = readTaxonomy(projectRoot);
|
|
291
|
+
|
|
292
|
+
// Story 6.4: optional pre-loaded operator resolutions via --resolution-file.
|
|
293
|
+
// Loaded once here so any validation error fails fast before we touch the manifest.
|
|
294
|
+
let resolutionMap = null;
|
|
295
|
+
if (args.resolutionFile) {
|
|
296
|
+
try {
|
|
297
|
+
resolutionMap = loadResolutionMap(args.resolutionFile, taxonomy);
|
|
298
|
+
const count = Object.keys(resolutionMap).length;
|
|
299
|
+
console.log(`Loaded ${count} operator resolution(s) from ${args.resolutionFile}.`);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
console.error(`Error loading resolution file: ${err.message}`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Resolve ambiguous files: resolution map (if any) → no-candidates skip → force skip → interactive prompt
|
|
307
|
+
const resolution = await resolveAmbiguous(manifest, taxonomy, projectRoot, {
|
|
308
|
+
force: args.force,
|
|
309
|
+
resolutionMap
|
|
310
|
+
});
|
|
311
|
+
if (resolution.resolved > 0 || resolution.skipped > 0) {
|
|
312
|
+
console.log(`\nAmbiguous resolution: ${resolution.resolved} resolved, ${resolution.skipped} skipped.`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Re-compute counts and re-check collisions after resolution (new RENAME entries may collide)
|
|
316
|
+
const { detectCollisions } = require('./lib/artifact-utils');
|
|
317
|
+
manifest.collisions = detectCollisions(manifest.entries);
|
|
318
|
+
const renameCount = manifest.summary.rename;
|
|
319
|
+
const skipCount = manifest.entries.filter(e => e.action === 'SKIP').length;
|
|
320
|
+
const ambiguousLeft = manifest.summary.ambiguous;
|
|
321
|
+
console.log(`\n${renameCount} files will be renamed. ${skipCount} skipped. ${ambiguousLeft} still ambiguous.`);
|
|
322
|
+
|
|
323
|
+
// Block on collisions (includes post-resolution collisions)
|
|
324
|
+
if (manifest.collisions.size > 0) {
|
|
325
|
+
console.error(`Error: ${manifest.collisions.size} filename collision(s) detected. Resolve before applying.`);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (renameCount === 0) {
|
|
330
|
+
console.log('Nothing to rename.');
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Confirmation prompt (unless --force)
|
|
335
|
+
if (!args.force) {
|
|
336
|
+
const confirmed = await confirmApply();
|
|
337
|
+
if (!confirmed) {
|
|
338
|
+
console.log('Migration aborted.');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Pre-flight: ensure clean tree
|
|
344
|
+
try {
|
|
345
|
+
ensureCleanTree(filteredIncludeDirs, projectRoot);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.error(`Error: ${err.message}`);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Execute migration phases
|
|
352
|
+
try {
|
|
353
|
+
// Phase routing based on idempotent recovery state
|
|
354
|
+
if (migrationState === 'renames-done') {
|
|
355
|
+
const priorCount = manifest.entries.filter(e => e.action === 'RENAME').length;
|
|
356
|
+
console.log(`\nDetected partial migration (${priorCount} renames done, frontmatter pending). Resuming commit 2.`);
|
|
357
|
+
} else {
|
|
358
|
+
// Commit 1: renames
|
|
359
|
+
const renameResult = executeRenames(manifest, projectRoot);
|
|
360
|
+
console.log(`\nRename phase complete. ${renameResult.renamedCount} files renamed. Commit: ${renameResult.commitSha}`);
|
|
361
|
+
|
|
362
|
+
// Verify history chain (informational)
|
|
363
|
+
const renamedEntries = manifest.entries.filter(e => e.action === 'RENAME');
|
|
364
|
+
const verification = verifyHistoryChain(renamedEntries, projectRoot);
|
|
365
|
+
if (verification.failed.length > 0) {
|
|
366
|
+
console.warn(`Warning: git log --follow failed for ${verification.failed.length} file(s):`);
|
|
367
|
+
for (const f of verification.failed) {
|
|
368
|
+
console.warn(` ${f}`);
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
console.log(`History chain verified for ${verification.verified} sample file(s).`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Commit 2: frontmatter injection + link updating + rename map
|
|
376
|
+
const injResult = await executeInjections(manifest, projectRoot, filteredIncludeDirs);
|
|
377
|
+
console.log(`\nInjection phase complete. ${injResult.injectedCount} files injected, ${injResult.linkUpdates.updatedLinks} links updated, ${injResult.conflictCount} conflicts skipped. Commit: ${injResult.commitSha}`);
|
|
378
|
+
|
|
379
|
+
// Commit 3: ADR generation (non-blocking — failure logs warning, doesn't rollback)
|
|
380
|
+
try {
|
|
381
|
+
const date = new Date().toISOString().split('T')[0];
|
|
382
|
+
const newADRFilename = `adr-artifact-governance-convention-${date}.md`;
|
|
383
|
+
const adrContent = generateGovernanceADR(date, {
|
|
384
|
+
renamedCount: renameCount,
|
|
385
|
+
injectedCount: injResult.injectedCount,
|
|
386
|
+
linksUpdated: injResult.linkUpdates.updatedLinks,
|
|
387
|
+
scopeDirs: filteredIncludeDirs
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const adrDir = path.join(projectRoot, '_bmad-output', 'planning-artifacts');
|
|
391
|
+
fs.ensureDirSync(adrDir);
|
|
392
|
+
const adrPath = path.join(adrDir, newADRFilename);
|
|
393
|
+
// Write new ADR FIRST, then supersede old (prevents orphaned supersession if write fails)
|
|
394
|
+
fs.writeFileSync(adrPath, adrContent, 'utf8');
|
|
395
|
+
supersedePreviousADR(projectRoot, newADRFilename);
|
|
396
|
+
|
|
397
|
+
const { execFileSync: execGit } = require('child_process');
|
|
398
|
+
execGit('git', ['add', '_bmad-output/planning-artifacts/'], { cwd: projectRoot, stdio: 'pipe' });
|
|
399
|
+
execGit('git', ['commit', '-m', 'chore: generate governance convention ADR'], { cwd: projectRoot, stdio: 'pipe' });
|
|
400
|
+
console.log(`\nADR generated: ${newADRFilename}`);
|
|
401
|
+
} catch (adrErr) {
|
|
402
|
+
console.warn(`\nWarning: ADR generation failed: ${adrErr.message}`);
|
|
403
|
+
console.warn('Migration data is intact (commits 1-2 preserved). ADR can be generated manually.');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Final summary
|
|
407
|
+
console.log(`\nMigration complete. ${renameCount} files renamed, ${injResult.injectedCount} frontmatter injected, ${injResult.linkUpdates.updatedLinks} links updated, ${skipCount} skipped.`);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
if (err instanceof ArtifactMigrationError && err.phase === 'rename') {
|
|
410
|
+
console.error(`\nRename failed: ${err.message}`);
|
|
411
|
+
if (err.recoverable) {
|
|
412
|
+
console.error('Rollback complete. No changes made.');
|
|
413
|
+
} else {
|
|
414
|
+
console.error('WARNING: Rollback may have failed. Run `git status` to check working tree state.');
|
|
415
|
+
}
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
if (err instanceof ArtifactMigrationError && err.phase === 'inject') {
|
|
419
|
+
console.error(`\nInjection failed: ${err.message}`);
|
|
420
|
+
if (err.recoverable) {
|
|
421
|
+
console.error('Renames preserved (commit 1). Injections discarded.');
|
|
422
|
+
} else {
|
|
423
|
+
console.error('WARNING: Rollback may have failed. Run `git status` to check working tree state.');
|
|
424
|
+
}
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
throw err;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Run if invoked directly
|
|
432
|
+
if (require.main === module) {
|
|
433
|
+
main().catch(err => {
|
|
434
|
+
console.error(`Error: ${err.message}`);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
module.exports = { parseArgs, main, confirmApply, bootstrapTaxonomy, DEFAULT_INCLUDE_DIRS, PLATFORM_INITIATIVES, DEFAULT_ARTIFACT_TYPES, VALID_DIR_PATTERN };
|