Run prettier on .pi folder
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 +271,10 @@ async function fetchErrors(
|
||||
token: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ErrorListResponse> {
|
||||
return fetchApi(url, token, (data) => {
|
||||
return fetchApi(
|
||||
url,
|
||||
token,
|
||||
(data) => {
|
||||
const obj = data as Record<string, unknown>;
|
||||
if (!obj.errors || !Array.isArray(obj.errors)) {
|
||||
throw new Error(
|
||||
@@ -285,7 +284,9 @@ async function fetchErrors(
|
||||
);
|
||||
}
|
||||
return data as ErrorListResponse;
|
||||
}, signal);
|
||||
},
|
||||
signal,
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchError(
|
||||
@@ -293,7 +294,10 @@ async function fetchError(
|
||||
token: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ErrorDetailResponse> {
|
||||
return fetchApi(url, token, (data) => {
|
||||
return fetchApi(
|
||||
url,
|
||||
token,
|
||||
(data) => {
|
||||
const obj = data as Record<string, unknown>;
|
||||
if (!obj.error || typeof obj.error !== "object") {
|
||||
throw new Error(
|
||||
@@ -303,7 +307,9 @@ async function fetchError(
|
||||
);
|
||||
}
|
||||
return data as ErrorDetailResponse;
|
||||
}, signal);
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 +563,8 @@ export default function prodLogsExtension(pi: ExtensionAPI) {
|
||||
|
||||
return {
|
||||
render(width: number) {
|
||||
const pct = viewer.total > 0
|
||||
const pct =
|
||||
viewer.total > 0
|
||||
? Math.round((viewer.offset / viewer.total) * 100)
|
||||
: 0;
|
||||
const from = viewer.offset + 1;
|
||||
@@ -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(
|
||||
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;
|
||||
|
||||
@@ -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,7 +180,8 @@ export default function s3BrowserExtension(pi: ExtensionAPI) {
|
||||
// Summary header line
|
||||
const summary = `${objects.length} files · ${humanSize(totalSize)} total`;
|
||||
|
||||
const selectedKey = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
||||
const selectedKey = await ctx.ui.custom<string | null>(
|
||||
(tui, theme, _kb, done) => {
|
||||
const container = new Container();
|
||||
|
||||
// Top border
|
||||
@@ -184,16 +191,19 @@ export default function s3BrowserExtension(pi: ExtensionAPI) {
|
||||
|
||||
// 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),
|
||||
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),
|
||||
);
|
||||
container.addChild(new Text(theme.fg("dim", "─".repeat(40)), 1, 0));
|
||||
|
||||
// SelectList
|
||||
const selectList = new SelectList(items, Math.min(items.length, 20), {
|
||||
@@ -233,7 +243,8 @@ export default function s3BrowserExtension(pi: ExtensionAPI) {
|
||||
tui.requestRender();
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (selectedKey) {
|
||||
const obj = objects.find((o) => o.key === selectedKey);
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -13,9 +13,5 @@
|
||||
".gnupg/",
|
||||
"~/.pi/agent/sessions/"
|
||||
],
|
||||
"blocked_commands": [
|
||||
"printenv",
|
||||
"\\benv\\b",
|
||||
"\\bset\\b"
|
||||
]
|
||||
"blocked_commands": ["printenv", "\\benv\\b", "\\bset\\b"]
|
||||
}
|
||||
|
||||
+2
-6
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"skills": [
|
||||
"../.claude/skills"
|
||||
],
|
||||
"packages": [
|
||||
"npm:pi-mcp-adapter"
|
||||
]
|
||||
"skills": ["../.claude/skills"],
|
||||
"packages": ["npm:pi-mcp-adapter"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user