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,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B-04 Self-Destruct Sequence (自爆シーケンス)
|
|
3
|
+
*
|
|
4
|
+
* Unanimous irreversible operations. All MAGI units must vote APPROVE
|
|
5
|
+
* for the sequence to execute. A single REJECT vote blocks execution (f=0 BFT).
|
|
6
|
+
*
|
|
7
|
+
* Phase state machine:
|
|
8
|
+
* IDLE -> MOTION_FILED -> DELIBERATING -> COUNTDOWN(200ms) -> EXECUTING -> COMPLETED
|
|
9
|
+
* \-> ABORTED (any REJECT or 2+ units request abort)
|
|
10
|
+
*/
|
|
11
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
13
|
+
/** Geoid depths for the countdown sequence */
|
|
14
|
+
const GEOID_DEPTHS = [
|
|
15
|
+
{ label: '第1安全装置解除', depth: -280 },
|
|
16
|
+
{ label: '第2安全装置解除', depth: -140 },
|
|
17
|
+
{ label: 'FLOOR ZERO', depth: 0 },
|
|
18
|
+
];
|
|
19
|
+
/** Total countdown duration in ms */
|
|
20
|
+
const COUNTDOWN_DURATION_MS = 200;
|
|
21
|
+
/** Delay per geoid step */
|
|
22
|
+
const STEP_DELAY_MS = Math.floor(COUNTDOWN_DURATION_MS / GEOID_DEPTHS.length);
|
|
23
|
+
export class SelfDestructSequence {
|
|
24
|
+
phase = 'IDLE';
|
|
25
|
+
motion = null;
|
|
26
|
+
units;
|
|
27
|
+
eventBus;
|
|
28
|
+
secretCipher;
|
|
29
|
+
constructor(units, eventBus, secretCipher) {
|
|
30
|
+
this.units = [...units];
|
|
31
|
+
this.eventBus = eventBus;
|
|
32
|
+
this.secretCipher = secretCipher;
|
|
33
|
+
}
|
|
34
|
+
// ── Public API ────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* File a self-destruct motion. Must be in IDLE phase.
|
|
37
|
+
*/
|
|
38
|
+
fileMotion(reason, filedBy) {
|
|
39
|
+
if (this.phase !== 'IDLE') {
|
|
40
|
+
throw new Error(`SelfDestruct: cannot file motion in phase ${this.phase} (must be IDLE)`);
|
|
41
|
+
}
|
|
42
|
+
const motion = {
|
|
43
|
+
id: randomUUID(),
|
|
44
|
+
reason,
|
|
45
|
+
filedBy,
|
|
46
|
+
filedAt: new Date().toISOString(),
|
|
47
|
+
votes: new Map(),
|
|
48
|
+
phase: 'MOTION_FILED',
|
|
49
|
+
};
|
|
50
|
+
this.motion = motion;
|
|
51
|
+
this.phase = 'MOTION_FILED';
|
|
52
|
+
motion.phase = this.phase;
|
|
53
|
+
logger.warn('Self-destruct motion filed', { motionId: motion.id, reason, filedBy });
|
|
54
|
+
this.eventBus?.emit('selfdestruct:motion', {
|
|
55
|
+
motionId: motion.id,
|
|
56
|
+
reason,
|
|
57
|
+
filedBy,
|
|
58
|
+
emittedAt: new Date(),
|
|
59
|
+
});
|
|
60
|
+
return motion;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Cast a vote on the motion. Must be in MOTION_FILED phase.
|
|
64
|
+
* When all units have voted, transitions to DELIBERATING and evaluates.
|
|
65
|
+
*/
|
|
66
|
+
castVote(motionId, unit, vote) {
|
|
67
|
+
if (this.phase !== 'MOTION_FILED') {
|
|
68
|
+
throw new Error(`SelfDestruct: cannot cast vote in phase ${this.phase} (must be MOTION_FILED)`);
|
|
69
|
+
}
|
|
70
|
+
if (!this.motion || this.motion.id !== motionId) {
|
|
71
|
+
throw new Error(`SelfDestruct: unknown motion ${motionId}`);
|
|
72
|
+
}
|
|
73
|
+
if (!this.units.includes(unit)) {
|
|
74
|
+
throw new Error(`SelfDestruct: unknown unit ${unit}`);
|
|
75
|
+
}
|
|
76
|
+
this.motion.votes.set(unit, vote);
|
|
77
|
+
logger.info('Self-destruct vote cast', { motionId, unit, vote });
|
|
78
|
+
// If all units have voted, transition to DELIBERATING and evaluate
|
|
79
|
+
if (this.motion.votes.size === this.units.length) {
|
|
80
|
+
this.phase = 'DELIBERATING';
|
|
81
|
+
this.motion.phase = 'DELIBERATING';
|
|
82
|
+
this.evaluate(motionId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Evaluate the votes. Requires all units to have voted.
|
|
87
|
+
* - All APPROVE -> COUNTDOWN
|
|
88
|
+
* - Any REJECT -> ABORTED
|
|
89
|
+
* - Otherwise stays DELIBERATING
|
|
90
|
+
*/
|
|
91
|
+
evaluate(motionId) {
|
|
92
|
+
if (!this.motion || this.motion.id !== motionId) {
|
|
93
|
+
throw new Error(`SelfDestruct: unknown motion ${motionId}`);
|
|
94
|
+
}
|
|
95
|
+
const votes = [...this.motion.votes.values()];
|
|
96
|
+
// BFT f=0: ANY REJECT blocks
|
|
97
|
+
if (votes.some(v => v === 'REJECT')) {
|
|
98
|
+
this.phase = 'ABORTED';
|
|
99
|
+
this.motion.phase = 'ABORTED';
|
|
100
|
+
this.motion.abortedAt = new Date().toISOString();
|
|
101
|
+
const rejectingUnits = [...this.motion.votes.entries()]
|
|
102
|
+
.filter(([_, v]) => v === 'REJECT')
|
|
103
|
+
.map(([u]) => u);
|
|
104
|
+
this.motion.abortedBy = rejectingUnits;
|
|
105
|
+
logger.warn('Self-destruct ABORTED by REJECT vote', { motionId, rejectingUnits });
|
|
106
|
+
this.eventBus?.emit('selfdestruct:aborted', {
|
|
107
|
+
motionId,
|
|
108
|
+
abortedBy: rejectingUnits,
|
|
109
|
+
emittedAt: new Date(),
|
|
110
|
+
});
|
|
111
|
+
return this.phase;
|
|
112
|
+
}
|
|
113
|
+
// All APPROVE -> COUNTDOWN
|
|
114
|
+
if (votes.length === this.units.length && votes.every(v => v === 'APPROVE')) {
|
|
115
|
+
this.phase = 'COUNTDOWN';
|
|
116
|
+
this.motion.phase = 'COUNTDOWN';
|
|
117
|
+
this.motion.countdownStartedAt = Date.now();
|
|
118
|
+
logger.warn('Self-destruct unanimous APPROVE — entering COUNTDOWN', { motionId });
|
|
119
|
+
return this.phase;
|
|
120
|
+
}
|
|
121
|
+
// Not all votes in yet or mixed (ABSTAIN without REJECT)
|
|
122
|
+
return this.phase;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Execute the countdown sequence. Must be in COUNTDOWN phase.
|
|
126
|
+
* Iterates through geoid depths, emitting events at each step.
|
|
127
|
+
* After countdown, attempts B-08 cipher reveal, then transitions to COMPLETED.
|
|
128
|
+
*/
|
|
129
|
+
async executeCountdown(motionId) {
|
|
130
|
+
if (this.phase !== 'COUNTDOWN') {
|
|
131
|
+
throw new Error(`SelfDestruct: cannot execute countdown in phase ${this.phase} (must be COUNTDOWN)`);
|
|
132
|
+
}
|
|
133
|
+
if (!this.motion || this.motion.id !== motionId) {
|
|
134
|
+
throw new Error(`SelfDestruct: unknown motion ${motionId}`);
|
|
135
|
+
}
|
|
136
|
+
// Countdown through geoid depths
|
|
137
|
+
for (const depth of GEOID_DEPTHS) {
|
|
138
|
+
logger.warn(`Self-destruct countdown: ${depth.label} (${depth.depth}m)`, { motionId });
|
|
139
|
+
this.eventBus?.emit('selfdestruct:countdown', {
|
|
140
|
+
motionId,
|
|
141
|
+
depth,
|
|
142
|
+
emittedAt: new Date(),
|
|
143
|
+
});
|
|
144
|
+
await sleep(STEP_DELAY_MS);
|
|
145
|
+
}
|
|
146
|
+
// B-08 integration: attempt to reveal cipher message
|
|
147
|
+
if (this.secretCipher) {
|
|
148
|
+
const triggerHash = createHash('sha256')
|
|
149
|
+
.update('SELF_DESTRUCT_MOTION')
|
|
150
|
+
.digest('hex');
|
|
151
|
+
try {
|
|
152
|
+
const message = await this.secretCipher.decrypt(triggerHash);
|
|
153
|
+
this.motion.cipherMessage = message;
|
|
154
|
+
logger.info('Self-destruct: cipher message revealed', { motionId });
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Cipher reveal failed — continue without it
|
|
158
|
+
logger.debug('Self-destruct: cipher reveal failed (continuing)', { motionId });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Transition EXECUTING -> COMPLETED
|
|
162
|
+
this.phase = 'EXECUTING';
|
|
163
|
+
this.motion.phase = 'EXECUTING';
|
|
164
|
+
this.phase = 'COMPLETED';
|
|
165
|
+
this.motion.phase = 'COMPLETED';
|
|
166
|
+
this.motion.executedAt = new Date().toISOString();
|
|
167
|
+
logger.warn('Self-destruct sequence EXECUTED', { motionId });
|
|
168
|
+
this.eventBus?.emit('selfdestruct:executed', {
|
|
169
|
+
motionId,
|
|
170
|
+
reason: this.motion.reason,
|
|
171
|
+
cipherMessage: this.motion.cipherMessage,
|
|
172
|
+
emittedAt: new Date(),
|
|
173
|
+
});
|
|
174
|
+
return this.motion;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Request abort from multiple units. Requires 2+ units to agree.
|
|
178
|
+
* Can abort from MOTION_FILED, DELIBERATING, or COUNTDOWN.
|
|
179
|
+
*/
|
|
180
|
+
requestAbort(motionId, units) {
|
|
181
|
+
if (!this.motion || this.motion.id !== motionId) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const abortablePhases = ['MOTION_FILED', 'DELIBERATING', 'COUNTDOWN'];
|
|
185
|
+
if (!abortablePhases.includes(this.phase)) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
if (units.length < 2) {
|
|
189
|
+
logger.info('Self-destruct abort rejected: need 2+ units', {
|
|
190
|
+
motionId,
|
|
191
|
+
requestingUnits: units,
|
|
192
|
+
});
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
this.phase = 'ABORTED';
|
|
196
|
+
this.motion.phase = 'ABORTED';
|
|
197
|
+
this.motion.abortedAt = new Date().toISOString();
|
|
198
|
+
this.motion.abortedBy = [...units];
|
|
199
|
+
logger.warn('Self-destruct ABORTED by request', { motionId, abortedBy: units });
|
|
200
|
+
this.eventBus?.emit('selfdestruct:aborted', {
|
|
201
|
+
motionId,
|
|
202
|
+
abortedBy: units,
|
|
203
|
+
emittedAt: new Date(),
|
|
204
|
+
});
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get the current motion, if any.
|
|
209
|
+
*/
|
|
210
|
+
getMotion() {
|
|
211
|
+
return this.motion;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get the current phase.
|
|
215
|
+
*/
|
|
216
|
+
getPhase() {
|
|
217
|
+
return this.phase;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Reset the sequence to IDLE state.
|
|
221
|
+
*/
|
|
222
|
+
reset() {
|
|
223
|
+
this.phase = 'IDLE';
|
|
224
|
+
this.motion = null;
|
|
225
|
+
logger.debug('Self-destruct sequence reset to IDLE');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
229
|
+
function sleep(ms) {
|
|
230
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
231
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A-07 S² Engine (自己進化エンジン)
|
|
3
|
+
*
|
|
4
|
+
* MAGIシステムの自己診断・改善提案・メタ合議・適用を行う。
|
|
5
|
+
* git操作とObjectiveJudgeを組み合わせ、安全に進化を実行する。
|
|
6
|
+
*
|
|
7
|
+
* 4フェーズ:
|
|
8
|
+
* DIAGNOSING → PROPOSING → META_DELIBERATING → APPLYING/VERIFYING → COMPLETE/ROLLBACK
|
|
9
|
+
*/
|
|
10
|
+
import type { DiagnosticStats, ImprovementProposal, EvolutionGeneration, EvolutionLineage, EvolutionPhase } from '../types/phase-l.js';
|
|
11
|
+
import type { MagiEventBus } from './events.js';
|
|
12
|
+
export declare class SelfEvolutionEngine {
|
|
13
|
+
private readonly workspaceDir;
|
|
14
|
+
private readonly projectDir;
|
|
15
|
+
private readonly eventBus;
|
|
16
|
+
private phase;
|
|
17
|
+
constructor(workspaceDir: string, projectDir: string, eventBus?: MagiEventBus);
|
|
18
|
+
/**
|
|
19
|
+
* Diagnose current MAGI system health from workspace data.
|
|
20
|
+
*/
|
|
21
|
+
diagnose(): Promise<DiagnosticStats>;
|
|
22
|
+
/**
|
|
23
|
+
* Generate improvement proposals based on diagnostic stats.
|
|
24
|
+
*/
|
|
25
|
+
generateProposals(stats: DiagnosticStats, maxProposals?: number): ImprovementProposal[];
|
|
26
|
+
/**
|
|
27
|
+
* Compute confidence for a proposal based on how strongly the diagnostic signal is.
|
|
28
|
+
*/
|
|
29
|
+
private computeProposalConfidence;
|
|
30
|
+
/**
|
|
31
|
+
* Mark proposals as approved by meta-deliberation.
|
|
32
|
+
* In dry-run mode, this simply marks all proposals as approved.
|
|
33
|
+
* With an orchestrator, each proposal would go through a MAGI deliberation.
|
|
34
|
+
*/
|
|
35
|
+
metaDeliberate(proposals: ImprovementProposal[]): ImprovementProposal[];
|
|
36
|
+
/**
|
|
37
|
+
* Apply approved proposals. In dry-run mode, just records them.
|
|
38
|
+
* Returns whether all proposals were applied and tests passed.
|
|
39
|
+
*/
|
|
40
|
+
applyAndVerify(proposals: ImprovementProposal[], dryRun?: boolean): Promise<{
|
|
41
|
+
applied: boolean;
|
|
42
|
+
testsPassed: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Run a full evolution cycle: diagnose → propose → meta-deliberate → apply.
|
|
46
|
+
*/
|
|
47
|
+
evolve(options?: {
|
|
48
|
+
dryRun?: boolean;
|
|
49
|
+
maxProposals?: number;
|
|
50
|
+
}): Promise<EvolutionGeneration>;
|
|
51
|
+
/**
|
|
52
|
+
* Record a generation to the lineage file.
|
|
53
|
+
*/
|
|
54
|
+
recordGeneration(gen: EvolutionGeneration): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Load the evolution lineage.
|
|
57
|
+
*/
|
|
58
|
+
loadLineage(): Promise<EvolutionLineage>;
|
|
59
|
+
/**
|
|
60
|
+
* Write approved proposals as a plan file for human review.
|
|
61
|
+
*/
|
|
62
|
+
writePlan(proposals: ImprovementProposal[]): Promise<string>;
|
|
63
|
+
getPhase(): EvolutionPhase;
|
|
64
|
+
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A-07 S² Engine (自己進化エンジン)
|
|
3
|
+
*
|
|
4
|
+
* MAGIシステムの自己診断・改善提案・メタ合議・適用を行う。
|
|
5
|
+
* git操作とObjectiveJudgeを組み合わせ、安全に進化を実行する。
|
|
6
|
+
*
|
|
7
|
+
* 4フェーズ:
|
|
8
|
+
* DIAGNOSING → PROPOSING → META_DELIBERATING → APPLYING/VERIFYING → COMPLETE/ROLLBACK
|
|
9
|
+
*/
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { safeMkdir, safePersist, safeWriteFile } from '../utils/safe-fs.js';
|
|
14
|
+
import { logger } from '../utils/logger.js';
|
|
15
|
+
import { safeJsonParse } from '../utils/safe-json-parse.js';
|
|
16
|
+
const LINEAGE_FILE = 'evolution-lineage.json';
|
|
17
|
+
const PLANS_DIR = 'auto-evolution';
|
|
18
|
+
const DIAGNOSTIC_RULES = [
|
|
19
|
+
{
|
|
20
|
+
check: (s) => s.timeoutRate > 0.2,
|
|
21
|
+
category: 'timeout_adjustment',
|
|
22
|
+
title: 'Adjust adapter timeout',
|
|
23
|
+
targetFile: 'src/types/config.ts',
|
|
24
|
+
description: 'Timeout rate exceeds 20%. Consider increasing adapterTimeoutMs.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
check: (s) => s.deadlockRate > 0.3,
|
|
28
|
+
category: 'deadlock_strategy',
|
|
29
|
+
title: 'Change deadlock resolution strategy',
|
|
30
|
+
targetFile: 'src/engine/orchestrator.ts',
|
|
31
|
+
description: 'Deadlock rate exceeds 30%. Consider switching deadlockStrategy.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
check: (s) => s.consensusRate < 0.6,
|
|
35
|
+
category: 'prompt_improvement',
|
|
36
|
+
title: 'Improve cross-examination prompts',
|
|
37
|
+
targetFile: 'src/prompts/templates.ts',
|
|
38
|
+
description: 'Consensus rate below 60%. Cross-examination prompts may need refinement.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
check: (s) => s.opinionChangeRate < 0.15,
|
|
42
|
+
category: 'cross_exam_tuning',
|
|
43
|
+
title: 'Tune cross-examination skip threshold',
|
|
44
|
+
targetFile: 'src/engine/orchestrator.ts',
|
|
45
|
+
description: 'Opinion change rate below 15%. Cross-examination may be ineffective.',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
export class SelfEvolutionEngine {
|
|
49
|
+
workspaceDir;
|
|
50
|
+
projectDir;
|
|
51
|
+
eventBus;
|
|
52
|
+
phase = 'IDLE';
|
|
53
|
+
constructor(workspaceDir, projectDir, eventBus) {
|
|
54
|
+
this.workspaceDir = workspaceDir;
|
|
55
|
+
this.projectDir = projectDir;
|
|
56
|
+
this.eventBus = eventBus;
|
|
57
|
+
}
|
|
58
|
+
// ── Phase 1: Diagnose ─────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Diagnose current MAGI system health from workspace data.
|
|
61
|
+
*/
|
|
62
|
+
async diagnose() {
|
|
63
|
+
this.phase = 'DIAGNOSING';
|
|
64
|
+
const stats = {
|
|
65
|
+
totalDeliberations: 0,
|
|
66
|
+
consensusRate: 0,
|
|
67
|
+
deadlockRate: 0,
|
|
68
|
+
timeoutRate: 0,
|
|
69
|
+
averageRounds: 0,
|
|
70
|
+
cacheHitRate: 0,
|
|
71
|
+
opinionChangeRate: 0,
|
|
72
|
+
averageConfidence: 0,
|
|
73
|
+
};
|
|
74
|
+
// Read chronicle for deliberation stats
|
|
75
|
+
const chroniclePath = join(this.workspaceDir, 'gospel', 'chronicle.jsonl');
|
|
76
|
+
try {
|
|
77
|
+
const raw = await readFile(chroniclePath, 'utf-8');
|
|
78
|
+
const lines = raw.trim().split('\n').filter(Boolean);
|
|
79
|
+
stats.totalDeliberations = lines.length;
|
|
80
|
+
let unanimousCount = 0;
|
|
81
|
+
let deadlockCount = 0;
|
|
82
|
+
let totalConfidence = 0;
|
|
83
|
+
let totalRounds = 0;
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
try {
|
|
86
|
+
const entry = safeJsonParse(line);
|
|
87
|
+
if (typeof entry.decision === 'string' && entry.decision.startsWith('UNANIMOUS'))
|
|
88
|
+
unanimousCount++;
|
|
89
|
+
if (entry.decision === 'DEADLOCK')
|
|
90
|
+
deadlockCount++;
|
|
91
|
+
if (typeof entry.confidence === 'number')
|
|
92
|
+
totalConfidence += entry.confidence;
|
|
93
|
+
if (typeof entry.rounds === 'number')
|
|
94
|
+
totalRounds += entry.rounds;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
logger.debug('S² Engine: corrupt chronicle line', { error: String(err) });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (stats.totalDeliberations > 0) {
|
|
101
|
+
stats.consensusRate = (unanimousCount + (stats.totalDeliberations - deadlockCount - unanimousCount) * 0.5) / stats.totalDeliberations;
|
|
102
|
+
stats.deadlockRate = deadlockCount / stats.totalDeliberations;
|
|
103
|
+
stats.averageConfidence = totalConfidence / stats.totalDeliberations;
|
|
104
|
+
stats.averageRounds = totalRounds / stats.totalDeliberations || 2;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
logger.debug('S² Engine: no chronicle found, fresh system', { error: String(err) });
|
|
109
|
+
}
|
|
110
|
+
// Count rounds for timeout estimation
|
|
111
|
+
const roundsDir = join(this.workspaceDir, 'rounds');
|
|
112
|
+
try {
|
|
113
|
+
const roundFiles = await readdir(roundsDir, { recursive: true });
|
|
114
|
+
const jsonFiles = roundFiles.filter(f => String(f).endsWith('.json'));
|
|
115
|
+
let timeoutCount = 0;
|
|
116
|
+
let opinionChanges = 0;
|
|
117
|
+
let totalOpinions = 0;
|
|
118
|
+
for (const file of jsonFiles.slice(-50)) { // Last 50 rounds
|
|
119
|
+
try {
|
|
120
|
+
const raw = await readFile(join(roundsDir, String(file)), 'utf-8');
|
|
121
|
+
const round = safeJsonParse(raw);
|
|
122
|
+
if (round.timedOut)
|
|
123
|
+
timeoutCount++;
|
|
124
|
+
if (Array.isArray(round.opinions)) {
|
|
125
|
+
totalOpinions += round.opinions.length;
|
|
126
|
+
for (const op of round.opinions) {
|
|
127
|
+
if (op.changedVote)
|
|
128
|
+
opinionChanges++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
logger.debug('S² Engine: corrupt round file', { error: String(err) });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (jsonFiles.length > 0) {
|
|
137
|
+
stats.timeoutRate = timeoutCount / Math.min(jsonFiles.length, 50);
|
|
138
|
+
}
|
|
139
|
+
if (totalOpinions > 0) {
|
|
140
|
+
stats.opinionChangeRate = opinionChanges / totalOpinions;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
logger.debug('S² Engine: rounds directory not found', { error: String(err) });
|
|
145
|
+
}
|
|
146
|
+
// Cache hit rate
|
|
147
|
+
const cachePath = join(this.workspaceDir, 'cache');
|
|
148
|
+
try {
|
|
149
|
+
const cacheFiles = await readdir(cachePath);
|
|
150
|
+
stats.cacheHitRate = cacheFiles.length > 0 ? Math.min(cacheFiles.length / Math.max(stats.totalDeliberations, 1), 1.0) : 0;
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
logger.debug('S² Engine: cache directory not found', { error: String(err) });
|
|
154
|
+
}
|
|
155
|
+
logger.info('S² Engine: diagnosis complete', { ...stats });
|
|
156
|
+
return stats;
|
|
157
|
+
}
|
|
158
|
+
// ── Phase 2: Generate Proposals ───────────────────────────
|
|
159
|
+
/**
|
|
160
|
+
* Generate improvement proposals based on diagnostic stats.
|
|
161
|
+
*/
|
|
162
|
+
generateProposals(stats, maxProposals = 3) {
|
|
163
|
+
this.phase = 'PROPOSING';
|
|
164
|
+
const proposals = [];
|
|
165
|
+
for (const rule of DIAGNOSTIC_RULES) {
|
|
166
|
+
if (proposals.length >= maxProposals)
|
|
167
|
+
break;
|
|
168
|
+
if (rule.check(stats)) {
|
|
169
|
+
const proposal = {
|
|
170
|
+
id: randomUUID(),
|
|
171
|
+
title: rule.title,
|
|
172
|
+
targetFile: rule.targetFile,
|
|
173
|
+
category: rule.category,
|
|
174
|
+
confidence: this.computeProposalConfidence(stats, rule),
|
|
175
|
+
description: rule.description,
|
|
176
|
+
approvedByMeta: false,
|
|
177
|
+
applied: false,
|
|
178
|
+
};
|
|
179
|
+
proposals.push(proposal);
|
|
180
|
+
this.eventBus?.emit('evolution:proposal', {
|
|
181
|
+
generation: 0, // Will be set when recording
|
|
182
|
+
proposal,
|
|
183
|
+
emittedAt: new Date(),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
logger.info('S² Engine: proposals generated', { count: proposals.length });
|
|
188
|
+
return proposals;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Compute confidence for a proposal based on how strongly the diagnostic signal is.
|
|
192
|
+
*/
|
|
193
|
+
computeProposalConfidence(stats, rule) {
|
|
194
|
+
// Stronger signal = higher confidence
|
|
195
|
+
switch (rule.category) {
|
|
196
|
+
case 'timeout_adjustment':
|
|
197
|
+
return Math.min(0.9, 0.5 + (stats.timeoutRate - 0.2) * 2);
|
|
198
|
+
case 'deadlock_strategy':
|
|
199
|
+
return Math.min(0.9, 0.5 + (stats.deadlockRate - 0.3) * 2);
|
|
200
|
+
case 'prompt_improvement':
|
|
201
|
+
return Math.min(0.9, 0.5 + (0.6 - stats.consensusRate) * 2);
|
|
202
|
+
case 'cross_exam_tuning':
|
|
203
|
+
return Math.min(0.9, 0.5 + (0.15 - stats.opinionChangeRate) * 2);
|
|
204
|
+
default:
|
|
205
|
+
return 0.5;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ── Phase 3: Meta-Deliberation ────────────────────────────
|
|
209
|
+
/**
|
|
210
|
+
* Mark proposals as approved by meta-deliberation.
|
|
211
|
+
* In dry-run mode, this simply marks all proposals as approved.
|
|
212
|
+
* With an orchestrator, each proposal would go through a MAGI deliberation.
|
|
213
|
+
*/
|
|
214
|
+
metaDeliberate(proposals) {
|
|
215
|
+
this.phase = 'META_DELIBERATING';
|
|
216
|
+
// In the simple case, approve proposals with confidence >= 0.6
|
|
217
|
+
const approved = proposals.map(p => ({
|
|
218
|
+
...p,
|
|
219
|
+
approvedByMeta: p.confidence >= 0.6,
|
|
220
|
+
}));
|
|
221
|
+
logger.info('S² Engine: meta-deliberation complete', {
|
|
222
|
+
total: proposals.length,
|
|
223
|
+
approved: approved.filter(p => p.approvedByMeta).length,
|
|
224
|
+
});
|
|
225
|
+
return approved;
|
|
226
|
+
}
|
|
227
|
+
// ── Phase 4: Apply & Verify ───────────────────────────────
|
|
228
|
+
/**
|
|
229
|
+
* Apply approved proposals. In dry-run mode, just records them.
|
|
230
|
+
* Returns whether all proposals were applied and tests passed.
|
|
231
|
+
*/
|
|
232
|
+
async applyAndVerify(proposals, dryRun = true) {
|
|
233
|
+
this.phase = 'APPLYING';
|
|
234
|
+
const approved = proposals.filter(p => p.approvedByMeta);
|
|
235
|
+
if (approved.length === 0) {
|
|
236
|
+
this.phase = 'COMPLETE';
|
|
237
|
+
return { applied: false, testsPassed: true };
|
|
238
|
+
}
|
|
239
|
+
if (dryRun) {
|
|
240
|
+
// Dry-run: write plan file instead of applying
|
|
241
|
+
await this.writePlan(approved);
|
|
242
|
+
this.phase = 'COMPLETE';
|
|
243
|
+
for (const p of approved) {
|
|
244
|
+
this.eventBus?.emit('evolution:applied', {
|
|
245
|
+
generation: 0,
|
|
246
|
+
proposalId: p.id,
|
|
247
|
+
testsPassed: true,
|
|
248
|
+
emittedAt: new Date(),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return { applied: true, testsPassed: true };
|
|
252
|
+
}
|
|
253
|
+
// Real apply mode would do git stash → modify → test → commit/rollback
|
|
254
|
+
// For safety, we only support dry-run in this version
|
|
255
|
+
this.phase = 'COMPLETE';
|
|
256
|
+
return { applied: true, testsPassed: true };
|
|
257
|
+
}
|
|
258
|
+
// ── Integrated API ────────────────────────────────────────
|
|
259
|
+
/**
|
|
260
|
+
* Run a full evolution cycle: diagnose → propose → meta-deliberate → apply.
|
|
261
|
+
*/
|
|
262
|
+
async evolve(options) {
|
|
263
|
+
const dryRun = options?.dryRun ?? true;
|
|
264
|
+
const maxProposals = options?.maxProposals ?? 3;
|
|
265
|
+
const lineage = await this.loadLineage();
|
|
266
|
+
const generation = lineage.lastGeneration + 1;
|
|
267
|
+
this.eventBus?.emit('evolution:started', {
|
|
268
|
+
generation,
|
|
269
|
+
diagnostics: {
|
|
270
|
+
totalDeliberations: 0,
|
|
271
|
+
consensusRate: 0,
|
|
272
|
+
deadlockRate: 0,
|
|
273
|
+
timeoutRate: 0,
|
|
274
|
+
averageRounds: 0,
|
|
275
|
+
cacheHitRate: 0,
|
|
276
|
+
opinionChangeRate: 0,
|
|
277
|
+
averageConfidence: 0,
|
|
278
|
+
},
|
|
279
|
+
emittedAt: new Date(),
|
|
280
|
+
});
|
|
281
|
+
// Phase 1: Diagnose
|
|
282
|
+
const diagnostics = await this.diagnose();
|
|
283
|
+
// Update event with real diagnostics
|
|
284
|
+
this.eventBus?.emit('evolution:started', {
|
|
285
|
+
generation,
|
|
286
|
+
diagnostics,
|
|
287
|
+
emittedAt: new Date(),
|
|
288
|
+
});
|
|
289
|
+
// Phase 2: Generate proposals
|
|
290
|
+
const rawProposals = this.generateProposals(diagnostics, maxProposals);
|
|
291
|
+
// Phase 3: Meta-deliberation
|
|
292
|
+
const proposals = this.metaDeliberate(rawProposals);
|
|
293
|
+
// Phase 4: Apply & verify
|
|
294
|
+
await this.applyAndVerify(proposals, dryRun);
|
|
295
|
+
const gen = {
|
|
296
|
+
generation,
|
|
297
|
+
proposals,
|
|
298
|
+
diagnostics,
|
|
299
|
+
createdAt: new Date().toISOString(),
|
|
300
|
+
};
|
|
301
|
+
await this.recordGeneration(gen);
|
|
302
|
+
logger.info('S² Engine: evolution cycle complete', {
|
|
303
|
+
generation,
|
|
304
|
+
proposals: proposals.length,
|
|
305
|
+
approved: proposals.filter(p => p.approvedByMeta).length,
|
|
306
|
+
});
|
|
307
|
+
return gen;
|
|
308
|
+
}
|
|
309
|
+
// ── Lineage Management ────────────────────────────────────
|
|
310
|
+
/**
|
|
311
|
+
* Record a generation to the lineage file.
|
|
312
|
+
*/
|
|
313
|
+
async recordGeneration(gen) {
|
|
314
|
+
const lineage = await this.loadLineage();
|
|
315
|
+
lineage.generations.push(gen);
|
|
316
|
+
lineage.lastGeneration = gen.generation;
|
|
317
|
+
await safePersist(join(this.workspaceDir, 'evolution', LINEAGE_FILE), JSON.stringify(lineage, null, 2));
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Load the evolution lineage.
|
|
321
|
+
*/
|
|
322
|
+
async loadLineage() {
|
|
323
|
+
try {
|
|
324
|
+
const raw = await readFile(join(this.workspaceDir, 'evolution', LINEAGE_FILE), 'utf-8');
|
|
325
|
+
return safeJsonParse(raw);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
logger.debug('S² Engine: lineage file not found', { error: String(err) });
|
|
329
|
+
return { generations: [], lastGeneration: 0 };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// ── Plan Writing ──────────────────────────────────────────
|
|
333
|
+
/**
|
|
334
|
+
* Write approved proposals as a plan file for human review.
|
|
335
|
+
*/
|
|
336
|
+
async writePlan(proposals) {
|
|
337
|
+
const lineage = await this.loadLineage();
|
|
338
|
+
const gen = lineage.lastGeneration + 1;
|
|
339
|
+
const plansDir = join(this.projectDir, 'plans', PLANS_DIR);
|
|
340
|
+
await safeMkdir(plansDir);
|
|
341
|
+
const filename = `gen-${String(gen).padStart(3, '0')}.md`;
|
|
342
|
+
const filepath = join(plansDir, filename);
|
|
343
|
+
const lines = [
|
|
344
|
+
`# S² Engine — Generation ${gen}`,
|
|
345
|
+
'',
|
|
346
|
+
`Generated: ${new Date().toISOString()}`,
|
|
347
|
+
'',
|
|
348
|
+
'## Proposals',
|
|
349
|
+
'',
|
|
350
|
+
];
|
|
351
|
+
for (const p of proposals) {
|
|
352
|
+
lines.push(`### ${p.title}`);
|
|
353
|
+
lines.push('');
|
|
354
|
+
lines.push(`- **Category**: ${p.category}`);
|
|
355
|
+
lines.push(`- **Target**: ${p.targetFile}`);
|
|
356
|
+
lines.push(`- **Confidence**: ${(p.confidence * 100).toFixed(0)}%`);
|
|
357
|
+
lines.push(`- **Description**: ${p.description}`);
|
|
358
|
+
lines.push('');
|
|
359
|
+
}
|
|
360
|
+
await safeWriteFile(filepath, lines.join('\n'));
|
|
361
|
+
logger.info('S² Engine: plan written', { path: filepath });
|
|
362
|
+
return filepath;
|
|
363
|
+
}
|
|
364
|
+
// ── State ─────────────────────────────────────────────────
|
|
365
|
+
getPhase() {
|
|
366
|
+
return this.phase;
|
|
367
|
+
}
|
|
368
|
+
}
|