oxe-cc 1.2.1 → 1.4.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 (281) 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/AGENTS.md +5 -3
  73. package/CHANGELOG.md +72 -10
  74. package/LICENSE +21 -674
  75. package/README.md +631 -535
  76. package/bin/banner.txt +6 -6
  77. package/bin/lib/oxe-agent-install.cjs +69 -69
  78. package/bin/lib/oxe-azure.cjs +1445 -1445
  79. package/bin/lib/oxe-context-engine.cjs +867 -867
  80. package/bin/lib/oxe-dashboard.cjs +76 -28
  81. package/bin/lib/oxe-operational.cjs +2144 -1340
  82. package/bin/lib/oxe-project-health.cjs +483 -1
  83. package/bin/lib/oxe-runtime-semantics.cjs +12 -0
  84. package/bin/oxe-cc.js +554 -152
  85. package/commands/oxe/ask.md +2 -2
  86. package/commands/oxe/capabilities.md +2 -2
  87. package/commands/oxe/checkpoint.md +2 -2
  88. package/commands/oxe/compact.md +2 -2
  89. package/commands/oxe/dashboard.md +2 -2
  90. package/commands/oxe/debug.md +2 -2
  91. package/commands/oxe/discuss.md +2 -2
  92. package/commands/oxe/execute.md +5 -2
  93. package/commands/oxe/forensics.md +2 -2
  94. package/commands/oxe/help.md +2 -2
  95. package/commands/oxe/loop.md +2 -2
  96. package/commands/oxe/milestone.md +2 -2
  97. package/commands/oxe/next.md +2 -2
  98. package/commands/oxe/obs.md +2 -2
  99. package/commands/oxe/oxe.md +2 -2
  100. package/commands/oxe/plan-agent.md +2 -2
  101. package/commands/oxe/plan.md +2 -2
  102. package/commands/oxe/project.md +2 -2
  103. package/commands/oxe/quick.md +2 -2
  104. package/commands/oxe/research.md +2 -2
  105. package/commands/oxe/retro.md +2 -2
  106. package/commands/oxe/review-pr.md +2 -2
  107. package/commands/oxe/route.md +2 -2
  108. package/commands/oxe/scan.md +2 -2
  109. package/commands/oxe/security.md +2 -2
  110. package/commands/oxe/session.md +2 -2
  111. package/commands/oxe/ship.md +2 -2
  112. package/commands/oxe/skill.md +2 -2
  113. package/commands/oxe/spec.md +2 -2
  114. package/commands/oxe/ui-review.md +2 -2
  115. package/commands/oxe/ui-spec.md +2 -2
  116. package/commands/oxe/update.md +2 -2
  117. package/commands/oxe/validate-gaps.md +2 -2
  118. package/commands/oxe/verify.md +5 -2
  119. package/commands/oxe/workstream.md +2 -2
  120. package/lib/runtime/delivery/branch-manager.d.ts +1 -0
  121. package/lib/runtime/delivery/branch-manager.js +7 -0
  122. package/lib/runtime/delivery/ci-checks.js +34 -1
  123. package/lib/runtime/delivery/delivery-records.d.ts +34 -0
  124. package/lib/runtime/delivery/delivery-records.js +48 -0
  125. package/lib/runtime/delivery/index.d.ts +1 -0
  126. package/lib/runtime/delivery/index.js +1 -0
  127. package/lib/runtime/delivery/promotion-pipeline.d.ts +26 -2
  128. package/lib/runtime/delivery/promotion-pipeline.js +111 -14
  129. package/lib/runtime/gate/gate-manager.d.ts +41 -0
  130. package/lib/runtime/gate/gate-manager.js +108 -1
  131. package/lib/runtime/index.d.ts +2 -2
  132. package/lib/runtime/index.js +3 -1
  133. package/lib/runtime/models/gate-decision.d.ts +4 -1
  134. package/lib/runtime/models/workspace.d.ts +3 -0
  135. package/lib/runtime/plugins/capability-adapter.d.ts +12 -0
  136. package/lib/runtime/plugins/capability-adapter.js +204 -0
  137. package/lib/runtime/plugins/capability-matrix.d.ts +5 -0
  138. package/lib/runtime/plugins/capability-matrix.js +48 -17
  139. package/lib/runtime/plugins/index.d.ts +1 -0
  140. package/lib/runtime/plugins/index.js +1 -0
  141. package/lib/runtime/plugins/plugin-abi.d.ts +2 -0
  142. package/lib/runtime/plugins/plugin-manifest.d.ts +1 -1
  143. package/lib/runtime/plugins/plugin-manifest.js +6 -2
  144. package/lib/runtime/plugins/plugin-registry.d.ts +46 -0
  145. package/lib/runtime/plugins/plugin-registry.js +79 -2
  146. package/lib/runtime/policy/policy-engine.d.ts +19 -0
  147. package/lib/runtime/policy/policy-engine.js +76 -4
  148. package/lib/runtime/projection/projection-engine.d.ts +9 -1
  149. package/lib/runtime/projection/projection-engine.js +73 -3
  150. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +43 -1
  151. package/lib/runtime/scheduler/multi-agent-coordinator.js +151 -39
  152. package/lib/runtime/scheduler/run-journal.d.ts +1 -1
  153. package/lib/runtime/scheduler/scheduler.d.ts +19 -1
  154. package/lib/runtime/scheduler/scheduler.js +258 -13
  155. package/lib/runtime/verification/verification-compiler.d.ts +43 -0
  156. package/lib/runtime/verification/verification-compiler.js +137 -0
  157. package/lib/runtime/verification/verification-manifest.d.ts +9 -0
  158. package/lib/runtime/verification/verification-manifest.js +56 -6
  159. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +1 -0
  160. package/lib/runtime/workspace/strategies/ephemeral-container.js +4 -0
  161. package/lib/runtime/workspace/strategies/git-worktree.d.ts +1 -0
  162. package/lib/runtime/workspace/strategies/git-worktree.js +2 -0
  163. package/lib/runtime/workspace/strategies/inplace.d.ts +1 -0
  164. package/lib/runtime/workspace/strategies/inplace.js +2 -0
  165. package/lib/runtime/workspace/workspace-manager.d.ts +2 -1
  166. package/lib/sdk/README.md +20 -8
  167. package/lib/sdk/index.cjs +33 -24
  168. package/lib/sdk/index.d.ts +149 -14
  169. package/oxe/templates/ACTIVE-RUN.template.json +32 -32
  170. package/oxe/templates/CAPABILITIES.template.md +7 -7
  171. package/oxe/templates/CAPABILITY.template.md +45 -45
  172. package/oxe/templates/CHECKPOINTS.template.md +7 -7
  173. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -68
  174. package/oxe/templates/HYPOTHESES.template.md +33 -33
  175. package/oxe/templates/LESSONS-METRICS.template.json +13 -13
  176. package/oxe/templates/NOTES.template.md +16 -16
  177. package/oxe/templates/PLAN-REVIEW.template.md +31 -31
  178. package/oxe/templates/SESSION.template.md +34 -34
  179. package/oxe/templates/SKILL.template.md +26 -26
  180. package/oxe/templates/STATE.md +55 -55
  181. package/oxe/templates/WORKFLOW_AUTHORING.md +18 -18
  182. package/oxe/workflows/ask.md +96 -96
  183. package/oxe/workflows/capabilities.md +25 -25
  184. package/oxe/workflows/dashboard.md +33 -33
  185. package/oxe/workflows/discuss.md +12 -12
  186. package/oxe/workflows/execute.md +14 -0
  187. package/oxe/workflows/help.md +352 -352
  188. package/oxe/workflows/next.md +22 -22
  189. package/oxe/workflows/oxe.md +6 -6
  190. package/oxe/workflows/plan-agent.md +9 -9
  191. package/oxe/workflows/plan.md +51 -20
  192. package/oxe/workflows/quick.md +10 -10
  193. package/oxe/workflows/references/reasoning-discovery.md +28 -28
  194. package/oxe/workflows/references/reasoning-execution.md +29 -29
  195. package/oxe/workflows/references/reasoning-planning.md +32 -32
  196. package/oxe/workflows/references/reasoning-review.md +29 -29
  197. package/oxe/workflows/references/reasoning-status.md +24 -24
  198. package/oxe/workflows/references/robustness-elevation.md +295 -295
  199. package/oxe/workflows/references/workflow-runtime-contracts.json +952 -930
  200. package/oxe/workflows/route.md +16 -16
  201. package/oxe/workflows/session.md +213 -213
  202. package/oxe/workflows/ship.md +142 -142
  203. package/oxe/workflows/skill.md +44 -44
  204. package/oxe/workflows/ui-review.md +36 -36
  205. package/oxe/workflows/verify-audit.md +73 -73
  206. package/oxe/workflows/verify.md +10 -0
  207. package/package.json +92 -92
  208. package/packages/runtime/package.json +16 -15
  209. package/packages/runtime/src/audit/audit-trail.ts +243 -243
  210. package/packages/runtime/src/audit/index.ts +2 -2
  211. package/packages/runtime/src/audit/policy-pack.ts +62 -62
  212. package/packages/runtime/src/compiler/graph-compiler.ts +245 -245
  213. package/packages/runtime/src/compiler/index.ts +1 -1
  214. package/packages/runtime/src/context/context-pack-builder.ts +259 -259
  215. package/packages/runtime/src/context/context-pack-store.ts +197 -197
  216. package/packages/runtime/src/context/context-profiles.ts +60 -60
  217. package/packages/runtime/src/context/index.ts +3 -3
  218. package/packages/runtime/src/decision/decision-engine.ts +174 -174
  219. package/packages/runtime/src/decision/decision-memo.ts +211 -211
  220. package/packages/runtime/src/decision/index.ts +2 -2
  221. package/packages/runtime/src/delivery/branch-manager.ts +91 -84
  222. package/packages/runtime/src/delivery/ci-checks.ts +285 -252
  223. package/packages/runtime/src/delivery/delivery-records.ts +75 -0
  224. package/packages/runtime/src/delivery/index.ts +5 -4
  225. package/packages/runtime/src/delivery/pr-manager.ts +112 -112
  226. package/packages/runtime/src/delivery/promotion-pipeline.ts +334 -180
  227. package/packages/runtime/src/events/bus.ts +92 -92
  228. package/packages/runtime/src/events/catalog.ts +29 -29
  229. package/packages/runtime/src/events/envelope.ts +14 -14
  230. package/packages/runtime/src/events/index.ts +3 -3
  231. package/packages/runtime/src/evidence/evidence-store.ts +130 -130
  232. package/packages/runtime/src/evidence/index.ts +1 -1
  233. package/packages/runtime/src/gate/gate-manager.ts +289 -137
  234. package/packages/runtime/src/gate/index.ts +1 -1
  235. package/packages/runtime/src/index.ts +41 -37
  236. package/packages/runtime/src/models/attempt.ts +19 -19
  237. package/packages/runtime/src/models/evidence.ts +21 -21
  238. package/packages/runtime/src/models/gate-decision.ts +25 -21
  239. package/packages/runtime/src/models/index.ts +8 -8
  240. package/packages/runtime/src/models/run.ts +24 -24
  241. package/packages/runtime/src/models/session.ts +11 -11
  242. package/packages/runtime/src/models/verification-result.ts +10 -10
  243. package/packages/runtime/src/models/work-item.ts +25 -25
  244. package/packages/runtime/src/models/workspace.ts +31 -28
  245. package/packages/runtime/src/plugins/capability-adapter.ts +206 -0
  246. package/packages/runtime/src/plugins/capability-matrix.ts +126 -83
  247. package/packages/runtime/src/plugins/index.ts +5 -4
  248. package/packages/runtime/src/plugins/plugin-abi.ts +97 -95
  249. package/packages/runtime/src/plugins/plugin-manifest.ts +118 -113
  250. package/packages/runtime/src/plugins/plugin-registry.ts +232 -124
  251. package/packages/runtime/src/policy/index.ts +1 -1
  252. package/packages/runtime/src/policy/policy-engine.ts +330 -244
  253. package/packages/runtime/src/projection/index.ts +1 -1
  254. package/packages/runtime/src/projection/projection-engine.ts +328 -249
  255. package/packages/runtime/src/reducers/debug-reducer.ts +36 -36
  256. package/packages/runtime/src/reducers/index.ts +2 -2
  257. package/packages/runtime/src/reducers/run-state-reducer.ts +269 -269
  258. package/packages/runtime/src/scheduler/agent-registry.ts +132 -132
  259. package/packages/runtime/src/scheduler/agent-roles.ts +109 -109
  260. package/packages/runtime/src/scheduler/index.ts +4 -4
  261. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +521 -333
  262. package/packages/runtime/src/scheduler/run-journal.ts +62 -62
  263. package/packages/runtime/src/scheduler/scheduler.ts +722 -441
  264. package/packages/runtime/src/verification/index.ts +2 -2
  265. package/packages/runtime/src/verification/verification-compiler.ts +436 -225
  266. package/packages/runtime/src/verification/verification-manifest.ts +252 -192
  267. package/packages/runtime/src/workspace/index.ts +5 -5
  268. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +126 -121
  269. package/packages/runtime/src/workspace/strategies/git-worktree.ts +79 -77
  270. package/packages/runtime/src/workspace/strategies/inplace.ts +38 -35
  271. package/packages/runtime/src/workspace/workspace-manager.ts +16 -15
  272. package/packages/runtime/tsconfig.json +17 -17
  273. package/vscode-extension/.vscodeignore +7 -7
  274. package/vscode-extension/LICENSE +21 -0
  275. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  276. package/vscode-extension/oxe-agents-1.4.0.vsix +0 -0
  277. package/vscode-extension/package.json +184 -184
  278. package/vscode-extension/src/extension.js +310 -310
  279. package/vscode-extension/src/shared/contextLoader.js +137 -137
  280. package/vscode-extension/src/shared/contractBuilder.js +159 -159
  281. 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
+ }