@mrclrchtr/supi-flow 0.6.1 → 0.10.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 +20 -20
- package/{src → extensions}/cli.ts +38 -27
- package/{src → extensions}/index.ts +29 -54
- package/{src → extensions}/tools/flow-tools.ts +59 -33
- package/{src → extensions}/tools/tndm-cli.ts +1 -44
- package/package.json +18 -12
- package/prompts/supi-coding-retro.md +52 -7
- package/skills/supi-flow-archive/SKILL.md +21 -24
- package/skills/supi-flow-plan/SKILL.md +2 -2
- package/skills/supi-flow-slop-detect/SKILL.md +0 -393
- package/skills/supi-flow-slop-detect/references/vocabulary.json +0 -161
- package/skills/supi-flow-slop-detect/scripts/slop-helpers.ts +0 -301
- package/skills/supi-flow-slop-detect/scripts/slop-scan-structural.ts +0 -269
- package/skills/supi-flow-slop-detect/scripts/slop-scan-vocab.ts +0 -161
- package/skills/supi-flow-slop-detect/scripts/slop-scan.ts +0 -209
package/README.md
CHANGED
|
@@ -53,20 +53,15 @@ flowchart TD
|
|
|
53
53
|
ARCHIVE["/skill:supi-flow-archive [ID]
|
|
54
54
|
Fresh verification (gate function)
|
|
55
55
|
Update living documentation
|
|
56
|
-
Slop-scan docs
|
|
57
56
|
Quality gate checklist"]
|
|
58
|
-
ARCHIVE -->
|
|
59
|
-
Tier 1-4 vocabulary
|
|
60
|
-
11 structural patterns
|
|
61
|
-
Score target: < 1.5"]
|
|
62
|
-
SLOP --> QGATE{"Quality gate
|
|
57
|
+
ARCHIVE --> QGATE{"Quality gate
|
|
63
58
|
passes?"}
|
|
64
|
-
QGATE -->|"No"|
|
|
59
|
+
QGATE -->|"No"| ARCHIVE
|
|
65
60
|
QGATE -->|"Yes"| CLOSE
|
|
66
61
|
|
|
67
62
|
CLOSE["supi_flow_close
|
|
68
63
|
Sets status=done, flow:done
|
|
69
|
-
|
|
64
|
+
Writes archive.md"]
|
|
70
65
|
|
|
71
66
|
classDef phase fill:#e8f5e9,stroke:#4caf50,stroke-width:2
|
|
72
67
|
classDef decision fill:#e3f2fd,stroke:#2196f3
|
|
@@ -84,7 +79,7 @@ Non-trivial flows require a TNDM ticket created by `supi_flow_start`. Trivial ch
|
|
|
84
79
|
|
|
85
80
|
## Skills
|
|
86
81
|
|
|
87
|
-
|
|
82
|
+
Five skills ship under `skills/`:
|
|
88
83
|
|
|
89
84
|
| Skill | Trigger | Purpose |
|
|
90
85
|
|---|---|---|
|
|
@@ -93,7 +88,6 @@ Six skills ship under `skills/`:
|
|
|
93
88
|
| `supi-flow-apply` | `/supi-flow-apply` | Execute plan task by task |
|
|
94
89
|
| `supi-flow-archive` | `/supi-flow-archive` | Verify, update docs, close out |
|
|
95
90
|
| `supi-flow-debug` | Loaded on demand when blocked | Root-cause debugging protocol |
|
|
96
|
-
| `supi-flow-slop-detect` | Loaded on demand during archive | AI-prose detection in docs |
|
|
97
91
|
|
|
98
92
|
## Tools
|
|
99
93
|
|
|
@@ -105,7 +99,7 @@ Five custom tools registered by the extension:
|
|
|
105
99
|
| `supi_flow_start` | Create a ticket with status=todo, tag=flow:brainstorm, and optional design context in `content.md` |
|
|
106
100
|
| `supi_flow_plan` | Store the executable implementation plan in `plan.md` while leaving `content.md` as the approved design summary |
|
|
107
101
|
| `supi_flow_complete_task` | Check off a numbered task (`**Task N**`) in the registered `plan` document |
|
|
108
|
-
| `supi_flow_close` | Mark done
|
|
102
|
+
| `supi_flow_close` | Mark done and write verification results to `archive.md` |
|
|
109
103
|
|
|
110
104
|
Tools should be used instead of calling `tndm` via bash. The agent invokes them with structured parameters.
|
|
111
105
|
|
|
@@ -119,13 +113,6 @@ Tools should be used instead of calling `tndm` via bash. The agent invokes them
|
|
|
119
113
|
|
|
120
114
|
Older tickets may still contain a legacy brainstorm sidecar document, but new flow work should not create or depend on it.
|
|
121
115
|
|
|
122
|
-
## Commands
|
|
123
|
-
|
|
124
|
-
| Command | Description |
|
|
125
|
-
|---|---|
|
|
126
|
-
| `/supi-flow` | List available flow commands |
|
|
127
|
-
| `/supi-flow-status` | Query TNDM for active flow tickets and show the next recommended step |
|
|
128
|
-
|
|
129
116
|
## Prompt templates
|
|
130
117
|
|
|
131
118
|
| Prompt | Description |
|
|
@@ -145,9 +132,22 @@ Flow phases map to TNDM statuses and tags:
|
|
|
145
132
|
|
|
146
133
|
## Dependencies
|
|
147
134
|
|
|
148
|
-
- **tndm CLI
|
|
135
|
+
- **tndm CLI** (`tandem-cli`): required (all ticket operations shell out to `tndm`)
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
brew install mrclrchtr/tap/tandem-cli
|
|
139
|
+
```
|
|
140
|
+
|
|
149
141
|
- **pi**: discovers bundled skills and prompt templates automatically from the package
|
|
150
142
|
|
|
143
|
+
## PI package
|
|
144
|
+
|
|
145
|
+
This extension is published as a [`pi-package`](https://pi.dev/packages) — listed in the PI package gallery. Install directly:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pi install npm:@mrclrchtr/supi-flow
|
|
149
|
+
```
|
|
150
|
+
|
|
151
151
|
## Installation
|
|
152
152
|
|
|
153
153
|
The extension is auto-discovered when the plugin directory is in pi's extension search path:
|
|
@@ -158,7 +158,7 @@ ln -s "$(pwd)/plugins/supi-flow" ~/.pi/agent/extensions/supi-flow
|
|
|
158
158
|
|
|
159
159
|
# Option 2: settings.json
|
|
160
160
|
# Add to ~/.pi/agent/settings.json:
|
|
161
|
-
# { "extensions": ["./plugins/supi-flow/
|
|
161
|
+
# { "extensions": ["./plugins/supi-flow/extensions/index.ts"] }
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
## Development
|
|
@@ -15,7 +15,13 @@ async function run(
|
|
|
15
15
|
const child = execFile(file, args, options, (error, stdout, stderr) => {
|
|
16
16
|
if (error) {
|
|
17
17
|
const msg = toString(stderr).trim() || error.message;
|
|
18
|
-
|
|
18
|
+
const wrapped = new Error(`"${file} ${args.join(" ")}" failed: ${msg}`);
|
|
19
|
+
// Preserve ENOENT and similar system error codes for callers
|
|
20
|
+
const errno = error as NodeJS.ErrnoException;
|
|
21
|
+
if (errno.code) {
|
|
22
|
+
(wrapped as NodeJS.ErrnoException).code = errno.code;
|
|
23
|
+
}
|
|
24
|
+
reject(wrapped);
|
|
19
25
|
return;
|
|
20
26
|
}
|
|
21
27
|
resolve({
|
|
@@ -31,7 +37,37 @@ async function run(
|
|
|
31
37
|
* Throws on non-zero exit, timeout, or other exec error.
|
|
32
38
|
*/
|
|
33
39
|
export async function tndm(args: string[]): Promise<ExecResult> {
|
|
34
|
-
|
|
40
|
+
try {
|
|
41
|
+
return await run("tndm", args, { timeout: 30_000 });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (
|
|
44
|
+
error instanceof Error &&
|
|
45
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
46
|
+
) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"tndm is not installed or not on your PATH.\n\n" +
|
|
49
|
+
"Install it with one of:\n" +
|
|
50
|
+
" brew install mrclrchtr/tap/tndm\n" +
|
|
51
|
+
" cargo install tandem-cli\n" +
|
|
52
|
+
" curl -LsSf https://github.com/mrclrchtr/tandem/releases/latest/download/tandem-cli-installer.sh | sh\n",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Run tndm --version and return the parsed semver string, or null if unavailable.
|
|
61
|
+
* Never throws — callers handle absence gracefully.
|
|
62
|
+
*/
|
|
63
|
+
export async function tndmVersion(): Promise<string | null> {
|
|
64
|
+
try {
|
|
65
|
+
const { stdout } = await run("tndm", ["--version"], { timeout: 5_000 });
|
|
66
|
+
const match = stdout.match(/tndm\s+(\d+\.\d+\.\d+)/);
|
|
67
|
+
return match ? match[1] : null;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
35
71
|
}
|
|
36
72
|
|
|
37
73
|
/**
|
|
@@ -53,28 +89,3 @@ export async function tndmJson<T = Record<string, unknown>>(
|
|
|
53
89
|
);
|
|
54
90
|
}
|
|
55
91
|
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Run `git add .tndm/` and `git commit -m <message>`.
|
|
59
|
-
* Uses `git diff --cached --quiet` to check for staged changes via exit code,
|
|
60
|
-
* avoiding locale-dependent string parsing.
|
|
61
|
-
* Throws on non-zero exit from `git commit`.
|
|
62
|
-
*/
|
|
63
|
-
export async function gitAddCommit(message: string): Promise<{ commitHash: string }> {
|
|
64
|
-
await run("git", ["add", ".tndm/"]);
|
|
65
|
-
|
|
66
|
-
// Check exit code instead of parsing locale-dependent output strings.
|
|
67
|
-
// git diff --cached --quiet exits 0 (no staged changes), non-zero (changes exist or error).
|
|
68
|
-
try {
|
|
69
|
-
await run("git", ["diff", "--cached", "--quiet"]);
|
|
70
|
-
// Exit 0: no changes staged — nothing to commit
|
|
71
|
-
return { commitHash: "" };
|
|
72
|
-
} catch {
|
|
73
|
-
// Exit non-zero: changes exist, or a real git error.
|
|
74
|
-
// Proceed to commit; real errors will surface there.
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const { stdout } = await run("git", ["commit", "-m", message]);
|
|
78
|
-
const match = stdout.match(/\[[^\]]+ ([a-f0-9]+)\]/);
|
|
79
|
-
return { commitHash: match ? match[1] : "" };
|
|
80
|
-
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { dirname, join } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import { tndmVersion } from "./cli.js";
|
|
6
7
|
import { supi_tndm_cli_params, executeTndmCli } from "./tools/tndm-cli.js";
|
|
7
8
|
import {
|
|
8
9
|
supiFlowStartParams,
|
|
@@ -16,13 +17,34 @@ import {
|
|
|
16
17
|
} from "./tools/flow-tools.js";
|
|
17
18
|
|
|
18
19
|
const baseDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
20
|
+
const pkg = JSON.parse(readFileSync(join(baseDir, "package.json"), "utf-8"));
|
|
21
|
+
export const FLOW_VERSION: string = pkg.version;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check tndm version against supi-flow version. Notifies on mismatch.
|
|
25
|
+
* Exported for testing.
|
|
26
|
+
*/
|
|
27
|
+
export async function checkTndmVersion(
|
|
28
|
+
event: { reason: string },
|
|
29
|
+
ctx: { ui: { notify: (message: string, type?: "info" | "warning" | "error") => void } },
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
if (event.reason !== "startup" && event.reason !== "reload") return;
|
|
32
|
+
const tndmVer = await tndmVersion();
|
|
33
|
+
if (!tndmVer) return;
|
|
34
|
+
if (tndmVer !== FLOW_VERSION) {
|
|
35
|
+
ctx.ui.notify(
|
|
36
|
+
`tndm v${tndmVer} found, but supi-flow expects v${FLOW_VERSION}. ` +
|
|
37
|
+
`Install matching version: brew install mrclrchtr/tap/tndm`,
|
|
38
|
+
"warning",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
19
42
|
|
|
20
43
|
export default function (pi: ExtensionAPI) {
|
|
21
|
-
// ──
|
|
22
|
-
pi.on("
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}));
|
|
44
|
+
// ── Version check on startup ────────────────────────────────
|
|
45
|
+
pi.on("session_start", async (event, ctx) => {
|
|
46
|
+
await checkTndmVersion(event, ctx);
|
|
47
|
+
});
|
|
26
48
|
|
|
27
49
|
// ── Tool: supi_tndm_cli ─────────────────────────────────────
|
|
28
50
|
pi.registerTool({
|
|
@@ -105,8 +127,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
105
127
|
label: "Flow Close",
|
|
106
128
|
description:
|
|
107
129
|
"Close a ticket and finalize the flow. " +
|
|
108
|
-
"Writes verification results to archive.md, sets status=done, tags=flow:done,
|
|
109
|
-
"and auto-commits .tndm/ changes.",
|
|
130
|
+
"Writes verification results to archive.md, sets status=done, and tags=flow:done.",
|
|
110
131
|
promptSnippet: "Close a TNDM ticket after implementation and verification",
|
|
111
132
|
promptGuidelines: [
|
|
112
133
|
"Use supi_flow_close at the end of the archive phase after all verification is complete",
|
|
@@ -118,50 +139,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
118
139
|
},
|
|
119
140
|
});
|
|
120
141
|
|
|
121
|
-
// ── Command: /supi-flow-status ──────────────────────────────
|
|
122
|
-
pi.registerCommand("supi-flow-status", {
|
|
123
|
-
description: "Show current flow workflow state",
|
|
124
|
-
handler: async (_args, ctx) => {
|
|
125
|
-
const tickets = await tndmJson<Array<{ id: string; status: string; tags?: string[] }>>([
|
|
126
|
-
"ticket",
|
|
127
|
-
"list",
|
|
128
|
-
]);
|
|
129
|
-
const activeTickets = tickets.filter((ticket) => {
|
|
130
|
-
if (ticket.status === "done") return false;
|
|
131
|
-
const tags = ticket.tags ?? [];
|
|
132
|
-
return tags.includes("flow:brainstorm") || tags.includes("flow:planned") || tags.includes("flow:applying");
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
if (activeTickets.length === 0) {
|
|
136
|
-
ctx.ui.notify("No active flow tickets. Start with /skill:supi-flow-brainstorm.", "info");
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const lines = activeTickets.map((ticket) => {
|
|
141
|
-
const tags = ticket.tags ?? [];
|
|
142
|
-
const nextStep = tags.includes("flow:applying")
|
|
143
|
-
? `/skill:supi-flow-archive ${ticket.id}`
|
|
144
|
-
: tags.includes("flow:planned")
|
|
145
|
-
? `/skill:supi-flow-apply ${ticket.id}`
|
|
146
|
-
: `/skill:supi-flow-plan ${ticket.id}`;
|
|
147
|
-
return `${ticket.id} (${ticket.status}) -> ${nextStep}`;
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
ctx.ui.notify(`Active flow tickets:\n${lines.join("\n")}`, "info");
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// ── Command: /supi-flow ─────────────────────────────────────
|
|
155
|
-
pi.registerCommand("supi-flow", {
|
|
156
|
-
description: "List available flow workflow commands",
|
|
157
|
-
handler: async (_args, ctx) => {
|
|
158
|
-
ctx.ui.notify(
|
|
159
|
-
"Flow: /skill:supi-flow-brainstorm -> /skill:supi-flow-plan -> /skill:supi-flow-apply -> /skill:supi-flow-archive\n" +
|
|
160
|
-
" /supi-flow-status -- show current state\n" +
|
|
161
|
-
" /supi-flow -- this help\n" +
|
|
162
|
-
"Available tools: supi_tndm_cli, supi_flow_start, supi_flow_plan, supi_flow_complete_task, supi_flow_close",
|
|
163
|
-
"info",
|
|
164
|
-
);
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
142
|
}
|
|
@@ -2,7 +2,7 @@ import { dirname, join } from "node:path";
|
|
|
2
2
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { type Static, Type } from "typebox";
|
|
4
4
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
5
|
-
import {
|
|
5
|
+
import { tndm, tndmJson } from "../cli.js";
|
|
6
6
|
|
|
7
7
|
// ─── supi_flow_start ───────────────────────────────────────────
|
|
8
8
|
|
|
@@ -10,12 +10,14 @@ export const supiFlowStartParams = Type.Object({
|
|
|
10
10
|
title: Type.String({ description: "Ticket title describing the change" }),
|
|
11
11
|
priority: Type.Optional(
|
|
12
12
|
StringEnum(["p0", "p1", "p2", "p3", "p4"] as const, {
|
|
13
|
-
description: "Priority
|
|
13
|
+
description: "Priority",
|
|
14
|
+
default: "p2",
|
|
14
15
|
}),
|
|
15
16
|
),
|
|
16
17
|
type: Type.Optional(
|
|
17
18
|
StringEnum(["task", "bug", "feature", "chore", "epic"] as const, {
|
|
18
|
-
description: "Ticket type
|
|
19
|
+
description: "Ticket type",
|
|
20
|
+
default: "task",
|
|
19
21
|
}),
|
|
20
22
|
),
|
|
21
23
|
context: Type.Optional(
|
|
@@ -41,8 +43,12 @@ export async function executeFlowStart(params: FlowStartParams) {
|
|
|
41
43
|
if (params.priority) args.push("--priority", params.priority);
|
|
42
44
|
if (params.type) args.push("--type", params.type);
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
+
// Use --json on create to get id + content_path in one call
|
|
47
|
+
const createResult = await tndmJson<{ id: string; content_path?: string }>(args);
|
|
48
|
+
const ticketId = createResult.id;
|
|
49
|
+
const contentPath = createResult.content_path ?? "";
|
|
50
|
+
const ticketDir = contentPath ? dirname(contentPath) : "";
|
|
51
|
+
const pathInfo = ticketDir ? ` at ${ticketDir}` : "";
|
|
46
52
|
|
|
47
53
|
if (params.context) {
|
|
48
54
|
await tndm(["ticket", "update", ticketId, "--content", params.context]);
|
|
@@ -52,10 +58,16 @@ export async function executeFlowStart(params: FlowStartParams) {
|
|
|
52
58
|
content: [
|
|
53
59
|
{
|
|
54
60
|
type: "text" as const,
|
|
55
|
-
text: `Created ticket ${ticketId} with status=todo and flow:brainstorm tag.`,
|
|
61
|
+
text: `Created ticket ${ticketId}${pathInfo} with status=todo and flow:brainstorm tag.`,
|
|
56
62
|
},
|
|
57
63
|
],
|
|
58
|
-
details: {
|
|
64
|
+
details: {
|
|
65
|
+
action: "flow_start",
|
|
66
|
+
ticketId,
|
|
67
|
+
ticketPath: ticketDir,
|
|
68
|
+
status: "todo",
|
|
69
|
+
tags: "flow:brainstorm",
|
|
70
|
+
},
|
|
59
71
|
};
|
|
60
72
|
}
|
|
61
73
|
|
|
@@ -79,8 +91,14 @@ export type FlowPlanParams = Static<typeof supiFlowPlanParams>;
|
|
|
79
91
|
|
|
80
92
|
export async function executeFlowPlan(params: FlowPlanParams) {
|
|
81
93
|
// Create a "plan" document and get its path
|
|
82
|
-
const docResult = await
|
|
83
|
-
|
|
94
|
+
const docResult = await tndmJson<{ path: string }>([
|
|
95
|
+
"ticket",
|
|
96
|
+
"doc",
|
|
97
|
+
"create",
|
|
98
|
+
params.ticket_id,
|
|
99
|
+
"plan",
|
|
100
|
+
]);
|
|
101
|
+
const docPath = docResult.path;
|
|
84
102
|
|
|
85
103
|
let content = params.plan_content;
|
|
86
104
|
|
|
@@ -100,14 +118,23 @@ export async function executeFlowPlan(params: FlowPlanParams) {
|
|
|
100
118
|
|
|
101
119
|
// Sync fingerprints and update tags
|
|
102
120
|
await tndm(["ticket", "sync", params.ticket_id]);
|
|
121
|
+
|
|
122
|
+
// Replace any flow-state tag with flow:planned — remove all possible flow-state tags
|
|
123
|
+
// first, then add flow:planned, to work correctly regardless of the ticket's current
|
|
124
|
+
// flow state (brainstorm, planned, applying, or done).
|
|
125
|
+
await tndm([
|
|
126
|
+
"ticket",
|
|
127
|
+
"update",
|
|
128
|
+
params.ticket_id,
|
|
129
|
+
"--remove-tags",
|
|
130
|
+
"flow:brainstorm,flow:planned,flow:applying,flow:done",
|
|
131
|
+
]);
|
|
103
132
|
await tndm([
|
|
104
133
|
"ticket",
|
|
105
134
|
"update",
|
|
106
135
|
params.ticket_id,
|
|
107
136
|
"--add-tags",
|
|
108
137
|
"flow:planned",
|
|
109
|
-
"--remove-tags",
|
|
110
|
-
"flow:brainstorm",
|
|
111
138
|
]);
|
|
112
139
|
|
|
113
140
|
return {
|
|
@@ -287,16 +314,28 @@ export async function executeFlowClose(params: FlowCloseParams) {
|
|
|
287
314
|
|
|
288
315
|
if (params.verification_results) {
|
|
289
316
|
// Create/register archive.md via document registry, then write results
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
317
|
+
const docResult = await tndmJson<{ path: string }>([
|
|
318
|
+
"ticket",
|
|
319
|
+
"doc",
|
|
320
|
+
"create",
|
|
321
|
+
params.ticket_id,
|
|
322
|
+
"archive",
|
|
323
|
+
]);
|
|
324
|
+
archivePath = docResult.path;
|
|
325
|
+
writeFileSync(archivePath, `# Archive\n\n${params.verification_results}\n`, "utf-8");
|
|
326
|
+
await tndm(["ticket", "sync", params.ticket_id]);
|
|
298
327
|
}
|
|
299
328
|
|
|
329
|
+
// Replace any flow-state tag with flow:done — remove all possible flow-state tags
|
|
330
|
+
// first, then set status and add flow:done, to work correctly regardless of the
|
|
331
|
+
// ticket's current flow state.
|
|
332
|
+
await tndm([
|
|
333
|
+
"ticket",
|
|
334
|
+
"update",
|
|
335
|
+
params.ticket_id,
|
|
336
|
+
"--remove-tags",
|
|
337
|
+
"flow:brainstorm,flow:planned,flow:applying,flow:done",
|
|
338
|
+
]);
|
|
300
339
|
await tndm([
|
|
301
340
|
"ticket",
|
|
302
341
|
"update",
|
|
@@ -305,25 +344,13 @@ export async function executeFlowClose(params: FlowCloseParams) {
|
|
|
305
344
|
"done",
|
|
306
345
|
"--add-tags",
|
|
307
346
|
"flow:done",
|
|
308
|
-
"--remove-tags",
|
|
309
|
-
"flow:applying",
|
|
310
347
|
]);
|
|
311
348
|
|
|
312
|
-
let commitHash = "";
|
|
313
|
-
try {
|
|
314
|
-
const commitResult = await gitAddCommit(`chore(tndm): close ${params.ticket_id}`);
|
|
315
|
-
commitHash = commitResult.commitHash;
|
|
316
|
-
} catch {
|
|
317
|
-
// Non-fatal if commit fails
|
|
318
|
-
}
|
|
319
|
-
|
|
320
349
|
return {
|
|
321
350
|
content: [
|
|
322
351
|
{
|
|
323
352
|
type: "text" as const,
|
|
324
|
-
text: `Ticket ${params.ticket_id} closed (status=done, flow:done)
|
|
325
|
-
commitHash ? ` Committed as ${commitHash}.` : ""
|
|
326
|
-
}`,
|
|
353
|
+
text: `Ticket ${params.ticket_id} closed (status=done, flow:done).`,
|
|
327
354
|
},
|
|
328
355
|
],
|
|
329
356
|
details: {
|
|
@@ -331,7 +358,6 @@ export async function executeFlowClose(params: FlowCloseParams) {
|
|
|
331
358
|
ticketId: params.ticket_id,
|
|
332
359
|
status: "done",
|
|
333
360
|
tags: "flow:done",
|
|
334
|
-
commitHash,
|
|
335
361
|
},
|
|
336
362
|
};
|
|
337
363
|
}
|
|
@@ -8,8 +8,6 @@ export const actionEnum = StringEnum([
|
|
|
8
8
|
"show",
|
|
9
9
|
"list",
|
|
10
10
|
"awareness",
|
|
11
|
-
"doc_create",
|
|
12
|
-
"sync",
|
|
13
11
|
] as const);
|
|
14
12
|
|
|
15
13
|
export const supi_tndm_cli_params = Type.Object({
|
|
@@ -61,11 +59,6 @@ export const supi_tndm_cli_params = Type.Object({
|
|
|
61
59
|
Type.String({ description: "Markdown content body for the ticket" }),
|
|
62
60
|
),
|
|
63
61
|
|
|
64
|
-
// Document params
|
|
65
|
-
name: Type.Optional(
|
|
66
|
-
Type.String({ description: "Document name for doc_create (e.g. 'plan', 'archive')" }),
|
|
67
|
-
),
|
|
68
|
-
|
|
69
62
|
// List params
|
|
70
63
|
all: Type.Optional(Type.Boolean({ description: "Include done tickets in list" })),
|
|
71
64
|
definition: Type.Optional(
|
|
@@ -89,6 +82,7 @@ export const supi_tndm_cli_params = Type.Object({
|
|
|
89
82
|
* show → tndm ticket show <id> --json
|
|
90
83
|
* list → tndm ticket list [--all] [--definition <state>] --json
|
|
91
84
|
* awareness → tndm awareness --against <ref> --json
|
|
85
|
+
* doc_create and sync are internal operations used by flow tools, not exposed here.
|
|
92
86
|
*/
|
|
93
87
|
export type TndmCliParams = Static<typeof supi_tndm_cli_params>;
|
|
94
88
|
|
|
@@ -192,43 +186,6 @@ export async function executeTndmCli(params: TndmCliParams) {
|
|
|
192
186
|
details: { action: "awareness", awareness: result },
|
|
193
187
|
};
|
|
194
188
|
}
|
|
195
|
-
|
|
196
|
-
case "doc_create": {
|
|
197
|
-
if (!params.id) {
|
|
198
|
-
throw new Error("supi_tndm_cli: id is required for doc_create");
|
|
199
|
-
}
|
|
200
|
-
if (!params.name) {
|
|
201
|
-
throw new Error("supi_tndm_cli: name is required for doc_create");
|
|
202
|
-
}
|
|
203
|
-
const result = await tndm(["ticket", "doc", "create", params.id, params.name]);
|
|
204
|
-
return {
|
|
205
|
-
content: [
|
|
206
|
-
{
|
|
207
|
-
type: "text" as const,
|
|
208
|
-
text: result.stdout || `Document '${params.name}' created for ${params.id}`,
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
details: {
|
|
212
|
-
action: "doc_create",
|
|
213
|
-
ticketId: params.id,
|
|
214
|
-
name: params.name,
|
|
215
|
-
path: result.stdout.trim(),
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
case "sync": {
|
|
221
|
-
if (!params.id) {
|
|
222
|
-
throw new Error("supi_tndm_cli: id is required for sync");
|
|
223
|
-
}
|
|
224
|
-
const result = await tndm(["ticket", "sync", params.id]);
|
|
225
|
-
return {
|
|
226
|
-
content: [
|
|
227
|
-
{ type: "text" as const, text: result.stdout || `Ticket ${params.id} synced` },
|
|
228
|
-
],
|
|
229
|
-
details: { action: "sync", ticketId: params.id },
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
189
|
}
|
|
233
190
|
}
|
|
234
191
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "PI extension for spec-driven workflow (brainstorm → plan → apply → archive) with TNDM ticket coordination, custom tools, and 5 auto-discovered skills",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"tndm",
|
|
10
|
+
"workflow",
|
|
11
|
+
"ticket-coordination"
|
|
12
|
+
],
|
|
6
13
|
"license": "Apache-2.0",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
]
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/mrclrchtr/tandem"
|
|
11
17
|
},
|
|
12
|
-
"main": "
|
|
18
|
+
"main": "extensions/index.ts",
|
|
13
19
|
"dependencies": {
|
|
14
20
|
"typebox": "^1.1.38"
|
|
15
21
|
},
|
|
@@ -18,15 +24,15 @@
|
|
|
18
24
|
"@earendil-works/pi-coding-agent": "*"
|
|
19
25
|
},
|
|
20
26
|
"files": [
|
|
21
|
-
"
|
|
27
|
+
"extensions/",
|
|
22
28
|
"skills/",
|
|
23
29
|
"prompts/",
|
|
24
30
|
"README.md"
|
|
25
31
|
],
|
|
26
32
|
"devDependencies": {
|
|
27
|
-
"@earendil-works/pi-ai": "
|
|
28
|
-
"@earendil-works/pi-coding-agent": "
|
|
29
|
-
"@types/node": "
|
|
30
|
-
"vitest": "
|
|
33
|
+
"@earendil-works/pi-ai": "0.74.0",
|
|
34
|
+
"@earendil-works/pi-coding-agent": "0.74.0",
|
|
35
|
+
"@types/node": "25.7.0",
|
|
36
|
+
"vitest": "4.1.6"
|
|
31
37
|
}
|
|
32
38
|
}
|
|
@@ -2,17 +2,62 @@
|
|
|
2
2
|
description: Agent retrospective on project setup, architecture, tooling, and workflows
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
Agent Retrospective — Project Setup & Tooling
|
|
5
|
+
# Agent Retrospective — Project Setup & Tooling
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Reflect on what made development harder than necessary during this coding session.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Focus only on friction points actually encountered while implementing, debugging, validating, or navigating the task. Do not infer or invent issues from the repository structure, architecture, codebase shape, or available tooling alone.
|
|
10
|
+
|
|
11
|
+
Do not do additional research or inspect the repository again. Use only your memory of this session.
|
|
12
|
+
|
|
13
|
+
If no concrete friction points were encountered, write exactly:
|
|
14
|
+
|
|
15
|
+
> No concrete friction points encountered during this session.
|
|
16
|
+
|
|
17
|
+
Consider concrete examples such as:
|
|
10
18
|
- sources of friction or unnecessary complexity
|
|
11
19
|
- confusing or inconsistent patterns
|
|
12
20
|
- tooling or processes that slowed implementation
|
|
13
|
-
- over-engineered abstractions
|
|
21
|
+
- over-engineered abstractions encountered during the work
|
|
14
22
|
- repetitive manual work that could be automated
|
|
15
|
-
- missing documentation, scripts, or conventions
|
|
16
|
-
-
|
|
23
|
+
- missing documentation, scripts, tests, or conventions
|
|
24
|
+
- setup, validation, CI, or workflow issues
|
|
25
|
+
- anything that should be simplified, standardized, removed, or made more obvious
|
|
26
|
+
|
|
27
|
+
Be honest and specific. The goal is to identify practical improvements that would reduce unnecessary work, reduce token usage, reduce debugging, or improve outcomes in future sessions. This is not a complaint log.
|
|
28
|
+
|
|
29
|
+
## Output Format
|
|
30
|
+
|
|
31
|
+
Group items under the sections below. Skip sections that have no concrete items.
|
|
32
|
+
|
|
33
|
+
Sections are grouped by where the fix should happen. Each friction point should appear once, under the best-fitting section.
|
|
34
|
+
|
|
35
|
+
Order items by expected future work avoided, highest first.
|
|
36
|
+
|
|
37
|
+
Each item should include:
|
|
38
|
+
|
|
39
|
+
- **Problem**: What concrete friction occurred?
|
|
40
|
+
- **Impact**: What extra work, risk, delay, token usage, or confusion it caused.
|
|
41
|
+
- **Fix**: The smallest concrete change that would prevent or reduce it next time.
|
|
42
|
+
|
|
43
|
+
## Sections
|
|
44
|
+
|
|
45
|
+
### Repository Changes
|
|
46
|
+
|
|
47
|
+
Changes to files, code organization, architecture, abstractions, conventions, tests, or docs.
|
|
48
|
+
|
|
49
|
+
### Tooling & Process Changes
|
|
50
|
+
|
|
51
|
+
Changes to scripts, commands, setup flows, CI checks, local validation, automation, or manual workflows.
|
|
52
|
+
|
|
53
|
+
### Harness / Agent Tool Changes
|
|
54
|
+
|
|
55
|
+
Changes to the agent harness, available tools, PI extensions, context providers, retrieval tools, or generated prompt context.
|
|
56
|
+
|
|
57
|
+
### Prompt / Context Changes
|
|
58
|
+
|
|
59
|
+
Changes to the coding-session prompt, repo instructions, task framing, generated context, or other information shown to the agent.
|
|
60
|
+
|
|
61
|
+
### Other
|
|
17
62
|
|
|
18
|
-
|
|
63
|
+
Anything that should be simplified, standardized, removed, or made more obvious that does not fit above.
|