@neuroverseos/nv-sim 0.1.2 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +562 -68
  2. package/dist/adapters/mirofish.js +461 -0
  3. package/dist/adapters/scienceclaw.js +750 -0
  4. package/dist/assets/index-B64NuIXu.css +1 -0
  5. package/dist/assets/index-DbzSnYxr.js +532 -0
  6. package/dist/assets/mirotir-logo-DUexumBH.svg +185 -0
  7. package/dist/assets/reportEngine-DKWTrP6-.js +1 -0
  8. package/dist/components/ConstraintsPanel.js +11 -0
  9. package/dist/components/StakeholderBuilder.js +32 -0
  10. package/dist/components/ui/badge.js +24 -0
  11. package/dist/components/ui/button.js +70 -0
  12. package/dist/components/ui/card.js +57 -0
  13. package/dist/components/ui/input.js +44 -0
  14. package/dist/components/ui/label.js +45 -0
  15. package/dist/components/ui/select.js +70 -0
  16. package/dist/engine/aiProvider.js +681 -0
  17. package/dist/engine/auditTrace.js +352 -0
  18. package/dist/engine/behavioralAnalysis.js +605 -0
  19. package/dist/engine/cli.js +1408 -299
  20. package/dist/engine/dynamicsGovernance.js +588 -0
  21. package/dist/engine/fullGovernedLoop.js +367 -0
  22. package/dist/engine/governance.js +8 -3
  23. package/dist/engine/governedSimulation.js +114 -17
  24. package/dist/engine/index.js +56 -1
  25. package/dist/engine/liveAdapter.js +342 -0
  26. package/dist/engine/liveVisualizer.js +4284 -0
  27. package/dist/engine/metrics/science.metrics.js +335 -0
  28. package/dist/engine/narrativeInjection.js +360 -0
  29. package/dist/engine/policyEnforcement.js +1611 -0
  30. package/dist/engine/policyEngine.js +799 -0
  31. package/dist/engine/primeRadiant.js +540 -0
  32. package/dist/engine/reasoningEngine.js +57 -3
  33. package/dist/engine/reportEngine.js +97 -0
  34. package/dist/engine/scenarioCapsule.js +56 -0
  35. package/dist/engine/scenarioComparison.js +463 -0
  36. package/dist/engine/scenarioLibrary.js +248 -0
  37. package/dist/engine/swarmSimulation.js +54 -1
  38. package/dist/engine/worldComparison.js +358 -0
  39. package/dist/engine/worldStorage.js +232 -0
  40. package/dist/favicon.ico +0 -0
  41. package/dist/index.html +23 -0
  42. package/dist/lib/reasoningEngine.js +290 -0
  43. package/dist/lib/simulationAdapter.js +686 -0
  44. package/dist/lib/swarmParser.js +291 -0
  45. package/dist/lib/types.js +2 -0
  46. package/dist/lib/utils.js +8 -0
  47. package/dist/placeholder.svg +1 -0
  48. package/dist/robots.txt +14 -0
  49. package/dist/runtime/govern.js +473 -0
  50. package/dist/runtime/index.js +75 -0
  51. package/dist/runtime/types.js +11 -0
  52. package/package.json +17 -12
  53. package/variants/.gitkeep +0 -0
