oxe-cc 0.9.3 → 1.2.1

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 (319) hide show
  1. package/.cursor/commands/oxe-ask.md +1 -1
  2. package/.cursor/commands/oxe-capabilities.md +1 -1
  3. package/.cursor/commands/oxe-checkpoint.md +1 -1
  4. package/.cursor/commands/oxe-compact.md +1 -1
  5. package/.cursor/commands/oxe-dashboard.md +1 -1
  6. package/.cursor/commands/oxe-debug.md +1 -1
  7. package/.cursor/commands/oxe-discuss.md +1 -1
  8. package/.cursor/commands/oxe-execute.md +2 -2
  9. package/.cursor/commands/oxe-forensics.md +1 -1
  10. package/.cursor/commands/oxe-help.md +1 -1
  11. package/.cursor/commands/oxe-loop.md +1 -1
  12. package/.cursor/commands/oxe-milestone.md +1 -1
  13. package/.cursor/commands/oxe-next.md +1 -1
  14. package/.cursor/commands/oxe-obs.md +1 -1
  15. package/.cursor/commands/oxe-plan-agent.md +1 -1
  16. package/.cursor/commands/oxe-plan.md +1 -1
  17. package/.cursor/commands/oxe-project.md +1 -1
  18. package/.cursor/commands/oxe-quick.md +1 -1
  19. package/.cursor/commands/oxe-research.md +1 -1
  20. package/.cursor/commands/oxe-retro.md +1 -1
  21. package/.cursor/commands/oxe-review-pr.md +1 -1
  22. package/.cursor/commands/oxe-route.md +1 -1
  23. package/.cursor/commands/oxe-scan.md +1 -1
  24. package/.cursor/commands/oxe-security.md +1 -1
  25. package/.cursor/commands/oxe-session.md +2 -2
  26. package/.cursor/commands/oxe-ship.md +45 -0
  27. package/.cursor/commands/oxe-skill.md +1 -1
  28. package/.cursor/commands/oxe-spec.md +1 -1
  29. package/.cursor/commands/oxe-ui-review.md +1 -1
  30. package/.cursor/commands/oxe-ui-spec.md +1 -1
  31. package/.cursor/commands/oxe-update.md +1 -1
  32. package/.cursor/commands/oxe-validate-gaps.md +1 -1
  33. package/.cursor/commands/oxe-verify.md +1 -1
  34. package/.cursor/commands/oxe-workstream.md +1 -1
  35. package/.cursor/commands/oxe.md +4 -4
  36. package/.github/copilot-instructions.md +91 -1
  37. package/.github/prompts/oxe-ask.prompt.md +1 -1
  38. package/.github/prompts/oxe-capabilities.prompt.md +1 -1
  39. package/.github/prompts/oxe-checkpoint.prompt.md +1 -1
  40. package/.github/prompts/oxe-compact.prompt.md +1 -1
  41. package/.github/prompts/oxe-dashboard.prompt.md +1 -1
  42. package/.github/prompts/oxe-debug.prompt.md +1 -1
  43. package/.github/prompts/oxe-discuss.prompt.md +1 -1
  44. package/.github/prompts/oxe-execute.prompt.md +2 -2
  45. package/.github/prompts/oxe-forensics.prompt.md +1 -1
  46. package/.github/prompts/oxe-help.prompt.md +1 -1
  47. package/.github/prompts/oxe-loop.prompt.md +1 -1
  48. package/.github/prompts/oxe-milestone.prompt.md +1 -1
  49. package/.github/prompts/oxe-next.prompt.md +1 -1
  50. package/.github/prompts/oxe-obs.prompt.md +1 -1
  51. package/.github/prompts/oxe-plan-agent.prompt.md +1 -1
  52. package/.github/prompts/oxe-plan.prompt.md +1 -1
  53. package/.github/prompts/oxe-project.prompt.md +1 -1
  54. package/.github/prompts/oxe-quick.prompt.md +1 -1
  55. package/.github/prompts/oxe-research.prompt.md +1 -1
  56. package/.github/prompts/oxe-retro.prompt.md +1 -1
  57. package/.github/prompts/oxe-review-pr.prompt.md +1 -1
  58. package/.github/prompts/oxe-route.prompt.md +1 -1
  59. package/.github/prompts/oxe-scan.prompt.md +1 -1
  60. package/.github/prompts/oxe-security.prompt.md +1 -1
  61. package/.github/prompts/oxe-session.prompt.md +2 -2
  62. package/.github/prompts/oxe-ship.prompt.md +45 -0
  63. package/.github/prompts/oxe-skill.prompt.md +1 -1
  64. package/.github/prompts/oxe-spec.prompt.md +1 -1
  65. package/.github/prompts/oxe-ui-review.prompt.md +1 -1
  66. package/.github/prompts/oxe-ui-spec.prompt.md +1 -1
  67. package/.github/prompts/oxe-update.prompt.md +1 -1
  68. package/.github/prompts/oxe-validate-gaps.prompt.md +1 -1
  69. package/.github/prompts/oxe-verify.prompt.md +1 -1
  70. package/.github/prompts/oxe-workstream.prompt.md +1 -1
  71. package/.github/prompts/oxe.prompt.md +3 -3
  72. package/AGENTS.md +43 -28
  73. package/CHANGELOG.md +158 -0
  74. package/README.md +72 -50
  75. package/bin/banner.txt +1 -1
  76. package/bin/lib/oxe-dashboard.cjs +9 -7
  77. package/bin/lib/oxe-operational.cjs +569 -4
  78. package/bin/lib/oxe-project-health.cjs +1 -1
  79. package/bin/oxe-cc.js +141 -57
  80. package/commands/oxe/ask.md +5 -1
  81. package/commands/oxe/checkpoint.md +1 -1
  82. package/commands/oxe/compact.md +1 -1
  83. package/commands/oxe/debug.md +1 -1
  84. package/commands/oxe/execute.md +2 -2
  85. package/commands/oxe/forensics.md +1 -1
  86. package/commands/oxe/loop.md +1 -1
  87. package/commands/oxe/milestone.md +1 -1
  88. package/commands/oxe/next.md +1 -1
  89. package/commands/oxe/obs.md +1 -1
  90. package/commands/oxe/oxe.md +3 -3
  91. package/commands/oxe/project.md +1 -1
  92. package/commands/oxe/research.md +1 -1
  93. package/commands/oxe/retro.md +1 -1
  94. package/commands/oxe/review-pr.md +1 -1
  95. package/commands/oxe/route.md +1 -1
  96. package/commands/oxe/scan.md +1 -1
  97. package/commands/oxe/security.md +1 -1
  98. package/commands/oxe/session.md +2 -2
  99. package/commands/oxe/ship.md +49 -0
  100. package/commands/oxe/spec.md +2 -2
  101. package/commands/oxe/ui-review.md +1 -1
  102. package/commands/oxe/ui-spec.md +1 -1
  103. package/commands/oxe/validate-gaps.md +1 -1
  104. package/commands/oxe/verify.md +2 -2
  105. package/commands/oxe/workstream.md +1 -1
  106. package/lib/runtime/audit/audit-trail.d.ts +71 -0
  107. package/lib/runtime/audit/audit-trail.js +154 -0
  108. package/lib/runtime/audit/index.d.ts +2 -0
  109. package/lib/runtime/audit/index.js +18 -0
  110. package/lib/runtime/audit/policy-pack.d.ts +15 -0
  111. package/lib/runtime/audit/policy-pack.js +57 -0
  112. package/lib/runtime/compiler/graph-compiler.d.ts +83 -0
  113. package/lib/runtime/compiler/graph-compiler.js +135 -0
  114. package/lib/runtime/compiler/index.d.ts +1 -0
  115. package/lib/runtime/compiler/index.js +17 -0
  116. package/lib/runtime/context/context-pack-builder.d.ts +51 -0
  117. package/lib/runtime/context/context-pack-builder.js +178 -0
  118. package/lib/runtime/context/context-pack-store.d.ts +38 -0
  119. package/lib/runtime/context/context-pack-store.js +142 -0
  120. package/lib/runtime/context/context-profiles.d.ts +11 -0
  121. package/lib/runtime/context/context-profiles.js +51 -0
  122. package/lib/runtime/context/index.d.ts +3 -0
  123. package/lib/runtime/context/index.js +19 -0
  124. package/lib/runtime/decision/decision-engine.d.ts +43 -0
  125. package/lib/runtime/decision/decision-engine.js +127 -0
  126. package/lib/runtime/decision/decision-memo.d.ts +53 -0
  127. package/lib/runtime/decision/decision-memo.js +173 -0
  128. package/lib/runtime/decision/index.d.ts +2 -0
  129. package/lib/runtime/decision/index.js +18 -0
  130. package/lib/runtime/delivery/branch-manager.d.ts +19 -0
  131. package/lib/runtime/delivery/branch-manager.js +78 -0
  132. package/lib/runtime/delivery/ci-checks.d.ts +34 -0
  133. package/lib/runtime/delivery/ci-checks.js +209 -0
  134. package/lib/runtime/delivery/index.d.ts +4 -0
  135. package/lib/runtime/delivery/index.js +20 -0
  136. package/lib/runtime/delivery/pr-manager.d.ts +30 -0
  137. package/lib/runtime/delivery/pr-manager.js +82 -0
  138. package/lib/runtime/delivery/promotion-pipeline.d.ts +39 -0
  139. package/lib/runtime/delivery/promotion-pipeline.js +127 -0
  140. package/lib/runtime/events/bus.d.ts +9 -0
  141. package/lib/runtime/events/bus.js +63 -0
  142. package/lib/runtime/events/catalog.d.ts +3 -0
  143. package/lib/runtime/events/catalog.js +30 -0
  144. package/lib/runtime/events/envelope.d.ts +13 -0
  145. package/lib/runtime/events/envelope.js +2 -0
  146. package/lib/runtime/events/index.d.ts +3 -0
  147. package/lib/runtime/events/index.js +19 -0
  148. package/lib/runtime/evidence/evidence-store.d.ts +22 -0
  149. package/lib/runtime/evidence/evidence-store.js +106 -0
  150. package/lib/runtime/evidence/index.d.ts +1 -0
  151. package/lib/runtime/evidence/index.js +17 -0
  152. package/lib/runtime/gate/gate-manager.d.ts +39 -0
  153. package/lib/runtime/gate/gate-manager.js +104 -0
  154. package/lib/runtime/gate/index.d.ts +1 -0
  155. package/lib/runtime/gate/index.js +17 -0
  156. package/lib/runtime/index.d.ts +19 -0
  157. package/lib/runtime/index.js +44 -0
  158. package/lib/runtime/models/attempt.d.ts +12 -0
  159. package/lib/runtime/models/attempt.js +2 -0
  160. package/lib/runtime/models/evidence.d.ts +9 -0
  161. package/lib/runtime/models/evidence.js +2 -0
  162. package/lib/runtime/models/gate-decision.d.ts +10 -0
  163. package/lib/runtime/models/gate-decision.js +2 -0
  164. package/lib/runtime/models/index.d.ts +8 -0
  165. package/lib/runtime/models/index.js +24 -0
  166. package/lib/runtime/models/run.d.ts +13 -0
  167. package/lib/runtime/models/run.js +2 -0
  168. package/lib/runtime/models/session.d.ts +10 -0
  169. package/lib/runtime/models/session.js +2 -0
  170. package/lib/runtime/models/verification-result.d.ts +9 -0
  171. package/lib/runtime/models/verification-result.js +2 -0
  172. package/lib/runtime/models/work-item.d.ts +15 -0
  173. package/lib/runtime/models/work-item.js +2 -0
  174. package/lib/runtime/models/workspace.d.ts +25 -0
  175. package/lib/runtime/models/workspace.js +2 -0
  176. package/lib/runtime/plugins/capability-matrix.d.ts +20 -0
  177. package/lib/runtime/plugins/capability-matrix.js +59 -0
  178. package/lib/runtime/plugins/index.d.ts +4 -0
  179. package/lib/runtime/plugins/index.js +20 -0
  180. package/lib/runtime/plugins/plugin-abi.d.ts +76 -0
  181. package/lib/runtime/plugins/plugin-abi.js +2 -0
  182. package/lib/runtime/plugins/plugin-manifest.d.ts +22 -0
  183. package/lib/runtime/plugins/plugin-manifest.js +91 -0
  184. package/lib/runtime/plugins/plugin-registry.d.ts +21 -0
  185. package/lib/runtime/plugins/plugin-registry.js +119 -0
  186. package/lib/runtime/policy/index.d.ts +1 -0
  187. package/lib/runtime/policy/index.js +17 -0
  188. package/lib/runtime/policy/policy-engine.d.ts +67 -0
  189. package/lib/runtime/policy/policy-engine.js +171 -0
  190. package/lib/runtime/projection/index.d.ts +1 -0
  191. package/lib/runtime/projection/index.js +17 -0
  192. package/lib/runtime/projection/projection-engine.d.ts +11 -0
  193. package/lib/runtime/projection/projection-engine.js +218 -0
  194. package/lib/runtime/reducers/debug-reducer.d.ts +10 -0
  195. package/lib/runtime/reducers/debug-reducer.js +30 -0
  196. package/lib/runtime/reducers/index.d.ts +2 -0
  197. package/lib/runtime/reducers/index.js +18 -0
  198. package/lib/runtime/reducers/run-state-reducer.d.ts +46 -0
  199. package/lib/runtime/reducers/run-state-reducer.js +226 -0
  200. package/lib/runtime/scheduler/agent-registry.d.ts +44 -0
  201. package/lib/runtime/scheduler/agent-registry.js +96 -0
  202. package/lib/runtime/scheduler/agent-roles.d.ts +54 -0
  203. package/lib/runtime/scheduler/agent-roles.js +62 -0
  204. package/lib/runtime/scheduler/index.d.ts +4 -0
  205. package/lib/runtime/scheduler/index.js +20 -0
  206. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +36 -0
  207. package/lib/runtime/scheduler/multi-agent-coordinator.js +253 -0
  208. package/lib/runtime/scheduler/run-journal.d.ts +18 -0
  209. package/lib/runtime/scheduler/run-journal.js +54 -0
  210. package/lib/runtime/scheduler/scheduler.d.ts +49 -0
  211. package/lib/runtime/scheduler/scheduler.js +324 -0
  212. package/lib/runtime/verification/index.d.ts +2 -0
  213. package/lib/runtime/verification/index.js +18 -0
  214. package/lib/runtime/verification/verification-compiler.d.ts +56 -0
  215. package/lib/runtime/verification/verification-compiler.js +147 -0
  216. package/lib/runtime/verification/verification-manifest.d.ts +58 -0
  217. package/lib/runtime/verification/verification-manifest.js +129 -0
  218. package/lib/runtime/workspace/index.d.ts +5 -0
  219. package/lib/runtime/workspace/index.js +24 -0
  220. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +22 -0
  221. package/lib/runtime/workspace/strategies/ephemeral-container.js +109 -0
  222. package/lib/runtime/workspace/strategies/git-worktree.d.ts +12 -0
  223. package/lib/runtime/workspace/strategies/git-worktree.js +79 -0
  224. package/lib/runtime/workspace/strategies/inplace.d.ts +10 -0
  225. package/lib/runtime/workspace/strategies/inplace.js +37 -0
  226. package/lib/runtime/workspace/workspace-manager.d.ts +13 -0
  227. package/lib/runtime/workspace/workspace-manager.js +2 -0
  228. package/lib/sdk/index.cjs +24 -7
  229. package/lib/sdk/index.d.ts +17 -7
  230. package/oxe/workflows/ask.md +4 -0
  231. package/oxe/workflows/checkpoint.md +14 -10
  232. package/oxe/workflows/debug.md +19 -15
  233. package/oxe/workflows/execute.md +30 -2
  234. package/oxe/workflows/forensics.md +13 -9
  235. package/oxe/workflows/help.md +97 -49
  236. package/oxe/workflows/loop.md +17 -13
  237. package/oxe/workflows/obs.md +4 -0
  238. package/oxe/workflows/oxe.md +64 -31
  239. package/oxe/workflows/project.md +6 -1
  240. package/oxe/workflows/references/workflow-runtime-contracts.json +23 -0
  241. package/oxe/workflows/research.md +32 -28
  242. package/oxe/workflows/retro.md +4 -0
  243. package/oxe/workflows/review-pr.md +15 -11
  244. package/oxe/workflows/scan.md +4 -0
  245. package/oxe/workflows/security.md +14 -10
  246. package/oxe/workflows/session.md +17 -1
  247. package/oxe/workflows/ship.md +142 -0
  248. package/oxe/workflows/spec.md +15 -0
  249. package/oxe/workflows/ui-review.md +20 -16
  250. package/oxe/workflows/ui-spec.md +7 -3
  251. package/oxe/workflows/validate-gaps.md +13 -9
  252. package/oxe/workflows/verify.md +42 -3
  253. package/package.json +9 -3
  254. package/packages/runtime/package.json +17 -0
  255. package/packages/runtime/src/audit/audit-trail.ts +243 -0
  256. package/packages/runtime/src/audit/index.ts +2 -0
  257. package/packages/runtime/src/audit/policy-pack.ts +62 -0
  258. package/packages/runtime/src/compiler/graph-compiler.ts +245 -0
  259. package/packages/runtime/src/compiler/index.ts +1 -0
  260. package/packages/runtime/src/context/context-pack-builder.ts +259 -0
  261. package/packages/runtime/src/context/context-pack-store.ts +197 -0
  262. package/packages/runtime/src/context/context-profiles.ts +60 -0
  263. package/packages/runtime/src/context/index.ts +3 -0
  264. package/packages/runtime/src/decision/decision-engine.ts +174 -0
  265. package/packages/runtime/src/decision/decision-memo.ts +211 -0
  266. package/packages/runtime/src/decision/index.ts +2 -0
  267. package/packages/runtime/src/delivery/branch-manager.ts +84 -0
  268. package/packages/runtime/src/delivery/ci-checks.ts +252 -0
  269. package/packages/runtime/src/delivery/index.ts +4 -0
  270. package/packages/runtime/src/delivery/pr-manager.ts +112 -0
  271. package/packages/runtime/src/delivery/promotion-pipeline.ts +180 -0
  272. package/packages/runtime/src/events/bus.ts +92 -0
  273. package/packages/runtime/src/events/catalog.ts +29 -0
  274. package/packages/runtime/src/events/envelope.ts +14 -0
  275. package/packages/runtime/src/events/index.ts +3 -0
  276. package/packages/runtime/src/evidence/evidence-store.ts +130 -0
  277. package/packages/runtime/src/evidence/index.ts +1 -0
  278. package/packages/runtime/src/gate/gate-manager.ts +137 -0
  279. package/packages/runtime/src/gate/index.ts +1 -0
  280. package/packages/runtime/src/index.ts +37 -0
  281. package/packages/runtime/src/models/attempt.ts +19 -0
  282. package/packages/runtime/src/models/evidence.ts +21 -0
  283. package/packages/runtime/src/models/gate-decision.ts +21 -0
  284. package/packages/runtime/src/models/index.ts +8 -0
  285. package/packages/runtime/src/models/run.ts +24 -0
  286. package/packages/runtime/src/models/session.ts +11 -0
  287. package/packages/runtime/src/models/verification-result.ts +10 -0
  288. package/packages/runtime/src/models/work-item.ts +25 -0
  289. package/packages/runtime/src/models/workspace.ts +28 -0
  290. package/packages/runtime/src/plugins/capability-matrix.ts +83 -0
  291. package/packages/runtime/src/plugins/index.ts +4 -0
  292. package/packages/runtime/src/plugins/plugin-abi.ts +95 -0
  293. package/packages/runtime/src/plugins/plugin-manifest.ts +113 -0
  294. package/packages/runtime/src/plugins/plugin-registry.ts +124 -0
  295. package/packages/runtime/src/policy/index.ts +1 -0
  296. package/packages/runtime/src/policy/policy-engine.ts +244 -0
  297. package/packages/runtime/src/projection/index.ts +1 -0
  298. package/packages/runtime/src/projection/projection-engine.ts +249 -0
  299. package/packages/runtime/src/reducers/debug-reducer.ts +36 -0
  300. package/packages/runtime/src/reducers/index.ts +2 -0
  301. package/packages/runtime/src/reducers/run-state-reducer.ts +269 -0
  302. package/packages/runtime/src/scheduler/agent-registry.ts +132 -0
  303. package/packages/runtime/src/scheduler/agent-roles.ts +109 -0
  304. package/packages/runtime/src/scheduler/index.ts +4 -0
  305. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +333 -0
  306. package/packages/runtime/src/scheduler/run-journal.ts +62 -0
  307. package/packages/runtime/src/scheduler/scheduler.ts +441 -0
  308. package/packages/runtime/src/verification/index.ts +2 -0
  309. package/packages/runtime/src/verification/verification-compiler.ts +225 -0
  310. package/packages/runtime/src/verification/verification-manifest.ts +192 -0
  311. package/packages/runtime/src/workspace/index.ts +5 -0
  312. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +121 -0
  313. package/packages/runtime/src/workspace/strategies/git-worktree.ts +77 -0
  314. package/packages/runtime/src/workspace/strategies/inplace.ts +35 -0
  315. package/packages/runtime/src/workspace/workspace-manager.ts +15 -0
  316. package/packages/runtime/tsconfig.json +17 -0
  317. package/vscode-extension/oxe-agents-0.9.2.vsix +0 -0
  318. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  319. package/vscode-extension/package.json +1 -1
