oxe-cc 1.2.1 → 1.3.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 (276) hide show
  1. package/.cursor/commands/oxe-ask.md +2 -2
  2. package/.cursor/commands/oxe-capabilities.md +2 -2
  3. package/.cursor/commands/oxe-checkpoint.md +2 -2
  4. package/.cursor/commands/oxe-compact.md +2 -2
  5. package/.cursor/commands/oxe-dashboard.md +2 -2
  6. package/.cursor/commands/oxe-debug.md +2 -2
  7. package/.cursor/commands/oxe-discuss.md +2 -2
  8. package/.cursor/commands/oxe-execute.md +5 -2
  9. package/.cursor/commands/oxe-forensics.md +2 -2
  10. package/.cursor/commands/oxe-help.md +2 -2
  11. package/.cursor/commands/oxe-loop.md +2 -2
  12. package/.cursor/commands/oxe-milestone.md +2 -2
  13. package/.cursor/commands/oxe-next.md +2 -2
  14. package/.cursor/commands/oxe-obs.md +2 -2
  15. package/.cursor/commands/oxe-plan-agent.md +2 -2
  16. package/.cursor/commands/oxe-plan.md +2 -2
  17. package/.cursor/commands/oxe-project.md +2 -2
  18. package/.cursor/commands/oxe-quick.md +2 -2
  19. package/.cursor/commands/oxe-research.md +2 -2
  20. package/.cursor/commands/oxe-retro.md +2 -2
  21. package/.cursor/commands/oxe-review-pr.md +2 -2
  22. package/.cursor/commands/oxe-route.md +2 -2
  23. package/.cursor/commands/oxe-scan.md +2 -2
  24. package/.cursor/commands/oxe-security.md +2 -2
  25. package/.cursor/commands/oxe-session.md +2 -2
  26. package/.cursor/commands/oxe-ship.md +2 -2
  27. package/.cursor/commands/oxe-skill.md +2 -2
  28. package/.cursor/commands/oxe-spec.md +2 -2
  29. package/.cursor/commands/oxe-ui-review.md +2 -2
  30. package/.cursor/commands/oxe-ui-spec.md +2 -2
  31. package/.cursor/commands/oxe-update.md +2 -2
  32. package/.cursor/commands/oxe-validate-gaps.md +2 -2
  33. package/.cursor/commands/oxe-verify.md +5 -2
  34. package/.cursor/commands/oxe-workstream.md +2 -2
  35. package/.cursor/commands/oxe.md +2 -2
  36. package/.github/copilot-instructions.md +13 -13
  37. package/.github/prompts/oxe-ask.prompt.md +2 -2
  38. package/.github/prompts/oxe-capabilities.prompt.md +2 -2
  39. package/.github/prompts/oxe-checkpoint.prompt.md +2 -2
  40. package/.github/prompts/oxe-compact.prompt.md +2 -2
  41. package/.github/prompts/oxe-dashboard.prompt.md +2 -2
  42. package/.github/prompts/oxe-debug.prompt.md +2 -2
  43. package/.github/prompts/oxe-discuss.prompt.md +2 -2
  44. package/.github/prompts/oxe-execute.prompt.md +5 -2
  45. package/.github/prompts/oxe-forensics.prompt.md +2 -2
  46. package/.github/prompts/oxe-help.prompt.md +2 -2
  47. package/.github/prompts/oxe-loop.prompt.md +2 -2
  48. package/.github/prompts/oxe-milestone.prompt.md +2 -2
  49. package/.github/prompts/oxe-next.prompt.md +2 -2
  50. package/.github/prompts/oxe-obs.prompt.md +2 -2
  51. package/.github/prompts/oxe-plan-agent.prompt.md +2 -2
  52. package/.github/prompts/oxe-plan.prompt.md +2 -2
  53. package/.github/prompts/oxe-project.prompt.md +2 -2
  54. package/.github/prompts/oxe-quick.prompt.md +2 -2
  55. package/.github/prompts/oxe-research.prompt.md +2 -2
  56. package/.github/prompts/oxe-retro.prompt.md +2 -2
  57. package/.github/prompts/oxe-review-pr.prompt.md +2 -2
  58. package/.github/prompts/oxe-route.prompt.md +2 -2
  59. package/.github/prompts/oxe-scan.prompt.md +2 -2
  60. package/.github/prompts/oxe-security.prompt.md +2 -2
  61. package/.github/prompts/oxe-session.prompt.md +2 -2
  62. package/.github/prompts/oxe-ship.prompt.md +2 -2
  63. package/.github/prompts/oxe-skill.prompt.md +2 -2
  64. package/.github/prompts/oxe-spec.prompt.md +2 -2
  65. package/.github/prompts/oxe-ui-review.prompt.md +2 -2
  66. package/.github/prompts/oxe-ui-spec.prompt.md +2 -2
  67. package/.github/prompts/oxe-update.prompt.md +2 -2
  68. package/.github/prompts/oxe-validate-gaps.prompt.md +2 -2
  69. package/.github/prompts/oxe-verify.prompt.md +5 -2
  70. package/.github/prompts/oxe-workstream.prompt.md +2 -2
  71. package/.github/prompts/oxe.prompt.md +2 -2
  72. package/CHANGELOG.md +52 -17
  73. package/README.md +610 -551
  74. package/bin/banner.txt +1 -1
  75. package/bin/lib/oxe-agent-install.cjs +69 -69
  76. package/bin/lib/oxe-azure.cjs +1445 -1445
  77. package/bin/lib/oxe-context-engine.cjs +867 -867
  78. package/bin/lib/oxe-dashboard.cjs +76 -28
  79. package/bin/lib/oxe-operational.cjs +2144 -1340
  80. package/bin/lib/oxe-project-health.cjs +483 -1
  81. package/bin/lib/oxe-runtime-semantics.cjs +12 -0
  82. package/bin/oxe-cc.js +554 -152
  83. package/commands/oxe/ask.md +2 -2
  84. package/commands/oxe/capabilities.md +2 -2
  85. package/commands/oxe/checkpoint.md +2 -2
  86. package/commands/oxe/compact.md +2 -2
  87. package/commands/oxe/dashboard.md +2 -2
  88. package/commands/oxe/debug.md +2 -2
  89. package/commands/oxe/discuss.md +2 -2
  90. package/commands/oxe/execute.md +5 -2
  91. package/commands/oxe/forensics.md +2 -2
  92. package/commands/oxe/help.md +2 -2
  93. package/commands/oxe/loop.md +2 -2
  94. package/commands/oxe/milestone.md +2 -2
  95. package/commands/oxe/next.md +2 -2
  96. package/commands/oxe/obs.md +2 -2
  97. package/commands/oxe/oxe.md +2 -2
  98. package/commands/oxe/plan-agent.md +2 -2
  99. package/commands/oxe/plan.md +2 -2
  100. package/commands/oxe/project.md +2 -2
  101. package/commands/oxe/quick.md +2 -2
  102. package/commands/oxe/research.md +2 -2
  103. package/commands/oxe/retro.md +2 -2
  104. package/commands/oxe/review-pr.md +2 -2
  105. package/commands/oxe/route.md +2 -2
  106. package/commands/oxe/scan.md +2 -2
  107. package/commands/oxe/security.md +2 -2
  108. package/commands/oxe/session.md +2 -2
  109. package/commands/oxe/ship.md +2 -2
  110. package/commands/oxe/skill.md +2 -2
  111. package/commands/oxe/spec.md +2 -2
  112. package/commands/oxe/ui-review.md +2 -2
  113. package/commands/oxe/ui-spec.md +2 -2
  114. package/commands/oxe/update.md +2 -2
  115. package/commands/oxe/validate-gaps.md +2 -2
  116. package/commands/oxe/verify.md +5 -2
  117. package/commands/oxe/workstream.md +2 -2
  118. package/lib/runtime/delivery/branch-manager.d.ts +1 -0
  119. package/lib/runtime/delivery/branch-manager.js +7 -0
  120. package/lib/runtime/delivery/ci-checks.js +34 -1
  121. package/lib/runtime/delivery/delivery-records.d.ts +34 -0
  122. package/lib/runtime/delivery/delivery-records.js +48 -0
  123. package/lib/runtime/delivery/index.d.ts +1 -0
  124. package/lib/runtime/delivery/index.js +1 -0
  125. package/lib/runtime/delivery/promotion-pipeline.d.ts +26 -2
  126. package/lib/runtime/delivery/promotion-pipeline.js +111 -14
  127. package/lib/runtime/gate/gate-manager.d.ts +41 -0
  128. package/lib/runtime/gate/gate-manager.js +108 -1
  129. package/lib/runtime/index.d.ts +2 -2
  130. package/lib/runtime/index.js +3 -1
  131. package/lib/runtime/models/gate-decision.d.ts +4 -1
  132. package/lib/runtime/models/workspace.d.ts +3 -0
  133. package/lib/runtime/plugins/capability-adapter.d.ts +12 -0
  134. package/lib/runtime/plugins/capability-adapter.js +204 -0
  135. package/lib/runtime/plugins/capability-matrix.d.ts +5 -0
  136. package/lib/runtime/plugins/capability-matrix.js +48 -17
  137. package/lib/runtime/plugins/index.d.ts +1 -0
  138. package/lib/runtime/plugins/index.js +1 -0
  139. package/lib/runtime/plugins/plugin-abi.d.ts +2 -0
  140. package/lib/runtime/plugins/plugin-manifest.d.ts +1 -1
  141. package/lib/runtime/plugins/plugin-manifest.js +6 -2
  142. package/lib/runtime/plugins/plugin-registry.d.ts +46 -0
  143. package/lib/runtime/plugins/plugin-registry.js +79 -2
  144. package/lib/runtime/policy/policy-engine.d.ts +19 -0
  145. package/lib/runtime/policy/policy-engine.js +76 -4
  146. package/lib/runtime/projection/projection-engine.d.ts +9 -1
  147. package/lib/runtime/projection/projection-engine.js +73 -3
  148. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +43 -1
  149. package/lib/runtime/scheduler/multi-agent-coordinator.js +151 -39
  150. package/lib/runtime/scheduler/run-journal.d.ts +1 -1
  151. package/lib/runtime/scheduler/scheduler.d.ts +19 -1
  152. package/lib/runtime/scheduler/scheduler.js +258 -13
  153. package/lib/runtime/verification/verification-compiler.d.ts +43 -0
  154. package/lib/runtime/verification/verification-compiler.js +137 -0
  155. package/lib/runtime/verification/verification-manifest.d.ts +9 -0
  156. package/lib/runtime/verification/verification-manifest.js +56 -6
  157. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +1 -0
  158. package/lib/runtime/workspace/strategies/ephemeral-container.js +4 -0
  159. package/lib/runtime/workspace/strategies/git-worktree.d.ts +1 -0
  160. package/lib/runtime/workspace/strategies/git-worktree.js +2 -0
  161. package/lib/runtime/workspace/strategies/inplace.d.ts +1 -0
  162. package/lib/runtime/workspace/strategies/inplace.js +2 -0
  163. package/lib/runtime/workspace/workspace-manager.d.ts +2 -1
  164. package/lib/sdk/README.md +9 -9
  165. package/lib/sdk/index.cjs +33 -24
  166. package/lib/sdk/index.d.ts +149 -14
  167. package/oxe/templates/ACTIVE-RUN.template.json +32 -32
  168. package/oxe/templates/CAPABILITIES.template.md +7 -7
  169. package/oxe/templates/CAPABILITY.template.md +45 -45
  170. package/oxe/templates/CHECKPOINTS.template.md +7 -7
  171. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -68
  172. package/oxe/templates/HYPOTHESES.template.md +33 -33
  173. package/oxe/templates/LESSONS-METRICS.template.json +13 -13
  174. package/oxe/templates/NOTES.template.md +16 -16
  175. package/oxe/templates/PLAN-REVIEW.template.md +31 -31
  176. package/oxe/templates/SESSION.template.md +34 -34
  177. package/oxe/templates/SKILL.template.md +26 -26
  178. package/oxe/templates/STATE.md +55 -55
  179. package/oxe/templates/WORKFLOW_AUTHORING.md +18 -18
  180. package/oxe/workflows/ask.md +96 -96
  181. package/oxe/workflows/capabilities.md +25 -25
  182. package/oxe/workflows/dashboard.md +33 -33
  183. package/oxe/workflows/discuss.md +12 -12
  184. package/oxe/workflows/execute.md +14 -0
  185. package/oxe/workflows/help.md +352 -352
  186. package/oxe/workflows/next.md +22 -22
  187. package/oxe/workflows/oxe.md +6 -6
  188. package/oxe/workflows/plan-agent.md +9 -9
  189. package/oxe/workflows/quick.md +10 -10
  190. package/oxe/workflows/references/reasoning-discovery.md +28 -28
  191. package/oxe/workflows/references/reasoning-execution.md +29 -29
  192. package/oxe/workflows/references/reasoning-planning.md +32 -32
  193. package/oxe/workflows/references/reasoning-review.md +29 -29
  194. package/oxe/workflows/references/reasoning-status.md +24 -24
  195. package/oxe/workflows/references/robustness-elevation.md +295 -295
  196. package/oxe/workflows/references/workflow-runtime-contracts.json +952 -930
  197. package/oxe/workflows/route.md +16 -16
  198. package/oxe/workflows/session.md +213 -213
  199. package/oxe/workflows/ship.md +142 -142
  200. package/oxe/workflows/skill.md +44 -44
  201. package/oxe/workflows/ui-review.md +36 -36
  202. package/oxe/workflows/verify-audit.md +73 -73
  203. package/oxe/workflows/verify.md +10 -0
  204. package/package.json +92 -92
  205. package/packages/runtime/package.json +17 -17
  206. package/packages/runtime/src/audit/audit-trail.ts +243 -243
  207. package/packages/runtime/src/audit/index.ts +2 -2
  208. package/packages/runtime/src/audit/policy-pack.ts +62 -62
  209. package/packages/runtime/src/compiler/graph-compiler.ts +245 -245
  210. package/packages/runtime/src/compiler/index.ts +1 -1
  211. package/packages/runtime/src/context/context-pack-builder.ts +259 -259
  212. package/packages/runtime/src/context/context-pack-store.ts +197 -197
  213. package/packages/runtime/src/context/context-profiles.ts +60 -60
  214. package/packages/runtime/src/context/index.ts +3 -3
  215. package/packages/runtime/src/decision/decision-engine.ts +174 -174
  216. package/packages/runtime/src/decision/decision-memo.ts +211 -211
  217. package/packages/runtime/src/decision/index.ts +2 -2
  218. package/packages/runtime/src/delivery/branch-manager.ts +91 -84
  219. package/packages/runtime/src/delivery/ci-checks.ts +285 -252
  220. package/packages/runtime/src/delivery/delivery-records.ts +75 -0
  221. package/packages/runtime/src/delivery/index.ts +5 -4
  222. package/packages/runtime/src/delivery/pr-manager.ts +112 -112
  223. package/packages/runtime/src/delivery/promotion-pipeline.ts +334 -180
  224. package/packages/runtime/src/events/bus.ts +92 -92
  225. package/packages/runtime/src/events/catalog.ts +29 -29
  226. package/packages/runtime/src/events/envelope.ts +14 -14
  227. package/packages/runtime/src/events/index.ts +3 -3
  228. package/packages/runtime/src/evidence/evidence-store.ts +130 -130
  229. package/packages/runtime/src/evidence/index.ts +1 -1
  230. package/packages/runtime/src/gate/gate-manager.ts +289 -137
  231. package/packages/runtime/src/gate/index.ts +1 -1
  232. package/packages/runtime/src/index.ts +41 -37
  233. package/packages/runtime/src/models/attempt.ts +19 -19
  234. package/packages/runtime/src/models/evidence.ts +21 -21
  235. package/packages/runtime/src/models/gate-decision.ts +25 -21
  236. package/packages/runtime/src/models/index.ts +8 -8
  237. package/packages/runtime/src/models/run.ts +24 -24
  238. package/packages/runtime/src/models/session.ts +11 -11
  239. package/packages/runtime/src/models/verification-result.ts +10 -10
  240. package/packages/runtime/src/models/work-item.ts +25 -25
  241. package/packages/runtime/src/models/workspace.ts +31 -28
  242. package/packages/runtime/src/plugins/capability-adapter.ts +206 -0
  243. package/packages/runtime/src/plugins/capability-matrix.ts +126 -83
  244. package/packages/runtime/src/plugins/index.ts +5 -4
  245. package/packages/runtime/src/plugins/plugin-abi.ts +97 -95
  246. package/packages/runtime/src/plugins/plugin-manifest.ts +118 -113
  247. package/packages/runtime/src/plugins/plugin-registry.ts +232 -124
  248. package/packages/runtime/src/policy/index.ts +1 -1
  249. package/packages/runtime/src/policy/policy-engine.ts +330 -244
  250. package/packages/runtime/src/projection/index.ts +1 -1
  251. package/packages/runtime/src/projection/projection-engine.ts +328 -249
  252. package/packages/runtime/src/reducers/debug-reducer.ts +36 -36
  253. package/packages/runtime/src/reducers/index.ts +2 -2
  254. package/packages/runtime/src/reducers/run-state-reducer.ts +269 -269
  255. package/packages/runtime/src/scheduler/agent-registry.ts +132 -132
  256. package/packages/runtime/src/scheduler/agent-roles.ts +109 -109
  257. package/packages/runtime/src/scheduler/index.ts +4 -4
  258. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +521 -333
  259. package/packages/runtime/src/scheduler/run-journal.ts +62 -62
  260. package/packages/runtime/src/scheduler/scheduler.ts +722 -441
  261. package/packages/runtime/src/verification/index.ts +2 -2
  262. package/packages/runtime/src/verification/verification-compiler.ts +436 -225
  263. package/packages/runtime/src/verification/verification-manifest.ts +252 -192
  264. package/packages/runtime/src/workspace/index.ts +5 -5
  265. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +126 -121
  266. package/packages/runtime/src/workspace/strategies/git-worktree.ts +79 -77
  267. package/packages/runtime/src/workspace/strategies/inplace.ts +38 -35
  268. package/packages/runtime/src/workspace/workspace-manager.ts +16 -15
  269. package/packages/runtime/tsconfig.json +17 -17
  270. package/vscode-extension/.vscodeignore +7 -7
  271. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  272. package/vscode-extension/package.json +185 -185
  273. package/vscode-extension/src/extension.js +310 -310
  274. package/vscode-extension/src/shared/contextLoader.js +137 -137
  275. package/vscode-extension/src/shared/contractBuilder.js +159 -159
  276. package/vscode-extension/src/shared/stateReader.js +101 -101
