teamspec 3.2.0 → 4.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.
- package/README.md +24 -12
- package/bin/teamspec-init.js +2 -2
- package/lib/cli.js +653 -99
- package/lib/extension-installer.js +19 -219
- package/lib/linter.js +823 -1076
- package/lib/prompt-generator.js +312 -330
- package/lib/structure-loader.js +400 -0
- package/package.json +14 -6
- package/teamspec-core/FOLDER_STRUCTURE.yml +131 -0
- package/teamspec-core/agents/AGENT_BA.md +188 -293
- package/teamspec-core/agents/AGENT_BOOTSTRAP.md +197 -102
- package/teamspec-core/agents/AGENT_DES.md +9 -8
- package/teamspec-core/agents/AGENT_DEV.md +68 -67
- package/teamspec-core/agents/AGENT_FA.md +437 -245
- package/teamspec-core/agents/AGENT_FIX.md +344 -74
- package/teamspec-core/agents/AGENT_PO.md +487 -0
- package/teamspec-core/agents/AGENT_QA.md +124 -98
- package/teamspec-core/agents/AGENT_SA.md +143 -84
- package/teamspec-core/agents/AGENT_SM.md +106 -83
- package/teamspec-core/agents/README.md +143 -93
- package/teamspec-core/copilot-instructions.md +281 -205
- package/teamspec-core/definitions/definition-of-done.md +47 -84
- package/teamspec-core/definitions/definition-of-ready.md +35 -60
- package/teamspec-core/registry.yml +898 -0
- package/teamspec-core/teamspec.yml +44 -28
- package/teamspec-core/templates/README.md +5 -5
- package/teamspec-core/templates/adr-template.md +19 -17
- package/teamspec-core/templates/bai-template.md +125 -0
- package/teamspec-core/templates/bug-report-template.md +21 -15
- package/teamspec-core/templates/business-analysis-template.md +16 -13
- package/teamspec-core/templates/decision-log-template.md +26 -22
- package/teamspec-core/templates/dev-plan-template.md +168 -0
- package/teamspec-core/templates/epic-template.md +204 -0
- package/teamspec-core/templates/feature-increment-template.md +84 -0
- package/teamspec-core/templates/feature-template.md +45 -32
- package/teamspec-core/templates/increments-index-template.md +53 -0
- package/teamspec-core/templates/product-template.yml +44 -0
- package/teamspec-core/templates/products-index-template.md +46 -0
- package/teamspec-core/templates/project-template.yml +70 -0
- package/teamspec-core/templates/ri-template.md +225 -0
- package/teamspec-core/templates/rt-template.md +104 -0
- package/teamspec-core/templates/sd-template.md +132 -0
- package/teamspec-core/templates/sdi-template.md +119 -0
- package/teamspec-core/templates/sprint-template.md +17 -15
- package/teamspec-core/templates/story-template-v4.md +202 -0
- package/teamspec-core/templates/story-template.md +48 -90
- package/teamspec-core/templates/ta-template.md +198 -0
- package/teamspec-core/templates/tai-template.md +131 -0
- package/teamspec-core/templates/tc-template.md +145 -0
- package/teamspec-core/templates/testcases-template.md +20 -17
- package/extensions/teamspec-0.1.0.vsix +0 -0
package/lib/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TeamSpec Init CLI - Pure JavaScript implementation
|
|
3
|
-
* Version
|
|
3
|
+
* Version 4.0.0 - Product/Project Operating Model
|
|
4
4
|
*
|
|
5
|
-
* This CLI bootstraps TeamSpec
|
|
5
|
+
* This CLI bootstraps TeamSpec 4.0 in any repository by:
|
|
6
6
|
* 1. Asking team setup questions
|
|
7
7
|
* 2. Deploying .teamspec/ folder with core files
|
|
8
|
-
* 3. Creating project structure
|
|
8
|
+
* 3. Creating product/project structure
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
@@ -49,7 +49,7 @@ function printBanner() {
|
|
|
49
49
|
║ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝ ║
|
|
50
50
|
║ ║
|
|
51
51
|
║ ║
|
|
52
|
-
║
|
|
52
|
+
║ Product/Project Operating Model v4.0 ║
|
|
53
53
|
╚══════════════════════════════════════════════════════════════════════╝
|
|
54
54
|
`;
|
|
55
55
|
console.log(colored(banner, colors.cyan));
|
|
@@ -110,13 +110,16 @@ function parseArgs(args) {
|
|
|
110
110
|
help: false,
|
|
111
111
|
version: false,
|
|
112
112
|
force: false,
|
|
113
|
+
fix: false,
|
|
114
|
+
rule: null,
|
|
115
|
+
verbose: false,
|
|
113
116
|
};
|
|
114
117
|
|
|
115
118
|
let i = 0;
|
|
116
119
|
|
|
117
120
|
if (args.length > 0 && !args[0].startsWith('-')) {
|
|
118
121
|
const cmd = args[0].toLowerCase();
|
|
119
|
-
if (['init', 'update', 'lint', 'generate-prompts'].includes(cmd)) {
|
|
122
|
+
if (['init', 'update', 'lint', 'generate-prompts', 'migrate'].includes(cmd)) {
|
|
120
123
|
options.command = cmd;
|
|
121
124
|
i = 1;
|
|
122
125
|
}
|
|
@@ -166,6 +169,15 @@ function parseArgs(args) {
|
|
|
166
169
|
case '-f':
|
|
167
170
|
options.force = true;
|
|
168
171
|
break;
|
|
172
|
+
case '--fix':
|
|
173
|
+
options.fix = true;
|
|
174
|
+
break;
|
|
175
|
+
case '--rule':
|
|
176
|
+
options.rule = args[++i];
|
|
177
|
+
break;
|
|
178
|
+
case '--verbose':
|
|
179
|
+
options.verbose = true;
|
|
180
|
+
break;
|
|
169
181
|
}
|
|
170
182
|
}
|
|
171
183
|
|
|
@@ -178,7 +190,7 @@ function parseArgs(args) {
|
|
|
178
190
|
|
|
179
191
|
function printHelp() {
|
|
180
192
|
console.log(`
|
|
181
|
-
${colored('TeamSpec Init', colors.bold)} - Bootstrap TeamSpec
|
|
193
|
+
${colored('TeamSpec Init', colors.bold)} - Bootstrap TeamSpec 4.0 Product-Canon Operating Model
|
|
182
194
|
|
|
183
195
|
${colored('USAGE:', colors.bold)}
|
|
184
196
|
teamspec [command] [options]
|
|
@@ -199,7 +211,8 @@ ${colored('OPTIONS:', colors.bold)}
|
|
|
199
211
|
--ide <ide> IDE integration (vscode, cursor, other, none)
|
|
200
212
|
--copilot <yes|no> Install GitHub Copilot instructions file (default: yes)
|
|
201
213
|
-y, --non-interactive Run without prompts (use defaults)
|
|
202
|
-
-f, --force Force update without confirmation
|
|
214
|
+
-f, --force Force update/migrate without confirmation
|
|
215
|
+
--fix Apply migration changes (default: dry-run)
|
|
203
216
|
|
|
204
217
|
${colored('PROFILES:', colors.bold)}
|
|
205
218
|
none Vanilla TeamSpec
|
|
@@ -209,24 +222,30 @@ ${colored('PROFILES:', colors.bold)}
|
|
|
209
222
|
enterprise Full governance, audit trails
|
|
210
223
|
|
|
211
224
|
${colored('EXAMPLES:', colors.bold)}
|
|
212
|
-
teamspec # Interactive setup
|
|
225
|
+
teamspec # Interactive setup (4.0)
|
|
213
226
|
teamspec --profile startup -y # Quick setup with startup profile
|
|
214
227
|
teamspec update # Update core files, keep context
|
|
215
228
|
teamspec update --force # Update without confirmation
|
|
216
229
|
teamspec lint # Lint all projects
|
|
217
230
|
teamspec lint --project my-project # Lint specific project
|
|
231
|
+
teamspec migrate # Analyze 2.0 → 4.0 migration (dry-run)
|
|
232
|
+
teamspec migrate --fix # Execute 2.0 → 4.0 migration
|
|
218
233
|
teamspec generate-prompts # Generate GitHub Copilot prompt files
|
|
219
234
|
|
|
220
|
-
${colored('WHAT GETS CREATED:', colors.bold)}
|
|
235
|
+
${colored('WHAT GETS CREATED (4.0):', colors.bold)}
|
|
221
236
|
.teamspec/ Core framework
|
|
222
237
|
├── templates/ Document templates
|
|
223
238
|
├── definitions/ DoR/DoD checklists
|
|
224
239
|
├── profiles/ Profile overlays
|
|
225
240
|
└── context/team.yml Team configuration
|
|
226
|
-
|
|
227
|
-
├── features/ Feature Canon
|
|
228
|
-
├──
|
|
229
|
-
|
|
241
|
+
products/<product-id>/ Product artifacts (AS-IS, source of truth)
|
|
242
|
+
├── features/ Feature Canon
|
|
243
|
+
├── business-analysis/ BA documents
|
|
244
|
+
└── ...
|
|
245
|
+
projects/<project-id>/ Project artifacts (TO-BE changes)
|
|
246
|
+
├── feature-increments/ Changes to product features
|
|
247
|
+
├── epics/ Story containers
|
|
248
|
+
├── stories/ User stories
|
|
230
249
|
└── ...
|
|
231
250
|
`);
|
|
232
251
|
}
|
|
@@ -260,6 +279,162 @@ function copyDirRecursive(src, dest) {
|
|
|
260
279
|
}
|
|
261
280
|
}
|
|
262
281
|
|
|
282
|
+
// =============================================================================
|
|
283
|
+
// TeamSpec Version Detection (4.0)
|
|
284
|
+
// =============================================================================
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Detect the TeamSpec version of a workspace
|
|
288
|
+
* @param {string} targetDir - Path to workspace root
|
|
289
|
+
* @returns {{ version: string, hasProducts: boolean, hasProjects: boolean, productCount: number, projectCount: number }}
|
|
290
|
+
*/
|
|
291
|
+
function detectWorkspaceVersion(targetDir) {
|
|
292
|
+
const teamspecDir = path.join(targetDir, '.teamspec');
|
|
293
|
+
const productsDir = path.join(targetDir, 'products');
|
|
294
|
+
const projectsDir = path.join(targetDir, 'projects');
|
|
295
|
+
|
|
296
|
+
// Not a TeamSpec workspace
|
|
297
|
+
if (!fs.existsSync(teamspecDir)) {
|
|
298
|
+
return { version: 'none', hasProducts: false, hasProjects: false, productCount: 0, projectCount: 0 };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const hasProducts = fs.existsSync(productsDir);
|
|
302
|
+
const hasProjects = fs.existsSync(projectsDir);
|
|
303
|
+
|
|
304
|
+
// Count products and projects
|
|
305
|
+
let productCount = 0;
|
|
306
|
+
let projectCount = 0;
|
|
307
|
+
|
|
308
|
+
if (hasProducts) {
|
|
309
|
+
productCount = fs.readdirSync(productsDir, { withFileTypes: true })
|
|
310
|
+
.filter(d => d.isDirectory())
|
|
311
|
+
.filter(d => fs.existsSync(path.join(productsDir, d.name, 'product.yml')))
|
|
312
|
+
.length;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (hasProjects) {
|
|
316
|
+
projectCount = fs.readdirSync(projectsDir, { withFileTypes: true })
|
|
317
|
+
.filter(d => d.isDirectory())
|
|
318
|
+
.filter(d => fs.existsSync(path.join(projectsDir, d.name, 'project.yml')))
|
|
319
|
+
.length;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Detect 4.0 vs 2.0
|
|
323
|
+
// 4.0 indicators: products/ folder with product.yml files, or product-index.md
|
|
324
|
+
// 2.0 indicators: projects/ with features/ (features as canon in project)
|
|
325
|
+
|
|
326
|
+
if (hasProducts && productCount > 0) {
|
|
327
|
+
return { version: '4.0', hasProducts: true, hasProjects, productCount, projectCount };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Check if any project has features/ folder (2.0 pattern)
|
|
331
|
+
if (hasProjects && projectCount > 0) {
|
|
332
|
+
const projects = fs.readdirSync(projectsDir, { withFileTypes: true })
|
|
333
|
+
.filter(d => d.isDirectory());
|
|
334
|
+
|
|
335
|
+
for (const project of projects) {
|
|
336
|
+
const featuresDir = path.join(projectsDir, project.name, 'features');
|
|
337
|
+
if (fs.existsSync(featuresDir)) {
|
|
338
|
+
// 2.0 pattern: features inside projects
|
|
339
|
+
return { version: '2.0', hasProducts: false, hasProjects: true, productCount: 0, projectCount };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Has .teamspec but no recognizable structure
|
|
345
|
+
return { version: 'unknown', hasProducts, hasProjects, productCount, projectCount };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Find all products in a workspace
|
|
350
|
+
* @param {string} targetDir - Path to workspace root
|
|
351
|
+
* @returns {Array<{ id: string, prefix: string, path: string, name: string }>}
|
|
352
|
+
*/
|
|
353
|
+
function findProducts(targetDir) {
|
|
354
|
+
const productsDir = path.join(targetDir, 'products');
|
|
355
|
+
|
|
356
|
+
if (!fs.existsSync(productsDir)) {
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const products = [];
|
|
361
|
+
const entries = fs.readdirSync(productsDir, { withFileTypes: true });
|
|
362
|
+
|
|
363
|
+
for (const entry of entries) {
|
|
364
|
+
if (!entry.isDirectory()) continue;
|
|
365
|
+
|
|
366
|
+
const productYmlPath = path.join(productsDir, entry.name, 'product.yml');
|
|
367
|
+
if (!fs.existsSync(productYmlPath)) continue;
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const content = fs.readFileSync(productYmlPath, 'utf-8');
|
|
371
|
+
// Simple YAML parsing for prefix and name
|
|
372
|
+
const prefixMatch = content.match(/prefix:\s*["']?([A-Z]{3,4})["']?/);
|
|
373
|
+
const nameMatch = content.match(/name:\s*["']?(.+?)["']?\s*$/m);
|
|
374
|
+
|
|
375
|
+
products.push({
|
|
376
|
+
id: entry.name,
|
|
377
|
+
prefix: prefixMatch ? prefixMatch[1] : entry.name.substring(0, 3).toUpperCase(),
|
|
378
|
+
path: path.join(productsDir, entry.name),
|
|
379
|
+
name: nameMatch ? nameMatch[1].trim() : entry.name
|
|
380
|
+
});
|
|
381
|
+
} catch (e) {
|
|
382
|
+
// Skip malformed product.yml
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return products;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Generate a product prefix from a name
|
|
391
|
+
* @param {string} name - Product name (e.g., "DnD Initiative Tracker")
|
|
392
|
+
* @returns {string} - 3-4 char uppercase prefix (e.g., "DIT")
|
|
393
|
+
*/
|
|
394
|
+
function generateProductPrefix(name) {
|
|
395
|
+
// Extract first letter of each significant word
|
|
396
|
+
const words = name
|
|
397
|
+
.replace(/[^a-zA-Z0-9\s]/g, '')
|
|
398
|
+
.split(/\s+/)
|
|
399
|
+
.filter(w => w.length > 0 && !['the', 'a', 'an', 'and', 'or', 'for', 'of', 'in', 'to'].includes(w.toLowerCase()));
|
|
400
|
+
|
|
401
|
+
if (words.length === 0) return 'PRD';
|
|
402
|
+
if (words.length === 1) return words[0].substring(0, 3).toUpperCase();
|
|
403
|
+
|
|
404
|
+
// Take first letter of first 3-4 words
|
|
405
|
+
return words
|
|
406
|
+
.slice(0, 4)
|
|
407
|
+
.map(w => w[0])
|
|
408
|
+
.join('')
|
|
409
|
+
.toUpperCase();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Validate a product prefix
|
|
414
|
+
* @param {string} prefix - The prefix to validate
|
|
415
|
+
* @param {string} targetDir - Workspace path to check for conflicts
|
|
416
|
+
* @returns {{ valid: boolean, error?: string }}
|
|
417
|
+
*/
|
|
418
|
+
function validateProductPrefix(prefix, targetDir) {
|
|
419
|
+
if (!prefix || prefix.length < 3 || prefix.length > 4) {
|
|
420
|
+
return { valid: false, error: 'Prefix must be 3-4 characters' };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!/^[A-Z]{3,4}$/.test(prefix)) {
|
|
424
|
+
return { valid: false, error: 'Prefix must be uppercase letters only' };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Check for conflicts with existing products
|
|
428
|
+
const existingProducts = findProducts(targetDir);
|
|
429
|
+
const conflict = existingProducts.find(p => p.prefix === prefix);
|
|
430
|
+
|
|
431
|
+
if (conflict) {
|
|
432
|
+
return { valid: false, error: `Prefix "${prefix}" already used by product "${conflict.id}"` };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return { valid: true };
|
|
436
|
+
}
|
|
437
|
+
|
|
263
438
|
// =============================================================================
|
|
264
439
|
// Interactive Prompts
|
|
265
440
|
// =============================================================================
|
|
@@ -377,16 +552,73 @@ async function runInteractive(options) {
|
|
|
377
552
|
options.sprintLengthDays = null;
|
|
378
553
|
}
|
|
379
554
|
|
|
380
|
-
// Project
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
555
|
+
// 4.0: Product vs Project-only mode
|
|
556
|
+
console.log(`\n${colored('TeamSpec 4.0 Structure', colors.bold)}`);
|
|
557
|
+
console.log(' Products = AS-IS (what exists, source of truth)');
|
|
558
|
+
console.log(' Projects = TO-BE (changes to products)');
|
|
559
|
+
|
|
560
|
+
const setupMode = await promptChoice(
|
|
561
|
+
rl,
|
|
562
|
+
'How do you want to set up your workspace?',
|
|
563
|
+
{
|
|
564
|
+
'product-first': 'Start with a Product (recommended) - creates product + optional project',
|
|
565
|
+
'project-only': 'Project only - for quick starts, can add products later',
|
|
566
|
+
},
|
|
567
|
+
'product-first'
|
|
568
|
+
);
|
|
569
|
+
options.setupMode = setupMode;
|
|
570
|
+
|
|
571
|
+
// Product setup (if product-first)
|
|
572
|
+
if (setupMode === 'product-first') {
|
|
573
|
+
console.log(`\n${colored('Product Setup', colors.bold)}`);
|
|
574
|
+
console.log(' Products have a unique prefix (PRX) used in artifact naming.');
|
|
575
|
+
console.log(' Example: "DnD Initiative Tracker" → prefix "DIT" → files like f-DIT-001.md');
|
|
576
|
+
|
|
577
|
+
options.productName = await prompt(
|
|
385
578
|
rl,
|
|
386
|
-
`${colored('
|
|
387
|
-
|
|
579
|
+
`${colored('Product name', colors.bold)}`,
|
|
580
|
+
'My Product'
|
|
388
581
|
);
|
|
389
|
-
|
|
582
|
+
|
|
583
|
+
// Generate suggested prefix
|
|
584
|
+
const suggestedPrefix = generateProductPrefix(options.productName);
|
|
585
|
+
options.productPrefix = await prompt(
|
|
586
|
+
rl,
|
|
587
|
+
`${colored('Product prefix (3-4 uppercase letters)', colors.bold)}`,
|
|
588
|
+
suggestedPrefix
|
|
589
|
+
);
|
|
590
|
+
options.productPrefix = options.productPrefix.toUpperCase().replace(/[^A-Z]/g, '').substring(0, 4);
|
|
591
|
+
|
|
592
|
+
// Validate prefix
|
|
593
|
+
if (options.productPrefix.length < 3) {
|
|
594
|
+
options.productPrefix = suggestedPrefix;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Generate product ID from name
|
|
598
|
+
options.productId = options.productName.toLowerCase()
|
|
599
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
600
|
+
.replace(/\s+/g, '-');
|
|
601
|
+
|
|
602
|
+
// Ask if they want a project too
|
|
603
|
+
options.createProject = await promptYesNo(
|
|
604
|
+
rl,
|
|
605
|
+
`\n${colored('Also create an initial project for this product?', colors.bold)}`,
|
|
606
|
+
true
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Project ID
|
|
611
|
+
if (setupMode === 'project-only' || options.createProject) {
|
|
612
|
+
if (!options.project) {
|
|
613
|
+
console.log(`\n${colored('Project Structure', colors.bold)}`);
|
|
614
|
+
console.log(' TeamSpec organizes artifacts in project folders: projects/<project-id>/');
|
|
615
|
+
options.project = await prompt(
|
|
616
|
+
rl,
|
|
617
|
+
`${colored('Initial project ID', colors.bold)} (lowercase, hyphenated)`,
|
|
618
|
+
DEFAULT_PROJECT_ID
|
|
619
|
+
);
|
|
620
|
+
options.project = normalizeProjectId(options.project);
|
|
621
|
+
}
|
|
390
622
|
}
|
|
391
623
|
|
|
392
624
|
// IDE Integration
|
|
@@ -418,7 +650,13 @@ async function runInteractive(options) {
|
|
|
418
650
|
if (options.sprintLengthDays) {
|
|
419
651
|
console.log(` Sprint Length: ${options.sprintLengthDays} days`);
|
|
420
652
|
}
|
|
421
|
-
console.log(`
|
|
653
|
+
console.log(` Setup Mode: ${options.setupMode}`);
|
|
654
|
+
if (options.productName) {
|
|
655
|
+
console.log(` Product: ${options.productName} (${options.productPrefix})`);
|
|
656
|
+
}
|
|
657
|
+
if (options.project) {
|
|
658
|
+
console.log(` Project ID: ${options.project}`);
|
|
659
|
+
}
|
|
422
660
|
console.log(` IDE: ${options.ide}`);
|
|
423
661
|
console.log(` Copilot: ${options.copilot ? 'Yes' : 'No'}`);
|
|
424
662
|
|
|
@@ -593,62 +831,298 @@ feature_canon:
|
|
|
593
831
|
}
|
|
594
832
|
|
|
595
833
|
// =============================================================================
|
|
596
|
-
//
|
|
834
|
+
// Product Structure Creation (4.0)
|
|
835
|
+
// =============================================================================
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Create a new product structure
|
|
839
|
+
* @param {string} targetDir - Workspace root path
|
|
840
|
+
* @param {string} productId - Slug identifier (e.g., "dnd-initiative-tracker")
|
|
841
|
+
* @param {string} productName - Human-readable name (e.g., "DnD Initiative Tracker")
|
|
842
|
+
* @param {string} prefix - 3-4 char uppercase prefix (e.g., "DIT")
|
|
843
|
+
* @returns {string} - Path to created product folder
|
|
844
|
+
*/
|
|
845
|
+
function createProductStructure(targetDir, productId, productName, prefix) {
|
|
846
|
+
const productDir = path.join(targetDir, 'products', productId);
|
|
847
|
+
fs.mkdirSync(productDir, { recursive: true });
|
|
848
|
+
|
|
849
|
+
console.log(`\n${colored(`Creating product structure: products/${productId}/ (${prefix})...`, colors.blue)}`);
|
|
850
|
+
|
|
851
|
+
// product.yml
|
|
852
|
+
const productYml = `# Product Configuration: ${productName}
|
|
853
|
+
product:
|
|
854
|
+
id: "${productId}"
|
|
855
|
+
name: "${productName}"
|
|
856
|
+
prefix: "${prefix}"
|
|
857
|
+
description: |
|
|
858
|
+
# TODO: Add product description - what IS this product (AS-IS)?
|
|
859
|
+
status: active # active, deprecated, archived
|
|
860
|
+
|
|
861
|
+
# List of projects currently modifying this product
|
|
862
|
+
active_projects: []
|
|
863
|
+
`;
|
|
864
|
+
fs.writeFileSync(path.join(productDir, 'product.yml'), productYml, 'utf-8');
|
|
865
|
+
console.log(` ✓ Created products/${productId}/product.yml`);
|
|
866
|
+
|
|
867
|
+
// README.md
|
|
868
|
+
const readme = `# ${productName}
|
|
869
|
+
|
|
870
|
+
> **Product Prefix:** \`${prefix}\`
|
|
871
|
+
|
|
872
|
+
## Overview
|
|
873
|
+
|
|
874
|
+
_TODO: Describe what this product IS (current state, AS-IS)._
|
|
875
|
+
|
|
876
|
+
## Structure
|
|
877
|
+
|
|
878
|
+
| Folder | Purpose | Artifact Pattern |
|
|
879
|
+
|--------|---------|------------------|
|
|
880
|
+
| \`business-analysis/\` | Business context & processes | \`ba-${prefix}-XXX-*.md\` |
|
|
881
|
+
| \`features/\` | Feature Canon (source of truth) | \`f-${prefix}-XXX-*.md\` |
|
|
882
|
+
| \`solution-designs/\` | Solution designs & architecture | \`sd-${prefix}-XXX-*.md\` |
|
|
883
|
+
| \`technical-architecture/\` | Technical architecture decisions | \`ta-${prefix}-XXX-*.md\` |
|
|
884
|
+
| \`decisions/\` | Product-level business decisions | \`dec-${prefix}-XXX-*.md\` |
|
|
885
|
+
|
|
886
|
+
## Active Projects
|
|
887
|
+
|
|
888
|
+
_See \`product.yml\` for the list of projects currently modifying this product._
|
|
889
|
+
`;
|
|
890
|
+
fs.writeFileSync(path.join(productDir, 'README.md'), readme, 'utf-8');
|
|
891
|
+
console.log(` ✓ Created products/${productId}/README.md`);
|
|
892
|
+
|
|
893
|
+
// business-analysis/
|
|
894
|
+
const baDir = path.join(productDir, 'business-analysis');
|
|
895
|
+
fs.mkdirSync(baDir, { recursive: true });
|
|
896
|
+
fs.writeFileSync(path.join(baDir, 'README.md'), `# Business Analysis
|
|
897
|
+
|
|
898
|
+
Documents the business context, processes, and domain knowledge for this product.
|
|
899
|
+
|
|
900
|
+
**Naming:** \`ba-${prefix}-XXX-<slug>.md\`
|
|
901
|
+
|
|
902
|
+
**Template:** \`/.teamspec/templates/business-analysis-template.md\`
|
|
903
|
+
`, 'utf-8');
|
|
904
|
+
console.log(` ✓ Created products/${productId}/business-analysis/`);
|
|
905
|
+
|
|
906
|
+
// features/
|
|
907
|
+
const featuresDir = path.join(productDir, 'features');
|
|
908
|
+
fs.mkdirSync(featuresDir, { recursive: true });
|
|
909
|
+
|
|
910
|
+
fs.writeFileSync(path.join(featuresDir, 'features-index.md'), `# Features Index (Feature Canon)
|
|
911
|
+
|
|
912
|
+
> **Product:** ${productName}
|
|
913
|
+
> **Prefix:** \`${prefix}\`
|
|
914
|
+
|
|
915
|
+
This is the master index of all features for this product.
|
|
916
|
+
The Feature Canon is the **source of truth** for system behavior (AS-IS).
|
|
917
|
+
|
|
918
|
+
## Feature Registry
|
|
919
|
+
|
|
920
|
+
| ID | Name | Status | Owner |
|
|
921
|
+
|----|------|--------|-------|
|
|
922
|
+
| _(none yet)_ | | | |
|
|
923
|
+
|
|
924
|
+
## Next Available ID: **f-${prefix}-001**
|
|
925
|
+
|
|
926
|
+
---
|
|
927
|
+
|
|
928
|
+
> **To add features:** Features are created by the Product Owner or extracted from business analysis.
|
|
929
|
+
`, 'utf-8');
|
|
930
|
+
|
|
931
|
+
fs.writeFileSync(path.join(featuresDir, 'story-ledger.md'), `# Story Ledger
|
|
932
|
+
|
|
933
|
+
Tracks completed stories and their impact on the Feature Canon.
|
|
934
|
+
|
|
935
|
+
| Story ID | Title | Project | Sprint | Features Modified | Synced Date |
|
|
936
|
+
|----------|-------|---------|--------|-------------------|-------------|
|
|
937
|
+
| _(none yet)_ | | | | | |
|
|
938
|
+
`, 'utf-8');
|
|
939
|
+
console.log(` ✓ Created products/${productId}/features/`);
|
|
940
|
+
|
|
941
|
+
// solution-designs/
|
|
942
|
+
const sdDir = path.join(productDir, 'solution-designs');
|
|
943
|
+
fs.mkdirSync(sdDir, { recursive: true });
|
|
944
|
+
fs.writeFileSync(path.join(sdDir, 'README.md'), `# Solution Designs
|
|
945
|
+
|
|
946
|
+
High-level solution architecture and design documents for this product.
|
|
947
|
+
|
|
948
|
+
**Naming:** \`sd-${prefix}-XXX-<slug>.md\`
|
|
949
|
+
|
|
950
|
+
**Template:** \`/.teamspec/templates/solution-design-template.md\`
|
|
951
|
+
`, 'utf-8');
|
|
952
|
+
console.log(` ✓ Created products/${productId}/solution-designs/`);
|
|
953
|
+
|
|
954
|
+
// technical-architecture/
|
|
955
|
+
const taDir = path.join(productDir, 'technical-architecture');
|
|
956
|
+
fs.mkdirSync(taDir, { recursive: true });
|
|
957
|
+
fs.writeFileSync(path.join(taDir, 'README.md'), `# Technical Architecture
|
|
958
|
+
|
|
959
|
+
Technical architecture decisions and documentation for this product.
|
|
960
|
+
|
|
961
|
+
**Naming:** \`ta-${prefix}-XXX-<slug>.md\`
|
|
962
|
+
|
|
963
|
+
**Template:** \`/.teamspec/templates/adr-template.md\`
|
|
964
|
+
`, 'utf-8');
|
|
965
|
+
console.log(` ✓ Created products/${productId}/technical-architecture/`);
|
|
966
|
+
|
|
967
|
+
// decisions/
|
|
968
|
+
const decisionsDir = path.join(productDir, 'decisions');
|
|
969
|
+
fs.mkdirSync(decisionsDir, { recursive: true });
|
|
970
|
+
fs.writeFileSync(path.join(decisionsDir, 'README.md'), `# Product Decisions
|
|
971
|
+
|
|
972
|
+
Product-level business decisions.
|
|
973
|
+
|
|
974
|
+
**Naming:** \`dec-${prefix}-XXX-<slug>.md\`
|
|
975
|
+
|
|
976
|
+
**Template:** \`/.teamspec/templates/decision-log-template.md\`
|
|
977
|
+
`, 'utf-8');
|
|
978
|
+
console.log(` ✓ Created products/${productId}/decisions/`);
|
|
979
|
+
|
|
980
|
+
return productDir;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Create products-index.md in the workspace root
|
|
985
|
+
* @param {string} targetDir - Workspace root path
|
|
986
|
+
*/
|
|
987
|
+
function createProductsIndex(targetDir) {
|
|
988
|
+
const productsDir = path.join(targetDir, 'products');
|
|
989
|
+
fs.mkdirSync(productsDir, { recursive: true });
|
|
990
|
+
|
|
991
|
+
const indexContent = `# Products Index
|
|
992
|
+
|
|
993
|
+
Master index of all products in this workspace.
|
|
994
|
+
|
|
995
|
+
## Product Registry
|
|
996
|
+
|
|
997
|
+
| Prefix | ID | Name | Status | Active Projects |
|
|
998
|
+
|--------|-----|------|--------|-----------------|
|
|
999
|
+
| _(none yet)_ | | | | |
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
> **To add products:** Run \`ts:po product\` or use \`teamspec init --product\`
|
|
1004
|
+
`;
|
|
1005
|
+
fs.writeFileSync(path.join(productsDir, 'products-index.md'), indexContent, 'utf-8');
|
|
1006
|
+
console.log(` ✓ Created products/products-index.md`);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// =============================================================================
|
|
1010
|
+
// Project Structure Creation (4.0)
|
|
597
1011
|
// =============================================================================
|
|
598
1012
|
|
|
599
|
-
|
|
1013
|
+
/**
|
|
1014
|
+
* Create a new project structure (4.0 format)
|
|
1015
|
+
* @param {string} targetDir - Workspace root path
|
|
1016
|
+
* @param {string} projectId - Slug identifier
|
|
1017
|
+
* @param {Object} options - Optional settings
|
|
1018
|
+
* @param {string[]} options.targetProducts - Products this project modifies
|
|
1019
|
+
*/
|
|
1020
|
+
function createProjectStructure(targetDir, projectId, options = {}) {
|
|
600
1021
|
const projectDir = path.join(targetDir, 'projects', projectId);
|
|
601
1022
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
602
1023
|
|
|
603
1024
|
console.log(`\n${colored(`Creating project structure: projects/${projectId}/...`, colors.blue)}`);
|
|
604
1025
|
|
|
605
|
-
|
|
1026
|
+
const targetProducts = options.targetProducts || [];
|
|
1027
|
+
|
|
1028
|
+
// project.yml (4.0 format with target_products)
|
|
606
1029
|
const projectYml = `# Project Configuration: ${projectId}
|
|
1030
|
+
# TeamSpec 4.0 - TO-BE changes project
|
|
607
1031
|
project:
|
|
608
1032
|
id: "${projectId}"
|
|
609
1033
|
name: "${projectId.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}"
|
|
610
1034
|
description: |
|
|
611
|
-
# TODO: Add project description
|
|
1035
|
+
# TODO: Add project description - what CHANGES will this project deliver?
|
|
612
1036
|
status: active # active, paused, completed, archived
|
|
1037
|
+
|
|
1038
|
+
# Products this project modifies (populated via ts:fa feature-increment)
|
|
1039
|
+
target_products: ${targetProducts.length > 0 ? '\n - ' + targetProducts.join('\n - ') : '[]'}
|
|
613
1040
|
`;
|
|
614
1041
|
fs.writeFileSync(path.join(projectDir, 'project.yml'), projectYml, 'utf-8');
|
|
615
1042
|
console.log(` ✓ Created projects/${projectId}/project.yml`);
|
|
616
1043
|
|
|
617
|
-
// features/
|
|
618
|
-
const
|
|
619
|
-
fs.mkdirSync(
|
|
1044
|
+
// feature-increments/ (replaces features/ in 4.0)
|
|
1045
|
+
const fiDir = path.join(projectDir, 'feature-increments');
|
|
1046
|
+
fs.mkdirSync(fiDir, { recursive: true });
|
|
1047
|
+
fs.writeFileSync(path.join(fiDir, 'increments-index.md'), `# Feature Increments Index
|
|
620
1048
|
|
|
621
|
-
|
|
1049
|
+
> **Project:** ${projectId}
|
|
622
1050
|
|
|
623
|
-
|
|
624
|
-
|
|
1051
|
+
Feature Increments describe TO-BE changes to Product Features.
|
|
1052
|
+
They are NOT the source of truth - the Product Canon is.
|
|
625
1053
|
|
|
626
|
-
##
|
|
1054
|
+
## Increment Registry
|
|
627
1055
|
|
|
628
|
-
| ID |
|
|
629
|
-
|
|
630
|
-
| _(none yet)_ | | | |
|
|
1056
|
+
| ID | Product | Feature | Status | Epic |
|
|
1057
|
+
|----|---------|---------|--------|------|
|
|
1058
|
+
| _(none yet)_ | | | | |
|
|
631
1059
|
|
|
632
|
-
## Next Available ID:
|
|
1060
|
+
## Next Available ID: _Derived from product prefix (fi-PRX-XXX)_
|
|
633
1061
|
|
|
634
1062
|
---
|
|
635
1063
|
|
|
636
|
-
> **To add
|
|
1064
|
+
> **To add feature-increments:** Run \`ts:fa feature-increment <product-id> <feature-id>\`
|
|
637
1065
|
`, 'utf-8');
|
|
1066
|
+
console.log(` ✓ Created projects/${projectId}/feature-increments/`);
|
|
638
1067
|
|
|
639
|
-
|
|
1068
|
+
// business-analysis-increments/
|
|
1069
|
+
const baiDir = path.join(projectDir, 'business-analysis-increments');
|
|
1070
|
+
fs.mkdirSync(baiDir, { recursive: true });
|
|
1071
|
+
fs.writeFileSync(path.join(baiDir, 'README.md'), `# Business Analysis Increments
|
|
640
1072
|
|
|
641
|
-
|
|
1073
|
+
TO-BE changes to product business analysis documents.
|
|
1074
|
+
|
|
1075
|
+
**Naming:** \`bai-PRX-XXX-<slug>.md\` (PRX = Product Prefix)
|
|
1076
|
+
|
|
1077
|
+
**Template:** \`/.teamspec/templates/business-analysis-template.md\`
|
|
1078
|
+
`, 'utf-8');
|
|
1079
|
+
console.log(` ✓ Created projects/${projectId}/business-analysis-increments/`);
|
|
1080
|
+
|
|
1081
|
+
// solution-design-increments/
|
|
1082
|
+
const sdiDir = path.join(projectDir, 'solution-design-increments');
|
|
1083
|
+
fs.mkdirSync(sdiDir, { recursive: true });
|
|
1084
|
+
fs.writeFileSync(path.join(sdiDir, 'README.md'), `# Solution Design Increments
|
|
1085
|
+
|
|
1086
|
+
TO-BE changes to product solution designs.
|
|
1087
|
+
|
|
1088
|
+
**Naming:** \`sdi-PRX-XXX-<slug>.md\` (PRX = Product Prefix)
|
|
1089
|
+
|
|
1090
|
+
**Template:** \`/.teamspec/templates/solution-design-template.md\`
|
|
1091
|
+
`, 'utf-8');
|
|
1092
|
+
console.log(` ✓ Created projects/${projectId}/solution-design-increments/`);
|
|
642
1093
|
|
|
643
|
-
|
|
644
|
-
|
|
1094
|
+
// technical-architecture-increments/
|
|
1095
|
+
const taiDir = path.join(projectDir, 'technical-architecture-increments');
|
|
1096
|
+
fs.mkdirSync(taiDir, { recursive: true });
|
|
1097
|
+
fs.writeFileSync(path.join(taiDir, 'README.md'), `# Technical Architecture Increments
|
|
1098
|
+
|
|
1099
|
+
TO-BE technical architecture decisions for this project.
|
|
1100
|
+
|
|
1101
|
+
**Naming:** \`tai-PRX-XXX-<slug>.md\` (PRX = Product Prefix)
|
|
1102
|
+
|
|
1103
|
+
**Template:** \`/.teamspec/templates/adr-template.md\`
|
|
1104
|
+
`, 'utf-8');
|
|
1105
|
+
console.log(` ✓ Created projects/${projectId}/technical-architecture-increments/`);
|
|
1106
|
+
|
|
1107
|
+
// epics/ (now required containers for stories)
|
|
1108
|
+
fs.mkdirSync(path.join(projectDir, 'epics'), { recursive: true });
|
|
1109
|
+
fs.writeFileSync(path.join(projectDir, 'epics', 'epics-index.md'), `# Epics Index
|
|
1110
|
+
|
|
1111
|
+
Epics are required containers for stories. Each Epic links to Feature-Increments.
|
|
1112
|
+
|
|
1113
|
+
| ID | Name | Status | Feature Increments | Stories |
|
|
1114
|
+
|----|------|--------|--------------------|---------|
|
|
645
1115
|
| _(none yet)_ | | | | |
|
|
1116
|
+
|
|
1117
|
+
## Next Available ID: _Derived from product prefix (epic-PRX-XXX)_
|
|
1118
|
+
|
|
1119
|
+
> **To add epics:** Run \`ts:fa epic\`
|
|
646
1120
|
`, 'utf-8');
|
|
647
|
-
console.log(` ✓ Created projects/${projectId}/
|
|
1121
|
+
console.log(` ✓ Created projects/${projectId}/epics/`);
|
|
648
1122
|
|
|
649
|
-
// stories/ with workflow folders
|
|
1123
|
+
// stories/ with workflow folders (updated folder names)
|
|
650
1124
|
const storiesDir = path.join(projectDir, 'stories');
|
|
651
|
-
for (const folder of ['backlog', 'ready-to-refine', 'ready-
|
|
1125
|
+
for (const folder of ['backlog', 'ready-to-refine', 'ready-to-develop']) {
|
|
652
1126
|
fs.mkdirSync(path.join(storiesDir, folder), { recursive: true });
|
|
653
1127
|
}
|
|
654
1128
|
|
|
@@ -660,31 +1134,24 @@ Tracks completed stories and their impact on the Feature Canon.
|
|
|
660
1134
|
|--------|--------|-------|
|
|
661
1135
|
| \`backlog/\` | New stories | FA |
|
|
662
1136
|
| \`ready-to-refine/\` | Ready for dev refinement | FA |
|
|
663
|
-
| \`ready-
|
|
1137
|
+
| \`ready-to-develop/\` | Refined, ready for sprint | DEV |
|
|
664
1138
|
|
|
665
|
-
## Rules
|
|
1139
|
+
## Rules (4.0)
|
|
666
1140
|
|
|
667
|
-
-
|
|
668
|
-
- Stories
|
|
669
|
-
-
|
|
1141
|
+
- **Stories MUST link to an Epic** via filename: \`s-eXXX-YYY-description.md\`
|
|
1142
|
+
- Stories MAY reference Feature-Increments (fi-PRX-XXX) in content
|
|
1143
|
+
- Stories describe DELTAS (before/after) not full behavior
|
|
1144
|
+
- Use \`ts:fa story <epic-id>\` to create properly formatted stories
|
|
670
1145
|
`, 'utf-8');
|
|
671
1146
|
console.log(` ✓ Created projects/${projectId}/stories/`);
|
|
672
1147
|
|
|
673
|
-
//
|
|
674
|
-
fs.mkdirSync(path.join(projectDir, 'adr'), { recursive: true });
|
|
675
|
-
fs.writeFileSync(path.join(projectDir, 'adr', 'README.md'), `# Architecture Decision Records
|
|
676
|
-
|
|
677
|
-
Naming: \`ADR-NNN-<slug>.md\`
|
|
678
|
-
|
|
679
|
-
Use template: \`/.teamspec/templates/adr-template.md\`
|
|
680
|
-
`, 'utf-8');
|
|
681
|
-
console.log(` ✓ Created projects/${projectId}/adr/`);
|
|
682
|
-
|
|
683
|
-
// decisions/
|
|
1148
|
+
// decisions/ (project-level decisions)
|
|
684
1149
|
fs.mkdirSync(path.join(projectDir, 'decisions'), { recursive: true });
|
|
685
|
-
fs.writeFileSync(path.join(projectDir, 'decisions', 'README.md'), `#
|
|
1150
|
+
fs.writeFileSync(path.join(projectDir, 'decisions', 'README.md'), `# Project Decisions
|
|
686
1151
|
|
|
687
|
-
|
|
1152
|
+
Project-level business decisions.
|
|
1153
|
+
|
|
1154
|
+
**Naming:** \`dec-XXX-<slug>.md\`
|
|
688
1155
|
|
|
689
1156
|
Use template: \`/.teamspec/templates/decision-log-template.md\`
|
|
690
1157
|
`, 'utf-8');
|
|
@@ -694,7 +1161,7 @@ Use template: \`/.teamspec/templates/decision-log-template.md\`
|
|
|
694
1161
|
fs.mkdirSync(path.join(projectDir, 'dev-plans'), { recursive: true });
|
|
695
1162
|
fs.writeFileSync(path.join(projectDir, 'dev-plans', 'README.md'), `# Development Plans
|
|
696
1163
|
|
|
697
|
-
Naming
|
|
1164
|
+
**Naming:** \`dp-sXXX-YYY-<slug>.md\` (matches story s-eXXX-YYY)
|
|
698
1165
|
|
|
699
1166
|
Rules:
|
|
700
1167
|
- NEVER start coding without a dev plan
|
|
@@ -707,7 +1174,7 @@ Rules:
|
|
|
707
1174
|
fs.mkdirSync(path.join(projectDir, 'qa', 'test-cases'), { recursive: true });
|
|
708
1175
|
fs.writeFileSync(path.join(projectDir, 'qa', 'README.md'), `# Quality Assurance
|
|
709
1176
|
|
|
710
|
-
Tests validate **Feature Canon** behavior
|
|
1177
|
+
Tests validate **Feature Canon** behavior (AS-IS + TO-BE changes).
|
|
711
1178
|
|
|
712
1179
|
Use template: \`/.teamspec/templates/testcases-template.md\`
|
|
713
1180
|
`, 'utf-8');
|
|
@@ -721,25 +1188,13 @@ Use template: \`/.teamspec/templates/testcases-template.md\`
|
|
|
721
1188
|
|
|
722
1189
|
## Sprint History
|
|
723
1190
|
|
|
724
|
-
| Sprint | Goal | Status | Stories | Canon Synced |
|
|
725
|
-
|
|
726
|
-
| _(none yet)_ | | | | |
|
|
1191
|
+
| Sprint | Goal | Status | Stories | Deployed | Canon Synced |
|
|
1192
|
+
|--------|------|--------|---------|----------|--------------|
|
|
1193
|
+
| _(none yet)_ | | | | | |
|
|
727
1194
|
`, 'utf-8');
|
|
728
1195
|
console.log(` ✓ Created projects/${projectId}/sprints/`);
|
|
729
1196
|
|
|
730
|
-
|
|
731
|
-
fs.mkdirSync(path.join(projectDir, 'epics'), { recursive: true });
|
|
732
|
-
fs.writeFileSync(path.join(projectDir, 'epics', 'epics-index.md'), `# Epics Index
|
|
733
|
-
|
|
734
|
-
| ID | Name | Status | Features | Target |
|
|
735
|
-
|----|------|--------|----------|--------|
|
|
736
|
-
| _(none yet)_ | | | | |
|
|
737
|
-
|
|
738
|
-
## Next Available ID: **EPIC-001**
|
|
739
|
-
|
|
740
|
-
> **To add epics:** Run \`ts:ba epic\` — epics are never created implicitly.
|
|
741
|
-
`, 'utf-8');
|
|
742
|
-
console.log(` ✓ Created projects/${projectId}/epics/`);
|
|
1197
|
+
return projectDir;
|
|
743
1198
|
}
|
|
744
1199
|
|
|
745
1200
|
// =============================================================================
|
|
@@ -886,7 +1341,7 @@ async function setupIDEIntegration(targetDir, options) {
|
|
|
886
1341
|
|
|
887
1342
|
function printNextSteps(targetDir, profile, projectId, ide, ideResult, copilot) {
|
|
888
1343
|
console.log(`\n${colored('='.repeat(70), colors.green)}`);
|
|
889
|
-
console.log(colored(' ✅ TeamSpec
|
|
1344
|
+
console.log(colored(' ✅ TeamSpec 4.0 initialized successfully!', colors.green + colors.bold));
|
|
890
1345
|
console.log(colored('='.repeat(70), colors.green));
|
|
891
1346
|
|
|
892
1347
|
const copilotSection = copilot !== false ? `
|
|
@@ -929,9 +1384,9 @@ ${colored('📁 Created Structure:', colors.bold)}
|
|
|
929
1384
|
${colored('1. Configure Your Team', colors.cyan)}
|
|
930
1385
|
Edit ${colored('.teamspec/context/team.yml', colors.bold)} to set your tech stack.
|
|
931
1386
|
|
|
932
|
-
${colored('2. Create Your First Feature', colors.cyan)}
|
|
933
|
-
|
|
934
|
-
${colored('ts:
|
|
1387
|
+
${colored('2. Create Your First Feature-Increment', colors.cyan)}
|
|
1388
|
+
Feature-Increments are NEVER created implicitly. Use:
|
|
1389
|
+
${colored('ts:fa feature-increment', colors.bold)} - Create feature-increment (FI)
|
|
935
1390
|
|
|
936
1391
|
${colored('3. Start Using TeamSpec Commands', colors.cyan)}
|
|
937
1392
|
${colored('ts:ba create', colors.bold)} - Create business analysis
|
|
@@ -988,26 +1443,105 @@ async function run(args) {
|
|
|
988
1443
|
|
|
989
1444
|
// Handle lint command
|
|
990
1445
|
if (options.command === 'lint') {
|
|
991
|
-
const {
|
|
1446
|
+
const { runLint } = require('./linter');
|
|
992
1447
|
const targetDir = path.resolve(options.target);
|
|
993
1448
|
|
|
994
|
-
|
|
995
|
-
|
|
1449
|
+
const exitCode = runLint(targetDir, {
|
|
1450
|
+
project: options.project,
|
|
1451
|
+
rule: options.rule,
|
|
1452
|
+
verbose: options.verbose
|
|
1453
|
+
});
|
|
996
1454
|
|
|
997
|
-
|
|
998
|
-
|
|
1455
|
+
process.exit(exitCode);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// Handle migrate command
|
|
1459
|
+
if (options.command === 'migrate') {
|
|
1460
|
+
const { analyzeMigration, printMigrationPlan, executeMigration } = require('./migrate');
|
|
1461
|
+
const targetDir = path.resolve(options.target);
|
|
1462
|
+
|
|
1463
|
+
console.log(`\n${colored('TeamSpec Migration Tool', colors.bold + colors.cyan)} `);
|
|
1464
|
+
console.log(`${colored('Target:', colors.bold)} ${targetDir} `);
|
|
1465
|
+
|
|
1466
|
+
// Detect workspace version
|
|
1467
|
+
const versionInfo = detectWorkspaceVersion(targetDir);
|
|
1468
|
+
console.log(`${colored('Detected version:', colors.bold)} ${versionInfo.version}`);
|
|
1469
|
+
|
|
1470
|
+
if (versionInfo.version === 'none') {
|
|
1471
|
+
console.error(colored(`\n❌ No TeamSpec workspace found in: ${targetDir}`, colors.red));
|
|
1472
|
+
console.error('Run `teamspec init` first to create a new workspace.');
|
|
1473
|
+
process.exit(1);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
if (versionInfo.version === '4.0') {
|
|
1477
|
+
console.log(colored(`\n✅ Workspace is already at version 4.0`, colors.green));
|
|
1478
|
+
console.log(` Products: ${versionInfo.productCount}`);
|
|
1479
|
+
console.log(` Projects: ${versionInfo.projectCount}`);
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
if (versionInfo.version !== '2.0') {
|
|
1484
|
+
console.error(colored(`\n❌ Unknown workspace version: ${versionInfo.version}`, colors.red));
|
|
1485
|
+
console.error('Manual migration may be required.');
|
|
1486
|
+
process.exit(1);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// Analyze and plan migration
|
|
1490
|
+
console.log(`\nAnalyzing workspace for migration...`);
|
|
1491
|
+
const plan = analyzeMigration(targetDir);
|
|
1492
|
+
printMigrationPlan(plan);
|
|
1493
|
+
|
|
1494
|
+
if (!plan.canMigrate) {
|
|
1495
|
+
process.exit(1);
|
|
999
1496
|
}
|
|
1000
1497
|
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1498
|
+
// Dry run by default unless --fix is specified
|
|
1499
|
+
const dryRun = !options.fix;
|
|
1003
1500
|
|
|
1004
|
-
|
|
1501
|
+
if (dryRun) {
|
|
1502
|
+
console.log(`\n${colored('ℹ️ Dry run mode', colors.yellow)} - no changes will be made.`);
|
|
1503
|
+
console.log(` Run with ${colored('--fix', colors.bold)} to apply changes.`);
|
|
1504
|
+
} else {
|
|
1505
|
+
if (!options.force && !options.nonInteractive) {
|
|
1506
|
+
const rl = createReadlineInterface();
|
|
1507
|
+
const proceed = await promptYesNo(
|
|
1508
|
+
rl,
|
|
1509
|
+
`\n${colored('Proceed with migration?', colors.bold)}`,
|
|
1510
|
+
false
|
|
1511
|
+
);
|
|
1512
|
+
rl.close();
|
|
1513
|
+
if (!proceed) {
|
|
1514
|
+
console.log('Cancelled.');
|
|
1515
|
+
process.exit(0);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// Execute migration
|
|
1521
|
+
console.log(`\n${colored('Executing migration...', colors.bold)}`);
|
|
1522
|
+
const result = executeMigration(targetDir, plan, { dryRun });
|
|
1005
1523
|
|
|
1006
|
-
//
|
|
1007
|
-
|
|
1008
|
-
|
|
1524
|
+
// Summary
|
|
1525
|
+
console.log(`\n${colored('Migration Summary:', colors.bold)}`);
|
|
1526
|
+
console.log(` Actions completed: ${result.actionsCompleted}`);
|
|
1527
|
+
console.log(` Actions failed: ${result.actionsFailed}`);
|
|
1528
|
+
|
|
1529
|
+
if (result.errors.length > 0) {
|
|
1530
|
+
console.log(`\n${colored('Errors:', colors.red)}`);
|
|
1531
|
+
for (const error of result.errors) {
|
|
1532
|
+
console.log(` • ${error}`);
|
|
1533
|
+
}
|
|
1009
1534
|
process.exit(1);
|
|
1010
1535
|
}
|
|
1536
|
+
|
|
1537
|
+
if (!dryRun) {
|
|
1538
|
+
console.log(`\n${colored('✅ Migration complete!', colors.green + colors.bold)}`);
|
|
1539
|
+
console.log(`\nNext steps:`);
|
|
1540
|
+
console.log(` 1. Review the migrated product structure in products/`);
|
|
1541
|
+
console.log(` 2. Update product descriptions in product.yml files`);
|
|
1542
|
+
console.log(` 3. Run ${colored('teamspec lint', colors.cyan)} to verify the migration`);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1011
1545
|
return;
|
|
1012
1546
|
}
|
|
1013
1547
|
|
|
@@ -1136,6 +1670,7 @@ async function run(args) {
|
|
|
1136
1670
|
options.industry = 'technology';
|
|
1137
1671
|
options.cadence = 'scrum';
|
|
1138
1672
|
options.sprintLengthDays = 14;
|
|
1673
|
+
options.setupMode = options.setupMode || 'project-only'; // Default to project-only for non-interactive
|
|
1139
1674
|
} else {
|
|
1140
1675
|
await runInteractive(options);
|
|
1141
1676
|
}
|
|
@@ -1146,9 +1681,11 @@ async function run(args) {
|
|
|
1146
1681
|
process.exit(1);
|
|
1147
1682
|
}
|
|
1148
1683
|
|
|
1149
|
-
|
|
1684
|
+
if (options.project) {
|
|
1685
|
+
options.project = normalizeProjectId(options.project);
|
|
1686
|
+
}
|
|
1150
1687
|
|
|
1151
|
-
console.log(`\n${colored('Initializing TeamSpec...', colors.bold)} `);
|
|
1688
|
+
console.log(`\n${colored('Initializing TeamSpec 4.0...', colors.bold)} `);
|
|
1152
1689
|
|
|
1153
1690
|
const sourceDir = getTeamspecCoreDir();
|
|
1154
1691
|
if (!fs.existsSync(sourceDir)) {
|
|
@@ -1158,7 +1695,24 @@ async function run(args) {
|
|
|
1158
1695
|
|
|
1159
1696
|
copyTeamspecCore(targetDir, sourceDir);
|
|
1160
1697
|
createTeamContext(targetDir, options);
|
|
1161
|
-
|
|
1698
|
+
|
|
1699
|
+
// Create products-index.md
|
|
1700
|
+
createProductsIndex(targetDir);
|
|
1701
|
+
|
|
1702
|
+
// Create product structure (if product-first mode)
|
|
1703
|
+
if (options.setupMode === 'product-first' && options.productName) {
|
|
1704
|
+
createProductStructure(targetDir, options.productId, options.productName, options.productPrefix);
|
|
1705
|
+
|
|
1706
|
+
// Create project linked to product
|
|
1707
|
+
if (options.createProject && options.project) {
|
|
1708
|
+
createProjectStructure(targetDir, options.project, {
|
|
1709
|
+
targetProducts: [options.productId]
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
} else if (options.project) {
|
|
1713
|
+
// Project-only mode
|
|
1714
|
+
createProjectStructure(targetDir, options.project);
|
|
1715
|
+
}
|
|
1162
1716
|
|
|
1163
1717
|
// Copy GitHub Copilot instructions if requested
|
|
1164
1718
|
if (options.copilot !== false) {
|