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.
- package/LICENSE +21 -0
- package/README.ja.md +377 -0
- package/README.md +377 -0
- package/dist/bin/magi-benchmark.d.ts +14 -0
- package/dist/bin/magi-benchmark.js +93 -0
- package/dist/bin/magi-mcp.d.ts +8 -0
- package/dist/bin/magi-mcp.js +28 -0
- package/dist/bin/magi.d.ts +2 -0
- package/dist/bin/magi.js +634 -0
- package/dist/src/adapters/base.d.ts +34 -0
- package/dist/src/adapters/base.js +149 -0
- package/dist/src/adapters/claude.d.ts +29 -0
- package/dist/src/adapters/claude.js +65 -0
- package/dist/src/adapters/codex.d.ts +21 -0
- package/dist/src/adapters/codex.js +41 -0
- package/dist/src/adapters/gemini.d.ts +18 -0
- package/dist/src/adapters/gemini.js +31 -0
- package/dist/src/adapters/registry.d.ts +19 -0
- package/dist/src/adapters/registry.js +59 -0
- package/dist/src/audit/hash-chain.d.ts +21 -0
- package/dist/src/audit/hash-chain.js +70 -0
- package/dist/src/audit/types.d.ts +25 -0
- package/dist/src/audit/types.js +1 -0
- package/dist/src/audit/writer.d.ts +18 -0
- package/dist/src/audit/writer.js +100 -0
- package/dist/src/benchmark/golden-tasks.d.ts +9 -0
- package/dist/src/benchmark/golden-tasks.js +476 -0
- package/dist/src/benchmark/reporter.d.ts +5 -0
- package/dist/src/benchmark/reporter.js +107 -0
- package/dist/src/benchmark/runner.d.ts +30 -0
- package/dist/src/benchmark/runner.js +224 -0
- package/dist/src/benchmark/scorer.d.ts +12 -0
- package/dist/src/benchmark/scorer.js +124 -0
- package/dist/src/benchmark/types.d.ts +54 -0
- package/dist/src/benchmark/types.js +1 -0
- package/dist/src/cache/deliberation-cache.d.ts +49 -0
- package/dist/src/cache/deliberation-cache.js +127 -0
- package/dist/src/cli/commands/config-cmd.d.ts +11 -0
- package/dist/src/cli/commands/config-cmd.js +190 -0
- package/dist/src/cli/commands/demo.d.ts +12 -0
- package/dist/src/cli/commands/demo.js +66 -0
- package/dist/src/cli/commands/setup.d.ts +7 -0
- package/dist/src/cli/commands/setup.js +182 -0
- package/dist/src/cli/i18n.d.ts +89 -0
- package/dist/src/cli/i18n.js +176 -0
- package/dist/src/cli/interactive-select.d.ts +27 -0
- package/dist/src/cli/interactive-select.js +130 -0
- package/dist/src/cli/tui-setup.d.ts +24 -0
- package/dist/src/cli/tui-setup.js +42 -0
- package/dist/src/config/cli-detector.d.ts +37 -0
- package/dist/src/config/cli-detector.js +99 -0
- package/dist/src/config/user-config.d.ts +81 -0
- package/dist/src/config/user-config.js +134 -0
- package/dist/src/context/auto-collector.d.ts +43 -0
- package/dist/src/context/auto-collector.js +337 -0
- package/dist/src/context/manager.d.ts +35 -0
- package/dist/src/context/manager.js +162 -0
- package/dist/src/context/serializer.d.ts +20 -0
- package/dist/src/context/serializer.js +52 -0
- package/dist/src/demo/recorded-deliberation.d.ts +13 -0
- package/dist/src/demo/recorded-deliberation.js +277 -0
- package/dist/src/engine/angel-detector.d.ts +83 -0
- package/dist/src/engine/angel-detector.js +334 -0
- package/dist/src/engine/at-field.d.ts +40 -0
- package/dist/src/engine/at-field.js +195 -0
- package/dist/src/engine/berserk-orchestrator.d.ts +66 -0
- package/dist/src/engine/berserk-orchestrator.js +378 -0
- package/dist/src/engine/change-metrics.d.ts +56 -0
- package/dist/src/engine/change-metrics.js +214 -0
- package/dist/src/engine/consensus.d.ts +20 -0
- package/dist/src/engine/consensus.js +146 -0
- package/dist/src/engine/dead-sea-scrolls.d.ts +132 -0
- package/dist/src/engine/dead-sea-scrolls.js +610 -0
- package/dist/src/engine/drift-detector.d.ts +39 -0
- package/dist/src/engine/drift-detector.js +225 -0
- package/dist/src/engine/dummy-plug.d.ts +44 -0
- package/dist/src/engine/dummy-plug.js +190 -0
- package/dist/src/engine/engram-manager.d.ts +55 -0
- package/dist/src/engine/engram-manager.js +306 -0
- package/dist/src/engine/events.d.ts +130 -0
- package/dist/src/engine/events.js +44 -0
- package/dist/src/engine/gospel.d.ts +30 -0
- package/dist/src/engine/gospel.js +129 -0
- package/dist/src/engine/hallucination-detector.d.ts +33 -0
- package/dist/src/engine/hallucination-detector.js +215 -0
- package/dist/src/engine/human-resolver.d.ts +19 -0
- package/dist/src/engine/human-resolver.js +89 -0
- package/dist/src/engine/instrumentality.d.ts +64 -0
- package/dist/src/engine/instrumentality.js +297 -0
- package/dist/src/engine/iruel-battle.d.ts +79 -0
- package/dist/src/engine/iruel-battle.js +319 -0
- package/dist/src/engine/kernel/deliberation-kernel.d.ts +12 -0
- package/dist/src/engine/kernel/deliberation-kernel.js +303 -0
- package/dist/src/engine/kernel/index.d.ts +8 -0
- package/dist/src/engine/kernel/index.js +7 -0
- package/dist/src/engine/kernel/phase-runner.d.ts +10 -0
- package/dist/src/engine/kernel/phase-runner.js +155 -0
- package/dist/src/engine/kernel/post-processor.d.ts +17 -0
- package/dist/src/engine/kernel/post-processor.js +131 -0
- package/dist/src/engine/kernel/types.d.ts +107 -0
- package/dist/src/engine/kernel/types.js +1 -0
- package/dist/src/engine/kernel/unit-executor.d.ts +6 -0
- package/dist/src/engine/kernel/unit-executor.js +132 -0
- package/dist/src/engine/lcl-manager.d.ts +44 -0
- package/dist/src/engine/lcl-manager.js +143 -0
- package/dist/src/engine/middleware/cache.d.ts +7 -0
- package/dist/src/engine/middleware/cache.js +29 -0
- package/dist/src/engine/middleware/chain.d.ts +18 -0
- package/dist/src/engine/middleware/chain.js +45 -0
- package/dist/src/engine/middleware/firewall.d.ts +8 -0
- package/dist/src/engine/middleware/firewall.js +24 -0
- package/dist/src/engine/middleware/index.d.ts +4 -0
- package/dist/src/engine/middleware/index.js +3 -0
- package/dist/src/engine/middleware/types.d.ts +43 -0
- package/dist/src/engine/middleware/types.js +1 -0
- package/dist/src/engine/nebuchadnezzar-key.d.ts +61 -0
- package/dist/src/engine/nebuchadnezzar-key.js +203 -0
- package/dist/src/engine/neon-genesis.d.ts +52 -0
- package/dist/src/engine/neon-genesis.js +203 -0
- package/dist/src/engine/objective-judge.d.ts +53 -0
- package/dist/src/engine/objective-judge.js +214 -0
- package/dist/src/engine/offline-mode.d.ts +18 -0
- package/dist/src/engine/offline-mode.js +46 -0
- package/dist/src/engine/orchestrator.d.ts +79 -0
- package/dist/src/engine/orchestrator.js +58 -0
- package/dist/src/engine/secret-cipher.d.ts +26 -0
- package/dist/src/engine/secret-cipher.js +114 -0
- package/dist/src/engine/seele-council.d.ts +90 -0
- package/dist/src/engine/seele-council.js +482 -0
- package/dist/src/engine/self-destruct.d.ts +61 -0
- package/dist/src/engine/self-destruct.js +231 -0
- package/dist/src/engine/self-evolution.d.ts +64 -0
- package/dist/src/engine/self-evolution.js +368 -0
- package/dist/src/engine/sync-rate.d.ts +45 -0
- package/dist/src/engine/sync-rate.js +151 -0
- package/dist/src/engine/type666-firewall.d.ts +76 -0
- package/dist/src/engine/type666-firewall.js +343 -0
- package/dist/src/engine/umbilical-cable.d.ts +41 -0
- package/dist/src/engine/umbilical-cable.js +192 -0
- package/dist/src/index.d.ts +106 -0
- package/dist/src/index.js +426 -0
- package/dist/src/mcp/server.d.ts +38 -0
- package/dist/src/mcp/server.js +196 -0
- package/dist/src/metrics/token-tracker.d.ts +38 -0
- package/dist/src/metrics/token-tracker.js +112 -0
- package/dist/src/parsers/json-extractor.d.ts +9 -0
- package/dist/src/parsers/json-extractor.js +239 -0
- package/dist/src/parsers/opinion-schema.d.ts +81 -0
- package/dist/src/parsers/opinion-schema.js +147 -0
- package/dist/src/parsers/unstructured-parser.d.ts +20 -0
- package/dist/src/parsers/unstructured-parser.js +122 -0
- package/dist/src/pipelines/architecture.d.ts +10 -0
- package/dist/src/pipelines/architecture.js +9 -0
- package/dist/src/pipelines/bug-analysis.d.ts +9 -0
- package/dist/src/pipelines/bug-analysis.js +8 -0
- package/dist/src/pipelines/code-review.d.ts +10 -0
- package/dist/src/pipelines/code-review.js +30 -0
- package/dist/src/pipelines/custom.d.ts +14 -0
- package/dist/src/pipelines/custom.js +29 -0
- package/dist/src/pipelines/registry.d.ts +9 -0
- package/dist/src/pipelines/registry.js +20 -0
- package/dist/src/prompts/personas.d.ts +6 -0
- package/dist/src/prompts/personas.js +44 -0
- package/dist/src/prompts/schemas.d.ts +4 -0
- package/dist/src/prompts/schemas.js +24 -0
- package/dist/src/prompts/templates.d.ts +6 -0
- package/dist/src/prompts/templates.js +91 -0
- package/dist/src/repl/accessibility.d.ts +23 -0
- package/dist/src/repl/accessibility.js +46 -0
- package/dist/src/repl/banner.d.ts +4 -0
- package/dist/src/repl/banner.js +28 -0
- package/dist/src/repl/boot-animation.d.ts +13 -0
- package/dist/src/repl/boot-animation.js +143 -0
- package/dist/src/repl/completer.d.ts +21 -0
- package/dist/src/repl/completer.js +168 -0
- package/dist/src/repl/context.d.ts +24 -0
- package/dist/src/repl/context.js +42 -0
- package/dist/src/repl/display-utils.d.ts +13 -0
- package/dist/src/repl/display-utils.js +65 -0
- package/dist/src/repl/event-listener.d.ts +18 -0
- package/dist/src/repl/event-listener.js +112 -0
- package/dist/src/repl/export-formatter.d.ts +8 -0
- package/dist/src/repl/export-formatter.js +73 -0
- package/dist/src/repl/ghost-text.d.ts +31 -0
- package/dist/src/repl/ghost-text.js +119 -0
- package/dist/src/repl/handoff-animation.d.ts +15 -0
- package/dist/src/repl/handoff-animation.js +65 -0
- package/dist/src/repl/history.d.ts +16 -0
- package/dist/src/repl/history.js +130 -0
- package/dist/src/repl/job-registry.d.ts +26 -0
- package/dist/src/repl/job-registry.js +80 -0
- package/dist/src/repl/magi-repl.d.ts +72 -0
- package/dist/src/repl/magi-repl.js +1008 -0
- package/dist/src/repl/multiline-input.d.ts +45 -0
- package/dist/src/repl/multiline-input.js +78 -0
- package/dist/src/repl/prompt-builder.d.ts +19 -0
- package/dist/src/repl/prompt-builder.js +36 -0
- package/dist/src/repl/repl-state.d.ts +5 -0
- package/dist/src/repl/repl-state.js +19 -0
- package/dist/src/repl/result-display.d.ts +8 -0
- package/dist/src/repl/result-display.js +195 -0
- package/dist/src/repl/session-stats.d.ts +26 -0
- package/dist/src/repl/session-stats.js +119 -0
- package/dist/src/repl/slash-commands.d.ts +60 -0
- package/dist/src/repl/slash-commands.js +725 -0
- package/dist/src/repl/terminal-sanitize.d.ts +14 -0
- package/dist/src/repl/terminal-sanitize.js +19 -0
- package/dist/src/reporters/console.d.ts +7 -0
- package/dist/src/reporters/console.js +78 -0
- package/dist/src/reporters/json.d.ts +2 -0
- package/dist/src/reporters/json.js +3 -0
- package/dist/src/reporters/markdown.d.ts +2 -0
- package/dist/src/reporters/markdown.js +65 -0
- package/dist/src/reporters/streaming.d.ts +20 -0
- package/dist/src/reporters/streaming.js +178 -0
- package/dist/src/tui/activity-log.d.ts +23 -0
- package/dist/src/tui/activity-log.js +67 -0
- package/dist/src/tui/animations.d.ts +39 -0
- package/dist/src/tui/animations.js +167 -0
- package/dist/src/tui/ansi.d.ts +28 -0
- package/dist/src/tui/ansi.js +51 -0
- package/dist/src/tui/boot-sequence.d.ts +11 -0
- package/dist/src/tui/boot-sequence.js +98 -0
- package/dist/src/tui/colors.d.ts +101 -0
- package/dist/src/tui/colors.js +71 -0
- package/dist/src/tui/header.d.ts +24 -0
- package/dist/src/tui/header.js +122 -0
- package/dist/src/tui/index.d.ts +3 -0
- package/dist/src/tui/index.js +3 -0
- package/dist/src/tui/keypress.d.ts +25 -0
- package/dist/src/tui/keypress.js +95 -0
- package/dist/src/tui/layout.d.ts +74 -0
- package/dist/src/tui/layout.js +171 -0
- package/dist/src/tui/magi-tui.d.ts +101 -0
- package/dist/src/tui/magi-tui.js +754 -0
- package/dist/src/tui/panel.d.ts +45 -0
- package/dist/src/tui/panel.js +292 -0
- package/dist/src/tui/screen-buffer.d.ts +54 -0
- package/dist/src/tui/screen-buffer.js +262 -0
- package/dist/src/tui/status-bar.d.ts +25 -0
- package/dist/src/tui/status-bar.js +124 -0
- package/dist/src/tui/terminal-detect.d.ts +26 -0
- package/dist/src/tui/terminal-detect.js +44 -0
- package/dist/src/tui/tui-helpers.d.ts +12 -0
- package/dist/src/tui/tui-helpers.js +37 -0
- package/dist/src/types/adapter.d.ts +75 -0
- package/dist/src/types/adapter.js +36 -0
- package/dist/src/types/config.d.ts +108 -0
- package/dist/src/types/config.js +85 -0
- package/dist/src/types/consensus.d.ts +55 -0
- package/dist/src/types/consensus.js +17 -0
- package/dist/src/types/core.d.ts +178 -0
- package/dist/src/types/core.js +85 -0
- package/dist/src/types/magi-api.d.ts +62 -0
- package/dist/src/types/magi-api.js +7 -0
- package/dist/src/types/phase-h.d.ts +142 -0
- package/dist/src/types/phase-h.js +7 -0
- package/dist/src/types/phase-i.d.ts +186 -0
- package/dist/src/types/phase-i.js +6 -0
- package/dist/src/types/phase-k.d.ts +259 -0
- package/dist/src/types/phase-k.js +6 -0
- package/dist/src/types/phase-l.d.ts +199 -0
- package/dist/src/types/phase-l.js +6 -0
- package/dist/src/types/pipeline.d.ts +37 -0
- package/dist/src/types/pipeline.js +2 -0
- package/dist/src/utils/abstain-factory.d.ts +2 -0
- package/dist/src/utils/abstain-factory.js +18 -0
- package/dist/src/utils/errors.d.ts +34 -0
- package/dist/src/utils/errors.js +59 -0
- package/dist/src/utils/file-validator.d.ts +50 -0
- package/dist/src/utils/file-validator.js +124 -0
- package/dist/src/utils/fire-and-forget.d.ts +5 -0
- package/dist/src/utils/fire-and-forget.js +10 -0
- package/dist/src/utils/flag-validator.d.ts +21 -0
- package/dist/src/utils/flag-validator.js +79 -0
- package/dist/src/utils/freeze.d.ts +8 -0
- package/dist/src/utils/freeze.js +16 -0
- package/dist/src/utils/language-detector.d.ts +16 -0
- package/dist/src/utils/language-detector.js +159 -0
- package/dist/src/utils/latency-tracker.d.ts +45 -0
- package/dist/src/utils/latency-tracker.js +100 -0
- package/dist/src/utils/logger.d.ts +33 -0
- package/dist/src/utils/logger.js +112 -0
- package/dist/src/utils/process.d.ts +40 -0
- package/dist/src/utils/process.js +253 -0
- package/dist/src/utils/retry.d.ts +12 -0
- package/dist/src/utils/retry.js +30 -0
- package/dist/src/utils/safe-fs.d.ts +38 -0
- package/dist/src/utils/safe-fs.js +56 -0
- package/dist/src/utils/safe-json-parse.d.ts +15 -0
- package/dist/src/utils/safe-json-parse.js +49 -0
- package/dist/src/utils/sanitize.d.ts +14 -0
- package/dist/src/utils/sanitize.js +186 -0
- package/dist/src/utils/semaphore.d.ts +22 -0
- package/dist/src/utils/semaphore.js +57 -0
- package/dist/src/utils/shutdown.d.ts +6 -0
- package/dist/src/utils/shutdown.js +51 -0
- package/dist/src/utils/tty.d.ts +5 -0
- package/dist/src/utils/tty.js +7 -0
- package/package.json +82 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EngramManager — 記憶・人格モジュール (A-01)
|
|
3
|
+
*
|
|
4
|
+
* MAGIシステムの長期記憶。過去の審議をエピソード記憶として保存し、
|
|
5
|
+
* TF-IDFベースの類似検索で関連する過去審議を検索する。
|
|
6
|
+
* 50件ごとに自動でセマンティック圧縮を行い、
|
|
7
|
+
* 手続き的戦略(どのタスクタイプでどの戦略が有効か)を蓄積する。
|
|
8
|
+
*/
|
|
9
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { isDeadlock } from '../types/consensus.js';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
13
|
+
import { safeMkdir, safeWriteFile } from '../utils/safe-fs.js';
|
|
14
|
+
import { safeJsonParse } from '../utils/safe-json-parse.js';
|
|
15
|
+
// ── TF-IDF Engine (built-in) ─────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* Tokenize text for TF-IDF: English space-split + Japanese bi-gram.
|
|
18
|
+
* Simple but effective for mixed-language deliberation content.
|
|
19
|
+
*/
|
|
20
|
+
export function tokenize(text) {
|
|
21
|
+
const tokens = [];
|
|
22
|
+
// English words (lowercase, 2+ chars)
|
|
23
|
+
const english = text.match(/[a-zA-Z_][a-zA-Z0-9_]*/g);
|
|
24
|
+
if (english) {
|
|
25
|
+
for (const w of english) {
|
|
26
|
+
if (w.length >= 2)
|
|
27
|
+
tokens.push(w.toLowerCase());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Japanese bi-grams (CJK Unified Ideographs + Hiragana + Katakana)
|
|
31
|
+
const japanese = text.replace(/[a-zA-Z0-9\s_.,;:!?'"()[{}/<>@#$%^&*+=|\\~`\]-]/g, '');
|
|
32
|
+
for (let i = 0; i < japanese.length - 1; i++) {
|
|
33
|
+
tokens.push(japanese.slice(i, i + 2));
|
|
34
|
+
}
|
|
35
|
+
return tokens;
|
|
36
|
+
}
|
|
37
|
+
/** Compute TF (term frequency) for a token list */
|
|
38
|
+
function computeTf(tokens) {
|
|
39
|
+
const tf = new Map();
|
|
40
|
+
for (const t of tokens) {
|
|
41
|
+
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
42
|
+
}
|
|
43
|
+
const total = tokens.length || 1;
|
|
44
|
+
for (const [k, v] of tf) {
|
|
45
|
+
tf.set(k, v / total);
|
|
46
|
+
}
|
|
47
|
+
return tf;
|
|
48
|
+
}
|
|
49
|
+
/** Compute IDF (inverse document frequency) from multiple documents */
|
|
50
|
+
function computeIdf(docs) {
|
|
51
|
+
const df = new Map();
|
|
52
|
+
for (const tokens of docs) {
|
|
53
|
+
const unique = new Set(tokens);
|
|
54
|
+
for (const t of unique) {
|
|
55
|
+
df.set(t, (df.get(t) ?? 0) + 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const N = docs.length || 1;
|
|
59
|
+
const idf = new Map();
|
|
60
|
+
for (const [term, count] of df) {
|
|
61
|
+
idf.set(term, Math.log((N + 1) / (count + 1)) + 1); // smoothed IDF
|
|
62
|
+
}
|
|
63
|
+
return idf;
|
|
64
|
+
}
|
|
65
|
+
/** Compute TF-IDF vector as a Map<term, weight> */
|
|
66
|
+
function computeTfIdf(tokens, idf) {
|
|
67
|
+
const tf = computeTf(tokens);
|
|
68
|
+
const tfidf = new Map();
|
|
69
|
+
for (const [term, tfVal] of tf) {
|
|
70
|
+
const idfVal = idf.get(term) ?? 1;
|
|
71
|
+
tfidf.set(term, tfVal * idfVal);
|
|
72
|
+
}
|
|
73
|
+
return tfidf;
|
|
74
|
+
}
|
|
75
|
+
/** Cosine similarity between two TF-IDF vectors */
|
|
76
|
+
export function cosineSimilarity(a, b) {
|
|
77
|
+
let dot = 0;
|
|
78
|
+
let normA = 0;
|
|
79
|
+
let normB = 0;
|
|
80
|
+
for (const [term, val] of a) {
|
|
81
|
+
normA += val * val;
|
|
82
|
+
const bVal = b.get(term);
|
|
83
|
+
if (bVal !== undefined)
|
|
84
|
+
dot += val * bVal;
|
|
85
|
+
}
|
|
86
|
+
for (const val of b.values()) {
|
|
87
|
+
normB += val * val;
|
|
88
|
+
}
|
|
89
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
90
|
+
return denom === 0 ? 0 : dot / denom;
|
|
91
|
+
}
|
|
92
|
+
/** Built-in TF-IDF similarity engine */
|
|
93
|
+
export class TfIdfEngine {
|
|
94
|
+
computeSimilarity(query, documents) {
|
|
95
|
+
if (documents.length === 0)
|
|
96
|
+
return [];
|
|
97
|
+
const queryTokens = tokenize(query);
|
|
98
|
+
const docTokensList = documents.map(d => tokenize(d));
|
|
99
|
+
const allDocs = [queryTokens, ...docTokensList];
|
|
100
|
+
const idf = computeIdf(allDocs);
|
|
101
|
+
const queryVec = computeTfIdf(queryTokens, idf);
|
|
102
|
+
return docTokensList.map(docTokens => {
|
|
103
|
+
const docVec = computeTfIdf(docTokens, idf);
|
|
104
|
+
return cosineSimilarity(queryVec, docVec);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ── EngramManager ────────────────────────────────────────────
|
|
109
|
+
const CONSOLIDATION_THRESHOLD = 50;
|
|
110
|
+
export class EngramManager {
|
|
111
|
+
episodic = [];
|
|
112
|
+
semantic = [];
|
|
113
|
+
procedural = [];
|
|
114
|
+
baseDir;
|
|
115
|
+
engine;
|
|
116
|
+
eventBus;
|
|
117
|
+
episodicSinceConsolidation = 0;
|
|
118
|
+
constructor(workspaceDir, eventBus, engine) {
|
|
119
|
+
this.baseDir = join(workspaceDir, 'engram');
|
|
120
|
+
this.eventBus = eventBus;
|
|
121
|
+
this.engine = engine ?? new TfIdfEngine();
|
|
122
|
+
}
|
|
123
|
+
/** Record a completed deliberation as episodic memory (fire-and-forget safe) */
|
|
124
|
+
async recordDeliberation(delib) {
|
|
125
|
+
const lastRound = delib.rounds[delib.rounds.length - 1];
|
|
126
|
+
const allKeyPoints = lastRound?.opinions.flatMap(o => o.keyPoints) ?? [];
|
|
127
|
+
const memory = {
|
|
128
|
+
deliberationId: delib.id,
|
|
129
|
+
taskType: delib.task.type,
|
|
130
|
+
taskTitle: delib.task.title,
|
|
131
|
+
taskDescription: delib.task.description,
|
|
132
|
+
decision: delib.consensus.decision,
|
|
133
|
+
confidence: delib.consensus.confidence,
|
|
134
|
+
keyPoints: allKeyPoints,
|
|
135
|
+
units: lastRound?.opinions.map(o => o.unit) ?? [],
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
};
|
|
138
|
+
this.episodic.push(memory);
|
|
139
|
+
this.episodicSinceConsolidation++;
|
|
140
|
+
// Persist episodic memory (fire-and-forget)
|
|
141
|
+
this.persistEpisodic(memory).catch(err => {
|
|
142
|
+
logger.warn('Failed to persist episodic memory', { error: String(err) });
|
|
143
|
+
});
|
|
144
|
+
// Auto-consolidation
|
|
145
|
+
if (this.episodicSinceConsolidation >= CONSOLIDATION_THRESHOLD) {
|
|
146
|
+
await this.consolidate();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Find similar past deliberations using TF-IDF cosine similarity */
|
|
150
|
+
findSimilar(taskDescription, limit = 3) {
|
|
151
|
+
if (this.episodic.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
const documents = this.episodic.map(e => `${e.taskTitle} ${e.taskDescription} ${e.keyPoints.join(' ')}`);
|
|
154
|
+
const scores = this.engine.computeSimilarity(taskDescription, documents);
|
|
155
|
+
const indexed = scores.map((score, i) => ({ score, index: i }));
|
|
156
|
+
indexed.sort((a, b) => b.score - a.score);
|
|
157
|
+
return indexed.slice(0, limit).map(({ index }) => this.episodic[index]);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Consolidate episodic memories → semantic patterns + procedural strategies.
|
|
161
|
+
* Rule-based extraction (no AI call needed).
|
|
162
|
+
*/
|
|
163
|
+
async consolidate() {
|
|
164
|
+
if (this.episodic.length === 0)
|
|
165
|
+
return;
|
|
166
|
+
const now = new Date().toISOString();
|
|
167
|
+
// Extract semantic patterns: group by frequent keyPoints
|
|
168
|
+
const pointFreq = new Map();
|
|
169
|
+
for (const ep of this.episodic) {
|
|
170
|
+
for (const kp of ep.keyPoints) {
|
|
171
|
+
const normalized = kp.toLowerCase().trim();
|
|
172
|
+
if (normalized.length < 5)
|
|
173
|
+
continue;
|
|
174
|
+
const entry = pointFreq.get(normalized) ?? { count: 0, domains: new Set(), totalConf: 0 };
|
|
175
|
+
entry.count++;
|
|
176
|
+
entry.domains.add(ep.taskType);
|
|
177
|
+
entry.totalConf += ep.confidence;
|
|
178
|
+
pointFreq.set(normalized, entry);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const newPatterns = [];
|
|
182
|
+
for (const [pattern, data] of pointFreq) {
|
|
183
|
+
if (data.count >= 2) {
|
|
184
|
+
newPatterns.push({
|
|
185
|
+
pattern,
|
|
186
|
+
frequency: data.count,
|
|
187
|
+
domains: [...data.domains],
|
|
188
|
+
confidence: data.totalConf / data.count,
|
|
189
|
+
extractedAt: now,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
this.semantic = newPatterns;
|
|
194
|
+
// Extract procedural strategies: success rate by taskType×decision
|
|
195
|
+
const stratMap = new Map();
|
|
196
|
+
for (const ep of this.episodic) {
|
|
197
|
+
const key = ep.taskType;
|
|
198
|
+
const entry = stratMap.get(key) ?? { success: 0, total: 0, lastUsed: ep.timestamp };
|
|
199
|
+
entry.total++;
|
|
200
|
+
if (!isDeadlock(ep.decision))
|
|
201
|
+
entry.success++;
|
|
202
|
+
if (ep.timestamp > entry.lastUsed)
|
|
203
|
+
entry.lastUsed = ep.timestamp;
|
|
204
|
+
stratMap.set(key, entry);
|
|
205
|
+
}
|
|
206
|
+
this.procedural = [];
|
|
207
|
+
for (const [taskType, data] of stratMap) {
|
|
208
|
+
this.procedural.push({
|
|
209
|
+
taskType,
|
|
210
|
+
strategy: `${taskType} deliberation pattern`,
|
|
211
|
+
successRate: data.total > 0 ? data.success / data.total : 0,
|
|
212
|
+
sampleCount: data.total,
|
|
213
|
+
lastUsed: data.lastUsed,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
this.episodicSinceConsolidation = 0;
|
|
217
|
+
// Persist consolidated data
|
|
218
|
+
await Promise.allSettled([
|
|
219
|
+
this.persistSemantic(),
|
|
220
|
+
this.persistProcedural(),
|
|
221
|
+
]);
|
|
222
|
+
this.eventBus?.emit('memory:consolidated', {
|
|
223
|
+
episodicCount: this.episodic.length,
|
|
224
|
+
patternsExtracted: newPatterns.length,
|
|
225
|
+
strategiesUpdated: this.procedural.length,
|
|
226
|
+
emittedAt: new Date(),
|
|
227
|
+
});
|
|
228
|
+
logger.info('Memory consolidation complete', {
|
|
229
|
+
episodic: this.episodic.length,
|
|
230
|
+
patterns: newPatterns.length,
|
|
231
|
+
strategies: this.procedural.length,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
// ── Persistence ────────────────────────────────────────────
|
|
235
|
+
/** Load all memory from disk */
|
|
236
|
+
async load() {
|
|
237
|
+
await Promise.allSettled([
|
|
238
|
+
this.loadEpisodic(),
|
|
239
|
+
this.loadSemantic(),
|
|
240
|
+
this.loadProcedural(),
|
|
241
|
+
]);
|
|
242
|
+
}
|
|
243
|
+
/** Persist all memory to disk */
|
|
244
|
+
async persist() {
|
|
245
|
+
await Promise.allSettled([
|
|
246
|
+
this.persistSemantic(),
|
|
247
|
+
this.persistProcedural(),
|
|
248
|
+
]);
|
|
249
|
+
}
|
|
250
|
+
async persistEpisodic(memory) {
|
|
251
|
+
const dir = join(this.baseDir, 'episodic');
|
|
252
|
+
await safeMkdir(dir);
|
|
253
|
+
const filePath = join(dir, `${memory.deliberationId}.json`);
|
|
254
|
+
await safeWriteFile(filePath, JSON.stringify(memory, null, 2));
|
|
255
|
+
}
|
|
256
|
+
async loadEpisodic() {
|
|
257
|
+
const dir = join(this.baseDir, 'episodic');
|
|
258
|
+
try {
|
|
259
|
+
const files = await readdir(dir);
|
|
260
|
+
const jsons = files.filter(f => f.endsWith('.json'));
|
|
261
|
+
const memories = await Promise.allSettled(jsons.map(async (f) => {
|
|
262
|
+
const data = await readFile(join(dir, f), 'utf-8');
|
|
263
|
+
return safeJsonParse(data);
|
|
264
|
+
}));
|
|
265
|
+
this.episodic = memories
|
|
266
|
+
.filter((r) => r.status === 'fulfilled')
|
|
267
|
+
.map(r => r.value);
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
logger.debug('EngramManager: episodic memory directory not found', { error: String(err) });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async persistSemantic() {
|
|
274
|
+
const dir = join(this.baseDir, 'semantic');
|
|
275
|
+
await safeMkdir(dir);
|
|
276
|
+
await safeWriteFile(join(dir, 'patterns.json'), JSON.stringify(this.semantic, null, 2));
|
|
277
|
+
}
|
|
278
|
+
async loadSemantic() {
|
|
279
|
+
try {
|
|
280
|
+
const data = await readFile(join(this.baseDir, 'semantic', 'patterns.json'), 'utf-8');
|
|
281
|
+
this.semantic = safeJsonParse(data);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
logger.debug('EngramManager: semantic patterns not found', { error: String(err) });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async persistProcedural() {
|
|
288
|
+
const dir = join(this.baseDir, 'procedural');
|
|
289
|
+
await safeMkdir(dir);
|
|
290
|
+
await safeWriteFile(join(dir, 'strategies.json'), JSON.stringify(this.procedural, null, 2));
|
|
291
|
+
}
|
|
292
|
+
async loadProcedural() {
|
|
293
|
+
try {
|
|
294
|
+
const data = await readFile(join(this.baseDir, 'procedural', 'strategies.json'), 'utf-8');
|
|
295
|
+
this.procedural = safeJsonParse(data);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
logger.debug('EngramManager: procedural strategies not found', { error: String(err) });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// ── Getters (for tests and integration) ────────────────────
|
|
302
|
+
getEpisodicMemories() { return this.episodic; }
|
|
303
|
+
getSemanticPatterns() { return this.semantic; }
|
|
304
|
+
getProceduralStrategies() { return this.procedural; }
|
|
305
|
+
getEpisodicSinceConsolidation() { return this.episodicSinceConsolidation; }
|
|
306
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { ConsensusDecision } from '../types/consensus.js';
|
|
3
|
+
import type { SyncRateUpdatedEvent, BerserkWarningEvent, BiasDetectedEvent, GospelMilestoneEvent, UmbilicalStatusChangedEvent, DummyPlugActivatedEvent, DummyPlugRecoveryEvent, CipherDiscoveredEvent } from '../types/phase-h.js';
|
|
4
|
+
import type { MemoryDriftWarningEvent, MemoryConsolidatedEvent, BerserkActivatedEvent, BerserkDeactivatedEvent, InstrumentalityTriggeredEvent, FusionCompleteEvent, LCLContaminationDetectedEvent, LCLPurifiedEvent } from '../types/phase-i.js';
|
|
5
|
+
import type { AngelDetectedEvent, NebuchadnezzarOverrideEvent, ScrollsProphecyEvent, ScrollsFulfilledEvent, SelfDestructMotionEvent, SelfDestructCountdownEvent, SelfDestructExecutedEvent, SelfDestructAbortedEvent, FirewallScanEvent, FirewallBlockedEvent } from '../types/phase-k.js';
|
|
6
|
+
import type { EvolutionStartedEvent, EvolutionProposalEvent, EvolutionAppliedEvent, EvolutionRollbackEvent, IruelInfiltrationEvent, IruelCounterhackEvent, IruelNeutralizedEvent, SeeleGatheringEvent, SeeleAttackEvent, SeeleConcludedEvent, GenesisStartedEvent, GenesisCompleteEvent } from '../types/phase-l.js';
|
|
7
|
+
export interface DeliberationStartEvent {
|
|
8
|
+
deliberationId: string;
|
|
9
|
+
taskType: string;
|
|
10
|
+
taskTitle: string;
|
|
11
|
+
emittedAt: Date;
|
|
12
|
+
}
|
|
13
|
+
export interface PhaseStartEvent {
|
|
14
|
+
deliberationId: string;
|
|
15
|
+
phase: string;
|
|
16
|
+
roundNumber: number;
|
|
17
|
+
emittedAt: Date;
|
|
18
|
+
}
|
|
19
|
+
export interface UnitStartEvent {
|
|
20
|
+
deliberationId: string;
|
|
21
|
+
unit: string;
|
|
22
|
+
phase: string;
|
|
23
|
+
emittedAt: Date;
|
|
24
|
+
}
|
|
25
|
+
export interface UnitCompleteEvent {
|
|
26
|
+
deliberationId: string;
|
|
27
|
+
unit: string;
|
|
28
|
+
phase: string;
|
|
29
|
+
vote: string;
|
|
30
|
+
confidence: number;
|
|
31
|
+
durationMs: number;
|
|
32
|
+
reasoningSummary?: string;
|
|
33
|
+
emittedAt: Date;
|
|
34
|
+
}
|
|
35
|
+
export interface PhaseSkippedEvent {
|
|
36
|
+
deliberationId: string;
|
|
37
|
+
phase: string;
|
|
38
|
+
reason: 'unanimous_early_exit' | 'majority_early_exit';
|
|
39
|
+
emittedAt: Date;
|
|
40
|
+
}
|
|
41
|
+
export interface UnitChunkEvent {
|
|
42
|
+
deliberationId: string;
|
|
43
|
+
unit: string;
|
|
44
|
+
chunk: string;
|
|
45
|
+
stream: 'stdout' | 'stderr';
|
|
46
|
+
emittedAt: Date;
|
|
47
|
+
}
|
|
48
|
+
export interface UnitErrorEvent {
|
|
49
|
+
deliberationId: string;
|
|
50
|
+
unit: string;
|
|
51
|
+
phase: string;
|
|
52
|
+
error: string;
|
|
53
|
+
emittedAt: Date;
|
|
54
|
+
}
|
|
55
|
+
export interface PhaseCompleteEvent {
|
|
56
|
+
deliberationId: string;
|
|
57
|
+
phase: string;
|
|
58
|
+
roundNumber: number;
|
|
59
|
+
durationMs: number;
|
|
60
|
+
emittedAt: Date;
|
|
61
|
+
}
|
|
62
|
+
export interface DeliberationCompleteEvent {
|
|
63
|
+
deliberationId: string;
|
|
64
|
+
decision: ConsensusDecision;
|
|
65
|
+
totalDurationMs: number;
|
|
66
|
+
rounds: number;
|
|
67
|
+
fromCache: boolean;
|
|
68
|
+
emittedAt: Date;
|
|
69
|
+
}
|
|
70
|
+
export interface MagiEventMap {
|
|
71
|
+
'deliberation:start': DeliberationStartEvent;
|
|
72
|
+
'phase:start': PhaseStartEvent;
|
|
73
|
+
'unit:start': UnitStartEvent;
|
|
74
|
+
'unit:chunk': UnitChunkEvent;
|
|
75
|
+
'unit:complete': UnitCompleteEvent;
|
|
76
|
+
'unit:error': UnitErrorEvent;
|
|
77
|
+
'phase:complete': PhaseCompleteEvent;
|
|
78
|
+
'phase:skipped': PhaseSkippedEvent;
|
|
79
|
+
'deliberation:complete': DeliberationCompleteEvent;
|
|
80
|
+
'syncrate:updated': SyncRateUpdatedEvent;
|
|
81
|
+
'berserk:warning': BerserkWarningEvent;
|
|
82
|
+
'bias:detected': BiasDetectedEvent;
|
|
83
|
+
'gospel:milestone': GospelMilestoneEvent;
|
|
84
|
+
'umbilical:status_changed': UmbilicalStatusChangedEvent;
|
|
85
|
+
'dummyplug:activated': DummyPlugActivatedEvent;
|
|
86
|
+
'dummyplug:recovery': DummyPlugRecoveryEvent;
|
|
87
|
+
'cipher:discovered': CipherDiscoveredEvent;
|
|
88
|
+
'memory:drift-warning': MemoryDriftWarningEvent;
|
|
89
|
+
'memory:consolidated': MemoryConsolidatedEvent;
|
|
90
|
+
'berserk:activated': BerserkActivatedEvent;
|
|
91
|
+
'berserk:deactivated': BerserkDeactivatedEvent;
|
|
92
|
+
'instrumentality:triggered': InstrumentalityTriggeredEvent;
|
|
93
|
+
'fusion:complete': FusionCompleteEvent;
|
|
94
|
+
'lcl:contamination-detected': LCLContaminationDetectedEvent;
|
|
95
|
+
'lcl:purified': LCLPurifiedEvent;
|
|
96
|
+
'angel:detected': AngelDetectedEvent;
|
|
97
|
+
'nebuchadnezzar:override': NebuchadnezzarOverrideEvent;
|
|
98
|
+
'scrolls:prophecy': ScrollsProphecyEvent;
|
|
99
|
+
'scrolls:fulfilled': ScrollsFulfilledEvent;
|
|
100
|
+
'selfdestruct:motion': SelfDestructMotionEvent;
|
|
101
|
+
'selfdestruct:countdown': SelfDestructCountdownEvent;
|
|
102
|
+
'selfdestruct:executed': SelfDestructExecutedEvent;
|
|
103
|
+
'selfdestruct:aborted': SelfDestructAbortedEvent;
|
|
104
|
+
'firewall:scan': FirewallScanEvent;
|
|
105
|
+
'firewall:blocked': FirewallBlockedEvent;
|
|
106
|
+
'evolution:started': EvolutionStartedEvent;
|
|
107
|
+
'evolution:proposal': EvolutionProposalEvent;
|
|
108
|
+
'evolution:applied': EvolutionAppliedEvent;
|
|
109
|
+
'evolution:rollback': EvolutionRollbackEvent;
|
|
110
|
+
'iruel:infiltration': IruelInfiltrationEvent;
|
|
111
|
+
'iruel:counterhack': IruelCounterhackEvent;
|
|
112
|
+
'iruel:neutralized': IruelNeutralizedEvent;
|
|
113
|
+
'seele:gathering': SeeleGatheringEvent;
|
|
114
|
+
'seele:attack': SeeleAttackEvent;
|
|
115
|
+
'seele:concluded': SeeleConcludedEvent;
|
|
116
|
+
'genesis:started': GenesisStartedEvent;
|
|
117
|
+
'genesis:complete': GenesisCompleteEvent;
|
|
118
|
+
'bus:error': {
|
|
119
|
+
originalEvent: string;
|
|
120
|
+
error: Error;
|
|
121
|
+
emittedAt: Date;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export type MagiEventName = keyof MagiEventMap;
|
|
125
|
+
export declare class MagiEventBus extends EventEmitter {
|
|
126
|
+
emit<K extends MagiEventName>(event: K, payload: MagiEventMap[K]): boolean;
|
|
127
|
+
on<K extends MagiEventName>(event: K, listener: (payload: MagiEventMap[K]) => void): this;
|
|
128
|
+
once<K extends MagiEventName>(event: K, listener: (payload: MagiEventMap[K]) => void): this;
|
|
129
|
+
off<K extends MagiEventName>(event: K, listener: (payload: MagiEventMap[K]) => void): this;
|
|
130
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
// ── Type-safe EventBus ─────────────────────────────────────────────
|
|
4
|
+
export class MagiEventBus extends EventEmitter {
|
|
5
|
+
emit(event, payload) {
|
|
6
|
+
// bus:error uses super.emit directly to prevent infinite loops
|
|
7
|
+
if (event === 'bus:error') {
|
|
8
|
+
try {
|
|
9
|
+
return super.emit(event, payload);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
logger.debug('EventBus: bus:error listener threw, swallowing to prevent recursion', { error: String(err) });
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// rawListeners() preserves once-wrappers (listeners() unwraps them)
|
|
17
|
+
const listeners = this.rawListeners(event);
|
|
18
|
+
for (const listener of listeners) {
|
|
19
|
+
try {
|
|
20
|
+
listener(payload);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
24
|
+
logger.error(`EventBus listener threw on "${String(event)}"`, { error: error.message });
|
|
25
|
+
try {
|
|
26
|
+
super.emit('bus:error', { originalEvent: String(event), error, emittedAt: new Date() });
|
|
27
|
+
}
|
|
28
|
+
catch (err2) {
|
|
29
|
+
logger.debug('EventBus: bus:error listener threw during error propagation', { error: String(err2) });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return listeners.length > 0;
|
|
34
|
+
}
|
|
35
|
+
on(event, listener) {
|
|
36
|
+
return super.on(event, listener);
|
|
37
|
+
}
|
|
38
|
+
once(event, listener) {
|
|
39
|
+
return super.once(event, listener);
|
|
40
|
+
}
|
|
41
|
+
off(event, listener) {
|
|
42
|
+
return super.off(event, listener);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A-10 福音 (GospelChronicle)
|
|
3
|
+
*
|
|
4
|
+
* MAGIシステムの審議履歴を「福音」として記録する。
|
|
5
|
+
* chronicle.jsonl にエントリを追記し、narrative.md に物語的サマリーを生成。
|
|
6
|
+
* マイルストーン(初回全会一致、初回デッドロック、100回、1000回)を検出する。
|
|
7
|
+
*/
|
|
8
|
+
import type { MagiDeliberation } from '../types/core.js';
|
|
9
|
+
import type { ConsensusDecision } from '../types/consensus.js';
|
|
10
|
+
import type { MagiEventBus } from './events.js';
|
|
11
|
+
import type { ChronicleEntry, MilestoneType } from '../types/phase-h.js';
|
|
12
|
+
export declare class GospelChronicle {
|
|
13
|
+
private gospelDir;
|
|
14
|
+
private count;
|
|
15
|
+
private hasUnanimous;
|
|
16
|
+
private hasDeadlock;
|
|
17
|
+
constructor(workspaceDir: string);
|
|
18
|
+
/** Initialize gospel directory and restore state from existing chronicle */
|
|
19
|
+
initialize(): Promise<void>;
|
|
20
|
+
/** Record a deliberation result */
|
|
21
|
+
record(deliberation: MagiDeliberation, eventBus?: MagiEventBus): Promise<ChronicleEntry>;
|
|
22
|
+
/** Detect milestones based on current state */
|
|
23
|
+
detectMilestone(decision: ConsensusDecision): MilestoneType | undefined;
|
|
24
|
+
/** Generate a narrative entry for the markdown chronicle */
|
|
25
|
+
generateNarrative(entry: ChronicleEntry): string;
|
|
26
|
+
/** Get current deliberation count */
|
|
27
|
+
getCount(): number;
|
|
28
|
+
private chroniclePath;
|
|
29
|
+
private narrativePath;
|
|
30
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A-10 福音 (GospelChronicle)
|
|
3
|
+
*
|
|
4
|
+
* MAGIシステムの審議履歴を「福音」として記録する。
|
|
5
|
+
* chronicle.jsonl にエントリを追記し、narrative.md に物語的サマリーを生成。
|
|
6
|
+
* マイルストーン(初回全会一致、初回デッドロック、100回、1000回)を検出する。
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { isUnanimous, isDeadlock } from '../types/consensus.js';
|
|
11
|
+
import { logger } from '../utils/logger.js';
|
|
12
|
+
import { safeMkdir, safeAppendFile } from '../utils/safe-fs.js';
|
|
13
|
+
import { safeJsonParse } from '../utils/safe-json-parse.js';
|
|
14
|
+
export class GospelChronicle {
|
|
15
|
+
gospelDir;
|
|
16
|
+
count = 0;
|
|
17
|
+
hasUnanimous = false;
|
|
18
|
+
hasDeadlock = false;
|
|
19
|
+
constructor(workspaceDir) {
|
|
20
|
+
this.gospelDir = join(workspaceDir, 'gospel');
|
|
21
|
+
}
|
|
22
|
+
/** Initialize gospel directory and restore state from existing chronicle */
|
|
23
|
+
async initialize() {
|
|
24
|
+
await safeMkdir(this.gospelDir);
|
|
25
|
+
try {
|
|
26
|
+
const raw = await readFile(this.chroniclePath(), 'utf-8');
|
|
27
|
+
const lines = raw.trim().split('\n').filter(Boolean);
|
|
28
|
+
this.count = lines.length;
|
|
29
|
+
// Scan for existing milestones to avoid re-detection
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
try {
|
|
32
|
+
const entry = safeJsonParse(line);
|
|
33
|
+
if (isUnanimous(entry.decision))
|
|
34
|
+
this.hasUnanimous = true;
|
|
35
|
+
if (isDeadlock(entry.decision))
|
|
36
|
+
this.hasDeadlock = true;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
logger.debug('Gospel: corrupt chronicle line', { error: String(err) });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
logger.debug('Gospel: no existing chronicle, starting fresh', { error: String(err) });
|
|
45
|
+
this.count = 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Record a deliberation result */
|
|
49
|
+
async record(deliberation, eventBus) {
|
|
50
|
+
this.count++;
|
|
51
|
+
const lastRound = deliberation.rounds[deliberation.rounds.length - 1];
|
|
52
|
+
const units = lastRound?.opinions.map(o => o.unit) ?? [];
|
|
53
|
+
const milestone = this.detectMilestone(deliberation.consensus.decision);
|
|
54
|
+
const entry = {
|
|
55
|
+
number: this.count,
|
|
56
|
+
deliberationId: deliberation.id,
|
|
57
|
+
taskType: deliberation.task.type,
|
|
58
|
+
taskTitle: deliberation.task.title,
|
|
59
|
+
decision: deliberation.consensus.decision,
|
|
60
|
+
confidence: deliberation.consensus.confidence,
|
|
61
|
+
units,
|
|
62
|
+
milestone,
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
};
|
|
65
|
+
// Append to chronicle.jsonl
|
|
66
|
+
await safeAppendFile(this.chroniclePath(), JSON.stringify(entry) + '\n');
|
|
67
|
+
// Append to narrative.md
|
|
68
|
+
await safeAppendFile(this.narrativePath(), this.generateNarrative(entry) + '\n');
|
|
69
|
+
// Emit milestone event
|
|
70
|
+
if (milestone && eventBus) {
|
|
71
|
+
eventBus.emit('gospel:milestone', {
|
|
72
|
+
deliberationId: deliberation.id,
|
|
73
|
+
milestone,
|
|
74
|
+
count: this.count,
|
|
75
|
+
emittedAt: new Date(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return entry;
|
|
79
|
+
}
|
|
80
|
+
/** Detect milestones based on current state */
|
|
81
|
+
detectMilestone(decision) {
|
|
82
|
+
if (this.count === 1)
|
|
83
|
+
return 'first_deliberation';
|
|
84
|
+
if (!this.hasUnanimous && isUnanimous(decision)) {
|
|
85
|
+
this.hasUnanimous = true;
|
|
86
|
+
return 'first_unanimous';
|
|
87
|
+
}
|
|
88
|
+
if (!this.hasDeadlock && isDeadlock(decision)) {
|
|
89
|
+
this.hasDeadlock = true;
|
|
90
|
+
return 'first_deadlock';
|
|
91
|
+
}
|
|
92
|
+
if (this.count === 100)
|
|
93
|
+
return 'centenary';
|
|
94
|
+
if (this.count === 1000)
|
|
95
|
+
return 'millennium';
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
/** Generate a narrative entry for the markdown chronicle */
|
|
99
|
+
generateNarrative(entry) {
|
|
100
|
+
const date = new Date(entry.timestamp);
|
|
101
|
+
const dateStr = date.toLocaleDateString('ja-JP', { year: 'numeric', month: 'long', day: 'numeric' });
|
|
102
|
+
const confidence = (entry.confidence * 100).toFixed(1);
|
|
103
|
+
let narrative = `### 第${entry.number}審議 — ${dateStr}\n\n`;
|
|
104
|
+
narrative += `**議題**: ${entry.taskTitle} (${entry.taskType})\n`;
|
|
105
|
+
narrative += `**判定**: ${entry.decision} (確信度 ${confidence}%)\n`;
|
|
106
|
+
narrative += `**参加**: ${entry.units.join(', ')}\n`;
|
|
107
|
+
if (entry.milestone) {
|
|
108
|
+
const milestoneLabels = {
|
|
109
|
+
first_deliberation: 'MAGIシステム、最初の審議を完了',
|
|
110
|
+
first_unanimous: '初の全会一致 — 三位一体の調和',
|
|
111
|
+
first_deadlock: '初のデッドロック — 意見の断絶',
|
|
112
|
+
centenary: '百の審議を超えて — centenary milestone',
|
|
113
|
+
millennium: '千の審議を超えて — millennium milestone',
|
|
114
|
+
};
|
|
115
|
+
narrative += `\n> **[MILESTONE]** ${milestoneLabels[entry.milestone]}\n`;
|
|
116
|
+
}
|
|
117
|
+
return narrative;
|
|
118
|
+
}
|
|
119
|
+
/** Get current deliberation count */
|
|
120
|
+
getCount() {
|
|
121
|
+
return this.count;
|
|
122
|
+
}
|
|
123
|
+
chroniclePath() {
|
|
124
|
+
return join(this.gospelDir, 'chronicle.jsonl');
|
|
125
|
+
}
|
|
126
|
+
narrativePath() {
|
|
127
|
+
return join(this.gospelDir, 'narrative.md');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HallucinationDetector — Detects fabricated content in MAGI opinions
|
|
3
|
+
*
|
|
4
|
+
* Stateless detector that identifies 4 types of hallucinations:
|
|
5
|
+
* 1. Fabricated file paths (generic placeholder paths)
|
|
6
|
+
* 2. Abnormal line numbers (impossibly large or zero)
|
|
7
|
+
* 3. Vote-reasoning mismatch (contradictory vote + reasoning)
|
|
8
|
+
* 4. Fabricated references (non-existent RFC/CVE numbers)
|
|
9
|
+
*
|
|
10
|
+
* Part of B-02 LCL (shared consciousness pool).
|
|
11
|
+
*/
|
|
12
|
+
import type { MagiOpinion } from '../types/core.js';
|
|
13
|
+
import type { HallucinationReport } from '../types/phase-i.js';
|
|
14
|
+
export declare class HallucinationDetector {
|
|
15
|
+
/**
|
|
16
|
+
* Run all hallucination checks on an opinion.
|
|
17
|
+
* Returns an array of reports (empty if no hallucinations detected).
|
|
18
|
+
*/
|
|
19
|
+
detect(opinion: MagiOpinion): HallucinationReport[];
|
|
20
|
+
/**
|
|
21
|
+
* Create a purified copy of the opinion with hallucinations annotated.
|
|
22
|
+
* Only purifies reports with confidence >= PURIFY_CONFIDENCE_THRESHOLD.
|
|
23
|
+
*/
|
|
24
|
+
purify(opinion: MagiOpinion, reports: HallucinationReport[]): MagiOpinion;
|
|
25
|
+
/** Detect fabricated file paths like /path/to/xxx, /foo/bar/baz.ts */
|
|
26
|
+
checkFilePaths(text: string): HallucinationReport[];
|
|
27
|
+
/** Detect abnormal line numbers (line 0, line > 50000) */
|
|
28
|
+
checkLineNumbers(text: string): HallucinationReport[];
|
|
29
|
+
/** Detect mismatch between vote and reasoning sentiment */
|
|
30
|
+
checkVoteReasoningMismatch(opinion: MagiOpinion): HallucinationReport[];
|
|
31
|
+
/** Detect fabricated RFC/CVE references */
|
|
32
|
+
checkFabricatedReferences(text: string): HallucinationReport[];
|
|
33
|
+
}
|