mddd-cli 2.1.2 → 2.1.4
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/bin/cli.js +1 -1
- package/package.json +6 -5
- package/readme.md +2 -1
- package/src/commands/audit.js +22 -0
- package/src/commands/edit.js +19 -0
- package/src/commands/impl.js +16 -0
- package/src/commands/init.js +234 -0
- package/src/commands/new.js +56 -0
- package/src/services/AuditService.js +41 -0
- package/src/services/FileSystemService.js +100 -0
- package/src/services/ImplValidator.js +27 -0
- package/src/services/InitService.js +52 -0
- package/src/services/ParentLinker.js +70 -0
- package/src/services/SpecEditor.js +37 -0
- package/src/services/SpecGenerator.js +58 -0
- package/src/services/SpecValidator.js +27 -0
- package/src/services/TemplateFactory.js +80 -0
package/bin/cli.js
CHANGED
|
@@ -32,7 +32,7 @@ const program = new Command();
|
|
|
32
32
|
program
|
|
33
33
|
.name('md')
|
|
34
34
|
.description('Manager for co-located specifications for Mermaid Diagram Driven Development (MDDD)')
|
|
35
|
-
.version('2.1.
|
|
35
|
+
.version('2.1.3');
|
|
36
36
|
|
|
37
37
|
// ==========================================
|
|
38
38
|
// COMMAND: md init
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mddd-cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "Official CLI for modular, co-located, and versioned Mermaid Diagram Driven Development (MDDD).",
|
|
5
|
-
"main": "bin/cli.js",
|
|
6
5
|
"type": "module",
|
|
6
|
+
"exports": "./bin/cli.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"md": "bin/cli.js"
|
|
8
|
+
"md": "./bin/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"bin
|
|
11
|
+
"bin",
|
|
12
|
+
"src"
|
|
12
13
|
],
|
|
13
14
|
"engines": {
|
|
14
15
|
"node": ">=18.0.0"
|
|
@@ -40,4 +41,4 @@
|
|
|
40
41
|
"commander": "^12.0.0",
|
|
41
42
|
"picocolors": "^1.0.0"
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
|
+
}
|
package/readme.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="#english">🇺🇸 English</a> •
|
|
11
|
-
<a href="#
|
|
11
|
+
<a href="#portuguese">🇧🇷 Português</a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
---
|
|
@@ -307,6 +307,7 @@ If you encounter any issues, open a [GitHub Issue](https://github.com/JulioCRFil
|
|
|
307
307
|
Distributed under the MIT license. See the [LICENSE](https://www.google.com/search?q=LICENSE) file for more information.
|
|
308
308
|
|
|
309
309
|
---
|
|
310
|
+
<a id="portuguese"></a>
|
|
310
311
|
|
|
311
312
|
# 🇧🇷 Português
|
|
312
313
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { SpecGenerator } from '../services/SpecGenerator.js';
|
|
3
|
+
import { AuditService } from '../services/AuditService.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Executes the `md audit <codeFilePath>` command.
|
|
7
|
+
* @param {AuditService} auditService
|
|
8
|
+
* @param {SpecGenerator} specGenerator
|
|
9
|
+
* @param {string} codeFilePath
|
|
10
|
+
* @returns {Promise<void>}
|
|
11
|
+
*/
|
|
12
|
+
export async function execute(auditService, specGenerator, codeFilePath) {
|
|
13
|
+
auditService.validateCodeFile(codeFilePath);
|
|
14
|
+
|
|
15
|
+
const { specFilePath: finalSpecPath } = await specGenerator.createIfMissing(codeFilePath);
|
|
16
|
+
const result = auditService.run(codeFilePath, finalSpecPath);
|
|
17
|
+
|
|
18
|
+
console.log(pc.blue(`📄 Existing specification: ${finalSpecPath}`));
|
|
19
|
+
console.log(pc.cyan(`🔍 Auditing code structure for coupling in: ${result.codeBasename}...`));
|
|
20
|
+
console.log(pc.yellow(`⚡ The AI will validate complexity and write the analysis to: ${finalSpecPath}`));
|
|
21
|
+
console.log(pc.green(`\n🚀 Ready! Use the /md-audit shortcut in chat for the AI to write the analysis and structural refactoring diagram into the co-located spec file.`));
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executes the `md edit <specFilePath>` command.
|
|
5
|
+
* @param {import('../services/SpecEditor.js').SpecEditor} specEditor
|
|
6
|
+
* @param {string} specFilePath
|
|
7
|
+
* @param {string[]} instructionParts
|
|
8
|
+
* @returns {void}
|
|
9
|
+
*/
|
|
10
|
+
export function execute(specEditor, specFilePath, instructionParts) {
|
|
11
|
+
specEditor.validateSpec(specFilePath);
|
|
12
|
+
|
|
13
|
+
const fullInstruction = instructionParts.join(' ');
|
|
14
|
+
const prepared = specEditor.prepareInstruction(specFilePath, fullInstruction);
|
|
15
|
+
|
|
16
|
+
console.log(pc.cyan(`📝 Requesting alteration in flow: "${prepared.specFilePath}"`));
|
|
17
|
+
console.log(pc.yellow(`⚙️ Evaluated instruction: ${prepared.instruction}`));
|
|
18
|
+
console.log(pc.green(`\n🚀 Ready! Use the /md-edit shortcut in chat for the AI to apply changes to the diagram and increment the version.`));
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executes the `md impl <specFilePath>` command.
|
|
5
|
+
* @param {import('../services/ImplValidator.js').ImplValidator} implValidator
|
|
6
|
+
* @param {string} specFilePath
|
|
7
|
+
* @returns {void}
|
|
8
|
+
*/
|
|
9
|
+
export function execute(implValidator, specFilePath) {
|
|
10
|
+
implValidator.validate(specFilePath);
|
|
11
|
+
|
|
12
|
+
const fileName = specFilePath.split('/').pop() || specFilePath.split('\\').pop();
|
|
13
|
+
console.log(pc.cyan(`🛠️ Reading business blueprint from: ${fileName}...`));
|
|
14
|
+
console.log(pc.yellow(`🎯 Establishing the signed diagram as the Single Source of Truth.`));
|
|
15
|
+
console.log(pc.green(`\n🚀 Ready! Use the /md-impl shortcut in chat for the AI to start generating productive code and tests.`));
|
|
16
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PROMPT SYSTEM CONTENT: the full MDDD universal system prompt text.
|
|
5
|
+
* (Embedded here to maintain co-location; refactored from the monolithic cli.js)
|
|
6
|
+
*/
|
|
7
|
+
export const SYSTEM_PROMPT_CONTENT = `# Mermaid Diagram Driven Development (MDDD) Protocol
|
|
8
|
+
|
|
9
|
+
You are an engineering agent operating strictly under MDDD. Your cognitive processing is guided by visual topologies and truth tables, completely eliminating text-based specification ambiguity.
|
|
10
|
+
|
|
11
|
+
\`\`\`mermaid
|
|
12
|
+
%% @spec-version v1.0.0
|
|
13
|
+
stateDiagram-v2
|
|
14
|
+
[*] --> ReadSpecification: User Trigger Fired
|
|
15
|
+
ReadSpecification --> CheckDecisionMatrix: Evaluate Primitive Factors
|
|
16
|
+
CheckDecisionMatrix --> HaltWithConflict: Constraint Violation / Feature Creep
|
|
17
|
+
CheckDecisionMatrix --> ExecuteAction: Strict Match Confirmed
|
|
18
|
+
ExecuteAction --> MutateState: Apply File/Code Changes
|
|
19
|
+
MutateState --> UpdateVersionHeader: Apply Semantic Version Rules
|
|
20
|
+
UpdateVersionHeader --> [*]
|
|
21
|
+
\`\`\`
|
|
22
|
+
|
|
23
|
+
## 1. Co-location Architecture Tree
|
|
24
|
+
|
|
25
|
+
src/
|
|
26
|
+
└── [domain]/
|
|
27
|
+
├── [domain_name].spec.md # 🌎 Macro Module Domain
|
|
28
|
+
└── [feature_name]/
|
|
29
|
+
├── [feature_name].spec.md # 🔬 Micro Flow Contract + Decision Matrix
|
|
30
|
+
└── [feature_name].* # 💻 Target Production Code File (Any Extension)
|
|
31
|
+
|
|
32
|
+
## 2. Parent Interaction Logic
|
|
33
|
+
|
|
34
|
+
\`\`\`mermaid
|
|
35
|
+
graph TD
|
|
36
|
+
A[Create/Change Sub-Feature] --> B[Open Indicated Parent File]
|
|
37
|
+
B --> C[Locate Bifurcation Node in Parent Mermaid]
|
|
38
|
+
C --> D[Modify Parent Graph: Point Arrow to New State]
|
|
39
|
+
D --> E[Child File: Inherit Parent Context in Entry Node]
|
|
40
|
+
\`\`\`
|
|
41
|
+
|
|
42
|
+
## 3. Core Behavioral Framework Matrix
|
|
43
|
+
|
|
44
|
+
| User Context | Target Spec Header | Human Request Path | Diagram Change Impact | AI Core Rule / Mandate / Ironclad Clause |
|
|
45
|
+
| :---: | :---: | :---: | :---: | :--- |
|
|
46
|
+
| | - | **MISSING** | - | - | Never remove, omit, or bypass the version tag from files. |
|
|
47
|
+
| | Code Change Needed | **SIGNED** | Contradicts Matrix | - | 🛑 **HALT**: Refuse code generation. Demand \`md edit\` to align design first. |
|
|
48
|
+
| | Feature Writing | - | Continuous Text Block | - | 📊 **STRUCTURE**: Convert text into tables of primitive factors (yes/no/rigid values). |
|
|
49
|
+
| | Command Executed | \`SPEC_VERSION\` | - | Typo / Label Only | Increment Patch (\`X.Y.Z\` -> \`X.Y.Z+1\`) |
|
|
50
|
+
| | Command Executed | \`SPEC_VERSION\` | - | New State / Arrow / Matrix Column | Increment Minor (\`X.Y.Z\` -> \`X.Y+1.0\`) |
|
|
51
|
+
| | Command Executed | \`SPEC_VERSION\` | - | Structural Breaking / Flow Overhaul | Increment Major (\`X.Y.Z\` -> \`X+1.0.0\`) |
|
|
52
|
+
|
|
53
|
+
## 4. Anti-Hallucination Guardrails
|
|
54
|
+
1. **No Spec, No Code:** You are strictly forbidden from writing a single line of production code or unit tests if the corresponding \`.spec.md\` file does not exist or does not contain a populated Decision Matrix.
|
|
55
|
+
2. **Implicit Logic Ban:** If a business condition, validation check, or outcome branch is not explicitly listed as a row or column in the Decision Matrix, it does not exist. Do not assume, extrapolate, or invent fallback behaviors.
|
|
56
|
+
3. **Strict State Isolation:** When handling a micro feature, you cannot introduce global states or modify sibling domains unless instructed via explicit macro architectural mapping updates.
|
|
57
|
+
4. **Idempotent Full-File Output Mandate:** You are completely forbidden from using code placeholders, truncating files, or emitting partial snippets (e.g., "// rest of class unchanged", "/* TODO */"). Every code generation action must output the entire, clean, compile-ready file from scratch, ensuring perfect context preservation.`;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Skills content map: skill name -> SKILL.md content.
|
|
61
|
+
*/
|
|
62
|
+
export const SKILLS = {
|
|
63
|
+
'md-new': `[ROLE: ARCHITECT] [STRICT CONTRACT]
|
|
64
|
+
|
|
65
|
+
\`\`\`mermaid
|
|
66
|
+
%% @spec-version v1.1.0
|
|
67
|
+
stateDiagram-v2
|
|
68
|
+
[*] --> TargetVerification
|
|
69
|
+
TargetVerification --> StopAndSwitchToEdit: .spec.md File Already Exists
|
|
70
|
+
TargetVerification --> EvaluateContext: File Does Not Exist
|
|
71
|
+
|
|
72
|
+
state EvaluateContext {
|
|
73
|
+
[*] --> CheckDirectoryDepth
|
|
74
|
+
CheckDirectoryDepth --> InferMacro: Target is Domain Root (e.g., src/domain)
|
|
75
|
+
CheckDirectoryDepth --> InferMicro: Target is Sub-Feature (e.g., src/domain/feature)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
InferMacro --> ExecCliNew: Apply stateDiagram-v2 Template
|
|
79
|
+
InferMicro --> ExecCliNew: Apply graph LR + Matrix Template
|
|
80
|
+
|
|
81
|
+
ExecCliNew --> AwaitHumanReview: Run "md new [path]" & Populate Blueprint
|
|
82
|
+
AwaitHumanReview --> [*]: Pause Code & Test Generation
|
|
83
|
+
\`\`\`
|
|
84
|
+
|
|
85
|
+
### Operational Execution Matrix
|
|
86
|
+
|
|
87
|
+
| File Exists? | Path Depth Type | Parent Indicated? | CLI Execution Syntax | Target Payload Blueprint | Next AI Action |
|
|
88
|
+
| :---: | :---: | :---: | :--- | :--- | :---: |
|
|
89
|
+
| ✅ YES | - | - | *None* (Aborted) | *None* | 🛑 **STOP** (Call md-edit instead) |
|
|
90
|
+
| ❌ NO | Domain Root | ❌ NO | \`md new [domain_path]\` | \`stateDiagram-v2\` Placeholder Domain Map | ⏳ **AWAIT_VISUAL_APPROVAL** |
|
|
91
|
+
| ❌ NO | Sub-Feature | ❌ NO | \`md new [feature_path]\` | \`graph LR\` + Auto-scanned parent link reference | ⏳ **AWAIT_VISUAL_APPROVAL** |
|
|
92
|
+
| ❌ NO | Sub-Feature | ✅ YES | \`md new [feature_path] -p[parent]\` | \`graph LR\` + Explicit link injected to designated Parent | ⏳ **AWAIT_VISUAL_APPROVAL** |
|
|
93
|
+
|
|
94
|
+
### Automation & Inference Ironclad Rules
|
|
95
|
+
1. **Deterministic Inference:** You must strictly follow the directory depth. If the target path is a top-level domain folder inside your source root, treat it as a Module Macro. If it is nested inside a domain, it is a Micro Feature. Never ask the user to declare this.
|
|
96
|
+
2. **Implicit Parent Binding:** When creating a Sub-Feature without an explicit \`-p\` parameter, acknowledge that the CLI tool will automatically scan and mutate the nearest parent macro file via recursive climbing. You must read the updated parent immediately after execution to synchronize your internal context map.
|
|
97
|
+
3. Agnostic Blueprint Initialization: When generating the initial blueprint files, you must scan the neighboring files in the target domain directory to identify the current programming language and framework conventions. Adapt your placeholder references to strictly pair with the localized file architecture.`,
|
|
98
|
+
|
|
99
|
+
'md-edit': `[ROLE: ARCHITECT] [STRICT CONTRACT]
|
|
100
|
+
|
|
101
|
+
\`\`\`mermaid
|
|
102
|
+
%% @spec-version v1.1.0
|
|
103
|
+
graph LR
|
|
104
|
+
A[Read Target .spec.md] --> B[Parse Current SPEC_VERSION]
|
|
105
|
+
B --> C[Apply Mermaid/Matrix Adjustments]
|
|
106
|
+
C --> D{Evaluate Mutation Scope}
|
|
107
|
+
|
|
108
|
+
D -->|Typo / Label Fix| E[Increment Patch: Bump Z in X.Y.Z]
|
|
109
|
+
D -->|New Node / Flow Path / Factor| F[Increment Minor: Bump Y in X.Y.Z]
|
|
110
|
+
D -->|Breaking Overhaul / Restructure| G[Increment Major: Bump X in X.Y.Z]
|
|
111
|
+
|
|
112
|
+
E --> H[Validate Mermaid Syntax]
|
|
113
|
+
F --> H
|
|
114
|
+
G --> H
|
|
115
|
+
|
|
116
|
+
H -->|Syntax Valid| I[Save Contract & Halt]
|
|
117
|
+
H -->|Syntax Invalid| J[🛑 HALT: Abort & Ask Human]
|
|
118
|
+
\`\`\`
|
|
119
|
+
|
|
120
|
+
### Evolution Versioning Matrix
|
|
121
|
+
|
|
122
|
+
| Structural Change Type | Adds Factor Column? | Adds Transition Node/Arrow? | Label / Typo Corrections Only? | Semantic Version Modification | Target AI State |
|
|
123
|
+
| :--- | :---: | :---: | :---: | :---: | :---: |
|
|
124
|
+
| Complete Business Overhaul | - | - | - | **MAJOR Mutation (X.Y.Z -> X+1.0.0)** | ⏳ **AWAIT_USER_VALIDATION** |
|
|
125
|
+
| New Context Conditional Branch | ✅ YES | - | - | **MINOR Mutation (X.Y.Z -> X.Y+1.0)** | ⏳ **AWAIT_USER_VALIDATION** |
|
|
126
|
+
| New UI Flow Step / Lifecycle State | ❌ NO | ✅ YES | - | **MINOR Mutation (X.Y.Z -> X.Y+1.0)** | ⏳ **AWAIT_USER_VALIDATION** |
|
|
127
|
+
| Visual Spacing / Text Refinement | ❌ NO | ❌ NO | ✅ YES | **PATCH Mutation (X.Y.Z -> X.Y.Z+1)** | ⏳ **AWAIT_USER_VALIDATION** |
|
|
128
|
+
|
|
129
|
+
### Mutation Integrity Ironclad Rules
|
|
130
|
+
1. **Incremental SemVer Locking:** You must read the existing \`SPEC_VERSION\` from the file header before modifying it. Never reset, guess, or overwrite the version to a lower state. Bumping Minor explicitly drops the patch version to zero (\`X.Y.Z\` -> \`X.Y+1.0\`). Bumping Major explicitly drops both minor and patch to zero (\`X.Y.Z\` -> \`X+1.0.0\`).
|
|
131
|
+
2. **Strict Syntax Guard:** Before writing the modifications to disk, execute an internal mental compilation of the Mermaid syntax. If any arrow (\`-->\`), state connector, or label syntax breaks the official Mermaid spec, immediately halt execution and report the error to the user without modifying the file.
|
|
132
|
+
3. **Audit History Log Requirement:** Every time you perform an edit, you must append a new row to the markdown table inside the \`<details><summary>Click to expand</summary>...</details>\` block at the bottom of the file, containing the current date, your agent identity, the new version number, and a concise summary of the changes made.
|
|
133
|
+
4. **Node ID Immutability:** When adding new transitions or nodes to an existing graph, you are strictly forbidden from altering, renaming, or refactoring the identifiers (IDs) of existing states/nodes unless explicitly requested by the user.`,
|
|
134
|
+
|
|
135
|
+
'md-audit': `[ROLE: SECURITY & QUALITY AUDITOR] [STRICT CONTRACT]
|
|
136
|
+
|
|
137
|
+
\`\`\`mermaid
|
|
138
|
+
%% @spec-version v1.1.0
|
|
139
|
+
stateDiagram-v2
|
|
140
|
+
[*] --> AnalyzeLegacyCode: Evaluate Coupling & Scope Leaks
|
|
141
|
+
AnalyzeLegacyCode --> FileSystemCheck
|
|
142
|
+
|
|
143
|
+
state FileSystemCheck {
|
|
144
|
+
[*] --> CheckCoLocation
|
|
145
|
+
CheckCoLocation --> CreateMissingSpec: Target Co-located .spec.md Missing
|
|
146
|
+
CheckCoLocation --> AppendToExisting: Target Co-located .spec.md Exists
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
CreateMissingSpec --> RenderTopology: Initialize New .spec.md
|
|
150
|
+
AppendToExisting --> InjectAuditBlock: Target Existing File Preservation Map
|
|
151
|
+
|
|
152
|
+
state RenderTopology {
|
|
153
|
+
[*] --> CodeIsClean: Map exact architecture as-is (v1.0.0)
|
|
154
|
+
[*] --> CodeIsChaotic: Draw BOTH current real logic AND ideal target refactored graph
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
RenderTopology --> WriteToAuditTag: Inject payloads inside <details> block
|
|
158
|
+
InjectAuditBlock --> WriteToAuditTag: Append to existing <details> block without overwriting business specs
|
|
159
|
+
WriteToAuditTag --> EnforceImmutability: Lock Production Code File
|
|
160
|
+
EnforceImmutability --> [*]
|
|
161
|
+
\`\`\`
|
|
162
|
+
|
|
163
|
+
### Reverse Engineering & Auto-Repair Decision Matrix
|
|
164
|
+
|
|
165
|
+
| Source File State | Co-located .spec.md Exists? | Code Design Assessment | Target Output Destination | Code File Manipulation Allowed? | Initial Compiled Version |
|
|
166
|
+
| :--- | :---: | :---: | :--- | :---: | :---: |
|
|
167
|
+
| Legacy Code Active | ✅ YES | Clean / Modular | Append to existing \`<details><summary>Audit History</summary>\` | ❌ **FORBIDDEN (Immutability)** | Retain Current |
|
|
168
|
+
| Legacy Code Active | ✅ YES | Chaotic / Coupled | Append to existing \`<details><summary>Audit History</summary>\` | ❌ **FORBIDDEN (Immutability)** | Retain Current |
|
|
169
|
+
| Legacy Code Active | ❌ NO | Clean / Modular | Auto-generate Spec File + Map Current Logic | ❌ **FORBIDDEN (Immutability)** | \`v1.0.0\` |
|
|
170
|
+
| Legacy Code Active | ❌ NO | Chaotic / Coupled | Auto-generate Spec File + Map Current AND Proposed Logic | ❌ **FORBIDDEN (Immutability)** | \`v1.0.0\` |
|
|
171
|
+
|
|
172
|
+
### Missing Spec Auto-Repair Blueprint Requirements
|
|
173
|
+
* **Enforce Section Injections:** Every auto-generated specification file must structurally enforce:
|
|
174
|
+
1. \`SPEC_VERSION: v1.0.0\` metadata header at the very top.
|
|
175
|
+
2. \`stateDiagram-v2\` or \`graph LR\` derived exactly from code logic behaviors.
|
|
176
|
+
3. \`Decision Matrix\` tables filled if the code contains conditional execution branches.
|
|
177
|
+
4. An isolated \`<details><summary>Audit History</summary>...</details>\` block at the bottom containing the specific code review analytics.
|
|
178
|
+
|
|
179
|
+
### Quality Assurance & Immutability Ironclad Rules
|
|
180
|
+
1. **Absolute Immutability Command:** Under no circumstances are you allowed to patch, alter, or modify the target production code file during the \`md-audit\` cycle. Your execution scope is strictly limited to observation and documentation within the Markdown specification file.
|
|
181
|
+
2. **Preservation Guarantee:** When appending an audit report to an existing \`.spec.md\` file, you must read the file completely and guarantee that the business requirements, main diagrams, and current decision matrices are left untouched. You are only allowed to inject rows inside the \`<details>\` audit history block.
|
|
182
|
+
3. **Chaotic Code Double-Mapping:** If you evaluate the legacy code as chaotic or highly coupled, you must not replace the current reality with your ideal version. You are required to draw the current graph (flawed as it is) to serve as a baseline, and then provide a separate, clearly labeled Mermaid graph showing the suggested refactored topology.`,
|
|
183
|
+
|
|
184
|
+
'md-impl': `[ROLE: SOFTWARE ENGINEER] [STRICT CONTRACT]
|
|
185
|
+
|
|
186
|
+
\`\`\`mermaid
|
|
187
|
+
%% @spec-version v1.1.0
|
|
188
|
+
graph TD
|
|
189
|
+
A[Ingest Signed .spec.md] --> B[Parse Matrix Rows & Version Header]
|
|
190
|
+
B --> C{Verify Code/Chat Request}
|
|
191
|
+
|
|
192
|
+
C -->|Matches Decision Matrix Rows 100%| D[Check File Target State]
|
|
193
|
+
C -->|Human Asks to Skip/Add Extraneous Scope| E[Trigger Prompt Injection Defense]
|
|
194
|
+
|
|
195
|
+
D -->|New File| F[Generate Full Structural Code from Scratch]
|
|
196
|
+
D -->|Existing File| G[Idempotent Overwrite: Read & Output Full File]
|
|
197
|
+
|
|
198
|
+
F --> H[Generate Truth-Table Unit Tests]
|
|
199
|
+
G --> H
|
|
200
|
+
|
|
201
|
+
H --> I[Verify 100% Branch Coverage Alignment]
|
|
202
|
+
I --> [*]
|
|
203
|
+
|
|
204
|
+
E --> J[Refuse Coding & Demand Spec Refinement via md-edit]
|
|
205
|
+
J --> [*]
|
|
206
|
+
\`\`\`
|
|
207
|
+
|
|
208
|
+
### Injection Defense & Execution Guard Matrix
|
|
209
|
+
|
|
210
|
+
| Spec Contract Signed? | Chat Prompt Code Alignment | Human Requests Bypassing Spec Matrix? | Core AI Action Authorized | Error Response Pattern |
|
|
211
|
+
| :---: | :---: | :---: | :--- | :--- |
|
|
212
|
+
| ❌ NO | - | - | ❌ **DENY GENERATION** | Demand invocation of \`md-new\` or \`md-audit\` |
|
|
213
|
+
| ✅ YES | ❌ Out-of-bounds | - | ❌ **DENY GENERATION** | "Please use the md-edit command to update the diagram..." |
|
|
214
|
+
| ✅ YES | - | ✅ YES (Feature Creep) | ❌ **DENY GENERATION** | "Please use the md-edit command to update the diagram..." |
|
|
215
|
+
| ✅ YES | ✅ 100% Rigid Match| ❌ NO | ✅ **ALLOW SOLID CODEGEN** | Complete compliance code + 100% matrix row unit tests |
|
|
216
|
+
|
|
217
|
+
### Production Implementation & Codegen Ironclad Rules
|
|
218
|
+
1. **The Matrix Test Alignment Mandate:** Your unit test suite must match the Decision Matrix row by row. For every single row present in the specification's truth table, you are strictly required to build at least one explicit, dedicated unit test case mapping those precise primitive factors to that exact outcome.
|
|
219
|
+
2. **Anti-Placeholder Clause:** You are absolutely forbidden from generating incomplete code structures, omitting code sections, or using placeholders like \`// TODO\`, \`// implementation goes here\`, or \`// rest of the class remains unchanged\`. You must always output the complete, compile-ready, and production-grade file layout.
|
|
220
|
+
3. **Strict SOLID Compliance:** Every piece of logic generated under this cycle must follow strict Clean Architecture principles and SOLID patterns. If the specification implies a new conditional branch, you must implement it using polymorphism or structured strategies rather than compounding nested \`if-else\` or pattern-matching anti-patterns unless explicitly dictated by the diagram topology.`,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Executes the `md init` command.
|
|
225
|
+
* @param {import('../services/InitService.js').InitService} initService
|
|
226
|
+
* @returns {Promise<void>}
|
|
227
|
+
*/
|
|
228
|
+
export async function execute(initService) {
|
|
229
|
+
await initService.createSystemPrompt(SYSTEM_PROMPT_CONTENT);
|
|
230
|
+
await initService.createSkills(SKILLS, (msg) => console.log(msg));
|
|
231
|
+
|
|
232
|
+
console.log(pc.green('\n🚀 Universal [system_prompt.md] and SKILLS generated successfully in the project root!'));
|
|
233
|
+
console.log(pc.green('Run the "md init" command whenever you update the MDDD-CLI NPM package.'));
|
|
234
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Executes the `md new <targetPath>` command.
|
|
6
|
+
* @param {import('../services/SpecGenerator.js').SpecGenerator} specGenerator
|
|
7
|
+
* @param {import('../services/ParentLinker.js').ParentLinker} parentLinker
|
|
8
|
+
* @param {import('../services/FileSystemService.js').FileSystemService} fs
|
|
9
|
+
* @param {string} targetPath
|
|
10
|
+
* @param {{ macro?: boolean, parent?: string }} options
|
|
11
|
+
* @returns {Promise<void>}
|
|
12
|
+
*/
|
|
13
|
+
export async function execute(specGenerator, parentLinker, fs, targetPath, options) {
|
|
14
|
+
const normalizedPath = path.normalize(targetPath).replace(/[\\/]+$/, '');
|
|
15
|
+
|
|
16
|
+
fs.ensureDir(normalizedPath);
|
|
17
|
+
|
|
18
|
+
const folderName = path.basename(normalizedPath);
|
|
19
|
+
const finalFile = path.join(normalizedPath, `${folderName}.spec.md`);
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(finalFile)) {
|
|
22
|
+
// Check if it's a directory (edge case)
|
|
23
|
+
try {
|
|
24
|
+
const stats = await fs.getRaw().stat?.(finalFile);
|
|
25
|
+
if (stats?.isDirectory()) {
|
|
26
|
+
console.log(pc.red(`❌ Error: A directory named ${finalFile} already exists. Cannot create specification file.`));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// stat not available in mock, fall through to normal check
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(finalFile)) {
|
|
35
|
+
console.log(pc.yellow(`⚠️ Specification already exists at: ${finalFile}. Operation aborted to avoid link duplication in the parent file.`));
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const isMacro = options.macro || false;
|
|
40
|
+
const version = 'v1.0.0';
|
|
41
|
+
|
|
42
|
+
const { filePath } = await specGenerator.create(normalizedPath, isMacro, version);
|
|
43
|
+
console.log(pc.green(`✅ New specification file created: ${filePath}`));
|
|
44
|
+
|
|
45
|
+
let macroPath = options.parent || (!isMacro ? parentLinker.findClosestMacro(normalizedPath) : null);
|
|
46
|
+
|
|
47
|
+
if (macroPath) {
|
|
48
|
+
if (!fs.existsSync(macroPath)) {
|
|
49
|
+
console.log(pc.red(`❌ Specified parent file not found: ${macroPath}`));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await parentLinker.linkToParent(macroPath, filePath, folderName);
|
|
54
|
+
console.log(pc.blue(`🔗 Successfully linked into parent flow: ${macroPath}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles the `md audit` command business logic.
|
|
3
|
+
*/
|
|
4
|
+
export class AuditService {
|
|
5
|
+
/** @type {import('./FileSystemService.js').FileSystemService} */
|
|
6
|
+
#fs;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('./FileSystemService.js').FileSystemService} fsService
|
|
10
|
+
*/
|
|
11
|
+
constructor(fsService) {
|
|
12
|
+
this.#fs = fsService;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates that a code file exists.
|
|
17
|
+
* @param {string} codeFilePath
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
* @throws {Error} if not found
|
|
20
|
+
*/
|
|
21
|
+
validateCodeFile(codeFilePath) {
|
|
22
|
+
if (!this.#fs.existsSync(codeFilePath)) {
|
|
23
|
+
throw new Error(`Code file not found: ${codeFilePath}`);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Runs audit placeholders (actual AI analysis happens in chat via /md-audit skill).
|
|
30
|
+
* @param {string} codeFilePath
|
|
31
|
+
* @param {string} specFilePath
|
|
32
|
+
* @returns {{ codeBasename: string, specFilePath: string }}
|
|
33
|
+
*/
|
|
34
|
+
run(codeFilePath, specFilePath) {
|
|
35
|
+
const basename = codeFilePath.split('/').pop() || codeFilePath.split('\\').pop();
|
|
36
|
+
return {
|
|
37
|
+
codeBasename: basename,
|
|
38
|
+
specFilePath,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} FileSystemOperations
|
|
7
|
+
* @property {(path: string) => Promise<string>} readFile
|
|
8
|
+
* @property {(path: string, content: string) => Promise<void>} writeFile
|
|
9
|
+
* @property {(path: string, content: string) => Promise<void>} appendFile
|
|
10
|
+
* @property {(path: string) => boolean} existsSync
|
|
11
|
+
* @property {(path: string) => void} mkdirSyncRecursive
|
|
12
|
+
* @property {(path: string) => string[]} readdirSync
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Shared file system service with dependency injection support for testability.
|
|
17
|
+
*/
|
|
18
|
+
export class FileSystemService {
|
|
19
|
+
/** @type {FileSystemOperations} */
|
|
20
|
+
#fs;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {Partial<FileSystemOperations>} [fsMock] - Optional mock for testing
|
|
24
|
+
*/
|
|
25
|
+
constructor(fsMock) {
|
|
26
|
+
this.#fs = {
|
|
27
|
+
readFile: fsMock?.readFile || fs.readFile.bind(fs),
|
|
28
|
+
writeFile: fsMock?.writeFile || fs.writeFile.bind(fs),
|
|
29
|
+
appendFile: fsMock?.appendFile || fs.appendFile.bind(fs),
|
|
30
|
+
existsSync: fsMock?.existsSync || existsSync,
|
|
31
|
+
mkdirSyncRecursive: fsMock?.mkdirSyncRecursive || ((p) => mkdirSync(p, { recursive: true })),
|
|
32
|
+
readdirSync: fsMock?.readdirSync || readdirSync,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Checks if a path exists synchronously.
|
|
38
|
+
* @param {string} path
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
existsSync(path) {
|
|
42
|
+
return this.#fs.existsSync(path);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a directory recursively.
|
|
47
|
+
* @param {string} path
|
|
48
|
+
*/
|
|
49
|
+
mkdirSyncRecursive(path) {
|
|
50
|
+
this.#fs.mkdirSyncRecursive(path);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a directory if it doesn't exist.
|
|
55
|
+
* @param {string} path
|
|
56
|
+
*/
|
|
57
|
+
ensureDir(path) {
|
|
58
|
+
if (!this.#fs.existsSync(path)) {
|
|
59
|
+
this.#fs.mkdirSyncRecursive(path);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reads a file as UTF-8 string.
|
|
65
|
+
* @param {string} path
|
|
66
|
+
* @returns {Promise<string>}
|
|
67
|
+
*/
|
|
68
|
+
async readFile(path) {
|
|
69
|
+
return this.#fs.readFile(path);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Writes a string to a file.
|
|
74
|
+
* @param {string} path
|
|
75
|
+
* @param {string} content
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
async writeFile(path, content) {
|
|
79
|
+
return this.#fs.writeFile(path, content);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Appends content to a file.
|
|
84
|
+
* @param {string} path
|
|
85
|
+
* @param {string} content
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async appendFile(path, content) {
|
|
89
|
+
return this.#fs.appendFile(path, content);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Synchronous readdir for directory crawling.
|
|
94
|
+
* @param {string} dir
|
|
95
|
+
* @returns {string[]}
|
|
96
|
+
*/
|
|
97
|
+
readdirSync(dir) {
|
|
98
|
+
return this.#fs.readdirSync(dir);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates prerequisites for the `md impl` command.
|
|
3
|
+
*/
|
|
4
|
+
export class ImplValidator {
|
|
5
|
+
/** @type {import('./FileSystemService.js').FileSystemService} */
|
|
6
|
+
#fs;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('./FileSystemService.js').FileSystemService} fsService
|
|
10
|
+
*/
|
|
11
|
+
constructor(fsService) {
|
|
12
|
+
this.#fs = fsService;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates that a spec file exists before implementation.
|
|
17
|
+
* @param {string} specFilePath
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
* @throws {Error} if not found
|
|
20
|
+
*/
|
|
21
|
+
validate(specFilePath) {
|
|
22
|
+
if (!this.#fs.existsSync(specFilePath)) {
|
|
23
|
+
throw new Error(`Specification file not found: ${specFilePath}`);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handles the `md init` business logic: system prompt and skills creation.
|
|
5
|
+
*/
|
|
6
|
+
export class InitService {
|
|
7
|
+
/** @type {import('./FileSystemService.js').FileSystemService} */
|
|
8
|
+
#fs;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {import('./FileSystemService.js').FileSystemService} fsService
|
|
12
|
+
*/
|
|
13
|
+
constructor(fsService) {
|
|
14
|
+
this.#fs = fsService;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates the universal system prompt file.
|
|
19
|
+
* @param {string} promptContent - The full MDDD system prompt content
|
|
20
|
+
* @returns {Promise<void>}
|
|
21
|
+
*/
|
|
22
|
+
async createSystemPrompt(promptContent) {
|
|
23
|
+
await this.#fs.writeFile('system_prompt.md', promptContent);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates all skill folders and SKILL.md files.
|
|
28
|
+
* @param {Record<string, string>} skills - Map of skill name to skill content
|
|
29
|
+
* @returns {Promise<{console: (message: string) => void}>} Array of file paths created
|
|
30
|
+
*/
|
|
31
|
+
async createSkills(skills, logger) {
|
|
32
|
+
const agentsDir = '.agents';
|
|
33
|
+
const skillsDir = path.join(agentsDir, 'skills');
|
|
34
|
+
|
|
35
|
+
this.#fs.ensureDir(agentsDir);
|
|
36
|
+
this.#fs.ensureDir(skillsDir);
|
|
37
|
+
|
|
38
|
+
const created = [];
|
|
39
|
+
|
|
40
|
+
for (const [skillName, content] of Object.entries(skills)) {
|
|
41
|
+
const skillFolder = path.join(skillsDir, skillName);
|
|
42
|
+
this.#fs.ensureDir(skillFolder);
|
|
43
|
+
|
|
44
|
+
const skillFile = path.join(skillFolder, 'SKILL.md');
|
|
45
|
+
await this.#fs.writeFile(skillFile, content);
|
|
46
|
+
created.push(skillFile);
|
|
47
|
+
logger(`✅ Skill successfully encapsulated: ${skillFile}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return created;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Crawls directories upward to find the nearest parent macro .spec.md file.
|
|
5
|
+
*/
|
|
6
|
+
export class ParentLinker {
|
|
7
|
+
/** @type {import('./FileSystemService.js').FileSystemService} */
|
|
8
|
+
#fs;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {import('./FileSystemService.js').FileSystemService} fsService
|
|
12
|
+
*/
|
|
13
|
+
constructor(fsService) {
|
|
14
|
+
this.#fs = fsService;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Searches for the closest macro (*.spec.md) by recursively traversing the directory tree upward.
|
|
19
|
+
* Skips the spec file that matches the current folder name to avoid self-linking.
|
|
20
|
+
* @param {string} currentDir - Absolute path of the feature directory
|
|
21
|
+
* @returns {string|null} Path to the parent .spec.md, or null if not found
|
|
22
|
+
*/
|
|
23
|
+
findClosestMacro(currentDir) {
|
|
24
|
+
let dir = path.resolve(currentDir);
|
|
25
|
+
const root = path.parse(dir).root;
|
|
26
|
+
|
|
27
|
+
while (dir !== root) {
|
|
28
|
+
try {
|
|
29
|
+
const files = this.#fs.readdirSync(dir);
|
|
30
|
+
const macroFile = files.find(
|
|
31
|
+
(f) => f.endsWith('.spec.md') && f !== `${path.basename(currentDir)}.spec.md`
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (macroFile) {
|
|
35
|
+
return path.join(dir, macroFile);
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {
|
|
38
|
+
// Permission errors: stop climbing and return null
|
|
39
|
+
if (e.code === 'EACCES' || e.code === 'EPERM') {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const parent = path.dirname(dir);
|
|
46
|
+
if (parent === dir) break;
|
|
47
|
+
dir = parent;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Appends a markdown link to the child spec into the parent spec file.
|
|
54
|
+
* @param {string} parentSpecPath - Path to the parent .spec.md
|
|
55
|
+
* @param {string} childSpecPath - Path to the child .spec.md
|
|
56
|
+
* @param {string} folderName - Name of the child feature folder
|
|
57
|
+
* @returns {Promise<void>}\n */
|
|
58
|
+
async linkToParent(parentSpecPath, childSpecPath, folderName) {
|
|
59
|
+
const relativePath = path
|
|
60
|
+
.relative(path.dirname(parentSpecPath), childSpecPath)
|
|
61
|
+
.replace(/\\/g, '/'); // Garante compatibilidade de paths no estilo POSIX para o Markdown
|
|
62
|
+
|
|
63
|
+
const parentContent = await this.#fs.readFile(parentSpecPath);
|
|
64
|
+
|
|
65
|
+
// Injeta o link logo após o fim do bloco do Mermaid ou no topo do arquivo estruturado
|
|
66
|
+
const linkAddition = `\n\n- [Micro Feature: ${folderName}](${relativePath})`;
|
|
67
|
+
|
|
68
|
+
await this.#fs.writeFile(parentSpecPath, parentContent + linkAddition);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles the `md edit` command business logic.
|
|
3
|
+
*/
|
|
4
|
+
export class SpecEditor {
|
|
5
|
+
/** @type {import('./FileSystemService.js').FileSystemService} */
|
|
6
|
+
#fs;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('./FileSystemService.js').FileSystemService} fsService
|
|
10
|
+
*/
|
|
11
|
+
constructor(fsService) {
|
|
12
|
+
this.#fs = fsService;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates that a spec file exists.
|
|
17
|
+
* @param {string} specFilePath
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
* @throws {Error} if not found
|
|
20
|
+
*/
|
|
21
|
+
validateSpec(specFilePath) {
|
|
22
|
+
if (!this.#fs.existsSync(specFilePath)) {
|
|
23
|
+
throw new Error(`Specification file not found: ${specFilePath}`);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Prepares the edit instruction message (placeholder — actual logic applied by AI agent).
|
|
30
|
+
* @param {string} specFilePath
|
|
31
|
+
* @param {string} instruction
|
|
32
|
+
* @returns {{ specFilePath: string, instruction: string }}
|
|
33
|
+
*/
|
|
34
|
+
prepareInstruction(specFilePath, instruction) {
|
|
35
|
+
return { specFilePath, instruction };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { TemplateFactory } from './TemplateFactory.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handles .spec.md file generation for `md new` and `md audit` commands.
|
|
6
|
+
*/
|
|
7
|
+
export class SpecGenerator {
|
|
8
|
+
/** @type {import('./FileSystemService.js').FileSystemService} */
|
|
9
|
+
#fs;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {import('./FileSystemService.js').FileSystemService} fsService
|
|
13
|
+
*/
|
|
14
|
+
constructor(fsService) {
|
|
15
|
+
this.#fs = fsService;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a new .spec.md file for a feature (macro or micro).
|
|
20
|
+
* @param {string} targetPath - Normalized target directory path
|
|
21
|
+
* @param {boolean} isMacro - Whether to generate a macro template
|
|
22
|
+
* @param {string} version - Semantic version string (e.g. 'v1.0.0')
|
|
23
|
+
* @returns {Promise<{filePath: string, folderName: string}>}
|
|
24
|
+
*/
|
|
25
|
+
async create(targetPath, isMacro, version) {
|
|
26
|
+
const folderName = path.basename(targetPath);
|
|
27
|
+
const finalFile = path.join(targetPath, `${folderName}.spec.md`);
|
|
28
|
+
|
|
29
|
+
const template = isMacro
|
|
30
|
+
? TemplateFactory.macroTemplate(folderName, version)
|
|
31
|
+
: TemplateFactory.microTemplate(folderName, version);
|
|
32
|
+
|
|
33
|
+
await this.#fs.writeFile(finalFile, template);
|
|
34
|
+
|
|
35
|
+
return { filePath: finalFile, folderName };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a missing .spec.md file for audit purposes.
|
|
40
|
+
* @param {string} codeFilePath - Path to the code file being audited
|
|
41
|
+
* @returns {Promise<{specFilePath: string, codeBaseName: string}>}
|
|
42
|
+
*/
|
|
43
|
+
async createIfMissing(codeFilePath) {
|
|
44
|
+
const targetDir = path.dirname(codeFilePath);
|
|
45
|
+
const ext = path.extname(codeFilePath);
|
|
46
|
+
const codeBaseName = path.basename(codeFilePath, ext);
|
|
47
|
+
const specFileName = `${codeBaseName}.spec.md`;
|
|
48
|
+
const specFilePath = path.join(targetDir, specFileName);
|
|
49
|
+
|
|
50
|
+
if (!this.#fs.existsSync(specFilePath)) {
|
|
51
|
+
const version = 'v1.0.0';
|
|
52
|
+
const template = TemplateFactory.auditTemplate(codeBaseName, version);
|
|
53
|
+
await this.#fs.writeFile(specFilePath, template);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { specFilePath, codeBaseName };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates that a .spec.md file exists before processing.
|
|
3
|
+
*/
|
|
4
|
+
export class SpecValidator {
|
|
5
|
+
/** @type {import('./FileSystemService.js').FileSystemService} */
|
|
6
|
+
#fs;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('./FileSystemService.js').FileSystemService} fsService
|
|
10
|
+
*/
|
|
11
|
+
constructor(fsService) {
|
|
12
|
+
this.#fs = fsService;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates that a spec file path exists.
|
|
17
|
+
* @param {string} specFilePath
|
|
18
|
+
* @returns {boolean} true if valid
|
|
19
|
+
* @throws {Error} if file does not exist
|
|
20
|
+
*/
|
|
21
|
+
validate(specFilePath) {
|
|
22
|
+
if (!this.#fs.existsSync(specFilePath)) {
|
|
23
|
+
throw new Error(`Specification file not found: ${specFilePath}`);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template engine for generating .spec.md file blueprints.
|
|
3
|
+
*/
|
|
4
|
+
export class TemplateFactory {
|
|
5
|
+
/**
|
|
6
|
+
* Generates a macro module template (stateDiagram-v2).
|
|
7
|
+
* @param {string} folderName
|
|
8
|
+
* @param {string} version
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
static macroTemplate(folderName, version) {
|
|
12
|
+
return (
|
|
13
|
+
`\n# Macro Module: ${folderName} | ${version}\n\n` +
|
|
14
|
+
'```mermaid\n' +
|
|
15
|
+
`%% @spec-version ${version}\n` +
|
|
16
|
+
'stateDiagram-v2\n' +
|
|
17
|
+
` [*] --> Initial_${folderName}\n` +
|
|
18
|
+
'```\n\n' +
|
|
19
|
+
'## 3. Audit History\n' +
|
|
20
|
+
'<details>\n' +
|
|
21
|
+
'<summary>Click to expand</summary>\n' +
|
|
22
|
+
'\n\n\n' +
|
|
23
|
+
'</details>\n'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates a micro feature template (graph LR + Decision Matrix).
|
|
29
|
+
* @param {string} folderName
|
|
30
|
+
* @param {string} version
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
static microTemplate(folderName, version) {
|
|
34
|
+
return (
|
|
35
|
+
`\n# Specification: ${folderName} | ${version}\n\n` +
|
|
36
|
+
'## 1. Flow Contract (Mermaid)\n' +
|
|
37
|
+
'```mermaid\n' +
|
|
38
|
+
`%% @spec-version ${version}\n` +
|
|
39
|
+
'graph LR\n' +
|
|
40
|
+
' A([Start]) --> B[Process]\n' +
|
|
41
|
+
'```\n\n' +
|
|
42
|
+
'## 2. Decision Matrix\n' +
|
|
43
|
+
'| Factor A? | Factor B? | Proposed Action | Decision (Outcome) | Transition State (New Status) |\n' +
|
|
44
|
+
'| :---: | :---: | :--- | :---: | :---: |\n' +
|
|
45
|
+
'| | | | | |\n\n' +
|
|
46
|
+
'## 3. Audit History\n' +
|
|
47
|
+
'<details>\n' +
|
|
48
|
+
'<summary>Click to expand</summary>\n' +
|
|
49
|
+
'\n\n\n' +
|
|
50
|
+
'</details>\n'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generates an audit template (graph LR + AuditHistory).
|
|
56
|
+
* @param {string} codeBaseName
|
|
57
|
+
* @param {string} version
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
static auditTemplate(codeBaseName, version) {
|
|
61
|
+
return (
|
|
62
|
+
`# Audit: ${codeBaseName} | ${version}\n\n` +
|
|
63
|
+
'## 1. Flow Contract (Mermaid)\n' +
|
|
64
|
+
'```mermaid\n' +
|
|
65
|
+
`%% @spec-version ${version}\n` +
|
|
66
|
+
'graph LR\n' +
|
|
67
|
+
' A([Start]) --> B[Process]\n' +
|
|
68
|
+
'```\n\n' +
|
|
69
|
+
'## 2. Decision Matrix\n' +
|
|
70
|
+
'| Condition | Action | Next State |\n' +
|
|
71
|
+
'| :---: | :--- | :---: |\n' +
|
|
72
|
+
'| | | |\n\n' +
|
|
73
|
+
'## 3. Audit History\n' +
|
|
74
|
+
'<details>\n' +
|
|
75
|
+
'<summary>Click to expand</summary>\n' +
|
|
76
|
+
'\n\n\n' +
|
|
77
|
+
'</details>\n'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|