@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,855 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { appendNexusAutomationRunRecord, buildNexusAutomationWorkItemQuery, eligibleNexusAutomationWorkItems, evaluateNexusAutomationLedgerBackoff, readNexusAutomationRunLedger, releaseNexusAutomationRunLock, acquireNexusAutomationRunLock, } from "./nexusAutomation.js";
5
+ import { defaultNexusAutomationCommandRunner, } from "./nexusAutomationCommandExecutor.js";
6
+ import { normalizeNexusAutomationAgentPolicy, } from "./nexusAutomationAgentProfile.js";
7
+ import { projectPluginCapabilityProjections, } from "./nexusPluginCapabilities.js";
8
+ import { loadProjectConfig, } from "./nexusProjectConfig.js";
9
+ import { readNexusAutomationTargetContext, } from "./nexusAutomationTarget.js";
10
+ import { resolvePrimaryProjectComponent, resolveProjectComponents, } from "./nexusProjectLifecycle.js";
11
+ import { createWorkTrackerProvider, } from "./workTrackingProviderService.js";
12
+ export class NexusAutomationAgentLaunchError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "NexusAutomationAgentLaunchError";
16
+ }
17
+ }
18
+ export async function runNexusAutomationAgentLaunchOnce(options) {
19
+ const projectRoot = path.resolve(requiredNonEmptyString(options.projectRoot, "projectRoot"));
20
+ const projectConfig = loadProjectConfig(projectRoot);
21
+ const automationConfig = projectConfig.automation ?? null;
22
+ const runId = options.runId ?? generateNexusAutomationAgentRunId(options.now);
23
+ let sourceRoot = null;
24
+ let components = [];
25
+ let lock = null;
26
+ let ledger = null;
27
+ let componentProviders = [];
28
+ let preflight = [];
29
+ let selectorQuery = null;
30
+ let eligibleWorkItems = [];
31
+ let componentEligibleWorkItems = [];
32
+ let contextFile = null;
33
+ let resultFile = null;
34
+ if (!automationConfig?.enabled) {
35
+ return launchResult({
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
+ selectorQuery,
47
+ eligibleWorkItems,
48
+ componentEligibleWorkItems,
49
+ components,
50
+ contextFile,
51
+ resultFile,
52
+ launch: null,
53
+ });
54
+ }
55
+ if (automationConfig.mode !== "agent_launch") {
56
+ return launchResult({
57
+ runId,
58
+ projectRoot,
59
+ sourceRoot,
60
+ projectConfig,
61
+ automationConfig,
62
+ status: "blocked",
63
+ summary: "Automation mode is not agent_launch",
64
+ ledger,
65
+ lock,
66
+ preflight: [],
67
+ selectorQuery,
68
+ eligibleWorkItems,
69
+ componentEligibleWorkItems,
70
+ components,
71
+ contextFile,
72
+ resultFile,
73
+ launch: null,
74
+ });
75
+ }
76
+ const startedAt = currentIso(options.now);
77
+ lock = acquireNexusAutomationRunLock({
78
+ projectRoot,
79
+ config: automationConfig,
80
+ runId,
81
+ owner: options.owner ?? null,
82
+ now: startedAt,
83
+ });
84
+ try {
85
+ ledger = readNexusAutomationRunLedger(projectRoot, automationConfig);
86
+ const backoff = evaluateNexusAutomationLedgerBackoff(automationConfig, ledger, startedAt);
87
+ if (!backoff.shouldRun) {
88
+ const summary = backoff.reason ?? "Automation retry backoff is active";
89
+ ledger = appendNexusAutomationRunRecord({
90
+ projectRoot,
91
+ config: automationConfig,
92
+ now: startedAt,
93
+ record: {
94
+ id: runId,
95
+ projectId: projectConfig.id,
96
+ status: "skipped",
97
+ startedAt,
98
+ finishedAt: startedAt,
99
+ summary,
100
+ nextRunNotBefore: backoff.retryAfter,
101
+ },
102
+ });
103
+ return launchResult({
104
+ runId,
105
+ projectRoot,
106
+ sourceRoot,
107
+ projectConfig,
108
+ automationConfig,
109
+ status: "skipped",
110
+ summary,
111
+ ledger,
112
+ lock,
113
+ preflight,
114
+ selectorQuery,
115
+ eligibleWorkItems,
116
+ componentEligibleWorkItems,
117
+ components,
118
+ contextFile,
119
+ resultFile,
120
+ launch: null,
121
+ });
122
+ }
123
+ components = resolveProjectComponents(projectRoot, projectConfig);
124
+ sourceRoot = resolvePrimaryProjectComponent(projectRoot, projectConfig).sourceRoot;
125
+ componentProviders = createAgentLaunchComponentProviders({
126
+ options,
127
+ projectRoot,
128
+ projectConfig,
129
+ components,
130
+ });
131
+ if (componentProviders.length === 0) {
132
+ const summary = "No project component has work tracking configured";
133
+ preflight = [
134
+ check("workTracking", false, "At least one component has work tracking configured", summary),
135
+ ];
136
+ ledger = appendNexusAutomationRunRecord({
137
+ projectRoot,
138
+ config: automationConfig,
139
+ now: startedAt,
140
+ record: {
141
+ id: runId,
142
+ projectId: projectConfig.id,
143
+ status: "blocked",
144
+ startedAt,
145
+ finishedAt: startedAt,
146
+ sourceRoot,
147
+ summary,
148
+ error: summary,
149
+ },
150
+ });
151
+ return launchResult({
152
+ runId,
153
+ projectRoot,
154
+ sourceRoot,
155
+ projectConfig,
156
+ automationConfig,
157
+ status: "blocked",
158
+ summary,
159
+ ledger,
160
+ lock,
161
+ preflight,
162
+ selectorQuery,
163
+ eligibleWorkItems,
164
+ componentEligibleWorkItems,
165
+ components,
166
+ contextFile,
167
+ resultFile,
168
+ launch: null,
169
+ });
170
+ }
171
+ preflight = preflightNexusAutomationAgentLaunch({
172
+ components,
173
+ componentProviders,
174
+ automationConfig,
175
+ });
176
+ const failedChecks = preflight.filter((check) => check.status === "failed");
177
+ if (failedChecks.length > 0) {
178
+ const summary = failedChecks.map((check) => check.message).join("; ");
179
+ ledger = appendNexusAutomationRunRecord({
180
+ projectRoot,
181
+ config: automationConfig,
182
+ now: startedAt,
183
+ record: {
184
+ id: runId,
185
+ projectId: projectConfig.id,
186
+ status: "blocked",
187
+ startedAt,
188
+ finishedAt: startedAt,
189
+ sourceRoot,
190
+ summary,
191
+ error: summary,
192
+ },
193
+ });
194
+ return launchResult({
195
+ runId,
196
+ projectRoot,
197
+ sourceRoot,
198
+ projectConfig,
199
+ automationConfig,
200
+ status: "blocked",
201
+ summary,
202
+ ledger,
203
+ lock,
204
+ preflight,
205
+ selectorQuery,
206
+ eligibleWorkItems,
207
+ componentEligibleWorkItems,
208
+ components,
209
+ contextFile,
210
+ resultFile,
211
+ launch: null,
212
+ });
213
+ }
214
+ selectorQuery = buildNexusAutomationWorkItemQuery(automationConfig);
215
+ componentEligibleWorkItems = await listEligibleWorkItemsByComponent(componentProviders, selectorQuery, automationConfig, projectRoot);
216
+ eligibleWorkItems = componentEligibleWorkItems.flatMap((component) => component.workItems);
217
+ if (eligibleWorkItems.length === 0) {
218
+ const summary = "No eligible work item matched the automation selector";
219
+ const finishedAt = currentIso(options.now);
220
+ ledger = appendNexusAutomationRunRecord({
221
+ projectRoot,
222
+ config: automationConfig,
223
+ now: finishedAt,
224
+ record: {
225
+ id: runId,
226
+ projectId: projectConfig.id,
227
+ status: "skipped",
228
+ startedAt,
229
+ finishedAt,
230
+ sourceRoot,
231
+ summary,
232
+ },
233
+ });
234
+ return launchResult({
235
+ runId,
236
+ projectRoot,
237
+ sourceRoot,
238
+ projectConfig,
239
+ automationConfig,
240
+ status: "skipped",
241
+ summary,
242
+ ledger,
243
+ lock,
244
+ preflight,
245
+ selectorQuery,
246
+ eligibleWorkItems,
247
+ componentEligibleWorkItems,
248
+ components,
249
+ contextFile,
250
+ resultFile,
251
+ launch: null,
252
+ });
253
+ }
254
+ const launchFiles = nexusAutomationAgentLaunchFiles({
255
+ projectRoot,
256
+ runId,
257
+ });
258
+ writeNexusAutomationAgentLaunchContext({
259
+ contextFile: launchFiles.contextFile,
260
+ context: buildAgentLaunchContext({
261
+ runId,
262
+ startedAt,
263
+ projectRoot,
264
+ sourceRoot,
265
+ components,
266
+ projectConfig,
267
+ automationConfig,
268
+ selectorQuery,
269
+ eligibleWorkItems,
270
+ componentEligibleWorkItems,
271
+ resultFile: launchFiles.resultFile,
272
+ }),
273
+ });
274
+ contextFile = launchFiles.contextFile;
275
+ resultFile = launchFiles.resultFile;
276
+ const agentResult = await options.launcher({
277
+ runId,
278
+ startedAt,
279
+ projectRoot,
280
+ sourceRoot,
281
+ components,
282
+ projectConfig,
283
+ automationConfig,
284
+ selectorQuery,
285
+ eligibleWorkItems,
286
+ componentEligibleWorkItems,
287
+ contextFile,
288
+ resultFile,
289
+ });
290
+ const status = normalizeAgentStatus(agentResult.status);
291
+ const finishedAt = currentIso(options.now);
292
+ const summary = agentResult.summary ?? defaultAgentSummary(status);
293
+ ledger = appendNexusAutomationRunRecord({
294
+ projectRoot,
295
+ config: automationConfig,
296
+ now: finishedAt,
297
+ record: {
298
+ id: runId,
299
+ projectId: projectConfig.id,
300
+ status,
301
+ startedAt,
302
+ finishedAt,
303
+ sourceRoot,
304
+ commitIds: agentResult.commitIds ?? [],
305
+ summary,
306
+ verification: verificationRecords(agentResult.verification, finishedAt),
307
+ publicationDecision: publicationDecisionRecord(agentResult.publicationDecision, finishedAt),
308
+ error: agentResult.error ?? null,
309
+ },
310
+ });
311
+ return launchResult({
312
+ runId,
313
+ projectRoot,
314
+ sourceRoot,
315
+ projectConfig,
316
+ automationConfig,
317
+ status,
318
+ summary,
319
+ ledger,
320
+ lock,
321
+ preflight,
322
+ selectorQuery,
323
+ eligibleWorkItems,
324
+ componentEligibleWorkItems,
325
+ components,
326
+ contextFile,
327
+ resultFile,
328
+ launch: agentResult,
329
+ });
330
+ }
331
+ catch (error) {
332
+ if (!automationConfig) {
333
+ throw error;
334
+ }
335
+ const finishedAt = currentIso(options.now);
336
+ const summary = errorMessage(error);
337
+ ledger = appendNexusAutomationRunRecord({
338
+ projectRoot,
339
+ config: automationConfig,
340
+ now: finishedAt,
341
+ record: {
342
+ id: runId,
343
+ projectId: projectConfig.id,
344
+ status: "failed",
345
+ startedAt,
346
+ finishedAt,
347
+ sourceRoot,
348
+ summary,
349
+ error: summary,
350
+ },
351
+ });
352
+ return launchResult({
353
+ runId,
354
+ projectRoot,
355
+ sourceRoot,
356
+ projectConfig,
357
+ automationConfig,
358
+ status: "failed",
359
+ summary,
360
+ ledger,
361
+ lock,
362
+ preflight,
363
+ selectorQuery,
364
+ eligibleWorkItems,
365
+ componentEligibleWorkItems,
366
+ components,
367
+ contextFile,
368
+ resultFile,
369
+ launch: null,
370
+ });
371
+ }
372
+ finally {
373
+ if (automationConfig) {
374
+ releaseNexusAutomationRunLock({
375
+ projectRoot,
376
+ config: automationConfig,
377
+ runId,
378
+ });
379
+ }
380
+ }
381
+ }
382
+ export function createNexusAutomationAgentCommandLauncher(options) {
383
+ const command = requiredNonEmptyString(options.command, "command");
384
+ const commandRunner = options.commandRunner ?? defaultNexusAutomationCommandRunner;
385
+ return (input) => {
386
+ const commandResult = commandRunner(command, {
387
+ cwd: input.projectRoot,
388
+ env: agentLaunchEnvironment(options.env ?? process.env, input),
389
+ timeoutMs: options.timeoutMs,
390
+ });
391
+ const commandVerification = verificationFromCommandResult(commandResult);
392
+ const reported = readAgentResultFile(input.resultFile);
393
+ if (reported.status === "failed") {
394
+ return {
395
+ status: "failed",
396
+ summary: reported.summary,
397
+ verification: [commandVerification],
398
+ error: reported.error,
399
+ };
400
+ }
401
+ const verification = [
402
+ commandVerification,
403
+ ...(reported.result?.verification ?? []),
404
+ ];
405
+ if (!commandSucceeded(commandResult)) {
406
+ return {
407
+ status: "failed",
408
+ summary: `Agent command failed: ${commandSummary(commandResult)}`,
409
+ verification,
410
+ commitIds: reported.result?.commitIds ?? [],
411
+ publicationDecision: reported.result?.publicationDecision,
412
+ error: commandSummary(commandResult),
413
+ };
414
+ }
415
+ if (reported.status === "missing") {
416
+ return {
417
+ status: "failed",
418
+ summary: reported.summary,
419
+ verification,
420
+ error: reported.error,
421
+ };
422
+ }
423
+ const status = normalizeAgentStatus(reported.result?.status);
424
+ return {
425
+ status,
426
+ summary: reported.result?.summary ?? defaultAgentSummary(status),
427
+ verification,
428
+ commitIds: reported.result?.commitIds ?? [],
429
+ publicationDecision: reported.result?.publicationDecision,
430
+ error: reported.result?.error ?? null,
431
+ };
432
+ };
433
+ }
434
+ export function preflightNexusAutomationAgentLaunch(options) {
435
+ return [
436
+ ...preflightNexusAutomationAgentPolicy(options.automationConfig),
437
+ check("workTracking", options.componentProviders.length > 0, "At least one component has work tracking configured", "No project component has work tracking configured"),
438
+ ...options.componentProviders.map(({ component, provider }) => check(`component:${component.id}:trackerListItems`, provider.capabilities.listItems, `Component ${component.id} tracker can list work items`, `Component ${component.id} tracker provider ${provider.provider} cannot list work items`)),
439
+ ...options.components.map((component) => check(`component:${component.id}:sourceRoot`, component.sourceRootExists, `Component ${component.id} source root exists`, `Component ${component.id} source root does not exist: ${component.sourceRoot}`)),
440
+ ];
441
+ }
442
+ function preflightNexusAutomationAgentPolicy(automationConfig) {
443
+ const policy = normalizeNexusAutomationAgentPolicy(automationConfig);
444
+ const checks = [
445
+ check("agent:maxConcurrentSubagents", policy.maxConcurrentSubagents > 0, `Agent subagent cap is ${policy.maxConcurrentSubagents}`, "automation.agent.maxConcurrentSubagents must be a positive integer"),
446
+ ];
447
+ if (!policy.coordinatorProfileId) {
448
+ checks.push(check("agent:coordinatorProfile", true, "No coordinator profile is configured; command policy must come from automation.agent.command or caller override", "Coordinator profile is not configured"));
449
+ return checks;
450
+ }
451
+ const coordinatorProfile = policy.coordinatorProfile;
452
+ checks.push(check("agent:coordinatorProfile", coordinatorProfile !== null, `Coordinator profile ${policy.coordinatorProfileId} is configured`, `automation.agent.coordinatorProfileId references missing profile: ${policy.coordinatorProfileId}`));
453
+ if (!coordinatorProfile) {
454
+ return checks;
455
+ }
456
+ checks.push(check(`agentProfile:${coordinatorProfile.id}:intendedUse`, coordinatorProfile.intendedUse !== "subagent", `Coordinator profile ${coordinatorProfile.id} intendedUse allows coordinator launch`, `automation.agent.profiles.${coordinatorProfile.id}.intendedUse must be coordinator or any for coordinator launch`), check(`agentProfile:${coordinatorProfile.id}:command`, coordinatorProfile.command !== null, `Coordinator profile ${coordinatorProfile.id} command is configured`, `automation.agent.profiles.${coordinatorProfile.id}.command must be configured for coordinator launch`));
457
+ return checks;
458
+ }
459
+ export function generateNexusAutomationAgentRunId(now) {
460
+ const timestamp = currentIso(now)
461
+ .replaceAll("-", "")
462
+ .replaceAll(":", "")
463
+ .replace(".", "-");
464
+ const suffix = Math.random().toString(36).slice(2, 8);
465
+ return `agent-${timestamp}-${suffix}`;
466
+ }
467
+ function createAgentLaunchComponentProviders(options) {
468
+ return options.components
469
+ .filter((component) => component.workTracking)
470
+ .map((component) => ({
471
+ component,
472
+ provider: createAgentLaunchProvider({
473
+ options: options.options,
474
+ projectRoot: options.projectRoot,
475
+ projectConfig: options.projectConfig,
476
+ component,
477
+ }),
478
+ }));
479
+ }
480
+ function createAgentLaunchProvider(options) {
481
+ const workTracking = options.component.workTracking;
482
+ if (!workTracking) {
483
+ throw new NexusAutomationAgentLaunchError(`Component ${options.component.id} work tracking is not configured`);
484
+ }
485
+ if (options.options.provider) {
486
+ return options.options.provider;
487
+ }
488
+ if (options.options.providerFactory) {
489
+ return options.options.providerFactory({
490
+ projectRoot: options.projectRoot,
491
+ sourceRoot: options.component.sourceRoot,
492
+ projectConfig: options.projectConfig,
493
+ component: options.component,
494
+ workTracking,
495
+ });
496
+ }
497
+ return createWorkTrackerProvider(workTracking, {
498
+ ...options.options.providerOptions,
499
+ projectRoot: options.projectRoot,
500
+ now: options.options.now,
501
+ });
502
+ }
503
+ async function listEligibleWorkItemsByComponent(componentProviders, selectorQuery, automationConfig, projectRoot) {
504
+ const grouped = [];
505
+ for (const { component, provider } of componentProviders) {
506
+ grouped.push({
507
+ componentId: component.id,
508
+ workItems: eligibleNexusAutomationWorkItems(await provider.listWorkItems({
509
+ ...selectorQuery,
510
+ projectRoot,
511
+ }), automationConfig),
512
+ });
513
+ }
514
+ return grouped;
515
+ }
516
+ function buildAgentLaunchContext(input) {
517
+ return {
518
+ version: 1,
519
+ runId: input.runId,
520
+ startedAt: input.startedAt,
521
+ projectRoot: input.projectRoot,
522
+ sourceRoot: input.sourceRoot,
523
+ project: {
524
+ id: input.projectConfig.id,
525
+ name: input.projectConfig.name,
526
+ componentCount: input.components.length,
527
+ },
528
+ components: input.components.map(componentContext),
529
+ automation: {
530
+ mode: "agent_launch",
531
+ selectorQuery: input.selectorQuery,
532
+ eligibleWorkItemCount: input.eligibleWorkItems.length,
533
+ },
534
+ target: readNexusAutomationTargetContext({
535
+ projectRoot: input.projectRoot,
536
+ config: input.automationConfig,
537
+ }),
538
+ agent: normalizeNexusAutomationAgentPolicy(input.automationConfig),
539
+ pluginCapabilities: projectPluginCapabilityProjections(input.projectConfig),
540
+ result: agentResultContract(input.resultFile),
541
+ eligibleWorkItems: input.eligibleWorkItems,
542
+ componentEligibleWorkItems: input.componentEligibleWorkItems,
543
+ safety: input.automationConfig.safety,
544
+ publication: input.automationConfig.publication,
545
+ };
546
+ }
547
+ function componentContext(component) {
548
+ return {
549
+ id: component.id,
550
+ name: component.name,
551
+ role: component.role,
552
+ kind: component.kind,
553
+ sourceRoot: component.sourceRoot,
554
+ sourceRootExists: component.sourceRootExists,
555
+ worktreesRoot: component.worktreesRoot,
556
+ worktreesRootExists: component.worktreesRootExists,
557
+ remoteUrl: component.remoteUrl,
558
+ defaultBranch: component.defaultBranch,
559
+ workTracker: {
560
+ provider: component.workTracking?.provider ?? null,
561
+ configured: Boolean(component.workTracking),
562
+ },
563
+ relationships: component.relationships,
564
+ };
565
+ }
566
+ function writeNexusAutomationAgentLaunchContext(options) {
567
+ fs.mkdirSync(path.dirname(options.contextFile), { recursive: true });
568
+ fs.writeFileSync(options.contextFile, `${JSON.stringify(options.context, null, 2)}\n`, "utf8");
569
+ }
570
+ function nexusAutomationAgentLaunchFiles(options) {
571
+ const launchDir = path.join(options.projectRoot, ".dev-nexus", "automation", "agent-launches", safePathSegment(options.runId));
572
+ const contextFile = path.join(launchDir, "context.json");
573
+ const resultFile = path.join(launchDir, "result.json");
574
+ return { contextFile, resultFile };
575
+ }
576
+ function agentResultContract(resultFile) {
577
+ return {
578
+ file: resultFile,
579
+ requiredFields: ["status", "summary"],
580
+ optionalFields: [
581
+ "commitIds",
582
+ "verification",
583
+ "publicationDecision",
584
+ "error",
585
+ ],
586
+ statuses: ["completed", "failed", "blocked"],
587
+ verificationStatuses: ["passed", "failed", "not_run"],
588
+ publicationDecisionTypes: [
589
+ "not_decided",
590
+ "local_only",
591
+ "direct_integration",
592
+ "review_handoff",
593
+ "blocked",
594
+ ],
595
+ };
596
+ }
597
+ function agentLaunchEnvironment(baseEnv, input) {
598
+ return {
599
+ ...baseEnv,
600
+ DEV_NEXUS_AUTOMATION_MODE: "agent_launch",
601
+ DEV_NEXUS_RUN_ID: input.runId,
602
+ DEV_NEXUS_STARTED_AT: input.startedAt,
603
+ DEV_NEXUS_PROJECT_ROOT: input.projectRoot,
604
+ DEV_NEXUS_SOURCE_ROOT: input.sourceRoot,
605
+ DEV_NEXUS_COMPONENT_COUNT: input.components.length.toString(),
606
+ DEV_NEXUS_COMPONENT_IDS: input.components
607
+ .map((component) => component.id)
608
+ .join(","),
609
+ DEV_NEXUS_PRIMARY_COMPONENT_ID: input.components.find((component) => component.role === "primary")?.id ??
610
+ input.components[0]?.id ??
611
+ "",
612
+ DEV_NEXUS_AGENT_CONTEXT_FILE: input.contextFile,
613
+ DEV_NEXUS_AGENT_RESULT_FILE: input.resultFile,
614
+ DEV_NEXUS_AGENT_RESULT_REQUIRED_FIELDS: "status,summary",
615
+ DEV_NEXUS_AGENT_RESULT_OPTIONAL_FIELDS: "commitIds,verification,publicationDecision,error",
616
+ DEV_NEXUS_TARGET_ID: input.automationConfig.target.id ?? "",
617
+ DEV_NEXUS_TARGET_STATE_FILE: readNexusAutomationTargetContext({
618
+ projectRoot: input.projectRoot,
619
+ config: input.automationConfig,
620
+ }).statePath,
621
+ DEV_NEXUS_TARGET_CYCLE_LEDGER_FILE: readNexusAutomationTargetContext({
622
+ projectRoot: input.projectRoot,
623
+ config: input.automationConfig,
624
+ }).cycleLedgerPath,
625
+ DEV_NEXUS_COORDINATOR_PROFILE_ID: input.automationConfig.agent.coordinatorProfileId ?? "",
626
+ DEV_NEXUS_MAX_CONCURRENT_SUBAGENTS: input.automationConfig.agent.maxConcurrentSubagents.toString(),
627
+ DEV_NEXUS_ELIGIBLE_WORK_ITEM_COUNT: input.eligibleWorkItems.length.toString(),
628
+ DEV_NEXUS_ELIGIBLE_WORK_ITEM_IDS: input.eligibleWorkItems
629
+ .map((item) => item.id)
630
+ .join(","),
631
+ };
632
+ }
633
+ function readAgentResultFile(resultFile) {
634
+ if (!fs.existsSync(resultFile)) {
635
+ const message = `Agent result file was not written: ${resultFile}`;
636
+ return {
637
+ status: "missing",
638
+ summary: message,
639
+ error: message,
640
+ };
641
+ }
642
+ try {
643
+ return {
644
+ status: "loaded",
645
+ summary: "Agent result file loaded",
646
+ error: null,
647
+ result: normalizeAgentResult(JSON.parse(fs.readFileSync(resultFile, "utf8").replace(/^\uFEFF/, ""))),
648
+ };
649
+ }
650
+ catch (error) {
651
+ const message = `Agent result file is invalid: ${errorMessage(error)}`;
652
+ return {
653
+ status: "failed",
654
+ summary: message,
655
+ error: message,
656
+ };
657
+ }
658
+ }
659
+ function normalizeAgentResult(value) {
660
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
661
+ throw new NexusAutomationAgentLaunchError("agent result must be an object");
662
+ }
663
+ const record = value;
664
+ return {
665
+ status: normalizeAgentStatus(requiredNonEmptyString(record.status, "agent result.status")),
666
+ summary: requiredNonEmptyString(record.summary, "agent result.summary"),
667
+ ...(record.commitIds === undefined
668
+ ? {}
669
+ : { commitIds: normalizeStringArray(record.commitIds, "agent result.commitIds") }),
670
+ ...(record.verification === undefined
671
+ ? {}
672
+ : { verification: normalizeVerificationInputList(record.verification) }),
673
+ ...(record.publicationDecision === undefined
674
+ ? {}
675
+ : {
676
+ publicationDecision: normalizePublicationDecisionInput(record.publicationDecision),
677
+ }),
678
+ ...(record.error === undefined
679
+ ? {}
680
+ : { error: optionalNullableString(record.error) ?? null }),
681
+ };
682
+ }
683
+ function normalizeVerificationInputList(value) {
684
+ if (!Array.isArray(value)) {
685
+ throw new NexusAutomationAgentLaunchError("agent result.verification must be an array");
686
+ }
687
+ return value.map((item, index) => {
688
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
689
+ throw new NexusAutomationAgentLaunchError(`agent result.verification[${index}] must be an object`);
690
+ }
691
+ const record = item;
692
+ return {
693
+ command: requiredNonEmptyString(record.command, `agent result.verification[${index}].command`),
694
+ ...(record.status === undefined
695
+ ? {}
696
+ : {
697
+ status: normalizeVerificationStatus(record.status, `agent result.verification[${index}].status`),
698
+ }),
699
+ ...(record.summary === undefined
700
+ ? {}
701
+ : { summary: optionalNullableString(record.summary) ?? null }),
702
+ };
703
+ });
704
+ }
705
+ function normalizePublicationDecisionInput(value) {
706
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
707
+ throw new NexusAutomationAgentLaunchError("agent result.publicationDecision must be an object");
708
+ }
709
+ const record = value;
710
+ return {
711
+ type: normalizePublicationDecisionType(record.type, "agent result.publicationDecision.type"),
712
+ targetBranch: optionalNullableString(record.targetBranch) ?? null,
713
+ remote: optionalNullableString(record.remote) ?? null,
714
+ prUrl: optionalNullableString(record.prUrl) ?? null,
715
+ reason: optionalNullableString(record.reason) ?? null,
716
+ };
717
+ }
718
+ function verificationRecords(values, recordedAt) {
719
+ return (values ?? []).map((value) => ({
720
+ command: requiredNonEmptyString(value.command, "verification.command"),
721
+ status: value.status ?? "passed",
722
+ summary: optionalNullableString(value.summary) ?? null,
723
+ recordedAt,
724
+ }));
725
+ }
726
+ function publicationDecisionRecord(value, decidedAt) {
727
+ if (!value) {
728
+ return null;
729
+ }
730
+ return {
731
+ type: value.type,
732
+ targetBranch: value.targetBranch ?? null,
733
+ remote: value.remote ?? null,
734
+ prUrl: value.prUrl ?? null,
735
+ reason: value.reason ?? null,
736
+ decidedAt,
737
+ };
738
+ }
739
+ function verificationFromCommandResult(result) {
740
+ return {
741
+ command: result.command,
742
+ status: commandSucceeded(result) ? "passed" : "failed",
743
+ summary: commandSummary(result),
744
+ };
745
+ }
746
+ function commandSucceeded(result) {
747
+ return result.exitCode === 0 && !result.error;
748
+ }
749
+ function commandSummary(result) {
750
+ if (result.error) {
751
+ return result.error;
752
+ }
753
+ const exit = result.exitCode === null ? "no exit code" : `exit ${result.exitCode}`;
754
+ const output = firstNonEmptyLine(result.stderr) ?? firstNonEmptyLine(result.stdout);
755
+ return output ? `${exit}: ${truncate(output, 180)}` : exit;
756
+ }
757
+ function defaultAgentSummary(status) {
758
+ if (status === "completed") {
759
+ return "Agent launch completed";
760
+ }
761
+ if (status === "blocked") {
762
+ return "Agent launch reported a blocker";
763
+ }
764
+ return "Agent launch failed";
765
+ }
766
+ function normalizeAgentStatus(status) {
767
+ if (status === undefined || status === null) {
768
+ return "completed";
769
+ }
770
+ if (status === "completed" || status === "failed" || status === "blocked") {
771
+ return status;
772
+ }
773
+ throw new NexusAutomationAgentLaunchError("agent launch status must be completed, failed, or blocked");
774
+ }
775
+ function normalizeVerificationStatus(status, name) {
776
+ if (status === "passed" || status === "failed" || status === "not_run") {
777
+ return status;
778
+ }
779
+ throw new NexusAutomationAgentLaunchError(`${name} must be passed, failed, or not_run`);
780
+ }
781
+ function normalizePublicationDecisionType(value, name) {
782
+ if (value === "not_decided" ||
783
+ value === "local_only" ||
784
+ value === "direct_integration" ||
785
+ value === "review_handoff" ||
786
+ value === "blocked") {
787
+ return value;
788
+ }
789
+ throw new NexusAutomationAgentLaunchError(`${name} must be not_decided, local_only, direct_integration, review_handoff, or blocked`);
790
+ }
791
+ function check(name, passed, passedMessage, failedMessage) {
792
+ return {
793
+ name,
794
+ status: (passed ? "passed" : "failed"),
795
+ message: passed ? passedMessage : failedMessage,
796
+ };
797
+ }
798
+ function launchResult(result) {
799
+ return {
800
+ ...result,
801
+ componentEligibleWorkItems: result.componentEligibleWorkItems ?? [],
802
+ components: result.components ?? [],
803
+ };
804
+ }
805
+ function safePathSegment(value) {
806
+ const normalized = value
807
+ .trim()
808
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
809
+ .replace(/^-+|-+$/g, "");
810
+ if (!normalized) {
811
+ throw new NexusAutomationAgentLaunchError("runId must contain at least one safe character");
812
+ }
813
+ return normalized;
814
+ }
815
+ function currentIso(now) {
816
+ const value = now ? now() : new Date();
817
+ const date = value instanceof Date ? value : new Date(value);
818
+ if (Number.isNaN(date.getTime())) {
819
+ throw new NexusAutomationAgentLaunchError("now must return a valid date");
820
+ }
821
+ return date.toISOString();
822
+ }
823
+ function firstNonEmptyLine(value) {
824
+ return value
825
+ .split(/\r?\n/u)
826
+ .map((line) => line.trim())
827
+ .find(Boolean);
828
+ }
829
+ function truncate(value, length) {
830
+ return value.length <= length ? value : `${value.slice(0, length - 3)}...`;
831
+ }
832
+ function normalizeStringArray(value, name) {
833
+ if (!Array.isArray(value)) {
834
+ throw new NexusAutomationAgentLaunchError(`${name} must be an array`);
835
+ }
836
+ return value.map((item, index) => requiredNonEmptyString(item, `${name}[${index}]`));
837
+ }
838
+ function optionalNullableString(value) {
839
+ if (value === undefined) {
840
+ return undefined;
841
+ }
842
+ if (value === null) {
843
+ return null;
844
+ }
845
+ return requiredNonEmptyString(value, "value");
846
+ }
847
+ function errorMessage(error) {
848
+ return error instanceof Error ? error.message : String(error);
849
+ }
850
+ function requiredNonEmptyString(value, name) {
851
+ if (typeof value !== "string" || value.trim().length === 0) {
852
+ throw new NexusAutomationAgentLaunchError(`${name} must be a non-empty string`);
853
+ }
854
+ return value.trim();
855
+ }