@evref-bl/dev-nexus 0.1.0-alpha.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 (100) hide show
  1. package/README.md +677 -0
  2. package/dist/browserOpener.d.ts +9 -0
  3. package/dist/browserOpener.js +47 -0
  4. package/dist/cli.d.ts +18 -0
  5. package/dist/cli.js +2374 -0
  6. package/dist/gitWorktreeService.d.ts +57 -0
  7. package/dist/gitWorktreeService.js +157 -0
  8. package/dist/index.d.ts +47 -0
  9. package/dist/index.js +47 -0
  10. package/dist/nexusAgentMcpConfig.d.ts +30 -0
  11. package/dist/nexusAgentMcpConfig.js +228 -0
  12. package/dist/nexusAutomation.d.ts +103 -0
  13. package/dist/nexusAutomation.js +390 -0
  14. package/dist/nexusAutomationAgentLaunch.d.ts +148 -0
  15. package/dist/nexusAutomationAgentLaunch.js +855 -0
  16. package/dist/nexusAutomationAgentProfile.d.ts +39 -0
  17. package/dist/nexusAutomationAgentProfile.js +103 -0
  18. package/dist/nexusAutomationAgentSurface.d.ts +62 -0
  19. package/dist/nexusAutomationAgentSurface.js +90 -0
  20. package/dist/nexusAutomationCommandExecutor.d.ts +29 -0
  21. package/dist/nexusAutomationCommandExecutor.js +251 -0
  22. package/dist/nexusAutomationConfig.d.ts +114 -0
  23. package/dist/nexusAutomationConfig.js +547 -0
  24. package/dist/nexusAutomationEnqueue.d.ts +37 -0
  25. package/dist/nexusAutomationEnqueue.js +128 -0
  26. package/dist/nexusAutomationRunOnce.d.ts +91 -0
  27. package/dist/nexusAutomationRunOnce.js +586 -0
  28. package/dist/nexusAutomationScheduler.d.ts +50 -0
  29. package/dist/nexusAutomationScheduler.js +196 -0
  30. package/dist/nexusAutomationStatus.d.ts +55 -0
  31. package/dist/nexusAutomationStatus.js +462 -0
  32. package/dist/nexusAutomationTarget.d.ts +19 -0
  33. package/dist/nexusAutomationTarget.js +33 -0
  34. package/dist/nexusAutomationTargetCycle.d.ts +90 -0
  35. package/dist/nexusAutomationTargetCycle.js +282 -0
  36. package/dist/nexusAutomationTargetReport.d.ts +136 -0
  37. package/dist/nexusAutomationTargetReport.js +504 -0
  38. package/dist/nexusAutomationWorktreeSetup.d.ts +89 -0
  39. package/dist/nexusAutomationWorktreeSetup.js +661 -0
  40. package/dist/nexusCoordination.d.ts +198 -0
  41. package/dist/nexusCoordination.js +1018 -0
  42. package/dist/nexusExtension.d.ts +31 -0
  43. package/dist/nexusExtension.js +1 -0
  44. package/dist/nexusHomeConfig.d.ts +38 -0
  45. package/dist/nexusHomeConfig.js +133 -0
  46. package/dist/nexusMcpServer.d.ts +31 -0
  47. package/dist/nexusMcpServer.js +1036 -0
  48. package/dist/nexusPluginCapabilities.d.ts +197 -0
  49. package/dist/nexusPluginCapabilities.js +201 -0
  50. package/dist/nexusProjectConfig.d.ts +95 -0
  51. package/dist/nexusProjectConfig.js +880 -0
  52. package/dist/nexusProjectHomeService.d.ts +121 -0
  53. package/dist/nexusProjectHomeService.js +171 -0
  54. package/dist/nexusProjectLifecycle.d.ts +62 -0
  55. package/dist/nexusProjectLifecycle.js +205 -0
  56. package/dist/nexusProjectOperations.d.ts +101 -0
  57. package/dist/nexusProjectOperations.js +296 -0
  58. package/dist/nexusProjectRegistry.d.ts +42 -0
  59. package/dist/nexusProjectRegistry.js +91 -0
  60. package/dist/nexusProjectScaffold.d.ts +25 -0
  61. package/dist/nexusProjectScaffold.js +61 -0
  62. package/dist/nexusProjectTemplate.d.ts +34 -0
  63. package/dist/nexusProjectTemplate.js +354 -0
  64. package/dist/nexusSkills.d.ts +134 -0
  65. package/dist/nexusSkills.js +647 -0
  66. package/dist/nexusWorkerContextBundle.d.ts +142 -0
  67. package/dist/nexusWorkerContextBundle.js +375 -0
  68. package/dist/processSupervisor.d.ts +89 -0
  69. package/dist/processSupervisor.js +440 -0
  70. package/dist/vibeKanbanApi.d.ts +11 -0
  71. package/dist/vibeKanbanApi.js +14 -0
  72. package/dist/vibeKanbanAuth.d.ts +25 -0
  73. package/dist/vibeKanbanAuth.js +101 -0
  74. package/dist/vibeKanbanBoardAdapter.d.ts +36 -0
  75. package/dist/vibeKanbanBoardAdapter.js +196 -0
  76. package/dist/vibeKanbanMcpConfig.d.ts +36 -0
  77. package/dist/vibeKanbanMcpConfig.js +191 -0
  78. package/dist/vibeKanbanProjectAdapter.d.ts +39 -0
  79. package/dist/vibeKanbanProjectAdapter.js +113 -0
  80. package/dist/vibeKanbanWorkspaceSetup.d.ts +1 -0
  81. package/dist/vibeKanbanWorkspaceSetup.js +96 -0
  82. package/dist/workItemService.d.ts +60 -0
  83. package/dist/workItemService.js +163 -0
  84. package/dist/workTrackingGitHubProvider.d.ts +71 -0
  85. package/dist/workTrackingGitHubProvider.js +663 -0
  86. package/dist/workTrackingGitLabProvider.d.ts +62 -0
  87. package/dist/workTrackingGitLabProvider.js +523 -0
  88. package/dist/workTrackingJiraProvider.d.ts +67 -0
  89. package/dist/workTrackingJiraProvider.js +652 -0
  90. package/dist/workTrackingLocalProvider.d.ts +49 -0
  91. package/dist/workTrackingLocalProvider.js +463 -0
  92. package/dist/workTrackingProviderService.d.ts +21 -0
  93. package/dist/workTrackingProviderService.js +117 -0
  94. package/dist/workTrackingTypes.d.ts +202 -0
  95. package/dist/workTrackingTypes.js +1 -0
  96. package/dist/workTrackingVibeProvider.d.ts +35 -0
  97. package/dist/workTrackingVibeProvider.js +119 -0
  98. package/dist/worktreeExecutionMetadata.d.ts +76 -0
  99. package/dist/worktreeExecutionMetadata.js +239 -0
  100. package/package.json +37 -0
