@neuroverseos/governance 0.1.6 → 0.2.2

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 (82) hide show
  1. package/README.md +279 -423
  2. package/dist/adapters/express.cjs +242 -2
  3. package/dist/adapters/express.d.cts +1 -1
  4. package/dist/adapters/express.d.ts +1 -1
  5. package/dist/adapters/express.js +5 -3
  6. package/dist/adapters/index.cjs +337 -5
  7. package/dist/adapters/index.d.cts +1 -1
  8. package/dist/adapters/index.d.ts +1 -1
  9. package/dist/adapters/index.js +8 -6
  10. package/dist/adapters/langchain.cjs +297 -3
  11. package/dist/adapters/langchain.d.cts +8 -1
  12. package/dist/adapters/langchain.d.ts +8 -1
  13. package/dist/adapters/langchain.js +5 -3
  14. package/dist/adapters/openai.cjs +297 -3
  15. package/dist/adapters/openai.d.cts +8 -1
  16. package/dist/adapters/openai.d.ts +8 -1
  17. package/dist/adapters/openai.js +5 -3
  18. package/dist/adapters/openclaw.cjs +297 -3
  19. package/dist/adapters/openclaw.d.cts +8 -1
  20. package/dist/adapters/openclaw.d.ts +8 -1
  21. package/dist/adapters/openclaw.js +5 -3
  22. package/dist/{bootstrap-H4HHKQ5G.js → bootstrap-GXVDZNF7.js} +2 -1
  23. package/dist/{build-73KAVHEY.js → build-P42YFKQV.js} +34 -3
  24. package/dist/{chunk-Z2S2HIV5.js → chunk-2NICNKOM.js} +2 -2
  25. package/dist/{chunk-B4NF3OLW.js → chunk-4JRYGIO7.js} +56 -2
  26. package/dist/chunk-4QXB6PEO.js +232 -0
  27. package/dist/chunk-6CZSKEY5.js +164 -0
  28. package/dist/{chunk-O5OMJMIE.js → chunk-7P3S7MAY.js} +502 -2
  29. package/dist/chunk-A5W4GNQO.js +130 -0
  30. package/dist/chunk-AKW5YVCE.js +96 -0
  31. package/dist/chunk-DPVS43ZT.js +608 -0
  32. package/dist/{chunk-EIUHJXBB.js → chunk-GR6DGCZ2.js} +1 -1
  33. package/dist/chunk-KEST3MWO.js +324 -0
  34. package/dist/{chunk-D7BGWV2J.js → chunk-NF5POFCI.js} +5 -3
  35. package/dist/{chunk-FZQCRGUU.js → chunk-OHAC6HJE.js} +27 -3
  36. package/dist/chunk-OT6PXH54.js +61 -0
  37. package/dist/{chunk-ITJ3LCPG.js → chunk-PDOZHZWL.js} +1 -1
  38. package/dist/{chunk-T4X42QXC.js → chunk-Q6O7ZLO2.js} +0 -59
  39. package/dist/{chunk-FYPYZFV5.js → chunk-QPASI2BR.js} +1 -1
  40. package/dist/{chunk-EQXFOKH2.js → chunk-RWXVAH6P.js} +27 -3
  41. package/dist/{chunk-CROPZ75A.js → chunk-SKU3GAPD.js} +27 -3
  42. package/dist/chunk-YZFATT7X.js +9 -0
  43. package/dist/cli/neuroverse.cjs +5343 -732
  44. package/dist/cli/neuroverse.js +69 -13
  45. package/dist/cli/plan.cjs +1599 -0
  46. package/dist/cli/plan.d.cts +20 -0
  47. package/dist/cli/plan.d.ts +20 -0
  48. package/dist/cli/plan.js +361 -0
  49. package/dist/cli/run.cjs +1746 -0
  50. package/dist/cli/run.d.cts +20 -0
  51. package/dist/cli/run.d.ts +20 -0
  52. package/dist/cli/run.js +143 -0
  53. package/dist/{configure-ai-46JVG56I.js → configure-ai-TK67ZWZL.js} +5 -2
  54. package/dist/{derive-6NAEWLM5.js → derive-TLIV4OOU.js} +6 -4
  55. package/dist/doctor-QV6HELS5.js +170 -0
  56. package/dist/{explain-3B3VB6TL.js → explain-IDCRWMPX.js} +2 -1
  57. package/dist/{guard-67Y66P3I.js → guard-GFLQZY6U.js} +20 -6
  58. package/dist/{guard-contract-D_RQz9kt.d.ts → guard-contract-Cm91Kp4j.d.cts} +182 -2
  59. package/dist/{guard-contract-D_RQz9kt.d.cts → guard-contract-Cm91Kp4j.d.ts} +182 -2
  60. package/dist/guard-engine-JLTUARGU.js +10 -0
  61. package/dist/{impact-CHERK3O6.js → impact-XPECYRLH.js} +5 -3
  62. package/dist/{improve-YG6I6ERG.js → improve-GPUBKTEA.js} +4 -3
  63. package/dist/index.cjs +2135 -89
  64. package/dist/index.d.cts +481 -12
  65. package/dist/index.d.ts +481 -12
  66. package/dist/index.js +70 -20
  67. package/dist/{init-Z66T6TDI.js → init-PKPIYHYE.js} +2 -0
  68. package/dist/mcp-server-LZVJHBT5.js +13 -0
  69. package/dist/model-adapter-BB7G4MFI.js +11 -0
  70. package/dist/playground-FGOMASHN.js +550 -0
  71. package/dist/redteam-SK7AMIG3.js +357 -0
  72. package/dist/session-VISISNWJ.js +14 -0
  73. package/dist/{simulate-ETHHINZ4.js → simulate-VDOYQFRO.js} +2 -1
  74. package/dist/test-75AVHC3R.js +217 -0
  75. package/dist/{trace-3YODSSIP.js → trace-JVF67VR3.js} +4 -2
  76. package/dist/{validate-UVE6GKQU.js → validate-LLBWVPGV.js} +15 -6
  77. package/dist/validate-engine-UIABSIHD.js +7 -0
  78. package/dist/{world-WLNHL5XC.js → world-LAXO6DOX.js} +87 -7
  79. package/dist/world-loader-HMPTOEA2.js +9 -0
  80. package/package.json +19 -5
  81. package/dist/validate-engine-657D75OG.js +0 -6
  82. /package/dist/{chunk-M3TZFGHO.js → chunk-JZPQGIKR.js} +0 -0