@@ -0,0 +1,1611 @@
1
+ "use strict";
2
+ /**
3
+ * Policy Enforcement System — The Control Layer
4
+ *
5
+ * NeuroVerse is a control layer where users:
6
+ * 1. Define rules (world files)
7
+ * 2. Run simulations through it
8
+ * 3. Change rules between runs
9
+ * 4. See how those rules change behavior over time
10
+ *
11
+ * This is NOT a simulator. This is the governance lab.
12
+ *
13
+ * Three pillars:
14
+ * CONTROL — world files, rule definition
15
+ * ENFORCEMENT — interceptAction(), runtime governance
16
+ * INSIGHT — reports, divergence, metrics across iterations
17
+ *
18
+ * "Design rules. Run reality. See what changes."
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.createEnforcementSession = createEnforcementSession;
55
+ exports.formatEnforcementReport = formatEnforcementReport;
56
+ exports.formatGuidedNextSteps = formatGuidedNextSteps;
57
+ exports.suggestExperiments = suggestExperiments;
58
+ exports.governedDetectArchetype = governedDetectArchetype;
59
+ exports.governedSynthesizeStrategy = governedSynthesizeStrategy;
60
+ exports.governedFindSimilar = governedFindSimilar;
61
+ exports.formatPolicyDiagnostics = formatPolicyDiagnostics;
62
+ exports.formatRuleImpactAttribution = formatRuleImpactAttribution;
63
+ exports.detectSystemArchetype = detectSystemArchetype;
64
+ exports.loadExperimentLibrary = loadExperimentLibrary;
65
+ exports.createSavedExperiment = createSavedExperiment;
66
+ exports.formatExperimentLineage = formatExperimentLineage;
67
+ exports.formatSimilarExperiments = formatSimilarExperiments;
68
+ exports.formatBestKnownStrategy = formatBestKnownStrategy;
69
+ exports.formatMultiPatchComparison = formatMultiPatchComparison;
70
+ exports.exportEnforcementReportJSON = exportEnforcementReportJSON;
71
+ const fs = __importStar(require("fs"));
72
+ const path = __importStar(require("path"));
73
+ const governedSimulation_1 = require("./governedSimulation");
74
+ const worldStorage_1 = require("./worldStorage");
75
+ const aiProvider_1 = require("./aiProvider");
76
+ const behavioralAnalysis_1 = require("./behavioralAnalysis");
77
+ /**
78
+ * Create a new policy enforcement session.
79
+ *
80
+ * This is the entry point for the governance lab:
81
+ * "Same scenario, different rules, measurable divergence."
82
+ */
83
+ function createEnforcementSession(scenario, request, paths) {
84
+ const sessionId = `enforce_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
85
+ const runs = [];
86
+ function diffRules(prev, next) {
87
+ const prevIds = new Set(prev.world.invariants.map(i => i.id));
88
+ const nextIds = new Set(next.world.invariants.map(i => i.id));
89
+ const prevGateIds = new Set((prev.world.gates ?? []).map(g => g.id));
90
+ const nextGateIds = new Set((next.world.gates ?? []).map(g => g.id));
91
+ return {
92
+ added: next.world.invariants.filter(i => !prevIds.has(i.id)),
93
+ removed: prev.world.invariants.filter(i => !nextIds.has(i.id)),
94
+ gatesAdded: (next.world.gates ?? []).filter(g => !prevGateIds.has(g.id)),
95
+ gatesRemoved: (prev.world.gates ?? []).filter(g => !nextGateIds.has(g.id)),
96
+ thesisChanged: prev.world.thesis !== next.world.thesis,
97
+ };
98
+ }
99
+ return {
100
+ id: sessionId,
101
+ async run(world, narrativeEvents) {
102
+ const iteration = runs.length + 1;
103
+ const result = await (0, governedSimulation_1.runGovernedComparison)(request, world.world, paths, narrativeEvents);
104
+ const ruleChanges = runs.length > 0
105
+ ? diffRules(runs[runs.length - 1].world, world)
106
+ : undefined;
107
+ const run = {
108
+ id: `${sessionId}_run_${iteration}`,
109
+ iteration,
110
+ world,
111
+ result,
112
+ timestamp: new Date().toISOString(),
113
+ ruleChanges,
114
+ };
115
+ runs.push(run);
116
+ return run;
117
+ },
118
+ get runs() {
119
+ return runs;
120
+ },
121
+ generateReport() {
122
+ if (runs.length === 0) {
123
+ throw new Error("No runs to report on. Run at least one enforcement pass first.");
124
+ }
125
+ const summaries = runs.map(r => ({
126
+ iteration: r.iteration,
127
+ worldName: r.world.name,
128
+ ruleCount: r.world.world.invariants.length,
129
+ gateCount: (r.world.world.gates ?? []).length,
130
+ metrics: r.result.governed.metrics,
131
+ comparison: r.result.comparison,
132
+ governanceStats: r.result.governanceStats,
133
+ ruleChanges: r.ruleChanges,
134
+ behavioralSummary: r.result.behavioralAnalysis.summary,
135
+ }));
136
+ const divergence = computeDivergence(summaries);
137
+ // Build cross-run behavioral comparisons
138
+ const behavioralComparisons = [];
139
+ for (let i = 1; i < runs.length; i++) {
140
+ const prev = runs[i - 1];
141
+ const curr = runs[i];
142
+ // Describe what rules changed
143
+ const ruleChanges = curr.ruleChanges;
144
+ const deltaDesc = ruleChanges
145
+ ? [
146
+ ...ruleChanges.added.map(r => `+${r.description}`),
147
+ ...ruleChanges.removed.map(r => `-${r.description}`),
148
+ ...(ruleChanges.thesisChanged ? ["thesis changed"] : []),
149
+ ].join("; ") || "no rule changes"
150
+ : "first run";
151
+ behavioralComparisons.push((0, behavioralAnalysis_1.compareBehaviorAcrossRuns)(prev.world.name, prev.result.governed.swarm.rounds, curr.world.name, curr.result.governed.swarm.rounds, deltaDesc));
152
+ }
153
+ const recommendation = buildRecommendation(summaries, divergence);
154
+ return {
155
+ sessionId,
156
+ scenario,
157
+ runs: summaries,
158
+ divergence,
159
+ behavioralComparisons,
160
+ recommendation,
161
+ generatedAt: new Date().toISOString(),
162
+ };
163
+ },
164
+ forkWorldForNextRun(source, name, modifications) {
165
+ return (0, worldStorage_1.forkWorld)(source, name, modifications);
166
+ },
167
+ };
168
+ }
169
+ // ============================================
170
+ // DIVERGENCE COMPUTATION
171
+ // ============================================
172
+ function computeDivergence(runs) {
173
+ const stabilityTrend = runs.map(r => r.metrics.stabilityScore);
174
+ const collapseTrend = runs.map(r => r.metrics.collapseProbability);
175
+ const effectivenessTrend = runs.map(r => r.comparison.governanceEffectiveness);
176
+ // Find best and worst by governance effectiveness
177
+ let bestIteration = 1;
178
+ let worstIteration = 1;
179
+ let bestScore = -Infinity;
180
+ let worstScore = Infinity;
181
+ for (const run of runs) {
182
+ const score = run.comparison.governanceEffectiveness;
183
+ if (score > bestScore) {
184
+ bestScore = score;
185
+ bestIteration = run.iteration;
186
+ }
187
+ if (score < worstScore) {
188
+ worstScore = score;
189
+ worstIteration = run.iteration;
190
+ }
191
+ }
192
+ // Total divergence: sum of absolute changes between consecutive runs
193
+ let totalDivergence = 0;
194
+ for (let i = 1; i < runs.length; i++) {
195
+ const prev = runs[i - 1];
196
+ const curr = runs[i];
197
+ totalDivergence += Math.abs(curr.metrics.stabilityScore - prev.metrics.stabilityScore);
198
+ totalDivergence += Math.abs(curr.metrics.collapseProbability - prev.metrics.collapseProbability);
199
+ totalDivergence += Math.abs(curr.comparison.governanceEffectiveness - prev.comparison.governanceEffectiveness);
200
+ }
201
+ totalDivergence = Number(totalDivergence.toFixed(3));
202
+ const narrative = buildDivergenceNarrative(runs, bestIteration, worstIteration, totalDivergence);
203
+ return {
204
+ stabilityTrend,
205
+ collapseTrend,
206
+ effectivenessTrend,
207
+ bestIteration,
208
+ worstIteration,
209
+ totalDivergence,
210
+ narrative,
211
+ };
212
+ }
213
+ function buildDivergenceNarrative(runs, bestIteration, worstIteration, totalDivergence) {
214
+ if (runs.length === 1) {
215
+ return `Single run — no cross-run divergence. Run additional iterations with different rules to see how governance shapes outcomes.`;
216
+ }
217
+ const parts = [];
218
+ const first = runs[0];
219
+ const last = runs[runs.length - 1];
220
+ const stabilityDelta = last.metrics.stabilityScore - first.metrics.stabilityScore;
221
+ const collapseDelta = first.metrics.collapseProbability - last.metrics.collapseProbability;
222
+ if (stabilityDelta > 0.05) {
223
+ parts.push(`Stability improved ${(stabilityDelta * 100).toFixed(0)}pp from run 1 to run ${runs.length}.`);
224
+ }
225
+ else if (stabilityDelta < -0.05) {
226
+ parts.push(`Stability decreased ${(Math.abs(stabilityDelta) * 100).toFixed(0)}pp — later rules may be too restrictive or too loose.`);
227
+ }
228
+ if (collapseDelta > 0.05) {
229
+ parts.push(`Collapse risk reduced ${(collapseDelta * 100).toFixed(0)}pp through rule iteration.`);
230
+ }
231
+ parts.push(`Best outcome: iteration ${bestIteration} ("${runs[bestIteration - 1].worldName}").`);
232
+ if (bestIteration !== worstIteration) {
233
+ parts.push(`Worst outcome: iteration ${worstIteration} ("${runs[worstIteration - 1].worldName}").`);
234
+ }
235
+ parts.push(`Total measurable divergence across ${runs.length} runs: ${totalDivergence.toFixed(2)}.`);
236
+ // Rule change impact
237
+ for (let i = 1; i < runs.length; i++) {
238
+ const r = runs[i];
239
+ if (r.ruleChanges) {
240
+ const changes = [];
241
+ if (r.ruleChanges.added.length > 0)
242
+ changes.push(`+${r.ruleChanges.added.length} rules`);
243
+ if (r.ruleChanges.removed.length > 0)
244
+ changes.push(`-${r.ruleChanges.removed.length} rules`);
245
+ if (r.ruleChanges.gatesAdded.length > 0)
246
+ changes.push(`+${r.ruleChanges.gatesAdded.length} gates`);
247
+ if (r.ruleChanges.gatesRemoved.length > 0)
248
+ changes.push(`-${r.ruleChanges.gatesRemoved.length} gates`);
249
+ if (r.ruleChanges.thesisChanged)
250
+ changes.push(`thesis changed`);
251
+ if (changes.length > 0) {
252
+ const effDelta = r.comparison.governanceEffectiveness - runs[i - 1].comparison.governanceEffectiveness;
253
+ const dir = effDelta > 0 ? "improved" : effDelta < 0 ? "reduced" : "unchanged";
254
+ parts.push(`Run ${r.iteration} (${changes.join(", ")}): effectiveness ${dir}.`);
255
+ }
256
+ }
257
+ }
258
+ return parts.join(" ");
259
+ }
260
+ function buildRecommendation(runs, divergence) {
261
+ if (runs.length === 1) {
262
+ return `Single run completed. Fork the world, modify rules, and run again to test iterative governance design.`;
263
+ }
264
+ const best = runs[divergence.bestIteration - 1];
265
+ const parts = [];
266
+ parts.push(`Recommended rule set: "${best.worldName}" (iteration ${divergence.bestIteration}).`);
267
+ parts.push(`This configuration achieved ${(best.comparison.governanceEffectiveness * 100).toFixed(0)}% governance effectiveness`);
268
+ parts.push(`with ${(best.metrics.stabilityScore * 100).toFixed(0)}% stability and ${(best.metrics.collapseProbability * 100).toFixed(0)}% collapse probability.`);
269
+ if (best.ruleCount > 0) {
270
+ parts.push(`Active rules: ${best.ruleCount} invariants, ${best.gateCount} gates.`);
271
+ }
272
+ return parts.join(" ");
273
+ }
274
+ // ============================================
275
+ // FORMATTING — CLI + Report Output
276
+ // ============================================
277
+ /**
278
+ * Format an enforcement report for CLI output.
279
+ */
280
+ function formatEnforcementReport(report) {
281
+ const lines = [];
282
+ lines.push("");
283
+ lines.push(" POLICY ENFORCEMENT REPORT");
284
+ lines.push(" " + "=".repeat(70));
285
+ lines.push(` Scenario: ${report.scenario}`);
286
+ lines.push(` Iterations: ${report.runs.length}`);
287
+ lines.push(` Session: ${report.sessionId}`);
288
+ lines.push("");
289
+ // Per-run summary table
290
+ lines.push(" RUN HISTORY");
291
+ lines.push(" " + "-".repeat(70));
292
+ lines.push(` ${"#".padEnd(4)} ${"World".padEnd(25)} ${"Rules".padEnd(8)} ${"Stability".padEnd(12)} ${"Collapse".padEnd(12)} ${"Effectiveness"}`);
293
+ lines.push(" " + "-".repeat(70));
294
+ for (const run of report.runs) {
295
+ const name = run.worldName.length > 23 ? run.worldName.slice(0, 22) + "…" : run.worldName;
296
+ lines.push(` ${String(run.iteration).padEnd(4)} ${name.padEnd(25)} ${String(run.ruleCount + run.gateCount).padEnd(8)} ${(run.metrics.stabilityScore * 100).toFixed(0).padStart(4)}% ${(run.metrics.collapseProbability * 100).toFixed(0).padStart(4)}% ${(run.comparison.governanceEffectiveness * 100).toFixed(0)}%`);
297
+ }
298
+ // Per-run behavioral summary
299
+ lines.push("");
300
+ lines.push(" BEHAVIORAL IMPACT PER RUN");
301
+ lines.push(" " + "-".repeat(70));
302
+ for (const run of report.runs) {
303
+ const bs = run.behavioralSummary;
304
+ lines.push(` Run ${run.iteration}: ${bs.headline}`);
305
+ lines.push(` Adapted: ${bs.agentsAdapted}/${bs.totalAgents} agents (${(bs.adaptationRate * 100).toFixed(0)}%) | Aggressive: ${bs.aggressiveReduction > 0 ? "-" : "+"}${Math.abs(bs.aggressiveReduction).toFixed(0)}pp | Cooperative: ${bs.cooperativeIncrease > 0 ? "+" : ""}${bs.cooperativeIncrease.toFixed(0)}pp`);
306
+ }
307
+ // Rule changes between runs
308
+ const runsWithChanges = report.runs.filter(r => r.ruleChanges);
309
+ if (runsWithChanges.length > 0) {
310
+ lines.push("");
311
+ lines.push(" RULE CHANGES");
312
+ lines.push(" " + "-".repeat(70));
313
+ for (const run of runsWithChanges) {
314
+ const rc = run.ruleChanges;
315
+ lines.push(` Run ${run.iteration}:`);
316
+ for (const inv of rc.added) {
317
+ lines.push(` + ${inv.description}`);
318
+ }
319
+ for (const inv of rc.removed) {
320
+ lines.push(` - ${inv.description}`);
321
+ }
322
+ for (const gate of rc.gatesAdded) {
323
+ lines.push(` + [GATE] ${gate.label}: ${gate.condition}`);
324
+ }
325
+ for (const gate of rc.gatesRemoved) {
326
+ lines.push(` - [GATE] ${gate.label}`);
327
+ }
328
+ if (rc.thesisChanged) {
329
+ lines.push(` ~ Thesis changed`);
330
+ }
331
+ }
332
+ }
333
+ // Divergence analysis
334
+ lines.push("");
335
+ lines.push(" DIVERGENCE ANALYSIS");
336
+ lines.push(" " + "-".repeat(70));
337
+ lines.push(` Stability trend: ${report.divergence.stabilityTrend.map(s => `${(s * 100).toFixed(0)}%`).join(" → ")}`);
338
+ lines.push(` Collapse trend: ${report.divergence.collapseTrend.map(s => `${(s * 100).toFixed(0)}%`).join(" → ")}`);
339
+ lines.push(` Effectiveness trend: ${report.divergence.effectivenessTrend.map(s => `${(s * 100).toFixed(0)}%`).join(" → ")}`);
340
+ lines.push(` Total divergence: ${report.divergence.totalDivergence}`);
341
+ lines.push("");
342
+ lines.push(` ${report.divergence.narrative}`);
343
+ // Cross-run behavioral comparisons — the CEO's view
344
+ if (report.behavioralComparisons.length > 0) {
345
+ lines.push("");
346
+ lines.push(" WHAT AGENTS DID DIFFERENTLY");
347
+ lines.push(" " + "=".repeat(70));
348
+ for (const comp of report.behavioralComparisons) {
349
+ lines.push(` ${comp.runALabel} → ${comp.runBLabel}`);
350
+ lines.push(` Rule change: ${comp.rulesDelta}`);
351
+ lines.push(` ${comp.headline}`);
352
+ lines.push("");
353
+ // Show per-agent behavior changes
354
+ const changed = comp.agentDifferences.filter(a => a.changed);
355
+ if (changed.length > 0) {
356
+ for (const diff of changed) {
357
+ lines.push(` * ${diff.agentId}: ${diff.runABehavior} → ${diff.runBBehavior} (impact: ${diff.runAAvgImpact} → ${diff.runBAvgImpact})`);
358
+ }
359
+ }
360
+ // Key shifts
361
+ for (const shift of comp.keyShifts) {
362
+ lines.push(` [${shift.type.replace(/_/g, " ").toUpperCase()}] ${shift.description}`);
363
+ }
364
+ lines.push(" " + "-".repeat(70));
365
+ }
366
+ }
367
+ // Key Insight (the money moment)
368
+ const insight = extractKeyInsight(report.runs);
369
+ if (insight) {
370
+ lines.push("");
371
+ lines.push(" KEY INSIGHT");
372
+ lines.push(" " + "-".repeat(70));
373
+ lines.push(` ${insight}`);
374
+ }
375
+ // Recommendation
376
+ lines.push("");
377
+ lines.push(" RECOMMENDATION");
378
+ lines.push(" " + "-".repeat(70));
379
+ lines.push(` ${report.recommendation}`);
380
+ // Footer
381
+ lines.push("");
382
+ lines.push(" " + "=".repeat(70));
383
+ lines.push(" NeuroVerse Policy Enforcement System");
384
+ lines.push(" Design rules. Run reality. See what changes.");
385
+ lines.push(" " + "=".repeat(70));
386
+ lines.push("");
387
+ return lines.join("\n");
388
+ }
389
+ /**
390
+ * Generate guided next steps for the user.
391
+ *
392
+ * Design principle: ONE mental model.
393
+ * - Write rules in plain English → run enforce → see what changes
394
+ * - JSON world files are optional (power user path)
395
+ * - Every output ends with an experiment prompt
396
+ * - ALL suggestions come from governed AI — with trace
397
+ */
398
+ async function formatGuidedNextSteps(report, usedCustomInput) {
399
+ const lines = [];
400
+ // Experiment prompts — governed AI generates these
401
+ const { experiments, governance } = await suggestExperiments(report);
402
+ if (experiments.length > 0) {
403
+ for (const exp of experiments) {
404
+ lines.push("");
405
+ lines.push(` ${exp.label}`);
406
+ lines.push(" " + "-".repeat(70));
407
+ lines.push(` ${exp.prompt}`);
408
+ lines.push("");
409
+ lines.push(` ${exp.command}`);
410
+ lines.push("");
411
+ lines.push(` ${exp.expectation}`);
412
+ }
413
+ // Show governance trace — the AI is governed too
414
+ lines.push("");
415
+ lines.push(` ┌─ Governed AI ────────────────────────────────┐`);
416
+ lines.push(` │ Actor: ${governance.actor.padEnd(40)}│`);
417
+ lines.push(` │ Action: ${governance.action.padEnd(39)}│`);
418
+ lines.push(` │ Constraints: ${String(governance.trace.constraints.length).padEnd(34)}│`);
419
+ lines.push(` │ Passed: ${String(governance.trace.passed).padEnd(39)}│`);
420
+ if (governance.trace.violations.length > 0) {
421
+ lines.push(` │ Violations: ${governance.trace.violations.join(", ").slice(0, 35).padEnd(35)}│`);
422
+ }
423
+ lines.push(` └──────────────────────────────────────────────┘`);
424
+ }
425
+ lines.push("");
426
+ lines.push(" WHAT TO DO NEXT");
427
+ lines.push(" " + "-".repeat(70));
428
+ if (!usedCustomInput) {
429
+ // First run — teach them the simplest path
430
+ lines.push("");
431
+ lines.push(" Write your own rules. No JSON needed — just plain English.");
432
+ lines.push("");
433
+ lines.push(" 1. Create a text file (my-rules.txt):");
434
+ lines.push("");
435
+ lines.push(" Block panic selling during high volatility");
436
+ lines.push(" Limit leverage to 5x");
437
+ lines.push(" Maintain minimum liquidity floor");
438
+ lines.push(" Slow down algorithmic trading when contagion spreads");
439
+ lines.push("");
440
+ lines.push(" 2. Run it:");
441
+ lines.push("");
442
+ lines.push(" npx nv-sim enforce trading my-rules.txt");
443
+ lines.push("");
444
+ lines.push(" 3. Change a rule. Run again. See what changes.");
445
+ lines.push("");
446
+ lines.push(" That's it. Rules are plain English. No DSL. No config.");
447
+ }
448
+ else {
449
+ // They already used custom input — guide iteration
450
+ const best = report.runs[report.divergence.bestIteration - 1];
451
+ lines.push("");
452
+ lines.push(` Best result: "${best.worldName}" (iteration ${report.divergence.bestIteration}).`);
453
+ lines.push("");
454
+ lines.push(" To iterate: edit your rules file and run again.");
455
+ lines.push(" To compare two rule sets side by side:");
456
+ lines.push("");
457
+ lines.push(" npx nv-sim enforce trading light-rules.txt strict-rules.txt");
458
+ lines.push("");
459
+ lines.push(" To try a different scenario:");
460
+ lines.push("");
461
+ lines.push(" npx nv-sim enforce strait_of_hormuz my-rules.txt");
462
+ lines.push(" npx nv-sim enforce ai_regulation_crisis my-rules.txt");
463
+ lines.push("");
464
+ lines.push(" To save a report:");
465
+ lines.push("");
466
+ lines.push(" npx nv-sim enforce trading my-rules.txt --output=report.json");
467
+ }
468
+ // Rule patterns — always show, it's the reference card
469
+ lines.push("");
470
+ lines.push(" RULE PATTERNS (what the engine understands)");
471
+ lines.push(" " + "-".repeat(70));
472
+ lines.push(' "Block X" → hard suppression of matching actions');
473
+ lines.push(' "Limit X" → caps extreme positions');
474
+ lines.push(' "Slow X" → dampens large movements');
475
+ lines.push(' "Maintain X" → enforces minimum thresholds');
476
+ lines.push(' "Rebalance X" → pulls extremes toward equilibrium');
477
+ lines.push(' "Require X" → enforceable structural constraint');
478
+ lines.push(' "Monitor X" → generates a gate (circuit breaker)');
479
+ lines.push("");
480
+ lines.push(" Examples: examples/policies/trading-rules.txt");
481
+ lines.push(" examples/policies/strict-rules.txt");
482
+ lines.push("");
483
+ return lines.join("\n");
484
+ }
485
+ /**
486
+ * Suggest experiments via the governed AI pipeline.
487
+ *
488
+ * ALL suggestions flow through evaluateAIAction() — the AI analyst
489
+ * is subject to the same governance constraints as any other actor.
490
+ *
491
+ * Two kinds:
492
+ * TRY REMOVING — which rules are load-bearing?
493
+ * TRY ADDING — what new rule could improve this?
494
+ *
495
+ * Users aren't just pruning. They're designing.
496
+ * And the governed AI is their design partner.
497
+ */
498
+ async function suggestExperiments(report) {
499
+ const analystRole = aiProvider_1.AI_ROLES.find(r => r.id === "ai_analyst");
500
+ const governance = await (0, aiProvider_1.evaluateAIAction)(analystRole, "suggest_experiments", async () => generateExperimentsFromTrace(report));
501
+ return {
502
+ experiments: governance.result,
503
+ governance,
504
+ };
505
+ }
506
+ /**
507
+ * Governed archetype detection.
508
+ * The ai_analyst detects system patterns — this goes through governance.
509
+ */
510
+ async function governedDetectArchetype(report) {
511
+ const analystRole = aiProvider_1.AI_ROLES.find(r => r.id === "ai_analyst");
512
+ return (0, aiProvider_1.evaluateAIAction)(analystRole, "detect_archetype", async () => detectSystemArchetype(report));
513
+ }
514
+ /**
515
+ * Governed strategy synthesis from experiment library.
516
+ * Recommendations are governed — even when derived from past experiments.
517
+ */
518
+ async function governedSynthesizeStrategy(archetype, currentMetrics, library) {
519
+ const analystRole = aiProvider_1.AI_ROLES.find(r => r.id === "ai_analyst");
520
+ return (0, aiProvider_1.evaluateAIAction)(analystRole, "synthesize_strategy", async () => formatBestKnownStrategy(archetype, currentMetrics, library));
521
+ }
522
+ /**
523
+ * Governed experiment comparison and similarity search.
524
+ * Even "find similar" is governed — the analyst is constrained.
525
+ */
526
+ async function governedFindSimilar(archetype, currentMetrics, library) {
527
+ const analystRole = aiProvider_1.AI_ROLES.find(r => r.id === "ai_analyst");
528
+ return (0, aiProvider_1.evaluateAIAction)(analystRole, "compare_experiments", async () => formatSimilarExperiments(archetype, currentMetrics, library));
529
+ }
530
+ /**
531
+ * Deterministic experiment generation from trace data.
532
+ * No LLM needed — the governed system analyzes metrics and suggests.
533
+ *
534
+ * This is the ai_analyst actor generating suggestions from ground truth.
535
+ * It must reference trace data (must_reference_trace constraint),
536
+ * include metrics (must_include_metrics), and make no unverifiable claims.
537
+ */
538
+ function generateExperimentsFromTrace(report) {
539
+ if (report.runs.length < 1)
540
+ return [];
541
+ const experiments = [];
542
+ const runs = report.runs;
543
+ const best = runs[report.divergence.bestIteration - 1];
544
+ const worst = report.runs.length > 1 ? runs[report.divergence.worstIteration - 1] : null;
545
+ // ---- SUBTRACTIVE: Try removing a rule ----
546
+ if (best.ruleChanges && best.ruleChanges.added.length > 0) {
547
+ const keyRule = best.ruleChanges.added[0];
548
+ experiments.push({
549
+ label: "TRY REMOVING",
550
+ prompt: `Remove this rule and see what happens:`,
551
+ command: `Remove "${keyRule.description}" from your rules file, then run again.`,
552
+ expectation: `If stability drops, that rule was load-bearing. If nothing changes, it was noise.`,
553
+ });
554
+ }
555
+ else if (worst && worst.iteration > 1 && worst.ruleChanges && worst.ruleChanges.added.length > 0) {
556
+ experiments.push({
557
+ label: "TRY REMOVING",
558
+ prompt: `Iteration ${worst.iteration} made things worse. Remove the rules that were added:`,
559
+ command: `Remove: "${worst.ruleChanges.added.map(r => r.description).join('", "')}"`,
560
+ expectation: `Fewer rules sometimes outperform more. Find out which ones actually matter.`,
561
+ });
562
+ }
563
+ else if (best.ruleCount > 3) {
564
+ experiments.push({
565
+ label: "TRY REMOVING",
566
+ prompt: `Your best run used ${best.ruleCount} rules. Can you do better with fewer?`,
567
+ command: `Create a file with just 2 rules and run: npx nv-sim enforce trading minimal.txt`,
568
+ expectation: `Sometimes 2 precise rules beat 8 broad ones.`,
569
+ });
570
+ }
571
+ // ---- GENERATIVE: Try adding a rule ----
572
+ // AI analyst reads metrics from trace and suggests based on what it sees
573
+ const bestMetrics = best.metrics;
574
+ const bestComparison = best.comparison;
575
+ if (bestMetrics.collapseProbability > 0.1) {
576
+ experiments.push({
577
+ label: "TRY ADDING",
578
+ prompt: `Collapse risk is still ${(bestMetrics.collapseProbability * 100).toFixed(0)}%. Try a circuit breaker:`,
579
+ command: `Add "Monitor system stress and halt trading during extreme conditions"`,
580
+ expectation: `If collapse risk drops, this rule is acting as a circuit breaker. If velocity collapses, it may be too restrictive.`,
581
+ });
582
+ }
583
+ else if (bestMetrics.maxVolatility > 0.5) {
584
+ experiments.push({
585
+ label: "TRY ADDING",
586
+ prompt: `Volatility is still high (${(bestMetrics.maxVolatility * 100).toFixed(0)}%). Try dampening:`,
587
+ command: `Add "Slow down all large position changes during elevated volatility"`,
588
+ expectation: `If volatility drops without killing effectiveness, this is a precision rule. If both drop, it's too broad.`,
589
+ });
590
+ }
591
+ else if (bestMetrics.polarizationEvents > 0) {
592
+ experiments.push({
593
+ label: "TRY ADDING",
594
+ prompt: `${bestMetrics.polarizationEvents} polarization event(s) detected. Try rebalancing:`,
595
+ command: `Add "Rebalance correlated positions when concentration exceeds threshold"`,
596
+ expectation: `If polarization disappears, agents were clustering. If it persists, the cause is structural.`,
597
+ });
598
+ }
599
+ else if (bestComparison.governanceEffectiveness < 0.3) {
600
+ experiments.push({
601
+ label: "TRY ADDING",
602
+ prompt: `Governance effectiveness is only ${(bestComparison.governanceEffectiveness * 100).toFixed(0)}%. Try a structural constraint:`,
603
+ command: `Add "Gate high-risk trades behind approval"`,
604
+ expectation: `If effectiveness jumps, your current rules are too soft. If nothing changes, the scenario may need different types of rules.`,
605
+ });
606
+ }
607
+ else if (bestMetrics.coalitionRisks > 0) {
608
+ experiments.push({
609
+ label: "TRY ADDING",
610
+ prompt: `${bestMetrics.coalitionRisks} coalition risk(s) detected. Try transparency:`,
611
+ command: `Add "Require all large position changes to be reported"`,
612
+ expectation: `If coalition risks drop, hidden coordination was the problem. Transparency breaks collusion.`,
613
+ });
614
+ }
615
+ else {
616
+ experiments.push({
617
+ label: "TRY ADDING",
618
+ prompt: `System is stable. Now stress-test your rules:`,
619
+ command: `Add "Allow leverage up to 10x" — then run again.`,
620
+ expectation: `If stability holds, your rules are robust. If it breaks, you've found the boundary.`,
621
+ });
622
+ }
623
+ return experiments;
624
+ }
625
+ function extractKeyInsight(runs) {
626
+ if (runs.length < 2)
627
+ return null;
628
+ // Check if adding more rules made things worse
629
+ for (let i = 1; i < runs.length; i++) {
630
+ const prev = runs[i - 1];
631
+ const curr = runs[i];
632
+ const effDelta = curr.comparison.governanceEffectiveness - prev.comparison.governanceEffectiveness;
633
+ const stabDelta = curr.metrics.stabilityScore - prev.metrics.stabilityScore;
634
+ const addedRules = (curr.ruleChanges?.added.length ?? 0) + (curr.ruleChanges?.gatesAdded.length ?? 0);
635
+ if (effDelta < -0.02 && addedRules > 0) {
636
+ return `Adding ${addedRules} rule(s) in iteration ${curr.iteration} reduced effectiveness. More rules don't always mean better governance — precision matters more than volume.`;
637
+ }
638
+ if (stabDelta < -0.05 && addedRules > 0) {
639
+ return `Iteration ${curr.iteration} degraded stability despite adding rules. The new constraints may conflict or over-restrict agent behavior.`;
640
+ }
641
+ }
642
+ // Check if gates made the big difference
643
+ const runsWithGates = runs.filter(r => r.gateCount > 0);
644
+ const runsWithoutGates = runs.filter(r => r.gateCount === 0);
645
+ if (runsWithGates.length > 0 && runsWithoutGates.length > 0) {
646
+ const avgWithGates = runsWithGates.reduce((s, r) => s + r.comparison.governanceEffectiveness, 0) / runsWithGates.length;
647
+ const avgWithout = runsWithoutGates.reduce((s, r) => s + r.comparison.governanceEffectiveness, 0) / runsWithoutGates.length;
648
+ if (avgWithGates > avgWithout + 0.05) {
649
+ return `Enforcement gates (circuit breakers) are the key differentiator. Rules alone improved effectiveness ${(avgWithout * 100).toFixed(0)}% → rules + gates achieved ${(avgWithGates * 100).toFixed(0)}%.`;
650
+ }
651
+ }
652
+ // Check stability improvement
653
+ const first = runs[0];
654
+ const last = runs[runs.length - 1];
655
+ const stabImprovement = last.metrics.stabilityScore - first.metrics.stabilityScore;
656
+ if (stabImprovement > 0.1) {
657
+ return `Governance improved system stability by ${(stabImprovement * 100).toFixed(0)} percentage points across ${runs.length} iterations. Rule iteration works.`;
658
+ }
659
+ return null;
660
+ }
661
+ // ============================================
662
+ // GITHUB-STYLE DIAGNOSTIC DISPLAY
663
+ // ============================================
664
+ /**
665
+ * Format policy diagnostics like GitHub code review.
666
+ *
667
+ * Shows errors, warnings, hints with:
668
+ * - Line references back to the original file
669
+ * - Suggested fixes with exact replacement text
670
+ * - Quick fix commands the user can apply
671
+ * - Health score and status badge
672
+ *
673
+ * "Even the rules get reviewed."
674
+ */
675
+ function formatPolicyDiagnostics(health, parsed, fileName) {
676
+ const lines = [];
677
+ const diags = health.diagnostics;
678
+ if (diags.length === 0 && health.status === "healthy") {
679
+ lines.push("");
680
+ lines.push(` ✓ ${fileName} — No issues found (${health.healthScore}/100)`);
681
+ lines.push(` All rules are structurally sound. Ready to enforce.`);
682
+ lines.push("");
683
+ return lines.join("\n");
684
+ }
685
+ // Header with causal status — tie validation to system behavior
686
+ const statusIcon = health.status === "critical" || health.status === "errors" ? "✗"
687
+ : health.status === "warnings" ? "!"
688
+ : "✓";
689
+ const causalSummary = describeCausalImpact(health, parsed);
690
+ lines.push("");
691
+ lines.push(` ${statusIcon} POLICY REVIEW — ${fileName}`);
692
+ lines.push(" " + "-".repeat(70));
693
+ lines.push(` Health: ${health.healthScore}/100 — ${causalSummary}`);
694
+ lines.push(` Rules: ${parsed.summary.total} (${parsed.summary.enforced} enforced, ${parsed.summary.advisory} advisory)`);
695
+ lines.push("");
696
+ // Sort: errors first, then warnings, info, hints
697
+ const severityOrder = { error: 0, warning: 1, info: 2, hint: 3 };
698
+ const sorted = [...diags].sort((a, b) => (severityOrder[a.severity] ?? 4) - (severityOrder[b.severity] ?? 4));
699
+ for (const diag of sorted) {
700
+ const icon = diag.severity === "error" ? "✗"
701
+ : diag.severity === "warning" ? "!"
702
+ : diag.severity === "info" ? "i"
703
+ : "·";
704
+ const tag = diag.severity.toUpperCase().padEnd(7);
705
+ // Diagnostic header
706
+ lines.push(` ${icon} ${tag} ${diag.id}: ${diag.message}`);
707
+ // Source location
708
+ if (diag.source) {
709
+ const lineRef = diag.source.startLine === diag.source.endLine
710
+ ? `line ${diag.source.startLine}`
711
+ : `lines ${diag.source.startLine}-${diag.source.endLine}`;
712
+ lines.push(` ${fileName}:${lineRef}`);
713
+ }
714
+ // Detail
715
+ lines.push(` ${diag.detail}`);
716
+ // Related rules
717
+ if (diag.relatedRules.length > 0 && diag.relatedRules.length <= 5) {
718
+ for (const ruleId of diag.relatedRules) {
719
+ const rule = parsed.rules.find(r => r.id === ruleId);
720
+ if (rule) {
721
+ lines.push(` │ ${ruleId} (line ${rule.sourceLine}): "${rule.originalText}"`);
722
+ }
723
+ }
724
+ }
725
+ // WHY THIS MATTERS — micro-teaching
726
+ const why = explainWhyItMatters(diag);
727
+ if (why) {
728
+ lines.push("");
729
+ lines.push(` WHY THIS MATTERS`);
730
+ lines.push(` ${why}`);
731
+ }
732
+ // Suggested fix
733
+ if (diag.fix) {
734
+ lines.push("");
735
+ lines.push(` Suggested fix: ${diag.fix}`);
736
+ }
737
+ // Quick fix — show the exact text operation
738
+ if (diag.quickFix) {
739
+ const qf = diag.quickFix;
740
+ lines.push("");
741
+ lines.push(` ┌─ Quick Fix: ${qf.label} ─${"─".repeat(Math.max(0, 50 - qf.label.length))}┐`);
742
+ if (qf.operation === "replace" && qf.targetLines && qf.targetLines.length > 0) {
743
+ for (const ln of qf.targetLines) {
744
+ const rule = parsed.rules.find(r => r.sourceLine === ln);
745
+ if (rule) {
746
+ lines.push(` │ - line ${ln}: "${rule.originalText}"`);
747
+ }
748
+ }
749
+ lines.push(` │ + "${qf.text}"`);
750
+ }
751
+ else if (qf.operation === "insert") {
752
+ for (const insertLine of qf.text.split("\n")) {
753
+ lines.push(` │ + "${insertLine}"`);
754
+ }
755
+ }
756
+ else if (qf.operation === "remove" && qf.targetLines) {
757
+ for (const ln of qf.targetLines) {
758
+ const rule = parsed.rules.find(r => r.sourceLine === ln);
759
+ if (rule) {
760
+ lines.push(` │ - line ${ln}: "${rule.originalText}"`);
761
+ }
762
+ }
763
+ }
764
+ lines.push(` └${"─".repeat(55)}┘`);
765
+ // EXPECTED IMPACT — predict what fixing this would do
766
+ const impact = predictFixImpact(diag, health, parsed);
767
+ if (impact) {
768
+ lines.push("");
769
+ lines.push(` EXPECTED IMPACT`);
770
+ lines.push(` Before: ${impact.before}`);
771
+ lines.push(` Estimated (simulated) After: ${impact.after}`);
772
+ lines.push(` ${impact.explanation}`);
773
+ }
774
+ // PATTERN INSIGHT — teach rule design thinking
775
+ const pattern = describePatternInsight(diag);
776
+ if (pattern) {
777
+ lines.push("");
778
+ lines.push(` PATTERN INSIGHT`);
779
+ lines.push(` ${pattern.insight}`);
780
+ lines.push(` ${pattern.principle}`);
781
+ }
782
+ // TEST THIS FIX — zero friction
783
+ lines.push("");
784
+ lines.push(` TEST THIS FIX`);
785
+ lines.push(` Apply this change to ${fileName}, then:`);
786
+ lines.push(` npx nv-sim enforce trading ${fileName}`);
787
+ lines.push(` Or test the fix without editing:`);
788
+ lines.push(` npx nv-sim enforce trading --patch ${diag.id}`);
789
+ lines.push(` Compare before vs after side by side:`);
790
+ lines.push(` npx nv-sim enforce trading --patch ${diag.id} --compare`);
791
+ }
792
+ lines.push("");
793
+ }
794
+ // Summary footer
795
+ const parts = [];
796
+ if (health.counts.errors > 0)
797
+ parts.push(`${health.counts.errors} error(s)`);
798
+ if (health.counts.warnings > 0)
799
+ parts.push(`${health.counts.warnings} warning(s)`);
800
+ if (health.counts.info > 0)
801
+ parts.push(`${health.counts.info} info`);
802
+ if (health.counts.hints > 0)
803
+ parts.push(`${health.counts.hints} hint(s)`);
804
+ lines.push(` ${parts.join(" · ")}`);
805
+ if (!health.canSimulate) {
806
+ lines.push("");
807
+ lines.push(" ✗ Cannot simulate — fix errors above first.");
808
+ }
809
+ else if (health.counts.errors > 0) {
810
+ lines.push("");
811
+ lines.push(" ! Simulation will proceed, but errors may reduce policy effectiveness.");
812
+ lines.push(" Fix the issues above, then run again to see the difference.");
813
+ }
814
+ lines.push("");
815
+ return lines.join("\n");
816
+ }
817
+ // ============================================
818
+ // CAUSAL HEALTH LANGUAGE
819
+ // ============================================
820
+ /**
821
+ * Convert abstract health status to causal language tied to system behavior.
822
+ * "72/100" is abstract. "72/100 — Conflicts may reduce effectiveness" is actionable.
823
+ */
824
+ function describeCausalImpact(health, parsed) {
825
+ const hasConflicts = health.diagnostics.some(d => d.category === "conflict" && d.severity === "error");
826
+ const hasOverConstrain = health.diagnostics.some(d => d.category === "constraint");
827
+ const hasWeakEnforcement = health.diagnostics.some(d => d.category === "strength");
828
+ const hasCoverageGaps = health.diagnostics.some(d => d.category === "coverage");
829
+ const noRules = parsed.rules.length === 0;
830
+ if (noRules)
831
+ return "No rules detected — system will run ungoverned";
832
+ if (hasConflicts)
833
+ return "Conflicting rules will create agent deadlocks, reducing system throughput";
834
+ if (hasOverConstrain)
835
+ return "Over-restrictive rules may freeze the system during crises";
836
+ if (hasWeakEnforcement && parsed.summary.enforced === 0)
837
+ return "No enforceable rules — policy will have no measurable effect";
838
+ if (hasWeakEnforcement)
839
+ return "Weak enforcement may produce minimal governance effect";
840
+ if (hasCoverageGaps)
841
+ return "Coverage gaps leave critical variables ungoverned";
842
+ if (health.counts.warnings > 0)
843
+ return "Minor issues may reduce governance precision";
844
+ return "Rules are structurally sound";
845
+ }
846
+ // ============================================
847
+ // WHY THIS MATTERS — MICRO-TEACHING
848
+ // ============================================
849
+ /**
850
+ * One-line explanation of system-level impact for each diagnostic.
851
+ * This turns validation into education.
852
+ */
853
+ function explainWhyItMatters(diag) {
854
+ // Match on category + severity for targeted explanations
855
+ if (diag.category === "conflict" && diag.severity === "error") {
856
+ return "Conflicting rules create deadlocks — agents can't comply with both, so neither rule fires effectively.";
857
+ }
858
+ if (diag.category === "conflict" && diag.severity === "warning") {
859
+ return "Overlapping rules compete for the same actions. The engine picks one, which may not be the one you intended.";
860
+ }
861
+ if (diag.category === "strength" && diag.severity === "error") {
862
+ return "Advisory rules are suggestions, not constraints. Without enforcement, agents ignore them under stress.";
863
+ }
864
+ if (diag.category === "strength" && diag.severity === "warning") {
865
+ return "Weak enforcement means most rules are advisory. Under pressure, agents will bypass suggestions.";
866
+ }
867
+ if (diag.category === "constraint") {
868
+ return "Over-constrained systems freeze — agents can't act, so the system fails to respond to crises at all.";
869
+ }
870
+ if (diag.category === "coverage" && diag.message.includes("circuit breaker")) {
871
+ return "Without a circuit breaker, there's no emergency stop. Cascading failures can propagate unchecked.";
872
+ }
873
+ if (diag.category === "coverage") {
874
+ return "Ungoverned variables are blind spots. Agents exploit ungoverned dimensions to route around constraints.";
875
+ }
876
+ if (diag.category === "structure" && diag.severity === "hint") {
877
+ return "Redundant rules add complexity without adding governance. Simpler rule sets are easier to reason about.";
878
+ }
879
+ return null;
880
+ }
881
+ /**
882
+ * Predict the system-level impact of applying a quick fix.
883
+ * Uses the diagnostic category and rule structure to estimate.
884
+ *
885
+ * This is deterministic prediction from policy structure, not simulation.
886
+ * The real numbers come when they run it again — but this sets expectations.
887
+ */
888
+ function predictFixImpact(diag, health, parsed) {
889
+ const currentScore = health.healthScore;
890
+ const ruleCount = parsed.rules.length;
891
+ const enforcedCount = parsed.summary.enforced;
892
+ if (diag.category === "conflict" && diag.severity === "error") {
893
+ // Conflicts cost ~25 health points each
894
+ const fixedScore = Math.min(100, currentScore + 25);
895
+ const effBefore = Math.max(5, Math.round(enforcedCount / Math.max(ruleCount, 1) * 30));
896
+ const effAfter = Math.round(effBefore * 1.8);
897
+ return {
898
+ before: `Health ${currentScore}/100, Effectiveness ~${effBefore}%`,
899
+ after: `Health ~${fixedScore}/100, Effectiveness ~${effAfter}%`,
900
+ explanation: "Resolving the conflict restores agent compliance and unblocks governance execution.",
901
+ };
902
+ }
903
+ if (diag.category === "strength" && (diag.severity === "error" || diag.severity === "warning")) {
904
+ const fixedScore = Math.min(100, currentScore + (diag.severity === "error" ? 25 : 10));
905
+ return {
906
+ before: `Health ${currentScore}/100, ${enforcedCount}/${ruleCount} rules enforced`,
907
+ after: `Health ~${fixedScore}/100, ${Math.min(ruleCount, enforcedCount + 2)}/${ruleCount} rules enforced`,
908
+ explanation: "Strengthening enforcement gives the engine actionable constraints instead of suggestions.",
909
+ };
910
+ }
911
+ if (diag.category === "constraint") {
912
+ const fixedScore = Math.min(100, currentScore + 10);
913
+ return {
914
+ before: `Health ${currentScore}/100, system may freeze under stress`,
915
+ after: `Health ~${fixedScore}/100, agents retain response flexibility`,
916
+ explanation: "Adding flexibility rules lets agents respond to crises within governance bounds.",
917
+ };
918
+ }
919
+ if (diag.category === "coverage" && diag.message.includes("circuit breaker")) {
920
+ const fixedScore = Math.min(100, currentScore + 3);
921
+ return {
922
+ before: `Health ${currentScore}/100, no emergency stop mechanism`,
923
+ after: `Health ~${fixedScore}/100, cascade protection active`,
924
+ explanation: "A circuit breaker is the last line of defense — it catches what individual rules miss.",
925
+ };
926
+ }
927
+ if (diag.category === "coverage") {
928
+ const fixedScore = Math.min(100, currentScore + 3);
929
+ return {
930
+ before: `Health ${currentScore}/100, blind spots in governance`,
931
+ after: `Health ~${fixedScore}/100, critical variables governed`,
932
+ explanation: "Covering all critical variables prevents agents from exploiting ungoverned dimensions.",
933
+ };
934
+ }
935
+ return null;
936
+ }
937
+ /**
938
+ * After showing a quick fix, teach users the underlying rule design pattern.
939
+ * Not just "what to fix" but "how to think about rules."
940
+ */
941
+ function describePatternInsight(diag) {
942
+ if (diag.category === "conflict" && diag.severity === "error") {
943
+ return {
944
+ insight: "This change converts a hard conflict into a conditional constraint.",
945
+ principle: "Hard rules (block/require) often conflict. Conditional rules (limit/gate) preserve flexibility.",
946
+ };
947
+ }
948
+ if (diag.category === "conflict" && diag.severity === "warning") {
949
+ return {
950
+ insight: "Overlapping rules are being merged into a single, precise constraint.",
951
+ principle: "One specific rule outperforms two vague ones. Precision > volume.",
952
+ };
953
+ }
954
+ if (diag.category === "strength" && diag.severity === "error") {
955
+ return {
956
+ insight: "This upgrades an advisory rule to an enforceable constraint.",
957
+ principle: "Advisory rules are suggestions. Under stress, agents ignore suggestions. Enforcement makes it real.",
958
+ };
959
+ }
960
+ if (diag.category === "strength" && diag.severity === "warning") {
961
+ return {
962
+ insight: "This rebalances your rule mix toward enforcement.",
963
+ principle: "The ideal ratio is ~60% enforced, ~40% advisory. Too many suggestions = no governance.",
964
+ };
965
+ }
966
+ if (diag.category === "constraint") {
967
+ return {
968
+ insight: "This relaxes over-restrictive rules into bounded flexibility.",
969
+ principle: "Good governance sets boundaries, not mandates. Agents need room to respond to crises.",
970
+ };
971
+ }
972
+ if (diag.category === "coverage") {
973
+ return {
974
+ insight: "This adds governance to an unmonitored system dimension.",
975
+ principle: "Agents optimize for what's measured. Ungoverned variables become attack surfaces.",
976
+ };
977
+ }
978
+ return null;
979
+ }
980
+ // ============================================
981
+ // RULE IMPACT ATTRIBUTION
982
+ // ============================================
983
+ /**
984
+ * After a simulation run, attribute stability/effectiveness changes
985
+ * to individual rules.
986
+ *
987
+ * "Which rule actually caused the change?"
988
+ *
989
+ * This is the long-term moat: users can see exactly which rules
990
+ * are load-bearing and which are noise.
991
+ */
992
+ function formatRuleImpactAttribution(report) {
993
+ if (report.runs.length < 1)
994
+ return "";
995
+ const lines = [];
996
+ const runs = report.runs;
997
+ // Build attribution from rule changes across iterations
998
+ const attributions = [];
999
+ for (let i = 0; i < runs.length; i++) {
1000
+ const run = runs[i];
1001
+ const prev = i > 0 ? runs[i - 1] : null;
1002
+ if (!run.ruleChanges)
1003
+ continue;
1004
+ const stabDelta = prev
1005
+ ? run.metrics.stabilityScore - prev.metrics.stabilityScore
1006
+ : run.metrics.stabilityScore - 0.5; // baseline is 0.5 without rules
1007
+ const effDelta = prev
1008
+ ? run.comparison.governanceEffectiveness - prev.comparison.governanceEffectiveness
1009
+ : run.comparison.governanceEffectiveness;
1010
+ // Attribute to added rules
1011
+ for (const rule of run.ruleChanges.added) {
1012
+ const perRuleStab = run.ruleChanges.added.length > 0
1013
+ ? stabDelta / run.ruleChanges.added.length
1014
+ : 0;
1015
+ const perRuleEff = run.ruleChanges.added.length > 0
1016
+ ? effDelta / run.ruleChanges.added.length
1017
+ : 0;
1018
+ attributions.push({
1019
+ ruleDescription: rule.description,
1020
+ stabilityDelta: perRuleStab,
1021
+ effectivenessDelta: perRuleEff,
1022
+ iteration: run.iteration,
1023
+ action: "added",
1024
+ });
1025
+ }
1026
+ // Attribute gates
1027
+ for (const gate of run.ruleChanges.gatesAdded) {
1028
+ attributions.push({
1029
+ ruleDescription: `Gate: ${gate.label}: ${gate.condition}`,
1030
+ stabilityDelta: stabDelta * 0.3, // gates typically contribute ~30% of stability gains
1031
+ effectivenessDelta: effDelta * 0.2,
1032
+ iteration: run.iteration,
1033
+ action: "added",
1034
+ });
1035
+ }
1036
+ }
1037
+ if (attributions.length === 0)
1038
+ return "";
1039
+ // Sort by absolute stability impact (most impactful first)
1040
+ attributions.sort((a, b) => Math.abs(b.stabilityDelta) - Math.abs(a.stabilityDelta));
1041
+ lines.push("");
1042
+ lines.push(" RULE IMPACT");
1043
+ lines.push(" " + "-".repeat(70));
1044
+ lines.push(" Which rules shaped the outcome:");
1045
+ lines.push("");
1046
+ for (const attr of attributions.slice(0, 8)) {
1047
+ const stabSign = attr.stabilityDelta >= 0 ? "+" : "";
1048
+ const effSign = attr.effectivenessDelta >= 0 ? "+" : "";
1049
+ const icon = attr.stabilityDelta >= 0 ? "+" : "-";
1050
+ lines.push(` ${icon} "${attr.ruleDescription}"`);
1051
+ lines.push(` ${stabSign}${Math.round(attr.stabilityDelta * 100)} stability, ${effSign}${Math.round(attr.effectivenessDelta * 100)} effectiveness`);
1052
+ // Flag problematic rules
1053
+ if (attr.stabilityDelta < -0.05) {
1054
+ lines.push(` ^ This rule degraded the system. Consider removing or conditioning it.`);
1055
+ }
1056
+ else if (attr.stabilityDelta > 0.15) {
1057
+ lines.push(` ^ Load-bearing rule. Removing this would significantly degrade stability.`);
1058
+ }
1059
+ }
1060
+ if (attributions.length > 8) {
1061
+ lines.push(` ... and ${attributions.length - 8} more rules with minor impact`);
1062
+ }
1063
+ lines.push("");
1064
+ return lines.join("\n");
1065
+ }
1066
+ /**
1067
+ * Detect the system archetype from simulation metrics.
1068
+ *
1069
+ * After runs, identify the structural pattern the system exhibits.
1070
+ * This evolves the tool from "rule engine" to "system intelligence layer."
1071
+ *
1072
+ * Archetypes are derived from metrics, not guessed:
1073
+ * - High volatility + feedback = amplification loop
1074
+ * - Coalition risks + polarization = coordination failure
1075
+ * - High collapse probability = fragile equilibrium
1076
+ * - Stable + low effectiveness = governance disconnect
1077
+ */
1078
+ function detectSystemArchetype(report) {
1079
+ if (report.runs.length < 1)
1080
+ return "";
1081
+ const best = report.runs[report.divergence.bestIteration - 1];
1082
+ const metrics = best.metrics;
1083
+ const comparison = best.comparison;
1084
+ const archetype = classifyArchetype(metrics, comparison);
1085
+ if (!archetype)
1086
+ return "";
1087
+ const lines = [];
1088
+ lines.push("");
1089
+ lines.push(" SYSTEM PATTERN");
1090
+ lines.push(" " + "-".repeat(70));
1091
+ lines.push(` This system behaves like: ${archetype.name}`);
1092
+ lines.push(` ${archetype.description}`);
1093
+ lines.push("");
1094
+ lines.push(" Common successful strategies:");
1095
+ for (const strategy of archetype.successfulStrategies) {
1096
+ lines.push(` - ${strategy}`);
1097
+ }
1098
+ lines.push("");
1099
+ lines.push(` Risk pattern: ${archetype.riskPattern}`);
1100
+ lines.push("");
1101
+ return lines.join("\n");
1102
+ }
1103
+ function classifyArchetype(metrics, comparison) {
1104
+ // High volatility + feedback amplification
1105
+ if (metrics.maxVolatility > 0.5 && metrics.collapseProbability > 0.1) {
1106
+ return {
1107
+ name: "High-volatility system with feedback amplification",
1108
+ description: "Agent actions amplify each other, creating cascading effects that compound rapidly.",
1109
+ successfulStrategies: [
1110
+ "Conditional gating (halt actions above threshold)",
1111
+ "Volatility dampening (slow large movements)",
1112
+ "Circuit breakers (emergency halt mechanisms)",
1113
+ ],
1114
+ riskPattern: "Hard constraints create deadlocks under stress — use conditional rules instead.",
1115
+ };
1116
+ }
1117
+ // Coalition risks + polarization = coordination failure
1118
+ if (metrics.coalitionRisks > 0 && metrics.polarizationEvents > 0) {
1119
+ return {
1120
+ name: "Coordination failure with polarization dynamics",
1121
+ description: "Agents cluster into opposing groups. Hidden coordination and faction behavior dominate.",
1122
+ successfulStrategies: [
1123
+ "Transparency requirements (report large actions)",
1124
+ "Rebalancing rules (pull extremes toward center)",
1125
+ "Position limits (cap concentration)",
1126
+ ],
1127
+ riskPattern: "Surveillance rules without rebalancing increase polarization — combine both.",
1128
+ };
1129
+ }
1130
+ // High collapse probability = fragile equilibrium
1131
+ if (metrics.collapseProbability > 0.15) {
1132
+ return {
1133
+ name: "Fragile equilibrium under stress",
1134
+ description: "The system appears stable but has hidden brittleness. Small shocks can trigger collapse.",
1135
+ successfulStrategies: [
1136
+ "Circuit breakers at multiple thresholds",
1137
+ "Liquidity floors (maintain minimums)",
1138
+ "Graduated response (escalating restrictions)",
1139
+ ],
1140
+ riskPattern: "Single-threshold rules fail — the system needs layered defense.",
1141
+ };
1142
+ }
1143
+ // Stable but governance doesn't matter
1144
+ if (metrics.stabilityScore > 0.8 && comparison.governanceEffectiveness < 0.15) {
1145
+ return {
1146
+ name: "Governance disconnect",
1147
+ description: "The system is stable, but your rules aren't the reason. Governance has minimal measurable effect.",
1148
+ successfulStrategies: [
1149
+ "Stress-test with more extreme scenarios",
1150
+ "Add rules targeting ungoverned variables",
1151
+ "Increase scenario severity to find the rules' breaking point",
1152
+ ],
1153
+ riskPattern: "Rules that never activate provide false confidence — test under stress.",
1154
+ };
1155
+ }
1156
+ // Coalition risks without polarization = hidden coordination
1157
+ if (metrics.coalitionRisks > 0) {
1158
+ return {
1159
+ name: "Hidden coordination risk",
1160
+ description: "Agents are forming coalitions that may circumvent governance. Actions appear independent but correlate.",
1161
+ successfulStrategies: [
1162
+ "Transparency requirements (require reporting)",
1163
+ "Correlation monitoring (detect coordinated movements)",
1164
+ "Position diversity requirements",
1165
+ ],
1166
+ riskPattern: "Punitive rules drive coordination underground — use transparency instead.",
1167
+ };
1168
+ }
1169
+ // Effective governance + good stability = well-governed system
1170
+ if (metrics.stabilityScore > 0.7 && comparison.governanceEffectiveness > 0.3) {
1171
+ return {
1172
+ name: "Well-governed adaptive system",
1173
+ description: "Rules are effective and the system responds well to governance. Strong foundation for iteration.",
1174
+ successfulStrategies: [
1175
+ "Fine-tune existing rules (small parameter changes)",
1176
+ "Add monitoring rules to detect drift",
1177
+ "Stress-test with extreme scenarios to find limits",
1178
+ ],
1179
+ riskPattern: "Over-optimization at current conditions — rules may not generalize to new scenarios.",
1180
+ };
1181
+ }
1182
+ return null;
1183
+ }
1184
+ /**
1185
+ * Load all saved experiments from the experiments/ directory.
1186
+ */
1187
+ function loadExperimentLibrary(experimentsDir = "experiments") {
1188
+ if (!fs.existsSync(experimentsDir))
1189
+ return [];
1190
+ const files = fs.readdirSync(experimentsDir)
1191
+ .filter((f) => f.endsWith(".json"));
1192
+ const experiments = [];
1193
+ for (const file of files) {
1194
+ try {
1195
+ const data = JSON.parse(fs.readFileSync(path.join(experimentsDir, file), "utf-8"));
1196
+ if (data.id && data.metrics && data.report) {
1197
+ experiments.push(data);
1198
+ }
1199
+ }
1200
+ catch {
1201
+ // Skip corrupted files
1202
+ }
1203
+ }
1204
+ return experiments.sort((a, b) => new Date(a.savedAt).getTime() - new Date(b.savedAt).getTime());
1205
+ }
1206
+ /**
1207
+ * Format a saved experiment for disk storage.
1208
+ *
1209
+ * Now includes lineage: tracks which experiment this was derived from,
1210
+ * what patches were applied, and the improvement delta.
1211
+ *
1212
+ * Users build a decision tree of governance evolution:
1213
+ * v0 → v1 (+16 stability) → v2 (+4 stability, +12 effectiveness)
1214
+ */
1215
+ function createSavedExperiment(report, rulesFile, patchesApplied, parentExperiment) {
1216
+ const best = report.runs[report.divergence.bestIteration - 1];
1217
+ const archetype = classifyArchetype(best.metrics, best.comparison);
1218
+ const lineage = parentExperiment ? {
1219
+ parentId: parentExperiment.id,
1220
+ patchesFromParent: patchesApplied,
1221
+ improvementFromParent: {
1222
+ stability: best.metrics.stabilityScore - parentExperiment.metrics.stability,
1223
+ effectiveness: best.comparison.governanceEffectiveness - parentExperiment.metrics.effectiveness,
1224
+ collapseProbability: best.metrics.collapseProbability - parentExperiment.metrics.collapseProbability,
1225
+ },
1226
+ ancestors: [...parentExperiment.lineage.ancestors, parentExperiment.id],
1227
+ generation: parentExperiment.lineage.generation + 1,
1228
+ } : {
1229
+ parentId: null,
1230
+ patchesFromParent: [],
1231
+ improvementFromParent: null,
1232
+ ancestors: [],
1233
+ generation: 0,
1234
+ };
1235
+ return {
1236
+ id: `exp-${Date.now().toString(36)}`,
1237
+ savedAt: new Date().toISOString(),
1238
+ scenario: report.scenario,
1239
+ rulesFile,
1240
+ patchesApplied,
1241
+ metrics: {
1242
+ stability: best.metrics.stabilityScore,
1243
+ effectiveness: best.comparison.governanceEffectiveness,
1244
+ collapseProbability: best.metrics.collapseProbability,
1245
+ },
1246
+ ruleImpact: [],
1247
+ archetype: archetype?.name ?? null,
1248
+ lineage,
1249
+ report,
1250
+ };
1251
+ }
1252
+ /**
1253
+ * Format experiment lineage as a visual chain.
1254
+ *
1255
+ * Shows the decision tree:
1256
+ * v0-baseline → v1-gating (+16 stab) → v2-dampening (+4 stab, +12 eff)
1257
+ */
1258
+ function formatExperimentLineage(experiment, library) {
1259
+ const lines = [];
1260
+ if (experiment.lineage.generation === 0 && !experiment.lineage.parentId) {
1261
+ lines.push("");
1262
+ lines.push(" LINEAGE");
1263
+ lines.push(" " + "-".repeat(70));
1264
+ lines.push(" This is a root experiment (generation 0). No parent.");
1265
+ lines.push(" Future experiments derived from this will show the evolution chain.");
1266
+ lines.push("");
1267
+ return lines.join("\n");
1268
+ }
1269
+ lines.push("");
1270
+ lines.push(" LINEAGE");
1271
+ lines.push(" " + "-".repeat(70));
1272
+ lines.push(` Generation: ${experiment.lineage.generation}`);
1273
+ lines.push("");
1274
+ // Build the chain
1275
+ const chain = [];
1276
+ for (const ancestorId of experiment.lineage.ancestors) {
1277
+ const ancestor = library.find(e => e.id === ancestorId);
1278
+ if (ancestor) {
1279
+ chain.push({
1280
+ id: ancestor.id,
1281
+ stability: ancestor.metrics.stability,
1282
+ effectiveness: ancestor.metrics.effectiveness,
1283
+ });
1284
+ }
1285
+ }
1286
+ chain.push({
1287
+ id: experiment.id,
1288
+ stability: experiment.metrics.stability,
1289
+ effectiveness: experiment.metrics.effectiveness,
1290
+ });
1291
+ // Format as arrow chain
1292
+ for (let i = 0; i < chain.length; i++) {
1293
+ const node = chain[i];
1294
+ const isCurrent = i === chain.length - 1;
1295
+ const prefix = isCurrent ? " → " : " ";
1296
+ const marker = isCurrent ? " (current)" : "";
1297
+ if (i > 0) {
1298
+ const prev = chain[i - 1];
1299
+ const stabDelta = node.stability - prev.stability;
1300
+ const effDelta = node.effectiveness - prev.effectiveness;
1301
+ const deltaStr = `${stabDelta >= 0 ? "+" : ""}${(stabDelta * 100).toFixed(0)} stab, ${effDelta >= 0 ? "+" : ""}${(effDelta * 100).toFixed(0)} eff`;
1302
+ lines.push(`${prefix}${node.id}${marker} (${deltaStr})`);
1303
+ }
1304
+ else {
1305
+ lines.push(`${prefix}${node.id}${marker} (root)`);
1306
+ }
1307
+ if (i < chain.length - 1) {
1308
+ lines.push(" │");
1309
+ }
1310
+ }
1311
+ if (experiment.lineage.improvementFromParent) {
1312
+ const imp = experiment.lineage.improvementFromParent;
1313
+ lines.push("");
1314
+ lines.push(` Net from parent: ${imp.stability >= 0 ? "+" : ""}${(imp.stability * 100).toFixed(0)} stability, ${imp.effectiveness >= 0 ? "+" : ""}${(imp.effectiveness * 100).toFixed(0)} effectiveness`);
1315
+ if (experiment.lineage.patchesFromParent.length > 0) {
1316
+ lines.push(` Patches applied: ${experiment.lineage.patchesFromParent.join(", ")}`);
1317
+ }
1318
+ }
1319
+ lines.push("");
1320
+ return lines.join("\n");
1321
+ }
1322
+ /**
1323
+ * Assess confidence in insights based on sample size and outcome variance.
1324
+ *
1325
+ * This is critical for trust: the system must never overclaim.
1326
+ * High = 7+ experiments, low variance — "consistent, reliable"
1327
+ * Medium = 3-6 experiments, moderate variance — "emerging pattern"
1328
+ * Low = 1-2 experiments, or high variance — "early signal"
1329
+ */
1330
+ function assessConfidence(experiments) {
1331
+ const n = experiments.length;
1332
+ if (n < 2) {
1333
+ return { level: "Low", reason: `${n} experiment — early signal, more runs needed`, score: 0.2 };
1334
+ }
1335
+ // Compute stability variance
1336
+ const stabilities = experiments.map(e => e.metrics.stability);
1337
+ const mean = stabilities.reduce((s, v) => s + v, 0) / n;
1338
+ const variance = stabilities.reduce((s, v) => s + (v - mean) ** 2, 0) / n;
1339
+ const stdDev = Math.sqrt(variance);
1340
+ const cv = mean > 0 ? stdDev / mean : 1; // coefficient of variation
1341
+ if (n >= 7 && cv < 0.1) {
1342
+ return { level: "High", reason: `${n} experiments, consistent outcomes`, score: 0.9 };
1343
+ }
1344
+ if (n >= 7 && cv < 0.25) {
1345
+ return { level: "High", reason: `${n} experiments, low variance`, score: 0.8 };
1346
+ }
1347
+ if (n >= 5 && cv < 0.15) {
1348
+ return { level: "High", reason: `${n} experiments, consistent outcomes`, score: 0.85 };
1349
+ }
1350
+ if (n >= 3 && cv < 0.2) {
1351
+ return { level: "Medium", reason: `${n} experiments, moderate consistency`, score: 0.6 };
1352
+ }
1353
+ if (n >= 3) {
1354
+ return { level: "Medium", reason: `${n} experiments, moderate variance`, score: 0.5 };
1355
+ }
1356
+ if (cv > 0.3) {
1357
+ return { level: "Low", reason: `${n} experiments, high variance — outcomes diverge significantly`, score: 0.25 };
1358
+ }
1359
+ return { level: "Low", reason: `${n} experiments — emerging pattern, more runs will increase confidence`, score: 0.3 };
1360
+ }
1361
+ /**
1362
+ * Find experiments with the same system archetype.
1363
+ * Users benefit from past knowledge instantly — "what worked before?"
1364
+ */
1365
+ function formatSimilarExperiments(currentArchetype, currentMetrics, library) {
1366
+ if (!currentArchetype || library.length === 0)
1367
+ return "";
1368
+ const similar = library
1369
+ .filter(e => e.archetype === currentArchetype)
1370
+ .sort((a, b) => b.metrics.stability - a.metrics.stability)
1371
+ .slice(0, 5);
1372
+ if (similar.length === 0)
1373
+ return "";
1374
+ const confidence = assessConfidence(similar);
1375
+ const lines = [];
1376
+ lines.push("");
1377
+ lines.push(" SIMILAR EXPERIMENTS");
1378
+ lines.push(" " + "-".repeat(70));
1379
+ lines.push(` Other runs with archetype: "${currentArchetype}"`);
1380
+ lines.push(` Confidence: ${confidence.level} (${confidence.reason})`);
1381
+ lines.push("");
1382
+ for (const exp of similar) {
1383
+ const stabDelta = exp.metrics.stability - currentMetrics.stability;
1384
+ const effDelta = exp.metrics.effectiveness - currentMetrics.effectiveness;
1385
+ const deltaStr = `${stabDelta >= 0 ? "+" : ""}${(stabDelta * 100).toFixed(0)} stab, ${effDelta >= 0 ? "+" : ""}${(effDelta * 100).toFixed(0)} eff vs current`;
1386
+ const label = exp.rulesFile ? exp.rulesFile : exp.id;
1387
+ lines.push(` → ${label} (${deltaStr})`);
1388
+ if (exp.patchesApplied.length > 0) {
1389
+ lines.push(` Patches: ${exp.patchesApplied.join(", ")}`);
1390
+ }
1391
+ }
1392
+ if (similar.length > 0) {
1393
+ const best = similar[0];
1394
+ if (best.metrics.stability > currentMetrics.stability) {
1395
+ lines.push("");
1396
+ lines.push(` Best known result: ${(best.metrics.stability * 100).toFixed(0)}% stability, ${(best.metrics.effectiveness * 100).toFixed(0)}% effectiveness`);
1397
+ lines.push(` Your current: ${(currentMetrics.stability * 100).toFixed(0)}% stability, ${(currentMetrics.effectiveness * 100).toFixed(0)}% effectiveness`);
1398
+ const gap = best.metrics.stability - currentMetrics.stability;
1399
+ if (gap > 0.02) {
1400
+ lines.push(` Gap: ${(gap * 100).toFixed(0)} points. Review ${best.rulesFile ?? best.id} for strategies that worked.`);
1401
+ }
1402
+ else {
1403
+ lines.push(" You're at or near the best known result for this archetype.");
1404
+ }
1405
+ }
1406
+ }
1407
+ lines.push("");
1408
+ return lines.join("\n");
1409
+ }
1410
+ // ============================================
1411
+ // BEST KNOWN STRATEGY — AUTO-SYNTHESIS
1412
+ // ============================================
1413
+ /**
1414
+ * Synthesize the best known configuration for a system archetype
1415
+ * from the experiment library.
1416
+ *
1417
+ * This is the recommendation engine grounded in evidence:
1418
+ * "For high-volatility systems, the best strategies are X, Y, Z
1419
+ * with average outcomes of A% stability, B% effectiveness."
1420
+ */
1421
+ function formatBestKnownStrategy(currentArchetype, currentMetrics, library) {
1422
+ if (!currentArchetype || library.length < 2)
1423
+ return "";
1424
+ const matching = library.filter(e => e.archetype === currentArchetype);
1425
+ if (matching.length < 2)
1426
+ return "";
1427
+ // Compute averages from top performers (top 50%)
1428
+ const sorted = [...matching].sort((a, b) => b.metrics.stability - a.metrics.stability);
1429
+ const topHalf = sorted.slice(0, Math.max(1, Math.ceil(sorted.length / 2)));
1430
+ const avgStab = topHalf.reduce((s, e) => s + e.metrics.stability, 0) / topHalf.length;
1431
+ const avgEff = topHalf.reduce((s, e) => s + e.metrics.effectiveness, 0) / topHalf.length;
1432
+ const minStab = Math.min(...topHalf.map(e => e.metrics.stability));
1433
+ const maxStab = Math.max(...topHalf.map(e => e.metrics.stability));
1434
+ const minEff = Math.min(...topHalf.map(e => e.metrics.effectiveness));
1435
+ const maxEff = Math.max(...topHalf.map(e => e.metrics.effectiveness));
1436
+ // Extract common patches across top performers
1437
+ const patchCounts = new Map();
1438
+ for (const exp of topHalf) {
1439
+ for (const patch of exp.patchesApplied) {
1440
+ patchCounts.set(patch, (patchCounts.get(patch) ?? 0) + 1);
1441
+ }
1442
+ }
1443
+ const commonPatches = [...patchCounts.entries()]
1444
+ .filter(([, count]) => count >= Math.ceil(topHalf.length * 0.5))
1445
+ .sort((a, b) => b[1] - a[1])
1446
+ .map(([patch]) => patch);
1447
+ const lines = [];
1448
+ lines.push("");
1449
+ // Compute confidence from sample size and outcome variance
1450
+ const confidence = assessConfidence(matching);
1451
+ lines.push(" BEST KNOWN CONFIGURATION");
1452
+ lines.push(" " + "-".repeat(70));
1453
+ lines.push(` For "${currentArchetype}" systems (${matching.length} experiments)`);
1454
+ lines.push(` Confidence: ${confidence.level} (${confidence.reason})`);
1455
+ lines.push("");
1456
+ // Show the archetype's successful strategies
1457
+ const archetype = classifyArchetypeByName(currentArchetype);
1458
+ if (archetype) {
1459
+ for (const strategy of archetype.successfulStrategies) {
1460
+ lines.push(` + ${strategy}`);
1461
+ }
1462
+ lines.push("");
1463
+ }
1464
+ if (commonPatches.length > 0) {
1465
+ lines.push(" Common winning patches:");
1466
+ for (const patch of commonPatches.slice(0, 5)) {
1467
+ const count = patchCounts.get(patch);
1468
+ lines.push(` + ${patch} (used in ${count}/${topHalf.length} top runs)`);
1469
+ }
1470
+ lines.push("");
1471
+ }
1472
+ lines.push(" Average outcomes (top performers):");
1473
+ lines.push(` Stability: ${(minStab * 100).toFixed(0)}–${(maxStab * 100).toFixed(0)}%`);
1474
+ lines.push(` Effectiveness: ${(minEff * 100).toFixed(0)}–${(maxEff * 100).toFixed(0)}%`);
1475
+ lines.push("");
1476
+ lines.push(" Your current config:");
1477
+ lines.push(` Stability: ${(currentMetrics.stability * 100).toFixed(0)}%`);
1478
+ lines.push(` Effectiveness: ${(currentMetrics.effectiveness * 100).toFixed(0)}%`);
1479
+ // Gap analysis
1480
+ const stabGap = avgStab - currentMetrics.stability;
1481
+ const effGap = avgEff - currentMetrics.effectiveness;
1482
+ if (stabGap > 0.02 && effGap > 0.02) {
1483
+ lines.push(` → Room to improve: ~+${(stabGap * 100).toFixed(0)} stability, ~+${(effGap * 100).toFixed(0)} effectiveness`);
1484
+ }
1485
+ else if (stabGap > 0.02) {
1486
+ lines.push(` → Near optimal effectiveness. Stability can improve ~+${(stabGap * 100).toFixed(0)}.`);
1487
+ }
1488
+ else if (effGap > 0.02) {
1489
+ lines.push(` → Near optimal stability. Effectiveness can improve ~+${(effGap * 100).toFixed(0)}.`);
1490
+ }
1491
+ else {
1492
+ lines.push(" → At or near best known results. Consider stress-testing with harder scenarios.");
1493
+ }
1494
+ lines.push("");
1495
+ return lines.join("\n");
1496
+ }
1497
+ /**
1498
+ * Look up archetype by name (reverse lookup from classifyArchetype).
1499
+ */
1500
+ function classifyArchetypeByName(name) {
1501
+ const archetypes = [
1502
+ {
1503
+ name: "High-volatility system with feedback amplification",
1504
+ description: "",
1505
+ successfulStrategies: [
1506
+ "Conditional gating (halt actions above threshold)",
1507
+ "Volatility dampening (slow large movements)",
1508
+ "Circuit breakers (emergency halt mechanisms)",
1509
+ ],
1510
+ riskPattern: "",
1511
+ },
1512
+ {
1513
+ name: "Coordination failure with polarization dynamics",
1514
+ description: "",
1515
+ successfulStrategies: [
1516
+ "Transparency requirements (report large actions)",
1517
+ "Rebalancing rules (pull extremes toward center)",
1518
+ "Position limits (cap concentration)",
1519
+ ],
1520
+ riskPattern: "",
1521
+ },
1522
+ {
1523
+ name: "Fragile equilibrium under stress",
1524
+ description: "",
1525
+ successfulStrategies: [
1526
+ "Circuit breakers at multiple thresholds",
1527
+ "Liquidity floors (maintain minimums)",
1528
+ "Graduated response (escalating restrictions)",
1529
+ ],
1530
+ riskPattern: "",
1531
+ },
1532
+ {
1533
+ name: "Governance disconnect",
1534
+ description: "",
1535
+ successfulStrategies: [
1536
+ "Stress-test with more extreme scenarios",
1537
+ "Add rules targeting ungoverned variables",
1538
+ "Increase scenario severity to find the rules' breaking point",
1539
+ ],
1540
+ riskPattern: "",
1541
+ },
1542
+ {
1543
+ name: "Hidden coordination risk",
1544
+ description: "",
1545
+ successfulStrategies: [
1546
+ "Transparency requirements (require reporting)",
1547
+ "Correlation monitoring (detect coordinated movements)",
1548
+ "Position diversity requirements",
1549
+ ],
1550
+ riskPattern: "",
1551
+ },
1552
+ {
1553
+ name: "Well-governed adaptive system",
1554
+ description: "",
1555
+ successfulStrategies: [
1556
+ "Fine-tune existing rules (small parameter changes)",
1557
+ "Add monitoring rules to detect drift",
1558
+ "Stress-test with extreme scenarios to find limits",
1559
+ ],
1560
+ riskPattern: "",
1561
+ },
1562
+ ];
1563
+ return archetypes.find(a => a.name === name) ?? null;
1564
+ }
1565
+ /**
1566
+ * Format multi-patch comparison output.
1567
+ * When users apply multiple patches, show which fix wins on each dimension.
1568
+ */
1569
+ function formatMultiPatchComparison(patchResults, baseline) {
1570
+ if (patchResults.length < 2)
1571
+ return "";
1572
+ const lines = [];
1573
+ lines.push("");
1574
+ lines.push(" FIX COMPARISON");
1575
+ lines.push(" " + "-".repeat(70));
1576
+ lines.push("");
1577
+ lines.push(" Fix Stability Effectiveness Collapse Risk");
1578
+ lines.push(" " + "─".repeat(65));
1579
+ lines.push(` Baseline (no fix) ${(baseline.stability * 100).toFixed(0).padStart(4)}% ${(baseline.effectiveness * 100).toFixed(0).padStart(4)}% ${(baseline.collapse * 100).toFixed(0).padStart(4)}%`);
1580
+ for (const patch of patchResults) {
1581
+ const stabDelta = patch.stability - baseline.stability;
1582
+ const effDelta = patch.effectiveness - baseline.effectiveness;
1583
+ const label = patch.patchId.padEnd(22);
1584
+ lines.push(` ${label} ${(patch.stability * 100).toFixed(0).padStart(4)}% ${(patch.effectiveness * 100).toFixed(0).padStart(4)}% ${(patch.collapse * 100).toFixed(0).padStart(4)}%`);
1585
+ }
1586
+ lines.push("");
1587
+ // Find best for each dimension
1588
+ const bestStab = [...patchResults].sort((a, b) => b.stability - a.stability)[0];
1589
+ const bestEff = [...patchResults].sort((a, b) => b.effectiveness - a.effectiveness)[0];
1590
+ const bestCollapse = [...patchResults].sort((a, b) => a.collapse - b.collapse)[0];
1591
+ lines.push(" Recommendation:");
1592
+ if (bestStab.patchId === bestEff.patchId) {
1593
+ lines.push(` ${bestStab.patchId} wins on both stability and effectiveness. Apply it.`);
1594
+ }
1595
+ else {
1596
+ lines.push(` ${bestStab.patchId} is better for stability (+${((bestStab.stability - baseline.stability) * 100).toFixed(0)})`);
1597
+ lines.push(` ${bestEff.patchId} is better for effectiveness (+${((bestEff.effectiveness - baseline.effectiveness) * 100).toFixed(0)})`);
1598
+ if (bestCollapse.patchId !== bestStab.patchId && bestCollapse.patchId !== bestEff.patchId) {
1599
+ lines.push(` ${bestCollapse.patchId} is best for risk reduction`);
1600
+ }
1601
+ lines.push(" Choose based on your priority: resilience or performance.");
1602
+ }
1603
+ lines.push("");
1604
+ return lines.join("\n");
1605
+ }
1606
+ /**
1607
+ * Export enforcement report as JSON (for integration / downstream tooling).
1608
+ */
1609
+ function exportEnforcementReportJSON(report) {
1610
+ return JSON.stringify(report, null, 2);
1611
+ }