@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,463 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export const localWorkTrackingDirectoryName = ".dev-nexus";
4
+ export const localWorkTrackingStoreFileName = "work-items.json";
5
+ export const localWorkTrackingStoreVersion = 1;
6
+ const workStatuses = new Set([
7
+ "todo",
8
+ "ready",
9
+ "in_progress",
10
+ "blocked",
11
+ "done",
12
+ "wont_do",
13
+ ]);
14
+ export class LocalWorkTrackerProviderError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = "LocalWorkTrackerProviderError";
18
+ }
19
+ }
20
+ export const localWorkTrackerCapabilities = {
21
+ createItem: true,
22
+ listItems: true,
23
+ getItem: true,
24
+ updateItem: true,
25
+ comment: true,
26
+ labels: true,
27
+ assignees: true,
28
+ milestones: true,
29
+ board: false,
30
+ boardStatus: false,
31
+ draftItems: false,
32
+ webhooks: false,
33
+ };
34
+ export function defaultLocalWorkTrackingStorePath(projectRoot) {
35
+ return path.join(resolveProjectRoot(projectRoot), localWorkTrackingDirectoryName, localWorkTrackingStoreFileName);
36
+ }
37
+ export function resolveLocalWorkTrackingStorePath(projectRoot, config) {
38
+ const configuredStorePath = typeof config === "string" || config === null
39
+ ? config
40
+ : config?.storePath;
41
+ if (!configuredStorePath) {
42
+ return defaultLocalWorkTrackingStorePath(projectRoot);
43
+ }
44
+ return path.isAbsolute(configuredStorePath)
45
+ ? path.resolve(configuredStorePath)
46
+ : path.resolve(resolveProjectRoot(projectRoot), configuredStorePath);
47
+ }
48
+ export function createLocalWorkTrackerProvider(options = {}) {
49
+ return new LocalWorkTrackerProvider(options);
50
+ }
51
+ export class LocalWorkTrackerProvider {
52
+ provider = "local";
53
+ capabilities = localWorkTrackerCapabilities;
54
+ projectRoot;
55
+ config;
56
+ storePath;
57
+ nowProvider;
58
+ constructor(options = {}) {
59
+ this.projectRoot = options.projectRoot;
60
+ this.config = options.config;
61
+ this.storePath = options.storePath;
62
+ this.nowProvider = options.now ?? (() => new Date());
63
+ }
64
+ async detect(input) {
65
+ const storePath = this.storePathFor(input.projectRoot);
66
+ if (!fs.existsSync(storePath)) {
67
+ return undefined;
68
+ }
69
+ return {
70
+ confidence: "high",
71
+ config: {
72
+ provider: "local",
73
+ storePath: path.relative(resolveProjectRoot(input.projectRoot), storePath),
74
+ },
75
+ reason: "Found local DevNexus work item store",
76
+ };
77
+ }
78
+ async ensureProject(context) {
79
+ const projectRoot = this.resolveProjectRoot(context.projectRoot);
80
+ const store = this.loadStore(projectRoot);
81
+ this.saveStore(projectRoot, store);
82
+ return {
83
+ provider: "local",
84
+ id: context.projectId,
85
+ name: context.projectName,
86
+ externalRef: {
87
+ provider: "local",
88
+ itemId: context.projectId,
89
+ projectId: context.projectId,
90
+ },
91
+ };
92
+ }
93
+ async createWorkItem(input) {
94
+ const projectRoot = this.resolveProjectRoot(input.projectRoot);
95
+ const store = this.loadStore(projectRoot);
96
+ const number = store.nextNumber;
97
+ const id = `local-${number}`;
98
+ const timestamp = this.now();
99
+ const status = input.status ?? "todo";
100
+ assertWorkStatus(status);
101
+ const item = {
102
+ id,
103
+ title: requiredNonEmptyString(input.title, "title"),
104
+ description: input.description ?? null,
105
+ status,
106
+ provider: "local",
107
+ labels: normalizeStringArray(input.labels, "labels"),
108
+ assignees: normalizeStringArray(input.assignees, "assignees"),
109
+ milestone: optionalNullableString(input.milestone, "milestone") ?? null,
110
+ createdAt: timestamp,
111
+ updatedAt: timestamp,
112
+ closedAt: isClosedStatus(status) ? timestamp : null,
113
+ webUrl: null,
114
+ externalRef: {
115
+ provider: "local",
116
+ itemId: id,
117
+ itemNumber: number,
118
+ },
119
+ };
120
+ store.items.push(item);
121
+ store.comments[item.id] = [];
122
+ store.nextNumber += 1;
123
+ this.saveStore(projectRoot, store);
124
+ return item;
125
+ }
126
+ async listWorkItems(query = {}) {
127
+ const projectRoot = this.resolveProjectRoot(query.projectRoot);
128
+ const statuses = query.status === undefined
129
+ ? undefined
130
+ : new Set(Array.isArray(query.status) ? query.status : [query.status]);
131
+ if (statuses) {
132
+ for (const status of statuses) {
133
+ assertWorkStatus(status);
134
+ }
135
+ }
136
+ const labels = normalizeStringArray(query.labels, "labels");
137
+ const assignees = normalizeStringArray(query.assignees, "assignees");
138
+ const search = query.search?.trim().toLowerCase();
139
+ const limit = normalizeLimit(query.limit);
140
+ let items = this.loadStore(projectRoot).items.filter((item) => {
141
+ if (statuses && !statuses.has(item.status)) {
142
+ return false;
143
+ }
144
+ if (labels.some((label) => !item.labels?.includes(label))) {
145
+ return false;
146
+ }
147
+ if (assignees.some((assignee) => !item.assignees?.includes(assignee))) {
148
+ return false;
149
+ }
150
+ if (search && !matchesSearch(item, search)) {
151
+ return false;
152
+ }
153
+ return true;
154
+ });
155
+ if (limit !== undefined) {
156
+ items = items.slice(0, limit);
157
+ }
158
+ return items;
159
+ }
160
+ async getWorkItem(ref) {
161
+ const store = this.loadStore(this.resolveProjectRoot());
162
+ return findWorkItem(store, ref);
163
+ }
164
+ async updateWorkItem(ref, patch) {
165
+ const projectRoot = this.resolveProjectRoot();
166
+ const store = this.loadStore(projectRoot);
167
+ const item = findWorkItem(store, ref);
168
+ const timestamp = this.now();
169
+ const updated = {
170
+ ...item,
171
+ updatedAt: timestamp,
172
+ };
173
+ if (patch.title !== undefined) {
174
+ updated.title = requiredNonEmptyString(patch.title, "title");
175
+ }
176
+ if (patch.description !== undefined) {
177
+ updated.description = patch.description;
178
+ }
179
+ if (patch.status !== undefined) {
180
+ assertWorkStatus(patch.status);
181
+ updated.status = patch.status;
182
+ updated.closedAt = isClosedStatus(patch.status)
183
+ ? item.closedAt ?? timestamp
184
+ : null;
185
+ }
186
+ if (patch.labels !== undefined) {
187
+ updated.labels = normalizeStringArray(patch.labels, "labels");
188
+ }
189
+ if (patch.assignees !== undefined) {
190
+ updated.assignees = normalizeStringArray(patch.assignees, "assignees");
191
+ }
192
+ if (patch.milestone !== undefined) {
193
+ updated.milestone = optionalNullableString(patch.milestone, "milestone");
194
+ }
195
+ store.items[store.items.indexOf(item)] = updated;
196
+ this.saveStore(projectRoot, store);
197
+ return updated;
198
+ }
199
+ async addComment(ref, body) {
200
+ const projectRoot = this.resolveProjectRoot();
201
+ const store = this.loadStore(projectRoot);
202
+ const item = findWorkItem(store, ref);
203
+ const timestamp = this.now();
204
+ const id = `local-comment-${store.nextCommentNumber}`;
205
+ const comment = {
206
+ id,
207
+ body: requiredNonEmptyString(body, "body"),
208
+ author: null,
209
+ createdAt: timestamp,
210
+ updatedAt: timestamp,
211
+ externalRef: {
212
+ provider: "local",
213
+ itemId: id,
214
+ },
215
+ };
216
+ store.nextCommentNumber += 1;
217
+ store.comments[item.id] = [...(store.comments[item.id] ?? []), comment];
218
+ item.updatedAt = timestamp;
219
+ this.saveStore(projectRoot, store);
220
+ return comment;
221
+ }
222
+ async setStatus(ref, status) {
223
+ return this.updateWorkItem(ref, { status });
224
+ }
225
+ resolveProjectRoot(projectRoot) {
226
+ return resolveProjectRoot(projectRoot ?? this.projectRoot);
227
+ }
228
+ storePathFor(projectRoot) {
229
+ return resolveLocalWorkTrackingStorePath(projectRoot, this.storePath ?? this.config);
230
+ }
231
+ loadStore(projectRoot) {
232
+ return loadLocalWorkTrackingStore(this.storePathFor(projectRoot), this.now());
233
+ }
234
+ saveStore(projectRoot, store) {
235
+ saveLocalWorkTrackingStore(this.storePathFor(projectRoot), {
236
+ ...store,
237
+ updatedAt: this.now(),
238
+ });
239
+ }
240
+ now() {
241
+ const value = this.nowProvider();
242
+ return typeof value === "string" ? value : value.toISOString();
243
+ }
244
+ }
245
+ export function loadLocalWorkTrackingStore(storePath, timestamp = new Date().toISOString()) {
246
+ if (!fs.existsSync(storePath)) {
247
+ return emptyStore(timestamp);
248
+ }
249
+ const raw = JSON.parse(fs.readFileSync(storePath, "utf8").replace(/^\uFEFF/, ""));
250
+ return validateStore(raw);
251
+ }
252
+ export function saveLocalWorkTrackingStore(storePath, store) {
253
+ fs.mkdirSync(path.dirname(storePath), { recursive: true });
254
+ fs.writeFileSync(storePath, `${JSON.stringify(validateStore(store), null, 2)}\n`, "utf8");
255
+ }
256
+ function emptyStore(timestamp) {
257
+ return {
258
+ version: localWorkTrackingStoreVersion,
259
+ nextNumber: 1,
260
+ nextCommentNumber: 1,
261
+ updatedAt: timestamp,
262
+ items: [],
263
+ comments: {},
264
+ };
265
+ }
266
+ function resolveProjectRoot(projectRoot) {
267
+ if (!projectRoot || projectRoot.trim().length === 0) {
268
+ throw new LocalWorkTrackerProviderError("projectRoot is required");
269
+ }
270
+ return path.resolve(projectRoot);
271
+ }
272
+ function validateStore(value) {
273
+ const record = assertRecord(value, "local work tracking store");
274
+ if (record.version !== localWorkTrackingStoreVersion) {
275
+ throw new LocalWorkTrackerProviderError(`local work tracking store.version must be ${localWorkTrackingStoreVersion}`);
276
+ }
277
+ const items = record.items;
278
+ if (!Array.isArray(items)) {
279
+ throw new LocalWorkTrackerProviderError("local work tracking store.items must be an array");
280
+ }
281
+ return {
282
+ version: localWorkTrackingStoreVersion,
283
+ nextNumber: positiveInteger(record.nextNumber, "nextNumber"),
284
+ nextCommentNumber: positiveInteger(record.nextCommentNumber, "nextCommentNumber"),
285
+ updatedAt: requiredNonEmptyString(record.updatedAt, "updatedAt"),
286
+ items: items.map(validateWorkItem),
287
+ comments: validateComments(record.comments),
288
+ };
289
+ }
290
+ function validateWorkItem(value, index) {
291
+ const record = assertRecord(value, `items[${index}]`);
292
+ if (record.provider !== "local") {
293
+ throw new LocalWorkTrackerProviderError(`items[${index}].provider must be local`);
294
+ }
295
+ const status = workStatus(record.status, `items[${index}].status`);
296
+ return {
297
+ id: requiredNonEmptyString(record.id, `items[${index}].id`),
298
+ title: requiredNonEmptyString(record.title, `items[${index}].title`),
299
+ description: optionalNullableText(record.description, `items[${index}].description`) ??
300
+ null,
301
+ status,
302
+ provider: "local",
303
+ labels: stringArray(record.labels, `items[${index}].labels`),
304
+ assignees: stringArray(record.assignees, `items[${index}].assignees`),
305
+ milestone: optionalNullableString(record.milestone, `items[${index}].milestone`) ??
306
+ null,
307
+ createdAt: optionalNullableString(record.createdAt, `items[${index}].createdAt`) ??
308
+ null,
309
+ updatedAt: optionalNullableString(record.updatedAt, `items[${index}].updatedAt`) ??
310
+ null,
311
+ closedAt: optionalNullableString(record.closedAt, `items[${index}].closedAt`) ??
312
+ null,
313
+ webUrl: optionalNullableString(record.webUrl, `items[${index}].webUrl`) ?? null,
314
+ externalRef: {
315
+ provider: "local",
316
+ itemId: requiredNonEmptyString(record.id, `items[${index}].id`),
317
+ itemNumber: localItemNumber(record),
318
+ },
319
+ };
320
+ }
321
+ function validateComments(value) {
322
+ if (value === undefined) {
323
+ return {};
324
+ }
325
+ const record = assertRecord(value, "comments");
326
+ const comments = {};
327
+ for (const [itemId, itemComments] of Object.entries(record)) {
328
+ if (!Array.isArray(itemComments)) {
329
+ throw new LocalWorkTrackerProviderError(`comments.${itemId} must be an array`);
330
+ }
331
+ comments[itemId] = itemComments.map((comment, index) => validateComment(comment, `comments.${itemId}[${index}]`));
332
+ }
333
+ return comments;
334
+ }
335
+ function validateComment(value, pathName) {
336
+ const record = assertRecord(value, pathName);
337
+ return {
338
+ id: requiredNonEmptyString(record.id, `${pathName}.id`),
339
+ body: requiredNonEmptyString(record.body, `${pathName}.body`),
340
+ author: optionalNullableString(record.author, `${pathName}.author`) ?? null,
341
+ createdAt: optionalNullableString(record.createdAt, `${pathName}.createdAt`) ?? null,
342
+ updatedAt: optionalNullableString(record.updatedAt, `${pathName}.updatedAt`) ?? null,
343
+ externalRef: {
344
+ provider: "local",
345
+ itemId: requiredNonEmptyString(record.id, `${pathName}.id`),
346
+ },
347
+ };
348
+ }
349
+ function findWorkItem(store, ref) {
350
+ if (ref.provider && ref.provider !== "local") {
351
+ throw new LocalWorkTrackerProviderError(`local provider cannot resolve ${ref.provider} work item refs`);
352
+ }
353
+ if (ref.externalRef?.provider && ref.externalRef.provider !== "local") {
354
+ throw new LocalWorkTrackerProviderError(`local provider cannot resolve ${ref.externalRef.provider} external refs`);
355
+ }
356
+ const id = ref.id ?? ref.externalRef?.itemId;
357
+ if (!id) {
358
+ throw new LocalWorkTrackerProviderError("work item id is required");
359
+ }
360
+ const item = store.items.find((candidate) => candidate.id === id);
361
+ if (!item) {
362
+ throw new LocalWorkTrackerProviderError(`Local work item not found: ${id}`);
363
+ }
364
+ return item;
365
+ }
366
+ function matchesSearch(item, search) {
367
+ return [item.id, item.title, item.description ?? ""].some((value) => value.toLowerCase().includes(search));
368
+ }
369
+ function localItemNumber(record) {
370
+ const externalRef = record.externalRef && typeof record.externalRef === "object"
371
+ ? record.externalRef
372
+ : undefined;
373
+ const itemNumber = externalRef?.itemNumber;
374
+ return typeof itemNumber === "number" && Number.isInteger(itemNumber)
375
+ ? itemNumber
376
+ : null;
377
+ }
378
+ function isClosedStatus(status) {
379
+ return status === "done" || status === "wont_do";
380
+ }
381
+ function assertWorkStatus(status) {
382
+ if (!workStatuses.has(status)) {
383
+ throw new LocalWorkTrackerProviderError(`Invalid work status: ${status}`);
384
+ }
385
+ }
386
+ function workStatus(value, pathName) {
387
+ if (typeof value !== "string" || !workStatuses.has(value)) {
388
+ throw new LocalWorkTrackerProviderError(`${pathName} must be a valid status`);
389
+ }
390
+ return value;
391
+ }
392
+ function normalizeLimit(limit) {
393
+ if (limit === undefined) {
394
+ return undefined;
395
+ }
396
+ if (!Number.isInteger(limit) || limit < 0) {
397
+ throw new LocalWorkTrackerProviderError("limit must be a non-negative integer");
398
+ }
399
+ return limit;
400
+ }
401
+ function normalizeStringArray(values, pathName) {
402
+ if (values === undefined) {
403
+ return [];
404
+ }
405
+ return stringArray(values, pathName);
406
+ }
407
+ function stringArray(value, pathName) {
408
+ if (value === undefined) {
409
+ return [];
410
+ }
411
+ if (!Array.isArray(value)) {
412
+ throw new LocalWorkTrackerProviderError(`${pathName} must be an array`);
413
+ }
414
+ const seen = new Set();
415
+ const result = [];
416
+ for (const item of value) {
417
+ const normalized = requiredNonEmptyString(item, pathName);
418
+ if (!seen.has(normalized)) {
419
+ seen.add(normalized);
420
+ result.push(normalized);
421
+ }
422
+ }
423
+ return result;
424
+ }
425
+ function optionalNullableString(value, pathName) {
426
+ if (value === undefined) {
427
+ return undefined;
428
+ }
429
+ if (value === null) {
430
+ return null;
431
+ }
432
+ return requiredNonEmptyString(value, pathName);
433
+ }
434
+ function optionalNullableText(value, pathName) {
435
+ if (value === undefined) {
436
+ return undefined;
437
+ }
438
+ if (value === null) {
439
+ return null;
440
+ }
441
+ if (typeof value !== "string") {
442
+ throw new LocalWorkTrackerProviderError(`${pathName} must be a string`);
443
+ }
444
+ return value;
445
+ }
446
+ function requiredNonEmptyString(value, pathName) {
447
+ if (typeof value !== "string" || value.trim().length === 0) {
448
+ throw new LocalWorkTrackerProviderError(`${pathName} must be a non-empty string`);
449
+ }
450
+ return value.trim();
451
+ }
452
+ function positiveInteger(value, pathName) {
453
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
454
+ throw new LocalWorkTrackerProviderError(`${pathName} must be a positive integer`);
455
+ }
456
+ return value;
457
+ }
458
+ function assertRecord(value, pathName) {
459
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
460
+ throw new LocalWorkTrackerProviderError(`${pathName} must be an object`);
461
+ }
462
+ return value;
463
+ }
@@ -0,0 +1,21 @@
1
+ import { type GitHubWorkTrackerProviderOptions } from "./workTrackingGitHubProvider.js";
2
+ import { type GitLabWorkTrackerProviderOptions } from "./workTrackingGitLabProvider.js";
3
+ import { type JiraWorkTrackerProviderOptions } from "./workTrackingJiraProvider.js";
4
+ import { type VibeWorkTrackerProviderOptions } from "./workTrackingVibeProvider.js";
5
+ import type { WorkTrackerCapabilityName, WorkTrackerCapabilityReport, WorkTrackingConfig, WorkTrackerProvider, TrackerCapabilities } from "./workTrackingTypes.js";
6
+ export interface CreateWorkTrackerProviderOptions {
7
+ projectRoot?: string;
8
+ now?: () => Date | string;
9
+ github?: Omit<GitHubWorkTrackerProviderOptions, "config">;
10
+ gitlab?: Omit<GitLabWorkTrackerProviderOptions, "config">;
11
+ jira?: Omit<JiraWorkTrackerProviderOptions, "config">;
12
+ vibeKanban?: Omit<VibeWorkTrackerProviderOptions, "config">;
13
+ }
14
+ export declare class WorkTrackingProviderServiceError extends Error {
15
+ constructor(message: string);
16
+ }
17
+ export declare function createWorkTrackerProvider(config: WorkTrackingConfig, options?: CreateWorkTrackerProviderOptions): WorkTrackerProvider;
18
+ export declare function workTrackerCapabilityReportForConfig(config: WorkTrackingConfig): WorkTrackerCapabilityReport;
19
+ export declare function workTrackerCapabilityReport(provider: Pick<WorkTrackerProvider, "provider" | "capabilities">): WorkTrackerCapabilityReport;
20
+ export declare function assertWorkTrackerCapability(provider: Pick<WorkTrackerProvider, "provider" | "capabilities">, capability: WorkTrackerCapabilityName, operation: string): void;
21
+ export declare function workTrackerCapabilitiesForConfig(config: WorkTrackingConfig): TrackerCapabilities;
@@ -0,0 +1,117 @@
1
+ import { createGitHubWorkTrackerProvider, githubWorkTrackerCapabilitiesForConfig, } from "./workTrackingGitHubProvider.js";
2
+ import { createGitLabWorkTrackerProvider, gitLabWorkTrackerCapabilities, } from "./workTrackingGitLabProvider.js";
3
+ import { createJiraWorkTrackerProvider, jiraWorkTrackerCapabilitiesForConfig, } from "./workTrackingJiraProvider.js";
4
+ import { createLocalWorkTrackerProvider, localWorkTrackerCapabilities, } from "./workTrackingLocalProvider.js";
5
+ import { createVibeWorkTrackerProvider, vibeWorkTrackerCapabilities, } from "./workTrackingVibeProvider.js";
6
+ export class WorkTrackingProviderServiceError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "WorkTrackingProviderServiceError";
10
+ }
11
+ }
12
+ const workTrackerCapabilityNames = [
13
+ "create",
14
+ "list",
15
+ "get",
16
+ "update",
17
+ "comment",
18
+ "labels",
19
+ "assignees",
20
+ "milestones",
21
+ "board",
22
+ "boardStatus",
23
+ ];
24
+ export function createWorkTrackerProvider(config, options = {}) {
25
+ const providerName = config.provider;
26
+ if (config.provider === "local") {
27
+ return createLocalWorkTrackerProvider({
28
+ projectRoot: options.projectRoot,
29
+ config,
30
+ now: options.now,
31
+ });
32
+ }
33
+ if (config.provider === "vibe-kanban") {
34
+ if (!options.vibeKanban) {
35
+ throw new WorkTrackingProviderServiceError("Vibe Kanban provider requires Vibe Kanban API options");
36
+ }
37
+ return createVibeWorkTrackerProvider({
38
+ ...options.vibeKanban,
39
+ config: config,
40
+ });
41
+ }
42
+ if (config.provider === "github") {
43
+ return createGitHubWorkTrackerProvider({
44
+ ...options.github,
45
+ config: config,
46
+ });
47
+ }
48
+ if (config.provider === "gitlab") {
49
+ return createGitLabWorkTrackerProvider({
50
+ ...options.gitlab,
51
+ config: config,
52
+ });
53
+ }
54
+ if (config.provider === "jira") {
55
+ return createJiraWorkTrackerProvider({
56
+ ...options.jira,
57
+ config: config,
58
+ });
59
+ }
60
+ throw new WorkTrackingProviderServiceError(`Work tracking provider is not available in DevNexus core: ${providerName}`);
61
+ }
62
+ export function workTrackerCapabilityReportForConfig(config) {
63
+ return workTrackerCapabilityReportFromCapabilities(config.provider, workTrackerCapabilitiesForConfig(config));
64
+ }
65
+ export function workTrackerCapabilityReport(provider) {
66
+ return workTrackerCapabilityReportFromCapabilities(provider.provider, provider.capabilities);
67
+ }
68
+ export function assertWorkTrackerCapability(provider, capability, operation) {
69
+ const report = workTrackerCapabilityReport(provider);
70
+ if (report.capabilities[capability]) {
71
+ return;
72
+ }
73
+ throw new WorkTrackingProviderServiceError(`Work tracking provider "${provider.provider}" cannot ${operation}; ` +
74
+ `required capability "${capability}" is disabled`);
75
+ }
76
+ export function workTrackerCapabilitiesForConfig(config) {
77
+ const providerName = config.provider;
78
+ if (config.provider === "local") {
79
+ return localWorkTrackerCapabilities;
80
+ }
81
+ if (config.provider === "vibe-kanban") {
82
+ return vibeWorkTrackerCapabilities;
83
+ }
84
+ if (config.provider === "github") {
85
+ return githubWorkTrackerCapabilitiesForConfig(config);
86
+ }
87
+ if (config.provider === "gitlab") {
88
+ return gitLabWorkTrackerCapabilities;
89
+ }
90
+ if (config.provider === "jira") {
91
+ return jiraWorkTrackerCapabilitiesForConfig(config);
92
+ }
93
+ throw new WorkTrackingProviderServiceError(`Work tracking provider is not available in DevNexus core: ${providerName}`);
94
+ }
95
+ function workTrackerCapabilityReportFromCapabilities(provider, capabilities) {
96
+ const actionCapabilities = workTrackerActionCapabilities(capabilities);
97
+ return {
98
+ provider,
99
+ capabilities: actionCapabilities,
100
+ unsupported: workTrackerCapabilityNames.filter((capability) => !actionCapabilities[capability]),
101
+ };
102
+ }
103
+ function workTrackerActionCapabilities(capabilities) {
104
+ const board = capabilities.board;
105
+ return {
106
+ create: capabilities.createItem,
107
+ list: capabilities.listItems,
108
+ get: capabilities.getItem,
109
+ update: capabilities.updateItem,
110
+ comment: capabilities.comment,
111
+ labels: capabilities.labels,
112
+ assignees: capabilities.assignees,
113
+ milestones: capabilities.milestones,
114
+ board,
115
+ boardStatus: board && capabilities.boardStatus,
116
+ };
117
+ }