magi-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +377 -0
  3. package/README.md +377 -0
  4. package/dist/bin/magi-benchmark.d.ts +14 -0
  5. package/dist/bin/magi-benchmark.js +93 -0
  6. package/dist/bin/magi-mcp.d.ts +8 -0
  7. package/dist/bin/magi-mcp.js +28 -0
  8. package/dist/bin/magi.d.ts +2 -0
  9. package/dist/bin/magi.js +634 -0
  10. package/dist/src/adapters/base.d.ts +34 -0
  11. package/dist/src/adapters/base.js +149 -0
  12. package/dist/src/adapters/claude.d.ts +29 -0
  13. package/dist/src/adapters/claude.js +65 -0
  14. package/dist/src/adapters/codex.d.ts +21 -0
  15. package/dist/src/adapters/codex.js +41 -0
  16. package/dist/src/adapters/gemini.d.ts +18 -0
  17. package/dist/src/adapters/gemini.js +31 -0
  18. package/dist/src/adapters/registry.d.ts +19 -0
  19. package/dist/src/adapters/registry.js +59 -0
  20. package/dist/src/audit/hash-chain.d.ts +21 -0
  21. package/dist/src/audit/hash-chain.js +70 -0
  22. package/dist/src/audit/types.d.ts +25 -0
  23. package/dist/src/audit/types.js +1 -0
  24. package/dist/src/audit/writer.d.ts +18 -0
  25. package/dist/src/audit/writer.js +100 -0
  26. package/dist/src/benchmark/golden-tasks.d.ts +9 -0
  27. package/dist/src/benchmark/golden-tasks.js +476 -0
  28. package/dist/src/benchmark/reporter.d.ts +5 -0
  29. package/dist/src/benchmark/reporter.js +107 -0
  30. package/dist/src/benchmark/runner.d.ts +30 -0
  31. package/dist/src/benchmark/runner.js +224 -0
  32. package/dist/src/benchmark/scorer.d.ts +12 -0
  33. package/dist/src/benchmark/scorer.js +124 -0
  34. package/dist/src/benchmark/types.d.ts +54 -0
  35. package/dist/src/benchmark/types.js +1 -0
  36. package/dist/src/cache/deliberation-cache.d.ts +49 -0
  37. package/dist/src/cache/deliberation-cache.js +127 -0
  38. package/dist/src/cli/commands/config-cmd.d.ts +11 -0
  39. package/dist/src/cli/commands/config-cmd.js +190 -0
  40. package/dist/src/cli/commands/demo.d.ts +12 -0
  41. package/dist/src/cli/commands/demo.js +66 -0
  42. package/dist/src/cli/commands/setup.d.ts +7 -0
  43. package/dist/src/cli/commands/setup.js +182 -0
  44. package/dist/src/cli/i18n.d.ts +89 -0
  45. package/dist/src/cli/i18n.js +176 -0
  46. package/dist/src/cli/interactive-select.d.ts +27 -0
  47. package/dist/src/cli/interactive-select.js +130 -0
  48. package/dist/src/cli/tui-setup.d.ts +24 -0
  49. package/dist/src/cli/tui-setup.js +42 -0
  50. package/dist/src/config/cli-detector.d.ts +37 -0
  51. package/dist/src/config/cli-detector.js +99 -0
  52. package/dist/src/config/user-config.d.ts +81 -0
  53. package/dist/src/config/user-config.js +134 -0
  54. package/dist/src/context/auto-collector.d.ts +43 -0
  55. package/dist/src/context/auto-collector.js +337 -0
  56. package/dist/src/context/manager.d.ts +35 -0
  57. package/dist/src/context/manager.js +162 -0
  58. package/dist/src/context/serializer.d.ts +20 -0
  59. package/dist/src/context/serializer.js +52 -0
  60. package/dist/src/demo/recorded-deliberation.d.ts +13 -0
  61. package/dist/src/demo/recorded-deliberation.js +277 -0
  62. package/dist/src/engine/angel-detector.d.ts +83 -0
  63. package/dist/src/engine/angel-detector.js +334 -0
  64. package/dist/src/engine/at-field.d.ts +40 -0
  65. package/dist/src/engine/at-field.js +195 -0
  66. package/dist/src/engine/berserk-orchestrator.d.ts +66 -0
  67. package/dist/src/engine/berserk-orchestrator.js +378 -0
  68. package/dist/src/engine/change-metrics.d.ts +56 -0
  69. package/dist/src/engine/change-metrics.js +214 -0
  70. package/dist/src/engine/consensus.d.ts +20 -0
  71. package/dist/src/engine/consensus.js +146 -0
  72. package/dist/src/engine/dead-sea-scrolls.d.ts +132 -0
  73. package/dist/src/engine/dead-sea-scrolls.js +610 -0
  74. package/dist/src/engine/drift-detector.d.ts +39 -0
  75. package/dist/src/engine/drift-detector.js +225 -0
  76. package/dist/src/engine/dummy-plug.d.ts +44 -0
  77. package/dist/src/engine/dummy-plug.js +190 -0
  78. package/dist/src/engine/engram-manager.d.ts +55 -0
  79. package/dist/src/engine/engram-manager.js +306 -0
  80. package/dist/src/engine/events.d.ts +130 -0
  81. package/dist/src/engine/events.js +44 -0
  82. package/dist/src/engine/gospel.d.ts +30 -0
  83. package/dist/src/engine/gospel.js +129 -0
  84. package/dist/src/engine/hallucination-detector.d.ts +33 -0
  85. package/dist/src/engine/hallucination-detector.js +215 -0
  86. package/dist/src/engine/human-resolver.d.ts +19 -0
  87. package/dist/src/engine/human-resolver.js +89 -0
  88. package/dist/src/engine/instrumentality.d.ts +64 -0
  89. package/dist/src/engine/instrumentality.js +297 -0
  90. package/dist/src/engine/iruel-battle.d.ts +79 -0
  91. package/dist/src/engine/iruel-battle.js +319 -0
  92. package/dist/src/engine/kernel/deliberation-kernel.d.ts +12 -0
  93. package/dist/src/engine/kernel/deliberation-kernel.js +303 -0
  94. package/dist/src/engine/kernel/index.d.ts +8 -0
  95. package/dist/src/engine/kernel/index.js +7 -0
  96. package/dist/src/engine/kernel/phase-runner.d.ts +10 -0
  97. package/dist/src/engine/kernel/phase-runner.js +155 -0
  98. package/dist/src/engine/kernel/post-processor.d.ts +17 -0
  99. package/dist/src/engine/kernel/post-processor.js +131 -0
  100. package/dist/src/engine/kernel/types.d.ts +107 -0
  101. package/dist/src/engine/kernel/types.js +1 -0
  102. package/dist/src/engine/kernel/unit-executor.d.ts +6 -0
  103. package/dist/src/engine/kernel/unit-executor.js +132 -0
  104. package/dist/src/engine/lcl-manager.d.ts +44 -0
  105. package/dist/src/engine/lcl-manager.js +143 -0
  106. package/dist/src/engine/middleware/cache.d.ts +7 -0
  107. package/dist/src/engine/middleware/cache.js +29 -0
  108. package/dist/src/engine/middleware/chain.d.ts +18 -0
  109. package/dist/src/engine/middleware/chain.js +45 -0
  110. package/dist/src/engine/middleware/firewall.d.ts +8 -0
  111. package/dist/src/engine/middleware/firewall.js +24 -0
  112. package/dist/src/engine/middleware/index.d.ts +4 -0
  113. package/dist/src/engine/middleware/index.js +3 -0
  114. package/dist/src/engine/middleware/types.d.ts +43 -0
  115. package/dist/src/engine/middleware/types.js +1 -0
  116. package/dist/src/engine/nebuchadnezzar-key.d.ts +61 -0
  117. package/dist/src/engine/nebuchadnezzar-key.js +203 -0
  118. package/dist/src/engine/neon-genesis.d.ts +52 -0
  119. package/dist/src/engine/neon-genesis.js +203 -0
  120. package/dist/src/engine/objective-judge.d.ts +53 -0
  121. package/dist/src/engine/objective-judge.js +214 -0
  122. package/dist/src/engine/offline-mode.d.ts +18 -0
  123. package/dist/src/engine/offline-mode.js +46 -0
  124. package/dist/src/engine/orchestrator.d.ts +79 -0
  125. package/dist/src/engine/orchestrator.js +58 -0
  126. package/dist/src/engine/secret-cipher.d.ts +26 -0
  127. package/dist/src/engine/secret-cipher.js +114 -0
  128. package/dist/src/engine/seele-council.d.ts +90 -0
  129. package/dist/src/engine/seele-council.js +482 -0
  130. package/dist/src/engine/self-destruct.d.ts +61 -0
  131. package/dist/src/engine/self-destruct.js +231 -0
  132. package/dist/src/engine/self-evolution.d.ts +64 -0
  133. package/dist/src/engine/self-evolution.js +368 -0
  134. package/dist/src/engine/sync-rate.d.ts +45 -0
  135. package/dist/src/engine/sync-rate.js +151 -0
  136. package/dist/src/engine/type666-firewall.d.ts +76 -0
  137. package/dist/src/engine/type666-firewall.js +343 -0
  138. package/dist/src/engine/umbilical-cable.d.ts +41 -0
  139. package/dist/src/engine/umbilical-cable.js +192 -0
  140. package/dist/src/index.d.ts +106 -0
  141. package/dist/src/index.js +426 -0
  142. package/dist/src/mcp/server.d.ts +38 -0
  143. package/dist/src/mcp/server.js +196 -0
  144. package/dist/src/metrics/token-tracker.d.ts +38 -0
  145. package/dist/src/metrics/token-tracker.js +112 -0
  146. package/dist/src/parsers/json-extractor.d.ts +9 -0
  147. package/dist/src/parsers/json-extractor.js +239 -0
  148. package/dist/src/parsers/opinion-schema.d.ts +81 -0
  149. package/dist/src/parsers/opinion-schema.js +147 -0
  150. package/dist/src/parsers/unstructured-parser.d.ts +20 -0
  151. package/dist/src/parsers/unstructured-parser.js +122 -0
  152. package/dist/src/pipelines/architecture.d.ts +10 -0
  153. package/dist/src/pipelines/architecture.js +9 -0
  154. package/dist/src/pipelines/bug-analysis.d.ts +9 -0
  155. package/dist/src/pipelines/bug-analysis.js +8 -0
  156. package/dist/src/pipelines/code-review.d.ts +10 -0
  157. package/dist/src/pipelines/code-review.js +30 -0
  158. package/dist/src/pipelines/custom.d.ts +14 -0
  159. package/dist/src/pipelines/custom.js +29 -0
  160. package/dist/src/pipelines/registry.d.ts +9 -0
  161. package/dist/src/pipelines/registry.js +20 -0
  162. package/dist/src/prompts/personas.d.ts +6 -0
  163. package/dist/src/prompts/personas.js +44 -0
  164. package/dist/src/prompts/schemas.d.ts +4 -0
  165. package/dist/src/prompts/schemas.js +24 -0
  166. package/dist/src/prompts/templates.d.ts +6 -0
  167. package/dist/src/prompts/templates.js +91 -0
  168. package/dist/src/repl/accessibility.d.ts +23 -0
  169. package/dist/src/repl/accessibility.js +46 -0
  170. package/dist/src/repl/banner.d.ts +4 -0
  171. package/dist/src/repl/banner.js +28 -0
  172. package/dist/src/repl/boot-animation.d.ts +13 -0
  173. package/dist/src/repl/boot-animation.js +143 -0
  174. package/dist/src/repl/completer.d.ts +21 -0
  175. package/dist/src/repl/completer.js +168 -0
  176. package/dist/src/repl/context.d.ts +24 -0
  177. package/dist/src/repl/context.js +42 -0
  178. package/dist/src/repl/display-utils.d.ts +13 -0
  179. package/dist/src/repl/display-utils.js +65 -0
  180. package/dist/src/repl/event-listener.d.ts +18 -0
  181. package/dist/src/repl/event-listener.js +112 -0
  182. package/dist/src/repl/export-formatter.d.ts +8 -0
  183. package/dist/src/repl/export-formatter.js +73 -0
  184. package/dist/src/repl/ghost-text.d.ts +31 -0
  185. package/dist/src/repl/ghost-text.js +119 -0
  186. package/dist/src/repl/handoff-animation.d.ts +15 -0
  187. package/dist/src/repl/handoff-animation.js +65 -0
  188. package/dist/src/repl/history.d.ts +16 -0
  189. package/dist/src/repl/history.js +130 -0
  190. package/dist/src/repl/job-registry.d.ts +26 -0
  191. package/dist/src/repl/job-registry.js +80 -0
  192. package/dist/src/repl/magi-repl.d.ts +72 -0
  193. package/dist/src/repl/magi-repl.js +1008 -0
  194. package/dist/src/repl/multiline-input.d.ts +45 -0
  195. package/dist/src/repl/multiline-input.js +78 -0
  196. package/dist/src/repl/prompt-builder.d.ts +19 -0
  197. package/dist/src/repl/prompt-builder.js +36 -0
  198. package/dist/src/repl/repl-state.d.ts +5 -0
  199. package/dist/src/repl/repl-state.js +19 -0
  200. package/dist/src/repl/result-display.d.ts +8 -0
  201. package/dist/src/repl/result-display.js +195 -0
  202. package/dist/src/repl/session-stats.d.ts +26 -0
  203. package/dist/src/repl/session-stats.js +119 -0
  204. package/dist/src/repl/slash-commands.d.ts +60 -0
  205. package/dist/src/repl/slash-commands.js +725 -0
  206. package/dist/src/repl/terminal-sanitize.d.ts +14 -0
  207. package/dist/src/repl/terminal-sanitize.js +19 -0
  208. package/dist/src/reporters/console.d.ts +7 -0
  209. package/dist/src/reporters/console.js +78 -0
  210. package/dist/src/reporters/json.d.ts +2 -0
  211. package/dist/src/reporters/json.js +3 -0
  212. package/dist/src/reporters/markdown.d.ts +2 -0
  213. package/dist/src/reporters/markdown.js +65 -0
  214. package/dist/src/reporters/streaming.d.ts +20 -0
  215. package/dist/src/reporters/streaming.js +178 -0
  216. package/dist/src/tui/activity-log.d.ts +23 -0
  217. package/dist/src/tui/activity-log.js +67 -0
  218. package/dist/src/tui/animations.d.ts +39 -0
  219. package/dist/src/tui/animations.js +167 -0
  220. package/dist/src/tui/ansi.d.ts +28 -0
  221. package/dist/src/tui/ansi.js +51 -0
  222. package/dist/src/tui/boot-sequence.d.ts +11 -0
  223. package/dist/src/tui/boot-sequence.js +98 -0
  224. package/dist/src/tui/colors.d.ts +101 -0
  225. package/dist/src/tui/colors.js +71 -0
  226. package/dist/src/tui/header.d.ts +24 -0
  227. package/dist/src/tui/header.js +122 -0
  228. package/dist/src/tui/index.d.ts +3 -0
  229. package/dist/src/tui/index.js +3 -0
  230. package/dist/src/tui/keypress.d.ts +25 -0
  231. package/dist/src/tui/keypress.js +95 -0
  232. package/dist/src/tui/layout.d.ts +74 -0
  233. package/dist/src/tui/layout.js +171 -0
  234. package/dist/src/tui/magi-tui.d.ts +101 -0
  235. package/dist/src/tui/magi-tui.js +754 -0
  236. package/dist/src/tui/panel.d.ts +45 -0
  237. package/dist/src/tui/panel.js +292 -0
  238. package/dist/src/tui/screen-buffer.d.ts +54 -0
  239. package/dist/src/tui/screen-buffer.js +262 -0
  240. package/dist/src/tui/status-bar.d.ts +25 -0
  241. package/dist/src/tui/status-bar.js +124 -0
  242. package/dist/src/tui/terminal-detect.d.ts +26 -0
  243. package/dist/src/tui/terminal-detect.js +44 -0
  244. package/dist/src/tui/tui-helpers.d.ts +12 -0
  245. package/dist/src/tui/tui-helpers.js +37 -0
  246. package/dist/src/types/adapter.d.ts +75 -0
  247. package/dist/src/types/adapter.js +36 -0
  248. package/dist/src/types/config.d.ts +108 -0
  249. package/dist/src/types/config.js +85 -0
  250. package/dist/src/types/consensus.d.ts +55 -0
  251. package/dist/src/types/consensus.js +17 -0
  252. package/dist/src/types/core.d.ts +178 -0
  253. package/dist/src/types/core.js +85 -0
  254. package/dist/src/types/magi-api.d.ts +62 -0
  255. package/dist/src/types/magi-api.js +7 -0
  256. package/dist/src/types/phase-h.d.ts +142 -0
  257. package/dist/src/types/phase-h.js +7 -0
  258. package/dist/src/types/phase-i.d.ts +186 -0
  259. package/dist/src/types/phase-i.js +6 -0
  260. package/dist/src/types/phase-k.d.ts +259 -0
  261. package/dist/src/types/phase-k.js +6 -0
  262. package/dist/src/types/phase-l.d.ts +199 -0
  263. package/dist/src/types/phase-l.js +6 -0
  264. package/dist/src/types/pipeline.d.ts +37 -0
  265. package/dist/src/types/pipeline.js +2 -0
  266. package/dist/src/utils/abstain-factory.d.ts +2 -0
  267. package/dist/src/utils/abstain-factory.js +18 -0
  268. package/dist/src/utils/errors.d.ts +34 -0
  269. package/dist/src/utils/errors.js +59 -0
  270. package/dist/src/utils/file-validator.d.ts +50 -0
  271. package/dist/src/utils/file-validator.js +124 -0
  272. package/dist/src/utils/fire-and-forget.d.ts +5 -0
  273. package/dist/src/utils/fire-and-forget.js +10 -0
  274. package/dist/src/utils/flag-validator.d.ts +21 -0
  275. package/dist/src/utils/flag-validator.js +79 -0
  276. package/dist/src/utils/freeze.d.ts +8 -0
  277. package/dist/src/utils/freeze.js +16 -0
  278. package/dist/src/utils/language-detector.d.ts +16 -0
  279. package/dist/src/utils/language-detector.js +159 -0
  280. package/dist/src/utils/latency-tracker.d.ts +45 -0
  281. package/dist/src/utils/latency-tracker.js +100 -0
  282. package/dist/src/utils/logger.d.ts +33 -0
  283. package/dist/src/utils/logger.js +112 -0
  284. package/dist/src/utils/process.d.ts +40 -0
  285. package/dist/src/utils/process.js +253 -0
  286. package/dist/src/utils/retry.d.ts +12 -0
  287. package/dist/src/utils/retry.js +30 -0
  288. package/dist/src/utils/safe-fs.d.ts +38 -0
  289. package/dist/src/utils/safe-fs.js +56 -0
  290. package/dist/src/utils/safe-json-parse.d.ts +15 -0
  291. package/dist/src/utils/safe-json-parse.js +49 -0
  292. package/dist/src/utils/sanitize.d.ts +14 -0
  293. package/dist/src/utils/sanitize.js +186 -0
  294. package/dist/src/utils/semaphore.d.ts +22 -0
  295. package/dist/src/utils/semaphore.js +57 -0
  296. package/dist/src/utils/shutdown.d.ts +6 -0
  297. package/dist/src/utils/shutdown.js +51 -0
  298. package/dist/src/utils/tty.d.ts +5 -0
  299. package/dist/src/utils/tty.js +7 -0
  300. package/package.json +82 -0
