musubi-sdd 3.10.0 → 5.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 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Constraints Engine
|
|
3
|
+
* LLM制約テンプレート構文と不確実性マーカー
|
|
4
|
+
*
|
|
5
|
+
* @module templates/template-constraints
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EventEmitter = require('events');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Constraint types
|
|
12
|
+
*/
|
|
13
|
+
const CONSTRAINT_TYPE = {
|
|
14
|
+
REQUIRED: 'required',
|
|
15
|
+
OPTIONAL: 'optional',
|
|
16
|
+
FORBIDDEN: 'forbidden',
|
|
17
|
+
CONDITIONAL: 'conditional',
|
|
18
|
+
PATTERN: 'pattern',
|
|
19
|
+
RANGE: 'range',
|
|
20
|
+
ENUM: 'enum'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Uncertainty levels
|
|
25
|
+
*/
|
|
26
|
+
const UNCERTAINTY = {
|
|
27
|
+
CERTAIN: 'certain', // 100% confidence
|
|
28
|
+
HIGH: 'high', // 80-99%
|
|
29
|
+
MEDIUM: 'medium', // 50-79%
|
|
30
|
+
LOW: 'low', // 20-49%
|
|
31
|
+
UNCERTAIN: 'uncertain' // <20%
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Marker types for structured thinking
|
|
36
|
+
*/
|
|
37
|
+
const MARKER_TYPE = {
|
|
38
|
+
ASSUMPTION: 'assumption',
|
|
39
|
+
DECISION: 'decision',
|
|
40
|
+
RISK: 'risk',
|
|
41
|
+
TODO: 'todo',
|
|
42
|
+
QUESTION: 'question',
|
|
43
|
+
VERIFIED: 'verified',
|
|
44
|
+
UNCERTAIN: 'uncertain'
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default constraint templates
|
|
49
|
+
*/
|
|
50
|
+
const DEFAULT_TEMPLATES = {
|
|
51
|
+
'requirements': {
|
|
52
|
+
name: 'Requirements Template',
|
|
53
|
+
sections: [
|
|
54
|
+
{ name: 'overview', required: true, minLength: 50 },
|
|
55
|
+
{ name: 'functional', required: true, format: 'ears' },
|
|
56
|
+
{ name: 'non-functional', required: false },
|
|
57
|
+
{ name: 'constraints', required: false },
|
|
58
|
+
{ name: 'assumptions', required: true }
|
|
59
|
+
],
|
|
60
|
+
markers: [MARKER_TYPE.ASSUMPTION, MARKER_TYPE.RISK]
|
|
61
|
+
},
|
|
62
|
+
'design': {
|
|
63
|
+
name: 'Design Template',
|
|
64
|
+
sections: [
|
|
65
|
+
{ name: 'architecture', required: true },
|
|
66
|
+
{ name: 'components', required: true, format: 'c4' },
|
|
67
|
+
{ name: 'decisions', required: true, format: 'adr' },
|
|
68
|
+
{ name: 'interfaces', required: false },
|
|
69
|
+
{ name: 'data-model', required: false }
|
|
70
|
+
],
|
|
71
|
+
markers: [MARKER_TYPE.DECISION, MARKER_TYPE.ASSUMPTION]
|
|
72
|
+
},
|
|
73
|
+
'implementation': {
|
|
74
|
+
name: 'Implementation Template',
|
|
75
|
+
sections: [
|
|
76
|
+
{ name: 'approach', required: true },
|
|
77
|
+
{ name: 'tasks', required: true, format: 'checklist' },
|
|
78
|
+
{ name: 'dependencies', required: false },
|
|
79
|
+
{ name: 'testing', required: true }
|
|
80
|
+
],
|
|
81
|
+
markers: [MARKER_TYPE.TODO, MARKER_TYPE.RISK]
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Template Constraints Engine
|
|
87
|
+
*/
|
|
88
|
+
class TemplateConstraints extends EventEmitter {
|
|
89
|
+
/**
|
|
90
|
+
* @param {Object} options
|
|
91
|
+
* @param {Object} options.templates - Custom templates
|
|
92
|
+
* @param {boolean} options.strict - Strict mode
|
|
93
|
+
* @param {boolean} options.trackUncertainty - Track uncertainty markers
|
|
94
|
+
*/
|
|
95
|
+
constructor(options = {}) {
|
|
96
|
+
super();
|
|
97
|
+
|
|
98
|
+
this.templates = {
|
|
99
|
+
...DEFAULT_TEMPLATES,
|
|
100
|
+
...(options.templates || {})
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
this.strict = options.strict ?? false;
|
|
104
|
+
this.trackUncertainty = options.trackUncertainty ?? true;
|
|
105
|
+
this.customConstraints = new Map();
|
|
106
|
+
this.validationHistory = [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate content against a template
|
|
111
|
+
* @param {string} content - Content to validate
|
|
112
|
+
* @param {string} templateId - Template identifier
|
|
113
|
+
* @returns {Object} Validation result
|
|
114
|
+
*/
|
|
115
|
+
validate(content, templateId) {
|
|
116
|
+
const template = this.templates[templateId];
|
|
117
|
+
if (!template) {
|
|
118
|
+
return {
|
|
119
|
+
valid: false,
|
|
120
|
+
errors: [{ type: 'unknown_template', message: `Unknown template: ${templateId}` }],
|
|
121
|
+
warnings: [],
|
|
122
|
+
score: 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const errors = [];
|
|
127
|
+
const warnings = [];
|
|
128
|
+
const markers = this.extractMarkers(content);
|
|
129
|
+
const sections = this.extractSections(content);
|
|
130
|
+
|
|
131
|
+
// Validate required sections
|
|
132
|
+
for (const section of template.sections) {
|
|
133
|
+
const found = sections.find(s =>
|
|
134
|
+
s.name.toLowerCase().includes(section.name.toLowerCase())
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (section.required && !found) {
|
|
138
|
+
errors.push({
|
|
139
|
+
type: 'missing_section',
|
|
140
|
+
section: section.name,
|
|
141
|
+
message: `Required section missing: ${section.name}`
|
|
142
|
+
});
|
|
143
|
+
} else if (found) {
|
|
144
|
+
// Validate section content
|
|
145
|
+
if (section.minLength && found.content.length < section.minLength) {
|
|
146
|
+
warnings.push({
|
|
147
|
+
type: 'short_section',
|
|
148
|
+
section: section.name,
|
|
149
|
+
message: `Section "${section.name}" is too short (${found.content.length} < ${section.minLength})`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (section.format) {
|
|
154
|
+
const formatValid = this.validateFormat(found.content, section.format);
|
|
155
|
+
if (!formatValid) {
|
|
156
|
+
warnings.push({
|
|
157
|
+
type: 'format_mismatch',
|
|
158
|
+
section: section.name,
|
|
159
|
+
format: section.format,
|
|
160
|
+
message: `Section "${section.name}" doesn't match expected format: ${section.format}`
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check for required markers
|
|
168
|
+
if (template.markers && template.markers.length > 0) {
|
|
169
|
+
for (const markerType of template.markers) {
|
|
170
|
+
const hasMarker = markers.some(m => m.type === markerType);
|
|
171
|
+
if (!hasMarker && this.trackUncertainty) {
|
|
172
|
+
warnings.push({
|
|
173
|
+
type: 'missing_marker',
|
|
174
|
+
markerType,
|
|
175
|
+
message: `Expected marker type not found: ${markerType}`
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Apply custom constraints
|
|
182
|
+
for (const [name, constraint] of this.customConstraints) {
|
|
183
|
+
const result = constraint.validate(content, sections, markers);
|
|
184
|
+
if (!result.valid) {
|
|
185
|
+
if (constraint.severity === 'error') {
|
|
186
|
+
errors.push({ type: 'custom_constraint', name, ...result });
|
|
187
|
+
} else {
|
|
188
|
+
warnings.push({ type: 'custom_constraint', name, ...result });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const score = this.calculateScore(template, errors, warnings);
|
|
194
|
+
const valid = errors.length === 0 && (score >= 50 || !this.strict);
|
|
195
|
+
|
|
196
|
+
const result = {
|
|
197
|
+
valid,
|
|
198
|
+
templateId,
|
|
199
|
+
templateName: template.name,
|
|
200
|
+
errors,
|
|
201
|
+
warnings,
|
|
202
|
+
markers,
|
|
203
|
+
sections: sections.map(s => s.name),
|
|
204
|
+
score,
|
|
205
|
+
timestamp: new Date().toISOString()
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
this.validationHistory.push(result);
|
|
209
|
+
this.emit('validated', result);
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Extract sections from content
|
|
216
|
+
* @param {string} content
|
|
217
|
+
* @returns {Array}
|
|
218
|
+
*/
|
|
219
|
+
extractSections(content) {
|
|
220
|
+
const sections = [];
|
|
221
|
+
const headerRegex = /^(#{1,3})\s+(.+?)$/gm;
|
|
222
|
+
let match;
|
|
223
|
+
const matches = [];
|
|
224
|
+
|
|
225
|
+
while ((match = headerRegex.exec(content)) !== null) {
|
|
226
|
+
matches.push({
|
|
227
|
+
level: match[1].length,
|
|
228
|
+
name: match[2].trim(),
|
|
229
|
+
index: match.index
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (let i = 0; i < matches.length; i++) {
|
|
234
|
+
const start = matches[i].index;
|
|
235
|
+
const end = i < matches.length - 1 ? matches[i + 1].index : content.length;
|
|
236
|
+
const sectionContent = content.slice(start, end).trim();
|
|
237
|
+
|
|
238
|
+
sections.push({
|
|
239
|
+
level: matches[i].level,
|
|
240
|
+
name: matches[i].name,
|
|
241
|
+
content: sectionContent,
|
|
242
|
+
length: sectionContent.length
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return sections;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Extract uncertainty and decision markers
|
|
251
|
+
* @param {string} content
|
|
252
|
+
* @returns {Array}
|
|
253
|
+
*/
|
|
254
|
+
extractMarkers(content) {
|
|
255
|
+
const markers = [];
|
|
256
|
+
const markerPatterns = {
|
|
257
|
+
[MARKER_TYPE.ASSUMPTION]: /\[ASSUMPTION\]:\s*(.+?)(?:\n|$)/gi,
|
|
258
|
+
[MARKER_TYPE.DECISION]: /\[DECISION\]:\s*(.+?)(?:\n|$)/gi,
|
|
259
|
+
[MARKER_TYPE.RISK]: /\[RISK\]:\s*(.+?)(?:\n|$)/gi,
|
|
260
|
+
[MARKER_TYPE.TODO]: /\[TODO\]:\s*(.+?)(?:\n|$)/gi,
|
|
261
|
+
[MARKER_TYPE.QUESTION]: /\[QUESTION\]:\s*(.+?)(?:\n|$)/gi,
|
|
262
|
+
[MARKER_TYPE.VERIFIED]: /\[VERIFIED\]:\s*(.+?)(?:\n|$)/gi,
|
|
263
|
+
[MARKER_TYPE.UNCERTAIN]: /\[UNCERTAIN(?:\s*:\s*(\d+)%)?\]:\s*(.+?)(?:\n|$)/gi
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
for (const [type, pattern] of Object.entries(markerPatterns)) {
|
|
267
|
+
let match;
|
|
268
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
269
|
+
if (type === MARKER_TYPE.UNCERTAIN) {
|
|
270
|
+
markers.push({
|
|
271
|
+
type,
|
|
272
|
+
confidence: match[1] ? parseInt(match[1]) : 50,
|
|
273
|
+
text: match[2].trim(),
|
|
274
|
+
index: match.index
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
markers.push({
|
|
278
|
+
type,
|
|
279
|
+
text: match[1].trim(),
|
|
280
|
+
index: match.index
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return markers.sort((a, b) => a.index - b.index);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Validate content format
|
|
291
|
+
* @param {string} content
|
|
292
|
+
* @param {string} format
|
|
293
|
+
* @returns {boolean}
|
|
294
|
+
*/
|
|
295
|
+
validateFormat(content, format) {
|
|
296
|
+
const formatValidators = {
|
|
297
|
+
ears: (c) => {
|
|
298
|
+
// EARS format: When/While/If/Where patterns
|
|
299
|
+
return /\b(when|while|if|where|shall|should|must)\b/i.test(c);
|
|
300
|
+
},
|
|
301
|
+
c4: (c) => {
|
|
302
|
+
// C4 model references
|
|
303
|
+
return /\b(system|container|component|context|boundary)\b/i.test(c);
|
|
304
|
+
},
|
|
305
|
+
adr: (c) => {
|
|
306
|
+
// ADR format: Status, Context, Decision, Consequences
|
|
307
|
+
return /\b(status|context|decision|consequences|accepted|proposed|deprecated)\b/i.test(c);
|
|
308
|
+
},
|
|
309
|
+
checklist: (c) => {
|
|
310
|
+
// Checklist format
|
|
311
|
+
return /^\s*[-*\[\]]\s+/m.test(c);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const validator = formatValidators[format];
|
|
316
|
+
return validator ? validator(content) : true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Calculate validation score
|
|
321
|
+
*/
|
|
322
|
+
calculateScore(template, errors, warnings) {
|
|
323
|
+
let score = 100;
|
|
324
|
+
const totalSections = template.sections.length;
|
|
325
|
+
const requiredSections = template.sections.filter(s => s.required).length;
|
|
326
|
+
|
|
327
|
+
// Deduct for errors
|
|
328
|
+
const missingSections = errors.filter(e => e.type === 'missing_section').length;
|
|
329
|
+
score -= (missingSections / requiredSections) * 50;
|
|
330
|
+
|
|
331
|
+
// Deduct for warnings
|
|
332
|
+
score -= warnings.length * 5;
|
|
333
|
+
|
|
334
|
+
// Deduct for other errors
|
|
335
|
+
const otherErrors = errors.filter(e => e.type !== 'missing_section').length;
|
|
336
|
+
score -= otherErrors * 10;
|
|
337
|
+
|
|
338
|
+
return Math.max(0, Math.min(100, Math.round(score)));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Add a template
|
|
343
|
+
* @param {string} id
|
|
344
|
+
* @param {Object} template
|
|
345
|
+
*/
|
|
346
|
+
addTemplate(id, template) {
|
|
347
|
+
if (!template.name || !template.sections) {
|
|
348
|
+
throw new Error('Template must have name and sections');
|
|
349
|
+
}
|
|
350
|
+
this.templates[id] = template;
|
|
351
|
+
this.emit('template-added', { id, template });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Remove a template
|
|
356
|
+
* @param {string} id
|
|
357
|
+
* @returns {boolean}
|
|
358
|
+
*/
|
|
359
|
+
removeTemplate(id) {
|
|
360
|
+
if (this.templates[id]) {
|
|
361
|
+
delete this.templates[id];
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Add a custom constraint
|
|
369
|
+
* @param {string} name
|
|
370
|
+
* @param {Object} constraint
|
|
371
|
+
*/
|
|
372
|
+
addConstraint(name, constraint) {
|
|
373
|
+
if (!constraint.validate || typeof constraint.validate !== 'function') {
|
|
374
|
+
throw new Error('Constraint must have a validate function');
|
|
375
|
+
}
|
|
376
|
+
this.customConstraints.set(name, {
|
|
377
|
+
severity: 'warning',
|
|
378
|
+
...constraint
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Remove a constraint
|
|
384
|
+
* @param {string} name
|
|
385
|
+
* @returns {boolean}
|
|
386
|
+
*/
|
|
387
|
+
removeConstraint(name) {
|
|
388
|
+
return this.customConstraints.delete(name);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get validation history
|
|
393
|
+
* @param {Object} filter
|
|
394
|
+
* @returns {Array}
|
|
395
|
+
*/
|
|
396
|
+
getHistory(filter = {}) {
|
|
397
|
+
let history = [...this.validationHistory];
|
|
398
|
+
|
|
399
|
+
if (filter.templateId) {
|
|
400
|
+
history = history.filter(h => h.templateId === filter.templateId);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (filter.valid !== undefined) {
|
|
404
|
+
history = history.filter(h => h.valid === filter.valid);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (filter.minScore !== undefined) {
|
|
408
|
+
history = history.filter(h => h.score >= filter.minScore);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return history;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Get statistics
|
|
416
|
+
* @returns {Object}
|
|
417
|
+
*/
|
|
418
|
+
getStats() {
|
|
419
|
+
const total = this.validationHistory.length;
|
|
420
|
+
const valid = this.validationHistory.filter(h => h.valid).length;
|
|
421
|
+
const avgScore = total > 0
|
|
422
|
+
? this.validationHistory.reduce((sum, h) => sum + h.score, 0) / total
|
|
423
|
+
: 0;
|
|
424
|
+
|
|
425
|
+
const byTemplate = {};
|
|
426
|
+
for (const h of this.validationHistory) {
|
|
427
|
+
if (!byTemplate[h.templateId]) {
|
|
428
|
+
byTemplate[h.templateId] = { total: 0, valid: 0, totalScore: 0 };
|
|
429
|
+
}
|
|
430
|
+
byTemplate[h.templateId].total++;
|
|
431
|
+
if (h.valid) byTemplate[h.templateId].valid++;
|
|
432
|
+
byTemplate[h.templateId].totalScore += h.score;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
for (const id in byTemplate) {
|
|
436
|
+
byTemplate[id].avgScore = byTemplate[id].totalScore / byTemplate[id].total;
|
|
437
|
+
delete byTemplate[id].totalScore;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
total,
|
|
442
|
+
valid,
|
|
443
|
+
invalid: total - valid,
|
|
444
|
+
validRate: total > 0 ? (valid / total) * 100 : 0,
|
|
445
|
+
avgScore: Math.round(avgScore),
|
|
446
|
+
byTemplate,
|
|
447
|
+
templateCount: Object.keys(this.templates).length,
|
|
448
|
+
constraintCount: this.customConstraints.size
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* List available templates
|
|
454
|
+
* @returns {Array}
|
|
455
|
+
*/
|
|
456
|
+
listTemplates() {
|
|
457
|
+
return Object.entries(this.templates).map(([id, template]) => ({
|
|
458
|
+
id,
|
|
459
|
+
name: template.name,
|
|
460
|
+
sections: template.sections.length,
|
|
461
|
+
requiredSections: template.sections.filter(s => s.required).length,
|
|
462
|
+
markers: template.markers || []
|
|
463
|
+
}));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Clear history
|
|
468
|
+
*/
|
|
469
|
+
clearHistory() {
|
|
470
|
+
this.validationHistory = [];
|
|
471
|
+
this.emit('history-cleared');
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Structured Thinking Checklist
|
|
477
|
+
*/
|
|
478
|
+
class ThinkingChecklist extends EventEmitter {
|
|
479
|
+
/**
|
|
480
|
+
* @param {Object} options
|
|
481
|
+
* @param {Array} options.items - Checklist items
|
|
482
|
+
* @param {string} options.name - Checklist name
|
|
483
|
+
*/
|
|
484
|
+
constructor(options = {}) {
|
|
485
|
+
super();
|
|
486
|
+
|
|
487
|
+
this.name = options.name || 'Thinking Checklist';
|
|
488
|
+
this.items = options.items || this.getDefaultItems();
|
|
489
|
+
this.completedItems = new Set();
|
|
490
|
+
this.notes = new Map();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Get default checklist items
|
|
495
|
+
*/
|
|
496
|
+
getDefaultItems() {
|
|
497
|
+
return [
|
|
498
|
+
{ id: 'understand', category: 'Analysis', text: 'Do I fully understand the requirements?' },
|
|
499
|
+
{ id: 'assumptions', category: 'Analysis', text: 'What assumptions am I making?' },
|
|
500
|
+
{ id: 'constraints', category: 'Analysis', text: 'What constraints exist?' },
|
|
501
|
+
{ id: 'alternatives', category: 'Design', text: 'Have I considered alternatives?' },
|
|
502
|
+
{ id: 'tradeoffs', category: 'Design', text: 'What are the tradeoffs?' },
|
|
503
|
+
{ id: 'edge-cases', category: 'Design', text: 'Have I considered edge cases?' },
|
|
504
|
+
{ id: 'risks', category: 'Risk', text: 'What could go wrong?' },
|
|
505
|
+
{ id: 'dependencies', category: 'Risk', text: 'What are the dependencies?' },
|
|
506
|
+
{ id: 'testing', category: 'Quality', text: 'How will this be tested?' },
|
|
507
|
+
{ id: 'maintainability', category: 'Quality', text: 'Is this maintainable?' }
|
|
508
|
+
];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Mark item as complete
|
|
513
|
+
* @param {string} itemId
|
|
514
|
+
* @param {string} note
|
|
515
|
+
*/
|
|
516
|
+
complete(itemId, note = '') {
|
|
517
|
+
if (!this.items.find(i => i.id === itemId)) {
|
|
518
|
+
throw new Error(`Unknown item: ${itemId}`);
|
|
519
|
+
}
|
|
520
|
+
this.completedItems.add(itemId);
|
|
521
|
+
if (note) {
|
|
522
|
+
this.notes.set(itemId, note);
|
|
523
|
+
}
|
|
524
|
+
this.emit('item-completed', { itemId, note });
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Unmark item
|
|
529
|
+
* @param {string} itemId
|
|
530
|
+
*/
|
|
531
|
+
uncomplete(itemId) {
|
|
532
|
+
this.completedItems.delete(itemId);
|
|
533
|
+
this.notes.delete(itemId);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Get progress
|
|
538
|
+
* @returns {Object}
|
|
539
|
+
*/
|
|
540
|
+
getProgress() {
|
|
541
|
+
const total = this.items.length;
|
|
542
|
+
const completed = this.completedItems.size;
|
|
543
|
+
const remaining = this.items.filter(i => !this.completedItems.has(i.id));
|
|
544
|
+
|
|
545
|
+
const byCategory = {};
|
|
546
|
+
for (const item of this.items) {
|
|
547
|
+
if (!byCategory[item.category]) {
|
|
548
|
+
byCategory[item.category] = { total: 0, completed: 0 };
|
|
549
|
+
}
|
|
550
|
+
byCategory[item.category].total++;
|
|
551
|
+
if (this.completedItems.has(item.id)) {
|
|
552
|
+
byCategory[item.category].completed++;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
total,
|
|
558
|
+
completed,
|
|
559
|
+
remaining: remaining.map(i => i.id),
|
|
560
|
+
percentage: Math.round((completed / total) * 100),
|
|
561
|
+
byCategory,
|
|
562
|
+
isComplete: completed === total
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Export as markdown
|
|
568
|
+
* @returns {string}
|
|
569
|
+
*/
|
|
570
|
+
toMarkdown() {
|
|
571
|
+
let md = `# ${this.name}\n\n`;
|
|
572
|
+
|
|
573
|
+
const byCategory = {};
|
|
574
|
+
for (const item of this.items) {
|
|
575
|
+
if (!byCategory[item.category]) {
|
|
576
|
+
byCategory[item.category] = [];
|
|
577
|
+
}
|
|
578
|
+
byCategory[item.category].push(item);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
for (const [category, items] of Object.entries(byCategory)) {
|
|
582
|
+
md += `## ${category}\n\n`;
|
|
583
|
+
for (const item of items) {
|
|
584
|
+
const checked = this.completedItems.has(item.id) ? 'x' : ' ';
|
|
585
|
+
md += `- [${checked}] ${item.text}\n`;
|
|
586
|
+
if (this.notes.has(item.id)) {
|
|
587
|
+
md += ` - Note: ${this.notes.get(item.id)}\n`;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
md += '\n';
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const progress = this.getProgress();
|
|
594
|
+
md += `---\nProgress: ${progress.completed}/${progress.total} (${progress.percentage}%)\n`;
|
|
595
|
+
|
|
596
|
+
return md;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Add custom item
|
|
601
|
+
* @param {Object} item
|
|
602
|
+
*/
|
|
603
|
+
addItem(item) {
|
|
604
|
+
if (!item.id || !item.text) {
|
|
605
|
+
throw new Error('Item must have id and text');
|
|
606
|
+
}
|
|
607
|
+
this.items.push({
|
|
608
|
+
category: 'Custom',
|
|
609
|
+
...item
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Reset checklist
|
|
615
|
+
*/
|
|
616
|
+
reset() {
|
|
617
|
+
this.completedItems.clear();
|
|
618
|
+
this.notes.clear();
|
|
619
|
+
this.emit('reset');
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Factory function
|
|
625
|
+
*/
|
|
626
|
+
function createTemplateConstraints(options = {}) {
|
|
627
|
+
return new TemplateConstraints(options);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Factory function for checklist
|
|
632
|
+
*/
|
|
633
|
+
function createThinkingChecklist(options = {}) {
|
|
634
|
+
return new ThinkingChecklist(options);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
module.exports = {
|
|
638
|
+
TemplateConstraints,
|
|
639
|
+
ThinkingChecklist,
|
|
640
|
+
createTemplateConstraints,
|
|
641
|
+
createThinkingChecklist,
|
|
642
|
+
CONSTRAINT_TYPE,
|
|
643
|
+
UNCERTAINTY,
|
|
644
|
+
MARKER_TYPE,
|
|
645
|
+
DEFAULT_TEMPLATES
|
|
646
|
+
};
|