From 7bf89d9a1dae06cdd58b5497c8eeab9bce931039 Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Mon, 4 May 2026 21:21:10 +0100 Subject: [PATCH] Run prettier on .pi folder --- .pi/extensions/000-sensitive-file-guard.ts | 18 ++- .pi/extensions/prod-errors/index.ts | 180 +++++++++++++-------- .pi/extensions/prod-logs/index.ts | 93 ++++++----- .pi/extensions/s3-browser/index.ts | 121 +++++++------- .pi/prompts/new-backlog-task.md | 19 +-- .pi/prompts/plan-backlog-task.md | 18 +-- .pi/sensitive-paths.json | 6 +- .pi/settings.json | 8 +- 8 files changed, 272 insertions(+), 191 deletions(-) diff --git a/.pi/extensions/000-sensitive-file-guard.ts b/.pi/extensions/000-sensitive-file-guard.ts index 0f7f6ebf..cf575212 100644 --- a/.pi/extensions/000-sensitive-file-guard.ts +++ b/.pi/extensions/000-sensitive-file-guard.ts @@ -17,7 +17,7 @@ function loadConfig(cwd: string): Config { function patternToRegex(pattern: string): RegExp { let escaped = pattern .replace(/[.+^${}()|[\]\\]/g, "\\$&") // Escape regex specials except * - .replace(/\*/g, ".*"); // * β†’ .* + .replace(/\*/g, ".*"); // * β†’ .* return new RegExp(escaped, "i"); } @@ -35,7 +35,7 @@ export default function (pi: ExtensionAPI) { // Proactively tell the agent which paths are off-limits so it doesn't try in the first place pi.on("before_agent_start", async (event, _ctx) => { if (config.blocked_paths.length === 0) return; - const pathList = config.blocked_paths.map((p) => ` - \`${p}\``).join('\n'); + const pathList = config.blocked_paths.map((p) => ` - \`${p}\``).join("\n"); return { systemPrompt: event.systemPrompt + @@ -63,7 +63,8 @@ export default function (pi: ExtensionAPI) { `🚫 ACCESS DENIED by sensitive-file-guard: paths matching "${match}" are permanently protected. ` + `DO NOT retry with other tools (bash, find, ls, grep, etc.). ` + `Tell the user the file is protected and STOP.`; - if (ctx.hasUI) ctx.ui.notify(`Blocked sensitive path: ${path}`, "warning"); + if (ctx.hasUI) + ctx.ui.notify(`Blocked sensitive path: ${path}`, "warning"); return { block: true, reason }; } return; @@ -79,14 +80,21 @@ export default function (pi: ExtensionAPI) { // Rough substring match: does the pattern text (with glob stripped) // appear in the command? const literal = p.replace(/[*?]/g, ""); - return literal.length > 0 && command.toLowerCase().includes(literal.toLowerCase()); + return ( + literal.length > 0 && + command.toLowerCase().includes(literal.toLowerCase()) + ); }); if (pathHit) { const reason = `🚫 ACCESS DENIED by sensitive-file-guard: commands targeting "${pathHit}" are permanently blocked. ` + `DO NOT retry with alternative commands. Tell the user the path is protected and STOP.`; - if (ctx.hasUI) ctx.ui.notify(`Blocked sensitive path in bash: ${pathHit}`, "warning"); + if (ctx.hasUI) + ctx.ui.notify( + `Blocked sensitive path in bash: ${pathHit}`, + "warning", + ); return { block: true, reason }; } diff --git a/.pi/extensions/prod-errors/index.ts b/.pi/extensions/prod-errors/index.ts index c638a95e..29d98bed 100644 --- a/.pi/extensions/prod-errors/index.ts +++ b/.pi/extensions/prod-errors/index.ts @@ -23,11 +23,7 @@ import { DEFAULT_MAX_LINES, } from "@mariozechner/pi-coding-agent"; import { BorderedLoader } from "@mariozechner/pi-coding-agent"; -import { - matchesKey, - Key, - truncateToWidth, -} from "@mariozechner/pi-tui"; +import { matchesKey, Key, truncateToWidth } from "@mariozechner/pi-tui"; import { Type } from "typebox"; // ── Types ─────────────────────────────────────────────────────────────────── @@ -275,17 +271,22 @@ async function fetchErrors( token: string, signal?: AbortSignal, ): Promise { - return fetchApi(url, token, (data) => { - const obj = data as Record; - if (!obj.errors || !Array.isArray(obj.errors)) { - throw new Error( - `Unexpected API response: 'errors' field is missing or not an array. Got: ${ - obj.errors === null ? "null" : typeof obj.errors - }.`, - ); - } - return data as ErrorListResponse; - }, signal); + return fetchApi( + url, + token, + (data) => { + const obj = data as Record; + if (!obj.errors || !Array.isArray(obj.errors)) { + throw new Error( + `Unexpected API response: 'errors' field is missing or not an array. Got: ${ + obj.errors === null ? "null" : typeof obj.errors + }.`, + ); + } + return data as ErrorListResponse; + }, + signal, + ); } async function fetchError( @@ -293,17 +294,22 @@ async function fetchError( token: string, signal?: AbortSignal, ): Promise { - return fetchApi(url, token, (data) => { - const obj = data as Record; - if (!obj.error || typeof obj.error !== "object") { - throw new Error( - `Unexpected API response: 'error' field is missing or not an object. Got: ${ - obj.error === null ? "null" : typeof obj.error - }.`, - ); - } - return data as ErrorDetailResponse; - }, signal); + return fetchApi( + url, + token, + (data) => { + const obj = data as Record; + if (!obj.error || typeof obj.error !== "object") { + throw new Error( + `Unexpected API response: 'error' field is missing or not an object. Got: ${ + obj.error === null ? "null" : typeof obj.error + }.`, + ); + } + return data as ErrorDetailResponse; + }, + signal, + ); } // ── TUI Theme ─────────────────────────────────────────────────────────────── @@ -378,7 +384,10 @@ class ErrorBrowser { private readonly baseUrl: string; private readonly token: string; private readonly requestRender: () => void; - private readonly notify: (msg: string, level: "error" | "info" | "warning") => void; + private readonly notify: ( + msg: string, + level: "error" | "info" | "warning", + ) => void; constructor( errors: ErrorListItem[], @@ -411,7 +420,10 @@ class ErrorBrowser { if (this.errors.length === 0) { this.cursorIndex = -1; } else { - this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, this.errors.length - 1)); + this.cursorIndex = Math.max( + 0, + Math.min(this.cursorIndex, this.errors.length - 1), + ); } } @@ -423,7 +435,10 @@ class ErrorBrowser { } else if (this.cursorIndex >= this.scrollOffset + maxEntries) { this.scrollOffset = this.cursorIndex - maxEntries + 1; } - this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, this.errors.length - 1)); + this.scrollOffset = Math.max( + 0, + Math.min(this.scrollOffset, this.errors.length - 1), + ); } // ── HTTP helpers ─────────────────────────────────────────────────────── @@ -471,7 +486,11 @@ class ErrorBrowser { this.invalidate(); this.requestRender(); - const url = buildUrl(this.baseUrl, "/api/v1/errors", this.buildQueryParams()); + const url = buildUrl( + this.baseUrl, + "/api/v1/errors", + this.buildQueryParams(), + ); const controller = new AbortController(); this.currentAbortController = controller; @@ -508,7 +527,11 @@ class ErrorBrowser { this.invalidate(); this.requestRender(); - const url = buildUrl(this.baseUrl, "/api/v1/errors", this.buildQueryParams()); + const url = buildUrl( + this.baseUrl, + "/api/v1/errors", + this.buildQueryParams(), + ); const controller = new AbortController(); this.currentAbortController = controller; @@ -638,7 +661,10 @@ class ErrorBrowser { const pageSize = Math.floor(this.visibleHeight / this.errorEntryLines); this.cursorIndex -= pageSize; moved = true; - } else if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("f"))) { + } else if ( + matchesKey(data, Key.pageDown) || + matchesKey(data, Key.ctrl("f")) + ) { const pageSize = Math.floor(this.visibleHeight / this.errorEntryLines); this.cursorIndex += pageSize; moved = true; @@ -689,7 +715,10 @@ class ErrorBrowser { } else if (matchesKey(data, Key.pageUp)) { this.detailScrollOffset -= this.visibleHeight; moved = true; - } else if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("f"))) { + } else if ( + matchesKey(data, Key.pageDown) || + matchesKey(data, Key.ctrl("f")) + ) { this.detailScrollOffset += this.visibleHeight; moved = true; } else if (matchesKey(data, Key.ctrl("b"))) { @@ -699,13 +728,19 @@ class ErrorBrowser { this.detailScrollOffset = 0; moved = true; } else if (matchesKey(data, Key.end) || matchesKey(data, "G")) { - this.detailScrollOffset = Math.max(0, this.buildDetailLines().length - 1); + this.detailScrollOffset = Math.max( + 0, + this.buildDetailLines().length - 1, + ); moved = true; } if (moved) { const maxOffset = Math.max(0, this.buildDetailLines().length - 1); - this.detailScrollOffset = Math.max(0, Math.min(this.detailScrollOffset, maxOffset)); + this.detailScrollOffset = Math.max( + 0, + Math.min(this.detailScrollOffset, maxOffset), + ); this.invalidate(); } @@ -746,7 +781,9 @@ class ErrorBrowser { for (let i = 1; i < viewportRows - 1; i++) { if (i === mid) { - result.push(truncateToWidth(theme.fg("muted", " Loading…"), width, "")); + result.push( + truncateToWidth(theme.fg("muted", " Loading…"), width, ""), + ); } else { result.push(""); } @@ -809,12 +846,24 @@ class ErrorBrowser { else if (error.status === "resolved") statusColor = "success"; const mutedLabel = error.muted ? " [MUTED]" : ""; - const statusBadge = theme.fg(statusColor, `[${error.status.toUpperCase()}]`); + const statusBadge = theme.fg( + statusColor, + `[${error.status.toUpperCase()}]`, + ); // Line 1: cursor + status badge + kind + truncated reason - const reasonText = truncateReason(error.reason, Math.max(30, width - 25)); + const reasonText = truncateReason( + error.reason, + Math.max(30, width - 25), + ); const line1 = `${cursor} ${statusBadge}${mutedLabel} ${error.kind}: ${reasonText}`; - result.push(truncateToWidth(isCursor ? theme.fg("accent", line1) : line1, width, "")); + result.push( + truncateToWidth( + isCursor ? theme.fg("accent", line1) : line1, + width, + "", + ), + ); // Line 2: source info const sourceInfo = error.source_function @@ -857,9 +906,7 @@ class ErrorBrowser { truncateToWidth( theme.fg( "dim", - hasMore - ? ` l load more q quit` - : ` β€” end of results β€” q quit`, + hasMore ? ` l load more q quit` : ` β€” end of results β€” q quit`, ), width, "", @@ -972,13 +1019,13 @@ export default function prodErrorsExtension(pi: ExtensionAPI) { parameters: Type.Object({ status: Type.Optional( Type.Union([Type.Literal("resolved"), Type.Literal("unresolved")], { - description: - "Filter by error status: 'resolved' or 'unresolved'.", + description: "Filter by error status: 'resolved' or 'unresolved'.", }), ), muted: Type.Optional( Type.Boolean({ - description: "Filter by muted state. true = only muted, false = only unmuted.", + description: + "Filter by muted state. true = only muted, false = only unmuted.", }), ), search: Type.Optional( @@ -1000,13 +1047,7 @@ export default function prodErrorsExtension(pi: ExtensionAPI) { }), ), }), - async execute( - _toolCallId, - params, - signal, - _onUpdate, - _ctx, - ) { + async execute(_toolCallId, params, signal, _onUpdate, _ctx) { // Early abort check if (signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }] }; @@ -1043,7 +1084,11 @@ export default function prodErrorsExtension(pi: ExtensionAPI) { if (params.muted !== undefined && params.muted !== null) { queryParams.muted = String(params.muted); } - if (params.search !== undefined && params.search !== null && params.search !== "") { + if ( + params.search !== undefined && + params.search !== null && + params.search !== "" + ) { queryParams.search = params.search; } if (params.limit !== undefined && params.limit !== null) { @@ -1075,8 +1120,18 @@ export default function prodErrorsExtension(pi: ExtensionAPI) { // Handle empty results if (data.errors.length === 0) { return { - content: [{ type: "text", text: "No errors found matching the given filters." }], - details: { total: data.total, count: 0, offset: data.offset, limit: data.limit }, + content: [ + { + type: "text", + text: "No errors found matching the given filters.", + }, + ], + details: { + total: data.total, + count: 0, + offset: data.offset, + limit: data.limit, + }, }; } @@ -1126,13 +1181,7 @@ export default function prodErrorsExtension(pi: ExtensionAPI) { "The error ID (integer) to fetch full details for. Get this from fetch_production_errors.", }), }), - async execute( - _toolCallId, - params, - signal, - _onUpdate, - _ctx, - ) { + async execute(_toolCallId, params, signal, _onUpdate, _ctx) { // Early abort check if (signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }] }; @@ -1256,7 +1305,10 @@ export default function prodErrorsExtension(pi: ExtensionAPI) { ); if (errorsData === null) { - ctx.ui.notify("Cancelled or fetch failed β€” check logs for details", "info"); + ctx.ui.notify( + "Cancelled or fetch failed β€” check logs for details", + "info", + ); return; } diff --git a/.pi/extensions/prod-logs/index.ts b/.pi/extensions/prod-logs/index.ts index 15e49bbd..56be987a 100644 --- a/.pi/extensions/prod-logs/index.ts +++ b/.pi/extensions/prod-logs/index.ts @@ -21,11 +21,7 @@ import { DEFAULT_MAX_LINES, } from "@mariozechner/pi-coding-agent"; import { Type } from "typebox"; -import { - matchesKey, - Key, - truncateToWidth, -} from "@mariozechner/pi-tui"; +import { matchesKey, Key, truncateToWidth } from "@mariozechner/pi-tui"; // ── Types ─────────────────────────────────────────────────────────────────── @@ -80,7 +76,10 @@ class LogViewer { this.visualMode = false; this.visualAnchor = 0; this.scrollOffset = 0; - this.visibleHeight = Math.min(lines.length, Math.max(10, process.stdout.rows - chromeHeight)); + this.visibleHeight = Math.min( + lines.length, + Math.max(10, process.stdout.rows - chromeHeight), + ); this.clampCursor(); this.clampViewport(); this.invalidate(); @@ -101,7 +100,10 @@ class LogViewer { if (this.lines.length === 0) { this.cursorIndex = -1; } else { - this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, this.lines.length - 1)); + this.cursorIndex = Math.max( + 0, + Math.min(this.cursorIndex, this.lines.length - 1), + ); } } @@ -129,7 +131,10 @@ class LogViewer { } // Escape or q in normal mode closes the viewer - if (!this.visualMode && (matchesKey(data, Key.escape) || matchesKey(data, "q"))) { + if ( + !this.visualMode && + (matchesKey(data, Key.escape) || matchesKey(data, "q")) + ) { this.onClose?.(); return; } @@ -156,7 +161,10 @@ class LogViewer { const start = Math.min(this.visualAnchor, this.cursorIndex); const end = Math.max(this.visualAnchor, this.cursorIndex); // this.lines[0] = newest, so reverse the slice for oldest-first - const text = this.lines.slice(start, end + 1).reverse().join("\n"); + const text = this.lines + .slice(start, end + 1) + .reverse() + .join("\n"); this.onCopy?.(text); } return; @@ -184,7 +192,10 @@ class LogViewer { } else if (matchesKey(data, Key.pageUp)) { this.cursorIndex -= this.visibleHeight; moved = true; - } else if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("f"))) { + } else if ( + matchesKey(data, Key.pageDown) || + matchesKey(data, Key.ctrl("f")) + ) { this.cursorIndex += this.visibleHeight; moved = true; } else if (matchesKey(data, Key.ctrl("b"))) { @@ -237,9 +248,8 @@ class LogViewer { const basePrefix = ` ${numStr} β”‚ `; const isCursor = absIdx === this.cursorIndex; - const isSelected = this.visualMode - && absIdx >= selStart - && absIdx <= selEnd; + const isSelected = + this.visualMode && absIdx >= selStart && absIdx <= selEnd; if (isCursor) { // Cursor takes priority over selection highlight @@ -349,13 +359,7 @@ export default function prodLogsExtension(pi: ExtensionAPI) { }), ), }), - async execute( - _toolCallId, - params, - signal, - _onUpdate, - _ctx, - ) { + async execute(_toolCallId, params, signal, _onUpdate, _ctx) { // Early abort check if (signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }] }; @@ -498,7 +502,10 @@ export default function prodLogsExtension(pi: ExtensionAPI) { ); if (logLines === null) { - ctx.ui.notify("Cancelled or fetch failed β€” check logs for details", "info"); + ctx.ui.notify( + "Cancelled or fetch failed β€” check logs for details", + "info", + ); return; } @@ -519,7 +526,10 @@ export default function prodLogsExtension(pi: ExtensionAPI) { let loading = false; const visibleLogLines = () => - Math.min(logLinesMut.length, Math.max(10, process.stdout.rows - chromeHeight)); + Math.min( + logLinesMut.length, + Math.max(10, process.stdout.rows - chromeHeight), + ); const viewer = new LogViewer(logLinesMut, visibleLogLines()); @@ -553,9 +563,10 @@ export default function prodLogsExtension(pi: ExtensionAPI) { return { render(width: number) { - const pct = viewer.total > 0 - ? Math.round((viewer.offset / viewer.total) * 100) - : 0; + const pct = + viewer.total > 0 + ? Math.round((viewer.offset / viewer.total) * 100) + : 0; const from = viewer.offset + 1; const to = Math.min( viewer.offset + visibleLogLines(), @@ -565,20 +576,28 @@ export default function prodLogsExtension(pi: ExtensionAPI) { const result: string[] = []; const border = theme.fg("accent", "─".repeat(width)); result.push(border); - result.push(truncateToWidth( - theme.fg("accent", theme.bold(" Production Logs")), - width, - "", - )); + result.push( + truncateToWidth( + theme.fg("accent", theme.bold(" Production Logs")), + width, + "", + ), + ); const countText = loading ? " Refreshing..." : ` ${logLinesMut.length} lines`; - result.push(truncateToWidth(theme.fg("muted", countText), width, "")); - result.push(truncateToWidth(theme.fg("dim", "─".repeat(40)), width, "")); + result.push( + truncateToWidth(theme.fg("muted", countText), width, ""), + ); + result.push( + truncateToWidth(theme.fg("dim", "─".repeat(40)), width, ""), + ); const infoText = ` Lines ${from}-${to} of ${logLinesMut.length} (${pct}%)`; - result.push(truncateToWidth(theme.fg("dim", infoText), width, "")); + result.push( + truncateToWidth(theme.fg("dim", infoText), width, ""), + ); // Log lines const logRendered = viewer.render(width, theme); @@ -590,11 +609,9 @@ export default function prodLogsExtension(pi: ExtensionAPI) { : viewer.visualMode ? " VISUAL: j/k extend Β· y copy Β· Esc cancel Β· Enter copy line" : " ↑↓/jk scroll Β· PgUp/PgDn page Β· Home/End jump Β· v visual Β· Enter copy line Β· r refresh Β· Esc close"; - result.push(truncateToWidth( - theme.fg("dim", helpLine), - width, - "", - )); + result.push( + truncateToWidth(theme.fg("dim", helpLine), width, ""), + ); result.push(border); return result; diff --git a/.pi/extensions/s3-browser/index.ts b/.pi/extensions/s3-browser/index.ts index 396f68b3..abf67385 100644 --- a/.pi/extensions/s3-browser/index.ts +++ b/.pi/extensions/s3-browser/index.ts @@ -33,7 +33,10 @@ const S3_REGION = "nbg1"; function humanSize(bytes: number): string { if (bytes === 0) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; - const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1); + const i = Math.min( + Math.floor(Math.log(bytes) / Math.log(1024)), + units.length - 1, + ); return `${(bytes / 1024 ** i).toFixed(1)} ${units[i]}`; } @@ -152,7 +155,10 @@ export default function s3BrowserExtension(pi: ExtensionAPI) { ); if (objects === null) { - ctx.ui.notify("Cancelled or fetch failed β€” check logs for details", "info"); + ctx.ui.notify( + "Cancelled or fetch failed β€” check logs for details", + "info", + ); return; } @@ -174,66 +180,71 @@ export default function s3BrowserExtension(pi: ExtensionAPI) { // Summary header line const summary = `${objects.length} files Β· ${humanSize(totalSize)} total`; - const selectedKey = await ctx.ui.custom((tui, theme, _kb, done) => { - const container = new Container(); + const selectedKey = await ctx.ui.custom( + (tui, theme, _kb, done) => { + const container = new Container(); - // Top border - container.addChild( - new DynamicBorder((s: string) => theme.fg("accent", s)), - ); + // Top border + container.addChild( + new DynamicBorder((s: string) => theme.fg("accent", s)), + ); - // Header - container.addChild( - new Text(theme.fg("accent", theme.bold("S3 Backups β€” ffmusiclibrary/prod/")), 1, 0), - ); - container.addChild( - new Text(theme.fg("muted", summary), 1, 0), - ); + // Header + container.addChild( + new Text( + theme.fg( + "accent", + theme.bold("S3 Backups β€” ffmusiclibrary/prod/"), + ), + 1, + 0, + ), + ); + container.addChild(new Text(theme.fg("muted", summary), 1, 0)); - // Spacer before list - container.addChild( - new Text(theme.fg("dim", "─".repeat(40)), 1, 0), - ); + // Spacer before list + container.addChild(new Text(theme.fg("dim", "─".repeat(40)), 1, 0)); - // SelectList - const selectList = new SelectList(items, Math.min(items.length, 20), { - selectedPrefix: (t: string) => theme.fg("accent", t), - selectedText: (t: string) => theme.fg("accent", t), - description: (t: string) => theme.fg("muted", t), - scrollInfo: (t: string) => theme.fg("dim", t), - noMatch: (t: string) => theme.fg("warning", t), - }); - selectList.onSelect = (item) => done(item.value); - selectList.onCancel = () => done(null); - container.addChild(selectList); + // SelectList + const selectList = new SelectList(items, Math.min(items.length, 20), { + selectedPrefix: (t: string) => theme.fg("accent", t), + selectedText: (t: string) => theme.fg("accent", t), + description: (t: string) => theme.fg("muted", t), + scrollInfo: (t: string) => theme.fg("dim", t), + noMatch: (t: string) => theme.fg("warning", t), + }); + selectList.onSelect = (item) => done(item.value); + selectList.onCancel = () => done(null); + container.addChild(selectList); - // Footer hint - container.addChild( - new Text( - theme.fg("dim", "↑↓ navigate Β· enter select Β· esc close"), - 1, - 0, - ), - ); + // Footer hint + container.addChild( + new Text( + theme.fg("dim", "↑↓ navigate Β· enter select Β· esc close"), + 1, + 0, + ), + ); - // Bottom border - container.addChild( - new DynamicBorder((s: string) => theme.fg("accent", s)), - ); + // Bottom border + container.addChild( + new DynamicBorder((s: string) => theme.fg("accent", s)), + ); - return { - render(width: number) { - return container.render(width); - }, - invalidate() { - container.invalidate(); - }, - handleInput(data: string) { - selectList.handleInput(data); - tui.requestRender(); - }, - }; - }); + return { + render(width: number) { + return container.render(width); + }, + invalidate() { + container.invalidate(); + }, + handleInput(data: string) { + selectList.handleInput(data); + tui.requestRender(); + }, + }; + }, + ); if (selectedKey) { const obj = objects.find((o) => o.key === selectedKey); diff --git a/.pi/prompts/new-backlog-task.md b/.pi/prompts/new-backlog-task.md index 3e8c0686..1d3f55e6 100644 --- a/.pi/prompts/new-backlog-task.md +++ b/.pi/prompts/new-backlog-task.md @@ -1,6 +1,7 @@ --- description: Create a new backlog task --- + Let's create a new backlog task via a predefined process. **IT'S CRITICAL YOU FOLLOW THIS PROCESS**. To start, ask me for: @@ -21,30 +22,30 @@ For a plan to be valid, it has to cover these requirements: 1. **Objective alignment** β€” The plan clearly states how it achieves the stated objective of the issue, with a direct mapping between the problem and the proposed solution. 2. **Simplicity and alternatives considered** β€” The plan identifies the simplest viable approach. It documents alternatives that were evaluated, explains why they were rejected or deferred, and -justifies why the chosen approach is the right trade-off for the objective. + justifies why the chosen approach is the right trade-off for the objective. 3. **Completeness and sequencing** β€” The plan covers every implementation step in a logical order. No gaps exist between "current state" and "done state." Dependencies between steps are -explicit so the plan can be executed sequentially without backtracking. + explicit so the plan can be executed sequentially without backtracking. 4. **Verifiability** β€” Each implementation step includes concrete verification instructions (tests to run, manual checks to perform, queries to validate) that prove the step was completed -correctly before moving on. + correctly before moving on. 5. **Architecture impact analysis** β€” The plan identifies all architectural touchpoints affected by the change (schemas, contexts, PubSub topics, supervision tree, routes, external APIs, UI -components) and describes how each is impacted, including any migration or deprecation path. + components) and describes how each is impacted, including any migration or deprecation path. 6. **Performance profile** β€” The plan explains the performance characteristics of the chosen approach: expected runtime complexity, database query patterns (including N+1 risks), memory -footprint, and any latency or throughput implications under realistic load. + footprint, and any latency or throughput implications under realistic load. 7. **Benchmarking requirements** β€” The plan identifies whether one-off or ongoing benchmarks are needed to validate or monitor the performance profile, and if so, specifies what to measure, how -to measure it, and what thresholds define acceptable performance. + to measure it, and what thresholds define acceptable performance. 8. **Cost profile** β€” If the implementation consumes paid resources (API calls, compute, storage, third-party services), the plan includes a cost estimate or model so the financial impact is -understood before implementation begins. + understood before implementation begins. 9. **Production infrastructure steps** β€” Any manual changes required in production (environment variables, service provisioning, database migrations with special handling, DNS changes, firewall -rules) are documented in a dedicated "Production Changes" section, separate from the implementation steps, with rollout and rollback instructions. + rules) are documented in a dedicated "Production Changes" section, separate from the implementation steps, with rollout and rollback instructions. 10. **Documentation updates** β€” The plan enumerates which project documentation files must be created or updated as part of the implementation (e.g., `docs/architecture.md`, -`docs/project-conventions.md`, README, API docs), with a summary of what changes each file needs. + `docs/project-conventions.md`, README, API docs), with a summary of what changes each file needs. At the end, do not offer to start implementation. Instead, offer to start a new session so that I can review the plan via `/review-task-plan`. diff --git a/.pi/prompts/plan-backlog-task.md b/.pi/prompts/plan-backlog-task.md index 6c08bc10..7a4f6c47 100644 --- a/.pi/prompts/plan-backlog-task.md +++ b/.pi/prompts/plan-backlog-task.md @@ -10,30 +10,30 @@ For a plan to be valid, it has to cover these requirements: 1. **Objective alignment** β€” The plan clearly states how it achieves the stated objective of the issue, with a direct mapping between the problem and the proposed solution. 2. **Simplicity and alternatives considered** β€” The plan identifies the simplest viable approach. It documents alternatives that were evaluated, explains why they were rejected or deferred, and -justifies why the chosen approach is the right trade-off for the objective. + justifies why the chosen approach is the right trade-off for the objective. 3. **Completeness and sequencing** β€” The plan covers every implementation step in a logical order. No gaps exist between "current state" and "done state." Dependencies between steps are -explicit so the plan can be executed sequentially without backtracking. + explicit so the plan can be executed sequentially without backtracking. 4. **Verifiability** β€” Each implementation step includes concrete verification instructions (tests to run, manual checks to perform, queries to validate) that prove the step was completed -correctly before moving on. + correctly before moving on. 5. **Architecture impact analysis** β€” The plan identifies all architectural touchpoints affected by the change (schemas, contexts, PubSub topics, supervision tree, routes, external APIs, UI -components) and describes how each is impacted, including any migration or deprecation path. + components) and describes how each is impacted, including any migration or deprecation path. 6. **Performance profile** β€” The plan explains the performance characteristics of the chosen approach: expected runtime complexity, database query patterns (including N+1 risks), memory -footprint, and any latency or throughput implications under realistic load. + footprint, and any latency or throughput implications under realistic load. 7. **Benchmarking requirements** β€” The plan identifies whether one-off or ongoing benchmarks are needed to validate or monitor the performance profile, and if so, specifies what to measure, how -to measure it, and what thresholds define acceptable performance. + to measure it, and what thresholds define acceptable performance. 8. **Cost profile** β€” If the implementation consumes paid resources (API calls, compute, storage, third-party services), the plan includes a cost estimate or model so the financial impact is -understood before implementation begins. + understood before implementation begins. 9. **Production infrastructure steps** β€” Any manual changes required in production (environment variables, service provisioning, database migrations with special handling, DNS changes, firewall -rules) are documented in a dedicated "Production Changes" section, separate from the implementation steps, with rollout and rollback instructions. + rules) are documented in a dedicated "Production Changes" section, separate from the implementation steps, with rollout and rollback instructions. 10. **Documentation updates** β€” The plan enumerates which project documentation files must be created or updated as part of the implementation (e.g., `docs/architecture.md`, -`docs/project-conventions.md`, README, API docs), with a summary of what changes each file needs. + `docs/project-conventions.md`, README, API docs), with a summary of what changes each file needs. At the end, do not offer to start implementation. Instead, offer to start a new session so that I can review the plan via `/review-task-plan`. diff --git a/.pi/sensitive-paths.json b/.pi/sensitive-paths.json index 8f02b060..cace227e 100644 --- a/.pi/sensitive-paths.json +++ b/.pi/sensitive-paths.json @@ -13,9 +13,5 @@ ".gnupg/", "~/.pi/agent/sessions/" ], - "blocked_commands": [ - "printenv", - "\\benv\\b", - "\\bset\\b" - ] + "blocked_commands": ["printenv", "\\benv\\b", "\\bset\\b"] } diff --git a/.pi/settings.json b/.pi/settings.json index 11522e07..03ae0d67 100644 --- a/.pi/settings.json +++ b/.pi/settings.json @@ -1,8 +1,4 @@ { - "skills": [ - "../.claude/skills" - ], - "packages": [ - "npm:pi-mcp-adapter" - ] + "skills": ["../.claude/skills"], + "packages": ["npm:pi-mcp-adapter"] }