@@ -0,0 +1,162 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { logger } from '../utils/logger.js';
4
+ import { MagiError } from '../utils/errors.js';
5
+ import { safeJsonParse } from '../utils/safe-json-parse.js';
6
+ import { safeMkdir as fsSafeMkdir, safeWriteFile as fsSafeWriteFile } from '../utils/safe-fs.js';
7
+ const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
8
+ export class PathTraversalError extends MagiError {
9
+ constructor(message) {
10
+ super(message, 'PATH_TRAVERSAL');
11
+ }
12
+ }
13
+ /**
14
+ * File-System-as-State context manager with path traversal prevention.
15
+ *
16
+ * Manages the .magi/ workspace directory where deliberation state is persisted.
17
+ * Each deliberation gets its own subdirectory with round-by-round JSON files.
18
+ *
19
+ * Security hardening:
20
+ * - Deliberation IDs are validated as UUID v4 format
21
+ * - All paths are resolved and verified to stay within the base directory
22
+ * - Null bytes are rejected
23
+ * - Symlinks at target directories are rejected
24
+ * - File permissions: directories 0o700, files 0o600
25
+ */
26
+ export class ContextManager {
27
+ resolvedBaseDir;
28
+ constructor(baseDir) {
29
+ this.resolvedBaseDir = resolve(baseDir);
30
+ }
31
+ /** Validate deliberation ID format and resolve path safely. */
32
+ validateAndResolvePath(deliberationId) {
33
+ // Check for null bytes
34
+ if (deliberationId.includes('\0')) {
35
+ throw new PathTraversalError('Deliberation ID contains null bytes');
36
+ }
37
+ // Validate UUID v4 format
38
+ if (!UUID_V4_RE.test(deliberationId)) {
39
+ throw new PathTraversalError(`Invalid deliberation ID format: expected UUID v4, got "${deliberationId.slice(0, 50)}"`);
40
+ }
41
+ // Resolve and verify path prefix
42
+ const resolved = resolve(this.resolvedBaseDir, deliberationId);
43
+ if (!resolved.startsWith(this.resolvedBaseDir + '/') && resolved !== this.resolvedBaseDir) {
44
+ throw new PathTraversalError(`Path traversal detected: resolved path "${resolved}" escapes base directory`);
45
+ }
46
+ return resolved;
47
+ }
48
+ /** Create directory with 0o700 permissions, rejecting symlinks. */
49
+ async safeMkdir(dirPath) {
50
+ try {
51
+ await fsSafeMkdir(dirPath);
52
+ }
53
+ catch (err) {
54
+ if (err instanceof Error && err.message.startsWith('Symlink detected')) {
55
+ throw new PathTraversalError(err.message);
56
+ }
57
+ throw err;
58
+ }
59
+ }
60
+ /** Write file with 0o600 permissions. */
61
+ async safeWriteFile(filePath, content) {
62
+ await fsSafeWriteFile(filePath, content);
63
+ }
64
+ /** Write JSON with safe permissions. */
65
+ async writeJson(path, data) {
66
+ await this.safeWriteFile(path, JSON.stringify(data, null, 2));
67
+ }
68
+ async initWorkspace(deliberationId, task) {
69
+ const workDir = this.validateAndResolvePath(deliberationId);
70
+ await this.safeMkdir(workDir);
71
+ await this.safeMkdir(join(workDir, 'debug'));
72
+ await this.writeJson(join(workDir, 'task.json'), {
73
+ ...task,
74
+ startedAt: new Date().toISOString(),
75
+ });
76
+ logger.debug(`Workspace initialized: ${workDir}`);
77
+ return workDir;
78
+ }
79
+ async saveRound(deliberationId, round) {
80
+ const workDir = this.validateAndResolvePath(deliberationId);
81
+ const roundDir = join(workDir, `round-${round.roundNumber}`);
82
+ const debugDir = join(workDir, 'debug');
83
+ // Create directories first (must precede file writes)
84
+ await Promise.all([this.safeMkdir(roundDir), this.safeMkdir(debugDir)]);
85
+ // Collect all file writes and execute in parallel
86
+ const writes = [];
87
+ for (const opinion of round.opinions) {
88
+ const filename = `${opinion.unit.toLowerCase()}-${round.phase}.json`;
89
+ writes.push(this.writeJson(join(roundDir, filename), {
90
+ unit: opinion.unit,
91
+ vote: opinion.vote,
92
+ confidence: opinion.confidence,
93
+ reasoning: opinion.reasoning,
94
+ keyPoints: opinion.keyPoints,
95
+ suggestions: opinion.suggestions,
96
+ meta: opinion.meta,
97
+ }));
98
+ const debugFile = `${opinion.unit.toLowerCase()}-raw-${round.roundNumber}.txt`;
99
+ writes.push(this.safeWriteFile(join(debugDir, debugFile), opinion.rawOutput));
100
+ }
101
+ writes.push(this.writeJson(join(roundDir, '_round.json'), {
102
+ roundNumber: round.roundNumber,
103
+ phase: round.phase,
104
+ startedAt: round.startedAt.toISOString(),
105
+ completedAt: round.completedAt.toISOString(),
106
+ votes: round.opinions.map((o) => ({
107
+ unit: o.unit,
108
+ vote: o.vote,
109
+ confidence: o.confidence,
110
+ })),
111
+ }));
112
+ const results = await Promise.allSettled(writes);
113
+ const rejected = results.filter((r) => r.status === 'rejected');
114
+ if (rejected.length > 0) {
115
+ logger.warn(`Round ${round.roundNumber} save: ${rejected.length}/${results.length} writes failed`, {
116
+ phase: round.phase,
117
+ errors: rejected.map(r => String(r.reason)),
118
+ });
119
+ }
120
+ logger.debug(`Round ${round.roundNumber} saved`, {
121
+ phase: round.phase,
122
+ succeeded: results.length - rejected.length,
123
+ failed: rejected.length,
124
+ });
125
+ }
126
+ async saveDeliberation(deliberation) {
127
+ const workDir = this.validateAndResolvePath(deliberation.id);
128
+ await this.writeJson(join(workDir, 'consensus.json'), deliberation.consensus);
129
+ await this.writeJson(join(workDir, 'deliberation.json'), {
130
+ id: deliberation.id,
131
+ task: deliberation.task,
132
+ totalDurationMs: deliberation.totalDurationMs,
133
+ startedAt: deliberation.startedAt.toISOString(),
134
+ completedAt: deliberation.completedAt.toISOString(),
135
+ consensus: deliberation.consensus,
136
+ roundCount: deliberation.rounds.length,
137
+ });
138
+ }
139
+ async listDeliberations() {
140
+ try {
141
+ const entries = await readdir(this.resolvedBaseDir, { withFileTypes: true });
142
+ return entries
143
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
144
+ .map((e) => e.name);
145
+ }
146
+ catch (err) {
147
+ logger.debug('ContextManager: failed to list deliberations', { error: String(err) });
148
+ return [];
149
+ }
150
+ }
151
+ async loadTask(deliberationId) {
152
+ try {
153
+ const workDir = this.validateAndResolvePath(deliberationId);
154
+ const content = await readFile(join(workDir, 'task.json'), 'utf-8');
155
+ return safeJsonParse(content);
156
+ }
157
+ catch (err) {
158
+ logger.debug('ContextManager: failed to load task', { deliberationId, error: String(err) });
159
+ return null;
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,20 @@
1
+ import type { MagiOpinion, DeliberationRound } from '../types/core.js';
2
+ /**
3
+ * Truncate a string to maxLen characters, appending "…" if truncated.
4
+ */
5
+ export declare function truncate(text: string, maxLen: number): string;
6
+ /**
7
+ * Compact serialization for cross-examination prompts.
8
+ * Omits suggestions, truncates reasoning to 500 chars.
9
+ */
10
+ export declare function serializeOpinionCompact(opinion: MagiOpinion): string;
11
+ /**
12
+ * Minimal serialization for summary views.
13
+ * Vote + confidence + key points only, no reasoning.
14
+ */
15
+ export declare function serializeOpinionMinimal(opinion: MagiOpinion): string;
16
+ /**
17
+ * Compact serialization for multiple rounds (final-vote prompts).
18
+ * Uses compact opinions for the latest round, minimal for earlier rounds.
19
+ */
20
+ export declare function serializeRoundsCompact(rounds: DeliberationRound[]): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Truncate a string to maxLen characters, appending "…" if truncated.
3
+ */
4
+ export function truncate(text, maxLen) {
5
+ if (text.length <= maxLen)
6
+ return text;
7
+ return text.slice(0, maxLen - 1) + '…';
8
+ }
9
+ /**
10
+ * Compact serialization for cross-examination prompts.
11
+ * Omits suggestions, truncates reasoning to 500 chars.
12
+ */
13
+ export function serializeOpinionCompact(opinion) {
14
+ return [
15
+ `### ${opinion.unit}`,
16
+ `Vote: ${opinion.vote} (${opinion.confidence.toFixed(2)})`,
17
+ `Reasoning: ${truncate(opinion.reasoning, 500)}`,
18
+ opinion.keyPoints.length > 0
19
+ ? `Key Points:\n${opinion.keyPoints.map((p) => `- ${p}`).join('\n')}`
20
+ : '',
21
+ ]
22
+ .filter(Boolean)
23
+ .join('\n');
24
+ }
25
+ /**
26
+ * Minimal serialization for summary views.
27
+ * Vote + confidence + key points only, no reasoning.
28
+ */
29
+ export function serializeOpinionMinimal(opinion) {
30
+ return [
31
+ `### ${opinion.unit}`,
32
+ `Vote: ${opinion.vote} (${opinion.confidence.toFixed(2)})`,
33
+ opinion.keyPoints.length > 0
34
+ ? opinion.keyPoints.map((p) => `- ${p}`).join('\n')
35
+ : '',
36
+ ]
37
+ .filter(Boolean)
38
+ .join('\n');
39
+ }
40
+ /**
41
+ * Compact serialization for multiple rounds (final-vote prompts).
42
+ * Uses compact opinions for the latest round, minimal for earlier rounds.
43
+ */
44
+ export function serializeRoundsCompact(rounds) {
45
+ return rounds.map((round, i) => {
46
+ const isLast = i === rounds.length - 1;
47
+ const header = `## Round ${round.roundNumber} (${round.phase})`;
48
+ const serialize = isLast ? serializeOpinionCompact : serializeOpinionMinimal;
49
+ const opinions = round.opinions.map(serialize).join('\n\n');
50
+ return `${header}\n\n${opinions}`;
51
+ }).join('\n\n---\n\n');
52
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Pre-recorded deliberation events for `magi demo`.
3
+ *
4
+ * Simulates a 3-body deliberation on "REST vs GraphQL for a new API"
5
+ * with realistic timing and opinions.
6
+ */
7
+ import type { MagiEventName } from '../engine/events.js';
8
+ export interface RecordedEvent {
9
+ type: MagiEventName;
10
+ payload: Record<string, unknown>;
11
+ delayMs: number;
12
+ }
13
+ export declare const DEMO_EVENTS: RecordedEvent[];
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Pre-recorded deliberation events for `magi demo`.
3
+ *
4
+ * Simulates a 3-body deliberation on "REST vs GraphQL for a new API"
5
+ * with realistic timing and opinions.
6
+ */
7
+ const DELIB_ID = 'demo-rest-vs-graphql';
8
+ export const DEMO_EVENTS = [
9
+ // ── Deliberation Start ───────────────────────────────────────
10
+ {
11
+ type: 'deliberation:start',
12
+ payload: {
13
+ deliberationId: DELIB_ID,
14
+ taskType: 'architecture-decision',
15
+ taskTitle: 'REST vs GraphQL for new public API',
16
+ emittedAt: new Date(),
17
+ },
18
+ delayMs: 0,
19
+ },
20
+ // ── Phase 1: Initial Opinion ─────────────────────────────────
21
+ {
22
+ type: 'phase:start',
23
+ payload: {
24
+ deliberationId: DELIB_ID,
25
+ phase: 'initial-opinion',
26
+ roundNumber: 1,
27
+ emittedAt: new Date(),
28
+ },
29
+ delayMs: 500,
30
+ },
31
+ // MELCHIOR starts
32
+ {
33
+ type: 'unit:start',
34
+ payload: { deliberationId: DELIB_ID, unit: 'MELCHIOR', phase: 'initial-opinion', emittedAt: new Date() },
35
+ delayMs: 200,
36
+ },
37
+ // BALTHASAR starts
38
+ {
39
+ type: 'unit:start',
40
+ payload: { deliberationId: DELIB_ID, unit: 'BALTHASAR', phase: 'initial-opinion', emittedAt: new Date() },
41
+ delayMs: 100,
42
+ },
43
+ // CASPER starts
44
+ {
45
+ type: 'unit:start',
46
+ payload: { deliberationId: DELIB_ID, unit: 'CASPER', phase: 'initial-opinion', emittedAt: new Date() },
47
+ delayMs: 100,
48
+ },
49
+ // Streaming chunks (simulate LLM output)
50
+ {
51
+ type: 'unit:chunk',
52
+ payload: { deliberationId: DELIB_ID, unit: 'MELCHIOR', chunk: 'Analyzing API patterns...', stream: 'stdout', emittedAt: new Date() },
53
+ delayMs: 800,
54
+ },
55
+ {
56
+ type: 'unit:chunk',
57
+ payload: { deliberationId: DELIB_ID, unit: 'BALTHASAR', chunk: 'Evaluating implementation complexity...', stream: 'stdout', emittedAt: new Date() },
58
+ delayMs: 400,
59
+ },
60
+ {
61
+ type: 'unit:chunk',
62
+ payload: { deliberationId: DELIB_ID, unit: 'CASPER', chunk: 'Assessing security implications...', stream: 'stdout', emittedAt: new Date() },
63
+ delayMs: 600,
64
+ },
65
+ // MELCHIOR completes — APPROVE (GraphQL)
66
+ {
67
+ type: 'unit:complete',
68
+ payload: {
69
+ deliberationId: DELIB_ID,
70
+ unit: 'MELCHIOR',
71
+ phase: 'initial-opinion',
72
+ vote: 'APPROVE',
73
+ confidence: 0.78,
74
+ durationMs: 3200,
75
+ reasoningSummary: 'GraphQL enables precise data fetching, reducing over-fetching by ~60%. Type system provides compile-time contract validation. Subscription support enables real-time features without additional infrastructure.',
76
+ emittedAt: new Date(),
77
+ },
78
+ delayMs: 1500,
79
+ },
80
+ // CASPER completes — REJECT (prefers REST)
81
+ {
82
+ type: 'unit:complete',
83
+ payload: {
84
+ deliberationId: DELIB_ID,
85
+ unit: 'CASPER',
86
+ phase: 'initial-opinion',
87
+ vote: 'REJECT',
88
+ confidence: 0.72,
89
+ durationMs: 3800,
90
+ reasoningSummary: 'REST is safer for public APIs: simpler auth model, built-in HTTP caching, no query complexity attacks. GraphQL\'s N+1 problem and lack of native rate limiting create operational risk for public-facing services.',
91
+ emittedAt: new Date(),
92
+ },
93
+ delayMs: 800,
94
+ },
95
+ // BALTHASAR completes — APPROVE (GraphQL, but cautious)
96
+ {
97
+ type: 'unit:complete',
98
+ payload: {
99
+ deliberationId: DELIB_ID,
100
+ unit: 'BALTHASAR',
101
+ phase: 'initial-opinion',
102
+ vote: 'APPROVE',
103
+ confidence: 0.65,
104
+ durationMs: 4100,
105
+ reasoningSummary: 'GraphQL reduces frontend-backend coordination overhead. Code generation from schema accelerates development. However, caching strategy needs careful design — persisted queries recommended for public API.',
106
+ emittedAt: new Date(),
107
+ },
108
+ delayMs: 1000,
109
+ },
110
+ // Phase 1 complete
111
+ {
112
+ type: 'phase:complete',
113
+ payload: { deliberationId: DELIB_ID, phase: 'initial-opinion', roundNumber: 1, durationMs: 4100, emittedAt: new Date() },
114
+ delayMs: 300,
115
+ },
116
+ // ── Phase 2: Cross-Examination ───────────────────────────────
117
+ {
118
+ type: 'phase:start',
119
+ payload: { deliberationId: DELIB_ID, phase: 'cross-examination', roundNumber: 1, emittedAt: new Date() },
120
+ delayMs: 500,
121
+ },
122
+ {
123
+ type: 'unit:start',
124
+ payload: { deliberationId: DELIB_ID, unit: 'MELCHIOR', phase: 'cross-examination', emittedAt: new Date() },
125
+ delayMs: 200,
126
+ },
127
+ {
128
+ type: 'unit:start',
129
+ payload: { deliberationId: DELIB_ID, unit: 'BALTHASAR', phase: 'cross-examination', emittedAt: new Date() },
130
+ delayMs: 100,
131
+ },
132
+ {
133
+ type: 'unit:start',
134
+ payload: { deliberationId: DELIB_ID, unit: 'CASPER', phase: 'cross-examination', emittedAt: new Date() },
135
+ delayMs: 100,
136
+ },
137
+ // Cross-exam chunks
138
+ {
139
+ type: 'unit:chunk',
140
+ payload: { deliberationId: DELIB_ID, unit: 'MELCHIOR', chunk: 'Reviewing CASPER\'s security concerns...', stream: 'stdout', emittedAt: new Date() },
141
+ delayMs: 1200,
142
+ },
143
+ {
144
+ type: 'unit:chunk',
145
+ payload: { deliberationId: DELIB_ID, unit: 'CASPER', chunk: 'Examining MELCHIOR\'s performance claims...', stream: 'stdout', emittedAt: new Date() },
146
+ delayMs: 800,
147
+ },
148
+ // Cross-exam completes
149
+ {
150
+ type: 'unit:complete',
151
+ payload: {
152
+ deliberationId: DELIB_ID,
153
+ unit: 'MELCHIOR',
154
+ phase: 'cross-examination',
155
+ vote: 'APPROVE',
156
+ confidence: 0.82,
157
+ durationMs: 5200,
158
+ reasoningSummary: 'CASPER raises valid security points. Mitigation: query depth limiting (max 10), persisted queries for public API, cost analysis middleware. These are solved problems in production GraphQL (Shopify, GitHub).',
159
+ emittedAt: new Date(),
160
+ },
161
+ delayMs: 2000,
162
+ },
163
+ {
164
+ type: 'unit:complete',
165
+ payload: {
166
+ deliberationId: DELIB_ID,
167
+ unit: 'CASPER',
168
+ phase: 'cross-examination',
169
+ vote: 'CONDITIONAL',
170
+ confidence: 0.68,
171
+ durationMs: 5800,
172
+ reasoningSummary: 'MELCHIOR\'s mitigations are viable if properly implemented. Revised position: GraphQL acceptable WITH mandatory persisted queries, query complexity budget, and REST fallback for critical auth endpoints.',
173
+ emittedAt: new Date(),
174
+ },
175
+ delayMs: 1500,
176
+ },
177
+ {
178
+ type: 'unit:complete',
179
+ payload: {
180
+ deliberationId: DELIB_ID,
181
+ unit: 'BALTHASAR',
182
+ phase: 'cross-examination',
183
+ vote: 'APPROVE',
184
+ confidence: 0.75,
185
+ durationMs: 5500,
186
+ reasoningSummary: 'Hybrid approach is pragmatic. GraphQL for data queries, REST for auth/webhooks. Apollo Server + Express coexistence is well-documented. Estimated 2-week setup overhead, offset by 30% faster feature velocity.',
187
+ emittedAt: new Date(),
188
+ },
189
+ delayMs: 800,
190
+ },
191
+ {
192
+ type: 'phase:complete',
193
+ payload: { deliberationId: DELIB_ID, phase: 'cross-examination', roundNumber: 1, durationMs: 5800, emittedAt: new Date() },
194
+ delayMs: 300,
195
+ },
196
+ // ── Phase 3: Final Vote ──────────────────────────────────────
197
+ {
198
+ type: 'phase:start',
199
+ payload: { deliberationId: DELIB_ID, phase: 'final-vote', roundNumber: 1, emittedAt: new Date() },
200
+ delayMs: 500,
201
+ },
202
+ {
203
+ type: 'unit:start',
204
+ payload: { deliberationId: DELIB_ID, unit: 'MELCHIOR', phase: 'final-vote', emittedAt: new Date() },
205
+ delayMs: 200,
206
+ },
207
+ {
208
+ type: 'unit:start',
209
+ payload: { deliberationId: DELIB_ID, unit: 'BALTHASAR', phase: 'final-vote', emittedAt: new Date() },
210
+ delayMs: 100,
211
+ },
212
+ {
213
+ type: 'unit:start',
214
+ payload: { deliberationId: DELIB_ID, unit: 'CASPER', phase: 'final-vote', emittedAt: new Date() },
215
+ delayMs: 100,
216
+ },
217
+ {
218
+ type: 'unit:complete',
219
+ payload: {
220
+ deliberationId: DELIB_ID,
221
+ unit: 'MELCHIOR',
222
+ phase: 'final-vote',
223
+ vote: 'APPROVE',
224
+ confidence: 0.85,
225
+ durationMs: 2200,
226
+ reasoningSummary: 'GraphQL with security guardrails. Query depth limit, persisted queries, complexity budget.',
227
+ emittedAt: new Date(),
228
+ },
229
+ delayMs: 1500,
230
+ },
231
+ {
232
+ type: 'unit:complete',
233
+ payload: {
234
+ deliberationId: DELIB_ID,
235
+ unit: 'BALTHASAR',
236
+ phase: 'final-vote',
237
+ vote: 'APPROVE',
238
+ confidence: 0.80,
239
+ durationMs: 2000,
240
+ reasoningSummary: 'Hybrid GraphQL + REST. Apollo Server for queries, Express for auth/webhooks.',
241
+ emittedAt: new Date(),
242
+ },
243
+ delayMs: 800,
244
+ },
245
+ {
246
+ type: 'unit:complete',
247
+ payload: {
248
+ deliberationId: DELIB_ID,
249
+ unit: 'CASPER',
250
+ phase: 'final-vote',
251
+ vote: 'APPROVE',
252
+ confidence: 0.72,
253
+ durationMs: 2500,
254
+ reasoningSummary: 'Conditional approval. GraphQL acceptable with: persisted queries mandatory, depth ≤10, complexity budget, REST fallback for auth.',
255
+ emittedAt: new Date(),
256
+ },
257
+ delayMs: 1200,
258
+ },
259
+ {
260
+ type: 'phase:complete',
261
+ payload: { deliberationId: DELIB_ID, phase: 'final-vote', roundNumber: 1, durationMs: 2500, emittedAt: new Date() },
262
+ delayMs: 300,
263
+ },
264
+ // ── Deliberation Complete (Unanimous) ────────────────────────
265
+ {
266
+ type: 'deliberation:complete',
267
+ payload: {
268
+ deliberationId: DELIB_ID,
269
+ decision: 'UNANIMOUS_APPROVE',
270
+ totalDurationMs: 18500,
271
+ rounds: 1,
272
+ fromCache: false,
273
+ emittedAt: new Date(),
274
+ },
275
+ delayMs: 500,
276
+ },
277
+ ];
@@ -0,0 +1,83 @@
1
+ /**
2
+ * AngelDetector -- 使徒検知システム (A-04)
3
+ *
4
+ * Monitors git diffs for 6 types of suspicious patterns (Angels):
5
+ *
6
+ * SACHIEL - Security-sensitive file changes
7
+ * SHAMSHEL - High-coupling hub node changes (co-change graph)
8
+ * RAMIEL - High-entropy (scattered) changes
9
+ * GAGHIEL - Ancient code modification
10
+ * ISRAFEL - Multi-developer conflict zones
11
+ * IRUEL - Boil-frog micro changes (death by a thousand cuts)
12
+ *
13
+ * Each detection returns a ThreatLevel (YELLOW / ORANGE / RED)
14
+ * and emits 'angel:detected' events via the MagiEventBus.
15
+ */
16
+ import type { AngelDetection, AngelDetectorConfig, CochangeEdge } from '../types/phase-k.js';
17
+ import type { MagiEventBus } from './events.js';
18
+ export declare class AngelDetector {
19
+ private readonly config;
20
+ private readonly eventBus?;
21
+ private cochangeGraph;
22
+ private microChangeHistory;
23
+ private watchTimer;
24
+ private lastWatchedCommit;
25
+ constructor(config?: Partial<AngelDetectorConfig>, eventBus?: MagiEventBus);
26
+ /**
27
+ * Run all 6 angel detectors against a single commit.
28
+ * Returns all detected angels (may be empty).
29
+ */
30
+ detectFromCommit(commitHash: string, cwd?: string): Promise<AngelDetection[]>;
31
+ /**
32
+ * Start polling for new commits and running detection.
33
+ */
34
+ startWatch(cwd?: string): void;
35
+ /**
36
+ * Stop the watch polling.
37
+ */
38
+ stopWatch(): void;
39
+ /**
40
+ * Get the current co-change graph.
41
+ */
42
+ getCochangeGraph(): CochangeEdge[];
43
+ /**
44
+ * Rebuild the internal co-change graph from a set of commits.
45
+ */
46
+ updateCochangeGraph(commitHashes: string[], cwd?: string): Promise<void>;
47
+ /**
48
+ * SACHIEL: Detect security-sensitive file changes.
49
+ * Checks if any changed file matches securityPatterns.
50
+ * ThreatLevel based on LA + LD: >100 RED, >30 ORANGE, else YELLOW.
51
+ */
52
+ private detectSachiel;
53
+ /**
54
+ * SHAMSHEL: Detect high-coupling hub node changes.
55
+ * If any changed file has degree >= hubDegreeThreshold in the co-change graph.
56
+ * RED if degree > 2x threshold, ORANGE if > 1.5x.
57
+ */
58
+ private detectShamshel;
59
+ /**
60
+ * RAMIEL: Detect high-entropy (scattered) changes.
61
+ * If computeEntropy(changes) > entropyThreshold.
62
+ * RED if > 0.95, ORANGE if > threshold.
63
+ */
64
+ private detectRamiel;
65
+ /**
66
+ * GAGHIEL: Detect ancient code modification.
67
+ * If any file age > ancientThresholdDays.
68
+ * RED if > 3x threshold, ORANGE if > 2x, else YELLOW.
69
+ */
70
+ private detectGaghiel;
71
+ /**
72
+ * ISRAFEL: Detect multi-developer conflict zones.
73
+ * If any file has NDEV >= 3 and changes from multiple devs.
74
+ * RED if NDEV >= 5, ORANGE if NDEV >= 3.
75
+ */
76
+ private detectIsrafel;
77
+ /**
78
+ * IRUEL: Detect boil-frog micro changes.
79
+ * Track commits with < 5 total lines changed.
80
+ * If accumulated micro-change count exceeds threshold, alert.
81
+ */
82
+ private detectIruel;
83
+ }