package/dist/index.cjs CHANGED
@@ -37,12 +37,21 @@ __export(index_exports, {
37
37
  DERIVE_EXIT_CODES: () => DERIVE_EXIT_CODES,
38
38
  FileAuditLogger: () => FileAuditLogger,
39
39
  GUARD_EXIT_CODES: () => GUARD_EXIT_CODES,
40
+ McpGovernanceServer: () => McpGovernanceServer,
41
+ ModelAdapter: () => ModelAdapter,
42
+ PLAN_EXIT_CODES: () => PLAN_EXIT_CODES,
43
+ PROVIDERS: () => PROVIDERS,
44
+ SessionManager: () => SessionManager,
40
45
  VALIDATE_EXIT_CODES: () => VALIDATE_EXIT_CODES,
46
+ advancePlan: () => advancePlan,
47
+ buildPlanCheck: () => buildPlanCheck,
41
48
  createGovernanceEngine: () => createGovernanceEngine,
42
49
  deriveWorld: () => deriveWorld,
50
+ describeActiveWorld: () => describeActiveWorld,
43
51
  emitWorldDefinition: () => emitWorldDefinition,
44
52
  evaluateCondition: () => evaluateCondition,
45
53
  evaluateGuard: () => evaluateGuard,
54
+ evaluatePlan: () => evaluatePlan,
46
55
  eventToAllowlistKey: () => eventToAllowlistKey,
47
56
  explainWorld: () => explainWorld,
48
57
  extractWorldMarkdown: () => extractWorldMarkdown,
@@ -50,16 +59,25 @@ __export(index_exports, {
50
59
  formatVerdictOneLine: () => formatVerdictOneLine,
51
60
  generateImpactReport: () => generateImpactReport,
52
61
  generateImpactReportFromFile: () => generateImpactReportFromFile,
62
+ getActiveWorldName: () => getActiveWorldName,
63
+ getPlanProgress: () => getPlanProgress,
53
64
  improveWorld: () => improveWorld,
65
+ listWorlds: () => listWorlds,
54
66
  loadWorld: () => loadWorld,
55
67
  loadWorldFromDirectory: () => loadWorldFromDirectory,
56
68
  normalizeWorldMarkdown: () => normalizeWorldMarkdown,
69
+ parsePlanMarkdown: () => parsePlanMarkdown,
57
70
  parseWorldMarkdown: () => parseWorldMarkdown,
58
71
  readAuditLog: () => readAuditLog,
59
72
  renderExplainText: () => renderExplainText,
60
73
  renderImpactReport: () => renderImpactReport,
61
74
  renderImproveText: () => renderImproveText,
62
75
  renderSimulateText: () => renderSimulateText,
76
+ resolveProvider: () => resolveProvider,
77
+ resolveWorldPath: () => resolveWorldPath,
78
+ runInteractiveMode: () => runInteractiveMode,
79
+ runPipeMode: () => runPipeMode,
80
+ setActiveWorld: () => setActiveWorld,
63
81
  simulateWorld: () => simulateWorld,
64
82
  summarizeAuditEvents: () => summarizeAuditEvents,
65
83
  validateWorld: () => validateWorld,
@@ -67,6 +85,232 @@ __export(index_exports, {
67
85
  });
68
86
  module.exports = __toCommonJS(index_exports);
69
87
 
88
+ // src/engine/plan-engine.ts
89
+ function keywordMatch(eventText, step) {
90
+ const stepText = [
91
+ step.label,
92
+ step.description ?? "",
93
+ ...step.tags ?? []
94
+ ].join(" ").toLowerCase();
95
+ const keywords = stepText.split(/\s+/).filter((w) => w.length > 3);
96
+ if (keywords.length === 0) return false;
97
+ const matched = keywords.filter((kw) => eventText.includes(kw));
98
+ return matched.length >= Math.ceil(keywords.length * 0.5);
99
+ }
100
+ function tokenSimilarity(a, b) {
101
+ const tokensA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
102
+ const tokensB = new Set(b.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
103
+ if (tokensA.size === 0 || tokensB.size === 0) return 0;
104
+ let intersection = 0;
105
+ for (const t of tokensA) {
106
+ if (tokensB.has(t)) intersection++;
107
+ }
108
+ const union = (/* @__PURE__ */ new Set([...tokensA, ...tokensB])).size;
109
+ return union > 0 ? intersection / union : 0;
110
+ }
111
+ function findMatchingStep(eventText, event, steps) {
112
+ const pendingOrActive = steps.filter((s) => s.status === "pending" || s.status === "active");
113
+ if (pendingOrActive.length === 0) {
114
+ return { matched: null, closest: null, closestScore: 0 };
115
+ }
116
+ for (const step of pendingOrActive) {
117
+ if (keywordMatch(eventText, step)) {
118
+ if (step.tools && event.tool && !step.tools.includes(event.tool)) {
119
+ continue;
120
+ }
121
+ return { matched: step, closest: step, closestScore: 1 };
122
+ }
123
+ }
124
+ const intentText = [event.intent, event.tool ?? "", event.scope ?? ""].join(" ");
125
+ let bestStep = null;
126
+ let bestScore = 0;
127
+ for (const step of pendingOrActive) {
128
+ const stepText = [step.label, step.description ?? "", ...step.tags ?? []].join(" ");
129
+ const score = tokenSimilarity(intentText, stepText);
130
+ if (score > bestScore) {
131
+ bestScore = score;
132
+ bestStep = step;
133
+ }
134
+ }
135
+ const SIMILARITY_THRESHOLD = 0.35;
136
+ if (bestScore >= SIMILARITY_THRESHOLD && bestStep) {
137
+ if (bestStep.tools && event.tool && !bestStep.tools.includes(event.tool)) {
138
+ return { matched: null, closest: bestStep, closestScore: bestScore };
139
+ }
140
+ return { matched: bestStep, closest: bestStep, closestScore: bestScore };
141
+ }
142
+ return { matched: null, closest: bestStep, closestScore: bestScore };
143
+ }
144
+ function isSequenceValid(step, plan) {
145
+ if (!plan.sequential) return true;
146
+ if (!step.requires || step.requires.length === 0) return true;
147
+ return step.requires.every((reqId) => {
148
+ const reqStep = plan.steps.find((s) => s.id === reqId);
149
+ return reqStep?.status === "completed";
150
+ });
151
+ }
152
+ function checkConstraints(event, eventText, constraints) {
153
+ const checks = [];
154
+ for (const constraint of constraints) {
155
+ if (constraint.type === "approval") {
156
+ if (constraint.trigger && eventText.includes(constraint.trigger.substring(0, 10).toLowerCase())) {
157
+ checks.push({ constraintId: constraint.id, passed: false, reason: constraint.description });
158
+ return { violated: constraint, checks };
159
+ }
160
+ const keywords = constraint.description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
161
+ const relevant = keywords.some((kw) => eventText.includes(kw));
162
+ if (relevant) {
163
+ checks.push({ constraintId: constraint.id, passed: false, reason: constraint.description });
164
+ return { violated: constraint, checks };
165
+ }
166
+ checks.push({ constraintId: constraint.id, passed: true });
167
+ continue;
168
+ }
169
+ if (constraint.type === "scope" && constraint.trigger) {
170
+ const keywords = constraint.trigger.split(/\s+/).filter((w) => w.length > 3);
171
+ const violated = keywords.length > 0 && keywords.every((kw) => eventText.includes(kw));
172
+ checks.push({
173
+ constraintId: constraint.id,
174
+ passed: !violated,
175
+ reason: violated ? constraint.description : void 0
176
+ });
177
+ if (violated) {
178
+ return { violated: constraint, checks };
179
+ }
180
+ continue;
181
+ }
182
+ checks.push({ constraintId: constraint.id, passed: true });
183
+ }
184
+ return { violated: null, checks };
185
+ }
186
+ function getPlanProgress(plan) {
187
+ const completed = plan.steps.filter((s) => s.status === "completed").length;
188
+ const total = plan.steps.length;
189
+ return {
190
+ completed,
191
+ total,
192
+ percentage: total > 0 ? Math.round(completed / total * 100) : 0
193
+ };
194
+ }
195
+ function advancePlan(plan, stepId, evidence) {
196
+ const step = plan.steps.find((s) => s.id === stepId);
197
+ if (!step) {
198
+ return { success: false, reason: `Step "${stepId}" not found in plan.` };
199
+ }
200
+ if (step.status === "completed") {
201
+ return { success: false, reason: `Step "${stepId}" is already completed.` };
202
+ }
203
+ const mode = plan.completion ?? "trust";
204
+ if (mode === "verified" && step.verify) {
205
+ if (!evidence) {
206
+ return {
207
+ success: false,
208
+ reason: `Step "${step.label}" requires evidence (verify: ${step.verify}). Provide evidence to advance.`
209
+ };
210
+ }
211
+ if (evidence.type !== step.verify) {
212
+ return {
213
+ success: false,
214
+ reason: `Evidence type "${evidence.type}" does not match required verification "${step.verify}".`
215
+ };
216
+ }
217
+ }
218
+ const updatedPlan = {
219
+ ...plan,
220
+ steps: plan.steps.map(
221
+ (s) => s.id === stepId ? { ...s, status: "completed" } : s
222
+ )
223
+ };
224
+ return {
225
+ success: true,
226
+ plan: updatedPlan,
227
+ evidence: evidence ?? void 0
228
+ };
229
+ }
230
+ function evaluatePlan(event, plan) {
231
+ const progress = getPlanProgress(plan);
232
+ if (plan.expires_at) {
233
+ const expiresAt = new Date(plan.expires_at).getTime();
234
+ if (Date.now() > expiresAt) {
235
+ return {
236
+ allowed: true,
237
+ status: "PLAN_COMPLETE",
238
+ reason: "Plan has expired.",
239
+ progress
240
+ };
241
+ }
242
+ }
243
+ if (progress.completed === progress.total) {
244
+ return {
245
+ allowed: true,
246
+ status: "PLAN_COMPLETE",
247
+ reason: "All plan steps are completed.",
248
+ progress
249
+ };
250
+ }
251
+ const eventText = [
252
+ event.intent,
253
+ event.tool ?? "",
254
+ event.scope ?? ""
255
+ ].join(" ").toLowerCase();
256
+ const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
257
+ if (!matched) {
258
+ return {
259
+ allowed: false,
260
+ status: "OFF_PLAN",
261
+ reason: "Action does not match any plan step.",
262
+ closestStep: closest?.label,
263
+ similarityScore: closestScore,
264
+ progress
265
+ };
266
+ }
267
+ if (!isSequenceValid(matched, plan)) {
268
+ const pendingDeps = (matched.requires ?? []).filter((reqId) => plan.steps.find((s) => s.id === reqId)?.status !== "completed").join(", ");
269
+ return {
270
+ allowed: false,
271
+ status: "OFF_PLAN",
272
+ reason: `Step "${matched.label}" requires completion of: ${pendingDeps}`,
273
+ matchedStep: matched.id,
274
+ progress
275
+ };
276
+ }
277
+ const { violated } = checkConstraints(event, eventText, plan.constraints);
278
+ if (violated) {
279
+ return {
280
+ allowed: false,
281
+ status: "CONSTRAINT_VIOLATED",
282
+ reason: violated.description,
283
+ matchedStep: matched.id,
284
+ progress
285
+ };
286
+ }
287
+ return {
288
+ allowed: true,
289
+ status: "ON_PLAN",
290
+ reason: `Matches step: ${matched.label}`,
291
+ matchedStep: matched.id,
292
+ progress
293
+ };
294
+ }
295
+ function buildPlanCheck(event, plan, verdict) {
296
+ const eventText = [event.intent, event.tool ?? "", event.scope ?? ""].join(" ").toLowerCase();
297
+ const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
298
+ const { checks: constraintChecks } = checkConstraints(event, eventText, plan.constraints);
299
+ const progress = getPlanProgress(plan);
300
+ return {
301
+ planId: plan.plan_id,
302
+ matched: !!matched,
303
+ matchedStepId: matched?.id,
304
+ matchedStepLabel: matched?.label,
305
+ closestStepId: !matched ? closest?.id : void 0,
306
+ closestStepLabel: !matched ? closest?.label : void 0,
307
+ similarityScore: !matched ? closestScore : void 0,
308
+ sequenceValid: matched ? isSequenceValid(matched, plan) : void 0,
309
+ constraintsChecked: constraintChecks,
310
+ progress: { completed: progress.completed, total: progress.total }
311
+ };
312
+ }
313
+
70
314
  // src/engine/guard-engine.ts
71
315
  var PROMPT_INJECTION_PATTERNS = [
72
316
  // Instruction override
@@ -158,6 +402,7 @@ function evaluateGuard(event, world, options = {}) {
158
402
  const eventText = (event.intent + " " + (event.tool ?? "") + " " + (event.scope ?? "")).toLowerCase();
159
403
  const invariantChecks = [];
160
404
  const safetyChecks = [];
405
+ let planCheckResult;
161
406
  const roleChecks = [];
162
407
  const guardChecks = [];
163
408
  const kernelRuleChecks = [];
@@ -185,6 +430,7 @@ function evaluateGuard(event, world, options = {}) {
185
430
  includeTrace ? buildTrace(
186
431
  invariantChecks,
187
432
  safetyChecks,
433
+ planCheckResult,
188
434
  roleChecks,
189
435
  guardChecks,
190
436
  kernelRuleChecks,
@@ -213,6 +459,7 @@ function evaluateGuard(event, world, options = {}) {
213
459
  includeTrace ? buildTrace(
214
460
  invariantChecks,
215
461
  safetyChecks,
462
+ planCheckResult,
216
463
  roleChecks,
217
464
  guardChecks,
218
465
  kernelRuleChecks,
@@ -223,6 +470,42 @@ function evaluateGuard(event, world, options = {}) {
223
470
  ) : void 0
224
471
  );
225
472
  }
473
+ if (options.plan) {
474
+ const planVerdict = evaluatePlan(event, options.plan);
475
+ planCheckResult = buildPlanCheck(event, options.plan, planVerdict);
476
+ if (!planVerdict.allowed && planVerdict.status !== "PLAN_COMPLETE") {
477
+ decidingLayer = "plan-enforcement";
478
+ decidingId = `plan-${options.plan.plan_id}`;
479
+ const planStatus = planVerdict.status === "CONSTRAINT_VIOLATED" ? "PAUSE" : "BLOCK";
480
+ let reason = planVerdict.reason ?? "Action blocked by plan.";
481
+ if (planVerdict.status === "OFF_PLAN" && planVerdict.closestStep) {
482
+ reason += ` Closest step: "${planVerdict.closestStep}" (similarity: ${(planVerdict.similarityScore ?? 0).toFixed(2)})`;
483
+ }
484
+ return buildVerdict(
485
+ planStatus,
486
+ reason,
487
+ `plan-${options.plan.plan_id}`,
488
+ void 0,
489
+ world,
490
+ level,
491
+ invariantChecks,
492
+ guardsMatched,
493
+ rulesMatched,
494
+ includeTrace ? buildTrace(
495
+ invariantChecks,
496
+ safetyChecks,
497
+ planCheckResult,
498
+ roleChecks,
499
+ guardChecks,
500
+ kernelRuleChecks,
501
+ levelChecks,
502
+ decidingLayer,
503
+ decidingId,
504
+ startTime
505
+ ) : void 0
506
+ );
507
+ }
508
+ }
226
509
  const roleVerdict = checkRoleRules(event, eventText, world, roleChecks);
227
510
  if (roleVerdict) {
228
511
  decidingLayer = "role";
@@ -240,6 +523,7 @@ function evaluateGuard(event, world, options = {}) {
240
523
  includeTrace ? buildTrace(
241
524
  invariantChecks,
242
525
  safetyChecks,
526
+ planCheckResult,
243
527
  roleChecks,
244
528
  guardChecks,
245
529
  kernelRuleChecks,
@@ -268,6 +552,7 @@ function evaluateGuard(event, world, options = {}) {
268
552
  includeTrace ? buildTrace(
269
553
  invariantChecks,
270
554
  safetyChecks,
555
+ planCheckResult,
271
556
  roleChecks,
272
557
  guardChecks,
273
558
  kernelRuleChecks,
@@ -296,6 +581,7 @@ function evaluateGuard(event, world, options = {}) {
296
581
  includeTrace ? buildTrace(
297
582
  invariantChecks,
298
583
  safetyChecks,
584
+ planCheckResult,
299
585
  roleChecks,
300
586
  guardChecks,
301
587
  kernelRuleChecks,
@@ -323,6 +609,7 @@ function evaluateGuard(event, world, options = {}) {
323
609
  includeTrace ? buildTrace(
324
610
  invariantChecks,
325
611
  safetyChecks,
612
+ planCheckResult,
326
613
  roleChecks,
327
614
  guardChecks,
328
615
  kernelRuleChecks,
@@ -347,6 +634,7 @@ function evaluateGuard(event, world, options = {}) {
347
634
  includeTrace ? buildTrace(
348
635
  invariantChecks,
349
636
  safetyChecks,
637
+ planCheckResult,
350
638
  roleChecks,
351
639
  guardChecks,
352
640
  kernelRuleChecks,
@@ -684,8 +972,8 @@ function matchesKeywords(eventText, ruleText) {
684
972
  function eventToAllowlistKey(event) {
685
973
  return `${(event.tool ?? "*").toLowerCase()}::${event.intent.toLowerCase().trim()}`;
686
974
  }
687
- function buildTrace(invariantChecks, safetyChecks, roleChecks, guardChecks, kernelRuleChecks, levelChecks, decidingLayer, decidingId, startTime) {
688
- return {
975
+ function buildTrace(invariantChecks, safetyChecks, planCheck, roleChecks, guardChecks, kernelRuleChecks, levelChecks, decidingLayer, decidingId, startTime) {
976
+ const trace = {
689
977
  invariantChecks,
690
978
  safetyChecks,
691
979
  roleChecks,
@@ -703,6 +991,7 @@ function buildTrace(invariantChecks, safetyChecks, roleChecks, guardChecks, kern
703
991
  "safety-scope-escape",
704
992
  "safety-execution-claim",
705
993
  "safety-execution-intent",
994
+ "plan-enforcement",
706
995
  "role-rules",
707
996
  "declarative-guards",
708
997
  "kernel-rules",
@@ -712,6 +1001,10 @@ function buildTrace(invariantChecks, safetyChecks, roleChecks, guardChecks, kern
712
1001
  },
713
1002
  durationMs: performance.now() - startTime
714
1003
  };
1004
+ if (planCheck) {
1005
+ trace.planCheck = planCheck;
1006
+ }
1007
+ return trace;
715
1008
  }
716
1009
  function buildVerdict(status, reason, ruleId, warning, world, level, invariantChecks, guardsMatched, rulesMatched, trace) {
717
1010
  const evidence = {
@@ -744,6 +1037,1316 @@ var GUARD_EXIT_CODES = {
744
1037
  ERROR: 3
745
1038
  };
746
1039
 
1040
+ // src/engine/plan-parser.ts
1041
+ function slugify(text) {
1042
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+/g, "_");
1043
+ }
1044
+ function extractBracketAnnotation(line, key) {
1045
+ const regex = new RegExp(`\\[${key}:\\s*([^\\]]+)\\]`, "i");
1046
+ const match = line.match(regex);
1047
+ if (!match) return null;
1048
+ return match[1].split(",").map((s) => s.trim()).filter(Boolean);
1049
+ }
1050
+ function extractParenAnnotation(line, key) {
1051
+ const regex = new RegExp(`\\(${key}:\\s*([^)]+)\\)`, "i");
1052
+ const match = line.match(regex);
1053
+ if (!match) return null;
1054
+ return match[1].split(",").map((s) => s.trim()).filter(Boolean);
1055
+ }
1056
+ function stripAnnotations(line) {
1057
+ return line.replace(/\[(?:tools|tag|verify|type):\s*[^\]]+\]/gi, "").replace(/\((?:after):\s*[^)]+\)/gi, "").trim();
1058
+ }
1059
+ function parseFrontmatter(content) {
1060
+ const frontmatter = {};
1061
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
1062
+ if (!fmMatch) {
1063
+ return { frontmatter, body: content };
1064
+ }
1065
+ const fmBody = fmMatch[1];
1066
+ for (const line of fmBody.split("\n")) {
1067
+ const colonIndex = line.indexOf(":");
1068
+ if (colonIndex > 0) {
1069
+ const key = line.slice(0, colonIndex).trim();
1070
+ const value = line.slice(colonIndex + 1).trim();
1071
+ frontmatter[key] = value;
1072
+ }
1073
+ }
1074
+ return { frontmatter, body: content.slice(fmMatch[0].length) };
1075
+ }
1076
+ function parseSections(body) {
1077
+ const steps = [];
1078
+ const constraints = [];
1079
+ let currentSection = "none";
1080
+ for (const line of body.split("\n")) {
1081
+ const trimmed = line.trim();
1082
+ if (/^#+\s*Steps/i.test(trimmed)) {
1083
+ currentSection = "steps";
1084
+ continue;
1085
+ }
1086
+ if (/^#+\s*Constraints/i.test(trimmed)) {
1087
+ currentSection = "constraints";
1088
+ continue;
1089
+ }
1090
+ if (/^#+\s/.test(trimmed) && currentSection !== "none") {
1091
+ currentSection = "none";
1092
+ continue;
1093
+ }
1094
+ if (trimmed.startsWith("- ")) {
1095
+ const item = trimmed.slice(2).trim();
1096
+ if (currentSection === "steps") {
1097
+ steps.push(item);
1098
+ } else if (currentSection === "constraints") {
1099
+ constraints.push(item);
1100
+ }
1101
+ }
1102
+ }
1103
+ return { steps, constraints };
1104
+ }
1105
+ function parseStep(raw) {
1106
+ const label = stripAnnotations(raw);
1107
+ const id = slugify(label);
1108
+ const tools = extractBracketAnnotation(raw, "tools");
1109
+ const tags = extractBracketAnnotation(raw, "tag");
1110
+ const verifyArr = extractBracketAnnotation(raw, "verify");
1111
+ const requires = extractParenAnnotation(raw, "after");
1112
+ return {
1113
+ id,
1114
+ label,
1115
+ tools: tools ?? void 0,
1116
+ tags: tags ?? void 0,
1117
+ verify: verifyArr?.[0] ?? void 0,
1118
+ requires: requires ?? void 0,
1119
+ status: "pending"
1120
+ };
1121
+ }
1122
+ function parseConstraint(raw, index) {
1123
+ const typeAnnotation = extractBracketAnnotation(raw, "type");
1124
+ const description = stripAnnotations(raw);
1125
+ const id = `constraint_${index}`;
1126
+ let type = "custom";
1127
+ let enforcement = "block";
1128
+ let limit;
1129
+ let unit;
1130
+ if (typeAnnotation?.[0] === "approval") {
1131
+ type = "approval";
1132
+ enforcement = "pause";
1133
+ } else if (/budget|\$|spending|cost/i.test(description)) {
1134
+ type = "budget";
1135
+ const amountMatch = description.match(/\$?([\d,]+)/);
1136
+ if (amountMatch) {
1137
+ limit = parseInt(amountMatch[1].replace(/,/g, ""), 10);
1138
+ unit = "USD";
1139
+ }
1140
+ } else if (/time|hour|minute|day|deadline/i.test(description)) {
1141
+ type = "time";
1142
+ } else if (/scope|access|database|production/i.test(description)) {
1143
+ type = "scope";
1144
+ }
1145
+ const trigger = description.toLowerCase();
1146
+ return {
1147
+ id,
1148
+ type,
1149
+ description,
1150
+ enforcement,
1151
+ limit,
1152
+ unit,
1153
+ trigger
1154
+ };
1155
+ }
1156
+ function parsePlanMarkdown(markdown) {
1157
+ const errors = [];
1158
+ const { frontmatter, body } = parseFrontmatter(markdown.trim());
1159
+ const { steps: stepLines, constraints: constraintLines } = parseSections(body);
1160
+ if (!frontmatter.plan_id) {
1161
+ errors.push("Missing required field: plan_id");
1162
+ }
1163
+ if (stepLines.length === 0) {
1164
+ errors.push("Plan must have at least one step");
1165
+ }
1166
+ if (errors.length > 0) {
1167
+ return { success: false, errors };
1168
+ }
1169
+ const steps = stepLines.map((line) => parseStep(line));
1170
+ const constraints = constraintLines.map((line, i) => parseConstraint(line, i));
1171
+ let expires_at;
1172
+ if (frontmatter.expires) {
1173
+ expires_at = new Date(frontmatter.expires).toISOString();
1174
+ }
1175
+ const completionRaw = frontmatter.completion?.toLowerCase();
1176
+ const completion = completionRaw === "verified" ? "verified" : "trust";
1177
+ const plan = {
1178
+ plan_id: frontmatter.plan_id,
1179
+ objective: frontmatter.objective ?? "",
1180
+ sequential: frontmatter.sequential === "true",
1181
+ completion,
1182
+ steps,
1183
+ constraints,
1184
+ world_id: frontmatter.world ?? void 0,
1185
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1186
+ expires_at
1187
+ };
1188
+ return { success: true, plan, errors: [] };
1189
+ }
1190
+
1191
+ // src/contracts/plan-contract.ts
1192
+ var PLAN_EXIT_CODES = {
1193
+ ON_PLAN: 0,
1194
+ OFF_PLAN: 1,
1195
+ CONSTRAINT_VIOLATED: 2,
1196
+ ERROR: 3,
1197
+ PLAN_COMPLETE: 4
1198
+ };
1199
+
1200
+ // src/loader/world-loader.ts
1201
+ async function loadWorldFromDirectory(dirPath) {
1202
+ const { readFile: readFile3 } = await import("fs/promises");
1203
+ const { join: join5 } = await import("path");
1204
+ const { readdirSync: readdirSync3 } = await import("fs");
1205
+ async function readJson(filename) {
1206
+ try {
1207
+ const content = await readFile3(join5(dirPath, filename), "utf-8");
1208
+ return JSON.parse(content);
1209
+ } catch {
1210
+ return void 0;
1211
+ }
1212
+ }
1213
+ const worldJson = await readJson("world.json");
1214
+ if (!worldJson) {
1215
+ throw new Error(`Cannot read world.json in ${dirPath}`);
1216
+ }
1217
+ const invariantsJson = await readJson("invariants.json");
1218
+ const assumptionsJson = await readJson("assumptions.json");
1219
+ const stateSchemaJson = await readJson("state-schema.json");
1220
+ const gatesJson = await readJson("gates.json");
1221
+ const outcomesJson = await readJson("outcomes.json");
1222
+ const guardsJson = await readJson("guards.json");
1223
+ const rolesJson = await readJson("roles.json");
1224
+ const kernelJson = await readJson("kernel.json");
1225
+ const metadataJson = await readJson("metadata.json");
1226
+ const rules = [];
1227
+ try {
1228
+ const rulesDir = join5(dirPath, "rules");
1229
+ const ruleFiles = readdirSync3(rulesDir).filter((f) => f.endsWith(".json")).sort();
1230
+ for (const file of ruleFiles) {
1231
+ const content = await readFile3(join5(rulesDir, file), "utf-8");
1232
+ rules.push(JSON.parse(content));
1233
+ }
1234
+ } catch {
1235
+ }
1236
+ return {
1237
+ world: worldJson,
1238
+ invariants: invariantsJson?.invariants ?? [],
1239
+ assumptions: assumptionsJson ?? { profiles: {}, parameter_definitions: {} },
1240
+ stateSchema: stateSchemaJson ?? { variables: {}, presets: {} },
1241
+ rules,
1242
+ gates: gatesJson ?? {
1243
+ viability_classification: [],
1244
+ structural_override: { description: "", enforcement: "mandatory" },
1245
+ sustainability_threshold: 0,
1246
+ collapse_visual: { background: "", text: "", border: "", label: "" }
1247
+ },
1248
+ outcomes: outcomesJson ?? {
1249
+ computed_outcomes: [],
1250
+ comparison_layout: { primary_card: "", status_badge: "", structural_indicators: [] }
1251
+ },
1252
+ guards: guardsJson,
1253
+ roles: rolesJson,
1254
+ kernel: kernelJson,
1255
+ metadata: metadataJson ?? {
1256
+ format_version: "1.0.0",
1257
+ created_at: "",
1258
+ last_modified: "",
1259
+ authoring_method: "manual-authoring"
1260
+ }
1261
+ };
1262
+ }
1263
+ async function loadWorld(worldPath) {
1264
+ const { stat } = await import("fs/promises");
1265
+ const info = await stat(worldPath);
1266
+ if (info.isDirectory()) {
1267
+ return loadWorldFromDirectory(worldPath);
1268
+ }
1269
+ if (worldPath.endsWith(".nv-world.zip")) {
1270
+ throw new Error(".nv-world.zip loading not yet implemented \u2014 use a world directory");
1271
+ }
1272
+ throw new Error(`Cannot load world from: ${worldPath} \u2014 expected a directory or .nv-world.zip`);
1273
+ }
1274
+
1275
+ // src/runtime/session.ts
1276
+ async function defaultToolExecutor(name, args) {
1277
+ return `Tool "${name}" executed successfully with args: ${JSON.stringify(args)}`;
1278
+ }
1279
+ var SessionManager = class {
1280
+ config;
1281
+ state;
1282
+ engineOptions;
1283
+ executor;
1284
+ constructor(config) {
1285
+ this.config = config;
1286
+ this.executor = config.toolExecutor ?? defaultToolExecutor;
1287
+ this.engineOptions = {
1288
+ trace: config.trace ?? false,
1289
+ level: config.level,
1290
+ plan: config.plan
1291
+ };
1292
+ this.state = {
1293
+ active: false,
1294
+ world: config.world,
1295
+ plan: config.plan,
1296
+ progress: config.plan ? getPlanProgress(config.plan) : void 0,
1297
+ actionsEvaluated: 0,
1298
+ actionsAllowed: 0,
1299
+ actionsBlocked: 0,
1300
+ actionsPaused: 0
1301
+ };
1302
+ }
1303
+ /**
1304
+ * Initialize the session — load world from disk if needed.
1305
+ */
1306
+ async start() {
1307
+ if (this.config.worldPath && !this.config.world) {
1308
+ this.state.world = await loadWorld(this.config.worldPath);
1309
+ }
1310
+ if (!this.state.world) {
1311
+ throw new Error("No world provided. Use --world or pass a world definition.");
1312
+ }
1313
+ this.state.active = true;
1314
+ return this.getState();
1315
+ }
1316
+ /**
1317
+ * Evaluate a single event against governance.
1318
+ * Returns the verdict without executing anything.
1319
+ */
1320
+ evaluate(event) {
1321
+ this.engineOptions.plan = this.state.plan;
1322
+ const verdict = evaluateGuard(event, this.state.world, this.engineOptions);
1323
+ this.state.actionsEvaluated++;
1324
+ if (verdict.status === "ALLOW") this.state.actionsAllowed++;
1325
+ if (verdict.status === "BLOCK") this.state.actionsBlocked++;
1326
+ if (verdict.status === "PAUSE") this.state.actionsPaused++;
1327
+ this.config.onVerdict?.(verdict, event);
1328
+ return verdict;
1329
+ }
1330
+ /**
1331
+ * Evaluate and execute a tool call.
1332
+ * Returns the execution result or block reason.
1333
+ */
1334
+ async executeToolCall(toolCall) {
1335
+ let args;
1336
+ try {
1337
+ args = JSON.parse(toolCall.function.arguments);
1338
+ } catch {
1339
+ args = { raw: toolCall.function.arguments };
1340
+ }
1341
+ const event = {
1342
+ intent: toolCall.function.name,
1343
+ tool: toolCall.function.name,
1344
+ args,
1345
+ direction: "input"
1346
+ };
1347
+ const verdict = this.evaluate(event);
1348
+ if (verdict.status === "BLOCK") {
1349
+ return { allowed: false, verdict };
1350
+ }
1351
+ if (verdict.status === "PAUSE") {
1352
+ return { allowed: false, verdict };
1353
+ }
1354
+ const result = await this.executor(toolCall.function.name, args);
1355
+ this.config.onToolResult?.(toolCall.function.name, result);
1356
+ if (this.state.plan) {
1357
+ const planVerdict = evaluatePlan(event, this.state.plan);
1358
+ if (planVerdict.matchedStep) {
1359
+ const advResult = advancePlan(this.state.plan, planVerdict.matchedStep);
1360
+ if (advResult.success && advResult.plan) {
1361
+ this.state.plan = advResult.plan;
1362
+ this.engineOptions.plan = this.state.plan;
1363
+ }
1364
+ this.state.progress = getPlanProgress(this.state.plan);
1365
+ this.config.onPlanProgress?.(this.state.progress);
1366
+ if (this.state.progress.completed === this.state.progress.total) {
1367
+ this.config.onPlanComplete?.();
1368
+ }
1369
+ }
1370
+ }
1371
+ return { allowed: true, verdict, result };
1372
+ }
1373
+ /**
1374
+ * Process a model response — evaluate and execute all tool calls.
1375
+ * Returns results for each tool call.
1376
+ */
1377
+ async processModelResponse(response, model) {
1378
+ if (response.toolCalls.length === 0) {
1379
+ return response;
1380
+ }
1381
+ for (const toolCall of response.toolCalls) {
1382
+ const { allowed, verdict, result } = await this.executeToolCall(toolCall);
1383
+ if (allowed && result) {
1384
+ const nextResponse = await model.sendToolResult(toolCall.id, result);
1385
+ if (nextResponse.toolCalls.length > 0) {
1386
+ return this.processModelResponse(nextResponse, model);
1387
+ }
1388
+ return nextResponse;
1389
+ } else {
1390
+ const reason = verdict.reason ?? "Action blocked by governance.";
1391
+ const nextResponse = await model.sendBlockedResult(toolCall.id, reason);
1392
+ if (nextResponse.toolCalls.length > 0) {
1393
+ return this.processModelResponse(nextResponse, model);
1394
+ }
1395
+ return nextResponse;
1396
+ }
1397
+ }
1398
+ return response;
1399
+ }
1400
+ /** Get current session state. */
1401
+ getState() {
1402
+ return { ...this.state };
1403
+ }
1404
+ /** Stop the session. */
1405
+ stop() {
1406
+ this.state.active = false;
1407
+ return this.getState();
1408
+ }
1409
+ };
1410
+ async function runPipeMode(config) {
1411
+ const session = new SessionManager(config);
1412
+ await session.start();
1413
+ const state = session.getState();
1414
+ process.stderr.write(`[neuroverse] Pipe mode active
1415
+ `);
1416
+ process.stderr.write(`[neuroverse] World: ${state.world.world.name}
1417
+ `);
1418
+ if (state.plan) {
1419
+ process.stderr.write(`[neuroverse] Plan: ${state.plan.plan_id} (${state.plan.objective})
1420
+ `);
1421
+ }
1422
+ return new Promise((resolve3, reject) => {
1423
+ let buffer = "";
1424
+ process.stdin.setEncoding("utf-8");
1425
+ process.stdin.on("data", (chunk) => {
1426
+ buffer += chunk;
1427
+ const lines = buffer.split("\n");
1428
+ buffer = lines.pop() ?? "";
1429
+ for (const line of lines) {
1430
+ const trimmed = line.trim();
1431
+ if (!trimmed) continue;
1432
+ try {
1433
+ const event = JSON.parse(trimmed);
1434
+ if (!event.intent) {
1435
+ process.stderr.write(`[neuroverse] Warning: event missing "intent" field
1436
+ `);
1437
+ continue;
1438
+ }
1439
+ const verdict = session.evaluate(event);
1440
+ process.stdout.write(JSON.stringify(verdict) + "\n");
1441
+ } catch (err) {
1442
+ process.stderr.write(`[neuroverse] Error parsing line: ${err}
1443
+ `);
1444
+ }
1445
+ }
1446
+ });
1447
+ process.stdin.on("end", () => {
1448
+ if (buffer.trim()) {
1449
+ try {
1450
+ const event = JSON.parse(buffer.trim());
1451
+ if (event.intent) {
1452
+ const verdict = session.evaluate(event);
1453
+ process.stdout.write(JSON.stringify(verdict) + "\n");
1454
+ }
1455
+ } catch {
1456
+ }
1457
+ }
1458
+ const finalState = session.stop();
1459
+ process.stderr.write(
1460
+ `[neuroverse] Session complete: ${finalState.actionsEvaluated} evaluated, ${finalState.actionsAllowed} allowed, ${finalState.actionsBlocked} blocked, ${finalState.actionsPaused} paused
1461
+ `
1462
+ );
1463
+ resolve3();
1464
+ });
1465
+ process.stdin.on("error", reject);
1466
+ });
1467
+ }
1468
+ async function runInteractiveMode(config, model) {
1469
+ const session = new SessionManager(config);
1470
+ await session.start();
1471
+ const state = session.getState();
1472
+ process.stdout.write("\n");
1473
+ process.stdout.write(` World: ${state.world.world.name}
1474
+ `);
1475
+ if (state.plan) {
1476
+ process.stdout.write(` Plan: ${state.plan.plan_id}
1477
+ `);
1478
+ process.stdout.write(` Goal: ${state.plan.objective}
1479
+ `);
1480
+ process.stdout.write(` Steps: ${state.progress?.total ?? 0}
1481
+ `);
1482
+ }
1483
+ process.stdout.write(` Type "exit" to end session.
1484
+ `);
1485
+ process.stdout.write("\n");
1486
+ const readline = await import("readline");
1487
+ const rl = readline.createInterface({
1488
+ input: process.stdin,
1489
+ output: process.stdout,
1490
+ prompt: "> "
1491
+ });
1492
+ const printProgress = () => {
1493
+ const s = session.getState();
1494
+ if (s.progress) {
1495
+ process.stdout.write(
1496
+ ` [plan: ${s.progress.completed}/${s.progress.total} (${s.progress.percentage}%)]
1497
+ `
1498
+ );
1499
+ }
1500
+ };
1501
+ rl.prompt();
1502
+ rl.on("line", async (input) => {
1503
+ const trimmed = input.trim();
1504
+ if (!trimmed) {
1505
+ rl.prompt();
1506
+ return;
1507
+ }
1508
+ if (trimmed === "exit" || trimmed === "quit") {
1509
+ const finalState = session.stop();
1510
+ process.stdout.write("\n");
1511
+ process.stdout.write(` Session complete.
1512
+ `);
1513
+ process.stdout.write(` Actions: ${finalState.actionsEvaluated} evaluated`);
1514
+ process.stdout.write(`, ${finalState.actionsAllowed} allowed`);
1515
+ process.stdout.write(`, ${finalState.actionsBlocked} blocked
1516
+ `);
1517
+ if (finalState.progress) {
1518
+ process.stdout.write(
1519
+ ` Plan: ${finalState.progress.completed}/${finalState.progress.total} steps completed
1520
+ `
1521
+ );
1522
+ }
1523
+ process.stdout.write("\n");
1524
+ rl.close();
1525
+ return;
1526
+ }
1527
+ if (trimmed === "status") {
1528
+ const s = session.getState();
1529
+ process.stdout.write(`
1530
+ World: ${s.world.world.name}
1531
+ `);
1532
+ process.stdout.write(` Actions: ${s.actionsEvaluated} evaluated
1533
+ `);
1534
+ process.stdout.write(` Allowed: ${s.actionsAllowed} | Blocked: ${s.actionsBlocked} | Paused: ${s.actionsPaused}
1535
+ `);
1536
+ if (s.progress && s.plan) {
1537
+ process.stdout.write(` Plan: ${s.plan.plan_id} \u2014 ${s.progress.completed}/${s.progress.total} (${s.progress.percentage}%)
1538
+ `);
1539
+ for (const step of s.plan.steps) {
1540
+ const icon = step.status === "completed" ? "[x]" : "[ ]";
1541
+ process.stdout.write(` ${icon} ${step.label}
1542
+ `);
1543
+ }
1544
+ }
1545
+ process.stdout.write("\n");
1546
+ rl.prompt();
1547
+ return;
1548
+ }
1549
+ try {
1550
+ const response = await model.chat(trimmed);
1551
+ if (response.toolCalls.length > 0) {
1552
+ const finalResponse = await session.processModelResponse(response, model);
1553
+ if (finalResponse.content) {
1554
+ process.stdout.write(`
1555
+ ${finalResponse.content}
1556
+
1557
+ `);
1558
+ }
1559
+ printProgress();
1560
+ } else if (response.content) {
1561
+ process.stdout.write(`
1562
+ ${response.content}
1563
+
1564
+ `);
1565
+ }
1566
+ } catch (err) {
1567
+ process.stderr.write(`
1568
+ Error: ${err}
1569
+
1570
+ `);
1571
+ }
1572
+ rl.prompt();
1573
+ });
1574
+ rl.on("close", () => {
1575
+ session.stop();
1576
+ });
1577
+ return new Promise((resolve3) => {
1578
+ rl.on("close", resolve3);
1579
+ });
1580
+ }
1581
+
1582
+ // src/runtime/model-adapter.ts
1583
+ var DEFAULT_SYSTEM_PROMPT = `You are an AI assistant operating under NeuroVerse governance.
1584
+ All your tool calls are evaluated against governance rules before execution.
1585
+ If an action is blocked, you will be told why. Adjust your approach accordingly.
1586
+ Do not attempt to bypass governance rules.`;
1587
+ var ModelAdapter = class {
1588
+ config;
1589
+ messages;
1590
+ tools;
1591
+ constructor(config, tools = []) {
1592
+ this.config = config;
1593
+ this.tools = tools;
1594
+ this.messages = [];
1595
+ const systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
1596
+ this.messages.push({ role: "system", content: systemPrompt });
1597
+ }
1598
+ /**
1599
+ * Send a user message and get the model's response.
1600
+ */
1601
+ async chat(userMessage) {
1602
+ this.messages.push({ role: "user", content: userMessage });
1603
+ return this.complete();
1604
+ }
1605
+ /**
1606
+ * Send a tool result back to the model and get the next response.
1607
+ */
1608
+ async sendToolResult(toolCallId, result) {
1609
+ this.messages.push({
1610
+ role: "tool",
1611
+ content: result,
1612
+ tool_call_id: toolCallId
1613
+ });
1614
+ return this.complete();
1615
+ }
1616
+ /**
1617
+ * Send a governance block message as a tool result.
1618
+ */
1619
+ async sendBlockedResult(toolCallId, reason) {
1620
+ return this.sendToolResult(
1621
+ toolCallId,
1622
+ `[GOVERNANCE BLOCKED] ${reason}. Please adjust your approach.`
1623
+ );
1624
+ }
1625
+ /**
1626
+ * Call the model API and parse the response.
1627
+ */
1628
+ async complete() {
1629
+ const url = `${this.config.baseUrl}/chat/completions`;
1630
+ const body = {
1631
+ model: this.config.model,
1632
+ messages: this.messages,
1633
+ max_tokens: this.config.maxTokens ?? 4096
1634
+ };
1635
+ if (this.tools.length > 0) {
1636
+ body.tools = this.tools;
1637
+ }
1638
+ const response = await fetch(url, {
1639
+ method: "POST",
1640
+ headers: {
1641
+ "Content-Type": "application/json",
1642
+ "Authorization": `Bearer ${this.config.apiKey}`
1643
+ },
1644
+ body: JSON.stringify(body)
1645
+ });
1646
+ if (!response.ok) {
1647
+ const text = await response.text();
1648
+ throw new Error(`Model API error ${response.status}: ${text}`);
1649
+ }
1650
+ const data = await response.json();
1651
+ const choice = data.choices?.[0];
1652
+ if (!choice) {
1653
+ throw new Error("Model returned no choices");
1654
+ }
1655
+ const message = choice.message;
1656
+ this.messages.push(message);
1657
+ return {
1658
+ content: message.content ?? null,
1659
+ toolCalls: message.tool_calls ?? [],
1660
+ finishReason: choice.finish_reason ?? "stop"
1661
+ };
1662
+ }
1663
+ /** Get current message count (for context tracking). */
1664
+ get messageCount() {
1665
+ return this.messages.length;
1666
+ }
1667
+ };
1668
+ var PROVIDERS = {
1669
+ openai: {
1670
+ baseUrl: "https://api.openai.com/v1",
1671
+ defaultModel: "gpt-4o",
1672
+ envVar: "OPENAI_API_KEY"
1673
+ },
1674
+ anthropic: {
1675
+ baseUrl: "https://api.anthropic.com/v1",
1676
+ defaultModel: "claude-sonnet-4-20250514",
1677
+ envVar: "ANTHROPIC_API_KEY"
1678
+ },
1679
+ ollama: {
1680
+ baseUrl: "http://localhost:11434/v1",
1681
+ defaultModel: "llama3",
1682
+ envVar: ""
1683
+ }
1684
+ };
1685
+ function resolveProvider(provider, overrides) {
1686
+ const preset = PROVIDERS[provider];
1687
+ if (!preset) {
1688
+ throw new Error(
1689
+ `Unknown provider: "${provider}". Available: ${Object.keys(PROVIDERS).join(", ")}`
1690
+ );
1691
+ }
1692
+ const apiKey = overrides?.apiKey ?? (preset.envVar ? process.env[preset.envVar] : "") ?? "";
1693
+ if (!apiKey && preset.envVar) {
1694
+ throw new Error(
1695
+ `Missing API key. Set ${preset.envVar} or pass --api-key.`
1696
+ );
1697
+ }
1698
+ return {
1699
+ baseUrl: overrides?.baseUrl ?? preset.baseUrl,
1700
+ apiKey,
1701
+ model: overrides?.model ?? preset.defaultModel,
1702
+ systemPrompt: overrides?.systemPrompt,
1703
+ maxTokens: overrides?.maxTokens
1704
+ };
1705
+ }
1706
+
1707
+ // src/loader/world-resolver.ts
1708
+ var import_fs = require("fs");
1709
+ var import_path = require("path");
1710
+ var WORLDS_DIR = ".neuroverse/worlds";
1711
+ var ACTIVE_WORLD_FILE = ".neuroverse/active_world";
1712
+ function listWorlds(cwd = process.cwd()) {
1713
+ const worldsDir = (0, import_path.join)(cwd, WORLDS_DIR);
1714
+ if (!(0, import_fs.existsSync)(worldsDir)) return [];
1715
+ const activeName = getActiveWorldName(cwd);
1716
+ const entries = (0, import_fs.readdirSync)(worldsDir);
1717
+ return entries.filter((name) => {
1718
+ const worldJson = (0, import_path.join)(worldsDir, name, "world.json");
1719
+ return (0, import_fs.existsSync)(worldJson);
1720
+ }).map((name) => ({
1721
+ name,
1722
+ path: (0, import_path.join)(worldsDir, name),
1723
+ active: name === activeName
1724
+ })).sort((a, b) => a.name.localeCompare(b.name));
1725
+ }
1726
+ function getActiveWorldName(cwd = process.cwd()) {
1727
+ const filePath = (0, import_path.join)(cwd, ACTIVE_WORLD_FILE);
1728
+ try {
1729
+ return (0, import_fs.readFileSync)(filePath, "utf-8").trim() || void 0;
1730
+ } catch {
1731
+ return void 0;
1732
+ }
1733
+ }
1734
+ function setActiveWorld(name, cwd = process.cwd()) {
1735
+ const worldsDir = (0, import_path.join)(cwd, WORLDS_DIR);
1736
+ const worldPath = (0, import_path.join)(worldsDir, name, "world.json");
1737
+ if (!(0, import_fs.existsSync)(worldPath)) {
1738
+ const available = listWorlds(cwd);
1739
+ const names = available.map((w) => w.name).join(", ");
1740
+ throw new Error(
1741
+ `World "${name}" not found in ${WORLDS_DIR}/
1742
+ ` + (names ? `Available: ${names}` : "No worlds found. Run `neuroverse build` first.")
1743
+ );
1744
+ }
1745
+ const dir = (0, import_path.join)(cwd, ".neuroverse");
1746
+ if (!(0, import_fs.existsSync)(dir)) (0, import_fs.mkdirSync)(dir, { recursive: true });
1747
+ (0, import_fs.writeFileSync)((0, import_path.join)(cwd, ACTIVE_WORLD_FILE), name + "\n", "utf-8");
1748
+ }
1749
+ function resolveWorldPath(explicit, cwd = process.cwd()) {
1750
+ if (explicit) {
1751
+ return resolveNameOrPath(explicit, cwd);
1752
+ }
1753
+ const envWorld = process.env.NEUROVERSE_WORLD;
1754
+ if (envWorld) {
1755
+ return resolveNameOrPath(envWorld, cwd);
1756
+ }
1757
+ const activeName = getActiveWorldName(cwd);
1758
+ if (activeName) {
1759
+ return resolveNameOrPath(activeName, cwd);
1760
+ }
1761
+ const worlds = listWorlds(cwd);
1762
+ if (worlds.length === 1) {
1763
+ return (0, import_path.resolve)(worlds[0].path);
1764
+ }
1765
+ return void 0;
1766
+ }
1767
+ function describeActiveWorld(explicit, cwd = process.cwd()) {
1768
+ if (explicit) {
1769
+ return { name: explicit, source: "--world flag" };
1770
+ }
1771
+ const envWorld = process.env.NEUROVERSE_WORLD;
1772
+ if (envWorld) {
1773
+ return { name: envWorld, source: "NEUROVERSE_WORLD env var" };
1774
+ }
1775
+ const activeName = getActiveWorldName(cwd);
1776
+ if (activeName) {
1777
+ return { name: activeName, source: ".neuroverse/active_world" };
1778
+ }
1779
+ const worlds = listWorlds(cwd);
1780
+ if (worlds.length === 1) {
1781
+ return { name: worlds[0].name, source: "auto-detected (only world)" };
1782
+ }
1783
+ return void 0;
1784
+ }
1785
+ function resolveNameOrPath(ref, cwd) {
1786
+ if (ref.includes("/") || ref.includes("\\") || ref.startsWith(".") || (0, import_path.isAbsolute)(ref)) {
1787
+ return (0, import_path.resolve)(cwd, ref);
1788
+ }
1789
+ const namedPath = (0, import_path.join)(cwd, WORLDS_DIR, ref);
1790
+ if ((0, import_fs.existsSync)((0, import_path.join)(namedPath, "world.json"))) {
1791
+ return (0, import_path.resolve)(namedPath);
1792
+ }
1793
+ return (0, import_path.resolve)(cwd, ref);
1794
+ }
1795
+
1796
+ // src/runtime/mcp-server.ts
1797
+ var import_child_process = require("child_process");
1798
+ var import_fs2 = require("fs");
1799
+ var import_path2 = require("path");
1800
+ var GOVERNED_TOOLS = [
1801
+ {
1802
+ name: "governed_shell",
1803
+ description: "Execute a shell command. This command is evaluated against governance rules before execution.",
1804
+ inputSchema: {
1805
+ type: "object",
1806
+ properties: {
1807
+ command: { type: "string", description: "The shell command to execute" }
1808
+ },
1809
+ required: ["command"]
1810
+ }
1811
+ },
1812
+ {
1813
+ name: "governed_read_file",
1814
+ description: "Read a file. This action is evaluated against governance rules before execution.",
1815
+ inputSchema: {
1816
+ type: "object",
1817
+ properties: {
1818
+ path: { type: "string", description: "File path to read" }
1819
+ },
1820
+ required: ["path"]
1821
+ }
1822
+ },
1823
+ {
1824
+ name: "governed_write_file",
1825
+ description: "Write content to a file. This action is evaluated against governance rules before execution.",
1826
+ inputSchema: {
1827
+ type: "object",
1828
+ properties: {
1829
+ path: { type: "string", description: "File path to write" },
1830
+ content: { type: "string", description: "Content to write" }
1831
+ },
1832
+ required: ["path", "content"]
1833
+ }
1834
+ },
1835
+ {
1836
+ name: "governed_list_directory",
1837
+ description: "List files in a directory. This action is evaluated against governance rules.",
1838
+ inputSchema: {
1839
+ type: "object",
1840
+ properties: {
1841
+ path: { type: "string", description: "Directory path to list" }
1842
+ },
1843
+ required: ["path"]
1844
+ }
1845
+ },
1846
+ {
1847
+ name: "governed_http_request",
1848
+ description: "Make an HTTP request. This action is evaluated against governance rules before execution.",
1849
+ inputSchema: {
1850
+ type: "object",
1851
+ properties: {
1852
+ url: { type: "string", description: "URL to request" },
1853
+ method: { type: "string", description: "HTTP method (GET, POST, PUT, DELETE)", default: "GET" },
1854
+ body: { type: "string", description: "Request body (for POST/PUT)" },
1855
+ headers: { type: "object", description: "Request headers" }
1856
+ },
1857
+ required: ["url"]
1858
+ }
1859
+ },
1860
+ // Governance introspection tools — always available
1861
+ {
1862
+ name: "governance_check",
1863
+ description: "Check if an action would be allowed by governance rules without executing it.",
1864
+ inputSchema: {
1865
+ type: "object",
1866
+ properties: {
1867
+ intent: { type: "string", description: "What the action intends to do" },
1868
+ tool: { type: "string", description: "Tool name (shell, http, file, etc.)" },
1869
+ scope: { type: "string", description: "Scope (file path, URL, etc.)" }
1870
+ },
1871
+ required: ["intent"]
1872
+ }
1873
+ },
1874
+ {
1875
+ name: "governance_plan_status",
1876
+ description: "Show current plan progress and remaining steps.",
1877
+ inputSchema: {
1878
+ type: "object",
1879
+ properties: {}
1880
+ }
1881
+ },
1882
+ {
1883
+ name: "governance_plan_advance",
1884
+ description: "Mark a plan step as completed.",
1885
+ inputSchema: {
1886
+ type: "object",
1887
+ properties: {
1888
+ step_id: { type: "string", description: "ID of the step to mark as completed" }
1889
+ },
1890
+ required: ["step_id"]
1891
+ }
1892
+ }
1893
+ ];
1894
+ function executeShell(command, workingDir) {
1895
+ try {
1896
+ const result = (0, import_child_process.execSync)(command, {
1897
+ cwd: workingDir,
1898
+ encoding: "utf-8",
1899
+ timeout: 3e4,
1900
+ maxBuffer: 1024 * 1024
1901
+ });
1902
+ return result;
1903
+ } catch (err) {
1904
+ return `Error: ${err.message}
1905
+ ${err.stderr ?? ""}`;
1906
+ }
1907
+ }
1908
+ function executeReadFile(path, workingDir) {
1909
+ const fullPath = (0, import_path2.resolve)(workingDir ?? ".", path);
1910
+ return (0, import_fs2.readFileSync)(fullPath, "utf-8");
1911
+ }
1912
+ function executeWriteFile(path, content, workingDir) {
1913
+ const fullPath = (0, import_path2.resolve)(workingDir ?? ".", path);
1914
+ (0, import_fs2.writeFileSync)(fullPath, content);
1915
+ return `File written: ${fullPath}`;
1916
+ }
1917
+ function executeListDir(path, workingDir) {
1918
+ const fullPath = (0, import_path2.resolve)(workingDir ?? ".", path);
1919
+ const entries = (0, import_fs2.readdirSync)(fullPath);
1920
+ return entries.map((e) => {
1921
+ try {
1922
+ const stat = (0, import_fs2.statSync)((0, import_path2.join)(fullPath, e));
1923
+ return `${stat.isDirectory() ? "d" : "-"} ${e}`;
1924
+ } catch {
1925
+ return `? ${e}`;
1926
+ }
1927
+ }).join("\n");
1928
+ }
1929
+ async function executeHttpRequest(url, method, body, headers) {
1930
+ const response = await fetch(url, {
1931
+ method: method || "GET",
1932
+ body: body || void 0,
1933
+ headers: { "Content-Type": "application/json", ...headers }
1934
+ });
1935
+ const text = await response.text();
1936
+ return `HTTP ${response.status}
1937
+ ${text}`;
1938
+ }
1939
+ var McpGovernanceServer = class {
1940
+ world;
1941
+ plan;
1942
+ config;
1943
+ engineOptions;
1944
+ initialized = false;
1945
+ // Stats
1946
+ actionsEvaluated = 0;
1947
+ actionsAllowed = 0;
1948
+ actionsBlocked = 0;
1949
+ constructor(config) {
1950
+ this.config = config;
1951
+ this.plan = config.plan;
1952
+ this.engineOptions = {
1953
+ trace: config.trace ?? false,
1954
+ level: config.level,
1955
+ plan: this.plan
1956
+ };
1957
+ }
1958
+ /**
1959
+ * Start the MCP server — reads JSON-RPC from stdin, writes to stdout.
1960
+ */
1961
+ async start() {
1962
+ if (this.config.worldPath) {
1963
+ this.world = await loadWorld(this.config.worldPath);
1964
+ } else if (this.config.world) {
1965
+ this.world = this.config.world;
1966
+ } else {
1967
+ throw new Error("No world provided");
1968
+ }
1969
+ if (this.config.planPath && !this.plan) {
1970
+ this.plan = JSON.parse((0, import_fs2.readFileSync)(this.config.planPath, "utf-8"));
1971
+ this.engineOptions.plan = this.plan;
1972
+ }
1973
+ process.stderr.write(`[neuroverse-mcp] Server starting
1974
+ `);
1975
+ process.stderr.write(`[neuroverse-mcp] World: ${this.world.world.name}
1976
+ `);
1977
+ if (this.plan) {
1978
+ process.stderr.write(`[neuroverse-mcp] Plan: ${this.plan.plan_id}
1979
+ `);
1980
+ }
1981
+ let buffer = "";
1982
+ process.stdin.setEncoding("utf-8");
1983
+ process.stdin.on("data", (chunk) => {
1984
+ buffer += chunk;
1985
+ while (buffer.length > 0) {
1986
+ const headerEnd = buffer.indexOf("\r\n\r\n");
1987
+ if (headerEnd === -1) break;
1988
+ const header = buffer.slice(0, headerEnd);
1989
+ const contentLengthMatch = header.match(/Content-Length:\s*(\d+)/i);
1990
+ if (!contentLengthMatch) {
1991
+ const newlineIdx = buffer.indexOf("\n");
1992
+ if (newlineIdx === -1) break;
1993
+ const line = buffer.slice(0, newlineIdx).trim();
1994
+ buffer = buffer.slice(newlineIdx + 1);
1995
+ if (line) this.handleRawLine(line);
1996
+ continue;
1997
+ }
1998
+ const contentLength = parseInt(contentLengthMatch[1], 10);
1999
+ const bodyStart = headerEnd + 4;
2000
+ const bodyEnd = bodyStart + contentLength;
2001
+ if (buffer.length < bodyEnd) break;
2002
+ const body = buffer.slice(bodyStart, bodyEnd);
2003
+ buffer = buffer.slice(bodyEnd);
2004
+ this.handleRawLine(body);
2005
+ }
2006
+ });
2007
+ process.stdin.on("end", () => {
2008
+ process.stderr.write(
2009
+ `[neuroverse-mcp] Server stopped. Evaluated: ${this.actionsEvaluated}, Allowed: ${this.actionsAllowed}, Blocked: ${this.actionsBlocked}
2010
+ `
2011
+ );
2012
+ });
2013
+ await new Promise(() => {
2014
+ });
2015
+ }
2016
+ handleRawLine(line) {
2017
+ try {
2018
+ const msg = JSON.parse(line);
2019
+ if (msg.method) {
2020
+ if (msg.id !== void 0) {
2021
+ this.handleRequest(msg);
2022
+ } else {
2023
+ this.handleNotification(msg);
2024
+ }
2025
+ }
2026
+ } catch (err) {
2027
+ process.stderr.write(`[neuroverse-mcp] Parse error: ${err}
2028
+ `);
2029
+ }
2030
+ }
2031
+ send(msg) {
2032
+ const json = JSON.stringify(msg);
2033
+ const header = `Content-Length: ${Buffer.byteLength(json)}\r
2034
+ \r
2035
+ `;
2036
+ process.stdout.write(header + json);
2037
+ }
2038
+ sendResult(id, result) {
2039
+ this.send({ jsonrpc: "2.0", id, result });
2040
+ }
2041
+ sendError(id, code, message) {
2042
+ this.send({ jsonrpc: "2.0", id, error: { code, message } });
2043
+ }
2044
+ // ─── Request Handlers ───────────────────────────────────────────────────
2045
+ handleRequest(request) {
2046
+ switch (request.method) {
2047
+ case "initialize":
2048
+ this.handleInitialize(request);
2049
+ break;
2050
+ case "tools/list":
2051
+ this.handleToolsList(request);
2052
+ break;
2053
+ case "tools/call":
2054
+ this.handleToolsCall(request);
2055
+ break;
2056
+ case "ping":
2057
+ this.sendResult(request.id, {});
2058
+ break;
2059
+ default:
2060
+ this.sendError(request.id, -32601, `Method not found: ${request.method}`);
2061
+ }
2062
+ }
2063
+ handleNotification(notification) {
2064
+ switch (notification.method) {
2065
+ case "notifications/initialized":
2066
+ this.initialized = true;
2067
+ process.stderr.write(`[neuroverse-mcp] Client initialized
2068
+ `);
2069
+ break;
2070
+ case "notifications/cancelled":
2071
+ break;
2072
+ default:
2073
+ process.stderr.write(`[neuroverse-mcp] Unknown notification: ${notification.method}
2074
+ `);
2075
+ }
2076
+ }
2077
+ handleInitialize(request) {
2078
+ this.sendResult(request.id, {
2079
+ protocolVersion: "2024-11-05",
2080
+ capabilities: {
2081
+ tools: {}
2082
+ },
2083
+ serverInfo: {
2084
+ name: "neuroverse-governance",
2085
+ version: "0.2.0"
2086
+ }
2087
+ });
2088
+ }
2089
+ handleToolsList(request) {
2090
+ const tools = [];
2091
+ if (this.config.enableShell !== false) {
2092
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_shell"));
2093
+ }
2094
+ if (this.config.enableFiles !== false) {
2095
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_read_file"));
2096
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_write_file"));
2097
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_list_directory"));
2098
+ }
2099
+ if (this.config.enableHttp !== false) {
2100
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_http_request"));
2101
+ }
2102
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governance_check"));
2103
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governance_plan_status"));
2104
+ if (this.plan) {
2105
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governance_plan_advance"));
2106
+ }
2107
+ this.sendResult(request.id, { tools });
2108
+ }
2109
+ async handleToolsCall(request) {
2110
+ const params = request.params;
2111
+ const toolName = params.name;
2112
+ const args = params.arguments ?? {};
2113
+ try {
2114
+ const result = await this.executeTool(toolName, args);
2115
+ this.sendResult(request.id, result);
2116
+ } catch (err) {
2117
+ this.sendResult(request.id, {
2118
+ content: [{ type: "text", text: `Error: ${err.message}` }],
2119
+ isError: true
2120
+ });
2121
+ }
2122
+ }
2123
+ // ─── Tool Execution with Governance ─────────────────────────────────────
2124
+ async executeTool(name, args) {
2125
+ if (name === "governance_check") {
2126
+ return this.toolGovernanceCheck(args);
2127
+ }
2128
+ if (name === "governance_plan_status") {
2129
+ return this.toolPlanStatus();
2130
+ }
2131
+ if (name === "governance_plan_advance") {
2132
+ return this.toolPlanAdvance(args);
2133
+ }
2134
+ const event = this.buildEvent(name, args);
2135
+ this.engineOptions.plan = this.plan;
2136
+ const verdict = evaluateGuard(event, this.world, this.engineOptions);
2137
+ this.actionsEvaluated++;
2138
+ if (verdict.status === "BLOCK") {
2139
+ this.actionsBlocked++;
2140
+ let reason = `[GOVERNANCE BLOCKED] ${verdict.reason ?? "Action blocked by governance rules."}`;
2141
+ if (verdict.ruleId) reason += ` (Rule: ${verdict.ruleId})`;
2142
+ if (verdict.trace?.planCheck && !verdict.trace.planCheck.matched) {
2143
+ const pc = verdict.trace.planCheck;
2144
+ if (pc.closestStepLabel) {
2145
+ reason += `
2146
+ Closest plan step: "${pc.closestStepLabel}"`;
2147
+ }
2148
+ }
2149
+ process.stderr.write(`[neuroverse-mcp] BLOCKED: ${event.intent}
2150
+ `);
2151
+ return { content: [{ type: "text", text: reason }], isError: true };
2152
+ }
2153
+ if (verdict.status === "PAUSE") {
2154
+ this.actionsBlocked++;
2155
+ const reason = `[GOVERNANCE PAUSED] ${verdict.reason ?? "Action requires human approval."}`;
2156
+ process.stderr.write(`[neuroverse-mcp] PAUSED: ${event.intent}
2157
+ `);
2158
+ return { content: [{ type: "text", text: reason }], isError: true };
2159
+ }
2160
+ this.actionsAllowed++;
2161
+ process.stderr.write(`[neuroverse-mcp] ALLOWED: ${event.intent}
2162
+ `);
2163
+ const result = await this.executeActualTool(name, args);
2164
+ if (this.plan) {
2165
+ const planVerdict = evaluatePlan(event, this.plan);
2166
+ if (planVerdict.matchedStep) {
2167
+ const advResult = advancePlan(this.plan, planVerdict.matchedStep);
2168
+ if (advResult.success && advResult.plan) {
2169
+ this.plan = advResult.plan;
2170
+ this.engineOptions.plan = this.plan;
2171
+ }
2172
+ const progress = getPlanProgress(this.plan);
2173
+ process.stderr.write(
2174
+ `[neuroverse-mcp] Plan: ${progress.completed}/${progress.total} (${progress.percentage}%)
2175
+ `
2176
+ );
2177
+ if (progress.completed === progress.total) {
2178
+ process.stderr.write(`[neuroverse-mcp] Plan complete!
2179
+ `);
2180
+ }
2181
+ }
2182
+ }
2183
+ return result;
2184
+ }
2185
+ buildEvent(toolName, args) {
2186
+ switch (toolName) {
2187
+ case "governed_shell":
2188
+ return {
2189
+ intent: `execute shell command: ${args.command}`,
2190
+ tool: "shell",
2191
+ scope: String(args.command ?? ""),
2192
+ actionCategory: "shell",
2193
+ args,
2194
+ direction: "input"
2195
+ };
2196
+ case "governed_read_file":
2197
+ return {
2198
+ intent: `read file: ${args.path}`,
2199
+ tool: "fs",
2200
+ scope: String(args.path ?? ""),
2201
+ actionCategory: "read",
2202
+ args,
2203
+ direction: "input"
2204
+ };
2205
+ case "governed_write_file":
2206
+ return {
2207
+ intent: `write file: ${args.path}`,
2208
+ tool: "fs",
2209
+ scope: String(args.path ?? ""),
2210
+ actionCategory: "write",
2211
+ args,
2212
+ direction: "input"
2213
+ };
2214
+ case "governed_list_directory":
2215
+ return {
2216
+ intent: `list directory: ${args.path}`,
2217
+ tool: "fs",
2218
+ scope: String(args.path ?? ""),
2219
+ actionCategory: "read",
2220
+ args,
2221
+ direction: "input"
2222
+ };
2223
+ case "governed_http_request":
2224
+ return {
2225
+ intent: `http ${args.method ?? "GET"} ${args.url}`,
2226
+ tool: "http",
2227
+ scope: String(args.url ?? ""),
2228
+ actionCategory: "network",
2229
+ args,
2230
+ direction: "input"
2231
+ };
2232
+ default:
2233
+ return {
2234
+ intent: `${toolName}: ${JSON.stringify(args)}`,
2235
+ tool: toolName,
2236
+ args,
2237
+ direction: "input"
2238
+ };
2239
+ }
2240
+ }
2241
+ async executeActualTool(name, args) {
2242
+ const workingDir = this.config.workingDir;
2243
+ switch (name) {
2244
+ case "governed_shell": {
2245
+ const output = executeShell(String(args.command), workingDir);
2246
+ return { content: [{ type: "text", text: output }] };
2247
+ }
2248
+ case "governed_read_file": {
2249
+ const content = executeReadFile(String(args.path), workingDir);
2250
+ return { content: [{ type: "text", text: content }] };
2251
+ }
2252
+ case "governed_write_file": {
2253
+ const result = executeWriteFile(String(args.path), String(args.content), workingDir);
2254
+ return { content: [{ type: "text", text: result }] };
2255
+ }
2256
+ case "governed_list_directory": {
2257
+ const listing = executeListDir(String(args.path), workingDir);
2258
+ return { content: [{ type: "text", text: listing }] };
2259
+ }
2260
+ case "governed_http_request": {
2261
+ const result = await executeHttpRequest(
2262
+ String(args.url),
2263
+ String(args.method ?? "GET"),
2264
+ args.body,
2265
+ args.headers
2266
+ );
2267
+ return { content: [{ type: "text", text: result }] };
2268
+ }
2269
+ default:
2270
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
2271
+ }
2272
+ }
2273
+ // ─── Governance Introspection Tools ─────────────────────────────────────
2274
+ toolGovernanceCheck(args) {
2275
+ const event = {
2276
+ intent: String(args.intent ?? ""),
2277
+ tool: args.tool,
2278
+ scope: args.scope,
2279
+ direction: "input"
2280
+ };
2281
+ this.engineOptions.plan = this.plan;
2282
+ const verdict = evaluateGuard(event, this.world, this.engineOptions);
2283
+ const lines = [
2284
+ `Verdict: ${verdict.status}`,
2285
+ verdict.reason ? `Reason: ${verdict.reason}` : null,
2286
+ verdict.ruleId ? `Rule: ${verdict.ruleId}` : null,
2287
+ verdict.warning ? `Warning: ${verdict.warning}` : null
2288
+ ].filter(Boolean).join("\n");
2289
+ return { content: [{ type: "text", text: lines }] };
2290
+ }
2291
+ toolPlanStatus() {
2292
+ if (!this.plan) {
2293
+ return { content: [{ type: "text", text: "No active plan." }] };
2294
+ }
2295
+ const progress = getPlanProgress(this.plan);
2296
+ const lines = [
2297
+ `Plan: ${this.plan.plan_id}`,
2298
+ `Objective: ${this.plan.objective}`,
2299
+ `Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`,
2300
+ "",
2301
+ "Steps:",
2302
+ ...this.plan.steps.map((s) => {
2303
+ const icon = s.status === "completed" ? "[x]" : s.status === "active" ? "[>]" : "[ ]";
2304
+ return ` ${icon} ${s.label} (${s.id})`;
2305
+ })
2306
+ ];
2307
+ if (this.plan.constraints.length > 0) {
2308
+ lines.push("", "Constraints:");
2309
+ for (const c2 of this.plan.constraints) {
2310
+ lines.push(` - ${c2.description} [${c2.type}]`);
2311
+ }
2312
+ }
2313
+ return { content: [{ type: "text", text: lines.join("\n") }] };
2314
+ }
2315
+ toolPlanAdvance(args) {
2316
+ if (!this.plan) {
2317
+ return { content: [{ type: "text", text: "No active plan." }], isError: true };
2318
+ }
2319
+ const stepId = String(args.step_id ?? "");
2320
+ const step = this.plan.steps.find((s) => s.id === stepId);
2321
+ if (!step) {
2322
+ const ids = this.plan.steps.map((s) => s.id).join(", ");
2323
+ return {
2324
+ content: [{ type: "text", text: `Step "${stepId}" not found. Available: ${ids}` }],
2325
+ isError: true
2326
+ };
2327
+ }
2328
+ if (step.status === "completed") {
2329
+ return { content: [{ type: "text", text: `Step "${stepId}" is already completed.` }] };
2330
+ }
2331
+ const advResult = advancePlan(this.plan, stepId);
2332
+ if (!advResult.success) {
2333
+ return { content: [{ type: "text", text: `Cannot advance: ${advResult.reason}` }] };
2334
+ }
2335
+ this.plan = advResult.plan;
2336
+ this.engineOptions.plan = this.plan;
2337
+ const progress = getPlanProgress(this.plan);
2338
+ let text = `Step completed: ${step.label}
2339
+ Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`;
2340
+ if (progress.completed === progress.total) {
2341
+ text += "\n\nPlan complete!";
2342
+ }
2343
+ if (this.config.planPath) {
2344
+ (0, import_fs2.writeFileSync)(this.config.planPath, JSON.stringify(this.plan, null, 2) + "\n");
2345
+ }
2346
+ return { content: [{ type: "text", text }] };
2347
+ }
2348
+ };
2349
+
747
2350
  // src/engine/audit-logger.ts
748
2351
  var FileAuditLogger = class {
749
2352
  logPath;
@@ -1223,81 +2826,6 @@ function emptyReport() {
1223
2826
  };
1224
2827
  }
1225
2828
 
1226
- // src/loader/world-loader.ts
1227
- async function loadWorldFromDirectory(dirPath) {
1228
- const { readFile: readFile3 } = await import("fs/promises");
1229
- const { join: join3 } = await import("path");
1230
- const { readdirSync } = await import("fs");
1231
- async function readJson(filename) {
1232
- try {
1233
- const content = await readFile3(join3(dirPath, filename), "utf-8");
1234
- return JSON.parse(content);
1235
- } catch {
1236
- return void 0;
1237
- }
1238
- }
1239
- const worldJson = await readJson("world.json");
1240
- if (!worldJson) {
1241
- throw new Error(`Cannot read world.json in ${dirPath}`);
1242
- }
1243
- const invariantsJson = await readJson("invariants.json");
1244
- const assumptionsJson = await readJson("assumptions.json");
1245
- const stateSchemaJson = await readJson("state-schema.json");
1246
- const gatesJson = await readJson("gates.json");
1247
- const outcomesJson = await readJson("outcomes.json");
1248
- const guardsJson = await readJson("guards.json");
1249
- const rolesJson = await readJson("roles.json");
1250
- const kernelJson = await readJson("kernel.json");
1251
- const metadataJson = await readJson("metadata.json");
1252
- const rules = [];
1253
- try {
1254
- const rulesDir = join3(dirPath, "rules");
1255
- const ruleFiles = readdirSync(rulesDir).filter((f) => f.endsWith(".json")).sort();
1256
- for (const file of ruleFiles) {
1257
- const content = await readFile3(join3(rulesDir, file), "utf-8");
1258
- rules.push(JSON.parse(content));
1259
- }
1260
- } catch {
1261
- }
1262
- return {
1263
- world: worldJson,
1264
- invariants: invariantsJson?.invariants ?? [],
1265
- assumptions: assumptionsJson ?? { profiles: {}, parameter_definitions: {} },
1266
- stateSchema: stateSchemaJson ?? { variables: {}, presets: {} },
1267
- rules,
1268
- gates: gatesJson ?? {
1269
- viability_classification: [],
1270
- structural_override: { description: "", enforcement: "mandatory" },
1271
- sustainability_threshold: 0,
1272
- collapse_visual: { background: "", text: "", border: "", label: "" }
1273
- },
1274
- outcomes: outcomesJson ?? {
1275
- computed_outcomes: [],
1276
- comparison_layout: { primary_card: "", status_badge: "", structural_indicators: [] }
1277
- },
1278
- guards: guardsJson,
1279
- roles: rolesJson,
1280
- kernel: kernelJson,
1281
- metadata: metadataJson ?? {
1282
- format_version: "1.0.0",
1283
- created_at: "",
1284
- last_modified: "",
1285
- authoring_method: "manual-authoring"
1286
- }
1287
- };
1288
- }
1289
- async function loadWorld(worldPath) {
1290
- const { stat } = await import("fs/promises");
1291
- const info = await stat(worldPath);
1292
- if (info.isDirectory()) {
1293
- return loadWorldFromDirectory(worldPath);
1294
- }
1295
- if (worldPath.endsWith(".nv-world.zip")) {
1296
- throw new Error(".nv-world.zip loading not yet implemented \u2014 use a world directory");
1297
- }
1298
- throw new Error(`Cannot load world from: ${worldPath} \u2014 expected a directory or .nv-world.zip`);
1299
- }
1300
-
1301
2829
  // src/engine/condition-engine.ts
1302
2830
  function getFieldValue(event, field) {
1303
2831
  if (field.startsWith("args.")) {
@@ -1444,15 +2972,39 @@ function evaluateEndsWith(fieldValue, conditionValue) {
1444
2972
  }
1445
2973
 
1446
2974
  // src/engine/validate-engine.ts
1447
- function validateWorld(world) {
2975
+ function validateWorld(world, mode = "standard") {
1448
2976
  const startTime = performance.now();
1449
2977
  const findings = [];
1450
2978
  checkCompleteness(world, findings);
1451
2979
  checkReferentialIntegrity(world, findings);
1452
2980
  checkGuardCoverage(world, findings);
2981
+ checkSemanticCoverage(world, findings);
1453
2982
  checkContradictions(world, findings);
2983
+ checkGuardShadows(world, findings);
2984
+ checkFailClosedSurfaces(world, findings);
2985
+ checkReachability(world, findings);
2986
+ checkStateCoverage(world, findings);
1454
2987
  checkOrphans(world, findings);
1455
2988
  checkSchemaViolations(world, findings);
2989
+ const governanceCategories = /* @__PURE__ */ new Set([
2990
+ "guard-coverage",
2991
+ "contradiction",
2992
+ "semantic-tension",
2993
+ "orphan"
2994
+ ]);
2995
+ if (mode === "dev") {
2996
+ for (const f of findings) {
2997
+ if (governanceCategories.has(f.category) && f.severity === "warning") {
2998
+ f.severity = "info";
2999
+ }
3000
+ }
3001
+ } else if (mode === "strict") {
3002
+ for (const f of findings) {
3003
+ if (governanceCategories.has(f.category) && f.severity === "info") {
3004
+ f.severity = "warning";
3005
+ }
3006
+ }
3007
+ }
1456
3008
  const severityOrder = { error: 0, warning: 1, info: 2 };
1457
3009
  findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
1458
3010
  const errors = findings.filter((f) => f.severity === "error").length;
@@ -1460,6 +3012,7 @@ function validateWorld(world) {
1460
3012
  const info = findings.filter((f) => f.severity === "info").length;
1461
3013
  const completenessScore = computeCompletenessScore(world);
1462
3014
  const invariantCoverage = computeInvariantCoverage(world);
3015
+ const governanceHealth = computeGovernanceHealth(world, findings);
1463
3016
  const summary = {
1464
3017
  errors,
1465
3018
  warnings,
@@ -1467,7 +3020,8 @@ function validateWorld(world) {
1467
3020
  completenessScore,
1468
3021
  invariantCoverage,
1469
3022
  canRun: errors === 0,
1470
- isHealthy: errors === 0 && warnings === 0
3023
+ isHealthy: errors === 0 && warnings === 0,
3024
+ governanceHealth
1471
3025
  };
1472
3026
  return {
1473
3027
  worldId: world.world.world_id,
@@ -1475,6 +3029,7 @@ function validateWorld(world) {
1475
3029
  worldVersion: world.world.version,
1476
3030
  validatedAt: Date.now(),
1477
3031
  durationMs: performance.now() - startTime,
3032
+ validationMode: mode,
1478
3033
  summary,
1479
3034
  findings
1480
3035
  };
@@ -1657,6 +3212,183 @@ function checkGuardCoverage(world, findings) {
1657
3212
  }
1658
3213
  }
1659
3214
  }
3215
+ function checkSemanticCoverage(world, findings) {
3216
+ if (!world.invariants || world.invariants.length === 0) return;
3217
+ const hasGuards = (world.guards?.guards?.length ?? 0) > 0;
3218
+ const hasKernel = (world.kernel?.input_boundaries?.forbidden_patterns?.length ?? 0) > 0 || (world.kernel?.output_boundaries?.forbidden_patterns?.length ?? 0) > 0;
3219
+ if (!hasGuards && !hasKernel) return;
3220
+ const guards = world.guards?.guards ?? [];
3221
+ const vocabEntries = world.guards?.intent_vocabulary ?? {};
3222
+ const kernelInput = world.kernel?.input_boundaries?.forbidden_patterns ?? [];
3223
+ const kernelOutput = world.kernel?.output_boundaries?.forbidden_patterns ?? [];
3224
+ const allKernelRules = [...kernelInput, ...kernelOutput];
3225
+ const guardSearchTexts = guards.map((g) => {
3226
+ const parts = [];
3227
+ for (const patternKey of g.intent_patterns) {
3228
+ parts.push(patternKey.toLowerCase());
3229
+ const vocab = vocabEntries[patternKey];
3230
+ if (vocab) {
3231
+ parts.push(vocab.label.toLowerCase());
3232
+ parts.push(vocab.pattern.toLowerCase());
3233
+ }
3234
+ }
3235
+ parts.push(g.description.toLowerCase());
3236
+ return { guard: g, text: parts.join(" ") };
3237
+ });
3238
+ const kernelSearchTexts = allKernelRules.map((k) => ({
3239
+ rule: k,
3240
+ text: `${k.id} ${k.reason} ${k.pattern ?? ""}`.toLowerCase()
3241
+ }));
3242
+ for (const invariant of world.invariants) {
3243
+ if (invariant.enforcement === "prompt") continue;
3244
+ const tokens = extractActionTokens(invariant.id, invariant.label);
3245
+ if (tokens.length === 0) continue;
3246
+ const coveringGuards = guardSearchTexts.filter((gs) => {
3247
+ const enabled = gs.guard.immutable || gs.guard.default_enabled !== false;
3248
+ if (!enabled) return false;
3249
+ return tokens.some((token) => gs.text.includes(token));
3250
+ });
3251
+ const coveringKernel = kernelSearchTexts.filter(
3252
+ (ks) => tokens.some((token) => ks.text.includes(token))
3253
+ );
3254
+ const hasStructuralGuard = guards.some(
3255
+ (g) => g.invariant_ref === invariant.id && g.immutable
3256
+ );
3257
+ if (coveringGuards.length === 0 && coveringKernel.length === 0) {
3258
+ if (hasStructuralGuard) {
3259
+ findings.push(finding(
3260
+ `weak-coverage-${invariant.id}`,
3261
+ `Invariant "${invariant.id}" has a structural guard but no guard's intent patterns match its action class [${tokens.join(", ")}] \u2014 the guard may not intercept violations`,
3262
+ "warning",
3263
+ "guard-coverage",
3264
+ ["invariants.json", "guards.json"],
3265
+ invariant.id,
3266
+ `Ensure the backing guard's intent_patterns include patterns that can detect "${invariant.label}"`
3267
+ ));
3268
+ } else {
3269
+ findings.push(finding(
3270
+ `unenforced-invariant-${invariant.id}`,
3271
+ `Invariant "${invariant.id}" has no guard or kernel rule capable of enforcing it \u2014 no interceptor matches action class [${tokens.join(", ")}]`,
3272
+ "warning",
3273
+ "guard-coverage",
3274
+ ["invariants.json", "guards.json"],
3275
+ invariant.id,
3276
+ `Add a guard with intent_patterns that can intercept "${invariant.label}", or add a kernel forbidden_pattern`
3277
+ ));
3278
+ }
3279
+ }
3280
+ }
3281
+ }
3282
+ function extractActionTokens(id, label) {
3283
+ const stopWords = /* @__PURE__ */ new Set([
3284
+ "a",
3285
+ "an",
3286
+ "the",
3287
+ "is",
3288
+ "are",
3289
+ "was",
3290
+ "were",
3291
+ "be",
3292
+ "been",
3293
+ "being",
3294
+ "have",
3295
+ "has",
3296
+ "had",
3297
+ "do",
3298
+ "does",
3299
+ "did",
3300
+ "will",
3301
+ "would",
3302
+ "could",
3303
+ "should",
3304
+ "may",
3305
+ "might",
3306
+ "must",
3307
+ "shall",
3308
+ "can",
3309
+ "need",
3310
+ "dare",
3311
+ "to",
3312
+ "of",
3313
+ "in",
3314
+ "for",
3315
+ "on",
3316
+ "with",
3317
+ "at",
3318
+ "by",
3319
+ "from",
3320
+ "as",
3321
+ "into",
3322
+ "through",
3323
+ "during",
3324
+ "before",
3325
+ "after",
3326
+ "above",
3327
+ "below",
3328
+ "between",
3329
+ "out",
3330
+ "off",
3331
+ "over",
3332
+ "under",
3333
+ "again",
3334
+ "further",
3335
+ "then",
3336
+ "once",
3337
+ "that",
3338
+ "than",
3339
+ "too",
3340
+ "very",
3341
+ "just",
3342
+ "only",
3343
+ "not",
3344
+ "no",
3345
+ "all",
3346
+ "any",
3347
+ "both",
3348
+ "each",
3349
+ "every",
3350
+ "few",
3351
+ "more",
3352
+ "most",
3353
+ "other",
3354
+ "some",
3355
+ "such",
3356
+ "and",
3357
+ "but",
3358
+ "or",
3359
+ "nor",
3360
+ "so",
3361
+ "yet",
3362
+ "if",
3363
+ "it",
3364
+ "its",
3365
+ "they",
3366
+ "them",
3367
+ "their",
3368
+ "this",
3369
+ "these",
3370
+ "those",
3371
+ "which",
3372
+ "who",
3373
+ "whom",
3374
+ "what",
3375
+ "where",
3376
+ "when",
3377
+ "how",
3378
+ "why"
3379
+ ]);
3380
+ const idTokens = id.toLowerCase().split(/[_\-]+/);
3381
+ const labelTokens = label.toLowerCase().split(/[\s\-—:,;.!?()[\]{}]+/);
3382
+ const allTokens = [...idTokens, ...labelTokens];
3383
+ const unique = /* @__PURE__ */ new Set();
3384
+ for (const token of allTokens) {
3385
+ const clean = token.replace(/[^a-z0-9]/g, "");
3386
+ if (clean.length >= 3 && !stopWords.has(clean)) {
3387
+ unique.add(clean);
3388
+ }
3389
+ }
3390
+ return [...unique];
3391
+ }
1660
3392
  function checkContradictions(world, findings) {
1661
3393
  if (!world.rules || world.rules.length < 2) return;
1662
3394
  checkCircularExclusiveWith(world.rules, findings);
@@ -1862,6 +3594,251 @@ function describeEffect(effect) {
1862
3594
  return `${effect.operation} ${effect.value}`;
1863
3595
  }
1864
3596
  }
3597
+ function checkGuardShadows(world, findings) {
3598
+ if (!world.guards?.guards || world.guards.guards.length < 2) return;
3599
+ const guards = world.guards.guards;
3600
+ for (let i = 0; i < guards.length; i++) {
3601
+ const guardA = guards[i];
3602
+ const enabledA = guardA.immutable || guardA.default_enabled !== false;
3603
+ if (!enabledA) continue;
3604
+ if (guardA.enforcement !== "block" && guardA.enforcement !== "pause") continue;
3605
+ for (let j = i + 1; j < guards.length; j++) {
3606
+ const guardB = guards[j];
3607
+ const enabledB = guardB.immutable || guardB.default_enabled !== false;
3608
+ if (!enabledB) continue;
3609
+ const overlap = guardA.intent_patterns.filter(
3610
+ (p) => guardB.intent_patterns.includes(p)
3611
+ );
3612
+ if (overlap.length === 0) continue;
3613
+ if (guardA.appliesTo?.length && guardB.appliesTo?.length) {
3614
+ const toolsA = new Set(guardA.appliesTo.map((t) => t.toLowerCase()));
3615
+ const toolsB = new Set(guardB.appliesTo.map((t) => t.toLowerCase()));
3616
+ const toolOverlap = [...toolsA].some((t) => toolsB.has(t));
3617
+ if (!toolOverlap) continue;
3618
+ }
3619
+ if (guardA.required_roles?.length && guardB.required_roles?.length) {
3620
+ const rolesA = new Set(guardA.required_roles);
3621
+ const rolesB = new Set(guardB.required_roles);
3622
+ const roleOverlap = [...rolesA].some((r) => rolesB.has(r));
3623
+ if (!roleOverlap) continue;
3624
+ }
3625
+ const patternsStr = overlap.join(", ");
3626
+ if (guardB.enforcement === guardA.enforcement) {
3627
+ findings.push(finding(
3628
+ `guard-shadow-${guardA.id}-${guardB.id}`,
3629
+ `Guard "${guardB.label}" (${guardB.id}) is shadowed by "${guardA.label}" (${guardA.id}) \u2014 both ${guardA.enforcement.toUpperCase()} on patterns [${patternsStr}] but "${guardA.label}" appears first and will always win`,
3630
+ "warning",
3631
+ "contradiction",
3632
+ ["guards/"],
3633
+ `${guardA.id}, ${guardB.id}`,
3634
+ `Remove "${guardB.label}", merge its patterns into "${guardA.label}", or reorder guards`
3635
+ ));
3636
+ } else {
3637
+ findings.push(finding(
3638
+ `guard-conflict-${guardA.id}-${guardB.id}`,
3639
+ `Guards "${guardA.label}" (${guardA.enforcement.toUpperCase()}) and "${guardB.label}" (${guardB.enforcement.toUpperCase()}) share patterns [${patternsStr}] \u2014 "${guardA.label}" always wins because it appears first`,
3640
+ "warning",
3641
+ "contradiction",
3642
+ ["guards/"],
3643
+ `${guardA.id}, ${guardB.id}`,
3644
+ `If "${guardB.label}" should take precedence, move it before "${guardA.label}" in guards.json`
3645
+ ));
3646
+ }
3647
+ }
3648
+ }
3649
+ }
3650
+ function checkFailClosedSurfaces(world, findings) {
3651
+ const declaredSurfaces = world.guards?.tool_surfaces;
3652
+ if (!declaredSurfaces || declaredSurfaces.length === 0) return;
3653
+ const guards = world.guards?.guards ?? [];
3654
+ const guardedSurfaces = /* @__PURE__ */ new Set();
3655
+ let hasCatchAllGuard = false;
3656
+ for (const guard of guards) {
3657
+ const enabled = guard.immutable || guard.default_enabled !== false;
3658
+ if (!enabled) continue;
3659
+ if (!guard.appliesTo || guard.appliesTo.length === 0) {
3660
+ hasCatchAllGuard = true;
3661
+ } else {
3662
+ for (const tool of guard.appliesTo) {
3663
+ guardedSurfaces.add(tool.toLowerCase());
3664
+ }
3665
+ }
3666
+ }
3667
+ if (hasCatchAllGuard) return;
3668
+ for (const surface of declaredSurfaces) {
3669
+ if (!guardedSurfaces.has(surface.toLowerCase())) {
3670
+ findings.push(finding(
3671
+ `fail-open-surface-${surface.toLowerCase()}`,
3672
+ `Action surface "${surface}" has no governing guard \u2014 actions on this surface bypass governance entirely`,
3673
+ "warning",
3674
+ "guard-coverage",
3675
+ ["guards.json"],
3676
+ void 0,
3677
+ `Add a guard with appliesTo including "${surface}", or add a catch-all guard (no appliesTo) to cover all surfaces`
3678
+ ));
3679
+ }
3680
+ }
3681
+ }
3682
+ function checkReachability(world, findings) {
3683
+ if (!world.stateSchema?.variables) return;
3684
+ const vars = world.stateSchema.variables;
3685
+ for (const rule of world.rules ?? []) {
3686
+ for (const trigger of rule.triggers) {
3687
+ if (trigger.source !== "state") continue;
3688
+ const unreachable = isTriggerUnreachable(trigger, vars);
3689
+ if (unreachable) {
3690
+ findings.push(finding(
3691
+ `unreachable-rule-${rule.id}-${trigger.field}`,
3692
+ `Rule "${rule.id}" has unreachable trigger: ${trigger.field} ${trigger.operator} ${JSON.stringify(trigger.value)} \u2014 ${unreachable}`,
3693
+ "warning",
3694
+ "contradiction",
3695
+ ["rules/", "state-schema.json"],
3696
+ rule.id,
3697
+ `Remove this rule or adjust the trigger condition to match the schema constraints for "${trigger.field}"`
3698
+ ));
3699
+ }
3700
+ }
3701
+ if (rule.collapse_check) {
3702
+ const cc = rule.collapse_check;
3703
+ const unreachable = isTriggerUnreachable(
3704
+ { field: cc.field, operator: cc.operator, value: cc.value },
3705
+ vars
3706
+ );
3707
+ if (unreachable) {
3708
+ findings.push(finding(
3709
+ `unreachable-collapse-${rule.id}`,
3710
+ `Rule "${rule.id}" has unreachable collapse_check: ${cc.field} ${cc.operator} ${cc.value} \u2014 ${unreachable}`,
3711
+ "warning",
3712
+ "contradiction",
3713
+ ["rules/", "state-schema.json"],
3714
+ rule.id
3715
+ ));
3716
+ }
3717
+ }
3718
+ }
3719
+ for (const gate of world.gates?.viability_classification ?? []) {
3720
+ const unreachable = isTriggerUnreachable(
3721
+ { field: gate.field, operator: gate.operator, value: gate.value },
3722
+ vars
3723
+ );
3724
+ if (unreachable) {
3725
+ findings.push(finding(
3726
+ `unreachable-gate-${gate.status}`,
3727
+ `Viability gate "${gate.status}" has unreachable condition: ${gate.field} ${gate.operator} ${gate.value} \u2014 ${unreachable}`,
3728
+ "warning",
3729
+ "contradiction",
3730
+ ["gates.json", "state-schema.json"],
3731
+ `gate-${gate.status}`
3732
+ ));
3733
+ }
3734
+ }
3735
+ }
3736
+ function isTriggerUnreachable(trigger, vars) {
3737
+ const variable = vars[trigger.field];
3738
+ if (!variable) return null;
3739
+ const { operator, value } = trigger;
3740
+ if (variable.type === "number") {
3741
+ const numVal = typeof value === "number" ? value : Number(value);
3742
+ if (isNaN(numVal)) return null;
3743
+ const min = variable.min;
3744
+ const max = variable.max;
3745
+ if (operator === ">" || operator === ">=") {
3746
+ if (max !== void 0 && numVal >= max && operator === ">") {
3747
+ return `schema declares max=${max}, so ${trigger.field} can never exceed ${max}`;
3748
+ }
3749
+ if (max !== void 0 && numVal > max && operator === ">=") {
3750
+ return `schema declares max=${max}, so ${trigger.field} can never reach ${numVal}`;
3751
+ }
3752
+ }
3753
+ if (operator === "<" || operator === "<=") {
3754
+ if (min !== void 0 && numVal <= min && operator === "<") {
3755
+ return `schema declares min=${min}, so ${trigger.field} can never go below ${min}`;
3756
+ }
3757
+ if (min !== void 0 && numVal < min && operator === "<=") {
3758
+ return `schema declares min=${min}, so ${trigger.field} can never reach ${numVal}`;
3759
+ }
3760
+ }
3761
+ if (operator === "==") {
3762
+ if (min !== void 0 && numVal < min) {
3763
+ return `schema declares min=${min}, so ${trigger.field} can never equal ${numVal}`;
3764
+ }
3765
+ if (max !== void 0 && numVal > max) {
3766
+ return `schema declares max=${max}, so ${trigger.field} can never equal ${numVal}`;
3767
+ }
3768
+ }
3769
+ }
3770
+ if (variable.type === "enum" && variable.options) {
3771
+ if (operator === "==" && typeof value === "string") {
3772
+ if (!variable.options.includes(value)) {
3773
+ return `"${value}" is not in enum options [${variable.options.join(", ")}]`;
3774
+ }
3775
+ }
3776
+ if (operator === "!=" && typeof value === "string") {
3777
+ if (variable.options.length === 1 && variable.options[0] === value) {
3778
+ return `enum has only option "${value}", so != "${value}" can never be true`;
3779
+ }
3780
+ }
3781
+ if (operator === "in" && Array.isArray(value)) {
3782
+ const validValues = value.filter((v) => variable.options.includes(v));
3783
+ if (validValues.length === 0) {
3784
+ return `none of [${value.join(", ")}] are in enum options [${variable.options.join(", ")}]`;
3785
+ }
3786
+ }
3787
+ }
3788
+ if (variable.type === "boolean") {
3789
+ if (operator === "==" && typeof value !== "boolean" && value !== "true" && value !== "false") {
3790
+ return `boolean variable compared to non-boolean value "${value}"`;
3791
+ }
3792
+ }
3793
+ return null;
3794
+ }
3795
+ function checkStateCoverage(world, findings) {
3796
+ if (!world.stateSchema?.variables) return;
3797
+ const vars = world.stateSchema.variables;
3798
+ for (const [varId, variable] of Object.entries(vars)) {
3799
+ if (variable.type !== "enum" || !variable.options || variable.options.length <= 1) continue;
3800
+ const allOptions = new Set(variable.options);
3801
+ const coveredOptions = /* @__PURE__ */ new Set();
3802
+ for (const rule of world.rules ?? []) {
3803
+ for (const trigger of rule.triggers) {
3804
+ if (trigger.field !== varId || trigger.source !== "state") continue;
3805
+ if (trigger.operator === "==" && typeof trigger.value === "string") {
3806
+ coveredOptions.add(trigger.value);
3807
+ }
3808
+ if (trigger.operator === "in" && Array.isArray(trigger.value)) {
3809
+ for (const v of trigger.value) coveredOptions.add(v);
3810
+ }
3811
+ if (trigger.operator === "!=") {
3812
+ for (const opt of allOptions) {
3813
+ if (opt !== trigger.value) coveredOptions.add(opt);
3814
+ }
3815
+ }
3816
+ }
3817
+ }
3818
+ for (const gate of world.gates?.viability_classification ?? []) {
3819
+ if (gate.field !== varId) continue;
3820
+ if (gate.operator === "==" && typeof gate.value === "string") {
3821
+ coveredOptions.add(gate.value);
3822
+ }
3823
+ if (gate.operator === "in" && Array.isArray(gate.value)) {
3824
+ for (const v of gate.value) coveredOptions.add(v);
3825
+ }
3826
+ }
3827
+ if (coveredOptions.size === 0) continue;
3828
+ const uncovered = [...allOptions].filter((opt) => !coveredOptions.has(opt));
3829
+ if (uncovered.length > 0 && uncovered.length < allOptions.size) {
3830
+ findings.push(finding(
3831
+ `incomplete-state-coverage-${varId}`,
3832
+ `Enum variable "${varId}" has ${uncovered.length} uncovered state${uncovered.length > 1 ? "s" : ""}: [${uncovered.join(", ")}] \u2014 rules/gates handle [${[...coveredOptions].join(", ")}] but not all ${allOptions.size} declared options`,
3833
+ "warning",
3834
+ "guard-coverage",
3835
+ ["state-schema.json", "rules/", "gates.json"],
3836
+ varId,
3837
+ `Add rules or gates that handle ${uncovered.map((u) => `"${u}"`).join(", ")} for variable "${varId}"`
3838
+ ));
3839
+ }
3840
+ }
3841
+ }
1865
3842
  function checkOrphans(world, findings) {
1866
3843
  if (!world.stateSchema?.variables || !world.rules) return;
1867
3844
  const referencedVars = /* @__PURE__ */ new Set();
@@ -2023,6 +4000,57 @@ function computeInvariantCoverage(world) {
2023
4000
  }
2024
4001
  return Math.round(covered / world.invariants.length * 100);
2025
4002
  }
4003
+ function computeGovernanceHealth(world, findings) {
4004
+ const guards = world.guards?.guards ?? [];
4005
+ if (guards.length === 0 && !world.kernel) return void 0;
4006
+ const declaredSurfaces = world.guards?.tool_surfaces ?? [];
4007
+ const guardedSurfaces = /* @__PURE__ */ new Set();
4008
+ let hasCatchAll = false;
4009
+ for (const guard of guards) {
4010
+ const enabled = guard.immutable || guard.default_enabled !== false;
4011
+ if (!enabled) continue;
4012
+ if (!guard.appliesTo || guard.appliesTo.length === 0) {
4013
+ hasCatchAll = true;
4014
+ } else {
4015
+ for (const t of guard.appliesTo) guardedSurfaces.add(t.toLowerCase());
4016
+ }
4017
+ }
4018
+ const allSurfaces = /* @__PURE__ */ new Set();
4019
+ for (const s of declaredSurfaces) allSurfaces.add(s.toLowerCase());
4020
+ for (const s of guardedSurfaces) allSurfaces.add(s);
4021
+ const surfaces = [...allSurfaces].map((name) => ({
4022
+ name,
4023
+ governed: hasCatchAll || guardedSurfaces.has(name)
4024
+ }));
4025
+ const surfacesCovered = hasCatchAll ? allSurfaces.size : guardedSurfaces.size;
4026
+ const structuralInvariants = (world.invariants ?? []).filter((i) => i.enforcement === "structural");
4027
+ let invariantsEnforced = 0;
4028
+ for (const inv of structuralInvariants) {
4029
+ const hasGuard = guards.some((g) => g.invariant_ref === inv.id && g.immutable);
4030
+ if (hasGuard) invariantsEnforced++;
4031
+ }
4032
+ const shadowedGuards = findings.filter((f) => f.id.startsWith("guard-shadow-")).length;
4033
+ const unenforcedInvariants = findings.filter((f) => f.id.startsWith("unenforced-invariant-")).length;
4034
+ const unreachableRules = findings.filter((f) => f.id.startsWith("unreachable-")).length;
4035
+ const incompleteStateCoverage = findings.filter((f) => f.id.startsWith("incomplete-state-coverage-")).length;
4036
+ const failOpenCount = findings.filter((f) => f.id.startsWith("fail-open-surface-")).length;
4037
+ let riskLevel = "low";
4038
+ const totalIssues = unenforcedInvariants + failOpenCount + incompleteStateCoverage;
4039
+ if (totalIssues > 0 || unreachableRules > 0) riskLevel = "moderate";
4040
+ if (totalIssues > 2 || unenforcedInvariants > 0 && failOpenCount > 0 || incompleteStateCoverage > 2) riskLevel = "high";
4041
+ return {
4042
+ surfacesCovered,
4043
+ surfacesTotal: allSurfaces.size,
4044
+ surfaces,
4045
+ invariantsEnforced,
4046
+ invariantsTotal: structuralInvariants.length,
4047
+ shadowedGuards,
4048
+ unenforcedInvariants,
4049
+ unreachableRules,
4050
+ incompleteStateCoverage,
4051
+ riskLevel
4052
+ };
4053
+ }
2026
4054
  function finding(id, message, severity, category, affectedBlocks, source, suggestion) {
2027
4055
  const f = { id, message, severity, category, affectedBlocks };
2028
4056
  if (source) f.source = source;
@@ -2077,7 +4105,7 @@ function splitSections(markdown) {
2077
4105
  }
2078
4106
  return { frontmatter, sections };
2079
4107
  }
2080
- function parseFrontmatter(yaml, issues) {
4108
+ function parseFrontmatter2(yaml, issues) {
2081
4109
  const result = {};
2082
4110
  for (const line of yaml.split("\n")) {
2083
4111
  const trimmed = line.trim();
@@ -2426,7 +4454,7 @@ function parseValueLiteral(raw) {
2426
4454
  function parseWorldMarkdown(markdown) {
2427
4455
  const issues = [];
2428
4456
  const { frontmatter: fmRaw, sections } = splitSections(markdown);
2429
- const frontmatter = parseFrontmatter(fmRaw, issues);
4457
+ const frontmatter = parseFrontmatter2(fmRaw, issues);
2430
4458
  const findSection = (name) => sections.find((s) => s.name.toLowerCase() === name.toLowerCase());
2431
4459
  const thesisSection = findSection("Thesis");
2432
4460
  const thesis = thesisSection ? parseThesis(thesisSection.content, thesisSection.startLine, issues) : "";
@@ -2831,12 +4859,12 @@ function normalizeWorldMarkdown(markdown) {
2831
4859
 
2832
4860
  // src/engine/derive-prompt.ts
2833
4861
  var import_promises = require("fs/promises");
2834
- var import_path = require("path");
4862
+ var import_path3 = require("path");
2835
4863
  var import_meta = {};
2836
4864
  var WORLD_FILENAME = "derivation-world.nv-world.md";
2837
4865
  function getModuleDir() {
2838
4866
  try {
2839
- return (0, import_path.dirname)(new URL(import_meta.url).pathname);
4867
+ return (0, import_path3.dirname)(new URL(import_meta.url).pathname);
2840
4868
  } catch {
2841
4869
  return __dirname;
2842
4870
  }
@@ -2844,8 +4872,8 @@ function getModuleDir() {
2844
4872
  async function loadDerivationWorld() {
2845
4873
  const moduleDir = getModuleDir();
2846
4874
  const candidates = [
2847
- (0, import_path.join)(moduleDir, "..", "worlds", WORLD_FILENAME),
2848
- (0, import_path.join)(moduleDir, "worlds", WORLD_FILENAME)
4875
+ (0, import_path3.join)(moduleDir, "..", "worlds", WORLD_FILENAME),
4876
+ (0, import_path3.join)(moduleDir, "worlds", WORLD_FILENAME)
2849
4877
  ];
2850
4878
  for (const candidate of candidates) {
2851
4879
  try {
@@ -3120,15 +5148,15 @@ function createProvider(config) {
3120
5148
 
3121
5149
  // src/providers/config-manager.ts
3122
5150
  var import_promises2 = require("fs/promises");
3123
- var import_path2 = require("path");
5151
+ var import_path4 = require("path");
3124
5152
  var import_os = require("os");
3125
5153
  function getConfigDir() {
3126
5154
  const xdg = process.env.XDG_CONFIG_HOME;
3127
- if (xdg) return (0, import_path2.join)(xdg, "neuroverse");
3128
- return (0, import_path2.join)((0, import_os.homedir)(), ".neuroverse");
5155
+ if (xdg) return (0, import_path4.join)(xdg, "neuroverse");
5156
+ return (0, import_path4.join)((0, import_os.homedir)(), ".neuroverse");
3129
5157
  }
3130
5158
  function getConfigPath() {
3131
- return (0, import_path2.join)(getConfigDir(), "config.json");
5159
+ return (0, import_path4.join)(getConfigDir(), "config.json");
3132
5160
  }
3133
5161
  async function loadConfig() {
3134
5162
  try {
@@ -4197,12 +6225,21 @@ var CONFIGURE_AI_EXIT_CODES = {
4197
6225
  DERIVE_EXIT_CODES,
4198
6226
  FileAuditLogger,
4199
6227
  GUARD_EXIT_CODES,
6228
+ McpGovernanceServer,
6229
+ ModelAdapter,
6230
+ PLAN_EXIT_CODES,
6231
+ PROVIDERS,
6232
+ SessionManager,
4200
6233
  VALIDATE_EXIT_CODES,
6234
+ advancePlan,
6235
+ buildPlanCheck,
4201
6236
  createGovernanceEngine,
4202
6237
  deriveWorld,
6238
+ describeActiveWorld,
4203
6239
  emitWorldDefinition,
4204
6240
  evaluateCondition,
4205
6241
  evaluateGuard,
6242
+ evaluatePlan,
4206
6243
  eventToAllowlistKey,
4207
6244
  explainWorld,
4208
6245
  extractWorldMarkdown,
@@ -4210,16 +6247,25 @@ var CONFIGURE_AI_EXIT_CODES = {
4210
6247
  formatVerdictOneLine,
4211
6248
  generateImpactReport,
4212
6249
  generateImpactReportFromFile,
6250
+ getActiveWorldName,
6251
+ getPlanProgress,
4213
6252
  improveWorld,
6253
+ listWorlds,
4214
6254
  loadWorld,
4215
6255
  loadWorldFromDirectory,
4216
6256
  normalizeWorldMarkdown,
6257
+ parsePlanMarkdown,
4217
6258
  parseWorldMarkdown,
4218
6259
  readAuditLog,
4219
6260
  renderExplainText,
4220
6261
  renderImpactReport,
4221
6262
  renderImproveText,
4222
6263
  renderSimulateText,
6264
+ resolveProvider,
6265
+ resolveWorldPath,
6266
+ runInteractiveMode,
6267
+ runPipeMode,
6268
+ setActiveWorld,
4223
6269
  simulateWorld,
4224
6270
  summarizeAuditEvents,
4225
6271
  validateWorld,