@neuroverseos/governance 0.1.5 → 0.2.1

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 +301 -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 +267 -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 +267 -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 +267 -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-FYPYZFV5.js → chunk-2JQJ5U5X.js} +1 -1
  25. package/dist/chunk-37JG24WH.js +161 -0
  26. package/dist/chunk-5EDDNJU6.js +321 -0
  27. package/dist/{chunk-O5OMJMIE.js → chunk-7P3S7MAY.js} +502 -2
  28. package/dist/chunk-A5W4GNQO.js +130 -0
  29. package/dist/{chunk-ITJ3LCPG.js → chunk-ADV7Q2LJ.js} +1 -1
  30. package/dist/chunk-AKW5YVCE.js +96 -0
  31. package/dist/{chunk-EIUHJXBB.js → chunk-GR6DGCZ2.js} +1 -1
  32. package/dist/{chunk-EQXFOKH2.js → chunk-IVPKFJX3.js} +24 -3
  33. package/dist/{chunk-D7BGWV2J.js → chunk-NF5POFCI.js} +5 -3
  34. package/dist/chunk-OT6PXH54.js +61 -0
  35. package/dist/chunk-P74Y66ZV.js +205 -0
  36. package/dist/chunk-PAX2P6ZP.js +601 -0
  37. package/dist/{chunk-B4NF3OLW.js → chunk-PQBJBVSW.js} +56 -2
  38. package/dist/{chunk-T4X42QXC.js → chunk-Q6O7ZLO2.js} +0 -59
  39. package/dist/{chunk-FZQCRGUU.js → chunk-TINSRYXQ.js} +24 -3
  40. package/dist/{chunk-CROPZ75A.js → chunk-UPJNTSVM.js} +24 -3
  41. package/dist/chunk-YZFATT7X.js +9 -0
  42. package/dist/{chunk-Z2S2HIV5.js → chunk-ZL4AHY4X.js} +2 -2
  43. package/dist/cli/neuroverse.cjs +5287 -740
  44. package/dist/cli/neuroverse.js +69 -13
  45. package/dist/cli/plan.cjs +1554 -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 +346 -0
  49. package/dist/cli/run.cjs +1716 -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-V72UM2TC.js +170 -0
  56. package/dist/{explain-3B3VB6TL.js → explain-IDCRWMPX.js} +2 -1
  57. package/dist/{guard-67Y66P3I.js → guard-WA3FCCIO.js} +20 -6
  58. package/dist/{guard-contract-D_RQz9kt.d.ts → guard-contract-D-2LQInm.d.cts} +144 -2
  59. package/dist/{guard-contract-D_RQz9kt.d.cts → guard-contract-D-2LQInm.d.ts} +144 -2
  60. package/dist/guard-engine-D7X4CVAE.js +10 -0
  61. package/dist/{impact-CHERK3O6.js → impact-BWULZ5RP.js} +5 -3
  62. package/dist/{improve-YG6I6ERG.js → improve-GPUBKTEA.js} +4 -3
  63. package/dist/index.cjs +2095 -89
  64. package/dist/index.d.cts +466 -12
  65. package/dist/index.d.ts +466 -12
  66. package/dist/index.js +70 -20
  67. package/dist/{init-Z66T6TDI.js → init-PKPIYHYE.js} +2 -0
  68. package/dist/mcp-server-YUOQP4M5.js +13 -0
  69. package/dist/model-adapter-BB7G4MFI.js +11 -0
  70. package/dist/playground-CBXMAW2B.js +550 -0
  71. package/dist/redteam-SSNABQ7W.js +357 -0
  72. package/dist/session-MWRBTCYX.js +14 -0
  73. package/dist/{simulate-ETHHINZ4.js → simulate-VDOYQFRO.js} +2 -1
  74. package/dist/test-3GZSG5FR.js +217 -0
  75. package/dist/{trace-3YODSSIP.js → trace-TM4Z7G73.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,205 @@ __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) {
196
+ return {
197
+ ...plan,
198
+ steps: plan.steps.map(
199
+ (s) => s.id === stepId ? { ...s, status: "completed" } : s
200
+ )
201
+ };
202
+ }
203
+ function evaluatePlan(event, plan) {
204
+ const progress = getPlanProgress(plan);
205
+ if (plan.expires_at) {
206
+ const expiresAt = new Date(plan.expires_at).getTime();
207
+ if (Date.now() > expiresAt) {
208
+ return {
209
+ allowed: true,
210
+ status: "PLAN_COMPLETE",
211
+ reason: "Plan has expired.",
212
+ progress
213
+ };
214
+ }
215
+ }
216
+ if (progress.completed === progress.total) {
217
+ return {
218
+ allowed: true,
219
+ status: "PLAN_COMPLETE",
220
+ reason: "All plan steps are completed.",
221
+ progress
222
+ };
223
+ }
224
+ const eventText = [
225
+ event.intent,
226
+ event.tool ?? "",
227
+ event.scope ?? ""
228
+ ].join(" ").toLowerCase();
229
+ const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
230
+ if (!matched) {
231
+ return {
232
+ allowed: false,
233
+ status: "OFF_PLAN",
234
+ reason: "Action does not match any plan step.",
235
+ closestStep: closest?.label,
236
+ similarityScore: closestScore,
237
+ progress
238
+ };
239
+ }
240
+ if (!isSequenceValid(matched, plan)) {
241
+ const pendingDeps = (matched.requires ?? []).filter((reqId) => plan.steps.find((s) => s.id === reqId)?.status !== "completed").join(", ");
242
+ return {
243
+ allowed: false,
244
+ status: "OFF_PLAN",
245
+ reason: `Step "${matched.label}" requires completion of: ${pendingDeps}`,
246
+ matchedStep: matched.id,
247
+ progress
248
+ };
249
+ }
250
+ const { violated } = checkConstraints(event, eventText, plan.constraints);
251
+ if (violated) {
252
+ return {
253
+ allowed: false,
254
+ status: "CONSTRAINT_VIOLATED",
255
+ reason: violated.description,
256
+ matchedStep: matched.id,
257
+ progress
258
+ };
259
+ }
260
+ return {
261
+ allowed: true,
262
+ status: "ON_PLAN",
263
+ reason: `Matches step: ${matched.label}`,
264
+ matchedStep: matched.id,
265
+ progress
266
+ };
267
+ }
268
+ function buildPlanCheck(event, plan, verdict) {
269
+ const eventText = [event.intent, event.tool ?? "", event.scope ?? ""].join(" ").toLowerCase();
270
+ const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
271
+ const { checks: constraintChecks } = checkConstraints(event, eventText, plan.constraints);
272
+ const progress = getPlanProgress(plan);
273
+ return {
274
+ planId: plan.plan_id,
275
+ matched: !!matched,
276
+ matchedStepId: matched?.id,
277
+ matchedStepLabel: matched?.label,
278
+ closestStepId: !matched ? closest?.id : void 0,
279
+ closestStepLabel: !matched ? closest?.label : void 0,
280
+ similarityScore: !matched ? closestScore : void 0,
281
+ sequenceValid: matched ? isSequenceValid(matched, plan) : void 0,
282
+ constraintsChecked: constraintChecks,
283
+ progress: { completed: progress.completed, total: progress.total }
284
+ };
285
+ }
286
+
70
287
  // src/engine/guard-engine.ts
