@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,71 @@
1
+ export type NodeType = 'hypothesis' | 'experiment' | 'finding' | 'knowledge' | 'question' | 'decision' | 'episode';
2
+ export type EdgeType = 'supports' | 'contradicts' | 'confirms' | 'spawns' | 'produces' | 'answers' | 'revises' | 'promotes' | 'depends_on' | 'extends' | 'relates_to' | 'informs' | 'part_of' | 'context_for' | 'tests' | 'answers_to' | 'confirmed_by' | 'supported_by' | 'answered_by' | 'spawned_from' | 'produced_by' | 'tested_by';
3
+ export declare const EDGE_TYPES: Set<string>;
4
+ export declare const REVERSE_LABELS: Record<string, string>;
5
+ export declare const ALL_VALID_RELATIONS: Set<string>;
6
+ export declare const NODE_TYPES: NodeType[];
7
+ export declare const NODE_TYPE_DIRS: Record<NodeType, string>;
8
+ export declare const ID_PREFIXES: Record<NodeType, string>;
9
+ export declare const PREFIX_TO_TYPE: Record<string, NodeType>;
10
+ export declare const VALID_STATUSES: Record<NodeType, readonly string[]>;
11
+ export declare const REQUIRED_FIELDS: Record<NodeType, readonly string[]>;
12
+ export interface Link {
13
+ target: string;
14
+ relation: string;
15
+ }
16
+ export interface Node {
17
+ id: string;
18
+ type: NodeType;
19
+ title: string;
20
+ path: string;
21
+ status?: string;
22
+ confidence?: number;
23
+ tags: string[];
24
+ links: Link[];
25
+ meta: Record<string, unknown>;
26
+ }
27
+ export interface Graph {
28
+ nodes: Map<string, Node>;
29
+ errors: string[];
30
+ warnings: string[];
31
+ }
32
+ export interface NodeFilter {
33
+ type?: NodeType;
34
+ status?: string;
35
+ }
36
+ export interface NodeDetail extends Node {
37
+ body: string;
38
+ }
39
+ export interface CreateNodeResult {
40
+ id: string;
41
+ type: NodeType;
42
+ path: string;
43
+ }
44
+ export interface CreateEdgeResult {
45
+ source: string;
46
+ target: string;
47
+ relation: string;
48
+ }
49
+ export interface HealthReport {
50
+ totalNodes: number;
51
+ totalEdges: number;
52
+ byType: Record<string, number>;
53
+ statusDistribution: Record<string, Record<string, number>>;
54
+ avgConfidence: number | null;
55
+ openQuestions: number;
56
+ linkDensity: number;
57
+ gaps: string[];
58
+ }
59
+ export interface CheckTrigger {
60
+ type: string;
61
+ message: string;
62
+ count?: number;
63
+ }
64
+ export interface CheckResult {
65
+ triggers: CheckTrigger[];
66
+ }
67
+ export interface PromoteCandidate {
68
+ id: string;
69
+ confidence: number;
70
+ supports: number;
71
+ }
@@ -0,0 +1,70 @@
1
+ // ── Node Types ──────────────────────────────────────────────────────
2
+ export const EDGE_TYPES = new Set([
3
+ // evidence
4
+ 'supports', 'contradicts', 'confirms',
5
+ // generation
6
+ 'spawns', 'produces', 'answers', 'revises', 'promotes',
7
+ // structure
8
+ 'depends_on', 'extends', 'relates_to', 'informs',
9
+ // composition
10
+ 'part_of', 'context_for',
11
+ // aliases
12
+ 'tests', 'answers_to',
13
+ ]);
14
+ export const REVERSE_LABELS = {
15
+ confirmed_by: 'confirms',
16
+ supported_by: 'supports',
17
+ answered_by: 'answers',
18
+ spawned_from: 'spawns',
19
+ produced_by: 'produces',
20
+ tested_by: 'tests',
21
+ };
22
+ export const ALL_VALID_RELATIONS = new Set([
23
+ ...EDGE_TYPES,
24
+ ...Object.keys(REVERSE_LABELS),
25
+ ]);
26
+ export const NODE_TYPES = [
27
+ 'hypothesis', 'experiment', 'finding', 'knowledge',
28
+ 'question', 'decision', 'episode',
29
+ ];
30
+ // ── Node Type → Directory Mapping ───────────────────────────────────
31
+ export const NODE_TYPE_DIRS = {
32
+ hypothesis: 'hypotheses',
33
+ experiment: 'experiments',
34
+ finding: 'findings',
35
+ knowledge: 'knowledge',
36
+ question: 'questions',
37
+ decision: 'decisions',
38
+ episode: 'episodes',
39
+ };
40
+ // ── ID Prefixes ─────────────────────────────────────────────────────
41
+ export const ID_PREFIXES = {
42
+ hypothesis: 'hyp',
43
+ experiment: 'exp',
44
+ finding: 'fnd',
45
+ knowledge: 'knw',
46
+ question: 'qst',
47
+ decision: 'dec',
48
+ episode: 'epi',
49
+ };
50
+ export const PREFIX_TO_TYPE = Object.fromEntries(Object.entries(ID_PREFIXES).map(([type, prefix]) => [prefix, type]));
51
+ // ── Valid Statuses per Node Type ────────────────────────────────────
52
+ export const VALID_STATUSES = {
53
+ hypothesis: ['PROPOSED', 'TESTING', 'SUPPORTED', 'REFUTED', 'REVISED', 'DEFERRED'],
54
+ experiment: ['PLANNED', 'RUNNING', 'COMPLETED', 'FAILED', 'ABANDONED'],
55
+ finding: ['DRAFT', 'VALIDATED', 'PROMOTED', 'RETRACTED'],
56
+ knowledge: ['ACTIVE', 'DISPUTED', 'SUPERSEDED', 'RETRACTED'],
57
+ question: ['OPEN', 'RESOLVED', 'ANSWERED', 'DEFERRED'],
58
+ decision: ['PROPOSED', 'ACCEPTED', 'SUPERSEDED', 'REVERTED'],
59
+ episode: ['ACTIVE', 'COMPLETED'],
60
+ };
61
+ // ── Required Fields per Node Type ───────────────────────────────────
62
+ export const REQUIRED_FIELDS = {
63
+ hypothesis: ['id', 'type', 'title', 'status', 'confidence', 'created', 'updated'],
64
+ experiment: ['id', 'type', 'title', 'status', 'created', 'updated'],
65
+ finding: ['id', 'type', 'title', 'status', 'confidence', 'created', 'updated'],
66
+ knowledge: ['id', 'type', 'title', 'status', 'confidence', 'created', 'updated'],
67
+ question: ['id', 'type', 'title', 'status', 'created', 'updated'],
68
+ decision: ['id', 'type', 'title', 'status', 'created', 'updated'],
69
+ episode: ['id', 'type', 'title', 'status', 'created', 'updated'],
70
+ };
@@ -0,0 +1,16 @@
1
+ import type { Node, Graph } from './types.js';
2
+ export interface LintError {
3
+ nodeId: string;
4
+ field: string;
5
+ message: string;
6
+ severity: 'error' | 'warning';
7
+ }
8
+ /**
9
+ * Validate a single node for schema correctness.
10
+ * Returns an array of LintError objects.
11
+ */
12
+ export declare function lintNode(node: Node): LintError[];
13
+ /**
14
+ * Validate an entire graph: per-node checks + cross-node link integrity.
15
+ */
16
+ export declare function lintGraph(graph: Graph): LintError[];
@@ -0,0 +1,120 @@
1
+ import { VALID_STATUSES, REQUIRED_FIELDS, ALL_VALID_RELATIONS } from './types.js';
2
+ import { t } from '../i18n/index.js';
3
+ /**
4
+ * Validate a single node for schema correctness.
5
+ * Returns an array of LintError objects.
6
+ */
7
+ export function lintNode(node) {
8
+ const errors = [];
9
+ const id = node.id;
10
+ // Check type field
11
+ if (!node.type) {
12
+ errors.push({
13
+ nodeId: id,
14
+ field: 'type',
15
+ message: t('lint.missing_field', { field: 'type' }),
16
+ severity: 'error',
17
+ });
18
+ // Can't do type-specific checks without a type
19
+ return errors;
20
+ }
21
+ // Check if type is a valid NodeType
22
+ const validTypes = Object.keys(VALID_STATUSES);
23
+ if (!validTypes.includes(node.type)) {
24
+ errors.push({
25
+ nodeId: id,
26
+ field: 'type',
27
+ message: t('lint.missing_field', { field: 'type' }),
28
+ severity: 'error',
29
+ });
30
+ return errors;
31
+ }
32
+ const nodeType = node.type;
33
+ // Check title
34
+ if (!node.title) {
35
+ errors.push({
36
+ nodeId: id,
37
+ field: 'title',
38
+ message: t('lint.missing_field', { field: 'title' }),
39
+ severity: 'error',
40
+ });
41
+ }
42
+ // Check status
43
+ if (!node.status) {
44
+ errors.push({
45
+ nodeId: id,
46
+ field: 'status',
47
+ message: t('lint.missing_field', { field: 'status' }),
48
+ severity: 'error',
49
+ });
50
+ }
51
+ else {
52
+ const validStatuses = VALID_STATUSES[nodeType];
53
+ if (validStatuses && !validStatuses.includes(node.status)) {
54
+ errors.push({
55
+ nodeId: id,
56
+ field: 'status',
57
+ message: t('lint.invalid_status', {
58
+ status: node.status,
59
+ type: nodeType,
60
+ valid: validStatuses.join(', '),
61
+ }),
62
+ severity: 'error',
63
+ });
64
+ }
65
+ }
66
+ // Check confidence range
67
+ if (node.confidence !== undefined) {
68
+ if (node.confidence < 0.0 || node.confidence > 1.0) {
69
+ errors.push({
70
+ nodeId: id,
71
+ field: 'confidence',
72
+ message: t('lint.confidence_range', { value: String(node.confidence) }),
73
+ severity: 'error',
74
+ });
75
+ }
76
+ }
77
+ // Check type-specific required fields: confidence for hypothesis, finding, knowledge
78
+ const requiredFields = REQUIRED_FIELDS[nodeType] ?? [];
79
+ if (requiredFields.includes('confidence') && node.confidence === undefined) {
80
+ errors.push({
81
+ nodeId: id,
82
+ field: 'confidence',
83
+ message: t('lint.missing_field', { field: 'confidence' }),
84
+ severity: 'warning',
85
+ });
86
+ }
87
+ // Check link relations
88
+ for (const link of node.links) {
89
+ if (!ALL_VALID_RELATIONS.has(link.relation)) {
90
+ errors.push({
91
+ nodeId: id,
92
+ field: 'links',
93
+ message: t('lint.invalid_relation', { relation: link.relation }),
94
+ severity: 'error',
95
+ });
96
+ }
97
+ }
98
+ return errors;
99
+ }
100
+ /**
101
+ * Validate an entire graph: per-node checks + cross-node link integrity.
102
+ */
103
+ export function lintGraph(graph) {
104
+ const errors = [];
105
+ for (const node of graph.nodes.values()) {
106
+ errors.push(...lintNode(node));
107
+ // Check link targets exist in graph
108
+ for (const link of node.links) {
109
+ if (!graph.nodes.has(link.target)) {
110
+ errors.push({
111
+ nodeId: node.id,
112
+ field: 'links',
113
+ message: t('lint.broken_link', { target: link.target }),
114
+ severity: 'error',
115
+ });
116
+ }
117
+ }
118
+ }
119
+ return errors;
120
+ }
@@ -0,0 +1 @@
1
+ export declare const messages: Record<string, string>;
@@ -0,0 +1,72 @@
1
+ export const messages = {
2
+ // Health dashboard
3
+ 'health.title': 'EMDD Health Dashboard',
4
+ 'health.total_nodes': 'Total Nodes',
5
+ 'health.by_type': 'By Type',
6
+ 'health.hypothesis_status': 'Hypothesis Status',
7
+ 'health.avg_confidence': 'Average Confidence',
8
+ 'health.open_questions': 'Open Questions',
9
+ 'health.orphan_findings': 'Orphan Findings',
10
+ 'health.link_density': 'Link Density',
11
+ 'health.recent_activity': 'Recent Activity',
12
+ 'health.gaps': 'Structural Gaps',
13
+ // CLI descriptions
14
+ 'cli.description': 'CLI for Evolving Mindmap-Driven Development',
15
+ 'cli.init.desc': 'Initialize EMDD project',
16
+ 'cli.new.desc': 'Create a new node',
17
+ 'cli.health.desc': 'Show health dashboard',
18
+ 'cli.check.desc': 'Check consolidation triggers',
19
+ 'cli.lint.desc': 'Validate graph schema and links',
20
+ 'cli.promote.desc': 'Identify promotion candidates',
21
+ 'cli.index.desc': 'Generate _index.md',
22
+ 'cli.graph.desc': 'Generate _graph.mmd',
23
+ 'cli.update.desc': 'Update node frontmatter',
24
+ 'cli.link.desc': 'Add a link between nodes',
25
+ 'cli.done.desc': 'Mark episode item as done',
26
+ 'cli.backlog.desc': 'Show backlog items',
27
+ // New node
28
+ 'new.created': 'Created {type} node: {id}',
29
+ 'new.invalid_type': 'Invalid node type: {type}. Valid types: {valid}',
30
+ // Init
31
+ 'init.success': 'EMDD project initialized at {path}',
32
+ 'init.already_exists': 'EMDD project already exists at {path}',
33
+ 'init.next_steps': 'Next: emdd new hypothesis <slug>',
34
+ // Lint
35
+ 'lint.clean': 'All nodes valid. No errors found.',
36
+ 'lint.errors_found': '{count} error(s) found',
37
+ 'lint.warnings_found': '{count} warning(s) found',
38
+ 'lint.missing_field': 'Missing required field: {field}',
39
+ 'lint.invalid_status': 'Invalid status "{status}" for type {type}. Valid: {valid}',
40
+ 'lint.confidence_range': 'Confidence must be between 0.0 and 1.0, got {value}',
41
+ 'lint.invalid_relation': 'Invalid link relation: {relation}',
42
+ 'lint.broken_link': 'Link target "{target}" not found in graph',
43
+ // Check
44
+ 'check.title': 'Consolidation Trigger Check',
45
+ 'check.findings_threshold': 'Findings pending consolidation: {count} (threshold: {threshold})',
46
+ 'check.episodes_threshold': 'Episodes since last consolidation: {count} (threshold: {threshold})',
47
+ 'check.stale_hypothesis': 'Stale hypothesis: {id} ({days} days in {status})',
48
+ 'check.no_triggers': 'No consolidation triggers active',
49
+ // Promote
50
+ 'promote.title': 'Promotion Candidates',
51
+ 'promote.candidate': '{id}: confidence={confidence}, supports={supports}',
52
+ 'promote.no_candidates': 'No promotion candidates found',
53
+ // Update
54
+ 'update.success': 'Updated {id}: {field} = {value}',
55
+ 'update.node_not_found': 'Node not found: {id}',
56
+ // Link
57
+ 'link.success': 'Linked {source} → {target} ({relation})',
58
+ 'link.invalid_relation': 'Invalid relation: {relation}. Valid: {valid}',
59
+ // Done
60
+ 'done.success': 'Marked as done: {item}',
61
+ 'done.item_not_found': 'Item not found in {id}: {item}',
62
+ // Index
63
+ 'index.generated': 'Generated _index.md ({nodes} nodes)',
64
+ // Graph
65
+ 'graph.generated': 'Generated _graph.mmd ({nodes} nodes, {edges} edges)',
66
+ // Backlog
67
+ 'backlog.title': 'Backlog Items',
68
+ 'backlog.empty': 'No pending backlog items',
69
+ // Errors
70
+ 'error.graph_not_found': 'No graph/ directory found',
71
+ 'error.node_not_found': 'Node not found: {id}',
72
+ };
@@ -0,0 +1,4 @@
1
+ export type Locale = 'en' | 'ko';
2
+ export declare function getLocale(override?: string): Locale;
3
+ export declare function setLocale(locale: Locale): void;
4
+ export declare function t(key: string, vars?: Record<string, string>): string;
@@ -0,0 +1,26 @@
1
+ import { messages as en } from './en.js';
2
+ import { messages as ko } from './ko.js';
3
+ const MESSAGES = { en, ko };
4
+ const VALID_LOCALES = new Set(['en', 'ko']);
5
+ let currentLocale = 'en';
6
+ export function getLocale(override) {
7
+ if (override && VALID_LOCALES.has(override))
8
+ return override;
9
+ if (override)
10
+ return 'en'; // invalid locale -> fallback
11
+ const env = process.env.EMDD_LANG;
12
+ if (env && VALID_LOCALES.has(env))
13
+ return env;
14
+ return 'en';
15
+ }
16
+ export function setLocale(locale) {
17
+ currentLocale = locale;
18
+ }
19
+ export function t(key, vars) {
20
+ const msg = MESSAGES[currentLocale]?.[key];
21
+ if (!msg)
22
+ return key; // fallback: return key itself
23
+ if (!vars)
24
+ return msg;
25
+ return msg.replace(/\{(\w+)\}/g, (_, name) => vars[name] ?? `{${name}}`);
26
+ }
@@ -0,0 +1 @@
1
+ export declare const messages: Record<string, string>;
@@ -0,0 +1,72 @@
1
+ export const messages = {
2
+ // 건강 대시보드
3
+ 'health.title': 'EMDD 건강 대시보드',
4
+ 'health.total_nodes': '전체 노드 수',
5
+ 'health.by_type': '타입별 분포',
6
+ 'health.hypothesis_status': '가설 상태',
7
+ 'health.avg_confidence': '평균 신뢰도',
8
+ 'health.open_questions': '미해결 질문',
9
+ 'health.orphan_findings': '고아 발견사항',
10
+ 'health.link_density': '링크 밀도',
11
+ 'health.recent_activity': '최근 활동',
12
+ 'health.gaps': '구조적 공백',
13
+ // CLI 설명
14
+ 'cli.description': '진화하는 마인드맵 주도 개발을 위한 CLI',
15
+ 'cli.init.desc': 'EMDD 프로젝트 초기화',
16
+ 'cli.new.desc': '새 노드 생성',
17
+ 'cli.health.desc': '건강 대시보드 표시',
18
+ 'cli.check.desc': '통합 트리거 확인',
19
+ 'cli.lint.desc': '그래프 스키마 및 링크 검증',
20
+ 'cli.promote.desc': '승격 후보 식별',
21
+ 'cli.index.desc': '_index.md 생성',
22
+ 'cli.graph.desc': '_graph.mmd 생성',
23
+ 'cli.update.desc': '노드 프론트매터 업데이트',
24
+ 'cli.link.desc': '노드 간 링크 추가',
25
+ 'cli.done.desc': '에피소드 항목 완료 표시',
26
+ 'cli.backlog.desc': '백로그 항목 표시',
27
+ // 새 노드
28
+ 'new.created': '{type} 노드 생성됨: {id}',
29
+ 'new.invalid_type': '잘못된 노드 타입: {type}. 유효한 타입: {valid}',
30
+ // 초기화
31
+ 'init.success': '{path}에 EMDD 프로젝트가 초기화되었습니다',
32
+ 'init.already_exists': '{path}에 EMDD 프로젝트가 이미 존재합니다',
33
+ 'init.next_steps': '다음: emdd new hypothesis <slug>',
34
+ // 린트
35
+ 'lint.clean': '모든 노드가 유효합니다. 오류가 없습니다.',
36
+ 'lint.errors_found': '{count}개 오류 발견',
37
+ 'lint.warnings_found': '{count}개 경고 발견',
38
+ 'lint.missing_field': '필수 필드 누락: {field}',
39
+ 'lint.invalid_status': '타입 {type}에 대한 잘못된 상태 "{status}". 유효한 값: {valid}',
40
+ 'lint.confidence_range': '신뢰도는 0.0에서 1.0 사이여야 합니다. 입력값: {value}',
41
+ 'lint.invalid_relation': '잘못된 링크 관계: {relation}',
42
+ 'lint.broken_link': '링크 대상 "{target}"을(를) 그래프에서 찾을 수 없습니다',
43
+ // 통합 확인
44
+ 'check.title': '통합 트리거 확인',
45
+ 'check.findings_threshold': '통합 대기 중인 발견사항: {count}개 (임계값: {threshold})',
46
+ 'check.episodes_threshold': '마지막 통합 이후 에피소드: {count}개 (임계값: {threshold})',
47
+ 'check.stale_hypothesis': '오래된 가설: {id} ({status} 상태로 {days}일)',
48
+ 'check.no_triggers': '활성화된 통합 트리거 없음',
49
+ // 승격
50
+ 'promote.title': '승격 후보',
51
+ 'promote.candidate': '{id}: 신뢰도={confidence}, 지지={supports}',
52
+ 'promote.no_candidates': '승격 후보가 없습니다',
53
+ // 업데이트
54
+ 'update.success': '{id} 업데이트됨: {field} = {value}',
55
+ 'update.node_not_found': '노드를 찾을 수 없습니다: {id}',
56
+ // 링크
57
+ 'link.success': '{source} → {target} 연결됨 ({relation})',
58
+ 'link.invalid_relation': '잘못된 관계: {relation}. 유효한 값: {valid}',
59
+ // 완료
60
+ 'done.success': '완료 표시됨: {item}',
61
+ 'done.item_not_found': '{id}에서 항목을 찾을 수 없습니다: {item}',
62
+ // 인덱스
63
+ 'index.generated': '_index.md 생성됨 ({nodes}개 노드)',
64
+ // 그래프
65
+ 'graph.generated': '_graph.mmd 생성됨 ({nodes}개 노드, {edges}개 엣지)',
66
+ // 백로그
67
+ 'backlog.title': '백로그 항목',
68
+ 'backlog.empty': '대기 중인 백로그 항목 없음',
69
+ // 오류
70
+ 'error.graph_not_found': 'graph/ 디렉토리를 찾을 수 없습니다',
71
+ 'error.node_not_found': '노드를 찾을 수 없습니다: {id}',
72
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { startMcpServer } from './index.js';
3
+ startMcpServer().catch((err) => {
4
+ console.error('Failed to start EMDD MCP server:', err);
5
+ process.exit(1);
6
+ });
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createEmddMcpServer(): McpServer;
3
+ export declare function startMcpServer(): Promise<void>;
@@ -0,0 +1,35 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { registerListNodes } from './tools/list-nodes.js';
4
+ import { registerReadNode } from './tools/read-node.js';
5
+ import { registerCreateNode } from './tools/create-node.js';
6
+ import { registerCreateEdge } from './tools/create-edge.js';
7
+ import { registerHealth } from './tools/health.js';
8
+ import { registerCheck } from './tools/check.js';
9
+ import { registerPromote } from './tools/promote.js';
10
+ import { registerContextLoading } from './prompts/context-loading.js';
11
+ import { registerEpisodeCreation } from './prompts/episode-creation.js';
12
+ import { registerConsolidation } from './prompts/consolidation.js';
13
+ import { registerHealthReview } from './prompts/health-review.js';
14
+ export function createEmddMcpServer() {
15
+ const server = new McpServer({ name: 'emdd', version: '0.1.0' });
16
+ // Tools
17
+ registerListNodes(server);
18
+ registerReadNode(server);
19
+ registerCreateNode(server);
20
+ registerCreateEdge(server);
21
+ registerHealth(server);
22
+ registerCheck(server);
23
+ registerPromote(server);
24
+ // Prompts
25
+ registerContextLoading(server);
26
+ registerEpisodeCreation(server);
27
+ registerConsolidation(server);
28
+ registerHealthReview(server);
29
+ return server;
30
+ }
31
+ export async function startMcpServer() {
32
+ const server = createEmddMcpServer();
33
+ const transport = new StdioServerTransport();
34
+ await server.connect(transport);
35
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerConsolidation(server: McpServer): void;
@@ -0,0 +1,64 @@
1
+ import { z } from 'zod';
2
+ import { checkConsolidation, getHealth } from '../../graph/operations.js';
3
+ export function registerConsolidation(server) {
4
+ server.prompt('consolidation', 'Consolidation execution guide — checks triggers and provides a step-by-step procedure for promoting findings, generating questions, and updating hypotheses', { path: z.string().describe('Path to the EMDD graph directory') }, async ({ path: graphDir }) => {
5
+ const checkResult = await checkConsolidation(graphDir);
6
+ const health = await getHealth(graphDir);
7
+ const triggersSection = checkResult.triggers.length > 0
8
+ ? checkResult.triggers.map(t => ` - [TRIGGERED] ${t.message}`).join('\n')
9
+ : ' No triggers active — consolidation is optional but you may still run it proactively.';
10
+ const text = `# EMDD Consolidation Guide
11
+
12
+ ## Current Trigger Status
13
+ ${triggersSection}
14
+
15
+ ## Graph State
16
+ - Total nodes: ${health.totalNodes}
17
+ - Total edges: ${health.totalEdges}
18
+ - Open questions: ${health.openQuestions}
19
+ - Average confidence: ${health.avgConfidence !== null ? health.avgConfidence.toFixed(2) : 'N/A'}
20
+
21
+ ## Consolidation Triggers (run if any apply)
22
+ - 5 or more Finding nodes added since last Consolidation
23
+ - 3 or more Episode nodes added since last Consolidation
24
+ - 0 open Questions (the illusion that research is "done")
25
+ - An Experiment has become a catch-all with 5+ Findings attached
26
+
27
+ ## Step-by-Step Consolidation Procedure
28
+
29
+ ### Step 1: Promotion
30
+ Review all Finding nodes. For each Finding with high confidence (>= 0.8) and strong evidence (2+ supporting links):
31
+ - Promote it to a Knowledge node using the \`create-node\` tool (type: knowledge).
32
+ - Add a \`promotes\` edge from the new Knowledge node to the original Finding.
33
+ - Use the \`promote\` tool to identify candidates automatically.
34
+
35
+ ### Step 2: Splitting
36
+ Review Experiments with many attached Findings (5+):
37
+ - Split bloated Experiments into focused sub-experiments.
38
+ - Reassign Findings to the appropriate sub-experiment.
39
+
40
+ ### Step 3: Question Generation
41
+ Review Episode "Questions That Arose" sections:
42
+ - Convert unrecorded questions into Question nodes using \`create-node\` (type: question).
43
+ - Link new Questions to their source Episodes with \`spawns\`.
44
+
45
+ ### Step 4: Hypothesis Update
46
+ Review all active Hypotheses:
47
+ - Update confidence values based on new Finding evidence.
48
+ - Create new Hypotheses if patterns suggest unexplored directions.
49
+ - Check kill criteria — mark REFUTED if a kill criterion is met.
50
+
51
+ ### Step 5: Orphan Cleanup
52
+ Find Findings without outgoing links:
53
+ - Add \`supports\`, \`contradicts\`, or \`spawns\` edges as appropriate.
54
+ - Every Finding should connect to at least one Hypothesis or Question.
55
+
56
+ ## Consolidation Principles
57
+ - Consolidation is an obligation, not optional — check triggers after creating Episodes or Findings.
58
+ - Do not record Consolidation itself as an Episode — it is a meta-activity.
59
+ - Do not start new exploration during Consolidation — this is garden tending, not planting.`;
60
+ return {
61
+ messages: [{ role: 'user', content: { type: 'text', text } }],
62
+ };
63
+ });
64
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerContextLoading(server: McpServer): void;
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ import { getHealth } from '../../graph/operations.js';
3
+ import { listNodes } from '../../graph/operations.js';
4
+ export function registerContextLoading(server) {
5
+ server.prompt('context-loading', 'Load EMDD graph context for session start — provides a summary of nodes, edges, health, and structural gaps', { path: z.string().describe('Path to the EMDD graph directory') }, async ({ path: graphDir }) => {
6
+ const health = await getHealth(graphDir);
7
+ const nodes = await listNodes(graphDir);
8
+ const typeBreakdown = Object.entries(health.byType)
9
+ .filter(([, count]) => count > 0)
10
+ .map(([type, count]) => ` - ${type}: ${count}`)
11
+ .join('\n');
12
+ const gapsSection = health.gaps.length > 0
13
+ ? health.gaps.map(g => ` - ${g}`).join('\n')
14
+ : ' None detected';
15
+ const confidenceInfo = health.avgConfidence !== null
16
+ ? `Average confidence: ${health.avgConfidence.toFixed(2)}`
17
+ : 'Average confidence: N/A (no confidence values)';
18
+ const recentNodes = nodes
19
+ .slice(0, 10)
20
+ .map(n => ` - [${n.id}] ${n.title} (${n.type}, ${n.status ?? 'no status'})`)
21
+ .join('\n');
22
+ const text = `# EMDD Graph Context — Session Start
23
+
24
+ ## Graph Overview
25
+ - Total nodes: ${health.totalNodes}
26
+ - Total edges: ${health.totalEdges}
27
+ - Link density: ${health.linkDensity.toFixed(2)} edges/node
28
+ - ${confidenceInfo}
29
+ - Open questions: ${health.openQuestions}
30
+
31
+ ## Node Counts by Type
32
+ ${typeBreakdown}
33
+
34
+ ## Structural Gaps
35
+ ${gapsSection}
36
+
37
+ ## Recent Nodes (up to 10)
38
+ ${recentNodes}
39
+
40
+ ## Session Start Instructions
41
+ 1. Review the graph overview above to understand the current state.
42
+ 2. Check structural gaps — these indicate missing connections in the research loop.
43
+ 3. Read the latest Episode node's "What's Next" section for planned work.
44
+ 4. Load prerequisite reading nodes listed in that Episode before starting.
45
+ 5. Decide today's direction based on graph state and open questions.
46
+
47
+ Use the \`list-nodes\` and \`read-node\` tools to explore specific nodes in detail.`;
48
+ return {
49
+ messages: [{ role: 'user', content: { type: 'text', text } }],
50
+ };
51
+ });
52
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerEpisodeCreation(server: McpServer): void;