monomind 2.0.3 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -92
- package/package.json +11 -7
- package/packages/@monomind/cli/.claude/helpers/control-start.cjs +13 -20
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +109 -6
- package/packages/@monomind/cli/.claude/helpers/graphify-freshen.cjs +19 -17
- package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +60 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +99 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +27 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +18 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/loops-status-handler.cjs +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +33 -4
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +75 -45
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +0 -12
- package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +53 -22
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +75 -4
- package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +94 -15
- package/packages/@monomind/cli/.claude/helpers/session.cjs +6 -1
- package/packages/@monomind/cli/.claude/helpers/statusline.cjs +10 -10
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +3 -3
- package/packages/@monomind/cli/.claude/helpers/utils/agent-registrations.cjs +41 -0
- package/packages/@monomind/cli/.claude/helpers/utils/fs-helpers.cjs +183 -0
- package/packages/@monomind/cli/.claude/helpers/utils/micro-agents.cjs +10 -6
- package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +73 -2
- package/packages/@monomind/cli/.claude/helpers/utils/telemetry.cjs +35 -19
- package/packages/@monomind/cli/README.md +74 -92
- package/packages/@monomind/cli/bin/cli.js +10 -1
- package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +9 -15
- package/packages/@monomind/cli/dist/src/commands/browse.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +20 -4
- package/packages/@monomind/cli/dist/src/commands/completions.js +66 -261
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +5 -0
- package/packages/@monomind/cli/dist/src/commands/doctor.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +7 -0
- package/packages/@monomind/cli/dist/src/commands/index.js +3 -0
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -6
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +12 -6
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +6 -13
- package/packages/@monomind/cli/dist/src/commands/monograph.js +3 -3
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/commands/org.js +321 -127
- package/packages/@monomind/cli/dist/src/commands/platforms.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/start.js +142 -37
- package/packages/@monomind/cli/dist/src/commands/tokens.js +21 -3
- package/packages/@monomind/cli/dist/src/config-adapter.js +37 -17
- package/packages/@monomind/cli/dist/src/index.js +64 -28
- package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +44 -11
- package/packages/@monomind/cli/dist/src/init/executor.js +93 -55
- package/packages/@monomind/cli/dist/src/mcp-client.d.ts +8 -3
- package/packages/@monomind/cli/dist/src/mcp-client.js +37 -3
- package/packages/@monomind/cli/dist/src/mcp-server.js +8 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +10 -5
- package/packages/@monomind/cli/dist/src/mcp-tools/auto-install.d.ts +8 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +31 -31
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +30 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +56 -35
- package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +9 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +56 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +88 -41
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +36 -36
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +6 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/types.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/types.js +36 -1
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +13 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +66 -14
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +99 -3
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +20 -0
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +73 -0
- package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +46 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +201 -18
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +10 -5
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +138 -8
- package/packages/@monomind/cli/dist/src/orgrt/inbox.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/policy.js +58 -5
- package/packages/@monomind/cli/dist/src/orgrt/provider.js +1 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.d.ts +13 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.js +48 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.js +39 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +10 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.js +24 -1
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +108 -0
- package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +52 -52
- package/packages/@monomind/cli/dist/src/output.d.ts +29 -29
- package/packages/@monomind/cli/dist/src/output.js +22 -7
- package/packages/@monomind/cli/dist/src/parser.d.ts +32 -0
- package/packages/@monomind/cli/dist/src/parser.js +130 -5
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +13 -1
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +31 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +21 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +60 -7
- package/packages/@monomind/cli/dist/src/services/crash-reporter.js +2 -1
- package/packages/@monomind/cli/dist/src/types.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +243 -49
- package/packages/@monomind/cli/dist/src/ui/orgs-files.js +192 -0
- package/packages/@monomind/cli/dist/src/ui/orgs.html +79 -11
- package/packages/@monomind/cli/dist/src/ui/server.mjs +346 -139
- package/packages/@monomind/cli/dist/src/utils/input-guards.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/utils/input-guards.js +39 -5
- package/packages/@monomind/cli/package.json +15 -15
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<h1 align="center">Monomind</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
9
|
-
|
|
8
|
+
<strong>An open-source MCP server that extends Claude Code with a codebase knowledge graph, persistent memory, and multi-agent coordination.</strong><br/>
|
|
9
|
+
MIT licensed · Fully local · No data leaves your machine
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -30,126 +30,105 @@
|
|
|
30
30
|
|
|
31
31
|
## What is Monomind?
|
|
32
32
|
|
|
33
|
-
Claude Code
|
|
33
|
+
Monomind is an **open-source CLI and MCP server** that plugs into Claude Code via the standard [Model Context Protocol](https://modelcontextprotocol.io/). It adds capabilities that Claude Code doesn't ship with out of the box:
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
- **Codebase knowledge graph** — tree-sitter parses your code into a SQLite-backed graph of files, functions, classes, and their relationships. Query imports, callers, and blast radius before making changes.
|
|
36
|
+
- **Persistent memory** — a JSON pattern store with episodic recall that survives across sessions. Agents and orgs share context without re-prompting.
|
|
37
|
+
- **Multi-agent coordination** — in-session, spawn ad-hoc agent teams via Claude Code's Task tool; for persistent background work, `monomind org run` starts a real SDK-backed daemon with policy-gated role agents and a live dashboard.
|
|
38
|
+
- **Reusable slash commands** — 30+ development workflows (build, review, debug, TDD, architecture) available as `/mastermind:*` commands inside Claude Code.
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# Or run the autonomous code improvement loop
|
|
43
|
-
/mastermind:autodev --tillend --focus security
|
|
41
|
+
npm install -g monomind # MIT licensed, 0 external API calls
|
|
42
|
+
cd your-project && monomind init
|
|
43
|
+
claude mcp add monomind npx monomind mcp start
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
### Trust & Security
|
|
47
|
+
|
|
48
|
+
| Concern | Answer |
|
|
49
|
+
|---|---|
|
|
50
|
+
| **License** | [MIT](LICENSE) — use it however you want |
|
|
51
|
+
| **Data privacy** | Everything runs locally. No telemetry, no phone-home, no external API calls from Monomind itself. Your code stays on your machine. |
|
|
52
|
+
| **Dependencies** | Standard npm packages (tree-sitter, sql.js, zod). No native binaries. No post-install scripts that download code. |
|
|
53
|
+
| **Permissions** | Registers as an MCP server — Claude Code controls what tools are available and prompts you before executing anything sensitive. |
|
|
54
|
+
| **Source** | Fully open. Read every line at [github.com/monoes/monomind](https://github.com/monoes/monomind). |
|
|
55
|
+
| **Maintenance** | Active development, regular releases on npm. |
|
|
47
56
|
|
|
48
57
|
---
|
|
49
58
|
|
|
50
59
|
## 🏢 Autonomous Organizations
|
|
51
60
|
|
|
52
|
-
> **This is the headline feature.**
|
|
61
|
+
> **This is the headline feature.** `monomind org run` starts a persistent, SDK-backed daemon that runs an autonomous agent organization — roles, hierarchy, policy-gated tool access, a live dashboard — until you stop it.
|
|
53
62
|
|
|
54
63
|
### The idea
|
|
55
64
|
|
|
56
|
-
Every business function needs a team.
|
|
65
|
+
Every business function needs a team. Define the org once as a JSON file — goal, roles, who reports to whom, per-role tool/file/budget policy — then run it as a real background daemon backed by the Claude Agent SDK. It persists across sessions, serves a live WebSocket dashboard, and can discover and message other Monomind orgs running on the same machine.
|
|
57
66
|
|
|
58
67
|
```mermaid
|
|
59
68
|
flowchart TD
|
|
60
69
|
U(["You"])
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
BOSS["Boss Agent\
|
|
64
|
-
W["Writer
|
|
70
|
+
DEF["org.json\nGoal + roles + policy"]
|
|
71
|
+
RUN["monomind org run\nStart SDK daemon"]
|
|
72
|
+
BOSS["Boss Agent\nAgent SDK session"]
|
|
73
|
+
W["Writer"]
|
|
65
74
|
S["SEO Specialist"]
|
|
66
75
|
R["Reviewer"]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
MEM[("Persistent\nMemory")]
|
|
76
|
+
DASH[("Live Dashboard\n:4243")]
|
|
77
|
+
XORG[("Other orgs\ncross-process")]
|
|
70
78
|
|
|
71
|
-
U -->
|
|
79
|
+
U --> DEF --> RUN --> BOSS
|
|
72
80
|
BOSS -->|spawns| W
|
|
73
81
|
BOSS -->|spawns| S
|
|
74
82
|
BOSS -->|spawns| R
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
W <-->|stores output| MEM
|
|
78
|
-
S <-->|reads context| MEM
|
|
83
|
+
RUN <-->|serves| DASH
|
|
84
|
+
RUN <-.->|--cross-process| XORG
|
|
79
85
|
|
|
80
86
|
style BOSS fill:#00D2AA22,stroke:#00D2AA
|
|
81
|
-
style
|
|
82
|
-
style
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Two commands to a running org
|
|
86
|
-
|
|
87
|
-
**Step 1 — Design it:**
|
|
88
|
-
|
|
87
|
+
style DASH fill:#F59E0B22,stroke:#F59E0B
|
|
88
|
+
style XORG fill:#8B5CF622,stroke:#8B5CF6
|
|
89
89
|
```
|
|
90
|
-
/ mastermind:createorg content-team
|
|
91
|
-
"Build and publish 3 blog posts per week on AI dev tools"
|
|
92
|
-
|
|
93
|
-
Deriving roles from goal...
|
|
94
|
-
|
|
95
|
-
╔ ORG: content-team
|
|
96
|
-
║ TOPOLOGY: star (5 roles)
|
|
97
90
|
|
|
98
|
-
|
|
99
|
-
writer → Content Creator
|
|
100
|
-
reviewer → reviewer
|
|
101
|
-
marketer → Growth Hacker
|
|
102
|
-
seo → SEO Specialist
|
|
91
|
+
### Run one
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
|
|
93
|
+
```bash
|
|
94
|
+
# .monomind/orgs/<name>.json defines the org: goal, roles, policy.
|
|
95
|
+
# See .monomind/orgs/sample-team.json in a fresh `monomind init` for a working example.
|
|
106
96
|
|
|
107
|
-
|
|
108
|
-
→ Run: /mastermind:runorg --org content-team
|
|
109
|
-
```
|
|
97
|
+
monomind org run content-team --task "Build and publish 3 blog posts per week"
|
|
110
98
|
|
|
111
|
-
|
|
99
|
+
# ✓ Live dashboard: http://localhost:4243
|
|
100
|
+
# ✓ Boss agent (Claude Agent SDK session) spawns, reads the org goal,
|
|
101
|
+
# assigns work to role agents, coordinates until the task completes
|
|
102
|
+
# or you stop it.
|
|
112
103
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# Boss agent spawns in background
|
|
117
|
-
# Coordinates all roles via shared task board
|
|
118
|
-
# Checkpoints every 30 minutes
|
|
119
|
-
# Loops until you stop it
|
|
104
|
+
monomind org status content-team # runtime state
|
|
105
|
+
monomind org stop content-team # request a graceful stop
|
|
106
|
+
monomind org list # every org + status
|
|
120
107
|
```
|
|
121
108
|
|
|
122
109
|
### What runs under the hood
|
|
123
110
|
|
|
124
111
|
| What | How |
|
|
125
112
|
|---|---|
|
|
126
|
-
| **
|
|
127
|
-
| **
|
|
128
|
-
| **
|
|
129
|
-
| **
|
|
130
|
-
| **
|
|
131
|
-
| **Governance** | `auto` (free), `board` (approve sensitive), `strict` (approve all external actions) |
|
|
132
|
-
|
|
133
|
-
### Topology is auto-derived
|
|
134
|
-
|
|
135
|
-
```mermaid
|
|
136
|
-
graph LR
|
|
137
|
-
A["1-3 roles"] -->|mesh| B["All-to-all\ndirect comms"]
|
|
138
|
-
C["4-6 roles"] -->|star| D["Boss to workers\nfan-out"]
|
|
139
|
-
E["7+ roles"] -->|hierarchical| F["Boss to leads to workers\nmiddle management"]
|
|
140
|
-
```
|
|
113
|
+
| **OrgDaemon** | Hosts one or more orgs in a single process; real Claude Agent SDK sessions per role, not simulated |
|
|
114
|
+
| **PolicyEngine** | Per-role gates on tool access, file read/write scope, web access, token budget — enforced, with a full audit trail |
|
|
115
|
+
| **Live dashboard** | WebSocket-served at `:4243` by default (`--port` to change, `--serve=false` to disable) |
|
|
116
|
+
| **Cross-process comms** | `--cross-process` (default on) lets orgs on different `monomind` processes/projects discover and message each other |
|
|
117
|
+
| **Scheduling** | `monomind org serve` hosts orgs whose definition has a `schedule` field, running them on interval |
|
|
141
118
|
|
|
142
119
|
### Org management commands
|
|
143
120
|
|
|
144
121
|
```bash
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
122
|
+
monomind org run <name> [--task "..."] [--port N] [--cross-process] # start a daemon
|
|
123
|
+
monomind org stop <name> # request a running org to stop
|
|
124
|
+
monomind org status [name] # runtime state for one or all orgs
|
|
125
|
+
monomind org list # list every org + status
|
|
126
|
+
monomind org serve [--port N] # host-only mode, runs scheduled orgs
|
|
127
|
+
monomind org delete <name> # remove an org
|
|
151
128
|
```
|
|
152
129
|
|
|
130
|
+
> **Note:** the older `/mastermind:createorg` + `/mastermind:runorg` prompt-orchestrated flow is deprecated — it has no delivery guarantees or ground-truth event stream. It still runs for orgs not yet migrated, but new orgs should use `monomind org run` directly against a hand-authored `.monomind/orgs/<name>.json`.
|
|
131
|
+
|
|
153
132
|
---
|
|
154
133
|
|
|
155
134
|
## ⚡ The Autonomous Build Loop
|
|
@@ -206,11 +185,11 @@ claude mcp add monomind npx monomind mcp start
|
|
|
206
185
|
monomind doctor --fix
|
|
207
186
|
```
|
|
208
187
|
|
|
209
|
-
Open Claude Code. You now have
|
|
188
|
+
Open Claude Code. You now have 60+ slash commands available:
|
|
210
189
|
|
|
211
190
|
```bash
|
|
212
191
|
/mastermind:autodev --tillend # start autonomous code loop
|
|
213
|
-
|
|
192
|
+
monomind org run my-team # run your first AI org (see .monomind/orgs/sample-team.json)
|
|
214
193
|
/mastermind:help # show all commands
|
|
215
194
|
```
|
|
216
195
|
|
|
@@ -262,7 +241,7 @@ Before touching any file, Monomind queries **Monograph** — a SQLite-backed kno
|
|
|
262
241
|
|
|
263
242
|
## 🎣 Hooks & Workers
|
|
264
243
|
|
|
265
|
-
Monomind wires
|
|
244
|
+
Monomind wires 29 hook subcommands into Claude Code across edit, task, command, and session lifecycle events — logging patterns, routing agents, and feeding the intelligence system.
|
|
266
245
|
|
|
267
246
|
```mermaid
|
|
268
247
|
flowchart LR
|
|
@@ -296,7 +275,7 @@ const result = await fence.detect(userInput);
|
|
|
296
275
|
|
|
297
276
|
---
|
|
298
277
|
|
|
299
|
-
## 📋
|
|
278
|
+
## 📋 60+ Slash Commands
|
|
300
279
|
|
|
301
280
|
Everything runs from inside Claude Code via slash commands. Here's the highlight reel:
|
|
302
281
|
|
|
@@ -315,9 +294,9 @@ Everything runs from inside Claude Code via slash commands. Here's the highlight
|
|
|
315
294
|
### Organizations
|
|
316
295
|
| Command | What it does |
|
|
317
296
|
|---|---|
|
|
318
|
-
|
|
|
319
|
-
|
|
|
320
|
-
|
|
|
297
|
+
| `monomind org run <name>` | Start an org as a real SDK-backed daemon |
|
|
298
|
+
| `monomind org status` / `list` | Runtime state for one or all orgs |
|
|
299
|
+
| `monomind org stop <name>` | Request a graceful stop |
|
|
321
300
|
| `/mastermind:approve` | Action pending approval requests |
|
|
322
301
|
|
|
323
302
|
### Business Domains
|
|
@@ -329,7 +308,7 @@ Everything runs from inside Claude Code via slash commands. Here's the highlight
|
|
|
329
308
|
| `/mastermind:finance` | Budgets, invoicing, modeling |
|
|
330
309
|
| `/mastermind:ops` | Operations and workflow automation |
|
|
331
310
|
|
|
332
|
-
**[→ Full reference (
|
|
311
|
+
**[→ Full reference (60+ commands)](https://monoes.github.io/monomind/#slash)**
|
|
333
312
|
|
|
334
313
|
---
|
|
335
314
|
|
|
@@ -351,24 +330,27 @@ graph TD
|
|
|
351
330
|
CC["Claude Code"]
|
|
352
331
|
MCP["MCP Server\nmonomind mcp start"]
|
|
353
332
|
D["Background Workers\n(@monomind/hooks, in-process)"]
|
|
333
|
+
ORG["OrgDaemon\nmonomind org run\nreal SDK sessions"]
|
|
354
334
|
|
|
355
|
-
CC <-->|"MCP tools: monograph, memory
|
|
335
|
+
CC <-->|"MCP tools: monograph, memory"| MCP
|
|
356
336
|
MCP <--> D
|
|
357
337
|
|
|
358
338
|
D --> ADB[("Memory store\npatterns + episodes")]
|
|
359
339
|
D --> MG[("Monograph\ncode graph")]
|
|
360
|
-
D --> HK["Hooks\
|
|
361
|
-
D --> SW["Swarm\n4 topologies\n3 consensus strategies"]
|
|
340
|
+
D --> HK["Hooks\n29 subcommands"]
|
|
362
341
|
|
|
363
|
-
CC -->|"Task tool - spawns agents"| AG["
|
|
342
|
+
CC -->|"Task tool - spawns agents"| AG["In-session agents\narchitect, coder\ntester, reviewer\nsecurity, perf"]
|
|
364
343
|
AG <-->|reads and writes| ADB
|
|
344
|
+
ORG -->|"spawns, policy-gated"| RA["Role agents"]
|
|
345
|
+
ORG <-->|reads and writes| ADB
|
|
365
346
|
|
|
366
347
|
style CC fill:#00D2AA22,stroke:#00D2AA
|
|
367
348
|
style AG fill:#8B5CF622,stroke:#8B5CF6
|
|
368
349
|
style ADB fill:#F59E0B22,stroke:#F59E0B
|
|
350
|
+
style ORG fill:#F59E0B22,stroke:#F59E0B
|
|
369
351
|
```
|
|
370
352
|
|
|
371
|
-
**Claude Code
|
|
353
|
+
**Claude Code's Task tool drives in-session multi-agent work; `monomind org run` drives persistent background orgs.** Your data never leaves your machine.
|
|
372
354
|
|
|
373
355
|
---
|
|
374
356
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monomind",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.3",
|
|
4
|
+
"description": "Open-source CLI extension for Claude Code. Adds an MCP server with a codebase knowledge graph, persistent memory, multi-agent coordination, and reusable slash commands. MIT licensed, runs locally, no data leaves your machine.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -60,15 +60,20 @@
|
|
|
60
60
|
"v1:security": "npm run security:audit && npm run security:test"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.207",
|
|
63
64
|
"@monoes/monobrowse": "^1.0.3",
|
|
64
65
|
"@monoes/monograph": "^1.4.0",
|
|
66
|
+
"@noble/ed25519": "^2.1.0",
|
|
65
67
|
"mammoth": "^1.12.0",
|
|
66
68
|
"pdf-parse": "^2.4.5",
|
|
67
|
-
"semver": "^7.6.0"
|
|
69
|
+
"semver": "^7.6.0",
|
|
70
|
+
"ws": "^8.21.0",
|
|
71
|
+
"yaml": "^2.8.0",
|
|
72
|
+
"zod": "^3.25.0"
|
|
68
73
|
},
|
|
69
74
|
"overrides": {
|
|
70
75
|
"hono": ">=4.12.21",
|
|
71
|
-
"ws": "
|
|
76
|
+
"ws": "$ws",
|
|
72
77
|
"axios": ">=1.16.0",
|
|
73
78
|
"qs": ">=6.15.2",
|
|
74
79
|
"esbuild": ">=0.28.1",
|
|
@@ -135,10 +140,9 @@
|
|
|
135
140
|
"devDependencies": {
|
|
136
141
|
"@types/node": "^20.0.0",
|
|
137
142
|
"tsx": "^4.21.0",
|
|
138
|
-
"typescript": "^
|
|
143
|
+
"typescript": "^7.0.2",
|
|
139
144
|
"vite": ">=8.0.16",
|
|
140
|
-
"vitest": "^4.1.4"
|
|
141
|
-
"zod": "^4.3.6"
|
|
145
|
+
"vitest": "^4.1.4"
|
|
142
146
|
},
|
|
143
147
|
"engines": {
|
|
144
148
|
"node": ">=20.0.0"
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { spawn } = require('child_process');
|
|
15
|
+
const { claimLock, releaseLock } = require('./utils/fs-helpers.cjs');
|
|
15
16
|
|
|
16
17
|
const CWD = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
17
18
|
const STATUS_FILE = path.join(CWD, '.monomind', 'control.json');
|
|
@@ -111,31 +112,23 @@ const LOCK_FILE = path.join(CWD, '.monomind', 'control.lock');
|
|
|
111
112
|
* Atomically claim the spawn lock. Concurrent hook events (a busy session can
|
|
112
113
|
* fire dozens of control-starts at once) must not each spawn a server — the
|
|
113
114
|
* loser processes exit and let the winner's server come up. A stale lock
|
|
114
|
-
* (
|
|
115
|
+
* (older than 30s) is broken and re-claimed.
|
|
116
|
+
*
|
|
117
|
+
* P2-25: previously this broke a stale lock via unlink-then-writeFileSync(wx),
|
|
118
|
+
* which is a TOCTOU race — a second process that also decided the lock was
|
|
119
|
+
* stale could unlink the FIRST process's freshly-claimed (non-stale) lock
|
|
120
|
+
* between that process's unlink and its own write, letting a third process
|
|
121
|
+
* then also claim it. `claimLock` (utils/fs-helpers.cjs) fixes this by
|
|
122
|
+
* breaking stale locks with an atomic rename-to-claim instead: only one
|
|
123
|
+
* racing process can win the rename, and a loser retries from the top
|
|
124
|
+
* rather than proceeding as if it owns the lock.
|
|
115
125
|
*/
|
|
116
126
|
function claimSpawnLock() {
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
fs.writeFileSync(LOCK_FILE, String(process.pid), { flag: 'wx' });
|
|
120
|
-
return true;
|
|
121
|
-
} catch {
|
|
122
|
-
try {
|
|
123
|
-
const stat = fs.statSync(LOCK_FILE);
|
|
124
|
-
const holder = Number(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
|
125
|
-
if (Date.now() - stat.mtimeMs < 30_000 && isPidAlive(holder)) return false;
|
|
126
|
-
fs.unlinkSync(LOCK_FILE);
|
|
127
|
-
fs.writeFileSync(LOCK_FILE, String(process.pid), { flag: 'wx' });
|
|
128
|
-
return true;
|
|
129
|
-
} catch {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
127
|
+
return claimLock(LOCK_FILE, 30_000);
|
|
133
128
|
}
|
|
134
129
|
|
|
135
130
|
function releaseSpawnLock() {
|
|
136
|
-
|
|
137
|
-
if (Number(fs.readFileSync(LOCK_FILE, 'utf-8')) === process.pid) fs.unlinkSync(LOCK_FILE);
|
|
138
|
-
} catch { /* ignore */ }
|
|
131
|
+
releaseLock(LOCK_FILE);
|
|
139
132
|
}
|
|
140
133
|
|
|
141
134
|
async function main() {
|
|
@@ -22,15 +22,36 @@ safety.unref();
|
|
|
22
22
|
|
|
23
23
|
const MAX_STDIN = 1024 * 1024; // 1 MiB
|
|
24
24
|
|
|
25
|
+
// P3-16: mirrors hook-handler.cjs's readStdin — byte-counted, discard-whole-
|
|
26
|
+
// payload-on-overflow. The old version did `if (data.length < MAX_STDIN)
|
|
27
|
+
// data += c`, which silently DROPPED characters once the cap was hit instead
|
|
28
|
+
// of aborting the read, so an oversized payload produced truncated, invalid
|
|
29
|
+
// JSON that then failed JSON.parse downstream with the whole event (and its
|
|
30
|
+
// toolName/sessionId attribution) discarded anyway — just with extra steps
|
|
31
|
+
// and no signal as to why. Discarding the entire payload up front (with a
|
|
32
|
+
// logged, flagged event) is equivalent in outcome but honest about it.
|
|
25
33
|
function readStdin() {
|
|
26
34
|
if (process.stdin.isTTY) return Promise.resolve('');
|
|
27
35
|
return new Promise((resolve) => {
|
|
28
36
|
let data = '';
|
|
29
|
-
|
|
37
|
+
let byteCount = 0;
|
|
38
|
+
let truncated = false;
|
|
39
|
+
const t = setTimeout(() => { process.stdin.pause(); resolve({ data, truncated }); }, 800);
|
|
30
40
|
process.stdin.setEncoding('utf8');
|
|
31
|
-
process.stdin.on('data', c => {
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
process.stdin.on('data', c => {
|
|
42
|
+
if (truncated) return;
|
|
43
|
+
byteCount += Buffer.byteLength(c, 'utf8');
|
|
44
|
+
if (byteCount > MAX_STDIN) {
|
|
45
|
+
truncated = true;
|
|
46
|
+
process.stdin.pause();
|
|
47
|
+
clearTimeout(t);
|
|
48
|
+
resolve({ data: '', truncated: true }); // discard oversized input, same as hook-handler.cjs
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
data += c;
|
|
52
|
+
});
|
|
53
|
+
process.stdin.on('end', () => { clearTimeout(t); resolve({ data, truncated }); });
|
|
54
|
+
process.stdin.on('error', () => { clearTimeout(t); resolve({ data, truncated }); });
|
|
34
55
|
process.stdin.resume();
|
|
35
56
|
});
|
|
36
57
|
}
|
|
@@ -60,7 +81,14 @@ function forwardToDashboard(entry) {
|
|
|
60
81
|
}
|
|
61
82
|
|
|
62
83
|
async function main() {
|
|
63
|
-
const
|
|
84
|
+
const stdinResult = await readStdin();
|
|
85
|
+
const raw = stdinResult.data;
|
|
86
|
+
const stdinTruncated = stdinResult.truncated;
|
|
87
|
+
if (stdinTruncated) {
|
|
88
|
+
// Oversized payload was discarded whole (see readStdin) — log it so the
|
|
89
|
+
// drop is visible instead of silently vanishing as a JSON.parse failure.
|
|
90
|
+
console.error('[event-logger] stdin payload exceeded ' + MAX_STDIN + ' bytes; discarded (event=' + eventType + ')');
|
|
91
|
+
}
|
|
64
92
|
let payload = {};
|
|
65
93
|
try { payload = JSON.parse(raw); } catch {}
|
|
66
94
|
|
|
@@ -80,6 +108,7 @@ async function main() {
|
|
|
80
108
|
toolResult: payload.toolResult || payload.tool_result || undefined,
|
|
81
109
|
message: payload.message || payload.notification || payload.prompt || undefined,
|
|
82
110
|
raw: (raw.length < 4096) ? payload : undefined,
|
|
111
|
+
truncated: stdinTruncated || undefined,
|
|
83
112
|
};
|
|
84
113
|
|
|
85
114
|
// Remove undefined keys
|
|
@@ -113,6 +142,13 @@ async function main() {
|
|
|
113
142
|
fs.appendFileSync(allLog, JSON.stringify(entry) + '\n');
|
|
114
143
|
} catch {}
|
|
115
144
|
|
|
145
|
+
// Rotation — event logs (daily + per-session) accumulate forever with no
|
|
146
|
+
// cleanup otherwise. Cheap: single readdir + stat pass, only on session-start
|
|
147
|
+
// (once per session, not per-event) so normal hook latency is unaffected.
|
|
148
|
+
if (eventType === 'session-start') {
|
|
149
|
+
rotateEventLogs(eventsDir);
|
|
150
|
+
}
|
|
151
|
+
|
|
116
152
|
// Write to per-session log
|
|
117
153
|
if (sessionId !== 'unknown') {
|
|
118
154
|
try {
|
|
@@ -148,8 +184,75 @@ async function main() {
|
|
|
148
184
|
forwardToDashboard(entry);
|
|
149
185
|
}
|
|
150
186
|
|
|
187
|
+
// Detect route changes and worker metric updates by mtime and forward them too.
|
|
188
|
+
// These aren't Claude Code hook events — they're file writes from route-handler.cjs
|
|
189
|
+
// and the @monomind/hooks workers respectively — so we poll cheaply on every hook invocation.
|
|
190
|
+
forwardFileChanges(monoDir, CWD);
|
|
191
|
+
|
|
151
192
|
clearTimeout(safety);
|
|
152
193
|
process.exit(0);
|
|
153
194
|
}
|
|
154
195
|
|
|
155
|
-
|
|
196
|
+
function forwardFileChanges(monoDir, workDir) {
|
|
197
|
+
try {
|
|
198
|
+
const cachePath = path.join(monoDir, 'dashboard-watch-cache.json');
|
|
199
|
+
let cache = {};
|
|
200
|
+
try { cache = JSON.parse(fs.readFileSync(cachePath, 'utf8')); } catch {}
|
|
201
|
+
let changed = false;
|
|
202
|
+
|
|
203
|
+
// Route changes (.monomind/last-route.json, written every prompt by route-handler.cjs)
|
|
204
|
+
const routePath = path.join(workDir, '.monomind', 'last-route.json');
|
|
205
|
+
try {
|
|
206
|
+
const routeMtime = fs.statSync(routePath).mtimeMs;
|
|
207
|
+
if (routeMtime !== cache.routeMtime) {
|
|
208
|
+
cache.routeMtime = routeMtime;
|
|
209
|
+
changed = true;
|
|
210
|
+
let route = null;
|
|
211
|
+
try { route = JSON.parse(fs.readFileSync(routePath, 'utf8')); } catch {}
|
|
212
|
+
forwardToDashboard({ hookType: 'route-change', ts: Date.now(), route });
|
|
213
|
+
}
|
|
214
|
+
} catch {}
|
|
215
|
+
|
|
216
|
+
// Worker-complete: newest mtime among .monomind/metrics/*.json (written by @monomind/hooks workers)
|
|
217
|
+
const metricsDir = path.join(workDir, '.monomind', 'metrics');
|
|
218
|
+
try {
|
|
219
|
+
const files = fs.readdirSync(metricsDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
|
|
220
|
+
let latestFile = null;
|
|
221
|
+
let latestMtime = cache.metricsMtime || 0;
|
|
222
|
+
for (const f of files) {
|
|
223
|
+
const mtime = fs.statSync(path.join(metricsDir, f)).mtimeMs;
|
|
224
|
+
if (mtime > latestMtime) { latestMtime = mtime; latestFile = f; }
|
|
225
|
+
}
|
|
226
|
+
if (latestFile) {
|
|
227
|
+
cache.metricsMtime = latestMtime;
|
|
228
|
+
changed = true;
|
|
229
|
+
forwardToDashboard({ hookType: 'daemon-complete', ts: Date.now(), metric: latestFile.replace(/\.json$/, '') });
|
|
230
|
+
}
|
|
231
|
+
} catch {}
|
|
232
|
+
|
|
233
|
+
if (changed) fs.writeFileSync(cachePath, JSON.stringify(cache));
|
|
234
|
+
} catch {}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Delete any daily or per-session event log whose mtime is older than
|
|
238
|
+
// maxAgeMs (default 14 days). Single readdir + stat pass — no recursion.
|
|
239
|
+
function rotateEventLogs(eventsDir, maxAgeMs) {
|
|
240
|
+
maxAgeMs = maxAgeMs || 14 * 24 * 60 * 60 * 1000;
|
|
241
|
+
try {
|
|
242
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
243
|
+
const entries = fs.readdirSync(eventsDir).filter(f => !f.startsWith('._'));
|
|
244
|
+
for (const f of entries) {
|
|
245
|
+
if (!f.endsWith('.jsonl')) continue;
|
|
246
|
+
const full = path.join(eventsDir, f);
|
|
247
|
+
try {
|
|
248
|
+
if (fs.statSync(full).mtimeMs < cutoff) fs.unlinkSync(full);
|
|
249
|
+
} catch {}
|
|
250
|
+
}
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = { rotateEventLogs };
|
|
255
|
+
|
|
256
|
+
if (require.main === module) {
|
|
257
|
+
main().catch(() => process.exit(0));
|
|
258
|
+
}
|
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const { spawn } = require('child_process');
|
|
7
7
|
const { pathToFileURL } = require('url');
|
|
8
|
+
const { claimLock } = require('./utils/fs-helpers.cjs');
|
|
8
9
|
|
|
9
10
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
10
11
|
const graphDir = path.join(projectDir, '.monomind', 'graph');
|
|
@@ -91,20 +92,18 @@ if (fs.existsSync(dbPath)) {
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
// Skip if another build is already in progress (avoids SQLite BUSY on concurrent init + session-start)
|
|
95
|
+
// P2-24: claim atomically (wx-create) instead of statSync-then-writeFileSync
|
|
96
|
+
// — two concurrent freshen triggers can both cross the same write-count
|
|
97
|
+
// threshold from parallel hook events, and a plain read-check-write lets
|
|
98
|
+
// both pass the check and both spawn a detached rebuild child. claimLock
|
|
99
|
+
// uses the same TOCTOU-safe stale-lock-break (atomic rename-to-claim) as
|
|
100
|
+
// control-start.cjs's spawn lock (see P2-25) — a lock older than 5 minutes
|
|
101
|
+
// is treated as abandoned and safely reclaimed.
|
|
94
102
|
const lockPath = path.join(graphDir, 'build.lock');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (now - stat.mtimeMs < 5 * 60 * 1000) {
|
|
100
|
-
console.log('[graph] build already in progress — skipping');
|
|
101
|
-
process.exit(0);
|
|
102
|
-
}
|
|
103
|
-
fs.unlinkSync(lockPath);
|
|
104
|
-
} catch { /* lock does not exist — proceed */ }
|
|
105
|
-
|
|
106
|
-
// Write lock file; the build process removes it on completion
|
|
107
|
-
try { fs.writeFileSync(lockPath, String(process.pid)); } catch { /* non-fatal */ }
|
|
103
|
+
if (!claimLock(lockPath, 5 * 60 * 1000)) {
|
|
104
|
+
console.log('[graph] build already in progress — skipping');
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
108
107
|
|
|
109
108
|
// Spawn a detached node process to run buildAsync from @monoes/monograph (ESM).
|
|
110
109
|
// After the build, VACUUM the DB if it has >50% bloat (reclaim space from
|
|
@@ -113,18 +112,21 @@ const dbPathStr = JSON.stringify(path.join(projectDir, '.monomind', 'monograph.d
|
|
|
113
112
|
const script = `
|
|
114
113
|
import { buildAsync } from ${JSON.stringify(pathToFileURL(entryPoint).href)};
|
|
115
114
|
import { unlinkSync, statSync } from 'fs';
|
|
116
|
-
import {
|
|
115
|
+
import { execFileSync } from 'child_process';
|
|
117
116
|
try {
|
|
118
117
|
await buildAsync(${JSON.stringify(projectDir)});
|
|
119
118
|
// Vacuum if bloat ratio is high — keeps openDb fast over time.
|
|
120
119
|
try {
|
|
121
120
|
const dbPath = ${dbPathStr};
|
|
122
121
|
const fileMB = statSync(dbPath).size / 1024 / 1024;
|
|
122
|
+
// execFileSync with array argv — no shell involved, so a project
|
|
123
|
+
// directory name containing '"' or '$(...)' cannot break out and
|
|
124
|
+
// execute arbitrary commands (P3-8).
|
|
123
125
|
const liveMB = parseInt(
|
|
124
|
-
|
|
125
|
-
{ encoding: 'utf-8' }).trim(), 10);
|
|
126
|
+
execFileSync('sqlite3', [dbPath, 'SELECT SUM(pgsize)/1024/1024 FROM dbstat;'],
|
|
127
|
+
{ encoding: 'utf-8', timeout: 30000 }).trim(), 10);
|
|
126
128
|
if (fileMB > 100 && liveMB / fileMB < 0.5) {
|
|
127
|
-
|
|
129
|
+
execFileSync('sqlite3', [dbPath, 'VACUUM;'], { timeout: 120000 });
|
|
128
130
|
}
|
|
129
131
|
} catch (_) {}
|
|
130
132
|
} finally {
|