@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.
- package/README.md +677 -0
- package/dist/browserOpener.d.ts +9 -0
- package/dist/browserOpener.js +47 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +2374 -0
- package/dist/gitWorktreeService.d.ts +57 -0
- package/dist/gitWorktreeService.js +157 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +47 -0
- package/dist/nexusAgentMcpConfig.d.ts +30 -0
- package/dist/nexusAgentMcpConfig.js +228 -0
- package/dist/nexusAutomation.d.ts +103 -0
- package/dist/nexusAutomation.js +390 -0
- package/dist/nexusAutomationAgentLaunch.d.ts +148 -0
- package/dist/nexusAutomationAgentLaunch.js +855 -0
- package/dist/nexusAutomationAgentProfile.d.ts +39 -0
- package/dist/nexusAutomationAgentProfile.js +103 -0
- package/dist/nexusAutomationAgentSurface.d.ts +62 -0
- package/dist/nexusAutomationAgentSurface.js +90 -0
- package/dist/nexusAutomationCommandExecutor.d.ts +29 -0
- package/dist/nexusAutomationCommandExecutor.js +251 -0
- package/dist/nexusAutomationConfig.d.ts +114 -0
- package/dist/nexusAutomationConfig.js +547 -0
- package/dist/nexusAutomationEnqueue.d.ts +37 -0
- package/dist/nexusAutomationEnqueue.js +128 -0
- package/dist/nexusAutomationRunOnce.d.ts +91 -0
- package/dist/nexusAutomationRunOnce.js +586 -0
- package/dist/nexusAutomationScheduler.d.ts +50 -0
- package/dist/nexusAutomationScheduler.js +196 -0
- package/dist/nexusAutomationStatus.d.ts +55 -0
- package/dist/nexusAutomationStatus.js +462 -0
- package/dist/nexusAutomationTarget.d.ts +19 -0
- package/dist/nexusAutomationTarget.js +33 -0
- package/dist/nexusAutomationTargetCycle.d.ts +90 -0
- package/dist/nexusAutomationTargetCycle.js +282 -0
- package/dist/nexusAutomationTargetReport.d.ts +136 -0
- package/dist/nexusAutomationTargetReport.js +504 -0
- package/dist/nexusAutomationWorktreeSetup.d.ts +89 -0
- package/dist/nexusAutomationWorktreeSetup.js +661 -0
- package/dist/nexusCoordination.d.ts +198 -0
- package/dist/nexusCoordination.js +1018 -0
- package/dist/nexusExtension.d.ts +31 -0
- package/dist/nexusExtension.js +1 -0
- package/dist/nexusHomeConfig.d.ts +38 -0
- package/dist/nexusHomeConfig.js +133 -0
- package/dist/nexusMcpServer.d.ts +31 -0
- package/dist/nexusMcpServer.js +1036 -0
- package/dist/nexusPluginCapabilities.d.ts +197 -0
- package/dist/nexusPluginCapabilities.js +201 -0
- package/dist/nexusProjectConfig.d.ts +95 -0
- package/dist/nexusProjectConfig.js +880 -0
- package/dist/nexusProjectHomeService.d.ts +121 -0
- package/dist/nexusProjectHomeService.js +171 -0
- package/dist/nexusProjectLifecycle.d.ts +62 -0
- package/dist/nexusProjectLifecycle.js +205 -0
- package/dist/nexusProjectOperations.d.ts +101 -0
- package/dist/nexusProjectOperations.js +296 -0
- package/dist/nexusProjectRegistry.d.ts +42 -0
- package/dist/nexusProjectRegistry.js +91 -0
- package/dist/nexusProjectScaffold.d.ts +25 -0
- package/dist/nexusProjectScaffold.js +61 -0
- package/dist/nexusProjectTemplate.d.ts +34 -0
- package/dist/nexusProjectTemplate.js +354 -0
- package/dist/nexusSkills.d.ts +134 -0
- package/dist/nexusSkills.js +647 -0
- package/dist/nexusWorkerContextBundle.d.ts +142 -0
- package/dist/nexusWorkerContextBundle.js +375 -0
- package/dist/processSupervisor.d.ts +89 -0
- package/dist/processSupervisor.js +440 -0
- package/dist/vibeKanbanApi.d.ts +11 -0
- package/dist/vibeKanbanApi.js +14 -0
- package/dist/vibeKanbanAuth.d.ts +25 -0
- package/dist/vibeKanbanAuth.js +101 -0
- package/dist/vibeKanbanBoardAdapter.d.ts +36 -0
- package/dist/vibeKanbanBoardAdapter.js +196 -0
- package/dist/vibeKanbanMcpConfig.d.ts +36 -0
- package/dist/vibeKanbanMcpConfig.js +191 -0
- package/dist/vibeKanbanProjectAdapter.d.ts +39 -0
- package/dist/vibeKanbanProjectAdapter.js +113 -0
- package/dist/vibeKanbanWorkspaceSetup.d.ts +1 -0
- package/dist/vibeKanbanWorkspaceSetup.js +96 -0
- package/dist/workItemService.d.ts +60 -0
- package/dist/workItemService.js +163 -0
- package/dist/workTrackingGitHubProvider.d.ts +71 -0
- package/dist/workTrackingGitHubProvider.js +663 -0
- package/dist/workTrackingGitLabProvider.d.ts +62 -0
- package/dist/workTrackingGitLabProvider.js +523 -0
- package/dist/workTrackingJiraProvider.d.ts +67 -0
- package/dist/workTrackingJiraProvider.js +652 -0
- package/dist/workTrackingLocalProvider.d.ts +49 -0
- package/dist/workTrackingLocalProvider.js +463 -0
- package/dist/workTrackingProviderService.d.ts +21 -0
- package/dist/workTrackingProviderService.js +117 -0
- package/dist/workTrackingTypes.d.ts +202 -0
- package/dist/workTrackingTypes.js +1 -0
- package/dist/workTrackingVibeProvider.d.ts +35 -0
- package/dist/workTrackingVibeProvider.js +119 -0
- package/dist/worktreeExecutionMetadata.d.ts +76 -0
- package/dist/worktreeExecutionMetadata.js +239 -0
- package/package.json +37 -0
|
@@ -0,0 +1,1036 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { defaultNexusHomePath, loadNexusHomeConfigFile, resolveNexusHome, saveNexusHomeConfigFile, validateNexusHomeConfigBase, } from "./nexusHomeConfig.js";
|
|
4
|
+
import { loadProjectConfig, } from "./nexusProjectConfig.js";
|
|
5
|
+
import { getNexusProjectStatus, } from "./nexusProjectHomeService.js";
|
|
6
|
+
import { resolvePrimaryProjectComponent, resolveProjectComponents, } from "./nexusProjectLifecycle.js";
|
|
7
|
+
import { buildNexusProjectStatusForPath, } from "./nexusProjectRegistry.js";
|
|
8
|
+
import { getNexusAutomationStatus, } from "./nexusAutomationStatus.js";
|
|
9
|
+
import { getNexusAutomationAgentProfileSummary, getNexusAutomationEligibleWorkSummary, } from "./nexusAutomationAgentSurface.js";
|
|
10
|
+
import { appendNexusAutomationTargetCycleRecord, maxNexusAutomationTargetCycleNoteLength, readNexusAutomationTargetCycleLedger, } from "./nexusAutomationTargetCycle.js";
|
|
11
|
+
import { buildNexusAutomationTargetReport, } from "./nexusAutomationTargetReport.js";
|
|
12
|
+
import { createNexusCoordinationHandoff, getNexusCoordinationIntegrationPlan, getNexusCoordinationStatus, parseNexusCoordinationHandoffStatus, } from "./nexusCoordination.js";
|
|
13
|
+
import { createWorkItemService, } from "./workItemService.js";
|
|
14
|
+
export const devNexusMcpProtocolVersion = "2024-11-05";
|
|
15
|
+
const tools = [
|
|
16
|
+
{
|
|
17
|
+
name: "project_status",
|
|
18
|
+
description: "Show one DevNexus project by registered id or filesystem path.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
homePath: { type: "string" },
|
|
23
|
+
project: { type: "string" },
|
|
24
|
+
},
|
|
25
|
+
required: ["project"],
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "automation_status",
|
|
31
|
+
description: "Read DevNexus automation readiness, target context, and eligible work without mutating state.",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
homePath: { type: "string" },
|
|
36
|
+
project: { type: "string" },
|
|
37
|
+
projectRoot: { type: "string" },
|
|
38
|
+
},
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "eligible_work",
|
|
44
|
+
description: "List concise DevNexus eligible work grouped by component using the configured automation selector.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
homePath: { type: "string" },
|
|
49
|
+
project: { type: "string" },
|
|
50
|
+
projectRoot: { type: "string" },
|
|
51
|
+
},
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "agent_profiles",
|
|
57
|
+
description: "Inspect concise DevNexus coordinator and subagent profile policy without dumping full project config.",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
homePath: { type: "string" },
|
|
62
|
+
project: { type: "string" },
|
|
63
|
+
projectRoot: { type: "string" },
|
|
64
|
+
},
|
|
65
|
+
additionalProperties: false,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "target_cycle_list",
|
|
70
|
+
description: "List recorded DevNexus target cycles without mutating state.",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
homePath: { type: "string" },
|
|
75
|
+
project: { type: "string" },
|
|
76
|
+
projectRoot: { type: "string" },
|
|
77
|
+
},
|
|
78
|
+
additionalProperties: false,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "target_cycle_record",
|
|
83
|
+
description: "Record caller-reported DevNexus target cycle progress.",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
homePath: { type: "string" },
|
|
88
|
+
project: { type: "string" },
|
|
89
|
+
projectRoot: { type: "string" },
|
|
90
|
+
cycleId: { type: "string" },
|
|
91
|
+
runId: { type: "string" },
|
|
92
|
+
status: {
|
|
93
|
+
type: "string",
|
|
94
|
+
enum: ["started", "dispatched", "completed", "blocked", "failed", "skipped"],
|
|
95
|
+
},
|
|
96
|
+
summary: { type: ["string", "null"] },
|
|
97
|
+
eligibleWorkItemCount: { type: ["number", "null"] },
|
|
98
|
+
workItems: {
|
|
99
|
+
type: "array",
|
|
100
|
+
items: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
componentId: { type: ["string", "null"] },
|
|
104
|
+
id: { type: "string" },
|
|
105
|
+
title: { type: ["string", "null"] },
|
|
106
|
+
status: { type: ["string", "null"] },
|
|
107
|
+
cycleStatus: {
|
|
108
|
+
type: ["string", "null"],
|
|
109
|
+
enum: [
|
|
110
|
+
"eligible",
|
|
111
|
+
"selected",
|
|
112
|
+
"dispatched",
|
|
113
|
+
"in_progress",
|
|
114
|
+
"completed",
|
|
115
|
+
"blocked",
|
|
116
|
+
"skipped",
|
|
117
|
+
null,
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
agentProfileId: { type: ["string", "null"] },
|
|
121
|
+
notes: {
|
|
122
|
+
type: ["string", "null"],
|
|
123
|
+
maxLength: maxNexusAutomationTargetCycleNoteLength,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ["id"],
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
blockers: { type: "array", items: { type: "string" } },
|
|
131
|
+
notes: {
|
|
132
|
+
type: "array",
|
|
133
|
+
items: {
|
|
134
|
+
type: "string",
|
|
135
|
+
maxLength: maxNexusAutomationTargetCycleNoteLength,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
required: ["status"],
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "target_report",
|
|
145
|
+
description: "Build a factual DevNexus target report from recorded target, cycle, run, and work-item facts.",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
homePath: { type: "string" },
|
|
150
|
+
project: { type: "string" },
|
|
151
|
+
projectRoot: { type: "string" },
|
|
152
|
+
},
|
|
153
|
+
additionalProperties: false,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "coordination_status",
|
|
158
|
+
description: "Report advisory shared coordination status for a component worktree and optional work item.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
homePath: { type: "string" },
|
|
163
|
+
project: { type: "string" },
|
|
164
|
+
projectRoot: { type: "string" },
|
|
165
|
+
componentId: { type: "string" },
|
|
166
|
+
workItemId: { type: "string" },
|
|
167
|
+
currentPath: { type: "string" },
|
|
168
|
+
},
|
|
169
|
+
additionalProperties: false,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "coordination_handoff",
|
|
174
|
+
description: "Record an advisory structured coordination handoff as a tracker-backed work-item comment.",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
homePath: { type: "string" },
|
|
179
|
+
project: { type: "string" },
|
|
180
|
+
projectRoot: { type: "string" },
|
|
181
|
+
componentId: { type: "string" },
|
|
182
|
+
workItemId: { type: "string" },
|
|
183
|
+
status: {
|
|
184
|
+
type: "string",
|
|
185
|
+
enum: ["working", "ready", "blocked", "merged"],
|
|
186
|
+
},
|
|
187
|
+
hostId: { type: "string" },
|
|
188
|
+
agentId: { type: "string" },
|
|
189
|
+
changedAreas: { type: "array", items: { type: "string" } },
|
|
190
|
+
decisions: { type: "array", items: { type: "string" } },
|
|
191
|
+
verificationSummary: { type: ["string", "null"] },
|
|
192
|
+
integrationPreference: { type: ["string", "null"] },
|
|
193
|
+
note: { type: ["string", "null"] },
|
|
194
|
+
currentPath: { type: "string" },
|
|
195
|
+
},
|
|
196
|
+
required: ["workItemId", "status"],
|
|
197
|
+
additionalProperties: false,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "coordination_integrate",
|
|
202
|
+
description: "Build a read-only integration plan from related handoff branches, Git merge analysis, and recorded decisions.",
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
homePath: { type: "string" },
|
|
207
|
+
project: { type: "string" },
|
|
208
|
+
projectRoot: { type: "string" },
|
|
209
|
+
componentId: { type: "string" },
|
|
210
|
+
workItemId: { type: "string" },
|
|
211
|
+
targetBranch: { type: "string" },
|
|
212
|
+
fetch: { type: "boolean" },
|
|
213
|
+
currentPath: { type: "string" },
|
|
214
|
+
},
|
|
215
|
+
additionalProperties: false,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "work_item_create",
|
|
220
|
+
description: "Create a work item through the configured DevNexus work tracker.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
homePath: { type: "string" },
|
|
225
|
+
project: { type: "string" },
|
|
226
|
+
projectRoot: { type: "string" },
|
|
227
|
+
componentId: { type: "string" },
|
|
228
|
+
title: { type: "string" },
|
|
229
|
+
description: { type: ["string", "null"] },
|
|
230
|
+
status: { type: "string" },
|
|
231
|
+
labels: { type: "array", items: { type: "string" } },
|
|
232
|
+
assignees: { type: "array", items: { type: "string" } },
|
|
233
|
+
milestone: { type: ["string", "null"] },
|
|
234
|
+
},
|
|
235
|
+
required: ["title"],
|
|
236
|
+
additionalProperties: false,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "work_item_list",
|
|
241
|
+
description: "List work items through the configured DevNexus work tracker.",
|
|
242
|
+
inputSchema: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
homePath: { type: "string" },
|
|
246
|
+
project: { type: "string" },
|
|
247
|
+
projectRoot: { type: "string" },
|
|
248
|
+
componentId: { type: "string" },
|
|
249
|
+
status: {
|
|
250
|
+
oneOf: [
|
|
251
|
+
{ type: "string" },
|
|
252
|
+
{ type: "array", items: { type: "string" } },
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
labels: { type: "array", items: { type: "string" } },
|
|
256
|
+
assignees: { type: "array", items: { type: "string" } },
|
|
257
|
+
search: { type: "string" },
|
|
258
|
+
limit: { type: "number" },
|
|
259
|
+
},
|
|
260
|
+
additionalProperties: false,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "work_item_get",
|
|
265
|
+
description: "Get a work item through the configured DevNexus work tracker.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
homePath: { type: "string" },
|
|
270
|
+
project: { type: "string" },
|
|
271
|
+
projectRoot: { type: "string" },
|
|
272
|
+
componentId: { type: "string" },
|
|
273
|
+
id: { type: "string" },
|
|
274
|
+
provider: { type: "string" },
|
|
275
|
+
externalRef: { type: "object" },
|
|
276
|
+
ref: { type: "object" },
|
|
277
|
+
},
|
|
278
|
+
additionalProperties: false,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "work_item_update",
|
|
283
|
+
description: "Update a work item through the configured DevNexus work tracker.",
|
|
284
|
+
inputSchema: {
|
|
285
|
+
type: "object",
|
|
286
|
+
properties: {
|
|
287
|
+
homePath: { type: "string" },
|
|
288
|
+
project: { type: "string" },
|
|
289
|
+
projectRoot: { type: "string" },
|
|
290
|
+
componentId: { type: "string" },
|
|
291
|
+
id: { type: "string" },
|
|
292
|
+
provider: { type: "string" },
|
|
293
|
+
externalRef: { type: "object" },
|
|
294
|
+
ref: { type: "object" },
|
|
295
|
+
title: { type: "string" },
|
|
296
|
+
description: { type: ["string", "null"] },
|
|
297
|
+
status: { type: "string" },
|
|
298
|
+
labels: { type: "array", items: { type: "string" } },
|
|
299
|
+
assignees: { type: "array", items: { type: "string" } },
|
|
300
|
+
milestone: { type: ["string", "null"] },
|
|
301
|
+
},
|
|
302
|
+
additionalProperties: false,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "work_item_comment",
|
|
307
|
+
description: "Add a comment to a work item through the configured DevNexus work tracker.",
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
homePath: { type: "string" },
|
|
312
|
+
project: { type: "string" },
|
|
313
|
+
projectRoot: { type: "string" },
|
|
314
|
+
componentId: { type: "string" },
|
|
315
|
+
id: { type: "string" },
|
|
316
|
+
provider: { type: "string" },
|
|
317
|
+
externalRef: { type: "object" },
|
|
318
|
+
ref: { type: "object" },
|
|
319
|
+
body: { type: "string" },
|
|
320
|
+
},
|
|
321
|
+
required: ["body"],
|
|
322
|
+
additionalProperties: false,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "work_item_set_status",
|
|
327
|
+
description: "Set a work item's status through the configured DevNexus work tracker.",
|
|
328
|
+
inputSchema: {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
homePath: { type: "string" },
|
|
332
|
+
project: { type: "string" },
|
|
333
|
+
projectRoot: { type: "string" },
|
|
334
|
+
componentId: { type: "string" },
|
|
335
|
+
id: { type: "string" },
|
|
336
|
+
provider: { type: "string" },
|
|
337
|
+
externalRef: { type: "object" },
|
|
338
|
+
ref: { type: "object" },
|
|
339
|
+
status: { type: "string" },
|
|
340
|
+
},
|
|
341
|
+
required: ["status"],
|
|
342
|
+
additionalProperties: false,
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
export function listDevNexusMcpTools() {
|
|
347
|
+
return tools;
|
|
348
|
+
}
|
|
349
|
+
export async function callDevNexusMcpTool(name, argsValue, context = {}) {
|
|
350
|
+
try {
|
|
351
|
+
const args = argsValue === undefined ? {} : asRecord(argsValue, "arguments");
|
|
352
|
+
switch (name) {
|
|
353
|
+
case "project_status":
|
|
354
|
+
return toolResult({
|
|
355
|
+
ok: true,
|
|
356
|
+
project: projectStatusFromArgs(args),
|
|
357
|
+
});
|
|
358
|
+
case "automation_status":
|
|
359
|
+
return toolResult({
|
|
360
|
+
ok: true,
|
|
361
|
+
...(await getNexusAutomationStatus({
|
|
362
|
+
projectRoot: projectRootFromArgs(args),
|
|
363
|
+
now: context.now,
|
|
364
|
+
})),
|
|
365
|
+
});
|
|
366
|
+
case "eligible_work":
|
|
367
|
+
return toolResult({
|
|
368
|
+
ok: true,
|
|
369
|
+
...(await getNexusAutomationEligibleWorkSummary({
|
|
370
|
+
projectRoot: projectRootFromArgs(args),
|
|
371
|
+
now: context.now,
|
|
372
|
+
})),
|
|
373
|
+
});
|
|
374
|
+
case "agent_profiles":
|
|
375
|
+
return toolResult({
|
|
376
|
+
ok: true,
|
|
377
|
+
...getNexusAutomationAgentProfileSummary(projectRootFromArgs(args)),
|
|
378
|
+
});
|
|
379
|
+
case "target_cycle_list":
|
|
380
|
+
return toolResult({
|
|
381
|
+
ok: true,
|
|
382
|
+
...targetCycleLedgerFromArgs(args),
|
|
383
|
+
});
|
|
384
|
+
case "target_cycle_record":
|
|
385
|
+
return toolResult({
|
|
386
|
+
ok: true,
|
|
387
|
+
...appendTargetCycleFromArgs(args, context),
|
|
388
|
+
});
|
|
389
|
+
case "target_report":
|
|
390
|
+
return toolResult({
|
|
391
|
+
ok: true,
|
|
392
|
+
report: buildNexusAutomationTargetReport({
|
|
393
|
+
projectRoot: projectRootFromArgs(args),
|
|
394
|
+
now: context.now?.(),
|
|
395
|
+
}),
|
|
396
|
+
});
|
|
397
|
+
case "coordination_status":
|
|
398
|
+
return toolResult({
|
|
399
|
+
ok: true,
|
|
400
|
+
status: await getNexusCoordinationStatus({
|
|
401
|
+
projectRoot: projectRootFromArgs(args),
|
|
402
|
+
componentId: optionalString(args, "componentId", "arguments"),
|
|
403
|
+
workItemId: optionalString(args, "workItemId", "arguments"),
|
|
404
|
+
currentPath: optionalString(args, "currentPath", "arguments") ??
|
|
405
|
+
context.currentPath ??
|
|
406
|
+
process.cwd(),
|
|
407
|
+
gitRunner: context.gitRunner,
|
|
408
|
+
now: context.now,
|
|
409
|
+
}),
|
|
410
|
+
});
|
|
411
|
+
case "coordination_handoff":
|
|
412
|
+
return toolResult({
|
|
413
|
+
ok: true,
|
|
414
|
+
...(await createNexusCoordinationHandoff({
|
|
415
|
+
projectRoot: projectRootFromArgs(args),
|
|
416
|
+
componentId: optionalString(args, "componentId", "arguments"),
|
|
417
|
+
workItemId: requiredString(args, "workItemId", "arguments"),
|
|
418
|
+
status: parseNexusCoordinationHandoffStatus(requiredString(args, "status", "arguments"), "arguments.status"),
|
|
419
|
+
hostId: optionalString(args, "hostId", "arguments"),
|
|
420
|
+
agentId: optionalString(args, "agentId", "arguments"),
|
|
421
|
+
changedAreas: optionalStringArray(args, "changedAreas", "arguments") ?? [],
|
|
422
|
+
decisions: optionalStringArray(args, "decisions", "arguments") ?? [],
|
|
423
|
+
verificationSummary: optionalNullableString(args, "verificationSummary", "arguments"),
|
|
424
|
+
integrationPreference: optionalNullableString(args, "integrationPreference", "arguments"),
|
|
425
|
+
note: optionalNullableString(args, "note", "arguments"),
|
|
426
|
+
currentPath: optionalString(args, "currentPath", "arguments") ??
|
|
427
|
+
context.currentPath ??
|
|
428
|
+
process.cwd(),
|
|
429
|
+
gitRunner: context.gitRunner,
|
|
430
|
+
now: context.now,
|
|
431
|
+
})),
|
|
432
|
+
});
|
|
433
|
+
case "coordination_integrate":
|
|
434
|
+
return toolResult({
|
|
435
|
+
ok: true,
|
|
436
|
+
plan: await getNexusCoordinationIntegrationPlan({
|
|
437
|
+
projectRoot: projectRootFromArgs(args),
|
|
438
|
+
componentId: optionalString(args, "componentId", "arguments"),
|
|
439
|
+
workItemId: optionalString(args, "workItemId", "arguments"),
|
|
440
|
+
targetBranch: optionalString(args, "targetBranch", "arguments"),
|
|
441
|
+
fetch: optionalBoolean(args, "fetch", "arguments"),
|
|
442
|
+
currentPath: optionalString(args, "currentPath", "arguments") ??
|
|
443
|
+
context.currentPath ??
|
|
444
|
+
process.cwd(),
|
|
445
|
+
gitRunner: context.gitRunner,
|
|
446
|
+
now: context.now,
|
|
447
|
+
}),
|
|
448
|
+
});
|
|
449
|
+
case "work_item_create":
|
|
450
|
+
return toolResult({
|
|
451
|
+
ok: true,
|
|
452
|
+
workItem: await workItemServiceFromArgs(args, context).createWorkItem({
|
|
453
|
+
...projectSelectorFromArgs(args),
|
|
454
|
+
title: requiredString(args, "title", "arguments"),
|
|
455
|
+
description: optionalNullableString(args, "description", "arguments"),
|
|
456
|
+
status: optionalWorkStatus(args, "status", "arguments"),
|
|
457
|
+
labels: optionalStringArray(args, "labels", "arguments") ?? [],
|
|
458
|
+
assignees: optionalStringArray(args, "assignees", "arguments") ?? [],
|
|
459
|
+
milestone: optionalNullableString(args, "milestone", "arguments"),
|
|
460
|
+
}),
|
|
461
|
+
});
|
|
462
|
+
case "work_item_list":
|
|
463
|
+
return toolResult({
|
|
464
|
+
ok: true,
|
|
465
|
+
workItems: await workItemServiceFromArgs(args, context).listWorkItems({
|
|
466
|
+
...projectSelectorFromArgs(args),
|
|
467
|
+
status: optionalWorkStatusQuery(args, "status", "arguments"),
|
|
468
|
+
labels: optionalStringArray(args, "labels", "arguments"),
|
|
469
|
+
assignees: optionalStringArray(args, "assignees", "arguments"),
|
|
470
|
+
search: optionalString(args, "search", "arguments"),
|
|
471
|
+
limit: optionalPositiveInteger(args, "limit", "arguments"),
|
|
472
|
+
}),
|
|
473
|
+
});
|
|
474
|
+
case "work_item_get":
|
|
475
|
+
return toolResult({
|
|
476
|
+
ok: true,
|
|
477
|
+
workItem: await workItemServiceFromArgs(args, context).getWorkItem({
|
|
478
|
+
...projectSelectorFromArgs(args),
|
|
479
|
+
...workItemRefFromArgs(args),
|
|
480
|
+
}),
|
|
481
|
+
});
|
|
482
|
+
case "work_item_update":
|
|
483
|
+
return toolResult({
|
|
484
|
+
ok: true,
|
|
485
|
+
workItem: await workItemServiceFromArgs(args, context).updateWorkItem({
|
|
486
|
+
...projectSelectorFromArgs(args),
|
|
487
|
+
ref: workItemRefFromArgs(args),
|
|
488
|
+
patch: workItemPatchFromArgs(args),
|
|
489
|
+
}),
|
|
490
|
+
});
|
|
491
|
+
case "work_item_comment":
|
|
492
|
+
return toolResult({
|
|
493
|
+
ok: true,
|
|
494
|
+
comment: await workItemServiceFromArgs(args, context).addComment({
|
|
495
|
+
...projectSelectorFromArgs(args),
|
|
496
|
+
ref: workItemRefFromArgs(args),
|
|
497
|
+
body: requiredString(args, "body", "arguments"),
|
|
498
|
+
}),
|
|
499
|
+
});
|
|
500
|
+
case "work_item_set_status":
|
|
501
|
+
return toolResult({
|
|
502
|
+
ok: true,
|
|
503
|
+
workItem: await workItemServiceFromArgs(args, context).setStatus({
|
|
504
|
+
...projectSelectorFromArgs(args),
|
|
505
|
+
ref: workItemRefFromArgs(args),
|
|
506
|
+
status: parseWorkStatus(requiredString(args, "status", "arguments"), "arguments.status"),
|
|
507
|
+
}),
|
|
508
|
+
});
|
|
509
|
+
default:
|
|
510
|
+
return toolResult({
|
|
511
|
+
ok: false,
|
|
512
|
+
error: `Unknown DevNexus MCP tool: ${name}`,
|
|
513
|
+
}, true);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
return toolResult({
|
|
518
|
+
ok: false,
|
|
519
|
+
error: error instanceof Error ? error.message : String(error),
|
|
520
|
+
}, true);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
export async function handleDevNexusMcpJsonRpcMessage(message) {
|
|
524
|
+
switch (message.method) {
|
|
525
|
+
case "initialize":
|
|
526
|
+
return jsonRpcResult(message.id, {
|
|
527
|
+
protocolVersion: devNexusMcpProtocolVersion,
|
|
528
|
+
capabilities: {
|
|
529
|
+
tools: {},
|
|
530
|
+
},
|
|
531
|
+
serverInfo: {
|
|
532
|
+
name: "dev-nexus",
|
|
533
|
+
version: "0.1.0",
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
case "notifications/initialized":
|
|
537
|
+
return undefined;
|
|
538
|
+
case "tools/list":
|
|
539
|
+
return jsonRpcResult(message.id, {
|
|
540
|
+
tools: listDevNexusMcpTools(),
|
|
541
|
+
});
|
|
542
|
+
case "tools/call": {
|
|
543
|
+
const params = parseToolCallParams(message.params);
|
|
544
|
+
return jsonRpcResult(message.id, await callDevNexusMcpTool(params.name, params.arguments));
|
|
545
|
+
}
|
|
546
|
+
default:
|
|
547
|
+
if (message.id === undefined) {
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
return jsonRpcError(message.id, -32601, `Method not found: ${message.method}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
export async function runDevNexusMcpStdioServer() {
|
|
554
|
+
const transport = new StdioJsonRpcTransport(handleDevNexusMcpJsonRpcMessage);
|
|
555
|
+
await transport.start();
|
|
556
|
+
}
|
|
557
|
+
function workItemServiceFromArgs(args, context) {
|
|
558
|
+
const homePath = homePathFromArgs(args);
|
|
559
|
+
return createWorkItemService({
|
|
560
|
+
resolveProject: (selector) => resolveWorkItemProject(selector, homePath),
|
|
561
|
+
now: context.now,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
function targetCycleLedgerFromArgs(args) {
|
|
565
|
+
const { projectRoot, projectConfig, automationConfig } = targetCycleProjectFromArgs(args);
|
|
566
|
+
return {
|
|
567
|
+
projectRoot,
|
|
568
|
+
projectId: projectConfig.id,
|
|
569
|
+
ledger: readNexusAutomationTargetCycleLedger(projectRoot, automationConfig),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function appendTargetCycleFromArgs(args, context) {
|
|
573
|
+
const { projectRoot, projectConfig, automationConfig } = targetCycleProjectFromArgs(args);
|
|
574
|
+
const cycleId = optionalString(args, "cycleId", "arguments");
|
|
575
|
+
const ledger = appendNexusAutomationTargetCycleRecord({
|
|
576
|
+
projectRoot,
|
|
577
|
+
config: automationConfig,
|
|
578
|
+
now: context.now?.(),
|
|
579
|
+
record: {
|
|
580
|
+
...(cycleId ? { id: cycleId } : {}),
|
|
581
|
+
projectId: projectConfig.id,
|
|
582
|
+
targetId: automationConfig.target.id,
|
|
583
|
+
objective: automationConfig.target.objective,
|
|
584
|
+
runId: optionalNullableString(args, "runId", "arguments") ?? null,
|
|
585
|
+
status: parseTargetCycleStatus(requiredString(args, "status", "arguments"), "arguments.status"),
|
|
586
|
+
summary: optionalNullableString(args, "summary", "arguments") ?? null,
|
|
587
|
+
eligibleWorkItemCount: optionalNullableNonNegativeInteger(args, "eligibleWorkItemCount", "arguments"),
|
|
588
|
+
workItems: optionalTargetCycleWorkItems(args, "workItems", "arguments") ?? [],
|
|
589
|
+
blockers: optionalStringArray(args, "blockers", "arguments") ?? [],
|
|
590
|
+
notes: optionalStringArray(args, "notes", "arguments") ?? [],
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
return {
|
|
594
|
+
projectRoot,
|
|
595
|
+
projectId: projectConfig.id,
|
|
596
|
+
record: ledger.cycles.at(-1),
|
|
597
|
+
ledger,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function targetCycleProjectFromArgs(args) {
|
|
601
|
+
const projectRoot = projectRootFromArgs(args);
|
|
602
|
+
const projectConfig = loadProjectConfig(projectRoot);
|
|
603
|
+
const automationConfig = projectConfig.automation;
|
|
604
|
+
if (!automationConfig) {
|
|
605
|
+
throw new Error("Project automation is not configured");
|
|
606
|
+
}
|
|
607
|
+
return { projectRoot, projectConfig, automationConfig };
|
|
608
|
+
}
|
|
609
|
+
function resolveWorkItemProject(selector, homePath) {
|
|
610
|
+
const projectRoot = selector.projectRoot
|
|
611
|
+
? path.resolve(selector.projectRoot)
|
|
612
|
+
: getNexusProjectStatus({
|
|
613
|
+
homePath,
|
|
614
|
+
homeStore: fileProjectHomeStore(),
|
|
615
|
+
project: requiredPlainString(selector.project, "project"),
|
|
616
|
+
}).project.projectRoot;
|
|
617
|
+
const config = loadProjectConfig(projectRoot);
|
|
618
|
+
const componentId = selector.componentId;
|
|
619
|
+
const component = componentId
|
|
620
|
+
? resolveProjectComponents(projectRoot, config).find((candidate) => candidate.id === componentId)
|
|
621
|
+
: resolvePrimaryProjectComponent(projectRoot, config);
|
|
622
|
+
if (!component) {
|
|
623
|
+
throw new Error(`Project component is not configured: ${componentId}`);
|
|
624
|
+
}
|
|
625
|
+
if (!component.workTracking) {
|
|
626
|
+
throw new Error(`Component ${component.id} work tracking is not configured`);
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
homePath: config.home ?? homePath,
|
|
630
|
+
projectRoot,
|
|
631
|
+
projectId: config.id,
|
|
632
|
+
projectName: config.name,
|
|
633
|
+
componentId: component.id,
|
|
634
|
+
componentName: component.name,
|
|
635
|
+
sourceRoot: component.sourceRoot,
|
|
636
|
+
workTracking: component.workTracking,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
function projectRootFromArgs(args) {
|
|
640
|
+
const projectRoot = optionalString(args, "projectRoot", "arguments");
|
|
641
|
+
if (projectRoot) {
|
|
642
|
+
return path.resolve(projectRoot);
|
|
643
|
+
}
|
|
644
|
+
return projectStatusFromArgs(args).projectRoot;
|
|
645
|
+
}
|
|
646
|
+
function projectStatusFromArgs(args) {
|
|
647
|
+
const project = requiredString(args, "project", "arguments");
|
|
648
|
+
const homePath = optionalString(args, "homePath", "arguments");
|
|
649
|
+
if (homePath) {
|
|
650
|
+
return getNexusProjectStatus({
|
|
651
|
+
homePath,
|
|
652
|
+
homeStore: fileProjectHomeStore(),
|
|
653
|
+
project,
|
|
654
|
+
}).project;
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
return buildNexusProjectStatusForPath(project);
|
|
658
|
+
}
|
|
659
|
+
catch (pathError) {
|
|
660
|
+
try {
|
|
661
|
+
return getNexusProjectStatus({
|
|
662
|
+
homePath: defaultNexusHomePath(),
|
|
663
|
+
homeStore: fileProjectHomeStore(),
|
|
664
|
+
project,
|
|
665
|
+
}).project;
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
throw pathError;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function fileProjectHomeStore() {
|
|
673
|
+
return {
|
|
674
|
+
resolveHomePath: resolveNexusHome,
|
|
675
|
+
loadHomeConfig: (homePath) => loadNexusHomeConfigFile(homePath, validateNexusHomeConfigBase),
|
|
676
|
+
saveHomeConfig: (homePath, registry) => saveNexusHomeConfigFile(homePath, registry, validateNexusHomeConfigBase),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function homePathFromArgs(args) {
|
|
680
|
+
return optionalString(args, "homePath", "arguments") ?? defaultNexusHomePath();
|
|
681
|
+
}
|
|
682
|
+
function projectSelectorFromArgs(args) {
|
|
683
|
+
const project = optionalString(args, "project", "arguments");
|
|
684
|
+
const projectRoot = optionalString(args, "projectRoot", "arguments");
|
|
685
|
+
const componentId = optionalString(args, "componentId", "arguments");
|
|
686
|
+
return {
|
|
687
|
+
...(project ? { project } : {}),
|
|
688
|
+
...(projectRoot ? { projectRoot } : {}),
|
|
689
|
+
...(componentId ? { componentId } : {}),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function workItemRefFromArgs(args) {
|
|
693
|
+
const ref = args.ref;
|
|
694
|
+
if (ref !== undefined && ref !== null) {
|
|
695
|
+
return workItemRefFromRecord(asRecord(ref, "arguments.ref"), "arguments.ref");
|
|
696
|
+
}
|
|
697
|
+
return workItemRefFromRecord(args, "arguments");
|
|
698
|
+
}
|
|
699
|
+
function workItemRefFromRecord(record, pathName) {
|
|
700
|
+
const provider = optionalString(record, "provider", pathName);
|
|
701
|
+
const id = optionalString(record, "id", pathName);
|
|
702
|
+
const externalRef = record.externalRef;
|
|
703
|
+
return {
|
|
704
|
+
...(provider ? { provider } : {}),
|
|
705
|
+
...(id ? { id } : {}),
|
|
706
|
+
...(externalRef !== undefined && externalRef !== null
|
|
707
|
+
? {
|
|
708
|
+
externalRef: externalRefFromRecord(asRecord(externalRef, `${pathName}.externalRef`), `${pathName}.externalRef`),
|
|
709
|
+
}
|
|
710
|
+
: {}),
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function externalRefFromRecord(record, pathName) {
|
|
714
|
+
const itemNumber = optionalPositiveInteger(record, "itemNumber", pathName);
|
|
715
|
+
return {
|
|
716
|
+
provider: requiredString(record, "provider", pathName),
|
|
717
|
+
host: optionalNullableString(record, "host", pathName),
|
|
718
|
+
repositoryId: optionalNullableString(record, "repositoryId", pathName),
|
|
719
|
+
repositoryOwner: optionalNullableString(record, "repositoryOwner", pathName),
|
|
720
|
+
repositoryName: optionalNullableString(record, "repositoryName", pathName),
|
|
721
|
+
projectId: optionalNullableString(record, "projectId", pathName),
|
|
722
|
+
boardId: optionalNullableString(record, "boardId", pathName),
|
|
723
|
+
itemId: requiredString(record, "itemId", pathName),
|
|
724
|
+
itemNumber,
|
|
725
|
+
itemKey: optionalNullableString(record, "itemKey", pathName),
|
|
726
|
+
nodeId: optionalNullableString(record, "nodeId", pathName),
|
|
727
|
+
webUrl: optionalNullableString(record, "webUrl", pathName),
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
function workItemPatchFromArgs(args) {
|
|
731
|
+
const patch = {};
|
|
732
|
+
if (hasOwn(args, "title")) {
|
|
733
|
+
patch.title = requiredString(args, "title", "arguments");
|
|
734
|
+
}
|
|
735
|
+
if (hasOwn(args, "description")) {
|
|
736
|
+
patch.description = optionalNullableString(args, "description", "arguments") ?? null;
|
|
737
|
+
}
|
|
738
|
+
if (hasOwn(args, "status")) {
|
|
739
|
+
patch.status = optionalWorkStatus(args, "status", "arguments");
|
|
740
|
+
}
|
|
741
|
+
if (hasOwn(args, "labels")) {
|
|
742
|
+
patch.labels = optionalStringArray(args, "labels", "arguments") ?? [];
|
|
743
|
+
}
|
|
744
|
+
if (hasOwn(args, "assignees")) {
|
|
745
|
+
patch.assignees = optionalStringArray(args, "assignees", "arguments") ?? [];
|
|
746
|
+
}
|
|
747
|
+
if (hasOwn(args, "milestone")) {
|
|
748
|
+
patch.milestone = optionalNullableString(args, "milestone", "arguments") ?? null;
|
|
749
|
+
}
|
|
750
|
+
if (Object.keys(patch).length === 0) {
|
|
751
|
+
throw new Error("arguments must include at least one work item field to update");
|
|
752
|
+
}
|
|
753
|
+
return patch;
|
|
754
|
+
}
|
|
755
|
+
function parseToolCallParams(value) {
|
|
756
|
+
const params = asRecord(value, "params");
|
|
757
|
+
return {
|
|
758
|
+
name: requiredString(params, "name", "params"),
|
|
759
|
+
arguments: params.arguments,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function toolResult(value, isError = false) {
|
|
763
|
+
return {
|
|
764
|
+
content: [
|
|
765
|
+
{
|
|
766
|
+
type: "text",
|
|
767
|
+
text: JSON.stringify(value, null, 2),
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
...(isError ? { isError: true } : {}),
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function jsonRpcResult(id, result) {
|
|
774
|
+
return {
|
|
775
|
+
jsonrpc: "2.0",
|
|
776
|
+
id,
|
|
777
|
+
result,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function jsonRpcError(id, code, message) {
|
|
781
|
+
return {
|
|
782
|
+
jsonrpc: "2.0",
|
|
783
|
+
id,
|
|
784
|
+
error: {
|
|
785
|
+
code,
|
|
786
|
+
message,
|
|
787
|
+
},
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
class StdioJsonRpcTransport {
|
|
791
|
+
onMessage;
|
|
792
|
+
buffer = Buffer.alloc(0);
|
|
793
|
+
processing = false;
|
|
794
|
+
constructor(onMessage) {
|
|
795
|
+
this.onMessage = onMessage;
|
|
796
|
+
}
|
|
797
|
+
start() {
|
|
798
|
+
return new Promise((resolve) => {
|
|
799
|
+
process.stdin.on("data", (chunk) => {
|
|
800
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
801
|
+
void this.processBuffer().catch((error) => {
|
|
802
|
+
this.send(jsonRpcError(undefined, -32603, error instanceof Error ? error.message : String(error)));
|
|
803
|
+
});
|
|
804
|
+
});
|
|
805
|
+
process.stdin.once("end", resolve);
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
async processBuffer() {
|
|
809
|
+
if (this.processing) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
this.processing = true;
|
|
813
|
+
try {
|
|
814
|
+
while (true) {
|
|
815
|
+
const headerEnd = this.headerEndIndex();
|
|
816
|
+
if (!headerEnd) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const [endIndex, separatorLength] = headerEnd;
|
|
820
|
+
const header = this.buffer.slice(0, endIndex).toString("utf8");
|
|
821
|
+
const lengthMatch = /^Content-Length:\s*(\d+)\s*$/imu.exec(header);
|
|
822
|
+
if (!lengthMatch) {
|
|
823
|
+
throw new Error("Missing Content-Length header");
|
|
824
|
+
}
|
|
825
|
+
const contentLength = Number(lengthMatch[1]);
|
|
826
|
+
const messageStart = endIndex + separatorLength;
|
|
827
|
+
const messageEnd = messageStart + contentLength;
|
|
828
|
+
if (this.buffer.length < messageEnd) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const body = this.buffer.slice(messageStart, messageEnd).toString("utf8");
|
|
832
|
+
this.buffer = this.buffer.slice(messageEnd);
|
|
833
|
+
const response = await this.onMessage(JSON.parse(body));
|
|
834
|
+
if (response) {
|
|
835
|
+
this.send(response);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
finally {
|
|
840
|
+
this.processing = false;
|
|
841
|
+
if (this.headerEndIndex()) {
|
|
842
|
+
void this.processBuffer();
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
headerEndIndex() {
|
|
847
|
+
const crlfIndex = this.buffer.indexOf("\r\n\r\n");
|
|
848
|
+
if (crlfIndex >= 0) {
|
|
849
|
+
return [crlfIndex, 4];
|
|
850
|
+
}
|
|
851
|
+
const lfIndex = this.buffer.indexOf("\n\n");
|
|
852
|
+
return lfIndex >= 0 ? [lfIndex, 2] : undefined;
|
|
853
|
+
}
|
|
854
|
+
send(message) {
|
|
855
|
+
const body = JSON.stringify(message);
|
|
856
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function asRecord(value, pathName) {
|
|
860
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
861
|
+
throw new Error(`${pathName} must be an object`);
|
|
862
|
+
}
|
|
863
|
+
return value;
|
|
864
|
+
}
|
|
865
|
+
function optionalString(record, key, pathName) {
|
|
866
|
+
const value = record[key];
|
|
867
|
+
if (value === undefined || value === null) {
|
|
868
|
+
return undefined;
|
|
869
|
+
}
|
|
870
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
871
|
+
throw new Error(`${pathName}.${key} must be a non-empty string`);
|
|
872
|
+
}
|
|
873
|
+
return value.trim();
|
|
874
|
+
}
|
|
875
|
+
function optionalNullableString(record, key, pathName) {
|
|
876
|
+
const value = record[key];
|
|
877
|
+
if (value === undefined) {
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
if (value === null) {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
884
|
+
throw new Error(`${pathName}.${key} must be a non-empty string or null`);
|
|
885
|
+
}
|
|
886
|
+
return value.trim();
|
|
887
|
+
}
|
|
888
|
+
function optionalStringArray(record, key, pathName) {
|
|
889
|
+
const value = record[key];
|
|
890
|
+
if (value === undefined || value === null) {
|
|
891
|
+
return undefined;
|
|
892
|
+
}
|
|
893
|
+
if (!Array.isArray(value)) {
|
|
894
|
+
throw new Error(`${pathName}.${key} must be an array of strings`);
|
|
895
|
+
}
|
|
896
|
+
return value.map((item, index) => {
|
|
897
|
+
if (typeof item !== "string" || item.trim().length === 0) {
|
|
898
|
+
throw new Error(`${pathName}.${key}[${index}] must be a non-empty string`);
|
|
899
|
+
}
|
|
900
|
+
return item.trim();
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
function optionalBoolean(record, key, pathName) {
|
|
904
|
+
const value = record[key];
|
|
905
|
+
if (value === undefined || value === null) {
|
|
906
|
+
return undefined;
|
|
907
|
+
}
|
|
908
|
+
if (typeof value !== "boolean") {
|
|
909
|
+
throw new Error(`${pathName}.${key} must be a boolean`);
|
|
910
|
+
}
|
|
911
|
+
return value;
|
|
912
|
+
}
|
|
913
|
+
function optionalPositiveInteger(record, key, pathName) {
|
|
914
|
+
const value = record[key];
|
|
915
|
+
if (value === undefined || value === null) {
|
|
916
|
+
return undefined;
|
|
917
|
+
}
|
|
918
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
919
|
+
throw new Error(`${pathName}.${key} must be a positive integer`);
|
|
920
|
+
}
|
|
921
|
+
return value;
|
|
922
|
+
}
|
|
923
|
+
function requiredString(record, key, pathName) {
|
|
924
|
+
const value = optionalString(record, key, pathName);
|
|
925
|
+
if (!value) {
|
|
926
|
+
throw new Error(`${pathName}.${key} is required`);
|
|
927
|
+
}
|
|
928
|
+
return value;
|
|
929
|
+
}
|
|
930
|
+
function requiredPlainString(value, name) {
|
|
931
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
932
|
+
throw new Error(`${name} is required`);
|
|
933
|
+
}
|
|
934
|
+
return value.trim();
|
|
935
|
+
}
|
|
936
|
+
const workStatuses = new Set([
|
|
937
|
+
"todo",
|
|
938
|
+
"ready",
|
|
939
|
+
"in_progress",
|
|
940
|
+
"blocked",
|
|
941
|
+
"done",
|
|
942
|
+
"wont_do",
|
|
943
|
+
]);
|
|
944
|
+
function parseWorkStatus(value, pathName) {
|
|
945
|
+
if (!workStatuses.has(value)) {
|
|
946
|
+
throw new Error(`${pathName} must be todo, ready, in_progress, blocked, done, or wont_do`);
|
|
947
|
+
}
|
|
948
|
+
return value;
|
|
949
|
+
}
|
|
950
|
+
function optionalWorkStatus(record, key, pathName) {
|
|
951
|
+
const value = optionalString(record, key, pathName);
|
|
952
|
+
return value === undefined ? undefined : parseWorkStatus(value, `${pathName}.${key}`);
|
|
953
|
+
}
|
|
954
|
+
function optionalWorkStatusQuery(record, key, pathName) {
|
|
955
|
+
const value = record[key];
|
|
956
|
+
if (value === undefined || value === null) {
|
|
957
|
+
return undefined;
|
|
958
|
+
}
|
|
959
|
+
if (typeof value === "string") {
|
|
960
|
+
return parseWorkStatus(value, `${pathName}.${key}`);
|
|
961
|
+
}
|
|
962
|
+
if (!Array.isArray(value)) {
|
|
963
|
+
throw new Error(`${pathName}.${key} must be a status or array of statuses`);
|
|
964
|
+
}
|
|
965
|
+
return value.map((item, index) => {
|
|
966
|
+
if (typeof item !== "string") {
|
|
967
|
+
throw new Error(`${pathName}.${key}[${index}] must be a status`);
|
|
968
|
+
}
|
|
969
|
+
return parseWorkStatus(item, `${pathName}.${key}[${index}]`);
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
function parseTargetCycleStatus(value, pathName) {
|
|
973
|
+
if (value === "started" ||
|
|
974
|
+
value === "dispatched" ||
|
|
975
|
+
value === "completed" ||
|
|
976
|
+
value === "blocked" ||
|
|
977
|
+
value === "failed" ||
|
|
978
|
+
value === "skipped") {
|
|
979
|
+
return value;
|
|
980
|
+
}
|
|
981
|
+
throw new Error(`${pathName} must be a valid target cycle status`);
|
|
982
|
+
}
|
|
983
|
+
function optionalNullableNonNegativeInteger(record, key, pathName) {
|
|
984
|
+
const value = record[key];
|
|
985
|
+
if (value === undefined || value === null) {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
989
|
+
throw new Error(`${pathName}.${key} must be a non-negative integer or null`);
|
|
990
|
+
}
|
|
991
|
+
return value;
|
|
992
|
+
}
|
|
993
|
+
function optionalTargetCycleWorkItems(record, key, pathName) {
|
|
994
|
+
const value = record[key];
|
|
995
|
+
if (value === undefined || value === null) {
|
|
996
|
+
return undefined;
|
|
997
|
+
}
|
|
998
|
+
if (!Array.isArray(value)) {
|
|
999
|
+
throw new Error(`${pathName}.${key} must be an array`);
|
|
1000
|
+
}
|
|
1001
|
+
return value.map((item, index) => targetCycleWorkItemFromRecord(asRecord(item, `${pathName}.${key}[${index}]`), `${pathName}.${key}[${index}]`));
|
|
1002
|
+
}
|
|
1003
|
+
function targetCycleWorkItemFromRecord(record, pathName) {
|
|
1004
|
+
return {
|
|
1005
|
+
componentId: optionalNullableString(record, "componentId", pathName) ?? null,
|
|
1006
|
+
id: requiredString(record, "id", pathName),
|
|
1007
|
+
title: optionalNullableString(record, "title", pathName) ?? null,
|
|
1008
|
+
status: nullableWorkStatus(record, "status", pathName),
|
|
1009
|
+
cycleStatus: nullableTargetCycleWorkItemStatus(record, "cycleStatus", pathName),
|
|
1010
|
+
agentProfileId: optionalNullableString(record, "agentProfileId", pathName) ?? null,
|
|
1011
|
+
notes: optionalNullableString(record, "notes", pathName) ?? null,
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
function nullableWorkStatus(record, key, pathName) {
|
|
1015
|
+
const value = optionalString(record, key, pathName);
|
|
1016
|
+
return value === undefined ? null : parseWorkStatus(value, `${pathName}.${key}`);
|
|
1017
|
+
}
|
|
1018
|
+
function nullableTargetCycleWorkItemStatus(record, key, pathName) {
|
|
1019
|
+
const value = optionalString(record, key, pathName);
|
|
1020
|
+
if (value === undefined) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
if (value === "eligible" ||
|
|
1024
|
+
value === "selected" ||
|
|
1025
|
+
value === "dispatched" ||
|
|
1026
|
+
value === "in_progress" ||
|
|
1027
|
+
value === "completed" ||
|
|
1028
|
+
value === "blocked" ||
|
|
1029
|
+
value === "skipped") {
|
|
1030
|
+
return value;
|
|
1031
|
+
}
|
|
1032
|
+
throw new Error(`${pathName}.${key} must be a valid target cycle work item status`);
|
|
1033
|
+
}
|
|
1034
|
+
function hasOwn(record, key) {
|
|
1035
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
1036
|
+
}
|