musubi-sdd 5.1.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.
Files changed (232) hide show
  1. package/README.ja.md +106 -48
  2. package/README.md +110 -32
  3. package/bin/musubi-analyze.js +74 -67
  4. package/bin/musubi-browser.js +27 -26
  5. package/bin/musubi-change.js +48 -47
  6. package/bin/musubi-checkpoint.js +10 -7
  7. package/bin/musubi-convert.js +25 -25
  8. package/bin/musubi-costs.js +27 -10
  9. package/bin/musubi-gui.js +52 -46
  10. package/bin/musubi-init.js +1952 -10
  11. package/bin/musubi-orchestrate.js +327 -239
  12. package/bin/musubi-remember.js +69 -56
  13. package/bin/musubi-resolve.js +53 -45
  14. package/bin/musubi-trace.js +51 -22
  15. package/bin/musubi-validate.js +39 -30
  16. package/bin/musubi-workflow.js +33 -34
  17. package/bin/musubi.js +39 -2
  18. package/package.json +1 -1
  19. package/src/agents/agent-loop.js +94 -95
  20. package/src/agents/agentic/code-generator.js +119 -109
  21. package/src/agents/agentic/code-reviewer.js +105 -108
  22. package/src/agents/agentic/index.js +4 -4
  23. package/src/agents/browser/action-executor.js +13 -13
  24. package/src/agents/browser/ai-comparator.js +11 -10
  25. package/src/agents/browser/context-manager.js +6 -6
  26. package/src/agents/browser/index.js +5 -5
  27. package/src/agents/browser/nl-parser.js +31 -46
  28. package/src/agents/browser/screenshot.js +2 -2
  29. package/src/agents/browser/test-generator.js +6 -4
  30. package/src/agents/function-tool.js +71 -65
  31. package/src/agents/index.js +7 -7
  32. package/src/agents/schema-generator.js +98 -94
  33. package/src/analyzers/ast-extractor.js +158 -146
  34. package/src/analyzers/codegraph-auto-update.js +858 -0
  35. package/src/analyzers/complexity-analyzer.js +536 -0
  36. package/src/analyzers/context-optimizer.js +241 -126
  37. package/src/analyzers/impact-analyzer.js +1 -1
  38. package/src/analyzers/large-project-analyzer.js +766 -0
  39. package/src/analyzers/repository-map.js +77 -81
  40. package/src/analyzers/security-analyzer.js +19 -11
  41. package/src/analyzers/stuck-detector.js +19 -17
  42. package/src/converters/index.js +78 -57
  43. package/src/converters/ir/types.js +12 -12
  44. package/src/converters/parsers/musubi-parser.js +134 -126
  45. package/src/converters/parsers/openapi-parser.js +70 -53
  46. package/src/converters/parsers/speckit-parser.js +239 -175
  47. package/src/converters/writers/musubi-writer.js +123 -118
  48. package/src/converters/writers/speckit-writer.js +124 -113
  49. package/src/generators/rust-migration-generator.js +512 -0
  50. package/src/gui/public/index.html +1365 -1211
  51. package/src/gui/server.js +41 -40
  52. package/src/gui/services/file-watcher.js +23 -8
  53. package/src/gui/services/project-scanner.js +26 -20
  54. package/src/gui/services/replanning-service.js +27 -23
  55. package/src/gui/services/traceability-service.js +8 -8
  56. package/src/gui/services/workflow-service.js +14 -7
  57. package/src/index.js +151 -0
  58. package/src/integrations/cicd.js +90 -104
  59. package/src/integrations/codegraph-mcp.js +643 -0
  60. package/src/integrations/documentation.js +142 -103
  61. package/src/integrations/examples.js +95 -80
  62. package/src/integrations/github-client.js +17 -17
  63. package/src/integrations/index.js +5 -5
  64. package/src/integrations/mcp/index.js +21 -21
  65. package/src/integrations/mcp/mcp-context-provider.js +76 -78
  66. package/src/integrations/mcp/mcp-discovery.js +74 -72
  67. package/src/integrations/mcp/mcp-tool-registry.js +99 -94
  68. package/src/integrations/mcp-connector.js +70 -66
  69. package/src/integrations/platforms.js +50 -49
  70. package/src/integrations/tool-discovery.js +37 -31
  71. package/src/llm-providers/anthropic-provider.js +11 -11
  72. package/src/llm-providers/base-provider.js +16 -18
  73. package/src/llm-providers/copilot-provider.js +22 -19
  74. package/src/llm-providers/index.js +26 -25
  75. package/src/llm-providers/ollama-provider.js +11 -11
  76. package/src/llm-providers/openai-provider.js +12 -12
  77. package/src/managers/agent-memory.js +36 -24
  78. package/src/managers/checkpoint-manager.js +4 -8
  79. package/src/managers/delta-spec.js +19 -19
  80. package/src/managers/index.js +13 -4
  81. package/src/managers/memory-condenser.js +35 -45
  82. package/src/managers/repo-skill-manager.js +57 -31
  83. package/src/managers/skill-loader.js +25 -22
  84. package/src/managers/skill-tools.js +36 -72
  85. package/src/managers/workflow.js +30 -22
  86. package/src/monitoring/cost-tracker.js +48 -46
  87. package/src/monitoring/incident-manager.js +116 -106
  88. package/src/monitoring/index.js +144 -134
  89. package/src/monitoring/observability.js +75 -62
  90. package/src/monitoring/quality-dashboard.js +45 -41
  91. package/src/monitoring/release-manager.js +63 -53
  92. package/src/orchestration/agent-skill-binding.js +39 -47
  93. package/src/orchestration/error-handler.js +65 -107
  94. package/src/orchestration/guardrails/base-guardrail.js +26 -24
  95. package/src/orchestration/guardrails/guardrail-rules.js +50 -64
  96. package/src/orchestration/guardrails/index.js +5 -5
  97. package/src/orchestration/guardrails/input-guardrail.js +58 -45
  98. package/src/orchestration/guardrails/output-guardrail.js +104 -81
  99. package/src/orchestration/guardrails/safety-check.js +79 -79
  100. package/src/orchestration/index.js +38 -55
  101. package/src/orchestration/mcp-tool-adapters.js +96 -99
  102. package/src/orchestration/orchestration-engine.js +21 -21
  103. package/src/orchestration/pattern-registry.js +60 -45
  104. package/src/orchestration/patterns/auto.js +34 -47
  105. package/src/orchestration/patterns/group-chat.js +59 -65
  106. package/src/orchestration/patterns/handoff.js +67 -65
  107. package/src/orchestration/patterns/human-in-loop.js +51 -72
  108. package/src/orchestration/patterns/nested.js +25 -40
  109. package/src/orchestration/patterns/sequential.js +35 -34
  110. package/src/orchestration/patterns/swarm.js +63 -56
  111. package/src/orchestration/patterns/triage.js +150 -109
  112. package/src/orchestration/reasoning/index.js +9 -9
  113. package/src/orchestration/reasoning/planning-engine.js +143 -140
  114. package/src/orchestration/reasoning/reasoning-engine.js +206 -144
  115. package/src/orchestration/reasoning/self-correction.js +121 -128
  116. package/src/orchestration/replanning/adaptive-goal-modifier.js +107 -112
  117. package/src/orchestration/replanning/alternative-generator.js +37 -42
  118. package/src/orchestration/replanning/config.js +63 -59
  119. package/src/orchestration/replanning/goal-progress-tracker.js +98 -100
  120. package/src/orchestration/replanning/index.js +24 -20
  121. package/src/orchestration/replanning/plan-evaluator.js +49 -50
  122. package/src/orchestration/replanning/plan-monitor.js +32 -28
  123. package/src/orchestration/replanning/proactive-path-optimizer.js +175 -178
  124. package/src/orchestration/replanning/replan-history.js +33 -26
  125. package/src/orchestration/replanning/replanning-engine.js +106 -108
  126. package/src/orchestration/skill-executor.js +107 -109
  127. package/src/orchestration/skill-registry.js +85 -89
  128. package/src/orchestration/workflow-examples.js +228 -231
  129. package/src/orchestration/workflow-executor.js +65 -68
  130. package/src/orchestration/workflow-orchestrator.js +72 -73
  131. package/src/phase4-integration.js +47 -40
  132. package/src/phase5-integration.js +89 -30
  133. package/src/reporters/coverage-report.js +82 -30
  134. package/src/reporters/hierarchical-reporter.js +498 -0
  135. package/src/reporters/traceability-matrix-report.js +29 -20
  136. package/src/resolvers/issue-resolver.js +43 -31
  137. package/src/steering/advanced-validation.js +133 -124
  138. package/src/steering/auto-updater.js +60 -73
  139. package/src/steering/index.js +6 -6
  140. package/src/steering/quality-metrics.js +41 -35
  141. package/src/steering/steering-auto-update.js +83 -86
  142. package/src/steering/steering-validator.js +98 -106
  143. package/src/steering/template-constraints.js +53 -54
  144. package/src/templates/agents/claude-code/CLAUDE.md +32 -32
  145. package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +13 -5
  146. package/src/templates/agents/claude-code/skills/ai-ml-engineer/mlops-guide.md +23 -23
  147. package/src/templates/agents/claude-code/skills/ai-ml-engineer/model-card-template.md +60 -41
  148. package/src/templates/agents/claude-code/skills/api-designer/api-patterns.md +27 -19
  149. package/src/templates/agents/claude-code/skills/api-designer/openapi-template.md +11 -7
  150. package/src/templates/agents/claude-code/skills/bug-hunter/SKILL.md +4 -3
  151. package/src/templates/agents/claude-code/skills/bug-hunter/root-cause-analysis.md +37 -15
  152. package/src/templates/agents/claude-code/skills/change-impact-analyzer/dependency-graph-patterns.md +36 -42
  153. package/src/templates/agents/claude-code/skills/change-impact-analyzer/impact-analysis-template.md +69 -60
  154. package/src/templates/agents/claude-code/skills/cloud-architect/aws-patterns.md +31 -38
  155. package/src/templates/agents/claude-code/skills/cloud-architect/azure-patterns.md +28 -23
  156. package/src/templates/agents/claude-code/skills/code-reviewer/SKILL.md +61 -0
  157. package/src/templates/agents/claude-code/skills/code-reviewer/best-practices.md +27 -0
  158. package/src/templates/agents/claude-code/skills/code-reviewer/review-checklist.md +29 -10
  159. package/src/templates/agents/claude-code/skills/code-reviewer/review-standards.md +29 -24
  160. package/src/templates/agents/claude-code/skills/constitution-enforcer/SKILL.md +8 -6
  161. package/src/templates/agents/claude-code/skills/constitution-enforcer/constitutional-articles.md +62 -26
  162. package/src/templates/agents/claude-code/skills/constitution-enforcer/phase-minus-one-gates.md +35 -16
  163. package/src/templates/agents/claude-code/skills/database-administrator/backup-recovery.md +27 -17
  164. package/src/templates/agents/claude-code/skills/database-administrator/tuning-guide.md +25 -20
  165. package/src/templates/agents/claude-code/skills/database-schema-designer/schema-patterns.md +39 -22
  166. package/src/templates/agents/claude-code/skills/devops-engineer/ci-cd-templates.md +25 -22
  167. package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +24 -21
  168. package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +148 -63
  169. package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +35 -16
  170. package/src/templates/agents/claude-code/skills/orchestrator/selection-matrix.md +69 -64
  171. package/src/templates/agents/claude-code/skills/performance-engineer/optimization-playbook.md +47 -47
  172. package/src/templates/agents/claude-code/skills/performance-optimizer/SKILL.md +69 -0
  173. package/src/templates/agents/claude-code/skills/performance-optimizer/benchmark-template.md +63 -45
  174. package/src/templates/agents/claude-code/skills/performance-optimizer/optimization-patterns.md +33 -35
  175. package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +7 -6
  176. package/src/templates/agents/claude-code/skills/project-manager/agile-ceremonies.md +47 -28
  177. package/src/templates/agents/claude-code/skills/project-manager/project-templates.md +94 -78
  178. package/src/templates/agents/claude-code/skills/quality-assurance/SKILL.md +20 -17
  179. package/src/templates/agents/claude-code/skills/quality-assurance/qa-plan-template.md +63 -49
  180. package/src/templates/agents/claude-code/skills/release-coordinator/SKILL.md +5 -5
  181. package/src/templates/agents/claude-code/skills/release-coordinator/feature-flag-guide.md +30 -26
  182. package/src/templates/agents/claude-code/skills/release-coordinator/release-plan-template.md +67 -35
  183. package/src/templates/agents/claude-code/skills/requirements-analyst/ears-format.md +54 -42
  184. package/src/templates/agents/claude-code/skills/requirements-analyst/validation-rules.md +36 -33
  185. package/src/templates/agents/claude-code/skills/security-auditor/SKILL.md +77 -19
  186. package/src/templates/agents/claude-code/skills/security-auditor/audit-checklists.md +24 -24
  187. package/src/templates/agents/claude-code/skills/security-auditor/owasp-top-10.md +61 -20
  188. package/src/templates/agents/claude-code/skills/security-auditor/vulnerability-patterns.md +43 -11
  189. package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +1 -0
  190. package/src/templates/agents/claude-code/skills/site-reliability-engineer/incident-response-template.md +55 -25
  191. package/src/templates/agents/claude-code/skills/site-reliability-engineer/observability-patterns.md +78 -68
  192. package/src/templates/agents/claude-code/skills/site-reliability-engineer/slo-sli-guide.md +73 -53
  193. package/src/templates/agents/claude-code/skills/software-developer/solid-principles.md +83 -37
  194. package/src/templates/agents/claude-code/skills/software-developer/test-first-workflow.md +38 -31
  195. package/src/templates/agents/claude-code/skills/steering/SKILL.md +1 -0
  196. package/src/templates/agents/claude-code/skills/steering/auto-update-rules.md +31 -0
  197. package/src/templates/agents/claude-code/skills/system-architect/adr-template.md +25 -7
  198. package/src/templates/agents/claude-code/skills/system-architect/c4-model-guide.md +74 -61
  199. package/src/templates/agents/claude-code/skills/technical-writer/doc-templates/documentation-templates.md +70 -52
  200. package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +2 -0
  201. package/src/templates/agents/claude-code/skills/test-engineer/ears-test-mapping.md +75 -71
  202. package/src/templates/agents/claude-code/skills/test-engineer/test-types.md +85 -63
  203. package/src/templates/agents/claude-code/skills/traceability-auditor/coverage-matrix-template.md +39 -36
  204. package/src/templates/agents/claude-code/skills/traceability-auditor/gap-detection-rules.md +22 -17
  205. package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +1 -0
  206. package/src/templates/agents/claude-code/skills/ui-ux-designer/accessibility-guidelines.md +49 -75
  207. package/src/templates/agents/claude-code/skills/ui-ux-designer/design-system-components.md +71 -59
  208. package/src/templates/agents/codex/AGENTS.md +74 -42
  209. package/src/templates/agents/cursor/AGENTS.md +74 -42
  210. package/src/templates/agents/gemini-cli/GEMINI.md +74 -42
  211. package/src/templates/agents/github-copilot/AGENTS.md +83 -51
  212. package/src/templates/agents/qwen-code/QWEN.md +74 -42
  213. package/src/templates/agents/windsurf/AGENTS.md +74 -42
  214. package/src/templates/architectures/README.md +41 -0
  215. package/src/templates/architectures/clean-architecture/README.md +113 -0
  216. package/src/templates/architectures/event-driven/README.md +162 -0
  217. package/src/templates/architectures/hexagonal/README.md +130 -0
  218. package/src/templates/index.js +6 -1
  219. package/src/templates/locale-manager.js +16 -16
  220. package/src/templates/shared/delta-spec-template.md +20 -13
  221. package/src/templates/shared/github-actions/musubi-issue-resolver.yml +5 -5
  222. package/src/templates/shared/github-actions/musubi-security-check.yml +3 -3
  223. package/src/templates/shared/github-actions/musubi-validate.yml +4 -4
  224. package/src/templates/shared/steering/structure.md +95 -0
  225. package/src/templates/skills/browser-agent.md +21 -16
  226. package/src/templates/skills/web-gui.md +8 -0
  227. package/src/templates/template-constraints.js +50 -53
  228. package/src/validators/advanced-validation.js +30 -36
  229. package/src/validators/constitutional-validator.js +77 -73
  230. package/src/validators/critic-system.js +49 -59
  231. package/src/validators/delta-format.js +59 -55
  232. package/src/validators/traceability-validator.js +7 -11
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MUSUBI Parser
3
- *
3
+ *
4
4
  * Parses MUSUBI project structure into Intermediate Representation (IR)
