@mrclrchtr/supi-flow 0.10.1 → 0.11.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 +227 -125
- package/extensions/index.ts +61 -10
- package/extensions/tools/flow-tools.ts +554 -165
- package/extensions/tools/tndm-cli.ts +249 -4
- package/package.json +5 -5
- package/skills/supi-flow-apply/SKILL.md +8 -6
- package/skills/supi-flow-archive/SKILL.md +2 -2
- package/skills/supi-flow-brainstorm/SKILL.md +3 -3
- package/skills/supi-flow-plan/SKILL.md +17 -14
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
1
3
|
import { type Static, Type } from "typebox";
|
|
2
4
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
3
5
|
import { tndm, tndmJson } from "../cli.js";
|
|
@@ -8,6 +10,12 @@ export const actionEnum = StringEnum([
|
|
|
8
10
|
"show",
|
|
9
11
|
"list",
|
|
10
12
|
"awareness",
|
|
13
|
+
"task_add",
|
|
14
|
+
"task_list",
|
|
15
|
+
"task_complete",
|
|
16
|
+
"task_remove",
|
|
17
|
+
"task_edit",
|
|
18
|
+
"task_set",
|
|
11
19
|
] as const);
|
|
12
20
|
|
|
13
21
|
export const supi_tndm_cli_params = Type.Object({
|
|
@@ -71,6 +79,35 @@ export const supi_tndm_cli_params = Type.Object({
|
|
|
71
79
|
against: Type.Optional(
|
|
72
80
|
Type.String({ description: "Git ref to run awareness against (required for awareness)" }),
|
|
73
81
|
),
|
|
82
|
+
|
|
83
|
+
// Task params
|
|
84
|
+
task_title: Type.Optional(
|
|
85
|
+
Type.String({ description: "Task title (required for task_add)" }),
|
|
86
|
+
),
|
|
87
|
+
task_number: Type.Optional(
|
|
88
|
+
Type.Number({ description: "Task number (required for task_complete, task_remove, task_edit)" }),
|
|
89
|
+
),
|
|
90
|
+
task_files: Type.Optional(
|
|
91
|
+
Type.Array(Type.String(), { description: "File paths for the task" }),
|
|
92
|
+
),
|
|
93
|
+
task_clear_files: Type.Optional(
|
|
94
|
+
Type.Boolean({ description: "Clear all file paths for the task" }),
|
|
95
|
+
),
|
|
96
|
+
task_verification: Type.Optional(
|
|
97
|
+
Type.String({ description: "Verification command for the task" }),
|
|
98
|
+
),
|
|
99
|
+
task_notes: Type.Optional(
|
|
100
|
+
Type.String({ description: "Extra notes for the task" }),
|
|
101
|
+
),
|
|
102
|
+
task_detail: Type.Optional(
|
|
103
|
+
Type.String({ description: "Optional markdown body for a task detail doc" }),
|
|
104
|
+
),
|
|
105
|
+
task_clear_detail: Type.Optional(
|
|
106
|
+
Type.Boolean({ description: "Explicitly detach a task detail doc link" }),
|
|
107
|
+
),
|
|
108
|
+
task_json: Type.Optional(
|
|
109
|
+
Type.String({ description: "JSON array of tasks (required for task_set)" }),
|
|
110
|
+
),
|
|
74
111
|
});
|
|
75
112
|
|
|
76
113
|
/**
|
|
@@ -82,6 +119,12 @@ export const supi_tndm_cli_params = Type.Object({
|
|
|
82
119
|
* show → tndm ticket show <id> --json
|
|
83
120
|
* list → tndm ticket list [--all] [--definition <state>] --json
|
|
84
121
|
* awareness → tndm awareness --against <ref> --json
|
|
122
|
+
* task_add → tndm ticket task add <id> --title <title> [--file <path> ...] [--verification] [--notes] --json, optionally followed by task detail ensure + sync when task_detail is provided
|
|
123
|
+
* task_list → tndm ticket task list <id> --json
|
|
124
|
+
* task_complete → tndm ticket task complete <id> <number> --json
|
|
125
|
+
* task_remove → tndm ticket task remove <id> <number> --json
|
|
126
|
+
* task_edit → tndm ticket task edit <id> <number> [--title] [--file <path> ...] [--verification] [--notes] --json, optionally followed by task detail ensure/clear
|
|
127
|
+
* task_set → tndm ticket task set <id> --tasks <json> --json
|
|
85
128
|
* doc_create and sync are internal operations used by flow tools, not exposed here.
|
|
86
129
|
*/
|
|
87
130
|
export type TndmCliParams = Static<typeof supi_tndm_cli_params>;
|
|
@@ -157,18 +200,24 @@ export async function executeTndmCli(params: TndmCliParams) {
|
|
|
157
200
|
if (params.all) args.push("--all");
|
|
158
201
|
if (params.definition) args.push("--definition", params.definition);
|
|
159
202
|
|
|
160
|
-
const
|
|
203
|
+
const rawResult = await tndmJson<
|
|
204
|
+
Record<string, unknown>[] | { schema_version?: number; tickets?: Record<string, unknown>[] }
|
|
205
|
+
>(args);
|
|
206
|
+
const envelope = Array.isArray(rawResult)
|
|
207
|
+
? { schema_version: 1, tickets: rawResult }
|
|
208
|
+
: rawResult;
|
|
209
|
+
const tickets = Array.isArray(envelope.tickets) ? envelope.tickets : [];
|
|
161
210
|
return {
|
|
162
211
|
content: [
|
|
163
212
|
{
|
|
164
213
|
type: "text" as const,
|
|
165
214
|
text:
|
|
166
|
-
|
|
167
|
-
? JSON.stringify(
|
|
215
|
+
tickets.length > 0
|
|
216
|
+
? JSON.stringify(envelope, null, 2)
|
|
168
217
|
: "No tickets found.",
|
|
169
218
|
},
|
|
170
219
|
],
|
|
171
|
-
details: { action: "list", tickets
|
|
220
|
+
details: { action: "list", tickets, envelope },
|
|
172
221
|
};
|
|
173
222
|
}
|
|
174
223
|
|
|
@@ -186,6 +235,142 @@ export async function executeTndmCli(params: TndmCliParams) {
|
|
|
186
235
|
details: { action: "awareness", awareness: result },
|
|
187
236
|
};
|
|
188
237
|
}
|
|
238
|
+
|
|
239
|
+
// ── Task actions ────────────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
case "task_add": {
|
|
242
|
+
if (!params.id) throw new Error("supi_tndm_cli: id is required for task_add");
|
|
243
|
+
if (!params.task_title) throw new Error("supi_tndm_cli: task_title is required for task_add");
|
|
244
|
+
const args: string[] = ["ticket", "task", "add", params.id, "--title", params.task_title];
|
|
245
|
+
for (const file of params.task_files ?? []) {
|
|
246
|
+
args.push("--file", file);
|
|
247
|
+
}
|
|
248
|
+
if (params.task_verification) args.push("--verification", params.task_verification);
|
|
249
|
+
if (params.task_notes) args.push("--notes", params.task_notes);
|
|
250
|
+
const result = await tndmJson<Record<string, unknown>>(args);
|
|
251
|
+
let finalResult = result;
|
|
252
|
+
|
|
253
|
+
if (params.task_detail !== undefined) {
|
|
254
|
+
const taskNumber = extractLatestTaskNumber(result);
|
|
255
|
+
const detailResult = await ensureTaskDetailDoc(params.id, taskNumber);
|
|
256
|
+
writeTaskDetailDoc(detailResult.path, taskNumber, params.task_title, params.task_detail);
|
|
257
|
+
await tndm(["ticket", "sync", params.id]);
|
|
258
|
+
finalResult = await loadTicket(params.id);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
content: [{ type: "text" as const, text: JSON.stringify(finalResult, null, 2) }],
|
|
263
|
+
details: { action: "task_add", ticketId: params.id, result: finalResult },
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case "task_list": {
|
|
268
|
+
if (!params.id) throw new Error("supi_tndm_cli: id is required for task_list");
|
|
269
|
+
const result = await tndmJson<Record<string, unknown>[]>([
|
|
270
|
+
"ticket", "task", "list", params.id,
|
|
271
|
+
]);
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
274
|
+
details: { action: "task_list", ticketId: params.id, tasks: result },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case "task_complete": {
|
|
279
|
+
if (!params.id) throw new Error("supi_tndm_cli: id is required for task_complete");
|
|
280
|
+
if (params.task_number === undefined) throw new Error("supi_tndm_cli: task_number is required for task_complete");
|
|
281
|
+
const result = await tndmJson<Record<string, unknown>>([
|
|
282
|
+
"ticket", "task", "complete", params.id, String(params.task_number),
|
|
283
|
+
]);
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
286
|
+
details: { action: "task_complete", ticketId: params.id, taskNumber: params.task_number, result },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
case "task_remove": {
|
|
291
|
+
if (!params.id) throw new Error("supi_tndm_cli: id is required for task_remove");
|
|
292
|
+
if (params.task_number === undefined) throw new Error("supi_tndm_cli: task_number is required for task_remove");
|
|
293
|
+
const result = await tndmJson<Record<string, unknown>>([
|
|
294
|
+
"ticket", "task", "remove", params.id, String(params.task_number),
|
|
295
|
+
]);
|
|
296
|
+
return {
|
|
297
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
298
|
+
details: { action: "task_remove", ticketId: params.id, taskNumber: params.task_number, result },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
case "task_edit": {
|
|
303
|
+
if (!params.id) throw new Error("supi_tndm_cli: id is required for task_edit");
|
|
304
|
+
if (params.task_number === undefined) throw new Error("supi_tndm_cli: task_number is required for task_edit");
|
|
305
|
+
if (params.task_detail !== undefined && params.task_clear_detail) {
|
|
306
|
+
throw new Error("supi_tndm_cli: task_detail and task_clear_detail cannot be used together");
|
|
307
|
+
}
|
|
308
|
+
const args: string[] = ["ticket", "task", "edit", params.id, String(params.task_number)];
|
|
309
|
+
if (params.task_title !== undefined) args.push("--title", params.task_title);
|
|
310
|
+
if (params.task_files !== undefined) {
|
|
311
|
+
if (params.task_files.length === 0) {
|
|
312
|
+
args.push("--clear-files");
|
|
313
|
+
} else {
|
|
314
|
+
for (const file of params.task_files) args.push("--file", file);
|
|
315
|
+
}
|
|
316
|
+
} else if (params.task_clear_files) {
|
|
317
|
+
args.push("--clear-files");
|
|
318
|
+
}
|
|
319
|
+
if (params.task_verification !== undefined) args.push("--verification", params.task_verification);
|
|
320
|
+
if (params.task_notes !== undefined) args.push("--notes", params.task_notes);
|
|
321
|
+
|
|
322
|
+
const hasManifestFieldChanges = args.length > 5;
|
|
323
|
+
const result = hasManifestFieldChanges
|
|
324
|
+
? await tndmJson<Record<string, unknown>>(args)
|
|
325
|
+
: undefined;
|
|
326
|
+
let finalResult = result;
|
|
327
|
+
|
|
328
|
+
if (params.task_detail !== undefined) {
|
|
329
|
+
const detailResult = await ensureTaskDetailDoc(params.id, params.task_number);
|
|
330
|
+
const taskSnapshot = result ?? await loadTicket(params.id);
|
|
331
|
+
const taskTitle =
|
|
332
|
+
params.task_title ??
|
|
333
|
+
extractTaskTitle(taskSnapshot, params.task_number) ??
|
|
334
|
+
`Task ${params.task_number}`;
|
|
335
|
+
writeTaskDetailDoc(
|
|
336
|
+
detailResult.path,
|
|
337
|
+
params.task_number,
|
|
338
|
+
taskTitle,
|
|
339
|
+
params.task_detail,
|
|
340
|
+
);
|
|
341
|
+
await tndm(["ticket", "sync", params.id]);
|
|
342
|
+
finalResult = await loadTicket(params.id);
|
|
343
|
+
} else if (params.task_clear_detail) {
|
|
344
|
+
await tndmJson([
|
|
345
|
+
"ticket",
|
|
346
|
+
"task",
|
|
347
|
+
"detail",
|
|
348
|
+
"clear",
|
|
349
|
+
params.id,
|
|
350
|
+
String(params.task_number),
|
|
351
|
+
]);
|
|
352
|
+
finalResult = await loadTicket(params.id);
|
|
353
|
+
} else if (!finalResult) {
|
|
354
|
+
finalResult = await tndmJson<Record<string, unknown>>(args);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
content: [{ type: "text" as const, text: JSON.stringify(finalResult, null, 2) }],
|
|
359
|
+
details: { action: "task_edit", ticketId: params.id, taskNumber: params.task_number, result: finalResult },
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
case "task_set": {
|
|
364
|
+
if (!params.id) throw new Error("supi_tndm_cli: id is required for task_set");
|
|
365
|
+
if (!params.task_json) throw new Error("supi_tndm_cli: task_json is required for task_set");
|
|
366
|
+
const result = await tndmJson<Record<string, unknown>>([
|
|
367
|
+
"ticket", "task", "set", params.id, "--tasks", params.task_json,
|
|
368
|
+
]);
|
|
369
|
+
return {
|
|
370
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
371
|
+
details: { action: "task_set", ticketId: params.id, result },
|
|
372
|
+
};
|
|
373
|
+
}
|
|
189
374
|
}
|
|
190
375
|
}
|
|
191
376
|
|
|
@@ -201,3 +386,63 @@ function addOptionalFlags(
|
|
|
201
386
|
args.push(`--${flagName}`, String(value));
|
|
202
387
|
}
|
|
203
388
|
}
|
|
389
|
+
|
|
390
|
+
// NOTE: This assumes task numbers are auto-incremented (1, 2, 3…). If task_add ever
|
|
391
|
+
// supports explicit task numbering, this will need to use a more precise source
|
|
392
|
+
// of truth (e.g. a top-level `task_number` field in the JSON response).
|
|
393
|
+
function extractLatestTaskNumber(result: Record<string, unknown>): number {
|
|
394
|
+
const tasks = extractTasks(result);
|
|
395
|
+
const numbers = tasks
|
|
396
|
+
.map((task) => task.number)
|
|
397
|
+
.filter((value): value is number => typeof value === "number");
|
|
398
|
+
|
|
399
|
+
if (numbers.length === 0) {
|
|
400
|
+
throw new Error("supi_tndm_cli: task_add did not return a task list; cannot attach task detail");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return Math.max(...numbers);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function extractTaskTitle(result: Record<string, unknown>, taskNumber: number): string | undefined {
|
|
407
|
+
return extractTasks(result).find((task) => task.number === taskNumber)?.title;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function extractTasks(result: Record<string, unknown>): Array<{ number?: number; title?: string }> {
|
|
411
|
+
if (Array.isArray(result.tasks)) {
|
|
412
|
+
return result.tasks.filter(
|
|
413
|
+
(task): task is { number?: number; title?: string } => typeof task === "object" && task !== null,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const ticket = result.ticket;
|
|
418
|
+
if (typeof ticket === "object" && ticket !== null) {
|
|
419
|
+
const state = (ticket as { state?: unknown }).state;
|
|
420
|
+
if (typeof state === "object" && state !== null && Array.isArray((state as { tasks?: unknown }).tasks)) {
|
|
421
|
+
return ((state as { tasks: unknown[] }).tasks).filter(
|
|
422
|
+
(task): task is { number?: number; title?: string } => typeof task === "object" && task !== null,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function loadTicket(id: string): Promise<Record<string, unknown>> {
|
|
431
|
+
return tndmJson<Record<string, unknown>>(["ticket", "show", id]);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function ensureTaskDetailDoc(id: string, taskNumber: number): Promise<{ path: string }> {
|
|
435
|
+
return tndmJson<{ path: string }>([
|
|
436
|
+
"ticket",
|
|
437
|
+
"task",
|
|
438
|
+
"detail",
|
|
439
|
+
"ensure",
|
|
440
|
+
id,
|
|
441
|
+
String(taskNumber),
|
|
442
|
+
]);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function writeTaskDetailDoc(path: string, taskNumber: number, title: string, detail: string): void {
|
|
446
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
447
|
+
writeFileSync(path, `# Task ${taskNumber}: ${title}\n\n${detail}\n`, "utf-8");
|
|
448
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "PI extension for spec-driven workflow (brainstorm → plan → apply → archive) with TNDM ticket coordination, custom tools, and 5 auto-discovered skills",
|
|
5
|
+
"description": "PI extension for spec-driven workflow (brainstorm → plan → apply → archive) with TNDM ticket coordination, 6 custom tools, and 5 auto-discovered skills",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
8
|
"pi-extension",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"README.md"
|
|
31
31
|
],
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@earendil-works/pi-ai": "0.
|
|
34
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
35
|
-
"@types/node": "25.
|
|
33
|
+
"@earendil-works/pi-ai": "0.75.1",
|
|
34
|
+
"@earendil-works/pi-coding-agent": "0.75.1",
|
|
35
|
+
"@types/node": "25.8.0",
|
|
36
36
|
"vitest": "4.1.6"
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -15,15 +15,17 @@ If you haven't run the verification command for this task, you cannot check it o
|
|
|
15
15
|
|
|
16
16
|
## Step 1: Find the plan
|
|
17
17
|
|
|
18
|
-
- A TNDM-ID was set during plan phase.
|
|
19
|
-
`
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
- A TNDM-ID was set during plan phase. Start apply with:
|
|
19
|
+
`supi_flow_apply { ticket_id: "<ID>" }`
|
|
20
|
+
This loads the approved overview from `content.md`, returns the structured task list with status, title, files, verification, and any linked `detail_path`, moves `flow:planned` tickets into `status=in_progress` with `flow:applying`, and preserves the current `in_progress` or `blocked` status for already-applying tickets.
|
|
21
|
+
- Read the returned overview before starting work. If a task has a linked task doc (`detail_path`), read that task doc before implementing so you do not miss task detail captured outside the manifest.
|
|
22
|
+
- If `supi_flow_apply` reports a missing overview, empty task manifest, or invalid lifecycle state, stop and resolve that gap before editing.
|
|
23
|
+
- If `supi_flow_apply` returns `status: "blocked"`, stop and resolve the blocker before resuming implementation.
|
|
22
24
|
- If no plan is available: ask which change to implement.
|
|
23
25
|
|
|
24
26
|
## Step 2: Review the plan critically
|
|
25
27
|
|
|
26
|
-
Read the whole plan before starting
|
|
28
|
+
Read the whole plan before starting, including the approved overview loaded by `supi_flow_apply`.
|
|
27
29
|
|
|
28
30
|
Raise questions before editing if:
|
|
29
31
|
|
|
@@ -40,7 +42,7 @@ Do not start implementation until those concerns are resolved.
|
|
|
40
42
|
For each unchecked task, in order:
|
|
41
43
|
|
|
42
44
|
1. Announce which task you are working on.
|
|
43
|
-
2. Follow the task as written.
|
|
45
|
+
2. Follow the task as written. If the task references a linked task doc via `detail_path`, read it first and treat it as part of the task definition.
|
|
44
46
|
3. Run the verification for that task and read the result carefully.
|
|
45
47
|
4. If verification passes: call `supi_flow_complete_task { ticket_id: "<ID>", task_number: <N> }` to check the task off in the ticket.
|
|
46
48
|
5. If verification fails: stop, diagnose, fix, and re-verify before moving on.
|
|
@@ -18,7 +18,7 @@ Before claiming the change is done, the docs are accurate, or the ticket can be
|
|
|
18
18
|
## Step 1: Find the change
|
|
19
19
|
|
|
20
20
|
- A TNDM-ID was set during plan phase. Read the ticket metadata first:
|
|
21
|
-
`supi_tndm_cli { action: "show", id: "<ID>" }` — inspect `content_path` and the registered documents, then read `content.md` for the approved design and `
|
|
21
|
+
`supi_tndm_cli { action: "show", id: "<ID>" }` — inspect `content_path` and the registered documents, then read `content.md` for the approved design and list tasks with `supi_tndm_cli { action: "task_list", id: "<ID>" }`.
|
|
22
22
|
- Archive runs only when a ticket exists. Trivial flows that skipped the ticket close out directly in conversation — do not run archive.
|
|
23
23
|
- If nothing is clear: ask which change to archive.
|
|
24
24
|
|
|
@@ -66,7 +66,7 @@ Do not assume documentation is correct just because it sounds right.
|
|
|
66
66
|
## Step 5: Close out
|
|
67
67
|
|
|
68
68
|
- Call `supi_flow_close { ticket_id: "<ID>", verification_results: "..." }` with the full verification evidence.
|
|
69
|
-
|
|
69
|
+
`supi_flow_close` requires nonblank `verification_results`, refuses to close while structured tasks remain incomplete, then sets `status=done`, tags=`flow:done`, and stores the evidence in `archive.md`.
|
|
70
70
|
- There is no ticket-less closeout.
|
|
71
71
|
|
|
72
72
|
## Step 6: Commit or finish
|
|
@@ -74,9 +74,9 @@ Keep each section proportional to the complexity. A small change may only need a
|
|
|
74
74
|
|
|
75
75
|
After approval:
|
|
76
76
|
|
|
77
|
-
- **Default to conversation-first.**
|
|
78
|
-
- **
|
|
79
|
-
- If a ticket exists, save the design to the ticket's canonical `content.md` via `supi_tndm_cli { action: "update", id: "<ID>", content: "<outcome>" }`.
|
|
77
|
+
- **Default to conversation-first for trivial work.** Small single-session changes can keep the design in chat and implement directly without a ticket.
|
|
78
|
+
- **Create a ticket for non-trivial work.** When the change is multi-file, needs tests/docs, spans multiple steps, or is likely multi-session, call `supi_flow_start` and persist the approved design in `content.md`.
|
|
79
|
+
- If a ticket exists, save the design to the ticket's canonical `content.md` via `supi_tndm_cli { action: "update", id: "<ID>", content: "<outcome>" }`. During plan phase, keep task authoring separate: the overview stays in `content.md`, and executable tasks are later authored one at a time via `supi_flow_task`.
|
|
80
80
|
- **Retroactive escalation:** if a trivial change grows in scope mid-implementation, stop, create a retroactive ticket via `supi_flow_start`, and store a summary of completed work + new scope.
|
|
81
81
|
|
|
82
82
|
## Self-review
|
|
@@ -33,9 +33,21 @@ Before writing tasks, list which files will be created or modified and what each
|
|
|
33
33
|
- Follow existing codebase patterns.
|
|
34
34
|
- Include doc targets when the change affects user-facing or maintainer-facing behavior.
|
|
35
35
|
|
|
36
|
-
## Step 5: Write ordered tasks
|
|
36
|
+
## Step 5: Write the overview, then define ordered tasks separately
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
The approved overview belongs in `content.md`. It can be pure design / plan prose and may contain zero tasks.
|
|
39
|
+
|
|
40
|
+
After the overview is approved and persisted, define executable tasks separately in `state.toml`. Start by inspecting the current manifest with `supi_tndm_cli { action: "task_list", id: "<ID>" }`.
|
|
41
|
+
|
|
42
|
+
For the normal path, use `supi_flow_task` to add, edit, or remove **one task at a time** instead of building raw `task_json` or managing `detail_path` manually:
|
|
43
|
+
|
|
44
|
+
- **Empty ticket / no tasks yet:** create the manifest with repeated `supi_flow_task { operation: "add", ... }` calls.
|
|
45
|
+
- **Replan / tasks already exist:** reconcile the existing manifest to the new final shape.
|
|
46
|
+
- same task, new details → `supi_flow_task { operation: "edit", task_number: <N>, ... }`
|
|
47
|
+
- task no longer belongs in the plan → `supi_flow_task { operation: "remove", task_number: <N> }`
|
|
48
|
+
- genuinely new task → `supi_flow_task { operation: "add", ... }`
|
|
49
|
+
|
|
50
|
+
For each task, include:
|
|
39
51
|
|
|
40
52
|
- the goal
|
|
41
53
|
- exact file paths
|
|
@@ -45,18 +57,9 @@ A good plan is broken into small, verifiable tasks. For each task, include:
|
|
|
45
57
|
|
|
46
58
|
Use enough detail that an agent can execute without guessing, but do not force huge code blocks into every step.
|
|
47
59
|
|
|
48
|
-
**Task numbering convention**:
|
|
49
|
-
|
|
50
|
-
```markdown
|
|
51
|
-
- [ ] **Task 1**: Create the CLI helper module
|
|
52
|
-
- File: `extensions/cli.ts`
|
|
53
|
-
- Verification: `pnpm exec tsc --noEmit`
|
|
54
|
-
- [ ] **Task 2**: Register the tools
|
|
55
|
-
- File: `extensions/tools/tndm-cli.ts`
|
|
56
|
-
- Verification: `pnpm exec vitest run`
|
|
57
|
-
```
|
|
60
|
+
**Task numbering convention**: On an empty ticket, adds through `supi_flow_task` start at 1 and increase sequentially. On a ticket that already has tasks, `add` returns the next available task number. Always use the returned task number for later edits, removals, and completion.
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
Use headline-only tasks when the title is enough. If a task needs real implementation detail or notices, pass `detail` markdown through `supi_flow_task` so it creates or updates the canonical `tasks/task-XX.md` task doc automatically.
|
|
60
63
|
|
|
61
64
|
## TDD by default
|
|
62
65
|
|
|
@@ -112,6 +115,6 @@ Fix issues inline before handing off.
|
|
|
112
115
|
|
|
113
116
|
Write the plan in the lightest form that will still survive execution:
|
|
114
117
|
|
|
115
|
-
- **If a ticket exists:** use `supi_flow_plan { ticket_id: "<ID>", plan_content: "..." }` to store the
|
|
118
|
+
- **If a ticket exists:** use `supi_flow_plan { ticket_id: "<ID>", plan_content: "..." }` to store the approved overview in `content.md`. Then list the current tasks. If the manifest is empty, build it with repeated `supi_flow_task { operation: "add", ... }` calls. If tasks already exist, reconcile them to the new final plan with `supi_flow_task` edit/remove/add operations. Keep `supi_tndm_cli` task_* actions as lower-level escape hatches for advanced/manual repair cases.
|
|
116
119
|
- **If no ticket exists:** default to conversation-first. Offer saving to a ticket or file if the work is larger or likely multi-session.
|
|
117
120
|
- Close with: `Plan ready. Review it and approve before we start. Then run /supi-flow-apply TNDM-XXXXXX.`
|