@made-by-moonlight/athene-core 0.9.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 (285) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +241 -0
  3. package/dist/activity-events.d.ts +42 -0
  4. package/dist/activity-events.d.ts.map +1 -0
  5. package/dist/activity-events.js +192 -0
  6. package/dist/activity-events.js.map +1 -0
  7. package/dist/activity-log.d.ts +71 -0
  8. package/dist/activity-log.d.ts.map +1 -0
  9. package/dist/activity-log.js +203 -0
  10. package/dist/activity-log.js.map +1 -0
  11. package/dist/activity-signal.d.ts +20 -0
  12. package/dist/activity-signal.d.ts.map +1 -0
  13. package/dist/activity-signal.js +91 -0
  14. package/dist/activity-signal.js.map +1 -0
  15. package/dist/agent-report.d.ts +148 -0
  16. package/dist/agent-report.d.ts.map +1 -0
  17. package/dist/agent-report.js +516 -0
  18. package/dist/agent-report.js.map +1 -0
  19. package/dist/agent-selection.d.ts +31 -0
  20. package/dist/agent-selection.d.ts.map +1 -0
  21. package/dist/agent-selection.js +69 -0
  22. package/dist/agent-selection.js.map +1 -0
  23. package/dist/agent-workspace-hooks.d.ts +74 -0
  24. package/dist/agent-workspace-hooks.d.ts.map +1 -0
  25. package/dist/agent-workspace-hooks.js +988 -0
  26. package/dist/agent-workspace-hooks.js.map +1 -0
  27. package/dist/atomic-write.d.ts +6 -0
  28. package/dist/atomic-write.d.ts.map +1 -0
  29. package/dist/atomic-write.js +49 -0
  30. package/dist/atomic-write.js.map +1 -0
  31. package/dist/cleanup-stack.d.ts +37 -0
  32. package/dist/cleanup-stack.d.ts.map +1 -0
  33. package/dist/cleanup-stack.js +45 -0
  34. package/dist/cleanup-stack.js.map +1 -0
  35. package/dist/code-review-manager.d.ts +118 -0
  36. package/dist/code-review-manager.d.ts.map +1 -0
  37. package/dist/code-review-manager.js +719 -0
  38. package/dist/code-review-manager.js.map +1 -0
  39. package/dist/code-review-store.d.ts +114 -0
  40. package/dist/code-review-store.d.ts.map +1 -0
  41. package/dist/code-review-store.js +346 -0
  42. package/dist/code-review-store.js.map +1 -0
  43. package/dist/config-generator.d.ts +84 -0
  44. package/dist/config-generator.d.ts.map +1 -0
  45. package/dist/config-generator.js +295 -0
  46. package/dist/config-generator.js.map +1 -0
  47. package/dist/config.d.ts +55 -0
  48. package/dist/config.d.ts.map +1 -0
  49. package/dist/config.js +852 -0
  50. package/dist/config.js.map +1 -0
  51. package/dist/daemon-children.d.ts +55 -0
  52. package/dist/daemon-children.d.ts.map +1 -0
  53. package/dist/daemon-children.js +435 -0
  54. package/dist/daemon-children.js.map +1 -0
  55. package/dist/dashboard-notifications.d.ts +42 -0
  56. package/dist/dashboard-notifications.d.ts.map +1 -0
  57. package/dist/dashboard-notifications.js +123 -0
  58. package/dist/dashboard-notifications.js.map +1 -0
  59. package/dist/events-db.d.ts +39 -0
  60. package/dist/events-db.d.ts.map +1 -0
  61. package/dist/events-db.js +185 -0
  62. package/dist/events-db.js.map +1 -0
  63. package/dist/feature-flags.d.ts +2 -0
  64. package/dist/feature-flags.d.ts.map +1 -0
  65. package/dist/feature-flags.js +9 -0
  66. package/dist/feature-flags.js.map +1 -0
  67. package/dist/feedback-tools.d.ts +97 -0
  68. package/dist/feedback-tools.d.ts.map +1 -0
  69. package/dist/feedback-tools.js +161 -0
  70. package/dist/feedback-tools.js.map +1 -0
  71. package/dist/file-lock.d.ts +5 -0
  72. package/dist/file-lock.d.ts.map +1 -0
  73. package/dist/file-lock.js +59 -0
  74. package/dist/file-lock.js.map +1 -0
  75. package/dist/format-automated-comments.d.ts +18 -0
  76. package/dist/format-automated-comments.d.ts.map +1 -0
  77. package/dist/gh-trace.d.ts +57 -0
  78. package/dist/gh-trace.d.ts.map +1 -0
  79. package/dist/gh-trace.js +320 -0
  80. package/dist/gh-trace.js.map +1 -0
  81. package/dist/git-activity.d.ts +10 -0
  82. package/dist/git-activity.d.ts.map +1 -0
  83. package/dist/git-activity.js +30 -0
  84. package/dist/git-activity.js.map +1 -0
  85. package/dist/global-config.d.ts +1085 -0
  86. package/dist/global-config.d.ts.map +1 -0
  87. package/dist/global-config.js +1067 -0
  88. package/dist/global-config.js.map +1 -0
  89. package/dist/index.d.ts +91 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +59 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/key-value.d.ts +7 -0
  94. package/dist/key-value.d.ts.map +1 -0
  95. package/dist/key-value.js +24 -0
  96. package/dist/key-value.js.map +1 -0
  97. package/dist/lifecycle-manager.d.ts +22 -0
  98. package/dist/lifecycle-manager.d.ts.map +1 -0
  99. package/dist/lifecycle-manager.js +2813 -0
  100. package/dist/lifecycle-manager.js.map +1 -0
  101. package/dist/lifecycle-state.d.ts +28 -0
  102. package/dist/lifecycle-state.d.ts.map +1 -0
  103. package/dist/lifecycle-state.js +446 -0
  104. package/dist/lifecycle-state.js.map +1 -0
  105. package/dist/lifecycle-status-decisions.d.ts +85 -0
  106. package/dist/lifecycle-status-decisions.d.ts.map +1 -0
  107. package/dist/lifecycle-status-decisions.js +262 -0
  108. package/dist/lifecycle-status-decisions.js.map +1 -0
  109. package/dist/lifecycle-transition.d.ts +81 -0
  110. package/dist/lifecycle-transition.d.ts.map +1 -0
  111. package/dist/lifecycle-transition.js +207 -0
  112. package/dist/lifecycle-transition.js.map +1 -0
  113. package/dist/metadata.d.ts +54 -0
  114. package/dist/metadata.d.ts.map +1 -0
  115. package/dist/metadata.js +484 -0
  116. package/dist/metadata.js.map +1 -0
  117. package/dist/migration/storage-v2.d.ts +76 -0
  118. package/dist/migration/storage-v2.d.ts.map +1 -0
  119. package/dist/migration/storage-v2.js +1614 -0
  120. package/dist/migration/storage-v2.js.map +1 -0
  121. package/dist/notification-data.d.ts +135 -0
  122. package/dist/notification-data.d.ts.map +1 -0
  123. package/dist/notification-data.js +204 -0
  124. package/dist/notification-data.js.map +1 -0
  125. package/dist/notification-observability.d.ts +21 -0
  126. package/dist/notification-observability.d.ts.map +1 -0
  127. package/dist/notification-observability.js +154 -0
  128. package/dist/notification-observability.js.map +1 -0
  129. package/dist/notifier-resolution.d.ts +14 -0
  130. package/dist/notifier-resolution.d.ts.map +1 -0
  131. package/dist/notifier-resolution.js +23 -0
  132. package/dist/notifier-resolution.js.map +1 -0
  133. package/dist/observability.d.ts +100 -0
  134. package/dist/observability.d.ts.map +1 -0
  135. package/dist/observability.js +535 -0
  136. package/dist/observability.js.map +1 -0
  137. package/dist/opencode-agents-md.d.ts +3 -0
  138. package/dist/opencode-agents-md.d.ts.map +1 -0
  139. package/dist/opencode-agents-md.js +40 -0
  140. package/dist/opencode-agents-md.js.map +1 -0
  141. package/dist/opencode-config.d.ts +2 -0
  142. package/dist/opencode-config.d.ts.map +1 -0
  143. package/dist/opencode-config.js +17 -0
  144. package/dist/opencode-config.js.map +1 -0
  145. package/dist/opencode-session-id.d.ts +2 -0
  146. package/dist/opencode-session-id.d.ts.map +1 -0
  147. package/dist/opencode-session-id.js +12 -0
  148. package/dist/opencode-session-id.js.map +1 -0
  149. package/dist/opencode-shared.d.ts +80 -0
  150. package/dist/opencode-shared.d.ts.map +1 -0
  151. package/dist/opencode-shared.js +202 -0
  152. package/dist/opencode-shared.js.map +1 -0
  153. package/dist/orchestrator-prompt.d.ts +19 -0
  154. package/dist/orchestrator-prompt.d.ts.map +1 -0
  155. package/dist/orchestrator-prompt.js +130 -0
  156. package/dist/orchestrator-prompt.js.map +1 -0
  157. package/dist/orchestrator-session-strategy.d.ts +5 -0
  158. package/dist/orchestrator-session-strategy.d.ts.map +1 -0
  159. package/dist/orchestrator-session-strategy.js +13 -0
  160. package/dist/orchestrator-session-strategy.js.map +1 -0
  161. package/dist/paths.d.ts +145 -0
  162. package/dist/paths.d.ts.map +1 -0
  163. package/dist/paths.js +288 -0
  164. package/dist/paths.js.map +1 -0
  165. package/dist/platform.d.ts +32 -0
  166. package/dist/platform.d.ts.map +1 -0
  167. package/dist/platform.js +211 -0
  168. package/dist/platform.js.map +1 -0
  169. package/dist/plugin-registry.d.ts +15 -0
  170. package/dist/plugin-registry.d.ts.map +1 -0
  171. package/dist/plugin-registry.js +499 -0
  172. package/dist/plugin-registry.js.map +1 -0
  173. package/dist/portfolio-projects.d.ts +7 -0
  174. package/dist/portfolio-projects.d.ts.map +1 -0
  175. package/dist/portfolio-projects.js +65 -0
  176. package/dist/portfolio-projects.js.map +1 -0
  177. package/dist/portfolio-registry.d.ts +42 -0
  178. package/dist/portfolio-registry.d.ts.map +1 -0
  179. package/dist/portfolio-registry.js +311 -0
  180. package/dist/portfolio-registry.js.map +1 -0
  181. package/dist/portfolio-routing.d.ts +5 -0
  182. package/dist/portfolio-routing.d.ts.map +1 -0
  183. package/dist/portfolio-routing.js +24 -0
  184. package/dist/portfolio-routing.js.map +1 -0
  185. package/dist/portfolio-session-service.d.ts +15 -0
  186. package/dist/portfolio-session-service.d.ts.map +1 -0
  187. package/dist/portfolio-session-service.js +206 -0
  188. package/dist/portfolio-session-service.js.map +1 -0
  189. package/dist/process-cache.d.ts +32 -0
  190. package/dist/process-cache.d.ts.map +1 -0
  191. package/dist/process-cache.js +44 -0
  192. package/dist/process-cache.js.map +1 -0
  193. package/dist/project-resolver.d.ts +5 -0
  194. package/dist/project-resolver.d.ts.map +1 -0
  195. package/dist/project-resolver.js +20 -0
  196. package/dist/project-resolver.js.map +1 -0
  197. package/dist/prompt-builder.d.ts +42 -0
  198. package/dist/prompt-builder.d.ts.map +1 -0
  199. package/dist/prompt-builder.js +182 -0
  200. package/dist/prompt-builder.js.map +1 -0
  201. package/dist/prompts/orchestrator.md.js +4 -0
  202. package/dist/prompts/orchestrator.md.js.map +1 -0
  203. package/dist/query-activity-events.d.ts +42 -0
  204. package/dist/query-activity-events.d.ts.map +1 -0
  205. package/dist/query-activity-events.js +170 -0
  206. package/dist/query-activity-events.js.map +1 -0
  207. package/dist/recovery/actions.d.ts +7 -0
  208. package/dist/recovery/actions.d.ts.map +1 -0
  209. package/dist/recovery/index.d.ts +8 -0
  210. package/dist/recovery/index.d.ts.map +1 -0
  211. package/dist/recovery/logger.d.ts +12 -0
  212. package/dist/recovery/logger.d.ts.map +1 -0
  213. package/dist/recovery/manager.d.ts +24 -0
  214. package/dist/recovery/manager.d.ts.map +1 -0
  215. package/dist/recovery/scanner.d.ts +11 -0
  216. package/dist/recovery/scanner.d.ts.map +1 -0
  217. package/dist/recovery/types.d.ts +170 -0
  218. package/dist/recovery/types.d.ts.map +1 -0
  219. package/dist/recovery/validator.d.ts +8 -0
  220. package/dist/recovery/validator.d.ts.map +1 -0
  221. package/dist/report-watcher.d.ts +93 -0
  222. package/dist/report-watcher.d.ts.map +1 -0
  223. package/dist/report-watcher.js +182 -0
  224. package/dist/report-watcher.js.map +1 -0
  225. package/dist/scm-webhook-utils.d.ts +6 -0
  226. package/dist/scm-webhook-utils.d.ts.map +1 -0
  227. package/dist/scm-webhook-utils.js +36 -0
  228. package/dist/scm-webhook-utils.js.map +1 -0
  229. package/dist/session-manager.d.ts +22 -0
  230. package/dist/session-manager.d.ts.map +1 -0
  231. package/dist/session-manager.js +3077 -0
  232. package/dist/session-manager.js.map +1 -0
  233. package/dist/spawn-target.d.ts +23 -0
  234. package/dist/spawn-target.d.ts.map +1 -0
  235. package/dist/spawn-target.js +39 -0
  236. package/dist/spawn-target.js.map +1 -0
  237. package/dist/storage-key.d.ts +9 -0
  238. package/dist/storage-key.d.ts.map +1 -0
  239. package/dist/storage-key.js +59 -0
  240. package/dist/storage-key.js.map +1 -0
  241. package/dist/tmux.d.ts +39 -0
  242. package/dist/tmux.d.ts.map +1 -0
  243. package/dist/tmux.js +141 -0
  244. package/dist/tmux.js.map +1 -0
  245. package/dist/types.d.ts +1496 -0
  246. package/dist/types.d.ts.map +1 -0
  247. package/dist/types.js +215 -0
  248. package/dist/types.js.map +1 -0
  249. package/dist/update-cache.d.ts +59 -0
  250. package/dist/update-cache.d.ts.map +1 -0
  251. package/dist/update-cache.js +77 -0
  252. package/dist/update-cache.js.map +1 -0
  253. package/dist/utils/metadata-flatten.d.ts +3 -0
  254. package/dist/utils/metadata-flatten.d.ts.map +1 -0
  255. package/dist/utils/metadata-flatten.js +18 -0
  256. package/dist/utils/metadata-flatten.js.map +1 -0
  257. package/dist/utils/pr.d.ts +7 -0
  258. package/dist/utils/pr.d.ts.map +1 -0
  259. package/dist/utils/pr.js +97 -0
  260. package/dist/utils/pr.js.map +1 -0
  261. package/dist/utils/session-from-metadata.d.ts +16 -0
  262. package/dist/utils/session-from-metadata.d.ts.map +1 -0
  263. package/dist/utils/session-from-metadata.js +87 -0
  264. package/dist/utils/session-from-metadata.js.map +1 -0
  265. package/dist/utils/session-id.d.ts +4 -0
  266. package/dist/utils/session-id.d.ts.map +1 -0
  267. package/dist/utils/session-id.js +9 -0
  268. package/dist/utils/session-id.js.map +1 -0
  269. package/dist/utils/validation.d.ts +9 -0
  270. package/dist/utils/validation.d.ts.map +1 -0
  271. package/dist/utils/validation.js +45 -0
  272. package/dist/utils/validation.js.map +1 -0
  273. package/dist/utils.d.ts +65 -0
  274. package/dist/utils.d.ts.map +1 -0
  275. package/dist/utils.js +189 -0
  276. package/dist/utils.js.map +1 -0
  277. package/dist/version-compare.d.ts +27 -0
  278. package/dist/version-compare.d.ts.map +1 -0
  279. package/dist/version-compare.js +121 -0
  280. package/dist/version-compare.js.map +1 -0
  281. package/dist/windows-pty-registry.d.ts +27 -0
  282. package/dist/windows-pty-registry.d.ts.map +1 -0
  283. package/dist/windows-pty-registry.js +109 -0
  284. package/dist/windows-pty-registry.js.map +1 -0
  285. package/package.json +110 -0
