@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EMDD Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# EMDD: Evolving Mindmap-Driven Development
|
|
2
|
+
|
|
3
|
+
> **A methodology that gives structure to R&D exploration through an AI-maintained evolving knowledge graph -- without killing the exploration itself.**
|
|
4
|
+
|
|
5
|
+
## Demo
|
|
6
|
+
|
|
7
|
+
<img src="docs/assets/demo.svg" alt="EMDD Demo" width="720">
|
|
8
|
+
|
|
9
|
+
## What is EMDD?
|
|
10
|
+
|
|
11
|
+
Too much structure suffocates research. Too little structure evaporates it. Existing approaches each solve one piece -- Zettelkasten gives bottom-up emergence, HDD gives hypothesis testing, DDP gives risk prioritization -- but none of them track the *relationships* between what you know, what you don't know, and what to explore next. EMDD fills that gap: it is a lightweight, AI-maintained knowledge graph that structures your exploration as it happens, surfaces blind spots, and remembers every dead end so you never walk it twice.
|
|
12
|
+
|
|
13
|
+
## Who is it for?
|
|
14
|
+
|
|
15
|
+
- **Solo researchers or small teams doing exploratory R&D** where the destination is unknown -- visual inspection R&D, architecture spikes, open-ended investigations.
|
|
16
|
+
- **Developers working with AI coding assistants** (e.g., Claude Code) who want the AI to maintain the knowledge structure while they retain judgment.
|
|
17
|
+
- **Anyone who has lost track of what they tried last week**, why they abandoned an approach, or which assumptions remain untested.
|
|
18
|
+
- **Teams that need more rigor than a scratchpad** but less overhead than a project management system.
|
|
19
|
+
- **Researchers who want to know what to explore next**, not just what they have already done.
|
|
20
|
+
|
|
21
|
+
## The EMDD Equation
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
EMDD = Zettelkasten's bottom-up emergence
|
|
25
|
+
+ DDP's risk-first validation
|
|
26
|
+
+ InfraNodus's structural gap detection
|
|
27
|
+
+ Graphiti's temporal evolution
|
|
28
|
+
─────────────────────────────────
|
|
29
|
+
Autonomous maintenance and suggestions by an AI agent
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Delegate cognitive load to the graph and the AI, but never delegate judgment.
|
|
33
|
+
|
|
34
|
+
## How It Works
|
|
35
|
+
|
|
36
|
+
EMDD has three roles. The **Researcher** exercises taste and judgment -- deciding which directions are worth pursuing, creating hypotheses, and making intuitive leaps the graph cannot derive on its own. The **Graph** is the living knowledge structure: a map of what is known, what remains unknown, and what has been tried. The **Agent** (AI) is the graph's gardener -- maintaining connections, detecting patterns and gaps, and suggesting what to explore next. Suggestions are always suggestions, never decisions.
|
|
37
|
+
|
|
38
|
+
### The Knowledge Graph
|
|
39
|
+
|
|
40
|
+
| Node Type | Purpose |
|
|
41
|
+
|-----------|---------|
|
|
42
|
+
| **Knowledge** | Confirmed facts, literature, domain rules |
|
|
43
|
+
| **Hypothesis** | Testable claims with confidence scores and kill criteria |
|
|
44
|
+
| **Experiment** | Units of work that validate or refute hypotheses |
|
|
45
|
+
| **Finding** | Facts or patterns discovered from experiments (observations, insights, negatives) |
|
|
46
|
+
| **Question** | Open research questions that need answers |
|
|
47
|
+
| **Decision** | Recorded decisions with rationale and alternatives considered |
|
|
48
|
+
| **Episode** | Record of one exploration session -- what was tried, what is next |
|
|
49
|
+
|
|
50
|
+
### The Lifecycle
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Question ──> Hypothesis ──> Experiment ──> Finding
|
|
54
|
+
│
|
|
55
|
+
┌───────────────┤
|
|
56
|
+
v v
|
|
57
|
+
Knowledge New Question
|
|
58
|
+
(promoted) (the cycle continues)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Hypotheses move through `PROPOSED -> TESTING -> SUPPORTED / REFUTED / REVISED`. Findings accumulate evidence. When a Finding has sufficient independent support, it is promoted to Knowledge. Refuted hypotheses are preserved -- the knowledge of *why* something failed is itself knowledge.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install -g emdd
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or use directly with npx:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx emdd <command>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Initialize an EMDD project
|
|
79
|
+
emdd init my-research
|
|
80
|
+
|
|
81
|
+
# Create your first nodes
|
|
82
|
+
cd my-research
|
|
83
|
+
emdd new question "what-causes-defects" --lang en
|
|
84
|
+
emdd new hypothesis "surface-cracks-from-stress"
|
|
85
|
+
|
|
86
|
+
# Link them
|
|
87
|
+
emdd link hyp-001 qst-001 spawned_from
|
|
88
|
+
|
|
89
|
+
# Check graph health
|
|
90
|
+
emdd lint
|
|
91
|
+
emdd health
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
See the [Quick Start Guide](docs/QUICK_START.md) for a full walkthrough.
|
|
95
|
+
|
|
96
|
+
## CLI Commands
|
|
97
|
+
|
|
98
|
+
| Command | Description |
|
|
99
|
+
|---------|-------------|
|
|
100
|
+
| `emdd init [path]` | Initialize a new EMDD project |
|
|
101
|
+
| `emdd new <type> <slug>` | Create a node (hypothesis, experiment, finding, ...) |
|
|
102
|
+
| `emdd link <source> <target> <relation>` | Add a link between nodes |
|
|
103
|
+
| `emdd update <node-id> --set key=value` | Update node frontmatter |
|
|
104
|
+
| `emdd done <episode-id> "<item>"` | Mark an episode item as done |
|
|
105
|
+
| `emdd lint [path]` | Validate schema and link integrity |
|
|
106
|
+
| `emdd health [path]` | Show graph health dashboard |
|
|
107
|
+
| `emdd check [path]` | Check consolidation triggers |
|
|
108
|
+
| `emdd promote [path]` | Identify promotion candidates |
|
|
109
|
+
| `emdd backlog [path]` | List incomplete items across all nodes |
|
|
110
|
+
| `emdd index [path]` | Generate `_index.md` |
|
|
111
|
+
| `emdd graph [path]` | Generate `_graph.mmd` (Mermaid) |
|
|
112
|
+
|
|
113
|
+
All commands support `--lang en|ko` for bilingual output.
|
|
114
|
+
|
|
115
|
+
## Phased Adoption
|
|
116
|
+
|
|
117
|
+
You do not need to adopt everything at once. Start lite and add structure as you need it.
|
|
118
|
+
|
|
119
|
+
| Phase | Duration | Node Types | Daily Overhead | You're doing it right when... |
|
|
120
|
+
|-------|----------|------------|----------------|-------------------------------|
|
|
121
|
+
| **Lite** | Week 1-2 | 4 (Hypothesis, Experiment, Finding, Episode) | ~15 min | You can open last week's Episode and immediately know what to do next |
|
|
122
|
+
| **Standard** | Week 3-4 | 6 (+Knowledge, Question) | ~25 min | Findings regularly get promoted to Knowledge |
|
|
123
|
+
| **Full** | Week 5+ | 7 (+Decision, all edge types, all ceremonies) | ~45 min | The graph tells you what to explore next |
|
|
124
|
+
|
|
125
|
+
See [section 11 of the specification](docs/spec/SPEC_EN.md#11-phased-adoption-guide) for details on each phase.
|
|
126
|
+
|
|
127
|
+
## Documentation
|
|
128
|
+
|
|
129
|
+
- [EMDD in 5 Minutes](docs/TUTORIAL.md) -- copy-paste tutorial
|
|
130
|
+
- [Quick Start Guide](docs/QUICK_START.md) -- get started in 15 minutes
|
|
131
|
+
- [Example Graph](examples/ml-backbone-selection/) -- a complete 13-node research narrative
|
|
132
|
+
- [Full Specification](docs/spec/SPEC_EN.md) -- the complete methodology
|
|
133
|
+
- [Philosophy](docs/PHILOSOPHY.md) -- why EMDD exists
|
|
134
|
+
- [Operations](docs/OPERATIONS.md) -- research loops, ceremonies, adoption
|
|
135
|
+
- [Tool Comparison](docs/COMPARISON.md) -- EMDD vs. Obsidian, Zettelkasten, DDP, HDD, nbdev, and more
|
|
136
|
+
- [Glossary](docs/GLOSSARY.md) -- definitions of all EMDD terms
|
|
137
|
+
- [한국어 스펙](docs/spec/SPEC_KO.md) -- Korean specification
|
|
138
|
+
|
|
139
|
+
## What EMDD is NOT
|
|
140
|
+
|
|
141
|
+
- **Not a project management tool.** No deadlines, no progress percentages -- it tracks what you know and what you don't.
|
|
142
|
+
- **Not a knowledge base.** The value is in the tensions, contradictions, and gaps between information, not in tidy organization.
|
|
143
|
+
- **Not SDD with a graph bolted on.** Direction emerges from exploration; the specification does not come first.
|
|
144
|
+
- **Not a personal knowledge management system.** It is project-scoped working memory, not a second brain for a lifetime.
|
|
145
|
+
- **Not outsourcing research to AI.** The AI prunes branches and points to empty ground. The researcher decides where to walk.
|
|
146
|
+
|
|
147
|
+
## Contributing
|
|
148
|
+
|
|
149
|
+
Contributions are welcome -- whether that is trying EMDD on your own project and reporting what worked, proposing changes to the spec, or building tooling. See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines and the RFC process.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
[MIT](LICENSE)
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { initCommand } from './commands/init.js';
|
|
4
|
+
import { newCommand } from './commands/new.js';
|
|
5
|
+
import { lintCommand } from './commands/lint.js';
|
|
6
|
+
import { healthCommand } from './commands/health.js';
|
|
7
|
+
import { checkCommand } from './commands/check.js';
|
|
8
|
+
import { promoteCommand } from './commands/promote.js';
|
|
9
|
+
import { updateCommand } from './commands/update.js';
|
|
10
|
+
import { linkCommand } from './commands/link.js';
|
|
11
|
+
import { doneCommand } from './commands/done.js';
|
|
12
|
+
import { indexCommand } from './commands/index.js';
|
|
13
|
+
import { graphCommand } from './commands/graph.js';
|
|
14
|
+
import { backlogCommand } from './commands/backlog.js';
|
|
15
|
+
import { resolveGraphDir } from './graph/loader.js';
|
|
16
|
+
import { startMcpServer } from './mcp-server/index.js';
|
|
17
|
+
const program = new Command();
|
|
18
|
+
program
|
|
19
|
+
.name('emdd')
|
|
20
|
+
.description('CLI for Evolving Mindmap-Driven Development')
|
|
21
|
+
.version('0.1.0');
|
|
22
|
+
program
|
|
23
|
+
.command('init [path]')
|
|
24
|
+
.description('Initialize EMDD project')
|
|
25
|
+
.option('--lang <locale>', 'Language', 'en')
|
|
26
|
+
.option('--tool <tool>', 'AI tool rules to generate (claude|cursor|windsurf|cline|copilot|all)', 'claude')
|
|
27
|
+
.action((path, options) => {
|
|
28
|
+
initCommand(path, options);
|
|
29
|
+
});
|
|
30
|
+
program
|
|
31
|
+
.command('new <type> <slug>')
|
|
32
|
+
.description('Create a new node')
|
|
33
|
+
.option('--path <path>', 'Project path')
|
|
34
|
+
.action(async (type, slug, options) => {
|
|
35
|
+
await newCommand(type, slug, options);
|
|
36
|
+
});
|
|
37
|
+
program
|
|
38
|
+
.command('lint [path]')
|
|
39
|
+
.description('Validate graph schema and links')
|
|
40
|
+
.action(async (path) => {
|
|
41
|
+
await lintCommand(path);
|
|
42
|
+
});
|
|
43
|
+
program
|
|
44
|
+
.command('health [path]')
|
|
45
|
+
.description('Show health dashboard')
|
|
46
|
+
.action(async (path) => {
|
|
47
|
+
await healthCommand(path);
|
|
48
|
+
});
|
|
49
|
+
program
|
|
50
|
+
.command('check [path]')
|
|
51
|
+
.description('Check consolidation triggers')
|
|
52
|
+
.action(async (path) => {
|
|
53
|
+
const graphDir = resolveGraphDir(path);
|
|
54
|
+
const result = await checkCommand(graphDir);
|
|
55
|
+
if (result.triggers.length === 0) {
|
|
56
|
+
console.log('No consolidation triggers active.');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
for (const trigger of result.triggers) {
|
|
60
|
+
console.log(`TRIGGER ${trigger.type} ${trigger.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
program
|
|
65
|
+
.command('promote [path]')
|
|
66
|
+
.description('Identify findings eligible for promotion')
|
|
67
|
+
.action(async (path) => {
|
|
68
|
+
const graphDir = resolveGraphDir(path);
|
|
69
|
+
const result = await promoteCommand(graphDir);
|
|
70
|
+
if (result.candidates.length === 0) {
|
|
71
|
+
console.log('No promotion candidates found.');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
for (const c of result.candidates) {
|
|
75
|
+
console.log(`CANDIDATE ${c.id} confidence=${c.confidence} supports=${c.supports}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
program
|
|
80
|
+
.command('update <node-id>')
|
|
81
|
+
.description('Update frontmatter fields on a node')
|
|
82
|
+
.option('--set <key=value...>', 'Key-value pairs to set')
|
|
83
|
+
.option('--path <path>', 'Project path')
|
|
84
|
+
.action(async (nodeId, options) => {
|
|
85
|
+
const graphDir = resolveGraphDir(options.path);
|
|
86
|
+
const updates = {};
|
|
87
|
+
if (options.set) {
|
|
88
|
+
const pairs = Array.isArray(options.set) ? options.set : [options.set];
|
|
89
|
+
for (const pair of pairs) {
|
|
90
|
+
const eqIdx = pair.indexOf('=');
|
|
91
|
+
if (eqIdx === -1) {
|
|
92
|
+
console.error(`Invalid key=value: ${pair}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
updates[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
await updateCommand(graphDir, nodeId, updates);
|
|
99
|
+
console.log(`Updated ${nodeId}`);
|
|
100
|
+
});
|
|
101
|
+
program
|
|
102
|
+
.command('link <source> <target> <relation>')
|
|
103
|
+
.description('Add a link between nodes')
|
|
104
|
+
.option('--path <path>', 'Project path')
|
|
105
|
+
.action(async (source, target, relation, options) => {
|
|
106
|
+
const graphDir = resolveGraphDir(options.path);
|
|
107
|
+
await linkCommand(graphDir, source, target, relation);
|
|
108
|
+
console.log(`Linked ${source} -> ${target} (${relation})`);
|
|
109
|
+
});
|
|
110
|
+
program
|
|
111
|
+
.command('done <episode-id> <item>')
|
|
112
|
+
.description('Mark a checklist item as done')
|
|
113
|
+
.option('--path <path>', 'Project path')
|
|
114
|
+
.action(async (episodeId, item, options) => {
|
|
115
|
+
const graphDir = resolveGraphDir(options.path);
|
|
116
|
+
await doneCommand(graphDir, episodeId, item);
|
|
117
|
+
console.log(`Done: ${item}`);
|
|
118
|
+
});
|
|
119
|
+
program
|
|
120
|
+
.command('index [path]')
|
|
121
|
+
.description('Generate _index.md for the graph')
|
|
122
|
+
.action(async (path) => {
|
|
123
|
+
const graphDir = resolveGraphDir(path);
|
|
124
|
+
const result = await indexCommand(graphDir);
|
|
125
|
+
console.log(`Index generated: ${result.nodeCount} nodes`);
|
|
126
|
+
});
|
|
127
|
+
program
|
|
128
|
+
.command('graph [path]')
|
|
129
|
+
.description('Generate _graph.mmd Mermaid diagram')
|
|
130
|
+
.action(async (path) => {
|
|
131
|
+
const graphDir = resolveGraphDir(path);
|
|
132
|
+
const result = await graphCommand(graphDir);
|
|
133
|
+
console.log(`Graph generated: ${result.nodeCount} nodes, ${result.edgeCount} edges`);
|
|
134
|
+
});
|
|
135
|
+
program
|
|
136
|
+
.command('backlog')
|
|
137
|
+
.description('Show unchecked backlog items')
|
|
138
|
+
.option('--path <path>', 'Project path')
|
|
139
|
+
.option('--status <status>', 'Filter by status')
|
|
140
|
+
.action(async (options) => {
|
|
141
|
+
const graphDir = resolveGraphDir(options.path);
|
|
142
|
+
const result = await backlogCommand(graphDir, options.status);
|
|
143
|
+
if (result.items.length === 0) {
|
|
144
|
+
console.log('No backlog items.');
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
for (const item of result.items) {
|
|
148
|
+
console.log(`[ ] ${item.episodeId} ${item.text}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
program
|
|
153
|
+
.command('mcp')
|
|
154
|
+
.description('Start MCP server over stdio')
|
|
155
|
+
.action(async () => {
|
|
156
|
+
await startMcpServer();
|
|
157
|
+
});
|
|
158
|
+
program.parse();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
export async function backlogCommand(graphDir, _statusFilter) {
|
|
6
|
+
const episodeDir = join(graphDir, 'episodes');
|
|
7
|
+
const pattern = join(episodeDir, '*.md');
|
|
8
|
+
const files = await glob(pattern, { nodir: true });
|
|
9
|
+
const items = [];
|
|
10
|
+
for (const file of files.sort()) {
|
|
11
|
+
let content;
|
|
12
|
+
try {
|
|
13
|
+
content = readFileSync(file, 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = matter(content);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const episodeId = parsed.data?.id ?? '';
|
|
26
|
+
const body = parsed.content;
|
|
27
|
+
for (const line of body.split('\n')) {
|
|
28
|
+
const unchecked = line.match(/^- \[ \]\s+(.+)/);
|
|
29
|
+
if (unchecked) {
|
|
30
|
+
items.push({ text: unchecked[1].trim(), episodeId });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { items };
|
|
35
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { checkConsolidation } from '../graph/operations.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check consolidation triggers in the graph.
|
|
4
|
+
* Delegates to the pure operations.checkConsolidation function.
|
|
5
|
+
*/
|
|
6
|
+
export async function checkCommand(graphDir) {
|
|
7
|
+
return checkConsolidation(graphDir);
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mark a checklist item as done in an episode node.
|
|
3
|
+
*
|
|
4
|
+
* Finds `- [ ] {item}` in the body and replaces with `- [x] {item}`.
|
|
5
|
+
* Throws if the item is not found.
|
|
6
|
+
*/
|
|
7
|
+
export declare function doneCommand(graphDir: string, episodeId: string, item: string): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { loadGraph } from '../graph/loader.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mark a checklist item as done in an episode node.
|
|
5
|
+
*
|
|
6
|
+
* Finds `- [ ] {item}` in the body and replaces with `- [x] {item}`.
|
|
7
|
+
* Throws if the item is not found.
|
|
8
|
+
*/
|
|
9
|
+
export async function doneCommand(graphDir, episodeId, item) {
|
|
10
|
+
const graph = await loadGraph(graphDir);
|
|
11
|
+
const node = graph.nodes.get(episodeId);
|
|
12
|
+
if (!node) {
|
|
13
|
+
throw new Error(`Node not found: ${episodeId}`);
|
|
14
|
+
}
|
|
15
|
+
const filePath = node.path;
|
|
16
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
17
|
+
// Find lines matching the unchecked item
|
|
18
|
+
const lines = raw.split('\n');
|
|
19
|
+
const matches = [];
|
|
20
|
+
for (let i = 0; i < lines.length; i++) {
|
|
21
|
+
if (lines[i].includes('- [ ]') && lines[i].includes(item)) {
|
|
22
|
+
matches.push(i);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (matches.length === 0) {
|
|
26
|
+
throw new Error(`Item not found in ${episodeId}: ${item}`);
|
|
27
|
+
}
|
|
28
|
+
if (matches.length > 1) {
|
|
29
|
+
throw new Error(`Multiple matches for '${item}' in ${episodeId}`);
|
|
30
|
+
}
|
|
31
|
+
// Replace the matched line
|
|
32
|
+
lines[matches[0]] = lines[matches[0]].replace('- [ ]', '- [x]');
|
|
33
|
+
fs.writeFileSync(filePath, lines.join('\n'));
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { loadGraph } from '../graph/loader.js';
|
|
4
|
+
import { generateMermaid } from '../graph/mermaid.js';
|
|
5
|
+
export async function graphCommand(graphDir) {
|
|
6
|
+
const graph = await loadGraph(graphDir);
|
|
7
|
+
const content = generateMermaid(graph);
|
|
8
|
+
writeFileSync(join(graphDir, '_graph.mmd'), content, 'utf-8');
|
|
9
|
+
let edgeCount = 0;
|
|
10
|
+
for (const node of graph.nodes.values()) {
|
|
11
|
+
for (const link of node.links) {
|
|
12
|
+
if (graph.nodes.has(link.target)) {
|
|
13
|
+
edgeCount++;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { nodeCount: graph.nodes.size, edgeCount };
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function healthCommand(targetPath: string | undefined): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolveGraphDir } from '../graph/loader.js';
|
|
2
|
+
import { getHealth } from '../graph/operations.js';
|
|
3
|
+
import { NODE_TYPES } from '../graph/types.js';
|
|
4
|
+
import { t } from '../i18n/index.js';
|
|
5
|
+
export async function healthCommand(targetPath) {
|
|
6
|
+
const graphDir = resolveGraphDir(targetPath);
|
|
7
|
+
const report = await getHealth(graphDir);
|
|
8
|
+
const avgConf = report.avgConfidence !== null ? report.avgConfidence.toFixed(2) : 'N/A';
|
|
9
|
+
const linkDensity = report.linkDensity.toFixed(2);
|
|
10
|
+
// Output
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log(`=== ${t('health.title')} ===`);
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(`${t('health.total_nodes')}: ${report.totalNodes}`);
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(`${t('health.by_type')}:`);
|
|
17
|
+
for (const nodeType of NODE_TYPES) {
|
|
18
|
+
const count = report.byType[nodeType] ?? 0;
|
|
19
|
+
if (count > 0) {
|
|
20
|
+
console.log(` ${nodeType}: ${count}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const hypStatus = report.statusDistribution['hypothesis'];
|
|
24
|
+
if (hypStatus && Object.keys(hypStatus).length > 0) {
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(`${t('health.hypothesis_status')}:`);
|
|
27
|
+
for (const [status, count] of Object.entries(hypStatus).sort()) {
|
|
28
|
+
console.log(` ${status}: ${count}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(`${t('health.avg_confidence')}: ${avgConf}`);
|
|
33
|
+
console.log(`${t('health.open_questions')}: ${report.openQuestions}`);
|
|
34
|
+
console.log(`${t('health.link_density')}: ${linkDensity}`);
|
|
35
|
+
console.log('');
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { loadGraph } from '../graph/loader.js';
|
|
4
|
+
import { generateIndex } from '../graph/index-generator.js';
|
|
5
|
+
export async function indexCommand(graphDir) {
|
|
6
|
+
const graph = await loadGraph(graphDir);
|
|
7
|
+
const content = generateIndex(graph);
|
|
8
|
+
writeFileSync(join(graphDir, '_index.md'), content, 'utf-8');
|
|
9
|
+
return { nodeCount: graph.nodes.size };
|
|
10
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { NODE_TYPE_DIRS } from '../graph/types.js';
|
|
4
|
+
import { t } from '../i18n/index.js';
|
|
5
|
+
import { generateRulesFile } from '../rules/generators.js';
|
|
6
|
+
export function initCommand(targetPath, options) {
|
|
7
|
+
const target = path.resolve(targetPath ?? '.');
|
|
8
|
+
const graphDir = path.join(target, 'graph');
|
|
9
|
+
const configPath = path.join(target, '.emdd.yml');
|
|
10
|
+
const lang = options.lang ?? 'en';
|
|
11
|
+
const tool = options.tool ?? 'claude';
|
|
12
|
+
// Check if already initialized (graph dir check)
|
|
13
|
+
if (fs.existsSync(graphDir)) {
|
|
14
|
+
console.log(t('init.already_exists', { path: target }));
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
// Create graph/ and all subdirectories
|
|
18
|
+
fs.mkdirSync(graphDir, { recursive: true });
|
|
19
|
+
for (const dir of Object.values(NODE_TYPE_DIRS)) {
|
|
20
|
+
fs.mkdirSync(path.join(graphDir, dir), { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
// Create .emdd.yml config
|
|
23
|
+
const config = [
|
|
24
|
+
`lang: ${lang}`,
|
|
25
|
+
`version: "1.0"`,
|
|
26
|
+
'',
|
|
27
|
+
].join('\n');
|
|
28
|
+
fs.writeFileSync(configPath, config, 'utf-8');
|
|
29
|
+
console.log(t('init.success', { path: target }));
|
|
30
|
+
console.log(t('init.next_steps'));
|
|
31
|
+
}
|
|
32
|
+
// Generate tool-specific rules files
|
|
33
|
+
const result = generateRulesFile(tool, target);
|
|
34
|
+
for (const created of result.created) {
|
|
35
|
+
console.log(`Created ${created}`);
|
|
36
|
+
}
|
|
37
|
+
for (const skipped of result.skipped) {
|
|
38
|
+
console.log(`Skipped (already exists): ${skipped}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createEdge } from '../graph/operations.js';
|
|
2
|
+
/**
|
|
3
|
+
* Add a link from source node to target node with the given relation.
|
|
4
|
+
* Delegates to the pure operations.createEdge function.
|
|
5
|
+
*/
|
|
6
|
+
export async function linkCommand(graphDir, source, target, relation) {
|
|
7
|
+
await createEdge(graphDir, source, target, relation);
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function lintCommand(targetPath: string | undefined): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveGraphDir, loadGraph } from '../graph/loader.js';
|
|
2
|
+
import { lintGraph } from '../graph/validator.js';
|
|
3
|
+
import { t } from '../i18n/index.js';
|
|
4
|
+
export async function lintCommand(targetPath) {
|
|
5
|
+
const graphDir = resolveGraphDir(targetPath);
|
|
6
|
+
const graph = await loadGraph(graphDir);
|
|
7
|
+
const errors = lintGraph(graph);
|
|
8
|
+
const realErrors = errors.filter(e => e.severity === 'error');
|
|
9
|
+
const warnings = errors.filter(e => e.severity === 'warning');
|
|
10
|
+
if (realErrors.length === 0 && warnings.length === 0) {
|
|
11
|
+
console.log(t('lint.clean'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
for (const err of realErrors) {
|
|
15
|
+
console.log(`ERROR ${err.nodeId} ${err.field} ${err.message}`);
|
|
16
|
+
}
|
|
17
|
+
for (const warn of warnings) {
|
|
18
|
+
console.log(`WARN ${warn.nodeId} ${warn.field} ${warn.message}`);
|
|
19
|
+
}
|
|
20
|
+
if (realErrors.length > 0) {
|
|
21
|
+
console.log(t('lint.errors_found', { count: String(realErrors.length) }));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (warnings.length > 0) {
|
|
25
|
+
console.log(t('lint.warnings_found', { count: String(warnings.length) }));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { resolveGraphDir } from '../graph/loader.js';
|
|
2
|
+
import { createNode } from '../graph/operations.js';
|
|
3
|
+
import { NODE_TYPES } from '../graph/types.js';
|
|
4
|
+
import { t } from '../i18n/index.js';
|
|
5
|
+
export async function newCommand(type, slug, options) {
|
|
6
|
+
// Validate type early for user-facing error message
|
|
7
|
+
if (!NODE_TYPES.includes(type)) {
|
|
8
|
+
console.error(t('new.invalid_type', { type, valid: NODE_TYPES.join(', ') }));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const graphDir = resolveGraphDir(options.path);
|
|
12
|
+
const result = await createNode(graphDir, type, slug);
|
|
13
|
+
console.log(t('new.created', { type: result.type, id: result.id }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PromoteCandidate } from '../graph/types.js';
|
|
2
|
+
export interface PromoteResult {
|
|
3
|
+
candidates: PromoteCandidate[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Identify findings eligible for promotion to knowledge.
|
|
7
|
+
* Delegates to the pure operations.getPromotionCandidates function.
|
|
8
|
+
*/
|
|
9
|
+
export declare function promoteCommand(graphDir: string): Promise<PromoteResult>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getPromotionCandidates } from '../graph/operations.js';
|
|
2
|
+
/**
|
|
3
|
+
* Identify findings eligible for promotion to knowledge.
|
|
4
|
+
* Delegates to the pure operations.getPromotionCandidates function.
|
|
5
|
+
*/
|
|
6
|
+
export async function promoteCommand(graphDir) {
|
|
7
|
+
const candidates = await getPromotionCandidates(graphDir);
|
|
8
|
+
return { candidates };
|
|
9
|
+
}
|