@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,504 @@
1
+ import path from "node:path";
2
+ import { readNexusAutomationRunLedger, } from "./nexusAutomation.js";
3
+ import { loadProjectConfig, } from "./nexusProjectConfig.js";
4
+ import { resolveProjectComponents, } from "./nexusProjectLifecycle.js";
5
+ import { readNexusAutomationTargetContext, } from "./nexusAutomationTarget.js";
6
+ import { readNexusAutomationTargetCycleLedger, summarizeNexusAutomationTargetCycles, } from "./nexusAutomationTargetCycle.js";
7
+ import { loadLocalWorkTrackingStore, resolveLocalWorkTrackingStorePath, } from "./workTrackingLocalProvider.js";
8
+ export class NexusAutomationTargetReportError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "NexusAutomationTargetReportError";
12
+ }
13
+ }
14
+ export function buildNexusAutomationTargetReport(options) {
15
+ const projectRoot = path.resolve(requiredNonEmptyString(options.projectRoot, "projectRoot"));
16
+ const projectConfig = loadProjectConfig(projectRoot);
17
+ const automationConfig = projectConfig.automation ?? null;
18
+ const generatedAt = isoString(options.now ?? new Date());
19
+ const components = resolveProjectComponents(projectRoot, projectConfig);
20
+ if (!automationConfig) {
21
+ return {
22
+ version: 1,
23
+ generatedAt,
24
+ projectRoot,
25
+ project: projectSummary(projectConfig, components),
26
+ target: null,
27
+ status: "not_started",
28
+ statusReason: "Project automation is not configured",
29
+ cycleSummary: null,
30
+ runSummary: null,
31
+ workItemSummary: null,
32
+ executionSummary: null,
33
+ componentProgress: [],
34
+ relaunchDecision: {
35
+ type: "not_ready",
36
+ reason: "Project automation is not configured",
37
+ eligibleWorkItemCount: null,
38
+ latestCycleId: null,
39
+ latestRunId: null,
40
+ },
41
+ activeBlockers: [],
42
+ blockers: [],
43
+ notes: [],
44
+ };
45
+ }
46
+ const target = readNexusAutomationTargetContext({
47
+ projectRoot,
48
+ config: automationConfig,
49
+ });
50
+ const cycleLedger = readNexusAutomationTargetCycleLedger(projectRoot, automationConfig);
51
+ const cycleSummary = summarizeNexusAutomationTargetCycles({
52
+ projectRoot,
53
+ config: automationConfig,
54
+ });
55
+ const runLedger = readNexusAutomationRunLedger(projectRoot, automationConfig);
56
+ const runSummary = summarizeRuns(runLedger);
57
+ const status = reportStatus(cycleLedger, runLedger);
58
+ const workItemResolver = localWorkItemResolver(projectRoot, components);
59
+ const workItemSummary = summarizeCycleWorkItems(cycleLedger, workItemResolver);
60
+ const executionSummary = summarizeExecution(runLedger, workItemResolver);
61
+ const activeBlockers = summarizeActiveBlockers({
62
+ lastCycle: cycleSummary.lastCycle,
63
+ lastRun: runSummary.lastRun,
64
+ workItemResolver,
65
+ });
66
+ return {
67
+ version: 1,
68
+ generatedAt,
69
+ projectRoot,
70
+ project: projectSummary(projectConfig, components),
71
+ target,
72
+ status,
73
+ statusReason: reportStatusReason(status, cycleSummary.lastCycle, runSummary.lastRun),
74
+ cycleSummary,
75
+ runSummary,
76
+ workItemSummary,
77
+ executionSummary,
78
+ componentProgress: summarizeComponentProgress({
79
+ components,
80
+ workItemSummary,
81
+ executionSummary,
82
+ activeBlockers,
83
+ }),
84
+ relaunchDecision: relaunchDecision({
85
+ automationConfig,
86
+ lastCycle: cycleSummary.lastCycle,
87
+ lastRun: runSummary.lastRun,
88
+ }),
89
+ activeBlockers,
90
+ blockers: uniqueStrings(cycleLedger.cycles.flatMap((cycle) => cycle.blockers)),
91
+ notes: uniqueStrings(cycleLedger.cycles.flatMap((cycle) => cycle.notes)),
92
+ };
93
+ }
94
+ function relaunchDecision(options) {
95
+ const { automationConfig, lastCycle, lastRun } = options;
96
+ if (!lastCycle) {
97
+ if (lastRun?.status === "blocked") {
98
+ return decision("report_blocked", `Latest automation run ${lastRun.id} is blocked and no target cycle is recorded`, null, null, lastRun.id);
99
+ }
100
+ if (lastRun?.status === "failed") {
101
+ return decision("report_failed", `Latest automation run ${lastRun.id} failed and no target cycle is recorded`, null, null, lastRun.id);
102
+ }
103
+ return decision("not_ready", "No target cycle is recorded", null, null, lastRun?.id ?? null);
104
+ }
105
+ const latestRunId = lastCycle.runId ?? lastRun?.id ?? null;
106
+ if (lastCycle.status === "started" || lastCycle.status === "dispatched") {
107
+ return decision("wait", `Latest target cycle ${lastCycle.id} is still ${lastCycle.status}`, lastCycle.eligibleWorkItemCount, lastCycle.id, latestRunId);
108
+ }
109
+ if (lastCycle.status === "blocked") {
110
+ return decision("report_blocked", `Latest target cycle ${lastCycle.id} is blocked`, lastCycle.eligibleWorkItemCount, lastCycle.id, latestRunId);
111
+ }
112
+ if (lastCycle.status === "failed") {
113
+ return decision("report_failed", `Latest target cycle ${lastCycle.id} failed`, lastCycle.eligibleWorkItemCount, lastCycle.id, latestRunId);
114
+ }
115
+ const eligibleWorkItemCount = lastCycle.eligibleWorkItemCount;
116
+ if (eligibleWorkItemCount === null) {
117
+ return decision("not_ready", `Latest target cycle ${lastCycle.id} did not record eligible work item count`, null, lastCycle.id, latestRunId);
118
+ }
119
+ if (eligibleWorkItemCount > 0) {
120
+ if (automationConfig.agent.relaunch.whileEligible) {
121
+ return decision("relaunch", `Latest target cycle ${lastCycle.id} recorded ${eligibleWorkItemCount} eligible work item(s) and relaunch while eligible is enabled`, eligibleWorkItemCount, lastCycle.id, latestRunId);
122
+ }
123
+ return decision("wait", `Latest target cycle ${lastCycle.id} recorded ${eligibleWorkItemCount} eligible work item(s), but relaunch while eligible is disabled`, eligibleWorkItemCount, lastCycle.id, latestRunId);
124
+ }
125
+ if (automationConfig.target.stopWhenNoEligibleWork) {
126
+ return decision("stop", `Latest target cycle ${lastCycle.id} recorded no eligible work item(s)`, eligibleWorkItemCount, lastCycle.id, latestRunId);
127
+ }
128
+ return decision("wait", `Latest target cycle ${lastCycle.id} recorded no eligible work item(s), but stopWhenNoEligibleWork is disabled`, eligibleWorkItemCount, lastCycle.id, latestRunId);
129
+ }
130
+ function decision(type, reason, eligibleWorkItemCount, latestCycleId, latestRunId) {
131
+ return {
132
+ type,
133
+ reason,
134
+ eligibleWorkItemCount,
135
+ latestCycleId,
136
+ latestRunId,
137
+ };
138
+ }
139
+ function projectSummary(projectConfig, components) {
140
+ return {
141
+ id: projectConfig.id,
142
+ name: projectConfig.name,
143
+ componentCount: components.length,
144
+ };
145
+ }
146
+ function summarizeRuns(ledger) {
147
+ return {
148
+ runCount: ledger.runs.length,
149
+ completedRunCount: ledger.runs.filter((run) => run.status === "completed")
150
+ .length,
151
+ blockedRunCount: ledger.runs.filter((run) => run.status === "blocked")
152
+ .length,
153
+ failedRunCount: ledger.runs.filter((run) => run.status === "failed").length,
154
+ skippedRunCount: ledger.runs.filter((run) => run.status === "skipped")
155
+ .length,
156
+ lastRun: ledger.runs.at(-1) ?? null,
157
+ };
158
+ }
159
+ function summarizeCycleWorkItems(ledger, workItemResolver) {
160
+ const all = ledger.cycles.flatMap((cycle) => cycle.workItems.map((item) => ({ cycle, item })));
161
+ const unique = new Map();
162
+ const componentCounts = new Map();
163
+ const byCycleStatus = {
164
+ eligible: 0,
165
+ selected: 0,
166
+ dispatched: 0,
167
+ in_progress: 0,
168
+ completed: 0,
169
+ blocked: 0,
170
+ skipped: 0,
171
+ };
172
+ for (const { cycle, item } of all) {
173
+ const key = workItemKey(item);
174
+ const resolved = workItemResolver(item.componentId, item.id);
175
+ unique.set(key, {
176
+ componentId: item.componentId,
177
+ id: item.id,
178
+ title: item.title ?? resolved?.title ?? null,
179
+ status: item.status ?? resolved?.status ?? null,
180
+ latestCycleStatus: item.cycleStatus,
181
+ latestCycleId: cycle.id,
182
+ agentProfileId: item.agentProfileId,
183
+ notes: item.notes,
184
+ });
185
+ const componentKey = item.componentId ?? "";
186
+ const component = componentCounts.get(componentKey) ?? {
187
+ componentId: item.componentId,
188
+ totalReferences: 0,
189
+ uniqueIds: new Set(),
190
+ };
191
+ component.totalReferences += 1;
192
+ component.uniqueIds.add(item.id);
193
+ componentCounts.set(componentKey, component);
194
+ if (item.cycleStatus) {
195
+ byCycleStatus[item.cycleStatus] += 1;
196
+ }
197
+ }
198
+ const uniqueReferences = [...unique.values()];
199
+ return {
200
+ totalReferences: all.length,
201
+ uniqueReferences,
202
+ byComponent: [...componentCounts.values()].map((component) => ({
203
+ componentId: component.componentId,
204
+ totalReferences: component.totalReferences,
205
+ uniqueWorkItemCount: component.uniqueIds.size,
206
+ })),
207
+ byCycleStatus,
208
+ progress: summarizeWorkItemProgress(uniqueReferences),
209
+ };
210
+ }
211
+ function summarizeExecution(ledger, workItemResolver) {
212
+ const runs = ledger.runs.map((run) => {
213
+ const resolved = run.workItemId
214
+ ? workItemResolver(run.componentId, run.workItemId)
215
+ : null;
216
+ return {
217
+ runId: run.id,
218
+ componentId: run.componentId,
219
+ status: run.status,
220
+ workItemId: run.workItemId,
221
+ workItemTitle: run.workItemTitle ?? resolved?.title ?? null,
222
+ workItemStatus: resolved?.status ?? null,
223
+ commitIds: run.commitIds,
224
+ summary: run.summary,
225
+ error: run.error,
226
+ };
227
+ });
228
+ const runById = new Map(runs.map((run) => [run.runId, run]));
229
+ const verification = ledger.runs.flatMap((run) => {
230
+ const summary = runById.get(run.id);
231
+ return run.verification.map((record) => ({
232
+ runId: run.id,
233
+ componentId: run.componentId,
234
+ workItemId: run.workItemId,
235
+ workItemTitle: summary?.workItemTitle ?? null,
236
+ ...record,
237
+ }));
238
+ });
239
+ const publicationDecisions = ledger.runs.flatMap((run) => {
240
+ if (!run.publicationDecision) {
241
+ return [];
242
+ }
243
+ const summary = runById.get(run.id);
244
+ return [
245
+ {
246
+ runId: run.id,
247
+ componentId: run.componentId,
248
+ workItemId: run.workItemId,
249
+ workItemTitle: summary?.workItemTitle ?? null,
250
+ ...run.publicationDecision,
251
+ },
252
+ ];
253
+ });
254
+ return {
255
+ runCount: ledger.runs.length,
256
+ commitIds: uniqueStrings(ledger.runs.flatMap((run) => run.commitIds)),
257
+ verification,
258
+ publicationDecisions,
259
+ runs,
260
+ };
261
+ }
262
+ function summarizeActiveBlockers(options) {
263
+ const { lastCycle, lastRun, workItemResolver } = options;
264
+ const active = [];
265
+ if (lastCycle &&
266
+ (lastCycle.status === "completed" || lastCycle.status === "skipped")) {
267
+ return active;
268
+ }
269
+ if (lastCycle) {
270
+ for (const blocker of lastCycle.blockers) {
271
+ active.push({
272
+ source: "cycle",
273
+ componentId: null,
274
+ cycleId: lastCycle.id,
275
+ runId: lastCycle.runId,
276
+ workItemId: null,
277
+ workItemTitle: null,
278
+ message: blocker,
279
+ });
280
+ }
281
+ for (const item of lastCycle.workItems) {
282
+ const resolved = workItemResolver(item.componentId, item.id);
283
+ const status = item.status ?? resolved?.status ?? null;
284
+ if (item.cycleStatus !== "blocked" && status !== "blocked") {
285
+ continue;
286
+ }
287
+ active.push({
288
+ source: "work_item",
289
+ componentId: item.componentId,
290
+ cycleId: lastCycle.id,
291
+ runId: lastCycle.runId,
292
+ workItemId: item.id,
293
+ workItemTitle: item.title ?? resolved?.title ?? null,
294
+ message: item.notes,
295
+ });
296
+ }
297
+ }
298
+ if (lastRun &&
299
+ (lastRun.status === "blocked" || lastRun.status === "failed") &&
300
+ (!lastCycle || lastCycle.runId === lastRun.id)) {
301
+ const resolved = lastRun.workItemId
302
+ ? workItemResolver(lastRun.componentId, lastRun.workItemId)
303
+ : null;
304
+ active.push({
305
+ source: "run",
306
+ componentId: lastRun.componentId,
307
+ cycleId: lastCycle?.id ?? null,
308
+ runId: lastRun.id,
309
+ workItemId: lastRun.workItemId,
310
+ workItemTitle: lastRun.workItemTitle ?? resolved?.title ?? null,
311
+ message: lastRun.error,
312
+ });
313
+ }
314
+ return active;
315
+ }
316
+ function summarizeComponentProgress(options) {
317
+ const drafts = new Map();
318
+ const ensureDraft = (componentId, component = null) => {
319
+ const key = componentId ?? "";
320
+ const existing = drafts.get(key);
321
+ if (existing) {
322
+ if (!existing.component && component) {
323
+ existing.component = component;
324
+ }
325
+ return existing;
326
+ }
327
+ const draft = {
328
+ component,
329
+ componentId,
330
+ workItems: [],
331
+ activeBlockers: [],
332
+ runs: [],
333
+ };
334
+ drafts.set(key, draft);
335
+ return draft;
336
+ };
337
+ for (const component of options.components) {
338
+ ensureDraft(component.id, component);
339
+ }
340
+ for (const item of options.workItemSummary.uniqueReferences) {
341
+ ensureDraft(item.componentId).workItems.push(item);
342
+ }
343
+ for (const blocker of options.activeBlockers) {
344
+ if (blocker.componentId !== null) {
345
+ ensureDraft(blocker.componentId).activeBlockers.push(blocker);
346
+ }
347
+ }
348
+ for (const run of options.executionSummary.runs) {
349
+ ensureDraft(run.componentId).runs.push(run);
350
+ }
351
+ return [...drafts.values()].map((draft) => {
352
+ const componentId = draft.component?.id ?? draft.componentId;
353
+ const verification = options.executionSummary.verification.filter((record) => record.componentId === componentId);
354
+ const publicationDecisions = options.executionSummary.publicationDecisions.filter((decisionRecord) => decisionRecord.componentId === componentId);
355
+ return {
356
+ componentId,
357
+ componentName: draft.component?.name ?? componentId,
358
+ role: draft.component?.role ?? null,
359
+ sourceRoot: draft.component?.sourceRoot ?? null,
360
+ workTrackingProvider: draft.component?.workTracking?.provider ?? null,
361
+ workItemCount: draft.workItems.length,
362
+ workItems: summarizeWorkItemProgress(draft.workItems),
363
+ activeBlockers: draft.activeBlockers,
364
+ commitIds: uniqueStrings(draft.runs.flatMap((run) => run.commitIds)),
365
+ verification,
366
+ publicationDecisions,
367
+ runs: draft.runs,
368
+ };
369
+ });
370
+ }
371
+ function summarizeWorkItemProgress(references) {
372
+ const progress = emptyWorkItemProgress();
373
+ for (const reference of references) {
374
+ const bucket = workItemProgressBucket(reference);
375
+ if (bucket) {
376
+ progress[bucket].push(reference);
377
+ }
378
+ }
379
+ return progress;
380
+ }
381
+ function emptyWorkItemProgress() {
382
+ return {
383
+ readyEligibleWork: [],
384
+ selectedWork: [],
385
+ blockedHitlWork: [],
386
+ completedWork: [],
387
+ skippedWork: [],
388
+ };
389
+ }
390
+ function workItemProgressBucket(reference) {
391
+ switch (reference.latestCycleStatus) {
392
+ case "eligible":
393
+ return "readyEligibleWork";
394
+ case "selected":
395
+ case "dispatched":
396
+ case "in_progress":
397
+ return "selectedWork";
398
+ case "blocked":
399
+ return "blockedHitlWork";
400
+ case "completed":
401
+ return "completedWork";
402
+ case "skipped":
403
+ return "skippedWork";
404
+ default:
405
+ break;
406
+ }
407
+ switch (reference.status) {
408
+ case "ready":
409
+ return "readyEligibleWork";
410
+ case "in_progress":
411
+ return "selectedWork";
412
+ case "blocked":
413
+ return "blockedHitlWork";
414
+ case "done":
415
+ return "completedWork";
416
+ case "wont_do":
417
+ return "skippedWork";
418
+ default:
419
+ return null;
420
+ }
421
+ }
422
+ function localWorkItemResolver(projectRoot, components) {
423
+ const primaryComponentId = components.find((component) => component.role === "primary")?.id ??
424
+ components[0]?.id ??
425
+ null;
426
+ const itemsByComponent = new Map();
427
+ for (const component of components) {
428
+ const workTracking = component.workTracking;
429
+ if (workTracking?.provider !== "local") {
430
+ continue;
431
+ }
432
+ try {
433
+ const store = loadLocalWorkTrackingStore(resolveLocalWorkTrackingStorePath(projectRoot, workTracking));
434
+ itemsByComponent.set(component.id, new Map(store.items.map((item) => [
435
+ item.id,
436
+ {
437
+ title: item.title,
438
+ status: item.status,
439
+ },
440
+ ])));
441
+ }
442
+ catch {
443
+ continue;
444
+ }
445
+ }
446
+ return (componentId, id) => {
447
+ const resolvedComponentId = componentId ?? primaryComponentId;
448
+ if (!resolvedComponentId) {
449
+ return null;
450
+ }
451
+ return itemsByComponent.get(resolvedComponentId)?.get(id) ?? null;
452
+ };
453
+ }
454
+ function reportStatus(cycleLedger, runLedger) {
455
+ const lastCycle = cycleLedger.cycles.at(-1);
456
+ if (lastCycle) {
457
+ return cycleStatusToReportStatus(lastCycle.status);
458
+ }
459
+ const lastRun = runLedger.runs.at(-1);
460
+ if (!lastRun) {
461
+ return "not_started";
462
+ }
463
+ return "not_started";
464
+ }
465
+ function cycleStatusToReportStatus(status) {
466
+ if (status === "started" || status === "dispatched") {
467
+ return "active";
468
+ }
469
+ return status;
470
+ }
471
+ function reportStatusReason(status, lastCycle, lastRun) {
472
+ if (lastCycle) {
473
+ return `Latest target cycle ${lastCycle.id} is ${lastCycle.status}`;
474
+ }
475
+ if (lastRun) {
476
+ return `No target cycle is recorded; latest automation run ${lastRun.id} is ${lastRun.status}`;
477
+ }
478
+ if (status === "not_started") {
479
+ return "No target cycles or automation runs are recorded";
480
+ }
481
+ return `Target report status is ${status}`;
482
+ }
483
+ function workItemKey(item) {
484
+ return `${item.componentId ?? ""}:${item.id}`;
485
+ }
486
+ function uniqueStrings(values) {
487
+ return [...new Set(values)];
488
+ }
489
+ function isoString(value) {
490
+ return dateFrom(value, "date").toISOString();
491
+ }
492
+ function dateFrom(value, name) {
493
+ const date = value instanceof Date ? value : new Date(value);
494
+ if (Number.isNaN(date.getTime())) {
495
+ throw new NexusAutomationTargetReportError(`${name} must be a valid date`);
496
+ }
497
+ return date;
498
+ }
499
+ function requiredNonEmptyString(value, name) {
500
+ if (typeof value !== "string" || value.trim().length === 0) {
501
+ throw new NexusAutomationTargetReportError(`${name} must be a non-empty string`);
502
+ }
503
+ return value.trim();
504
+ }
@@ -0,0 +1,89 @@
1
+ import { type GitRunner } from "./gitWorktreeService.js";
2
+ import type { NexusAutomationConfig } from "./nexusAutomationConfig.js";
3
+ import { type MaterializeNexusWorkerContextBundleResult, type NexusWorkerContextDependencyProjection, type NexusWorkerContextDependencyProjectionSourceControl, type NexusWorkerContextDependencyProjectionSourceMetadata, type NexusWorkerContextDependencyProjectionStatus, type NexusWorkerContextBundleWorktree } from "./nexusWorkerContextBundle.js";
4
+ import { type NexusProjectSkillsConfig, type NexusSkillMaterializationMode, type NexusSkillSourceControl } from "./nexusSkills.js";
5
+ import type { NexusPluginWorkerFragmentsProjection } from "./nexusPluginCapabilities.js";
6
+ export type NexusAutomationWorktreeSetupLinkStatus = "linked" | "present" | "skipped";
7
+ export interface NexusAutomationWorktreeSetupPreflightCheck {
8
+ name: string;
9
+ status: "passed" | "failed";
10
+ message: string;
11
+ }
12
+ export interface NexusAutomationWorktreeSetupLinkResult {
13
+ source: string;
14
+ target: string;
15
+ sourcePath: string;
16
+ targetPath: string;
17
+ required: boolean;
18
+ status: NexusAutomationWorktreeSetupLinkStatus;
19
+ message: string;
20
+ }
21
+ export interface NexusAutomationPluginDependencyProjection {
22
+ id: string;
23
+ source: string;
24
+ target: string;
25
+ required: boolean;
26
+ sourceControl: NexusWorkerContextDependencyProjectionSourceControl;
27
+ reason: string | null;
28
+ sourceMetadata: NexusWorkerContextDependencyProjectionSourceMetadata;
29
+ }
30
+ export interface NexusAutomationWorktreeDependencyProjectionResult extends NexusWorkerContextDependencyProjection {
31
+ status: NexusWorkerContextDependencyProjectionStatus;
32
+ }
33
+ export type NexusAutomationWorktreeSkillProjectionStatus = "missing" | "stale" | "present";
34
+ export interface NexusAutomationWorktreeSkillProjectionSkillResult {
35
+ id: string;
36
+ name: string;
37
+ version: string;
38
+ materialization: NexusSkillMaterializationMode;
39
+ sourceSkillRoot: string;
40
+ projectedSkillRoot: string;
41
+ skillPath: string | null;
42
+ beforeStatus: NexusAutomationWorktreeSkillProjectionStatus;
43
+ afterStatus: NexusAutomationWorktreeSkillProjectionStatus;
44
+ refreshed: boolean;
45
+ reasons: string[];
46
+ }
47
+ export interface NexusAutomationWorktreeSkillProjectionResult {
48
+ agent: string;
49
+ projectManagedSkillsRoot: string;
50
+ skillsDirectory: string;
51
+ sourceControl: NexusSkillSourceControl;
52
+ skills: NexusAutomationWorktreeSkillProjectionSkillResult[];
53
+ }
54
+ export interface NexusAutomationWorktreeSetupResult {
55
+ links: NexusAutomationWorktreeSetupLinkResult[];
56
+ dependencyProjections: NexusAutomationWorktreeDependencyProjectionResult[];
57
+ skillProjections: NexusAutomationWorktreeSkillProjectionResult[];
58
+ context?: MaterializeNexusWorkerContextBundleResult;
59
+ }
60
+ export interface NexusAutomationWorktreeSetupContextInput {
61
+ project: {
62
+ id?: string | null;
63
+ name?: string | null;
64
+ root: string;
65
+ };
66
+ ownership: NexusWorkerContextBundleWorktree;
67
+ targetStatePath?: string | null;
68
+ pluginFragments?: NexusPluginWorkerFragmentsProjection;
69
+ }
70
+ export interface NexusAutomationWorktreeSetupOptions {
71
+ sourceRoot: string;
72
+ worktreesRoot?: string;
73
+ worktreePath: string;
74
+ automationConfig: NexusAutomationConfig;
75
+ pluginDependencyProjections?: NexusAutomationPluginDependencyProjection[];
76
+ skillsConfig?: NexusProjectSkillsConfig;
77
+ context?: NexusAutomationWorktreeSetupContextInput;
78
+ gitRunner?: GitRunner;
79
+ platform?: NodeJS.Platform;
80
+ }
81
+ export declare class NexusAutomationWorktreeSetupError extends Error {
82
+ constructor(message: string);
83
+ }
84
+ export declare function preflightNexusAutomationWorktreeSetup(options: {
85
+ sourceRoot: string;
86
+ automationConfig: NexusAutomationConfig;
87
+ pluginDependencyProjections?: NexusAutomationPluginDependencyProjection[];
88
+ }): NexusAutomationWorktreeSetupPreflightCheck[];
89
+ export declare function materializeNexusAutomationWorktreeSetup(options: NexusAutomationWorktreeSetupOptions): NexusAutomationWorktreeSetupResult;