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.
- 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 +158 -146
- package/src/analyzers/codegraph-auto-update.js +858 -0
- package/src/analyzers/complexity-analyzer.js +536 -0
- package/src/analyzers/context-optimizer.js +241 -126
- package/src/analyzers/impact-analyzer.js +1 -1
- package/src/analyzers/large-project-analyzer.js +766 -0
- package/src/analyzers/repository-map.js +77 -81
- 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 +48 -46
- package/src/monitoring/incident-manager.js +116 -106
- package/src/monitoring/index.js +144 -134
- package/src/monitoring/observability.js +75 -62
- package/src/monitoring/quality-dashboard.js +45 -41
- package/src/monitoring/release-manager.js +63 -53
- 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,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 {
|
|
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(/[
|
|
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 (
|
|
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
|
|