@@ -0,0 +1,719 @@
1
+ import { mkdirSync, existsSync, rmSync, readFileSync, openSync, writeFileSync, closeSync, unlinkSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { setTimeout as setTimeout$1 } from 'node:timers/promises';
4
+ import { promisify } from 'node:util';
5
+ import { createCodeReviewStore } from './code-review-store.js';
6
+ import { getProjectCodeReviewsDir } from './paths.js';
7
+ import { SessionNotFoundError, isOrchestratorSession } from './types.js';
8
+ import { getShell, isWindows, killProcessTree } from './platform.js';
9
+
10
+ const REVIEW_COMMAND_TIMEOUT_MS = 10 * 60_000;
11
+ const REVIEW_COMMAND_MAX_BUFFER = 8 * 1024 * 1024;
12
+ const REVIEW_RUN_CREATION_LOCK_FILE = ".create-run.lock";
13
+ const REVIEW_RUN_EXECUTION_LOCK_PREFIX = ".execute-run-";
14
+ const REVIEW_RUN_CREATION_LOCK_WAIT_MS = 5_000;
15
+ const REVIEW_RUN_CREATION_LOCK_STALE_MS = 30_000;
16
+ const REVIEW_LOCK_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
17
+ async function execFileAsync(file, args, options = {}) {
18
+ const { execFile } = await import('node:child_process');
19
+ return promisify(execFile)(file, args, { windowsHide: true, ...options });
20
+ }
21
+ async function execFileWithClosedStdin(file, args, options = {}) {
22
+ const { spawn } = await import('node:child_process');
23
+ return new Promise((resolve, reject) => {
24
+ const child = spawn(file, args, {
25
+ cwd: options.cwd,
26
+ env: options.env,
27
+ shell: options.shell,
28
+ windowsHide: options.windowsHide ?? true,
29
+ detached: !isWindows(),
30
+ stdio: ["ignore", "pipe", "pipe"],
31
+ });
32
+ const maxBuffer = options.maxBuffer ?? REVIEW_COMMAND_MAX_BUFFER;
33
+ let stdout = "";
34
+ let stderr = "";
35
+ let settled = false;
36
+ const finish = (callback) => {
37
+ if (settled)
38
+ return;
39
+ settled = true;
40
+ if (timer)
41
+ clearTimeout(timer);
42
+ callback();
43
+ };
44
+ const terminateChild = () => {
45
+ if (child.pid !== undefined) {
46
+ return killProcessTree(child.pid).catch(() => undefined);
47
+ }
48
+ child.kill("SIGTERM");
49
+ return Promise.resolve();
50
+ };
51
+ const fail = (message, code, signal) => {
52
+ const error = new Error(message);
53
+ error.code = code;
54
+ error.signal = signal;
55
+ error.stdout = stdout;
56
+ error.stderr = stderr;
57
+ reject(error);
58
+ };
59
+ const timer = options.timeout && options.timeout > 0
60
+ ? setTimeout(() => {
61
+ void terminateChild().finally(() => {
62
+ finish(() => fail(`Command timed out after ${options.timeout}ms`, null, "SIGTERM"));
63
+ });
64
+ }, options.timeout)
65
+ : null;
66
+ const append = (kind, chunk) => {
67
+ const next = chunk.toString();
68
+ if (kind === "stdout")
69
+ stdout += next;
70
+ else
71
+ stderr += next;
72
+ if (Buffer.byteLength(stdout) + Buffer.byteLength(stderr) <= maxBuffer)
73
+ return;
74
+ void terminateChild();
75
+ finish(() => fail(`Command output exceeded maxBuffer ${maxBuffer}`));
76
+ };
77
+ child.stdout?.on("data", (chunk) => append("stdout", chunk));
78
+ child.stderr?.on("data", (chunk) => append("stderr", chunk));
79
+ child.once("error", (error) => finish(() => reject(error)));
80
+ child.once("close", (code, signal) => {
81
+ finish(() => {
82
+ if (code === 0) {
83
+ resolve({ stdout, stderr });
84
+ return;
85
+ }
86
+ fail(`Command failed with code ${code ?? signal ?? "unknown"}`, code, signal);
87
+ });
88
+ });
89
+ });
90
+ }
91
+ class CodeReviewRunNotFoundError extends Error {
92
+ constructor(runId) {
93
+ super(`Code review run not found: ${runId}`);
94
+ this.name = "CodeReviewRunNotFoundError";
95
+ }
96
+ }
97
+ class CodeReviewRunNotExecutableError extends Error {
98
+ runId;
99
+ reviewerSessionId;
100
+ status;
101
+ constructor(run) {
102
+ super(`Code review run ${run.reviewerSessionId} is ${run.status}, not queued`);
103
+ this.name = "CodeReviewRunNotExecutableError";
104
+ this.runId = run.id;
105
+ this.reviewerSessionId = run.reviewerSessionId;
106
+ this.status = run.status;
107
+ }
108
+ }
109
+ class CodeReviewInvalidSessionError extends Error {
110
+ constructor(message) {
111
+ super(message);
112
+ this.name = "CodeReviewInvalidSessionError";
113
+ }
114
+ }
115
+ class CodeReviewNoOpenFindingsError extends Error {
116
+ runId;
117
+ reviewerSessionId;
118
+ constructor(run) {
119
+ super(`No open review findings to send for ${run.reviewerSessionId}.`);
120
+ this.name = "CodeReviewNoOpenFindingsError";
121
+ this.runId = run.id;
122
+ this.reviewerSessionId = run.reviewerSessionId;
123
+ }
124
+ }
125
+ function escapeRegex(value) {
126
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
127
+ }
128
+ function parsePrNumber(url) {
129
+ if (!url)
130
+ return undefined;
131
+ const match = url.match(/\/pull\/(\d+)(?:\D*$|$)/);
132
+ if (!match)
133
+ return undefined;
134
+ const parsed = Number.parseInt(match[1], 10);
135
+ return Number.isNaN(parsed) ? undefined : parsed;
136
+ }
137
+ function truncate(value, maxLength) {
138
+ return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
139
+ }
140
+ function formatFindingLocation(finding) {
141
+ if (!finding.filePath)
142
+ return null;
143
+ if (finding.startLine === undefined)
144
+ return finding.filePath;
145
+ if (finding.endLine !== undefined && finding.endLine !== finding.startLine) {
146
+ return `${finding.filePath}:${finding.startLine}-${finding.endLine}`;
147
+ }
148
+ return `${finding.filePath}:${finding.startLine}`;
149
+ }
150
+ function formatFindingForAgent(finding, index) {
151
+ const lines = [`${index}. [${finding.severity}] ${finding.title}`];
152
+ const location = formatFindingLocation(finding);
153
+ if (location)
154
+ lines.push(` Location: ${location}`);
155
+ if (finding.confidence !== undefined)
156
+ lines.push(` Confidence: ${finding.confidence}`);
157
+ lines.push(" Details:");
158
+ lines.push(...finding.body
159
+ .split(/\r?\n/)
160
+ .map((line) => ` ${line}`)
161
+ .filter((line, lineIndex, allLines) => line.trim() || lineIndex < allLines.length - 1));
162
+ return lines.join("\n");
163
+ }
164
+ function parseFiniteNumber(value) {
165
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
166
+ }
167
+ function parseSeverity(value) {
168
+ switch (value) {
169
+ case "error":
170
+ case "warning":
171
+ case "info":
172
+ return value;
173
+ default:
174
+ return "warning";
175
+ }
176
+ }
177
+ function asRecord(value) {
178
+ return typeof value === "object" && value !== null && !Array.isArray(value)
179
+ ? value
180
+ : null;
181
+ }
182
+ function stripMarkdownJsonFence(value) {
183
+ const trimmed = value.trim();
184
+ const match = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
185
+ return match?.[1]?.trim() ?? trimmed;
186
+ }
187
+ function tryParseJsonCandidate(value) {
188
+ const candidates = [stripMarkdownJsonFence(value)];
189
+ const fenced = value.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
190
+ if (fenced?.[1])
191
+ candidates.push(fenced[1].trim());
192
+ for (const line of value
193
+ .split(/\r?\n/)
194
+ .map((entry) => entry.trim())
195
+ .filter(Boolean)
196
+ .reverse()) {
197
+ if (line.startsWith("{") || line.startsWith("[")) {
198
+ candidates.push(line);
199
+ }
200
+ }
201
+ for (const candidate of candidates) {
202
+ try {
203
+ return JSON.parse(candidate);
204
+ }
205
+ catch {
206
+ // Keep trying looser candidates.
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+ function normalizeFinding(value, fallbackIndex) {
212
+ const record = asRecord(value);
213
+ if (!record)
214
+ return null;
215
+ const body = typeof record["body"] === "string"
216
+ ? record["body"].trim()
217
+ : typeof record["message"] === "string"
218
+ ? record["message"].trim()
219
+ : "";
220
+ if (!body)
221
+ return null;
222
+ const title = typeof record["title"] === "string" && record["title"].trim()
223
+ ? record["title"].trim()
224
+ : `Review finding ${fallbackIndex}`;
225
+ const filePath = typeof record["filePath"] === "string"
226
+ ? record["filePath"]
227
+ : typeof record["path"] === "string"
228
+ ? record["path"]
229
+ : undefined;
230
+ return {
231
+ severity: parseSeverity(record["severity"]),
232
+ title: truncate(title, 160),
233
+ body: truncate(body, 12_000),
234
+ filePath,
235
+ startLine: parseFiniteNumber(record["startLine"] ?? record["line"]),
236
+ endLine: parseFiniteNumber(record["endLine"]),
237
+ category: typeof record["category"] === "string" ? record["category"] : undefined,
238
+ confidence: parseFiniteNumber(record["confidence"]),
239
+ fingerprint: typeof record["fingerprint"] === "string" ? record["fingerprint"] : undefined,
240
+ };
241
+ }
242
+ function parseReviewerOutput(output) {
243
+ const trimmed = output.trim();
244
+ if (!trimmed)
245
+ return [];
246
+ const parsed = tryParseJsonCandidate(trimmed);
247
+ const parsedRecord = asRecord(parsed);
248
+ const rawFindings = Array.isArray(parsed)
249
+ ? parsed
250
+ : Array.isArray(parsedRecord?.["findings"])
251
+ ? parsedRecord["findings"]
252
+ : null;
253
+ if (rawFindings) {
254
+ return rawFindings
255
+ .map((finding, index) => normalizeFinding(finding, index + 1))
256
+ .filter((finding) => finding !== null);
257
+ }
258
+ if (/^no findings?\.?$/i.test(trimmed))
259
+ return [];
260
+ return [
261
+ {
262
+ severity: "warning",
263
+ title: "Reviewer output",
264
+ body: truncate(trimmed, 12_000),
265
+ },
266
+ ];
267
+ }
268
+ function allocateReviewerSessionId(existingRuns, sessionPrefix) {
269
+ let max = 0;
270
+ const pattern = new RegExp(`^${escapeRegex(sessionPrefix)}-rev-(\\d+)$`);
271
+ for (const run of existingRuns) {
272
+ const match = run.reviewerSessionId.match(pattern);
273
+ if (!match)
274
+ continue;
275
+ const parsed = Number.parseInt(match[1], 10);
276
+ if (!Number.isNaN(parsed) && parsed > max) {
277
+ max = parsed;
278
+ }
279
+ }
280
+ return `${sessionPrefix}-rev-${max + 1}`;
281
+ }
282
+ function isFsErrorWithCode(error, code) {
283
+ return (typeof error === "object" &&
284
+ error !== null &&
285
+ "code" in error &&
286
+ error.code === code);
287
+ }
288
+ function assertSafeReviewLockId(id, label) {
289
+ if (!id || id === "." || id === ".." || !REVIEW_LOCK_ID_PATTERN.test(id)) {
290
+ throw new Error(`Unsafe ${label}: "${id}"`);
291
+ }
292
+ }
293
+ async function acquireCodeReviewStoreLock({ store, lockFileName, label, }) {
294
+ mkdirSync(store.storeDir, { recursive: true });
295
+ const lockPath = join(store.storeDir, lockFileName);
296
+ const startedAt = Date.now();
297
+ while (true) {
298
+ try {
299
+ const fd = openSync(lockPath, "wx");
300
+ writeFileSync(fd, `${process.pid}\n${new Date().toISOString()}\n`);
301
+ let released = false;
302
+ return () => {
303
+ if (released)
304
+ return;
305
+ released = true;
306
+ try {
307
+ closeSync(fd);
308
+ }
309
+ catch {
310
+ // The descriptor may already be closed if process cleanup raced with release.
311
+ }
312
+ try {
313
+ unlinkSync(lockPath);
314
+ }
315
+ catch {
316
+ // Another process may already have cleaned up a stale lock.
317
+ }
318
+ };
319
+ }
320
+ catch (error) {
321
+ if (!isFsErrorWithCode(error, "EEXIST")) {
322
+ throw error;
323
+ }
324
+ try {
325
+ const lockAgeMs = Date.now() - statSync(lockPath).mtimeMs;
326
+ if (lockAgeMs > REVIEW_RUN_CREATION_LOCK_STALE_MS) {
327
+ unlinkSync(lockPath);
328
+ continue;
329
+ }
330
+ }
331
+ catch (staleError) {
332
+ if (isFsErrorWithCode(staleError, "ENOENT")) {
333
+ continue;
334
+ }
335
+ }
336
+ if (Date.now() - startedAt > REVIEW_RUN_CREATION_LOCK_WAIT_MS) {
337
+ throw new Error(`Timed out waiting for ${label} lock`, { cause: error });
338
+ }
339
+ await setTimeout$1(25);
340
+ }
341
+ }
342
+ }
343
+ async function withReviewRunCreationLock(store, callback) {
344
+ const release = await acquireCodeReviewStoreLock({
345
+ store,
346
+ lockFileName: REVIEW_RUN_CREATION_LOCK_FILE,
347
+ label: "code review run creation",
348
+ });
349
+ try {
350
+ return await callback();
351
+ }
352
+ finally {
353
+ release();
354
+ }
355
+ }
356
+ async function withReviewRunExecutionLock(store, runId, callback) {
357
+ assertSafeReviewLockId(runId, "review run id");
358
+ const release = await acquireCodeReviewStoreLock({
359
+ store,
360
+ lockFileName: `${REVIEW_RUN_EXECUTION_LOCK_PREFIX}${runId}.lock`,
361
+ label: `code review run ${runId} execution`,
362
+ });
363
+ try {
364
+ return await callback();
365
+ }
366
+ finally {
367
+ release();
368
+ }
369
+ }
370
+ const SUPERSEDABLE_RUN_STATUSES = new Set([
371
+ "queued",
372
+ "needs_triage",
373
+ "sent_to_agent",
374
+ "waiting_update",
375
+ "clean",
376
+ ]);
377
+ function markSupersededReviewRuns({ store, existingRuns, linkedSessionId, targetSha, now, }) {
378
+ if (!targetSha)
379
+ return 0;
380
+ let updatedCount = 0;
381
+ for (const run of existingRuns) {
382
+ if (run.linkedSessionId !== linkedSessionId)
383
+ continue;
384
+ if (!run.targetSha || run.targetSha === targetSha)
385
+ continue;
386
+ if (!SUPERSEDABLE_RUN_STATUSES.has(run.status))
387
+ continue;
388
+ store.updateRun(run.id, { status: "outdated" }, now);
389
+ updatedCount++;
390
+ }
391
+ return updatedCount;
392
+ }
393
+ async function resolveGitHeadSha(session) {
394
+ const cwd = session.workspacePath;
395
+ if (!cwd)
396
+ return undefined;
397
+ try {
398
+ const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], {
399
+ cwd,
400
+ timeout: 5_000,
401
+ });
402
+ const sha = stdout.trim();
403
+ return sha.length > 0 ? sha : undefined;
404
+ }
405
+ catch {
406
+ return undefined;
407
+ }
408
+ }
409
+ async function git(cwd, args, timeout = 30_000) {
410
+ const { stdout } = await execFileAsync("git", args, { cwd, timeout });
411
+ return stdout.trim();
412
+ }
413
+ async function resolveWorkspaceHead(workspacePath) {
414
+ if (!workspacePath)
415
+ return undefined;
416
+ try {
417
+ return await git(workspacePath, ["rev-parse", "HEAD"], 5_000);
418
+ }
419
+ catch {
420
+ return undefined;
421
+ }
422
+ }
423
+ async function removeReviewerWorktree(repoPath, workspacePath) {
424
+ if (!existsSync(workspacePath)) {
425
+ try {
426
+ await git(repoPath, ["worktree", "prune"]);
427
+ }
428
+ catch {
429
+ // Best-effort cleanup of stale git metadata before adding the worktree again.
430
+ }
431
+ return;
432
+ }
433
+ try {
434
+ await git(repoPath, ["worktree", "remove", "--force", workspacePath]);
435
+ return;
436
+ }
437
+ catch {
438
+ try {
439
+ await git(repoPath, ["worktree", "prune"]);
440
+ }
441
+ catch {
442
+ // Best-effort before falling back to directory removal.
443
+ }
444
+ rmSync(workspacePath, { recursive: true, force: true });
445
+ }
446
+ }
447
+ async function markOutdatedCodeReviewRunsForSession({ store, session, resolveTargetSha = resolveGitHeadSha, now = new Date(), }) {
448
+ const targetSha = await resolveTargetSha(session);
449
+ return markSupersededReviewRuns({
450
+ store,
451
+ existingRuns: store.listRuns({ linkedSessionId: session.id }),
452
+ linkedSessionId: session.id,
453
+ targetSha,
454
+ now,
455
+ });
456
+ }
457
+ async function prepareGitReviewerWorkspace({ projectId, project, session, run, }) {
458
+ const workspaceRoot = join(getProjectCodeReviewsDir(projectId), "workspaces");
459
+ const workspacePath = join(workspaceRoot, run.reviewerSessionId);
460
+ mkdirSync(workspaceRoot, { recursive: true });
461
+ await removeReviewerWorktree(project.path, workspacePath);
462
+ const ref = run.targetSha ?? (await resolveWorkspaceHead(session.workspacePath)) ?? "HEAD";
463
+ await git(project.path, ["worktree", "add", "--detach", workspacePath, ref], 60_000);
464
+ return workspacePath;
465
+ }
466
+ function buildDefaultReviewPrompt(context) {
467
+ return [
468
+ "You are an AO reviewer agent. Review this repository snapshot for concrete bugs only.",
469
+ "Do not modify files. Do not publish comments anywhere.",
470
+ `Review the changes against base ref "${context.baseRef}". Start with: git diff --merge-base ${context.baseRef} HEAD -- .`,
471
+ "If that diff command fails, inspect git status/log and compare this detached reviewer workspace to the base ref using read-only commands.",
472
+ `Linked coding worker: ${context.session.id}`,
473
+ `Reviewer run: ${context.run.reviewerSessionId}`,
474
+ `Base ref: ${context.baseRef}`,
475
+ "Return only JSON using this schema:",
476
+ '{"findings":[{"severity":"warning|error|info","title":"short title","body":"specific issue and fix","filePath":"optional/path","startLine":1,"endLine":1,"confidence":0.8}]}',
477
+ 'If there are no concrete bugs, return {"findings":[]}.',
478
+ ].join("\n");
479
+ }
480
+ async function readOutputFile(path) {
481
+ if (!existsSync(path))
482
+ return null;
483
+ try {
484
+ return readFileSync(path, "utf-8");
485
+ }
486
+ catch {
487
+ return null;
488
+ }
489
+ }
490
+ function createShellCodeReviewRunner(command) {
491
+ return async (context) => {
492
+ const shell = getShell();
493
+ const { stdout, stderr } = await execFileAsync(shell.cmd, shell.args(command), {
494
+ cwd: context.workspacePath,
495
+ timeout: REVIEW_COMMAND_TIMEOUT_MS,
496
+ maxBuffer: REVIEW_COMMAND_MAX_BUFFER,
497
+ env: process.env,
498
+ });
499
+ return { rawOutput: stdout.trim() || stderr.trim() };
500
+ };
501
+ }
502
+ function buildCodexCodeReviewArgs(outputFile, prompt) {
503
+ return ["exec", "--sandbox", "read-only", "--output-last-message", outputFile, prompt];
504
+ }
505
+ async function runCodexCodeReview(context) {
506
+ const outputFile = join(context.workspacePath, ".ao-code-review-output.json");
507
+ const prompt = buildDefaultReviewPrompt(context);
508
+ const args = buildCodexCodeReviewArgs(outputFile, prompt);
509
+ try {
510
+ const { stdout, stderr } = await execFileWithClosedStdin("codex", args, {
511
+ cwd: context.workspacePath,
512
+ timeout: REVIEW_COMMAND_TIMEOUT_MS,
513
+ maxBuffer: REVIEW_COMMAND_MAX_BUFFER,
514
+ env: process.env,
515
+ shell: isWindows(),
516
+ });
517
+ const outputFileContents = await readOutputFile(outputFile);
518
+ const rawOutput = outputFileContents ?? (stdout.trim() || stderr.trim());
519
+ return { rawOutput };
520
+ }
521
+ catch (error) {
522
+ const details = error instanceof Error && "stderr" in error && typeof error.stderr === "string"
523
+ ? error.stderr.trim()
524
+ : error instanceof Error
525
+ ? error.message
526
+ : String(error);
527
+ throw new Error(`Codex review failed: ${details}`, { cause: error });
528
+ }
529
+ }
530
+ function defaultReviewSummary(session, source) {
531
+ const sourceLabel = source === "cli" ? "CLI" : source === "web" ? "dashboard" : "automation";
532
+ return `Review requested from ${sourceLabel} for ${session.id}.`;
533
+ }
534
+ function formatCodeReviewFindingsForAgent({ run, findings, session, }) {
535
+ const prLabel = run.prNumber
536
+ ? `PR #${run.prNumber}${run.prUrl ? ` (${run.prUrl})` : ""}`
537
+ : run.prUrl
538
+ ? `PR ${run.prUrl}`
539
+ : "the current PR";
540
+ const targetLabel = run.targetSha ? `\nTarget SHA reviewed: ${run.targetSha}` : "";
541
+ return [
542
+ `AO reviewer ${run.reviewerSessionId} found ${findings.length} open issue${findings.length === 1 ? "" : "s"} for ${prLabel}.`,
543
+ `Linked coding worker: ${session.id}`,
544
+ `Review run: ${run.id}${targetLabel}`,
545
+ "",
546
+ "Please address each finding below. Verify each issue against the current source before editing, then update the PR branch and push your fixes.",
547
+ "When you start working on these, report `athene report addressing-reviews`. When the fixes are ready for another review, report `athene report ready-for-review`.",
548
+ "",
549
+ "Findings:",
550
+ findings.map((finding, index) => formatFindingForAgent(finding, index + 1)).join("\n\n"),
551
+ ].join("\n");
552
+ }
553
+ async function triggerCodeReviewForSession({ config, sessionManager, storeFactory = createCodeReviewStore, resolveTargetSha = resolveGitHeadSha, now = new Date(), }, input) {
554
+ const session = await sessionManager.get(input.sessionId);
555
+ if (!session) {
556
+ throw new SessionNotFoundError(input.sessionId);
557
+ }
558
+ const project = config.projects[session.projectId];
559
+ if (!project) {
560
+ throw new CodeReviewInvalidSessionError(`Unknown project for session ${session.id}: ${session.projectId}`);
561
+ }
562
+ const sessionPrefix = project.sessionPrefix ?? session.projectId;
563
+ const allSessionPrefixes = Object.entries(config.projects).map(([projectId, projectConfig]) => projectConfig.sessionPrefix ?? projectId);
564
+ if (isOrchestratorSession(session, sessionPrefix, allSessionPrefixes)) {
565
+ throw new CodeReviewInvalidSessionError(`Cannot request code review for orchestrator session: ${session.id}`);
566
+ }
567
+ const store = storeFactory(session.projectId);
568
+ const prUrl = session.pr?.url ?? session.metadata["pr"];
569
+ const prNumber = session.pr?.number ?? parsePrNumber(prUrl);
570
+ const targetSha = await resolveTargetSha(session);
571
+ const requestedBy = input.requestedBy ?? "system";
572
+ return withReviewRunCreationLock(store, () => {
573
+ const existingRuns = store.listRuns();
574
+ const reviewerSessionId = allocateReviewerSessionId(existingRuns, sessionPrefix);
575
+ markSupersededReviewRuns({
576
+ store,
577
+ existingRuns,
578
+ linkedSessionId: session.id,
579
+ targetSha,
580
+ now,
581
+ });
582
+ const run = store.createRun({
583
+ linkedSessionId: session.id,
584
+ reviewerSessionId,
585
+ status: input.status ?? "queued",
586
+ targetSha,
587
+ prNumber,
588
+ prUrl,
589
+ summary: input.summary ?? defaultReviewSummary(session, requestedBy),
590
+ }, now);
591
+ return {
592
+ ...run,
593
+ findingCount: 0,
594
+ openFindingCount: 0,
595
+ dismissedFindingCount: 0,
596
+ sentFindingCount: 0,
597
+ resolvedFindingCount: 0,
598
+ };
599
+ });
600
+ }
601
+ function summarizeRun(store, runId) {
602
+ const run = store.listRunSummaries().find((entry) => entry.id === runId);
603
+ if (!run) {
604
+ throw new CodeReviewRunNotFoundError(runId);
605
+ }
606
+ return run;
607
+ }
608
+ function getExecutableRun(store, runId, force) {
609
+ const run = store.getRun(runId);
610
+ if (!run) {
611
+ throw new CodeReviewRunNotFoundError(runId);
612
+ }
613
+ if (run.status === "preparing" || run.status === "running") {
614
+ throw new CodeReviewRunNotExecutableError(run);
615
+ }
616
+ if (!force && !["queued", "failed"].includes(run.status)) {
617
+ throw new CodeReviewRunNotExecutableError(run);
618
+ }
619
+ return run;
620
+ }
621
+ async function executeCodeReviewRun({ config, sessionManager, storeFactory = createCodeReviewStore, prepareWorkspace = prepareGitReviewerWorkspace, runReviewer = runCodexCodeReview, now = () => new Date(), force = false, }, { projectId, runId }) {
622
+ const project = config.projects[projectId];
623
+ if (!project) {
624
+ throw new Error(`Unknown project: ${projectId}`);
625
+ }
626
+ const store = storeFactory(projectId);
627
+ const claimed = await withReviewRunExecutionLock(store, runId, async () => {
628
+ const executableRun = getExecutableRun(store, runId, force);
629
+ const session = await sessionManager.get(executableRun.linkedSessionId);
630
+ if (!session) {
631
+ throw new SessionNotFoundError(executableRun.linkedSessionId);
632
+ }
633
+ const startedAt = now();
634
+ const run = store.updateRun(executableRun.id, {
635
+ status: "preparing",
636
+ startedAt: executableRun.startedAt ?? startedAt.toISOString(),
637
+ completedAt: undefined,
638
+ terminationReason: undefined,
639
+ }, startedAt);
640
+ return { run, session };
641
+ });
642
+ let run = claimed.run;
643
+ const session = claimed.session;
644
+ try {
645
+ const workspacePath = await prepareWorkspace({ projectId, project, session, run });
646
+ run = store.updateRun(run.id, { status: "running", reviewerWorkspacePath: workspacePath }, now());
647
+ const baseRef = session.pr?.baseBranch?.trim() || project.defaultBranch;
648
+ const result = await runReviewer({ config, project, session, run, workspacePath, baseRef });
649
+ const findings = result.findings ?? parseReviewerOutput(result.rawOutput ?? "");
650
+ for (const finding of findings) {
651
+ store.createFinding({
652
+ runId: run.id,
653
+ linkedSessionId: run.linkedSessionId,
654
+ severity: finding.severity ?? "warning",
655
+ title: finding.title?.trim() || "Review finding",
656
+ body: finding.body?.trim() || "Reviewer reported an issue without details.",
657
+ filePath: finding.filePath,
658
+ startLine: finding.startLine,
659
+ endLine: finding.endLine,
660
+ category: finding.category,
661
+ confidence: finding.confidence,
662
+ fingerprint: finding.fingerprint,
663
+ }, now());
664
+ }
665
+ const completedAt = now();
666
+ store.updateRun(run.id, {
667
+ status: findings.length > 0 ? "needs_triage" : "clean",
668
+ completedAt: completedAt.toISOString(),
669
+ summary: result.summary ?? run.summary,
670
+ terminationReason: undefined,
671
+ }, completedAt);
672
+ }
673
+ catch (error) {
674
+ const completedAt = now();
675
+ store.updateRun(run.id, {
676
+ status: "failed",
677
+ completedAt: completedAt.toISOString(),
678
+ terminationReason: error instanceof Error ? error.message : String(error),
679
+ }, completedAt);
680
+ }
681
+ return summarizeRun(store, run.id);
682
+ }
683
+ async function sendCodeReviewFindingsToAgent({ config, sessionManager, storeFactory = createCodeReviewStore, now = () => new Date(), }, { projectId, runId }) {
684
+ const project = config.projects[projectId];
685
+ if (!project) {
686
+ throw new Error(`Unknown project: ${projectId}`);
687
+ }
688
+ const store = storeFactory(projectId);
689
+ const run = store.getRun(runId);
690
+ if (!run) {
691
+ throw new CodeReviewRunNotFoundError(runId);
692
+ }
693
+ const session = await sessionManager.get(run.linkedSessionId);
694
+ if (!session) {
695
+ throw new SessionNotFoundError(run.linkedSessionId);
696
+ }
697
+ const findings = store.listFindings({ runId: run.id, status: "open" });
698
+ if (findings.length === 0) {
699
+ throw new CodeReviewNoOpenFindingsError(run);
700
+ }
701
+ const message = formatCodeReviewFindingsForAgent({ run, findings, session });
702
+ await sessionManager.send(session.id, message);
703
+ const sentAt = now();
704
+ for (const finding of findings) {
705
+ store.updateFinding(finding.id, {
706
+ status: "sent_to_agent",
707
+ sentToAgentAt: sentAt.toISOString(),
708
+ }, sentAt);
709
+ }
710
+ store.updateRun(run.id, { status: "waiting_update" }, sentAt);
711
+ return {
712
+ run: summarizeRun(store, run.id),
713
+ sentFindingCount: findings.length,
714
+ message,
715
+ };
716
+ }
717
+
718
+ export { CodeReviewInvalidSessionError, CodeReviewNoOpenFindingsError, CodeReviewRunNotExecutableError, CodeReviewRunNotFoundError, buildCodexCodeReviewArgs, createShellCodeReviewRunner, executeCodeReviewRun, formatCodeReviewFindingsForAgent, markOutdatedCodeReviewRunsForSession, parseReviewerOutput, prepareGitReviewerWorkspace, runCodexCodeReview, sendCodeReviewFindingsToAgent, triggerCodeReviewForSession };
719
+ //# sourceMappingURL=code-review-manager.js.map