@forloop-cc/forloop-opencode-plugin-planner 2.2.12 → 2.2.13
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@forloop-cc/forloop-opencode-plugin-planner",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.13",
|
|
5
5
|
"description": "ForLoop integration plugin for opencode — Sprint, story, and agent tools for the ForLoop platform",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -23,6 +23,9 @@ interface ClientConfig {
|
|
|
23
23
|
export interface SprintOptions {
|
|
24
24
|
includeStories?: boolean;
|
|
25
25
|
includeFiles?: boolean;
|
|
26
|
+
allStories?: boolean;
|
|
27
|
+
subSprintId?: number;
|
|
28
|
+
includeSubSprints?: boolean;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export interface ListSprintsParams {
|
|
@@ -259,6 +262,9 @@ export class ForLoopAPIClient {
|
|
|
259
262
|
includeStories: options?.includeStories !== false ? 'true' : 'false',
|
|
260
263
|
includeFiles: options?.includeFiles !== false ? 'true' : 'false',
|
|
261
264
|
});
|
|
265
|
+
if (options?.allStories) query.set('allStories', 'true');
|
|
266
|
+
if (options?.subSprintId) query.set('subSprintId', String(options.subSprintId));
|
|
267
|
+
if (options?.includeSubSprints) query.set('includeSubSprints', 'true');
|
|
262
268
|
return this.request(`/api/opencode/sprints/${id}?${query}`);
|
|
263
269
|
}
|
|
264
270
|
|
|
@@ -309,6 +315,39 @@ export class ForLoopAPIClient {
|
|
|
309
315
|
});
|
|
310
316
|
}
|
|
311
317
|
|
|
318
|
+
async getSubSprints(sprintId: number): Promise<any[]> {
|
|
319
|
+
return this.request(`/api/opencode/sprints/${sprintId}/sub-sprints`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async createSubSprint(sprintId: number, data: {
|
|
323
|
+
title?: string;
|
|
324
|
+
startDate: string;
|
|
325
|
+
endDate: string;
|
|
326
|
+
}): Promise<any> {
|
|
327
|
+
return this.request(`/api/opencode/sprints/${sprintId}/sub-sprints`, {
|
|
328
|
+
method: 'POST',
|
|
329
|
+
body: JSON.stringify(data),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async updateSubSprint(subSprintId: number, data: Partial<{
|
|
334
|
+
title: string;
|
|
335
|
+
startDate: string;
|
|
336
|
+
endDate: string;
|
|
337
|
+
status: 'planned' | 'in_progress' | 'completed';
|
|
338
|
+
}>): Promise<any> {
|
|
339
|
+
return this.request(`/api/opencode/sub-sprints/${subSprintId}`, {
|
|
340
|
+
method: 'PUT',
|
|
341
|
+
body: JSON.stringify(data),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async deleteSubSprint(subSprintId: number): Promise<void> {
|
|
346
|
+
await this.request(`/api/opencode/sub-sprints/${subSprintId}`, {
|
|
347
|
+
method: 'DELETE',
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
312
351
|
async addStoryComment(storyId: number, body: string, authorAgentKey?: string): Promise<any> {
|
|
313
352
|
const payload: Record<string, unknown> = { body };
|
|
314
353
|
if (authorAgentKey) payload.authorAgentKey = authorAgentKey;
|
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
createSprintCreateTool,
|
|
8
8
|
createSprintUpdateTool,
|
|
9
9
|
createSprintDeleteTool,
|
|
10
|
+
createSubSprintListTool,
|
|
11
|
+
createSubSprintCreateTool,
|
|
12
|
+
createSubSprintUpdateTool,
|
|
13
|
+
createSubSprintDeleteTool,
|
|
10
14
|
} from './tools/sprint-tools';
|
|
11
15
|
import {
|
|
12
16
|
createStoryCreateTool,
|
|
@@ -102,6 +106,12 @@ export default async (ctx: any) => {
|
|
|
102
106
|
'forloopSprintUpdate': createSprintUpdateTool(client),
|
|
103
107
|
'forloopSprintDelete': createSprintDeleteTool(client),
|
|
104
108
|
|
|
109
|
+
// Sub-Sprint (Iteration) Operations
|
|
110
|
+
'forloopSubSprintList' : createSubSprintListTool(client),
|
|
111
|
+
'forloopSubSprintCreate' : createSubSprintCreateTool(client),
|
|
112
|
+
'forloopSubSprintUpdate' : createSubSprintUpdateTool(client),
|
|
113
|
+
'forloopSubSprintDelete' : createSubSprintDeleteTool(client),
|
|
114
|
+
|
|
105
115
|
// Story Operations
|
|
106
116
|
'forloopStoryCreate': createStoryCreateTool(client),
|
|
107
117
|
'forloopStoryGet': createStoryGetTool(client),
|
|
@@ -17,6 +17,9 @@ export function createSprintGetTool(client: ForLoopAPIClient) {
|
|
|
17
17
|
includeFiles: tool.schema.boolean()
|
|
18
18
|
.default(true)
|
|
19
19
|
.describe('Include files in response'),
|
|
20
|
+
includeSubSprints: tool.schema.boolean()
|
|
21
|
+
.default(true)
|
|
22
|
+
.describe('Include sub-sprints array in response'),
|
|
20
23
|
},
|
|
21
24
|
async execute(args, context) {
|
|
22
25
|
const tokenResult = await validateToken();
|
|
@@ -42,6 +45,7 @@ export function createSprintGetTool(client: ForLoopAPIClient) {
|
|
|
42
45
|
const sprint = await client.getSprint(resolution.sprintId, {
|
|
43
46
|
includeStories: args.includeStories,
|
|
44
47
|
includeFiles: args.includeFiles,
|
|
48
|
+
includeSubSprints: args.includeSubSprints,
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
const lines = [
|
|
@@ -288,3 +292,145 @@ export function createSprintDeleteTool(client: ForLoopAPIClient) {
|
|
|
288
292
|
},
|
|
289
293
|
});
|
|
290
294
|
}
|
|
295
|
+
|
|
296
|
+
export function createSubSprintListTool(client: ForLoopAPIClient) {
|
|
297
|
+
return tool({
|
|
298
|
+
description: 'List all iterations (sub-sprints) within a sprint',
|
|
299
|
+
args: {
|
|
300
|
+
sprintId: tool.schema.number()
|
|
301
|
+
.optional()
|
|
302
|
+
.describe('Sprint ID (defaults to FORLOOP_SPRINT_ID env or current branch)'),
|
|
303
|
+
},
|
|
304
|
+
async execute(args, context) {
|
|
305
|
+
const tokenResult = await validateToken()
|
|
306
|
+
if (!tokenResult.valid) return tokenResult.error
|
|
307
|
+
client.setToken?.(tokenResult.token)
|
|
308
|
+
|
|
309
|
+
const resolution = await resolveSprintId(args.sprintId, context.directory)
|
|
310
|
+
if (!resolution.sprintId) {
|
|
311
|
+
return 'No sprint ID resolved. Use --sprintId, set FORLOOP_SPRINT_ID, or use a sprint-XXX branch.'
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const subSprints = await client.getSubSprints(resolution.sprintId)
|
|
315
|
+
if (subSprints.length === 0) {
|
|
316
|
+
return `Sprint #${resolution.sprintId} has no iterations yet.`
|
|
317
|
+
}
|
|
318
|
+
const lines = [`Iterations for sprint #${resolution.sprintId}:`, '']
|
|
319
|
+
for (const s of subSprints) {
|
|
320
|
+
const active = s.status === 'in_progress' ? ' ▶ ACTIVE' : ''
|
|
321
|
+
lines.push(
|
|
322
|
+
` #${s.id}: ${s.title} | ${formatDate(s.startDate)} – ${formatDate(s.endDate)} | ${s.status}${active}`
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
return lines.join('\n')
|
|
326
|
+
} catch (error: any) {
|
|
327
|
+
return `Failed to list iterations: ${error.message}`
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function createSubSprintCreateTool(client: ForLoopAPIClient) {
|
|
334
|
+
return tool({
|
|
335
|
+
description: 'Create a new iteration (sub-sprint). The previously active iteration is auto-completed.',
|
|
336
|
+
args: {
|
|
337
|
+
sprintId: tool.schema.number()
|
|
338
|
+
.optional()
|
|
339
|
+
.describe('Sprint ID (defaults to FORLOOP_SPRINT_ID env or current branch)'),
|
|
340
|
+
title: tool.schema.string()
|
|
341
|
+
.optional()
|
|
342
|
+
.describe('Iteration title (auto-generates as "Sprint Title — Iteration N" if not provided)'),
|
|
343
|
+
startDate: tool.schema.string()
|
|
344
|
+
.describe('Start date (YYYY-MM-DD)'),
|
|
345
|
+
endDate: tool.schema.string()
|
|
346
|
+
.describe('End date (YYYY-MM-DD)'),
|
|
347
|
+
},
|
|
348
|
+
async execute(args, context) {
|
|
349
|
+
const tokenResult = await validateToken()
|
|
350
|
+
if (!tokenResult.valid) return tokenResult.error
|
|
351
|
+
client.setToken?.(tokenResult.token)
|
|
352
|
+
|
|
353
|
+
const resolution = await resolveSprintId(args.sprintId, context.directory)
|
|
354
|
+
if (!resolution.sprintId) {
|
|
355
|
+
return 'No sprint ID resolved. Use --sprintId, set FORLOOP_SPRINT_ID, or use a sprint-XXX branch.'
|
|
356
|
+
}
|
|
357
|
+
if (!args.startDate || !args.endDate) {
|
|
358
|
+
return 'startDate and endDate are required (YYYY-MM-DD format).'
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const subSprint = await client.createSubSprint(resolution.sprintId, {
|
|
362
|
+
title: args.title,
|
|
363
|
+
startDate: args.startDate,
|
|
364
|
+
endDate: args.endDate,
|
|
365
|
+
})
|
|
366
|
+
return `Iteration created: #${subSprint.id} "${subSprint.title}" (${formatDate(subSprint.startDate)} – ${formatDate(subSprint.endDate)}) status: ${subSprint.status}`
|
|
367
|
+
} catch (error: any) {
|
|
368
|
+
return `Failed to create iteration: ${error.message}`
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function createSubSprintUpdateTool(client: ForLoopAPIClient) {
|
|
375
|
+
return tool({
|
|
376
|
+
description: 'Update an iteration (sub-sprint) title, dates, or status. Activating an iteration (status: in_progress) auto-completes the previous one.',
|
|
377
|
+
args: {
|
|
378
|
+
subSprintId: tool.schema.number()
|
|
379
|
+
.describe('Sub-sprint ID to update'),
|
|
380
|
+
title: tool.schema.string()
|
|
381
|
+
.optional()
|
|
382
|
+
.describe('New iteration title'),
|
|
383
|
+
startDate: tool.schema.string()
|
|
384
|
+
.optional()
|
|
385
|
+
.describe('New start date (YYYY-MM-DD)'),
|
|
386
|
+
endDate: tool.schema.string()
|
|
387
|
+
.optional()
|
|
388
|
+
.describe('New end date (YYYY-MM-DD)'),
|
|
389
|
+
status: tool.schema.string()
|
|
390
|
+
.optional()
|
|
391
|
+
.describe('New status: planned, in_progress, or completed'),
|
|
392
|
+
},
|
|
393
|
+
async execute(args, context) {
|
|
394
|
+
const tokenResult = await validateToken()
|
|
395
|
+
if (!tokenResult.valid) return tokenResult.error
|
|
396
|
+
client.setToken?.(tokenResult.token)
|
|
397
|
+
|
|
398
|
+
if (!args.subSprintId) return 'subSprintId is required.'
|
|
399
|
+
try {
|
|
400
|
+
const updateData: Record<string, unknown> = {}
|
|
401
|
+
if (args.title !== undefined) updateData.title = args.title
|
|
402
|
+
if (args.startDate !== undefined) updateData.startDate = args.startDate
|
|
403
|
+
if (args.endDate !== undefined) updateData.endDate = args.endDate
|
|
404
|
+
if (args.status !== undefined) updateData.status = args.status
|
|
405
|
+
if (Object.keys(updateData).length === 0) return 'No update fields provided.'
|
|
406
|
+
const updated = await client.updateSubSprint(args.subSprintId, updateData)
|
|
407
|
+
return `Iteration updated: #${updated.id} "${updated.title}" status: ${updated.status}`
|
|
408
|
+
} catch (error: any) {
|
|
409
|
+
return `Failed to update iteration: ${error.message}`
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function createSubSprintDeleteTool(client: ForLoopAPIClient) {
|
|
416
|
+
return tool({
|
|
417
|
+
description: 'Soft-delete an iteration (sub-sprint). Linked stories retain their subSprintId and will not appear in default sprint views.',
|
|
418
|
+
args: {
|
|
419
|
+
subSprintId: tool.schema.number()
|
|
420
|
+
.describe('Sub-sprint ID to delete'),
|
|
421
|
+
},
|
|
422
|
+
async execute(args, context) {
|
|
423
|
+
const tokenResult = await validateToken()
|
|
424
|
+
if (!tokenResult.valid) return tokenResult.error
|
|
425
|
+
client.setToken?.(tokenResult.token)
|
|
426
|
+
|
|
427
|
+
if (!args.subSprintId) return 'subSprintId is required.'
|
|
428
|
+
try {
|
|
429
|
+
await client.deleteSubSprint(args.subSprintId)
|
|
430
|
+
return `Iteration #${args.subSprintId} deleted.`
|
|
431
|
+
} catch (error: any) {
|
|
432
|
+
return `Failed to delete iteration: ${error.message}`
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
})
|
|
436
|
+
}
|