5
5
  */
6
6
 
@@ -9,7 +9,11 @@
9
9
  const fs = require('fs-extra');
10
10
  const path = require('path');
11
11
  const yaml = require('js-yaml');
12
- const { createEmptyProjectIR, createEmptyFeatureIR, createRequirementFromEARS } = require('../ir/types');
12
+ const {
13
+ createEmptyProjectIR,
14
+ createEmptyFeatureIR,
15
+ createRequirementFromEARS,
16
+ } = require('../ir/types');
13
17
 
14
18
  /**
15
19
  * Parse a MUSUBI project into IR
@@ -18,34 +22,34 @@ const { createEmptyProjectIR, createEmptyFeatureIR, createRequirementFromEARS }
18
22
  */
19
23
  async function parseMusubiProject(projectPath) {
20
24
  const ir = createEmptyProjectIR();
21
-
25
+
22
26
  // Parse project metadata
23
27
  ir.metadata = await parseProjectMetadata(projectPath);
24
-
28
+
25
29
  // Parse constitution
26
30
  ir.constitution = await parseConstitution(projectPath);
27
-
31
+
28
32
  // Parse features
29
33
  ir.features = await parseFeatures(projectPath);
30
-
34
+
31
35
  // Parse templates
32
36
  ir.templates = await parseTemplates(projectPath);
33
-
37
+
34
38
  // Parse memories
35
39
  ir.memories = await parseMemories(projectPath);
36
-
40
+
37
41
  return ir;
38
42
  }
