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.
Files changed (51) hide show
  1. package/README.md +24 -12
  2. package/bin/teamspec-init.js +2 -2
  3. package/lib/cli.js +653 -99
  4. package/lib/extension-installer.js +19 -219
  5. package/lib/linter.js +823 -1076
  6. package/lib/prompt-generator.js +312 -330
  7. package/lib/structure-loader.js +400 -0
  8. package/package.json +14 -6
  9. package/teamspec-core/FOLDER_STRUCTURE.yml +131 -0
  10. package/teamspec-core/agents/AGENT_BA.md +188 -293
  11. package/teamspec-core/agents/AGENT_BOOTSTRAP.md +197 -102
  12. package/teamspec-core/agents/AGENT_DES.md +9 -8
  13. package/teamspec-core/agents/AGENT_DEV.md +68 -67
  14. package/teamspec-core/agents/AGENT_FA.md +437 -245
  15. package/teamspec-core/agents/AGENT_FIX.md +344 -74
  16. package/teamspec-core/agents/AGENT_PO.md +487 -0
  17. package/teamspec-core/agents/AGENT_QA.md +124 -98
  18. package/teamspec-core/agents/AGENT_SA.md +143 -84
  19. package/teamspec-core/agents/AGENT_SM.md +106 -83
  20. package/teamspec-core/agents/README.md +143 -93
  21. package/teamspec-core/copilot-instructions.md +281 -205
  22. package/teamspec-core/definitions/definition-of-done.md +47 -84
  23. package/teamspec-core/definitions/definition-of-ready.md +35 -60
  24. package/teamspec-core/registry.yml +898 -0
  25. package/teamspec-core/teamspec.yml +44 -28
  26. package/teamspec-core/templates/README.md +5 -5
  27. package/teamspec-core/templates/adr-template.md +19 -17
  28. package/teamspec-core/templates/bai-template.md +125 -0
  29. package/teamspec-core/templates/bug-report-template.md +21 -15
  30. package/teamspec-core/templates/business-analysis-template.md +16 -13
  31. package/teamspec-core/templates/decision-log-template.md +26 -22
  32. package/teamspec-core/templates/dev-plan-template.md +168 -0
  33. package/teamspec-core/templates/epic-template.md +204 -0
  34. package/teamspec-core/templates/feature-increment-template.md +84 -0
  35. package/teamspec-core/templates/feature-template.md +45 -32
  36. package/teamspec-core/templates/increments-index-template.md +53 -0
  37. package/teamspec-core/templates/product-template.yml +44 -0
  38. package/teamspec-core/templates/products-index-template.md +46 -0
  39. package/teamspec-core/templates/project-template.yml +70 -0
  40. package/teamspec-core/templates/ri-template.md +225 -0
  41. package/teamspec-core/templates/rt-template.md +104 -0
  42. package/teamspec-core/templates/sd-template.md +132 -0
  43. package/teamspec-core/templates/sdi-template.md +119 -0
  44. package/teamspec-core/templates/sprint-template.md +17 -15
  45. package/teamspec-core/templates/story-template-v4.md +202 -0
  46. package/teamspec-core/templates/story-template.md +48 -90
  47. package/teamspec-core/templates/ta-template.md +198 -0
  48. package/teamspec-core/templates/tai-template.md +131 -0
  49. package/teamspec-core/templates/tc-template.md +145 -0
  50. package/teamspec-core/templates/testcases-template.md +20 -17
  51. 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.0.0 - Feature Canon Operating Model
3
+ * Version 4.0.0 - Product/Project Operating Model
4
4
  *
