@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 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 --> SLOP["/skill:supi-flow-slop-detect
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"| SLOP
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
- Auto-commits .tndm/"]
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
- Six skills ship under `skills/`:
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, write verification results to `archive.md`, and auto-commit `.tndm/` |
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**: required (all ticket operations shell out to `tndm`)
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/src/index.ts"] }
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
- reject(new Error(`"${file} ${args.join(" ")}" failed: ${msg}`));
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
- return run("tndm", args, { timeout: 30_000 });
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 { tndmJson } from "./cli.js";
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
- // ── Resource discovery ──────────────────────────────────────
22
- pi.on("resources_discover", () => ({
23
- skillPaths: [join(baseDir, "skills")],
24
- promptPaths: [join(baseDir, "prompts")],
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 { gitAddCommit, tndm, tndmJson } from "../cli.js";
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 (default: p2)",
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 (default: task)",
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
- const result = await tndm(args);
45
- const ticketId = result.stdout.trim();
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: { action: "flow_start", ticketId, status: "todo", tags: "flow:brainstorm" },
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 tndm(["ticket", "doc", "create", params.ticket_id, "plan"]);
83
- const docPath = docResult.stdout.trim();
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
- try {
291
- const docResult = await tndm(["ticket", "doc", "create", params.ticket_id, "archive"]);
292
- archivePath = docResult.stdout.trim();
293
- writeFileSync(archivePath, `# Archive\n\n${params.verification_results}\n`, "utf-8");
294
- await tndm(["ticket", "sync", params.ticket_id]);
295
- } catch {
296
- // Non-fatal if doc create fails
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.6.1",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
- "description": "SuPi flow extension spec-driven workflow coupled to TNDM ticket coordination",
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
- "pi": {
8
- "extensions": [
9
- "./src/index.ts"
10
- ]
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/mrclrchtr/tandem"
11
17
  },
12
- "main": "src/index.ts",
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
- "src/",
27
+ "extensions/",
22
28
  "skills/",
23
29
  "prompts/",
24
30
  "README.md"
25
31
  ],
26
32
  "devDependencies": {
27
- "@earendil-works/pi-ai": "^0.74.0",
28
- "@earendil-works/pi-coding-agent": "*",
29
- "@types/node": "^25.6.0",
30
- "vitest": "^4.1.4"
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
- While implementing this feature, what aspects of the project setup, architecture, tooling, workflows, or conventions made development harder than necessary?
7
+ Reflect on what made development harder than necessary during this coding session.
8
8
 
9
- Please specifically identify:
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
- - anything that should be simplified, standardized, or removed
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
- Focus on concrete examples and practical improvements that would increase implementation speed, reliability, and developer/agent experience for future work.
63
+ Anything that should be simplified, standardized, removed, or made more obvious that does not fit above.