39
43
 
40
44
  /**
41
45
  * Parse project metadata from project.yml
42
- * @param {string} projectPath
46
+ * @param {string} projectPath
43
47
  * @returns {Promise<import('../ir/types').ProjectMetadata>}
44
48
  */
45
49
  async function parseProjectMetadata(projectPath) {
46
50
  const projectYmlPath = path.join(projectPath, 'steering', 'project.yml');
47
51
  const productMdPath = path.join(projectPath, 'steering', 'product.md');
48
-
52
+
49
53
  const metadata = {
50
54
  name: '',
51
55
  version: '1.0.0',
@@ -54,13 +58,13 @@ async function parseProjectMetadata(projectPath) {
54
58
  convertedAt: new Date(),
55
59
  preservedFields: {},
56
60
  };
57
-
61
+
58
62
  // Try to load project.yml
59
63
  if (await fs.pathExists(projectYmlPath)) {
60
64
  try {
61
65
  const content = await fs.readFile(projectYmlPath, 'utf-8');
62
66
  const projectYml = yaml.load(content);
63
-
67
+
64
68
  metadata.name = projectYml.project?.name || '';
65
69
  metadata.version = projectYml.project?.version || '1.0.0';
66
70
  metadata.preservedFields.projectYml = projectYml;
@@ -68,9 +72,9 @@ async function parseProjectMetadata(projectPath) {
68
72
  console.warn(`Warning: Failed to parse project.yml: ${error.message}`);
69
73
  }
70
74
  }
71
-
75
+
72
76
  // Try to extract name from product.md if not found
73
- if (!metadata.name && await fs.pathExists(productMdPath)) {
77
+ if (!metadata.name && (await fs.pathExists(productMdPath))) {
74
78
  try {
75
79
  const content = await fs.readFile(productMdPath, 'utf-8');
76
80
  const titleMatch = content.match(/^#\s+(.+)$/m);
@@ -81,18 +85,18 @@ async function parseProjectMetadata(projectPath) {
81
85
  console.warn(`Warning: Failed to parse product.md: ${error.message}`);
82
86
  }
83
87
  }
84
-
88
+
85
89
  return metadata;
86
90
  }
87
91
 
88
92
  /**
89
93
  * Parse constitution from steering/rules/constitution.md
90
- * @param {string} projectPath
94
+ * @param {string} projectPath
91
95
  * @returns {Promise<import('../ir/types').ConstitutionIR>}
92
96
  */
93
97
  async function parseConstitution(projectPath) {
94
98
  const constitutionPath = path.join(projectPath, 'steering', 'rules', 'constitution.md');
95
-
99
+
96
100
  const constitution = {
97
101
  articles: [],
98
102
  corePrinciples: [],
@@ -101,31 +105,31 @@ async function parseConstitution(projectPath) {
101
105
  rules: [],
102
106
  },
103
107
  };
104
-
105
- if (!await fs.pathExists(constitutionPath)) {
108
+
109
+ if (!(await fs.pathExists(constitutionPath))) {
106
110
  return constitution;
107
111
  }
108
-
112
+
109
113
  try {
110
114
  const content = await fs.readFile(constitutionPath, 'utf-8');
111
115
  constitution.rawContent = content;
112
-
116
+
113
117
  // Parse articles
114
118
  const articleRegex = /##\s+Article\s+(\d+)[:\s]+(.+?)(?=\n##|\n$|$)/gs;
115
119
  let match;
116
-
120
+
117
121
  while ((match = articleRegex.exec(content)) !== null) {
118
122
  const articleNumber = parseInt(match[1], 10);
119
123
  const articleContent = match[2].trim();
120
-
124
+
121
125
  // Extract article name from first line
122
126
  const lines = articleContent.split('\n');
123
127
  const name = lines[0].trim();
124
-
128
+
125
129
  // Extract description (everything after name until rules)
126
130
  let description = '';
127
131
  let rulesStart = -1;
128
-
132
+
129
133
  for (let i = 1; i < lines.length; i++) {
130
134
  if (lines[i].match(/^[-*]\s+/)) {
131
135
  rulesStart = i;
@@ -133,7 +137,7 @@ async function parseConstitution(projectPath) {
133
137
  }
134
138
  description += lines[i] + '\n';
135
139
  }
136
-
140
+
137
141
  // Extract rules (bullet points)
138
142
  const rules = [];
139
143
  if (rulesStart !== -1) {
@@ -144,7 +148,7 @@ async function parseConstitution(projectPath) {
144
148
  }
145
149
  }
146
150
  }
147
-
151
+
148
152
  constitution.articles.push({
149
153
  number: articleNumber,
150
154
  name,
@@ -152,7 +156,7 @@ async function parseConstitution(projectPath) {
152
156
  rules,
153
157
  });
154
158
  }
155
-
159
+
156
160
  // Parse governance section if present
157
161
  const governanceMatch = content.match(/##\s+Governance\s*\n([\s\S]+?)(?=\n##|$)/);
158
162
  if (governanceMatch) {
@@ -161,7 +165,7 @@ async function parseConstitution(projectPath) {
161
165
  if (versionMatch) {
162
166
  constitution.governance.version = versionMatch[1];
163
167
  }
164
-
168
+
165
169
  // Extract governance rules
166
170
  const ruleLines = governanceContent.match(/^[-*]\s+(.+)/gm);
167
171
  if (ruleLines) {
@@ -171,26 +175,26 @@ async function parseConstitution(projectPath) {
171
175
  } catch (error) {
172
176
  console.warn(`Warning: Failed to parse constitution: ${error.message}`);
173
177
  }
174
-
178
+
175
179
  return constitution;
176
180
  }
177
181
 
178
182
  /**
179
183
  * Parse features from storage/specs/
180
- * @param {string} projectPath
184
+ * @param {string} projectPath
181
185
  * @returns {Promise<import('../ir/types').FeatureIR[]>}
182
186
  */
183
187
  async function parseFeatures(projectPath) {
184
188
  const specsPath = path.join(projectPath, 'storage', 'specs');
185
189
  const features = [];
186
-
187
- if (!await fs.pathExists(specsPath)) {
190
+
191
+ if (!(await fs.pathExists(specsPath))) {
188
192
  return features;
189
193
  }
190
-
194
+
191
195
  try {
192
196
  const entries = await fs.readdir(specsPath, { withFileTypes: true });
193
-
197
+
194
198
  for (const entry of entries) {
195
199
  if (entry.isDirectory()) {
196
200
  const featurePath = path.join(specsPath, entry.name);
@@ -203,66 +207,66 @@ async function parseFeatures(projectPath) {
203
207
  } catch (error) {
204
208
  console.warn(`Warning: Failed to parse features: ${error.message}`);
205
209
  }
206
-
210
+
207
211
  return features;
208
212
  }
209
213
 
210
214
  /**
211
215
  * Parse a single feature
212
- * @param {string} featurePath
213
- * @param {string} featureId
216
+ * @param {string} featurePath
217
+ * @param {string} featureId
214
218
  * @returns {Promise<import('../ir/types').FeatureIR|null>}
215
219
  */
216
220
  async function parseFeature(featurePath, featureId) {
217
221
  const feature = createEmptyFeatureIR(featureId, featureId);
218
-
222
+
219
223
  // Parse spec.md
220
224
  const specPath = path.join(featurePath, 'spec.md');
221
225
  if (await fs.pathExists(specPath)) {
222
226
  feature.specification = await parseSpecification(specPath);
223
227
  }
224
-
228
+
225
229
  // Parse plan.md
226
230
  const planPath = path.join(featurePath, 'plan.md');
227
231
  if (await fs.pathExists(planPath)) {
228
232
  feature.plan = await parsePlan(planPath);
229
233
  }
230
-
234
+
231
235
  // Parse tasks.md
232
236
  const tasksPath = path.join(featurePath, 'tasks.md');
233
237
  if (await fs.pathExists(tasksPath)) {
234
238
  feature.tasks = await parseTasks(tasksPath);
235
239
  }
236
-
240
+
237
241
  // Parse research.md
238
242
  const researchPath = path.join(featurePath, 'research.md');
239
243
  if (await fs.pathExists(researchPath)) {
240
244
  feature.research = await parseResearch(researchPath);
241
245
  }
242
-
246
+
243
247
  // Parse data-model.md
244
248
  const dataModelPath = path.join(featurePath, 'data-model.md');
245
249
  if (await fs.pathExists(dataModelPath)) {
246
250
  feature.dataModel = await parseDataModel(dataModelPath);
247
251
  }
248
-
252
+
249
253
  // Parse contracts directory
250
254
  const contractsPath = path.join(featurePath, 'contracts');
251
255
  if (await fs.pathExists(contractsPath)) {
252
256
  feature.contracts = await parseContracts(contractsPath);
253
257
  }
254
-
258
+
255
259
  return feature;
256
260
  }
257
261
 
258
262
  /**
259
263
  * Parse specification file
260
- * @param {string} specPath
264
+ * @param {string} specPath
261
265
  * @returns {Promise<import('../ir/types').SpecificationIR>}
262
266
  */
263
267
  async function parseSpecification(specPath) {
264
268
  const content = await fs.readFile(specPath, 'utf-8');
265
-
269
+
266
270
  const specification = {
267
271
  title: '',
268
272
  description: '',
@@ -271,33 +275,33 @@ async function parseSpecification(specPath) {
271
275
  successCriteria: [],
272
276
  rawContent: content,
273
277
  };
274
-
278
+
275
279
  // Extract title from first heading
276
280
  const titleMatch = content.match(/^#\s+(.+)$/m);
277
281
  if (titleMatch) {
278
282
  specification.title = titleMatch[1].trim();
279
283
  }
280
-
284
+
281
285
  // Extract description (content before first ## heading)
282
286
  const descMatch = content.match(/^#\s+.+\n([\s\S]+?)(?=\n##|$)/);
283
287
  if (descMatch) {
284
288
  specification.description = descMatch[1].trim();
285
289
  }
286
-
290
+
287
291
  // Parse EARS requirements
288
292
  const requirementsSection = content.match(/##\s+Requirements?\s*\n([\s\S]+?)(?=\n##[^#]|$)/i);
289
293
  if (requirementsSection) {
290
294
  const reqContent = requirementsSection[1];
291
-
295
+
292
296
  // Match requirement patterns like REQ-001, REQ-P0-001, etc.
293
297
  // Use greedy match until next ### or ## (non-###) heading
294
298
  const reqRegex = /###?\s+(REQ[-\w]+)[:\s]+([^#]+?)(?=\n###?\s+REQ|\n##[^#]|$)/gs;
295
299
  let match;
296
-
300
+
297
301
  while ((match = reqRegex.exec(reqContent)) !== null) {
298
302
  const reqId = match[1];
299
303
  const reqBody = match[2].trim();
300
-
304
+
301
305
  // Extract EARS statement
302
306
  const earsMatch = reqBody.match(/((?:WHEN|WHILE|WHERE|IF)[\s\S]+?SHALL[\s\S]+?\.)/i);
303
307
  if (earsMatch) {
@@ -316,7 +320,7 @@ async function parseSpecification(specPath) {
316
320
  }
317
321
  }
318
322
  }
319
-
323
+
320
324
  // Parse success criteria
321
325
  const successSection = content.match(/##\s+Success\s+Criteria\s*\n([\s\S]+?)(?=\n##|$)/i);
322
326
  if (successSection) {
@@ -326,13 +330,13 @@ async function parseSpecification(specPath) {
326
330
  specification.successCriteria = criteria.map(c => c.replace(/^[-*]\s+/, ''));
327
331
  }
328
332
  }
329
-
333
+
330
334
  return specification;
331
335
  }
332
336
 
333
337
  /**
334
338
  * Extract priority from requirement ID
335
- * @param {string} reqId
339
+ * @param {string} reqId
336
340
  * @returns {import('../ir/types').Priority}
337
341
  */
338
342
  function extractPriority(reqId) {
@@ -345,12 +349,12 @@ function extractPriority(reqId) {
345
349
 
346
350
  /**
347
351
  * Parse plan file
348
- * @param {string} planPath
352
+ * @param {string} planPath
349
353
  * @returns {Promise<import('../ir/types').PlanIR>}
350
354
  */
351
355
  async function parsePlan(planPath) {
352
356
  const content = await fs.readFile(planPath, 'utf-8');
353
-
357
+
354
358
  const plan = {
355
359
  summary: '',
356
360
  technicalContext: {
@@ -369,47 +373,47 @@ async function parsePlan(planPath) {
369
373
  phases: [],
370
374
  rawContent: content,
371
375
  };
372
-
376
+
373
377
  // Extract summary from first paragraph
374
378
  const summaryMatch = content.match(/^#\s+.+\n([\s\S]+?)(?=\n##|$)/);
375
379
  if (summaryMatch) {
376
380
  plan.summary = summaryMatch[1].trim();
377
381
  }
378
-
382
+
379
383
  // Parse technical context
380
384
  const techSection = content.match(/##\s+Technical\s+Context\s*\n([\s\S]+?)(?=\n##|$)/i);
381
385
  if (techSection) {
382
386
  const techContent = techSection[1];
383
-
387
+
384
388
  // Extract key-value pairs
385
389
  const langMatch = techContent.match(/language[:\s]+(.+)/i);
386
390
  if (langMatch) plan.technicalContext.language = langMatch[1].trim();
387
-
391
+
388
392
  const versionMatch = techContent.match(/version[:\s]+(.+)/i);
389
393
  if (versionMatch) plan.technicalContext.version = versionMatch[1].trim();
390
-
394
+
391
395
  const frameworkMatch = techContent.match(/framework[:\s]+(.+)/i);
392
396
  if (frameworkMatch) plan.technicalContext.framework = frameworkMatch[1].trim();
393
-
397
+
394
398
  const testingMatch = techContent.match(/testing[:\s]+(.+)/i);
395
399
  if (testingMatch) plan.technicalContext.testing = testingMatch[1].trim();
396
-
400
+
397
401
  const platformMatch = techContent.match(/platform[:\s]+(.+)/i);
398
402
  if (platformMatch) plan.technicalContext.targetPlatform = platformMatch[1].trim();
399
403
  }
400
-
404
+
401
405
  // Parse phases
402
406
  const phasesSection = content.match(/##\s+Phases?\s*\n([\s\S]+?)(?=\n##|$)/i);
403
407
  if (phasesSection) {
404
408
  const phasesContent = phasesSection[1];
405
409
  const phaseRegex = /###\s+Phase\s+(\d+)[:\s]+(.+?)(?=\n###|\n##|$)/gs;
406
410
  let match;
407
-
411
+
408
412
  while ((match = phaseRegex.exec(phasesContent)) !== null) {
409
413
  const phaseNumber = parseInt(match[1], 10);
410
414
  const phaseContent = match[2].trim();
411
415
  const lines = phaseContent.split('\n');
412
-
416
+
413
417
  plan.phases.push({
414
418
  number: phaseNumber,
415
419
  name: lines[0].trim(),
@@ -419,28 +423,28 @@ async function parsePlan(planPath) {
419
423
  });
420
424
  }
421
425
  }
422
-
426
+
423
427
  return plan;
424
428
  }
425
429
 
426
430
  /**
427
431
  * Parse tasks file
428
- * @param {string} tasksPath
432
+ * @param {string} tasksPath
429
433
  * @returns {Promise<import('../ir/types').TaskIR[]>}
430
434
  */
431
435
  async function parseTasks(tasksPath) {
432
436
  const content = await fs.readFile(tasksPath, 'utf-8');
433
437
  const tasks = [];
434
-
438
+
435
439
  // Match task lines like: - [ ] T001: Description
436
440
  const taskRegex = /^[-*]\s+\[([xX ])\]\s+(T\d+)[:\s]+(.+)$/gm;
437
441
  let match;
438
-
442
+
439
443
  while ((match = taskRegex.exec(content)) !== null) {
440
444
  const completed = match[1].toLowerCase() === 'x';
441
445
  const taskId = match[2];
442
446
  const description = match[3].trim();
443
-
447
+
444
448
  // Extract phase from context
445
449
  let phase = 1;
446
450
  const phaseMatch = content.slice(0, match.index).match(/##\s+Phase\s+(\d+)/gi);
@@ -451,18 +455,18 @@ async function parseTasks(tasksPath) {
451
455
  phase = parseInt(phaseNum[1], 10);
452
456
  }
453
457
  }
454
-
458
+
455
459
  // Check for parallel marker [P]
456
460
  const parallel = description.includes('[P]');
457
-
461
+
458
462
  // Extract file path if present
459
463
  const filePathMatch = description.match(/(?:at|in|path:)\s+([^\s]+)/i);
460
464
  const filePath = filePathMatch ? filePathMatch[1] : undefined;
461
-
465
+
462
466
  // Extract user story reference
463
467
  const storyMatch = description.match(/\[US\d+\]/);
464
- const userStory = storyMatch ? storyMatch[0].replace(/[\[\]]/g, '') : undefined;
465
-
468
+ const userStory = storyMatch ? storyMatch[0].replace(/[[\]]/g, '') : undefined;
469
+
466
470
  tasks.push({
467
471
  id: taskId,
468
472
  description: description.replace(/\[P\]|\[US\d+\]/g, '').trim(),
@@ -473,38 +477,38 @@ async function parseTasks(tasksPath) {
473
477
  completed,
474
478
  });
475
479
  }
476
-
480
+
477
481
  return tasks;
478
482
  }
479
483
 
480
484
  /**
481
485
  * Parse research file
482
- * @param {string} researchPath
486
+ * @param {string} researchPath
483
487
  * @returns {Promise<import('../ir/types').ResearchIR>}
484
488
  */
485
489
  async function parseResearch(researchPath) {
486
490
  const content = await fs.readFile(researchPath, 'utf-8');
487
-
491
+
488
492
  const research = {
489
493
  decisions: [],
490
494
  alternatives: [],
491
495
  rawContent: content,
492
496
  };
493
-
497
+
494
498
  // Parse decisions section
495
499
  const decisionsSection = content.match(/##\s+Decisions?\s*\n([\s\S]+?)(?=\n##|$)/i);
496
500
  if (decisionsSection) {
497
501
  const decisionContent = decisionsSection[1];
498
502
  const decisionRegex = /###\s+(.+?)\n([\s\S]+?)(?=\n###|$)/g;
499
503
  let match;
500
-
504
+
501
505
  while ((match = decisionRegex.exec(decisionContent)) !== null) {
502
506
  const topic = match[1].trim();
503
507
  const body = match[2].trim();
504
-
508
+
505
509
  const decisionMatch = body.match(/decision[:\s]+(.+)/i);
506
510
  const rationaleMatch = body.match(/rationale[:\s]+(.+)/i);
507
-
511
+
508
512
  research.decisions.push({
509
513
  topic,
510
514
  decision: decisionMatch ? decisionMatch[1].trim() : body.split('\n')[0],
@@ -512,31 +516,31 @@ async function parseResearch(researchPath) {
512
516
  });
513
517
  }
514
518
  }
515
-
519
+
516
520
  // Parse alternatives section
517
521
  const alternativesSection = content.match(/##\s+Alternatives?\s*\n([\s\S]+?)(?=\n##|$)/i);
518
522
  if (alternativesSection) {
519
523
  const altContent = alternativesSection[1];
520
524
  const altRegex = /###\s+(.+?)\n([\s\S]+?)(?=\n###|$)/g;
521
525
  let match;
522
-
526
+
523
527
  while ((match = altRegex.exec(altContent)) !== null) {
524
528
  const name = match[1].trim();
525
529
  const body = match[2].trim();
526
-
530
+
527
531
  // Extract pros and cons
528
532
  const prosMatch = body.match(/pros?[:\s]*([\s\S]+?)(?=cons?|rejected|$)/i);
529
533
  const consMatch = body.match(/cons?[:\s]*([\s\S]+?)(?=pros?|rejected|$)/i);
530
534
  const rejectedMatch = body.match(/rejected[:\s]*(yes|no|true|false)/i);
531
535
  const reasonMatch = body.match(/reason[:\s]+(.+)/i);
532
-
533
- const pros = prosMatch
536
+
537
+ const pros = prosMatch
534
538
  ? (prosMatch[1].match(/^[-*]\s+(.+)/gm) || []).map(p => p.replace(/^[-*]\s+/, ''))
535
539
  : [];
536
- const cons = consMatch
540
+ const cons = consMatch
537
541
  ? (consMatch[1].match(/^[-*]\s+(.+)/gm) || []).map(c => c.replace(/^[-*]\s+/, ''))
538
542
  : [];
539
-
543
+
540
544
  research.alternatives.push({
541
545
  name,
542
546
  pros,
@@ -546,37 +550,37 @@ async function parseResearch(researchPath) {
546
550
  });
547
551
  }
548
552
  }
549
-
553
+
550
554
  return research;
551
555
  }
552
556
 
553
557
  /**
554
558
  * Parse data model file
555
- * @param {string} dataModelPath
559
+ * @param {string} dataModelPath
556
560
  * @returns {Promise<import('../ir/types').DataModelIR>}
557
561
  */
558
562
  async function parseDataModel(dataModelPath) {
559
563
  const content = await fs.readFile(dataModelPath, 'utf-8');
560
-
564
+
561
565
  const dataModel = {
562
566
  entities: [],
563
567
  relationships: [],
564
568
  rawContent: content,
565
569
  };
566
-
570
+
567
571
  // Parse entities (look for ### Entity: Name or ### Name patterns)
568
572
  const entityRegex = /###\s+(?:Entity:?\s+)?(\w+)\s*\n([\s\S]+?)(?=\n###|$)/gi;
569
573
  let match;
570
-
574
+
571
575
  while ((match = entityRegex.exec(content)) !== null) {
572
576
  const name = match[1];
573
577
  const body = match[2].trim();
574
-
578
+
575
579
  // Extract fields from table or list
576
580
  const fields = [];
577
581
  const fieldRegex = /[-*]\s+(\w+):\s+(.+)/g;
578
582
  let fieldMatch;
579
-
583
+
580
584
  while ((fieldMatch = fieldRegex.exec(body)) !== null) {
581
585
  fields.push({
582
586
  name: fieldMatch[1],
@@ -585,21 +589,21 @@ async function parseDataModel(dataModelPath) {
585
589
  unique: false,
586
590
  });
587
591
  }
588
-
592
+
589
593
  dataModel.entities.push({
590
594
  name,
591
595
  description: '',
592
596
  fields,
593
597
  });
594
598
  }
595
-
599
+
596
600
  // Parse relationships
597
601
  const relationshipSection = content.match(/##\s+Relationships?\s*\n([\s\S]+?)(?=\n##|$)/i);
598
602
  if (relationshipSection) {
599
603
  const relContent = relationshipSection[1];
600
604
  const relRegex = /(\w+)\s*(?:→|->|has many|has one|belongs to)\s*(\w+)/gi;
601
605
  let relMatch;
602
-
606
+
603
607
  while ((relMatch = relRegex.exec(relContent)) !== null) {
604
608
  dataModel.relationships.push({
605
609
  from: relMatch[1],
@@ -608,38 +612,42 @@ async function parseDataModel(dataModelPath) {
608
612
  });
609
613
  }
610
614
  }
611
-
615
+
612
616
  return dataModel;
613
617
  }
614
618
 
615
619
  /**
616
620
  * Parse contracts directory
617
- * @param {string} contractsPath
621
+ * @param {string} contractsPath
618
622
  * @returns {Promise<import('../ir/types').ContractIR[]>}
619
623
  */
620
624
  async function parseContracts(contractsPath) {
621
625
  const contracts = [];
622
-
626
+
623
627
  try {
624
628
  const entries = await fs.readdir(contractsPath, { withFileTypes: true });
625
-
629
+
626
630
  for (const entry of entries) {
627
631
  if (entry.isFile() && entry.name.endsWith('.md')) {
628
632
  const contractFile = path.join(contractsPath, entry.name);
629
633
  const content = await fs.readFile(contractFile, 'utf-8');
630
-
634
+
631
635
  // Determine contract type
632
636
  let type = 'other';
633
637
  if (content.includes('REST') || content.includes('GET') || content.includes('POST')) {
634
638
  type = 'rest';
635
- } else if (content.includes('GraphQL') || content.includes('query') || content.includes('mutation')) {
639
+ } else if (
640
+ content.includes('GraphQL') ||
641
+ content.includes('query') ||
642
+ content.includes('mutation')
643
+ ) {
636
644
  type = 'graphql';
637
645
  } else if (content.includes('gRPC')) {
638
646
  type = 'grpc';
639
647
  } else if (content.includes('WebSocket')) {
640
648
  type = 'websocket';
641
649
  }
642
-
650
+
643
651
  contracts.push({
644
652
  type,
645
653
  name: entry.name.replace('.md', ''),
@@ -651,31 +659,31 @@ async function parseContracts(contractsPath) {
651
659
  } catch (error) {
652
660
  console.warn(`Warning: Failed to parse contracts: ${error.message}`);
653
661
  }
654
-
662
+
655
663
  return contracts;
656
664
  }
657
665
 
658
666
  /**
659
667
  * Parse templates from steering/templates/
660
- * @param {string} projectPath
668
+ * @param {string} projectPath
661
669
  * @returns {Promise<import('../ir/types').TemplateIR[]>}
662
670
  */
663
671
  async function parseTemplates(projectPath) {
664
672
  const templatesPath = path.join(projectPath, 'steering', 'templates');
665
673
  const templates = [];
666
-
667
- if (!await fs.pathExists(templatesPath)) {
674
+
675
+ if (!(await fs.pathExists(templatesPath))) {
668
676
  return templates;
669
677
  }
670
-
678
+
671
679
  try {
672
680
  const entries = await fs.readdir(templatesPath, { withFileTypes: true });
673
-
681
+
674
682
  for (const entry of entries) {
675
683
  if (entry.isFile() && entry.name.endsWith('.md')) {
676
684
  const templateFile = path.join(templatesPath, entry.name);
677
685
  const content = await fs.readFile(templateFile, 'utf-8');
678
-
686
+
679
687
  // Determine template type
680
688
  let type = 'other';
681
689
  if (entry.name.includes('spec')) {
@@ -685,7 +693,7 @@ async function parseTemplates(projectPath) {
685
693
  } else if (entry.name.includes('task')) {
686
694
  type = 'tasks';
687
695
  }
688
-
696
+
689
697
  templates.push({
690
698
  name: entry.name.replace('.md', ''),
691
699
  type,
@@ -696,31 +704,31 @@ async function parseTemplates(projectPath) {
696
704
  } catch (error) {
697
705
  console.warn(`Warning: Failed to parse templates: ${error.message}`);
698
706
  }
699
-
707
+
700
708
  return templates;
701
709
  }
702
710
 
703
711
  /**
704
712
  * Parse memories from steering/memories/
705
- * @param {string} projectPath
713
+ * @param {string} projectPath
706
714
  * @returns {Promise<import('../ir/types').MemoryIR[]>}
707
715
  */
708
716
  async function parseMemories(projectPath) {
709
717
  const memoriesPath = path.join(projectPath, 'steering', 'memories');
710
718
  const memories = [];
711
-
712
- if (!await fs.pathExists(memoriesPath)) {
719
+
720
+ if (!(await fs.pathExists(memoriesPath))) {
713
721
  return memories;
714
722
  }
715
-
723
+
716
724
  try {
717
725
  const entries = await fs.readdir(memoriesPath, { withFileTypes: true });
718
-
726
+
719
727
  for (const entry of entries) {
720
728
  if (entry.isFile() && entry.name.endsWith('.md')) {
721
729
  const memoryFile = path.join(memoriesPath, entry.name);
722
730
  const content = await fs.readFile(memoryFile, 'utf-8');
723
-
731
+
724
732
  // Determine memory type
725
733
  let type = 'context';
726
734
  if (entry.name.includes('decision')) {
@@ -728,7 +736,7 @@ async function parseMemories(projectPath) {
728
736
  } else if (entry.name.includes('learning')) {
729
737
  type = 'learning';
730
738
  }
731
-
739
+
732
740
  memories.push({
733
741
  category: type,
734
742
  entries: [{ content, source: entry.name }],
@@ -738,7 +746,7 @@ async function parseMemories(projectPath) {
738
746
  } catch (error) {
739
747
  console.warn(`Warning: Failed to parse memories: ${error.message}`);
740
748
  }
741
-
749
+
742
750
  return memories;
743
751
  }
744
752