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