oxe-cc 0.9.3 → 1.0.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.
Files changed (151) hide show
  1. package/README.md +1 -1
  2. package/bin/banner.txt +1 -1
  3. package/bin/lib/oxe-dashboard.cjs +9 -7
  4. package/bin/lib/oxe-operational.cjs +569 -4
  5. package/bin/oxe-cc.js +141 -57
  6. package/lib/runtime/compiler/graph-compiler.d.ts +83 -0
  7. package/lib/runtime/compiler/graph-compiler.js +135 -0
  8. package/lib/runtime/compiler/index.d.ts +1 -0
  9. package/lib/runtime/compiler/index.js +17 -0
  10. package/lib/runtime/context/context-pack-builder.d.ts +36 -0
  11. package/lib/runtime/context/context-pack-builder.js +136 -0
  12. package/lib/runtime/context/index.d.ts +1 -0
  13. package/lib/runtime/context/index.js +17 -0
  14. package/lib/runtime/delivery/branch-manager.d.ts +19 -0
  15. package/lib/runtime/delivery/branch-manager.js +78 -0
  16. package/lib/runtime/delivery/ci-checks.d.ts +34 -0
  17. package/lib/runtime/delivery/ci-checks.js +209 -0
  18. package/lib/runtime/delivery/index.d.ts +3 -0
  19. package/lib/runtime/delivery/index.js +19 -0
  20. package/lib/runtime/delivery/pr-manager.d.ts +30 -0
  21. package/lib/runtime/delivery/pr-manager.js +82 -0
  22. package/lib/runtime/events/bus.d.ts +9 -0
  23. package/lib/runtime/events/bus.js +63 -0
  24. package/lib/runtime/events/catalog.d.ts +3 -0
  25. package/lib/runtime/events/catalog.js +30 -0
  26. package/lib/runtime/events/envelope.d.ts +13 -0
  27. package/lib/runtime/events/envelope.js +2 -0
  28. package/lib/runtime/events/index.d.ts +3 -0
  29. package/lib/runtime/events/index.js +19 -0
  30. package/lib/runtime/evidence/evidence-store.d.ts +22 -0
  31. package/lib/runtime/evidence/evidence-store.js +106 -0
  32. package/lib/runtime/evidence/index.d.ts +1 -0
  33. package/lib/runtime/evidence/index.js +17 -0
  34. package/lib/runtime/gate/gate-manager.d.ts +39 -0
  35. package/lib/runtime/gate/gate-manager.js +104 -0
  36. package/lib/runtime/gate/index.d.ts +1 -0
  37. package/lib/runtime/gate/index.js +17 -0
  38. package/lib/runtime/index.d.ts +16 -0
  39. package/lib/runtime/index.js +40 -0
  40. package/lib/runtime/models/attempt.d.ts +12 -0
  41. package/lib/runtime/models/attempt.js +2 -0
  42. package/lib/runtime/models/evidence.d.ts +9 -0
  43. package/lib/runtime/models/evidence.js +2 -0
  44. package/lib/runtime/models/gate-decision.d.ts +10 -0
  45. package/lib/runtime/models/gate-decision.js +2 -0
  46. package/lib/runtime/models/index.d.ts +8 -0
  47. package/lib/runtime/models/index.js +24 -0
  48. package/lib/runtime/models/run.d.ts +13 -0
  49. package/lib/runtime/models/run.js +2 -0
  50. package/lib/runtime/models/session.d.ts +10 -0
  51. package/lib/runtime/models/session.js +2 -0
  52. package/lib/runtime/models/verification-result.d.ts +9 -0
  53. package/lib/runtime/models/verification-result.js +2 -0
  54. package/lib/runtime/models/work-item.d.ts +15 -0
  55. package/lib/runtime/models/work-item.js +2 -0
  56. package/lib/runtime/models/workspace.d.ts +25 -0
  57. package/lib/runtime/models/workspace.js +2 -0
  58. package/lib/runtime/plugins/index.d.ts +2 -0
  59. package/lib/runtime/plugins/index.js +18 -0
  60. package/lib/runtime/plugins/plugin-abi.d.ts +76 -0
  61. package/lib/runtime/plugins/plugin-abi.js +2 -0
  62. package/lib/runtime/plugins/plugin-registry.d.ts +21 -0
  63. package/lib/runtime/plugins/plugin-registry.js +114 -0
  64. package/lib/runtime/policy/index.d.ts +1 -0
  65. package/lib/runtime/policy/index.js +17 -0
  66. package/lib/runtime/policy/policy-engine.d.ts +40 -0
  67. package/lib/runtime/policy/policy-engine.js +80 -0
  68. package/lib/runtime/projection/index.d.ts +1 -0
  69. package/lib/runtime/projection/index.js +17 -0
  70. package/lib/runtime/projection/projection-engine.d.ts +11 -0
  71. package/lib/runtime/projection/projection-engine.js +218 -0
  72. package/lib/runtime/reducers/debug-reducer.d.ts +10 -0
  73. package/lib/runtime/reducers/debug-reducer.js +30 -0
  74. package/lib/runtime/reducers/index.d.ts +2 -0
  75. package/lib/runtime/reducers/index.js +18 -0
  76. package/lib/runtime/reducers/run-state-reducer.d.ts +20 -0
  77. package/lib/runtime/reducers/run-state-reducer.js +110 -0
  78. package/lib/runtime/scheduler/index.d.ts +1 -0
  79. package/lib/runtime/scheduler/index.js +17 -0
  80. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +34 -0
  81. package/lib/runtime/scheduler/multi-agent-coordinator.js +166 -0
  82. package/lib/runtime/scheduler/scheduler.d.ts +39 -0
  83. package/lib/runtime/scheduler/scheduler.js +196 -0
  84. package/lib/runtime/verification/index.d.ts +1 -0
  85. package/lib/runtime/verification/index.js +17 -0
  86. package/lib/runtime/verification/verification-compiler.d.ts +56 -0
  87. package/lib/runtime/verification/verification-compiler.js +147 -0
  88. package/lib/runtime/workspace/index.d.ts +5 -0
  89. package/lib/runtime/workspace/index.js +24 -0
  90. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +22 -0
  91. package/lib/runtime/workspace/strategies/ephemeral-container.js +109 -0
  92. package/lib/runtime/workspace/strategies/git-worktree.d.ts +12 -0
  93. package/lib/runtime/workspace/strategies/git-worktree.js +79 -0
  94. package/lib/runtime/workspace/strategies/inplace.d.ts +10 -0
  95. package/lib/runtime/workspace/strategies/inplace.js +37 -0
  96. package/lib/runtime/workspace/workspace-manager.d.ts +13 -0
  97. package/lib/runtime/workspace/workspace-manager.js +2 -0
  98. package/lib/sdk/index.cjs +24 -7
  99. package/lib/sdk/index.d.ts +17 -7
  100. package/package.json +9 -3
  101. package/packages/runtime/package.json +17 -0
  102. package/packages/runtime/src/compiler/graph-compiler.ts +245 -0
  103. package/packages/runtime/src/compiler/index.ts +1 -0
  104. package/packages/runtime/src/context/context-pack-builder.ts +193 -0
  105. package/packages/runtime/src/context/index.ts +1 -0
  106. package/packages/runtime/src/delivery/branch-manager.ts +84 -0
  107. package/packages/runtime/src/delivery/ci-checks.ts +252 -0
  108. package/packages/runtime/src/delivery/index.ts +3 -0
  109. package/packages/runtime/src/delivery/pr-manager.ts +112 -0
  110. package/packages/runtime/src/events/bus.ts +92 -0
  111. package/packages/runtime/src/events/catalog.ts +29 -0
  112. package/packages/runtime/src/events/envelope.ts +14 -0
  113. package/packages/runtime/src/events/index.ts +3 -0
  114. package/packages/runtime/src/evidence/evidence-store.ts +130 -0
  115. package/packages/runtime/src/evidence/index.ts +1 -0
  116. package/packages/runtime/src/gate/gate-manager.ts +137 -0
  117. package/packages/runtime/src/gate/index.ts +1 -0
  118. package/packages/runtime/src/index.ts +32 -0
  119. package/packages/runtime/src/models/attempt.ts +19 -0
  120. package/packages/runtime/src/models/evidence.ts +21 -0
  121. package/packages/runtime/src/models/gate-decision.ts +21 -0
  122. package/packages/runtime/src/models/index.ts +8 -0
  123. package/packages/runtime/src/models/run.ts +24 -0
  124. package/packages/runtime/src/models/session.ts +11 -0
  125. package/packages/runtime/src/models/verification-result.ts +10 -0
  126. package/packages/runtime/src/models/work-item.ts +25 -0
  127. package/packages/runtime/src/models/workspace.ts +28 -0
  128. package/packages/runtime/src/plugins/index.ts +2 -0
  129. package/packages/runtime/src/plugins/plugin-abi.ts +95 -0
  130. package/packages/runtime/src/plugins/plugin-registry.ts +119 -0
  131. package/packages/runtime/src/policy/index.ts +1 -0
  132. package/packages/runtime/src/policy/policy-engine.ts +113 -0
  133. package/packages/runtime/src/projection/index.ts +1 -0
  134. package/packages/runtime/src/projection/projection-engine.ts +249 -0
  135. package/packages/runtime/src/reducers/debug-reducer.ts +36 -0
  136. package/packages/runtime/src/reducers/index.ts +2 -0
  137. package/packages/runtime/src/reducers/run-state-reducer.ts +127 -0
  138. package/packages/runtime/src/scheduler/index.ts +1 -0
  139. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +231 -0
  140. package/packages/runtime/src/scheduler/scheduler.ts +281 -0
  141. package/packages/runtime/src/verification/index.ts +1 -0
  142. package/packages/runtime/src/verification/verification-compiler.ts +225 -0
  143. package/packages/runtime/src/workspace/index.ts +5 -0
  144. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +121 -0
  145. package/packages/runtime/src/workspace/strategies/git-worktree.ts +77 -0
  146. package/packages/runtime/src/workspace/strategies/inplace.ts +35 -0
  147. package/packages/runtime/src/workspace/workspace-manager.ts +15 -0
  148. package/packages/runtime/tsconfig.json +17 -0
  149. package/vscode-extension/oxe-agents-0.9.2.vsix +0 -0
  150. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  151. package/vscode-extension/package.json +1 -1
