repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/the-system/system-command.ts
Rawimport { lstatSync } from "node:fs";
import path from "node:path";
import type {
ExtensionAPI,
ExtensionCommandContext,
RegisteredCommand,
} from "@earendil-works/pi-coding-agent";
import { boardSummaryBlock } from "./board-summary.ts";
import { runDoctor } from "./doctor.ts";
import { applyInitialization, inspectInitialization } from "./init.ts";
import { loadSystemRenderer } from "./loaders.js";
import { openHtmlFile } from "./open-browser.ts";
import { openEditableFile } from "./open-editor.ts";
import { loadProjectIndex } from "./project-index.ts";
import { systemArgumentCompletions } from "./system-command-meta.ts";
import { systemPromptFor } from "./system-prompts.ts";
const LIFECYCLE = new Set([
"research",
"spec",
"spec-verify",
"assess",
"phase",
"plan",
"issue",
"exec",
]);
const BUNDLE_SUBCOMMANDS = new Set([
"research",
"spec",
"phase",
"plan",
"issue",
]);
const NON_LIFECYCLE = new Set([
"init",
"doctor",
"view",
"book",
"board",
"edit",
]);
type CommandOptions = Omit<RegisteredCommand, "name" | "sourceInfo">;
type SystemCommandHooks = Readonly<{
beginLifecycle?: () => void;
activateBundleTool?: () => void;
activateBoardProposal?: (cwd: string) => void | Promise<void>;
activateBoardUpdate?: (cwd: string) => void | Promise<void>;
activateBundleMigration?: () => void;
openBoard?: (cwd: string) => Promise<string>;
}>;
export function createSystemCommand(
pi: ExtensionAPI,
hooks: SystemCommandHooks = {},
): CommandOptions {
return {
description: "The System project governance",
getArgumentCompletions: systemArgumentCompletions,
handler: async (args: string, ctx: ExtensionCommandContext) => {
const parsed = parseSystemArgs(args);
if (!parsed.subcommand) {
ctx.ui.notify(systemHelp(), "info");
return;
}
if (parsed.subcommand === "init") {
await runSystemInit(
pi,
hooks.activateBundleMigration,
parsed.rest,
ctx,
);
return;
}
if (parsed.subcommand === "view") {
const selectors = parsed.rest.trim().split(/\s+/).filter(Boolean);
if (selectors.length !== 1) {
ctx.ui.notify("Usage: /system view <path|id|slug>", "error");
return;
}
const output = await renderSystemView(ctx.cwd, selectors[0] as string);
ctx.ui.notify(await openNotice("System view", output), "info");
return;
}
if (parsed.subcommand === "edit") {
const selectors = parsed.rest.trim().split(/\s+/).filter(Boolean);
if (selectors.length !== 1) {
ctx.ui.notify("Usage: /system edit <path|id|slug>", "error");
return;
}
const output = await editSystemDocument(
ctx.cwd,
selectors[0] as string,
);
ctx.ui.notify(output, output.includes("opened:") ? "info" : "error");
return;
}
if (parsed.subcommand === "book") {
if (parsed.rest.trim()) {
ctx.ui.notify("Usage: /system book", "error");
return;
}
const output = await renderSystemBook(ctx.cwd);
ctx.ui.notify(await openNotice("System book", output), "info");
return;
}
if (parsed.subcommand === "board") {
if (parsed.rest.trim()) {
ctx.ui.notify("Usage: /system board", "error");
return;
}
if (!hooks.openBoard)
throw new Error("System board runtime is unavailable");
ctx.ui.notify(await hooks.openBoard(ctx.cwd), "info");
return;
}
if (parsed.subcommand === "doctor") {
const result = runDoctor(ctx.cwd);
ctx.ui.notify(
formatDoctor(result),
result.exitCode === 0 ? "info" : "warning",
);
return;
}
hooks.beginLifecycle?.();
if (BUNDLE_SUBCOMMANDS.has(parsed.subcommand))
hooks.activateBundleTool?.();
if (parsed.subcommand === "assess")
await hooks.activateBoardProposal?.(ctx.cwd);
if (parsed.subcommand === "exec")
await hooks.activateBoardUpdate?.(ctx.cwd);
pi.sendUserMessage(
systemPromptFor(
parsed.subcommand,
parsed.rest,
boardSummaryBlock(ctx.cwd),
),
);
if (ctx.mode === "print" || ctx.mode === "json") {
await new Promise((resolve) => setTimeout(resolve, 0));
await ctx.waitForIdle();
}
},
};
}
function parseSystemArgs(args: string): { subcommand?: string; rest: string } {
const trimmed = args.trim();
if (!trimmed) return { rest: "" };
const [raw = "", ...rest] = trimmed.split(/\s+/);
const subcommand = raw.toLowerCase();
if (!LIFECYCLE.has(subcommand) && !NON_LIFECYCLE.has(subcommand))
return { rest: "" };
return { subcommand, rest: rest.join(" ") };
}
function systemHelp(): string {
return [
"/system <subcommand>",
"init [PROJECT-PREFIX]",
"doctor",
"view <path|id|slug>",
"edit <path|id|slug>",
"book",
"board",
"research [instructions]",
"spec [instructions]",
"spec-verify [instructions]",
"assess [instructions]",
"phase [instructions]",
"plan [instructions]",
"issue [instructions]",
"exec [instructions]",
].join("\n");
}
async function runSystemInit(
pi: ExtensionAPI,
activateBundleMigration: (() => void) | undefined,
args: string,
ctx: ExtensionCommandContext,
): Promise<void> {
let requested = args.trim() || undefined;
let inspection = inspectInitialization(ctx.cwd, requested);
requested ??= inspection.prefix;
if (!requested) {
requested = await ctx.ui.input("Project prefix", "SYS");
if (!requested) {
ctx.ui.notify("/system init cancelled", "info");
return;
}
inspection = inspectInitialization(ctx.cwd, requested);
}
const configMigration = inspection.rewrites.find((item) =>
item.startsWith(".system/config.json "),
);
const summary = [
`Root: ${inspection.root}`,
`Prefix: ${inspection.prefix}`,
...inspection.creates.map((item) => `Create: ${item}`),
...inspection.cores
.filter((item) => item.state === "missing")
.map((item) => `Create: ${item.path}`),
...inspection.rewrites.map((item) => `Update: ${item}`),
].join("\n");
if (!(await ctx.ui.confirm("Initialize The System?", summary))) {
ctx.ui.notify("/system init cancelled", "info");
return;
}
const reloadRequired =
inspection.creates.length > 0 ||
inspection.rewrites.length > 0 ||
inspection.cores.some((core) => core.state === "missing");
const result = applyInitialization(ctx.cwd, requested, true);
ctx.ui.notify(
[
`Initialized The System at ${result.root}`,
`Prefix: ${result.prefix}`,
...result.changed.map((item) => `Wrote: ${item}`),
...(reloadRequired
? ["Run /reload to activate System format skills."]
: []),
].join("\n"),
"info",
);
if (
!configMigration ||
!(await ctx.ui.confirm(
"Should System bundles be migrated by agent?",
`Machine schema updated mechanically.\n${configMigration}`,
))
)
return;
await ctx.waitForIdle();
activateBundleMigration?.();
pi.sendUserMessage(
systemPromptFor(
"init",
[
"The human approved agent-led bundle migration.",
"Machine config and board schema markers are already current; do not edit them.",
`Source: ${configMigration}`,
"Inspect existing bundles and migrate obsolete structure and content to the current contracts.",
].join("\n"),
),
);
}
function formatDoctor(result: ReturnType<typeof runDoctor>): string {
if (result.exitCode === 0) return `System doctor OK\nRoot: ${result.root}`;
return [
`System doctor found ${result.diagnostics.length} issue(s)`,
`Root: ${result.root}`,
...result.references.map((item) => `Ref: ${item.path} -> ${item.spec}`),
...result.diagnostics.map(
(item) => `${item.code} ${item.path}: ${item.message}`,
),
].join("\n");
}
async function renderSystemView(
cwd: string,
selector: string,
): Promise<string> {
const index = loadProjectIndex(cwd);
const document = resolveDocument(index.root, index.documents, selector);
const { renderSystemViewSite } = await loadSystemRenderer();
return renderSystemViewSite(index, document).file;
}
async function renderSystemBook(cwd: string): Promise<string> {
const { renderSystemBookSite } = await loadSystemRenderer();
return renderSystemBookSite(loadProjectIndex(cwd)).file;
}
async function editSystemDocument(
cwd: string,
selector: string,
): Promise<string> {
const index = loadProjectIndex(cwd);
const file = editableSystemPath(index.root, index.documents, selector);
const opened = await openEditableFile(file);
if (opened.ok)
return `System document opened: ${file}\nEditor: ${opened.command}`;
return `System document not opened: ${file}\nEditor: ${opened.error}`;
}
function editableSystemPath(
root: string,
documents: ReturnType<typeof loadProjectIndex>["documents"],
selector: string,
): string {
const normalized = selector.split(path.sep).join("/");
if (normalized.includes("/") || normalized.endsWith(".md")) {
const target = path.resolve(root, selector);
const relative = path.relative(root, target).split(path.sep).join("/");
if (
relative.startsWith("../") ||
relative === ".." ||
!relative.startsWith(".system/")
)
throw new Error(`System edit path must be inside .system: ${selector}`);
const stats = lstatSync(target, { throwIfNoEntry: false });
if (!stats?.isFile() || stats.isSymbolicLink())
throw new Error(`No editable System file: ${selector}`);
return target;
}
return path.join(root, resolveDocument(root, documents, selector).path);
}
function resolveDocument(
root: string,
documents: ReturnType<typeof loadProjectIndex>["documents"],
selector: string,
) {
const normalized = selector.split(path.sep).join("/");
if (normalized.includes("/") || normalized.endsWith(".md")) {
const relative = path
.relative(root, path.resolve(root, selector))
.split(path.sep)
.join("/");
const found = documents.find((document) => document.path === relative);
if (!found) throw new Error(`No System document path: ${selector}`);
return found;
}
const byId = documents.find((document) => document.metadata.id === selector);
if (byId) return byId;
const bySlug = documents.filter((document) => {
const bundle = document.path.split("/")[2] ?? "";
return bundle.endsWith(`-${selector}`);
});
if (bySlug.length === 1 && bySlug[0]) return bySlug[0];
if (bySlug.length > 1) throw new Error(`Ambiguous System slug: ${selector}`);
throw new Error(`No System document: ${selector}`);
}
async function openNotice(label: string, output: string): Promise<string> {
const opened = await openHtmlFile(output);
if (opened.ok)
return `${label} opened: ${opened.url}\nFile: ${output}\nBrowser: ${opened.command}`;
return `${label} written: ${output}\nOpen: ${opened.url}\nBrowser: ${opened.error}`;
}