teamspec 3.2.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -12
- package/bin/teamspec-init.js +2 -2
- package/lib/cli.js +653 -99
- package/lib/extension-installer.js +19 -219
- package/lib/linter.js +823 -1076
- package/lib/prompt-generator.js +312 -330
- package/lib/structure-loader.js +400 -0
- package/package.json +14 -6
- package/teamspec-core/FOLDER_STRUCTURE.yml +131 -0
- package/teamspec-core/agents/AGENT_BA.md +188 -293
- package/teamspec-core/agents/AGENT_BOOTSTRAP.md +197 -102
- package/teamspec-core/agents/AGENT_DES.md +9 -8
- package/teamspec-core/agents/AGENT_DEV.md +68 -67
- package/teamspec-core/agents/AGENT_FA.md +437 -245
- package/teamspec-core/agents/AGENT_FIX.md +344 -74
- package/teamspec-core/agents/AGENT_PO.md +487 -0
- package/teamspec-core/agents/AGENT_QA.md +124 -98
- package/teamspec-core/agents/AGENT_SA.md +143 -84
- package/teamspec-core/agents/AGENT_SM.md +106 -83
- package/teamspec-core/agents/README.md +143 -93
- package/teamspec-core/copilot-instructions.md +281 -205
- package/teamspec-core/definitions/definition-of-done.md +47 -84
- package/teamspec-core/definitions/definition-of-ready.md +35 -60
- package/teamspec-core/registry.yml +898 -0
- package/teamspec-core/teamspec.yml +44 -28
- package/teamspec-core/templates/README.md +5 -5
- package/teamspec-core/templates/adr-template.md +19 -17
- package/teamspec-core/templates/bai-template.md +125 -0
- package/teamspec-core/templates/bug-report-template.md +21 -15
- package/teamspec-core/templates/business-analysis-template.md +16 -13
- package/teamspec-core/templates/decision-log-template.md +26 -22
- package/teamspec-core/templates/dev-plan-template.md +168 -0
- package/teamspec-core/templates/epic-template.md +204 -0
- package/teamspec-core/templates/feature-increment-template.md +84 -0
- package/teamspec-core/templates/feature-template.md +45 -32
- package/teamspec-core/templates/increments-index-template.md +53 -0
- package/teamspec-core/templates/product-template.yml +44 -0
- package/teamspec-core/templates/products-index-template.md +46 -0
- package/teamspec-core/templates/project-template.yml +70 -0
- package/teamspec-core/templates/ri-template.md +225 -0
- package/teamspec-core/templates/rt-template.md +104 -0
- package/teamspec-core/templates/sd-template.md +132 -0
- package/teamspec-core/templates/sdi-template.md +119 -0
- package/teamspec-core/templates/sprint-template.md +17 -15
- package/teamspec-core/templates/story-template-v4.md +202 -0
- package/teamspec-core/templates/story-template.md +48 -90
- package/teamspec-core/templates/ta-template.md +198 -0
- package/teamspec-core/templates/tai-template.md +131 -0
- package/teamspec-core/templates/tc-template.md +145 -0
- package/teamspec-core/templates/testcases-template.md +20 -17
- package/extensions/teamspec-0.1.0.vsix +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeamSpec Structure Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads folder structure and registry definitions from YAML files.
|
|
5
|
+
* This module is the single source of truth for CLI structure generation.
|
|
6
|
+
*
|
|
7
|
+
* Version: 4.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const YAML = require('yaml');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse YAML content using the yaml library
|
|
16
|
+
*/
|
|
17
|
+
function parseYaml(content) {
|
|
18
|
+
try {
|
|
19
|
+
return YAML.parse(content);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.warn(`Warning: YAML parse error: ${err.message}`);
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Load the registry.yml from spec/4.0/
|
|
28
|
+
* Falls back to bundled version in cli/teamspec-core/ if not found
|
|
29
|
+
*/
|
|
30
|
+
function loadRegistry(workspaceRoot) {
|
|
31
|
+
const specPath = path.join(workspaceRoot, 'spec', '4.0', 'registry.yml');
|
|
32
|
+
const bundledPath = path.join(__dirname, '..', 'teamspec-core', 'registry.yml');
|
|
33
|
+
const localBundled = path.join(__dirname, '..', '..', 'spec', '4.0', 'registry.yml');
|
|
34
|
+
|
|
35
|
+
let registryPath;
|
|
36
|
+
if (fs.existsSync(specPath)) {
|
|
37
|
+
registryPath = specPath;
|
|
38
|
+
} else if (fs.existsSync(bundledPath)) {
|
|
39
|
+
registryPath = bundledPath;
|
|
40
|
+
} else if (fs.existsSync(localBundled)) {
|
|
41
|
+
registryPath = localBundled;
|
|
42
|
+
} else {
|
|
43
|
+
return getDefaultRegistry();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const content = fs.readFileSync(registryPath, 'utf-8');
|
|
48
|
+
return parseYaml(content);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.warn(`Warning: Could not parse registry.yml: ${err.message}`);
|
|
51
|
+
return getDefaultRegistry();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Load FOLDER_STRUCTURE.yml from spec/4.0/
|
|
57
|
+
*/
|
|
58
|
+
function loadFolderStructure(workspaceRoot) {
|
|
59
|
+
const specPath = path.join(workspaceRoot, 'spec', '4.0', 'FOLDER_STRUCTURE.yml');
|
|
60
|
+
const bundledPath = path.join(__dirname, '..', 'teamspec-core', 'FOLDER_STRUCTURE.yml');
|
|
61
|
+
const localBundled = path.join(__dirname, '..', '..', 'spec', '4.0', 'FOLDER_STRUCTURE.yml');
|
|
62
|
+
|
|
63
|
+
let structurePath;
|
|
64
|
+
if (fs.existsSync(specPath)) {
|
|
65
|
+
structurePath = specPath;
|
|
66
|
+
} else if (fs.existsSync(bundledPath)) {
|
|
67
|
+
structurePath = bundledPath;
|
|
68
|
+
} else if (fs.existsSync(localBundled)) {
|
|
69
|
+
structurePath = localBundled;
|
|
70
|
+
} else {
|
|
71
|
+
return getDefaultFolderStructure();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const content = fs.readFileSync(structurePath, 'utf-8');
|
|
76
|
+
return parseYaml(content);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.warn(`Warning: Could not parse FOLDER_STRUCTURE.yml: ${err.message}`);
|
|
79
|
+
return getDefaultFolderStructure();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get default folder structure when YAML not available
|
|
85
|
+
*/
|
|
86
|
+
function getDefaultFolderStructure() {
|
|
87
|
+
return {
|
|
88
|
+
structure: {
|
|
89
|
+
products: {
|
|
90
|
+
path: 'products/',
|
|
91
|
+
product_folder: {
|
|
92
|
+
path: 'products/{product-id}/',
|
|
93
|
+
subfolders: [
|
|
94
|
+
{ path: 'business-analysis/' },
|
|
95
|
+
{ path: 'features/' },
|
|
96
|
+
{ path: 'solution-designs/' },
|
|
97
|
+
{ path: 'technical-architecture/' },
|
|
98
|
+
{ path: 'qa/regression-tests/' },
|
|
99
|
+
{ path: 'decisions/' }
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
projects: {
|
|
104
|
+
path: 'projects/',
|
|
105
|
+
project_folder: {
|
|
106
|
+
path: 'projects/{project-id}/',
|
|
107
|
+
subfolders: [
|
|
108
|
+
{ path: 'business-analysis-increments/' },
|
|
109
|
+
{ path: 'feature-increments/' },
|
|
110
|
+
{ path: 'solution-design-increments/' },
|
|
111
|
+
{ path: 'technical-architecture-increments/' },
|
|
112
|
+
{ path: 'epics/' },
|
|
113
|
+
{ path: 'stories/', workflow_subfolders: ['backlog/', 'ready-to-refine/', 'ready-to-develop/', 'deferred/', 'out-of-scope/'] },
|
|
114
|
+
{ path: 'decisions/' },
|
|
115
|
+
{ path: 'dev-plans/' },
|
|
116
|
+
{ path: 'qa/', subfolders: ['test-cases/', 'bug-reports/', 'uat/', 'regression-impact/'] },
|
|
117
|
+
{ path: 'sprints/' }
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
story_workflow_folders: [
|
|
123
|
+
{ folder: 'stories/backlog/', state: 'backlog' },
|
|
124
|
+
{ folder: 'stories/ready-to-refine/', state: 'ready-to-refine' },
|
|
125
|
+
{ folder: 'stories/ready-to-develop/', state: 'ready-to-develop' },
|
|
126
|
+
{ folder: 'sprints/sprint-N/', state: 'in-sprint' },
|
|
127
|
+
{ folder: 'stories/deferred/', state: 'deferred', terminal: true },
|
|
128
|
+
{ folder: 'stories/out-of-scope/', state: 'out-of-scope', terminal: true }
|
|
129
|
+
],
|
|
130
|
+
canon_sync_paths: [
|
|
131
|
+
{ from: 'projects/{project-id}/feature-increments/', to: 'products/{product-id}/features/' },
|
|
132
|
+
{ from: 'projects/{project-id}/business-analysis-increments/', to: 'products/{product-id}/business-analysis/' },
|
|
133
|
+
{ from: 'projects/{project-id}/solution-design-increments/', to: 'products/{product-id}/solution-designs/' },
|
|
134
|
+
{ from: 'projects/{project-id}/technical-architecture-increments/', to: 'products/{product-id}/technical-architecture/' }
|
|
135
|
+
]
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get default registry when YAML not available
|
|
141
|
+
*/
|
|
142
|
+
function getDefaultRegistry() {
|
|
143
|
+
return {
|
|
144
|
+
version: '4.0',
|
|
145
|
+
model: 'Product-Canon',
|
|
146
|
+
roles: {
|
|
147
|
+
PO: { name: 'Product Owner', commands: ['ts:po product', 'ts:po project', 'ts:po sync', 'ts:po status'] },
|
|
148
|
+
BA: { name: 'Business Analyst', commands: ['ts:ba analysis', 'ts:ba ba-increment', 'ts:ba review'] },
|
|
149
|
+
FA: { name: 'Functional Analyst', commands: ['ts:fa feature', 'ts:fa feature-increment', 'ts:fa epic', 'ts:fa story', 'ts:fa sync-proposal', 'ts:fa slice'] },
|
|
150
|
+
SA: { name: 'Solution Architect', commands: ['ts:sa ta', 'ts:sa ta-increment', 'ts:sa sd', 'ts:sa sd-increment', 'ts:sa review'] },
|
|
151
|
+
DEV: { name: 'Developer', commands: ['ts:dev plan', 'ts:dev implement'] },
|
|
152
|
+
QA: { name: 'QA Engineer', commands: ['ts:qa test', 'ts:qa verify', 'ts:qa regression', 'ts:qa bug', 'ts:qa uat'] },
|
|
153
|
+
SM: { name: 'Scrum Master', commands: ['ts:sm sprint', 'ts:sm deploy-checklist', 'ts:sm planning', 'ts:sm standup', 'ts:sm retro', 'ts:sm sync'] }
|
|
154
|
+
},
|
|
155
|
+
artifacts: {
|
|
156
|
+
feature: { location: 'products/{product-id}/features/', naming: 'f-{PRX}-{NNN}-{description}.md', owner: 'FA' },
|
|
157
|
+
'feature-increment': { location: 'projects/{project-id}/feature-increments/', naming: 'fi-{PRX}-{NNN}-{description}.md', owner: 'FA' },
|
|
158
|
+
epic: { location: 'projects/{project-id}/epics/', naming: 'epic-{PRX}-{NNN}-{description}.md', owner: 'FA' },
|
|
159
|
+
story: { location: 'projects/{project-id}/stories/{state}/', naming: 's-e{EEE}-{SSS}-{description}.md', owner: 'FA' },
|
|
160
|
+
'dev-plan': { location: 'projects/{project-id}/dev-plans/', naming: 'dp-e{EEE}-s{SSS}-{description}.md', owner: 'DEV' },
|
|
161
|
+
'project-test-case': { location: 'projects/{project-id}/qa/test-cases/', naming: 'tc-fi-{PRX}-{NNN}-{description}.md', owner: 'QA' },
|
|
162
|
+
'product-regression-test': { location: 'products/{product-id}/qa/regression-tests/', naming: 'rt-f-{PRX}-{NNN}-{description}.md', owner: 'QA' },
|
|
163
|
+
'regression-impact': { location: 'projects/{project-id}/qa/regression-impact/', naming: 'ri-fi-{PRX}-{NNN}.md', owner: 'QA' },
|
|
164
|
+
'bug-report': { location: 'projects/{project-id}/qa/bug-reports/', naming: 'bug-{project-id}-{NNN}-{description}.md', owner: 'QA' }
|
|
165
|
+
},
|
|
166
|
+
commands: [],
|
|
167
|
+
gates: {
|
|
168
|
+
dor: { name: 'Definition of Ready', checks: [] },
|
|
169
|
+
dod: { name: 'Definition of Done', checks: [] },
|
|
170
|
+
deployment: { name: 'Deployment Verification Gate', checks: [] }
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Extract all commands from registry for prompt generation
|
|
177
|
+
*/
|
|
178
|
+
function getCommandsFromRegistry(registry) {
|
|
179
|
+
const commands = [];
|
|
180
|
+
|
|
181
|
+
// Extract commands array if it exists
|
|
182
|
+
if (Array.isArray(registry.commands)) {
|
|
183
|
+
for (const cmd of registry.commands) {
|
|
184
|
+
if (cmd.status === 'REMOVED') continue;
|
|
185
|
+
if (!cmd.invocation) continue;
|
|
186
|
+
|
|
187
|
+
commands.push({
|
|
188
|
+
id: cmd.id,
|
|
189
|
+
invocation: cmd.invocation,
|
|
190
|
+
role: cmd.role,
|
|
191
|
+
purpose: cmd.purpose,
|
|
192
|
+
output: cmd.output
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Also extract from roles.*.commands
|
|
198
|
+
if (registry.roles) {
|
|
199
|
+
for (const [roleKey, roleData] of Object.entries(registry.roles)) {
|
|
200
|
+
if (Array.isArray(roleData.commands)) {
|
|
201
|
+
for (const cmdInvocation of roleData.commands) {
|
|
202
|
+
// Check if already in commands array
|
|
203
|
+
const exists = commands.some(c => c.invocation === cmdInvocation);
|
|
204
|
+
if (!exists) {
|
|
205
|
+
commands.push({
|
|
206
|
+
id: cmdInvocation.replace('ts:', '').replace(' ', '.'),
|
|
207
|
+
invocation: cmdInvocation,
|
|
208
|
+
role: roleKey,
|
|
209
|
+
purpose: `${roleData.name} command`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return commands;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get artifact naming patterns from registry
|
|
222
|
+
*/
|
|
223
|
+
function getArtifactPatterns(registry) {
|
|
224
|
+
const patterns = {};
|
|
225
|
+
|
|
226
|
+
if (registry.artifacts) {
|
|
227
|
+
for (const [artifactKey, artifactData] of Object.entries(registry.artifacts)) {
|
|
228
|
+
patterns[artifactKey] = {
|
|
229
|
+
location: artifactData.location,
|
|
230
|
+
naming: artifactData.naming,
|
|
231
|
+
owner: artifactData.owner,
|
|
232
|
+
example: artifactData.example
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return patterns;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get product folders from structure
|
|
242
|
+
*/
|
|
243
|
+
function getProductFolders(structure) {
|
|
244
|
+
const folders = [];
|
|
245
|
+
|
|
246
|
+
if (structure?.structure?.products?.product_folder?.subfolders) {
|
|
247
|
+
for (const subfolder of structure.structure.products.product_folder.subfolders) {
|
|
248
|
+
if (subfolder.path) {
|
|
249
|
+
folders.push(subfolder.path.replace(/\/$/, ''));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return folders.length > 0 ? folders : [
|
|
255
|
+
'business-analysis',
|
|
256
|
+
'features',
|
|
257
|
+
'solution-designs',
|
|
258
|
+
'technical-architecture',
|
|
259
|
+
'qa/regression-tests',
|
|
260
|
+
'decisions'
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get project folders from structure
|
|
266
|
+
*/
|
|
267
|
+
function getProjectFolders(structure) {
|
|
268
|
+
const folders = [];
|
|
269
|
+
|
|
270
|
+
if (structure?.structure?.projects?.project_folder?.subfolders) {
|
|
271
|
+
for (const subfolder of structure.structure.projects.project_folder.subfolders) {
|
|
272
|
+
if (subfolder.path) {
|
|
273
|
+
const basePath = subfolder.path.replace(/\/$/, '');
|
|
274
|
+
folders.push(basePath);
|
|
275
|
+
|
|
276
|
+
// Add nested workflow subfolders
|
|
277
|
+
if (subfolder.workflow_subfolders) {
|
|
278
|
+
for (const wf of subfolder.workflow_subfolders) {
|
|
279
|
+
folders.push(`${basePath}/${wf.replace(/\/$/, '')}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Add nested subfolders
|
|
284
|
+
if (subfolder.subfolders) {
|
|
285
|
+
for (const sf of subfolder.subfolders) {
|
|
286
|
+
folders.push(`${basePath}/${sf.replace(/\/$/, '')}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return folders.length > 0 ? folders : [
|
|
294
|
+
'business-analysis-increments',
|
|
295
|
+
'feature-increments',
|
|
296
|
+
'solution-design-increments',
|
|
297
|
+
'technical-architecture-increments',
|
|
298
|
+
'epics',
|
|
299
|
+
'stories',
|
|
300
|
+
'stories/backlog',
|
|
301
|
+
'stories/ready-to-refine',
|
|
302
|
+
'stories/ready-to-develop',
|
|
303
|
+
'stories/deferred',
|
|
304
|
+
'stories/out-of-scope',
|
|
305
|
+
'decisions',
|
|
306
|
+
'dev-plans',
|
|
307
|
+
'qa',
|
|
308
|
+
'qa/test-cases',
|
|
309
|
+
'qa/bug-reports',
|
|
310
|
+
'qa/uat',
|
|
311
|
+
'qa/regression-impact',
|
|
312
|
+
'sprints'
|
|
313
|
+
];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get story workflow folders
|
|
318
|
+
*/
|
|
319
|
+
function getStoryWorkflowFolders(structure) {
|
|
320
|
+
if (structure?.story_workflow_folders) {
|
|
321
|
+
return structure.story_workflow_folders;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return [
|
|
325
|
+
{ folder: 'stories/backlog/', state: 'backlog' },
|
|
326
|
+
{ folder: 'stories/ready-to-refine/', state: 'ready-to-refine' },
|
|
327
|
+
{ folder: 'stories/ready-to-develop/', state: 'ready-to-develop' },
|
|
328
|
+
{ folder: 'sprints/sprint-N/', state: 'in-sprint' }
|
|
329
|
+
];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Convert naming pattern from registry.yml to regex
|
|
334
|
+
*
|
|
335
|
+
* Converts patterns like:
|
|
336
|
+
* - "f-{PRX}-{NNN}-{description}.md" -> /^f-[A-Z]{3,4}-\d{3}-[\w-]+\.md$/
|
|
337
|
+
* - "s-e{EEE}-{SSS}-{description}.md" -> /^s-e\d{3}-\d{3}-[\w-]+\.md$/
|
|
338
|
+
*
|
|
339
|
+
* @param {string} pattern - Naming pattern from registry
|
|
340
|
+
* @returns {RegExp} - Compiled regex pattern
|
|
341
|
+
*/
|
|
342
|
+
function namingPatternToRegex(pattern) {
|
|
343
|
+
if (!pattern) return null;
|
|
344
|
+
|
|
345
|
+
// Escape special regex characters first (except { and })
|
|
346
|
+
let regexStr = pattern
|
|
347
|
+
.replace(/\./g, '\\.')
|
|
348
|
+
.replace(/\*/g, '.*');
|
|
349
|
+
|
|
350
|
+
// Replace placeholders with regex patterns
|
|
351
|
+
regexStr = regexStr
|
|
352
|
+
.replace(/\{PRX\}/g, '[A-Z]{3,4}') // Product prefix
|
|
353
|
+
.replace(/\{NNN\}/g, '\\d{3}') // 3-digit number
|
|
354
|
+
.replace(/\{EEE\}/g, '\\d{3}') // Epic number
|
|
355
|
+
.replace(/\{SSS\}/g, '\\d{3}') // Story sequence
|
|
356
|
+
.replace(/\{N\}/g, '\\d+') // Sprint number
|
|
357
|
+
.replace(/\{description\}/g, '[\\w-]+') // Description slug
|
|
358
|
+
.replace(/\{product-id\}/g, '[\\w-]+') // Product ID
|
|
359
|
+
.replace(/\{project-id\}/g, '[\\w-]+') // Project ID
|
|
360
|
+
.replace(/\{state\}/g, '[\\w-]+'); // Story state
|
|
361
|
+
|
|
362
|
+
return new RegExp(`^${regexStr}$`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get naming patterns as regex from registry artifacts
|
|
367
|
+
* @param {Object} registry - Parsed registry
|
|
368
|
+
* @returns {Object} - Map of artifact type to regex pattern
|
|
369
|
+
*/
|
|
370
|
+
function getArtifactNamingRegex(registry) {
|
|
371
|
+
const patterns = {};
|
|
372
|
+
|
|
373
|
+
if (registry?.artifacts) {
|
|
374
|
+
for (const [artifactKey, artifactData] of Object.entries(registry.artifacts)) {
|
|
375
|
+
if (artifactData.naming) {
|
|
376
|
+
const regex = namingPatternToRegex(artifactData.naming);
|
|
377
|
+
if (regex) {
|
|
378
|
+
patterns[artifactKey] = regex;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return patterns;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
module.exports = {
|
|
388
|
+
parseYaml,
|
|
389
|
+
loadRegistry,
|
|
390
|
+
loadFolderStructure,
|
|
391
|
+
getCommandsFromRegistry,
|
|
392
|
+
getArtifactPatterns,
|
|
393
|
+
getArtifactNamingRegex,
|
|
394
|
+
namingPatternToRegex,
|
|
395
|
+
getProductFolders,
|
|
396
|
+
getProjectFolders,
|
|
397
|
+
getStoryWorkflowFolders,
|
|
398
|
+
getDefaultRegistry,
|
|
399
|
+
getDefaultFolderStructure
|
|
400
|
+
};
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamspec",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "CLI tool to bootstrap TeamSpec
|
|
3
|
+
"version": "4.1.0",
|
|
4
|
+
"description": "CLI tool to bootstrap TeamSpec 4.0 Product-Canon operating model in any repository",
|
|
5
5
|
"main": "lib/cli.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"teamspec": "
|
|
7
|
+
"teamspec": "bin/teamspec-init.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
+
"sync": "node ../scripts/sync-cli-core.js",
|
|
10
11
|
"test": "node --test test/**/*.test.js",
|
|
11
12
|
"lint": "eslint lib/**/*.js",
|
|
12
|
-
"
|
|
13
|
+
"check:parity": "node ../scripts/check-parity.js",
|
|
14
|
+
"check:consistency": "node ../scripts/check-consistency.js",
|
|
15
|
+
"pretest": "npm run check:consistency",
|
|
16
|
+
"prepublishOnly": "npm run sync && npm test"
|
|
13
17
|
},
|
|
14
18
|
"keywords": [
|
|
15
19
|
"teamspec",
|
|
16
20
|
"agile",
|
|
17
21
|
"specifications",
|
|
22
|
+
"product-canon",
|
|
18
23
|
"feature-canon",
|
|
19
24
|
"copilot",
|
|
20
25
|
"cursor",
|
|
@@ -47,5 +52,8 @@
|
|
|
47
52
|
"README.md",
|
|
48
53
|
"LICENSE"
|
|
49
54
|
],
|
|
50
|
-
"preferGlobal": true
|
|
51
|
-
|
|
55
|
+
"preferGlobal": true,
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"yaml": "^2.8.2"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# TeamSpec 4.0 Folder Structure Encoding
|
|
2
|
+
# Encodes the physical folder hierarchy for AI prompts and tooling
|
|
3
|
+
# Version: 4.0
|
|
4
|
+
# Last Updated: 2026-01-11
|
|
5
|
+
#
|
|
6
|
+
# NOTE: Role ownership, artifact naming, and gates are defined in registry.yml
|
|
7
|
+
# This file focuses ONLY on folder paths and hierarchy
|
|
8
|
+
|
|
9
|
+
# =============================================================================
|
|
10
|
+
# CORE CONCEPT: Products vs Projects
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# Products = Production truth (AS-IS state) - canonical, permanent
|
|
13
|
+
# Projects = Change proposals (TO-BE state) - time-bound, sync to product on deploy
|
|
14
|
+
|
|
15
|
+
structure:
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# PRODUCTS - Production Truth
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
products:
|
|
20
|
+
path: "products/"
|
|
21
|
+
|
|
22
|
+
product_folder:
|
|
23
|
+
path: "products/{product-id}/"
|
|
24
|
+
files:
|
|
25
|
+
- "product.yml"
|
|
26
|
+
- "README.md"
|
|
27
|
+
|
|
28
|
+
subfolders:
|
|
29
|
+
- path: "business-analysis/"
|
|
30
|
+
- path: "features/"
|
|
31
|
+
key_files:
|
|
32
|
+
- "features-index.md"
|
|
33
|
+
- "story-ledger.md"
|
|
34
|
+
- path: "solution-designs/"
|
|
35
|
+
- path: "technical-architecture/"
|
|
36
|
+
- path: "qa/regression-tests/"
|
|
37
|
+
- path: "decisions/"
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# PROJECTS - Change Proposals
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
projects:
|
|
43
|
+
path: "projects/"
|
|
44
|
+
|
|
45
|
+
project_folder:
|
|
46
|
+
path: "projects/{project-id}/"
|
|
47
|
+
files:
|
|
48
|
+
- "project.yml"
|
|
49
|
+
- "README.md"
|
|
50
|
+
|
|
51
|
+
subfolders:
|
|
52
|
+
- path: "business-analysis-increments/"
|
|
53
|
+
- path: "feature-increments/"
|
|
54
|
+
key_files:
|
|
55
|
+
- "increments-index.md"
|
|
56
|
+
- path: "solution-design-increments/"
|
|
57
|
+
- path: "technical-architecture-increments/"
|
|
58
|
+
- path: "epics/"
|
|
59
|
+
key_files:
|
|
60
|
+
- "epics-index.md"
|
|
61
|
+
- path: "stories/"
|
|
62
|
+
workflow_subfolders:
|
|
63
|
+
- "backlog/"
|
|
64
|
+
- "ready-to-refine/"
|
|
65
|
+
- "ready-to-develop/"
|
|
66
|
+
- "deferred/"
|
|
67
|
+
- "out-of-scope/"
|
|
68
|
+
- path: "decisions/"
|
|
69
|
+
- path: "dev-plans/"
|
|
70
|
+
- path: "qa/"
|
|
71
|
+
subfolders:
|
|
72
|
+
- "test-cases/"
|
|
73
|
+
- "bug-reports/"
|
|
74
|
+
- "uat/"
|
|
75
|
+
- "regression-impact/"
|
|
76
|
+
- path: "sprints/"
|
|
77
|
+
key_files:
|
|
78
|
+
- "active-sprint.md"
|
|
79
|
+
sprint_folder:
|
|
80
|
+
pattern: "sprint-N/"
|
|
81
|
+
files:
|
|
82
|
+
- "sprint-goal.md"
|
|
83
|
+
- "committed-stories.md"
|
|
84
|
+
- "retrospective.md"
|
|
85
|
+
|
|
86
|
+
# =============================================================================
|
|
87
|
+
# STORY WORKFLOW FOLDERS
|
|
88
|
+
# =============================================================================
|
|
89
|
+
# Stories move between folders based on workflow state
|
|
90
|
+
story_workflow_folders:
|
|
91
|
+
- folder: "stories/backlog/"
|
|
92
|
+
state: "backlog"
|
|
93
|
+
next: "stories/ready-to-refine/"
|
|
94
|
+
|
|
95
|
+
- folder: "stories/ready-to-refine/"
|
|
96
|
+
state: "ready-to-refine"
|
|
97
|
+
next: "stories/ready-to-develop/"
|
|
98
|
+
|
|
99
|
+
- folder: "stories/ready-to-develop/"
|
|
100
|
+
state: "ready-to-develop"
|
|
101
|
+
next: "sprints/sprint-N/"
|
|
102
|
+
|
|
103
|
+
- folder: "sprints/sprint-N/"
|
|
104
|
+
state: "in-sprint"
|
|
105
|
+
next: "completed (archived)"
|
|
106
|
+
|
|
107
|
+
# Terminal states
|
|
108
|
+
- folder: "stories/deferred/"
|
|
109
|
+
state: "deferred"
|
|
110
|
+
terminal: true
|
|
111
|
+
|
|
112
|
+
- folder: "stories/out-of-scope/"
|
|
113
|
+
state: "out-of-scope"
|
|
114
|
+
terminal: true
|
|
115
|
+
|
|
116
|
+
# =============================================================================
|
|
117
|
+
# CANON SYNC FOLDER MAPPINGS
|
|
118
|
+
# =============================================================================
|
|
119
|
+
# Post-deployment, project artifacts sync to product folders
|
|
120
|
+
canon_sync_paths:
|
|
121
|
+
- from: "projects/{project-id}/feature-increments/"
|
|
122
|
+
to: "products/{product-id}/features/"
|
|
123
|
+
|
|
124
|
+
- from: "projects/{project-id}/business-analysis-increments/"
|
|
125
|
+
to: "products/{product-id}/business-analysis/"
|
|
126
|
+
|
|
127
|
+
- from: "projects/{project-id}/solution-design-increments/"
|
|
128
|
+
to: "products/{product-id}/solution-designs/"
|
|
129
|
+
|
|
130
|
+
- from: "projects/{project-id}/technical-architecture-increments/"
|
|
131
|
+
to: "products/{product-id}/technical-architecture/"
|