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,245 @@
1
+ import crypto from 'crypto';
2
+ import type { WorkspaceStrategy } from '../models/workspace';
3
+
4
+ // Mirror of SDK ParsedTask / ParsedSpec / ParsedPlan interfaces
5
+ // (avoids a hard dependency on the CJS SDK from TypeScript source)
6
+ export interface ParsedTask {
7
+ id: string;
8
+ title: string;
9
+ wave: number | null;
10
+ dependsOn: string[];
11
+ files: string[];
12
+ verifyCommand: string | null;
13
+ aceite: string[];
14
+ done: boolean;
15
+ meta?: Record<string, unknown> | null;
16
+ }
17
+
18
+ export interface ParsedSpecCriterion {
19
+ id: string;
20
+ criterion: string;
21
+ howToVerify: string;
22
+ }
23
+
24
+ export interface ParsedSpec {
25
+ objective: string | null;
26
+ criteria: ParsedSpecCriterion[];
27
+ }
28
+
29
+ export interface ParsedPlan {
30
+ tasks: ParsedTask[];
31
+ waves: Record<number, string[]>;
32
+ totalTasks: number;
33
+ }
34
+
35
+ export interface Action {
36
+ type: 'read_code' | 'generate_patch' | 'run_tests' | 'run_lint' | 'collect_evidence' | 'custom';
37
+ command?: string;
38
+ targets?: string[];
39
+ }
40
+
41
+ export interface VerifyContract {
42
+ must_pass: string[];
43
+ acceptance_refs: string[];
44
+ command: string | null;
45
+ }
46
+
47
+ export interface NodePolicy {
48
+ requires_human_approval: boolean;
49
+ max_retries: number;
50
+ }
51
+
52
+ export interface GraphNode {
53
+ id: string;
54
+ title: string;
55
+ wave: number;
56
+ depends_on: string[];
57
+ workspace_strategy: WorkspaceStrategy;
58
+ mutation_scope: string[];
59
+ actions: Action[];
60
+ verify: VerifyContract;
61
+ policy: NodePolicy;
62
+ }
63
+
64
+ export interface GraphEdge {
65
+ from: string;
66
+ to: string;
67
+ type: 'dependency' | 'wave_sequence';
68
+ }
69
+
70
+ export interface Wave {
71
+ wave_number: number;
72
+ node_ids: string[];
73
+ }
74
+
75
+ export interface ExecutionGraphMetadata {
76
+ compiled_at: string;
77
+ plan_hash: string;
78
+ spec_hash: string;
79
+ node_count: number;
80
+ wave_count: number;
81
+ }
82
+
83
+ export interface ExecutionGraph {
84
+ nodes: Map<string, GraphNode>;
85
+ edges: GraphEdge[];
86
+ waves: Wave[];
87
+ metadata: ExecutionGraphMetadata;
88
+ }
89
+
90
+ export interface CompilerOptions {
91
+ default_workspace_strategy?: WorkspaceStrategy;
92
+ default_max_retries?: number;
93
+ require_approval_for_all?: boolean;
94
+ skip_done_tasks?: boolean;
95
+ }
96
+
97
+ export function compile(
98
+ plan: ParsedPlan,
99
+ spec: ParsedSpec,
100
+ options: CompilerOptions = {}
101
+ ): ExecutionGraph {
102
+ const {
103
+ default_workspace_strategy = 'git_worktree',
104
+ default_max_retries = 2,
105
+ require_approval_for_all = false,
106
+ skip_done_tasks = false,
107
+ } = options;
108
+
109
+ const nodes = new Map<string, GraphNode>();
110
+ const edges: GraphEdge[] = [];
111
+ const waveMap = new Map<number, string[]>();
112
+
113
+ for (const task of plan.tasks) {
114
+ if (skip_done_tasks && task.done) continue;
115
+
116
+ const waveNumber = task.wave ?? 1;
117
+ const node: GraphNode = {
118
+ id: task.id,
119
+ title: task.title,
120
+ wave: waveNumber,
121
+ depends_on: task.dependsOn,
122
+ workspace_strategy: default_workspace_strategy,
123
+ mutation_scope: task.files,
124
+ actions: buildActions(task),
125
+ verify: {
126
+ must_pass: task.verifyCommand ? ['tests'] : [],
127
+ acceptance_refs: task.aceite,
128
+ command: task.verifyCommand,
129
+ },
130
+ policy: {
131
+ requires_human_approval: require_approval_for_all,
132
+ max_retries: default_max_retries,
133
+ },
134
+ };
135
+
136
+ nodes.set(task.id, node);
137
+
138
+ for (const dep of task.dependsOn) {
139
+ edges.push({ from: dep, to: task.id, type: 'dependency' });
140
+ }
141
+
142
+ if (!waveMap.has(waveNumber)) waveMap.set(waveNumber, []);
143
+ waveMap.get(waveNumber)!.push(task.id);
144
+ }
145
+
146
+ const waves: Wave[] = Array.from(waveMap.entries())
147
+ .sort(([a], [b]) => a - b)
148
+ .map(([wave_number, node_ids]) => ({ wave_number, node_ids }));
149
+
150
+ return {
151
+ nodes,
152
+ edges,
153
+ waves,
154
+ metadata: {
155
+ compiled_at: new Date().toISOString(),
156
+ plan_hash: hashObject(plan),
157
+ spec_hash: hashObject(spec),
158
+ node_count: nodes.size,
159
+ wave_count: waves.length,
160
+ },
161
+ };
162
+ }
163
+
164
+ export function validateGraph(graph: ExecutionGraph): string[] {
165
+ const errors: string[] = [];
166
+ const nodeIds = new Set(graph.nodes.keys());
167
+
168
+ for (const [id, node] of graph.nodes) {
169
+ for (const dep of node.depends_on) {
170
+ if (!nodeIds.has(dep)) {
171
+ errors.push(`Node ${id} depends on unknown node ${dep}`);
172
+ }
173
+ }
174
+ }
175
+
176
+ // Detect cycles using DFS
177
+ const visited = new Set<string>();
178
+ const inStack = new Set<string>();
179
+
180
+ function hasCycle(nodeId: string): boolean {
181
+ if (inStack.has(nodeId)) return true;
182
+ if (visited.has(nodeId)) return false;
183
+ visited.add(nodeId);
184
+ inStack.add(nodeId);
185
+ const node = graph.nodes.get(nodeId);
186
+ if (node) {
187
+ for (const dep of node.depends_on) {
188
+ if (hasCycle(dep)) return true;
189
+ }
190
+ }
191
+ inStack.delete(nodeId);
192
+ return false;
193
+ }
194
+
195
+ for (const id of nodeIds) {
196
+ if (hasCycle(id)) {
197
+ errors.push(`Cycle detected involving node ${id}`);
198
+ break;
199
+ }
200
+ }
201
+
202
+ return errors;
203
+ }
204
+
205
+ export function toSerializable(graph: ExecutionGraph): Record<string, unknown> {
206
+ return {
207
+ nodes: Object.fromEntries(graph.nodes),
208
+ edges: graph.edges,
209
+ waves: graph.waves,
210
+ metadata: graph.metadata,
211
+ };
212
+ }
213
+
214
+ export function fromSerializable(raw: Record<string, unknown>): ExecutionGraph {
215
+ const nodes = new Map<string, GraphNode>(
216
+ Object.entries(raw.nodes as Record<string, GraphNode>)
217
+ );
218
+ return {
219
+ nodes,
220
+ edges: raw.edges as GraphEdge[],
221
+ waves: raw.waves as Wave[],
222
+ metadata: raw.metadata as ExecutionGraphMetadata,
223
+ };
224
+ }
225
+
226
+ function buildActions(task: ParsedTask): Action[] {
227
+ const actions: Action[] = [];
228
+ if (task.files.length > 0) {
229
+ actions.push({ type: 'read_code', targets: task.files });
230
+ }
231
+ actions.push({ type: 'generate_patch' });
232
+ if (task.verifyCommand) {
233
+ actions.push({ type: 'run_tests', command: task.verifyCommand });
234
+ }
235
+ actions.push({ type: 'collect_evidence' });
236
+ return actions;
237
+ }
238
+
239
+ function hashObject(obj: unknown): string {
240
+ return crypto
241
+ .createHash('sha256')
242
+ .update(JSON.stringify(obj))
243
+ .digest('hex')
244
+ .slice(0, 12);
245
+ }
@@ -0,0 +1 @@
1
+ export * from './graph-compiler';
@@ -0,0 +1,193 @@
1
+ import type { WorkItem } from '../models/work-item';
2
+ import type { Evidence } from '../models/evidence';
3
+ import type { RunState } from '../reducers/run-state-reducer';
4
+
5
+ export interface ContextArtifact {
6
+ id: string;
7
+ kind: 'evidence' | 'lesson' | 'file' | 'summary';
8
+ content: string;
9
+ relevanceScore: number;
10
+ tags: string[];
11
+ }
12
+
13
+ export interface LessonMetric {
14
+ lesson_id: string;
15
+ title: string;
16
+ tags: string[];
17
+ embedding?: number[];
18
+ content: string;
19
+ }
20
+
21
+ export interface ContextPackOptions {
22
+ maxArtifacts?: number;
23
+ maxTokensEstimate?: number;
24
+ deduplicateThreshold?: number;
25
+ }
26
+
27
+ export interface ContextPack {
28
+ work_item_id: string;
29
+ artifacts: ContextArtifact[];
30
+ total_artifacts_considered: number;
31
+ redundancy_removed: number;
32
+ built_at: string;
33
+ }
34
+
35
+ // ─── Relevance scoring ────────────────────────────────────────────────────────
36
+
37
+ function scoreEvidenceRelevance(evidence: Evidence, workItem: WorkItem): number {
38
+ let score = 0.5;
39
+ const eid = evidence.evidence_id.toLowerCase();
40
+ const title = workItem.title.toLowerCase();
41
+ const scope = workItem.mutation_scope ?? [];
42
+
43
+ if (scope.some((s) => eid.includes(s.toLowerCase()))) score += 0.3;
44
+ if (title.split(/\s+/).some((w) => w.length > 3 && eid.includes(w))) score += 0.1;
45
+ if (evidence.type === 'junit_xml') score += 0.05;
46
+ if (evidence.type === 'diff') score += 0.05;
47
+
48
+ return Math.min(score, 1.0);
49
+ }
50
+
51
+ function scoreLessonRelevance(lesson: LessonMetric, workItem: WorkItem): number {
52
+ const itemTags = new Set([
53
+ ...workItem.mutation_scope.map((s) => s.toLowerCase()),
54
+ ...workItem.title.toLowerCase().split(/\s+/).filter((w) => w.length > 3),
55
+ ]);
56
+
57
+ const lessonTags = lesson.tags.map((t) => t.toLowerCase());
58
+ const overlap = lessonTags.filter((t) => itemTags.has(t)).length;
59
+ const jaccard = overlap / (itemTags.size + lessonTags.length - overlap || 1);
60
+
61
+ return Math.min(0.3 + jaccard * 0.7, 1.0);
62
+ }
63
+
64
+ // ─── Redundancy elimination ───────────────────────────────────────────────────
65
+
66
+ function cosineSimilarity(a: ContextArtifact, b: ContextArtifact): number {
67
+ const wordsA = new Set(a.content.toLowerCase().split(/\W+/).filter((w) => w.length > 3));
68
+ const wordsB = new Set(b.content.toLowerCase().split(/\W+/).filter((w) => w.length > 3));
69
+ if (wordsA.size === 0 || wordsB.size === 0) return 0;
70
+
71
+ let intersection = 0;
72
+ for (const w of wordsA) if (wordsB.has(w)) intersection++;
73
+ return intersection / Math.sqrt(wordsA.size * wordsB.size);
74
+ }
75
+
76
+ function deduplicateArtifacts(artifacts: ContextArtifact[], threshold: number): { kept: ContextArtifact[]; removed: number } {
77
+ const kept: ContextArtifact[] = [];
78
+ let removed = 0;
79
+
80
+ for (const candidate of artifacts) {
81
+ const isDuplicate = kept.some((existing) => cosineSimilarity(candidate, existing) >= threshold);
82
+ if (isDuplicate) {
83
+ removed++;
84
+ } else {
85
+ kept.push(candidate);
86
+ }
87
+ }
88
+
89
+ return { kept, removed };
90
+ }
91
+
92
+ // ─── Token budget estimation ──────────────────────────────────────────────────
93
+
94
+ function estimateTokens(text: string): number {
95
+ return Math.ceil(text.length / 4);
96
+ }
97
+
98
+ function applyTokenBudget(artifacts: ContextArtifact[], maxTokens: number): ContextArtifact[] {
99
+ let used = 0;
100
+ const result: ContextArtifact[] = [];
101
+ for (const a of artifacts) {
102
+ const t = estimateTokens(a.content);
103
+ if (used + t > maxTokens) break;
104
+ result.push(a);
105
+ used += t;
106
+ }
107
+ return result;
108
+ }
109
+
110
+ // ─── Context Pack Builder ─────────────────────────────────────────────────────
111
+
112
+ export class ContextPackBuilder {
113
+ constructor(private readonly opts: ContextPackOptions = {}) {}
114
+
115
+ build(
116
+ workItem: WorkItem,
117
+ state: RunState,
118
+ evidenceItems: Evidence[],
119
+ evidenceContents: Map<string, string>,
120
+ lessons: LessonMetric[],
121
+ ): ContextPack {
122
+ const {
123
+ maxArtifacts = 20,
124
+ maxTokensEstimate = 8000,
125
+ deduplicateThreshold = 0.85,
126
+ } = this.opts;
127
+
128
+ const raw: ContextArtifact[] = [];
129
+
130
+ // Score and collect evidence
131
+ for (const ev of evidenceItems) {
132
+ const content = evidenceContents.get(ev.evidence_id) ?? '';
133
+ if (!content) continue;
134
+ raw.push({
135
+ id: ev.evidence_id,
136
+ kind: 'evidence',
137
+ content,
138
+ relevanceScore: scoreEvidenceRelevance(ev, workItem),
139
+ tags: [ev.type, workItem.work_item_id],
140
+ });
141
+ }
142
+
143
+ // Score and collect lessons
144
+ for (const lesson of lessons) {
145
+ raw.push({
146
+ id: lesson.lesson_id,
147
+ kind: 'lesson',
148
+ content: lesson.content,
149
+ relevanceScore: scoreLessonRelevance(lesson, workItem),
150
+ tags: lesson.tags,
151
+ });
152
+ }
153
+
154
+ // Add run-level summary if available
155
+ const completedCount = state.completedWorkItems.size;
156
+ if (completedCount > 0) {
157
+ raw.push({
158
+ id: `run-summary-${workItem.run_id}`,
159
+ kind: 'summary',
160
+ content: `Run progress: ${completedCount} completed, ${state.failedWorkItems.size} failed, ${state.blockedWorkItems.size} blocked.`,
161
+ relevanceScore: 0.4,
162
+ tags: ['run-context'],
163
+ });
164
+ }
165
+
166
+ const totalConsidered = raw.length;
167
+
168
+ // Sort by relevance descending
169
+ raw.sort((a, b) => b.relevanceScore - a.relevanceScore);
170
+
171
+ // Deduplicate
172
+ const { kept, removed } = deduplicateArtifacts(raw, deduplicateThreshold);
173
+
174
+ // Apply max artifacts cap
175
+ const capped = kept.slice(0, maxArtifacts);
176
+
177
+ // Apply token budget
178
+ const final = applyTokenBudget(capped, maxTokensEstimate);
179
+
180
+ return {
181
+ work_item_id: workItem.work_item_id,
182
+ artifacts: final,
183
+ total_artifacts_considered: totalConsidered,
184
+ redundancy_removed: removed,
185
+ built_at: new Date().toISOString(),
186
+ };
187
+ }
188
+
189
+ /** Convenience: build with no evidence, just lessons and state summary */
190
+ buildLightweight(workItem: WorkItem, state: RunState, lessons: LessonMetric[]): ContextPack {
191
+ return this.build(workItem, state, [], new Map(), lessons);
192
+ }
193
+ }
@@ -0,0 +1 @@
1
+ export * from './context-pack-builder';
@@ -0,0 +1,84 @@
1
+ import { execFileSync, spawnSync } from 'child_process';
2
+
3
+ export interface BranchInfo {
4
+ name: string;
5
+ current: boolean;
6
+ commit: string;
7
+ }
8
+
9
+ export class BranchManager {
10
+ constructor(private readonly projectRoot: string) {}
11
+
12
+ currentBranch(): string {
13
+ return this.git(['rev-parse', '--abbrev-ref', 'HEAD']).trim();
14
+ }
15
+
16
+ currentCommit(): string {
17
+ return this.git(['rev-parse', 'HEAD']).trim();
18
+ }
19
+
20
+ createSessionBranch(sessionId: string): string {
21
+ const name = `oxe/${sessionId}`;
22
+ this.git(['checkout', '-b', name]);
23
+ return name;
24
+ }
25
+
26
+ createOxeBranch(name: string, base?: string): string {
27
+ const fullName = name.startsWith('oxe/') ? name : `oxe/${name}`;
28
+ if (base) {
29
+ this.git(['checkout', '-b', fullName, base]);
30
+ } else {
31
+ this.git(['checkout', '-b', fullName]);
32
+ }
33
+ return fullName;
34
+ }
35
+
36
+ switchTo(branchName: string): void {
37
+ this.git(['checkout', branchName]);
38
+ }
39
+
40
+ deleteBranch(name: string, force = false): void {
41
+ const flag = force ? '-D' : '-d';
42
+ this.git(['branch', flag, name]);
43
+ }
44
+
45
+ listOxeBranches(): BranchInfo[] {
46
+ const raw = this.git(['branch', '--list', 'oxe/*', '--format=%(refname:short) %(objectname:short) %(HEAD)']);
47
+ return raw
48
+ .split('\n')
49
+ .filter(Boolean)
50
+ .map((line) => {
51
+ const parts = line.trim().split(/\s+/);
52
+ return {
53
+ name: parts[0],
54
+ commit: parts[1] ?? '',
55
+ current: parts[2] === '*',
56
+ };
57
+ });
58
+ }
59
+
60
+ mergeWorktreeBranch(worktreeBranch: string, targetBranch: string): void {
61
+ const saved = this.currentBranch();
62
+ try {
63
+ this.git(['checkout', targetBranch]);
64
+ this.git(['merge', '--no-ff', worktreeBranch, '-m', `oxe: merge ${worktreeBranch}`]);
65
+ } finally {
66
+ try { this.git(['checkout', saved]); } catch { /* best effort */ }
67
+ }
68
+ }
69
+
70
+ branchExists(name: string): boolean {
71
+ const result = spawnSync('git', ['rev-parse', '--verify', name], {
72
+ cwd: this.projectRoot,
73
+ encoding: 'utf8',
74
+ });
75
+ return result.status === 0;
76
+ }
77
+
78
+ private git(args: string[]): string {
79
+ return execFileSync('git', args, {
80
+ cwd: this.projectRoot,
81
+ encoding: 'utf8',
82
+ });
83
+ }
84
+ }