musubi-sdd 5.0.0 → 5.6.1
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 +106 -48
- package/README.md +110 -32
- package/bin/musubi-analyze.js +74 -67
- package/bin/musubi-browser.js +27 -26
- package/bin/musubi-change.js +48 -47
- package/bin/musubi-checkpoint.js +10 -7
- package/bin/musubi-convert.js +25 -25
- package/bin/musubi-costs.js +27 -10
- package/bin/musubi-gui.js +52 -46
- package/bin/musubi-init.js +1952 -10
- package/bin/musubi-orchestrate.js +327 -239
- package/bin/musubi-remember.js +69 -56
- package/bin/musubi-resolve.js +53 -45
- package/bin/musubi-trace.js +51 -22
- package/bin/musubi-validate.js +39 -30
- package/bin/musubi-workflow.js +33 -34
- package/bin/musubi.js +39 -2
- package/package.json +1 -1
- package/src/agents/agent-loop.js +94 -95
- package/src/agents/agentic/code-generator.js +119 -109
- package/src/agents/agentic/code-reviewer.js +105 -108
- package/src/agents/agentic/index.js +4 -4
- package/src/agents/browser/action-executor.js +13 -13
- package/src/agents/browser/ai-comparator.js +11 -10
- package/src/agents/browser/context-manager.js +6 -6
- package/src/agents/browser/index.js +5 -5
- package/src/agents/browser/nl-parser.js +31 -46
- package/src/agents/browser/screenshot.js +2 -2
- package/src/agents/browser/test-generator.js +6 -4
- package/src/agents/function-tool.js +71 -65
- package/src/agents/index.js +7 -7
- package/src/agents/schema-generator.js +98 -94
- package/src/analyzers/ast-extractor.js +164 -145
- package/src/analyzers/codegraph-auto-update.js +858 -0
- package/src/analyzers/complexity-analyzer.js +536 -0
- package/src/analyzers/context-optimizer.js +247 -125
- package/src/analyzers/impact-analyzer.js +1 -1
- package/src/analyzers/large-project-analyzer.js +766 -0
- package/src/analyzers/repository-map.js +83 -80
- package/src/analyzers/security-analyzer.js +19 -11
- package/src/analyzers/stuck-detector.js +19 -17
- package/src/converters/index.js +78 -57
- package/src/converters/ir/types.js +12 -12
- package/src/converters/parsers/musubi-parser.js +134 -126
- package/src/converters/parsers/openapi-parser.js +70 -53
- package/src/converters/parsers/speckit-parser.js +239 -175
- package/src/converters/writers/musubi-writer.js +123 -118
- package/src/converters/writers/speckit-writer.js +124 -113
- package/src/generators/rust-migration-generator.js +512 -0
- package/src/gui/public/index.html +1365 -1211
- package/src/gui/server.js +41 -40
- package/src/gui/services/file-watcher.js +23 -8
- package/src/gui/services/project-scanner.js +26 -20
- package/src/gui/services/replanning-service.js +27 -23
- package/src/gui/services/traceability-service.js +8 -8
- package/src/gui/services/workflow-service.js +14 -7
- package/src/index.js +151 -0
- package/src/integrations/cicd.js +90 -104
- package/src/integrations/codegraph-mcp.js +643 -0
- package/src/integrations/documentation.js +142 -103
- package/src/integrations/examples.js +95 -80
- package/src/integrations/github-client.js +17 -17
- package/src/integrations/index.js +5 -5
- package/src/integrations/mcp/index.js +21 -21
- package/src/integrations/mcp/mcp-context-provider.js +76 -78
- package/src/integrations/mcp/mcp-discovery.js +74 -72
- package/src/integrations/mcp/mcp-tool-registry.js +99 -94
- package/src/integrations/mcp-connector.js +70 -66
- package/src/integrations/platforms.js +50 -49
- package/src/integrations/tool-discovery.js +37 -31
- package/src/llm-providers/anthropic-provider.js +11 -11
- package/src/llm-providers/base-provider.js +16 -18
- package/src/llm-providers/copilot-provider.js +22 -19
- package/src/llm-providers/index.js +26 -25
- package/src/llm-providers/ollama-provider.js +11 -11
- package/src/llm-providers/openai-provider.js +12 -12
- package/src/managers/agent-memory.js +36 -24
- package/src/managers/checkpoint-manager.js +4 -8
- package/src/managers/delta-spec.js +19 -19
- package/src/managers/index.js +13 -4
- package/src/managers/memory-condenser.js +35 -45
- package/src/managers/repo-skill-manager.js +57 -31
- package/src/managers/skill-loader.js +25 -22
- package/src/managers/skill-tools.js +36 -72
- package/src/managers/workflow.js +30 -22
- package/src/monitoring/cost-tracker.js +53 -44
- package/src/monitoring/incident-manager.js +123 -103
- package/src/monitoring/index.js +144 -134
- package/src/monitoring/observability.js +82 -59
- package/src/monitoring/quality-dashboard.js +51 -39
- package/src/monitoring/release-manager.js +70 -50
- package/src/orchestration/agent-skill-binding.js +39 -47
- package/src/orchestration/error-handler.js +65 -107
- package/src/orchestration/guardrails/base-guardrail.js +26 -24
- package/src/orchestration/guardrails/guardrail-rules.js +50 -64
- package/src/orchestration/guardrails/index.js +5 -5
- package/src/orchestration/guardrails/input-guardrail.js +58 -45
- package/src/orchestration/guardrails/output-guardrail.js +104 -81
- package/src/orchestration/guardrails/safety-check.js +79 -79
- package/src/orchestration/index.js +38 -55
- package/src/orchestration/mcp-tool-adapters.js +96 -99
- package/src/orchestration/orchestration-engine.js +21 -21
- package/src/orchestration/pattern-registry.js +60 -45
- package/src/orchestration/patterns/auto.js +34 -47
- package/src/orchestration/patterns/group-chat.js +59 -65
- package/src/orchestration/patterns/handoff.js +67 -65
- package/src/orchestration/patterns/human-in-loop.js +51 -72
- package/src/orchestration/patterns/nested.js +25 -40
- package/src/orchestration/patterns/sequential.js +35 -34
- package/src/orchestration/patterns/swarm.js +63 -56
- package/src/orchestration/patterns/triage.js +150 -109
- package/src/orchestration/reasoning/index.js +9 -9
- package/src/orchestration/reasoning/planning-engine.js +143 -140
- package/src/orchestration/reasoning/reasoning-engine.js +206 -144
- package/src/orchestration/reasoning/self-correction.js +121 -128
- package/src/orchestration/replanning/adaptive-goal-modifier.js +107 -112
- package/src/orchestration/replanning/alternative-generator.js +37 -42
- package/src/orchestration/replanning/config.js +63 -59
- package/src/orchestration/replanning/goal-progress-tracker.js +98 -100
- package/src/orchestration/replanning/index.js +24 -20
- package/src/orchestration/replanning/plan-evaluator.js +49 -50
- package/src/orchestration/replanning/plan-monitor.js +32 -28
- package/src/orchestration/replanning/proactive-path-optimizer.js +175 -178
- package/src/orchestration/replanning/replan-history.js +33 -26
- package/src/orchestration/replanning/replanning-engine.js +106 -108
- package/src/orchestration/skill-executor.js +107 -109
- package/src/orchestration/skill-registry.js +85 -89
- package/src/orchestration/workflow-examples.js +228 -231
- package/src/orchestration/workflow-executor.js +65 -68
- package/src/orchestration/workflow-orchestrator.js +72 -73
- package/src/phase4-integration.js +47 -40
- package/src/phase5-integration.js +89 -30
- package/src/reporters/coverage-report.js +82 -30
- package/src/reporters/hierarchical-reporter.js +498 -0
- package/src/reporters/traceability-matrix-report.js +29 -20
- package/src/resolvers/issue-resolver.js +43 -31
- package/src/steering/advanced-validation.js +133 -124
- package/src/steering/auto-updater.js +60 -73
- package/src/steering/index.js +6 -6
- package/src/steering/quality-metrics.js +41 -35
- package/src/steering/steering-auto-update.js +83 -86
- package/src/steering/steering-validator.js +98 -106
- package/src/steering/template-constraints.js +53 -54
- package/src/templates/agents/claude-code/CLAUDE.md +32 -32
- package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +13 -5
- package/src/templates/agents/claude-code/skills/ai-ml-engineer/mlops-guide.md +23 -23
- package/src/templates/agents/claude-code/skills/ai-ml-engineer/model-card-template.md +60 -41
- package/src/templates/agents/claude-code/skills/api-designer/api-patterns.md +27 -19
- package/src/templates/agents/claude-code/skills/api-designer/openapi-template.md +11 -7
- package/src/templates/agents/claude-code/skills/bug-hunter/SKILL.md +4 -3
- package/src/templates/agents/claude-code/skills/bug-hunter/root-cause-analysis.md +37 -15
- package/src/templates/agents/claude-code/skills/change-impact-analyzer/dependency-graph-patterns.md +36 -42
- package/src/templates/agents/claude-code/skills/change-impact-analyzer/impact-analysis-template.md +69 -60
- package/src/templates/agents/claude-code/skills/cloud-architect/aws-patterns.md +31 -38
- package/src/templates/agents/claude-code/skills/cloud-architect/azure-patterns.md +28 -23
- package/src/templates/agents/claude-code/skills/code-reviewer/SKILL.md +61 -0
- package/src/templates/agents/claude-code/skills/code-reviewer/best-practices.md +27 -0
- package/src/templates/agents/claude-code/skills/code-reviewer/review-checklist.md +29 -10
- package/src/templates/agents/claude-code/skills/code-reviewer/review-standards.md +29 -24
- package/src/templates/agents/claude-code/skills/constitution-enforcer/SKILL.md +8 -6
- package/src/templates/agents/claude-code/skills/constitution-enforcer/constitutional-articles.md +62 -26
- package/src/templates/agents/claude-code/skills/constitution-enforcer/phase-minus-one-gates.md +35 -16
- package/src/templates/agents/claude-code/skills/database-administrator/backup-recovery.md +27 -17
- package/src/templates/agents/claude-code/skills/database-administrator/tuning-guide.md +25 -20
- package/src/templates/agents/claude-code/skills/database-schema-designer/schema-patterns.md +39 -22
- package/src/templates/agents/claude-code/skills/devops-engineer/ci-cd-templates.md +25 -22
- package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +24 -21
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +148 -63
- package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +35 -16
- package/src/templates/agents/claude-code/skills/orchestrator/selection-matrix.md +69 -64
- package/src/templates/agents/claude-code/skills/performance-engineer/optimization-playbook.md +47 -47
- package/src/templates/agents/claude-code/skills/performance-optimizer/SKILL.md +69 -0
- package/src/templates/agents/claude-code/skills/performance-optimizer/benchmark-template.md +63 -45
- package/src/templates/agents/claude-code/skills/performance-optimizer/optimization-patterns.md +33 -35
- package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +7 -6
- package/src/templates/agents/claude-code/skills/project-manager/agile-ceremonies.md +47 -28
- package/src/templates/agents/claude-code/skills/project-manager/project-templates.md +94 -78
- package/src/templates/agents/claude-code/skills/quality-assurance/SKILL.md +20 -17
- package/src/templates/agents/claude-code/skills/quality-assurance/qa-plan-template.md +63 -49
- package/src/templates/agents/claude-code/skills/release-coordinator/SKILL.md +5 -5
- package/src/templates/agents/claude-code/skills/release-coordinator/feature-flag-guide.md +30 -26
- package/src/templates/agents/claude-code/skills/release-coordinator/release-plan-template.md +67 -35
- package/src/templates/agents/claude-code/skills/requirements-analyst/ears-format.md +54 -42
- package/src/templates/agents/claude-code/skills/requirements-analyst/validation-rules.md +36 -33
- package/src/templates/agents/claude-code/skills/security-auditor/SKILL.md +77 -19
- package/src/templates/agents/claude-code/skills/security-auditor/audit-checklists.md +24 -24
- package/src/templates/agents/claude-code/skills/security-auditor/owasp-top-10.md +61 -20
- package/src/templates/agents/claude-code/skills/security-auditor/vulnerability-patterns.md +43 -11
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/incident-response-template.md +55 -25
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/observability-patterns.md +78 -68
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/slo-sli-guide.md +73 -53
- package/src/templates/agents/claude-code/skills/software-developer/solid-principles.md +83 -37
- package/src/templates/agents/claude-code/skills/software-developer/test-first-workflow.md +38 -31
- package/src/templates/agents/claude-code/skills/steering/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/steering/auto-update-rules.md +31 -0
- package/src/templates/agents/claude-code/skills/system-architect/adr-template.md +25 -7
- package/src/templates/agents/claude-code/skills/system-architect/c4-model-guide.md +74 -61
- package/src/templates/agents/claude-code/skills/technical-writer/doc-templates/documentation-templates.md +70 -52
- package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +2 -0
- package/src/templates/agents/claude-code/skills/test-engineer/ears-test-mapping.md +75 -71
- package/src/templates/agents/claude-code/skills/test-engineer/test-types.md +85 -63
- package/src/templates/agents/claude-code/skills/traceability-auditor/coverage-matrix-template.md +39 -36
- package/src/templates/agents/claude-code/skills/traceability-auditor/gap-detection-rules.md +22 -17
- package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/ui-ux-designer/accessibility-guidelines.md +49 -75
- package/src/templates/agents/claude-code/skills/ui-ux-designer/design-system-components.md +71 -59
- package/src/templates/agents/codex/AGENTS.md +74 -42
- package/src/templates/agents/cursor/AGENTS.md +74 -42
- package/src/templates/agents/gemini-cli/GEMINI.md +74 -42
- package/src/templates/agents/github-copilot/AGENTS.md +83 -51
- package/src/templates/agents/qwen-code/QWEN.md +74 -42
- package/src/templates/agents/windsurf/AGENTS.md +74 -42
- package/src/templates/architectures/README.md +41 -0
- package/src/templates/architectures/clean-architecture/README.md +113 -0
- package/src/templates/architectures/event-driven/README.md +162 -0
- package/src/templates/architectures/hexagonal/README.md +130 -0
- package/src/templates/index.js +6 -1
- package/src/templates/locale-manager.js +16 -16
- package/src/templates/shared/delta-spec-template.md +20 -13
- package/src/templates/shared/github-actions/musubi-issue-resolver.yml +5 -5
- package/src/templates/shared/github-actions/musubi-security-check.yml +3 -3
- package/src/templates/shared/github-actions/musubi-validate.yml +4 -4
- package/src/templates/shared/steering/structure.md +95 -0
- package/src/templates/skills/browser-agent.md +21 -16
- package/src/templates/skills/web-gui.md +8 -0
- package/src/templates/template-constraints.js +50 -53
- package/src/validators/advanced-validation.js +30 -36
- package/src/validators/constitutional-validator.js +77 -73
- package/src/validators/critic-system.js +49 -59
- package/src/validators/delta-format.js +59 -55
- package/src/validators/traceability-validator.js +7 -11
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MUSUBI Stuck Detector
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* AIエージェントのスタック状態(無限ループ、繰り返しエラー)を検出
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @module src/analyzers/stuck-detector
|
|
7
7
|
* @see REQ-P0-B001
|
|
8
8
|
* @inspired-by OpenHands openhands/controller/stuck.py
|
|
@@ -52,7 +52,7 @@ const Severity = {
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* イベントのハッシュを生成
|
|
55
|
-
* @param {Object} event
|
|
55
|
+
* @param {Object} event
|
|
56
56
|
* @returns {string}
|
|
57
57
|
*/
|
|
58
58
|
function hashEvent(event) {
|
|
@@ -131,7 +131,7 @@ class StuckDetector {
|
|
|
131
131
|
this.maxMonologueSteps = options.maxMonologueSteps || 10;
|
|
132
132
|
this.maxContextErrors = options.maxContextErrors || 3;
|
|
133
133
|
this.maxStageOscillations = options.maxStageOscillations || 3;
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
this.history = [];
|
|
136
136
|
this.stuckAnalysis = null;
|
|
137
137
|
}
|
|
@@ -252,11 +252,11 @@ class StuckDetector {
|
|
|
252
252
|
|
|
253
253
|
const lastN = this.history.slice(-this.maxRepeatErrors);
|
|
254
254
|
const allErrors = lastN.every(e => e.type === EventType.ERROR);
|
|
255
|
-
|
|
255
|
+
|
|
256
256
|
if (allErrors) {
|
|
257
257
|
const firstHash = lastN[0].hash;
|
|
258
258
|
const sameError = lastN.every(e => e.hash === firstHash);
|
|
259
|
-
|
|
259
|
+
|
|
260
260
|
if (sameError) {
|
|
261
261
|
return new StuckAnalysis({
|
|
262
262
|
loopType: LoopType.ERROR_LOOP,
|
|
@@ -282,10 +282,11 @@ class StuckDetector {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
const lastN = this.history.slice(-this.maxMonologueSteps);
|
|
285
|
-
const allMessages = lastN.every(
|
|
286
|
-
e
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
const allMessages = lastN.every(
|
|
286
|
+
e =>
|
|
287
|
+
e.type === EventType.MESSAGE &&
|
|
288
|
+
!e.content.includes('```') && // コードブロックなし
|
|
289
|
+
e.content.length < 500 // 短いメッセージ
|
|
289
290
|
);
|
|
290
291
|
|
|
291
292
|
if (allMessages) {
|
|
@@ -320,11 +321,12 @@ class StuckDetector {
|
|
|
320
321
|
];
|
|
321
322
|
|
|
322
323
|
const lastN = this.history.slice(-this.maxContextErrors);
|
|
323
|
-
const allContextErrors = lastN.every(
|
|
324
|
-
e
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
324
|
+
const allContextErrors = lastN.every(
|
|
325
|
+
e =>
|
|
326
|
+
e.type === EventType.ERROR &&
|
|
327
|
+
contextErrorPatterns.some(pattern =>
|
|
328
|
+
e.content.toLowerCase().includes(pattern.toLowerCase())
|
|
329
|
+
)
|
|
328
330
|
);
|
|
329
331
|
|
|
330
332
|
if (allContextErrors) {
|
|
@@ -353,7 +355,7 @@ class StuckDetector {
|
|
|
353
355
|
|
|
354
356
|
const lastN = this.history.slice(-minEvents);
|
|
355
357
|
const stages = lastN.map(e => e.stage);
|
|
356
|
-
|
|
358
|
+
|
|
357
359
|
// 2つのステージ間を往復しているかチェック
|
|
358
360
|
const uniqueStages = [...new Set(stages)];
|
|
359
361
|
if (uniqueStages.length !== 2) {
|
|
@@ -383,7 +385,7 @@ class StuckDetector {
|
|
|
383
385
|
|
|
384
386
|
/**
|
|
385
387
|
* 代替アプローチを提案
|
|
386
|
-
* @param {string} loopType
|
|
388
|
+
* @param {string} loopType
|
|
387
389
|
* @returns {string[]}
|
|
388
390
|
*/
|
|
389
391
|
_suggestAlternatives(loopType) {
|
package/src/converters/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Converters Module
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Cross-format conversion between MUSUBI and Spec Kit
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -20,21 +20,27 @@ const irTypes = require('./ir/types');
|
|
|
20
20
|
* @returns {Promise<{filesConverted: number, warnings: string[], outputPath: string}>}
|
|
21
21
|
*/
|
|
22
22
|
async function convertFromSpeckit(sourcePath, options = {}) {
|
|
23
|
-
const {
|
|
24
|
-
|
|
23
|
+
const {
|
|
24
|
+
output = '.',
|
|
25
|
+
dryRun = false,
|
|
26
|
+
force = false,
|
|
27
|
+
verbose = false,
|
|
28
|
+
preserveRaw = false,
|
|
29
|
+
} = options;
|
|
30
|
+
|
|
25
31
|
if (verbose) console.log(`Converting Spec Kit project from: ${sourcePath}`);
|
|
26
|
-
|
|
32
|
+
|
|
27
33
|
// Parse Spec Kit project to IR
|
|
28
34
|
const ir = await parseSpeckitProject(sourcePath);
|
|
29
|
-
|
|
35
|
+
|
|
30
36
|
if (verbose) {
|
|
31
37
|
console.log(` Found ${ir.features.length} features`);
|
|
32
38
|
console.log(` Found ${ir.constitution.articles.length} constitution articles`);
|
|
33
39
|
}
|
|
34
|
-
|
|
40
|
+
|
|
35
41
|
// Write to MUSUBI format
|
|
36
42
|
const result = await writeMusubiProject(ir, output, { dryRun, force, preserveRaw, verbose });
|
|
37
|
-
|
|
43
|
+
|
|
38
44
|
return {
|
|
39
45
|
filesConverted: result.filesWritten,
|
|
40
46
|
warnings: result.warnings,
|
|
@@ -48,28 +54,33 @@ async function convertFromSpeckit(sourcePath, options = {}) {
|
|
|
48
54
|
* @returns {Promise<{filesConverted: number, warnings: string[], outputPath: string}>}
|
|
49
55
|
*/
|
|
50
56
|
async function convertToSpeckit(options = {}) {
|
|
51
|
-
const {
|
|
52
|
-
source = '.',
|
|
53
|
-
output = './.specify',
|
|
54
|
-
dryRun = false,
|
|
55
|
-
force = false,
|
|
56
|
-
verbose = false,
|
|
57
|
-
preserveRaw = false
|
|
57
|
+
const {
|
|
58
|
+
source = '.',
|
|
59
|
+
output = './.specify',
|
|
60
|
+
dryRun = false,
|
|
61
|
+
force = false,
|
|
62
|
+
verbose = false,
|
|
63
|
+
preserveRaw = false,
|
|
58
64
|
} = options;
|
|
59
|
-
|
|
65
|
+
|
|
60
66
|
if (verbose) console.log(`Converting MUSUBI project to Spec Kit format`);
|
|
61
|
-
|
|
67
|
+
|
|
62
68
|
// Parse MUSUBI project to IR
|
|
63
69
|
const ir = await parseMusubiProject(source);
|
|
64
|
-
|
|
70
|
+
|
|
65
71
|
if (verbose) {
|
|
66
72
|
console.log(` Found ${ir.features.length} features`);
|
|
67
73
|
console.log(` Found ${ir.constitution.articles.length} constitution articles`);
|
|
68
74
|
}
|
|
69
|
-
|
|
75
|
+
|
|
70
76
|
// Write to Spec Kit format
|
|
71
|
-
const result = await writeSpeckitProject(ir, output.replace('/.specify', ''), {
|
|
72
|
-
|
|
77
|
+
const result = await writeSpeckitProject(ir, output.replace('/.specify', ''), {
|
|
78
|
+
dryRun,
|
|
79
|
+
force,
|
|
80
|
+
preserveRaw,
|
|
81
|
+
verbose,
|
|
82
|
+
});
|
|
83
|
+
|
|
73
84
|
return {
|
|
74
85
|
filesConverted: result.filesWritten,
|
|
75
86
|
warnings: result.warnings,
|
|
@@ -86,7 +97,7 @@ async function convertToSpeckit(options = {}) {
|
|
|
86
97
|
async function validateFormat(format, projectPath) {
|
|
87
98
|
const errors = [];
|
|
88
99
|
const warnings = [];
|
|
89
|
-
|
|
100
|
+
|
|
90
101
|
try {
|
|
91
102
|
if (format === 'speckit') {
|
|
92
103
|
await parseSpeckitProject(projectPath);
|
|
@@ -98,7 +109,7 @@ async function validateFormat(format, projectPath) {
|
|
|
98
109
|
} catch (error) {
|
|
99
110
|
errors.push(error.message);
|
|
100
111
|
}
|
|
101
|
-
|
|
112
|
+
|
|
102
113
|
return {
|
|
103
114
|
valid: errors.length === 0,
|
|
104
115
|
errors,
|
|
@@ -115,36 +126,38 @@ async function validateFormat(format, projectPath) {
|
|
|
115
126
|
async function testRoundtrip(projectPath, options = {}) {
|
|
116
127
|
const { verbose = false } = options;
|
|
117
128
|
const differences = [];
|
|
118
|
-
|
|
129
|
+
|
|
119
130
|
try {
|
|
120
131
|
// Detect format
|
|
121
132
|
const fs = require('fs-extra');
|
|
122
133
|
const path = require('path');
|
|
123
|
-
|
|
134
|
+
|
|
124
135
|
const isSpeckit = await fs.pathExists(path.join(projectPath, '.specify'));
|
|
125
136
|
const isMusubi = await fs.pathExists(path.join(projectPath, 'steering'));
|
|
126
|
-
|
|
137
|
+
|
|
127
138
|
if (!isSpeckit && !isMusubi) {
|
|
128
139
|
return {
|
|
129
140
|
passed: false,
|
|
130
141
|
similarity: 0,
|
|
131
|
-
differences: [
|
|
142
|
+
differences: [
|
|
143
|
+
'Could not detect project format (neither .specify nor steering directory found)',
|
|
144
|
+
],
|
|
132
145
|
};
|
|
133
146
|
}
|
|
134
|
-
|
|
147
|
+
|
|
135
148
|
if (verbose) {
|
|
136
149
|
console.log(`Detected format: ${isSpeckit ? 'Spec Kit' : 'MUSUBI'}`);
|
|
137
150
|
}
|
|
138
|
-
|
|
151
|
+
|
|
139
152
|
// Parse original
|
|
140
|
-
const originalIR = isSpeckit
|
|
153
|
+
const originalIR = isSpeckit
|
|
141
154
|
? await parseSpeckitProject(projectPath)
|
|
142
155
|
: await parseMusubiProject(projectPath);
|
|
143
|
-
|
|
156
|
+
|
|
144
157
|
// Convert to other format (in memory)
|
|
145
158
|
const tempDir = path.join(projectPath, '.roundtrip-temp');
|
|
146
159
|
await fs.ensureDir(tempDir);
|
|
147
|
-
|
|
160
|
+
|
|
148
161
|
try {
|
|
149
162
|
// Write to other format
|
|
150
163
|
if (isSpeckit) {
|
|
@@ -152,34 +165,34 @@ async function testRoundtrip(projectPath, options = {}) {
|
|
|
152
165
|
} else {
|
|
153
166
|
await writeSpeckitProject(originalIR, tempDir, { force: true });
|
|
154
167
|
}
|
|
155
|
-
|
|
168
|
+
|
|
156
169
|
// Parse converted
|
|
157
170
|
const convertedIR = isSpeckit
|
|
158
171
|
? await parseMusubiProject(tempDir)
|
|
159
172
|
: await parseSpeckitProject(tempDir);
|
|
160
|
-
|
|
173
|
+
|
|
161
174
|
// Write back to original format
|
|
162
175
|
const tempDir2 = path.join(projectPath, '.roundtrip-temp2');
|
|
163
176
|
await fs.ensureDir(tempDir2);
|
|
164
|
-
|
|
177
|
+
|
|
165
178
|
if (isSpeckit) {
|
|
166
179
|
await writeSpeckitProject(convertedIR, tempDir2, { force: true });
|
|
167
180
|
} else {
|
|
168
181
|
await writeMusubiProject(convertedIR, tempDir2, { force: true });
|
|
169
182
|
}
|
|
170
|
-
|
|
183
|
+
|
|
171
184
|
// Parse roundtrip result
|
|
172
185
|
const roundtripIR = isSpeckit
|
|
173
186
|
? await parseSpeckitProject(tempDir2)
|
|
174
187
|
: await parseMusubiProject(tempDir2);
|
|
175
|
-
|
|
188
|
+
|
|
176
189
|
// Compare
|
|
177
190
|
const similarity = compareIR(originalIR, roundtripIR, differences);
|
|
178
|
-
|
|
191
|
+
|
|
179
192
|
// Cleanup
|
|
180
193
|
await fs.remove(tempDir);
|
|
181
194
|
await fs.remove(tempDir2);
|
|
182
|
-
|
|
195
|
+
|
|
183
196
|
return {
|
|
184
197
|
passed: similarity >= 90,
|
|
185
198
|
similarity,
|
|
@@ -206,27 +219,33 @@ async function testRoundtrip(projectPath, options = {}) {
|
|
|
206
219
|
* @returns {Promise<{featuresCreated: number, requirementsCreated: number, warnings: string[], outputPath: string}>}
|
|
207
220
|
*/
|
|
208
221
|
async function convertFromOpenAPI(specPath, options = {}) {
|
|
209
|
-
const {
|
|
210
|
-
|
|
222
|
+
const {
|
|
223
|
+
output = '.',
|
|
224
|
+
dryRun = false,
|
|
225
|
+
force = false,
|
|
226
|
+
verbose = false,
|
|
227
|
+
featureName: _featureName,
|
|
228
|
+
} = options;
|
|
229
|
+
|
|
211
230
|
if (verbose) console.log(`Converting OpenAPI spec from: ${specPath}`);
|
|
212
|
-
|
|
231
|
+
|
|
213
232
|
// Parse OpenAPI spec to IR
|
|
214
233
|
const ir = await parseOpenAPISpec(specPath);
|
|
215
|
-
|
|
234
|
+
|
|
216
235
|
// Count requirements
|
|
217
236
|
let requirementsCreated = 0;
|
|
218
237
|
for (const feature of ir.features) {
|
|
219
238
|
requirementsCreated += feature.requirements?.length || 0;
|
|
220
239
|
}
|
|
221
|
-
|
|
240
|
+
|
|
222
241
|
if (verbose) {
|
|
223
242
|
console.log(` Found ${ir.features.length} features`);
|
|
224
243
|
console.log(` Found ${requirementsCreated} requirements`);
|
|
225
244
|
}
|
|
226
|
-
|
|
245
|
+
|
|
227
246
|
// Write to MUSUBI format
|
|
228
247
|
const result = await writeMusubiProject(ir, output, { dryRun, force, verbose });
|
|
229
|
-
|
|
248
|
+
|
|
230
249
|
return {
|
|
231
250
|
featuresCreated: ir.features.length,
|
|
232
251
|
requirementsCreated,
|
|
@@ -237,15 +256,15 @@ async function convertFromOpenAPI(specPath, options = {}) {
|
|
|
237
256
|
|
|
238
257
|
/**
|
|
239
258
|
* Compare two IR structures and return similarity percentage
|
|
240
|
-
* @param {import('./ir/types').ProjectIR} original
|
|
241
|
-
* @param {import('./ir/types').ProjectIR} roundtrip
|
|
242
|
-
* @param {string[]} differences
|
|
259
|
+
* @param {import('./ir/types').ProjectIR} original
|
|
260
|
+
* @param {import('./ir/types').ProjectIR} roundtrip
|
|
261
|
+
* @param {string[]} differences
|
|
243
262
|
* @returns {number} Similarity percentage (0-100)
|
|
244
263
|
*/
|
|
245
264
|
function compareIR(original, roundtrip, differences) {
|
|
246
265
|
let matches = 0;
|
|
247
266
|
let total = 0;
|
|
248
|
-
|
|
267
|
+
|
|
249
268
|
// Compare metadata
|
|
250
269
|
total++;
|
|
251
270
|
if (original.metadata.name === roundtrip.metadata.name) {
|
|
@@ -253,20 +272,22 @@ function compareIR(original, roundtrip, differences) {
|
|
|
253
272
|
} else {
|
|
254
273
|
differences.push(`Name mismatch: "${original.metadata.name}" vs "${roundtrip.metadata.name}"`);
|
|
255
274
|
}
|
|
256
|
-
|
|
275
|
+
|
|
257
276
|
// Compare features count
|
|
258
277
|
total++;
|
|
259
278
|
if (original.features.length === roundtrip.features.length) {
|
|
260
279
|
matches++;
|
|
261
280
|
} else {
|
|
262
|
-
differences.push(
|
|
281
|
+
differences.push(
|
|
282
|
+
`Feature count mismatch: ${original.features.length} vs ${roundtrip.features.length}`
|
|
283
|
+
);
|
|
263
284
|
}
|
|
264
|
-
|
|
285
|
+
|
|
265
286
|
// Compare each feature
|
|
266
287
|
for (let i = 0; i < Math.min(original.features.length, roundtrip.features.length); i++) {
|
|
267
288
|
const origFeature = original.features[i];
|
|
268
289
|
const rtFeature = roundtrip.features[i];
|
|
269
|
-
|
|
290
|
+
|
|
270
291
|
// Compare feature name
|
|
271
292
|
total++;
|
|
272
293
|
if (origFeature.name === rtFeature.name) {
|
|
@@ -274,7 +295,7 @@ function compareIR(original, roundtrip, differences) {
|
|
|
274
295
|
} else {
|
|
275
296
|
differences.push(`Feature ${i} name mismatch: "${origFeature.name}" vs "${rtFeature.name}"`);
|
|
276
297
|
}
|
|
277
|
-
|
|
298
|
+
|
|
278
299
|
// Compare requirements count
|
|
279
300
|
total++;
|
|
280
301
|
const origReqs = origFeature.specification?.requirements?.length || 0;
|
|
@@ -284,7 +305,7 @@ function compareIR(original, roundtrip, differences) {
|
|
|
284
305
|
} else {
|
|
285
306
|
differences.push(`Feature ${i} requirements count mismatch: ${origReqs} vs ${rtReqs}`);
|
|
286
307
|
}
|
|
287
|
-
|
|
308
|
+
|
|
288
309
|
// Compare tasks count
|
|
289
310
|
total++;
|
|
290
311
|
const origTasks = origFeature.tasks?.length || 0;
|
|
@@ -295,7 +316,7 @@ function compareIR(original, roundtrip, differences) {
|
|
|
295
316
|
differences.push(`Feature ${i} tasks count mismatch: ${origTasks} vs ${rtTasks}`);
|
|
296
317
|
}
|
|
297
318
|
}
|
|
298
|
-
|
|
319
|
+
|
|
299
320
|
// Compare constitution articles
|
|
300
321
|
total++;
|
|
301
322
|
const origArticles = original.constitution?.articles?.length || 0;
|
|
@@ -305,7 +326,7 @@ function compareIR(original, roundtrip, differences) {
|
|
|
305
326
|
} else {
|
|
306
327
|
differences.push(`Constitution articles count mismatch: ${origArticles} vs ${rtArticles}`);
|
|
307
328
|
}
|
|
308
|
-
|
|
329
|
+
|
|
309
330
|
return Math.round((matches / total) * 100);
|
|
310
331
|
}
|
|
311
332
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Intermediate Representation (IR) Types for Cross-Format Conversion
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Enables bidirectional conversion between MUSUBI and Spec Kit formats
|
|
5
5
|
* with minimal information loss.
|
|
6
6
|
*/
|
|
@@ -369,7 +369,7 @@ function createEmptyFeatureIR(id, name) {
|
|
|
369
369
|
function createRequirementFromEARS(id, statement) {
|
|
370
370
|
const pattern = detectEARSPattern(statement);
|
|
371
371
|
const parsed = parseEARSStatement(statement, pattern);
|
|
372
|
-
|
|
372
|
+
|
|
373
373
|
return {
|
|
374
374
|
id,
|
|
375
375
|
title: '',
|
|
@@ -390,7 +390,7 @@ function createRequirementFromEARS(id, statement) {
|
|
|
390
390
|
*/
|
|
391
391
|
function detectEARSPattern(statement) {
|
|
392
392
|
const upper = statement.toUpperCase();
|
|
393
|
-
|
|
393
|
+
|
|
394
394
|
if (upper.includes('WHILE') && upper.includes('WHEN')) {
|
|
395
395
|
return 'complex';
|
|
396
396
|
}
|
|
@@ -412,33 +412,33 @@ function detectEARSPattern(statement) {
|
|
|
412
412
|
* @param {EARSPattern} pattern - EARS pattern
|
|
413
413
|
* @returns {{trigger?: string, condition?: string, action: string}}
|
|
414
414
|
*/
|
|
415
|
-
function parseEARSStatement(statement,
|
|
415
|
+
function parseEARSStatement(statement, _pattern) {
|
|
416
416
|
const result = { action: '' };
|
|
417
|
-
|
|
417
|
+
|
|
418
418
|
// Extract SHALL clause
|
|
419
419
|
const shallMatch = statement.match(/SHALL\s+(.+?)(?:\.|$)/i);
|
|
420
420
|
if (shallMatch) {
|
|
421
421
|
result.action = shallMatch[1].trim();
|
|
422
422
|
}
|
|
423
|
-
|
|
423
|
+
|
|
424
424
|
// Extract WHEN clause
|
|
425
425
|
const whenMatch = statement.match(/WHEN\s+(.+?),/i);
|
|
426
426
|
if (whenMatch) {
|
|
427
427
|
result.trigger = whenMatch[1].trim();
|
|
428
428
|
}
|
|
429
|
-
|
|
429
|
+
|
|
430
430
|
// Extract WHILE clause
|
|
431
431
|
const whileMatch = statement.match(/WHILE\s+(.+?),/i);
|
|
432
432
|
if (whileMatch) {
|
|
433
433
|
result.condition = whileMatch[1].trim();
|
|
434
434
|
}
|
|
435
|
-
|
|
435
|
+
|
|
436
436
|
// Extract WHERE clause
|
|
437
437
|
const whereMatch = statement.match(/WHERE\s+(.+?),/i);
|
|
438
438
|
if (whereMatch) {
|
|
439
439
|
result.condition = whereMatch[1].trim();
|
|
440
440
|
}
|
|
441
|
-
|
|
441
|
+
|
|
442
442
|
return result;
|
|
443
443
|
}
|
|
444
444
|
|
|
@@ -452,7 +452,7 @@ function userScenarioToRequirement(userScenario, reqId) {
|
|
|
452
452
|
// Convert "As a [actor], I want [action] so that [benefit]"
|
|
453
453
|
// to "WHEN the [actor] [action], the system SHALL [provide benefit]"
|
|
454
454
|
const statement = `WHEN the ${userScenario.actor} ${userScenario.action}, the system SHALL ${userScenario.benefit}.`;
|
|
455
|
-
|
|
455
|
+
|
|
456
456
|
return {
|
|
457
457
|
id: reqId,
|
|
458
458
|
title: userScenario.title,
|
|
@@ -477,7 +477,7 @@ function requirementToUserScenario(requirement, storyId) {
|
|
|
477
477
|
let actor = 'user';
|
|
478
478
|
let action = requirement.trigger || 'performs an action';
|
|
479
479
|
let benefit = requirement.action;
|
|
480
|
-
|
|
480
|
+
|
|
481
481
|
// Try to extract actor from trigger
|
|
482
482
|
if (requirement.trigger) {
|
|
483
483
|
const actorMatch = requirement.trigger.match(/the\s+(\w+)/i);
|
|
@@ -485,7 +485,7 @@ function requirementToUserScenario(requirement, storyId) {
|
|
|
485
485
|
actor = actorMatch[1];
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
|
-
|
|
488
|
+
|
|
489
489
|
return {
|
|
490
490
|
id: storyId,
|
|
491
491
|
title: requirement.title || `Story for ${requirement.id}`,
|