5
- * This CLI bootstraps TeamSpec 2.0 in any repository by:
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
- Feature Canon Operating Model v2.0
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 2.0 Feature Canon Operating Model
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
- projects/<project-id>/ Project artifacts
227
- ├── features/ Feature Canon (source of truth)
228
- ├── stories/ User stories (workflow folders)
229
- ├── adr/ Architecture decisions
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 ID
381
- if (!options.project) {
382
- console.log(`\n${colored('Project Structure', colors.bold)}`);
383
- console.log(' TeamSpec organizes artifacts in project folders: projects/<project-id>/');
384
- options.project = await prompt(
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('Initial project ID', colors.bold)} (lowercase, hyphenated)`,
387
- DEFAULT_PROJECT_ID
579
+ `${colored('Product name', colors.bold)}`,
580
+ 'My Product'
388
581
  );
389
- options.project = normalizeProjectId(options.project);
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(` Project ID: ${options.project}`);
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
- // Project Structure Creation
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
- function createProjectStructure(targetDir, projectId) {
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
- // project.yml
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 featuresDir = path.join(projectDir, 'features');
619
- fs.mkdirSync(featuresDir, { recursive: true });
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
- fs.writeFileSync(path.join(featuresDir, 'features-index.md'), `# Features Index (Feature Canon)
1049
+ > **Project:** ${projectId}
622
1050
 
623
- This is the master index of all features in this project.
624
- The Feature Canon is the **source of truth** for system behavior.
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
- ## Feature Registry
1054
+ ## Increment Registry
627
1055
 
628
- | ID | Name | Status | Owner |
629
- |----|------|--------|-------|
630
- | _(none yet)_ | | | |
1056
+ | ID | Product | Feature | Status | Epic |
1057
+ |----|---------|---------|--------|------|
1058
+ | _(none yet)_ | | | | |
631
1059
 
632
- ## Next Available ID: **F-001**
1060
+ ## Next Available ID: _Derived from product prefix (fi-PRX-XXX)_
633
1061
 
634
1062
  ---
635
1063
 
636
- > **To add features:** Run \`ts:ba feature\` features are never created implicitly.
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
- fs.writeFileSync(path.join(featuresDir, 'story-ledger.md'), `# Story Ledger
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
- Tracks completed stories and their impact on the Feature Canon.
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
- | Story ID | Title | Sprint | Features Modified | Merged Date |
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}/features/`);
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-for-development']) {
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-for-development/\` | Refined, ready for sprint | DEV |
1137
+ | \`ready-to-develop/\` | Refined, ready for sprint | DEV |
664
1138
 
665
- ## Rules
1139
+ ## Rules (4.0)
666
1140
 
667
- - Every story must link to features in the Feature Canon
668
- - Stories describe DELTAS (changes), not full behavior
669
- - Use \`ts:fa story\` to create properly formatted stories
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
- // adr/
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'), `# Business Decisions
1150
+ fs.writeFileSync(path.join(projectDir, 'decisions', 'README.md'), `# Project Decisions
686
1151
 
687
- Naming: \`DECISION-NNN-<slug>.md\`
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: \`story-NNN-tasks.md\`
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, not individual stories.
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
- // epics/
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 2.0 initialized successfully!', colors.green + colors.bold));
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
- Features are NEVER created implicitly. Use:
934
- ${colored('ts:ba feature', colors.bold)} - Create feature
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 { Linter, SEVERITY } = require('./linter');
1446
+ const { runLint } = require('./linter');
992
1447
  const targetDir = path.resolve(options.target);
993
1448
 
994
- console.log(`\n${colored('TeamSpec Linter', colors.bold + colors.cyan)} `);
995
- console.log(`${colored('Scanning:', colors.bold)} ${targetDir} `);
1449
+ const exitCode = runLint(targetDir, {
1450
+ project: options.project,
1451
+ rule: options.rule,
1452
+ verbose: options.verbose
1453
+ });
996
1454
 
997
- if (options.project) {
998
- console.log(`${colored('Project:', colors.bold)} ${options.project} `);
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
- const linter = new Linter(targetDir);
1002
- const results = await linter.run({ project: options.project });
1498
+ // Dry run by default unless --fix is specified
1499
+ const dryRun = !options.fix;
1003
1500
 
1004
- console.log(linter.formatResults(results));
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
- // Exit with error code if there are errors or blockers
1007
- const hasErrors = results.some(r => r.severity === SEVERITY.ERROR || r.severity === SEVERITY.BLOCKER);
1008
- if (hasErrors) {
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
- options.project = normalizeProjectId(options.project);
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
- createProjectStructure(targetDir, options.project);
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) {