@@ -0,0 +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
+ }
@@ -0,0 +1,2 @@
1
+ export * from './decision-engine';
2
+ export * from './decision-memo';
@@ -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
+ }
@@ -0,0 +1,252 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import type { EvidenceStore } from '../evidence/evidence-store';
4
+
5
+ export type CICheckStatus = 'pass' | 'fail' | 'skip' | 'error';
6
+
7
+ export interface CICheckContext {
8
+ projectRoot: string;
9
+ sessionId: string | null;
10
+ runId?: string;
11
+ evidenceStore?: EvidenceStore;
12
+ }
13
+
14
+ export interface CICheckResult {
15
+ check: string;
16
+ status: CICheckStatus;
17
+ message: string;
18
+ details?: unknown;
19
+ }
20
+
21
+ export interface CICheck {
22
+ name: string;
23
+ description: string;
24
+ run(ctx: CICheckContext): Promise<CICheckResult>;
25
+ }
26
+
27
+ // ─── Check: plan-consistency ─────────────────────────────────────────────────
28
+
29
+ export const planConsistencyCheck: CICheck = {
30
+ name: 'oxe-plan-consistency',
31
+ description: 'Verifies ACTIVE-RUN.json exists and has a compiled ExecutionGraph',
32
+ async run(ctx) {
33
+ const activeRunPath = ctx.sessionId
34
+ ? path.join(ctx.projectRoot, '.oxe', ctx.sessionId, 'execution', 'ACTIVE-RUN.json')
35
+ : path.join(ctx.projectRoot, '.oxe', 'ACTIVE-RUN.json');
36
+
37
+ if (!fs.existsSync(activeRunPath)) {
38
+ return { check: this.name, status: 'skip', message: 'No ACTIVE-RUN.json found' };
39
+ }
40
+
41
+ try {
42
+ const raw = JSON.parse(fs.readFileSync(activeRunPath, 'utf8')) as Record<string, unknown>;
43
+ const hasGraph = raw.compiled_graph && typeof raw.compiled_graph === 'object';
44
+ const hasRunId = typeof raw.run_id === 'string';
45
+
46
+ if (!hasRunId) {
47
+ return { check: this.name, status: 'fail', message: 'ACTIVE-RUN.json missing run_id', details: raw };
48
+ }
49
+ if (!hasGraph) {
50
+ return { check: this.name, status: 'fail', message: 'No compiled ExecutionGraph found in ACTIVE-RUN.json', details: { run_id: raw.run_id } };
51
+ }
52
+ return { check: this.name, status: 'pass', message: `Run ${String(raw.run_id)} has compiled graph` };
53
+ } catch (err) {
54
+ return { check: this.name, status: 'error', message: `Failed to parse ACTIVE-RUN.json: ${String(err)}` };
55
+ }
56
+ },
57
+ };
58
+
59
+ // ─── Check: verify-acceptance ────────────────────────────────────────────────
60
+
61
+ export const verifyAcceptanceCheck: CICheck = {
62
+ name: 'oxe-verify-acceptance',
63
+ description: 'Checks that VERIFY.md exists and contains no failed criteria',
64
+ async run(ctx) {
65
+ const verifyPath = ctx.sessionId
66
+ ? path.join(ctx.projectRoot, '.oxe', ctx.sessionId, 'verification', 'VERIFY.md')
67
+ : path.join(ctx.projectRoot, '.oxe', 'VERIFY.md');
68
+
69
+ if (!fs.existsSync(verifyPath)) {
70
+ return { check: this.name, status: 'skip', message: 'No VERIFY.md found — run /oxe-verify first' };
71
+ }
72
+
73
+ const content = fs.readFileSync(verifyPath, 'utf8');
74
+ const failLines = content.split('\n').filter((l) => l.includes('✗ FAIL'));
75
+ const passLines = content.split('\n').filter((l) => l.includes('✓ PASS'));
76
+
77
+ if (failLines.length > 0) {
78
+ return {
79
+ check: this.name,
80
+ status: 'fail',
81
+ message: `${failLines.length} acceptance criteria failed`,
82
+ details: { failed: failLines, passed: passLines.length },
83
+ };
84
+ }
85
+ if (passLines.length === 0) {
86
+ return { check: this.name, status: 'skip', message: 'VERIFY.md has no pass/fail markers' };
87
+ }
88
+ return { check: this.name, status: 'pass', message: `${passLines.length} acceptance criteria passed` };
89
+ },
90
+ };
91
+
92
+ // ─── Check: policy ───────────────────────────────────────────────────────────
93
+
94
+ export const policyCheck: CICheck = {
95
+ name: 'oxe-policy',
96
+ description: 'Checks that no gates are pending (unresolved human approval)',
97
+ async run(ctx) {
98
+ const gatesPath = ctx.sessionId
99
+ ? path.join(ctx.projectRoot, '.oxe', ctx.sessionId, 'execution', 'GATES.json')
100
+ : path.join(ctx.projectRoot, '.oxe', 'execution', 'GATES.json');
101
+
102
+ if (!fs.existsSync(gatesPath)) {
103
+ return { check: this.name, status: 'pass', message: 'No pending gates' };
104
+ }
105
+
106
+ try {
107
+ const gates = JSON.parse(fs.readFileSync(gatesPath, 'utf8')) as Array<{ status: string; scope: string; gate_id: string }>;
108
+ const pending = gates.filter((g) => g.status === 'pending');
109
+ if (pending.length > 0) {
110
+ return {
111
+ check: this.name,
112
+ status: 'fail',
113
+ message: `${pending.length} unresolved gate(s)`,
114
+ details: pending.map((g) => ({ gate_id: g.gate_id, scope: g.scope })),
115
+ };
116
+ }
117
+ return { check: this.name, status: 'pass', message: 'All gates resolved' };
118
+ } catch (err) {
119
+ return { check: this.name, status: 'error', message: `Failed to read GATES.json: ${String(err)}` };
120
+ }
121
+ },
122
+ };
123
+
124
+ // ─── Check: security-baseline ────────────────────────────────────────────────
125
+
126
+ const SECRET_PATTERNS = [
127
+ /(?:password|passwd|secret|api[_-]?key|auth[_-]?token)\s*[:=]\s*['"]?\S{8,}/i,
128
+ /(?:AKIA|ASIA)[A-Z0-9]{16}/,
129
+ /-----BEGIN (?:RSA|EC|OPENSSH) PRIVATE KEY-----/,
130
+ ];
131
+
132
+ export const securityBaselineCheck: CICheck = {
133
+ name: 'oxe-security-baseline',
134
+ description: 'Scans evidence artifacts for common secret patterns',
135
+ async run(ctx) {
136
+ if (!ctx.evidenceStore || !ctx.runId) {
137
+ return { check: this.name, status: 'skip', message: 'No evidence store or run ID provided' };
138
+ }
139
+
140
+ const evidenceDir = path.join(ctx.projectRoot, '.oxe', 'evidence', 'runs', ctx.runId);
141
+ if (!fs.existsSync(evidenceDir)) {
142
+ return { check: this.name, status: 'skip', message: 'No evidence found for this run' };
143
+ }
144
+
145
+ const findings: string[] = [];
146
+ walkDir(evidenceDir, (filePath) => {
147
+ if (filePath.endsWith('.json') || filePath.endsWith('.patch') || filePath.endsWith('.txt')) {
148
+ try {
149
+ const content = fs.readFileSync(filePath, 'utf8');
150
+ for (const pattern of SECRET_PATTERNS) {
151
+ if (pattern.test(content)) {
152
+ findings.push(`${path.basename(filePath)}: matches pattern ${pattern.source.slice(0, 40)}`);
153
+ break;
154
+ }
155
+ }
156
+ } catch { /* skip unreadable */ }
157
+ }
158
+ });
159
+
160
+ if (findings.length > 0) {
161
+ return { check: this.name, status: 'fail', message: `Secret patterns detected in ${findings.length} evidence file(s)`, details: findings };
162
+ }
163
+ return { check: this.name, status: 'pass', message: 'No secret patterns detected in evidence' };
164
+ },
165
+ };
166
+
167
+ // ─── Check: runtime-evidence-integrity ───────────────────────────────────────
168
+
169
+ export const runtimeEvidenceIntegrityCheck: CICheck = {
170
+ name: 'oxe-runtime-evidence-integrity',
171
+ description: 'Validates that all evidence index files are valid JSON and files exist on disk',
172
+ async run(ctx) {
173
+ if (!ctx.runId) {
174
+ return { check: this.name, status: 'skip', message: 'No run ID provided' };
175
+ }
176
+
177
+ const runEvidenceDir = path.join(ctx.projectRoot, '.oxe', 'evidence', 'runs', ctx.runId);
178
+ if (!fs.existsSync(runEvidenceDir)) {
179
+ return { check: this.name, status: 'skip', message: 'No evidence directory for this run' };
180
+ }
181
+
182
+ const errors: string[] = [];
183
+ let indexCount = 0;
184
+ let evidenceCount = 0;
185
+
186
+ walkDir(runEvidenceDir, (filePath) => {
187
+ if (path.basename(filePath) !== 'index.json') return;
188
+ indexCount++;
189
+ try {
190
+ const items = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Array<{ path: string; evidence_id: string }>;
191
+ for (const item of items) {
192
+ evidenceCount++;
193
+ const absPath = path.join(ctx.projectRoot, item.path);
194
+ if (!fs.existsSync(absPath)) {
195
+ errors.push(`Missing file for ${item.evidence_id}: ${item.path}`);
196
+ }
197
+ }
198
+ } catch (err) {
199
+ errors.push(`Corrupt index at ${filePath}: ${String(err)}`);
200
+ }
201
+ });
202
+
203
+ if (errors.length > 0) {
204
+ return { check: this.name, status: 'fail', message: `${errors.length} integrity error(s)`, details: errors };
205
+ }
206
+ return {
207
+ check: this.name,
208
+ status: indexCount === 0 ? 'skip' : 'pass',
209
+ message: `${evidenceCount} evidence artifact(s) across ${indexCount} index(es) — all valid`,
210
+ };
211
+ },
212
+ };
213
+
214
+ // ─── Suite ───────────────────────────────────────────────────────────────────
215
+
216
+ export const OXE_CI_CHECKS: CICheck[] = [
217
+ planConsistencyCheck,
218
+ verifyAcceptanceCheck,
219
+ policyCheck,
220
+ securityBaselineCheck,
221
+ runtimeEvidenceIntegrityCheck,
222
+ ];
223
+
224
+ export async function runCIChecks(
225
+ ctx: CICheckContext,
226
+ checks: CICheck[] = OXE_CI_CHECKS
227
+ ): Promise<CICheckResult[]> {
228
+ const results: CICheckResult[] = [];
229
+ for (const check of checks) {
230
+ results.push(await check.run(ctx));
231
+ }
232
+ return results;
233
+ }
234
+
235
+ export function summarizeCIResults(results: CICheckResult[]): {
236
+ total: number; pass: number; fail: number; skip: number; error: number; allPassed: boolean;
237
+ } {
238
+ const counts = { total: results.length, pass: 0, fail: 0, skip: 0, error: 0 };
239
+ for (const r of results) counts[r.status]++;
240
+ return { ...counts, allPassed: counts.fail === 0 && counts.error === 0 };
241
+ }
242
+
243
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
244
+
245
+ function walkDir(dir: string, visitor: (filePath: string) => void): void {
246
+ if (!fs.existsSync(dir)) return;
247
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
248
+ const full = path.join(dir, entry.name);
249
+ if (entry.isDirectory()) walkDir(full, visitor);
250
+ else visitor(full);
251
+ }
252
+ }
@@ -0,0 +1,4 @@
1
+ export * from './branch-manager';
2
+ export * from './pr-manager';
3
+ export * from './ci-checks';
4
+ export * from './promotion-pipeline';
@@ -0,0 +1,112 @@
1
+ import { spawnSync } from 'child_process';
2
+
3
+ export interface PRDraftOptions {
4
+ title: string;
5
+ body: string;
6
+ base?: string;
7
+ head?: string;
8
+ draft?: boolean;
9
+ }
10
+
11
+ export interface PRInfo {
12
+ number: number;
13
+ title: string;
14
+ url: string;
15
+ state: string;
16
+ draft: boolean;
17
+ head: string;
18
+ base: string;
19
+ }
20
+
21
+ export interface PRResult {
22
+ success: boolean;
23
+ url?: string;
24
+ error?: string;
25
+ pr?: PRInfo;
26
+ }
27
+
28
+ function isGhAvailable(cwd: string): boolean {
29
+ const result = spawnSync('gh', ['--version'], { cwd, encoding: 'utf8' });
30
+ return result.status === 0;
31
+ }
32
+
33
+ export class PRManager {
34
+ constructor(private readonly projectRoot: string) {}
35
+
36
+ isAvailable(): boolean {
37
+ return isGhAvailable(this.projectRoot);
38
+ }
39
+
40
+ createDraft(opts: PRDraftOptions): PRResult {
41
+ if (!this.isAvailable()) {
42
+ return { success: false, error: 'gh CLI not available — install from https://cli.github.com' };
43
+ }
44
+ const args = [
45
+ 'pr', 'create',
46
+ '--title', opts.title,
47
+ '--body', opts.body,
48
+ ];
49
+ if (opts.draft !== false) args.push('--draft');
50
+ if (opts.base) args.push('--base', opts.base);
51
+ if (opts.head) args.push('--head', opts.head);
52
+
53
+ const result = spawnSync('gh', args, {
54
+ cwd: this.projectRoot,
55
+ encoding: 'utf8',
56
+ });
57
+
58
+ if (result.status !== 0) {
59
+ return { success: false, error: result.stderr?.trim() ?? 'gh pr create failed' };
60
+ }
61
+ const url = result.stdout?.trim();
62
+ return { success: true, url };
63
+ }
64
+
65
+ view(prNumberOrUrl: string | number): PRResult {
66
+ if (!this.isAvailable()) {
67
+ return { success: false, error: 'gh CLI not available' };
68
+ }
69
+ const result = spawnSync(
70
+ 'gh',
71
+ ['pr', 'view', String(prNumberOrUrl), '--json', 'number,title,url,state,isDraft,headRefName,baseRefName'],
72
+ { cwd: this.projectRoot, encoding: 'utf8' }
73
+ );
74
+ if (result.status !== 0) {
75
+ return { success: false, error: result.stderr?.trim() };
76
+ }
77
+ try {
78
+ const raw = JSON.parse(result.stdout) as {
79
+ number: number; title: string; url: string; state: string;
80
+ isDraft: boolean; headRefName: string; baseRefName: string;
81
+ };
82
+ return {
83
+ success: true,
84
+ url: raw.url,
85
+ pr: {
86
+ number: raw.number,
87
+ title: raw.title,
88
+ url: raw.url,
89
+ state: raw.state.toLowerCase(),
90
+ draft: raw.isDraft,
91
+ head: raw.headRefName,
92
+ base: raw.baseRefName,
93
+ },
94
+ };
95
+ } catch {
96
+ return { success: false, error: 'Failed to parse gh output' };
97
+ }
98
+ }
99
+
100
+ mergePR(prNumber: number, method: 'merge' | 'squash' | 'rebase' = 'merge'): PRResult {
101
+ if (!this.isAvailable()) {
102
+ return { success: false, error: 'gh CLI not available' };
103
+ }
104
+ const result = spawnSync('gh', ['pr', 'merge', String(prNumber), `--${method}`, '--delete-branch'], {
105
+ cwd: this.projectRoot,
106
+ encoding: 'utf8',
107
+ });
108
+ return result.status === 0
109
+ ? { success: true }
110
+ : { success: false, error: result.stderr?.trim() };
111
+ }
112
+ }