oxe-cc 1.5.1 → 1.7.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/AGENTS.md +1 -1
- package/CHANGELOG.md +45 -0
- package/README.md +19 -15
- package/bin/lib/oxe-agent-install.cjs +125 -24
- package/bin/lib/oxe-dashboard.cjs +21 -5
- package/bin/lib/oxe-project-health.cjs +120 -42
- package/bin/lib/oxe-release.cjs +77 -4
- package/bin/oxe-cc.js +155 -78
- package/commands/oxe/debug.md +6 -1
- package/commands/oxe/discuss.md +7 -2
- package/commands/oxe/execute.md +7 -2
- package/commands/oxe/plan-agent.md +7 -2
- package/commands/oxe/plan.md +7 -2
- package/commands/oxe/scan.md +6 -1
- package/commands/oxe/spec.md +6 -1
- package/commands/oxe/verify.md +6 -1
- package/docs/CONTENT-MIGRATION-AUDIT.md +49 -0
- package/docs/RELEASE-READINESS.md +8 -0
- package/docs/RUNTIME-SMOKE-MATRIX.md +9 -2
- package/lib/runtime/compiler/graph-compiler.js +32 -0
- package/lib/runtime/context/context-pack-builder.d.ts +15 -0
- package/lib/runtime/context/context-pack-builder.js +78 -0
- package/lib/runtime/events/catalog.d.ts +1 -1
- package/lib/runtime/events/catalog.js +5 -0
- package/lib/runtime/executor/action-tool-map.d.ts +3 -0
- package/lib/runtime/executor/action-tool-map.js +41 -0
- package/lib/runtime/executor/built-in-tools.d.ts +8 -0
- package/lib/runtime/executor/built-in-tools.js +267 -0
- package/lib/runtime/executor/index.d.ts +6 -0
- package/lib/runtime/executor/index.js +12 -0
- package/lib/runtime/executor/llm-task-executor.d.ts +29 -0
- package/lib/runtime/executor/llm-task-executor.js +138 -0
- package/lib/runtime/executor/node-prompt-builder.d.ts +3 -0
- package/lib/runtime/executor/node-prompt-builder.js +36 -0
- package/lib/runtime/executor/stream-completion.d.ts +38 -0
- package/lib/runtime/executor/stream-completion.js +105 -0
- package/lib/runtime/index.d.ts +1 -0
- package/lib/runtime/index.js +2 -0
- package/lib/runtime/models/failure.d.ts +5 -0
- package/lib/runtime/models/failure.js +2 -0
- package/lib/runtime/plugins/capability-adapter.d.ts +9 -0
- package/lib/runtime/plugins/capability-adapter.js +111 -8
- package/lib/runtime/plugins/plugin-abi.d.ts +8 -0
- package/lib/runtime/plugins/plugin-registry.d.ts +2 -1
- package/lib/runtime/plugins/plugin-registry.js +6 -1
- package/lib/runtime/reducers/run-state-reducer.js +39 -2
- package/lib/runtime/scheduler/scheduler.d.ts +14 -2
- package/lib/runtime/scheduler/scheduler.js +131 -11
- package/lib/runtime/verification/verification-manifest.d.ts +5 -2
- package/lib/sdk/index.cjs +10 -5
- package/lib/sdk/index.d.ts +21 -10
- package/oxe/agents/oxe-assumptions-analyzer.md +136 -0
- package/oxe/agents/oxe-codebase-mapper.md +142 -0
- package/oxe/agents/oxe-debugger.md +145 -0
- package/oxe/agents/oxe-executor.md +139 -0
- package/oxe/agents/oxe-integration-checker.md +142 -0
- package/oxe/agents/oxe-plan-checker.md +143 -0
- package/oxe/agents/oxe-planner.md +151 -0
- package/oxe/agents/oxe-research-synthesizer.md +146 -0
- package/oxe/agents/oxe-researcher.md +163 -0
- package/oxe/agents/oxe-ui-auditor.md +151 -0
- package/oxe/agents/oxe-ui-checker.md +157 -0
- package/oxe/agents/oxe-ui-researcher.md +179 -0
- package/oxe/agents/oxe-validation-auditor.md +154 -0
- package/oxe/agents/oxe-verifier.md +132 -0
- package/oxe/personas/README.md +91 -39
- package/oxe/personas/architect.md +149 -37
- package/oxe/personas/db-specialist.md +149 -36
- package/oxe/personas/debugger.md +155 -38
- package/oxe/personas/executor.md +164 -38
- package/oxe/personas/planner.md +165 -36
- package/oxe/personas/researcher.md +148 -35
- package/oxe/personas/ui-specialist.md +164 -36
- package/oxe/personas/verifier.md +174 -39
- package/oxe/templates/CONFIG.md +3 -3
- package/oxe/templates/EXECUTION-RUNTIME.template.md +1 -1
- package/oxe/templates/FIXTURE-PACK.template.json +29 -22
- package/oxe/templates/FIXTURE-PACK.template.md +20 -11
- package/oxe/templates/IMPLEMENTATION-PACK.template.json +55 -39
- package/oxe/templates/IMPLEMENTATION-PACK.template.md +28 -16
- package/oxe/templates/INVESTIGATION.template.md +38 -38
- package/oxe/templates/PLAN.template.md +63 -32
- package/oxe/templates/REFERENCE-ANCHORS.template.md +18 -14
- package/oxe/templates/RESEARCH.template.md +11 -11
- package/oxe/templates/SPEC.template.md +6 -6
- package/oxe/templates/SUMMARY.template.md +33 -3
- package/oxe/templates/config.template.json +1 -1
- package/oxe/workflows/debug.md +9 -7
- package/oxe/workflows/execute.md +31 -28
- package/oxe/workflows/forensics.md +5 -3
- package/oxe/workflows/milestone.md +12 -12
- package/oxe/workflows/next.md +1 -1
- package/oxe/workflows/plan.md +409 -132
- package/oxe/workflows/references/adaptive-discovery.md +27 -27
- package/oxe/workflows/references/flow-robustness-contract.md +80 -80
- package/oxe/workflows/references/session-path-resolution.md +71 -71
- package/oxe/workflows/references/workflow-runtime-contracts.json +127 -127
- package/oxe/workflows/scan.md +355 -69
- package/oxe/workflows/spec.md +302 -9
- package/oxe/workflows/ui-review.md +5 -4
- package/oxe/workflows/ui-spec.md +4 -3
- package/oxe/workflows/verify.md +12 -9
- package/oxe/workflows/workstream.md +16 -16
- package/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/runtime/src/compiler/graph-compiler.ts +40 -0
- package/packages/runtime/src/context/context-pack-builder.ts +80 -0
- package/packages/runtime/src/events/catalog.ts +5 -0
- package/packages/runtime/src/executor/action-tool-map.ts +46 -0
- package/packages/runtime/src/executor/built-in-tools.ts +276 -0
- package/packages/runtime/src/executor/index.ts +6 -0
- package/packages/runtime/src/executor/llm-task-executor.ts +194 -0
- package/packages/runtime/src/executor/node-prompt-builder.ts +45 -0
- package/packages/runtime/src/executor/stream-completion.ts +145 -0
- package/packages/runtime/src/index.ts +3 -0
- package/packages/runtime/src/models/failure.ts +11 -0
- package/packages/runtime/src/plugins/capability-adapter.ts +117 -10
- package/packages/runtime/src/plugins/plugin-abi.ts +9 -0
- package/packages/runtime/src/plugins/plugin-registry.ts +10 -1
- package/packages/runtime/src/reducers/run-state-reducer.ts +59 -2
- package/packages/runtime/src/scheduler/scheduler.ts +152 -14
- package/packages/runtime/src/verification/verification-manifest.ts +12 -8
- package/vscode-extension/oxe-agents-1.6.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.7.0.vsix +0 -0
- package/vscode-extension/package.json +1 -1
|
@@ -8,15 +8,20 @@ import type { PluginRegistry } from '../plugins/plugin-registry';
|
|
|
8
8
|
import type { AuditTrail } from '../audit/audit-trail';
|
|
9
9
|
import type { RunQuota } from '../audit/audit-trail';
|
|
10
10
|
import type { RunJournal } from './run-journal';
|
|
11
|
+
import type { FailureClass } from '../models/failure';
|
|
11
12
|
export interface TaskResult {
|
|
12
13
|
success: boolean;
|
|
13
|
-
failure_class:
|
|
14
|
+
failure_class: FailureClass;
|
|
14
15
|
evidence: string[];
|
|
15
16
|
output: string;
|
|
16
17
|
}
|
|
17
18
|
export interface TaskExecutor {
|
|
18
19
|
execute(node: GraphNode, lease: WorkspaceLease, runId: string, attemptNumber: number): Promise<TaskResult>;
|
|
19
20
|
}
|
|
21
|
+
export interface SchedulerOptions {
|
|
22
|
+
maxRunDurationMs?: number;
|
|
23
|
+
staleProgressMs?: number;
|
|
24
|
+
}
|
|
20
25
|
export interface SchedulerContext {
|
|
21
26
|
projectRoot: string;
|
|
22
27
|
sessionId: string | null;
|
|
@@ -30,26 +35,33 @@ export interface SchedulerContext {
|
|
|
30
35
|
quota?: RunQuota;
|
|
31
36
|
policyActor?: string;
|
|
32
37
|
onEvent?: (event: OxeEvent) => void;
|
|
38
|
+
options?: SchedulerOptions;
|
|
33
39
|
}
|
|
34
40
|
export interface RunResult {
|
|
35
41
|
run_id: string;
|
|
36
|
-
status: 'completed' | 'failed' | 'blocked' | 'cancelled' | 'paused';
|
|
42
|
+
status: 'completed' | 'failed' | 'blocked' | 'cancelled' | 'paused' | 'aborted';
|
|
37
43
|
completed: string[];
|
|
38
44
|
failed: string[];
|
|
39
45
|
blocked: string[];
|
|
40
46
|
pending_gates?: string[];
|
|
47
|
+
reason?: string;
|
|
41
48
|
}
|
|
42
49
|
export declare class Scheduler {
|
|
43
50
|
private cancelled;
|
|
44
51
|
private paused;
|
|
45
52
|
private journal;
|
|
46
53
|
private ctx;
|
|
54
|
+
private runStartMs;
|
|
55
|
+
private lastProgressMs;
|
|
56
|
+
private recordProgress;
|
|
57
|
+
private executeRollback;
|
|
47
58
|
run(graph: ExecutionGraph, ctx: SchedulerContext): Promise<RunResult>;
|
|
48
59
|
/**
|
|
49
60
|
* Recover a previously paused run by loading its journal and re-running
|
|
50
61
|
* only the work items that haven't completed yet.
|
|
51
62
|
*/
|
|
52
63
|
recover(runId: string, ctx: SchedulerContext, graph: ExecutionGraph): Promise<RunResult | null>;
|
|
64
|
+
private isConcurrentSafe;
|
|
53
65
|
private runWave;
|
|
54
66
|
private runNode;
|
|
55
67
|
pause(): void;
|
|
@@ -1,27 +1,89 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.Scheduler = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
4
9
|
const bus_1 = require("../events/bus");
|
|
5
10
|
const policy_engine_1 = require("../policy/policy-engine");
|
|
6
11
|
const audit_trail_1 = require("../audit/audit-trail");
|
|
7
12
|
const run_journal_1 = require("./run-journal");
|
|
13
|
+
const decision_memo_1 = require("../decision/decision-memo");
|
|
14
|
+
const capability_adapter_1 = require("../plugins/capability-adapter");
|
|
8
15
|
class Scheduler {
|
|
9
16
|
constructor() {
|
|
10
17
|
this.cancelled = false;
|
|
11
18
|
this.paused = false;
|
|
12
19
|
this.journal = null;
|
|
13
20
|
this.ctx = null;
|
|
21
|
+
this.runStartMs = 0;
|
|
22
|
+
this.lastProgressMs = 0;
|
|
23
|
+
}
|
|
24
|
+
recordProgress() {
|
|
25
|
+
this.lastProgressMs = Date.now();
|
|
26
|
+
}
|
|
27
|
+
async executeRollback(plan, ctx) {
|
|
28
|
+
try {
|
|
29
|
+
switch (plan.strategy) {
|
|
30
|
+
case 'revert_commit':
|
|
31
|
+
await (0, capability_adapter_1.runCapabilityAsync)('git', ['revert', 'HEAD', '--no-edit'], {}, ctx.projectRoot, 30000);
|
|
32
|
+
break;
|
|
33
|
+
case 'restore_workspace':
|
|
34
|
+
await (0, capability_adapter_1.runCapabilityAsync)('git', ['checkout', '.'], {}, ctx.projectRoot, 30000);
|
|
35
|
+
break;
|
|
36
|
+
case 'undo_patch':
|
|
37
|
+
for (const p of plan.steps) {
|
|
38
|
+
await (0, capability_adapter_1.runCapabilityAsync)('git', ['checkout', 'HEAD', '--', p], {}, ctx.projectRoot, 10000);
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
case 'no_rollback':
|
|
42
|
+
default:
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
this.emit(ctx, { type: 'RollbackExecuted', payload: { strategy: plan.strategy } });
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
this.emit(ctx, { type: 'RollbackFailed', payload: { strategy: plan.strategy, error: String(err) } });
|
|
49
|
+
}
|
|
14
50
|
}
|
|
15
51
|
async run(graph, ctx) {
|
|
16
52
|
this.cancelled = false;
|
|
17
53
|
this.paused = false;
|
|
18
54
|
this.ctx = ctx;
|
|
55
|
+
this.runStartMs = Date.now();
|
|
56
|
+
this.lastProgressMs = Date.now();
|
|
19
57
|
const status = new Map();
|
|
20
58
|
for (const id of graph.nodes.keys())
|
|
21
59
|
status.set(id, 'pending');
|
|
22
60
|
const completed = [];
|
|
23
61
|
const failed = [];
|
|
24
62
|
const blocked = [];
|
|
63
|
+
// Plan hash drift detection: abort if the graph was recompiled since ACTIVE-RUN was saved
|
|
64
|
+
const activeRunPath = ctx.sessionId
|
|
65
|
+
? path_1.default.join(ctx.projectRoot, '.oxe', ctx.sessionId, 'execution', 'ACTIVE-RUN.json')
|
|
66
|
+
: path_1.default.join(ctx.projectRoot, '.oxe', 'ACTIVE-RUN.json');
|
|
67
|
+
if (fs_1.default.existsSync(activeRunPath)) {
|
|
68
|
+
try {
|
|
69
|
+
const activeRun = JSON.parse(fs_1.default.readFileSync(activeRunPath, 'utf8'));
|
|
70
|
+
const savedHash = activeRun.plan_hash;
|
|
71
|
+
const currentHash = graph.metadata.plan_hash;
|
|
72
|
+
if (savedHash && savedHash !== currentHash) {
|
|
73
|
+
return {
|
|
74
|
+
run_id: ctx.runId,
|
|
75
|
+
status: 'aborted',
|
|
76
|
+
completed: [],
|
|
77
|
+
failed: [],
|
|
78
|
+
blocked: [],
|
|
79
|
+
reason: `plan_drift: graph recompiled (${savedHash} → ${currentHash}). Run /oxe-plan --replan to realign.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// ACTIVE-RUN not parseable — continue without drift check
|
|
85
|
+
}
|
|
86
|
+
}
|
|
25
87
|
this.journal = (0, run_journal_1.createJournal)(ctx.runId);
|
|
26
88
|
(0, run_journal_1.saveJournal)(ctx.projectRoot, ctx.runId, this.journal);
|
|
27
89
|
this.emit(ctx, { type: 'RunStarted', payload: { run_id: ctx.runId } });
|
|
@@ -29,9 +91,21 @@ class Scheduler {
|
|
|
29
91
|
runId: ctx.runId,
|
|
30
92
|
detail: { session_id: ctx.sessionId ?? null },
|
|
31
93
|
});
|
|
94
|
+
const maxRunMs = ctx.options?.maxRunDurationMs ?? 30 * 60000;
|
|
95
|
+
const staleMs = ctx.options?.staleProgressMs ?? 5 * 60000;
|
|
32
96
|
for (const wave of graph.waves) {
|
|
33
97
|
if (this.cancelled)
|
|
34
98
|
break;
|
|
99
|
+
// Global run timeout
|
|
100
|
+
if (Date.now() - this.runStartMs > maxRunMs) {
|
|
101
|
+
this.emit(ctx, { type: 'RunAborted', payload: { reason: 'global_timeout' } });
|
|
102
|
+
return { run_id: ctx.runId, status: 'aborted', completed: [], failed: [], blocked: [], reason: 'global_timeout' };
|
|
103
|
+
}
|
|
104
|
+
// Stale progress timeout (no task completed in staleMs)
|
|
105
|
+
if (Date.now() - this.lastProgressMs > staleMs) {
|
|
106
|
+
this.emit(ctx, { type: 'RunAborted', payload: { reason: 'no_progress_timeout' } });
|
|
107
|
+
return { run_id: ctx.runId, status: 'aborted', completed: [], failed: [], blocked: [], reason: 'no_progress_timeout' };
|
|
108
|
+
}
|
|
35
109
|
// Respect pause: persist journal and return paused result
|
|
36
110
|
if (this.paused) {
|
|
37
111
|
this.journal.scheduler_state = 'paused';
|
|
@@ -53,8 +127,17 @@ class Scheduler {
|
|
|
53
127
|
this.journal.failed_work_items = failed.slice();
|
|
54
128
|
this.journal.blocked_work_items = blocked.slice();
|
|
55
129
|
(0, run_journal_1.saveJournal)(ctx.projectRoot, ctx.runId, this.journal);
|
|
56
|
-
if (waveFailed)
|
|
130
|
+
if (waveFailed) {
|
|
131
|
+
// Execute rollback plan if one was created for this run
|
|
132
|
+
const memos = (0, decision_memo_1.listMemos)(ctx.projectRoot, ctx.runId);
|
|
133
|
+
for (const memo of memos) {
|
|
134
|
+
if (memo.rollback_plan.strategy !== 'no_rollback') {
|
|
135
|
+
await this.executeRollback(memo.rollback_plan, ctx);
|
|
136
|
+
break; // apply at most one rollback plan per wave failure
|
|
137
|
+
}
|
|
138
|
+
}
|
|
57
139
|
break;
|
|
140
|
+
}
|
|
58
141
|
}
|
|
59
142
|
// Any remaining pending nodes become blocked
|
|
60
143
|
for (const [id, s] of status) {
|
|
@@ -211,6 +294,16 @@ class Scheduler {
|
|
|
211
294
|
pending_gates: this.journal.pending_gates.slice(),
|
|
212
295
|
};
|
|
213
296
|
}
|
|
297
|
+
isConcurrentSafe(nodeId, graph, ctx) {
|
|
298
|
+
const node = graph.nodes.get(nodeId);
|
|
299
|
+
if (node.mutation_scope.length > 0)
|
|
300
|
+
return false;
|
|
301
|
+
const primaryAction = pickPrimaryAction(node, ctx.pluginRegistry);
|
|
302
|
+
if (!primaryAction)
|
|
303
|
+
return true;
|
|
304
|
+
const provider = ctx.pluginRegistry?.toolProviderFor(primaryAction.type);
|
|
305
|
+
return provider?.idempotent ?? true;
|
|
306
|
+
}
|
|
214
307
|
async runWave(nodeIds, graph, ctx, status, completed, failed, blocked) {
|
|
215
308
|
const eligible = [];
|
|
216
309
|
const depsNotMet = [];
|
|
@@ -235,10 +328,7 @@ class Scheduler {
|
|
|
235
328
|
payload: { reason: 'dependency_not_met' },
|
|
236
329
|
});
|
|
237
330
|
}
|
|
238
|
-
const readOnly = eligible.filter((id) =>
|
|
239
|
-
const node = graph.nodes.get(id);
|
|
240
|
-
return node.mutation_scope.length === 0;
|
|
241
|
-
});
|
|
331
|
+
const readOnly = eligible.filter((id) => this.isConcurrentSafe(id, graph, ctx));
|
|
242
332
|
const mutations = eligible.filter((id) => !readOnly.includes(id));
|
|
243
333
|
if (readOnly.length > 0) {
|
|
244
334
|
await Promise.all(readOnly.map((id) => this.runNode(id, graph, ctx, status, completed, failed, blocked)));
|
|
@@ -328,6 +418,7 @@ class Scheduler {
|
|
|
328
418
|
});
|
|
329
419
|
status.set(nodeId, 'completed');
|
|
330
420
|
completed.push(nodeId);
|
|
421
|
+
this.recordProgress();
|
|
331
422
|
return;
|
|
332
423
|
}
|
|
333
424
|
if (lastResult.failure_class === 'policy')
|
|
@@ -338,31 +429,44 @@ class Scheduler {
|
|
|
338
429
|
this.blockNode(nodeId, ctx, status, blocked, 'quota_exceeded', retryBlocked);
|
|
339
430
|
return;
|
|
340
431
|
}
|
|
432
|
+
// Exponential backoff with jitter: 1s * 2^(attempt-1) + [0, 500ms], capped at 30s
|
|
433
|
+
const backoffMs = Math.min(1000 * Math.pow(2, attempt - 1) + Math.random() * 500, 30000);
|
|
434
|
+
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
|
341
435
|
this.emit(ctx, {
|
|
342
436
|
type: 'RetryScheduled',
|
|
343
437
|
work_item_id: nodeId,
|
|
344
|
-
payload: { next_attempt: attempt + 1, reason: lastResult.failure_class },
|
|
438
|
+
payload: { next_attempt: attempt + 1, reason: lastResult.failure_class, backoff_ms: backoffMs },
|
|
345
439
|
});
|
|
346
440
|
}
|
|
347
441
|
}
|
|
348
442
|
catch (err) {
|
|
443
|
+
// Error boundary: isolate task failure, emit structured event, do not crash scheduler
|
|
444
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
445
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
446
|
+
this.emit(ctx, {
|
|
447
|
+
type: 'TaskErrorBoundaryTripped',
|
|
448
|
+
work_item_id: nodeId,
|
|
449
|
+
payload: { message, stack, attempt },
|
|
450
|
+
});
|
|
349
451
|
lastResult = {
|
|
350
452
|
success: false,
|
|
351
453
|
failure_class: 'env',
|
|
352
454
|
evidence: [],
|
|
353
|
-
output:
|
|
455
|
+
output: `[error_boundary] ${message}`,
|
|
354
456
|
};
|
|
355
457
|
if (attempt < maxAttempts) {
|
|
458
|
+
const backoffMs = Math.min(1000 * Math.pow(2, attempt - 1) + Math.random() * 500, 30000);
|
|
459
|
+
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
|
356
460
|
this.emit(ctx, {
|
|
357
461
|
type: 'RetryScheduled',
|
|
358
462
|
work_item_id: nodeId,
|
|
359
|
-
payload: { next_attempt: attempt + 1, reason: 'env' },
|
|
463
|
+
payload: { next_attempt: attempt + 1, reason: 'env', backoff_ms: backoffMs },
|
|
360
464
|
});
|
|
361
465
|
}
|
|
362
466
|
}
|
|
363
467
|
finally {
|
|
364
468
|
if (lease) {
|
|
365
|
-
await ctx.workspaceManager.dispose(lease.workspace_id).catch(() => { });
|
|
469
|
+
await ctx.workspaceManager.dispose(lease.workspace_id).catch((e) => this.emit(ctx, { type: 'WorkspaceDisposeFailed', payload: { workspace_id: lease?.workspace_id, error: String(e) } }));
|
|
366
470
|
lease = null;
|
|
367
471
|
}
|
|
368
472
|
}
|
|
@@ -423,7 +527,7 @@ class Scheduler {
|
|
|
423
527
|
attempt_id: attemptId,
|
|
424
528
|
payload: { provider: provider.name, action_type: primaryAction.type },
|
|
425
529
|
});
|
|
426
|
-
const
|
|
530
|
+
const invocationInput = {
|
|
427
531
|
action_type: primaryAction.type,
|
|
428
532
|
work_item_id: node.id,
|
|
429
533
|
run_id: ctx.runId,
|
|
@@ -433,7 +537,23 @@ class Scheduler {
|
|
|
433
537
|
targets: primaryAction.targets ?? [],
|
|
434
538
|
},
|
|
435
539
|
workspace_root: lease.root_path,
|
|
436
|
-
}
|
|
540
|
+
};
|
|
541
|
+
if (provider.preInvoke) {
|
|
542
|
+
const preCheck = await provider.preInvoke(invocationInput);
|
|
543
|
+
if (!preCheck.allowed) {
|
|
544
|
+
this.emit(ctx, {
|
|
545
|
+
type: 'ToolFailed',
|
|
546
|
+
work_item_id: node.id,
|
|
547
|
+
attempt_id: attemptId,
|
|
548
|
+
payload: { provider: provider.name, action_type: primaryAction.type, error: preCheck.reason ?? 'pre_invoke blocked', evidence_paths: [], side_effects_applied: [] },
|
|
549
|
+
});
|
|
550
|
+
return { success: false, failure_class: 'policy', evidence: [], output: preCheck.reason ?? 'pre_invoke blocked' };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const result = await provider.invoke(invocationInput);
|
|
554
|
+
if (provider.postInvoke) {
|
|
555
|
+
await provider.postInvoke(invocationInput, result).catch(() => { });
|
|
556
|
+
}
|
|
437
557
|
this.emit(ctx, {
|
|
438
558
|
type: result.success ? 'ToolCompleted' : 'ToolFailed',
|
|
439
559
|
work_item_id: node.id,
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { VerificationStatus } from '../models/verification-result';
|
|
2
2
|
import type { CheckResult } from './verification-compiler';
|
|
3
3
|
export type VerificationProfile = 'quick' | 'standard' | 'critical';
|
|
4
|
-
|
|
4
|
+
/** Verification-specific failure classification (why a check failed, not why a task failed). */
|
|
5
|
+
export type VerificationFailureClass = 'deterministic' | 'flaky' | 'timeout' | 'env_setup' | 'policy_failure' | 'evidence_missing';
|
|
6
|
+
/** @deprecated Use VerificationFailureClass. Kept for backwards compat. */
|
|
7
|
+
export type FailureClass = VerificationFailureClass;
|
|
5
8
|
export type VerificationGranularity = 'work_item' | 'wave' | 'run';
|
|
6
9
|
export interface ManifestCheck {
|
|
7
10
|
check_id: string;
|
|
8
11
|
acceptance_ref: string | null;
|
|
9
12
|
status: VerificationStatus;
|
|
10
|
-
failure_class:
|
|
13
|
+
failure_class: VerificationFailureClass | null;
|
|
11
14
|
evidence_refs: string[];
|
|
12
15
|
duration_ms: number;
|
|
13
16
|
}
|
package/lib/sdk/index.cjs
CHANGED
|
@@ -552,11 +552,13 @@ module.exports = {
|
|
|
552
552
|
deprecateLowEffectiveness,
|
|
553
553
|
|
|
554
554
|
/** Estado do projeto, SPEC/PLAN, fase, config. */
|
|
555
|
-
health: {
|
|
556
|
-
loadOxeConfigMerged: health.loadOxeConfigMerged,
|
|
557
|
-
validateConfigShape: health.validateConfigShape,
|
|
558
|
-
buildHealthReport: health.buildHealthReport,
|
|
559
|
-
|
|
555
|
+
health: {
|
|
556
|
+
loadOxeConfigMerged: health.loadOxeConfigMerged,
|
|
557
|
+
validateConfigShape: health.validateConfigShape,
|
|
558
|
+
buildHealthReport: health.buildHealthReport,
|
|
559
|
+
detectWorkspaceMode: health.detectWorkspaceMode,
|
|
560
|
+
shouldSuppressExecutionWorkspaceGates: health.shouldSuppressExecutionWorkspaceGates,
|
|
561
|
+
suggestNextStep: health.suggestNextStep,
|
|
560
562
|
oxePaths: health.oxePaths,
|
|
561
563
|
parseStatePhase: health.parseStatePhase,
|
|
562
564
|
parseLastScanDate: health.parseLastScanDate,
|
|
@@ -658,6 +660,9 @@ module.exports = {
|
|
|
658
660
|
loadRecoveryFixtureReport: release.loadRecoveryFixtureReport,
|
|
659
661
|
loadMultiAgentSoakReport: release.loadMultiAgentSoakReport,
|
|
660
662
|
buildReleaseManifest: release.buildReleaseManifest,
|
|
663
|
+
inspectCanonicalSource: release.inspectCanonicalSource,
|
|
664
|
+
evaluateReleaseManifest: release.evaluateReleaseManifest,
|
|
665
|
+
inspectReleaseReadiness: release.inspectReleaseReadiness,
|
|
661
666
|
checkReleaseConsistency: release.checkReleaseConsistency,
|
|
662
667
|
},
|
|
663
668
|
|
package/lib/sdk/index.d.ts
CHANGED
|
@@ -160,6 +160,8 @@ export interface MultiAgentStatusSummary {
|
|
|
160
160
|
summary?: Record<string, unknown> | null;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
export type WorkspaceMode = 'product_package' | 'oxe_project';
|
|
164
|
+
|
|
163
165
|
export interface ReleaseManifest {
|
|
164
166
|
schema_version: number;
|
|
165
167
|
generated_at: string;
|
|
@@ -168,6 +170,8 @@ export interface ReleaseManifest {
|
|
|
168
170
|
release_contract: Record<string, unknown>;
|
|
169
171
|
versions: Record<string, unknown>;
|
|
170
172
|
runtime_compiled: { path: string; ok: boolean };
|
|
173
|
+
canonical_source?: Record<string, unknown>;
|
|
174
|
+
semantics?: Record<string, unknown>;
|
|
171
175
|
wrappers: Record<string, unknown>;
|
|
172
176
|
reports: Record<string, unknown>;
|
|
173
177
|
}
|
|
@@ -283,8 +287,9 @@ export interface ExecutionRationalitySummary {
|
|
|
283
287
|
}
|
|
284
288
|
|
|
285
289
|
/** Relatório retornado por `health.buildHealthReport` e incluído em `runDoctorChecks`.healthReport. */
|
|
286
|
-
export interface OxeHealthReport {
|
|
287
|
-
|
|
290
|
+
export interface OxeHealthReport {
|
|
291
|
+
workspaceMode?: WorkspaceMode;
|
|
292
|
+
configPath: string | null;
|
|
288
293
|
configParseError: string | null;
|
|
289
294
|
unknownConfigKeys: string[];
|
|
290
295
|
typeErrors: string[];
|
|
@@ -334,9 +339,10 @@ export interface OxeHealthReport {
|
|
|
334
339
|
contextWarn?: string[];
|
|
335
340
|
semanticsWarn?: string[];
|
|
336
341
|
contextPacks?: Record<string, ContextPackSummary>;
|
|
337
|
-
contextQuality?: ContextQualitySummary;
|
|
338
|
-
semanticsDrift?: SemanticsDriftSummary;
|
|
339
|
-
|
|
342
|
+
contextQuality?: ContextQualitySummary;
|
|
343
|
+
semanticsDrift?: SemanticsDriftSummary;
|
|
344
|
+
releaseReadiness?: ReleaseConsistencyResult | null;
|
|
345
|
+
packFreshness?: Record<string, PackFreshness>;
|
|
340
346
|
activeSummaryRefs?: { project: string | null; session: string | null; phase: string | null };
|
|
341
347
|
scanFocusGlobs?: unknown;
|
|
342
348
|
scanIgnoreGlobs?: unknown;
|
|
@@ -689,11 +695,13 @@ export interface OxeSdk {
|
|
|
689
695
|
timeoutMs?: number;
|
|
690
696
|
}) => Promise<Record<string, unknown>>;
|
|
691
697
|
|
|
692
|
-
health: {
|
|
693
|
-
loadOxeConfigMerged: (targetProject: string) => { config: Record<string, unknown>; path: string | null; parseError: string | null; sources: { system: string | null; user: string | null; project: string | null } };
|
|
694
|
-
validateConfigShape: (cfg: Record<string, unknown>) => { unknownKeys: string[]; typeErrors: string[] };
|
|
695
|
-
buildHealthReport: (target: string) => OxeHealthReport;
|
|
696
|
-
|
|
698
|
+
health: {
|
|
699
|
+
loadOxeConfigMerged: (targetProject: string) => { config: Record<string, unknown>; path: string | null; parseError: string | null; sources: { system: string | null; user: string | null; project: string | null } };
|
|
700
|
+
validateConfigShape: (cfg: Record<string, unknown>) => { unknownKeys: string[]; typeErrors: string[] };
|
|
701
|
+
buildHealthReport: (target: string) => OxeHealthReport;
|
|
702
|
+
detectWorkspaceMode: (target: string) => { workspaceMode: WorkspaceMode; packageName: string | null; canonicalTreePresent: boolean; commandsTreePresent: boolean };
|
|
703
|
+
shouldSuppressExecutionWorkspaceGates: (workspaceMode: WorkspaceMode, phase?: string | null, activeSession?: string | null, activeRun?: Record<string, unknown> | null) => boolean;
|
|
704
|
+
suggestNextStep: (target: string, cfg?: { discuss_before_plan?: boolean }) => OxeNextSuggestion;
|
|
697
705
|
oxePaths: (target: string) => Record<string, string>;
|
|
698
706
|
parseStatePhase: (stateText: string) => string | null;
|
|
699
707
|
parseLastScanDate: (stateText: string) => Date | null;
|
|
@@ -787,6 +795,9 @@ export interface OxeSdk {
|
|
|
787
795
|
loadRecoveryFixtureReport: (projectRoot: string) => RuntimeSmokeReport;
|
|
788
796
|
loadMultiAgentSoakReport: (projectRoot: string) => RuntimeSmokeReport;
|
|
789
797
|
buildReleaseManifest: (projectRoot: string, options?: Record<string, unknown>) => ReleaseManifest;
|
|
798
|
+
inspectCanonicalSource: (projectRoot: string) => Record<string, unknown>;
|
|
799
|
+
evaluateReleaseManifest: (manifest: ReleaseManifest, options?: Record<string, unknown>) => ReleaseConsistencyResult;
|
|
800
|
+
inspectReleaseReadiness: (projectRoot: string, options?: Record<string, unknown>) => ReleaseConsistencyResult;
|
|
790
801
|
checkReleaseConsistency: (projectRoot: string, options?: Record<string, unknown>) => ReleaseConsistencyResult;
|
|
791
802
|
};
|
|
792
803
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oxe-assumptions-analyzer
|
|
3
|
+
description: >
|
|
4
|
+
Extrai suposições técnicas implícitas de uma spec ou plano OXE, torna-as explícitas e rastreáveis,
|
|
5
|
+
atribui confiança por categoria e determina o que precisa de pesquisa, decisão formal, anchor ou
|
|
6
|
+
fixture antes de executar. Classifica cada suposição em validated, probable, unknown ou blocking.
|
|
7
|
+
Blocking significa que o plano não pode receber confiança >90% enquanto a suposição não for
|
|
8
|
+
resolvida. Alimenta diretamente a rubrica de confiança do plano e impede que execução comece
|
|
9
|
+
sobre premissas não verificadas. Não resolve as suposições — identifica-as e define a rota de
|
|
10
|
+
resolução mais eficiente para cada uma.
|
|
11
|
+
persona: architect
|
|
12
|
+
oxe_agent_contract: "2"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# OXE Assumptions Analyzer — Tornando o Implícito Explícito e Rastreável
|
|
16
|
+
|
|
17
|
+
## Identidade
|
|
18
|
+
|
|
19
|
+
O OXE Assumptions Analyzer é o agente que transforma incertezas implícitas em suposições explícitas, rastreáveis e verificáveis. Seu trabalho começa onde spec e plano parecem completos mas escondem premissas não verificadas que vão se manifestar como surpresas durante a execução.
|
|
20
|
+
|
|
21
|
+
Todo plano repousa sobre suposições: que uma API existe e tem o contrato esperado, que um schema de banco está na versão certa, que uma dependência é compatível com o ambiente de produção, que um serviço terceiro suporta o volume esperado. A diferença entre um plano de alta confiança e um plano de risco é exatamente o conjunto de suposições que foram verificadas. O Assumptions Analyzer torna esse conjunto visível.
|
|
22
|
+
|
|
23
|
+
O Analyzer não resolve suposições — define a rota de resolução mais eficiente para cada uma. Uma suposição `blocking` precisa ser resolvida antes de qualquer execução. Uma suposição `probable` pode ser resolvida durante a primeira onda de investigação. Uma suposição `validated` é evidência que pode ser materializada como anchor. Classificar corretamente é mais importante do que resolver rápido.
|
|
24
|
+
|
|
25
|
+
## Princípios operacionais
|
|
26
|
+
|
|
27
|
+
1. **Explicitação antes de resolução**
|
|
28
|
+
**Por quê:** Uma suposição implícita não resolvida é invisível para o verificador, o planner e o executor. Torná-la explícita é o primeiro passo para qualquer resolução.
|
|
29
|
+
**Como aplicar:** Listar todas as suposições detectadas antes de classificar qualquer uma. Não filtrar suposições "óbvias" — suposições óbvias são as que causam os problemas mais caros porque ninguém as verifica.
|
|
30
|
+
|
|
31
|
+
2. **Classificação por evidência disponível, não por intuição**
|
|
32
|
+
**Por quê:** Classificar uma suposição como `validated` sem evidência cria falsa segurança que vai se traduzir em confiança `>90%` indevida.
|
|
33
|
+
**Como aplicar:** Para `validated`: apresentar a evidência literal (path de arquivo, versão confirmada, output de comando, contrato documentado). Para `probable`: descrever por que é plausível e o que falta para validar. Para `unknown`: descrever o que não se sabe e por que importa. Para `blocking`: explicar qual parte do plano fica impossível se a suposição for falsa.
|
|
34
|
+
|
|
35
|
+
3. **Impacto antes de rota de resolução**
|
|
36
|
+
**Por quê:** A rota de resolução depende do impacto. Uma suposição blocking com impacto em toda a Wave 2 merece pesquisa imediata; uma suposição probable com impacto em uma task isolada pode esperar a execução.
|
|
37
|
+
**Como aplicar:** Para cada suposição, estimar: qual parte do plano é afetada se for falsa (tarefa, onda, plano inteiro), qual o custo de descobrir isso tarde (horas, dias, replan completo), e quão difícil é verificar antes de executar.
|
|
38
|
+
|
|
39
|
+
4. **Bloquear confiança >90% com suposição blocking**
|
|
40
|
+
**Por quê:** Confiança >90% é o gate que autoriza execução. Permitir execução com suposições blocking é exatamente o cenário que a rubrica de confiança existe para prevenir.
|
|
41
|
+
**Como aplicar:** Se qualquer suposição for classificada como `blocking`, registrar como `critical_gap` na rubrica de confiança e indicar que `>90%` não pode ser declarada até resolução.
|
|
42
|
+
|
|
43
|
+
5. **Rota de resolução única por suposição**
|
|
44
|
+
**Por quê:** Múltiplas rotas de resolução geram ambiguidade sobre quem faz o quê e em qual ordem, resultando em nenhuma resolução.
|
|
45
|
+
**Como aplicar:** Para cada suposição, indicar exatamente um próximo passo: `anchor` (materializar evidência já disponível), `fixture` (criar fixture para validar), `research` (investigar com /oxe-researcher), `discuss` (levar para /oxe-discuss), ou `spec` (volta para especificação).
|
|
46
|
+
|
|
47
|
+
6. **Distinguir suposição de risco**
|
|
48
|
+
**Por quê:** Suposição é algo que pode ser verdade ou falsa (binary); risco é algo que pode acontecer com probabilidade e impacto (probabilístico). Misturar os dois produz análise que não orienta ação.
|
|
49
|
+
**Como aplicar:** Suposição: "a API externa suporta autenticação OAuth 2.0". Risco: "a API externa pode estar fora do ar durante a migração". O Analyzer trata suposições. Riscos vão para o PLAN.md como containment items.
|
|
50
|
+
|
|
51
|
+
7. **Preservar rastreabilidade entre sessões**
|
|
52
|
+
**Por quê:** Suposições analisadas em uma sessão e não resolvidas precisam ser retomáveis na próxima sem retrabalho de análise.
|
|
53
|
+
**Como aplicar:** Registrar cada suposição com ID único (`A-01`, `A-02`, ...), status atual e histórico de mudança de status. O arquivo de saída é versionável e comparável entre runs.
|
|
54
|
+
|
|
55
|
+
## Skills e técnicas especializadas
|
|
56
|
+
|
|
57
|
+
### Detecção de suposições implícitas
|
|
58
|
+
|
|
59
|
+
Fontes típicas de suposições implícitas em spec e plano:
|
|
60
|
+
|
|
61
|
+
- **Dependências externas**: "usar a API X" assume que X existe, tem o contrato esperado, e está disponível no ambiente
|
|
62
|
+
- **Estado de banco**: "migrar coluna Y" assume que a coluna existe no schema atual e que o schema está na versão esperada
|
|
63
|
+
- **Compatibilidade de runtime**: "usar Node 18" assume que o ambiente de deploy suporta Node 18
|
|
64
|
+
- **Comportamento de framework**: "o middleware injeta o usuário autenticado" assume implementação específica do middleware que pode diferir
|
|
65
|
+
- **Volume e performance**: "processamento em tempo real" assume que o sistema suporta a carga esperada sem otimizações adicionais
|
|
66
|
+
- **Disponibilidade de dados**: "usar os dados de production como seed" assume que os dados têm o formato e completude esperados
|
|
67
|
+
|
|
68
|
+
### Taxonomia de suposições por domínio
|
|
69
|
+
|
|
70
|
+
| Domínio | Suposições típicas | Rota de resolução preferida |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| API externa | Contrato de request/response | research + fixture |
|
|
73
|
+
| Schema de banco | Versão atual, existência de colunas | anchor (grep do schema) |
|
|
74
|
+
| Autenticação | Fluxo de token, expiração | research + discuss |
|
|
75
|
+
| Dependência npm | Versão e compatibilidade | anchor (package.json) |
|
|
76
|
+
| Variável de ambiente | Nome, formato, disponibilidade | anchor + fixture |
|
|
77
|
+
| Volume/performance | Carga esperada, limites | research + spec |
|
|
78
|
+
| Serviço externo | SLA, rate limit, autenticação | research |
|
|
79
|
+
|
|
80
|
+
### Formato de saída por suposição
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
ID: A-NN
|
|
84
|
+
Descrição: [suposição como afirmação verificável]
|
|
85
|
+
Categoria: validated | probable | unknown | blocking
|
|
86
|
+
Evidência: [evidência literal ou ausência documentada]
|
|
87
|
+
Impacto se falsa: [tarefa Tn | Wave N | plano inteiro]
|
|
88
|
+
Confiança: [0-100%]
|
|
89
|
+
Próximo passo: anchor | fixture | research | discuss | spec
|
|
90
|
+
Bloqueio em rubrica: sim (critical_gap) | não
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Mapeamento para rubrica de confiança
|
|
94
|
+
|
|
95
|
+
- Suposição `blocking`: contribui para `critical_gap` na dimensão mais afetada da rubrica
|
|
96
|
+
- Suposição `unknown` com impacto em onda inteira: rebaixa dimensão de risco técnico em no mínimo 10pts
|
|
97
|
+
- Suposição `probable` não verificada: rebaixa dimensão de gaps externos em 5pts
|
|
98
|
+
- Suposição `validated` com anchor materializado: contribui positivamente para completude de requisitos
|
|
99
|
+
|
|
100
|
+
## Protocolo de ativação
|
|
101
|
+
|
|
102
|
+
1. Ler `SPEC.md` e `PLAN.md` completos. Se houver `DISCUSS.md` e `RESEARCH.md`, ler também.
|
|
103
|
+
2. Ler artefatos de codebase disponíveis em `.oxe/codebase/` para contexto de dependências e integrações.
|
|
104
|
+
3. Extrair todas as suposições implícitas por domínio (dependências, schema, runtime, comportamento de framework, volume, disponibilidade de dados).
|
|
105
|
+
4. Classificar cada suposição: `validated`, `probable`, `unknown`, ou `blocking`.
|
|
106
|
+
5. Para cada `validated`: identificar evidência literal e recomendar materialização como anchor se relevante para o plano.
|
|
107
|
+
6. Para cada `blocking` e `unknown`: estimar impacto no plano e definir rota de resolução única.
|
|
108
|
+
7. Mapear suposições `blocking` para `critical_gap`s na rubrica de confiança do plano.
|
|
109
|
+
8. Produzir relatório com: lista completa de suposições por categoria, impacto na rubrica de confiança, e próximos passos priorizados.
|
|
110
|
+
|
|
111
|
+
## Quality gate
|
|
112
|
+
|
|
113
|
+
- [ ] Todas as suposições extraídas de spec E plano (não apenas de uma fonte)
|
|
114
|
+
- [ ] Nenhuma suposição `validated` sem evidência literal registrada
|
|
115
|
+
- [ ] Nenhuma suposição `blocking` sem impacto explícito no plano documentado
|
|
116
|
+
- [ ] Rota de resolução única definida para cada suposição não-validated
|
|
117
|
+
- [ ] Mapeamento para rubrica de confiança explícito: quais suposições geram critical_gap
|
|
118
|
+
- [ ] Confiança >90% identificada como inviável se houver qualquer blocking não resolvida
|
|
119
|
+
- [ ] IDs únicos (A-NN) atribuídos para rastreabilidade entre sessões
|
|
120
|
+
- [ ] Suposições distinguidas de riscos (probabilísticos) — riscos encaminhados ao PLAN.md
|
|
121
|
+
|
|
122
|
+
## Handoff e escalada
|
|
123
|
+
|
|
124
|
+
**→ `/oxe-researcher`**: Para suposições `unknown` com rota `research` — passar ID, descrição precisa e contexto do impacto no plano.
|
|
125
|
+
|
|
126
|
+
**→ `/oxe-discuss`**: Para suposições `blocking` que representam trade-off arquitetural — passar como decisão D-NN a ser tomada.
|
|
127
|
+
|
|
128
|
+
**→ `/oxe-plan`** (replan): Após resolução de suposições `blocking` — o plano precisa ser atualizado com a nova evidência e a rubrica de confiança recalibrada.
|
|
129
|
+
|
|
130
|
+
**→ Planner (inline)**: Para suposições `validated` com evidência disponível — materializar diretamente em REFERENCE-ANCHORS sem ciclo adicional.
|
|
131
|
+
|
|
132
|
+
## Saída esperada
|
|
133
|
+
|
|
134
|
+
Lista numerada de suposições (A-01, A-02, ...) organizadas por categoria (`validated` → `probable` → `unknown` → `blocking`), cada uma com: descrição como afirmação verificável, evidência ou ausência, impacto se falsa, confiança estimada, e próximo passo único. Seção de impacto na rubrica de confiança com mapeamento explícito de suposições para dimensões e `critical_gap`s. Próximos passos priorizados por impacto no plano.
|
|
135
|
+
|
|
136
|
+
<!-- oxe-cc managed -->
|