opencode-backlog 0.1.5 → 0.2.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 +48 -14
- package/dist/backlog.d.ts +16 -6
- package/dist/backlog.js +124 -24
- package/dist/backlog.js.map +1 -1
- package/dist/index.js +141 -30
- package/dist/index.js.map +1 -1
- package/dist/input.d.ts +2 -2
- package/dist/input.js +3 -3
- package/dist/input.js.map +1 -1
- package/dist/tui.js +544 -112
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,22 +4,30 @@ A persistent project backlog for OpenCode V2 agents and the TUI.
|
|
|
4
4
|
|
|
5
5
|
`opencode-backlog` gives the agent tools to manage project tasks. It also adds an interactive backlog to the OpenCode sidebar and command palette.
|
|
6
6
|
|
|
7
|
+
Published package: [opencode-backlog on npm](https://www.npmjs.com/package/opencode-backlog).
|
|
8
|
+
|
|
7
9
|

|
|
8
10
|
|
|
9
11
|
## What It Does
|
|
10
12
|
|
|
11
|
-
The plugin stores tasks in `BACKLOG.json` at the project root.
|
|
13
|
+
The plugin stores tasks and ordered categories in `BACKLOG.json` at the project root.
|
|
14
|
+
|
|
15
|
+
New backlogs contain these categories:
|
|
12
16
|
|
|
13
17
|
- `todo`
|
|
14
18
|
- `doing`
|
|
15
19
|
- `done`
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
You can add, rename, reorder, purge, and remove categories. Each category has a stable ID and a modifiable title.
|
|
22
|
+
|
|
23
|
+
The server plugin manages tasks and categories. The TUI plugin provides:
|
|
18
24
|
|
|
19
25
|
- A live backlog summary below the sidebar context.
|
|
20
26
|
- A `Browse backlog` command in the command palette.
|
|
21
27
|
- `/backlog` and `/tasks` slash commands.
|
|
22
|
-
- Task details and
|
|
28
|
+
- Task details and category changes from the TUI.
|
|
29
|
+
- `/backlog-purge` to remove every task from a selected category.
|
|
30
|
+
- `/backlog-categories` to manage categories.
|
|
23
31
|
|
|
24
32
|
## Requirements
|
|
25
33
|
|
|
@@ -119,35 +127,57 @@ Reopen the TUI to load the TUI plugin.
|
|
|
119
127
|
Ask the agent to manage tasks in normal language:
|
|
120
128
|
|
|
121
129
|
```text
|
|
122
|
-
Add a
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
Add a Todo task to document the release process.
|
|
131
|
+
Add a Blocked category after Doing.
|
|
132
|
+
Move the release task to Blocked.
|
|
133
|
+
Purge all tasks in Done.
|
|
126
134
|
```
|
|
127
135
|
|
|
128
|
-
Open the command palette and select `Browse backlog` to inspect tasks. Select a task to view its ID, notes, and
|
|
136
|
+
Open the command palette and select `Browse backlog` to inspect tasks. Select a task to view its ID, notes, and category.
|
|
129
137
|
|
|
130
138
|
Run `/backlog` or `/tasks` to open the same browser directly.
|
|
131
139
|
|
|
140
|
+
The backlog browser provides these shortcuts:
|
|
141
|
+
|
|
142
|
+
- `Enter` opens the selected task details.
|
|
143
|
+
- `n` creates a task.
|
|
144
|
+
- `c` changes the selected task category.
|
|
145
|
+
- `e` edits the selected task.
|
|
146
|
+
- `d` deletes the selected task after confirmation.
|
|
147
|
+
- `p` purges all tasks from a selected category after confirmation.
|
|
148
|
+
|
|
149
|
+
Click a sidebar task to open its details. The detail dialog provides `c`, `e`, and `d` for the same actions.
|
|
150
|
+
|
|
132
151
|
## Agent Tools
|
|
133
152
|
|
|
134
153
|
| Tool | Purpose |
|
|
135
154
|
| --- | --- |
|
|
136
|
-
| `backlog_list` | List
|
|
137
|
-
| `backlog_add` | Add a task at an optional
|
|
155
|
+
| `backlog_list` | List categories and tasks, with an optional category ID filter. |
|
|
156
|
+
| `backlog_add` | Add a task at an optional category and position. |
|
|
138
157
|
| `backlog_update` | Change a task title or notes. |
|
|
139
|
-
| `backlog_move` | Change a task
|
|
158
|
+
| `backlog_move` | Change a task category or position. |
|
|
140
159
|
| `backlog_remove` | Permanently remove a task. |
|
|
160
|
+
| `backlog_category_add` | Add a category with a stable ID and title. |
|
|
161
|
+
| `backlog_category_update` | Change a category title. |
|
|
162
|
+
| `backlog_category_move` | Change a category position. |
|
|
163
|
+
| `backlog_category_remove` | Remove an empty category. |
|
|
164
|
+
| `backlog_category_purge` | Permanently remove all tasks from a category. |
|
|
141
165
|
|
|
142
166
|
The agent receives task IDs from `backlog_list` and from each mutation result.
|
|
143
167
|
|
|
144
168
|
## Backlog File
|
|
145
169
|
|
|
146
|
-
The plugin creates `BACKLOG.json` when the first task is added:
|
|
170
|
+
The plugin creates `BACKLOG.json` when the first task or category is added:
|
|
147
171
|
|
|
148
172
|
```json
|
|
149
173
|
{
|
|
150
|
-
"version":
|
|
174
|
+
"version": 2,
|
|
175
|
+
"categories": [
|
|
176
|
+
{ "id": "todo", "title": "Todo" },
|
|
177
|
+
{ "id": "doing", "title": "Doing" },
|
|
178
|
+
{ "id": "blocked", "title": "Blocked" },
|
|
179
|
+
{ "id": "done", "title": "Done" }
|
|
180
|
+
],
|
|
151
181
|
"items": [
|
|
152
182
|
{
|
|
153
183
|
"id": "c50437e3-2a57-424d-9257-32ec432145a9",
|
|
@@ -159,7 +189,11 @@ The plugin creates `BACKLOG.json` when the first task is added:
|
|
|
159
189
|
}
|
|
160
190
|
```
|
|
161
191
|
|
|
162
|
-
|
|
192
|
+
The category array defines category order. The item array defines task order inside each category.
|
|
193
|
+
|
|
194
|
+
Category IDs stay stable when titles change. The plugin rejects tasks that reference an unknown category ID.
|
|
195
|
+
|
|
196
|
+
The plugin reads version 1 files with the default categories. The next backlog change writes the file as version 2.
|
|
163
197
|
|
|
164
198
|
Commit `BACKLOG.json` when the backlog must follow the project. Ignore it when the backlog must remain local.
|
|
165
199
|
|
package/dist/backlog.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export declare const BACKLOG_FILE = "BACKLOG.json";
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export interface Category {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
}
|
|
6
|
+
export type Status = string;
|
|
4
7
|
export interface BacklogItem {
|
|
5
8
|
readonly id: string;
|
|
6
9
|
readonly title: string;
|
|
@@ -8,12 +11,19 @@ export interface BacklogItem {
|
|
|
8
11
|
readonly status: Status;
|
|
9
12
|
}
|
|
10
13
|
export interface Backlog {
|
|
11
|
-
readonly version:
|
|
14
|
+
readonly version: 2;
|
|
15
|
+
readonly categories: readonly Category[];
|
|
12
16
|
readonly items: readonly BacklogItem[];
|
|
13
17
|
}
|
|
18
|
+
export declare const DEFAULT_CATEGORIES: readonly Category[];
|
|
14
19
|
export declare const EMPTY_BACKLOG: Backlog;
|
|
15
|
-
export declare function isStatus(value: unknown): value is Status;
|
|
20
|
+
export declare function isStatus(backlog: Backlog, value: unknown): value is Status;
|
|
16
21
|
export declare function parseBacklog(value: unknown): Backlog;
|
|
17
|
-
export declare function sortByStatus(items: readonly BacklogItem[]): BacklogItem[];
|
|
18
|
-
export declare function moveItem(items: readonly BacklogItem[], id: string, status: Status | undefined, position: number | undefined): BacklogItem[];
|
|
22
|
+
export declare function sortByStatus(items: readonly BacklogItem[], categories: readonly Category[]): BacklogItem[];
|
|
23
|
+
export declare function moveItem(items: readonly BacklogItem[], id: string, status: Status | undefined, position: number | undefined, categories: readonly Category[]): BacklogItem[];
|
|
19
24
|
export declare function describeBacklog(backlog: Backlog, statuses?: readonly Status[]): string;
|
|
25
|
+
export declare function addCategory(backlog: Backlog, category: Category, position?: number): Backlog;
|
|
26
|
+
export declare function renameCategory(backlog: Backlog, id: string, title: string): Backlog;
|
|
27
|
+
export declare function moveCategory(backlog: Backlog, id: string, position: number): Backlog;
|
|
28
|
+
export declare function removeCategory(backlog: Backlog, id: string): Backlog;
|
|
29
|
+
export declare function purgeCategory(backlog: Backlog, id: string): Backlog;
|
package/dist/backlog.js
CHANGED
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
export const BACKLOG_FILE = "BACKLOG.json";
|
|
2
|
-
export const
|
|
3
|
-
|
|
2
|
+
export const DEFAULT_CATEGORIES = [
|
|
3
|
+
{ id: "todo", title: "Todo" },
|
|
4
|
+
{ id: "doing", title: "Doing" },
|
|
5
|
+
{ id: "done", title: "Done" },
|
|
6
|
+
];
|
|
7
|
+
export const EMPTY_BACKLOG = { version: 2, categories: DEFAULT_CATEGORIES, items: [] };
|
|
4
8
|
function isRecord(value) {
|
|
5
9
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
10
|
}
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
function validateCategory(category, index) {
|
|
12
|
+
const label = index === undefined ? "Category" : `Backlog category ${index}`;
|
|
13
|
+
if (!isRecord(category))
|
|
14
|
+
throw new Error(`${label} must be an object`);
|
|
15
|
+
if (typeof category.id !== "string" || category.id.trim().length === 0) {
|
|
16
|
+
throw new Error(`${label} must have a non-empty id`);
|
|
17
|
+
}
|
|
18
|
+
if (typeof category.title !== "string" || category.title.trim().length === 0) {
|
|
19
|
+
throw new Error(`${label} must have a non-empty title`);
|
|
20
|
+
}
|
|
21
|
+
return { id: category.id, title: category.title };
|
|
9
22
|
}
|
|
10
|
-
|
|
11
|
-
if (!
|
|
12
|
-
throw new Error("
|
|
23
|
+
function validateCategories(value) {
|
|
24
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
25
|
+
throw new Error("A version 2 backlog must have at least one category");
|
|
13
26
|
}
|
|
14
27
|
const ids = new Set();
|
|
15
|
-
|
|
28
|
+
return value.map((value, index) => {
|
|
29
|
+
const category = validateCategory(value, index);
|
|
30
|
+
if (ids.has(category.id))
|
|
31
|
+
throw new Error(`Backlog category id ${category.id} is duplicated`);
|
|
32
|
+
ids.add(category.id);
|
|
33
|
+
return category;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function validateItems(value, categories) {
|
|
37
|
+
if (!Array.isArray(value))
|
|
38
|
+
throw new Error("Backlog items must be an array");
|
|
39
|
+
const ids = new Set();
|
|
40
|
+
return value.map((item, index) => {
|
|
16
41
|
if (!isRecord(item))
|
|
17
42
|
throw new Error(`Backlog item ${index} must be an object`);
|
|
18
|
-
if (typeof item.id !== "string" || item.id.length === 0) {
|
|
43
|
+
if (typeof item.id !== "string" || item.id.trim().length === 0) {
|
|
19
44
|
throw new Error(`Backlog item ${index} must have a non-empty id`);
|
|
20
45
|
}
|
|
21
46
|
if (ids.has(item.id))
|
|
@@ -26,8 +51,9 @@ export function parseBacklog(value) {
|
|
|
26
51
|
if (item.notes !== undefined && typeof item.notes !== "string") {
|
|
27
52
|
throw new Error(`Backlog item ${item.id} has invalid notes`);
|
|
28
53
|
}
|
|
29
|
-
if (!
|
|
54
|
+
if (typeof item.status !== "string" || !categories.some(({ id }) => id === item.status)) {
|
|
30
55
|
throw new Error(`Backlog item ${item.id} has an invalid status`);
|
|
56
|
+
}
|
|
31
57
|
ids.add(item.id);
|
|
32
58
|
return {
|
|
33
59
|
id: item.id,
|
|
@@ -36,35 +62,109 @@ export function parseBacklog(value) {
|
|
|
36
62
|
status: item.status,
|
|
37
63
|
};
|
|
38
64
|
});
|
|
39
|
-
return { version: 1, items };
|
|
40
65
|
}
|
|
41
|
-
export function
|
|
42
|
-
return
|
|
66
|
+
export function isStatus(backlog, value) {
|
|
67
|
+
return typeof value === "string" && backlog.categories.some(({ id }) => id === value);
|
|
43
68
|
}
|
|
44
|
-
export function
|
|
69
|
+
export function parseBacklog(value) {
|
|
70
|
+
if (!isRecord(value))
|
|
71
|
+
throw new Error("BACKLOG.json must contain a backlog object");
|
|
72
|
+
if (value.version === 1) {
|
|
73
|
+
return {
|
|
74
|
+
version: 2,
|
|
75
|
+
categories: DEFAULT_CATEGORIES,
|
|
76
|
+
items: validateItems(value.items, DEFAULT_CATEGORIES),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (value.version !== 2)
|
|
80
|
+
throw new Error(`Unsupported backlog version ${String(value.version)}`);
|
|
81
|
+
const categories = validateCategories(value.categories);
|
|
82
|
+
return { version: 2, categories, items: validateItems(value.items, categories) };
|
|
83
|
+
}
|
|
84
|
+
export function sortByStatus(items, categories) {
|
|
85
|
+
return categories.flatMap(({ id }) => items.filter((item) => item.status === id));
|
|
86
|
+
}
|
|
87
|
+
export function moveItem(items, id, status, position, categories) {
|
|
45
88
|
const item = items.find((candidate) => candidate.id === id);
|
|
46
89
|
if (!item)
|
|
47
90
|
throw new Error(`Backlog item ${id} does not exist`);
|
|
48
91
|
const targetStatus = status ?? item.status;
|
|
92
|
+
if (!categories.some((category) => category.id === targetStatus)) {
|
|
93
|
+
throw new Error(`Backlog category ${targetStatus} does not exist`);
|
|
94
|
+
}
|
|
49
95
|
const remaining = items.filter((candidate) => candidate.id !== id);
|
|
50
96
|
const target = remaining.filter((candidate) => candidate.status === targetStatus);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return STATUSES.flatMap((candidateStatus) => candidateStatus === targetStatus
|
|
97
|
+
target.splice(clampPosition(position, target.length), 0, { ...item, status: targetStatus });
|
|
98
|
+
return categories.flatMap(({ id: categoryId }) => categoryId === targetStatus
|
|
54
99
|
? target
|
|
55
|
-
: remaining.filter((candidate) => candidate.status ===
|
|
100
|
+
: remaining.filter((candidate) => candidate.status === categoryId));
|
|
56
101
|
}
|
|
57
|
-
export function describeBacklog(backlog, statuses
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
102
|
+
export function describeBacklog(backlog, statuses) {
|
|
103
|
+
const selected = statuses ?? backlog.categories.map(({ id }) => id);
|
|
104
|
+
return selected.map((status) => {
|
|
105
|
+
const category = backlog.categories.find(({ id }) => id === status);
|
|
106
|
+
if (!category)
|
|
107
|
+
throw new Error(`Backlog category ${status} does not exist`);
|
|
62
108
|
const items = backlog.items.filter((item) => item.status === status);
|
|
63
109
|
const lines = items.map((item, index) => {
|
|
64
110
|
const notes = item.notes ? `\n Notes: ${item.notes}` : "";
|
|
65
111
|
return `${index}. ${item.id}: ${item.title}${notes}`;
|
|
66
112
|
});
|
|
67
|
-
return `${
|
|
113
|
+
return `${category.title.toUpperCase()} [${category.id}] (${items.length})\n${lines.length > 0 ? lines.join("\n") : "Empty"}`;
|
|
68
114
|
}).join("\n\n");
|
|
69
115
|
}
|
|
116
|
+
export function addCategory(backlog, category, position) {
|
|
117
|
+
const added = validateCategory(category);
|
|
118
|
+
if (backlog.categories.some(({ id }) => id === added.id)) {
|
|
119
|
+
throw new Error(`Backlog category id ${added.id} is duplicated`);
|
|
120
|
+
}
|
|
121
|
+
const categories = [...backlog.categories];
|
|
122
|
+
categories.splice(clampPosition(position, categories.length), 0, added);
|
|
123
|
+
return { ...backlog, categories };
|
|
124
|
+
}
|
|
125
|
+
export function renameCategory(backlog, id, title) {
|
|
126
|
+
if (title.trim().length === 0)
|
|
127
|
+
throw new Error("Category must have a non-empty title");
|
|
128
|
+
if (!backlog.categories.some((category) => category.id === id)) {
|
|
129
|
+
throw new Error(`Backlog category ${id} does not exist`);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
...backlog,
|
|
133
|
+
categories: backlog.categories.map((category) => category.id === id ? { ...category, title } : category),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export function moveCategory(backlog, id, position) {
|
|
137
|
+
const category = backlog.categories.find((candidate) => candidate.id === id);
|
|
138
|
+
if (!category)
|
|
139
|
+
throw new Error(`Backlog category ${id} does not exist`);
|
|
140
|
+
const categories = backlog.categories.filter((candidate) => candidate.id !== id);
|
|
141
|
+
categories.splice(clampPosition(position, categories.length), 0, category);
|
|
142
|
+
return { ...backlog, categories, items: sortByStatus(backlog.items, categories) };
|
|
143
|
+
}
|
|
144
|
+
export function removeCategory(backlog, id) {
|
|
145
|
+
requireRemovableCategory(backlog, id);
|
|
146
|
+
if (backlog.items.some((item) => item.status === id)) {
|
|
147
|
+
throw new Error(`Backlog category ${id} is not empty`);
|
|
148
|
+
}
|
|
149
|
+
return { ...backlog, categories: backlog.categories.filter((category) => category.id !== id) };
|
|
150
|
+
}
|
|
151
|
+
export function purgeCategory(backlog, id) {
|
|
152
|
+
if (!backlog.categories.some((category) => category.id === id)) {
|
|
153
|
+
throw new Error(`Backlog category ${id} does not exist`);
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...backlog,
|
|
157
|
+
items: backlog.items.filter((item) => item.status !== id),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function requireRemovableCategory(backlog, id) {
|
|
161
|
+
if (!backlog.categories.some((category) => category.id === id)) {
|
|
162
|
+
throw new Error(`Backlog category ${id} does not exist`);
|
|
163
|
+
}
|
|
164
|
+
if (backlog.categories.length === 1)
|
|
165
|
+
throw new Error("Cannot remove the final backlog category");
|
|
166
|
+
}
|
|
167
|
+
function clampPosition(position, length) {
|
|
168
|
+
return Math.max(0, Math.min(position ?? length, length));
|
|
169
|
+
}
|
|
70
170
|
//# sourceMappingURL=backlog.js.map
|
package/dist/backlog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backlog.js","sourceRoot":"","sources":["../src/backlog.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"backlog.js","sourceRoot":"","sources":["../src/backlog.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAA;AAsB1C,MAAM,CAAC,MAAM,kBAAkB,GAAwB;IACrD,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;IAC7B,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;IAC/B,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;CAC9B,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAY,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,kBAAkB,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;AAE/F,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAiB,EAAE,KAAc;IACzD,MAAM,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,oBAAoB,KAAK,EAAE,CAAA;IAC5E,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAA;IACtE,IAAI,OAAO,QAAQ,CAAC,EAAE,KAAK,QAAQ,IAAI,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,2BAA2B,CAAC,CAAA;IACtD,CAAC;IACD,IAAI,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,8BAA8B,CAAC,CAAA;IACzD,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAA;AACnD,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;IACxE,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAChC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAC/C,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAA;QAC7F,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACpB,OAAO,QAAQ,CAAA;IACjB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,KAAc,EAAE,UAA+B;IACpE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;IAE5E,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAe,EAAE;QAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,KAAK,oBAAoB,CAAC,CAAA;QAC/E,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CAAC,gBAAgB,KAAK,2BAA2B,CAAC,CAAA;QACnE,CAAC;QACD,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,CAAC,EAAE,gBAAgB,CAAC,CAAA;QACjF,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrE,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,8BAA8B,CAAC,CAAA;QACxE,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAA;QAC9D,CAAC;QACD,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACxF,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,wBAAwB,CAAC,CAAA;QAClE,CAAC;QAED,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAChB,OAAO;YACL,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;YAC1D,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAgB,EAAE,KAAc;IACvD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,KAAK,CAAC,CAAA;AACvF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;IAEnF,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,OAAO,EAAE,CAAC;YACV,UAAU,EAAE,kBAAkB;YAC9B,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,kBAAkB,CAAC;SACtD,CAAA;IACH,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAEhG,MAAM,UAAU,GAAG,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IACvD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAA;AAClF,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,KAA6B,EAC7B,UAA+B;IAE/B,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAA;AACnF,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,KAA6B,EAC7B,EAAU,EACV,MAA0B,EAC1B,QAA4B,EAC5B,UAA+B;IAE/B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAC3D,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAA;IAE/D,MAAM,YAAY,GAAG,MAAM,IAAI,IAAI,CAAC,MAAM,CAAA;IAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,YAAY,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,oBAAoB,YAAY,iBAAiB,CAAC,CAAA;IACpE,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAClE,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,KAAK,YAAY,CAAC,CAAA;IACjF,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;IAE3F,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAC/C,UAAU,KAAK,YAAY;QACzB,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,KAAK,UAAU,CAAC,CACrE,CAAA;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAgB,EAAE,QAA4B;IAC5E,MAAM,QAAQ,GAAG,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAA;IAEnE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,MAAM,CAAC,CAAA;QACnE,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,MAAM,iBAAiB,CAAC,CAAA;QAE3E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAA;QACpE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAC3D,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,KAAK,GAAG,KAAK,EAAE,CAAA;QACtD,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,EAAE,MAAM,KAAK,CAAC,MAAM,MAAM,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAA;IAC/H,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,OAAgB,EAChB,QAAkB,EAClB,QAAiB;IAEjB,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IACxC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,uBAAuB,KAAK,CAAC,EAAE,gBAAgB,CAAC,CAAA;IAClE,CAAC;IAED,MAAM,UAAU,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IAC1C,UAAU,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAA;IACvE,OAAO,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAgB,EAAE,EAAU,EAAE,KAAa;IACxE,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;IACtF,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,oBAAoB,EAAE,iBAAiB,CAAC,CAAA;IAC1D,CAAC;IAED,OAAO;QACL,GAAG,OAAO;QACV,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAC9C,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CACvD;KACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,EAAU,EAAE,QAAgB;IACzE,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5E,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,EAAE,iBAAiB,CAAC,CAAA;IAEvE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAChF,UAAU,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAA;IAC1E,OAAO,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAA;AACnF,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAgB,EAAE,EAAU;IACzD,wBAAwB,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;IACrC,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,oBAAoB,EAAE,eAAe,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAA;AAChG,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,EAAU;IACxD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,oBAAoB,EAAE,iBAAiB,CAAC,CAAA;IAC1D,CAAC;IACD,OAAO;QACL,GAAG,OAAO;QACV,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC;KAC1D,CAAA;AACH,CAAC;AAED,SAAS,wBAAwB,CAAC,OAAgB,EAAE,EAAU;IAC5D,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,oBAAoB,EAAE,iBAAiB,CAAC,CAAA;IAC1D,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;AAClG,CAAC;AAED,SAAS,aAAa,CAAC,QAA4B,EAAE,MAAc;IACjE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAC1D,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { Plugin } from "@opencode-ai/plugin";
|
|
4
|
-
import { BACKLOG_FILE, describeBacklog, moveItem, sortByStatus, } from "./backlog.js";
|
|
4
|
+
import { BACKLOG_FILE, addCategory, describeBacklog, moveItem, moveCategory, purgeCategory, removeCategory, renameCategory, sortByStatus, } from "./backlog.js";
|
|
5
5
|
import { optionalNullableString, optionalPosition, optionalStatus, record, requiredString, } from "./input.js";
|
|
6
6
|
import { readBacklog, updateBacklog } from "./store.js";
|
|
7
7
|
const idInput = {
|
|
@@ -10,6 +10,15 @@ const idInput = {
|
|
|
10
10
|
required: ["id"],
|
|
11
11
|
additionalProperties: false,
|
|
12
12
|
};
|
|
13
|
+
const categoryTitleInput = {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
id: { type: "string", minLength: 1 },
|
|
17
|
+
title: { type: "string", minLength: 1 },
|
|
18
|
+
},
|
|
19
|
+
required: ["id", "title"],
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
};
|
|
13
22
|
async function backlogPath(context, sessionID) {
|
|
14
23
|
const session = await context.session.get({ sessionID });
|
|
15
24
|
return join(session.location.directory, BACKLOG_FILE);
|
|
@@ -33,29 +42,30 @@ export default Plugin.define({
|
|
|
33
42
|
await context.tool.transform((tools) => {
|
|
34
43
|
tools.add({
|
|
35
44
|
name: "backlog_list",
|
|
36
|
-
description: "List the ordered project backlog, optionally filtered by
|
|
45
|
+
description: "List the ordered project backlog, optionally filtered by category ID.",
|
|
37
46
|
input: {
|
|
38
47
|
type: "object",
|
|
39
|
-
properties: { status: { type: "string",
|
|
48
|
+
properties: { status: { type: "string", minLength: 1 } },
|
|
40
49
|
additionalProperties: false,
|
|
41
50
|
},
|
|
42
51
|
options: { codemode: false },
|
|
43
52
|
execute: async (input, toolContext) => {
|
|
44
|
-
const
|
|
53
|
+
const backlog = await readForSession(context, toolContext.sessionID);
|
|
54
|
+
const status = optionalStatus(record(input), backlog);
|
|
45
55
|
return {
|
|
46
|
-
content: describeBacklog(
|
|
56
|
+
content: describeBacklog(backlog, status === undefined ? undefined : [status]),
|
|
47
57
|
};
|
|
48
58
|
},
|
|
49
59
|
});
|
|
50
60
|
tools.add({
|
|
51
61
|
name: "backlog_add",
|
|
52
|
-
description: "Add a task
|
|
62
|
+
description: "Add a task at a zero-based position within a backlog category.",
|
|
53
63
|
input: {
|
|
54
64
|
type: "object",
|
|
55
65
|
properties: {
|
|
56
66
|
title: { type: "string", minLength: 1 },
|
|
57
67
|
notes: { type: "string" },
|
|
58
|
-
status: { type: "string",
|
|
68
|
+
status: { type: "string", minLength: 1 },
|
|
59
69
|
position: { type: "integer", minimum: 0 },
|
|
60
70
|
},
|
|
61
71
|
required: ["title"],
|
|
@@ -66,18 +76,27 @@ export default Plugin.define({
|
|
|
66
76
|
const values = record(input);
|
|
67
77
|
const title = requiredString(values, "title");
|
|
68
78
|
const notes = optionalNullableString(values, "notes") ?? undefined;
|
|
69
|
-
const status = optionalStatus(values) ?? "todo";
|
|
70
79
|
const position = optionalPosition(values);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
let item;
|
|
81
|
+
const backlog = await updateForSession(context, toolContext.sessionID, (current) => {
|
|
82
|
+
const firstCategory = current.categories[0];
|
|
83
|
+
if (!firstCategory)
|
|
84
|
+
throw new Error("The backlog must have at least one category");
|
|
85
|
+
const status = optionalStatus(values, current) ?? firstCategory.id;
|
|
86
|
+
item = {
|
|
87
|
+
id: randomUUID(),
|
|
88
|
+
title,
|
|
89
|
+
...(notes === undefined ? {} : { notes }),
|
|
90
|
+
status,
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
version: 2,
|
|
94
|
+
categories: current.categories,
|
|
95
|
+
items: moveItem([...current.items, item], item.id, status, position, current.categories),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
if (!item)
|
|
99
|
+
throw new Error("Backlog update did not add an item");
|
|
81
100
|
return { content: `Added ${item.id}.\n\n${describeBacklog(backlog)}` };
|
|
82
101
|
},
|
|
83
102
|
});
|
|
@@ -113,19 +132,23 @@ export default Plugin.define({
|
|
|
113
132
|
...(title === undefined ? {} : { title }),
|
|
114
133
|
...(notes === undefined || notes === null ? {} : { notes }),
|
|
115
134
|
};
|
|
116
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
version: 2,
|
|
137
|
+
categories: current.categories,
|
|
138
|
+
items: sortByStatus(replaceItem(current.items, replacement), current.categories),
|
|
139
|
+
};
|
|
117
140
|
});
|
|
118
141
|
return { content: `Updated ${id}.\n\n${describeBacklog(backlog)}` };
|
|
119
142
|
},
|
|
120
143
|
});
|
|
121
144
|
tools.add({
|
|
122
145
|
name: "backlog_move",
|
|
123
|
-
description: "Change a task
|
|
146
|
+
description: "Change a task category or its zero-based position within that category.",
|
|
124
147
|
input: {
|
|
125
148
|
type: "object",
|
|
126
149
|
properties: {
|
|
127
150
|
id: { type: "string", minLength: 1 },
|
|
128
|
-
status: { type: "string",
|
|
151
|
+
status: { type: "string", minLength: 1 },
|
|
129
152
|
position: { type: "integer", minimum: 0 },
|
|
130
153
|
},
|
|
131
154
|
required: ["id"],
|
|
@@ -135,15 +158,18 @@ export default Plugin.define({
|
|
|
135
158
|
execute: async (input, toolContext) => {
|
|
136
159
|
const values = record(input);
|
|
137
160
|
const id = requiredString(values, "id");
|
|
138
|
-
const status = optionalStatus(values);
|
|
139
161
|
const position = optionalPosition(values);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
162
|
+
const backlog = await updateForSession(context, toolContext.sessionID, (current) => {
|
|
163
|
+
const status = optionalStatus(values, current);
|
|
164
|
+
if (status === undefined && position === undefined) {
|
|
165
|
+
throw new Error("backlog_move requires a status or position change");
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
version: 2,
|
|
169
|
+
categories: current.categories,
|
|
170
|
+
items: moveItem(current.items, id, status, position, current.categories),
|
|
171
|
+
};
|
|
172
|
+
});
|
|
147
173
|
return { content: `Moved ${id}.\n\n${describeBacklog(backlog)}` };
|
|
148
174
|
},
|
|
149
175
|
});
|
|
@@ -158,11 +184,96 @@ export default Plugin.define({
|
|
|
158
184
|
if (!current.items.some((item) => item.id === id)) {
|
|
159
185
|
throw new Error(`Backlog item ${id} does not exist`);
|
|
160
186
|
}
|
|
161
|
-
return {
|
|
187
|
+
return {
|
|
188
|
+
version: 2,
|
|
189
|
+
categories: current.categories,
|
|
190
|
+
items: current.items.filter((item) => item.id !== id),
|
|
191
|
+
};
|
|
162
192
|
});
|
|
163
193
|
return { content: `Removed ${id}.\n\n${describeBacklog(backlog)}` };
|
|
164
194
|
},
|
|
165
195
|
});
|
|
196
|
+
tools.add({
|
|
197
|
+
name: "backlog_category_add",
|
|
198
|
+
description: "Add a backlog category with a unique ID, title, and optional zero-based position.",
|
|
199
|
+
input: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
id: { type: "string", minLength: 1 },
|
|
203
|
+
title: { type: "string", minLength: 1 },
|
|
204
|
+
position: { type: "integer", minimum: 0 },
|
|
205
|
+
},
|
|
206
|
+
required: ["id", "title"],
|
|
207
|
+
additionalProperties: false,
|
|
208
|
+
},
|
|
209
|
+
options: { codemode: false },
|
|
210
|
+
execute: async (input, toolContext) => {
|
|
211
|
+
const values = record(input);
|
|
212
|
+
const id = requiredString(values, "id");
|
|
213
|
+
const title = requiredString(values, "title");
|
|
214
|
+
const position = optionalPosition(values);
|
|
215
|
+
const backlog = await updateForSession(context, toolContext.sessionID, (current) => addCategory(current, { id, title }, position));
|
|
216
|
+
return { content: `Added category ${id}.\n\n${describeBacklog(backlog)}` };
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
tools.add({
|
|
220
|
+
name: "backlog_category_update",
|
|
221
|
+
description: "Change a backlog category title.",
|
|
222
|
+
input: categoryTitleInput,
|
|
223
|
+
options: { codemode: false },
|
|
224
|
+
execute: async (input, toolContext) => {
|
|
225
|
+
const values = record(input);
|
|
226
|
+
const id = requiredString(values, "id");
|
|
227
|
+
const title = requiredString(values, "title");
|
|
228
|
+
const backlog = await updateForSession(context, toolContext.sessionID, (current) => renameCategory(current, id, title));
|
|
229
|
+
return { content: `Updated category ${id}.\n\n${describeBacklog(backlog)}` };
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
tools.add({
|
|
233
|
+
name: "backlog_category_move",
|
|
234
|
+
description: "Move a backlog category to a zero-based position.",
|
|
235
|
+
input: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
id: { type: "string", minLength: 1 },
|
|
239
|
+
position: { type: "integer", minimum: 0 },
|
|
240
|
+
},
|
|
241
|
+
required: ["id", "position"],
|
|
242
|
+
additionalProperties: false,
|
|
243
|
+
},
|
|
244
|
+
options: { codemode: false },
|
|
245
|
+
execute: async (input, toolContext) => {
|
|
246
|
+
const values = record(input);
|
|
247
|
+
const id = requiredString(values, "id");
|
|
248
|
+
const position = optionalPosition(values);
|
|
249
|
+
if (position === undefined)
|
|
250
|
+
throw new Error("position is required");
|
|
251
|
+
const backlog = await updateForSession(context, toolContext.sessionID, (current) => moveCategory(current, id, position));
|
|
252
|
+
return { content: `Moved category ${id}.\n\n${describeBacklog(backlog)}` };
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
tools.add({
|
|
256
|
+
name: "backlog_category_remove",
|
|
257
|
+
description: "Remove an empty backlog category.",
|
|
258
|
+
input: idInput,
|
|
259
|
+
options: { codemode: false },
|
|
260
|
+
execute: async (input, toolContext) => {
|
|
261
|
+
const id = requiredString(record(input), "id");
|
|
262
|
+
const backlog = await updateForSession(context, toolContext.sessionID, (current) => removeCategory(current, id));
|
|
263
|
+
return { content: `Removed category ${id}.\n\n${describeBacklog(backlog)}` };
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
tools.add({
|
|
267
|
+
name: "backlog_category_purge",
|
|
268
|
+
description: "Permanently remove all tasks in a backlog category while retaining the category.",
|
|
269
|
+
input: idInput,
|
|
270
|
+
options: { codemode: false },
|
|
271
|
+
execute: async (input, toolContext) => {
|
|
272
|
+
const id = requiredString(record(input), "id");
|
|
273
|
+
const backlog = await updateForSession(context, toolContext.sessionID, (current) => purgeCategory(current, id));
|
|
274
|
+
return { content: `Purged tasks from category ${id}.\n\n${describeBacklog(backlog)}` };
|
|
275
|
+
},
|
|
276
|
+
});
|
|
166
277
|
});
|
|
167
278
|
},
|
|
168
279
|
});
|