@monoes/monomindcli 2.16.12 → 2.16.13

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 (217) hide show
  1. package/.claude/agents/core/coder.md +1 -1
  2. package/.claude/agents/engineering/engineering-devops-automator.md +1 -1
  3. package/.claude/agents/github/repo-architect.md +1 -1
  4. package/.claude/agents/testing/production-validator.md +1 -1
  5. package/.claude/agents/testing/testing-tool-evaluator.md +1 -1
  6. package/.claude/commands/github/pr-manager.md +1 -1
  7. package/.claude/helpers/pick-rank.cjs +34 -4
  8. package/.claude/skills/monolean-audit/SKILL.md +1 -1
  9. package/dist/src/commands/cleanup-data.d.ts +30 -0
  10. package/dist/src/commands/cleanup-data.d.ts.map +1 -0
  11. package/dist/src/commands/cleanup-data.js +138 -0
  12. package/dist/src/commands/cleanup-data.js.map +1 -0
  13. package/dist/src/commands/cleanup-scratch.d.ts +43 -0
  14. package/dist/src/commands/cleanup-scratch.d.ts.map +1 -0
  15. package/dist/src/commands/cleanup-scratch.js +125 -0
  16. package/dist/src/commands/cleanup-scratch.js.map +1 -0
  17. package/dist/src/commands/cleanup.d.ts +2 -39
  18. package/dist/src/commands/cleanup.d.ts.map +1 -1
  19. package/dist/src/commands/cleanup.js +5 -247
  20. package/dist/src/commands/cleanup.js.map +1 -1
  21. package/dist/src/commands/doctor-gitignore-checks.d.ts +10 -0
  22. package/dist/src/commands/doctor-gitignore-checks.d.ts.map +1 -0
  23. package/dist/src/commands/doctor-gitignore-checks.js +141 -0
  24. package/dist/src/commands/doctor-gitignore-checks.js.map +1 -0
  25. package/dist/src/commands/doctor-helpers-checks.d.ts +10 -0
  26. package/dist/src/commands/doctor-helpers-checks.d.ts.map +1 -0
  27. package/dist/src/commands/doctor-helpers-checks.js +292 -0
  28. package/dist/src/commands/doctor-helpers-checks.js.map +1 -0
  29. package/dist/src/commands/doctor-memory-checks.d.ts +47 -0
  30. package/dist/src/commands/doctor-memory-checks.d.ts.map +1 -0
  31. package/dist/src/commands/doctor-memory-checks.js +298 -0
  32. package/dist/src/commands/doctor-memory-checks.js.map +1 -0
  33. package/dist/src/commands/doctor-monograph-checks.d.ts +9 -0
  34. package/dist/src/commands/doctor-monograph-checks.d.ts.map +1 -0
  35. package/dist/src/commands/doctor-monograph-checks.js +331 -0
  36. package/dist/src/commands/doctor-monograph-checks.js.map +1 -0
  37. package/dist/src/commands/doctor-project-checks.d.ts +7 -66
  38. package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
  39. package/dist/src/commands/doctor-project-checks.js +10 -1331
  40. package/dist/src/commands/doctor-project-checks.js.map +1 -1
  41. package/dist/src/commands/doctor-routing-checks.d.ts +10 -0
  42. package/dist/src/commands/doctor-routing-checks.d.ts.map +1 -0
  43. package/dist/src/commands/doctor-routing-checks.js +194 -0
  44. package/dist/src/commands/doctor-routing-checks.js.map +1 -0
  45. package/dist/src/commands/doctor-worker-checks.d.ts +17 -0
  46. package/dist/src/commands/doctor-worker-checks.d.ts.map +1 -0
  47. package/dist/src/commands/doctor-worker-checks.js +122 -0
  48. package/dist/src/commands/doctor-worker-checks.js.map +1 -0
  49. package/dist/src/commands/hooks-routing-commands.js +1 -1
  50. package/dist/src/commands/hooks-routing-commands.js.map +1 -1
  51. package/dist/src/commands/init.d.ts.map +1 -1
  52. package/dist/src/commands/init.js +6 -15
  53. package/dist/src/commands/init.js.map +1 -1
  54. package/dist/src/index.js +2 -2
  55. package/dist/src/index.js.map +1 -1
  56. package/dist/src/init/never-commit.d.ts +3 -3
  57. package/dist/src/init/types.d.ts +1 -1
  58. package/dist/src/init/types.js +1 -1
  59. package/dist/src/knowledge/document-store.js +1 -1
  60. package/dist/src/knowledge/document-store.js.map +1 -1
  61. package/dist/src/mcp-tools/hooks-edit-command.d.ts +10 -0
  62. package/dist/src/mcp-tools/hooks-edit-command.d.ts.map +1 -0
  63. package/dist/src/mcp-tools/hooks-edit-command.js +247 -0
  64. package/dist/src/mcp-tools/hooks-edit-command.js.map +1 -0
  65. package/dist/src/mcp-tools/hooks-learning.d.ts +9 -0
  66. package/dist/src/mcp-tools/hooks-learning.d.ts.map +1 -0
  67. package/dist/src/mcp-tools/hooks-learning.js +408 -0
  68. package/dist/src/mcp-tools/hooks-learning.js.map +1 -0
  69. package/dist/src/mcp-tools/hooks-metrics-list.d.ts +8 -0
  70. package/dist/src/mcp-tools/hooks-metrics-list.d.ts.map +1 -0
  71. package/dist/src/mcp-tools/hooks-metrics-list.js +205 -0
  72. package/dist/src/mcp-tools/hooks-metrics-list.js.map +1 -0
  73. package/dist/src/mcp-tools/hooks-route.d.ts +12 -0
  74. package/dist/src/mcp-tools/hooks-route.d.ts.map +1 -0
  75. package/dist/src/mcp-tools/hooks-route.js +251 -0
  76. package/dist/src/mcp-tools/hooks-route.js.map +1 -0
  77. package/dist/src/mcp-tools/hooks-routing.d.ts +6 -27
  78. package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
  79. package/dist/src/mcp-tools/hooks-routing.js +6 -1727
  80. package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
  81. package/dist/src/mcp-tools/hooks-session.d.ts +9 -0
  82. package/dist/src/mcp-tools/hooks-session.d.ts.map +1 -0
  83. package/dist/src/mcp-tools/hooks-session.js +243 -0
  84. package/dist/src/mcp-tools/hooks-session.js.map +1 -0
  85. package/dist/src/mcp-tools/hooks-task.d.ts +17 -0
  86. package/dist/src/mcp-tools/hooks-task.d.ts.map +1 -0
  87. package/dist/src/mcp-tools/hooks-task.js +427 -0
  88. package/dist/src/mcp-tools/hooks-task.js.map +1 -0
  89. package/dist/src/mcp-tools/hooks-tools.d.ts +2 -1
  90. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  91. package/dist/src/mcp-tools/hooks-tools.js +2 -1
  92. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  93. package/dist/src/mcp-tools/memory-tools.js +1 -1
  94. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  95. package/dist/src/memory/bm25-index.d.ts +2 -2
  96. package/dist/src/memory/bm25-index.js +2 -2
  97. package/dist/src/memory/embedding-operations.js +2 -2
  98. package/dist/src/memory/embedding-operations.js.map +1 -1
  99. package/dist/src/memory/entity-name-key.d.ts +1 -1
  100. package/dist/src/memory/entity-name-key.js +1 -1
  101. package/dist/src/memory/hnsw-operations.d.ts +1 -1
  102. package/dist/src/memory/hnsw-operations.js +1 -1
  103. package/dist/src/memory/memory-bridge-backend.d.ts +43 -0
  104. package/dist/src/memory/memory-bridge-backend.d.ts.map +1 -0
  105. package/dist/src/memory/memory-bridge-backend.js +363 -0
  106. package/dist/src/memory/memory-bridge-backend.js.map +1 -0
  107. package/dist/src/memory/memory-bridge-core.d.ts +42 -0
  108. package/dist/src/memory/memory-bridge-core.d.ts.map +1 -0
  109. package/dist/src/memory/memory-bridge-core.js +126 -0
  110. package/dist/src/memory/memory-bridge-core.js.map +1 -0
  111. package/dist/src/memory/memory-bridge-entries.d.ts +131 -0
  112. package/dist/src/memory/memory-bridge-entries.d.ts.map +1 -0
  113. package/dist/src/memory/memory-bridge-entries.js +227 -0
  114. package/dist/src/memory/memory-bridge-entries.js.map +1 -0
  115. package/dist/src/memory/memory-bridge-feedback.d.ts +60 -0
  116. package/dist/src/memory/memory-bridge-feedback.d.ts.map +1 -0
  117. package/dist/src/memory/memory-bridge-feedback.js +118 -0
  118. package/dist/src/memory/memory-bridge-feedback.js.map +1 -0
  119. package/dist/src/memory/memory-bridge-paths.d.ts +123 -0
  120. package/dist/src/memory/memory-bridge-paths.d.ts.map +1 -0
  121. package/dist/src/memory/memory-bridge-paths.js +335 -0
  122. package/dist/src/memory/memory-bridge-paths.js.map +1 -0
  123. package/dist/src/memory/memory-bridge-search.d.ts +48 -0
  124. package/dist/src/memory/memory-bridge-search.d.ts.map +1 -0
  125. package/dist/src/memory/memory-bridge-search.js +383 -0
  126. package/dist/src/memory/memory-bridge-search.js.map +1 -0
  127. package/dist/src/memory/memory-bridge-sessions.d.ts +38 -0
  128. package/dist/src/memory/memory-bridge-sessions.d.ts.map +1 -0
  129. package/dist/src/memory/memory-bridge-sessions.js +109 -0
  130. package/dist/src/memory/memory-bridge-sessions.js.map +1 -0
  131. package/dist/src/memory/memory-bridge-store.d.ts +58 -0
  132. package/dist/src/memory/memory-bridge-store.d.ts.map +1 -0
  133. package/dist/src/memory/memory-bridge-store.js +201 -0
  134. package/dist/src/memory/memory-bridge-store.js.map +1 -0
  135. package/dist/src/memory/memory-bridge.d.ts +10 -438
  136. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  137. package/dist/src/memory/memory-bridge.js +14 -1787
  138. package/dist/src/memory/memory-bridge.js.map +1 -1
  139. package/dist/src/memory/memory-kg-claims.d.ts +52 -0
  140. package/dist/src/memory/memory-kg-claims.d.ts.map +1 -0
  141. package/dist/src/memory/memory-kg-claims.js +99 -0
  142. package/dist/src/memory/memory-kg-claims.js.map +1 -0
  143. package/dist/src/memory/memory-kg-index.d.ts +86 -0
  144. package/dist/src/memory/memory-kg-index.d.ts.map +1 -0
  145. package/dist/src/memory/memory-kg-index.js +256 -0
  146. package/dist/src/memory/memory-kg-index.js.map +1 -0
  147. package/dist/src/memory/memory-kg-ingest.d.ts +44 -0
  148. package/dist/src/memory/memory-kg-ingest.d.ts.map +1 -0
  149. package/dist/src/memory/memory-kg-ingest.js +361 -0
  150. package/dist/src/memory/memory-kg-ingest.js.map +1 -0
  151. package/dist/src/memory/memory-kg-model.d.ts +168 -0
  152. package/dist/src/memory/memory-kg-model.d.ts.map +1 -0
  153. package/dist/src/memory/memory-kg-model.js +158 -0
  154. package/dist/src/memory/memory-kg-model.js.map +1 -0
  155. package/dist/src/memory/memory-kg-names.d.ts +64 -0
  156. package/dist/src/memory/memory-kg-names.d.ts.map +1 -0
  157. package/dist/src/memory/memory-kg-names.js +187 -0
  158. package/dist/src/memory/memory-kg-names.js.map +1 -0
  159. package/dist/src/memory/memory-kg-promote.d.ts +79 -0
  160. package/dist/src/memory/memory-kg-promote.d.ts.map +1 -0
  161. package/dist/src/memory/memory-kg-promote.js +248 -0
  162. package/dist/src/memory/memory-kg-promote.js.map +1 -0
  163. package/dist/src/memory/memory-kg-rebuild.d.ts +77 -0
  164. package/dist/src/memory/memory-kg-rebuild.d.ts.map +1 -0
  165. package/dist/src/memory/memory-kg-rebuild.js +299 -0
  166. package/dist/src/memory/memory-kg-rebuild.js.map +1 -0
  167. package/dist/src/memory/memory-kg-rollback.d.ts +47 -0
  168. package/dist/src/memory/memory-kg-rollback.d.ts.map +1 -0
  169. package/dist/src/memory/memory-kg-rollback.js +193 -0
  170. package/dist/src/memory/memory-kg-rollback.js.map +1 -0
  171. package/dist/src/memory/memory-kg-rules.d.ts +64 -0
  172. package/dist/src/memory/memory-kg-rules.d.ts.map +1 -0
  173. package/dist/src/memory/memory-kg-rules.js +251 -0
  174. package/dist/src/memory/memory-kg-rules.js.map +1 -0
  175. package/dist/src/memory/memory-kg-scan.d.ts +118 -0
  176. package/dist/src/memory/memory-kg-scan.d.ts.map +1 -0
  177. package/dist/src/memory/memory-kg-scan.js +209 -0
  178. package/dist/src/memory/memory-kg-scan.js.map +1 -0
  179. package/dist/src/memory/memory-kg-search.d.ts +80 -0
  180. package/dist/src/memory/memory-kg-search.d.ts.map +1 -0
  181. package/dist/src/memory/memory-kg-search.js +239 -0
  182. package/dist/src/memory/memory-kg-search.js.map +1 -0
  183. package/dist/src/memory/memory-kg.d.ts +19 -547
  184. package/dist/src/memory/memory-kg.d.ts.map +1 -1
  185. package/dist/src/memory/memory-kg.js +10 -2389
  186. package/dist/src/memory/memory-kg.js.map +1 -1
  187. package/dist/src/ui/org-hil.mjs +1 -1
  188. package/dist/src/ui/routes-org-agents.mjs +318 -0
  189. package/dist/src/ui/routes-org-approvals.mjs +133 -0
  190. package/dist/src/ui/routes-org-config.mjs +459 -0
  191. package/dist/src/ui/routes-org-control.mjs +300 -0
  192. package/dist/src/ui/routes-org-files.mjs +172 -0
  193. package/dist/src/ui/routes-org-helpers.mjs +84 -0
  194. package/dist/src/ui/routes-org-knowledge.mjs +262 -0
  195. package/dist/src/ui/routes-org-lifecycle.mjs +224 -0
  196. package/dist/src/ui/routes-org-live.mjs +278 -0
  197. package/dist/src/ui/routes-org-mastermind.mjs +346 -0
  198. package/dist/src/ui/routes-org-planning.mjs +398 -0
  199. package/dist/src/ui/routes-org-runs.mjs +430 -0
  200. package/dist/src/ui/routes-org-status.mjs +342 -0
  201. package/dist/src/ui/routes-org-workflows.mjs +212 -0
  202. package/dist/src/ui/routes-org.mjs +26 -3847
  203. package/dist/src/ui/server.mjs +1 -1
  204. package/dist/src/utils/native-error.d.ts +1 -1
  205. package/dist/src/utils/native-error.js +1 -1
  206. package/dist/src/utils/paths.d.ts +2 -1
  207. package/dist/src/utils/paths.d.ts.map +1 -1
  208. package/dist/src/utils/paths.js +2 -1
  209. package/dist/src/utils/paths.js.map +1 -1
  210. package/dist/tsconfig.tsbuildinfo +1 -1
  211. package/org-skills/adversarial-reviewer/SKILL.md +1 -1
  212. package/org-skills/board-deck-builder/SKILL.md +1 -1
  213. package/org-skills/code-reviewer/SKILL.md +1 -1
  214. package/org-skills/debugging-wizard/SKILL.md +1 -1
  215. package/org-skills/scientific-thinking-literature-review/SKILL.md +1 -1
  216. package/org-skills/scientific-thinking-scholar-evaluation/SKILL.md +1 -1
  217. package/package.json +8 -8
