musubi-sdd 2.2.0 → 3.0.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.ja.md +1 -1
- package/README.md +1 -1
- package/bin/musubi-browser.js +457 -0
- package/bin/musubi-convert.js +179 -0
- package/bin/musubi-gui.js +270 -0
- package/package.json +13 -3
- package/src/agents/browser/action-executor.js +255 -0
- package/src/agents/browser/ai-comparator.js +255 -0
- package/src/agents/browser/context-manager.js +207 -0
- package/src/agents/browser/index.js +265 -0
- package/src/agents/browser/nl-parser.js +408 -0
- package/src/agents/browser/screenshot.js +174 -0
- package/src/agents/browser/test-generator.js +271 -0
- package/src/converters/index.js +285 -0
- package/src/converters/ir/types.js +508 -0
- package/src/converters/parsers/musubi-parser.js +759 -0
- package/src/converters/parsers/speckit-parser.js +1001 -0
- package/src/converters/writers/musubi-writer.js +808 -0
- package/src/converters/writers/speckit-writer.js +718 -0
- package/src/gui/public/index.html +856 -0
- package/src/gui/server.js +352 -0
- package/src/gui/services/file-watcher.js +119 -0
- package/src/gui/services/index.js +16 -0
- package/src/gui/services/project-scanner.js +547 -0
- package/src/gui/services/traceability-service.js +372 -0
- package/src/gui/services/workflow-service.js +242 -0
- package/src/templates/skills/browser-agent.md +164 -0
- package/src/templates/skills/web-gui.md +188 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intermediate Representation (IR) Types for Cross-Format Conversion
|
|
3
|
+
*
|
|
4
|
+
* Enables bidirectional conversion between MUSUBI and Spec Kit formats
|
|
5
|
+
* with minimal information loss.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* EARS Requirement Patterns
|
|
12
|
+
* @typedef {'ubiquitous' | 'event-driven' | 'state-driven' | 'optional' | 'complex'} EARSPattern
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Priority levels
|
|
17
|
+
* @typedef {'P0' | 'P1' | 'P2' | 'P3'} Priority
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Feature status
|
|
22
|
+
* @typedef {'draft' | 'in-progress' | 'completed'} FeatureStatus
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Source format
|
|
27
|
+
* @typedef {'musubi' | 'speckit'} SourceFormat
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Project Metadata
|
|
32
|
+
* @typedef {Object} ProjectMetadata
|
|
33
|
+
* @property {string} name - Project name
|
|
34
|
+
* @property {string} version - Project version
|
|
35
|
+
* @property {SourceFormat} sourceFormat - Original format
|
|
36
|
+
* @property {string} sourceVersion - Original format version
|
|
37
|
+
* @property {Date} convertedAt - Conversion timestamp
|
|
38
|
+
* @property {Object} preservedFields - Format-specific fields preserved
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Constitution Article (MUSUBI 9 Articles)
|
|
43
|
+
* @typedef {Object} ArticleIR
|
|
44
|
+
* @property {number} number - Article number (1-9)
|
|
45
|
+
* @property {string} name - Article name
|
|
46
|
+
* @property {string} description - Article description
|
|
47
|
+
* @property {string[]} rules - Article rules
|
|
48
|
+
* @property {string} [mappedFrom] - Original section if converted
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Spec Kit Principle
|
|
53
|
+
* @typedef {Object} PrincipleIR
|
|
54
|
+
* @property {string} name - Principle name
|
|
55
|
+
* @property {string} description - Principle description
|
|
56
|
+
* @property {number} [mappedToArticle] - Mapped MUSUBI article number
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Governance information
|
|
61
|
+
* @typedef {Object} GovernanceIR
|
|
62
|
+
* @property {string} version - Governance version
|
|
63
|
+
* @property {Date} [ratified] - Ratification date
|
|
64
|
+
* @property {Date} [lastAmended] - Last amendment date
|
|
65
|
+
* @property {string[]} rules - Governance rules
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Constitution IR
|
|
70
|
+
* @typedef {Object} ConstitutionIR
|
|
71
|
+
* @property {ArticleIR[]} articles - MUSUBI articles
|
|
72
|
+
* @property {PrincipleIR[]} corePrinciples - Spec Kit principles
|
|
73
|
+
* @property {GovernanceIR} governance - Governance info
|
|
74
|
+
* @property {string} [rawContent] - Original raw content
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Acceptance Criterion
|
|
79
|
+
* @typedef {Object} AcceptanceCriterionIR
|
|
80
|
+
* @property {string} id - Criterion ID
|
|
81
|
+
* @property {string} description - Criterion description
|
|
82
|
+
* @property {boolean} testable - Whether testable
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* User Scenario (Spec Kit style)
|
|
87
|
+
* @typedef {Object} UserScenarioIR
|
|
88
|
+
* @property {string} id - Scenario ID
|
|
89
|
+
* @property {string} title - Scenario title
|
|
90
|
+
* @property {string} actor - Who performs the action
|
|
91
|
+
* @property {string} action - What action is performed
|
|
92
|
+
* @property {string} benefit - Expected benefit
|
|
93
|
+
* @property {Priority} priority - Priority level
|
|
94
|
+
* @property {AcceptanceCriterionIR[]} acceptanceCriteria - Acceptance criteria
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Requirement (EARS format)
|
|
99
|
+
* @typedef {Object} RequirementIR
|
|
100
|
+
* @property {string} id - Requirement ID (e.g., "REQ-001")
|
|
101
|
+
* @property {string} title - Requirement title
|
|
102
|
+
* @property {EARSPattern} pattern - EARS pattern type
|
|
103
|
+
* @property {Priority} priority - Priority level
|
|
104
|
+
* @property {string} [trigger] - WHEN clause
|
|
105
|
+
* @property {string} [condition] - WHILE/WHERE clause
|
|
106
|
+
* @property {string} action - SHALL clause
|
|
107
|
+
* @property {string} statement - Full EARS statement
|
|
108
|
+
* @property {AcceptanceCriterionIR[]} acceptanceCriteria - Acceptance criteria
|
|
109
|
+
* @property {string} [traceability] - Traceability info
|
|
110
|
+
* @property {string} [mappedFromUserStory] - Original user story if converted
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Specification IR
|
|
115
|
+
* @typedef {Object} SpecificationIR
|
|
116
|
+
* @property {string} title - Specification title
|
|
117
|
+
* @property {string} description - Specification description
|
|
118
|
+
* @property {UserScenarioIR[]} userScenarios - User scenarios
|
|
119
|
+
* @property {RequirementIR[]} requirements - EARS requirements
|
|
120
|
+
* @property {string[]} successCriteria - Success criteria
|
|
121
|
+
* @property {string} [rawContent] - Original raw content
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Technical Context
|
|
126
|
+
* @typedef {Object} TechnicalContextIR
|
|
127
|
+
* @property {string} language - Primary language
|
|
128
|
+
* @property {string} version - Language version
|
|
129
|
+
* @property {string} framework - Primary framework
|
|
130
|
+
* @property {string[]} dependencies - Dependencies
|
|
131
|
+
* @property {string} [storage] - Storage solution
|
|
132
|
+
* @property {string} testing - Testing framework
|
|
133
|
+
* @property {string} targetPlatform - Target platform
|
|
134
|
+
* @property {string} [performanceGoals] - Performance goals
|
|
135
|
+
* @property {string} [constraints] - Constraints
|
|
136
|
+
* @property {string} [scale] - Scale requirements
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Constitution Check
|
|
141
|
+
* @typedef {Object} ConstitutionCheckIR
|
|
142
|
+
* @property {string} principle - Principle name
|
|
143
|
+
* @property {'pass' | 'fail' | 'needs-justification'} status - Check status
|
|
144
|
+
* @property {string} [notes] - Additional notes
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Directory structure
|
|
149
|
+
* @typedef {Object} DirectoryIR
|
|
150
|
+
* @property {string} path - Directory path
|
|
151
|
+
* @property {string} purpose - Directory purpose
|
|
152
|
+
* @property {DirectoryIR[]} [children] - Child directories
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Project Structure
|
|
157
|
+
* @typedef {Object} ProjectStructureIR
|
|
158
|
+
* @property {'single' | 'web' | 'mobile' | 'monorepo'} type - Project type
|
|
159
|
+
* @property {DirectoryIR[]} directories - Directory structure
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Task Reference
|
|
164
|
+
* @typedef {Object} TaskReferenceIR
|
|
165
|
+
* @property {string} taskId - Task ID
|
|
166
|
+
* @property {number} order - Execution order
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Phase
|
|
171
|
+
* @typedef {Object} PhaseIR
|
|
172
|
+
* @property {number} number - Phase number
|
|
173
|
+
* @property {string} name - Phase name
|
|
174
|
+
* @property {string} purpose - Phase purpose
|
|
175
|
+
* @property {string[]} [prerequisites] - Prerequisites
|
|
176
|
+
* @property {string[]} outputs - Phase outputs
|
|
177
|
+
* @property {TaskReferenceIR[]} tasks - Tasks in this phase
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Plan IR
|
|
182
|
+
* @typedef {Object} PlanIR
|
|
183
|
+
* @property {string} summary - Plan summary
|
|
184
|
+
* @property {TechnicalContextIR} technicalContext - Technical context
|
|
185
|
+
* @property {ConstitutionCheckIR[]} constitutionCheck - Constitution checks
|
|
186
|
+
* @property {ProjectStructureIR} projectStructure - Project structure
|
|
187
|
+
* @property {PhaseIR[]} phases - Implementation phases
|
|
188
|
+
* @property {string} [rawContent] - Original raw content
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Task IR
|
|
193
|
+
* @typedef {Object} TaskIR
|
|
194
|
+
* @property {string} id - Task ID (e.g., "T001")
|
|
195
|
+
* @property {string} description - Task description
|
|
196
|
+
* @property {number} phase - Phase number
|
|
197
|
+
* @property {string} [userStory] - Related user story
|
|
198
|
+
* @property {boolean} parallel - Can run in parallel
|
|
199
|
+
* @property {string} [filePath] - Related file path
|
|
200
|
+
* @property {boolean} completed - Completion status
|
|
201
|
+
* @property {string[]} [dependencies] - Task dependencies
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Decision
|
|
206
|
+
* @typedef {Object} DecisionIR
|
|
207
|
+
* @property {string} topic - Decision topic
|
|
208
|
+
* @property {string} decision - Decision made
|
|
209
|
+
* @property {string} rationale - Decision rationale
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Alternative
|
|
214
|
+
* @typedef {Object} AlternativeIR
|
|
215
|
+
* @property {string} name - Alternative name
|
|
216
|
+
* @property {string[]} pros - Pros
|
|
217
|
+
* @property {string[]} cons - Cons
|
|
218
|
+
* @property {boolean} rejected - Whether rejected
|
|
219
|
+
* @property {string} [reason] - Rejection reason
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Research IR
|
|
224
|
+
* @typedef {Object} ResearchIR
|
|
225
|
+
* @property {DecisionIR[]} decisions - Decisions made
|
|
226
|
+
* @property {AlternativeIR[]} alternatives - Alternatives considered
|
|
227
|
+
* @property {string} [rawContent] - Original raw content
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Entity
|
|
232
|
+
* @typedef {Object} EntityIR
|
|
233
|
+
* @property {string} name - Entity name
|
|
234
|
+
* @property {string} description - Entity description
|
|
235
|
+
* @property {Object[]} fields - Entity fields
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Relationship
|
|
240
|
+
* @typedef {Object} RelationshipIR
|
|
241
|
+
* @property {string} from - Source entity
|
|
242
|
+
* @property {string} to - Target entity
|
|
243
|
+
* @property {string} type - Relationship type
|
|
244
|
+
* @property {string} [cardinality] - Cardinality
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Data Model IR
|
|
249
|
+
* @typedef {Object} DataModelIR
|
|
250
|
+
* @property {EntityIR[]} entities - Entities
|
|
251
|
+
* @property {RelationshipIR[]} relationships - Relationships
|
|
252
|
+
* @property {string} [rawContent] - Original raw content
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Contract IR
|
|
257
|
+
* @typedef {Object} ContractIR
|
|
258
|
+
* @property {string} type - Contract type (REST, GraphQL, etc.)
|
|
259
|
+
* @property {string} name - Contract name
|
|
260
|
+
* @property {Object} definition - Contract definition
|
|
261
|
+
* @property {string} [rawContent] - Original raw content
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Quickstart IR
|
|
266
|
+
* @typedef {Object} QuickstartIR
|
|
267
|
+
* @property {Object[]} steps - Setup steps
|
|
268
|
+
* @property {string} [rawContent] - Original raw content
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Feature IR
|
|
273
|
+
* @typedef {Object} FeatureIR
|
|
274
|
+
* @property {string} id - Feature ID (e.g., "001-photo-albums")
|
|
275
|
+
* @property {string} name - Feature name
|
|
276
|
+
* @property {string} [branch] - Feature branch
|
|
277
|
+
* @property {FeatureStatus} status - Feature status
|
|
278
|
+
* @property {Date} createdAt - Creation date
|
|
279
|
+
* @property {SpecificationIR} specification - Specification
|
|
280
|
+
* @property {PlanIR} [plan] - Implementation plan
|
|
281
|
+
* @property {TaskIR[]} [tasks] - Tasks
|
|
282
|
+
* @property {ResearchIR} [research] - Research
|
|
283
|
+
* @property {DataModelIR} [dataModel] - Data model
|
|
284
|
+
* @property {ContractIR[]} [contracts] - API contracts
|
|
285
|
+
* @property {QuickstartIR} [quickstart] - Quickstart guide
|
|
286
|
+
*/
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Template IR
|
|
290
|
+
* @typedef {Object} TemplateIR
|
|
291
|
+
* @property {string} name - Template name
|
|
292
|
+
* @property {string} type - Template type
|
|
293
|
+
* @property {string} content - Template content
|
|
294
|
+
*/
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Memory IR
|
|
298
|
+
* @typedef {Object} MemoryIR
|
|
299
|
+
* @property {string} category - Memory category
|
|
300
|
+
* @property {Object[]} entries - Memory entries
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Project IR - Root structure
|
|
305
|
+
* @typedef {Object} ProjectIR
|
|
306
|
+
* @property {ProjectMetadata} metadata - Project metadata
|
|
307
|
+
* @property {ConstitutionIR} constitution - Constitution
|
|
308
|
+
* @property {FeatureIR[]} features - Features
|
|
309
|
+
* @property {TemplateIR[]} templates - Templates
|
|
310
|
+
* @property {MemoryIR[]} memories - Memories
|
|
311
|
+
*/
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Create empty Project IR
|
|
315
|
+
* @returns {ProjectIR}
|
|
316
|
+
*/
|
|
317
|
+
function createEmptyProjectIR() {
|
|
318
|
+
return {
|
|
319
|
+
metadata: {
|
|
320
|
+
name: '',
|
|
321
|
+
version: '1.0.0',
|
|
322
|
+
sourceFormat: 'musubi',
|
|
323
|
+
sourceVersion: '2.2.0',
|
|
324
|
+
convertedAt: new Date(),
|
|
325
|
+
preservedFields: {},
|
|
326
|
+
},
|
|
327
|
+
constitution: {
|
|
328
|
+
articles: [],
|
|
329
|
+
corePrinciples: [],
|
|
330
|
+
governance: {
|
|
331
|
+
version: '1.0',
|
|
332
|
+
rules: [],
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
features: [],
|
|
336
|
+
templates: [],
|
|
337
|
+
memories: [],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Create empty Feature IR
|
|
343
|
+
* @param {string} id - Feature ID
|
|
344
|
+
* @param {string} name - Feature name
|
|
345
|
+
* @returns {FeatureIR}
|
|
346
|
+
*/
|
|
347
|
+
function createEmptyFeatureIR(id, name) {
|
|
348
|
+
return {
|
|
349
|
+
id,
|
|
350
|
+
name,
|
|
351
|
+
status: 'draft',
|
|
352
|
+
createdAt: new Date(),
|
|
353
|
+
specification: {
|
|
354
|
+
title: name,
|
|
355
|
+
description: '',
|
|
356
|
+
userScenarios: [],
|
|
357
|
+
requirements: [],
|
|
358
|
+
successCriteria: [],
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Create Requirement IR from EARS statement
|
|
365
|
+
* @param {string} id - Requirement ID
|
|
366
|
+
* @param {string} statement - EARS statement
|
|
367
|
+
* @returns {RequirementIR}
|
|
368
|
+
*/
|
|
369
|
+
function createRequirementFromEARS(id, statement) {
|
|
370
|
+
const pattern = detectEARSPattern(statement);
|
|
371
|
+
const parsed = parseEARSStatement(statement, pattern);
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
id,
|
|
375
|
+
title: '',
|
|
376
|
+
pattern,
|
|
377
|
+
priority: 'P1',
|
|
378
|
+
trigger: parsed.trigger,
|
|
379
|
+
condition: parsed.condition,
|
|
380
|
+
action: parsed.action,
|
|
381
|
+
statement,
|
|
382
|
+
acceptanceCriteria: [],
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Detect EARS pattern from statement
|
|
388
|
+
* @param {string} statement - EARS statement
|
|
389
|
+
* @returns {EARSPattern}
|
|
390
|
+
*/
|
|
391
|
+
function detectEARSPattern(statement) {
|
|
392
|
+
const upper = statement.toUpperCase();
|
|
393
|
+
|
|
394
|
+
if (upper.includes('WHILE') && upper.includes('WHEN')) {
|
|
395
|
+
return 'complex';
|
|
396
|
+
}
|
|
397
|
+
if (upper.includes('WHEN')) {
|
|
398
|
+
return 'event-driven';
|
|
399
|
+
}
|
|
400
|
+
if (upper.includes('WHILE')) {
|
|
401
|
+
return 'state-driven';
|
|
402
|
+
}
|
|
403
|
+
if (upper.includes('WHERE')) {
|
|
404
|
+
return 'optional';
|
|
405
|
+
}
|
|
406
|
+
return 'ubiquitous';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Parse EARS statement into components
|
|
411
|
+
* @param {string} statement - EARS statement
|
|
412
|
+
* @param {EARSPattern} pattern - EARS pattern
|
|
413
|
+
* @returns {{trigger?: string, condition?: string, action: string}}
|
|
414
|
+
*/
|
|
415
|
+
function parseEARSStatement(statement, pattern) {
|
|
416
|
+
const result = { action: '' };
|
|
417
|
+
|
|
418
|
+
// Extract SHALL clause
|
|
419
|
+
const shallMatch = statement.match(/SHALL\s+(.+?)(?:\.|$)/i);
|
|
420
|
+
if (shallMatch) {
|
|
421
|
+
result.action = shallMatch[1].trim();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Extract WHEN clause
|
|
425
|
+
const whenMatch = statement.match(/WHEN\s+(.+?),/i);
|
|
426
|
+
if (whenMatch) {
|
|
427
|
+
result.trigger = whenMatch[1].trim();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Extract WHILE clause
|
|
431
|
+
const whileMatch = statement.match(/WHILE\s+(.+?),/i);
|
|
432
|
+
if (whileMatch) {
|
|
433
|
+
result.condition = whileMatch[1].trim();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Extract WHERE clause
|
|
437
|
+
const whereMatch = statement.match(/WHERE\s+(.+?),/i);
|
|
438
|
+
if (whereMatch) {
|
|
439
|
+
result.condition = whereMatch[1].trim();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Convert User Story to EARS Requirement
|
|
447
|
+
* @param {UserScenarioIR} userScenario - User scenario
|
|
448
|
+
* @param {string} reqId - Requirement ID
|
|
449
|
+
* @returns {RequirementIR}
|
|
450
|
+
*/
|
|
451
|
+
function userScenarioToRequirement(userScenario, reqId) {
|
|
452
|
+
// Convert "As a [actor], I want [action] so that [benefit]"
|
|
453
|
+
// to "WHEN the [actor] [action], the system SHALL [provide benefit]"
|
|
454
|
+
const statement = `WHEN the ${userScenario.actor} ${userScenario.action}, the system SHALL ${userScenario.benefit}.`;
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
id: reqId,
|
|
458
|
+
title: userScenario.title,
|
|
459
|
+
pattern: 'event-driven',
|
|
460
|
+
priority: userScenario.priority,
|
|
461
|
+
trigger: `the ${userScenario.actor} ${userScenario.action}`,
|
|
462
|
+
action: userScenario.benefit,
|
|
463
|
+
statement,
|
|
464
|
+
acceptanceCriteria: userScenario.acceptanceCriteria,
|
|
465
|
+
mappedFromUserStory: userScenario.id,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Convert EARS Requirement to User Story
|
|
471
|
+
* @param {RequirementIR} requirement - Requirement
|
|
472
|
+
* @param {string} storyId - Story ID
|
|
473
|
+
* @returns {UserScenarioIR}
|
|
474
|
+
*/
|
|
475
|
+
function requirementToUserScenario(requirement, storyId) {
|
|
476
|
+
// Convert EARS to User Story format
|
|
477
|
+
let actor = 'user';
|
|
478
|
+
let action = requirement.trigger || 'performs an action';
|
|
479
|
+
let benefit = requirement.action;
|
|
480
|
+
|
|
481
|
+
// Try to extract actor from trigger
|
|
482
|
+
if (requirement.trigger) {
|
|
483
|
+
const actorMatch = requirement.trigger.match(/the\s+(\w+)/i);
|
|
484
|
+
if (actorMatch) {
|
|
485
|
+
actor = actorMatch[1];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
id: storyId,
|
|
491
|
+
title: requirement.title || `Story for ${requirement.id}`,
|
|
492
|
+
actor,
|
|
493
|
+
action,
|
|
494
|
+
benefit,
|
|
495
|
+
priority: requirement.priority,
|
|
496
|
+
acceptanceCriteria: requirement.acceptanceCriteria,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
module.exports = {
|
|
501
|
+
createEmptyProjectIR,
|
|
502
|
+
createEmptyFeatureIR,
|
|
503
|
+
createRequirementFromEARS,
|
|
504
|
+
detectEARSPattern,
|
|
505
|
+
parseEARSStatement,
|
|
506
|
+
userScenarioToRequirement,
|
|
507
|
+
requirementToUserScenario,
|
|
508
|
+
};
|