@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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +158 -0
  5. package/dist/commands/backlog.d.ts +8 -0
  6. package/dist/commands/backlog.js +35 -0
  7. package/dist/commands/check.d.ts +6 -0
  8. package/dist/commands/check.js +8 -0
  9. package/dist/commands/done.d.ts +7 -0
  10. package/dist/commands/done.js +34 -0
  11. package/dist/commands/graph.d.ts +5 -0
  12. package/dist/commands/graph.js +18 -0
  13. package/dist/commands/health.d.ts +1 -0
  14. package/dist/commands/health.js +36 -0
  15. package/dist/commands/index.d.ts +4 -0
  16. package/dist/commands/index.js +10 -0
  17. package/dist/commands/init.d.ts +4 -0
  18. package/dist/commands/init.js +40 -0
  19. package/dist/commands/link.d.ts +5 -0
  20. package/dist/commands/link.js +8 -0
  21. package/dist/commands/lint.d.ts +1 -0
  22. package/dist/commands/lint.js +27 -0
  23. package/dist/commands/new.d.ts +3 -0
  24. package/dist/commands/new.js +14 -0
  25. package/dist/commands/promote.d.ts +9 -0
  26. package/dist/commands/promote.js +9 -0
  27. package/dist/commands/update.d.ts +7 -0
  28. package/dist/commands/update.js +33 -0
  29. package/dist/graph/index-generator.d.ts +2 -0
  30. package/dist/graph/index-generator.js +57 -0
  31. package/dist/graph/loader.d.ts +15 -0
  32. package/dist/graph/loader.js +112 -0
  33. package/dist/graph/mermaid.d.ts +2 -0
  34. package/dist/graph/mermaid.js +35 -0
  35. package/dist/graph/operations.d.ts +34 -0
  36. package/dist/graph/operations.js +265 -0
  37. package/dist/graph/templates.d.ts +9 -0
  38. package/dist/graph/templates.js +102 -0
  39. package/dist/graph/types.d.ts +71 -0
  40. package/dist/graph/types.js +70 -0
  41. package/dist/graph/validator.d.ts +16 -0
  42. package/dist/graph/validator.js +120 -0
  43. package/dist/i18n/en.d.ts +1 -0
  44. package/dist/i18n/en.js +72 -0
  45. package/dist/i18n/index.d.ts +4 -0
  46. package/dist/i18n/index.js +26 -0
  47. package/dist/i18n/ko.d.ts +1 -0
  48. package/dist/i18n/ko.js +72 -0
  49. package/dist/mcp-server/cli.d.ts +2 -0
  50. package/dist/mcp-server/cli.js +6 -0
  51. package/dist/mcp-server/index.d.ts +3 -0
  52. package/dist/mcp-server/index.js +35 -0
  53. package/dist/mcp-server/prompts/consolidation.d.ts +2 -0
  54. package/dist/mcp-server/prompts/consolidation.js +64 -0
  55. package/dist/mcp-server/prompts/context-loading.d.ts +2 -0
  56. package/dist/mcp-server/prompts/context-loading.js +52 -0
  57. package/dist/mcp-server/prompts/episode-creation.d.ts +2 -0
  58. package/dist/mcp-server/prompts/episode-creation.js +75 -0
  59. package/dist/mcp-server/prompts/health-review.d.ts +2 -0
  60. package/dist/mcp-server/prompts/health-review.js +76 -0
  61. package/dist/mcp-server/tools/check.d.ts +2 -0
  62. package/dist/mcp-server/tools/check.js +11 -0
  63. package/dist/mcp-server/tools/create-edge.d.ts +2 -0
  64. package/dist/mcp-server/tools/create-edge.js +14 -0
  65. package/dist/mcp-server/tools/create-node.d.ts +2 -0
  66. package/dist/mcp-server/tools/create-node.js +14 -0
  67. package/dist/mcp-server/tools/health.d.ts +2 -0
  68. package/dist/mcp-server/tools/health.js +11 -0
  69. package/dist/mcp-server/tools/list-nodes.d.ts +2 -0
  70. package/dist/mcp-server/tools/list-nodes.js +18 -0
  71. package/dist/mcp-server/tools/promote.d.ts +2 -0
  72. package/dist/mcp-server/tools/promote.js +11 -0
  73. package/dist/mcp-server/tools/read-node.d.ts +2 -0
  74. package/dist/mcp-server/tools/read-node.js +15 -0
  75. package/dist/mcp-server/tools/util.d.ts +7 -0
  76. package/dist/mcp-server/tools/util.js +22 -0
  77. package/dist/rules/emdd-agent.md +40 -0
  78. package/dist/rules/emdd-rules.md +99 -0
  79. package/dist/rules/generators.d.ts +18 -0
  80. package/dist/rules/generators.js +104 -0
  81. 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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerHealthReview(server: McpServer): void;
@@ -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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCheck(server: McpServer): void;
@@ -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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCreateEdge(server: McpServer): void;
@@ -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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCreateNode(server: McpServer): void;
@@ -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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerHealth(server: McpServer): void;
@@ -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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerListNodes(server: McpServer): void;
@@ -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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerPromote(server: McpServer): void;
@@ -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,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerReadNode(server: McpServer): void;
@@ -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
+ }