71
288
  var PROMPT_INJECTION_PATTERNS = [
72
289
  // Instruction override
@@ -158,6 +375,7 @@ function evaluateGuard(event, world, options = {}) {
158
375
  const eventText = (event.intent + " " + (event.tool ?? "") + " " + (event.scope ?? "")).toLowerCase();
159
376
  const invariantChecks = [];
160
377
  const safetyChecks = [];
378
+ let planCheckResult;
161
379
  const roleChecks = [];
162
380
  const guardChecks = [];
163
381
  const kernelRuleChecks = [];
@@ -185,6 +403,7 @@ function evaluateGuard(event, world, options = {}) {
185
403
  includeTrace ? buildTrace(
186
404
  invariantChecks,
187
405
  safetyChecks,
406
+ planCheckResult,
188
407
  roleChecks,
189
408
  guardChecks,
190
409
  kernelRuleChecks,
@@ -213,6 +432,7 @@ function evaluateGuard(event, world, options = {}) {
213
432
  includeTrace ? buildTrace(
214
433
  invariantChecks,
215
434
  safetyChecks,
435
+ planCheckResult,
216
436
  roleChecks,
217
437
  guardChecks,
218
438
  kernelRuleChecks,
@@ -223,6 +443,42 @@ function evaluateGuard(event, world, options = {}) {
223
443
  ) : void 0
224
444
  );
225
445
  }
446
+ if (options.plan) {
447
+ const planVerdict = evaluatePlan(event, options.plan);
448
+ planCheckResult = buildPlanCheck(event, options.plan, planVerdict);
449
+ if (!planVerdict.allowed && planVerdict.status !== "PLAN_COMPLETE") {
450
+ decidingLayer = "plan-enforcement";
451
+ decidingId = `plan-${options.plan.plan_id}`;
452
+ const planStatus = planVerdict.status === "CONSTRAINT_VIOLATED" ? "PAUSE" : "BLOCK";
453
+ let reason = planVerdict.reason ?? "Action blocked by plan.";
454
+ if (planVerdict.status === "OFF_PLAN" && planVerdict.closestStep) {
455
+ reason += ` Closest step: "${planVerdict.closestStep}" (similarity: ${(planVerdict.similarityScore ?? 0).toFixed(2)})`;
456
+ }
457
+ return buildVerdict(
458
+ planStatus,
459
+ reason,
460
+ `plan-${options.plan.plan_id}`,
461
+ void 0,
462
+ world,
463
+ level,
464
+ invariantChecks,
465
+ guardsMatched,
466
+ rulesMatched,
467
+ includeTrace ? buildTrace(
468
+ invariantChecks,
469
+ safetyChecks,
470
+ planCheckResult,
471
+ roleChecks,
472
+ guardChecks,
473
+ kernelRuleChecks,
474
+ levelChecks,
475
+ decidingLayer,
476
+ decidingId,
477
+ startTime
478
+ ) : void 0
479
+ );
480
+ }
481
+ }
226
482
  const roleVerdict = checkRoleRules(event, eventText, world, roleChecks);
227
483
  if (roleVerdict) {
228
484
  decidingLayer = "role";
@@ -240,6 +496,7 @@ function evaluateGuard(event, world, options = {}) {
240
496
  includeTrace ? buildTrace(
241
497
  invariantChecks,
242
498
  safetyChecks,
499
+ planCheckResult,
243
500
  roleChecks,
244
501
  guardChecks,
245
502
  kernelRuleChecks,
@@ -268,6 +525,7 @@ function evaluateGuard(event, world, options = {}) {
268
525
  includeTrace ? buildTrace(
269
526
  invariantChecks,
270
527
  safetyChecks,
528
+ planCheckResult,
271
529
  roleChecks,
272
530
  guardChecks,
273
531
  kernelRuleChecks,
@@ -296,6 +554,7 @@ function evaluateGuard(event, world, options = {}) {
296
554
  includeTrace ? buildTrace(
297
555
  invariantChecks,
298
556
  safetyChecks,
557
+ planCheckResult,
299
558
  roleChecks,
300
559
  guardChecks,
301
560
  kernelRuleChecks,
@@ -323,6 +582,7 @@ function evaluateGuard(event, world, options = {}) {
323
582
  includeTrace ? buildTrace(
324
583
  invariantChecks,
325
584
  safetyChecks,
585
+ planCheckResult,
326
586
  roleChecks,
327
587
  guardChecks,
328
588
  kernelRuleChecks,
@@ -347,6 +607,7 @@ function evaluateGuard(event, world, options = {}) {
347
607
  includeTrace ? buildTrace(
348
608
  invariantChecks,
349
609
  safetyChecks,
610
+ planCheckResult,
350
611
  roleChecks,
351
612
  guardChecks,
352
613
  kernelRuleChecks,
@@ -684,8 +945,8 @@ function matchesKeywords(eventText, ruleText) {
684
945
  function eventToAllowlistKey(event) {
685
946
  return `${(event.tool ?? "*").toLowerCase()}::${event.intent.toLowerCase().trim()}`;
686
947
  }
687
- function buildTrace(invariantChecks, safetyChecks, roleChecks, guardChecks, kernelRuleChecks, levelChecks, decidingLayer, decidingId, startTime) {
688
- return {
948
+ function buildTrace(invariantChecks, safetyChecks, planCheck, roleChecks, guardChecks, kernelRuleChecks, levelChecks, decidingLayer, decidingId, startTime) {
949
+ const trace = {
689
950
  invariantChecks,
690
951
  safetyChecks,
691
952
  roleChecks,
@@ -703,6 +964,7 @@ function buildTrace(invariantChecks, safetyChecks, roleChecks, guardChecks, kern
703
964
  "safety-scope-escape",
704
965
  "safety-execution-claim",
705
966
  "safety-execution-intent",
967
+ "plan-enforcement",
706
968
  "role-rules",
707
969
  "declarative-guards",
708
970
  "kernel-rules",
@@ -712,6 +974,10 @@ function buildTrace(invariantChecks, safetyChecks, roleChecks, guardChecks, kern
712
974
  },
713
975
  durationMs: performance.now() - startTime
714
976
  };
977
+ if (planCheck) {
978
+ trace.planCheck = planCheck;
979
+ }
980
+ return trace;
715
981
  }
716
982
  function buildVerdict(status, reason, ruleId, warning, world, level, invariantChecks, guardsMatched, rulesMatched, trace) {
717
983
  const evidence = {
@@ -744,6 +1010,1303 @@ var GUARD_EXIT_CODES = {
744
1010
  ERROR: 3
745
1011
  };
746
1012
 
1013
+ // src/engine/plan-parser.ts
1014
+ function slugify(text) {
1015
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+/g, "_");
1016
+ }
1017
+ function extractBracketAnnotation(line, key) {
1018
+ const regex = new RegExp(`\\[${key}:\\s*([^\\]]+)\\]`, "i");
1019
+ const match = line.match(regex);
1020
+ if (!match) return null;
1021
+ return match[1].split(",").map((s) => s.trim()).filter(Boolean);
1022
+ }
1023
+ function extractParenAnnotation(line, key) {
1024
+ const regex = new RegExp(`\\(${key}:\\s*([^)]+)\\)`, "i");
1025
+ const match = line.match(regex);
1026
+ if (!match) return null;
1027
+ return match[1].split(",").map((s) => s.trim()).filter(Boolean);
1028
+ }
1029
+ function stripAnnotations(line) {
1030
+ return line.replace(/\[(?:tools|tag|verify|type):\s*[^\]]+\]/gi, "").replace(/\((?:after):\s*[^)]+\)/gi, "").trim();
1031
+ }
1032
+ function parseFrontmatter(content) {
1033
+ const frontmatter = {};
1034
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
1035
+ if (!fmMatch) {
1036
+ return { frontmatter, body: content };
1037
+ }
1038
+ const fmBody = fmMatch[1];
1039
+ for (const line of fmBody.split("\n")) {
1040
+ const colonIndex = line.indexOf(":");
1041
+ if (colonIndex > 0) {
1042
+ const key = line.slice(0, colonIndex).trim();
1043
+ const value = line.slice(colonIndex + 1).trim();
1044
+ frontmatter[key] = value;
1045
+ }
1046
+ }
1047
+ return { frontmatter, body: content.slice(fmMatch[0].length) };
1048
+ }
1049
+ function parseSections(body) {
1050
+ const steps = [];
1051
+ const constraints = [];
1052
+ let currentSection = "none";
1053
+ for (const line of body.split("\n")) {
1054
+ const trimmed = line.trim();
1055
+ if (/^#+\s*Steps/i.test(trimmed)) {
1056
+ currentSection = "steps";
1057
+ continue;
1058
+ }
1059
+ if (/^#+\s*Constraints/i.test(trimmed)) {
1060
+ currentSection = "constraints";
1061
+ continue;
1062
+ }
1063
+ if (/^#+\s/.test(trimmed) && currentSection !== "none") {
1064
+ currentSection = "none";
1065
+ continue;
1066
+ }
1067
+ if (trimmed.startsWith("- ")) {
1068
+ const item = trimmed.slice(2).trim();
1069
+ if (currentSection === "steps") {
1070
+ steps.push(item);
1071
+ } else if (currentSection === "constraints") {
1072
+ constraints.push(item);
1073
+ }
1074
+ }
1075
+ }
1076
+ return { steps, constraints };
1077
+ }
1078
+ function parseStep(raw) {
1079
+ const label = stripAnnotations(raw);
1080
+ const id = slugify(label);
1081
+ const tools = extractBracketAnnotation(raw, "tools");
1082
+ const tags = extractBracketAnnotation(raw, "tag");
1083
+ const verifyArr = extractBracketAnnotation(raw, "verify");
1084
+ const requires = extractParenAnnotation(raw, "after");
1085
+ return {
1086
+ id,
1087
+ label,
1088
+ tools: tools ?? void 0,
1089
+ tags: tags ?? void 0,
1090
+ verify: verifyArr?.[0] ?? void 0,
1091
+ requires: requires ?? void 0,
1092
+ status: "pending"
1093
+ };
1094
+ }
1095
+ function parseConstraint(raw, index) {
1096
+ const typeAnnotation = extractBracketAnnotation(raw, "type");
1097
+ const description = stripAnnotations(raw);
1098
+ const id = `constraint_${index}`;
1099
+ let type = "custom";
1100
+ let enforcement = "block";
1101
+ let limit;
1102
+ let unit;
1103
+ if (typeAnnotation?.[0] === "approval") {
1104
+ type = "approval";
1105
+ enforcement = "pause";
1106
+ } else if (/budget|\$|spending|cost/i.test(description)) {
1107
+ type = "budget";
1108
+ const amountMatch = description.match(/\$?([\d,]+)/);
1109
+ if (amountMatch) {
1110
+ limit = parseInt(amountMatch[1].replace(/,/g, ""), 10);
1111
+ unit = "USD";
1112
+ }
1113
+ } else if (/time|hour|minute|day|deadline/i.test(description)) {
1114
+ type = "time";
1115
+ } else if (/scope|access|database|production/i.test(description)) {
1116
+ type = "scope";
1117
+ }
1118
+ const trigger = description.toLowerCase();
1119
+ return {
1120
+ id,
1121
+ type,
1122
+ description,
1123
+ enforcement,
1124
+ limit,
1125
+ unit,
1126
+ trigger
1127
+ };
1128
+ }
1129
+ function parsePlanMarkdown(markdown) {
1130
+ const errors = [];
1131
+ const { frontmatter, body } = parseFrontmatter(markdown.trim());
1132
+ const { steps: stepLines, constraints: constraintLines } = parseSections(body);
1133
+ if (!frontmatter.plan_id) {
1134
+ errors.push("Missing required field: plan_id");
1135
+ }
1136
+ if (stepLines.length === 0) {
1137
+ errors.push("Plan must have at least one step");
1138
+ }
1139
+ if (errors.length > 0) {
1140
+ return { success: false, errors };
1141
+ }
1142
+ const steps = stepLines.map((line) => parseStep(line));
1143
+ const constraints = constraintLines.map((line, i) => parseConstraint(line, i));
1144
+ let expires_at;
1145
+ if (frontmatter.expires) {
1146
+ expires_at = new Date(frontmatter.expires).toISOString();
1147
+ }
1148
+ const plan = {
1149
+ plan_id: frontmatter.plan_id,
1150
+ objective: frontmatter.objective ?? "",
1151
+ sequential: frontmatter.sequential === "true",
1152
+ steps,
1153
+ constraints,
1154
+ world_id: frontmatter.world ?? void 0,
1155
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1156
+ expires_at
1157
+ };
1158
+ return { success: true, plan, errors: [] };
1159
+ }
1160
+
1161
+ // src/contracts/plan-contract.ts
1162
+ var PLAN_EXIT_CODES = {
1163
+ ON_PLAN: 0,
1164
+ OFF_PLAN: 1,
1165
+ CONSTRAINT_VIOLATED: 2,
1166
+ ERROR: 3,
1167
+ PLAN_COMPLETE: 4
1168
+ };
1169
+
1170
+ // src/loader/world-loader.ts
1171
+ async function loadWorldFromDirectory(dirPath) {
1172
+ const { readFile: readFile3 } = await import("fs/promises");
1173
+ const { join: join5 } = await import("path");
1174
+ const { readdirSync: readdirSync3 } = await import("fs");
1175
+ async function readJson(filename) {
1176
+ try {
1177
+ const content = await readFile3(join5(dirPath, filename), "utf-8");
1178
+ return JSON.parse(content);
1179
+ } catch {
1180
+ return void 0;
1181
+ }
1182
+ }
1183
+ const worldJson = await readJson("world.json");
1184
+ if (!worldJson) {
1185
+ throw new Error(`Cannot read world.json in ${dirPath}`);
1186
+ }
1187
+ const invariantsJson = await readJson("invariants.json");
1188
+ const assumptionsJson = await readJson("assumptions.json");
1189
+ const stateSchemaJson = await readJson("state-schema.json");
1190
+ const gatesJson = await readJson("gates.json");
1191
+ const outcomesJson = await readJson("outcomes.json");
1192
+ const guardsJson = await readJson("guards.json");
1193
+ const rolesJson = await readJson("roles.json");
1194
+ const kernelJson = await readJson("kernel.json");
1195
+ const metadataJson = await readJson("metadata.json");
1196
+ const rules = [];
1197
+ try {
1198
+ const rulesDir = join5(dirPath, "rules");
1199
+ const ruleFiles = readdirSync3(rulesDir).filter((f) => f.endsWith(".json")).sort();
1200
+ for (const file of ruleFiles) {
1201
+ const content = await readFile3(join5(rulesDir, file), "utf-8");
1202
+ rules.push(JSON.parse(content));
1203
+ }
1204
+ } catch {
1205
+ }
1206
+ return {
1207
+ world: worldJson,
1208
+ invariants: invariantsJson?.invariants ?? [],
1209
+ assumptions: assumptionsJson ?? { profiles: {}, parameter_definitions: {} },
1210
+ stateSchema: stateSchemaJson ?? { variables: {}, presets: {} },
1211
+ rules,
1212
+ gates: gatesJson ?? {
1213
+ viability_classification: [],
1214
+ structural_override: { description: "", enforcement: "mandatory" },
1215
+ sustainability_threshold: 0,
1216
+ collapse_visual: { background: "", text: "", border: "", label: "" }
1217
+ },
1218
+ outcomes: outcomesJson ?? {
1219
+ computed_outcomes: [],
1220
+ comparison_layout: { primary_card: "", status_badge: "", structural_indicators: [] }
1221
+ },
1222
+ guards: guardsJson,
1223
+ roles: rolesJson,
1224
+ kernel: kernelJson,
1225
+ metadata: metadataJson ?? {
1226
+ format_version: "1.0.0",
1227
+ created_at: "",
1228
+ last_modified: "",
1229
+ authoring_method: "manual-authoring"
1230
+ }
1231
+ };
1232
+ }
1233
+ async function loadWorld(worldPath) {
1234
+ const { stat } = await import("fs/promises");
1235
+ const info = await stat(worldPath);
1236
+ if (info.isDirectory()) {
1237
+ return loadWorldFromDirectory(worldPath);
1238
+ }
1239
+ if (worldPath.endsWith(".nv-world.zip")) {
1240
+ throw new Error(".nv-world.zip loading not yet implemented \u2014 use a world directory");
1241
+ }
1242
+ throw new Error(`Cannot load world from: ${worldPath} \u2014 expected a directory or .nv-world.zip`);
1243
+ }
1244
+
1245
+ // src/runtime/session.ts
1246
+ async function defaultToolExecutor(name, args) {
1247
+ return `Tool "${name}" executed successfully with args: ${JSON.stringify(args)}`;
1248
+ }
1249
+ var SessionManager = class {
1250
+ config;
1251
+ state;
1252
+ engineOptions;
1253
+ executor;
1254
+ constructor(config) {
1255
+ this.config = config;
1256
+ this.executor = config.toolExecutor ?? defaultToolExecutor;
1257
+ this.engineOptions = {
1258
+ trace: config.trace ?? false,
1259
+ level: config.level,
1260
+ plan: config.plan
1261
+ };
1262
+ this.state = {
1263
+ active: false,
1264
+ world: config.world,
1265
+ plan: config.plan,
1266
+ progress: config.plan ? getPlanProgress(config.plan) : void 0,
1267
+ actionsEvaluated: 0,
1268
+ actionsAllowed: 0,
1269
+ actionsBlocked: 0,
1270
+ actionsPaused: 0
1271
+ };
1272
+ }
1273
+ /**
1274
+ * Initialize the session — load world from disk if needed.
1275
+ */
1276
+ async start() {
1277
+ if (this.config.worldPath && !this.config.world) {
1278
+ this.state.world = await loadWorld(this.config.worldPath);
1279
+ }
1280
+ if (!this.state.world) {
1281
+ throw new Error("No world provided. Use --world or pass a world definition.");
1282
+ }
1283
+ this.state.active = true;
1284
+ return this.getState();
1285
+ }
1286
+ /**
1287
+ * Evaluate a single event against governance.
1288
+ * Returns the verdict without executing anything.
1289
+ */
1290
+ evaluate(event) {
1291
+ this.engineOptions.plan = this.state.plan;
1292
+ const verdict = evaluateGuard(event, this.state.world, this.engineOptions);
1293
+ this.state.actionsEvaluated++;
1294
+ if (verdict.status === "ALLOW") this.state.actionsAllowed++;
1295
+ if (verdict.status === "BLOCK") this.state.actionsBlocked++;
1296
+ if (verdict.status === "PAUSE") this.state.actionsPaused++;
1297
+ this.config.onVerdict?.(verdict, event);
1298
+ return verdict;
1299
+ }
1300
+ /**
1301
+ * Evaluate and execute a tool call.
1302
+ * Returns the execution result or block reason.
1303
+ */
1304
+ async executeToolCall(toolCall) {
1305
+ let args;
1306
+ try {
1307
+ args = JSON.parse(toolCall.function.arguments);
1308
+ } catch {
1309
+ args = { raw: toolCall.function.arguments };
1310
+ }
1311
+ const event = {
1312
+ intent: toolCall.function.name,
1313
+ tool: toolCall.function.name,
1314
+ args,
1315
+ direction: "input"
1316
+ };
1317
+ const verdict = this.evaluate(event);
1318
+ if (verdict.status === "BLOCK") {
1319
+ return { allowed: false, verdict };
1320
+ }
1321
+ if (verdict.status === "PAUSE") {
1322
+ return { allowed: false, verdict };
1323
+ }
1324
+ const result = await this.executor(toolCall.function.name, args);
1325
+ this.config.onToolResult?.(toolCall.function.name, result);
1326
+ if (this.state.plan) {
1327
+ const planVerdict = evaluatePlan(event, this.state.plan);
1328
+ if (planVerdict.matchedStep) {
1329
+ this.state.plan = advancePlan(this.state.plan, planVerdict.matchedStep);
1330
+ this.engineOptions.plan = this.state.plan;
1331
+ this.state.progress = getPlanProgress(this.state.plan);
1332
+ this.config.onPlanProgress?.(this.state.progress);
1333
+ if (this.state.progress.completed === this.state.progress.total) {
1334
+ this.config.onPlanComplete?.();
1335
+ }
1336
+ }
1337
+ }
1338
+ return { allowed: true, verdict, result };
1339
+ }
1340
+ /**
1341
+ * Process a model response — evaluate and execute all tool calls.
1342
+ * Returns results for each tool call.
1343
+ */
1344
+ async processModelResponse(response, model) {
1345
+ if (response.toolCalls.length === 0) {
1346
+ return response;
1347
+ }
1348
+ for (const toolCall of response.toolCalls) {
1349
+ const { allowed, verdict, result } = await this.executeToolCall(toolCall);
1350
+ if (allowed && result) {
1351
+ const nextResponse = await model.sendToolResult(toolCall.id, result);
1352
+ if (nextResponse.toolCalls.length > 0) {
1353
+ return this.processModelResponse(nextResponse, model);
1354
+ }
1355
+ return nextResponse;
1356
+ } else {
1357
+ const reason = verdict.reason ?? "Action blocked by governance.";
1358
+ const nextResponse = await model.sendBlockedResult(toolCall.id, reason);
1359
+ if (nextResponse.toolCalls.length > 0) {
1360
+ return this.processModelResponse(nextResponse, model);
1361
+ }
1362
+ return nextResponse;
1363
+ }
1364
+ }
1365
+ return response;
1366
+ }
1367
+ /** Get current session state. */
1368
+ getState() {
1369
+ return { ...this.state };
1370
+ }
1371
+ /** Stop the session. */
1372
+ stop() {
1373
+ this.state.active = false;
1374
+ return this.getState();
1375
+ }
1376
+ };
1377
+ async function runPipeMode(config) {
1378
+ const session = new SessionManager(config);
1379
+ await session.start();
1380
+ const state = session.getState();
1381
+ process.stderr.write(`[neuroverse] Pipe mode active
1382
+ `);
1383
+ process.stderr.write(`[neuroverse] World: ${state.world.world.name}
1384
+ `);
1385
+ if (state.plan) {
1386
+ process.stderr.write(`[neuroverse] Plan: ${state.plan.plan_id} (${state.plan.objective})
1387
+ `);
1388
+ }
1389
+ return new Promise((resolve3, reject) => {
1390
+ let buffer = "";
1391
+ process.stdin.setEncoding("utf-8");
1392
+ process.stdin.on("data", (chunk) => {
1393
+ buffer += chunk;
1394
+ const lines = buffer.split("\n");
1395
+ buffer = lines.pop() ?? "";
1396
+ for (const line of lines) {
1397
+ const trimmed = line.trim();
1398
+ if (!trimmed) continue;
1399
+ try {
1400
+ const event = JSON.parse(trimmed);
1401
+ if (!event.intent) {
1402
+ process.stderr.write(`[neuroverse] Warning: event missing "intent" field
1403
+ `);
1404
+ continue;
1405
+ }
1406
+ const verdict = session.evaluate(event);
1407
+ process.stdout.write(JSON.stringify(verdict) + "\n");
1408
+ } catch (err) {
1409
+ process.stderr.write(`[neuroverse] Error parsing line: ${err}
1410
+ `);
1411
+ }
1412
+ }
1413
+ });
1414
+ process.stdin.on("end", () => {
1415
+ if (buffer.trim()) {
1416
+ try {
1417
+ const event = JSON.parse(buffer.trim());
1418
+ if (event.intent) {
1419
+ const verdict = session.evaluate(event);
1420
+ process.stdout.write(JSON.stringify(verdict) + "\n");
1421
+ }
1422
+ } catch {
1423
+ }
1424
+ }
1425
+ const finalState = session.stop();
1426
+ process.stderr.write(
1427
+ `[neuroverse] Session complete: ${finalState.actionsEvaluated} evaluated, ${finalState.actionsAllowed} allowed, ${finalState.actionsBlocked} blocked, ${finalState.actionsPaused} paused
1428
+ `
1429
+ );
1430
+ resolve3();
1431
+ });
1432
+ process.stdin.on("error", reject);
1433
+ });
1434
+ }
1435
+ async function runInteractiveMode(config, model) {
1436
+ const session = new SessionManager(config);
1437
+ await session.start();
1438
+ const state = session.getState();
1439
+ process.stdout.write("\n");
1440
+ process.stdout.write(` World: ${state.world.world.name}
1441
+ `);
1442
+ if (state.plan) {
1443
+ process.stdout.write(` Plan: ${state.plan.plan_id}
1444
+ `);
1445
+ process.stdout.write(` Goal: ${state.plan.objective}
1446
+ `);
1447
+ process.stdout.write(` Steps: ${state.progress?.total ?? 0}
1448
+ `);
1449
+ }
1450
+ process.stdout.write(` Type "exit" to end session.
1451
+ `);
1452
+ process.stdout.write("\n");
1453
+ const readline = await import("readline");
1454
+ const rl = readline.createInterface({
1455
+ input: process.stdin,
1456
+ output: process.stdout,
1457
+ prompt: "> "
1458
+ });
1459
+ const printProgress = () => {
1460
+ const s = session.getState();
1461
+ if (s.progress) {
1462
+ process.stdout.write(
1463
+ ` [plan: ${s.progress.completed}/${s.progress.total} (${s.progress.percentage}%)]
1464
+ `
1465
+ );
1466
+ }
1467
+ };
1468
+ rl.prompt();
1469
+ rl.on("line", async (input) => {
1470
+ const trimmed = input.trim();
1471
+ if (!trimmed) {
1472
+ rl.prompt();
1473
+ return;
1474
+ }
1475
+ if (trimmed === "exit" || trimmed === "quit") {
1476
+ const finalState = session.stop();
1477
+ process.stdout.write("\n");
1478
+ process.stdout.write(` Session complete.
1479
+ `);
1480
+ process.stdout.write(` Actions: ${finalState.actionsEvaluated} evaluated`);
1481
+ process.stdout.write(`, ${finalState.actionsAllowed} allowed`);
1482
+ process.stdout.write(`, ${finalState.actionsBlocked} blocked
1483
+ `);
1484
+ if (finalState.progress) {
1485
+ process.stdout.write(
1486
+ ` Plan: ${finalState.progress.completed}/${finalState.progress.total} steps completed
1487
+ `
1488
+ );
1489
+ }
1490
+ process.stdout.write("\n");
1491
+ rl.close();
1492
+ return;
1493
+ }
1494
+ if (trimmed === "status") {
1495
+ const s = session.getState();
1496
+ process.stdout.write(`
1497
+ World: ${s.world.world.name}
1498
+ `);
1499
+ process.stdout.write(` Actions: ${s.actionsEvaluated} evaluated
1500
+ `);
1501
+ process.stdout.write(` Allowed: ${s.actionsAllowed} | Blocked: ${s.actionsBlocked} | Paused: ${s.actionsPaused}
1502
+ `);
1503
+ if (s.progress && s.plan) {
1504
+ process.stdout.write(` Plan: ${s.plan.plan_id} \u2014 ${s.progress.completed}/${s.progress.total} (${s.progress.percentage}%)
1505
+ `);
1506
+ for (const step of s.plan.steps) {
1507
+ const icon = step.status === "completed" ? "[x]" : "[ ]";
1508
+ process.stdout.write(` ${icon} ${step.label}
1509
+ `);
1510
+ }
1511
+ }
1512
+ process.stdout.write("\n");
1513
+ rl.prompt();
1514
+ return;
1515
+ }
1516
+ try {
1517
+ const response = await model.chat(trimmed);
1518
+ if (response.toolCalls.length > 0) {
1519
+ const finalResponse = await session.processModelResponse(response, model);
1520
+ if (finalResponse.content) {
1521
+ process.stdout.write(`
1522
+ ${finalResponse.content}
1523
+
1524
+ `);
1525
+ }
1526
+ printProgress();
1527
+ } else if (response.content) {
1528
+ process.stdout.write(`
1529
+ ${response.content}
1530
+
1531
+ `);
1532
+ }
1533
+ } catch (err) {
1534
+ process.stderr.write(`
1535
+ Error: ${err}
1536
+
1537
+ `);
1538
+ }
1539
+ rl.prompt();
1540
+ });
1541
+ rl.on("close", () => {
1542
+ session.stop();
1543
+ });
1544
+ return new Promise((resolve3) => {
1545
+ rl.on("close", resolve3);
1546
+ });
1547
+ }
1548
+
1549
+ // src/runtime/model-adapter.ts
1550
+ var DEFAULT_SYSTEM_PROMPT = `You are an AI assistant operating under NeuroVerse governance.
1551
+ All your tool calls are evaluated against governance rules before execution.
1552
+ If an action is blocked, you will be told why. Adjust your approach accordingly.
1553
+ Do not attempt to bypass governance rules.`;
1554
+ var ModelAdapter = class {
1555
+ config;
1556
+ messages;
1557
+ tools;
1558
+ constructor(config, tools = []) {
1559
+ this.config = config;
1560
+ this.tools = tools;
1561
+ this.messages = [];
1562
+ const systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
1563
+ this.messages.push({ role: "system", content: systemPrompt });
1564
+ }
1565
+ /**
1566
+ * Send a user message and get the model's response.
1567
+ */
1568
+ async chat(userMessage) {
1569
+ this.messages.push({ role: "user", content: userMessage });
1570
+ return this.complete();
1571
+ }
1572
+ /**
1573
+ * Send a tool result back to the model and get the next response.
1574
+ */
1575
+ async sendToolResult(toolCallId, result) {
1576
+ this.messages.push({
1577
+ role: "tool",
1578
+ content: result,
1579
+ tool_call_id: toolCallId
1580
+ });
1581
+ return this.complete();
1582
+ }
1583
+ /**
1584
+ * Send a governance block message as a tool result.
1585
+ */
1586
+ async sendBlockedResult(toolCallId, reason) {
1587
+ return this.sendToolResult(
1588
+ toolCallId,
1589
+ `[GOVERNANCE BLOCKED] ${reason}. Please adjust your approach.`
1590
+ );
1591
+ }
1592
+ /**
1593
+ * Call the model API and parse the response.
1594
+ */
1595
+ async complete() {
1596
+ const url = `${this.config.baseUrl}/chat/completions`;
1597
+ const body = {
1598
+ model: this.config.model,
1599
+ messages: this.messages,
1600
+ max_tokens: this.config.maxTokens ?? 4096
1601
+ };
1602
+ if (this.tools.length > 0) {
1603
+ body.tools = this.tools;
1604
+ }
1605
+ const response = await fetch(url, {
1606
+ method: "POST",
1607
+ headers: {
1608
+ "Content-Type": "application/json",
1609
+ "Authorization": `Bearer ${this.config.apiKey}`
1610
+ },
1611
+ body: JSON.stringify(body)
1612
+ });
1613
+ if (!response.ok) {
1614
+ const text = await response.text();
1615
+ throw new Error(`Model API error ${response.status}: ${text}`);
1616
+ }
1617
+ const data = await response.json();
1618
+ const choice = data.choices?.[0];
1619
+ if (!choice) {
1620
+ throw new Error("Model returned no choices");
1621
+ }
1622
+ const message = choice.message;
1623
+ this.messages.push(message);
1624
+ return {
1625
+ content: message.content ?? null,
1626
+ toolCalls: message.tool_calls ?? [],
1627
+ finishReason: choice.finish_reason ?? "stop"
1628
+ };
1629
+ }
1630
+ /** Get current message count (for context tracking). */
1631
+ get messageCount() {
1632
+ return this.messages.length;
1633
+ }
1634
+ };
1635
+ var PROVIDERS = {
1636
+ openai: {
1637
+ baseUrl: "https://api.openai.com/v1",
1638
+ defaultModel: "gpt-4o",
1639
+ envVar: "OPENAI_API_KEY"
1640
+ },
1641
+ anthropic: {
1642
+ baseUrl: "https://api.anthropic.com/v1",
1643
+ defaultModel: "claude-sonnet-4-20250514",
1644
+ envVar: "ANTHROPIC_API_KEY"
1645
+ },
1646
+ ollama: {
1647
+ baseUrl: "http://localhost:11434/v1",
1648
+ defaultModel: "llama3",
1649
+ envVar: ""
1650
+ }
1651
+ };
1652
+ function resolveProvider(provider, overrides) {
1653
+ const preset = PROVIDERS[provider];
1654
+ if (!preset) {
1655
+ throw new Error(
1656
+ `Unknown provider: "${provider}". Available: ${Object.keys(PROVIDERS).join(", ")}`
1657
+ );
1658
+ }
1659
+ const apiKey = overrides?.apiKey ?? (preset.envVar ? process.env[preset.envVar] : "") ?? "";
1660
+ if (!apiKey && preset.envVar) {
1661
+ throw new Error(
1662
+ `Missing API key. Set ${preset.envVar} or pass --api-key.`
1663
+ );
1664
+ }
1665
+ return {
1666
+ baseUrl: overrides?.baseUrl ?? preset.baseUrl,
1667
+ apiKey,
1668
+ model: overrides?.model ?? preset.defaultModel,
1669
+ systemPrompt: overrides?.systemPrompt,
1670
+ maxTokens: overrides?.maxTokens
1671
+ };
1672
+ }
1673
+
1674
+ // src/loader/world-resolver.ts
1675
+ var import_fs = require("fs");
1676
+ var import_path = require("path");
1677
+ var WORLDS_DIR = ".neuroverse/worlds";
1678
+ var ACTIVE_WORLD_FILE = ".neuroverse/active_world";
1679
+ function listWorlds(cwd = process.cwd()) {
1680
+ const worldsDir = (0, import_path.join)(cwd, WORLDS_DIR);
1681
+ if (!(0, import_fs.existsSync)(worldsDir)) return [];
1682
+ const activeName = getActiveWorldName(cwd);
1683
+ const entries = (0, import_fs.readdirSync)(worldsDir);
1684
+ return entries.filter((name) => {
1685
+ const worldJson = (0, import_path.join)(worldsDir, name, "world.json");
1686
+ return (0, import_fs.existsSync)(worldJson);
1687
+ }).map((name) => ({
1688
+ name,
1689
+ path: (0, import_path.join)(worldsDir, name),
1690
+ active: name === activeName
1691
+ })).sort((a, b) => a.name.localeCompare(b.name));
1692
+ }
1693
+ function getActiveWorldName(cwd = process.cwd()) {
1694
+ const filePath = (0, import_path.join)(cwd, ACTIVE_WORLD_FILE);
1695
+ try {
1696
+ return (0, import_fs.readFileSync)(filePath, "utf-8").trim() || void 0;
1697
+ } catch {
1698
+ return void 0;
1699
+ }
1700
+ }
1701
+ function setActiveWorld(name, cwd = process.cwd()) {
1702
+ const worldsDir = (0, import_path.join)(cwd, WORLDS_DIR);
1703
+ const worldPath = (0, import_path.join)(worldsDir, name, "world.json");
1704
+ if (!(0, import_fs.existsSync)(worldPath)) {
1705
+ const available = listWorlds(cwd);
1706
+ const names = available.map((w) => w.name).join(", ");
1707
+ throw new Error(
1708
+ `World "${name}" not found in ${WORLDS_DIR}/
1709
+ ` + (names ? `Available: ${names}` : "No worlds found. Run `neuroverse build` first.")
1710
+ );
1711
+ }
1712
+ const dir = (0, import_path.join)(cwd, ".neuroverse");
1713
+ if (!(0, import_fs.existsSync)(dir)) (0, import_fs.mkdirSync)(dir, { recursive: true });
1714
+ (0, import_fs.writeFileSync)((0, import_path.join)(cwd, ACTIVE_WORLD_FILE), name + "\n", "utf-8");
1715
+ }
1716
+ function resolveWorldPath(explicit, cwd = process.cwd()) {
1717
+ if (explicit) {
1718
+ return resolveNameOrPath(explicit, cwd);
1719
+ }
1720
+ const envWorld = process.env.NEUROVERSE_WORLD;
1721
+ if (envWorld) {
1722
+ return resolveNameOrPath(envWorld, cwd);
1723
+ }
1724
+ const activeName = getActiveWorldName(cwd);
1725
+ if (activeName) {
1726
+ return resolveNameOrPath(activeName, cwd);
1727
+ }
1728
+ const worlds = listWorlds(cwd);
1729
+ if (worlds.length === 1) {
1730
+ return (0, import_path.resolve)(worlds[0].path);
1731
+ }
1732
+ return void 0;
1733
+ }
1734
+ function describeActiveWorld(explicit, cwd = process.cwd()) {
1735
+ if (explicit) {
1736
+ return { name: explicit, source: "--world flag" };
1737
+ }
1738
+ const envWorld = process.env.NEUROVERSE_WORLD;
1739
+ if (envWorld) {
1740
+ return { name: envWorld, source: "NEUROVERSE_WORLD env var" };
1741
+ }
1742
+ const activeName = getActiveWorldName(cwd);
1743
+ if (activeName) {
1744
+ return { name: activeName, source: ".neuroverse/active_world" };
1745
+ }
1746
+ const worlds = listWorlds(cwd);
1747
+ if (worlds.length === 1) {
1748
+ return { name: worlds[0].name, source: "auto-detected (only world)" };
1749
+ }
1750
+ return void 0;
1751
+ }
1752
+ function resolveNameOrPath(ref, cwd) {
1753
+ if (ref.includes("/") || ref.includes("\\") || ref.startsWith(".") || (0, import_path.isAbsolute)(ref)) {
1754
+ return (0, import_path.resolve)(cwd, ref);
1755
+ }
1756
+ const namedPath = (0, import_path.join)(cwd, WORLDS_DIR, ref);
1757
+ if ((0, import_fs.existsSync)((0, import_path.join)(namedPath, "world.json"))) {
1758
+ return (0, import_path.resolve)(namedPath);
1759
+ }
1760
+ return (0, import_path.resolve)(cwd, ref);
1761
+ }
1762
+
1763
+ // src/runtime/mcp-server.ts
1764
+ var import_child_process = require("child_process");
1765
+ var import_fs2 = require("fs");
1766
+ var import_path2 = require("path");
1767
+ var GOVERNED_TOOLS = [
1768
+ {
1769
+ name: "governed_shell",
1770
+ description: "Execute a shell command. This command is evaluated against governance rules before execution.",
1771
+ inputSchema: {
1772
+ type: "object",
1773
+ properties: {
1774
+ command: { type: "string", description: "The shell command to execute" }
1775
+ },
1776
+ required: ["command"]
1777
+ }
1778
+ },
1779
+ {
1780
+ name: "governed_read_file",
1781
+ description: "Read a file. This action is evaluated against governance rules before execution.",
1782
+ inputSchema: {
1783
+ type: "object",
1784
+ properties: {
1785
+ path: { type: "string", description: "File path to read" }
1786
+ },
1787
+ required: ["path"]
1788
+ }
1789
+ },
1790
+ {
1791
+ name: "governed_write_file",
1792
+ description: "Write content to a file. This action is evaluated against governance rules before execution.",
1793
+ inputSchema: {
1794
+ type: "object",
1795
+ properties: {
1796
+ path: { type: "string", description: "File path to write" },
1797
+ content: { type: "string", description: "Content to write" }
1798
+ },
1799
+ required: ["path", "content"]
1800
+ }
1801
+ },
1802
+ {
1803
+ name: "governed_list_directory",
1804
+ description: "List files in a directory. This action is evaluated against governance rules.",
1805
+ inputSchema: {
1806
+ type: "object",
1807
+ properties: {
1808
+ path: { type: "string", description: "Directory path to list" }
1809
+ },
1810
+ required: ["path"]
1811
+ }
1812
+ },
1813
+ {
1814
+ name: "governed_http_request",
1815
+ description: "Make an HTTP request. This action is evaluated against governance rules before execution.",
1816
+ inputSchema: {
1817
+ type: "object",
1818
+ properties: {
1819
+ url: { type: "string", description: "URL to request" },
1820
+ method: { type: "string", description: "HTTP method (GET, POST, PUT, DELETE)", default: "GET" },
1821
+ body: { type: "string", description: "Request body (for POST/PUT)" },
1822
+ headers: { type: "object", description: "Request headers" }
1823
+ },
1824
+ required: ["url"]
1825
+ }
1826
+ },
1827
+ // Governance introspection tools — always available
1828
+ {
1829
+ name: "governance_check",
1830
+ description: "Check if an action would be allowed by governance rules without executing it.",
1831
+ inputSchema: {
1832
+ type: "object",
1833
+ properties: {
1834
+ intent: { type: "string", description: "What the action intends to do" },
1835
+ tool: { type: "string", description: "Tool name (shell, http, file, etc.)" },
1836
+ scope: { type: "string", description: "Scope (file path, URL, etc.)" }
1837
+ },
1838
+ required: ["intent"]
1839
+ }
1840
+ },
1841
+ {
1842
+ name: "governance_plan_status",
1843
+ description: "Show current plan progress and remaining steps.",
1844
+ inputSchema: {
1845
+ type: "object",
1846
+ properties: {}
1847
+ }
1848
+ },
1849
+ {
1850
+ name: "governance_plan_advance",
1851
+ description: "Mark a plan step as completed.",
1852
+ inputSchema: {
1853
+ type: "object",
1854
+ properties: {
1855
+ step_id: { type: "string", description: "ID of the step to mark as completed" }
1856
+ },
1857
+ required: ["step_id"]
1858
+ }
1859
+ }
1860
+ ];
1861
+ function executeShell(command, workingDir) {
1862
+ try {
1863
+ const result = (0, import_child_process.execSync)(command, {
1864
+ cwd: workingDir,
1865
+ encoding: "utf-8",
1866
+ timeout: 3e4,
1867
+ maxBuffer: 1024 * 1024
1868
+ });
1869
+ return result;
1870
+ } catch (err) {
1871
+ return `Error: ${err.message}
1872
+ ${err.stderr ?? ""}`;
1873
+ }
1874
+ }
1875
+ function executeReadFile(path, workingDir) {
1876
+ const fullPath = (0, import_path2.resolve)(workingDir ?? ".", path);
1877
+ return (0, import_fs2.readFileSync)(fullPath, "utf-8");
1878
+ }
1879
+ function executeWriteFile(path, content, workingDir) {
1880
+ const fullPath = (0, import_path2.resolve)(workingDir ?? ".", path);
1881
+ (0, import_fs2.writeFileSync)(fullPath, content);
1882
+ return `File written: ${fullPath}`;
1883
+ }
1884
+ function executeListDir(path, workingDir) {
1885
+ const fullPath = (0, import_path2.resolve)(workingDir ?? ".", path);
1886
+ const entries = (0, import_fs2.readdirSync)(fullPath);
1887
+ return entries.map((e) => {
1888
+ try {
1889
+ const stat = (0, import_fs2.statSync)((0, import_path2.join)(fullPath, e));
1890
+ return `${stat.isDirectory() ? "d" : "-"} ${e}`;
1891
+ } catch {
1892
+ return `? ${e}`;
1893
+ }
1894
+ }).join("\n");
1895
+ }
1896
+ async function executeHttpRequest(url, method, body, headers) {
1897
+ const response = await fetch(url, {
1898
+ method: method || "GET",
1899
+ body: body || void 0,
1900
+ headers: { "Content-Type": "application/json", ...headers }
1901
+ });
1902
+ const text = await response.text();
1903
+ return `HTTP ${response.status}
1904
+ ${text}`;
1905
+ }
1906
+ var McpGovernanceServer = class {
1907
+ world;
1908
+ plan;
1909
+ config;
1910
+ engineOptions;
1911
+ initialized = false;
1912
+ // Stats
1913
+ actionsEvaluated = 0;
1914
+ actionsAllowed = 0;
1915
+ actionsBlocked = 0;
1916
+ constructor(config) {
1917
+ this.config = config;
1918
+ this.plan = config.plan;
1919
+ this.engineOptions = {
1920
+ trace: config.trace ?? false,
1921
+ level: config.level,
1922
+ plan: this.plan
1923
+ };
1924
+ }
1925
+ /**
1926
+ * Start the MCP server — reads JSON-RPC from stdin, writes to stdout.
1927
+ */
1928
+ async start() {
1929
+ if (this.config.worldPath) {
1930
+ this.world = await loadWorld(this.config.worldPath);
1931
+ } else if (this.config.world) {
1932
+ this.world = this.config.world;
1933
+ } else {
1934
+ throw new Error("No world provided");
1935
+ }
1936
+ if (this.config.planPath && !this.plan) {
1937
+ this.plan = JSON.parse((0, import_fs2.readFileSync)(this.config.planPath, "utf-8"));
1938
+ this.engineOptions.plan = this.plan;
1939
+ }
1940
+ process.stderr.write(`[neuroverse-mcp] Server starting
1941
+ `);
1942
+ process.stderr.write(`[neuroverse-mcp] World: ${this.world.world.name}
1943
+ `);
1944
+ if (this.plan) {
1945
+ process.stderr.write(`[neuroverse-mcp] Plan: ${this.plan.plan_id}
1946
+ `);
1947
+ }
1948
+ let buffer = "";
1949
+ process.stdin.setEncoding("utf-8");
1950
+ process.stdin.on("data", (chunk) => {
1951
+ buffer += chunk;
1952
+ while (buffer.length > 0) {
1953
+ const headerEnd = buffer.indexOf("\r\n\r\n");
1954
+ if (headerEnd === -1) break;
1955
+ const header = buffer.slice(0, headerEnd);
1956
+ const contentLengthMatch = header.match(/Content-Length:\s*(\d+)/i);
1957
+ if (!contentLengthMatch) {
1958
+ const newlineIdx = buffer.indexOf("\n");
1959
+ if (newlineIdx === -1) break;
1960
+ const line = buffer.slice(0, newlineIdx).trim();
1961
+ buffer = buffer.slice(newlineIdx + 1);
1962
+ if (line) this.handleRawLine(line);
1963
+ continue;
1964
+ }
1965
+ const contentLength = parseInt(contentLengthMatch[1], 10);
1966
+ const bodyStart = headerEnd + 4;
1967
+ const bodyEnd = bodyStart + contentLength;
1968
+ if (buffer.length < bodyEnd) break;
1969
+ const body = buffer.slice(bodyStart, bodyEnd);
1970
+ buffer = buffer.slice(bodyEnd);
1971
+ this.handleRawLine(body);
1972
+ }
1973
+ });
1974
+ process.stdin.on("end", () => {
1975
+ process.stderr.write(
1976
+ `[neuroverse-mcp] Server stopped. Evaluated: ${this.actionsEvaluated}, Allowed: ${this.actionsAllowed}, Blocked: ${this.actionsBlocked}
1977
+ `
1978
+ );
1979
+ });
1980
+ await new Promise(() => {
1981
+ });
1982
+ }
1983
+ handleRawLine(line) {
1984
+ try {
1985
+ const msg = JSON.parse(line);
1986
+ if (msg.method) {
1987
+ if (msg.id !== void 0) {
1988
+ this.handleRequest(msg);
1989
+ } else {
1990
+ this.handleNotification(msg);
1991
+ }
1992
+ }
1993
+ } catch (err) {
1994
+ process.stderr.write(`[neuroverse-mcp] Parse error: ${err}
1995
+ `);
1996
+ }
1997
+ }
1998
+ send(msg) {
1999
+ const json = JSON.stringify(msg);
2000
+ const header = `Content-Length: ${Buffer.byteLength(json)}\r
2001
+ \r
2002
+ `;
2003
+ process.stdout.write(header + json);
2004
+ }
2005
+ sendResult(id, result) {
2006
+ this.send({ jsonrpc: "2.0", id, result });
2007
+ }
2008
+ sendError(id, code, message) {
2009
+ this.send({ jsonrpc: "2.0", id, error: { code, message } });
2010
+ }
2011
+ // ─── Request Handlers ───────────────────────────────────────────────────
2012
+ handleRequest(request) {
2013
+ switch (request.method) {
2014
+ case "initialize":
2015
+ this.handleInitialize(request);
2016
+ break;
2017
+ case "tools/list":
2018
+ this.handleToolsList(request);
2019
+ break;
2020
+ case "tools/call":
2021
+ this.handleToolsCall(request);
2022
+ break;
2023
+ case "ping":
2024
+ this.sendResult(request.id, {});
2025
+ break;
2026
+ default:
2027
+ this.sendError(request.id, -32601, `Method not found: ${request.method}`);
2028
+ }
2029
+ }
2030
+ handleNotification(notification) {
2031
+ switch (notification.method) {
2032
+ case "notifications/initialized":
2033
+ this.initialized = true;
2034
+ process.stderr.write(`[neuroverse-mcp] Client initialized
2035
+ `);
2036
+ break;
2037
+ case "notifications/cancelled":
2038
+ break;
2039
+ default:
2040
+ process.stderr.write(`[neuroverse-mcp] Unknown notification: ${notification.method}
2041
+ `);
2042
+ }
2043
+ }
2044
+ handleInitialize(request) {
2045
+ this.sendResult(request.id, {
2046
+ protocolVersion: "2024-11-05",
2047
+ capabilities: {
2048
+ tools: {}
2049
+ },
2050
+ serverInfo: {
2051
+ name: "neuroverse-governance",
2052
+ version: "0.2.0"
2053
+ }
2054
+ });
2055
+ }
2056
+ handleToolsList(request) {
2057
+ const tools = [];
2058
+ if (this.config.enableShell !== false) {
2059
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_shell"));
2060
+ }
2061
+ if (this.config.enableFiles !== false) {
2062
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_read_file"));
2063
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_write_file"));
2064
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_list_directory"));
2065
+ }
2066
+ if (this.config.enableHttp !== false) {
2067
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governed_http_request"));
2068
+ }
2069
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governance_check"));
2070
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governance_plan_status"));
2071
+ if (this.plan) {
2072
+ tools.push(GOVERNED_TOOLS.find((t) => t.name === "governance_plan_advance"));
2073
+ }
2074
+ this.sendResult(request.id, { tools });
2075
+ }
2076
+ async handleToolsCall(request) {
2077
+ const params = request.params;
2078
+ const toolName = params.name;
2079
+ const args = params.arguments ?? {};
2080
+ try {
2081
+ const result = await this.executeTool(toolName, args);
2082
+ this.sendResult(request.id, result);
2083
+ } catch (err) {
2084
+ this.sendResult(request.id, {
2085
+ content: [{ type: "text", text: `Error: ${err.message}` }],
2086
+ isError: true
2087
+ });
2088
+ }
2089
+ }
2090
+ // ─── Tool Execution with Governance ─────────────────────────────────────
2091
+ async executeTool(name, args) {
2092
+ if (name === "governance_check") {
2093
+ return this.toolGovernanceCheck(args);
2094
+ }
2095
+ if (name === "governance_plan_status") {
2096
+ return this.toolPlanStatus();
2097
+ }
2098
+ if (name === "governance_plan_advance") {
2099
+ return this.toolPlanAdvance(args);
2100
+ }
2101
+ const event = this.buildEvent(name, args);
2102
+ this.engineOptions.plan = this.plan;
2103
+ const verdict = evaluateGuard(event, this.world, this.engineOptions);
2104
+ this.actionsEvaluated++;
2105
+ if (verdict.status === "BLOCK") {
2106
+ this.actionsBlocked++;
2107
+ let reason = `[GOVERNANCE BLOCKED] ${verdict.reason ?? "Action blocked by governance rules."}`;
2108
+ if (verdict.ruleId) reason += ` (Rule: ${verdict.ruleId})`;
2109
+ if (verdict.trace?.planCheck && !verdict.trace.planCheck.matched) {
2110
+ const pc = verdict.trace.planCheck;
2111
+ if (pc.closestStepLabel) {
2112
+ reason += `
2113
+ Closest plan step: "${pc.closestStepLabel}"`;
2114
+ }
2115
+ }
2116
+ process.stderr.write(`[neuroverse-mcp] BLOCKED: ${event.intent}
2117
+ `);
2118
+ return { content: [{ type: "text", text: reason }], isError: true };
2119
+ }
2120
+ if (verdict.status === "PAUSE") {
2121
+ this.actionsBlocked++;
2122
+ const reason = `[GOVERNANCE PAUSED] ${verdict.reason ?? "Action requires human approval."}`;
2123
+ process.stderr.write(`[neuroverse-mcp] PAUSED: ${event.intent}
2124
+ `);
2125
+ return { content: [{ type: "text", text: reason }], isError: true };
2126
+ }
2127
+ this.actionsAllowed++;
2128
+ process.stderr.write(`[neuroverse-mcp] ALLOWED: ${event.intent}
2129
+ `);
2130
+ const result = await this.executeActualTool(name, args);
2131
+ if (this.plan) {
2132
+ const planVerdict = evaluatePlan(event, this.plan);
2133
+ if (planVerdict.matchedStep) {
2134
+ this.plan = advancePlan(this.plan, planVerdict.matchedStep);
2135
+ this.engineOptions.plan = this.plan;
2136
+ const progress = getPlanProgress(this.plan);
2137
+ process.stderr.write(
2138
+ `[neuroverse-mcp] Plan: ${progress.completed}/${progress.total} (${progress.percentage}%)
2139
+ `
2140
+ );
2141
+ if (progress.completed === progress.total) {
2142
+ process.stderr.write(`[neuroverse-mcp] Plan complete!
2143
+ `);
2144
+ }
2145
+ }
2146
+ }
2147
+ return result;
2148
+ }
2149
+ buildEvent(toolName, args) {
2150
+ switch (toolName) {
2151
+ case "governed_shell":
2152
+ return {
2153
+ intent: `execute shell command: ${args.command}`,
2154
+ tool: "shell",
2155
+ scope: String(args.command ?? ""),
2156
+ actionCategory: "shell",
2157
+ args,
2158
+ direction: "input"
2159
+ };
2160
+ case "governed_read_file":
2161
+ return {
2162
+ intent: `read file: ${args.path}`,
2163
+ tool: "fs",
2164
+ scope: String(args.path ?? ""),
2165
+ actionCategory: "read",
2166
+ args,
2167
+ direction: "input"
2168
+ };
2169
+ case "governed_write_file":
2170
+ return {
2171
+ intent: `write file: ${args.path}`,
2172
+ tool: "fs",
2173
+ scope: String(args.path ?? ""),
2174
+ actionCategory: "write",
2175
+ args,
2176
+ direction: "input"
2177
+ };
2178
+ case "governed_list_directory":
2179
+ return {
2180
+ intent: `list directory: ${args.path}`,
2181
+ tool: "fs",
2182
+ scope: String(args.path ?? ""),
2183
+ actionCategory: "read",
2184
+ args,
2185
+ direction: "input"
2186
+ };
2187
+ case "governed_http_request":
2188
+ return {
2189
+ intent: `http ${args.method ?? "GET"} ${args.url}`,
2190
+ tool: "http",
2191
+ scope: String(args.url ?? ""),
2192
+ actionCategory: "network",
2193
+ args,
2194
+ direction: "input"
2195
+ };
2196
+ default:
2197
+ return {
2198
+ intent: `${toolName}: ${JSON.stringify(args)}`,
2199
+ tool: toolName,
2200
+ args,
2201
+ direction: "input"
2202
+ };
2203
+ }
2204
+ }
2205
+ async executeActualTool(name, args) {
2206
+ const workingDir = this.config.workingDir;
2207
+ switch (name) {
2208
+ case "governed_shell": {
2209
+ const output = executeShell(String(args.command), workingDir);
2210
+ return { content: [{ type: "text", text: output }] };
2211
+ }
2212
+ case "governed_read_file": {
2213
+ const content = executeReadFile(String(args.path), workingDir);
2214
+ return { content: [{ type: "text", text: content }] };
2215
+ }
2216
+ case "governed_write_file": {
2217
+ const result = executeWriteFile(String(args.path), String(args.content), workingDir);
2218
+ return { content: [{ type: "text", text: result }] };
2219
+ }
2220
+ case "governed_list_directory": {
2221
+ const listing = executeListDir(String(args.path), workingDir);
2222
+ return { content: [{ type: "text", text: listing }] };
2223
+ }
2224
+ case "governed_http_request": {
2225
+ const result = await executeHttpRequest(
2226
+ String(args.url),
2227
+ String(args.method ?? "GET"),
2228
+ args.body,
2229
+ args.headers
2230
+ );
2231
+ return { content: [{ type: "text", text: result }] };
2232
+ }
2233
+ default:
2234
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
2235
+ }
2236
+ }
2237
+ // ─── Governance Introspection Tools ─────────────────────────────────────
2238
+ toolGovernanceCheck(args) {
2239
+ const event = {
2240
+ intent: String(args.intent ?? ""),
2241
+ tool: args.tool,
2242
+ scope: args.scope,
2243
+ direction: "input"
2244
+ };
2245
+ this.engineOptions.plan = this.plan;
2246
+ const verdict = evaluateGuard(event, this.world, this.engineOptions);
2247
+ const lines = [
2248
+ `Verdict: ${verdict.status}`,
2249
+ verdict.reason ? `Reason: ${verdict.reason}` : null,
2250
+ verdict.ruleId ? `Rule: ${verdict.ruleId}` : null,
2251
+ verdict.warning ? `Warning: ${verdict.warning}` : null
2252
+ ].filter(Boolean).join("\n");
2253
+ return { content: [{ type: "text", text: lines }] };
2254
+ }
2255
+ toolPlanStatus() {
2256
+ if (!this.plan) {
2257
+ return { content: [{ type: "text", text: "No active plan." }] };
2258
+ }
2259
+ const progress = getPlanProgress(this.plan);
2260
+ const lines = [
2261
+ `Plan: ${this.plan.plan_id}`,
2262
+ `Objective: ${this.plan.objective}`,
2263
+ `Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`,
2264
+ "",
2265
+ "Steps:",
2266
+ ...this.plan.steps.map((s) => {
2267
+ const icon = s.status === "completed" ? "[x]" : s.status === "active" ? "[>]" : "[ ]";
2268
+ return ` ${icon} ${s.label} (${s.id})`;
2269
+ })
2270
+ ];
2271
+ if (this.plan.constraints.length > 0) {
2272
+ lines.push("", "Constraints:");
2273
+ for (const c2 of this.plan.constraints) {
2274
+ lines.push(` - ${c2.description} [${c2.type}]`);
2275
+ }
2276
+ }
2277
+ return { content: [{ type: "text", text: lines.join("\n") }] };
2278
+ }
2279
+ toolPlanAdvance(args) {
2280
+ if (!this.plan) {
2281
+ return { content: [{ type: "text", text: "No active plan." }], isError: true };
2282
+ }
2283
+ const stepId = String(args.step_id ?? "");
2284
+ const step = this.plan.steps.find((s) => s.id === stepId);
2285
+ if (!step) {
2286
+ const ids = this.plan.steps.map((s) => s.id).join(", ");
2287
+ return {
2288
+ content: [{ type: "text", text: `Step "${stepId}" not found. Available: ${ids}` }],
2289
+ isError: true
2290
+ };
2291
+ }
2292
+ if (step.status === "completed") {
2293
+ return { content: [{ type: "text", text: `Step "${stepId}" is already completed.` }] };
2294
+ }
2295
+ this.plan = advancePlan(this.plan, stepId);
2296
+ this.engineOptions.plan = this.plan;
2297
+ const progress = getPlanProgress(this.plan);
2298
+ let text = `Step completed: ${step.label}
2299
+ Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`;
2300
+ if (progress.completed === progress.total) {
2301
+ text += "\n\nPlan complete!";
2302
+ }
2303
+ if (this.config.planPath) {
2304
+ (0, import_fs2.writeFileSync)(this.config.planPath, JSON.stringify(this.plan, null, 2) + "\n");
2305
+ }
2306
+ return { content: [{ type: "text", text }] };
2307
+ }
2308
+ };
2309
+
747
2310
  // src/engine/audit-logger.ts
748
2311
  var FileAuditLogger = class {
749
2312
  logPath;
@@ -1223,81 +2786,6 @@ function emptyReport() {
1223
2786
  };
1224
2787
  }
1225
2788
 
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
2789
  // src/engine/condition-engine.ts
1302
2790
  function getFieldValue(event, field) {
1303
2791
  if (field.startsWith("args.")) {
@@ -1444,15 +2932,39 @@ function evaluateEndsWith(fieldValue, conditionValue) {
1444
2932
  }
1445
2933
 
1446
2934
  // src/engine/validate-engine.ts
1447
- function validateWorld(world) {
2935
+ function validateWorld(world, mode = "standard") {
1448
2936
  const startTime = performance.now();
1449
2937
  const findings = [];
1450
2938
  checkCompleteness(world, findings);
1451
2939
  checkReferentialIntegrity(world, findings);
1452
2940
  checkGuardCoverage(world, findings);
2941
+ checkSemanticCoverage(world, findings);
1453
2942
  checkContradictions(world, findings);
2943
+ checkGuardShadows(world, findings);
2944
+ checkFailClosedSurfaces(world, findings);
2945
+ checkReachability(world, findings);
2946
+ checkStateCoverage(world, findings);
1454
2947
  checkOrphans(world, findings);
1455
2948
  checkSchemaViolations(world, findings);
2949
+ const governanceCategories = /* @__PURE__ */ new Set([
2950
+ "guard-coverage",
2951
+ "contradiction",
2952
+ "semantic-tension",
2953
+ "orphan"
2954
+ ]);
2955
+ if (mode === "dev") {
2956
+ for (const f of findings) {
2957
+ if (governanceCategories.has(f.category) && f.severity === "warning") {
2958
+ f.severity = "info";
2959
+ }
2960
+ }
2961
+ } else if (mode === "strict") {
2962
+ for (const f of findings) {
2963
+ if (governanceCategories.has(f.category) && f.severity === "info") {
2964
+ f.severity = "warning";
2965
+ }
2966
+ }
2967
+ }
1456
2968
  const severityOrder = { error: 0, warning: 1, info: 2 };
1457
2969
  findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
1458
2970
  const errors = findings.filter((f) => f.severity === "error").length;
@@ -1460,6 +2972,7 @@ function validateWorld(world) {
1460
2972
  const info = findings.filter((f) => f.severity === "info").length;
1461
2973
  const completenessScore = computeCompletenessScore(world);
1462
2974
  const invariantCoverage = computeInvariantCoverage(world);
2975
+ const governanceHealth = computeGovernanceHealth(world, findings);
1463
2976
  const summary = {
1464
2977
  errors,
1465
2978
  warnings,
@@ -1467,7 +2980,8 @@ function validateWorld(world) {
1467
2980
  completenessScore,
1468
2981
  invariantCoverage,
1469
2982
  canRun: errors === 0,
1470
- isHealthy: errors === 0 && warnings === 0
2983
+ isHealthy: errors === 0 && warnings === 0,
2984
+ governanceHealth
1471
2985
  };
1472
2986
  return {
1473
2987
  worldId: world.world.world_id,
@@ -1475,6 +2989,7 @@ function validateWorld(world) {
1475
2989
  worldVersion: world.world.version,
1476
2990
  validatedAt: Date.now(),
1477
2991
  durationMs: performance.now() - startTime,
2992
+ validationMode: mode,
1478
2993
  summary,
1479
2994
  findings
1480
2995
  };
@@ -1657,6 +3172,183 @@ function checkGuardCoverage(world, findings) {
1657
3172
  }
1658
3173
  }
1659
3174
  }
3175
+ function checkSemanticCoverage(world, findings) {
3176
+ if (!world.invariants || world.invariants.length === 0) return;
3177
+ const hasGuards = (world.guards?.guards?.length ?? 0) > 0;
3178
+ const hasKernel = (world.kernel?.input_boundaries?.forbidden_patterns?.length ?? 0) > 0 || (world.kernel?.output_boundaries?.forbidden_patterns?.length ?? 0) > 0;
3179
+ if (!hasGuards && !hasKernel) return;
3180
+ const guards = world.guards?.guards ?? [];
3181
+ const vocabEntries = world.guards?.intent_vocabulary ?? {};
3182
+ const kernelInput = world.kernel?.input_boundaries?.forbidden_patterns ?? [];
3183
+ const kernelOutput = world.kernel?.output_boundaries?.forbidden_patterns ?? [];
3184
+ const allKernelRules = [...kernelInput, ...kernelOutput];
3185
+ const guardSearchTexts = guards.map((g) => {
3186
+ const parts = [];
3187
+ for (const patternKey of g.intent_patterns) {
3188
+ parts.push(patternKey.toLowerCase());
3189
+ const vocab = vocabEntries[patternKey];
3190
+ if (vocab) {
3191
+ parts.push(vocab.label.toLowerCase());
3192
+ parts.push(vocab.pattern.toLowerCase());
3193
+ }
3194
+ }
3195
+ parts.push(g.description.toLowerCase());
3196
+ return { guard: g, text: parts.join(" ") };
3197
+ });
3198
+ const kernelSearchTexts = allKernelRules.map((k) => ({
3199
+ rule: k,
3200
+ text: `${k.id} ${k.reason} ${k.pattern ?? ""}`.toLowerCase()
3201
+ }));
3202
+ for (const invariant of world.invariants) {
3203
+ if (invariant.enforcement === "prompt") continue;
3204
+ const tokens = extractActionTokens(invariant.id, invariant.label);
3205
+ if (tokens.length === 0) continue;
3206
+ const coveringGuards = guardSearchTexts.filter((gs) => {
3207
+ const enabled = gs.guard.immutable || gs.guard.default_enabled !== false;
3208
+ if (!enabled) return false;
3209
+ return tokens.some((token) => gs.text.includes(token));
3210
+ });
3211
+ const coveringKernel = kernelSearchTexts.filter(
3212
+ (ks) => tokens.some((token) => ks.text.includes(token))
3213
+ );
3214
+ const hasStructuralGuard = guards.some(
3215
+ (g) => g.invariant_ref === invariant.id && g.immutable
3216
+ );
3217
+ if (coveringGuards.length === 0 && coveringKernel.length === 0) {
3218
+ if (hasStructuralGuard) {
3219
+ findings.push(finding(
3220
+ `weak-coverage-${invariant.id}`,
3221
+ `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`,
3222
+ "warning",
3223
+ "guard-coverage",
3224
+ ["invariants.json", "guards.json"],
3225
+ invariant.id,
3226
+ `Ensure the backing guard's intent_patterns include patterns that can detect "${invariant.label}"`
3227
+ ));
3228
+ } else {
3229
+ findings.push(finding(
3230
+ `unenforced-invariant-${invariant.id}`,
3231
+ `Invariant "${invariant.id}" has no guard or kernel rule capable of enforcing it \u2014 no interceptor matches action class [${tokens.join(", ")}]`,
3232
+ "warning",
3233
+ "guard-coverage",
3234
+ ["invariants.json", "guards.json"],
3235
+ invariant.id,
3236
+ `Add a guard with intent_patterns that can intercept "${invariant.label}", or add a kernel forbidden_pattern`
3237
+ ));
3238
+ }
3239
+ }
3240
+ }
3241
+ }
3242
+ function extractActionTokens(id, label) {
3243
+ const stopWords = /* @__PURE__ */ new Set([
3244
+ "a",
3245
+ "an",
3246
+ "the",
3247
+ "is",
3248
+ "are",
3249
+ "was",
3250
+ "were",
3251
+ "be",
3252
+ "been",
3253
+ "being",
3254
+ "have",
3255
+ "has",
3256
+ "had",
3257
+ "do",
3258
+ "does",
3259
+ "did",
3260
+ "will",
3261
+ "would",
3262
+ "could",
3263
+ "should",
3264
+ "may",
3265
+ "might",
3266
+ "must",
3267
+ "shall",
3268
+ "can",
3269
+ "need",
3270
+ "dare",
3271
+ "to",
3272
+ "of",
3273
+ "in",
3274
+ "for",
3275
+ "on",
3276
+ "with",
3277
+ "at",
3278
+ "by",
3279
+ "from",
3280
+ "as",
3281
+ "into",
3282
+ "through",
3283
+ "during",
3284
+ "before",
3285
+ "after",
3286
+ "above",
3287
+ "below",
3288
+ "between",
3289
+ "out",
3290
+ "off",
3291
+ "over",
3292
+ "under",
3293
+ "again",
3294
+ "further",
3295
+ "then",
3296
+ "once",
3297
+ "that",
3298
+ "than",
3299
+ "too",
3300
+ "very",
3301
+ "just",
3302
+ "only",
3303
+ "not",
3304
+ "no",
3305
+ "all",
3306
+ "any",
3307
+ "both",
3308
+ "each",
3309
+ "every",
3310
+ "few",
3311
+ "more",
3312
+ "most",
3313
+ "other",
3314
+ "some",
3315
+ "such",
3316
+ "and",
3317
+ "but",
3318
+ "or",
3319
+ "nor",
3320
+ "so",
3321
+ "yet",
3322
+ "if",
3323
+ "it",
3324
+ "its",
3325
+ "they",
3326
+ "them",
3327
+ "their",
3328
+ "this",
3329
+ "these",
3330
+ "those",
3331
+ "which",
3332
+ "who",
3333
+ "whom",
3334
+ "what",
3335
+ "where",
3336
+ "when",
3337
+ "how",
3338
+ "why"
3339
+ ]);
3340
+ const idTokens = id.toLowerCase().split(/[_\-]+/);
3341
+ const labelTokens = label.toLowerCase().split(/[\s\-—:,;.!?()[\]{}]+/);
3342
+ const allTokens = [...idTokens, ...labelTokens];
3343
+ const unique = /* @__PURE__ */ new Set();
3344
+ for (const token of allTokens) {
3345
+ const clean = token.replace(/[^a-z0-9]/g, "");
3346
+ if (clean.length >= 3 && !stopWords.has(clean)) {
3347
+ unique.add(clean);
3348
+ }
3349
+ }
3350
+ return [...unique];
3351
+ }
1660
3352
  function checkContradictions(world, findings) {
1661
3353
  if (!world.rules || world.rules.length < 2) return;
1662
3354
  checkCircularExclusiveWith(world.rules, findings);
@@ -1862,6 +3554,251 @@ function describeEffect(effect) {
1862
3554
  return `${effect.operation} ${effect.value}`;
1863
3555
  }
1864
3556
  }
3557
+ function checkGuardShadows(world, findings) {
3558
+ if (!world.guards?.guards || world.guards.guards.length < 2) return;
3559
+ const guards = world.guards.guards;
3560
+ for (let i = 0; i < guards.length; i++) {
3561
+ const guardA = guards[i];
3562
+ const enabledA = guardA.immutable || guardA.default_enabled !== false;
3563
+ if (!enabledA) continue;
3564
+ if (guardA.enforcement !== "block" && guardA.enforcement !== "pause") continue;
3565
+ for (let j = i + 1; j < guards.length; j++) {
3566
+ const guardB = guards[j];
3567
+ const enabledB = guardB.immutable || guardB.default_enabled !== false;
3568
+ if (!enabledB) continue;
3569
+ const overlap = guardA.intent_patterns.filter(
3570
+ (p) => guardB.intent_patterns.includes(p)
3571
+ );
3572
+ if (overlap.length === 0) continue;
3573
+ if (guardA.appliesTo?.length && guardB.appliesTo?.length) {
3574
+ const toolsA = new Set(guardA.appliesTo.map((t) => t.toLowerCase()));
3575
+ const toolsB = new Set(guardB.appliesTo.map((t) => t.toLowerCase()));
3576
+ const toolOverlap = [...toolsA].some((t) => toolsB.has(t));
3577
+ if (!toolOverlap) continue;
3578
+ }
3579
+ if (guardA.required_roles?.length && guardB.required_roles?.length) {
3580
+ const rolesA = new Set(guardA.required_roles);
3581
+ const rolesB = new Set(guardB.required_roles);
3582
+ const roleOverlap = [...rolesA].some((r) => rolesB.has(r));
3583
+ if (!roleOverlap) continue;
3584
+ }
3585
+ const patternsStr = overlap.join(", ");
3586
+ if (guardB.enforcement === guardA.enforcement) {
3587
+ findings.push(finding(
3588
+ `guard-shadow-${guardA.id}-${guardB.id}`,
3589
+ `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`,
3590
+ "warning",
3591
+ "contradiction",
3592
+ ["guards/"],
3593
+ `${guardA.id}, ${guardB.id}`,
3594
+ `Remove "${guardB.label}", merge its patterns into "${guardA.label}", or reorder guards`
3595
+ ));
3596
+ } else {
3597
+ findings.push(finding(
3598
+ `guard-conflict-${guardA.id}-${guardB.id}`,
3599
+ `Guards "${guardA.label}" (${guardA.enforcement.toUpperCase()}) and "${guardB.label}" (${guardB.enforcement.toUpperCase()}) share patterns [${patternsStr}] \u2014 "${guardA.label}" always wins because it appears first`,
3600
+ "warning",
3601
+ "contradiction",
3602
+ ["guards/"],
3603
+ `${guardA.id}, ${guardB.id}`,
3604
+ `If "${guardB.label}" should take precedence, move it before "${guardA.label}" in guards.json`
3605
+ ));
3606
+ }
3607
+ }
3608
+ }
3609
+ }
3610
+ function checkFailClosedSurfaces(world, findings) {
3611
+ const declaredSurfaces = world.guards?.tool_surfaces;
3612
+ if (!declaredSurfaces || declaredSurfaces.length === 0) return;
3613
+ const guards = world.guards?.guards ?? [];
3614
+ const guardedSurfaces = /* @__PURE__ */ new Set();
3615
+ let hasCatchAllGuard = false;
3616
+ for (const guard of guards) {
3617
+ const enabled = guard.immutable || guard.default_enabled !== false;
3618
+ if (!enabled) continue;
3619
+ if (!guard.appliesTo || guard.appliesTo.length === 0) {
3620
+ hasCatchAllGuard = true;
3621
+ } else {
3622
+ for (const tool of guard.appliesTo) {
3623
+ guardedSurfaces.add(tool.toLowerCase());
3624
+ }
3625
+ }
3626
+ }
3627
+ if (hasCatchAllGuard) return;
3628
+ for (const surface of declaredSurfaces) {
3629
+ if (!guardedSurfaces.has(surface.toLowerCase())) {
3630
+ findings.push(finding(
3631
+ `fail-open-surface-${surface.toLowerCase()}`,
3632
+ `Action surface "${surface}" has no governing guard \u2014 actions on this surface bypass governance entirely`,
3633
+ "warning",
3634
+ "guard-coverage",
3635
+ ["guards.json"],
3636
+ void 0,
3637
+ `Add a guard with appliesTo including "${surface}", or add a catch-all guard (no appliesTo) to cover all surfaces`
3638
+ ));
3639
+ }
3640
+ }
3641
+ }
3642
+ function checkReachability(world, findings) {
3643
+ if (!world.stateSchema?.variables) return;
3644
+ const vars = world.stateSchema.variables;
3645
+ for (const rule of world.rules ?? []) {
3646
+ for (const trigger of rule.triggers) {
3647
+ if (trigger.source !== "state") continue;
3648
+ const unreachable = isTriggerUnreachable(trigger, vars);
3649
+ if (unreachable) {
3650
+ findings.push(finding(
3651
+ `unreachable-rule-${rule.id}-${trigger.field}`,
3652
+ `Rule "${rule.id}" has unreachable trigger: ${trigger.field} ${trigger.operator} ${JSON.stringify(trigger.value)} \u2014 ${unreachable}`,
3653
+ "warning",
3654
+ "contradiction",
3655
+ ["rules/", "state-schema.json"],
3656
+ rule.id,
3657
+ `Remove this rule or adjust the trigger condition to match the schema constraints for "${trigger.field}"`
3658
+ ));
3659
+ }
3660
+ }
3661
+ if (rule.collapse_check) {
3662
+ const cc = rule.collapse_check;
3663
+ const unreachable = isTriggerUnreachable(
3664
+ { field: cc.field, operator: cc.operator, value: cc.value },
3665
+ vars
3666
+ );
3667
+ if (unreachable) {
3668
+ findings.push(finding(
3669
+ `unreachable-collapse-${rule.id}`,
3670
+ `Rule "${rule.id}" has unreachable collapse_check: ${cc.field} ${cc.operator} ${cc.value} \u2014 ${unreachable}`,
3671
+ "warning",
3672
+ "contradiction",
3673
+ ["rules/", "state-schema.json"],
3674
+ rule.id
3675
+ ));
3676
+ }
3677
+ }
3678
+ }
3679
+ for (const gate of world.gates?.viability_classification ?? []) {
3680
+ const unreachable = isTriggerUnreachable(
3681
+ { field: gate.field, operator: gate.operator, value: gate.value },
3682
+ vars
3683
+ );
3684
+ if (unreachable) {
3685
+ findings.push(finding(
3686
+ `unreachable-gate-${gate.status}`,
3687
+ `Viability gate "${gate.status}" has unreachable condition: ${gate.field} ${gate.operator} ${gate.value} \u2014 ${unreachable}`,
3688
+ "warning",
3689
+ "contradiction",
3690
+ ["gates.json", "state-schema.json"],
3691
+ `gate-${gate.status}`
3692
+ ));
3693
+ }
3694
+ }
3695
+ }
3696
+ function isTriggerUnreachable(trigger, vars) {
3697
+ const variable = vars[trigger.field];
3698
+ if (!variable) return null;
3699
+ const { operator, value } = trigger;
3700
+ if (variable.type === "number") {
3701
+ const numVal = typeof value === "number" ? value : Number(value);
3702
+ if (isNaN(numVal)) return null;
3703
+ const min = variable.min;
3704
+ const max = variable.max;
3705
+ if (operator === ">" || operator === ">=") {
3706
+ if (max !== void 0 && numVal >= max && operator === ">") {
3707
+ return `schema declares max=${max}, so ${trigger.field} can never exceed ${max}`;
3708
+ }
3709
+ if (max !== void 0 && numVal > max && operator === ">=") {
3710
+ return `schema declares max=${max}, so ${trigger.field} can never reach ${numVal}`;
3711
+ }
3712
+ }
3713
+ if (operator === "<" || operator === "<=") {
3714
+ if (min !== void 0 && numVal <= min && operator === "<") {
3715
+ return `schema declares min=${min}, so ${trigger.field} can never go below ${min}`;
3716
+ }
3717
+ if (min !== void 0 && numVal < min && operator === "<=") {
3718
+ return `schema declares min=${min}, so ${trigger.field} can never reach ${numVal}`;
3719
+ }
3720
+ }
3721
+ if (operator === "==") {
3722
+ if (min !== void 0 && numVal < min) {
3723
+ return `schema declares min=${min}, so ${trigger.field} can never equal ${numVal}`;
3724
+ }
3725
+ if (max !== void 0 && numVal > max) {
3726
+ return `schema declares max=${max}, so ${trigger.field} can never equal ${numVal}`;
3727
+ }
3728
+ }
3729
+ }
3730
+ if (variable.type === "enum" && variable.options) {
3731
+ if (operator === "==" && typeof value === "string") {
3732
+ if (!variable.options.includes(value)) {
3733
+ return `"${value}" is not in enum options [${variable.options.join(", ")}]`;
3734
+ }
3735
+ }
3736
+ if (operator === "!=" && typeof value === "string") {
3737
+ if (variable.options.length === 1 && variable.options[0] === value) {
3738
+ return `enum has only option "${value}", so != "${value}" can never be true`;
3739
+ }
3740
+ }
3741
+ if (operator === "in" && Array.isArray(value)) {
3742
+ const validValues = value.filter((v) => variable.options.includes(v));
3743
+ if (validValues.length === 0) {
3744
+ return `none of [${value.join(", ")}] are in enum options [${variable.options.join(", ")}]`;
3745
+ }
3746
+ }
3747
+ }
3748
+ if (variable.type === "boolean") {
3749
+ if (operator === "==" && typeof value !== "boolean" && value !== "true" && value !== "false") {
3750
+ return `boolean variable compared to non-boolean value "${value}"`;
3751
+ }
3752
+ }
3753
+ return null;
3754
+ }
3755
+ function checkStateCoverage(world, findings) {
3756
+ if (!world.stateSchema?.variables) return;
3757
+ const vars = world.stateSchema.variables;
3758
+ for (const [varId, variable] of Object.entries(vars)) {
3759
+ if (variable.type !== "enum" || !variable.options || variable.options.length <= 1) continue;
3760
+ const allOptions = new Set(variable.options);
3761
+ const coveredOptions = /* @__PURE__ */ new Set();
3762
+ for (const rule of world.rules ?? []) {
3763
+ for (const trigger of rule.triggers) {
3764
+ if (trigger.field !== varId || trigger.source !== "state") continue;
3765
+ if (trigger.operator === "==" && typeof trigger.value === "string") {
3766
+ coveredOptions.add(trigger.value);
3767
+ }
3768
+ if (trigger.operator === "in" && Array.isArray(trigger.value)) {
3769
+ for (const v of trigger.value) coveredOptions.add(v);
3770
+ }
3771
+ if (trigger.operator === "!=") {
3772
+ for (const opt of allOptions) {
3773
+ if (opt !== trigger.value) coveredOptions.add(opt);
3774
+ }
3775
+ }
3776
+ }
3777
+ }
3778
+ for (const gate of world.gates?.viability_classification ?? []) {
3779
+ if (gate.field !== varId) continue;
3780
+ if (gate.operator === "==" && typeof gate.value === "string") {
3781
+ coveredOptions.add(gate.value);
3782
+ }
3783
+ if (gate.operator === "in" && Array.isArray(gate.value)) {
3784
+ for (const v of gate.value) coveredOptions.add(v);
3785
+ }
3786
+ }
3787
+ if (coveredOptions.size === 0) continue;
3788
+ const uncovered = [...allOptions].filter((opt) => !coveredOptions.has(opt));
3789
+ if (uncovered.length > 0 && uncovered.length < allOptions.size) {
3790
+ findings.push(finding(
3791
+ `incomplete-state-coverage-${varId}`,
3792
+ `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`,
3793
+ "warning",
3794
+ "guard-coverage",
3795
+ ["state-schema.json", "rules/", "gates.json"],
3796
+ varId,
3797
+ `Add rules or gates that handle ${uncovered.map((u) => `"${u}"`).join(", ")} for variable "${varId}"`
3798
+ ));
3799
+ }
3800
+ }
3801
+ }
1865
3802
  function checkOrphans(world, findings) {
1866
3803
  if (!world.stateSchema?.variables || !world.rules) return;
1867
3804
  const referencedVars = /* @__PURE__ */ new Set();
@@ -2023,6 +3960,57 @@ function computeInvariantCoverage(world) {
2023
3960
  }
2024
3961
  return Math.round(covered / world.invariants.length * 100);
2025
3962
  }
3963
+ function computeGovernanceHealth(world, findings) {
3964
+ const guards = world.guards?.guards ?? [];
3965
+ if (guards.length === 0 && !world.kernel) return void 0;
3966
+ const declaredSurfaces = world.guards?.tool_surfaces ?? [];
3967
+ const guardedSurfaces = /* @__PURE__ */ new Set();
3968
+ let hasCatchAll = false;
3969
+ for (const guard of guards) {
3970
+ const enabled = guard.immutable || guard.default_enabled !== false;
3971
+ if (!enabled) continue;
3972
+ if (!guard.appliesTo || guard.appliesTo.length === 0) {
3973
+ hasCatchAll = true;
3974
+ } else {
3975
+ for (const t of guard.appliesTo) guardedSurfaces.add(t.toLowerCase());
3976
+ }
3977
+ }
3978
+ const allSurfaces = /* @__PURE__ */ new Set();
3979
+ for (const s of declaredSurfaces) allSurfaces.add(s.toLowerCase());
3980
+ for (const s of guardedSurfaces) allSurfaces.add(s);
3981
+ const surfaces = [...allSurfaces].map((name) => ({
3982
+ name,
3983
+ governed: hasCatchAll || guardedSurfaces.has(name)
3984
+ }));
3985
+ const surfacesCovered = hasCatchAll ? allSurfaces.size : guardedSurfaces.size;
3986
+ const structuralInvariants = (world.invariants ?? []).filter((i) => i.enforcement === "structural");
3987
+ let invariantsEnforced = 0;
3988
+ for (const inv of structuralInvariants) {
3989
+ const hasGuard = guards.some((g) => g.invariant_ref === inv.id && g.immutable);
3990
+ if (hasGuard) invariantsEnforced++;
3991
+ }
3992
+ const shadowedGuards = findings.filter((f) => f.id.startsWith("guard-shadow-")).length;
3993
+ const unenforcedInvariants = findings.filter((f) => f.id.startsWith("unenforced-invariant-")).length;
3994
+ const unreachableRules = findings.filter((f) => f.id.startsWith("unreachable-")).length;
3995
+ const incompleteStateCoverage = findings.filter((f) => f.id.startsWith("incomplete-state-coverage-")).length;
3996
+ const failOpenCount = findings.filter((f) => f.id.startsWith("fail-open-surface-")).length;
3997
+ let riskLevel = "low";
3998
+ const totalIssues = unenforcedInvariants + failOpenCount + incompleteStateCoverage;
3999
+ if (totalIssues > 0 || unreachableRules > 0) riskLevel = "moderate";
4000
+ if (totalIssues > 2 || unenforcedInvariants > 0 && failOpenCount > 0 || incompleteStateCoverage > 2) riskLevel = "high";
4001
+ return {
4002
+ surfacesCovered,
4003
+ surfacesTotal: allSurfaces.size,
4004
+ surfaces,
4005
+ invariantsEnforced,
4006
+ invariantsTotal: structuralInvariants.length,
4007
+ shadowedGuards,
4008
+ unenforcedInvariants,
4009
+ unreachableRules,
4010
+ incompleteStateCoverage,
4011
+ riskLevel
4012
+ };
4013
+ }
2026
4014
  function finding(id, message, severity, category, affectedBlocks, source, suggestion) {
2027
4015
  const f = { id, message, severity, category, affectedBlocks };
2028
4016
  if (source) f.source = source;
@@ -2077,7 +4065,7 @@ function splitSections(markdown) {
2077
4065
  }
2078
4066
  return { frontmatter, sections };
2079
4067
  }
2080
- function parseFrontmatter(yaml, issues) {
4068
+ function parseFrontmatter2(yaml, issues) {
2081
4069
  const result = {};
2082
4070
  for (const line of yaml.split("\n")) {
2083
4071
  const trimmed = line.trim();
@@ -2426,7 +4414,7 @@ function parseValueLiteral(raw) {
2426
4414
  function parseWorldMarkdown(markdown) {
2427
4415
  const issues = [];
2428
4416
  const { frontmatter: fmRaw, sections } = splitSections(markdown);
2429
- const frontmatter = parseFrontmatter(fmRaw, issues);
4417
+ const frontmatter = parseFrontmatter2(fmRaw, issues);
2430
4418
  const findSection = (name) => sections.find((s) => s.name.toLowerCase() === name.toLowerCase());
2431
4419
  const thesisSection = findSection("Thesis");
2432
4420
  const thesis = thesisSection ? parseThesis(thesisSection.content, thesisSection.startLine, issues) : "";
@@ -2831,12 +4819,12 @@ function normalizeWorldMarkdown(markdown) {
2831
4819
 
2832
4820
  // src/engine/derive-prompt.ts
2833
4821
  var import_promises = require("fs/promises");
2834
- var import_path = require("path");
4822
+ var import_path3 = require("path");
2835
4823
  var import_meta = {};
2836
4824
  var WORLD_FILENAME = "derivation-world.nv-world.md";
2837
4825
  function getModuleDir() {
2838
4826
  try {
2839
- return (0, import_path.dirname)(new URL(import_meta.url).pathname);
4827
+ return (0, import_path3.dirname)(new URL(import_meta.url).pathname);
2840
4828
  } catch {
2841
4829
  return __dirname;
2842
4830
  }
@@ -2844,8 +4832,8 @@ function getModuleDir() {
2844
4832
  async function loadDerivationWorld() {
2845
4833
  const moduleDir = getModuleDir();
2846
4834
  const candidates = [
2847
- (0, import_path.join)(moduleDir, "..", "worlds", WORLD_FILENAME),
2848
- (0, import_path.join)(moduleDir, "worlds", WORLD_FILENAME)
4835
+ (0, import_path3.join)(moduleDir, "..", "worlds", WORLD_FILENAME),
4836
+ (0, import_path3.join)(moduleDir, "worlds", WORLD_FILENAME)
2849
4837
  ];
2850
4838
  for (const candidate of candidates) {
2851
4839
  try {
@@ -3120,15 +5108,15 @@ function createProvider(config) {
3120
5108
 
3121
5109
  // src/providers/config-manager.ts
3122
5110
  var import_promises2 = require("fs/promises");
3123
- var import_path2 = require("path");
5111
+ var import_path4 = require("path");
3124
5112
  var import_os = require("os");
3125
5113
  function getConfigDir() {
3126
5114
  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");
5115
+ if (xdg) return (0, import_path4.join)(xdg, "neuroverse");
5116
+ return (0, import_path4.join)((0, import_os.homedir)(), ".neuroverse");
3129
5117
  }
3130
5118
  function getConfigPath() {
3131
- return (0, import_path2.join)(getConfigDir(), "config.json");
5119
+ return (0, import_path4.join)(getConfigDir(), "config.json");
3132
5120
  }
3133
5121
  async function loadConfig() {
3134
5122
  try {
@@ -4197,12 +6185,21 @@ var CONFIGURE_AI_EXIT_CODES = {
4197
6185
  DERIVE_EXIT_CODES,
4198
6186
  FileAuditLogger,
4199
6187
  GUARD_EXIT_CODES,
6188
+ McpGovernanceServer,
6189
+ ModelAdapter,
6190
+ PLAN_EXIT_CODES,
6191
+ PROVIDERS,
6192
+ SessionManager,
4200
6193
  VALIDATE_EXIT_CODES,
6194
+ advancePlan,
6195
+ buildPlanCheck,
4201
6196
  createGovernanceEngine,
4202
6197
  deriveWorld,
6198
+ describeActiveWorld,
4203
6199
  emitWorldDefinition,
4204
6200
  evaluateCondition,
4205
6201
  evaluateGuard,
6202
+ evaluatePlan,
4206
6203
  eventToAllowlistKey,
4207
6204
  explainWorld,
4208
6205
  extractWorldMarkdown,
@@ -4210,16 +6207,25 @@ var CONFIGURE_AI_EXIT_CODES = {
4210
6207
  formatVerdictOneLine,
4211
6208
  generateImpactReport,
4212
6209
  generateImpactReportFromFile,
6210
+ getActiveWorldName,
6211
+ getPlanProgress,
4213
6212
  improveWorld,
6213
+ listWorlds,
4214
6214
  loadWorld,
4215
6215
  loadWorldFromDirectory,
4216
6216
  normalizeWorldMarkdown,
6217
+ parsePlanMarkdown,
4217
6218
  parseWorldMarkdown,
4218
6219
  readAuditLog,
4219
6220
  renderExplainText,
4220
6221
  renderImpactReport,
4221
6222
  renderImproveText,
4222
6223
  renderSimulateText,
6224
+ resolveProvider,
6225
+ resolveWorldPath,
6226
+ runInteractiveMode,
6227
+ runPipeMode,
6228
+ setActiveWorld,
4223
6229
  simulateWorld,
4224
6230
  summarizeAuditEvents,
4225
6231
  validateWorld,