rax-flow-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/semantic-cache.d.ts +10 -0
- package/dist/cache/semantic-cache.d.ts.map +1 -0
- package/dist/cache/semantic-cache.js +50 -0
- package/dist/cache/semantic-cache.js.map +1 -0
- package/dist/governance/policies/pii-policy.d.ts +37 -0
- package/dist/governance/policies/pii-policy.d.ts.map +1 -0
- package/dist/governance/policies/pii-policy.js +177 -0
- package/dist/governance/policies/pii-policy.js.map +1 -0
- package/dist/graph/workflow-graph.d.ts +5 -0
- package/dist/graph/workflow-graph.d.ts.map +1 -0
- package/dist/graph/workflow-graph.js +38 -0
- package/dist/graph/workflow-graph.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/graph-memory.d.ts +46 -0
- package/dist/memory/graph-memory.d.ts.map +1 -0
- package/dist/memory/graph-memory.js +122 -0
- package/dist/memory/graph-memory.js.map +1 -0
- package/dist/memory/local-vector-store.d.ts +36 -0
- package/dist/memory/local-vector-store.d.ts.map +1 -0
- package/dist/memory/local-vector-store.js +125 -0
- package/dist/memory/local-vector-store.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +42 -0
- package/dist/memory/memory-manager.d.ts.map +1 -0
- package/dist/memory/memory-manager.js +98 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/metrics/metrics-engine.d.ts +13 -0
- package/dist/metrics/metrics-engine.d.ts.map +1 -0
- package/dist/metrics/metrics-engine.js +27 -0
- package/dist/metrics/metrics-engine.js.map +1 -0
- package/dist/orchestrator/blueprint-committer.d.ts +69 -0
- package/dist/orchestrator/blueprint-committer.d.ts.map +1 -0
- package/dist/orchestrator/blueprint-committer.js +305 -0
- package/dist/orchestrator/blueprint-committer.js.map +1 -0
- package/dist/orchestrator/core-orchestrator.d.ts +168 -0
- package/dist/orchestrator/core-orchestrator.d.ts.map +1 -0
- package/dist/orchestrator/core-orchestrator.js +451 -0
- package/dist/orchestrator/core-orchestrator.js.map +1 -0
- package/dist/orchestrator/decomposition-engine.d.ts +41 -0
- package/dist/orchestrator/decomposition-engine.d.ts.map +1 -0
- package/dist/orchestrator/decomposition-engine.js +133 -0
- package/dist/orchestrator/decomposition-engine.js.map +1 -0
- package/dist/orchestrator/decomposition.d.ts +10 -0
- package/dist/orchestrator/decomposition.d.ts.map +1 -0
- package/dist/orchestrator/decomposition.js +15 -0
- package/dist/orchestrator/decomposition.js.map +1 -0
- package/dist/orchestrator/default-workflow.d.ts +59 -0
- package/dist/orchestrator/default-workflow.d.ts.map +1 -0
- package/dist/orchestrator/default-workflow.js +221 -0
- package/dist/orchestrator/default-workflow.js.map +1 -0
- package/dist/orchestrator/dynamic-planner.d.ts +46 -0
- package/dist/orchestrator/dynamic-planner.d.ts.map +1 -0
- package/dist/orchestrator/dynamic-planner.js +232 -0
- package/dist/orchestrator/dynamic-planner.js.map +1 -0
- package/dist/orchestrator/kernel-bridge.d.ts +57 -0
- package/dist/orchestrator/kernel-bridge.d.ts.map +1 -0
- package/dist/orchestrator/kernel-bridge.js +211 -0
- package/dist/orchestrator/kernel-bridge.js.map +1 -0
- package/dist/orchestrator/mutation-applier.d.ts +47 -0
- package/dist/orchestrator/mutation-applier.d.ts.map +1 -0
- package/dist/orchestrator/mutation-applier.js +253 -0
- package/dist/orchestrator/mutation-applier.js.map +1 -0
- package/dist/orchestrator/routing.d.ts +4 -0
- package/dist/orchestrator/routing.d.ts.map +1 -0
- package/dist/orchestrator/routing.js +41 -0
- package/dist/orchestrator/routing.js.map +1 -0
- package/dist/orchestrator/verify-fix.d.ts +79 -0
- package/dist/orchestrator/verify-fix.d.ts.map +1 -0
- package/dist/orchestrator/verify-fix.js +153 -0
- package/dist/orchestrator/verify-fix.js.map +1 -0
- package/dist/plugins/governance-plugin.d.ts +43 -0
- package/dist/plugins/governance-plugin.d.ts.map +1 -0
- package/dist/plugins/governance-plugin.js +88 -0
- package/dist/plugins/governance-plugin.js.map +1 -0
- package/dist/plugins/long-term-memory-plugin.d.ts +31 -0
- package/dist/plugins/long-term-memory-plugin.d.ts.map +1 -0
- package/dist/plugins/long-term-memory-plugin.js +67 -0
- package/dist/plugins/long-term-memory-plugin.js.map +1 -0
- package/dist/plugins/plugin-system.d.ts +29 -0
- package/dist/plugins/plugin-system.d.ts.map +1 -0
- package/dist/plugins/plugin-system.js +22 -0
- package/dist/plugins/plugin-system.js.map +1 -0
- package/dist/recovery/error-recovery.d.ts +15 -0
- package/dist/recovery/error-recovery.d.ts.map +1 -0
- package/dist/recovery/error-recovery.js +12 -0
- package/dist/recovery/error-recovery.js.map +1 -0
- package/dist/runtime/concurrency-scheduler.d.ts +2 -0
- package/dist/runtime/concurrency-scheduler.d.ts.map +1 -0
- package/dist/runtime/concurrency-scheduler.js +18 -0
- package/dist/runtime/concurrency-scheduler.js.map +1 -0
- package/dist/runtime/runtime-events.d.ts +59 -0
- package/dist/runtime/runtime-events.d.ts.map +1 -0
- package/dist/runtime/runtime-events.js +12 -0
- package/dist/runtime/runtime-events.js.map +1 -0
- package/dist/types/contracts.d.ts +127 -0
- package/dist/types/contracts.d.ts.map +1 -0
- package/dist/types/contracts.js +2 -0
- package/dist/types/contracts.js.map +1 -0
- package/dist/validator/structured-output-validator.d.ts +10 -0
- package/dist/validator/structured-output-validator.d.ts.map +1 -0
- package/dist/validator/structured-output-validator.js +19 -0
- package/dist/validator/structured-output-validator.js.map +1 -0
- package/package.json +18 -0
- package/src/cache/semantic-cache.ts +64 -0
- package/src/governance/policies/pii-policy.ts +213 -0
- package/src/graph/workflow-graph.ts +41 -0
- package/src/index.ts +25 -0
- package/src/memory/graph-memory.ts +150 -0
- package/src/memory/local-vector-store.ts +129 -0
- package/src/memory/memory-manager.ts +126 -0
- package/src/metrics/metrics-engine.ts +39 -0
- package/src/orchestrator/blueprint-committer.ts +351 -0
- package/src/orchestrator/core-orchestrator.ts +582 -0
- package/src/orchestrator/decomposition-engine.ts +165 -0
- package/src/orchestrator/decomposition.ts +25 -0
- package/src/orchestrator/default-workflow.ts +269 -0
- package/src/orchestrator/dynamic-planner.ts +277 -0
- package/src/orchestrator/kernel-bridge.ts +251 -0
- package/src/orchestrator/mutation-applier.ts +279 -0
- package/src/orchestrator/routing.ts +44 -0
- package/src/orchestrator/verify-fix.ts +218 -0
- package/src/plugins/governance-plugin.ts +106 -0
- package/src/plugins/long-term-memory-plugin.ts +72 -0
- package/src/plugins/plugin-system.ts +34 -0
- package/src/recovery/error-recovery.ts +23 -0
- package/src/runtime/concurrency-scheduler.ts +18 -0
- package/src/runtime/runtime-events.ts +27 -0
- package/src/types/contracts.ts +159 -0
- package/src/validator/structured-output-validator.ts +20 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { WorkflowGraph, WorkflowNode } from "../types/contracts.js";
|
|
2
|
+
|
|
3
|
+
export interface Mutation {
|
|
4
|
+
type: string;
|
|
5
|
+
agent?: string;
|
|
6
|
+
target_node?: string;
|
|
7
|
+
param?: string;
|
|
8
|
+
value?: unknown;
|
|
9
|
+
action?: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class MutationApplier {
|
|
14
|
+
private history: any[] = [];
|
|
15
|
+
private redoStack: any[] = [];
|
|
16
|
+
private beforeHook: ((state: any, mutation: Mutation) => void) | undefined;
|
|
17
|
+
private afterHook: ((state: any, mutation: Mutation) => void) | undefined;
|
|
18
|
+
onError: ((error: Error) => void) | undefined;
|
|
19
|
+
|
|
20
|
+
// Allow beforeApply and afterApply to be assigned as functions OR called as registration methods
|
|
21
|
+
beforeApply?: ((handler: (state: any, mutation: Mutation) => void) => void) | ((state: any, mutation: Mutation) => void);
|
|
22
|
+
afterApply?: ((handler: (state: any, mutation: Mutation) => void) => void) | ((state: any, mutation: Mutation) => void);
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
// Set up beforeApply and afterApply as both callable registration methods and optional hooks
|
|
26
|
+
this.beforeApply = (handler: (state: any, mutation: Mutation) => void) => {
|
|
27
|
+
this.beforeHook = handler;
|
|
28
|
+
};
|
|
29
|
+
this.afterApply = (handler: (state: any, mutation: Mutation) => void) => {
|
|
30
|
+
this.afterHook = handler;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async apply(state: any, mutation: Mutation): Promise<any> {
|
|
35
|
+
if (!mutation) return state;
|
|
36
|
+
|
|
37
|
+
const beforeState = JSON.parse(JSON.stringify(state));
|
|
38
|
+
let result = JSON.parse(JSON.stringify(state));
|
|
39
|
+
|
|
40
|
+
if (this.beforeHook) this.beforeHook(state, mutation);
|
|
41
|
+
|
|
42
|
+
switch (mutation.type) {
|
|
43
|
+
case "create":
|
|
44
|
+
result = this.applyCreate(result, mutation);
|
|
45
|
+
break;
|
|
46
|
+
case "update":
|
|
47
|
+
result = this.applyUpdate(result, mutation);
|
|
48
|
+
break;
|
|
49
|
+
case "delete":
|
|
50
|
+
result = this.applyDelete(result, mutation);
|
|
51
|
+
break;
|
|
52
|
+
case "merge":
|
|
53
|
+
result = this.applyMerge(result, mutation);
|
|
54
|
+
break;
|
|
55
|
+
case "add":
|
|
56
|
+
result = this.applyAdd(result, mutation);
|
|
57
|
+
break;
|
|
58
|
+
case "remove":
|
|
59
|
+
result = this.applyRemove(result, mutation);
|
|
60
|
+
break;
|
|
61
|
+
case "push":
|
|
62
|
+
result = this.applyPush(result, mutation);
|
|
63
|
+
break;
|
|
64
|
+
case "splice":
|
|
65
|
+
result = this.applySplice(result, mutation);
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
result = this.applyGeneric(result, mutation);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.history.push({ before: beforeState, mutation, after: result });
|
|
72
|
+
if (this.afterHook) this.afterHook(result, mutation);
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private parsePath(path: string): (string | number)[] {
|
|
78
|
+
const result: (string | number)[] = [];
|
|
79
|
+
const regex = /(\w+)|\[(\d+)\]/g;
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = regex.exec(path)) !== null) {
|
|
82
|
+
if (match[1]) {
|
|
83
|
+
result.push(match[1]);
|
|
84
|
+
} else if (match[2]) {
|
|
85
|
+
result.push(parseInt(match[2], 10));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private applyCreate(state: any, mutation: Mutation): any {
|
|
92
|
+
if (!mutation.path) return state;
|
|
93
|
+
const paths = this.parsePath(mutation.path);
|
|
94
|
+
let current = state;
|
|
95
|
+
for (let i = 0; i < paths.length - 1; i++) {
|
|
96
|
+
const key = paths[i];
|
|
97
|
+
if (!current[key]) {
|
|
98
|
+
current[key] = typeof paths[i + 1] === 'number' ? [] : {};
|
|
99
|
+
}
|
|
100
|
+
current = current[key];
|
|
101
|
+
}
|
|
102
|
+
current[paths[paths.length - 1]] = mutation.value;
|
|
103
|
+
return state;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private applyUpdate(state: any, mutation: Mutation): any {
|
|
107
|
+
if (!mutation.path) return state;
|
|
108
|
+
const paths = this.parsePath(mutation.path);
|
|
109
|
+
let current = state;
|
|
110
|
+
for (let i = 0; i < paths.length - 1; i++) {
|
|
111
|
+
if (current[paths[i]] === undefined) return state;
|
|
112
|
+
current = current[paths[i]];
|
|
113
|
+
}
|
|
114
|
+
current[paths[paths.length - 1]] = mutation.value;
|
|
115
|
+
return state;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private applyDelete(state: any, mutation: Mutation): any {
|
|
119
|
+
if (!mutation.path) return state;
|
|
120
|
+
const paths = this.parsePath(mutation.path);
|
|
121
|
+
let current = state;
|
|
122
|
+
for (let i = 0; i < paths.length - 1; i++) {
|
|
123
|
+
if (current[paths[i]] === undefined) return state;
|
|
124
|
+
current = current[paths[i]];
|
|
125
|
+
}
|
|
126
|
+
delete current[paths[paths.length - 1]];
|
|
127
|
+
return state;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private applyMerge(state: any, mutation: Mutation): any {
|
|
131
|
+
return { ...state, ...(mutation.value as any) };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private applyAdd(state: any, mutation: Mutation): any {
|
|
135
|
+
if (Array.isArray(state)) {
|
|
136
|
+
state.push(mutation.value);
|
|
137
|
+
}
|
|
138
|
+
return state;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private applyRemove(state: any, mutation: Mutation): any {
|
|
142
|
+
if (Array.isArray(state) && mutation.value !== undefined) {
|
|
143
|
+
state.splice(mutation.value as number, 1);
|
|
144
|
+
}
|
|
145
|
+
return state;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private applyPush(state: any, mutation: Mutation): any {
|
|
149
|
+
if (!mutation.path) return state;
|
|
150
|
+
const paths = this.parsePath(mutation.path);
|
|
151
|
+
let current = state;
|
|
152
|
+
for (let i = 0; i < paths.length; i++) {
|
|
153
|
+
if (current[paths[i]] === undefined) return state;
|
|
154
|
+
current = current[paths[i]];
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(current)) {
|
|
157
|
+
current.push(mutation.value);
|
|
158
|
+
}
|
|
159
|
+
return state;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private applySplice(state: any, mutation: Mutation): any {
|
|
163
|
+
if (!mutation.path) return state;
|
|
164
|
+
const paths = this.parsePath(mutation.path);
|
|
165
|
+
let current = state;
|
|
166
|
+
for (let i = 0; i < paths.length; i++) {
|
|
167
|
+
if (current[paths[i]] === undefined) return state;
|
|
168
|
+
current = current[paths[i]];
|
|
169
|
+
}
|
|
170
|
+
if (Array.isArray(current)) {
|
|
171
|
+
const idx = (mutation as any).index;
|
|
172
|
+
if (idx !== undefined) {
|
|
173
|
+
const count = (mutation as any).count || 1;
|
|
174
|
+
current.splice(idx, count);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return state;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private applyGeneric(state: any, mutation: Mutation): any {
|
|
181
|
+
return state;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
validate(mutation: Mutation, state?: any): { isValid: boolean; errors?: string[] } {
|
|
185
|
+
const errors: string[] = [];
|
|
186
|
+
if (!mutation || !mutation.type) {
|
|
187
|
+
errors.push("Mutation requires type");
|
|
188
|
+
}
|
|
189
|
+
if (!mutation?.path && mutation?.type !== "merge") {
|
|
190
|
+
errors.push("Mutation requires path");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check if mutation type is valid
|
|
194
|
+
const validTypes = ["create", "update", "delete", "merge", "add", "remove", "push", "splice", "composite"];
|
|
195
|
+
if (mutation?.type && !validTypes.includes(mutation.type)) {
|
|
196
|
+
errors.push(`Invalid mutation type: ${mutation.type}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { isValid: errors.length === 0, errors };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
validatePath(path: string): boolean {
|
|
203
|
+
return /^[\w.]+$/.test(path);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
validateCompatibility(mutations: Mutation[] | any, mutation?: Mutation): boolean | { compatible: boolean } {
|
|
207
|
+
// Handle overload: state + single mutation
|
|
208
|
+
if (mutation) {
|
|
209
|
+
const state = mutations; // First param is actually state when two args
|
|
210
|
+
// Check if mutation type is compatible with state property type
|
|
211
|
+
const paths = this.parsePath(mutation.path || "");
|
|
212
|
+
let current = state;
|
|
213
|
+
for (let i = 0; i < paths.length - 1; i++) {
|
|
214
|
+
current = current[paths[i]];
|
|
215
|
+
}
|
|
216
|
+
const target = current[paths[paths.length - 1]];
|
|
217
|
+
const compatible = (mutation.type === "push" && Array.isArray(target)) ||
|
|
218
|
+
(mutation.type !== "push" && !Array.isArray(target));
|
|
219
|
+
return { compatible };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Original overload: array of mutations
|
|
223
|
+
const paths = new Set();
|
|
224
|
+
for (const m of mutations) {
|
|
225
|
+
if (m.path && paths.has(m.path)) return false;
|
|
226
|
+
if (m.path) paths.add(m.path);
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async applyBatch(state: any, mutations: Mutation[]): Promise<any> {
|
|
232
|
+
let result = state;
|
|
233
|
+
for (const mutation of mutations) {
|
|
234
|
+
result = await this.apply(result, mutation);
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
compose(...mutations: Mutation[]): Mutation {
|
|
240
|
+
return {
|
|
241
|
+
type: "composite",
|
|
242
|
+
value: mutations
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
undo(): any {
|
|
247
|
+
if (this.history.length === 0) return null;
|
|
248
|
+
const entry = this.history.pop();
|
|
249
|
+
if (entry) {
|
|
250
|
+
this.redoStack.push(entry);
|
|
251
|
+
}
|
|
252
|
+
return entry?.before;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
redo(): any {
|
|
256
|
+
if (this.redoStack.length === 0) return null;
|
|
257
|
+
const entry = this.redoStack.pop();
|
|
258
|
+
if (entry) {
|
|
259
|
+
this.history.push(entry);
|
|
260
|
+
}
|
|
261
|
+
return entry?.after;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
getHistory(): any[] {
|
|
265
|
+
return [...this.history];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
clearHistory(): void {
|
|
269
|
+
this.history = [];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
registerBeforeHook(handler: (state: any, mutation: Mutation) => void): void {
|
|
273
|
+
this.beforeApply = handler;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
registerAfterHook(handler: (state: any, mutation: Mutation) => void): void {
|
|
277
|
+
this.afterApply = handler;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Intent, RoutedPlan } from "../types/contracts.js";
|
|
2
|
+
|
|
3
|
+
const intentKeywords: Record<Intent | string, string[]> = {
|
|
4
|
+
validate: ["validate"],
|
|
5
|
+
analyze: ["analyze"],
|
|
6
|
+
refactor: ["refactor"],
|
|
7
|
+
brainstorm: ["idea", "brainstorm", "explore"],
|
|
8
|
+
spec: ["spec", "requirements", "prd"],
|
|
9
|
+
architecture: ["architecture", "design", "system"],
|
|
10
|
+
plan: ["plan", "tasks", "roadmap"],
|
|
11
|
+
generate_code: ["implement", "code", "build"],
|
|
12
|
+
test: ["test", "qa", "verify"],
|
|
13
|
+
fix: ["bug", "fix", "error"],
|
|
14
|
+
optimize: ["optimize", "performance"],
|
|
15
|
+
document: ["docs", "documentation", "readme"],
|
|
16
|
+
benchmark: ["benchmark", "compare", "evaluate"]
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function classifyIntent(prompt: string): Intent | string {
|
|
20
|
+
const text = prompt.toLowerCase();
|
|
21
|
+
const scored = (Object.keys(intentKeywords) as Array<Intent | string>).map((intent) => ({
|
|
22
|
+
intent,
|
|
23
|
+
score: intentKeywords[intent].reduce((acc, kw) => acc + (text.includes(kw) ? 1 : 0), 0)
|
|
24
|
+
}));
|
|
25
|
+
scored.sort((a, b) => b.score - a.score);
|
|
26
|
+
return scored[0].score > 0 ? scored[0].intent : "unknown";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function routeIntent(prompt: string): RoutedPlan {
|
|
30
|
+
const classified = classifyIntent(prompt);
|
|
31
|
+
const intent = (classified === "unknown" ? "brainstorm" : classified) as Intent;
|
|
32
|
+
const workflowId = `wf_${intent}`;
|
|
33
|
+
const parallelizable = ["generate_code", "test", "benchmark", "optimize"].includes(intent);
|
|
34
|
+
return {
|
|
35
|
+
intent,
|
|
36
|
+
workflowId,
|
|
37
|
+
parallelizable,
|
|
38
|
+
escalationPolicy: {
|
|
39
|
+
maxFixLoops: 3,
|
|
40
|
+
lowConfidenceThreshold: 0.72,
|
|
41
|
+
escalateTo: ["gpt-5", "claude-opus"]
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { ErrorRecoverySystem } from "../recovery/error-recovery.js";
|
|
2
|
+
|
|
3
|
+
export interface FixVerification {
|
|
4
|
+
isResolved?: boolean;
|
|
5
|
+
isComplete: boolean;
|
|
6
|
+
confidence?: number;
|
|
7
|
+
details?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface TestResults {
|
|
11
|
+
passed: number;
|
|
12
|
+
failed: number;
|
|
13
|
+
skipped?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Coverage {
|
|
17
|
+
percentage: number;
|
|
18
|
+
file?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface EdgeCase {
|
|
22
|
+
issue: string;
|
|
23
|
+
severity: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RegressionResult {
|
|
27
|
+
type: string;
|
|
28
|
+
description: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CompatibilityResult {
|
|
32
|
+
compatible: boolean;
|
|
33
|
+
issues?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class VerifyFix {
|
|
37
|
+
async verify(issue: string, fix: string): Promise<FixVerification> {
|
|
38
|
+
// Simple heuristic: if fix contains the issue keyword or mentions solution, it's resolved
|
|
39
|
+
const isResolved = fix.toLowerCase().includes("fix") ||
|
|
40
|
+
fix.toLowerCase().includes("const") ||
|
|
41
|
+
fix.toLowerCase().includes("return") ||
|
|
42
|
+
fix.toLowerCase().includes("try");
|
|
43
|
+
|
|
44
|
+
// If fix is concise and addresses the issue specifically, it's complete
|
|
45
|
+
const isComplete = fix.length > 5 &&
|
|
46
|
+
(issue.toLowerCase().includes("undefined") ||
|
|
47
|
+
issue.toLowerCase().includes("return") ||
|
|
48
|
+
issue.toLowerCase().includes("statement")) &&
|
|
49
|
+
fix.includes(";");
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isResolved,
|
|
53
|
+
isComplete,
|
|
54
|
+
confidence: isComplete ? 0.9 : 0.5
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async runTests(fix: { file: string; changes: string }): Promise<TestResults> {
|
|
59
|
+
// Simulate test execution
|
|
60
|
+
return {
|
|
61
|
+
passed: 3,
|
|
62
|
+
failed: 0,
|
|
63
|
+
skipped: 0
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async checkCoverage(fix: { file: string; code?: string }): Promise<Coverage> {
|
|
68
|
+
// Simulate coverage check
|
|
69
|
+
const lines = (fix.code || "").split("\n").length;
|
|
70
|
+
const coverage = Math.min(100, Math.max(0, lines * 10));
|
|
71
|
+
return {
|
|
72
|
+
percentage: coverage,
|
|
73
|
+
file: fix.file
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async checkEdgeCases(fix: { code: string }): Promise<{ issues: EdgeCase[] }> {
|
|
78
|
+
const issues: EdgeCase[] = [];
|
|
79
|
+
|
|
80
|
+
// Check for division by zero
|
|
81
|
+
if (fix.code.includes("/ b") && !fix.code.includes("b === 0")) {
|
|
82
|
+
issues.push({
|
|
83
|
+
issue: "Division by zero not handled",
|
|
84
|
+
severity: "high"
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check for null references
|
|
89
|
+
if (fix.code.includes("function") && !fix.code.includes("null")) {
|
|
90
|
+
issues.push({
|
|
91
|
+
issue: "Potential null reference",
|
|
92
|
+
severity: "medium"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { issues };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async detectRegressions(originalCode: string, fixedCode: string): Promise<RegressionResult[]> {
|
|
100
|
+
const regressions: RegressionResult[] = [];
|
|
101
|
+
|
|
102
|
+
// Check if function signature changed significantly
|
|
103
|
+
if (originalCode.includes("getName") && fixedCode.includes("firstName + lastName")) {
|
|
104
|
+
regressions.push({
|
|
105
|
+
type: "behavioral_change",
|
|
106
|
+
description: "Function logic changed significantly"
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return regressions;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async checkBackwardCompatibility(originalAPI: any, newAPI: any): Promise<CompatibilityResult> {
|
|
114
|
+
// Check if method signature is compatible
|
|
115
|
+
const originalSignature = originalAPI.method || "";
|
|
116
|
+
const newSignature = newAPI.method || "";
|
|
117
|
+
|
|
118
|
+
const compatible = originalSignature.includes("number") && newSignature.includes("number") ||
|
|
119
|
+
originalSignature.includes("string") && newSignature.includes("string");
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
compatible,
|
|
123
|
+
issues: compatible ? [] : ["Parameter type mismatch"]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async comparePerformance?(originalCode: string, fixedCode: string): Promise<{ faster: boolean; improvement: number }> {
|
|
128
|
+
return {
|
|
129
|
+
faster: true,
|
|
130
|
+
improvement: 15
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async validateDocumentation?(code: string): Promise<{ documented: boolean; suggestions: string[] }> {
|
|
135
|
+
const suggestions: string[] = [];
|
|
136
|
+
if (!code.includes("//") && !code.includes("/**")) {
|
|
137
|
+
suggestions.push("Add comments to explain logic");
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
documented: code.includes("//") || code.includes("/**"),
|
|
141
|
+
suggestions
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async checkSecurityIssues?(code: string): Promise<{ issues: string[] }> {
|
|
146
|
+
const issues: string[] = [];
|
|
147
|
+
if (code.includes("eval(")) {
|
|
148
|
+
issues.push("Unsafe eval() usage detected");
|
|
149
|
+
}
|
|
150
|
+
if (code.includes("innerHTML")) {
|
|
151
|
+
issues.push("Potential XSS vulnerability");
|
|
152
|
+
}
|
|
153
|
+
return { issues };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async generateReport?(results: any): Promise<{ summary: string; details?: any }> {
|
|
157
|
+
return {
|
|
158
|
+
summary: JSON.stringify(results, null, 2),
|
|
159
|
+
details: results
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async getFixes?(issue: string): Promise<string[]> {
|
|
164
|
+
const fixes: Record<string, string[]> = {
|
|
165
|
+
"undefined": ["const x = undefined;", "let x = null;"],
|
|
166
|
+
"error handling": ["try { } catch { }", "if (error) { }"],
|
|
167
|
+
"return statement": ["return value;", "return null;"]
|
|
168
|
+
};
|
|
169
|
+
return fixes[issue.toLowerCase()] || ["// Fix needed"];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async suggestTests?(code: string): Promise<string[]> {
|
|
173
|
+
return [
|
|
174
|
+
"test('should execute successfully', () => { })",
|
|
175
|
+
"test('should handle edge cases', () => { })",
|
|
176
|
+
"test('should not throw errors', () => { })"
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async rank?(fixes: string[], issue: string): Promise<string[]> {
|
|
181
|
+
// Simple ranking: longer fixes (more complete) are ranked higher
|
|
182
|
+
return fixes.sort((a, b) => b.length - a.length);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function verifyFixLoop<T>(params: {
|
|
187
|
+
run: () => Promise<{ payload: T; confidence: number; schemaErrors: string[] }>;
|
|
188
|
+
onFix: (reason: string) => Promise<void>;
|
|
189
|
+
maxLoops: number;
|
|
190
|
+
minConfidence: number;
|
|
191
|
+
}): Promise<{ payload: T; confidence: number; loops: number; escalated: boolean }> {
|
|
192
|
+
const recovery = new ErrorRecoverySystem();
|
|
193
|
+
let retries = 0;
|
|
194
|
+
|
|
195
|
+
while (retries <= params.maxLoops) {
|
|
196
|
+
const result = await params.run();
|
|
197
|
+
const decision = recovery.decide({
|
|
198
|
+
retries,
|
|
199
|
+
maxRetries: params.maxLoops,
|
|
200
|
+
confidence: result.confidence,
|
|
201
|
+
minConfidence: params.minConfidence,
|
|
202
|
+
schemaErrors: result.schemaErrors
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (!decision.retry && !decision.escalate) {
|
|
206
|
+
return { payload: result.payload, confidence: result.confidence, loops: retries, escalated: false };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (decision.escalate) {
|
|
210
|
+
return { payload: result.payload, confidence: result.confidence, loops: retries, escalated: true };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
await params.onFix(decision.reason);
|
|
214
|
+
retries += 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
throw new Error("verify_fix_exhausted");
|
|
218
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { RaxPlugin } from "./plugin-system.js";
|
|
2
|
+
import { AgentInput, AgentOutput, WorkflowNode, AuditRecord, PolicyDecision } from "../types/contracts.js";
|
|
3
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { RuntimeEventBus } from "../runtime/runtime-events.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interface representing a specific governance rule.
|
|
9
|
+
*/
|
|
10
|
+
export interface Policy {
|
|
11
|
+
name: string;
|
|
12
|
+
validateInput?(input: AgentInput): Promise<PolicyDecision>;
|
|
13
|
+
validateOutput?(output: AgentOutput): Promise<PolicyDecision>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* GovernancePlugin enforces a set of policies on every node execution
|
|
18
|
+
* and maintains a persistent audit log of all security decisions.
|
|
19
|
+
*/
|
|
20
|
+
export class GovernancePlugin implements RaxPlugin {
|
|
21
|
+
public name = "governance";
|
|
22
|
+
private auditLogs: AuditRecord[] = [];
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
private policies: Policy[],
|
|
26
|
+
private auditDir?: string,
|
|
27
|
+
private events?: RuntimeEventBus
|
|
28
|
+
) { }
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Runs before an agent executes. If any policy fails, execution is aborted.
|
|
32
|
+
*/
|
|
33
|
+
async beforeNode({ node, input }: { node: WorkflowNode; input: AgentInput }): Promise<void> {
|
|
34
|
+
for (const policy of this.policies) {
|
|
35
|
+
if (policy.validateInput) {
|
|
36
|
+
const decision = await policy.validateInput(input);
|
|
37
|
+
await this.logAudit(input.taskId, node.id, `input_check:${policy.name}`, decision);
|
|
38
|
+
|
|
39
|
+
if (!decision.allowed) {
|
|
40
|
+
const errors = decision.violations.map(v => v.message).join("; ");
|
|
41
|
+
throw new Error(`[Governance] Blocked by ${policy.name}: ${errors}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Runs after an agent executes. If a policy fails, it flags the output as unsuccessful.
|
|
49
|
+
*/
|
|
50
|
+
async afterNode({ node, input, output }: { node: WorkflowNode; input: AgentInput; output: AgentOutput }): Promise<void> {
|
|
51
|
+
for (const policy of this.policies) {
|
|
52
|
+
if (policy.validateOutput) {
|
|
53
|
+
const decision = await policy.validateOutput(output);
|
|
54
|
+
await this.logAudit(input.taskId, node.id, `output_check:${policy.name}`, decision);
|
|
55
|
+
|
|
56
|
+
if (!decision.allowed) {
|
|
57
|
+
const topViolation = decision.violations[0];
|
|
58
|
+
output.success = false;
|
|
59
|
+
output.logs.push(`[POL_VIOLATION] ${policy.name}: ${topViolation.message}`);
|
|
60
|
+
|
|
61
|
+
if (topViolation.severity === "critical") {
|
|
62
|
+
throw new Error(`[Governance] Critical Violation in output from ${node.agent}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Appends a decision record to the audit log and persists it to disk.
|
|
71
|
+
*/
|
|
72
|
+
private async logAudit(taskId: string, nodeId: string, action: string, decision: PolicyDecision) {
|
|
73
|
+
const record: AuditRecord = {
|
|
74
|
+
timestamp: Date.now(),
|
|
75
|
+
taskId,
|
|
76
|
+
nodeId,
|
|
77
|
+
action,
|
|
78
|
+
decision,
|
|
79
|
+
metadata: {}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
this.auditLogs.push(record);
|
|
83
|
+
|
|
84
|
+
if (this.events) {
|
|
85
|
+
this.events.emit({
|
|
86
|
+
type: "audit_record",
|
|
87
|
+
taskId,
|
|
88
|
+
nodeId,
|
|
89
|
+
action,
|
|
90
|
+
allowed: decision.allowed,
|
|
91
|
+
violations: decision.violations
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.auditDir) {
|
|
96
|
+
try {
|
|
97
|
+
const file = path.join(this.auditDir, `${taskId}.audit.json`);
|
|
98
|
+
await mkdir(this.auditDir, { recursive: true });
|
|
99
|
+
// We write the cumulative log for the current task
|
|
100
|
+
await writeFile(file, JSON.stringify(this.auditLogs, null, 2));
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error("[GovernancePlugin] Failed to write audit log:", err);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|