@beomjk/emdd 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +38 -26
- package/dist/commands/backlog.js +4 -2
- package/dist/commands/update.js +5 -1
- package/dist/graph/operations.d.ts +13 -1
- package/dist/graph/operations.js +54 -20
- package/dist/graph/templates.d.ts +1 -0
- package/dist/graph/templates.js +13 -2
- package/dist/graph/types.d.ts +22 -0
- package/dist/mcp-server/tools/create-node.js +3 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -63,13 +63,13 @@ Hypotheses move through `PROPOSED -> TESTING -> SUPPORTED / REFUTED / REVISED`.
|
|
|
63
63
|
## Installation
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
npm install -g emdd
|
|
66
|
+
npm install -g @beomjk/emdd
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
Or use directly with npx:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
npx emdd <command>
|
|
72
|
+
npx @beomjk/emdd <command>
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
## Quick Start
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,18 @@ import { graphCommand } from './commands/graph.js';
|
|
|
14
14
|
import { backlogCommand } from './commands/backlog.js';
|
|
15
15
|
import { resolveGraphDir } from './graph/loader.js';
|
|
16
16
|
import { startMcpServer } from './mcp-server/index.js';
|
|
17
|
+
function withCliErrorHandling(fn) {
|
|
18
|
+
return async (...args) => {
|
|
19
|
+
try {
|
|
20
|
+
await fn(...args);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24
|
+
console.error(`Error: ${message}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
17
29
|
const program = new Command();
|
|
18
30
|
program
|
|
19
31
|
.name('emdd')
|
|
@@ -24,32 +36,32 @@ program
|
|
|
24
36
|
.description('Initialize EMDD project')
|
|
25
37
|
.option('--lang <locale>', 'Language', 'en')
|
|
26
38
|
.option('--tool <tool>', 'AI tool rules to generate (claude|cursor|windsurf|cline|copilot|all)', 'claude')
|
|
27
|
-
.action((path, options) => {
|
|
39
|
+
.action(withCliErrorHandling(async (path, options) => {
|
|
28
40
|
initCommand(path, options);
|
|
29
|
-
});
|
|
41
|
+
}));
|
|
30
42
|
program
|
|
31
43
|
.command('new <type> <slug>')
|
|
32
44
|
.description('Create a new node')
|
|
33
45
|
.option('--path <path>', 'Project path')
|
|
34
|
-
.action(async (type, slug, options) => {
|
|
46
|
+
.action(withCliErrorHandling(async (type, slug, options) => {
|
|
35
47
|
await newCommand(type, slug, options);
|
|
36
|
-
});
|
|
48
|
+
}));
|
|
37
49
|
program
|
|
38
50
|
.command('lint [path]')
|
|
39
51
|
.description('Validate graph schema and links')
|
|
40
|
-
.action(async (path) => {
|
|
52
|
+
.action(withCliErrorHandling(async (path) => {
|
|
41
53
|
await lintCommand(path);
|
|
42
|
-
});
|
|
54
|
+
}));
|
|
43
55
|
program
|
|
44
56
|
.command('health [path]')
|
|
45
57
|
.description('Show health dashboard')
|
|
46
|
-
.action(async (path) => {
|
|
58
|
+
.action(withCliErrorHandling(async (path) => {
|
|
47
59
|
await healthCommand(path);
|
|
48
|
-
});
|
|
60
|
+
}));
|
|
49
61
|
program
|
|
50
62
|
.command('check [path]')
|
|
51
63
|
.description('Check consolidation triggers')
|
|
52
|
-
.action(async (path) => {
|
|
64
|
+
.action(withCliErrorHandling(async (path) => {
|
|
53
65
|
const graphDir = resolveGraphDir(path);
|
|
54
66
|
const result = await checkCommand(graphDir);
|
|
55
67
|
if (result.triggers.length === 0) {
|
|
@@ -60,11 +72,11 @@ program
|
|
|
60
72
|
console.log(`TRIGGER ${trigger.type} ${trigger.message}`);
|
|
61
73
|
}
|
|
62
74
|
}
|
|
63
|
-
});
|
|
75
|
+
}));
|
|
64
76
|
program
|
|
65
77
|
.command('promote [path]')
|
|
66
78
|
.description('Identify findings eligible for promotion')
|
|
67
|
-
.action(async (path) => {
|
|
79
|
+
.action(withCliErrorHandling(async (path) => {
|
|
68
80
|
const graphDir = resolveGraphDir(path);
|
|
69
81
|
const result = await promoteCommand(graphDir);
|
|
70
82
|
if (result.candidates.length === 0) {
|
|
@@ -75,13 +87,13 @@ program
|
|
|
75
87
|
console.log(`CANDIDATE ${c.id} confidence=${c.confidence} supports=${c.supports}`);
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
|
-
});
|
|
90
|
+
}));
|
|
79
91
|
program
|
|
80
92
|
.command('update <node-id>')
|
|
81
93
|
.description('Update frontmatter fields on a node')
|
|
82
94
|
.option('--set <key=value...>', 'Key-value pairs to set')
|
|
83
95
|
.option('--path <path>', 'Project path')
|
|
84
|
-
.action(async (nodeId, options) => {
|
|
96
|
+
.action(withCliErrorHandling(async (nodeId, options) => {
|
|
85
97
|
const graphDir = resolveGraphDir(options.path);
|
|
86
98
|
const updates = {};
|
|
87
99
|
if (options.set) {
|
|
@@ -97,47 +109,47 @@ program
|
|
|
97
109
|
}
|
|
98
110
|
await updateCommand(graphDir, nodeId, updates);
|
|
99
111
|
console.log(`Updated ${nodeId}`);
|
|
100
|
-
});
|
|
112
|
+
}));
|
|
101
113
|
program
|
|
102
114
|
.command('link <source> <target> <relation>')
|
|
103
115
|
.description('Add a link between nodes')
|
|
104
116
|
.option('--path <path>', 'Project path')
|
|
105
|
-
.action(async (source, target, relation, options) => {
|
|
117
|
+
.action(withCliErrorHandling(async (source, target, relation, options) => {
|
|
106
118
|
const graphDir = resolveGraphDir(options.path);
|
|
107
119
|
await linkCommand(graphDir, source, target, relation);
|
|
108
120
|
console.log(`Linked ${source} -> ${target} (${relation})`);
|
|
109
|
-
});
|
|
121
|
+
}));
|
|
110
122
|
program
|
|
111
123
|
.command('done <episode-id> <item>')
|
|
112
124
|
.description('Mark a checklist item as done')
|
|
113
125
|
.option('--path <path>', 'Project path')
|
|
114
|
-
.action(async (episodeId, item, options) => {
|
|
126
|
+
.action(withCliErrorHandling(async (episodeId, item, options) => {
|
|
115
127
|
const graphDir = resolveGraphDir(options.path);
|
|
116
128
|
await doneCommand(graphDir, episodeId, item);
|
|
117
129
|
console.log(`Done: ${item}`);
|
|
118
|
-
});
|
|
130
|
+
}));
|
|
119
131
|
program
|
|
120
132
|
.command('index [path]')
|
|
121
133
|
.description('Generate _index.md for the graph')
|
|
122
|
-
.action(async (path) => {
|
|
134
|
+
.action(withCliErrorHandling(async (path) => {
|
|
123
135
|
const graphDir = resolveGraphDir(path);
|
|
124
136
|
const result = await indexCommand(graphDir);
|
|
125
137
|
console.log(`Index generated: ${result.nodeCount} nodes`);
|
|
126
|
-
});
|
|
138
|
+
}));
|
|
127
139
|
program
|
|
128
140
|
.command('graph [path]')
|
|
129
141
|
.description('Generate _graph.mmd Mermaid diagram')
|
|
130
|
-
.action(async (path) => {
|
|
142
|
+
.action(withCliErrorHandling(async (path) => {
|
|
131
143
|
const graphDir = resolveGraphDir(path);
|
|
132
144
|
const result = await graphCommand(graphDir);
|
|
133
145
|
console.log(`Graph generated: ${result.nodeCount} nodes, ${result.edgeCount} edges`);
|
|
134
|
-
});
|
|
146
|
+
}));
|
|
135
147
|
program
|
|
136
148
|
.command('backlog')
|
|
137
149
|
.description('Show unchecked backlog items')
|
|
138
150
|
.option('--path <path>', 'Project path')
|
|
139
151
|
.option('--status <status>', 'Filter by status')
|
|
140
|
-
.action(async (options) => {
|
|
152
|
+
.action(withCliErrorHandling(async (options) => {
|
|
141
153
|
const graphDir = resolveGraphDir(options.path);
|
|
142
154
|
const result = await backlogCommand(graphDir, options.status);
|
|
143
155
|
if (result.items.length === 0) {
|
|
@@ -148,11 +160,11 @@ program
|
|
|
148
160
|
console.log(`[ ] ${item.episodeId} ${item.text}`);
|
|
149
161
|
}
|
|
150
162
|
}
|
|
151
|
-
});
|
|
163
|
+
}));
|
|
152
164
|
program
|
|
153
165
|
.command('mcp')
|
|
154
166
|
.description('Start MCP server over stdio')
|
|
155
|
-
.action(async () => {
|
|
167
|
+
.action(withCliErrorHandling(async () => {
|
|
156
168
|
await startMcpServer();
|
|
157
|
-
});
|
|
169
|
+
}));
|
|
158
170
|
program.parse();
|
package/dist/commands/backlog.js
CHANGED
|
@@ -12,14 +12,16 @@ export async function backlogCommand(graphDir, _statusFilter) {
|
|
|
12
12
|
try {
|
|
13
13
|
content = readFileSync(file, 'utf-8');
|
|
14
14
|
}
|
|
15
|
-
catch {
|
|
15
|
+
catch (err) {
|
|
16
|
+
console.warn(`Warning: Could not read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16
17
|
continue;
|
|
17
18
|
}
|
|
18
19
|
let parsed;
|
|
19
20
|
try {
|
|
20
21
|
parsed = matter(content);
|
|
21
22
|
}
|
|
22
|
-
catch {
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.warn(`Warning: Could not parse ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
23
25
|
continue;
|
|
24
26
|
}
|
|
25
27
|
const episodeId = parsed.data?.id ?? '';
|
package/dist/commands/update.js
CHANGED
|
@@ -19,7 +19,11 @@ export async function updateCommand(graphDir, nodeId, updates) {
|
|
|
19
19
|
// Apply updates
|
|
20
20
|
for (const [key, value] of Object.entries(updates)) {
|
|
21
21
|
if (key === 'confidence') {
|
|
22
|
-
|
|
22
|
+
const num = parseFloat(value);
|
|
23
|
+
if (isNaN(num) || num < 0 || num > 1) {
|
|
24
|
+
throw new Error(`Invalid confidence value: "${value}" (must be 0-1)`);
|
|
25
|
+
}
|
|
26
|
+
parsed.data[key] = num;
|
|
23
27
|
}
|
|
24
28
|
else {
|
|
25
29
|
parsed.data[key] = value;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Node, NodeFilter, NodeDetail, CreateNodeResult, CreateEdgeResult, HealthReport, CheckResult, PromoteCandidate } from './types.js';
|
|
1
|
+
import type { Node, NodeFilter, NodeDetail, CreateNodeResult, CreateEdgeResult, CreateNodePlan, CreateEdgePlan, FileOp, HealthReport, CheckResult, PromoteCandidate } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* List all nodes in the graph, optionally filtered by type and/or status.
|
|
4
4
|
*/
|
|
@@ -8,11 +8,23 @@ export declare function listNodes(graphDir: string, filter?: NodeFilter): Promis
|
|
|
8
8
|
* Returns null if the node is not found.
|
|
9
9
|
*/
|
|
10
10
|
export declare function readNode(graphDir: string, nodeId: string): Promise<NodeDetail | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Execute a list of file operations (mkdir / write).
|
|
13
|
+
*/
|
|
14
|
+
export declare function executeOps(ops: FileOp[]): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Plan the creation of a new node (pure computation, no I/O).
|
|
17
|
+
*/
|
|
18
|
+
export declare function planCreateNode(graphDir: string, type: string, slug: string, lang?: string): CreateNodePlan;
|
|
11
19
|
/**
|
|
12
20
|
* Create a new node of the given type with the given slug.
|
|
13
21
|
* Returns the created node's ID, type, and file path.
|
|
14
22
|
*/
|
|
15
23
|
export declare function createNode(graphDir: string, type: string, slug: string, lang?: string): Promise<CreateNodeResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Plan the creation of an edge (pure computation after graph load).
|
|
26
|
+
*/
|
|
27
|
+
export declare function planCreateEdge(graphDir: string, source: string, target: string, relation: string): Promise<CreateEdgePlan>;
|
|
16
28
|
/**
|
|
17
29
|
* Add an edge (link) from source to target with the given relation.
|
|
18
30
|
* Validates relation, source existence, and target existence.
|
package/dist/graph/operations.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import matter from 'gray-matter';
|
|
3
4
|
import { loadGraph } from './loader.js';
|
|
4
|
-
import { nextId, renderTemplate, nodePath } from './templates.js';
|
|
5
|
+
import { nextId, renderTemplate, nodePath, sanitizeSlug } from './templates.js';
|
|
5
6
|
import { NODE_TYPES, ALL_VALID_RELATIONS, REVERSE_LABELS } from './types.js';
|
|
6
7
|
// ── listNodes ───────────────────────────────────────────────────────
|
|
7
8
|
/**
|
|
@@ -36,36 +37,61 @@ export async function readNode(graphDir, nodeId) {
|
|
|
36
37
|
body: parsed.content,
|
|
37
38
|
};
|
|
38
39
|
}
|
|
40
|
+
// ── executeOps ──────────────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Execute a list of file operations (mkdir / write).
|
|
43
|
+
*/
|
|
44
|
+
export async function executeOps(ops) {
|
|
45
|
+
for (const op of ops) {
|
|
46
|
+
switch (op.kind) {
|
|
47
|
+
case 'mkdir':
|
|
48
|
+
if (!fs.existsSync(op.path)) {
|
|
49
|
+
fs.mkdirSync(op.path, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case 'write':
|
|
53
|
+
fs.writeFileSync(op.path, op.content, 'utf-8');
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
39
58
|
// ── createNode ──────────────────────────────────────────────────────
|
|
40
59
|
/**
|
|
41
|
-
*
|
|
42
|
-
* Returns the created node's ID, type, and file path.
|
|
60
|
+
* Plan the creation of a new node (pure computation, no I/O).
|
|
43
61
|
*/
|
|
44
|
-
export
|
|
62
|
+
export function planCreateNode(graphDir, type, slug, lang) {
|
|
45
63
|
if (!NODE_TYPES.includes(type)) {
|
|
46
64
|
throw new Error(`Invalid node type: ${type}. Valid types: ${NODE_TYPES.join(', ')}`);
|
|
47
65
|
}
|
|
48
66
|
const nodeType = type;
|
|
49
67
|
const id = nextId(graphDir, nodeType);
|
|
50
|
-
const
|
|
68
|
+
const sanitized = sanitizeSlug(slug);
|
|
69
|
+
const content = renderTemplate(nodeType, sanitized, {
|
|
51
70
|
id,
|
|
52
71
|
locale: lang ?? 'en',
|
|
53
72
|
});
|
|
54
|
-
const filePath = nodePath(graphDir, nodeType, id,
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
const filePath = nodePath(graphDir, nodeType, id, sanitized);
|
|
74
|
+
const dir = path.dirname(filePath);
|
|
75
|
+
const ops = [
|
|
76
|
+
{ kind: 'mkdir', path: dir },
|
|
77
|
+
{ kind: 'write', path: filePath, content },
|
|
78
|
+
];
|
|
79
|
+
return { id, type: nodeType, path: filePath, ops };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a new node of the given type with the given slug.
|
|
83
|
+
* Returns the created node's ID, type, and file path.
|
|
84
|
+
*/
|
|
85
|
+
export async function createNode(graphDir, type, slug, lang) {
|
|
86
|
+
const plan = planCreateNode(graphDir, type, slug, lang);
|
|
87
|
+
await executeOps(plan.ops);
|
|
88
|
+
return { id: plan.id, type: plan.type, path: plan.path };
|
|
62
89
|
}
|
|
63
90
|
// ── createEdge ──────────────────────────────────────────────────────
|
|
64
91
|
/**
|
|
65
|
-
*
|
|
66
|
-
* Validates relation, source existence, and target existence.
|
|
92
|
+
* Plan the creation of an edge (pure computation after graph load).
|
|
67
93
|
*/
|
|
68
|
-
export async function
|
|
94
|
+
export async function planCreateEdge(graphDir, source, target, relation) {
|
|
69
95
|
// Validate relation
|
|
70
96
|
if (!ALL_VALID_RELATIONS.has(relation)) {
|
|
71
97
|
const valid = [...ALL_VALID_RELATIONS].sort().join(', ');
|
|
@@ -78,7 +104,6 @@ export async function createEdge(graphDir, source, target, relation) {
|
|
|
78
104
|
if (!sourceNode) {
|
|
79
105
|
throw new Error(`Source node not found: ${source}`);
|
|
80
106
|
}
|
|
81
|
-
// Validate target exists (new behavior not in original link.ts)
|
|
82
107
|
const targetNode = graph.nodes.get(target);
|
|
83
108
|
if (!targetNode) {
|
|
84
109
|
throw new Error(`Target node not found: ${target}`);
|
|
@@ -94,10 +119,19 @@ export async function createEdge(graphDir, source, target, relation) {
|
|
|
94
119
|
parsed.data.links.push({ target, relation: canonical });
|
|
95
120
|
// Auto-update the `updated` field
|
|
96
121
|
parsed.data.updated = new Date().toISOString().slice(0, 10);
|
|
97
|
-
//
|
|
122
|
+
// Compute new file content
|
|
98
123
|
const output = matter.stringify(parsed.content, parsed.data);
|
|
99
|
-
|
|
100
|
-
return { source, target, relation: canonical };
|
|
124
|
+
const ops = [{ kind: 'write', path: filePath, content: output }];
|
|
125
|
+
return { source, target, relation: canonical, ops };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Add an edge (link) from source to target with the given relation.
|
|
129
|
+
* Validates relation, source existence, and target existence.
|
|
130
|
+
*/
|
|
131
|
+
export async function createEdge(graphDir, source, target, relation) {
|
|
132
|
+
const plan = await planCreateEdge(graphDir, source, target, relation);
|
|
133
|
+
await executeOps(plan.ops);
|
|
134
|
+
return { source: plan.source, target: plan.target, relation: plan.relation };
|
|
101
135
|
}
|
|
102
136
|
// ── getHealth ───────────────────────────────────────────────────────
|
|
103
137
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NodeType } from './types.js';
|
|
2
2
|
import type { Locale } from '../i18n/index.js';
|
|
3
|
+
export declare function sanitizeSlug(slug: string): string;
|
|
3
4
|
export declare function renderTemplate(type: NodeType, slug: string, options?: {
|
|
4
5
|
locale?: Locale;
|
|
5
6
|
user?: string;
|
package/dist/graph/templates.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { NODE_TYPE_DIRS, ID_PREFIXES, VALID_STATUSES } from './types.js';
|
|
4
|
+
// ── sanitizeSlug ──────────────────────────────────────────────────
|
|
5
|
+
export function sanitizeSlug(slug) {
|
|
6
|
+
let s = slug.replace(/[\/\\\.]+/g, '-');
|
|
7
|
+
s = s.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
8
|
+
s = s.replace(/-{2,}/g, '-');
|
|
9
|
+
s = s.replace(/^-+|-+$/g, '');
|
|
10
|
+
s = s.slice(0, 80);
|
|
11
|
+
if (s.length === 0)
|
|
12
|
+
throw new Error('Slug is empty after sanitization');
|
|
13
|
+
return s;
|
|
14
|
+
}
|
|
4
15
|
// ── Body templates per type and locale ─────────────────────────────
|
|
5
16
|
const BODY_TEMPLATES = {
|
|
6
17
|
en: {
|
|
@@ -57,7 +68,7 @@ export function renderTemplate(type, slug, options) {
|
|
|
57
68
|
if (data.id)
|
|
58
69
|
lines.push(`id: ${data.id}`);
|
|
59
70
|
lines.push(`type: ${data.type}`);
|
|
60
|
-
lines.push(`title: "${data.title}"`);
|
|
71
|
+
lines.push(`title: "${String(data.title).replace(/"/g, '\\"')}"`);
|
|
61
72
|
lines.push(`status: ${data.status}`);
|
|
62
73
|
if (confidence !== undefined) {
|
|
63
74
|
lines.push(`confidence: ${data.confidence}`);
|
|
@@ -98,5 +109,5 @@ export function nextId(graphDir, type) {
|
|
|
98
109
|
// ── nodePath ───────────────────────────────────────────────────────
|
|
99
110
|
export function nodePath(graphDir, type, id, slug) {
|
|
100
111
|
const typeDir = NODE_TYPE_DIRS[type];
|
|
101
|
-
return path.join(graphDir, typeDir, `${id}-${slug}.md`);
|
|
112
|
+
return path.join(graphDir, typeDir, `${id}-${sanitizeSlug(slug)}.md`);
|
|
102
113
|
}
|
package/dist/graph/types.d.ts
CHANGED
|
@@ -69,3 +69,25 @@ export interface PromoteCandidate {
|
|
|
69
69
|
confidence: number;
|
|
70
70
|
supports: number;
|
|
71
71
|
}
|
|
72
|
+
export interface WriteFileOp {
|
|
73
|
+
kind: 'write';
|
|
74
|
+
path: string;
|
|
75
|
+
content: string;
|
|
76
|
+
}
|
|
77
|
+
export interface MkdirOp {
|
|
78
|
+
kind: 'mkdir';
|
|
79
|
+
path: string;
|
|
80
|
+
}
|
|
81
|
+
export type FileOp = WriteFileOp | MkdirOp;
|
|
82
|
+
export interface CreateNodePlan {
|
|
83
|
+
id: string;
|
|
84
|
+
type: NodeType;
|
|
85
|
+
path: string;
|
|
86
|
+
ops: FileOp[];
|
|
87
|
+
}
|
|
88
|
+
export interface CreateEdgePlan {
|
|
89
|
+
source: string;
|
|
90
|
+
target: string;
|
|
91
|
+
relation: string;
|
|
92
|
+
ops: FileOp[];
|
|
93
|
+
}
|
|
@@ -5,7 +5,9 @@ export function registerCreateNode(server) {
|
|
|
5
5
|
server.tool('create-node', 'Create a new node of the given type with the given slug', {
|
|
6
6
|
graphDir: z.string().describe('Path to the EMDD graph directory'),
|
|
7
7
|
type: z.string().describe('Node type (hypothesis, experiment, finding, knowledge, question, decision, episode)'),
|
|
8
|
-
slug: z.string().
|
|
8
|
+
slug: z.string().min(1).max(80)
|
|
9
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, 'Slug must be alphanumeric with hyphens/underscores')
|
|
10
|
+
.describe('URL-friendly slug for the node'),
|
|
9
11
|
lang: z.string().optional().describe('Language locale (default: en)'),
|
|
10
12
|
}, async ({ graphDir, type, slug, lang }) => withErrorHandling(async () => {
|
|
11
13
|
const result = await createNode(graphDir, type, slug, lang);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beomjk/emdd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI for Evolving Mindmap-Driven Development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
],
|
|
15
15
|
"main": "./dist/index.js",
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "tsc",
|
|
17
|
+
"build": "tsc && cp src/rules/*.md dist/rules/",
|
|
18
18
|
"dev": "tsx src/cli.ts",
|
|
19
19
|
"test": "vitest",
|
|
20
20
|
"test:run": "vitest run --passWithNoTests",
|
|
21
21
|
"lint": "tsc --noEmit",
|
|
22
|
-
"demo:record": "terminal-demo play docs/assets/demo.md --record docs/assets/demo.cast && svg-term --in docs/assets/demo.cast --out docs/assets/demo.svg --window --width 80 --height 24"
|
|
22
|
+
"demo:record": "npx terminal-demo play docs/assets/demo.md --record docs/assets/demo.cast && npx svg-term --in docs/assets/demo.cast --out docs/assets/demo.svg --window --width 80 --height 24"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"emdd",
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
"cli"
|
|
30
30
|
],
|
|
31
31
|
"license": "MIT",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
32
35
|
"dependencies": {
|
|
33
36
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
34
37
|
"chalk": "^5.6.2",
|
|
@@ -41,9 +44,6 @@
|
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"@types/node": "^25.5.0",
|
|
44
|
-
"react": "^16.14.0",
|
|
45
|
-
"svg-term-cli": "^2.1.1",
|
|
46
|
-
"terminal-demo": "^0.1.11",
|
|
47
47
|
"tsx": "^4.21.0",
|
|
48
48
|
"typescript": "^5.9.3",
|
|
49
49
|
"vitest": "^4.1.0"
|