zouroboros-selfheal 2.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 marlandoj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI for Zouroboros evolve — execute prescriptions with regression detection
4
+ *
5
+ * Usage: zouroboros-evolve [--prescription <path>] [--dry-run] [--skip-governor]
6
+ */
7
+ export {};
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI for Zouroboros evolve — execute prescriptions with regression detection
4
+ *
5
+ * Usage: zouroboros-evolve [--prescription <path>] [--dry-run] [--skip-governor]
6
+ */
7
+ import { parseArgs } from 'util';
8
+ import { evolve } from '../index.js';
9
+ const { values } = parseArgs({
10
+ args: Bun.argv.slice(2),
11
+ options: {
12
+ prescription: { type: 'string', short: 'p' },
13
+ 'dry-run': { type: 'boolean' },
14
+ 'skip-governor': { type: 'boolean' },
15
+ help: { type: 'boolean', short: 'h' },
16
+ },
17
+ strict: false,
18
+ });
19
+ if (values.help) {
20
+ console.log(`
21
+ zouroboros-evolve — execute prescriptions with regression detection
22
+
23
+ USAGE:
24
+ zouroboros-evolve [options]
25
+
26
+ OPTIONS:
27
+ --prescription, -p Path to prescription JSON (default: run prescribe first)
28
+ --dry-run Show what would be executed without making changes
29
+ --skip-governor Bypass the governor safety gate (use with caution)
30
+ --help, -h Show this help
31
+
32
+ SAFETY:
33
+ The governor gate blocks prescriptions with high regression risk.
34
+ Use --skip-governor only for manual overrides after review.
35
+ All evolutions are git-committed for rollback capability.
36
+
37
+ EXAMPLES:
38
+ zouroboros-evolve --prescription ./prescription.json
39
+ zouroboros-evolve --dry-run
40
+ zouroboros-evolve --prescription ./rx.json --skip-governor
41
+ `);
42
+ process.exit(0);
43
+ }
44
+ async function main() {
45
+ const result = await evolve({
46
+ prescription: values.prescription,
47
+ dryRun: !!values['dry-run'],
48
+ skipGovernor: !!values['skip-governor'],
49
+ });
50
+ if (result.success) {
51
+ console.log(`✓ Evolution complete`);
52
+ console.log(` Delta: ${(result.delta * 100).toFixed(1)}%`);
53
+ console.log(` Reverted: ${result.reverted ? 'yes' : 'no'}`);
54
+ console.log(` Detail: ${result.detail}`);
55
+ }
56
+ else {
57
+ console.error(`✗ Evolution failed: ${result.detail}`);
58
+ process.exit(1);
59
+ }
60
+ }
61
+ main().catch((err) => {
62
+ console.error('Evolve failed:', err.message);
63
+ process.exit(1);
64
+ });
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI for Zouroboros introspection — 7-metric health scorecard
4
+ *
5
+ * Usage: zouroboros-introspect [--json] [--store] [--verbose]
6
+ */
7
+ export {};
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI for Zouroboros introspection — 7-metric health scorecard
4
+ *
5
+ * Usage: zouroboros-introspect [--json] [--store] [--verbose]
6
+ */
7
+ import { parseArgs } from 'util';
8
+ import { introspect } from '../index.js';
9
+ import { formatScorecard } from '../introspect/scorecard.js';
10
+ const { values } = parseArgs({
11
+ args: Bun.argv.slice(2),
12
+ options: {
13
+ json: { type: 'boolean', short: 'j' },
14
+ store: { type: 'boolean', short: 's' },
15
+ verbose: { type: 'boolean', short: 'v' },
16
+ help: { type: 'boolean', short: 'h' },
17
+ },
18
+ strict: false,
19
+ });
20
+ if (values.help) {
21
+ console.log(`
22
+ zouroboros-introspect — 7-metric health scorecard for Zo ecosystem
23
+
24
+ USAGE:
25
+ zouroboros-introspect [options]
26
+
27
+ OPTIONS:
28
+ --json, -j Output raw JSON scorecard
29
+ --store, -s Persist scorecard to ~/.zo/selfheal/
30
+ --verbose, -v Print formatted scorecard table
31
+ --help, -h Show this help
32
+
33
+ METRICS:
34
+ memory_health WAL size, episode count, decay distribution
35
+ skill_coverage Skills with SKILL.md, scripts, references
36
+ eval_density Evaluations per seed in last 30 days
37
+ swarm_success Swarm procedure pass rate
38
+ persona_depth Persona completeness (SOUL, rules, memory)
39
+ graph_connectivity Cross-entity link density in knowledge graph
40
+ self_heal_cadence Days since last introspect→prescribe→evolve cycle
41
+
42
+ EXAMPLES:
43
+ zouroboros-introspect --verbose
44
+ zouroboros-introspect --json --store
45
+ `);
46
+ process.exit(0);
47
+ }
48
+ async function main() {
49
+ const scorecard = await introspect({
50
+ json: !!values.json,
51
+ store: !!values.store,
52
+ verbose: !!values.verbose,
53
+ });
54
+ if (!values.json && !values.verbose) {
55
+ console.log(formatScorecard(scorecard));
56
+ }
57
+ }
58
+ main().catch((err) => {
59
+ console.error('Introspect failed:', err.message);
60
+ process.exit(1);
61
+ });
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI for Zouroboros prescribe — auto-generate improvement seeds
4
+ *
5
+ * Usage: zouroboros-prescribe [--scorecard <path>] [--target <metric>] [--live] [--output <dir>] [--dry-run]
6
+ */
7
+ export {};
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI for Zouroboros prescribe — auto-generate improvement seeds
4
+ *
5
+ * Usage: zouroboros-prescribe [--scorecard <path>] [--target <metric>] [--live] [--output <dir>] [--dry-run]
6
+ */
7
+ import { parseArgs } from 'util';
8
+ import { writeFileSync, mkdirSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { prescribe } from '../index.js';
11
+ const { values } = parseArgs({
12
+ args: Bun.argv.slice(2),
13
+ options: {
14
+ scorecard: { type: 'string', short: 's' },
15
+ target: { type: 'string', short: 't' },
16
+ live: { type: 'boolean', short: 'l' },
17
+ output: { type: 'string', short: 'o', default: '.' },
18
+ 'dry-run': { type: 'boolean' },
19
+ help: { type: 'boolean', short: 'h' },
20
+ },
21
+ strict: false,
22
+ });
23
+ if (values.help) {
24
+ console.log(`
25
+ zouroboros-prescribe — generate improvement prescriptions from scorecard
26
+
27
+ USAGE:
28
+ zouroboros-prescribe [options]
29
+
30
+ OPTIONS:
31
+ --scorecard, -s Path to a saved scorecard JSON (default: run live introspect)
32
+ --target, -t Target metric name (default: weakest metric)
33
+ --live, -l Run live introspection instead of using cached scorecard
34
+ --output, -o Output directory for prescription (default: .)
35
+ --dry-run Show what would be prescribed without writing files
36
+ --help, -h Show this help
37
+
38
+ PLAYBOOKS:
39
+ 14 playbooks map metrics to concrete improvement strategies.
40
+ The governor gate evaluates risk before approving execution.
41
+
42
+ EXAMPLES:
43
+ zouroboros-prescribe --live --target memory_health
44
+ zouroboros-prescribe --scorecard ./scorecard.json --output ./prescriptions
45
+ zouroboros-prescribe --dry-run
46
+ `);
47
+ process.exit(0);
48
+ }
49
+ async function main() {
50
+ const prescription = await prescribe({
51
+ scorecard: values.scorecard,
52
+ live: !!values.live,
53
+ target: values.target,
54
+ });
55
+ if (values['dry-run']) {
56
+ console.log('\n[dry-run] Prescription:');
57
+ console.log(JSON.stringify(prescription, null, 2));
58
+ return;
59
+ }
60
+ const outDir = values.output || '.';
61
+ mkdirSync(outDir, { recursive: true });
62
+ const outPath = join(outDir, `prescription-${Date.now()}.json`);
63
+ writeFileSync(outPath, JSON.stringify(prescription, null, 2));
64
+ console.log(`✓ Prescription written to: ${outPath}`);
65
+ console.log(` Metric: ${prescription.metric.name} (${(prescription.metric.score * 100).toFixed(0)}%)`);
66
+ console.log(` Playbook: ${prescription.playbook.name}`);
67
+ console.log(` Governor: ${prescription.governor.approved ? 'APPROVED' : 'BLOCKED'}`);
68
+ }
69
+ main().catch((err) => {
70
+ console.error('Prescribe failed:', err.message);
71
+ process.exit(1);
72
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Evolution executor for prescribed improvements
3
+ */
4
+ import type { Prescription, EvolutionResult } from '../types.js';
5
+ export declare function executeEvolution(prescription: Prescription, options: {
6
+ dryRun?: boolean;
7
+ skipGovernor?: boolean;
8
+ }): Promise<EvolutionResult>;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Evolution executor for prescribed improvements
3
+ */
4
+ import { execSync } from 'child_process';
5
+ import { mkdirSync, writeFileSync } from 'fs';
6
+ import { join } from 'path';
7
+ const WORKSPACE = process.env.ZO_WORKSPACE || '/home/workspace';
8
+ const RESULTS_DIR = join(WORKSPACE, 'Seeds/zouroboros/results');
9
+ function run(cmd, timeout = 120000) {
10
+ try {
11
+ const stdout = execSync(cmd, {
12
+ cwd: WORKSPACE,
13
+ timeout,
14
+ encoding: 'utf-8',
15
+ stdio: ['pipe', 'pipe', 'pipe'],
16
+ });
17
+ return { stdout: stdout.trim(), ok: true, code: 0 };
18
+ }
19
+ catch (e) {
20
+ return { stdout: (e.stdout || '').toString().trim(), ok: false, code: e.status ?? 1 };
21
+ }
22
+ }
23
+ function measureMetric(cmd) {
24
+ const result = run(cmd, 60000);
25
+ if (!result.ok)
26
+ return null;
27
+ const num = parseFloat(result.stdout);
28
+ return isNaN(num) ? null : num;
29
+ }
30
+ async function executeAutoloopMode(prescription) {
31
+ const programPath = `/tmp/z-prescription-${prescription.id}.md`;
32
+ if (prescription.program) {
33
+ writeFileSync(programPath, prescription.program);
34
+ }
35
+ console.error(` [evolve] Starting autoloop: ${prescription.playbook.name}`);
36
+ const result = run(`bun Skills/autoloop/scripts/autoloop.ts --program "${programPath}" 2>&1`, 8 * 60 * 60 * 1000 // 8 hour timeout
37
+ );
38
+ const success = result.ok && result.stdout.includes('KEEP');
39
+ return {
40
+ prescriptionId: prescription.id,
41
+ success,
42
+ baseline: { composite: prescription.metric.score, metrics: [] },
43
+ postFlight: null, // Would need to parse autoloop results
44
+ delta: 0,
45
+ reverted: !success,
46
+ detail: result.stdout.slice(0, 500),
47
+ };
48
+ }
49
+ async function executeScriptMode(prescription) {
50
+ console.error(` [evolve] Executing script mode: ${prescription.playbook.name}`);
51
+ // Run setup commands
52
+ if (prescription.playbook.setupCommands) {
53
+ for (const cmd of prescription.playbook.setupCommands) {
54
+ console.error(` [evolve] Setup: ${cmd.slice(0, 60)}...`);
55
+ const setupResult = run(cmd);
56
+ if (!setupResult.ok) {
57
+ return {
58
+ prescriptionId: prescription.id,
59
+ success: false,
60
+ baseline: { composite: prescription.metric.score, metrics: [] },
61
+ postFlight: null,
62
+ delta: 0,
63
+ reverted: false,
64
+ detail: `Setup failed: ${setupResult.stdout.slice(0, 200)}`,
65
+ };
66
+ }
67
+ }
68
+ }
69
+ // Run main command
70
+ const runCmd = prescription.playbook.runCommand || 'echo "No run command"';
71
+ console.error(` [evolve] Executing: ${runCmd.slice(0, 60)}...`);
72
+ const result = run(runCmd, 300000);
73
+ // Measure post-flight metric
74
+ const postValue = measureMetric(prescription.playbook.metricCommand);
75
+ const baseline = prescription.metric.value;
76
+ const delta = postValue !== null ? postValue - baseline : 0;
77
+ const improved = prescription.playbook.metricDirection === 'higher_is_better'
78
+ ? delta > 0
79
+ : delta < 0;
80
+ return {
81
+ prescriptionId: prescription.id,
82
+ success: result.ok,
83
+ baseline: { composite: baseline, metrics: [{ name: prescription.metric.name, value: baseline, score: prescription.metric.score, status: prescription.metric.status }] },
84
+ postFlight: postValue !== null ? {
85
+ composite: postValue,
86
+ metrics: [{ name: prescription.metric.name, value: postValue, score: postValue, status: improved ? 'HEALTHY' : prescription.metric.status }],
87
+ } : null,
88
+ delta,
89
+ reverted: !improved && result.ok,
90
+ detail: result.stdout.slice(0, 500),
91
+ };
92
+ }
93
+ export async function executeEvolution(prescription, options) {
94
+ const { dryRun = false, skipGovernor = false } = options;
95
+ // Governor check
96
+ if (!skipGovernor && !prescription.governor.approved) {
97
+ return {
98
+ prescriptionId: prescription.id,
99
+ success: false,
100
+ baseline: { composite: prescription.metric.score, metrics: [] },
101
+ postFlight: null,
102
+ delta: 0,
103
+ reverted: false,
104
+ detail: `Governor blocked: ${prescription.governor.reason}`,
105
+ };
106
+ }
107
+ if (dryRun) {
108
+ return {
109
+ prescriptionId: prescription.id,
110
+ success: true,
111
+ baseline: { composite: prescription.metric.score, metrics: [] },
112
+ postFlight: null,
113
+ delta: 0,
114
+ reverted: false,
115
+ detail: 'Dry run - no changes made',
116
+ };
117
+ }
118
+ // Ensure results directory
119
+ mkdirSync(RESULTS_DIR, { recursive: true });
120
+ // Execute based on mode
121
+ const result = prescription.program
122
+ ? await executeAutoloopMode(prescription)
123
+ : await executeScriptMode(prescription);
124
+ // Save result
125
+ const resultPath = join(RESULTS_DIR, `${prescription.id}-result.json`);
126
+ writeFileSync(resultPath, JSON.stringify(result, null, 2));
127
+ console.error(` [evolve] Result: ${result.success ? '✅ SUCCESS' : '❌ FAILED'}`);
128
+ if (result.delta !== 0) {
129
+ const sign = result.delta > 0 ? '+' : '';
130
+ console.error(` [evolve] Delta: ${sign}${(result.delta * 100).toFixed(1)}%`);
131
+ }
132
+ return result;
133
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Feedback Loop Tuning
3
+ *
4
+ * Auto-adjusts metric weights based on evolution outcomes.
5
+ * Learns which metrics are most predictive of successful improvements.
6
+ */
7
+ import type { EvolutionResult, MetricResult } from './types.js';
8
+ export interface WeightConfig {
9
+ weights: Record<string, number>;
10
+ learningRate: number;
11
+ minWeight: number;
12
+ maxWeight: number;
13
+ version: number;
14
+ lastUpdated: string;
15
+ }
16
+ export interface FeedbackRecord {
17
+ timestamp: string;
18
+ metricName: string;
19
+ prescriptionId: string;
20
+ baselineScore: number;
21
+ postFlightScore: number | null;
22
+ delta: number;
23
+ success: boolean;
24
+ weightBefore: number;
25
+ weightAfter: number;
26
+ }
27
+ export declare class FeedbackTuner {
28
+ private weightsFile;
29
+ private storeFile;
30
+ private config;
31
+ private store;
32
+ constructor(dataDir: string);
33
+ get weights(): Record<string, number>;
34
+ get version(): number;
35
+ recordOutcome(metric: MetricResult, result: EvolutionResult): FeedbackRecord;
36
+ getWeightForMetric(name: string): number;
37
+ getHistory(limit?: number): FeedbackRecord[];
38
+ getWeightHistory(limit?: number): Array<{
39
+ timestamp: string;
40
+ weights: Record<string, number>;
41
+ trigger: string;
42
+ }>;
43
+ getSuccessRate(metricName?: string): number;
44
+ getAvgDelta(metricName?: string): number;
45
+ resetWeights(): void;
46
+ private normalizeWeights;
47
+ private loadWeights;
48
+ private loadStore;
49
+ private save;
50
+ }
51
+ export declare function createFeedbackTuner(dataDir: string): FeedbackTuner;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Feedback Loop Tuning
3
+ *
4
+ * Auto-adjusts metric weights based on evolution outcomes.
5
+ * Learns which metrics are most predictive of successful improvements.
6
+ */
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
8
+ import { join } from 'path';
9
+ const DEFAULT_WEIGHTS = {
10
+ 'Memory Recall': 0.20,
11
+ 'Graph Connectivity': 0.15,
12
+ 'Routing Accuracy': 0.20,
13
+ 'Eval Calibration': 0.15,
14
+ 'Procedure Freshness': 0.15,
15
+ 'Episode Velocity': 0.15,
16
+ };
17
+ export class FeedbackTuner {
18
+ weightsFile;
19
+ storeFile;
20
+ config;
21
+ store;
22
+ constructor(dataDir) {
23
+ mkdirSync(dataDir, { recursive: true });
24
+ this.weightsFile = join(dataDir, 'metric-weights.json');
25
+ this.storeFile = join(dataDir, 'feedback-store.json');
26
+ this.config = this.loadWeights();
27
+ this.store = this.loadStore();
28
+ }
29
+ get weights() {
30
+ return { ...this.config.weights };
31
+ }
32
+ get version() {
33
+ return this.config.version;
34
+ }
35
+ recordOutcome(metric, result) {
36
+ const weightBefore = this.config.weights[metric.name] || 0.10;
37
+ let weightAfter = weightBefore;
38
+ if (result.success && result.delta !== 0) {
39
+ // Successful evolution — adjust weight based on impact
40
+ const impactMagnitude = Math.abs(result.delta);
41
+ if (result.delta > 0) {
42
+ // Positive improvement — slightly increase weight (reward productive metrics)
43
+ weightAfter = weightBefore + this.config.learningRate * impactMagnitude;
44
+ }
45
+ else {
46
+ // Regression despite "success" — decrease weight
47
+ weightAfter = weightBefore - this.config.learningRate * impactMagnitude;
48
+ }
49
+ }
50
+ else if (!result.success) {
51
+ // Failed evolution — slightly decrease weight (this metric led to bad outcomes)
52
+ weightAfter = weightBefore - this.config.learningRate * 0.5;
53
+ }
54
+ // Clamp
55
+ weightAfter = Math.max(this.config.minWeight, Math.min(this.config.maxWeight, weightAfter));
56
+ const record = {
57
+ timestamp: new Date().toISOString(),
58
+ metricName: metric.name,
59
+ prescriptionId: result.prescriptionId,
60
+ baselineScore: metric.score,
61
+ postFlightScore: result.postFlight?.composite ?? null,
62
+ delta: result.delta,
63
+ success: result.success,
64
+ weightBefore,
65
+ weightAfter,
66
+ };
67
+ // Apply weight change
68
+ this.config.weights[metric.name] = weightAfter;
69
+ this.normalizeWeights();
70
+ this.config.version++;
71
+ this.config.lastUpdated = record.timestamp;
72
+ // Store record
73
+ this.store.records.push(record);
74
+ if (this.store.records.length > 500) {
75
+ this.store.records = this.store.records.slice(-500);
76
+ }
77
+ // Store weight snapshot
78
+ this.store.weightHistory.push({
79
+ timestamp: record.timestamp,
80
+ weights: { ...this.config.weights },
81
+ trigger: `${metric.name}: ${result.success ? 'success' : 'fail'} (delta=${result.delta.toFixed(4)})`,
82
+ });
83
+ if (this.store.weightHistory.length > 100) {
84
+ this.store.weightHistory = this.store.weightHistory.slice(-100);
85
+ }
86
+ this.save();
87
+ return record;
88
+ }
89
+ getWeightForMetric(name) {
90
+ return this.config.weights[name] || 0.10;
91
+ }
92
+ getHistory(limit = 20) {
93
+ return this.store.records.slice(-limit);
94
+ }
95
+ getWeightHistory(limit = 20) {
96
+ return this.store.weightHistory.slice(-limit);
97
+ }
98
+ getSuccessRate(metricName) {
99
+ const records = metricName
100
+ ? this.store.records.filter(r => r.metricName === metricName)
101
+ : this.store.records;
102
+ if (records.length === 0)
103
+ return 0;
104
+ return records.filter(r => r.success).length / records.length;
105
+ }
106
+ getAvgDelta(metricName) {
107
+ const records = metricName
108
+ ? this.store.records.filter(r => r.metricName === metricName)
109
+ : this.store.records;
110
+ if (records.length === 0)
111
+ return 0;
112
+ return records.reduce((sum, r) => sum + r.delta, 0) / records.length;
113
+ }
114
+ resetWeights() {
115
+ this.config.weights = { ...DEFAULT_WEIGHTS };
116
+ this.config.version++;
117
+ this.config.lastUpdated = new Date().toISOString();
118
+ this.save();
119
+ }
120
+ normalizeWeights() {
121
+ const total = Object.values(this.config.weights).reduce((s, w) => s + w, 0);
122
+ if (total === 0)
123
+ return;
124
+ for (const key of Object.keys(this.config.weights)) {
125
+ this.config.weights[key] /= total;
126
+ }
127
+ }
128
+ loadWeights() {
129
+ if (existsSync(this.weightsFile)) {
130
+ try {
131
+ return JSON.parse(readFileSync(this.weightsFile, 'utf-8'));
132
+ }
133
+ catch { /* fall through */ }
134
+ }
135
+ return {
136
+ weights: { ...DEFAULT_WEIGHTS },
137
+ learningRate: 0.05,
138
+ minWeight: 0.05,
139
+ maxWeight: 0.40,
140
+ version: 0,
141
+ lastUpdated: new Date().toISOString(),
142
+ };
143
+ }
144
+ loadStore() {
145
+ if (existsSync(this.storeFile)) {
146
+ try {
147
+ return JSON.parse(readFileSync(this.storeFile, 'utf-8'));
148
+ }
149
+ catch { /* fall through */ }
150
+ }
151
+ return { records: [], weightHistory: [] };
152
+ }
153
+ save() {
154
+ writeFileSync(this.weightsFile, JSON.stringify(this.config, null, 2));
155
+ writeFileSync(this.storeFile, JSON.stringify(this.store, null, 2));
156
+ }
157
+ }
158
+ export function createFeedbackTuner(dataDir) {
159
+ return new FeedbackTuner(dataDir);
160
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Evolution History
3
+ *
4
+ * Track and visualize system improvements over time.
5
+ */
6
+ import type { EvolutionResult, ScorecardSnapshot } from './types.js';
7
+ export interface HistoryEntry {
8
+ id: string;
9
+ timestamp: string;
10
+ prescriptionId: string;
11
+ playbookId: string;
12
+ playbookName: string;
13
+ metricName: string;
14
+ baseline: ScorecardSnapshot;
15
+ postFlight: ScorecardSnapshot | null;
16
+ delta: number;
17
+ success: boolean;
18
+ reverted: boolean;
19
+ duration?: number;
20
+ tags: string[];
21
+ }
22
+ export interface HistoryStats {
23
+ totalEvolutions: number;
24
+ successCount: number;
25
+ failCount: number;
26
+ revertCount: number;
27
+ successRate: number;
28
+ avgDelta: number;
29
+ avgPositiveDelta: number;
30
+ bestEvolution: HistoryEntry | null;
31
+ worstEvolution: HistoryEntry | null;
32
+ streakCurrent: number;
33
+ streakBest: number;
34
+ byMetric: Record<string, {
35
+ count: number;
36
+ avgDelta: number;
37
+ successRate: number;
38
+ }>;
39
+ byPlaybook: Record<string, {
40
+ count: number;
41
+ avgDelta: number;
42
+ successRate: number;
43
+ }>;
44
+ }
45
+ export interface TrendPoint {
46
+ timestamp: string;
47
+ composite: number;
48
+ metrics: Record<string, number>;
49
+ }
50
+ export declare class EvolutionHistory {
51
+ private dataFile;
52
+ private store;
53
+ private maxEntries;
54
+ private maxTrends;
55
+ constructor(dataDir: string, maxEntries?: number, maxTrends?: number);
56
+ record(entry: Omit<HistoryEntry, 'id'>): HistoryEntry;
57
+ recordFromResult(result: EvolutionResult, playbookId: string, playbookName: string, metricName: string, tags?: string[]): HistoryEntry;
58
+ getStats(): HistoryStats;
59
+ getTrends(limit?: number): TrendPoint[];
60
+ getEntries(options?: {
61
+ limit?: number;
62
+ metricName?: string;
63
+ playbookId?: string;
64
+ successOnly?: boolean;
65
+ }): HistoryEntry[];
66
+ getRecentEntries(limit?: number): HistoryEntry[];
67
+ clear(): void;
68
+ private load;
69
+ private save;
70
+ }
71
+ export declare function createEvolutionHistory(dataDir: string): EvolutionHistory;