@@ -0,0 +1,137 @@
1
+ import crypto from 'crypto';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { appendEvent } from '../events/bus';
5
+ import type { GateScope, GateDecisionValue } from '../models/gate-decision';
6
+
7
+ export interface GateContext {
8
+ work_item_id?: string;
9
+ run_id?: string;
10
+ description: string;
11
+ evidence_refs: string[];
12
+ risks: string[];
13
+ }
14
+
15
+ export interface GateToken {
16
+ gate_id: string;
17
+ scope: GateScope;
18
+ requested_at: string;
19
+ context: GateContext;
20
+ status: 'pending' | 'resolved';
21
+ decision?: GateDecisionValue;
22
+ actor?: string;
23
+ reason?: string;
24
+ resolved_at?: string;
25
+ }
26
+
27
+ export interface GateResolution {
28
+ decision: GateDecisionValue;
29
+ actor: string;
30
+ reason?: string;
31
+ }
32
+
33
+ export class GateManager {
34
+ constructor(
35
+ private readonly projectRoot: string,
36
+ private readonly sessionId: string | null,
37
+ private readonly runId: string
38
+ ) {}
39
+
40
+ private gatesPath(): string {
41
+ if (this.sessionId) {
42
+ return path.join(this.projectRoot, '.oxe', this.sessionId, 'execution', 'GATES.json');
43
+ }
44
+ return path.join(this.projectRoot, '.oxe', 'execution', 'GATES.json');
45
+ }
46
+
47
+ private readGates(): GateToken[] {
48
+ const p = this.gatesPath();
49
+ if (!fs.existsSync(p)) return [];
50
+ try {
51
+ return JSON.parse(fs.readFileSync(p, 'utf8')) as GateToken[];
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ private writeGates(gates: GateToken[]): void {
58
+ const p = this.gatesPath();
59
+ fs.mkdirSync(path.dirname(p), { recursive: true });
60
+ fs.writeFileSync(p, JSON.stringify(gates, null, 2), 'utf8');
61
+ }
62
+
63
+ async request(scope: GateScope, ctx: GateContext): Promise<GateToken> {
64
+ const token: GateToken = {
65
+ gate_id: `gate-${crypto.randomBytes(4).toString('hex')}`,
66
+ scope,
67
+ requested_at: new Date().toISOString(),
68
+ context: ctx,
69
+ status: 'pending',
70
+ };
71
+
72
+ const gates = this.readGates();
73
+ gates.push(token);
74
+ this.writeGates(gates);
75
+
76
+ appendEvent(this.projectRoot, this.sessionId, {
77
+ type: 'GateRequested',
78
+ run_id: this.runId,
79
+ work_item_id: ctx.work_item_id ?? null,
80
+ payload: {
81
+ gate_id: token.gate_id,
82
+ scope,
83
+ description: ctx.description,
84
+ evidence_refs: ctx.evidence_refs,
85
+ risks: ctx.risks,
86
+ },
87
+ });
88
+
89
+ return token;
90
+ }
91
+
92
+ async resolve(token: GateToken, resolution: GateResolution): Promise<GateToken> {
93
+ const gates = this.readGates();
94
+ const idx = gates.findIndex((g) => g.gate_id === token.gate_id);
95
+ if (idx === -1) throw new Error(`Gate ${token.gate_id} not found`);
96
+
97
+ const resolved: GateToken = {
98
+ ...gates[idx],
99
+ status: 'resolved',
100
+ decision: resolution.decision,
101
+ actor: resolution.actor,
102
+ reason: resolution.reason ?? undefined,
103
+ resolved_at: new Date().toISOString(),
104
+ };
105
+ gates[idx] = resolved;
106
+ this.writeGates(gates);
107
+
108
+ appendEvent(this.projectRoot, this.sessionId, {
109
+ type: 'GateResolved',
110
+ run_id: this.runId,
111
+ payload: {
112
+ gate_id: token.gate_id,
113
+ scope: token.scope,
114
+ decision: resolution.decision,
115
+ actor: resolution.actor,
116
+ },
117
+ });
118
+
119
+ return resolved;
120
+ }
121
+
122
+ isPending(scope: GateScope): boolean {
123
+ return this.readGates().some((g) => g.scope === scope && g.status === 'pending');
124
+ }
125
+
126
+ listPending(): GateToken[] {
127
+ return this.readGates().filter((g) => g.status === 'pending');
128
+ }
129
+
130
+ listAll(): GateToken[] {
131
+ return this.readGates();
132
+ }
133
+
134
+ get(gateId: string): GateToken | null {
135
+ return this.readGates().find((g) => g.gate_id === gateId) ?? null;
136
+ }
137
+ }
@@ -0,0 +1 @@
1
+ export * from './gate-manager';
@@ -0,0 +1,32 @@
1
+ // R1 Public ABI — OXE Runtime Foundation
2
+ export * from './models/index';
3
+ export * from './events/index';
4
+ export * from './reducers/index';
5
+ export * from './compiler/index';
6
+ export * from './scheduler/index';
7
+ export * from './workspace/index';
8
+
9
+ // R2 Public ABI — OXE Evidence & Verification
10
+ export * from './evidence/index';
11
+ // verification exports compile as compileVerification to avoid conflict with compiler/compile
12
+ export {
13
+ compile as compileVerification,
14
+ runCheck,
15
+ runSuite,
16
+ summarizeSuite,
17
+ } from './verification/verification-compiler';
18
+ export type {
19
+ CheckType,
20
+ AcceptanceCheck,
21
+ AcceptanceCheckSuite,
22
+ CheckResult,
23
+ } from './verification/verification-compiler';
24
+ export * from './policy/index';
25
+ export * from './gate/index';
26
+ export * from './projection/index';
27
+
28
+ // R3 Public ABI — OXE Delivery & Extensibility
29
+ export * from './plugins/index';
30
+ export * from './delivery/index';
31
+ export * from './context/index';
32
+ export * from './scheduler/multi-agent-coordinator';
@@ -0,0 +1,19 @@
1
+ export type AttemptOutcome =
2
+ | 'success'
3
+ | 'failure_env'
4
+ | 'failure_policy'
5
+ | 'failure_test'
6
+ | 'failure_timeout'
7
+ | 'cancelled';
8
+
9
+ export interface Attempt {
10
+ attempt_id: string;
11
+ work_item_id: string;
12
+ attempt_number: number;
13
+ workspace_id: string | null;
14
+ agent_profile: string | null;
15
+ model: string | null;
16
+ started_at: string;
17
+ ended_at: string | null;
18
+ outcome: AttemptOutcome | null;
19
+ }
@@ -0,0 +1,21 @@
1
+ export type EvidenceType =
2
+ | 'diff'
3
+ | 'stdout'
4
+ | 'stderr'
5
+ | 'junit_xml'
6
+ | 'coverage'
7
+ | 'screenshot'
8
+ | 'trace'
9
+ | 'log'
10
+ | 'security_report'
11
+ | 'api_output'
12
+ | 'summary';
13
+
14
+ export interface Evidence {
15
+ evidence_id: string;
16
+ attempt_id: string;
17
+ type: EvidenceType;
18
+ path: string;
19
+ checksum: string | null;
20
+ created_at: string;
21
+ }
@@ -0,0 +1,21 @@
1
+ export type GateDecisionValue =
2
+ | 'approved'
3
+ | 'rejected'
4
+ | 'approved_with_caveats'
5
+ | 'needs_more_evidence';
6
+
7
+ export type GateScope =
8
+ | 'plan_approval'
9
+ | 'critical_mutation'
10
+ | 'security'
11
+ | 'pr_promotion'
12
+ | 'merge';
13
+
14
+ export interface GateDecision {
15
+ gate_id: string;
16
+ scope: GateScope;
17
+ decision: GateDecisionValue;
18
+ actor: string;
19
+ reason: string | null;
20
+ timestamp: string;
21
+ }
@@ -0,0 +1,8 @@
1
+ export * from './session';
2
+ export * from './run';
3
+ export * from './work-item';
4
+ export * from './attempt';
5
+ export * from './workspace';
6
+ export * from './evidence';
7
+ export * from './gate-decision';
8
+ export * from './verification-result';
@@ -0,0 +1,24 @@
1
+ export type RunStatus =
2
+ | 'planned'
3
+ | 'running'
4
+ | 'paused'
5
+ | 'waiting_approval'
6
+ | 'failed'
7
+ | 'completed'
8
+ | 'replaying'
9
+ | 'aborted'
10
+ | 'cancelled';
11
+
12
+ export type RunMode = 'completo' | 'por_onda' | 'por_tarefa';
13
+ export type RunInitiator = 'user' | 'scheduler' | 'retry' | 'replay';
14
+
15
+ export interface Run {
16
+ run_id: string;
17
+ session_id: string | null;
18
+ graph_version: string;
19
+ started_at: string;
20
+ ended_at: string | null;
21
+ status: RunStatus;
22
+ initiator: RunInitiator;
23
+ mode: RunMode;
24
+ }
@@ -0,0 +1,11 @@
1
+ export type SessionStatus = 'active' | 'archived' | 'closed';
2
+
3
+ export interface Session {
4
+ session_id: string;
5
+ title: string;
6
+ created_at: string;
7
+ status: SessionStatus;
8
+ repo_root: string;
9
+ baseline_commit: string | null;
10
+ active_run_id: string | null;
11
+ }
@@ -0,0 +1,10 @@
1
+ export type VerificationStatus = 'pass' | 'fail' | 'skip' | 'error';
2
+
3
+ export interface VerificationResult {
4
+ verification_id: string;
5
+ work_item_id: string;
6
+ check_id: string;
7
+ status: VerificationStatus;
8
+ evidence_refs: string[];
9
+ summary: string | null;
10
+ }
@@ -0,0 +1,25 @@
1
+ import type { WorkspaceStrategy } from './workspace';
2
+
3
+ export type WorkItemStatus =
4
+ | 'pending'
5
+ | 'ready'
6
+ | 'running'
7
+ | 'completed'
8
+ | 'failed'
9
+ | 'blocked'
10
+ | 'skipped';
11
+
12
+ export type WorkItemType = 'task' | 'checkpoint' | 'gate' | 'verification';
13
+
14
+ export interface WorkItem {
15
+ work_item_id: string;
16
+ run_id: string;
17
+ title: string;
18
+ type: WorkItemType;
19
+ depends_on: string[];
20
+ mutation_scope: string[];
21
+ policy_ref: string | null;
22
+ verify_ref: string[];
23
+ status: WorkItemStatus;
24
+ workspace_strategy: WorkspaceStrategy;
25
+ }
@@ -0,0 +1,28 @@
1
+ export type WorkspaceStrategy = 'inplace' | 'git_worktree' | 'ephemeral_container';
2
+ export type WorkspaceStatus = 'allocating' | 'ready' | 'dirty' | 'disposed' | 'error';
3
+
4
+ export interface Workspace {
5
+ workspace_id: string;
6
+ strategy: WorkspaceStrategy;
7
+ base_commit: string | null;
8
+ branch: string | null;
9
+ container_ref: string | null;
10
+ status: WorkspaceStatus;
11
+ root_path: string;
12
+ }
13
+
14
+ export interface WorkspaceLease {
15
+ workspace_id: string;
16
+ strategy: WorkspaceStrategy;
17
+ branch: string | null;
18
+ base_commit: string | null;
19
+ root_path: string;
20
+ ttl_minutes: number;
21
+ }
22
+
23
+ export interface SnapshotRef {
24
+ snapshot_id: string;
25
+ workspace_id: string;
26
+ commit: string;
27
+ created_at: string;
28
+ }
@@ -0,0 +1,2 @@
1
+ export * from './plugin-abi';
2
+ export * from './plugin-registry';
@@ -0,0 +1,95 @@
1
+ import type { WorkspaceRequest, WorkspaceManager } from '../workspace/workspace-manager';
2
+ import type { WorkspaceLease, SnapshotRef } from '../models/workspace';
3
+ import type { VerificationResult } from '../models/verification-result';
4
+
5
+ // ─── ToolProvider ────────────────────────────────────────────────────────────
6
+
7
+ export interface ToolInvocationInput {
8
+ action_type: string;
9
+ work_item_id: string;
10
+ run_id: string;
11
+ attempt_id: string;
12
+ params: Record<string, unknown>;
13
+ workspace_root: string;
14
+ }
15
+
16
+ export interface ToolInvocationResult {
17
+ success: boolean;
18
+ output: string;
19
+ evidence_paths: string[];
20
+ side_effects_applied: string[];
21
+ error?: string;
22
+ }
23
+
24
+ export interface ToolProvider {
25
+ readonly name: string;
26
+ readonly kind: 'read' | 'mutation' | 'verification' | 'analysis' | 'external_operation';
27
+ readonly idempotent: boolean;
28
+ supports(actionType: string): boolean;
29
+ invoke(input: ToolInvocationInput): Promise<ToolInvocationResult>;
30
+ }
31
+
32
+ // ─── WorkspaceProvider ───────────────────────────────────────────────────────
33
+
34
+ export interface WorkspaceProvider extends WorkspaceManager {
35
+ readonly name: string;
36
+ supportsStrategy(strategy: string): boolean;
37
+ }
38
+
39
+ // ─── VerifierProvider ────────────────────────────────────────────────────────
40
+
41
+ export interface VerificationInput {
42
+ check_id: string;
43
+ check_type: string;
44
+ command: string | null;
45
+ work_item_id: string;
46
+ workspace_root: string;
47
+ evidence_dir: string;
48
+ }
49
+
50
+ export interface VerifierProvider {
51
+ readonly name: string;
52
+ supports(checkType: string): boolean;
53
+ execute(input: VerificationInput): Promise<VerificationResult>;
54
+ }
55
+
56
+ // ─── ContextProvider ─────────────────────────────────────────────────────────
57
+
58
+ export interface ContextRequest {
59
+ work_item_id: string;
60
+ run_id: string;
61
+ decision_type: 'execute' | 'verify' | 'plan' | 'review';
62
+ artifact_paths: string[];
63
+ project_root: string;
64
+ }
65
+
66
+ export interface PluginContextArtifact {
67
+ source: string;
68
+ weight: number;
69
+ reason: string;
70
+ content?: string;
71
+ }
72
+
73
+ export interface PluginContextArtifacts {
74
+ included: PluginContextArtifact[];
75
+ excluded: Array<{ source: string; reason: string }>;
76
+ total_weight: number;
77
+ }
78
+
79
+ export interface ContextProvider {
80
+ readonly name: string;
81
+ collect(input: ContextRequest): Promise<PluginContextArtifacts>;
82
+ }
83
+
84
+ // ─── OxePlugin (unified) ─────────────────────────────────────────────────────
85
+
86
+ export interface OxePlugin {
87
+ readonly name: string;
88
+ readonly version?: string;
89
+ toolProviders?: ToolProvider[];
90
+ workspaceProviders?: WorkspaceProvider[];
91
+ verifierProviders?: VerifierProvider[];
92
+ contextProviders?: ContextProvider[];
93
+ /** Legacy lifecycle hooks (compatible with oxe-plugins.cjs) */
94
+ hooks?: Record<string, (ctx: Record<string, unknown>) => Promise<void> | void>;
95
+ }
@@ -0,0 +1,119 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import type {
4
+ OxePlugin,
5
+ ToolProvider,
6
+ WorkspaceProvider,
7
+ VerifierProvider,
8
+ ContextProvider,
9
+ } from './plugin-abi';
10
+
11
+ export class PluginRegistry {
12
+ private plugins: OxePlugin[] = [];
13
+
14
+ register(plugin: OxePlugin): void {
15
+ if (this.plugins.some((p) => p.name === plugin.name)) {
16
+ throw new Error(`Plugin "${plugin.name}" is already registered`);
17
+ }
18
+ this.plugins.push(plugin);
19
+ }
20
+
21
+ unregister(name: string): void {
22
+ this.plugins = this.plugins.filter((p) => p.name !== name);
23
+ }
24
+
25
+ loadFromDirectory(dir: string): string[] {
26
+ if (!fs.existsSync(dir)) return [];
27
+ const loaded: string[] = [];
28
+ for (const file of fs.readdirSync(dir)) {
29
+ if (!file.endsWith('.cjs') && !file.endsWith('.js')) continue;
30
+ const fullPath = path.resolve(dir, file);
31
+ try {
32
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
33
+ const mod = require(fullPath) as OxePlugin | { default?: OxePlugin };
34
+ const plugin = 'default' in mod && mod.default ? mod.default : (mod as OxePlugin);
35
+ if (plugin && plugin.name) {
36
+ this.register(plugin);
37
+ loaded.push(plugin.name);
38
+ }
39
+ } catch {
40
+ // skip invalid plugin files
41
+ }
42
+ }
43
+ return loaded;
44
+ }
45
+
46
+ toolProviderFor(actionType: string): ToolProvider | null {
47
+ for (const plugin of this.plugins) {
48
+ const provider = plugin.toolProviders?.find((p) => p.supports(actionType));
49
+ if (provider) return provider;
50
+ }
51
+ return null;
52
+ }
53
+
54
+ workspaceProviderFor(strategy: string): WorkspaceProvider | null {
55
+ for (const plugin of this.plugins) {
56
+ const provider = plugin.workspaceProviders?.find((p) => p.supportsStrategy(strategy));
57
+ if (provider) return provider;
58
+ }
59
+ return null;
60
+ }
61
+
62
+ verifierProviderFor(checkType: string): VerifierProvider | null {
63
+ for (const plugin of this.plugins) {
64
+ const provider = plugin.verifierProviders?.find((p) => p.supports(checkType));
65
+ if (provider) return provider;
66
+ }
67
+ return null;
68
+ }
69
+
70
+ contextProviderFor(name: string): ContextProvider | null {
71
+ for (const plugin of this.plugins) {
72
+ const provider = plugin.contextProviders?.find((p) => p.name === name);
73
+ if (provider) return provider;
74
+ }
75
+ return null;
76
+ }
77
+
78
+ allContextProviders(): ContextProvider[] {
79
+ return this.plugins.flatMap((p) => p.contextProviders ?? []);
80
+ }
81
+
82
+ allToolProviders(): ToolProvider[] {
83
+ return this.plugins.flatMap((p) => p.toolProviders ?? []);
84
+ }
85
+
86
+ async runHook(
87
+ hookName: string,
88
+ ctx: Record<string, unknown>
89
+ ): Promise<void> {
90
+ for (const plugin of this.plugins) {
91
+ const hook = plugin.hooks?.[hookName];
92
+ if (hook) await hook(ctx);
93
+ }
94
+ }
95
+
96
+ list(): Array<{ name: string; version?: string; providers: string[] }> {
97
+ return this.plugins.map((p) => ({
98
+ name: p.name,
99
+ version: p.version,
100
+ providers: [
101
+ ...(p.toolProviders?.map((tp) => `tool:${tp.name}`) ?? []),
102
+ ...(p.workspaceProviders?.map((wp) => `workspace:${wp.name}`) ?? []),
103
+ ...(p.verifierProviders?.map((vp) => `verifier:${vp.name}`) ?? []),
104
+ ...(p.contextProviders?.map((cp) => `context:${cp.name}`) ?? []),
105
+ ],
106
+ }));
107
+ }
108
+ }
109
+
110
+ let _globalRegistry: PluginRegistry | null = null;
111
+
112
+ export function globalRegistry(): PluginRegistry {
113
+ if (!_globalRegistry) _globalRegistry = new PluginRegistry();
114
+ return _globalRegistry;
115
+ }
116
+
117
+ export function resetGlobalRegistry(): void {
118
+ _globalRegistry = null;
119
+ }
@@ -0,0 +1 @@
1
+ export * from './policy-engine';
@@ -0,0 +1,113 @@
1
+ export type PolicyAction = 'allow' | 'deny' | 'require_human_gate';
2
+
3
+ export interface PolicyWhenClause {
4
+ tool?: string;
5
+ env?: string;
6
+ kind?: string;
7
+ }
8
+
9
+ export interface PolicyAssertClause {
10
+ diff_within_scope?: boolean;
11
+ }
12
+
13
+ export interface PolicyRule {
14
+ id: string;
15
+ when: PolicyWhenClause;
16
+ assert?: PolicyAssertClause;
17
+ action: PolicyAction;
18
+ }
19
+
20
+ export interface PolicyContext {
21
+ tool: string;
22
+ env?: string;
23
+ kind?: string;
24
+ mutation_scope?: string[];
25
+ affected_paths?: string[];
26
+ }
27
+
28
+ export interface PolicyDecision {
29
+ allowed: boolean;
30
+ gate_required: boolean;
31
+ reason: string;
32
+ rule_id: string | null;
33
+ }
34
+
35
+ const ALLOW_ALL: PolicyDecision = {
36
+ allowed: true,
37
+ gate_required: false,
38
+ reason: 'no matching policy — default allow',
39
+ rule_id: null,
40
+ };
41
+
42
+ export class PolicyEngine {
43
+ constructor(private readonly rules: PolicyRule[] = []) {}
44
+
45
+ evaluate(ctx: PolicyContext): PolicyDecision {
46
+ for (const rule of this.rules) {
47
+ if (!this.matches(rule.when, ctx)) continue;
48
+
49
+ if (rule.assert) {
50
+ const assertFailed = this.checkAssert(rule.assert, ctx);
51
+ if (assertFailed) {
52
+ return {
53
+ allowed: false,
54
+ gate_required: false,
55
+ reason: `Assert failed for rule ${rule.id}: ${assertFailed}`,
56
+ rule_id: rule.id,
57
+ };
58
+ }
59
+ }
60
+
61
+ switch (rule.action) {
62
+ case 'allow':
63
+ return { allowed: true, gate_required: false, reason: `Allowed by rule ${rule.id}`, rule_id: rule.id };
64
+ case 'deny':
65
+ return { allowed: false, gate_required: false, reason: `Denied by rule ${rule.id}`, rule_id: rule.id };
66
+ case 'require_human_gate':
67
+ return { allowed: true, gate_required: true, reason: `Gate required by rule ${rule.id}`, rule_id: rule.id };
68
+ }
69
+ }
70
+ return ALLOW_ALL;
71
+ }
72
+
73
+ private matches(when: PolicyWhenClause, ctx: PolicyContext): boolean {
74
+ if (when.tool && when.tool !== ctx.tool) return false;
75
+ if (when.env && when.env !== ctx.env) return false;
76
+ if (when.kind && when.kind !== ctx.kind) return false;
77
+ return true;
78
+ }
79
+
80
+ private checkAssert(assert: PolicyAssertClause, ctx: PolicyContext): string | null {
81
+ if (assert.diff_within_scope === true) {
82
+ const scope = ctx.mutation_scope ?? [];
83
+ const affected = ctx.affected_paths ?? [];
84
+ if (scope.length === 0) return null; // no scope declared — pass
85
+ const outsideScope = affected.filter(
86
+ (p) => !scope.some((s) => p.startsWith(s) || s.startsWith(p))
87
+ );
88
+ if (outsideScope.length > 0) {
89
+ return `paths outside mutation scope: ${outsideScope.join(', ')}`;
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+
95
+ withRule(rule: PolicyRule): PolicyEngine {
96
+ return new PolicyEngine([...this.rules, rule]);
97
+ }
98
+
99
+ static fromConfig(config: { policies?: PolicyRule[] }): PolicyEngine {
100
+ return new PolicyEngine(config.policies ?? []);
101
+ }
102
+
103
+ static fromConfigFile(configPath: string): PolicyEngine {
104
+ try {
105
+ // Dynamic require to avoid bundling issues
106
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
107
+ const cfg = require(configPath) as { policies?: PolicyRule[] };
108
+ return PolicyEngine.fromConfig(cfg);
109
+ } catch {
110
+ return new PolicyEngine();
111
+ }
112
+ }
113
+ }
@@ -0,0 +1 @@
1
+ export * from './projection-engine';