teamspec 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/LICENSE +21 -0
- package/README.md +252 -0
- package/bin/teamspec-init.js +10 -0
- package/extensions/teamspec-0.1.0.vsix +0 -0
- package/lib/cli.js +1174 -0
- package/lib/extension-installer.js +236 -0
- package/lib/linter.js +1184 -0
- package/lib/prompt-generator.js +409 -0
- package/package.json +51 -0
- package/teamspec-core/agents/AGENT_BA.md +486 -0
- package/teamspec-core/agents/AGENT_BOOTSTRAP.md +447 -0
- package/teamspec-core/agents/AGENT_DES.md +623 -0
- package/teamspec-core/agents/AGENT_DEV.md +611 -0
- package/teamspec-core/agents/AGENT_FA.md +736 -0
- package/teamspec-core/agents/AGENT_FEEDBACK.md +202 -0
- package/teamspec-core/agents/AGENT_FIX.md +380 -0
- package/teamspec-core/agents/AGENT_QA.md +756 -0
- package/teamspec-core/agents/AGENT_SA.md +581 -0
- package/teamspec-core/agents/AGENT_SM.md +771 -0
- package/teamspec-core/agents/README.md +383 -0
- package/teamspec-core/context/_schema.yml +222 -0
- package/teamspec-core/copilot-instructions.md +356 -0
- package/teamspec-core/definitions/definition-of-done.md +129 -0
- package/teamspec-core/definitions/definition-of-ready.md +104 -0
- package/teamspec-core/profiles/enterprise.yml +127 -0
- package/teamspec-core/profiles/platform-team.yml +104 -0
- package/teamspec-core/profiles/regulated.yml +97 -0
- package/teamspec-core/profiles/startup.yml +85 -0
- package/teamspec-core/teamspec.yml +69 -0
- package/teamspec-core/templates/README.md +211 -0
- package/teamspec-core/templates/active-sprint-template.md +98 -0
- package/teamspec-core/templates/adr-template.md +194 -0
- package/teamspec-core/templates/bug-report-template.md +188 -0
- package/teamspec-core/templates/business-analysis-template.md +164 -0
- package/teamspec-core/templates/decision-log-template.md +216 -0
- package/teamspec-core/templates/feature-template.md +269 -0
- package/teamspec-core/templates/functional-spec-template.md +161 -0
- package/teamspec-core/templates/refinement-notes-template.md +133 -0
- package/teamspec-core/templates/sprint-goal-template.md +129 -0
- package/teamspec-core/templates/sprint-template.md +175 -0
- package/teamspec-core/templates/sprints-index-template.md +67 -0
- package/teamspec-core/templates/story-template.md +244 -0
- package/teamspec-core/templates/storymap-template.md +204 -0
- package/teamspec-core/templates/testcases-template.md +147 -0
- package/teamspec-core/templates/uat-pack-template.md +161 -0
package/lib/cli.js
ADDED
|
@@ -0,0 +1,1174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeamSpec Init CLI - Pure JavaScript implementation
|
|
3
|
+
* Version 3.0.0 - Feature Canon Operating Model
|
|
4
|
+
*
|
|
5
|
+
* This CLI bootstraps TeamSpec 2.0 in any repository by:
|
|
6
|
+
* 1. Asking team setup questions
|
|
7
|
+
* 2. Deploying .teamspec/ folder with core files
|
|
8
|
+
* 3. Creating project structure
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const readline = require('readline');
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// ANSI Color Helpers
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
const colors = {
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
bold: '\x1b[1m',
|
|
22
|
+
red: '\x1b[91m',
|
|
23
|
+
green: '\x1b[92m',
|
|
24
|
+
yellow: '\x1b[93m',
|
|
25
|
+
blue: '\x1b[94m',
|
|
26
|
+
cyan: '\x1b[96m',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function colored(text, color) {
|
|
30
|
+
if (process.stdout.isTTY) {
|
|
31
|
+
return `${color}${text}${colors.reset}`;
|
|
32
|
+
}
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Banner
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
function printBanner() {
|
|
41
|
+
const banner = `
|
|
42
|
+
╔══════════════════════════════════════════════════════════════════════╗
|
|
43
|
+
║ ║
|
|
44
|
+
║ ████████╗███████╗ █████╗ ███╗ ███╗███████╗██████╗ ███████╗ ██████╗ ║
|
|
45
|
+
║ ╚══██╔══╝██╔════╝██╔══██╗████╗ ████║██╔════╝██╔══██╗██╔════╝██╔════╝ ║
|
|
46
|
+
║ ██║ █████╗ ███████║██╔████╔██║███████╗██████╔╝█████╗ ██║ ║
|
|
47
|
+
║ ██║ ██╔══╝ ██╔══██║██║╚██╔╝██║╚════██║██╔═══╝ ██╔══╝ ██║ ║
|
|
48
|
+
║ ██║ ███████╗██║ ██║██║ ╚═╝ ██║███████║██║ ███████╗╚██████╗ ║
|
|
49
|
+
║ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝ ║
|
|
50
|
+
║ ║
|
|
51
|
+
║ ║
|
|
52
|
+
║ Feature Canon Operating Model v2.0 ║
|
|
53
|
+
╚══════════════════════════════════════════════════════════════════════╝
|
|
54
|
+
`;
|
|
55
|
+
console.log(colored(banner, colors.cyan));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Configuration Options
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
const PROFILE_OPTIONS = {
|
|
63
|
+
none: 'No specific profile - vanilla TeamSpec',
|
|
64
|
+
regulated: 'Regulated industry (banking, healthcare, government) - strict compliance',
|
|
65
|
+
startup: 'Startup/MVP mode - lean documentation, speed focus',
|
|
66
|
+
'platform-team': 'Platform/infrastructure team - API-first, SLA focus',
|
|
67
|
+
enterprise: 'Enterprise - governance, audit trails, multi-team coordination',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const INDUSTRY_OPTIONS = {
|
|
71
|
+
technology: 'Technology / Software',
|
|
72
|
+
finance: 'Financial Services / Banking',
|
|
73
|
+
healthcare: 'Healthcare / Life Sciences',
|
|
74
|
+
retail: 'Retail / E-commerce',
|
|
75
|
+
manufacturing: 'Manufacturing / Industrial',
|
|
76
|
+
government: 'Government / Public Sector',
|
|
77
|
+
other: 'Other',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const CADENCE_OPTIONS = {
|
|
81
|
+
scrum: 'Scrum (time-boxed sprints)',
|
|
82
|
+
kanban: 'Kanban (continuous flow)',
|
|
83
|
+
scrumban: 'Scrumban (hybrid)',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const IDE_OPTIONS = {
|
|
87
|
+
none: 'No IDE integration - just files',
|
|
88
|
+
vscode: 'VS Code with @teamspec chat participant',
|
|
89
|
+
cursor: 'Cursor (VS Code settings only, no chat participant)',
|
|
90
|
+
other: 'Other IDE (manual setup)',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const DEFAULT_PROJECT_ID = 'main-project';
|
|
94
|
+
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// Argument Parsing
|
|
97
|
+
// =============================================================================
|
|
98
|
+
|
|
99
|
+
function parseArgs(args) {
|
|
100
|
+
const options = {
|
|
101
|
+
command: 'init',
|
|
102
|
+
target: process.cwd(),
|
|
103
|
+
profile: null,
|
|
104
|
+
org: null,
|
|
105
|
+
team: null,
|
|
106
|
+
project: null,
|
|
107
|
+
ide: null,
|
|
108
|
+
copilot: null,
|
|
109
|
+
nonInteractive: false,
|
|
110
|
+
help: false,
|
|
111
|
+
version: false,
|
|
112
|
+
force: false,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
let i = 0;
|
|
116
|
+
|
|
117
|
+
if (args.length > 0 && !args[0].startsWith('-')) {
|
|
118
|
+
const cmd = args[0].toLowerCase();
|
|
119
|
+
if (['init', 'update', 'lint', 'generate-prompts'].includes(cmd)) {
|
|
120
|
+
options.command = cmd;
|
|
121
|
+
i = 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (; i < args.length; i++) {
|
|
126
|
+
const arg = args[i];
|
|
127
|
+
switch (arg) {
|
|
128
|
+
case '--help':
|
|
129
|
+
case '-h':
|
|
130
|
+
options.help = true;
|
|
131
|
+
break;
|
|
132
|
+
case '--version':
|
|
133
|
+
case '-v':
|
|
134
|
+
options.version = true;
|
|
135
|
+
break;
|
|
136
|
+
case '--target':
|
|
137
|
+
case '-t':
|
|
138
|
+
options.target = args[++i];
|
|
139
|
+
break;
|
|
140
|
+
case '--profile':
|
|
141
|
+
case '-p':
|
|
142
|
+
options.profile = args[++i];
|
|
143
|
+
break;
|
|
144
|
+
case '--org':
|
|
145
|
+
case '-o':
|
|
146
|
+
options.org = args[++i];
|
|
147
|
+
break;
|
|
148
|
+
case '--team':
|
|
149
|
+
options.team = args[++i];
|
|
150
|
+
break;
|
|
151
|
+
case '--project':
|
|
152
|
+
options.project = args[++i];
|
|
153
|
+
break;
|
|
154
|
+
case '--ide':
|
|
155
|
+
options.ide = args[++i];
|
|
156
|
+
break;
|
|
157
|
+
case '--copilot':
|
|
158
|
+
const copilotArg = args[++i];
|
|
159
|
+
options.copilot = copilotArg === 'true' || copilotArg === 'yes';
|
|
160
|
+
break;
|
|
161
|
+
case '--non-interactive':
|
|
162
|
+
case '-y':
|
|
163
|
+
options.nonInteractive = true;
|
|
164
|
+
break;
|
|
165
|
+
case '--force':
|
|
166
|
+
case '-f':
|
|
167
|
+
options.force = true;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return options;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// =============================================================================
|
|
176
|
+
// Help and Version
|
|
177
|
+
// =============================================================================
|
|
178
|
+
|
|
179
|
+
function printHelp() {
|
|
180
|
+
console.log(`
|
|
181
|
+
${colored('TeamSpec Init', colors.bold)} - Bootstrap TeamSpec 2.0 Feature Canon Operating Model
|
|
182
|
+
|
|
183
|
+
${colored('USAGE:', colors.bold)}
|
|
184
|
+
teamspec [command] [options]
|
|
185
|
+
|
|
186
|
+
${colored('COMMANDS:', colors.bold)}
|
|
187
|
+
init [options] Initialize TeamSpec in a repository (default)
|
|
188
|
+
update [options] Update TeamSpec core files (keeps team context)
|
|
189
|
+
lint [options] Lint project artifacts against TeamSpec rules
|
|
190
|
+
|
|
191
|
+
${colored('OPTIONS:', colors.bold)}
|
|
192
|
+
-h, --help Show this help message
|
|
193
|
+
-v, --version Show version number
|
|
194
|
+
-t, --target <dir> Target directory (default: current directory)
|
|
195
|
+
-p, --profile <profile> Team profile to use
|
|
196
|
+
-o, --org <name> Organization name
|
|
197
|
+
--team <name> Team name
|
|
198
|
+
--project <id> Project ID for folder structure (default: main-project)
|
|
199
|
+
--ide <ide> IDE integration (vscode, cursor, other, none)
|
|
200
|
+
--copilot <yes|no> Install GitHub Copilot instructions file (default: yes)
|
|
201
|
+
-y, --non-interactive Run without prompts (use defaults)
|
|
202
|
+
-f, --force Force update without confirmation
|
|
203
|
+
|
|
204
|
+
${colored('PROFILES:', colors.bold)}
|
|
205
|
+
none Vanilla TeamSpec
|
|
206
|
+
regulated Banking, healthcare, government (strict compliance)
|
|
207
|
+
startup Lean documentation, speed focus
|
|
208
|
+
platform-team API-first, SLA focus
|
|
209
|
+
enterprise Full governance, audit trails
|
|
210
|
+
|
|
211
|
+
${colored('EXAMPLES:', colors.bold)}
|
|
212
|
+
teamspec # Interactive setup
|
|
213
|
+
teamspec --profile startup -y # Quick setup with startup profile
|
|
214
|
+
teamspec update # Update core files, keep context
|
|
215
|
+
teamspec update --force # Update without confirmation
|
|
216
|
+
teamspec lint # Lint all projects
|
|
217
|
+
teamspec lint --project my-project # Lint specific project
|
|
218
|
+
teamspec generate-prompts # Generate GitHub Copilot prompt files
|
|
219
|
+
|
|
220
|
+
${colored('WHAT GETS CREATED:', colors.bold)}
|
|
221
|
+
.teamspec/ Core framework
|
|
222
|
+
├── templates/ Document templates
|
|
223
|
+
├── definitions/ DoR/DoD checklists
|
|
224
|
+
├── profiles/ Profile overlays
|
|
225
|
+
└── 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
|
|
230
|
+
└── ...
|
|
231
|
+
`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function printVersion() {
|
|
235
|
+
const pkg = require('../package.json');
|
|
236
|
+
console.log(`teamspec ${pkg.version}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// =============================================================================
|
|
240
|
+
// File System Utilities
|
|
241
|
+
// =============================================================================
|
|
242
|
+
|
|
243
|
+
function getTeamspecCoreDir() {
|
|
244
|
+
return path.join(__dirname, '..', 'teamspec-core');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function copyDirRecursive(src, dest) {
|
|
248
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
249
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
250
|
+
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
const srcPath = path.join(src, entry.name);
|
|
253
|
+
const destPath = path.join(dest, entry.name);
|
|
254
|
+
|
|
255
|
+
if (entry.isDirectory()) {
|
|
256
|
+
copyDirRecursive(srcPath, destPath);
|
|
257
|
+
} else {
|
|
258
|
+
fs.copyFileSync(srcPath, destPath);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// =============================================================================
|
|
264
|
+
// Interactive Prompts
|
|
265
|
+
// =============================================================================
|
|
266
|
+
|
|
267
|
+
function createReadlineInterface() {
|
|
268
|
+
return readline.createInterface({
|
|
269
|
+
input: process.stdin,
|
|
270
|
+
output: process.stdout,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function prompt(rl, question, defaultValue = '') {
|
|
275
|
+
return new Promise((resolve) => {
|
|
276
|
+
const defaultHint = defaultValue ? ` [${defaultValue}]` : '';
|
|
277
|
+
rl.question(`${question}${defaultHint}: `, (answer) => {
|
|
278
|
+
resolve(answer.trim() || defaultValue);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function promptYesNo(rl, question, defaultValue = true) {
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
const choices = defaultValue ? '[Y/n]' : '[y/N]';
|
|
286
|
+
rl.question(`${question} ${choices}: `, (answer) => {
|
|
287
|
+
const normalized = answer.trim().toLowerCase();
|
|
288
|
+
if (!normalized) {
|
|
289
|
+
resolve(defaultValue);
|
|
290
|
+
} else if (normalized === 'y' || normalized === 'yes') {
|
|
291
|
+
resolve(true);
|
|
292
|
+
} else {
|
|
293
|
+
resolve(false);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function promptChoice(rl, question, options, defaultValue = null) {
|
|
300
|
+
console.log(`\n${colored(question, colors.bold)}`);
|
|
301
|
+
const keys = Object.keys(options);
|
|
302
|
+
for (const key of keys) {
|
|
303
|
+
const marker = key === defaultValue ? ' (default)' : '';
|
|
304
|
+
console.log(` ${colored(key, colors.cyan)}: ${options[key]}${marker}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
while (true) {
|
|
308
|
+
const answer = await prompt(rl, `\nYour choice`, defaultValue || '');
|
|
309
|
+
if (keys.includes(answer.toLowerCase())) {
|
|
310
|
+
return answer.toLowerCase();
|
|
311
|
+
}
|
|
312
|
+
console.log(`Invalid choice. Please choose from: ${keys.join(', ')}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// =============================================================================
|
|
317
|
+
// Team Setup Questions
|
|
318
|
+
// =============================================================================
|
|
319
|
+
|
|
320
|
+
async function runInteractive(options) {
|
|
321
|
+
const rl = createReadlineInterface();
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
// Profile selection
|
|
325
|
+
if (!options.profile) {
|
|
326
|
+
options.profile = await promptChoice(
|
|
327
|
+
rl,
|
|
328
|
+
'What type of team/environment?',
|
|
329
|
+
PROFILE_OPTIONS,
|
|
330
|
+
'none'
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Organization name
|
|
335
|
+
if (!options.org) {
|
|
336
|
+
options.org = await prompt(
|
|
337
|
+
rl,
|
|
338
|
+
`\n${colored('Organization name', colors.bold)}`,
|
|
339
|
+
'My Organization'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Team name
|
|
344
|
+
if (!options.team) {
|
|
345
|
+
options.team = await prompt(
|
|
346
|
+
rl,
|
|
347
|
+
`${colored('Team name', colors.bold)}`,
|
|
348
|
+
'My Team'
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Industry
|
|
353
|
+
options.industry = await promptChoice(
|
|
354
|
+
rl,
|
|
355
|
+
'What industry is your organization in?',
|
|
356
|
+
INDUSTRY_OPTIONS,
|
|
357
|
+
'technology'
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
// Cadence
|
|
361
|
+
options.cadence = await promptChoice(
|
|
362
|
+
rl,
|
|
363
|
+
'What development cadence does your team use?',
|
|
364
|
+
CADENCE_OPTIONS,
|
|
365
|
+
'scrum'
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// Sprint length (if scrum/scrumban)
|
|
369
|
+
if (options.cadence !== 'kanban') {
|
|
370
|
+
const sprintLength = await prompt(
|
|
371
|
+
rl,
|
|
372
|
+
`\n${colored('Sprint length (days)', colors.bold)}`,
|
|
373
|
+
'14'
|
|
374
|
+
);
|
|
375
|
+
options.sprintLengthDays = parseInt(sprintLength, 10) || 14;
|
|
376
|
+
} else {
|
|
377
|
+
options.sprintLengthDays = null;
|
|
378
|
+
}
|
|
379
|
+
|
|
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(
|
|
385
|
+
rl,
|
|
386
|
+
`${colored('Initial project ID', colors.bold)} (lowercase, hyphenated)`,
|
|
387
|
+
DEFAULT_PROJECT_ID
|
|
388
|
+
);
|
|
389
|
+
options.project = normalizeProjectId(options.project);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// IDE Integration
|
|
393
|
+
if (!options.ide) {
|
|
394
|
+
options.ide = await promptChoice(
|
|
395
|
+
rl,
|
|
396
|
+
'Which IDE are you using?',
|
|
397
|
+
IDE_OPTIONS,
|
|
398
|
+
'vscode'
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// GitHub Copilot Instructions
|
|
403
|
+
if (options.copilot === null) {
|
|
404
|
+
options.copilot = await promptYesNo(
|
|
405
|
+
rl,
|
|
406
|
+
`\n${colored('Install GitHub Copilot instructions file?', colors.bold)}\n (Adds .github/copilot-instructions.md with TeamSpec guidance)`,
|
|
407
|
+
true
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Confirmation
|
|
412
|
+
console.log(`\n${colored('Configuration:', colors.bold)}`);
|
|
413
|
+
console.log(` Profile: ${options.profile}`);
|
|
414
|
+
console.log(` Organization: ${options.org}`);
|
|
415
|
+
console.log(` Team: ${options.team}`);
|
|
416
|
+
console.log(` Industry: ${options.industry}`);
|
|
417
|
+
console.log(` Cadence: ${options.cadence}`);
|
|
418
|
+
if (options.sprintLengthDays) {
|
|
419
|
+
console.log(` Sprint Length: ${options.sprintLengthDays} days`);
|
|
420
|
+
}
|
|
421
|
+
console.log(` Project ID: ${options.project}`);
|
|
422
|
+
console.log(` IDE: ${options.ide}`);
|
|
423
|
+
console.log(` Copilot: ${options.copilot ? 'Yes' : 'No'}`);
|
|
424
|
+
|
|
425
|
+
const proceed = await promptYesNo(
|
|
426
|
+
rl,
|
|
427
|
+
`\n${colored('Proceed with initialization?', colors.bold)}`,
|
|
428
|
+
true
|
|
429
|
+
);
|
|
430
|
+
if (!proceed) {
|
|
431
|
+
console.log('Aborted.');
|
|
432
|
+
process.exit(0);
|
|
433
|
+
}
|
|
434
|
+
} finally {
|
|
435
|
+
rl.close();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return options;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function normalizeProjectId(projectId) {
|
|
442
|
+
const normalized = projectId.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
443
|
+
return normalized || DEFAULT_PROJECT_ID;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// =============================================================================
|
|
447
|
+
// Core File Deployment
|
|
448
|
+
// =============================================================================
|
|
449
|
+
|
|
450
|
+
function copyTeamspecCore(targetDir, sourceDir) {
|
|
451
|
+
const targetTeamspec = path.join(targetDir, '.teamspec');
|
|
452
|
+
|
|
453
|
+
const dirsToCopy = [
|
|
454
|
+
'agents',
|
|
455
|
+
'definitions',
|
|
456
|
+
'profiles',
|
|
457
|
+
'templates',
|
|
458
|
+
];
|
|
459
|
+
|
|
460
|
+
const filesToCopy = ['teamspec.yml'];
|
|
461
|
+
|
|
462
|
+
console.log(`\n${colored('Copying TeamSpec core files...', colors.blue)}`);
|
|
463
|
+
|
|
464
|
+
fs.mkdirSync(targetTeamspec, { recursive: true });
|
|
465
|
+
|
|
466
|
+
for (const dirName of dirsToCopy) {
|
|
467
|
+
const src = path.join(sourceDir, dirName);
|
|
468
|
+
const dest = path.join(targetTeamspec, dirName);
|
|
469
|
+
if (fs.existsSync(src)) {
|
|
470
|
+
if (fs.existsSync(dest)) {
|
|
471
|
+
fs.rmSync(dest, { recursive: true });
|
|
472
|
+
}
|
|
473
|
+
copyDirRecursive(src, dest);
|
|
474
|
+
console.log(` ✓ Copied ${dirName}/`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
for (const fileName of filesToCopy) {
|
|
479
|
+
const src = path.join(sourceDir, fileName);
|
|
480
|
+
const dest = path.join(targetTeamspec, fileName);
|
|
481
|
+
if (fs.existsSync(src)) {
|
|
482
|
+
fs.copyFileSync(src, dest);
|
|
483
|
+
console.log(` ✓ Copied ${fileName}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const contextDir = path.join(targetTeamspec, 'context');
|
|
488
|
+
fs.mkdirSync(contextDir, { recursive: true });
|
|
489
|
+
|
|
490
|
+
const schemaSrc = path.join(sourceDir, 'context', '_schema.yml');
|
|
491
|
+
if (fs.existsSync(schemaSrc)) {
|
|
492
|
+
fs.copyFileSync(schemaSrc, path.join(contextDir, '_schema.yml'));
|
|
493
|
+
console.log(' ✓ Copied context/_schema.yml');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// =============================================================================
|
|
498
|
+
// Copilot Instructions Deployment
|
|
499
|
+
// =============================================================================
|
|
500
|
+
|
|
501
|
+
function copyCopilotInstructions(targetDir, sourceDir) {
|
|
502
|
+
const githubDir = path.join(targetDir, '.github');
|
|
503
|
+
fs.mkdirSync(githubDir, { recursive: true });
|
|
504
|
+
|
|
505
|
+
const copilotSrc = path.join(sourceDir, 'copilot-instructions.md');
|
|
506
|
+
const copilotDest = path.join(githubDir, 'copilot-instructions.md');
|
|
507
|
+
|
|
508
|
+
if (fs.existsSync(copilotSrc)) {
|
|
509
|
+
fs.copyFileSync(copilotSrc, copilotDest);
|
|
510
|
+
console.log(`\n${colored('Copying GitHub Copilot instructions...', colors.blue)}`);
|
|
511
|
+
console.log(' ✓ Copied .github/copilot-instructions.md');
|
|
512
|
+
return true;
|
|
513
|
+
} else {
|
|
514
|
+
console.log(` ⚠ Copilot instructions not found in source`);
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// =============================================================================
|
|
520
|
+
// Team Context Creation
|
|
521
|
+
// =============================================================================
|
|
522
|
+
|
|
523
|
+
function createTeamContext(targetDir, options) {
|
|
524
|
+
const contextDir = path.join(targetDir, '.teamspec', 'context');
|
|
525
|
+
fs.mkdirSync(contextDir, { recursive: true });
|
|
526
|
+
|
|
527
|
+
const teamYml = `# Team Context Configuration
|
|
528
|
+
# This file parameterizes the TeamSpec Core for your specific team.
|
|
529
|
+
# See _schema.yml for full schema documentation.
|
|
530
|
+
|
|
531
|
+
# ==============================================================================
|
|
532
|
+
# Organization
|
|
533
|
+
# ==============================================================================
|
|
534
|
+
org:
|
|
535
|
+
name: "${options.org}"
|
|
536
|
+
department: "Engineering"
|
|
537
|
+
industry: ${options.industry}
|
|
538
|
+
profile: ${options.profile} # Options: regulated, startup, platform-team, enterprise, none
|
|
539
|
+
compliance: [] # Options: SOX, PCI-DSS, HIPAA, GDPR, SOC2, ISO27001, FedRAMP, POPIA
|
|
540
|
+
|
|
541
|
+
# ==============================================================================
|
|
542
|
+
# Team
|
|
543
|
+
# ==============================================================================
|
|
544
|
+
team:
|
|
545
|
+
name: "${options.team}"
|
|
546
|
+
roles:
|
|
547
|
+
- BA # Business Analyst
|
|
548
|
+
- FA # Functional Analyst
|
|
549
|
+
- SA # Solution Architect
|
|
550
|
+
- DEV # Developer
|
|
551
|
+
- QA # Quality Assurance
|
|
552
|
+
- SM # Scrum Master
|
|
553
|
+
|
|
554
|
+
cadence:
|
|
555
|
+
type: ${options.cadence} # Options: scrum, kanban, scrumban
|
|
556
|
+
${options.sprintLengthDays ? ` sprint_length_days: ${options.sprintLengthDays}` : ' # sprint_length_days: N/A for kanban'}
|
|
557
|
+
|
|
558
|
+
# ==============================================================================
|
|
559
|
+
# Technology Stack
|
|
560
|
+
# ==============================================================================
|
|
561
|
+
tech:
|
|
562
|
+
stack: []
|
|
563
|
+
# Add your technologies, e.g.:
|
|
564
|
+
# - Python
|
|
565
|
+
# - React
|
|
566
|
+
# - PostgreSQL
|
|
567
|
+
|
|
568
|
+
architecture:
|
|
569
|
+
type: monolith # Options: monolith, microservices, modular-monolith, serverless
|
|
570
|
+
|
|
571
|
+
# ==============================================================================
|
|
572
|
+
# Governance
|
|
573
|
+
# ==============================================================================
|
|
574
|
+
governance:
|
|
575
|
+
sign_off_required: ${options.profile === 'regulated' || options.profile === 'enterprise'}
|
|
576
|
+
audit_trail: ${options.profile === 'regulated' || options.profile === 'enterprise'}
|
|
577
|
+
change_control_board: ${options.profile === 'regulated'}
|
|
578
|
+
|
|
579
|
+
# ==============================================================================
|
|
580
|
+
# Feature Canon Configuration
|
|
581
|
+
# ==============================================================================
|
|
582
|
+
feature_canon:
|
|
583
|
+
ownership:
|
|
584
|
+
purpose_and_scope: BA
|
|
585
|
+
behavior: FA
|
|
586
|
+
change_log: FA
|
|
587
|
+
require_feature_links: true
|
|
588
|
+
require_delta_format: true
|
|
589
|
+
`;
|
|
590
|
+
|
|
591
|
+
fs.writeFileSync(path.join(contextDir, 'team.yml'), teamYml, 'utf-8');
|
|
592
|
+
console.log(' ✓ Created context/team.yml');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// =============================================================================
|
|
596
|
+
// Project Structure Creation
|
|
597
|
+
// =============================================================================
|
|
598
|
+
|
|
599
|
+
function createProjectStructure(targetDir, projectId) {
|
|
600
|
+
const projectDir = path.join(targetDir, 'projects', projectId);
|
|
601
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
602
|
+
|
|
603
|
+
console.log(`\n${colored(`Creating project structure: projects/${projectId}/...`, colors.blue)}`);
|
|
604
|
+
|
|
605
|
+
// project.yml
|
|
606
|
+
const projectYml = `# Project Configuration: ${projectId}
|
|
607
|
+
project:
|
|
608
|
+
id: "${projectId}"
|
|
609
|
+
name: "${projectId.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}"
|
|
610
|
+
description: |
|
|
611
|
+
# TODO: Add project description
|
|
612
|
+
status: active # active, paused, completed, archived
|
|
613
|
+
`;
|
|
614
|
+
fs.writeFileSync(path.join(projectDir, 'project.yml'), projectYml, 'utf-8');
|
|
615
|
+
console.log(` ✓ Created projects/${projectId}/project.yml`);
|
|
616
|
+
|
|
617
|
+
// features/
|
|
618
|
+
const featuresDir = path.join(projectDir, 'features');
|
|
619
|
+
fs.mkdirSync(featuresDir, { recursive: true });
|
|
620
|
+
|
|
621
|
+
fs.writeFileSync(path.join(featuresDir, 'features-index.md'), `# Features Index (Feature Canon)
|
|
622
|
+
|
|
623
|
+
This is the master index of all features in this project.
|
|
624
|
+
The Feature Canon is the **source of truth** for system behavior.
|
|
625
|
+
|
|
626
|
+
## Feature Registry
|
|
627
|
+
|
|
628
|
+
| ID | Name | Status | Owner |
|
|
629
|
+
|----|------|--------|-------|
|
|
630
|
+
| _(none yet)_ | | | |
|
|
631
|
+
|
|
632
|
+
## Next Available ID: **F-001**
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
> **To add features:** Run \`ts:ba feature\` — features are never created implicitly.
|
|
637
|
+
`, 'utf-8');
|
|
638
|
+
|
|
639
|
+
fs.writeFileSync(path.join(featuresDir, 'story-ledger.md'), `# Story Ledger
|
|
640
|
+
|
|
641
|
+
Tracks completed stories and their impact on the Feature Canon.
|
|
642
|
+
|
|
643
|
+
| Story ID | Title | Sprint | Features Modified | Merged Date |
|
|
644
|
+
|----------|-------|--------|-------------------|-------------|
|
|
645
|
+
| _(none yet)_ | | | | |
|
|
646
|
+
`, 'utf-8');
|
|
647
|
+
console.log(` ✓ Created projects/${projectId}/features/`);
|
|
648
|
+
|
|
649
|
+
// stories/ with workflow folders
|
|
650
|
+
const storiesDir = path.join(projectDir, 'stories');
|
|
651
|
+
for (const folder of ['backlog', 'ready-to-refine', 'ready-for-development']) {
|
|
652
|
+
fs.mkdirSync(path.join(storiesDir, folder), { recursive: true });
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
fs.writeFileSync(path.join(storiesDir, 'README.md'), `# Stories
|
|
656
|
+
|
|
657
|
+
## Workflow Folders
|
|
658
|
+
|
|
659
|
+
| Folder | Status | Owner |
|
|
660
|
+
|--------|--------|-------|
|
|
661
|
+
| \`backlog/\` | New stories | FA |
|
|
662
|
+
| \`ready-to-refine/\` | Ready for dev refinement | FA |
|
|
663
|
+
| \`ready-for-development/\` | Refined, ready for sprint | DEV |
|
|
664
|
+
|
|
665
|
+
## Rules
|
|
666
|
+
|
|
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
|
|
670
|
+
`, 'utf-8');
|
|
671
|
+
console.log(` ✓ Created projects/${projectId}/stories/`);
|
|
672
|
+
|
|
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/
|
|
684
|
+
fs.mkdirSync(path.join(projectDir, 'decisions'), { recursive: true });
|
|
685
|
+
fs.writeFileSync(path.join(projectDir, 'decisions', 'README.md'), `# Business Decisions
|
|
686
|
+
|
|
687
|
+
Naming: \`DECISION-NNN-<slug>.md\`
|
|
688
|
+
|
|
689
|
+
Use template: \`/.teamspec/templates/decision-log-template.md\`
|
|
690
|
+
`, 'utf-8');
|
|
691
|
+
console.log(` ✓ Created projects/${projectId}/decisions/`);
|
|
692
|
+
|
|
693
|
+
// dev-plans/
|
|
694
|
+
fs.mkdirSync(path.join(projectDir, 'dev-plans'), { recursive: true });
|
|
695
|
+
fs.writeFileSync(path.join(projectDir, 'dev-plans', 'README.md'), `# Development Plans
|
|
696
|
+
|
|
697
|
+
Naming: \`story-NNN-tasks.md\`
|
|
698
|
+
|
|
699
|
+
Rules:
|
|
700
|
+
- NEVER start coding without a dev plan
|
|
701
|
+
- No TBD/TODO items before implementation
|
|
702
|
+
- Mark tasks ✅ as completed
|
|
703
|
+
`, 'utf-8');
|
|
704
|
+
console.log(` ✓ Created projects/${projectId}/dev-plans/`);
|
|
705
|
+
|
|
706
|
+
// qa/
|
|
707
|
+
fs.mkdirSync(path.join(projectDir, 'qa', 'test-cases'), { recursive: true });
|
|
708
|
+
fs.writeFileSync(path.join(projectDir, 'qa', 'README.md'), `# Quality Assurance
|
|
709
|
+
|
|
710
|
+
Tests validate **Feature Canon** behavior, not individual stories.
|
|
711
|
+
|
|
712
|
+
Use template: \`/.teamspec/templates/testcases-template.md\`
|
|
713
|
+
`, 'utf-8');
|
|
714
|
+
console.log(` ✓ Created projects/${projectId}/qa/`);
|
|
715
|
+
|
|
716
|
+
// sprints/
|
|
717
|
+
fs.mkdirSync(path.join(projectDir, 'sprints'), { recursive: true });
|
|
718
|
+
fs.writeFileSync(path.join(projectDir, 'sprints', 'sprints-index.md'), `# Sprints Index
|
|
719
|
+
|
|
720
|
+
## Current Sprint: _None active_
|
|
721
|
+
|
|
722
|
+
## Sprint History
|
|
723
|
+
|
|
724
|
+
| Sprint | Goal | Status | Stories | Canon Synced |
|
|
725
|
+
|--------|------|--------|---------|--------------|
|
|
726
|
+
| _(none yet)_ | | | | |
|
|
727
|
+
`, 'utf-8');
|
|
728
|
+
console.log(` ✓ Created projects/${projectId}/sprints/`);
|
|
729
|
+
|
|
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/`);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// =============================================================================
|
|
746
|
+
// Update Command
|
|
747
|
+
// =============================================================================
|
|
748
|
+
|
|
749
|
+
function updateTeamspecCore(targetDir, sourceDir) {
|
|
750
|
+
const targetTeamspec = path.join(targetDir, '.teamspec');
|
|
751
|
+
|
|
752
|
+
const dirsToUpdate = ['agents', 'definitions', 'profiles', 'templates'];
|
|
753
|
+
const filesToUpdate = ['teamspec.yml'];
|
|
754
|
+
const contextFilesToUpdate = ['_schema.yml'];
|
|
755
|
+
|
|
756
|
+
console.log(`\n${colored('Updating TeamSpec core files...', colors.blue)}`);
|
|
757
|
+
|
|
758
|
+
const updated = [];
|
|
759
|
+
const skipped = [];
|
|
760
|
+
|
|
761
|
+
for (const dirName of dirsToUpdate) {
|
|
762
|
+
const src = path.join(sourceDir, dirName);
|
|
763
|
+
const dest = path.join(targetTeamspec, dirName);
|
|
764
|
+
if (fs.existsSync(src)) {
|
|
765
|
+
if (fs.existsSync(dest)) {
|
|
766
|
+
fs.rmSync(dest, { recursive: true });
|
|
767
|
+
}
|
|
768
|
+
copyDirRecursive(src, dest);
|
|
769
|
+
updated.push(`${dirName}/`);
|
|
770
|
+
console.log(` ✓ Updated ${dirName}/`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
for (const fileName of filesToUpdate) {
|
|
775
|
+
const src = path.join(sourceDir, fileName);
|
|
776
|
+
const dest = path.join(targetTeamspec, fileName);
|
|
777
|
+
if (fs.existsSync(src)) {
|
|
778
|
+
fs.copyFileSync(src, dest);
|
|
779
|
+
updated.push(fileName);
|
|
780
|
+
console.log(` ✓ Updated ${fileName}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const contextDir = path.join(targetTeamspec, 'context');
|
|
785
|
+
if (fs.existsSync(contextDir)) {
|
|
786
|
+
for (const fileName of contextFilesToUpdate) {
|
|
787
|
+
const src = path.join(sourceDir, 'context', fileName);
|
|
788
|
+
const dest = path.join(contextDir, fileName);
|
|
789
|
+
if (fs.existsSync(src)) {
|
|
790
|
+
fs.copyFileSync(src, dest);
|
|
791
|
+
updated.push(`context/${fileName}`);
|
|
792
|
+
console.log(` ✓ Updated context/${fileName}`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (fs.existsSync(path.join(contextDir, 'team.yml'))) {
|
|
796
|
+
skipped.push('context/team.yml');
|
|
797
|
+
console.log(` ⏭ Preserved context/team.yml`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return { updated, skipped };
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// =============================================================================
|
|
805
|
+
// IDE Integration
|
|
806
|
+
// =============================================================================
|
|
807
|
+
|
|
808
|
+
async function setupIDEIntegration(targetDir, options) {
|
|
809
|
+
if (!options.ide || options.ide === 'none') {
|
|
810
|
+
return { skipped: true };
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const { installExtension, copyExtensionToWorkspace, isVSCodeAvailable } = require('./extension-installer');
|
|
814
|
+
const { generateAllPrompts } = require('./prompt-generator');
|
|
815
|
+
|
|
816
|
+
console.log(`\n${colored('Setting up IDE integration...', colors.blue)}`);
|
|
817
|
+
|
|
818
|
+
// Generate GitHub Copilot prompt files for VS Code and Cursor
|
|
819
|
+
if (options.ide === 'vscode' || options.ide === 'cursor') {
|
|
820
|
+
try {
|
|
821
|
+
console.log(' → Generating GitHub Copilot prompt files...');
|
|
822
|
+
const generatedFiles = generateAllPrompts(targetDir);
|
|
823
|
+
console.log(` ✓ Generated ${generatedFiles.length} prompt files in .github/prompts/`);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
console.log(` ⚠ Failed to generate prompts: ${error.message}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Create .vscode folder with settings
|
|
830
|
+
const vscodeDir = path.join(targetDir, '.vscode');
|
|
831
|
+
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
832
|
+
|
|
833
|
+
// Create or update settings.json
|
|
834
|
+
const settingsPath = path.join(vscodeDir, 'settings.json');
|
|
835
|
+
let settings = {};
|
|
836
|
+
|
|
837
|
+
if (fs.existsSync(settingsPath)) {
|
|
838
|
+
try {
|
|
839
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
840
|
+
} catch {
|
|
841
|
+
// Ignore parse errors
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Add TeamSpec-specific settings
|
|
846
|
+
settings['teamspec.enforceGates'] = true;
|
|
847
|
+
settings['files.associations'] = settings['files.associations'] || {};
|
|
848
|
+
settings['files.associations']['*.teamspec'] = 'yaml';
|
|
849
|
+
settings['editor.formatOnSave'] = settings['editor.formatOnSave'] ?? true;
|
|
850
|
+
|
|
851
|
+
// Add markdown settings for better feature/story editing
|
|
852
|
+
settings['[markdown]'] = settings['[markdown]'] || {};
|
|
853
|
+
settings['[markdown]']['editor.wordWrap'] = 'on';
|
|
854
|
+
|
|
855
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
856
|
+
console.log(' ✓ Created .vscode/settings.json');
|
|
857
|
+
|
|
858
|
+
// Create extensions.json to recommend the extension
|
|
859
|
+
const extensionsPath = path.join(vscodeDir, 'extensions.json');
|
|
860
|
+
const extensionsJson = {
|
|
861
|
+
recommendations: ['teamspec.teamspec']
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
if (fs.existsSync(extensionsPath)) {
|
|
865
|
+
try {
|
|
866
|
+
const existing = JSON.parse(fs.readFileSync(extensionsPath, 'utf-8'));
|
|
867
|
+
if (existing.recommendations) {
|
|
868
|
+
const recs = new Set(existing.recommendations);
|
|
869
|
+
recs.add('teamspec.teamspec');
|
|
870
|
+
extensionsJson.recommendations = Array.from(recs);
|
|
871
|
+
}
|
|
872
|
+
} catch {
|
|
873
|
+
// Ignore parse errors
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
fs.writeFileSync(extensionsPath, JSON.stringify(extensionsJson, null, 2));
|
|
878
|
+
console.log(' ✓ Created .vscode/extensions.json');
|
|
879
|
+
|
|
880
|
+
return { success: true };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// =============================================================================
|
|
884
|
+
// Summary Output
|
|
885
|
+
// =============================================================================
|
|
886
|
+
|
|
887
|
+
function printNextSteps(targetDir, profile, projectId, ide, ideResult, copilot) {
|
|
888
|
+
console.log(`\n${colored('='.repeat(70), colors.green)}`);
|
|
889
|
+
console.log(colored(' ✅ TeamSpec 2.0 initialized successfully!', colors.green + colors.bold));
|
|
890
|
+
console.log(colored('='.repeat(70), colors.green));
|
|
891
|
+
|
|
892
|
+
const copilotSection = copilot !== false ? `
|
|
893
|
+
.github/
|
|
894
|
+
└── copilot-instructions.md - GitHub Copilot guidance` : '';
|
|
895
|
+
|
|
896
|
+
console.log(`
|
|
897
|
+
${colored('📁 Created Structure:', colors.bold)}
|
|
898
|
+
.teamspec/ - Core framework
|
|
899
|
+
├── templates/ - Document templates
|
|
900
|
+
├── definitions/ - DoR/DoD checklists
|
|
901
|
+
├── profiles/ - Profile overlays
|
|
902
|
+
└── context/team.yml - Team configuration${copilotSection}
|
|
903
|
+
projects/${projectId}/
|
|
904
|
+
├── features/ - Feature Canon (source of truth)
|
|
905
|
+
├── stories/ - User stories (workflow folders)
|
|
906
|
+
├── adr/ - Architecture decisions
|
|
907
|
+
├── decisions/ - Business decisions
|
|
908
|
+
├── dev-plans/ - Development task breakdowns
|
|
909
|
+
├── qa/ - Test cases
|
|
910
|
+
├── sprints/ - Sprint management
|
|
911
|
+
└── epics/ - Epic specifications
|
|
912
|
+
`);
|
|
913
|
+
|
|
914
|
+
// IDE Integration Info
|
|
915
|
+
if (ide === 'vscode') {
|
|
916
|
+
console.log(`${colored('🖥️ VS Code Integration:', colors.bold)}`);
|
|
917
|
+
console.log(` ✓ Prompt templates generated in .github/prompts/`);
|
|
918
|
+
console.log(` ℹ Use with GitHub Copilot, Cursor, or your preferred AI assistant`);
|
|
919
|
+
console.log('');
|
|
920
|
+
} else if (ide === 'cursor') {
|
|
921
|
+
console.log(`${colored('🖥️ Cursor Integration:', colors.bold)}`);
|
|
922
|
+
console.log(` ✓ Prompt templates generated in .github/prompts/`);
|
|
923
|
+
console.log(` ℹ VS Code settings configured for Cursor compatibility`);
|
|
924
|
+
console.log('');
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
console.log(`${colored('🚀 Next Steps:', colors.bold + colors.yellow)}
|
|
928
|
+
|
|
929
|
+
${colored('1. Configure Your Team', colors.cyan)}
|
|
930
|
+
Edit ${colored('.teamspec/context/team.yml', colors.bold)} to set your tech stack.
|
|
931
|
+
|
|
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
|
|
935
|
+
|
|
936
|
+
${colored('3. Start Using TeamSpec Commands', colors.cyan)}
|
|
937
|
+
${colored('ts:ba create', colors.bold)} - Create business analysis
|
|
938
|
+
${colored('ts:fa story', colors.bold)} - Create user story
|
|
939
|
+
${colored('ts:dev plan', colors.bold)} - Create development plan
|
|
940
|
+
${colored('ts:qa test', colors.bold)} - Design test cases
|
|
941
|
+
|
|
942
|
+
Or use templates in .github/prompts/ with GitHub Copilot, Cursor, or Claude
|
|
943
|
+
|
|
944
|
+
${colored('📚 Documentation:', colors.bold)}
|
|
945
|
+
- Templates: .teamspec/templates/
|
|
946
|
+
- Definitions: .teamspec/definitions/
|
|
947
|
+
`);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function printUpdateSummary(coreResult) {
|
|
951
|
+
const pkg = require('../package.json');
|
|
952
|
+
|
|
953
|
+
console.log(`\n${colored('='.repeat(70), colors.green)} `);
|
|
954
|
+
console.log(colored(' ✅ TeamSpec updated successfully!', colors.green + colors.bold));
|
|
955
|
+
console.log(colored('='.repeat(70), colors.green));
|
|
956
|
+
|
|
957
|
+
console.log(`\n${colored('Version:', colors.bold)} ${pkg.version} `);
|
|
958
|
+
|
|
959
|
+
console.log(`\n${colored('Updated:', colors.bold)} `);
|
|
960
|
+
for (const item of coreResult.updated) {
|
|
961
|
+
console.log(` ✓ ${item} `);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (coreResult.skipped.length > 0) {
|
|
965
|
+
console.log(`\n${colored('Preserved:', colors.bold)} `);
|
|
966
|
+
for (const item of coreResult.skipped) {
|
|
967
|
+
console.log(` ⏭ ${item} `);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// =============================================================================
|
|
973
|
+
// Main Entry Point
|
|
974
|
+
// =============================================================================
|
|
975
|
+
|
|
976
|
+
async function run(args) {
|
|
977
|
+
const options = parseArgs(args);
|
|
978
|
+
|
|
979
|
+
if (options.help) {
|
|
980
|
+
printHelp();
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (options.version) {
|
|
985
|
+
printVersion();
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Handle lint command
|
|
990
|
+
if (options.command === 'lint') {
|
|
991
|
+
const { Linter, SEVERITY } = require('./linter');
|
|
992
|
+
const targetDir = path.resolve(options.target);
|
|
993
|
+
|
|
994
|
+
console.log(`\n${colored('TeamSpec Linter', colors.bold + colors.cyan)} `);
|
|
995
|
+
console.log(`${colored('Scanning:', colors.bold)} ${targetDir} `);
|
|
996
|
+
|
|
997
|
+
if (options.project) {
|
|
998
|
+
console.log(`${colored('Project:', colors.bold)} ${options.project} `);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const linter = new Linter(targetDir);
|
|
1002
|
+
const results = await linter.run({ project: options.project });
|
|
1003
|
+
|
|
1004
|
+
console.log(linter.formatResults(results));
|
|
1005
|
+
|
|
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) {
|
|
1009
|
+
process.exit(1);
|
|
1010
|
+
}
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Handle generate-prompts command
|
|
1015
|
+
if (options.command === 'generate-prompts') {
|
|
1016
|
+
const { generateAllPrompts } = require('./prompt-generator');
|
|
1017
|
+
const targetDir = path.resolve(options.target);
|
|
1018
|
+
|
|
1019
|
+
console.log(`\n${colored('TeamSpec Copilot Prompt Generator', colors.bold + colors.cyan)} `);
|
|
1020
|
+
console.log(`${colored('Target:', colors.bold)} ${targetDir} \n`);
|
|
1021
|
+
|
|
1022
|
+
try {
|
|
1023
|
+
generateAllPrompts(targetDir);
|
|
1024
|
+
console.log(`\n${colored('✨ Done!', colors.green + colors.bold)} `);
|
|
1025
|
+
console.log(`\nYou can now use ${colored('ts:role-command', colors.cyan)} in GitHub Copilot Chat.`);
|
|
1026
|
+
console.log(`Example: ${colored('ts:ba-project', colors.cyan)} or ${colored('ts:fa-story', colors.cyan)} `);
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
console.error(colored(`❌ Error: ${error.message} `, colors.red));
|
|
1029
|
+
process.exit(1);
|
|
1030
|
+
}
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Handle update command
|
|
1035
|
+
if (options.command === 'update') {
|
|
1036
|
+
const targetDir = path.resolve(options.target);
|
|
1037
|
+
const teamspecDir = path.join(targetDir, '.teamspec');
|
|
1038
|
+
|
|
1039
|
+
if (!fs.existsSync(teamspecDir)) {
|
|
1040
|
+
console.error(colored(`❌ TeamSpec not found in: ${targetDir} `, colors.red));
|
|
1041
|
+
console.error('Run `teamspec init` first.');
|
|
1042
|
+
process.exit(1);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const pkg = require('../package.json');
|
|
1046
|
+
console.log(`\n${colored('TeamSpec Update', colors.bold + colors.cyan)} v${pkg.version} `);
|
|
1047
|
+
|
|
1048
|
+
if (!options.force && !options.nonInteractive) {
|
|
1049
|
+
const rl = createReadlineInterface();
|
|
1050
|
+
const proceed = await promptYesNo(
|
|
1051
|
+
rl,
|
|
1052
|
+
`${colored('Update core files (preserves team.yml)?', colors.bold)} `,
|
|
1053
|
+
true
|
|
1054
|
+
);
|
|
1055
|
+
rl.close();
|
|
1056
|
+
if (!proceed) {
|
|
1057
|
+
console.log('Cancelled.');
|
|
1058
|
+
process.exit(0);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const sourceDir = getTeamspecCoreDir();
|
|
1063
|
+
if (!fs.existsSync(sourceDir)) {
|
|
1064
|
+
console.error(colored(`Error: Core files not found at: ${sourceDir} `, colors.red));
|
|
1065
|
+
process.exit(1);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const coreResult = updateTeamspecCore(targetDir, sourceDir);
|
|
1069
|
+
|
|
1070
|
+
// Update copilot instructions if they exist
|
|
1071
|
+
const copilotPath = path.join(targetDir, '.github', 'copilot-instructions.md');
|
|
1072
|
+
if (fs.existsSync(copilotPath)) {
|
|
1073
|
+
console.log(`\n${colored('Updating GitHub Copilot instructions...', colors.blue)}`);
|
|
1074
|
+
copyCopilotInstructions(targetDir, sourceDir);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Regenerate prompt files if prompts folder exists
|
|
1078
|
+
const promptsPath = path.join(targetDir, '.github', 'prompts');
|
|
1079
|
+
if (fs.existsSync(promptsPath)) {
|
|
1080
|
+
console.log(`\n${colored('Regenerating prompt files...', colors.blue)}`);
|
|
1081
|
+
// Delete old prompts folder to remove obsolete files
|
|
1082
|
+
fs.rmSync(promptsPath, { recursive: true });
|
|
1083
|
+
const { generateAllPrompts } = require('./prompt-generator');
|
|
1084
|
+
try {
|
|
1085
|
+
const generatedFiles = generateAllPrompts(targetDir);
|
|
1086
|
+
console.log(` ✓ Regenerated ${generatedFiles.length} prompt files`);
|
|
1087
|
+
coreResult.updated.push('.github/prompts/');
|
|
1088
|
+
} catch (error) {
|
|
1089
|
+
console.log(` ⚠ Failed to regenerate prompts: ${error.message}`);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
printUpdateSummary(coreResult);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Default: init command
|
|
1098
|
+
printBanner();
|
|
1099
|
+
|
|
1100
|
+
const targetDir = path.resolve(options.target);
|
|
1101
|
+
|
|
1102
|
+
if (!fs.existsSync(targetDir)) {
|
|
1103
|
+
console.error(colored(`Error: Directory does not exist: ${targetDir} `, colors.red));
|
|
1104
|
+
process.exit(1);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const teamspecDir = path.join(targetDir, '.teamspec');
|
|
1108
|
+
if (fs.existsSync(teamspecDir)) {
|
|
1109
|
+
if (options.nonInteractive) {
|
|
1110
|
+
console.log(colored('TeamSpec exists. Overwriting...', colors.yellow));
|
|
1111
|
+
} else {
|
|
1112
|
+
const rl = createReadlineInterface();
|
|
1113
|
+
const overwrite = await promptYesNo(
|
|
1114
|
+
rl,
|
|
1115
|
+
colored('⚠️ TeamSpec exists. Overwrite?', colors.yellow),
|
|
1116
|
+
false
|
|
1117
|
+
);
|
|
1118
|
+
rl.close();
|
|
1119
|
+
if (!overwrite) {
|
|
1120
|
+
console.log('Aborted.');
|
|
1121
|
+
process.exit(0);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
console.log(`\n${colored('Target:', colors.bold)} ${targetDir} `);
|
|
1127
|
+
|
|
1128
|
+
// Set defaults for non-interactive mode
|
|
1129
|
+
if (options.nonInteractive) {
|
|
1130
|
+
options.profile = options.profile || 'none';
|
|
1131
|
+
options.org = options.org || 'My Organization';
|
|
1132
|
+
options.team = options.team || 'My Team';
|
|
1133
|
+
options.project = options.project || DEFAULT_PROJECT_ID;
|
|
1134
|
+
options.ide = options.ide || 'none';
|
|
1135
|
+
options.copilot = options.copilot !== false; // Default to true unless explicitly set to false
|
|
1136
|
+
options.industry = 'technology';
|
|
1137
|
+
options.cadence = 'scrum';
|
|
1138
|
+
options.sprintLengthDays = 14;
|
|
1139
|
+
} else {
|
|
1140
|
+
await runInteractive(options);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Validate profile
|
|
1144
|
+
if (!PROFILE_OPTIONS[options.profile]) {
|
|
1145
|
+
console.error(colored(`Error: Unknown profile: ${options.profile} `, colors.red));
|
|
1146
|
+
process.exit(1);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
options.project = normalizeProjectId(options.project);
|
|
1150
|
+
|
|
1151
|
+
console.log(`\n${colored('Initializing TeamSpec...', colors.bold)} `);
|
|
1152
|
+
|
|
1153
|
+
const sourceDir = getTeamspecCoreDir();
|
|
1154
|
+
if (!fs.existsSync(sourceDir)) {
|
|
1155
|
+
console.error(colored(`Error: Core files not found at: ${sourceDir} `, colors.red));
|
|
1156
|
+
process.exit(1);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
copyTeamspecCore(targetDir, sourceDir);
|
|
1160
|
+
createTeamContext(targetDir, options);
|
|
1161
|
+
createProjectStructure(targetDir, options.project);
|
|
1162
|
+
|
|
1163
|
+
// Copy GitHub Copilot instructions if requested
|
|
1164
|
+
if (options.copilot !== false) {
|
|
1165
|
+
copyCopilotInstructions(targetDir, sourceDir);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Setup IDE integration
|
|
1169
|
+
const ideResult = await setupIDEIntegration(targetDir, options);
|
|
1170
|
+
|
|
1171
|
+
printNextSteps(targetDir, options.profile, options.project, options.ide, ideResult, options.copilot);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
module.exports = { run };
|