@@ -0,0 +1,278 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ // forwarder.js is compiled from orgrt/forwarder.ts (dist/src/orgrt/forwarder.js
4
+ // sits alongside this file's own compiled copy in dist/src/ui/ after
5
+ // build; under vitest the .js specifier resolves straight to the .ts source,
6
+ // same as every other cross-reference in this codebase). translate()/
7
+ // companionEvents() are pure — no filesystem or process side effects — so
8
+ // importing them here doesn't pull in attachForwarder's spawn/heal logic.
9
+ import { companionEvents, translate } from '../orgrt/forwarder.js';
10
+ import { getMmClientCount } from './sse-manager.mjs';
11
+
12
+ // Org dashboard routes: loops, status, current run, history and memory.
13
+ // Registered, in order, by handleOrgRoutes in routes-org.mjs.
14
+ export async function handleOrgLiveRoutes(req, res, url, corsOrigin, ctx) {
15
+ // GET /api/mastermind/loops — list all active loop state files
16
+ if (req.method === 'GET' && url === '/api/mastermind/loops') {
17
+ try {
18
+ const loopsDir = path.join(ctx.projectDir || process.cwd(), '.monomind', 'loops');
19
+ const loops = [];
20
+ if (fs.existsSync(loopsDir)) {
21
+ const files = fs
22
+ .readdirSync(loopsDir)
23
+ .filter((f) => f.endsWith('.json') && !f.includes('-hil'));
24
+ for (const f of files) {
25
+ try {
26
+ const d = JSON.parse(fs.readFileSync(path.join(loopsDir, f), 'utf8'));
27
+ loops.push(d);
28
+ } catch (_) {}
29
+ }
30
+ }
31
+ loops.sort((a, b) => (b.lastRunAt || 0) - (a.lastRunAt || 0));
32
+ res.writeHead(200, { 'Content-Type': 'application/json' });
33
+ res.end(JSON.stringify({ loops }));
34
+ } catch (_) {
35
+ res.writeHead(500);
36
+ res.end('{"loops":[]}');
37
+ }
38
+ return true;
39
+ }
40
+
41
+ // GET /api/status — live system snapshot for dashboard polling
42
+ if (req.method === 'GET' && url === '/api/status') {
43
+ try {
44
+ const root = ctx.projectDir || process.cwd();
45
+ // Active org runs: { orgName -> runId }
46
+ const orgRuns = {};
47
+ ctx.activeOrgRuns.forEach((runId, org) => {
48
+ orgRuns[org] = runId;
49
+ });
50
+ // Recent events (last 10)
51
+ let recentEvents = [];
52
+ try {
53
+ const evPath = path.join(root, 'data', 'mastermind-events.jsonl');
54
+ const lines = fs
55
+ .readFileSync(evPath, 'utf8')
56
+ .split('\n')
57
+ .filter((l) => l.trim())
58
+ .slice(-10);
59
+ recentEvents = lines
60
+ .map((l) => {
61
+ try {
62
+ return JSON.parse(l);
63
+ } catch (_) {
64
+ return null;
65
+ }
66
+ })
67
+ .filter(Boolean);
68
+ } catch (_) {}
69
+ res.writeHead(200, {
70
+ 'Content-Type': 'application/json',
71
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
72
+ });
73
+ res.end(
74
+ JSON.stringify({
75
+ ts: Date.now(),
76
+ pid: process.pid,
77
+ uptime: process.uptime(),
78
+ dir: root,
79
+ sseClients: getMmClientCount(),
80
+ activeOrgs: Object.keys(orgRuns).length,
81
+ orgRuns,
82
+ recentEvents,
83
+ }),
84
+ );
85
+ } catch (err) {
86
+ res.writeHead(500, { 'Content-Type': 'application/json' });
87
+ res.end(JSON.stringify({ error: err.message }));
88
+ }
89
+ return true;
90
+ }
91
+
92
+ // GET /api/orgs/:name/runs/current — events from the active run file for an org
93
+ if (req.method === 'GET' && /^\/api\/orgs\/[^/]+\/runs\/current$/.test(url)) {
94
+ try {
95
+ const orgName = decodeURIComponent(url.split('/')[3]);
96
+ const _curQs = new URL(req.url, 'http://localhost').searchParams;
97
+ const root = path.resolve(_curQs.get('dir') || ctx.projectDir || process.cwd());
98
+ // Validate orgName
99
+ if (!orgName || orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
100
+ res.writeHead(400);
101
+ res.end('{"error":"invalid org name"}');
102
+ return true;
103
+ }
104
+ const runId = ctx.activeOrgRuns.get(orgName);
105
+ const monoDir = ctx._getGitMonomindDir(root) || path.join(root, '.monomind');
106
+ // Try active run first, then fall back to most recent run file.
107
+ // Org Runtime v2 (#238): the active run's own events live under
108
+ // orgs/<org>/<runId>/bus.jsonl, never the v1 flat orgs/<org>/runs/
109
+ // layout — check that shape (and translate it) before the v1 lookup.
110
+ let runFile = null;
111
+ let isV2 = false;
112
+ if (runId) {
113
+ const v2Candidate = path.join(monoDir, 'orgs', orgName, runId, 'bus.jsonl');
114
+ if (fs.existsSync(v2Candidate)) {
115
+ runFile = v2Candidate;
116
+ isV2 = true;
117
+ } else {
118
+ const candidate = path.join(monoDir, 'orgs', orgName, 'runs', `${runId}.jsonl`);
119
+ if (fs.existsSync(candidate)) runFile = candidate;
120
+ }
121
+ }
122
+ if (!runFile) {
123
+ const orgDir = path.join(monoDir, 'orgs', orgName);
124
+ const runDirs = fs.existsSync(orgDir)
125
+ ? fs
126
+ .readdirSync(orgDir)
127
+ .filter(
128
+ (d) => d.startsWith('run-') && fs.existsSync(path.join(orgDir, d, 'bus.jsonl')),
129
+ )
130
+ .sort()
131
+ .reverse()
132
+ : [];
133
+ if (runDirs.length) {
134
+ runFile = path.join(orgDir, runDirs[0], 'bus.jsonl');
135
+ isV2 = true;
136
+ }
137
+ }
138
+ if (!runFile) {
139
+ const runsDir = path.join(monoDir, 'orgs', orgName, 'runs');
140
+ if (fs.existsSync(runsDir)) {
141
+ const files = fs
142
+ .readdirSync(runsDir)
143
+ .filter((f) => f.endsWith('.jsonl') && !f.startsWith('._'));
144
+ if (files.length) {
145
+ files.sort();
146
+ runFile = path.join(runsDir, files[files.length - 1]);
147
+ }
148
+ }
149
+ }
150
+ if (!runFile) {
151
+ res.writeHead(404);
152
+ res.end('{"events":[],"runId":null}');
153
+ return true;
154
+ }
155
+ const detectedRunId = isV2
156
+ ? path.basename(path.dirname(runFile))
157
+ : path.basename(runFile, '.jsonl');
158
+ const lines = fs
159
+ .readFileSync(runFile, 'utf8')
160
+ .split('\n')
161
+ .filter((l) => l.trim())
162
+ .slice(-100);
163
+ const rawEvents = lines
164
+ .map((l) => {
165
+ try {
166
+ return JSON.parse(l);
167
+ } catch (_) {
168
+ return null;
169
+ }
170
+ })
171
+ .filter(Boolean);
172
+ // v1's stored events are already dashboard-native; v2's raw BusEvents
173
+ // need the same translate()/companionEvents() pass the live view gets.
174
+ const events = isV2
175
+ ? rawEvents.flatMap((e) => [...companionEvents(e), translate(e)])
176
+ : rawEvents;
177
+ res.writeHead(200, {
178
+ 'Content-Type': 'application/json',
179
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
180
+ });
181
+ res.end(
182
+ JSON.stringify({ runId: detectedRunId, events, active: ctx.activeOrgRuns.has(orgName) }),
183
+ );
184
+ } catch (err) {
185
+ res.writeHead(500);
186
+ res.end(JSON.stringify({ error: err.message }));
187
+ }
188
+ return true;
189
+ }
190
+
191
+ // GET /api/orgs/:name/history — per-run outcome summaries (history.jsonl, newest first)
192
+ if (req.method === 'GET' && /^\/api\/orgs\/[^/]+\/history$/.test(url)) {
193
+ try {
194
+ const orgName = decodeURIComponent(url.split('/')[3]);
195
+ const _histQs = new URL(req.url, 'http://localhost').searchParams;
196
+ const root = path.resolve(_histQs.get('dir') || ctx.projectDir || process.cwd());
197
+ if (!orgName || orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
198
+ res.writeHead(400);
199
+ res.end('{"error":"invalid org name"}');
200
+ return true;
201
+ }
202
+ const monoDir = ctx._getGitMonomindDir(root) || path.join(root, '.monomind');
203
+ const histFile = path.join(monoDir, 'orgs', orgName, 'history.jsonl');
204
+ let runs = [];
205
+ if (fs.existsSync(histFile)) {
206
+ runs = fs
207
+ .readFileSync(histFile, 'utf8')
208
+ .split('\n')
209
+ .filter((l) => l.trim())
210
+ .map((l) => {
211
+ try {
212
+ return JSON.parse(l);
213
+ } catch (_) {
214
+ return null;
215
+ }
216
+ })
217
+ .filter(Boolean)
218
+ .reverse()
219
+ .slice(0, 50);
220
+ }
221
+ res.writeHead(200, {
222
+ 'Content-Type': 'application/json',
223
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
224
+ });
225
+ res.end(JSON.stringify({ runs }));
226
+ } catch (err) {
227
+ res.writeHead(500);
228
+ res.end(JSON.stringify({ error: err.message }));
229
+ }
230
+ return true;
231
+ }
232
+
233
+ // GET /api/orgs/:name/memory — org knowledge-graph stats + glossary + rules
234
+ if (req.method === 'GET' && /^\/api\/orgs\/[^/]+\/memory$/.test(url)) {
235
+ try {
236
+ const orgName = decodeURIComponent(url.split('/')[3]);
237
+ const _memQs = new URL(req.url, 'http://localhost').searchParams;
238
+ const root = path.resolve(_memQs.get('dir') || ctx.projectDir || process.cwd());
239
+ if (!orgName || orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
240
+ res.writeHead(400);
241
+ res.end('{"error":"invalid org name"}');
242
+ return true;
243
+ }
244
+ const monoDir = ctx._getGitMonomindDir(root) || path.join(root, '.monomind');
245
+ const dbPath = path.join(monoDir, 'org-memory');
246
+ if (!fs.existsSync(dbPath)) {
247
+ res.writeHead(200, {
248
+ 'Content-Type': 'application/json',
249
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
250
+ });
251
+ res.end(JSON.stringify({ nodes: 0, edges: 0, rules: 0, glossary: [], ruleTexts: [] }));
252
+ return true;
253
+ }
254
+ const kg = await import('../memory/memory-kg.js');
255
+ const [stats, glossary, ruleList] = await Promise.all([
256
+ kg.kgStats({ dbPath }),
257
+ kg.kgGlossary({ dbPath, limit: 12 }),
258
+ kg.kgListRules({ dbPath, limit: 10 }),
259
+ ]);
260
+ res.writeHead(200, {
261
+ 'Content-Type': 'application/json',
262
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
263
+ });
264
+ res.end(
265
+ JSON.stringify({
266
+ ...stats,
267
+ glossary,
268
+ ruleTexts: ruleList.map((r) => r.rule.slice(0, 200)),
269
+ }),
270
+ );
271
+ } catch (err) {
272
+ res.writeHead(500);
273
+ res.end(JSON.stringify({ error: err.message }));
274
+ }
275
+ return true;
276
+ }
277
+ return false;
278
+ }
@@ -0,0 +1,346 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ // Org dashboard routes: artifacts, mastermind events, stream, sessions and pages.
5
+ // Registered, in order, by handleOrgRoutes in routes-org.mjs.
6
+ export async function handleOrgMastermindRoutes(req, res, url, corsOrigin, ctx) {
7
+ // GET /api/org/:name/artifact — serve file content for chat "View" button
8
+ if (req.method === 'GET' && /^\/api\/org\/[^/]+\/artifact/.test(url)) {
9
+ try {
10
+ const _artOrgName = decodeURIComponent(url.split('/')[3] || '');
11
+ if (_artOrgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(_artOrgName)) {
12
+ res.writeHead(400);
13
+ res.end(JSON.stringify({ error: 'Invalid org name' }));
14
+ return true;
15
+ }
16
+ const _artQp = new URL(`http://x${req.url}`).searchParams;
17
+ const _rawPath = _artQp.get('path');
18
+ if (!_rawPath) {
19
+ res.writeHead(400);
20
+ res.end(JSON.stringify({ error: 'path required' }));
21
+ return true;
22
+ }
23
+ const _filePath = path.resolve(decodeURIComponent(_rawPath));
24
+ // Security: this endpoint used to allow reads anywhere under the project
25
+ // root or any data/known-projects.json entry — letting any dashboard
26
+ // client read .env, .git/config, or other arbitrary project files.
27
+ // Restrict it, like /api/file-content above, to this org's own
28
+ // .monomind/orgs/<name>/ directory — never outside .monomind.
29
+ const _artBaseDir = path.resolve(_artQp.get('dir') || ctx.projectDir || process.cwd());
30
+ const _artProjDir = ctx._resolveOrgProjectDir(_artOrgName, _artBaseDir) || _artBaseDir;
31
+ const _artMonoDir =
32
+ ctx._getGitMonomindDir(_artProjDir) || path.join(_artProjDir, '.monomind');
33
+ const _artOrgDir = path.join(_artMonoDir, 'orgs', _artOrgName);
34
+ const _safe = _filePath === _artOrgDir || _filePath.startsWith(_artOrgDir + path.sep);
35
+ if (!_safe) {
36
+ res.writeHead(403);
37
+ res.end(JSON.stringify({ error: 'path not allowed' }));
38
+ return true;
39
+ }
40
+ if (!fs.existsSync(_filePath)) {
41
+ res.writeHead(404);
42
+ res.end(JSON.stringify({ error: 'file not found' }));
43
+ return true;
44
+ }
45
+ const _mime = ctx._detectMimeType(_filePath);
46
+ const _size = fs.statSync(_filePath).size;
47
+ // Reject files >2MB to avoid blocking the event loop
48
+ if (_size > 2 * 1024 * 1024) {
49
+ res.writeHead(413);
50
+ res.end(JSON.stringify({ error: 'file too large', size: _size }));
51
+ return true;
52
+ }
53
+ if (!_mime.startsWith('text/') && _mime !== 'application/json') {
54
+ res.writeHead(200, {
55
+ 'Content-Type': 'application/json',
56
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
57
+ });
58
+ res.end(JSON.stringify({ binary: true, mimeType: _mime, size: _size }));
59
+ return true;
60
+ }
61
+ const _content = fs.readFileSync(_filePath, 'utf8');
62
+ res.writeHead(200, {
63
+ 'Content-Type': 'application/json',
64
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
65
+ });
66
+ res.end(JSON.stringify({ content: _content, mimeType: _mime, size: _size }));
67
+ } catch (_e) {
68
+ res.writeHead(500);
69
+ res.end(JSON.stringify({ error: 'read failed' }));
70
+ }
71
+ return true;
72
+ }
73
+
74
+ // ------------------------------------------------- Mastermind event system
75
+ // POST /api/mastermind/event — ingest event from mastermind skill
76
+ if (req.method === 'POST' && url === '/api/mastermind/event') {
77
+ await ctx.handleMastermindEvent(req, res, corsOrigin);
78
+ return true;
79
+ }
80
+
81
+ // GET /api/mastermind-stream — SSE for real-time events
82
+ if (req.method === 'GET' && url === '/api/mastermind-stream') {
83
+ res.writeHead(200, {
84
+ 'Content-Type': 'text/event-stream',
85
+ 'Cache-Control': 'no-cache',
86
+ Connection: 'keep-alive',
87
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
88
+ });
89
+ res.write(': connected\n\n');
90
+ ctx.addMmClient(res);
91
+ // Replay last 50 events from disk (use ?project= param if provided)
92
+ try {
93
+ const _sseQp = new URL(`http://x${req.url}`).searchParams;
94
+ const _sseProj = _sseQp.get('project');
95
+ const root2 = _sseProj || ctx.projectDir || process.cwd();
96
+ const evFile = path.join(root2, 'data', 'mastermind-events.jsonl');
97
+ const lines = fs.readFileSync(evFile, 'utf8').trim().split('\n').filter(Boolean).slice(-50);
98
+ for (const l of lines) res.write(`data: ${l}\n\n`);
99
+ } catch (_) {}
100
+ const ka = setInterval(() => {
101
+ try {
102
+ res.write(': ping\n\n');
103
+ } catch (_) {
104
+ clearInterval(ka);
105
+ ctx.removeMmClient(res);
106
+ }
107
+ }, 20000);
108
+ req.on('close', () => {
109
+ ctx.removeMmClient(res);
110
+ clearInterval(ka);
111
+ });
112
+ return true;
113
+ }
114
+
115
+ // GET /api/mastermind/sessions
116
+ if (req.method === 'GET' && url.startsWith('/api/mastermind/sessions')) {
117
+ try {
118
+ const qp = new URL(`http://x${req.url}`).searchParams;
119
+ const filterProject = qp.get('project');
120
+ const limitParam = Math.min(parseInt(qp.get('limit') || '200', 10) || 200, 500);
121
+ const serverRoot = ctx.projectDir || process.cwd();
122
+ // Collect all project dirs to aggregate
123
+ const projectDirs = new Set([serverRoot]);
124
+ try {
125
+ const known = JSON.parse(
126
+ fs.readFileSync(path.join(serverRoot, 'data', 'known-projects.json'), 'utf8'),
127
+ );
128
+ known.forEach((p) => projectDirs.add(p));
129
+ } catch (_) {}
130
+ let allSessions = [];
131
+ for (const pd of projectDirs) {
132
+ if (filterProject && pd !== filterProject) continue;
133
+ const sessDir = path.join(pd, 'data', 'sessions');
134
+ const indexFile = path.join(sessDir, '_index.json');
135
+ // ── New format: per-session JSONL + _index.json ──
136
+ if (fs.existsSync(indexFile)) {
137
+ try {
138
+ const idx = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
139
+ const top = idx.slice(0, limitParam);
140
+ for (const entry of top) {
141
+ const _sid = String(entry.id || '').trim();
142
+ if (!_sid || !ctx.SESSION_ID_RE.test(_sid)) continue;
143
+ let events = [];
144
+ try {
145
+ const jl = fs.readFileSync(path.join(sessDir, `${_sid}.jsonl`), 'utf8');
146
+ events = jl
147
+ .trim()
148
+ .split('\n')
149
+ .filter(Boolean)
150
+ .map((l) => {
151
+ try {
152
+ return JSON.parse(l);
153
+ } catch {
154
+ return null;
155
+ }
156
+ })
157
+ .filter(Boolean);
158
+ } catch (_) {}
159
+ allSessions.push({ ...entry, events, project: pd });
160
+ }
161
+ } catch (_) {}
162
+ } else {
163
+ // ── Legacy fallback: mastermind-sessions.json ──
164
+ const f = path.join(pd, 'data', 'mastermind-sessions.json');
165
+ if (fs.existsSync(f)) {
166
+ try {
167
+ const s = JSON.parse(fs.readFileSync(f, 'utf8'));
168
+ s.forEach((sess) => {
169
+ if (!sess.project) sess.project = pd;
170
+ });
171
+ allSessions = allSessions.concat(s);
172
+ } catch (_) {}
173
+ }
174
+ }
175
+ }
176
+ allSessions.sort((a, b) => (b.ts || b.startedAt || 0) - (a.ts || a.startedAt || 0));
177
+ res.writeHead(200, {
178
+ 'Content-Type': 'application/json',
179
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
180
+ });
181
+ res.end(JSON.stringify(allSessions.slice(0, limitParam)));
182
+ } catch (_) {
183
+ res.writeHead(200);
184
+ res.end('[]');
185
+ }
186
+ return true;
187
+ }
188
+
189
+ // GET /api/mastermind/session/:id/trace — human-readable markdown trace
190
+ if (req.method === 'GET' && url.match(/^\/api\/mastermind\/session\/[^/]+\/trace$/)) {
191
+ try {
192
+ const sid = url.split('/')[4];
193
+ const sessFile = path.join(
194
+ ctx.projectDir || process.cwd(),
195
+ 'data',
196
+ 'sessions',
197
+ `${sid}.json`,
198
+ );
199
+ let s = null;
200
+ if (fs.existsSync(sessFile)) {
201
+ s = JSON.parse(fs.readFileSync(sessFile, 'utf8'));
202
+ } else {
203
+ const f = path.join(ctx.projectDir || process.cwd(), 'data', 'mastermind-sessions.json');
204
+ const sessions = JSON.parse(fs.readFileSync(f, 'utf8'));
205
+ s = sessions.find((x) => x.id === sid);
206
+ }
207
+ if (!s) {
208
+ res.writeHead(404);
209
+ res.end('Session not found');
210
+ return true;
211
+ }
212
+ const fmt = (ts) => `${new Date(ts).toISOString().replace('T', ' ').slice(0, 19)} UTC`;
213
+ const lines = [
214
+ `# Mastermind Session Trace: ${s.id}`,
215
+ ``,
216
+ `**Prompt:** ${s.prompt || '(none)'}`,
217
+ `**Status:** ${s.status}`,
218
+ `**Started:** ${fmt(s.ts)}`,
219
+ s.endTs ? `**Ended:** ${fmt(s.endTs)}` : '',
220
+ `**Domains:** ${(s.domains || []).join(', ') || '(none yet)'}`,
221
+ ``,
222
+ ];
223
+ for (const ev of s.events || []) {
224
+ const t = fmt(ev.ts);
225
+ if (ev.type === 'session:start')
226
+ lines.push(`\`${t}\` **SESSION START** — prompt: "${ev.prompt || ''}"`);
227
+ else if (ev.type === 'domain:dispatch')
228
+ lines.push(`\`${t}\` **DOMAIN DISPATCH** → \`${ev.domain}\` — ${ev.cmd || ''}`);
229
+ else if (ev.type === 'agent:spawn')
230
+ lines.push(
231
+ `\`${t}\` **AGENT SPAWN** [\`${ev.domain}\`] → agent: \`${ev.agent}\` — ${ev.task || ''}`,
232
+ );
233
+ else if (ev.type === 'intercom')
234
+ lines.push(`\`${t}\` **INTERCOM** \`${ev.from}\` → \`${ev.to}\`: ${ev.msg || ''}`);
235
+ else if (ev.type === 'domain:complete')
236
+ lines.push(
237
+ `\`${t}\` **DOMAIN COMPLETE** [\`${ev.domain}\`] status: ${ev.status}${ev.artifacts?.length ? ` — artifacts: ${ev.artifacts.join(', ')}` : ''}`,
238
+ );
239
+ else if (ev.type === 'session:complete')
240
+ lines.push(
241
+ `\`${t}\` **SESSION COMPLETE** — status: ${ev.status}, domains: ${(ev.domains || []).join(', ')}`,
242
+ );
243
+ else lines.push(`\`${t}\` ${ev.type} ${JSON.stringify(ev)}`);
244
+ }
245
+ res.writeHead(200, {
246
+ 'Content-Type': 'text/plain; charset=utf-8',
247
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
248
+ });
249
+ res.end(lines.join('\n'));
250
+ } catch (_) {
251
+ res.writeHead(500);
252
+ res.end('Error');
253
+ }
254
+ return true;
255
+ }
256
+
257
+ // GET /api/mastermind/session/:id
258
+ if (req.method === 'GET' && url.startsWith('/api/mastermind/session/')) {
259
+ try {
260
+ const sid = url.slice('/api/mastermind/session/'.length);
261
+ // Check individual session file first
262
+ const sessFile = path.join(
263
+ ctx.projectDir || process.cwd(),
264
+ 'data',
265
+ 'sessions',
266
+ `${sid}.json`,
267
+ );
268
+ if (fs.existsSync(sessFile)) {
269
+ res.writeHead(200, {
270
+ 'Content-Type': 'application/json',
271
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
272
+ });
273
+ res.end(fs.readFileSync(sessFile, 'utf8'));
274
+ return true;
275
+ }
276
+ const f = path.join(ctx.projectDir || process.cwd(), 'data', 'mastermind-sessions.json');
277
+ const sessions = JSON.parse(fs.readFileSync(f, 'utf8'));
278
+ const s = sessions.find((x) => x.id === sid);
279
+ res.writeHead(200, {
280
+ 'Content-Type': 'application/json',
281
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
282
+ });
283
+ res.end(JSON.stringify(s || null));
284
+ } catch (_) {
285
+ res.writeHead(200);
286
+ res.end('null');
287
+ }
288
+ return true;
289
+ }
290
+
291
+ // -------------------------------------------------------- GET /mastermind
292
+ if (req.method === 'GET' && url === '/mastermind') {
293
+ // Serve local file if present (dev), otherwise fall back to bundled HTML
294
+ const root = ctx.projectDir || process.cwd();
295
+ const htmlPath = path.join(root, 'docs', 'mastermind-diagram.html');
296
+ let html = ctx.MASTERMIND_DIAGRAM_HTML;
297
+ try {
298
+ html = fs.readFileSync(htmlPath, 'utf8');
299
+ } catch (_) {}
300
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
301
+ res.end(html);
302
+ return true;
303
+ }
304
+
305
+ // ----------------------------------------------------------- GET /orgs
306
+ if (req.method === 'GET' && url === '/orgs') {
307
+ try {
308
+ const htmlPath = path.join(ctx.__dirname, 'orgs.html');
309
+ let html = fs.readFileSync(htmlPath, 'utf8');
310
+ // Inject this process's auth credential the same way GET / does for
311
+ // dashboard.html — orgs.html's fetch() calls hit the same now
312
+ // default-closed /api/* routes and need a way to know the token.
313
+ html = html.replace(
314
+ '<head>',
315
+ `<head>\n<meta name="mm-token" content="${ctx.dashboardAuthValue}">`,
316
+ );
317
+ res.writeHead(200, {
318
+ 'Content-Type': 'text/html; charset=utf-8',
319
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
320
+ });
321
+ res.end(html);
322
+ } catch (err) {
323
+ res.writeHead(404);
324
+ res.end(`orgs.html not found: ${err.message}`);
325
+ }
326
+ return true;
327
+ }
328
+
329
+ // ------------------------------------------------ GET /orgs-files.js
330
+ // Files-tab + diff-view script, split out of orgs.html (see orgs.html's
331
+ // script-src comment). Not a generic static handler — this UI serves each
332
+ // sibling asset via its own hardcoded route, same as GET /orgs above.
333
+ if (req.method === 'GET' && url === '/orgs-files.js') {
334
+ try {
335
+ const jsPath = path.join(ctx.__dirname, 'orgs-files.js');
336
+ const js = fs.readFileSync(jsPath, 'utf8');
337
+ res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' });
338
+ res.end(js);
339
+ } catch (err) {
340
+ res.writeHead(404);
341
+ res.end(`orgs-files.js not found: ${err.message}`);
342
+ }
343
+ return true;
344
+ }
345
+ return false;
346
+ }