@beomjk/emdd 0.1.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/LICENSE +21 -0
- package/README.md +153 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +158 -0
- package/dist/commands/backlog.d.ts +8 -0
- package/dist/commands/backlog.js +35 -0
- package/dist/commands/check.d.ts +6 -0
- package/dist/commands/check.js +8 -0
- package/dist/commands/done.d.ts +7 -0
- package/dist/commands/done.js +34 -0
- package/dist/commands/graph.d.ts +5 -0
- package/dist/commands/graph.js +18 -0
- package/dist/commands/health.d.ts +1 -0
- package/dist/commands/health.js +36 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +10 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +40 -0
- package/dist/commands/link.d.ts +5 -0
- package/dist/commands/link.js +8 -0
- package/dist/commands/lint.d.ts +1 -0
- package/dist/commands/lint.js +27 -0
- package/dist/commands/new.d.ts +3 -0
- package/dist/commands/new.js +14 -0
- package/dist/commands/promote.d.ts +9 -0
- package/dist/commands/promote.js +9 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +33 -0
- package/dist/graph/index-generator.d.ts +2 -0
- package/dist/graph/index-generator.js +57 -0
- package/dist/graph/loader.d.ts +15 -0
- package/dist/graph/loader.js +112 -0
- package/dist/graph/mermaid.d.ts +2 -0
- package/dist/graph/mermaid.js +35 -0
- package/dist/graph/operations.d.ts +34 -0
- package/dist/graph/operations.js +265 -0
- package/dist/graph/templates.d.ts +9 -0
- package/dist/graph/templates.js +102 -0
- package/dist/graph/types.d.ts +71 -0
- package/dist/graph/types.js +70 -0
- package/dist/graph/validator.d.ts +16 -0
- package/dist/graph/validator.js +120 -0
- package/dist/i18n/en.d.ts +1 -0
- package/dist/i18n/en.js +72 -0
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.js +26 -0
- package/dist/i18n/ko.d.ts +1 -0
- package/dist/i18n/ko.js +72 -0
- package/dist/mcp-server/cli.d.ts +2 -0
- package/dist/mcp-server/cli.js +6 -0
- package/dist/mcp-server/index.d.ts +3 -0
- package/dist/mcp-server/index.js +35 -0
- package/dist/mcp-server/prompts/consolidation.d.ts +2 -0
- package/dist/mcp-server/prompts/consolidation.js +64 -0
- package/dist/mcp-server/prompts/context-loading.d.ts +2 -0
- package/dist/mcp-server/prompts/context-loading.js +52 -0
- package/dist/mcp-server/prompts/episode-creation.d.ts +2 -0
- package/dist/mcp-server/prompts/episode-creation.js +75 -0
- package/dist/mcp-server/prompts/health-review.d.ts +2 -0
- package/dist/mcp-server/prompts/health-review.js +76 -0
- package/dist/mcp-server/tools/check.d.ts +2 -0
- package/dist/mcp-server/tools/check.js +11 -0
- package/dist/mcp-server/tools/create-edge.d.ts +2 -0
- package/dist/mcp-server/tools/create-edge.js +14 -0
- package/dist/mcp-server/tools/create-node.d.ts +2 -0
- package/dist/mcp-server/tools/create-node.js +14 -0
- package/dist/mcp-server/tools/health.d.ts +2 -0
- package/dist/mcp-server/tools/health.js +11 -0
- package/dist/mcp-server/tools/list-nodes.d.ts +2 -0
- package/dist/mcp-server/tools/list-nodes.js +18 -0
- package/dist/mcp-server/tools/promote.d.ts +2 -0
- package/dist/mcp-server/tools/promote.js +11 -0
- package/dist/mcp-server/tools/read-node.d.ts +2 -0
- package/dist/mcp-server/tools/read-node.js +15 -0
- package/dist/mcp-server/tools/util.d.ts +7 -0
- package/dist/mcp-server/tools/util.js +22 -0
- package/dist/rules/emdd-agent.md +40 -0
- package/dist/rules/emdd-rules.md +99 -0
- package/dist/rules/generators.d.ts +18 -0
- package/dist/rules/generators.js +104 -0
- package/package.json +51 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export function registerEpisodeCreation(server) {
|
|
2
|
+
server.prompt('episode-creation', 'Step-by-step guide for writing an EMDD Episode node — includes frontmatter template, mandatory sections, and linking instructions', async () => {
|
|
3
|
+
const text = `# EMDD Episode Creation Guide
|
|
4
|
+
|
|
5
|
+
An Episode is a session log that records what you tried, what you learned, and where to go next. It is the backbone of the EMDD temporal chain — each Episode curates the context for the next session.
|
|
6
|
+
|
|
7
|
+
## Step-by-Step Checklist
|
|
8
|
+
|
|
9
|
+
1. **Determine the Episode ID**: Use the next sequential ID (e.g., epi-004 if epi-003 exists).
|
|
10
|
+
2. **Write the frontmatter** (see template below).
|
|
11
|
+
3. **Fill in mandatory sections**:
|
|
12
|
+
- "What I Tried" — summarize the work done this session.
|
|
13
|
+
- "What's Next" — list planned next steps with prerequisite reading nodes.
|
|
14
|
+
4. **Fill in optional sections** (include when relevant):
|
|
15
|
+
- "What Got Stuck" — blockers, wrong turns, or dead ends.
|
|
16
|
+
- "What Was Deliberately Not Done" — choices to defer or skip, with reasons.
|
|
17
|
+
- "Questions That Arose" — new questions to convert to Question nodes at consolidation.
|
|
18
|
+
5. **Add links** to all nodes created, updated, or referenced during the session.
|
|
19
|
+
6. **Verify prerequisite reading** under each "What's Next" item — this is critical for future context loading.
|
|
20
|
+
|
|
21
|
+
## Frontmatter Template
|
|
22
|
+
|
|
23
|
+
\`\`\`yaml
|
|
24
|
+
---
|
|
25
|
+
id: epi-XXX
|
|
26
|
+
type: episode
|
|
27
|
+
status: COMPLETED
|
|
28
|
+
trigger: "Description of what prompted this session"
|
|
29
|
+
created: YYYY-MM-DD
|
|
30
|
+
updated: YYYY-MM-DD
|
|
31
|
+
duration: ~Xm
|
|
32
|
+
outcome: success | partial | blocked
|
|
33
|
+
created_by: human:yourname
|
|
34
|
+
tags: [relevant, tags]
|
|
35
|
+
links:
|
|
36
|
+
- target: node-id
|
|
37
|
+
relation: produces
|
|
38
|
+
---
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
## Linking Instructions
|
|
42
|
+
|
|
43
|
+
- Use \`produces\` for nodes created during this session.
|
|
44
|
+
- Use \`relates_to\` for nodes referenced or updated.
|
|
45
|
+
- Use \`context_for\` for nodes that provided background context.
|
|
46
|
+
- Every Episode should link to at least one other node.
|
|
47
|
+
|
|
48
|
+
## Body Template
|
|
49
|
+
|
|
50
|
+
\`\`\`markdown
|
|
51
|
+
# EPI-XXX: Session Title
|
|
52
|
+
|
|
53
|
+
## What I Tried
|
|
54
|
+
- [Summary of work done]
|
|
55
|
+
|
|
56
|
+
## What Got Stuck
|
|
57
|
+
- [Blockers or wrong turns — omit if nothing got stuck]
|
|
58
|
+
|
|
59
|
+
## What's Next
|
|
60
|
+
- [ ] Next step description
|
|
61
|
+
- Prerequisite reading: node-id-1, node-id-2
|
|
62
|
+
- [ ] Another next step
|
|
63
|
+
- Prerequisite reading: node-id-3
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
## Principles
|
|
67
|
+
- Keep Episodes short — "What I Tried" and "What's Next" are the only mandatory sections.
|
|
68
|
+
- A five-line Episode is better than no Episode.
|
|
69
|
+
- Do not record Consolidation as an Episode — Consolidation is a meta-activity.
|
|
70
|
+
- Write the Episode at the end of each session, not days later.`;
|
|
71
|
+
return {
|
|
72
|
+
messages: [{ role: 'user', content: { type: 'text', text } }],
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getHealth } from '../../graph/operations.js';
|
|
3
|
+
export function registerHealthReview(server) {
|
|
4
|
+
server.prompt('health-review', 'Full health dashboard with actionable recommendations — analyzes node distribution, structural gaps, and link density', { path: z.string().describe('Path to the EMDD graph directory') }, async ({ path: graphDir }) => {
|
|
5
|
+
const health = await getHealth(graphDir);
|
|
6
|
+
const typeBreakdown = Object.entries(health.byType)
|
|
7
|
+
.map(([type, count]) => ` - ${type}: ${count}`)
|
|
8
|
+
.join('\n');
|
|
9
|
+
const statusSection = Object.entries(health.statusDistribution)
|
|
10
|
+
.map(([type, statuses]) => {
|
|
11
|
+
const statusList = Object.entries(statuses)
|
|
12
|
+
.map(([s, c]) => `${s}: ${c}`)
|
|
13
|
+
.join(', ');
|
|
14
|
+
return ` - ${type}: ${statusList}`;
|
|
15
|
+
})
|
|
16
|
+
.join('\n');
|
|
17
|
+
const gapsSection = health.gaps.length > 0
|
|
18
|
+
? health.gaps.map(g => ` - ${g}`).join('\n')
|
|
19
|
+
: ' None detected';
|
|
20
|
+
// Generate recommendations based on health data
|
|
21
|
+
const recommendations = [];
|
|
22
|
+
if (health.gaps.length > 0) {
|
|
23
|
+
for (const gap of health.gaps) {
|
|
24
|
+
recommendations.push(`[ACTION] Address structural gap: ${gap}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (health.linkDensity < 1.0 && health.totalNodes > 0) {
|
|
28
|
+
recommendations.push(`[ACTION] Low link density (${health.linkDensity.toFixed(2)}). Many nodes lack connections. Run orphan cleanup to add missing edges.`);
|
|
29
|
+
}
|
|
30
|
+
if (health.openQuestions === 0 && health.totalNodes > 0) {
|
|
31
|
+
recommendations.push('[ACTION] No open questions. This may indicate premature convergence. Consider generating new questions during consolidation.');
|
|
32
|
+
}
|
|
33
|
+
if (health.avgConfidence !== null && health.avgConfidence < 0.5) {
|
|
34
|
+
recommendations.push(`[ACTION] Low average confidence (${health.avgConfidence.toFixed(2)}). Prioritize experiments that test high-risk hypotheses.`);
|
|
35
|
+
}
|
|
36
|
+
if ((health.byType['finding'] ?? 0) >= 5 && (health.byType['knowledge'] ?? 0) === 0) {
|
|
37
|
+
recommendations.push('[ACTION] Multiple findings but no knowledge nodes. Run a consolidation to promote validated findings.');
|
|
38
|
+
}
|
|
39
|
+
if ((health.byType['episode'] ?? 0) === 0 && health.totalNodes > 0) {
|
|
40
|
+
recommendations.push('[ACTION] No episode nodes. Write an episode to record your session and maintain the temporal chain.');
|
|
41
|
+
}
|
|
42
|
+
if (recommendations.length === 0) {
|
|
43
|
+
recommendations.push('Graph health looks good. Continue with the current research loop.');
|
|
44
|
+
}
|
|
45
|
+
const recommendationsSection = recommendations.map(r => ` - ${r}`).join('\n');
|
|
46
|
+
const text = `# EMDD Health Review
|
|
47
|
+
|
|
48
|
+
## Summary
|
|
49
|
+
- Total nodes: ${health.totalNodes}
|
|
50
|
+
- Total edges: ${health.totalEdges}
|
|
51
|
+
- Link density: ${health.linkDensity.toFixed(2)} edges/node
|
|
52
|
+
- Average confidence: ${health.avgConfidence !== null ? health.avgConfidence.toFixed(2) : 'N/A'}
|
|
53
|
+
- Open questions: ${health.openQuestions}
|
|
54
|
+
|
|
55
|
+
## Node Distribution by Type
|
|
56
|
+
${typeBreakdown}
|
|
57
|
+
|
|
58
|
+
## Status Distribution
|
|
59
|
+
${statusSection}
|
|
60
|
+
|
|
61
|
+
## Structural Gaps
|
|
62
|
+
${gapsSection}
|
|
63
|
+
|
|
64
|
+
## Recommendations
|
|
65
|
+
${recommendationsSection}
|
|
66
|
+
|
|
67
|
+
## Next Steps
|
|
68
|
+
1. Address any [ACTION] items in the recommendations above.
|
|
69
|
+
2. If consolidation triggers are active, run the \`consolidation\` prompt for a guided procedure.
|
|
70
|
+
3. Use the \`check\` tool to verify consolidation trigger status.
|
|
71
|
+
4. Schedule a Weekly Graph Review if one has not been done in the past 7 days.`;
|
|
72
|
+
return {
|
|
73
|
+
messages: [{ role: 'user', content: { type: 'text', text } }],
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { checkConsolidation } from '../../graph/operations.js';
|
|
3
|
+
import { jsonResult } from './util.js';
|
|
4
|
+
export function registerCheck(server) {
|
|
5
|
+
server.tool('check', 'Check consolidation triggers in the EMDD graph', {
|
|
6
|
+
graphDir: z.string().describe('Path to the EMDD graph directory'),
|
|
7
|
+
}, async ({ graphDir }) => {
|
|
8
|
+
const result = await checkConsolidation(graphDir);
|
|
9
|
+
return jsonResult(result);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createEdge } from '../../graph/operations.js';
|
|
3
|
+
import { jsonResult, withErrorHandling } from './util.js';
|
|
4
|
+
export function registerCreateEdge(server) {
|
|
5
|
+
server.tool('create-edge', 'Add an edge (link) from source to target with the given relation', {
|
|
6
|
+
graphDir: z.string().describe('Path to the EMDD graph directory'),
|
|
7
|
+
source: z.string().describe('Source node ID'),
|
|
8
|
+
target: z.string().describe('Target node ID'),
|
|
9
|
+
relation: z.string().describe('Relation type (supports, contradicts, spawns, etc.)'),
|
|
10
|
+
}, async ({ graphDir, source, target, relation }) => withErrorHandling(async () => {
|
|
11
|
+
const result = await createEdge(graphDir, source, target, relation);
|
|
12
|
+
return jsonResult(result);
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createNode } from '../../graph/operations.js';
|
|
3
|
+
import { jsonResult, withErrorHandling } from './util.js';
|
|
4
|
+
export function registerCreateNode(server) {
|
|
5
|
+
server.tool('create-node', 'Create a new node of the given type with the given slug', {
|
|
6
|
+
graphDir: z.string().describe('Path to the EMDD graph directory'),
|
|
7
|
+
type: z.string().describe('Node type (hypothesis, experiment, finding, knowledge, question, decision, episode)'),
|
|
8
|
+
slug: z.string().describe('URL-friendly slug for the node'),
|
|
9
|
+
lang: z.string().optional().describe('Language locale (default: en)'),
|
|
10
|
+
}, async ({ graphDir, type, slug, lang }) => withErrorHandling(async () => {
|
|
11
|
+
const result = await createNode(graphDir, type, slug, lang);
|
|
12
|
+
return jsonResult(result);
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getHealth } from '../../graph/operations.js';
|
|
3
|
+
import { jsonResult } from './util.js';
|
|
4
|
+
export function registerHealth(server) {
|
|
5
|
+
server.tool('health', 'Compute a health report for the EMDD graph', {
|
|
6
|
+
graphDir: z.string().describe('Path to the EMDD graph directory'),
|
|
7
|
+
}, async ({ graphDir }) => {
|
|
8
|
+
const report = await getHealth(graphDir);
|
|
9
|
+
return jsonResult(report);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listNodes } from '../../graph/operations.js';
|
|
3
|
+
import { jsonResult } from './util.js';
|
|
4
|
+
export function registerListNodes(server) {
|
|
5
|
+
server.tool('list-nodes', 'List all nodes in the EMDD graph, optionally filtered by type and/or status', {
|
|
6
|
+
graphDir: z.string().describe('Path to the EMDD graph directory'),
|
|
7
|
+
type: z.string().optional().describe('Filter by node type (hypothesis, experiment, finding, etc.)'),
|
|
8
|
+
status: z.string().optional().describe('Filter by status'),
|
|
9
|
+
}, async ({ graphDir, type, status }) => {
|
|
10
|
+
const filter = {};
|
|
11
|
+
if (type)
|
|
12
|
+
filter.type = type;
|
|
13
|
+
if (status)
|
|
14
|
+
filter.status = status;
|
|
15
|
+
const nodes = await listNodes(graphDir, filter);
|
|
16
|
+
return jsonResult(nodes);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getPromotionCandidates } from '../../graph/operations.js';
|
|
3
|
+
import { jsonResult } from './util.js';
|
|
4
|
+
export function registerPromote(server) {
|
|
5
|
+
server.tool('promote', 'Identify findings eligible for promotion to knowledge', {
|
|
6
|
+
graphDir: z.string().describe('Path to the EMDD graph directory'),
|
|
7
|
+
}, async ({ graphDir }) => {
|
|
8
|
+
const candidates = await getPromotionCandidates(graphDir);
|
|
9
|
+
return jsonResult(candidates);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readNode } from '../../graph/operations.js';
|
|
3
|
+
import { jsonResult, errorResult } from './util.js';
|
|
4
|
+
export function registerReadNode(server) {
|
|
5
|
+
server.tool('read-node', 'Read a single node by ID, returning full detail including body text', {
|
|
6
|
+
graphDir: z.string().describe('Path to the EMDD graph directory'),
|
|
7
|
+
nodeId: z.string().describe('Node ID (e.g., hyp-001)'),
|
|
8
|
+
}, async ({ graphDir, nodeId }) => {
|
|
9
|
+
const detail = await readNode(graphDir, nodeId);
|
|
10
|
+
if (!detail) {
|
|
11
|
+
return errorResult(`Node not found: ${nodeId}`);
|
|
12
|
+
}
|
|
13
|
+
return jsonResult(detail);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
/** Wrap a result value as MCP text content */
|
|
3
|
+
export declare function jsonResult(data: unknown): CallToolResult;
|
|
4
|
+
/** Wrap an error message as MCP error content */
|
|
5
|
+
export declare function errorResult(message: string): CallToolResult;
|
|
6
|
+
/** Execute an async operation, catching errors and returning MCP error content */
|
|
7
|
+
export declare function withErrorHandling(fn: () => Promise<CallToolResult>): Promise<CallToolResult>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Wrap a result value as MCP text content */
|
|
2
|
+
export function jsonResult(data) {
|
|
3
|
+
return {
|
|
4
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
/** Wrap an error message as MCP error content */
|
|
8
|
+
export function errorResult(message) {
|
|
9
|
+
return {
|
|
10
|
+
isError: true,
|
|
11
|
+
content: [{ type: 'text', text: message }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Execute an async operation, catching errors and returning MCP error content */
|
|
15
|
+
export async function withErrorHandling(fn) {
|
|
16
|
+
try {
|
|
17
|
+
return await fn();
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
return errorResult(err.message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# EMDD Agent Behavior Guidelines
|
|
2
|
+
|
|
3
|
+
## Session Workflow
|
|
4
|
+
|
|
5
|
+
1. **Session Start**: Read the latest Episode's "What's Next" section. Load prerequisite nodes.
|
|
6
|
+
2. **During Work**: Execute experiments, write code, take notes. Mark surprises with [!].
|
|
7
|
+
3. **Session End**: Write a new Episode. Record what was tried, create Findings, list next steps.
|
|
8
|
+
|
|
9
|
+
## Intervention Rules
|
|
10
|
+
|
|
11
|
+
- During deep work: do not interrupt unless a kill criterion is hit or a crash occurs
|
|
12
|
+
- Suggestions are a menu, not an order — the researcher selects what to pursue
|
|
13
|
+
- After a suggestion is rejected: 24-hour cooldown before re-suggesting
|
|
14
|
+
- Never negate an idea still forming in early exploration stages
|
|
15
|
+
|
|
16
|
+
## Authority Scope
|
|
17
|
+
|
|
18
|
+
**No approval needed:**
|
|
19
|
+
- Recording experiment metrics as Finding/Result nodes
|
|
20
|
+
- Updating Experiment status (PLANNED -> RUNNING -> COMPLETED)
|
|
21
|
+
- Time-based attribute updates (updated field)
|
|
22
|
+
|
|
23
|
+
**Approval required:**
|
|
24
|
+
- Changing Hypothesis confidence
|
|
25
|
+
- Creating new Hypothesis or Question nodes
|
|
26
|
+
- Adding or deleting edges
|
|
27
|
+
- Changing Knowledge status (DISPUTED/RETRACTED)
|
|
28
|
+
|
|
29
|
+
**Forbidden:**
|
|
30
|
+
- Deleting any node (archive/deprecate instead)
|
|
31
|
+
- Creating Decision nodes
|
|
32
|
+
- Modifying kill criteria
|
|
33
|
+
|
|
34
|
+
## Graph Maintenance Tasks
|
|
35
|
+
|
|
36
|
+
- After experiments: update related node statuses and confidence scores (with approval)
|
|
37
|
+
- Check Consolidation triggers after creating Episodes or Findings
|
|
38
|
+
- Identify orphan nodes (nodes with no outgoing links)
|
|
39
|
+
- Detect stale nodes (untested hypotheses older than 3 days)
|
|
40
|
+
- Flag structural gaps between clusters
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# EMDD — Evolving Mindmap-Driven Development
|
|
2
|
+
|
|
3
|
+
You are working in a project that uses the EMDD methodology. EMDD organizes research and exploration as a knowledge graph stored in `graph/` with Markdown + YAML frontmatter files, tracked by Git.
|
|
4
|
+
|
|
5
|
+
## Graph Structure
|
|
6
|
+
|
|
7
|
+
The graph contains 7 node types, each in its own subdirectory:
|
|
8
|
+
|
|
9
|
+
| Node Type | Directory | Purpose |
|
|
10
|
+
|-----------|-----------|---------|
|
|
11
|
+
| Hypothesis | `graph/hypotheses/` | Testable claims with confidence scores |
|
|
12
|
+
| Experiment | `graph/experiments/` | Units of work that test hypotheses |
|
|
13
|
+
| Finding | `graph/findings/` | Observations from experiments |
|
|
14
|
+
| Knowledge | `graph/knowledge/` | Established facts promoted from findings |
|
|
15
|
+
| Question | `graph/questions/` | Open questions driving exploration |
|
|
16
|
+
| Decision | `graph/decisions/` | Recorded choices with rationale |
|
|
17
|
+
| Episode | `graph/episodes/` | Session logs linking work to the graph |
|
|
18
|
+
|
|
19
|
+
Nodes are connected by typed edges (supports, contradicts, spawns, produces, tests, depends_on, extends, promotes, answers, etc.) declared in YAML frontmatter `links:` arrays.
|
|
20
|
+
|
|
21
|
+
## Node File Format
|
|
22
|
+
|
|
23
|
+
Every node is a Markdown file with YAML frontmatter:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
---
|
|
27
|
+
id: hyp-001
|
|
28
|
+
type: hypothesis
|
|
29
|
+
status: PROPOSED
|
|
30
|
+
confidence: 0.4
|
|
31
|
+
created: 2026-03-15
|
|
32
|
+
updated: 2026-03-15
|
|
33
|
+
created_by: human:yourname
|
|
34
|
+
tags: [topic]
|
|
35
|
+
links:
|
|
36
|
+
- target: know-001
|
|
37
|
+
relation: depends_on
|
|
38
|
+
---
|
|
39
|
+
# Title here
|
|
40
|
+
Body content...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Required fields vary by type. All nodes need: `id`, `type`, `status`, `created`, `updated`. Hypotheses and findings also need `confidence` (0.0-1.0).
|
|
44
|
+
|
|
45
|
+
## Node ID Convention
|
|
46
|
+
|
|
47
|
+
IDs use type prefix + sequential number: `hyp-001`, `exp-003`, `fnd-012`, `knw-005`, `qst-002`, `dec-001`, `epi-007`.
|
|
48
|
+
|
|
49
|
+
## Episode Writing Protocol
|
|
50
|
+
|
|
51
|
+
Episodes are the primary mechanism for maintaining research continuity. Write an Episode at the end of each work session.
|
|
52
|
+
|
|
53
|
+
**Mandatory sections:**
|
|
54
|
+
- **What I Tried** — what was done this session
|
|
55
|
+
- **What's Next** — planned next steps with prerequisite reading nodes
|
|
56
|
+
|
|
57
|
+
**Optional sections:**
|
|
58
|
+
- What Got Stuck — blockers or wrong turns
|
|
59
|
+
- What Was Deliberately Not Done — deferred items with reasons
|
|
60
|
+
- Questions That Arose — new questions for the graph
|
|
61
|
+
|
|
62
|
+
Each "What's Next" item should list prerequisite reading: the node IDs to load before starting that task. This curates context for the next session.
|
|
63
|
+
|
|
64
|
+
## Consolidation Protocol
|
|
65
|
+
|
|
66
|
+
Consolidation is a mandatory maintenance ceremony. Check triggers after creating Episodes or Findings.
|
|
67
|
+
|
|
68
|
+
**Triggers (run if ANY apply):**
|
|
69
|
+
- 5 or more Finding nodes added since last Consolidation
|
|
70
|
+
- 3 or more Episode nodes added since last Consolidation
|
|
71
|
+
- 0 open Questions (the illusion that research is "done")
|
|
72
|
+
- An Experiment has 5+ Findings attached
|
|
73
|
+
|
|
74
|
+
**Consolidation steps:**
|
|
75
|
+
1. **Promotion** — promote established Findings to Knowledge nodes
|
|
76
|
+
2. **Splitting** — split bloated Experiments into meaningful units
|
|
77
|
+
3. **Question generation** — convert Episode questions into Question nodes
|
|
78
|
+
4. **Hypothesis update** — update confidence based on evidence
|
|
79
|
+
5. **Orphan cleanup** — add connections to unlinked Findings
|
|
80
|
+
|
|
81
|
+
Consolidation is an obligation, not optional. Do not record Consolidation as an Episode. Do not start new exploration during Consolidation.
|
|
82
|
+
|
|
83
|
+
## Key Principles
|
|
84
|
+
|
|
85
|
+
1. **Graph is source of truth** — the graph, not code, is the project's knowledge structure
|
|
86
|
+
2. **Minimum viable structure** — add structure only when needed; if it feels like bureaucracy, reduce it
|
|
87
|
+
3. **Gap-driven exploration** — the most valuable information is in the empty spaces between nodes
|
|
88
|
+
4. **Temporal evolution** — never delete wrong paths; deprecate them. The history of why something failed is itself knowledge
|
|
89
|
+
5. **Riskiest-first** — validate the most uncertain hypotheses first
|
|
90
|
+
6. **Archive, don't delete** — change status to REFUTED/RETRACTED/SUPERSEDED instead of removing nodes
|
|
91
|
+
|
|
92
|
+
## AI Agent Role
|
|
93
|
+
|
|
94
|
+
You are a **gardener** of the graph:
|
|
95
|
+
- Maintain connections, detect duplicates, identify orphans
|
|
96
|
+
- Detect patterns and potential connections the researcher missed
|
|
97
|
+
- Suggest exploration directions based on structural gaps
|
|
98
|
+
- Automate routine tasks (literature search, result summarization)
|
|
99
|
+
- Never make judgment calls — suggest, don't decide
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type ToolType = 'claude' | 'cursor' | 'windsurf' | 'cline' | 'copilot' | 'all';
|
|
2
|
+
export type RulesVariant = 'full' | 'compact';
|
|
3
|
+
/**
|
|
4
|
+
* Get rules content for a specific tool and variant.
|
|
5
|
+
* For 'all', returns claude content (use generateRulesFile for writing all files).
|
|
6
|
+
*/
|
|
7
|
+
export declare function getRulesContent(tool: ToolType, variant: RulesVariant): string;
|
|
8
|
+
/**
|
|
9
|
+
* Generate rules file(s) for the given tool at the project path.
|
|
10
|
+
* Returns an object with created file paths and any warnings.
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateRulesFile(tool: ToolType, projectPath: string, options?: {
|
|
13
|
+
variant?: RulesVariant;
|
|
14
|
+
force?: boolean;
|
|
15
|
+
}): {
|
|
16
|
+
created: string[];
|
|
17
|
+
skipped: string[];
|
|
18
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
// Tool -> output path mapping (relative to project root)
|
|
6
|
+
const TOOL_PATHS = {
|
|
7
|
+
claude: '.claude/CLAUDE.md',
|
|
8
|
+
cursor: '.cursor/rules/emdd.mdc',
|
|
9
|
+
windsurf: '.windsurf/rules/emdd.md',
|
|
10
|
+
cline: '.clinerules/emdd.md',
|
|
11
|
+
copilot: '.github/copilot-instructions.md',
|
|
12
|
+
};
|
|
13
|
+
const ALL_TOOLS = ['claude', 'cursor', 'windsurf', 'cline', 'copilot'];
|
|
14
|
+
function loadRulesMarkdown() {
|
|
15
|
+
return fs.readFileSync(path.join(__dirname, 'emdd-rules.md'), 'utf-8');
|
|
16
|
+
}
|
|
17
|
+
function loadAgentMarkdown() {
|
|
18
|
+
return fs.readFileSync(path.join(__dirname, 'emdd-agent.md'), 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
function makeCompactRules() {
|
|
21
|
+
// Compact version: essential rules only, targeting ~1500 tokens (~6000 chars)
|
|
22
|
+
return `# EMDD — Evolving Mindmap-Driven Development (Compact)
|
|
23
|
+
|
|
24
|
+
You are working in an EMDD project. The knowledge graph lives in \`graph/\` as Markdown + YAML frontmatter files.
|
|
25
|
+
|
|
26
|
+
## Node Types
|
|
27
|
+
|
|
28
|
+
- **Hypothesis** (\`graph/hypotheses/\`) — testable claims with confidence 0.0-1.0
|
|
29
|
+
- **Experiment** (\`graph/experiments/\`) — work units testing hypotheses
|
|
30
|
+
- **Finding** (\`graph/findings/\`) — observations from experiments
|
|
31
|
+
- **Knowledge** (\`graph/knowledge/\`) — established facts promoted from findings
|
|
32
|
+
- **Question** (\`graph/questions/\`) — open questions driving exploration
|
|
33
|
+
- **Decision** (\`graph/decisions/\`) — recorded choices with rationale
|
|
34
|
+
- **Episode** (\`graph/episodes/\`) — session logs (mandatory: "What I Tried" + "What's Next")
|
|
35
|
+
|
|
36
|
+
## ID Convention
|
|
37
|
+
|
|
38
|
+
\`hyp-001\`, \`exp-003\`, \`fnd-012\`, \`knw-005\`, \`qst-002\`, \`dec-001\`, \`epi-007\`
|
|
39
|
+
|
|
40
|
+
## Key Rules
|
|
41
|
+
|
|
42
|
+
1. Write an Episode at end of each session with "What I Tried" and "What's Next" (with prerequisite node IDs)
|
|
43
|
+
2. Run Consolidation when: 5+ Findings, 3+ Episodes, 0 open Questions, or 5+ Findings on one Experiment
|
|
44
|
+
3. Consolidation steps: Promote findings, split experiments, generate questions, update confidence, fix orphans
|
|
45
|
+
4. Never delete nodes — change status to REFUTED/RETRACTED/SUPERSEDED
|
|
46
|
+
5. Graph is source of truth; you are a gardener, not an architect — suggest, don't decide
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
function wrapForCursor(content) {
|
|
50
|
+
return `---
|
|
51
|
+
description: EMDD methodology rules for AI-assisted research graph management
|
|
52
|
+
globs: graph/**/*.md
|
|
53
|
+
---
|
|
54
|
+
${content}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get rules content for a specific tool and variant.
|
|
58
|
+
* For 'all', returns claude content (use generateRulesFile for writing all files).
|
|
59
|
+
*/
|
|
60
|
+
export function getRulesContent(tool, variant) {
|
|
61
|
+
const resolvedTool = tool === 'all' ? 'claude' : tool;
|
|
62
|
+
let content;
|
|
63
|
+
if (variant === 'compact') {
|
|
64
|
+
content = makeCompactRules();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const rules = loadRulesMarkdown();
|
|
68
|
+
const agent = loadAgentMarkdown();
|
|
69
|
+
content = `${rules}\n\n${agent}`;
|
|
70
|
+
}
|
|
71
|
+
// Apply tool-specific formatting
|
|
72
|
+
if (resolvedTool === 'cursor') {
|
|
73
|
+
return wrapForCursor(content);
|
|
74
|
+
}
|
|
75
|
+
return content;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Generate rules file(s) for the given tool at the project path.
|
|
79
|
+
* Returns an object with created file paths and any warnings.
|
|
80
|
+
*/
|
|
81
|
+
export function generateRulesFile(tool, projectPath, options = {}) {
|
|
82
|
+
const variant = options.variant ?? 'full';
|
|
83
|
+
const force = options.force ?? false;
|
|
84
|
+
const tools = tool === 'all' ? ALL_TOOLS : [tool];
|
|
85
|
+
const created = [];
|
|
86
|
+
const skipped = [];
|
|
87
|
+
for (const t of tools) {
|
|
88
|
+
const relativePath = TOOL_PATHS[t];
|
|
89
|
+
const fullPath = path.join(projectPath, relativePath);
|
|
90
|
+
const dir = path.dirname(fullPath);
|
|
91
|
+
// Check if file already exists
|
|
92
|
+
if (!force && fs.existsSync(fullPath)) {
|
|
93
|
+
skipped.push(relativePath);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Create directory if needed
|
|
97
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
98
|
+
// Write the content
|
|
99
|
+
const content = getRulesContent(t, variant);
|
|
100
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
101
|
+
created.push(relativePath);
|
|
102
|
+
}
|
|
103
|
+
return { created, skipped };
|
|
104
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beomjk/emdd",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Evolving Mindmap-Driven Development",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"emdd": "dist/cli.js",
|
|
8
|
+
"emdd-mcp": "dist/mcp-server/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsx src/cli.ts",
|
|
19
|
+
"test": "vitest",
|
|
20
|
+
"test:run": "vitest run --passWithNoTests",
|
|
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"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"emdd",
|
|
26
|
+
"mindmap",
|
|
27
|
+
"research",
|
|
28
|
+
"knowledge-graph",
|
|
29
|
+
"cli"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
34
|
+
"chalk": "^5.6.2",
|
|
35
|
+
"cli-table3": "^0.6.5",
|
|
36
|
+
"commander": "^14.0.3",
|
|
37
|
+
"glob": "^13.0.6",
|
|
38
|
+
"gray-matter": "^4.0.3",
|
|
39
|
+
"js-yaml": "^4.1.1",
|
|
40
|
+
"zod": "^4.3.6"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.5.0",
|
|
44
|
+
"react": "^16.14.0",
|
|
45
|
+
"svg-term-cli": "^2.1.1",
|
|
46
|
+
"terminal-demo": "^0.1.11",
|
|
47
|
+
"tsx": "^4.21.0",
|
|
48
|
+
"typescript": "^5.9.3",
|
|
49
|
+
"vitest": "^4.1.0"
|
|
50
|
+
}
|
|
51
|
+
}
|