mdan-cli 2.2.0 → 2.4.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/.mcp.json +46 -0
- package/AGENTS.md +246 -0
- package/README.md +32 -7
- package/agents/test.md +60 -2
- package/cli/mdan.js +149 -26
- package/cli/mdan.py +111 -54
- package/cli/mdan.sh +43 -43
- package/install.sh +30 -167
- package/integrations/all-integrations.md +2 -2
- package/integrations/cursor.md +11 -11
- package/integrations/mcp.md +153 -0
- package/integrations/windsurf.md +4 -4
- package/package.json +4 -2
- package/phases/04-verify.md +9 -3
- package/templates/prompts/README.md +108 -0
- package/templates/prompts/dev-agent.yaml +85 -0
- package/templates/prompts/orchestrator.yaml +97 -0
- package/templates/prompts.json +81 -0
- package/templates/tests/evaluations/README.md +80 -0
- package/templates/tests/evaluations/classification_eval.md +136 -0
- package/templates/tests/evaluations/rag_eval.md +116 -0
- package/templates/tests/scenarios/README.md +62 -0
- package/templates/tests/scenarios/basic_authentication.test.md +82 -0
- package/templates/tests/scenarios/user_registration.test.md +107 -0
package/cli/mdan.js
CHANGED
|
@@ -6,7 +6,7 @@ const { execSync } = require('child_process');
|
|
|
6
6
|
const { intro, text, select, isCancel, cancel, outro, spinner } = require('@clack/prompts');
|
|
7
7
|
const pc = require('picocolors');
|
|
8
8
|
|
|
9
|
-
const VERSION = '2.
|
|
9
|
+
const VERSION = '2.4.0';
|
|
10
10
|
const MDAN_DIR = path.resolve(__dirname, '..');
|
|
11
11
|
|
|
12
12
|
// Colors
|
|
@@ -38,7 +38,7 @@ function showHelp() {
|
|
|
38
38
|
console.log(`${colors.bold}USAGE${colors.nc}
|
|
39
39
|
mdan <command> [options]
|
|
40
40
|
|
|
41
|
-
${colors.bold}COMMANDS${colors.nc}
|
|
41
|
+
${colors.bold}COMMANDS${colors.nc}
|
|
42
42
|
init [name] Create a new project
|
|
43
43
|
attach [--rebuild] Add MDAN to existing project
|
|
44
44
|
status Show project status
|
|
@@ -48,16 +48,20 @@ ${colors.bold}COMMANDS${colors.nc}
|
|
|
48
48
|
agent [name] Show agent prompt
|
|
49
49
|
oc Copy orchestrator prompt to clipboard
|
|
50
50
|
skills List available skills
|
|
51
|
+
mcp [action] MCP config (init|validate|list)
|
|
52
|
+
prompt [action] Manage prompts (list|show <name>)
|
|
51
53
|
version Show version
|
|
52
54
|
|
|
53
|
-
${colors.bold}EXAMPLES${colors.nc}
|
|
55
|
+
${colors.bold}EXAMPLES${colors.nc}
|
|
54
56
|
mdan init my-app # New project
|
|
55
57
|
cd my-project && mdan attach # Existing project
|
|
56
58
|
mdan attach --rebuild # Rebuild from scratch
|
|
59
|
+
mdan mcp init # Generate .mcp.json
|
|
60
|
+
mdan prompt list # List versioned prompts
|
|
57
61
|
|
|
58
|
-
${colors.bold}AGENTS${colors.nc}
|
|
62
|
+
${colors.bold}AGENTS${colors.nc}
|
|
59
63
|
product, architect, ux, dev, test, security, devops, doc
|
|
60
|
-
`);
|
|
64
|
+
`);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
async function cmdInit(initialName) {
|
|
@@ -100,20 +104,23 @@ async function cmdInit(initialName) {
|
|
|
100
104
|
s.start(`Creating ${name} project structure...`);
|
|
101
105
|
|
|
102
106
|
const dirs = [
|
|
103
|
-
`${name}
|
|
104
|
-
`${name}
|
|
107
|
+
`${name}/mdan/agents`,
|
|
108
|
+
`${name}/mdan/skills`,
|
|
105
109
|
`${name}/mdan_output`,
|
|
106
110
|
`${name}/.claude/skills`,
|
|
107
|
-
`${name}/.github
|
|
111
|
+
`${name}/.github`,
|
|
112
|
+
`${name}/tests/scenarios`,
|
|
113
|
+
`${name}/tests/evaluations`,
|
|
114
|
+
`${name}/templates/prompts`
|
|
108
115
|
];
|
|
109
116
|
|
|
110
117
|
dirs.forEach(dir => fs.mkdirSync(dir, { recursive: true }));
|
|
111
118
|
|
|
112
|
-
fs.copyFileSync(`${MDAN_DIR}/core/orchestrator.md`, `${name}
|
|
113
|
-
fs.copyFileSync(`${MDAN_DIR}/core/universal-envelope.md`, `${name}
|
|
119
|
+
fs.copyFileSync(`${MDAN_DIR}/core/orchestrator.md`, `${name}/mdan/orchestrator.md`);
|
|
120
|
+
fs.copyFileSync(`${MDAN_DIR}/core/universal-envelope.md`, `${name}/mdan/universal-envelope.md`);
|
|
114
121
|
|
|
115
122
|
fs.readdirSync(`${MDAN_DIR}/agents`).filter(f => f.endsWith('.md')).forEach(f => {
|
|
116
|
-
fs.copyFileSync(`${MDAN_DIR}/agents/${f}`, `${name}
|
|
123
|
+
fs.copyFileSync(`${MDAN_DIR}/agents/${f}`, `${name}/mdan/agents/${f}`);
|
|
117
124
|
});
|
|
118
125
|
|
|
119
126
|
fs.readdirSync(`${MDAN_DIR}/templates`).filter(f => f.endsWith('.md')).forEach(f => {
|
|
@@ -125,7 +132,7 @@ async function cmdInit(initialName) {
|
|
|
125
132
|
if (fs.existsSync(skillsDir)) {
|
|
126
133
|
fs.readdirSync(skillsDir).forEach(skill => {
|
|
127
134
|
const src = `${skillsDir}/${skill}`;
|
|
128
|
-
const dest1 = `${name}
|
|
135
|
+
const dest1 = `${name}/mdan/skills/${skill}`;
|
|
129
136
|
const dest2 = `${name}/.claude/skills/${skill}`;
|
|
130
137
|
if (fs.statSync(src).isDirectory()) {
|
|
131
138
|
fs.cpSync(src, dest1, { recursive: true });
|
|
@@ -136,13 +143,30 @@ async function cmdInit(initialName) {
|
|
|
136
143
|
|
|
137
144
|
// Create .cursorrules
|
|
138
145
|
let cursorrules = fs.readFileSync(`${MDAN_DIR}/core/orchestrator.md`, 'utf8');
|
|
139
|
-
cursorrules += '\n\n## CURSOR INSTRUCTIONS\nAgent files are in
|
|
146
|
+
cursorrules += '\n\n## CURSOR INSTRUCTIONS\nAgent files are in mdan/agents/\nSkills are in mdan/skills/';
|
|
140
147
|
fs.writeFileSync(`${name}/.cursorrules`, cursorrules);
|
|
141
148
|
fs.copyFileSync(`${name}/.cursorrules`, `${name}/.windsurfrules`);
|
|
142
149
|
fs.copyFileSync(`${MDAN_DIR}/core/orchestrator.md`, `${name}/.github/copilot-instructions.md`);
|
|
143
150
|
|
|
144
151
|
fs.writeFileSync(`${name}/README.md`, `# ${name}\n\n> Built with MDAN (${setupType} profile)\n`);
|
|
145
152
|
|
|
153
|
+
// Copy AGENTS.md and generate .mcp.json
|
|
154
|
+
if (fs.existsSync(`${MDAN_DIR}/AGENTS.md`)) {
|
|
155
|
+
fs.copyFileSync(`${MDAN_DIR}/AGENTS.md`, `${name}/AGENTS.md`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const mcpConfig = {
|
|
159
|
+
mcpServers: { "mdan-memory": { command: "node", args: ["-e", "console.log('MDAN MCP')"] } },
|
|
160
|
+
metadata: { version: VERSION, framework: "mdan", generated: new Date().toISOString().split('T')[0] },
|
|
161
|
+
capabilities: {
|
|
162
|
+
scenarios: { enabled: true, test_paths: ["tests/scenarios/", "templates/tests/scenarios/"] },
|
|
163
|
+
evaluations: { enabled: true, eval_paths: ["tests/evaluations/", "templates/tests/evaluations/"] },
|
|
164
|
+
prompts: { enabled: true, prompt_paths: ["templates/prompts/"], registry: "templates/prompts.json" }
|
|
165
|
+
},
|
|
166
|
+
quality_gates: { min_test_coverage: 80, require_evaluations: true, require_scenarios: false }
|
|
167
|
+
};
|
|
168
|
+
fs.writeFileSync(`${name}/.mcp.json`, JSON.stringify(mcpConfig, null, 2));
|
|
169
|
+
|
|
146
170
|
s.stop(pc.green(`Project ${name} initialized successfully!`));
|
|
147
171
|
|
|
148
172
|
outro(
|
|
@@ -181,16 +205,19 @@ async function cmdAttach(rebuildMode) {
|
|
|
181
205
|
? `Preparing REBUILD environment for ${projectName}...`
|
|
182
206
|
: `Attaching MDAN to ${projectName}...`);
|
|
183
207
|
|
|
184
|
-
fs.mkdirSync('
|
|
185
|
-
fs.mkdirSync('
|
|
208
|
+
fs.mkdirSync('mdan/agents', { recursive: true });
|
|
209
|
+
fs.mkdirSync('mdan/skills', { recursive: true });
|
|
186
210
|
fs.mkdirSync('.claude/skills', { recursive: true });
|
|
187
211
|
fs.mkdirSync('.github', { recursive: true });
|
|
212
|
+
fs.mkdirSync('tests/scenarios', { recursive: true });
|
|
213
|
+
fs.mkdirSync('tests/evaluations', { recursive: true });
|
|
214
|
+
fs.mkdirSync('templates/prompts', { recursive: true });
|
|
188
215
|
|
|
189
|
-
fs.copyFileSync(`${MDAN_DIR}/core/orchestrator.md`, '
|
|
190
|
-
fs.copyFileSync(`${MDAN_DIR}/core/universal-envelope.md`, '
|
|
216
|
+
fs.copyFileSync(`${MDAN_DIR}/core/orchestrator.md`, 'mdan/orchestrator.md');
|
|
217
|
+
fs.copyFileSync(`${MDAN_DIR}/core/universal-envelope.md`, 'mdan/universal-envelope.md');
|
|
191
218
|
|
|
192
219
|
fs.readdirSync(`${MDAN_DIR}/agents`).filter(f => f.endsWith('.md')).forEach(f => {
|
|
193
|
-
fs.copyFileSync(`${MDAN_DIR}/agents/${f}`,
|
|
220
|
+
fs.copyFileSync(`${MDAN_DIR}/agents/${f}`, `mdan/agents/${f}`);
|
|
194
221
|
});
|
|
195
222
|
|
|
196
223
|
// Copy skills
|
|
@@ -199,7 +226,7 @@ async function cmdAttach(rebuildMode) {
|
|
|
199
226
|
fs.readdirSync(skillsDir).forEach(skill => {
|
|
200
227
|
const src = `${skillsDir}/${skill}`;
|
|
201
228
|
if (fs.statSync(src).isDirectory()) {
|
|
202
|
-
fs.cpSync(src,
|
|
229
|
+
fs.cpSync(src, `mdan/skills/${skill}`, { recursive: true });
|
|
203
230
|
fs.cpSync(src, `.claude/skills/${skill}`, { recursive: true });
|
|
204
231
|
}
|
|
205
232
|
});
|
|
@@ -216,6 +243,23 @@ async function cmdAttach(rebuildMode) {
|
|
|
216
243
|
fs.copyFileSync('.cursorrules', '.windsurfrules');
|
|
217
244
|
fs.copyFileSync(`${MDAN_DIR}/core/orchestrator.md`, '.github/copilot-instructions.md');
|
|
218
245
|
|
|
246
|
+
// Copy AGENTS.md and generate .mcp.json
|
|
247
|
+
if (fs.existsSync(`${MDAN_DIR}/AGENTS.md`)) {
|
|
248
|
+
fs.copyFileSync(`${MDAN_DIR}/AGENTS.md`, 'AGENTS.md');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const mcpConfig = {
|
|
252
|
+
mcpServers: { "mdan-memory": { command: "node", args: ["-e", "console.log('MDAN MCP')"] } },
|
|
253
|
+
metadata: { version: VERSION, framework: "mdan", generated: new Date().toISOString().split('T')[0] },
|
|
254
|
+
capabilities: {
|
|
255
|
+
scenarios: { enabled: true, test_paths: ["tests/scenarios/", "templates/tests/scenarios/"] },
|
|
256
|
+
evaluations: { enabled: true, eval_paths: ["tests/evaluations/", "templates/tests/evaluations/"] },
|
|
257
|
+
prompts: { enabled: true, prompt_paths: ["templates/prompts/"], registry: "templates/prompts.json" }
|
|
258
|
+
},
|
|
259
|
+
quality_gates: { min_test_coverage: 80, require_evaluations: true, require_scenarios: false }
|
|
260
|
+
};
|
|
261
|
+
fs.writeFileSync('.mcp.json', JSON.stringify(mcpConfig, null, 2));
|
|
262
|
+
|
|
219
263
|
s.stop(pc.green(`MDAN attached successfully!`));
|
|
220
264
|
|
|
221
265
|
outro(
|
|
@@ -229,7 +273,7 @@ async function cmdAttach(rebuildMode) {
|
|
|
229
273
|
}
|
|
230
274
|
|
|
231
275
|
function cmdOc() {
|
|
232
|
-
let orchFile = '
|
|
276
|
+
let orchFile = 'mdan/orchestrator.md';
|
|
233
277
|
if (!fs.existsSync(orchFile)) {
|
|
234
278
|
orchFile = `${MDAN_DIR}/core/orchestrator.md`;
|
|
235
279
|
}
|
|
@@ -262,10 +306,10 @@ function cmdOc() {
|
|
|
262
306
|
}
|
|
263
307
|
|
|
264
308
|
function cmdStatus() {
|
|
265
|
-
if (fs.existsSync('
|
|
309
|
+
if (fs.existsSync('mdan/orchestrator.md')) {
|
|
266
310
|
console.log(`${colors.green}✅ MDAN is active in this project${colors.nc}`);
|
|
267
|
-
if (fs.existsSync('
|
|
268
|
-
console.log(fs.readFileSync('
|
|
311
|
+
if (fs.existsSync('mdan/STATUS.md')) {
|
|
312
|
+
console.log(fs.readFileSync('mdan/STATUS.md', 'utf8'));
|
|
269
313
|
}
|
|
270
314
|
} else {
|
|
271
315
|
console.log(`${colors.yellow}No MDAN project here.${colors.nc}`);
|
|
@@ -350,15 +394,15 @@ function cmdModule(action, name) {
|
|
|
350
394
|
|
|
351
395
|
console.log(`${colors.cyan}📦 Installing module: ${colors.bold}${name}${colors.nc}`);
|
|
352
396
|
|
|
353
|
-
if (!fs.existsSync('
|
|
354
|
-
console.log(`${colors.yellow}⚠️ No
|
|
397
|
+
if (!fs.existsSync('mdan')) {
|
|
398
|
+
console.log(`${colors.yellow}⚠️ No mdan folder found. Are you in an MDAN project?${colors.nc}`);
|
|
355
399
|
return;
|
|
356
400
|
}
|
|
357
401
|
|
|
358
402
|
// Copy agents
|
|
359
403
|
if (fs.existsSync(`${moduleDir}/agents`)) {
|
|
360
404
|
fs.readdirSync(`${moduleDir}/agents`).forEach(f => {
|
|
361
|
-
fs.copyFileSync(`${moduleDir}/agents/${f}`,
|
|
405
|
+
fs.copyFileSync(`${moduleDir}/agents/${f}`, `mdan/agents/${f}`);
|
|
362
406
|
console.log(`${colors.green} Added agent:${colors.nc} ${f}`);
|
|
363
407
|
});
|
|
364
408
|
}
|
|
@@ -430,6 +474,79 @@ function cmdSkills() {
|
|
|
430
474
|
}
|
|
431
475
|
}
|
|
432
476
|
|
|
477
|
+
function cmdMcp(action) {
|
|
478
|
+
if (!action || action === 'init') {
|
|
479
|
+
const mcpConfig = {
|
|
480
|
+
mcpServers: {
|
|
481
|
+
"mdan-memory": {
|
|
482
|
+
command: "node",
|
|
483
|
+
args: ["-e", "console.log(JSON.stringify({tools: []}))"]
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
metadata: {
|
|
487
|
+
version: VERSION,
|
|
488
|
+
framework: "mdan",
|
|
489
|
+
generated: new Date().toISOString().split('T')[0]
|
|
490
|
+
},
|
|
491
|
+
capabilities: {
|
|
492
|
+
scenarios: { enabled: true, test_paths: ["tests/scenarios/", "templates/tests/scenarios/"] },
|
|
493
|
+
evaluations: { enabled: true, eval_paths: ["tests/evaluations/", "templates/tests/evaluations/"] },
|
|
494
|
+
prompts: { enabled: true, prompt_paths: ["templates/prompts/"], registry: "templates/prompts.json" }
|
|
495
|
+
},
|
|
496
|
+
quality_gates: {
|
|
497
|
+
min_test_coverage: 80,
|
|
498
|
+
require_evaluations: true,
|
|
499
|
+
require_scenarios: false
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
fs.writeFileSync('.mcp.json', JSON.stringify(mcpConfig, null, 2));
|
|
503
|
+
console.log(`${colors.green}✅ .mcp.json created!${colors.nc}`);
|
|
504
|
+
console.log(' Configure your IDE to use MCP with this file.');
|
|
505
|
+
} else if (action === 'validate') {
|
|
506
|
+
if (fs.existsSync('.mcp.json')) {
|
|
507
|
+
try {
|
|
508
|
+
JSON.parse(fs.readFileSync('.mcp.json', 'utf8'));
|
|
509
|
+
console.log(`${colors.green}✅ .mcp.json is valid${colors.nc}`);
|
|
510
|
+
} catch (e) {
|
|
511
|
+
console.log(`${colors.red}❌ Invalid JSON: ${e.message}${colors.nc}`);
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
console.log(`${colors.yellow}⚠️ No .mcp.json found${colors.nc}`);
|
|
515
|
+
}
|
|
516
|
+
} else if (action === 'list') {
|
|
517
|
+
console.log(`${colors.cyan}MCP Tools:${colors.nc}`);
|
|
518
|
+
console.log(' - mdan-state: Read/write project state');
|
|
519
|
+
console.log(' - mdan-agents: List MDAN agents');
|
|
520
|
+
console.log(' - mdan-phases: Get phase information');
|
|
521
|
+
} else {
|
|
522
|
+
console.log('Usage: mdan mcp [init|validate|list]');
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function cmdPrompt(action, name) {
|
|
527
|
+
const promptsDir = `${MDAN_DIR}/templates/prompts`;
|
|
528
|
+
if (!fs.existsSync(promptsDir)) {
|
|
529
|
+
console.log(`${colors.yellow}No prompts directory found${colors.nc}`);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (!action || action === 'list') {
|
|
534
|
+
console.log(`${colors.cyan}Available Prompts:${colors.nc}`);
|
|
535
|
+
fs.readdirSync(promptsDir).filter(f => f.endsWith('.yaml')).forEach(f => {
|
|
536
|
+
console.log(` ${f.replace('.yaml', '')}`);
|
|
537
|
+
});
|
|
538
|
+
} else if (action === 'show' && name) {
|
|
539
|
+
const file = `${promptsDir}/${name}.yaml`;
|
|
540
|
+
if (fs.existsSync(file)) {
|
|
541
|
+
console.log(fs.readFileSync(file, 'utf8'));
|
|
542
|
+
} else {
|
|
543
|
+
console.log(`${colors.red}Prompt not found: ${name}${colors.nc}`);
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
console.log('Usage: mdan prompt [list|show <name>]');
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
433
550
|
// Main
|
|
434
551
|
const [,, cmd, ...args] = process.argv;
|
|
435
552
|
|
|
@@ -494,6 +611,12 @@ async function main() {
|
|
|
494
611
|
case 'skills':
|
|
495
612
|
cmdSkills();
|
|
496
613
|
break;
|
|
614
|
+
case 'mcp':
|
|
615
|
+
cmdMcp(args[0]);
|
|
616
|
+
break;
|
|
617
|
+
case 'prompt':
|
|
618
|
+
cmdPrompt(args[0], args[1]);
|
|
619
|
+
break;
|
|
497
620
|
case 'version':
|
|
498
621
|
case '-v':
|
|
499
622
|
console.log(`MDAN v${VERSION}`);
|
package/cli/mdan.py
CHANGED
|
@@ -7,7 +7,7 @@ import shutil
|
|
|
7
7
|
import json
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
VERSION = "2.
|
|
10
|
+
VERSION = "2.4.0"
|
|
11
11
|
MDAN_DIR = Path(__file__).parent.parent
|
|
12
12
|
|
|
13
13
|
# Colors
|
|
@@ -19,6 +19,7 @@ MAGENTA = "\033[0;35m"
|
|
|
19
19
|
BOLD = "\033[1m"
|
|
20
20
|
NC = "\033[0m"
|
|
21
21
|
|
|
22
|
+
|
|
22
23
|
def banner():
|
|
23
24
|
print(f"{CYAN}")
|
|
24
25
|
print(" ███╗ ███╗██████╗ █████╗ ███╗ ██╗")
|
|
@@ -30,6 +31,7 @@ def banner():
|
|
|
30
31
|
print(f"{NC}")
|
|
31
32
|
print(f" {BOLD}Multi-Agent Development Agentic Network{NC} v{VERSION}\n")
|
|
32
33
|
|
|
34
|
+
|
|
33
35
|
def show_help():
|
|
34
36
|
banner()
|
|
35
37
|
print(f"{BOLD}USAGE{NC}")
|
|
@@ -50,77 +52,90 @@ def show_help():
|
|
|
50
52
|
print(f"{BOLD}AGENTS{NC}")
|
|
51
53
|
print(" product, architect, ux, dev, test, security, devops, doc")
|
|
52
54
|
|
|
55
|
+
|
|
53
56
|
def cmd_init(name="my-project"):
|
|
54
57
|
print(f"{CYAN}🚀 Creating: {BOLD}{name}{NC}")
|
|
55
|
-
|
|
58
|
+
|
|
56
59
|
dirs = [
|
|
57
|
-
f"{name}
|
|
58
|
-
f"{name}
|
|
60
|
+
f"{name}/mdan/agents",
|
|
61
|
+
f"{name}/mdan/skills",
|
|
62
|
+
f"{name}/mdan_output",
|
|
63
|
+
f"{name}/.claude/skills",
|
|
64
|
+
f"{name}/.github",
|
|
59
65
|
]
|
|
60
66
|
for d in dirs:
|
|
61
67
|
Path(d).mkdir(parents=True, exist_ok=True)
|
|
62
|
-
|
|
63
|
-
shutil.copy(f"{MDAN_DIR}/core/orchestrator.md", f"{name}
|
|
64
|
-
shutil.copy(f"{MDAN_DIR}/core/universal-envelope.md", f"{name}
|
|
65
|
-
|
|
68
|
+
|
|
69
|
+
shutil.copy(f"{MDAN_DIR}/core/orchestrator.md", f"{name}/mdan/")
|
|
70
|
+
shutil.copy(f"{MDAN_DIR}/core/universal-envelope.md", f"{name}/mdan/")
|
|
71
|
+
|
|
66
72
|
for f in Path(f"{MDAN_DIR}/agents").glob("*.md"):
|
|
67
|
-
shutil.copy(f, f"{name}
|
|
68
|
-
|
|
73
|
+
shutil.copy(f, f"{name}/mdan/agents/")
|
|
74
|
+
|
|
69
75
|
for f in Path(f"{MDAN_DIR}/templates").glob("*.md"):
|
|
70
76
|
shutil.copy(f, f"{name}/mdan_output/")
|
|
71
|
-
|
|
77
|
+
|
|
72
78
|
skills_dir = Path(f"{MDAN_DIR}/skills")
|
|
73
79
|
if skills_dir.exists():
|
|
74
80
|
for s in skills_dir.iterdir():
|
|
75
81
|
if s.is_dir():
|
|
76
|
-
shutil.copytree(s, f"{name}
|
|
77
|
-
shutil.copytree(
|
|
78
|
-
|
|
82
|
+
shutil.copytree(s, f"{name}/mdan/skills/{s.name}", dirs_exist_ok=True)
|
|
83
|
+
shutil.copytree(
|
|
84
|
+
s, f"{name}/.claude/skills/{s.name}", dirs_exist_ok=True
|
|
85
|
+
)
|
|
86
|
+
|
|
79
87
|
cursorrules = Path(f"{MDAN_DIR}/core/orchestrator.md").read_text()
|
|
80
|
-
cursorrules += "\n\n## CURSOR INSTRUCTIONS\nAgent files in
|
|
88
|
+
cursorrules += "\n\n## CURSOR INSTRUCTIONS\nAgent files in mdan/agents/\nSkills in mdan/skills/"
|
|
81
89
|
Path(f"{name}/.cursorrules").write_text(cursorrules)
|
|
82
90
|
shutil.copy(f"{name}/.cursorrules", f"{name}/.windsurfrules")
|
|
83
|
-
shutil.copy(
|
|
91
|
+
shutil.copy(
|
|
92
|
+
f"{MDAN_DIR}/core/orchestrator.md", f"{name}/.github/copilot-instructions.md"
|
|
93
|
+
)
|
|
84
94
|
Path(f"{name}/README.md").write_text(f"# {name}\n\n> Built with MDAN\n")
|
|
85
|
-
|
|
95
|
+
|
|
86
96
|
print(f"{GREEN}✅ Created {name}/{NC}\n")
|
|
87
97
|
print(f" {BOLD}Next:{NC} cursor {name}")
|
|
88
98
|
|
|
99
|
+
|
|
89
100
|
def cmd_attach(rebuild=None):
|
|
90
101
|
project = Path.cwd().name
|
|
91
|
-
|
|
102
|
+
|
|
92
103
|
if rebuild == "--rebuild":
|
|
93
104
|
print(f"{MAGENTA}🔄 REBUILD MODE: {BOLD}{project}{NC}")
|
|
94
105
|
else:
|
|
95
106
|
print(f"{CYAN}🔗 Attaching to: {BOLD}{project}{NC}")
|
|
96
|
-
|
|
97
|
-
Path("
|
|
98
|
-
Path("
|
|
107
|
+
|
|
108
|
+
Path("mdan/agents").mkdir(parents=True, exist_ok=True)
|
|
109
|
+
Path("mdan/skills").mkdir(parents=True, exist_ok=True)
|
|
99
110
|
Path(".claude/skills").mkdir(parents=True, exist_ok=True)
|
|
100
111
|
Path(".github").mkdir(parents=True, exist_ok=True)
|
|
101
|
-
|
|
102
|
-
shutil.copy(f"{MDAN_DIR}/core/orchestrator.md", "
|
|
103
|
-
shutil.copy(f"{MDAN_DIR}/core/universal-envelope.md", "
|
|
104
|
-
|
|
112
|
+
|
|
113
|
+
shutil.copy(f"{MDAN_DIR}/core/orchestrator.md", "mdan/")
|
|
114
|
+
shutil.copy(f"{MDAN_DIR}/core/universal-envelope.md", "mdan/")
|
|
115
|
+
|
|
105
116
|
for f in Path(f"{MDAN_DIR}/agents").glob("*.md"):
|
|
106
|
-
shutil.copy(f, "
|
|
107
|
-
|
|
117
|
+
shutil.copy(f, "mdan/agents/")
|
|
118
|
+
|
|
108
119
|
skills_dir = Path(f"{MDAN_DIR}/skills")
|
|
109
120
|
if skills_dir.exists():
|
|
110
121
|
for s in skills_dir.iterdir():
|
|
111
122
|
if s.is_dir():
|
|
112
|
-
shutil.copytree(s, f"
|
|
123
|
+
shutil.copytree(s, f"mdan/skills/{s.name}", dirs_exist_ok=True)
|
|
113
124
|
shutil.copytree(s, f".claude/skills/{s.name}", dirs_exist_ok=True)
|
|
114
|
-
|
|
125
|
+
|
|
115
126
|
cursorrules = Path(f"{MDAN_DIR}/core/orchestrator.md").read_text()
|
|
116
127
|
if rebuild == "--rebuild":
|
|
117
|
-
cursorrules +=
|
|
128
|
+
cursorrules += (
|
|
129
|
+
"\n\n## REBUILD MODE\nAnalyze existing code then rewrite from scratch."
|
|
130
|
+
)
|
|
118
131
|
else:
|
|
119
|
-
cursorrules +=
|
|
132
|
+
cursorrules += (
|
|
133
|
+
"\n\n## EXISTING PROJECT\nAnalyze codebase before making changes."
|
|
134
|
+
)
|
|
120
135
|
Path(".cursorrules").write_text(cursorrules)
|
|
121
136
|
shutil.copy(".cursorrules", ".windsurfrules")
|
|
122
137
|
shutil.copy(f"{MDAN_DIR}/core/orchestrator.md", ".github/copilot-instructions.md")
|
|
123
|
-
|
|
138
|
+
|
|
124
139
|
print(f"{GREEN}✅ MDAN ready!{NC}\n")
|
|
125
140
|
print(f" {BOLD}Next:{NC} Run {CYAN}mdan oc{NC} to copy prompt")
|
|
126
141
|
if rebuild == "--rebuild":
|
|
@@ -128,45 +143,63 @@ def cmd_attach(rebuild=None):
|
|
|
128
143
|
else:
|
|
129
144
|
print(f" Start: {CYAN}MDAN: Analyze this project{NC}")
|
|
130
145
|
|
|
146
|
+
|
|
131
147
|
def cmd_oc():
|
|
132
148
|
import subprocess
|
|
133
|
-
|
|
149
|
+
|
|
150
|
+
orch_file = Path("mdan/orchestrator.md")
|
|
134
151
|
if not orch_file.exists():
|
|
135
152
|
orch_file = Path(f"{MDAN_DIR}/core/orchestrator.md")
|
|
136
|
-
|
|
153
|
+
|
|
137
154
|
if orch_file.exists():
|
|
138
155
|
content = orch_file.read_text()
|
|
139
156
|
try:
|
|
140
157
|
if sys.platform == "darwin":
|
|
141
|
-
subprocess.run(
|
|
158
|
+
subprocess.run(
|
|
159
|
+
"pbcopy", universal_newlines=True, input=content, check=True
|
|
160
|
+
)
|
|
142
161
|
elif sys.platform == "win32":
|
|
143
|
-
subprocess.run(
|
|
162
|
+
subprocess.run(
|
|
163
|
+
"clip", universal_newlines=True, input=content, check=True
|
|
164
|
+
)
|
|
144
165
|
elif sys.platform == "linux":
|
|
145
166
|
if shutil.which("xclip"):
|
|
146
|
-
subprocess.run(
|
|
167
|
+
subprocess.run(
|
|
168
|
+
["xclip", "-selection", "clipboard"],
|
|
169
|
+
universal_newlines=True,
|
|
170
|
+
input=content,
|
|
171
|
+
check=True,
|
|
172
|
+
)
|
|
147
173
|
elif shutil.which("wl-copy"):
|
|
148
|
-
subprocess.run(
|
|
174
|
+
subprocess.run(
|
|
175
|
+
["wl-copy"], universal_newlines=True, input=content, check=True
|
|
176
|
+
)
|
|
149
177
|
else:
|
|
150
178
|
raise Exception("No clipboard tool found")
|
|
151
179
|
print(f"{GREEN}✅ Orchestrator prompt copied to clipboard!{NC}")
|
|
152
180
|
print(" Paste it into Claude, ChatGPT, or your favorite LLM.")
|
|
153
181
|
except Exception:
|
|
154
182
|
print(content)
|
|
155
|
-
print(
|
|
183
|
+
print(
|
|
184
|
+
f"\n{YELLOW}⚠️ Could not copy to clipboard automatically. Please copy the text above.{NC}"
|
|
185
|
+
)
|
|
156
186
|
else:
|
|
157
187
|
print(f"{RED}Orchestrator file not found.{NC}")
|
|
158
188
|
|
|
189
|
+
|
|
159
190
|
def cmd_status():
|
|
160
|
-
if Path("
|
|
191
|
+
if Path("mdan/orchestrator.md").exists():
|
|
161
192
|
print(f"{GREEN}✅ MDAN is active in this project{NC}")
|
|
162
|
-
if Path("
|
|
163
|
-
print(Path("
|
|
193
|
+
if Path("mdan/STATUS.md").exists():
|
|
194
|
+
print(Path("mdan/STATUS.md").read_text())
|
|
164
195
|
else:
|
|
165
196
|
print(f"{YELLOW}No MDAN project here.{NC}")
|
|
166
197
|
print(" Run: mdan init [name] or mdan attach")
|
|
167
198
|
|
|
199
|
+
|
|
168
200
|
def cmd_phase(num, action=None):
|
|
169
201
|
import subprocess
|
|
202
|
+
|
|
170
203
|
phases = {
|
|
171
204
|
"1": ("01-discover.md", "DISCOVER"),
|
|
172
205
|
"discover": ("01-discover.md", "DISCOVER"),
|
|
@@ -177,43 +210,62 @@ def cmd_phase(num, action=None):
|
|
|
177
210
|
"4": ("04-verify.md", "VERIFY"),
|
|
178
211
|
"verify": ("04-verify.md", "VERIFY"),
|
|
179
212
|
"5": ("05-ship.md", "SHIP"),
|
|
180
|
-
"ship": ("05-ship.md", "SHIP")
|
|
213
|
+
"ship": ("05-ship.md", "SHIP"),
|
|
181
214
|
}
|
|
182
|
-
|
|
215
|
+
|
|
183
216
|
if num not in phases:
|
|
184
217
|
print("Usage: mdan phase [1-5|name] [copy]")
|
|
185
218
|
return
|
|
186
|
-
|
|
219
|
+
|
|
187
220
|
file, name = phases[num]
|
|
188
221
|
phase_file = Path(f"{MDAN_DIR}/phases/{file}")
|
|
189
|
-
|
|
222
|
+
|
|
190
223
|
if phase_file.exists():
|
|
191
224
|
content = phase_file.read_text()
|
|
192
225
|
if action in ["copy", "-c"]:
|
|
193
226
|
try:
|
|
194
227
|
if sys.platform == "darwin":
|
|
195
|
-
subprocess.run(
|
|
228
|
+
subprocess.run(
|
|
229
|
+
"pbcopy", universal_newlines=True, input=content, check=True
|
|
230
|
+
)
|
|
196
231
|
elif sys.platform == "win32":
|
|
197
|
-
subprocess.run(
|
|
232
|
+
subprocess.run(
|
|
233
|
+
"clip", universal_newlines=True, input=content, check=True
|
|
234
|
+
)
|
|
198
235
|
elif sys.platform == "linux":
|
|
199
236
|
if shutil.which("xclip"):
|
|
200
|
-
subprocess.run(
|
|
237
|
+
subprocess.run(
|
|
238
|
+
["xclip", "-selection", "clipboard"],
|
|
239
|
+
universal_newlines=True,
|
|
240
|
+
input=content,
|
|
241
|
+
check=True,
|
|
242
|
+
)
|
|
201
243
|
elif shutil.which("wl-copy"):
|
|
202
|
-
subprocess.run(
|
|
244
|
+
subprocess.run(
|
|
245
|
+
["wl-copy"],
|
|
246
|
+
universal_newlines=True,
|
|
247
|
+
input=content,
|
|
248
|
+
check=True,
|
|
249
|
+
)
|
|
203
250
|
else:
|
|
204
251
|
raise Exception("No clipboard tool found")
|
|
205
252
|
print(f"{GREEN}✅ Phase {name} prompt copied to clipboard!{NC}")
|
|
206
253
|
print(" Paste it into your LLM to start the phase.")
|
|
207
254
|
except Exception:
|
|
208
255
|
print(content)
|
|
209
|
-
print(
|
|
256
|
+
print(
|
|
257
|
+
f"\n{YELLOW}⚠️ Could not copy automatically. Please copy the text above.{NC}"
|
|
258
|
+
)
|
|
210
259
|
else:
|
|
211
260
|
print(f"{CYAN}{BOLD}Phase {name}{NC}")
|
|
212
261
|
print(content)
|
|
213
|
-
print(
|
|
262
|
+
print(
|
|
263
|
+
f"\n{YELLOW}Tip: Run '{CYAN}mdan phase {num} copy{YELLOW}' to copy this content to clipboard.{NC}"
|
|
264
|
+
)
|
|
214
265
|
else:
|
|
215
266
|
print(f"Phase file not found: {file}")
|
|
216
267
|
|
|
268
|
+
|
|
217
269
|
def cmd_agent(name):
|
|
218
270
|
file = Path(f"{MDAN_DIR}/agents/{name}.md")
|
|
219
271
|
if file.exists():
|
|
@@ -221,6 +273,7 @@ def cmd_agent(name):
|
|
|
221
273
|
else:
|
|
222
274
|
print("Agents: product, architect, ux, dev, test, security, devops, doc")
|
|
223
275
|
|
|
276
|
+
|
|
224
277
|
def cmd_skills():
|
|
225
278
|
print(f"{CYAN}Skills:{NC}")
|
|
226
279
|
skills_dir = Path(f"{MDAN_DIR}/skills")
|
|
@@ -230,10 +283,11 @@ def cmd_skills():
|
|
|
230
283
|
else:
|
|
231
284
|
print(" No skills installed")
|
|
232
285
|
|
|
286
|
+
|
|
233
287
|
def main():
|
|
234
288
|
args = sys.argv[1:]
|
|
235
289
|
cmd = args[0] if args else "help"
|
|
236
|
-
|
|
290
|
+
|
|
237
291
|
if cmd in ["help", "--help", "-h", None]:
|
|
238
292
|
show_help()
|
|
239
293
|
elif cmd == "init":
|
|
@@ -245,7 +299,9 @@ def main():
|
|
|
245
299
|
elif cmd == "status":
|
|
246
300
|
cmd_status()
|
|
247
301
|
elif cmd == "phase":
|
|
248
|
-
cmd_phase(
|
|
302
|
+
cmd_phase(
|
|
303
|
+
args[1] if len(args) > 1 else None, args[2] if len(args) > 2 else None
|
|
304
|
+
)
|
|
249
305
|
elif cmd == "agent":
|
|
250
306
|
cmd_agent(args[1] if len(args) > 1 else None)
|
|
251
307
|
elif cmd == "skills":
|
|
@@ -255,5 +311,6 @@ def main():
|
|
|
255
311
|
else:
|
|
256
312
|
print(f"Unknown: {cmd}. Run: mdan help")
|
|
257
313
|
|
|
314
|
+
|
|
258
315
|
if __name__ == "__main__":
|
|
259
316
|
main()
|