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,252 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import type { EvidenceStore } from '../evidence/evidence-store';
4
+
5
+ export type CICheckStatus = 'pass' | 'fail' | 'skip' | 'error';
6
+
7
+ export interface CICheckContext {
8
+ projectRoot: string;
9
+ sessionId: string | null;
10
+ runId?: string;
11
+ evidenceStore?: EvidenceStore;
12
+ }
13
+
14
+ export interface CICheckResult {
15
+ check: string;
16
+ status: CICheckStatus;
17
+ message: string;
18
+ details?: unknown;
19
+ }
20
+
21
+ export interface CICheck {
22
+ name: string;
23
+ description: string;
24
+ run(ctx: CICheckContext): Promise<CICheckResult>;
25
+ }
26
+
27
+ // ─── Check: plan-consistency ─────────────────────────────────────────────────
28
+
29
+ export const planConsistencyCheck: CICheck = {
30
+ name: 'oxe-plan-consistency',
31
+ description: 'Verifies ACTIVE-RUN.json exists and has a compiled ExecutionGraph',
32
+ async run(ctx) {
33
+ const activeRunPath = ctx.sessionId
34
+ ? path.join(ctx.projectRoot, '.oxe', ctx.sessionId, 'execution', 'ACTIVE-RUN.json')
35
+ : path.join(ctx.projectRoot, '.oxe', 'ACTIVE-RUN.json');
36
+
37
+ if (!fs.existsSync(activeRunPath)) {
38
+ return { check: this.name, status: 'skip', message: 'No ACTIVE-RUN.json found' };
39
+ }
40
+
41
+ try {
42
+ const raw = JSON.parse(fs.readFileSync(activeRunPath, 'utf8')) as Record<string, unknown>;
43
+ const hasGraph = raw.compiled_graph && typeof raw.compiled_graph === 'object';
44
+ const hasRunId = typeof raw.run_id === 'string';
45
+
46
+ if (!hasRunId) {
47
+ return { check: this.name, status: 'fail', message: 'ACTIVE-RUN.json missing run_id', details: raw };
48
+ }
49
+ if (!hasGraph) {
50
+ return { check: this.name, status: 'fail', message: 'No compiled ExecutionGraph found in ACTIVE-RUN.json', details: { run_id: raw.run_id } };
51
+ }
52
+ return { check: this.name, status: 'pass', message: `Run ${String(raw.run_id)} has compiled graph` };
53
+ } catch (err) {
54
+ return { check: this.name, status: 'error', message: `Failed to parse ACTIVE-RUN.json: ${String(err)}` };
55
+ }
56
+ },
57
+ };
58
+
59
+ // ─── Check: verify-acceptance ────────────────────────────────────────────────
60
+
61
+ export const verifyAcceptanceCheck: CICheck = {
62
+ name: 'oxe-verify-acceptance',
63
+ description: 'Checks that VERIFY.md exists and contains no failed criteria',
64
+ async run(ctx) {
65
+ const verifyPath = ctx.sessionId
66
+ ? path.join(ctx.projectRoot, '.oxe', ctx.sessionId, 'verification', 'VERIFY.md')
67
+ : path.join(ctx.projectRoot, '.oxe', 'VERIFY.md');
68
+
69
+ if (!fs.existsSync(verifyPath)) {
70
+ return { check: this.name, status: 'skip', message: 'No VERIFY.md found — run /oxe-verify first' };
71
+ }
72
+
73
+ const content = fs.readFileSync(verifyPath, 'utf8');
74
+ const failLines = content.split('\n').filter((l) => l.includes('✗ FAIL'));
75
+ const passLines = content.split('\n').filter((l) => l.includes('✓ PASS'));
76
+
77
+ if (failLines.length > 0) {
78
+ return {
79
+ check: this.name,
80
+ status: 'fail',
81
+ message: `${failLines.length} acceptance criteria failed`,
82
+ details: { failed: failLines, passed: passLines.length },
83
+ };
84
+ }
85
+ if (passLines.length === 0) {
86
+ return { check: this.name, status: 'skip', message: 'VERIFY.md has no pass/fail markers' };
87
+ }
88
+ return { check: this.name, status: 'pass', message: `${passLines.length} acceptance criteria passed` };
89
+ },
90
+ };
91
+
92
+ // ─── Check: policy ───────────────────────────────────────────────────────────
93
+
94
+ export const policyCheck: CICheck = {
95
+ name: 'oxe-policy',
96
+ description: 'Checks that no gates are pending (unresolved human approval)',
97
+ async run(ctx) {
98
+ const gatesPath = ctx.sessionId
99
+ ? path.join(ctx.projectRoot, '.oxe', ctx.sessionId, 'execution', 'GATES.json')
100
+ : path.join(ctx.projectRoot, '.oxe', 'execution', 'GATES.json');
101
+
102
+ if (!fs.existsSync(gatesPath)) {
103
+ return { check: this.name, status: 'pass', message: 'No pending gates' };
104
+ }
105
+
106
+ try {
107
+ const gates = JSON.parse(fs.readFileSync(gatesPath, 'utf8')) as Array<{ status: string; scope: string; gate_id: string }>;
108
+ const pending = gates.filter((g) => g.status === 'pending');
109
+ if (pending.length > 0) {
110
+ return {
111
+ check: this.name,
112
+ status: 'fail',
113
+ message: `${pending.length} unresolved gate(s)`,
114
+ details: pending.map((g) => ({ gate_id: g.gate_id, scope: g.scope })),
115
+ };
116
+ }
117
+ return { check: this.name, status: 'pass', message: 'All gates resolved' };
118
+ } catch (err) {
119
+ return { check: this.name, status: 'error', message: `Failed to read GATES.json: ${String(err)}` };
120
+ }
121
+ },
122
+ };
123
+
124
+ // ─── Check: security-baseline ────────────────────────────────────────────────
125
+
126
+ const SECRET_PATTERNS = [
127
+ /(?:password|passwd|secret|api[_-]?key|auth[_-]?token)\s*[:=]\s*['"]?\S{8,}/i,
128
+ /(?:AKIA|ASIA)[A-Z0-9]{16}/,
129
+ /-----BEGIN (?:RSA|EC|OPENSSH) PRIVATE KEY-----/,
130
+ ];
131
+
132
+ export const securityBaselineCheck: CICheck = {
133
+ name: 'oxe-security-baseline',
134
+ description: 'Scans evidence artifacts for common secret patterns',
135
+ async run(ctx) {
136
+ if (!ctx.evidenceStore || !ctx.runId) {
137
+ return { check: this.name, status: 'skip', message: 'No evidence store or run ID provided' };
138
+ }
139
+
140
+ const evidenceDir = path.join(ctx.projectRoot, '.oxe', 'evidence', 'runs', ctx.runId);
141
+ if (!fs.existsSync(evidenceDir)) {
142
+ return { check: this.name, status: 'skip', message: 'No evidence found for this run' };
143
+ }
144
+
145
+ const findings: string[] = [];
146
+ walkDir(evidenceDir, (filePath) => {
147
+ if (filePath.endsWith('.json') || filePath.endsWith('.patch') || filePath.endsWith('.txt')) {
148
+ try {
149
+ const content = fs.readFileSync(filePath, 'utf8');
150
+ for (const pattern of SECRET_PATTERNS) {
151
+ if (pattern.test(content)) {
152
+ findings.push(`${path.basename(filePath)}: matches pattern ${pattern.source.slice(0, 40)}`);
153
+ break;
154
+ }
155
+ }
156
+ } catch { /* skip unreadable */ }
157
+ }
158
+ });
159
+
160
+ if (findings.length > 0) {
161
+ return { check: this.name, status: 'fail', message: `Secret patterns detected in ${findings.length} evidence file(s)`, details: findings };
162
+ }
163
+ return { check: this.name, status: 'pass', message: 'No secret patterns detected in evidence' };
164
+ },
165
+ };
166
+
167
+ // ─── Check: runtime-evidence-integrity ───────────────────────────────────────
168
+
169
+ export const runtimeEvidenceIntegrityCheck: CICheck = {
170
+ name: 'oxe-runtime-evidence-integrity',
171
+ description: 'Validates that all evidence index files are valid JSON and files exist on disk',
172
+ async run(ctx) {
173
+ if (!ctx.runId) {
174
+ return { check: this.name, status: 'skip', message: 'No run ID provided' };
175
+ }
176
+
177
+ const runEvidenceDir = path.join(ctx.projectRoot, '.oxe', 'evidence', 'runs', ctx.runId);
178
+ if (!fs.existsSync(runEvidenceDir)) {
179
+ return { check: this.name, status: 'skip', message: 'No evidence directory for this run' };
180
+ }
181
+
182
+ const errors: string[] = [];
183
+ let indexCount = 0;
184
+ let evidenceCount = 0;
185
+
186
+ walkDir(runEvidenceDir, (filePath) => {
187
+ if (path.basename(filePath) !== 'index.json') return;
188
+ indexCount++;
189
+ try {
190
+ const items = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Array<{ path: string; evidence_id: string }>;
191
+ for (const item of items) {
192
+ evidenceCount++;
193
+ const absPath = path.join(ctx.projectRoot, item.path);
194
+ if (!fs.existsSync(absPath)) {
195
+ errors.push(`Missing file for ${item.evidence_id}: ${item.path}`);
196
+ }
197
+ }
198
+ } catch (err) {
199
+ errors.push(`Corrupt index at ${filePath}: ${String(err)}`);
200
+ }
201
+ });
202
+
203
+ if (errors.length > 0) {
204
+ return { check: this.name, status: 'fail', message: `${errors.length} integrity error(s)`, details: errors };
205
+ }
206
+ return {
207
+ check: this.name,
208
+ status: indexCount === 0 ? 'skip' : 'pass',
209
+ message: `${evidenceCount} evidence artifact(s) across ${indexCount} index(es) — all valid`,
210
+ };
211
+ },
212
+ };
213
+
214
+ // ─── Suite ───────────────────────────────────────────────────────────────────
215
+
216
+ export const OXE_CI_CHECKS: CICheck[] = [
217
+ planConsistencyCheck,
218
+ verifyAcceptanceCheck,
219
+ policyCheck,
220
+ securityBaselineCheck,
221
+ runtimeEvidenceIntegrityCheck,
222
+ ];
223
+
224
+ export async function runCIChecks(
225
+ ctx: CICheckContext,
226
+ checks: CICheck[] = OXE_CI_CHECKS
227
+ ): Promise<CICheckResult[]> {
228
+ const results: CICheckResult[] = [];
229
+ for (const check of checks) {
230
+ results.push(await check.run(ctx));
231
+ }
232
+ return results;
233
+ }
234
+
235
+ export function summarizeCIResults(results: CICheckResult[]): {
236
+ total: number; pass: number; fail: number; skip: number; error: number; allPassed: boolean;
237
+ } {
238
+ const counts = { total: results.length, pass: 0, fail: 0, skip: 0, error: 0 };
239
+ for (const r of results) counts[r.status]++;
240
+ return { ...counts, allPassed: counts.fail === 0 && counts.error === 0 };
241
+ }
242
+
243
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
244
+
245
+ function walkDir(dir: string, visitor: (filePath: string) => void): void {
246
+ if (!fs.existsSync(dir)) return;
247
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
248
+ const full = path.join(dir, entry.name);
249
+ if (entry.isDirectory()) walkDir(full, visitor);
250
+ else visitor(full);
251
+ }
252
+ }
@@ -0,0 +1,3 @@
1
+ export * from './branch-manager';
2
+ export * from './pr-manager';
3
+ export * from './ci-checks';
@@ -0,0 +1,112 @@
1
+ import { spawnSync } from 'child_process';
2
+
3
+ export interface PRDraftOptions {
4
+ title: string;
5
+ body: string;
6
+ base?: string;
7
+ head?: string;
8
+ draft?: boolean;
9
+ }
10
+
11
+ export interface PRInfo {
12
+ number: number;
13
+ title: string;
14
+ url: string;
15
+ state: string;
16
+ draft: boolean;
17
+ head: string;
18
+ base: string;
19
+ }
20
+
21
+ export interface PRResult {
22
+ success: boolean;
23
+ url?: string;
24
+ error?: string;
25
+ pr?: PRInfo;
26
+ }
27
+
28
+ function isGhAvailable(cwd: string): boolean {
29
+ const result = spawnSync('gh', ['--version'], { cwd, encoding: 'utf8' });
30
+ return result.status === 0;
31
+ }
32
+
33
+ export class PRManager {
34
+ constructor(private readonly projectRoot: string) {}
35
+
36
+ isAvailable(): boolean {
37
+ return isGhAvailable(this.projectRoot);
38
+ }
39
+
40
+ createDraft(opts: PRDraftOptions): PRResult {
41
+ if (!this.isAvailable()) {
42
+ return { success: false, error: 'gh CLI not available — install from https://cli.github.com' };
43
+ }
44
+ const args = [
45
+ 'pr', 'create',
46
+ '--title', opts.title,
47
+ '--body', opts.body,
48
+ ];
49
+ if (opts.draft !== false) args.push('--draft');
50
+ if (opts.base) args.push('--base', opts.base);
51
+ if (opts.head) args.push('--head', opts.head);
52
+
53
+ const result = spawnSync('gh', args, {
54
+ cwd: this.projectRoot,
55
+ encoding: 'utf8',
56
+ });
57
+
58
+ if (result.status !== 0) {
59
+ return { success: false, error: result.stderr?.trim() ?? 'gh pr create failed' };
60
+ }
61
+ const url = result.stdout?.trim();
62
+ return { success: true, url };
63
+ }
64
+
65
+ view(prNumberOrUrl: string | number): PRResult {
66
+ if (!this.isAvailable()) {
67
+ return { success: false, error: 'gh CLI not available' };
68
+ }
69
+ const result = spawnSync(
70
+ 'gh',
71
+ ['pr', 'view', String(prNumberOrUrl), '--json', 'number,title,url,state,isDraft,headRefName,baseRefName'],
72
+ { cwd: this.projectRoot, encoding: 'utf8' }
73
+ );
74
+ if (result.status !== 0) {
75
+ return { success: false, error: result.stderr?.trim() };
76
+ }
77
+ try {
78
+ const raw = JSON.parse(result.stdout) as {
79
+ number: number; title: string; url: string; state: string;
80
+ isDraft: boolean; headRefName: string; baseRefName: string;
81
+ };
82
+ return {
83
+ success: true,
84
+ url: raw.url,
85
+ pr: {
86
+ number: raw.number,
87
+ title: raw.title,
88
+ url: raw.url,
89
+ state: raw.state.toLowerCase(),
90
+ draft: raw.isDraft,
91
+ head: raw.headRefName,
92
+ base: raw.baseRefName,
93
+ },
94
+ };
95
+ } catch {
96
+ return { success: false, error: 'Failed to parse gh output' };
97
+ }
98
+ }
99
+
100
+ mergePR(prNumber: number, method: 'merge' | 'squash' | 'rebase' = 'merge'): PRResult {
101
+ if (!this.isAvailable()) {
102
+ return { success: false, error: 'gh CLI not available' };
103
+ }
104
+ const result = spawnSync('gh', ['pr', 'merge', String(prNumber), `--${method}`, '--delete-branch'], {
105
+ cwd: this.projectRoot,
106
+ encoding: 'utf8',
107
+ });
108
+ return result.status === 0
109
+ ? { success: true }
110
+ : { success: false, error: result.stderr?.trim() };
111
+ }
112
+ }
@@ -0,0 +1,92 @@
1
+ import type { OxeEvent } from './envelope';
2
+ import type { EventType } from './catalog';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+
6
+ export type EventInput = Partial<Omit<OxeEvent, 'type'>> & { type: EventType };
7
+
8
+ interface OperationalEvent {
9
+ event_id?: string;
10
+ type?: string;
11
+ timestamp?: string;
12
+ session_id?: string | null;
13
+ run_id?: string | null;
14
+ task_id?: string | null;
15
+ work_item_id?: string | null;
16
+ attempt_id?: string | null;
17
+ causation_id?: string | null;
18
+ correlation_id?: string | null;
19
+ payload?: Record<string, unknown>;
20
+ }
21
+
22
+ function loadOperationalModule(): {
23
+ appendEvent: (projectRoot: string, sessionId: string | null, event: Record<string, unknown>) => OperationalEvent;
24
+ readEvents: (projectRoot: string, sessionId: string | null) => OperationalEvent[];
25
+ } {
26
+ const candidates = [
27
+ path.resolve(__dirname, '../../../bin/lib/oxe-operational.cjs'),
28
+ path.resolve(__dirname, '../../../../bin/lib/oxe-operational.cjs'),
29
+ path.resolve(__dirname, '../../../../../bin/lib/oxe-operational.cjs'),
30
+ ];
31
+ for (const candidate of candidates) {
32
+ if (!fs.existsSync(candidate)) continue;
33
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
34
+ return require(candidate) as {
35
+ appendEvent: (projectRoot: string, sessionId: string | null, event: Record<string, unknown>) => OperationalEvent;
36
+ readEvents: (projectRoot: string, sessionId: string | null) => OperationalEvent[];
37
+ };
38
+ }
39
+ throw new Error(`Unable to locate oxe-operational.cjs from ${__dirname}`);
40
+ }
41
+
42
+ const operational = loadOperationalModule();
43
+
44
+ function fromOperationalEvent(raw: OperationalEvent): OxeEvent {
45
+ return {
46
+ id: String(raw.event_id || ''),
47
+ type: String(raw.type || 'RunStarted') as EventType,
48
+ timestamp: String(raw.timestamp || new Date().toISOString()),
49
+ session_id: raw.session_id ?? null,
50
+ run_id: raw.run_id ?? null,
51
+ work_item_id: raw.work_item_id ?? raw.task_id ?? null,
52
+ attempt_id: raw.attempt_id ?? null,
53
+ causation_id: raw.causation_id ?? null,
54
+ correlation_id: raw.correlation_id ?? null,
55
+ payload: raw.payload && typeof raw.payload === 'object' ? raw.payload : {},
56
+ };
57
+ }
58
+
59
+ export function appendEvent(
60
+ projectRoot: string,
61
+ sessionId: string | null,
62
+ input: EventInput,
63
+ causationId?: string
64
+ ): OxeEvent {
65
+ const event = operational.appendEvent(projectRoot, sessionId, {
66
+ event_id: input.id,
67
+ type: input.type,
68
+ timestamp: input.timestamp,
69
+ run_id: input.run_id ?? null,
70
+ work_item_id: input.work_item_id ?? null,
71
+ attempt_id: input.attempt_id ?? null,
72
+ causation_id: input.causation_id ?? causationId ?? null,
73
+ correlation_id: input.correlation_id ?? null,
74
+ payload: input.payload && typeof input.payload === 'object' ? input.payload : {},
75
+ });
76
+ return fromOperationalEvent(event);
77
+ }
78
+
79
+ export function readEvents(
80
+ projectRoot: string,
81
+ sessionId: string | null
82
+ ): OxeEvent[] {
83
+ return operational.readEvents(projectRoot, sessionId).map(fromOperationalEvent);
84
+ }
85
+
86
+ export function filterByRun(events: OxeEvent[], runId: string): OxeEvent[] {
87
+ return events.filter((e) => e.run_id === runId);
88
+ }
89
+
90
+ export function filterByWorkItem(events: OxeEvent[], workItemId: string): OxeEvent[] {
91
+ return events.filter((e) => e.work_item_id === workItemId);
92
+ }
@@ -0,0 +1,29 @@
1
+ export const EVENT_TYPES = [
2
+ 'SessionCreated',
3
+ 'RunStarted',
4
+ 'GraphCompiled',
5
+ 'WorkItemReady',
6
+ 'WorkspaceAllocated',
7
+ 'AttemptStarted',
8
+ 'ToolInvoked',
9
+ 'ToolCompleted',
10
+ 'ToolFailed',
11
+ 'EvidenceCollected',
12
+ 'PolicyEvaluated',
13
+ 'GateRequested',
14
+ 'GateResolved',
15
+ 'VerificationStarted',
16
+ 'VerificationCompleted',
17
+ 'RetryScheduled',
18
+ 'WorkItemCompleted',
19
+ 'WorkItemBlocked',
20
+ 'RunCompleted',
21
+ 'RetroPublished',
22
+ 'LessonPromoted',
23
+ ] as const;
24
+
25
+ export type EventType = (typeof EVENT_TYPES)[number];
26
+
27
+ export function isValidEventType(type: string): type is EventType {
28
+ return (EVENT_TYPES as readonly string[]).includes(type);
29
+ }
@@ -0,0 +1,14 @@
1
+ import type { EventType } from './catalog';
2
+
3
+ export interface OxeEvent {
4
+ id: string;
5
+ type: EventType;
6
+ timestamp: string;
7
+ session_id: string | null;
8
+ run_id: string | null;
9
+ work_item_id: string | null;
10
+ attempt_id: string | null;
11
+ causation_id: string | null;
12
+ correlation_id: string | null;
13
+ payload: Record<string, unknown>;
14
+ }
@@ -0,0 +1,3 @@
1
+ export * from './catalog';
2
+ export * from './envelope';
3
+ export * from './bus';
@@ -0,0 +1,130 @@
1
+ import crypto from 'crypto';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import type { Evidence, EvidenceType } from '../models/evidence';
5
+
6
+ export interface EvidenceCollectOptions {
7
+ work_item_id: string;
8
+ run_id: string;
9
+ attempt_number: number;
10
+ }
11
+
12
+ export interface EvidenceContent {
13
+ evidence: Evidence;
14
+ content: Buffer;
15
+ }
16
+
17
+ const EXT_MAP: Record<EvidenceType, string> = {
18
+ diff: 'patch',
19
+ stdout: 'txt',
20
+ stderr: 'txt',
21
+ junit_xml: 'xml',
22
+ coverage: 'json',
23
+ screenshot: 'png',
24
+ trace: 'json',
25
+ log: 'txt',
26
+ security_report: 'json',
27
+ api_output: 'json',
28
+ summary: 'json',
29
+ };
30
+
31
+ export class EvidenceStore {
32
+ constructor(private readonly projectRoot: string) {}
33
+
34
+ private evidenceDir(runId: string, workItemId: string, attemptNumber: number): string {
35
+ return path.join(
36
+ this.projectRoot,
37
+ '.oxe',
38
+ 'evidence',
39
+ 'runs',
40
+ runId,
41
+ workItemId,
42
+ `attempt-${attemptNumber}`
43
+ );
44
+ }
45
+
46
+ private indexPath(runId: string, workItemId: string, attemptNumber: number): string {
47
+ return path.join(this.evidenceDir(runId, workItemId, attemptNumber), 'index.json');
48
+ }
49
+
50
+ private readIndex(runId: string, workItemId: string, attemptNumber: number): Evidence[] {
51
+ const p = this.indexPath(runId, workItemId, attemptNumber);
52
+ if (!fs.existsSync(p)) return [];
53
+ try {
54
+ return JSON.parse(fs.readFileSync(p, 'utf8')) as Evidence[];
55
+ } catch {
56
+ return [];
57
+ }
58
+ }
59
+
60
+ private writeIndex(runId: string, workItemId: string, attemptNumber: number, items: Evidence[]): void {
61
+ fs.writeFileSync(this.indexPath(runId, workItemId, attemptNumber), JSON.stringify(items, null, 2), 'utf8');
62
+ }
63
+
64
+ async collect(
65
+ type: EvidenceType,
66
+ content: Buffer | string,
67
+ opts: EvidenceCollectOptions
68
+ ): Promise<Evidence> {
69
+ const { work_item_id, run_id, attempt_number } = opts;
70
+ const dir = this.evidenceDir(run_id, work_item_id, attempt_number);
71
+ fs.mkdirSync(dir, { recursive: true });
72
+
73
+ const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8');
74
+ const checksum = crypto.createHash('sha256').update(buf).digest('hex').slice(0, 16);
75
+ const ext = EXT_MAP[type] ?? 'bin';
76
+
77
+ const existing = this.readIndex(run_id, work_item_id, attempt_number);
78
+ const seq = existing.filter((e) => e.type === type).length + 1;
79
+ const filename = seq === 1 ? `${type}.${ext}` : `${type}-${seq}.${ext}`;
80
+ const filePath = path.join(dir, filename);
81
+
82
+ fs.writeFileSync(filePath, buf);
83
+
84
+ const evidence: Evidence = {
85
+ evidence_id: `ev-${run_id}-${work_item_id}-a${attempt_number}-${type}-${seq}`,
86
+ attempt_id: `${work_item_id}-a${attempt_number}`,
87
+ type,
88
+ path: path.relative(this.projectRoot, filePath),
89
+ checksum,
90
+ created_at: new Date().toISOString(),
91
+ };
92
+
93
+ this.writeIndex(run_id, work_item_id, attempt_number, [...existing, evidence]);
94
+ return evidence;
95
+ }
96
+
97
+ async list(opts: EvidenceCollectOptions): Promise<Evidence[]> {
98
+ return this.readIndex(opts.run_id, opts.work_item_id, opts.attempt_number);
99
+ }
100
+
101
+ async get(evidenceId: string, opts: EvidenceCollectOptions): Promise<EvidenceContent | null> {
102
+ const items = this.readIndex(opts.run_id, opts.work_item_id, opts.attempt_number);
103
+ const ev = items.find((e) => e.evidence_id === evidenceId);
104
+ if (!ev) return null;
105
+ const absPath = path.join(this.projectRoot, ev.path);
106
+ if (!fs.existsSync(absPath)) return null;
107
+ return { evidence: ev, content: fs.readFileSync(absPath) };
108
+ }
109
+
110
+ async listByRun(runId: string): Promise<Evidence[]> {
111
+ const runDir = path.join(this.projectRoot, '.oxe', 'evidence', 'runs', runId);
112
+ if (!fs.existsSync(runDir)) return [];
113
+ const all: Evidence[] = [];
114
+ for (const workItem of fs.readdirSync(runDir)) {
115
+ const wiDir = path.join(runDir, workItem);
116
+ for (const attempt of fs.readdirSync(wiDir)) {
117
+ const indexPath = path.join(wiDir, attempt, 'index.json');
118
+ if (fs.existsSync(indexPath)) {
119
+ try {
120
+ const items = JSON.parse(fs.readFileSync(indexPath, 'utf8')) as Evidence[];
121
+ all.push(...items);
122
+ } catch {
123
+ // skip corrupt index
124
+ }
125
+ }
126
+ }
127
+ }
128
+ return all;
129
+ }
130
+ }
@@ -0,0 +1 @@
1
+ export * from './evidence-store';