@@ -1,211 +1,211 @@
1
- import crypto from 'crypto';
2
- import path from 'path';
3
- import fs from 'fs';
4
-
5
- export type ChangeStrategy =
6
- | 'minimal_patch'
7
- | 'isolated_refactor'
8
- | 'expand_contract'
9
- | 'feature_flag'
10
- | 'no_op';
11
-
12
- export interface BlastRadiusEstimate {
13
- estimated_files: number;
14
- subsystems: string[];
15
- risk_score: number;
16
- reversible: boolean;
17
- }
18
-
19
- export interface RollbackPlan {
20
- strategy: 'revert_commit' | 'restore_workspace' | 'undo_patch' | 'no_rollback';
21
- steps: string[];
22
- estimated_cost: 'low' | 'medium' | 'high';
23
- preconditions: string[];
24
- }
25
-
26
- export interface DecisionMemo {
27
- memo_id: string;
28
- work_item_id: string;
29
- run_id: string;
30
- problem_summary: string;
31
- chosen_strategy: ChangeStrategy;
32
- alternatives_rejected: Array<{ strategy: ChangeStrategy; reason: string }>;
33
- blast_radius: BlastRadiusEstimate;
34
- rollback_plan: RollbackPlan;
35
- min_evidence_required: string[];
36
- confidence: number;
37
- created_at: string;
38
- }
39
-
40
- // ─── BlastRadius estimation ───────────────────────────────────────────────────
41
-
42
- function deriveSubsystems(mutationScope: string[]): string[] {
43
- const seen = new Set<string>();
44
- for (const p of mutationScope) {
45
- const parts = p.replace(/\\/g, '/').split('/');
46
- if (parts.length >= 2) seen.add(parts[0]);
47
- else seen.add(p);
48
- }
49
- return [...seen];
50
- }
51
-
52
- function estimateRiskScore(mutationScope: string[], retryCount: number, riskLevel: string): number {
53
- let score = 0;
54
- score += Math.min(0.4, mutationScope.length * 0.05);
55
- score += retryCount > 0 ? Math.min(0.2, retryCount * 0.05) : 0;
56
- switch (riskLevel) {
57
- case 'critical': score += 0.4; break;
58
- case 'high': score += 0.3; break;
59
- case 'medium': score += 0.15; break;
60
- case 'low': score += 0.05; break;
61
- default: break;
62
- }
63
- return Math.min(1, score);
64
- }
65
-
66
- export function buildBlastRadius(
67
- mutationScope: string[],
68
- retryCount: number,
69
- riskLevel: string
70
- ): BlastRadiusEstimate {
71
- const risk_score = estimateRiskScore(mutationScope, retryCount, riskLevel);
72
- return {
73
- estimated_files: mutationScope.length,
74
- subsystems: deriveSubsystems(mutationScope),
75
- risk_score: Math.round(risk_score * 100) / 100,
76
- reversible: riskLevel !== 'critical' && mutationScope.length <= 10,
77
- };
78
- }
79
-
80
- // ─── RollbackPlan ─────────────────────────────────────────────────────────────
81
-
82
- export function buildRollbackPlan(
83
- blastRadius: BlastRadiusEstimate,
84
- retryCount: number
85
- ): RollbackPlan {
86
- if (blastRadius.risk_score >= 0.7 || !blastRadius.reversible) {
87
- return {
88
- strategy: 'restore_workspace',
89
- steps: ['snapshot workspace before mutation', 'restore snapshot on failure', 'verify clean state'],
90
- estimated_cost: 'high',
91
- preconditions: ['workspace snapshot available', 'no shared-state mutations'],
92
- };
93
- }
94
- if (retryCount > 1 || blastRadius.estimated_files > 5) {
95
- return {
96
- strategy: 'revert_commit',
97
- steps: ['record commit SHA before change', 'git revert on failure'],
98
- estimated_cost: 'medium',
99
- preconditions: ['git repo initialized', 'changes committed atomically'],
100
- };
101
- }
102
- return {
103
- strategy: 'undo_patch',
104
- steps: ['apply inverse patch'],
105
- estimated_cost: 'low',
106
- preconditions: ['original file state recorded'],
107
- };
108
- }
109
-
110
- // ─── StrategySelector ────────────────────────────────────────────────────────
111
-
112
- export class StrategySelector {
113
- select(mutationScope: string[], retryCount: number, riskLevel: string): ChangeStrategy {
114
- if (riskLevel === 'critical') return 'feature_flag';
115
- if (retryCount > 1) return 'minimal_patch';
116
- if (mutationScope.length > 8) return 'isolated_refactor';
117
- if (riskLevel === 'high') return 'feature_flag';
118
- if (mutationScope.length > 3) return 'expand_contract';
119
- if (mutationScope.length === 0) return 'no_op';
120
- return 'minimal_patch';
121
- }
122
-
123
- alternatives(chosen: ChangeStrategy, mutationScope: string[], riskLevel: string): Array<{ strategy: ChangeStrategy; reason: string }> {
124
- const all: ChangeStrategy[] = ['minimal_patch', 'isolated_refactor', 'expand_contract', 'feature_flag', 'no_op'];
125
- return all
126
- .filter((s) => s !== chosen)
127
- .map((s) => ({ strategy: s, reason: this.rejectionReason(s, chosen, mutationScope, riskLevel) }));
128
- }
129
-
130
- private rejectionReason(s: ChangeStrategy, chosen: ChangeStrategy, mutationScope: string[], riskLevel: string): string {
131
- switch (s) {
132
- case 'no_op': return 'mutation scope is non-empty; no-op would not satisfy requirements';
133
- case 'feature_flag': return riskLevel !== 'critical' && riskLevel !== 'high' ? 'risk level does not warrant feature flag overhead' : `${chosen} preferred for scope size`;
134
- case 'isolated_refactor': return mutationScope.length <= 8 ? 'scope is small enough for simpler strategy' : `${chosen} preferred`;
135
- case 'expand_contract': return 'expand-contract requires coordinated deployment; overhead not justified here';
136
- case 'minimal_patch': return `${chosen} preferred due to scope or risk level`;
137
- default: return 'not applicable';
138
- }
139
- }
140
- }
141
-
142
- // ─── Memo builder & persistence ──────────────────────────────────────────────
143
-
144
- export interface BuildMemoInput {
145
- work_item_id: string;
146
- run_id: string;
147
- problem_summary: string;
148
- mutation_scope: string[];
149
- retry_count: number;
150
- risk_level: string;
151
- min_evidence_required?: string[];
152
- confidence?: number;
153
- }
154
-
155
- export function buildMemo(input: BuildMemoInput): DecisionMemo {
156
- const selector = new StrategySelector();
157
- const chosen = selector.select(input.mutation_scope, input.retry_count, input.risk_level);
158
- const blast_radius = buildBlastRadius(input.mutation_scope, input.retry_count, input.risk_level);
159
- const rollback_plan = buildRollbackPlan(blast_radius, input.retry_count);
160
- const alternatives_rejected = selector.alternatives(chosen, input.mutation_scope, input.risk_level);
161
-
162
- return {
163
- memo_id: `memo-${crypto.randomBytes(4).toString('hex')}`,
164
- work_item_id: input.work_item_id,
165
- run_id: input.run_id,
166
- problem_summary: input.problem_summary,
167
- chosen_strategy: chosen,
168
- alternatives_rejected,
169
- blast_radius,
170
- rollback_plan,
171
- min_evidence_required: input.min_evidence_required ?? [],
172
- confidence: input.confidence ?? (1 - blast_radius.risk_score),
173
- created_at: new Date().toISOString(),
174
- };
175
- }
176
-
177
- function memoDir(projectRoot: string, runId: string): string {
178
- return path.join(projectRoot, '.oxe', 'runs', runId, 'memos');
179
- }
180
-
181
- export function saveMemo(projectRoot: string, memo: DecisionMemo): void {
182
- const dir = memoDir(projectRoot, memo.run_id);
183
- fs.mkdirSync(dir, { recursive: true });
184
- fs.writeFileSync(path.join(dir, `${memo.memo_id}.json`), JSON.stringify(memo, null, 2), 'utf8');
185
- }
186
-
187
- export function loadMemo(projectRoot: string, runId: string, memoId: string): DecisionMemo | null {
188
- const p = path.join(memoDir(projectRoot, runId), `${memoId}.json`);
189
- if (!fs.existsSync(p)) return null;
190
- try {
191
- return JSON.parse(fs.readFileSync(p, 'utf8')) as DecisionMemo;
192
- } catch {
193
- return null;
194
- }
195
- }
196
-
197
- export function listMemos(projectRoot: string, runId: string): DecisionMemo[] {
198
- const dir = memoDir(projectRoot, runId);
199
- if (!fs.existsSync(dir)) return [];
200
- return fs
201
- .readdirSync(dir)
202
- .filter((f) => f.endsWith('.json'))
203
- .map((f) => {
204
- try {
205
- return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')) as DecisionMemo;
206
- } catch {
207
- return null;
208
- }
209
- })
210
- .filter((m): m is DecisionMemo => m !== null);
211
- }
1
+ import crypto from 'crypto';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+
5
+ export type ChangeStrategy =
6
+ | 'minimal_patch'
7
+ | 'isolated_refactor'
8
+ | 'expand_contract'
9
+ | 'feature_flag'
10
+ | 'no_op';
11
+
12
+ export interface BlastRadiusEstimate {
13
+ estimated_files: number;
14
+ subsystems: string[];
15
+ risk_score: number;
16
+ reversible: boolean;
17
+ }
18
+
19
+ export interface RollbackPlan {
20
+ strategy: 'revert_commit' | 'restore_workspace' | 'undo_patch' | 'no_rollback';
21
+ steps: string[];
22
+ estimated_cost: 'low' | 'medium' | 'high';
23
+ preconditions: string[];
24
+ }
25
+
26
+ export interface DecisionMemo {
27
+ memo_id: string;
28
+ work_item_id: string;
29
+ run_id: string;
30
+ problem_summary: string;
31
+ chosen_strategy: ChangeStrategy;
32
+ alternatives_rejected: Array<{ strategy: ChangeStrategy; reason: string }>;
33
+ blast_radius: BlastRadiusEstimate;
34
+ rollback_plan: RollbackPlan;
35
+ min_evidence_required: string[];
36
+ confidence: number;
37
+ created_at: string;
38
+ }
39
+
40
+ // ─── BlastRadius estimation ───────────────────────────────────────────────────
41
+
42
+ function deriveSubsystems(mutationScope: string[]): string[] {
43
+ const seen = new Set<string>();
44
+ for (const p of mutationScope) {
45
+ const parts = p.replace(/\\/g, '/').split('/');
46
+ if (parts.length >= 2) seen.add(parts[0]);
47
+ else seen.add(p);
48
+ }
49
+ return [...seen];
50
+ }
51
+
52
+ function estimateRiskScore(mutationScope: string[], retryCount: number, riskLevel: string): number {
53
+ let score = 0;
54
+ score += Math.min(0.4, mutationScope.length * 0.05);
55
+ score += retryCount > 0 ? Math.min(0.2, retryCount * 0.05) : 0;
56
+ switch (riskLevel) {
57
+ case 'critical': score += 0.4; break;
58
+ case 'high': score += 0.3; break;
59
+ case 'medium': score += 0.15; break;
60
+ case 'low': score += 0.05; break;
61
+ default: break;
62
+ }
63
+ return Math.min(1, score);
64
+ }
65
+
66
+ export function buildBlastRadius(
67
+ mutationScope: string[],
68
+ retryCount: number,
69
+ riskLevel: string
70
+ ): BlastRadiusEstimate {
71
+ const risk_score = estimateRiskScore(mutationScope, retryCount, riskLevel);
72
+ return {
73
+ estimated_files: mutationScope.length,
74
+ subsystems: deriveSubsystems(mutationScope),
75
+ risk_score: Math.round(risk_score * 100) / 100,
76
+ reversible: riskLevel !== 'critical' && mutationScope.length <= 10,
77
+ };
78
+ }
79
+
80
+ // ─── RollbackPlan ─────────────────────────────────────────────────────────────
81
+
82
+ export function buildRollbackPlan(
83
+ blastRadius: BlastRadiusEstimate,
84
+ retryCount: number
85
+ ): RollbackPlan {
86
+ if (blastRadius.risk_score >= 0.7 || !blastRadius.reversible) {
87
+ return {
88
+ strategy: 'restore_workspace',
89
+ steps: ['snapshot workspace before mutation', 'restore snapshot on failure', 'verify clean state'],
90
+ estimated_cost: 'high',
91
+ preconditions: ['workspace snapshot available', 'no shared-state mutations'],
92
+ };
93
+ }
94
+ if (retryCount > 1 || blastRadius.estimated_files > 5) {
95
+ return {
96
+ strategy: 'revert_commit',
97
+ steps: ['record commit SHA before change', 'git revert on failure'],
98
+ estimated_cost: 'medium',
99
+ preconditions: ['git repo initialized', 'changes committed atomically'],
100
+ };
101
+ }
102
+ return {
103
+ strategy: 'undo_patch',
104
+ steps: ['apply inverse patch'],
105
+ estimated_cost: 'low',
106
+ preconditions: ['original file state recorded'],
107
+ };
108
+ }
109
+
110
+ // ─── StrategySelector ────────────────────────────────────────────────────────
111
+
112
+ export class StrategySelector {
113
+ select(mutationScope: string[], retryCount: number, riskLevel: string): ChangeStrategy {
114
+ if (riskLevel === 'critical') return 'feature_flag';
115
+ if (retryCount > 1) return 'minimal_patch';
116
+ if (mutationScope.length > 8) return 'isolated_refactor';
117
+ if (riskLevel === 'high') return 'feature_flag';
118
+ if (mutationScope.length > 3) return 'expand_contract';
119
+ if (mutationScope.length === 0) return 'no_op';
120
+ return 'minimal_patch';
121
+ }
122
+
123
+ alternatives(chosen: ChangeStrategy, mutationScope: string[], riskLevel: string): Array<{ strategy: ChangeStrategy; reason: string }> {
124
+ const all: ChangeStrategy[] = ['minimal_patch', 'isolated_refactor', 'expand_contract', 'feature_flag', 'no_op'];
125
+ return all
126
+ .filter((s) => s !== chosen)
127
+ .map((s) => ({ strategy: s, reason: this.rejectionReason(s, chosen, mutationScope, riskLevel) }));
128
+ }
129
+
130
+ private rejectionReason(s: ChangeStrategy, chosen: ChangeStrategy, mutationScope: string[], riskLevel: string): string {
131
+ switch (s) {
132
+ case 'no_op': return 'mutation scope is non-empty; no-op would not satisfy requirements';
133
+ case 'feature_flag': return riskLevel !== 'critical' && riskLevel !== 'high' ? 'risk level does not warrant feature flag overhead' : `${chosen} preferred for scope size`;
134
+ case 'isolated_refactor': return mutationScope.length <= 8 ? 'scope is small enough for simpler strategy' : `${chosen} preferred`;
135
+ case 'expand_contract': return 'expand-contract requires coordinated deployment; overhead not justified here';
136
+ case 'minimal_patch': return `${chosen} preferred due to scope or risk level`;
137
+ default: return 'not applicable';
138
+ }
139
+ }
140
+ }
141
+
142
+ // ─── Memo builder & persistence ──────────────────────────────────────────────
143
+
144
+ export interface BuildMemoInput {
145
+ work_item_id: string;
146
+ run_id: string;
147
+ problem_summary: string;
148
+ mutation_scope: string[];
149
+ retry_count: number;
150
+ risk_level: string;
151
+ min_evidence_required?: string[];
152
+ confidence?: number;
153
+ }
154
+
155
+ export function buildMemo(input: BuildMemoInput): DecisionMemo {
156
+ const selector = new StrategySelector();
157
+ const chosen = selector.select(input.mutation_scope, input.retry_count, input.risk_level);
158
+ const blast_radius = buildBlastRadius(input.mutation_scope, input.retry_count, input.risk_level);
159
+ const rollback_plan = buildRollbackPlan(blast_radius, input.retry_count);
160
+ const alternatives_rejected = selector.alternatives(chosen, input.mutation_scope, input.risk_level);
161
+
162
+ return {
163
+ memo_id: `memo-${crypto.randomBytes(4).toString('hex')}`,
164
+ work_item_id: input.work_item_id,
165
+ run_id: input.run_id,
166
+ problem_summary: input.problem_summary,
167
+ chosen_strategy: chosen,
168
+ alternatives_rejected,
169
+ blast_radius,
170
+ rollback_plan,
171
+ min_evidence_required: input.min_evidence_required ?? [],
172
+ confidence: input.confidence ?? (1 - blast_radius.risk_score),
173
+ created_at: new Date().toISOString(),
174
+ };
175
+ }
176
+
177
+ function memoDir(projectRoot: string, runId: string): string {
178
+ return path.join(projectRoot, '.oxe', 'runs', runId, 'memos');
179
+ }
180
+
181
+ export function saveMemo(projectRoot: string, memo: DecisionMemo): void {
182
+ const dir = memoDir(projectRoot, memo.run_id);
183
+ fs.mkdirSync(dir, { recursive: true });
184
+ fs.writeFileSync(path.join(dir, `${memo.memo_id}.json`), JSON.stringify(memo, null, 2), 'utf8');
185
+ }
186
+
187
+ export function loadMemo(projectRoot: string, runId: string, memoId: string): DecisionMemo | null {
188
+ const p = path.join(memoDir(projectRoot, runId), `${memoId}.json`);
189
+ if (!fs.existsSync(p)) return null;
190
+ try {
191
+ return JSON.parse(fs.readFileSync(p, 'utf8')) as DecisionMemo;
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
196
+
197
+ export function listMemos(projectRoot: string, runId: string): DecisionMemo[] {
198
+ const dir = memoDir(projectRoot, runId);
199
+ if (!fs.existsSync(dir)) return [];
200
+ return fs
201
+ .readdirSync(dir)
202
+ .filter((f) => f.endsWith('.json'))
203
+ .map((f) => {
204
+ try {
205
+ return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')) as DecisionMemo;
206
+ } catch {
207
+ return null;
208
+ }
209
+ })
210
+ .filter((m): m is DecisionMemo => m !== null);
211
+ }
@@ -1,2 +1,2 @@
1
- export * from './decision-engine';
2
- export * from './decision-memo';
1
+ export * from './decision-engine';
2
+ export * from './decision-memo';
@@ -1,84 +1,91 @@
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
- }
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
+ push(remote: string, branchName: string, setUpstream = false): void {
79
+ const args = ['push'];
80
+ if (setUpstream) args.push('-u');
81
+ args.push(remote, branchName);
82
+ this.git(args);
83
+ }
84
+
85
+ private git(args: string[]): string {
86
+ return execFileSync('git', args, {
87
+ cwd: this.projectRoot,
88
+ encoding: 'utf8',
89
+ });
90
+ }
91
+ }