mddd-cli 6.2.2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +15 -1
- package/package.json +2 -2
- package/readme.md +4 -4
- package/src/commands/init.js +3 -3
- package/src/commands/status.js +534 -0
- package/src/commands/status.spec.md +131 -0
- package/src/services/FileSystemService.spec.md +3 -1
- package/src/services/InitService.js +1 -1
- package/src/services/InitService.spec.md +7 -5
- /package/{system_prompt.md → AGENTS.md} +0 -0
package/bin/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { SpecFinderService } from '../src/services/SpecFinderService.js';
|
|
|
8
8
|
import { validateMermaidSyntax } from '../src/commands/validator.js';
|
|
9
9
|
import * as initCmd from '../src/commands/init.js';
|
|
10
10
|
import * as listSpecsCmd from '../src/commands/listSpecs.js';
|
|
11
|
+
import * as statusCmd from '../src/commands/status.js';
|
|
11
12
|
|
|
12
13
|
// ─── Services ───────────────────────────────────────────────────────────────
|
|
13
14
|
const fs = new FileSystemService();
|
|
@@ -20,7 +21,7 @@ const program = new Command();
|
|
|
20
21
|
program
|
|
21
22
|
.name('md')
|
|
22
23
|
.description('Manager for co-located specifications for Mermaid Diagram Driven Development (MDDD)')
|
|
23
|
-
.version('
|
|
24
|
+
.version('7.0.0');
|
|
24
25
|
|
|
25
26
|
// ==========================================
|
|
26
27
|
// COMMAND: md init
|
|
@@ -54,6 +55,19 @@ program
|
|
|
54
55
|
process.exit(0);
|
|
55
56
|
});
|
|
56
57
|
|
|
58
|
+
program
|
|
59
|
+
.command('status')
|
|
60
|
+
.description('Generate a beautiful MDDD coverage report with metrics from all .spec.md files')
|
|
61
|
+
.action(async () => {
|
|
62
|
+
try {
|
|
63
|
+
await statusCmd.execute(specFinderService);
|
|
64
|
+
process.exit(0);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error(pc.red(`❌ ${err.message}`));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
57
71
|
program
|
|
58
72
|
.command('list-specs')
|
|
59
73
|
.description('List all .spec.md files in the project tree (returns JSON)')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mddd-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Official CLI for modular, co-located, and versioned Mermaid Diagram Driven Development (MDDD).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./bin/cli.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"src",
|
|
12
|
-
"
|
|
12
|
+
"AGENTS.md",
|
|
13
13
|
".agents/skills",
|
|
14
14
|
".agents/templates"
|
|
15
15
|
],
|
package/readme.md
CHANGED
|
@@ -155,7 +155,7 @@ md init
|
|
|
155
155
|
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
This will create the `
|
|
158
|
+
This will create the `AGENTS.md` and `SKILL.md` files in the root directory, containing the global instructions that will guide the AI in understanding the MDDD methodology and interacting with Git logs. You can rename `AGENTS.md` to any .rules file you need (.cursorrules, .clinerules, etc.).
|
|
159
159
|
|
|
160
160
|
### 2. Audit legacy files or make new ones.
|
|
161
161
|
|
|
@@ -209,7 +209,7 @@ src/
|
|
|
209
209
|
|
|
210
210
|
| Command | Description |
|
|
211
211
|
| --- | --- |
|
|
212
|
-
| `md init` | Configures the `
|
|
212
|
+
| `md init` | Configures the `AGENTS.md` file and the SKILL.md files which instructs the AI how to behave. Run this everytime you update MDDD-CLI NPM Package. |
|
|
213
213
|
|
|
214
214
|
### Project Architecture
|
|
215
215
|
|
|
@@ -396,7 +396,7 @@ md init
|
|
|
396
396
|
|
|
397
397
|
```
|
|
398
398
|
|
|
399
|
-
Isso criará os arquivos `
|
|
399
|
+
Isso criará os arquivos `AGENTS.md` e `SKILL.md` no diretório raiz, contendo as instruções globais que guiarão a IA na compreensão da metodologia MDDD e na interação com os logs do Git. Você pode renomear `AGENTS.md` para qualquer arquivo .rules que precisar (.cursorrules, .clinerules, etc.).
|
|
400
400
|
|
|
401
401
|
### 2. Auditar arquivos legados ou criar novos.
|
|
402
402
|
|
|
@@ -449,7 +449,7 @@ src/
|
|
|
449
449
|
|
|
450
450
|
| Comando | Descrição |
|
|
451
451
|
| --- | --- |
|
|
452
|
-
| `md init` | Configura os arquivos `
|
|
452
|
+
| `md init` | Configura os arquivos `AGENTS.md` e `SKILL.md` que instruem a IA sobre como se comportar. Execute isto sempre que atualizar o pacote NPM do MDDD-CLI. |
|
|
453
453
|
|
|
454
454
|
### Arquitetura do Projeto
|
|
455
455
|
|
package/src/commands/init.js
CHANGED
|
@@ -8,13 +8,13 @@ import pc from 'picocolors';
|
|
|
8
8
|
import { GITHUB_WORKFLOW_CONTENT } from '../workflows/mddd-preview.yml.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Resolve and read
|
|
11
|
+
* Resolve and read AGENTS.md from the project root.
|
|
12
12
|
* @returns {string}
|
|
13
13
|
*/
|
|
14
14
|
function readSystemPrompt() {
|
|
15
15
|
const currentFile = fileURLToPath(import.meta.url);
|
|
16
16
|
const rootDir = path.resolve(path.dirname(currentFile), '..', '..');
|
|
17
|
-
const promptPath = path.join(rootDir, '
|
|
17
|
+
const promptPath = path.join(rootDir, 'AGENTS.md');
|
|
18
18
|
return readFileSync(promptPath, 'utf-8');
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -49,6 +49,6 @@ export async function execute(initService) {
|
|
|
49
49
|
// 6. Copia o ARCHITECTURE template (usado pela skill mddd-context-map)
|
|
50
50
|
await initService.createArchitectureTemplate(cliArchitectureTemplatePath, (msg) => console.log(msg));
|
|
51
51
|
|
|
52
|
-
console.log(pc.green('\n🚀 Universal [
|
|
52
|
+
console.log(pc.green('\n🚀 Universal [AGENTS.md], SKILLS, spec template, and architecture template generated successfully in the project root!'));
|
|
53
53
|
console.log(pc.green('Run the "md init" command whenever you update the MDDD-CLI NPM package.'));
|
|
54
54
|
}
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executes the `md status` command.
|
|
3
|
+
* Generates a beautiful MDDD coverage report with metrics from all .spec.md files.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} SpecMetrics
|
|
12
|
+
* @property {string} relativePath
|
|
13
|
+
* @property {string} version
|
|
14
|
+
* @property {'draft' | 'stable'} status
|
|
15
|
+
* @property {'Coeso' | 'Caótico' | undefined} classification
|
|
16
|
+
* @property {number} tasksTotal
|
|
17
|
+
* @property {number} tasksCompleted
|
|
18
|
+
* @property {number} tasksPending
|
|
19
|
+
* @property {number} totalChanges
|
|
20
|
+
* @property {{ major: number, minor: number, patch: number }} changeBreakdown
|
|
21
|
+
* @property {{ discovery: number, fix: number, improvement: number, documentation: number, refactor: number, other: number }} changeTypes
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} DashboardSummary
|
|
26
|
+
* @property {number} totalSpecs
|
|
27
|
+
* @property {number} coesoCount
|
|
28
|
+
* @property {number} caoticoCount
|
|
29
|
+
* @property {number} unclassifiedCount
|
|
30
|
+
* @property {number} totalTasks
|
|
31
|
+
* @property {number} completedTasks
|
|
32
|
+
* @property {number} pendingTasks
|
|
33
|
+
* @property {number} totalChanges
|
|
34
|
+
* @property {number} totalDiscoveries
|
|
35
|
+
* @property {number} totalFixes
|
|
36
|
+
* @property {number} totalImprovements
|
|
37
|
+
* @property {number} totalDocumentation
|
|
38
|
+
* @property {number} totalRefactors
|
|
39
|
+
* @property {number} totalMajors
|
|
40
|
+
* @property {number} totalMinors
|
|
41
|
+
* @property {number} totalPatches
|
|
42
|
+
* @property {string[]} criticalPoints
|
|
43
|
+
* @property {SpecMetrics[]} specs // detailed per-spec data for testing
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Classifies a change summary entry into a type category.
|
|
48
|
+
* @param {string} summary
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
function classifyChangeType(summary) {
|
|
52
|
+
const s = summary.toLowerCase();
|
|
53
|
+
|
|
54
|
+
if (/\b(discovery|descoberta|found|encontrado)\b/.test(s)) return 'discovery';
|
|
55
|
+
if (/\b(fix|bug|correç[ãa]o|corrigido|defeito)\b/.test(s)) return 'fix';
|
|
56
|
+
if (/\b(improvement|melhoria|enhance|refinamento|aprimoramento)\b/.test(s)) return 'improvement';
|
|
57
|
+
if (/\b(documentation|doc|docs|diagram|documentaç[ãa]o|readme)\b/.test(s)) return 'documentation';
|
|
58
|
+
if (/\b(refactor|refatoraç[ãa]o|reestrutura|simplifica|reescrita)\b/.test(s)) return 'refactor';
|
|
59
|
+
|
|
60
|
+
return 'other';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Analyzes a single .spec.md file and extracts metrics.
|
|
65
|
+
* @param {string} relativePath
|
|
66
|
+
* @returns {SpecMetrics}
|
|
67
|
+
*/
|
|
68
|
+
function analyzeSpec(relativePath) {
|
|
69
|
+
const absolutePath = path.resolve(process.cwd(), relativePath);
|
|
70
|
+
let content;
|
|
71
|
+
try {
|
|
72
|
+
content = fs.readFileSync(absolutePath, 'utf-8');
|
|
73
|
+
} catch {
|
|
74
|
+
return {
|
|
75
|
+
relativePath,
|
|
76
|
+
version: 'unknown',
|
|
77
|
+
status: 'draft',
|
|
78
|
+
classification: undefined,
|
|
79
|
+
tasksTotal: 0,
|
|
80
|
+
tasksCompleted: 0,
|
|
81
|
+
tasksPending: 0,
|
|
82
|
+
totalChanges: 0,
|
|
83
|
+
changeBreakdown: { major: 0, minor: 0, patch: 0 },
|
|
84
|
+
changeTypes: { discovery: 0, fix: 0, improvement: 0, documentation: 0, refactor: 0, other: 0 }
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Extract version — try multiple patterns
|
|
89
|
+
let version = 'unknown';
|
|
90
|
+
const versionMatch = content.match(/@spec-version\s+v?(\d+\.\d+\.\d+)/);
|
|
91
|
+
if (versionMatch) {
|
|
92
|
+
version = versionMatch[1];
|
|
93
|
+
} else {
|
|
94
|
+
const specVersionMatch = content.match(/SPEC_VERSION:\s*v?(\d+\.\d+\.\d+)/i);
|
|
95
|
+
if (specVersionMatch) version = specVersionMatch[1];
|
|
96
|
+
}
|
|
97
|
+
if (version === 'unknown') {
|
|
98
|
+
// Fallback: match any standalone vX.Y.Z at the start of a line
|
|
99
|
+
const fallbackMatch = content.match(/^#+?\s+v?(\d+\.\d+\.\d+)/m);
|
|
100
|
+
if (fallbackMatch) version = fallbackMatch[1];
|
|
101
|
+
}
|
|
102
|
+
if (version === 'unknown') {
|
|
103
|
+
// Fallback: match vX.Y.Z near the top of the file (first 3 lines)
|
|
104
|
+
const topLines = content.split('\n').slice(0, 3).join('\n');
|
|
105
|
+
const topMatch = topLines.match(/v?(\d+\.\d+\.\d+)/);
|
|
106
|
+
if (topMatch) version = topMatch[1];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Extract status — multiple formats:
|
|
110
|
+
// "SPEC_VERSION: v1.0.0 — stable" (MDDD-CLI padrão)
|
|
111
|
+
// "*SPEC_VERSION:** v1.0.0 stable" (Appfy/legacy)
|
|
112
|
+
// "SPEC_VERSION: v1.0.0 — draft"
|
|
113
|
+
// "**SPEC_VERSION: v1.0.0** (draft|stable)"
|
|
114
|
+
let status = 'draft';
|
|
115
|
+
const statusPatterns = [
|
|
116
|
+
// Pattern 1: **SPEC_VERSION:** vX.Y.Z — stable|draft (bold label format)
|
|
117
|
+
/\*{0,2}SPEC_VERSION\*{0,2}:?\*{0,2}\s+v?\d+\.\d+\.\d+\s*[—–-]?\s*(draft|stable)/i,
|
|
118
|
+
// Pattern 2: *SPEC_VERSION:** vX.Y.Z stable (Appfy/legacy — bold before and after)
|
|
119
|
+
/\*{0,2}SPEC_VERSION\*{0,2}:?\*{1,2}\s+v?\d+\.\d+\.\d+\s+(draft|stable)/i,
|
|
120
|
+
// Pattern 3: Title line: "# CLI Module | v6.3.0 (Stable)" or "(Draft)"
|
|
121
|
+
/^#\s+.*?\(?(Stable|Draft)\)?/im,
|
|
122
|
+
// Pattern 4: SPEC_VERSION: vX.Y.Z — stable|draft (plain label, no bold)
|
|
123
|
+
/SPEC_VERSION:\s*v?\d+\.\d+\.\d+\s*[—–-]\s*(draft|stable)/i,
|
|
124
|
+
];
|
|
125
|
+
for (const pattern of statusPatterns) {
|
|
126
|
+
const match = content.match(pattern);
|
|
127
|
+
if (match) {
|
|
128
|
+
status = /** @type {'draft' | 'stable'} */ (match[1].toLowerCase());
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Extract classification (Coeso / Caótico)
|
|
134
|
+
// Common patterns in specs:
|
|
135
|
+
// "**Classification:** Coeso" (English, bold label)
|
|
136
|
+
// "Classificação: **Coeso**" (Portuguese, bold value)
|
|
137
|
+
// "classificado como **Caótico/Acoplado**" (Portuguese, full sentence)
|
|
138
|
+
// "Código classificado como **Caótico/Acoplado**" (Portuguese variant)
|
|
139
|
+
let classification;
|
|
140
|
+
const classificationPatterns = [
|
|
141
|
+
// Pattern 1: "**Classification:** Coeso" or "**Classification:** Caótico" (English)
|
|
142
|
+
/Classification:\s*\*{0,2}\s*(Coeso|Caótico)\s*\*{0,2}/i,
|
|
143
|
+
// Pattern 2: "Classificação: Coeso" or "Classificação: **Coeso**" or "Classificação: **Caótico**"
|
|
144
|
+
// (title line or body, with or without bold markers around the value)
|
|
145
|
+
/classificaç[ãa]o:\s*\*{0,2}\s*(Coeso|Caótico)\s*\*{0,2}/i,
|
|
146
|
+
// Pattern 3: "classificado como **Caótico/Acoplado**" or "Código classificado como **Coeso**"
|
|
147
|
+
/classificado\s+como\s*\*\*(Caótico|Coeso)/i,
|
|
148
|
+
// Pattern 4: "classificado com 'Caótico'" or "classificado com "Caótico""
|
|
149
|
+
/classificado\s+com\s*['"](Caótico|Coeso)['"]/i,
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
for (const pattern of classificationPatterns) {
|
|
153
|
+
const match = content.match(pattern);
|
|
154
|
+
if (match) {
|
|
155
|
+
classification = match[1];
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Heuristic fallback if no explicit classification found
|
|
161
|
+
if (!classification) {
|
|
162
|
+
if (/\bcaótico\b/i.test(content) && /\baco(lamento|plado)\b/i.test(content)) {
|
|
163
|
+
classification = 'Caótico';
|
|
164
|
+
} else if (/\bcoeso\b/i.test(content) && /\b(baixo|clean|modular|bau)\b/i.test(content)) {
|
|
165
|
+
classification = 'Coeso';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Extract tasks from the Tasks section
|
|
170
|
+
// Match sections like "## 4. Tasks" or "## Tasks" with checklist items
|
|
171
|
+
const tasksSection = content.match(/##\s+\d*\.?\s*Tasks?\s*\n([\s\S]*?)(?=\n##\s|$)/i);
|
|
172
|
+
const tasksText = tasksSection ? tasksSection[1] : '';
|
|
173
|
+
|
|
174
|
+
const pendingMatches = tasksText.match(/- \[ \]/g);
|
|
175
|
+
const completedMatches = tasksText.match(/- \[x\]/gi);
|
|
176
|
+
const tasksPending = pendingMatches ? pendingMatches.length : 0;
|
|
177
|
+
const tasksCompleted = completedMatches ? completedMatches.length : 0;
|
|
178
|
+
const tasksTotal = tasksPending + tasksCompleted;
|
|
179
|
+
|
|
180
|
+
// Extract Audit History entries
|
|
181
|
+
const auditSection = content.match(/(?:##\s+\d*\.?\s*(?:Audit History|Change History)\s*\n[\s\S]*?)(?=\n##\s|$)/i);
|
|
182
|
+
const auditText = auditSection ? auditSection[0] : '';
|
|
183
|
+
|
|
184
|
+
// Count data rows in the audit table (lines that start with `|` and contain a date pattern)
|
|
185
|
+
const auditLines = auditText.split('\n').filter(line => {
|
|
186
|
+
const trimmed = line.trim();
|
|
187
|
+
// Match rows that start with `|` and contain a date like YYYY-MM-DD or a version like v1.2.3
|
|
188
|
+
return /^\s*\|.*\d{4}-\d{2}-\d{2}.*\|/.test(trimmed) || /^\s*\|.*v\d+\.\d+\.\d+.*\|/.test(trimmed);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const totalChanges = auditLines.length;
|
|
192
|
+
|
|
193
|
+
// Breakdown by MAJOR/MINOR/PATCH
|
|
194
|
+
let major = 0, minor = 0, patch = 0;
|
|
195
|
+
for (const line of auditLines) {
|
|
196
|
+
if (/\bMAJOR\b/i.test(line) || /\bMajor\b/.test(line)) major++;
|
|
197
|
+
else if (/\bMINOR\b/i.test(line) || /\bMinor\b/.test(line)) minor++;
|
|
198
|
+
else if (/\bPATCH\b/i.test(line) || /\bPatch\b/.test(line)) patch++;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Classify change types from the "Change Summary" column
|
|
202
|
+
// Two common table formats:
|
|
203
|
+
// (a) Date | Agent | Version | Change Summary | Change Type | (5 cols, summary = 2nd-to-last)
|
|
204
|
+
// (b) Data | Auditor | Versão | Resumo das Mudanças | (4 cols, summary = last)
|
|
205
|
+
// Heuristic: if last column contains MAJOR/MINOR/PATCH, it's a Change Type column → use 2nd-to-last as summary
|
|
206
|
+
const changeTypes = { discovery: 0, fix: 0, improvement: 0, documentation: 0, refactor: 0, other: 0 };
|
|
207
|
+
for (const line of auditLines) {
|
|
208
|
+
const columns = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
209
|
+
if (columns.length < 2) continue;
|
|
210
|
+
|
|
211
|
+
const lastCol = columns[columns.length - 1];
|
|
212
|
+
// Detect if last column is a Change Type column (short, just MAJOR/MINOR/PATCH)
|
|
213
|
+
// vs a full sentence summary that happens to mention those words
|
|
214
|
+
const isChangeTypeColumn = columns.length >= 4
|
|
215
|
+
&& lastCol.length <= 10
|
|
216
|
+
&& /^(MAJOR|MINOR|PATCH)$/i.test(lastCol.trim());
|
|
217
|
+
|
|
218
|
+
let summary;
|
|
219
|
+
if (isChangeTypeColumn) {
|
|
220
|
+
// Format (a): last col is Change Type (e.g. "MAJOR"), summary is 2nd-to-last
|
|
221
|
+
summary = columns[columns.length - 2];
|
|
222
|
+
} else {
|
|
223
|
+
// Format (b) or fallback: last col is the summary itself
|
|
224
|
+
summary = lastCol;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const type = classifyChangeType(summary);
|
|
228
|
+
changeTypes[type]++;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
relativePath,
|
|
233
|
+
version,
|
|
234
|
+
status,
|
|
235
|
+
classification,
|
|
236
|
+
tasksTotal,
|
|
237
|
+
tasksCompleted,
|
|
238
|
+
tasksPending,
|
|
239
|
+
totalChanges,
|
|
240
|
+
changeBreakdown: { major, minor, patch },
|
|
241
|
+
changeTypes
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Creates a simple text-based progress bar.
|
|
247
|
+
* @param {number} completed
|
|
248
|
+
* @param {number} total
|
|
249
|
+
* @param {number} width
|
|
250
|
+
* @returns {string}
|
|
251
|
+
*/
|
|
252
|
+
function progressBar(completed, total, width = 10) {
|
|
253
|
+
if (total === 0) return '░'.repeat(width);
|
|
254
|
+
const filled = Math.round((completed / total) * width);
|
|
255
|
+
const empty = width - filled;
|
|
256
|
+
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
257
|
+
const pct = total === 0 ? 0 : Math.round((completed / total) * 100);
|
|
258
|
+
return `${bar} ${pct}%`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Prints the dashboard to the console.
|
|
263
|
+
* @param {DashboardSummary} summary
|
|
264
|
+
*/
|
|
265
|
+
function printDashboard(summary) {
|
|
266
|
+
if (summary.totalSpecs === 0) {
|
|
267
|
+
console.log(pc.yellow(pc.bold('\n ⚠️ No .spec.md files found in this project.')));
|
|
268
|
+
console.log(pc.gray(' Run "md init" to create the initial specification.'));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
var W = 58;
|
|
273
|
+
|
|
274
|
+
function boxTop() { return pc.cyan(pc.bold(' +' + '='.repeat(W) + '+')); }
|
|
275
|
+
function boxBot() { return pc.cyan(pc.bold(' +' + '='.repeat(W) + '+')); }
|
|
276
|
+
function boxMid() { return pc.cyan(pc.bold(' +' + '-'.repeat(W) + '+')); }
|
|
277
|
+
function boxLine(content) {
|
|
278
|
+
var visible = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
279
|
+
var pad = Math.max(0, W - visible.length);
|
|
280
|
+
return pc.cyan(pc.bold(' |')) + content + ' '.repeat(pad) + pc.cyan(pc.bold('|'));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function section(title) {
|
|
284
|
+
console.log(boxTop());
|
|
285
|
+
console.log(boxLine(pc.bold(pc.white(' ' + title))));
|
|
286
|
+
console.log(boxMid());
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Header
|
|
290
|
+
console.log();
|
|
291
|
+
console.log(boxTop());
|
|
292
|
+
console.log(boxLine(pc.bold(pc.white(' ** MDDD COVERAGE REPORT **'))));
|
|
293
|
+
console.log(boxBot());
|
|
294
|
+
console.log();
|
|
295
|
+
|
|
296
|
+
// ── SPECS ──
|
|
297
|
+
section('SPECS');
|
|
298
|
+
|
|
299
|
+
var coesoC = summary.coesoCount > 0 ? pc.green : pc.gray;
|
|
300
|
+
var caoticoC = summary.caoticoCount > 0 ? pc.red : pc.gray;
|
|
301
|
+
var unaudC = summary.unclassifiedCount > 0 ? pc.yellow : pc.gray;
|
|
302
|
+
|
|
303
|
+
console.log(boxLine(' ' + pc.bold(coesoC('COHESIVE')) + ' ' + pc.bold(coesoC(String(summary.coesoCount).padStart(4)))));
|
|
304
|
+
console.log(boxLine(' ' + pc.bold(caoticoC('CHAOTIC')) + ' ' + pc.bold(caoticoC(String(summary.caoticoCount).padStart(4)))));
|
|
305
|
+
console.log(boxLine(' ' + pc.bold(unaudC('UNAUDITED')) + ' ' + pc.bold(unaudC(String(summary.unclassifiedCount).padStart(4)))));
|
|
306
|
+
console.log(boxMid());
|
|
307
|
+
console.log(boxLine(pc.bold(' TOTAL: ' + String(summary.totalSpecs) + ' specs')));
|
|
308
|
+
|
|
309
|
+
console.log(boxBot());
|
|
310
|
+
console.log();
|
|
311
|
+
|
|
312
|
+
// ── TASKS ──
|
|
313
|
+
section('TASKS');
|
|
314
|
+
|
|
315
|
+
if (summary.totalTasks > 0) {
|
|
316
|
+
var pct = Math.round((summary.completedTasks / summary.totalTasks) * 100);
|
|
317
|
+
var barW = 22;
|
|
318
|
+
var filled = Math.round((summary.completedTasks / summary.totalTasks) * barW);
|
|
319
|
+
var bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barW - filled);
|
|
320
|
+
|
|
321
|
+
console.log(boxLine(' ' + pc.bold(String(summary.totalTasks).padStart(4)) + ' total ' + pc.green('done') + ' ' + pc.bold(pc.green(String(summary.completedTasks).padStart(4))) + ' ' + pc.yellow('pend') + ' ' + pc.bold(pc.yellow(String(summary.pendingTasks).padStart(4)))));
|
|
322
|
+
console.log(boxLine(' ' + bar + ' ' + pc.bold(String(pct) + '%')));
|
|
323
|
+
} else {
|
|
324
|
+
console.log(boxLine(pc.gray(' (no tasks defined)')));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log(boxBot());
|
|
328
|
+
console.log();
|
|
329
|
+
|
|
330
|
+
// ── CHANGES ──
|
|
331
|
+
section('VERSION CHANGES');
|
|
332
|
+
|
|
333
|
+
if (summary.totalChanges > 0) {
|
|
334
|
+
console.log(boxLine(pc.bold(' ' + String(summary.totalChanges) + ' total changes')));
|
|
335
|
+
|
|
336
|
+
var maj = summary.totalMajors > 0 ? pc.red(pc.bold('MAJOR ' + summary.totalMajors)) : pc.gray('MAJOR 0');
|
|
337
|
+
var min = summary.totalMinors > 0 ? pc.blue(pc.bold('MINOR ' + summary.totalMinors)) : pc.gray('MINOR 0');
|
|
338
|
+
var pat = summary.totalPatches > 0 ? pc.green(pc.bold('PATCH ' + summary.totalPatches)) : pc.gray('PATCH 0');
|
|
339
|
+
console.log(boxLine(' ' + maj + ' ' + pc.gray('|') + ' ' + min + ' ' + pc.gray('|') + ' ' + pat));
|
|
340
|
+
} else {
|
|
341
|
+
console.log(boxLine(pc.gray(' (no changes recorded)')));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
console.log(boxBot());
|
|
345
|
+
console.log();
|
|
346
|
+
|
|
347
|
+
// ── MDDD IMPACT ──
|
|
348
|
+
var impactItems = [];
|
|
349
|
+
if (summary.totalDiscoveries > 0) impactItems.push({icon: '>>>', label: 'DISCOVERIES', value: summary.totalDiscoveries, color: pc.green});
|
|
350
|
+
if (summary.totalFixes > 0) impactItems.push({icon: '>>>', label: 'FIXES', value: summary.totalFixes, color: pc.yellow});
|
|
351
|
+
if (summary.totalImprovements > 0) impactItems.push({icon: '>>>', label: 'IMPROVEMENTS', value: summary.totalImprovements, color: pc.blue});
|
|
352
|
+
if (summary.totalDocumentation > 0) impactItems.push({icon: '>>>', label: 'DOCUMENTATION', value: summary.totalDocumentation, color: pc.cyan});
|
|
353
|
+
if (summary.totalRefactors > 0) impactItems.push({icon: '>>>', label: 'REFACTORS', value: summary.totalRefactors, color: pc.magenta});
|
|
354
|
+
|
|
355
|
+
section('MDDD IMPACT');
|
|
356
|
+
|
|
357
|
+
if (impactItems.length > 0) {
|
|
358
|
+
for (var i = 0; i < impactItems.length; i++) {
|
|
359
|
+
var it = impactItems[i];
|
|
360
|
+
var label = pc.bold(it.color(it.label.padEnd(16)));
|
|
361
|
+
var val = pc.bold(it.color(String(it.value)));
|
|
362
|
+
console.log(boxLine(' ' + it.icon + ' ' + label + ' ' + val));
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
console.log(boxLine(pc.gray(' (no impact metrics yet)')));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
console.log(boxBot());
|
|
369
|
+
console.log();
|
|
370
|
+
|
|
371
|
+
// ── CRITICAL POINTS ──
|
|
372
|
+
if (summary.criticalPoints.length > 0) {
|
|
373
|
+
var draftByDomain = new Map();
|
|
374
|
+
var caoticoList = [];
|
|
375
|
+
var pendingList = [];
|
|
376
|
+
|
|
377
|
+
for (var j = 0; j < summary.criticalPoints.length; j++) {
|
|
378
|
+
var pt = summary.criticalPoints[j];
|
|
379
|
+
if (pt.indexOf('CAOTICO') !== -1 || pt.indexOf('CAÓTICO') !== -1) caoticoList.push(pt);
|
|
380
|
+
else if (pt.indexOf('pending tasks') !== -1) pendingList.push(pt);
|
|
381
|
+
else if (pt.indexOf('DRAFT') !== -1) {
|
|
382
|
+
var pts = pt.split(' — ');
|
|
383
|
+
var dom = pts[0].split('/')[0];
|
|
384
|
+
if (!draftByDomain.has(dom)) draftByDomain.set(dom, []);
|
|
385
|
+
draftByDomain.get(dom).push(pts[0]);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
section('CRITICAL POINTS');
|
|
390
|
+
|
|
391
|
+
for (var k = 0; k < caoticoList.length; k++) {
|
|
392
|
+
var cp = caoticoList[k].split(' — ');
|
|
393
|
+
console.log(boxLine(' ' + pc.red(pc.bold('!!! ' + (cp[0] || '')))));
|
|
394
|
+
if (cp[1]) console.log(boxLine(' ' + pc.red(cp[1])));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
var maxShow = 5;
|
|
398
|
+
var showItems = pendingList.slice(0, maxShow);
|
|
399
|
+
var moreCount = pendingList.length - maxShow;
|
|
400
|
+
for (var m = 0; m < showItems.length; m++) {
|
|
401
|
+
var pp = showItems[m].split(' — ');
|
|
402
|
+
var tm = pp[1] ? pp[1].match(/(\d+) pending/) : null;
|
|
403
|
+
var pendingNum = tm ? tm[1] : '0';
|
|
404
|
+
var filePath = pp[0] || '';
|
|
405
|
+
var maxPathLen = W - 22;
|
|
406
|
+
if (filePath.length > maxPathLen) filePath = '...' + filePath.slice(-(maxPathLen - 3));
|
|
407
|
+
console.log(boxLine(' ' + pc.yellow(pc.bold('PENDING ' + filePath)) + ' ' + pc.bold(pc.yellow(pendingNum))));
|
|
408
|
+
}
|
|
409
|
+
if (moreCount > 0) {
|
|
410
|
+
console.log(boxLine(pc.gray(' ... and ' + moreCount + ' more specs')));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
var draftCount = 0;
|
|
414
|
+
for (var d of draftByDomain.values()) draftCount += d.length;
|
|
415
|
+
if (draftCount > 0) {
|
|
416
|
+
var domainArr = [];
|
|
417
|
+
for (var entry of draftByDomain) {
|
|
418
|
+
domainArr.push({domain: entry[0], count: entry[1].length});
|
|
419
|
+
}
|
|
420
|
+
domainArr.sort(function(a, b) { return b.count - a.count; });
|
|
421
|
+
var domainStr = domainArr.map(function(e) {
|
|
422
|
+
return pc.bold(pc.cyan(e.domain)) + pc.gray('(' + e.count + ')');
|
|
423
|
+
}).join(' ');
|
|
424
|
+
console.log(boxLine(pc.bold(pc.cyan(' DRAFT: ' + String(draftCount) + ' specs'))));
|
|
425
|
+
// Truncate domain summary if too long
|
|
426
|
+
if (domainStr.length > W - 12) {
|
|
427
|
+
domainStr = domainStr.slice(0, W - 15) + '...';
|
|
428
|
+
}
|
|
429
|
+
console.log(boxLine(' ' + domainStr));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
console.log(boxBot());
|
|
433
|
+
console.log();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ── Footer ───────────────────────────────────────
|
|
437
|
+
var versions = summary.specs.map(function(s) { return s.version; }).filter(function(v) { return v !== 'unknown'; });
|
|
438
|
+
var versionRange;
|
|
439
|
+
if (versions.length > 0) {
|
|
440
|
+
var sorted = versions.sort(function(a, b) {
|
|
441
|
+
var ap = a.split('.').map(Number);
|
|
442
|
+
var bp = b.split('.').map(Number);
|
|
443
|
+
if (ap[0] !== bp[0]) return ap[0] - bp[0];
|
|
444
|
+
if (ap[1] !== bp[1]) return ap[1] - bp[1];
|
|
445
|
+
return ap[2] - bp[2];
|
|
446
|
+
});
|
|
447
|
+
versionRange = 'v' + sorted[0] + ' -> v' + sorted[sorted.length - 1];
|
|
448
|
+
} else {
|
|
449
|
+
versionRange = 'no version data';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
console.log(pc.gray(' ' + '-'.repeat(W)));
|
|
453
|
+
console.log(pc.gray(' MDDD Protocol · ' + summary.specs.length + ' specs · ' + versionRange));
|
|
454
|
+
console.log(pc.gray(' Generated: ' + new Date().toISOString().split('T')[0]));
|
|
455
|
+
console.log();
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Executes the `md status` command.
|
|
459
|
+
* @param {{ findSpecs: (rootDir: string) => string[] }} specFinder
|
|
460
|
+
* @returns {Promise<DashboardSummary>}
|
|
461
|
+
*/
|
|
462
|
+
export async function execute(specFinder) {
|
|
463
|
+
const specs = specFinder.findSpecs(process.cwd());
|
|
464
|
+
|
|
465
|
+
if (specs.length === 0) {
|
|
466
|
+
const emptySummary = {
|
|
467
|
+
totalSpecs: 0,
|
|
468
|
+
coesoCount: 0,
|
|
469
|
+
caoticoCount: 0,
|
|
470
|
+
unclassifiedCount: 0,
|
|
471
|
+
totalTasks: 0,
|
|
472
|
+
completedTasks: 0,
|
|
473
|
+
pendingTasks: 0,
|
|
474
|
+
totalChanges: 0,
|
|
475
|
+
totalDiscoveries: 0,
|
|
476
|
+
totalFixes: 0,
|
|
477
|
+
totalImprovements: 0,
|
|
478
|
+
totalDocumentation: 0,
|
|
479
|
+
totalRefactors: 0,
|
|
480
|
+
totalMajors: 0,
|
|
481
|
+
totalMinors: 0,
|
|
482
|
+
totalPatches: 0,
|
|
483
|
+
criticalPoints: [],
|
|
484
|
+
specs: []
|
|
485
|
+
};
|
|
486
|
+
printDashboard(emptySummary);
|
|
487
|
+
return emptySummary;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Analyze each spec
|
|
491
|
+
const metricsList = specs.map(spec => analyzeSpec(spec));
|
|
492
|
+
|
|
493
|
+
// Aggregate into summary
|
|
494
|
+
/** @type {DashboardSummary} */
|
|
495
|
+
const summary = {
|
|
496
|
+
totalSpecs: metricsList.length,
|
|
497
|
+
coesoCount: metricsList.filter(m => m.classification === 'Coeso').length,
|
|
498
|
+
caoticoCount: metricsList.filter(m => m.classification === 'Caótico').length,
|
|
499
|
+
unclassifiedCount: metricsList.filter(m => !m.classification).length,
|
|
500
|
+
totalTasks: metricsList.reduce((acc, m) => acc + m.tasksTotal, 0),
|
|
501
|
+
completedTasks: metricsList.reduce((acc, m) => acc + m.tasksCompleted, 0),
|
|
502
|
+
pendingTasks: metricsList.reduce((acc, m) => acc + m.tasksPending, 0),
|
|
503
|
+
totalChanges: metricsList.reduce((acc, m) => acc + m.totalChanges, 0),
|
|
504
|
+
totalDiscoveries: metricsList.reduce((acc, m) => acc + m.changeTypes.discovery, 0),
|
|
505
|
+
totalFixes: metricsList.reduce((acc, m) => acc + m.changeTypes.fix, 0),
|
|
506
|
+
totalImprovements: metricsList.reduce((acc, m) => acc + m.changeTypes.improvement, 0),
|
|
507
|
+
totalDocumentation: metricsList.reduce((acc, m) => acc + m.changeTypes.documentation, 0),
|
|
508
|
+
totalRefactors: metricsList.reduce((acc, m) => acc + m.changeTypes.refactor, 0),
|
|
509
|
+
totalMajors: metricsList.reduce((acc, m) => acc + m.changeBreakdown.major, 0),
|
|
510
|
+
totalMinors: metricsList.reduce((acc, m) => acc + m.changeBreakdown.minor, 0),
|
|
511
|
+
totalPatches: metricsList.reduce((acc, m) => acc + m.changeBreakdown.patch, 0),
|
|
512
|
+
criticalPoints: [],
|
|
513
|
+
specs: metricsList
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// Build critical points
|
|
517
|
+
for (const m of metricsList) {
|
|
518
|
+
if (m.totalChanges === 0) {
|
|
519
|
+
summary.criticalPoints.push(`${m.relativePath} — No Audit History (0 changes)`);
|
|
520
|
+
}
|
|
521
|
+
if (m.tasksPending > 5) {
|
|
522
|
+
summary.criticalPoints.push(`${m.relativePath} — ⚠️ ${m.tasksPending} pending tasks`);
|
|
523
|
+
}
|
|
524
|
+
if (m.classification === 'Caótico') {
|
|
525
|
+
summary.criticalPoints.push(`${m.relativePath} — Classified as CAÓTICO`);
|
|
526
|
+
}
|
|
527
|
+
if (m.status === 'draft') {
|
|
528
|
+
summary.criticalPoints.push(`${m.relativePath} — Still in DRAFT status`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
printDashboard(summary);
|
|
533
|
+
return summary;
|
|
534
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# status — Specification
|
|
2
|
+
|
|
3
|
+
**SPEC_VERSION:** v1.0.0 — stable
|
|
4
|
+
**Classification:** Coeso
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
The `md status` command scans all `.spec.md` files in the project, extracts metrics from each spec's **Tasks** checklist and **Audit History** table, and generates a beautiful color-coded dashboard showing the MDDD protocol coverage: discoveries, fixes, improvements, documentation, refactors, task completion rates, and version evolution.
|
|
9
|
+
|
|
10
|
+
Reuses `SpecFinderService.findSpecs()` to discover specs — no custom file walking.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Behavioral Flow (Mermaid)
|
|
15
|
+
|
|
16
|
+
```mermaid
|
|
17
|
+
%% @spec-version v1.0.0
|
|
18
|
+
stateDiagram-v2
|
|
19
|
+
[*] --> FindSpecs: specFinder.findSpecs(process.cwd())
|
|
20
|
+
|
|
21
|
+
state FindSpecs {
|
|
22
|
+
[*] --> WalkTree: readdirSync + recursion
|
|
23
|
+
WalkTree --> CollectSpecs: filter *.spec.md
|
|
24
|
+
CollectSpecs --> [*]: return sorted string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
FindSpecs --> NoSpecs: specs.length === 0
|
|
28
|
+
NoSpecs --> PrintEmpty: pc.yellow warning
|
|
29
|
+
PrintEmpty --> [*]
|
|
30
|
+
|
|
31
|
+
FindSpecs --> AnalyzeLoop: for each spec
|
|
32
|
+
|
|
33
|
+
state AnalyzeSpec {
|
|
34
|
+
[*] --> ReadFile: read spec file
|
|
35
|
+
ReadFile --> ParseVersion: extract version
|
|
36
|
+
ParseVersion --> ParseStatus: detect draft or stable
|
|
37
|
+
ParseStatus --> ParseClassification: detect Coeso or Caótico
|
|
38
|
+
ParseClassification --> ParseTasks: count checklist items
|
|
39
|
+
ParseTasks --> ParseAudit: extract audit rows
|
|
40
|
+
ParseAudit --> ClassifyChanges: keyword analysis
|
|
41
|
+
ClassifyChanges --> [*]: return SpecMetrics
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
AnalyzeLoop --> AnalyzeSpec
|
|
45
|
+
AnalyzeSpec --> NextSpec: more specs remaining
|
|
46
|
+
NextSpec --> AnalyzeSpec
|
|
47
|
+
AnalyzeSpec --> Aggregate: all specs processed
|
|
48
|
+
|
|
49
|
+
state Aggregate {
|
|
50
|
+
[*] --> SumTasks: sum totals across specs
|
|
51
|
+
SumTasks --> SumChanges: sum MAJOR/MINOR/PATCH
|
|
52
|
+
SumChanges --> BuildCriticalPoints: detect anomalies
|
|
53
|
+
BuildCriticalPoints --> [*]: return DashboardSummary
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Aggregate --> PrintDashboard
|
|
57
|
+
|
|
58
|
+
state PrintDashboard {
|
|
59
|
+
[*] --> PrintHeader: render title box
|
|
60
|
+
PrintHeader --> PrintSpecs: coeso-caotico-unclassified
|
|
61
|
+
PrintSpecs --> PrintTasks: tasks with progress bar
|
|
62
|
+
PrintTasks --> PrintChanges: MAJOR-MINOR-PATCH
|
|
63
|
+
PrintChanges --> PrintImpact: discoveries-fixes-etc
|
|
64
|
+
PrintImpact --> PrintCritical: warnings highlighted
|
|
65
|
+
PrintCritical --> PrintFooter: timestamps and version
|
|
66
|
+
PrintFooter --> [*]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
PrintDashboard --> [*]: exit 0
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Decision Matrix
|
|
75
|
+
|
|
76
|
+
### Primitive Factors
|
|
77
|
+
|
|
78
|
+
| Factor | Type | Allowed Values | Default |
|
|
79
|
+
| :--- | :--- | :--- | :--- |
|
|
80
|
+
| Specs found in project? | Binary | `✅ YES` / `❌ NO` | `❌ NO` |
|
|
81
|
+
| Has Tasks section? | Binary | `✅ YES` / `❌ NO` | `❌ NO` |
|
|
82
|
+
| Has Audit History table? | Binary | `✅ YES` / `❌ NO` | `❌ NO` |
|
|
83
|
+
| Has Coeso/Caótico classification? | Binary | `✅ YES` / `❌ NO` | `❌ NO` |
|
|
84
|
+
| Total specs > 0? | Binary | `✅ YES` / `❌ NO` | `❌ NO` |
|
|
85
|
+
|
|
86
|
+
### Resolution Table
|
|
87
|
+
|
|
88
|
+
| Specs found? | Has Tasks? | Has Audit? | Has Classif.? | Total > 0? | Proposed Action | Decision | Transition State |
|
|
89
|
+
| :---: | :---: | :---: | :---: | :---: | :--- | :---: | :--- |
|
|
90
|
+
| ❌ NO | - | - | - | ❌ NO | `PRINT_DASHBOARD` | ✅ ALLOW | `EMPTY` |
|
|
91
|
+
| ✅ YES | ❌ NO | ❌ NO | ❌ NO | ✅ YES | `PRINT_DASHBOARD` | ✅ ALLOW | `BASIC` |
|
|
92
|
+
| ✅ YES | ✅ YES | ❌ NO | ❌ NO | ✅ YES | `PRINT_DASHBOARD` | ✅ ALLOW | `TASKS_ONLY` |
|
|
93
|
+
| ✅ YES | ❌ NO | ✅ YES | ❌ NO | ✅ YES | `PRINT_DASHBOARD` | ✅ ALLOW | `AUDIT_ONLY` |
|
|
94
|
+
| ✅ YES | ✅ YES | ✅ YES | ❌ NO | ✅ YES | `PRINT_DASHBOARD` | ✅ ALLOW | `FULL` |
|
|
95
|
+
| ✅ YES | ✅ YES | ✅ YES | ✅ YES | ✅ YES | `PRINT_DASHBOARD` | ✅ ALLOW | `FULL_CLASSIFIED` |
|
|
96
|
+
|
|
97
|
+
> `-` = wildcard / any value matches.
|
|
98
|
+
|
|
99
|
+
### Resolution Rules (per MDDD protocol, section 3.3)
|
|
100
|
+
|
|
101
|
+
1. ALL columns must match the current system state.
|
|
102
|
+
2. If no row fully matches → `HaltWithConflict`.
|
|
103
|
+
3. If multiple rows match → `HaltWithConflict` (ambiguous).
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Tasks
|
|
108
|
+
|
|
109
|
+
Atomic checklist derived from the spec above:
|
|
110
|
+
|
|
111
|
+
- [x] Create `src/commands/status.js` with full analysis and dashboard logic
|
|
112
|
+
- [x] Create `src/commands/status.spec.md` (this file)
|
|
113
|
+
- [x] Edit `bin/cli.js` — register the `status` command
|
|
114
|
+
- [x] Edit `bin/cli.spec.md` — update topology, matrix, and audit history
|
|
115
|
+
- [x] Create `tests/commands/status.spec.js` with 5 test scenarios
|
|
116
|
+
- [x] Run tests: `node --test tests/commands/status.spec.js`
|
|
117
|
+
- [x] Run live: `node bin/cli.js status` and verify dashboard output
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Audit History
|
|
122
|
+
|
|
123
|
+
<details>
|
|
124
|
+
<summary>Click to expand</summary>
|
|
125
|
+
|
|
126
|
+
| Date | Agent | Version | Change Summary |
|
|
127
|
+
| :--- | :--- | :---: | :--- |
|
|
128
|
+
| 2026-06-09 | Cline (md-edit) | v1.0.1 | **Fixed SPEC_VERSION header format** (added colon separator per template spec) and **added Classification: Coeso** field — resolves validation error where spec lacked required Coeso/Caótico classification that its own diagram and decision matrix reference. PATCH bump. |
|
|
129
|
+
| 2026-06-08 | Cline (md-edit) | v1.0.0 | **Spec created from template.** Behavioral flow covers the full pipeline: FindSpecs → AnalyzeLoop → Aggregate → PrintDashboard. Decision Matrix covers 5 primitive factors with 6 resolution rows. Tasks checklist mirrors implementation order. All 7 tasks completed and verified — 5/5 tests passing on `node --test tests/commands/status.spec.js`. Validated against 71-spec project (Appfy). Status promoted to **stable**. |
|
|
130
|
+
|
|
131
|
+
</details>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# FileSystemService — Specification
|
|
2
2
|
|
|
3
|
-
**SPEC_VERSION
|
|
3
|
+
**SPEC_VERSION:** v1.0.0 — stable
|
|
4
|
+
**Classification:** Coeso
|
|
4
5
|
|
|
5
6
|
## Overview
|
|
6
7
|
|
|
@@ -95,6 +96,7 @@ stateDiagram-v2
|
|
|
95
96
|
|
|
96
97
|
| Date | Agent | Version | Change Summary |
|
|
97
98
|
| :--- | :--- | :---: | :--- |
|
|
99
|
+
| 2026-06-09 | Cline (md-audit) | v1.0.1 | **Fixed SPEC_VERSION header format** (added colon separator per template spec) and **added Classification: Coeso** — aligns with the existing code quality (DI-ready, modular, testable). PATCH bump. |
|
|
98
100
|
| 2026-05-28 | Cline (md-audit) | v1.0.0 | **Spec created by md-audit.** Reverse-engineered from `src/services/FileSystemService.js` (38 lines). Code classified as **Clean / DI-ready**. All 7 methods documented with primitive factor analysis. No modifications to production code. |
|
|
99
101
|
|
|
100
102
|
</details>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# InitService — Specification
|
|
2
2
|
|
|
3
|
-
**SPEC_VERSION
|
|
3
|
+
**SPEC_VERSION:** v1.2.0 — stable
|
|
4
|
+
**Classification:** Coeso
|
|
4
5
|
|
|
5
6
|
## Overview
|
|
6
7
|
|
|
@@ -16,7 +17,7 @@ Co-located with `src/services/InitService.js`.
|
|
|
16
17
|
%% @spec-version v1.2.0
|
|
17
18
|
stateDiagram-v2
|
|
18
19
|
[*] --> createSystemPrompt: initService.createSystemPrompt(promptContent)
|
|
19
|
-
createSystemPrompt --> writeFile: this.#fs.writeFile('
|
|
20
|
+
createSystemPrompt --> writeFile: this.#fs.writeFile('AGENTS.md', content)
|
|
20
21
|
writeFile --> createSkills: initService.createSkills(sourceSkillsDir, logger)
|
|
21
22
|
|
|
22
23
|
createSkills --> ensureAgentsDir: this.#fs.ensureDir('.agents')
|
|
@@ -42,7 +43,7 @@ stateDiagram-v2
|
|
|
42
43
|
|
|
43
44
|
| Step | Method | I/O | Conditional Branch? | Error Handling | FS Side Effect |
|
|
44
45
|
| :--- | :--- | :--- | :---: | :--- | :--- |
|
|
45
|
-
| 1 | `createSystemPrompt(promptContent)` | Input: `string`<br>Output: `Promise<void>` | ❌ No | Delegated to `#fs.writeFile` | ✅ Writes `
|
|
46
|
+
| 1 | `createSystemPrompt(promptContent)` | Input: `string`<br>Output: `Promise<void>` | ❌ No | Delegated to `#fs.writeFile` | ✅ Writes `AGENTS.md` |
|
|
46
47
|
| 2 | `createSkills(sourceSkillsDir, logger)` | Input: `string` (source dir) + logger fn<br>Output: `Promise<void>` | ✅ Checks `fs.existsSync(sourceSkillsDir)` | Throws `Error` if source dir not found | ✅ Creates `.agents/`, `.agents/skills/`, copies all skill folders recursively |
|
|
47
48
|
| 3 | `createGitHubWorkflow(workflowYaml, logger)` | Input: `string` + logger fn<br>Output: `Promise<string>` | ❌ No | Delegated to `#fs` methods | ✅ Creates `.github/workflows/mddd-preview.yml` |
|
|
48
49
|
| 4 | `createSpecTemplate(sourceTemplatePath, logger)` | Input: `string` (source path) + logger fn<br>Output: `Promise<void>` | ✅ Checks `fs.existsSync(sourceTemplatePath)` | Throws `Error` if source file not found | ✅ Creates `.agents/templates/`, writes `spec-template.md` |
|
|
@@ -77,8 +78,9 @@ stateDiagram-v2
|
|
|
77
78
|
|
|
78
79
|
| Date | Agent | Version | Change Summary |
|
|
79
80
|
| :--- | :--- | :---: | :--- |
|
|
80
|
-
| 2026-
|
|
81
|
-
| 2026-05-28 | Cline (md-edit) | v1.1.0 | **New method `createGitHubWorkflow`.** Added to support `md init` creating `.github/workflows/mddd-preview.yml`. Updated behavioral flow diagram with new states. Updated Decision Matrix with steps 3, 7, 8. SPEC_VERSION bumped from v1.0.0 to v1.1.0 (minor — new method). Status promoted from **draft** to **stable** — implementation and tests verified. |
|
|
81
|
+
| 2026-06-09 | Cline (md-audit) | v1.2.1 | **Fixed SPEC_VERSION header format** (added colon separator per template spec) and **added Classification: Coeso** — aligns with the existing code quality (DI, modular, well-structured orchestration). PATCH bump. |
|
|
82
82
|
| 2026-06-04 | Cline (md-edit) | v1.2.0 | **New method `createSpecTemplate`.** Added to support `md init` copying `.agents/templates/spec-template.md` from the CLI package to the project. Reads the template file content via `fs.readFileSync`, ensures `.agents/templates/` dir exists, then writes `spec-template.md` via `#fs.writeFile`. Updated behavioral flow diagram with new states (`createSpecTemplate → ensureTemplatesDir → writeTemplateFile → logTemplate`). Updated Decision Matrix with steps 4, 8. SPEC_VERSION bumped from v1.1.0 to v1.2.0 (minor — new method). |
|
|
83
|
+
| 2026-05-28 | Cline (md-edit) | v1.1.0 | **New method `createGitHubWorkflow`.** Added to support `md init` creating `.github/workflows/mddd-preview.yml`. Updated behavioral flow diagram with new states. Updated Decision Matrix with steps 3, 7, 8. SPEC_VERSION bumped from v1.0.0 to v1.1.0 (minor — new method). Status promoted from **draft** to **stable** — implementation and tests verified. |
|
|
84
|
+
| 2026-05-28 | Cline (md-audit) | v1.0.0 | **Spec created by md-audit.** Reverse-engineered from `src/services/InitService.js` (52 lines). Code classified as **Clean / Service with DI**. All orchestration steps documented with primitive factor analysis. No modifications to production code. |
|
|
83
85
|
|
|
84
86
|
</details>
|
|
File without changes
|