@@ -0,0 +1,586 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { prepareGitWorktree, safeDirectoryName, } from "./gitWorktreeService.js";
4
+ import { acquireNexusAutomationRunLock, appendNexusAutomationRunRecord, buildNexusAutomationWorkItemQuery, evaluateNexusAutomationLedgerBackoff, readNexusAutomationRunLedger, releaseNexusAutomationRunLock, selectNexusAutomationWorkItem, } from "./nexusAutomation.js";
5
+ import { materializeNexusAutomationWorktreeSetup, preflightNexusAutomationWorktreeSetup, } from "./nexusAutomationWorktreeSetup.js";
6
+ import { loadProjectConfig, } from "./nexusProjectConfig.js";
7
+ import { projectPluginDependencyProjections, projectPluginWorkerFragments, } from "./nexusPluginCapabilities.js";
8
+ import { resolvePrimaryProjectComponent, } from "./nexusProjectLifecycle.js";
9
+ import { applyWorktreeExecutionUpdate, emptyWorktreeExecutionMetadata, writeWorktreeExecutionMetadata, worktreeOwnershipMetadataFromPreparedWorktree, } from "./worktreeExecutionMetadata.js";
10
+ import { createWorkTrackerProvider, } from "./workTrackingProviderService.js";
11
+ export class NexusAutomationRunOnceError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "NexusAutomationRunOnceError";
15
+ }
16
+ }
17
+ export async function runNexusAutomationOnce(options) {
18
+ const projectRoot = path.resolve(requiredNonEmptyString(options.projectRoot, "projectRoot"));
19
+ const projectConfig = loadProjectConfig(projectRoot);
20
+ const automationConfig = projectConfig.automation ?? null;
21
+ const runId = options.runId ?? generateNexusAutomationRunId(options.now);
22
+ const comments = [];
23
+ let sourceRoot = null;
24
+ let lock = null;
25
+ let workItem = null;
26
+ let updatedWorkItem = null;
27
+ let worktree = null;
28
+ let setup = null;
29
+ let ledger = null;
30
+ let provider = null;
31
+ let primaryComponent = null;
32
+ let preflight = [];
33
+ let pluginDependencyProjections = [];
34
+ if (!automationConfig?.enabled) {
35
+ return {
36
+ runId,
37
+ projectRoot,
38
+ sourceRoot,
39
+ projectConfig,
40
+ automationConfig,
41
+ status: "skipped",
42
+ summary: "Automation is not enabled for this project",
43
+ ledger,
44
+ lock,
45
+ preflight: [],
46
+ workItem,
47
+ worktree,
48
+ setup,
49
+ execution: null,
50
+ comments,
51
+ updatedWorkItem,
52
+ };
53
+ }
54
+ const startedAt = currentIso(options.now);
55
+ lock = acquireNexusAutomationRunLock({
56
+ projectRoot,
57
+ config: automationConfig,
58
+ runId,
59
+ owner: options.owner ?? null,
60
+ now: startedAt,
61
+ });
62
+ try {
63
+ ledger = readNexusAutomationRunLedger(projectRoot, automationConfig);
64
+ const backoff = evaluateNexusAutomationLedgerBackoff(automationConfig, ledger, startedAt);
65
+ if (!backoff.shouldRun) {
66
+ const summary = backoff.reason ?? "Automation retry backoff is active";
67
+ ledger = appendNexusAutomationRunRecord({
68
+ projectRoot,
69
+ config: automationConfig,
70
+ now: startedAt,
71
+ record: {
72
+ id: runId,
73
+ projectId: projectConfig.id,
74
+ status: "skipped",
75
+ startedAt,
76
+ finishedAt: startedAt,
77
+ summary,
78
+ nextRunNotBefore: backoff.retryAfter,
79
+ },
80
+ });
81
+ return runResult({
82
+ runId,
83
+ projectRoot,
84
+ sourceRoot,
85
+ projectConfig,
86
+ automationConfig,
87
+ status: "skipped",
88
+ summary,
89
+ ledger,
90
+ lock,
91
+ preflight: [],
92
+ workItem,
93
+ worktree,
94
+ setup,
95
+ execution: null,
96
+ comments,
97
+ updatedWorkItem,
98
+ });
99
+ }
100
+ primaryComponent = resolvePrimaryProjectComponent(projectRoot, projectConfig);
101
+ sourceRoot = primaryComponent.sourceRoot;
102
+ if (!primaryComponent.workTracking) {
103
+ const summary = "Primary component work tracking is not configured";
104
+ preflight = [
105
+ check("workTracking", false, "Primary component has work tracking configured", summary),
106
+ ];
107
+ ledger = appendNexusAutomationRunRecord({
108
+ projectRoot,
109
+ config: automationConfig,
110
+ now: startedAt,
111
+ record: {
112
+ id: runId,
113
+ projectId: projectConfig.id,
114
+ componentId: primaryComponent.id,
115
+ status: "blocked",
116
+ startedAt,
117
+ finishedAt: startedAt,
118
+ sourceRoot,
119
+ summary,
120
+ error: summary,
121
+ },
122
+ });
123
+ return runResult({
124
+ runId,
125
+ projectRoot,
126
+ sourceRoot,
127
+ projectConfig,
128
+ automationConfig,
129
+ status: "blocked",
130
+ summary,
131
+ ledger,
132
+ lock,
133
+ preflight,
134
+ workItem,
135
+ worktree,
136
+ setup,
137
+ execution: null,
138
+ comments,
139
+ updatedWorkItem,
140
+ });
141
+ }
142
+ pluginDependencyProjections = automationPluginDependencyProjections(projectConfig, primaryComponent.id);
143
+ provider = createAutomationProvider({
144
+ options,
145
+ projectRoot,
146
+ sourceRoot,
147
+ projectConfig,
148
+ component: primaryComponent,
149
+ });
150
+ preflight = preflightNexusAutomationRunOnce({
151
+ projectRoot,
152
+ sourceRoot,
153
+ projectConfig,
154
+ component: primaryComponent,
155
+ automationConfig,
156
+ provider,
157
+ pluginDependencyProjections,
158
+ });
159
+ const failedChecks = preflight.filter((check) => check.status === "failed");
160
+ if (failedChecks.length > 0) {
161
+ const summary = failedChecks.map((check) => check.message).join("; ");
162
+ ledger = appendNexusAutomationRunRecord({
163
+ projectRoot,
164
+ config: automationConfig,
165
+ now: startedAt,
166
+ record: {
167
+ id: runId,
168
+ projectId: projectConfig.id,
169
+ componentId: primaryComponent.id,
170
+ status: "blocked",
171
+ startedAt,
172
+ finishedAt: startedAt,
173
+ sourceRoot,
174
+ summary,
175
+ error: summary,
176
+ },
177
+ });
178
+ return runResult({
179
+ runId,
180
+ projectRoot,
181
+ sourceRoot,
182
+ projectConfig,
183
+ automationConfig,
184
+ status: "blocked",
185
+ summary,
186
+ ledger,
187
+ lock,
188
+ preflight,
189
+ workItem,
190
+ worktree,
191
+ setup,
192
+ execution: null,
193
+ comments,
194
+ updatedWorkItem,
195
+ });
196
+ }
197
+ const candidates = await provider.listWorkItems({
198
+ ...buildNexusAutomationWorkItemQuery(automationConfig),
199
+ projectRoot,
200
+ });
201
+ workItem = selectNexusAutomationWorkItem(candidates, automationConfig) ?? null;
202
+ if (!workItem) {
203
+ const summary = "No eligible work item matched the automation selector";
204
+ const finishedAt = currentIso(options.now);
205
+ ledger = appendNexusAutomationRunRecord({
206
+ projectRoot,
207
+ config: automationConfig,
208
+ now: finishedAt,
209
+ record: {
210
+ id: runId,
211
+ projectId: projectConfig.id,
212
+ componentId: primaryComponent.id,
213
+ status: "skipped",
214
+ startedAt,
215
+ finishedAt,
216
+ sourceRoot,
217
+ summary,
218
+ },
219
+ });
220
+ return runResult({
221
+ runId,
222
+ projectRoot,
223
+ sourceRoot,
224
+ projectConfig,
225
+ automationConfig,
226
+ status: "skipped",
227
+ summary,
228
+ ledger,
229
+ lock,
230
+ preflight,
231
+ workItem,
232
+ worktree,
233
+ setup,
234
+ execution: null,
235
+ comments,
236
+ updatedWorkItem,
237
+ });
238
+ }
239
+ const baseRef = options.baseRef ??
240
+ primaryComponent.defaultBranch ??
241
+ projectConfig.repo.defaultBranch ??
242
+ null;
243
+ const branchName = options.branchName ?? defaultAutomationBranchName(projectConfig.id, workItem.id, runId);
244
+ worktree = prepareGitWorktree({
245
+ componentId: primaryComponent.id,
246
+ sourceRoot,
247
+ worktreesRoot: primaryComponent.worktreesRoot,
248
+ branchName,
249
+ ...(options.worktreeName ? { worktreeName: options.worktreeName } : {}),
250
+ ...(baseRef ? { baseRef } : {}),
251
+ workItemId: workItem.id,
252
+ workItemTitle: workItem.title,
253
+ ...(options.gitRunner ? { gitRunner: options.gitRunner } : {}),
254
+ });
255
+ setup = materializeNexusAutomationWorktreeSetup({
256
+ sourceRoot,
257
+ worktreesRoot: primaryComponent.worktreesRoot,
258
+ worktreePath: worktree.worktreePath,
259
+ automationConfig,
260
+ pluginDependencyProjections,
261
+ skillsConfig: projectConfig.skills,
262
+ context: {
263
+ project: {
264
+ id: projectConfig.id,
265
+ name: projectConfig.name,
266
+ root: projectRoot,
267
+ },
268
+ ownership: {
269
+ componentId: worktree.componentId,
270
+ sourceRoot: worktree.sourceRoot,
271
+ worktreesRoot: worktree.worktreesRoot,
272
+ worktreePath: worktree.worktreePath,
273
+ branchName: worktree.branchName,
274
+ baseRef: worktree.baseRef,
275
+ workItem: worktree.workItem,
276
+ },
277
+ pluginFragments: projectPluginWorkerFragments(projectConfig, {
278
+ componentId: primaryComponent.id,
279
+ }),
280
+ },
281
+ ...(options.gitRunner ? { gitRunner: options.gitRunner } : {}),
282
+ });
283
+ updatedWorkItem = await setTrackerStatus(provider, workItem, "in_progress");
284
+ comments.push(await provider.addComment(workItemRef(workItem), startedTrackerComment(runId, worktree)));
285
+ const executorResult = await options.executor({
286
+ runId,
287
+ startedAt,
288
+ projectRoot,
289
+ sourceRoot,
290
+ projectConfig,
291
+ automationConfig,
292
+ workItem,
293
+ worktree,
294
+ setup,
295
+ });
296
+ const finishedAt = currentIso(options.now);
297
+ const execution = executionMetadataFromExecutorResult(executorResult, finishedAt, worktree);
298
+ writeWorktreeExecutionMetadata(worktree.worktreePath, execution);
299
+ const status = normalizeExecutorStatus(executorResult.status);
300
+ if (status === "completed") {
301
+ updatedWorkItem = await setTrackerStatus(provider, workItem, "done");
302
+ }
303
+ else if (status === "blocked") {
304
+ updatedWorkItem = await setTrackerStatus(provider, workItem, "blocked");
305
+ }
306
+ const summary = executorResult.summary ?? defaultRunSummary(status);
307
+ comments.push(await provider.addComment(workItemRef(workItem), finishedTrackerComment(runId, status, summary)));
308
+ ledger = appendNexusAutomationRunRecord({
309
+ projectRoot,
310
+ config: automationConfig,
311
+ now: finishedAt,
312
+ record: {
313
+ id: runId,
314
+ projectId: projectConfig.id,
315
+ componentId: primaryComponent.id,
316
+ status,
317
+ startedAt,
318
+ finishedAt,
319
+ workItemId: workItem.id,
320
+ workItemTitle: workItem.title,
321
+ sourceRoot,
322
+ worktreePath: worktree.worktreePath,
323
+ branchName: worktree.branchName,
324
+ baseRef: worktree.baseRef,
325
+ commitIds: execution.commitIds,
326
+ summary,
327
+ verification: execution.verification,
328
+ publicationDecision: execution.publicationDecision,
329
+ error: executorResult.error ?? null,
330
+ },
331
+ });
332
+ return runResult({
333
+ runId,
334
+ projectRoot,
335
+ sourceRoot,
336
+ projectConfig,
337
+ automationConfig,
338
+ status,
339
+ summary,
340
+ ledger,
341
+ lock,
342
+ preflight,
343
+ workItem,
344
+ worktree,
345
+ setup,
346
+ execution,
347
+ comments,
348
+ updatedWorkItem,
349
+ });
350
+ }
351
+ catch (error) {
352
+ if (!automationConfig) {
353
+ throw error;
354
+ }
355
+ const finishedAt = currentIso(options.now);
356
+ const summary = errorMessage(error);
357
+ await tryAddFailureComment(provider ?? undefined, workItem, runId, summary);
358
+ ledger = appendNexusAutomationRunRecord({
359
+ projectRoot,
360
+ config: automationConfig,
361
+ now: finishedAt,
362
+ record: {
363
+ id: runId,
364
+ projectId: projectConfig.id,
365
+ componentId: primaryComponent?.id ?? null,
366
+ status: "failed",
367
+ startedAt,
368
+ finishedAt,
369
+ workItemId: workItem?.id ?? null,
370
+ workItemTitle: workItem?.title ?? null,
371
+ sourceRoot,
372
+ worktreePath: worktree?.worktreePath ?? null,
373
+ branchName: worktree?.branchName ?? null,
374
+ baseRef: worktree?.baseRef ?? null,
375
+ summary,
376
+ error: summary,
377
+ },
378
+ });
379
+ return runResult({
380
+ runId,
381
+ projectRoot,
382
+ sourceRoot,
383
+ projectConfig,
384
+ automationConfig,
385
+ status: "failed",
386
+ summary,
387
+ ledger,
388
+ lock,
389
+ preflight,
390
+ workItem,
391
+ worktree,
392
+ setup,
393
+ execution: null,
394
+ comments,
395
+ updatedWorkItem,
396
+ });
397
+ }
398
+ finally {
399
+ if (automationConfig) {
400
+ releaseNexusAutomationRunLock({
401
+ projectRoot,
402
+ config: automationConfig,
403
+ runId,
404
+ });
405
+ }
406
+ }
407
+ }
408
+ export function preflightNexusAutomationRunOnce(options) {
409
+ const worktreesRoot = options.component.worktreesRoot;
410
+ return [
411
+ check("workTracking", Boolean(options.component.workTracking), "Primary component has work tracking configured", "Primary component work tracking is not configured"),
412
+ check("trackerListItems", options.provider.capabilities.listItems, "Tracker can list work items", `Tracker provider ${options.provider.provider} cannot list work items`),
413
+ check("trackerUpdateItems", options.provider.capabilities.updateItem, "Tracker can update work items", `Tracker provider ${options.provider.provider} cannot update work items`),
414
+ check("trackerComments", options.provider.capabilities.comment, "Tracker can record run comments", `Tracker provider ${options.provider.provider} cannot record comments`),
415
+ check("gitRepository", options.component.kind === "git", "Primary component source is Git-backed", "Automation worktree preparation requires the primary component to be Git-backed"),
416
+ check("sourceRoot", fs.existsSync(options.sourceRoot) && fs.statSync(options.sourceRoot).isDirectory(), "Primary component source root exists", `Primary component source root does not exist: ${options.sourceRoot}`),
417
+ check("worktreesRoot", pathIsInside(options.projectRoot, worktreesRoot), "Project worktrees root stays inside the project root", `Automation worktreesRoot must resolve inside the project root: ${worktreesRoot}`),
418
+ ...preflightNexusAutomationWorktreeSetup({
419
+ sourceRoot: options.sourceRoot,
420
+ automationConfig: options.automationConfig,
421
+ pluginDependencyProjections: options.pluginDependencyProjections,
422
+ }),
423
+ ];
424
+ }
425
+ function automationPluginDependencyProjections(projectConfig, componentId) {
426
+ return projectPluginDependencyProjections(projectConfig, {
427
+ componentId,
428
+ }).map((projection) => ({
429
+ id: projection.id,
430
+ source: projection.source,
431
+ target: projection.target,
432
+ required: projection.required,
433
+ sourceControl: projection.sourceControl,
434
+ reason: projection.reason,
435
+ sourceMetadata: {
436
+ pluginId: projection.pluginSource.pluginId,
437
+ pluginName: projection.pluginSource.pluginName,
438
+ version: projection.pluginSource.version,
439
+ capabilityId: projection.pluginSource.capabilityId,
440
+ },
441
+ }));
442
+ }
443
+ export function generateNexusAutomationRunId(now) {
444
+ const timestamp = currentIso(now)
445
+ .replaceAll("-", "")
446
+ .replaceAll(":", "")
447
+ .replace(".", "-");
448
+ const suffix = Math.random().toString(36).slice(2, 8);
449
+ return `run-${timestamp}-${suffix}`;
450
+ }
451
+ function createAutomationProvider(options) {
452
+ const workTracking = options.component.workTracking;
453
+ if (!workTracking) {
454
+ throw new NexusAutomationRunOnceError("Primary component work tracking is not configured");
455
+ }
456
+ if (options.options.provider) {
457
+ return options.options.provider;
458
+ }
459
+ if (options.options.providerFactory) {
460
+ return options.options.providerFactory({
461
+ projectRoot: options.projectRoot,
462
+ sourceRoot: options.sourceRoot,
463
+ projectConfig: options.projectConfig,
464
+ component: options.component,
465
+ workTracking,
466
+ });
467
+ }
468
+ return createWorkTrackerProvider(workTracking, {
469
+ ...options.options.providerOptions,
470
+ projectRoot: options.projectRoot,
471
+ now: options.options.now,
472
+ });
473
+ }
474
+ function executionMetadataFromExecutorResult(result, finishedAt, worktree) {
475
+ let metadata = emptyWorktreeExecutionMetadata();
476
+ const verification = result.verification ?? [];
477
+ const initialUpdate = {
478
+ worktree: worktreeOwnershipMetadataFromPreparedWorktree(worktree),
479
+ ...(result.commitIds?.length ? { commitIds: result.commitIds } : {}),
480
+ ...(result.publicationDecision
481
+ ? { publicationDecision: result.publicationDecision }
482
+ : {}),
483
+ ...(verification[0] ? { verification: verification[0] } : {}),
484
+ };
485
+ if (hasExecutionUpdate(initialUpdate)) {
486
+ metadata = applyWorktreeExecutionUpdate(metadata, initialUpdate, finishedAt);
487
+ }
488
+ for (const verificationRecord of verification.slice(1)) {
489
+ metadata = applyWorktreeExecutionUpdate(metadata, { verification: verificationRecord }, finishedAt);
490
+ }
491
+ return metadata;
492
+ }
493
+ function hasExecutionUpdate(update) {
494
+ return Boolean(Object.prototype.hasOwnProperty.call(update, "worktree") ||
495
+ update.commitIds?.length ||
496
+ update.verification ||
497
+ update.publicationDecision);
498
+ }
499
+ async function setTrackerStatus(provider, item, status) {
500
+ if (provider.setStatus) {
501
+ return provider.setStatus(workItemRef(item), status);
502
+ }
503
+ return provider.updateWorkItem(workItemRef(item), { status });
504
+ }
505
+ async function tryAddFailureComment(provider, item, runId, summary) {
506
+ if (!provider || !item || !provider.capabilities.comment) {
507
+ return;
508
+ }
509
+ try {
510
+ await provider.addComment(workItemRef(item), finishedTrackerComment(runId, "failed", summary));
511
+ }
512
+ catch {
513
+ // Failure recording must not be masked by a best-effort tracker comment.
514
+ }
515
+ }
516
+ function workItemRef(item) {
517
+ return {
518
+ id: item.id,
519
+ provider: item.provider,
520
+ ...(item.externalRef ? { externalRef: item.externalRef } : {}),
521
+ };
522
+ }
523
+ function startedTrackerComment(runId, worktree) {
524
+ return [
525
+ `DevNexus automation run ${runId} started.`,
526
+ `Worktree: ${worktree.worktreePath}`,
527
+ `Branch: ${worktree.branchName}`,
528
+ ].join("\n");
529
+ }
530
+ function finishedTrackerComment(runId, status, summary) {
531
+ return [
532
+ `DevNexus automation run ${runId} ${status}.`,
533
+ `Summary: ${summary}`,
534
+ ].join("\n");
535
+ }
536
+ function normalizeExecutorStatus(status) {
537
+ return status ?? "completed";
538
+ }
539
+ function defaultRunSummary(status) {
540
+ if (status === "completed") {
541
+ return "Automation executor completed";
542
+ }
543
+ if (status === "blocked") {
544
+ return "Automation executor reported a blocker";
545
+ }
546
+ return "Automation executor failed";
547
+ }
548
+ function defaultAutomationBranchName(projectId, workItemId, runId) {
549
+ return [
550
+ "codex",
551
+ safeDirectoryName(projectId),
552
+ safeDirectoryName(workItemId),
553
+ safeDirectoryName(runId),
554
+ ].join("/");
555
+ }
556
+ function check(name, passed, passedMessage, failedMessage) {
557
+ return {
558
+ name,
559
+ status: passed ? "passed" : "failed",
560
+ message: passed ? passedMessage : failedMessage,
561
+ };
562
+ }
563
+ function pathIsInside(root, target) {
564
+ const relative = path.relative(path.resolve(root), path.resolve(target));
565
+ return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
566
+ }
567
+ function runResult(result) {
568
+ return result;
569
+ }
570
+ function currentIso(now) {
571
+ const value = now ? now() : new Date();
572
+ const date = value instanceof Date ? value : new Date(value);
573
+ if (Number.isNaN(date.getTime())) {
574
+ throw new NexusAutomationRunOnceError("now must return a valid date");
575
+ }
576
+ return date.toISOString();
577
+ }
578
+ function errorMessage(error) {
579
+ return error instanceof Error ? error.message : String(error);
580
+ }
581
+ function requiredNonEmptyString(value, name) {
582
+ if (typeof value !== "string" || value.trim().length === 0) {
583
+ throw new NexusAutomationRunOnceError(`${name} must be a non-empty string`);
584
+ }
585
+ return value.trim();
586
+ }
@@ -0,0 +1,50 @@
1
+ import type { GitRunner } from "./gitWorktreeService.js";
2
+ import { type NexusAutomationExecutor, type RunNexusAutomationOnceResult, type NexusAutomationWorkTrackerProviderFactory } from "./nexusAutomationRunOnce.js";
3
+ import { type NexusAutomationAgentLauncher, type RunNexusAutomationAgentLaunchOnceResult } from "./nexusAutomationAgentLaunch.js";
4
+ import { type NexusAutomationStatus } from "./nexusAutomationStatus.js";
5
+ import type { CreateWorkTrackerProviderOptions } from "./workTrackingProviderService.js";
6
+ import type { WorkTrackerProvider } from "./workTrackingTypes.js";
7
+ export type NexusAutomationSchedulerAction = "ran" | "waited" | "stopped";
8
+ export type NexusAutomationSchedulerStopReason = "disabled" | "max_runs" | "max_ticks" | "stopped";
9
+ export type NexusAutomationSchedulerRunResult = RunNexusAutomationOnceResult | RunNexusAutomationAgentLaunchOnceResult;
10
+ export interface NexusAutomationSchedulerTick {
11
+ index: number;
12
+ startedAt: string;
13
+ finishedAt: string;
14
+ status: NexusAutomationStatus;
15
+ action: NexusAutomationSchedulerAction;
16
+ waitMs: number | null;
17
+ run: NexusAutomationSchedulerRunResult | null;
18
+ }
19
+ export interface RunNexusAutomationSchedulerOptions {
20
+ projectRoot: string;
21
+ executor?: NexusAutomationExecutor;
22
+ agentLauncher?: NexusAutomationAgentLauncher;
23
+ owner?: string | null;
24
+ baseRef?: string | null;
25
+ provider?: WorkTrackerProvider;
26
+ providerFactory?: NexusAutomationWorkTrackerProviderFactory;
27
+ providerOptions?: CreateWorkTrackerProviderOptions;
28
+ gitRunner?: GitRunner;
29
+ now?: () => Date | string;
30
+ sleep?: (ms: number) => Promise<void>;
31
+ onTick?: (tick: NexusAutomationSchedulerTick) => void | Promise<void>;
32
+ intervalMs?: number;
33
+ maxTicks?: number;
34
+ maxRuns?: number;
35
+ runIdPrefix?: string;
36
+ shouldStop?: () => boolean;
37
+ }
38
+ export interface RunNexusAutomationSchedulerResult {
39
+ projectRoot: string;
40
+ startedAt: string;
41
+ finishedAt: string;
42
+ ticks: NexusAutomationSchedulerTick[];
43
+ runs: NexusAutomationSchedulerRunResult[];
44
+ stoppedReason: NexusAutomationSchedulerStopReason;
45
+ }
46
+ export declare class NexusAutomationSchedulerError extends Error {
47
+ constructor(message: string);
48
+ }
49
+ export declare function runNexusAutomationScheduler(options: RunNexusAutomationSchedulerOptions): Promise<RunNexusAutomationSchedulerResult>;
50
+ export declare function nextNexusAutomationSchedulerDelayMs(status: NexusAutomationStatus, intervalMs: number, now?: Date | string): number;