import { createHash } from "node:crypto"; import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import { getAgentDir, type ExtensionAPI, type Theme } from "@earendil-works/pi-coding-agent"; import { truncateToWidth } from "@earendil-works/pi-tui"; import { PRIMUS_ASCII_LOGO, PRIMUS_HELP_HINT, PRIMUS_LOGO_INNER_WIDTH, PRIMUS_SUBTITLE, PRIMUS_WORDMARK, } from "../logo.mjs"; /** Below this width, render compact text instead of the large banner. */ const NARROW_HEADER_WIDTH = PRIMUS_LOGO_INNER_WIDTH + 6; const PROJECT_DIRS = ["plans", "models", "tests", "research", "reviews", "output", "scratch"] as const; const GITIGNORE_ENTRIES = ["/output", "/scratch"] as const; const TRACKED_DIRECTORY_PLACEHOLDER = ".gitkeep"; const SCAFFOLD_COMMIT_MESSAGE = "chore: initialize primus project"; const GIT_COMMAND_TIMEOUT_MS = 5_000; const GIT_COMMIT_TIMEOUT_MS = 30_000; const SCAFFOLD_COMMIT_PATHS = [ ".gitignore", ...PROJECT_DIRS.map((dir) => `${dir}/${TRACKED_DIRECTORY_PLACEHOLDER}`), ]; const WORKFLOW_MESSAGE_TYPE = "primus-workflow"; const WORKFLOW_STATE_TYPE = "primus-workflow-state"; const LEGACY_WORKFLOW_STATE_TYPE = "primus-design-workflow-state"; const REQUIRED_WORKFLOW_STAGE_MARKERS = ["Stage 1", "Stage 2", "Stage 3", "Stage 4"] as const; const CAD_REQUEST_PREFIX = /^(design|make|create|build|model|draft|generate|sketch)\b/i; const CAD_INTENT_PATTERN = /^(i need|i want|need|want|please)\b.*\b(box|bracket|blower|enclosure|mount|plate|adapter|housing|fixture|jig|part|assembly|model)\b/i; const NON_DESIGN_PROMPT_PATTERN = /^(help|what can you do|who are you|explain|why|how|status|summarize|review|fix|debug|refactor)\b/i; const MODIFY_ACTION_PATTERN = /\b(modify|change|update|adjust|widen|narrow|increase|decrease|reduce|resize|move|shift|replace|remove|thicken|thin|shorten|lengthen|tweak)\b/i; const MODIFY_EXISTING_CUE_PATTERN = /\b(existing|current|already|same|this|that|it)\b/i; const MODIFY_COMPARATIVE_PATTERN = /\b(wider|narrower|larger|smaller|bigger|thicker|thinner|longer|shorter|deeper|shallower|higher|lower)\b/i; const CAD_REQUEST_WITH_LEADING_COMPARATIVE_PATTERN = /^(design|make|create|build|model|draft|generate|sketch)\b(?:\s+(?:me|us))?(?:\s+(?:a|an|the))?\s+(wider|narrower|larger|smaller|bigger|thicker|thinner|longer|shorter|deeper|shallower|higher|lower)\b/i; const MODIFY_TARGET_NOUN_PATTERN = /\b(box|bracket|blower|outlet|enclosure|mount|plate|adapter|housing|fixture|jig|part|assembly|duct|shroud|fan|lid|cover|hole|port)\b/i; const MODIFY_TOKEN_STOP_WORDS = new Set([ "a", "an", "and", "change", "current", "decrease", "existing", "for", "from", "increase", "it", "make", "modify", "please", "reduce", "remove", "resize", "same", "shift", "that", "the", "this", "update", "wider", "widen", ]); const MODIFY_SHORTLIST_LIMIT = 3; const WORKFLOW_KINDS = ["design", "modify"] as const; type PrimusWorkflowKind = (typeof WORKFLOW_KINDS)[number]; type PrimusWorkflowSource = "command" | "router" | "legacy"; type ScaffoldResult = { created: string[]; updated: string[]; kept: string[]; }; type GitRepositoryStatus = "initialized" | "existing"; type ScaffoldCommitStatus = "created" | "skipped"; type PrimusWorkflowState = { active: boolean; kind?: PrimusWorkflowKind; workflowHash?: string; source?: PrimusWorkflowSource; targetModelSlug?: string; }; type PrimusDesignWorkflowState = Omit; type PrimusWorkflowDefinition = { kind: PrimusWorkflowKind; commandUsage: string; helpLine: string; commandDescription: string; workflowFileName: `${PrimusWorkflowKind}.md`; fullSignature: string; activeSignature: string; startInstruction: string; stageReminderLines: string[]; }; type ModifyTargetCandidate = { slug: string; label: string; summary: string; score: number; }; type ModifyTargetDiscovery = | { status: "shortlist"; candidates: ModifyTargetCandidate[]; } | { status: "none"; }; const WORKFLOW_DEFINITIONS: Record = { design: { kind: "design", commandUsage: "/design ", helpLine: " /design Start new CAD workflow", commandDescription: "Start or resume CAD design workflow with dynamic injection.", workflowFileName: "design.md", fullSignature: "[Primus Environment: Loading CAD Design Workflow Specification]", activeSignature: "[Primus Environment: CAD Design Workflow Active]", startInstruction: "Begin with Stage 1 — Goal Alignment unless an approved plan already exists for this request.", stageReminderLines: [ "1. Goal Alignment — if approved `plans//plan.md` is missing, start here.", "2. Research — only after an approved plan exists.", "3. Script Authoring — only after research artifacts exist.", "4. Review Loop — required before final preview handoff and completion.", ], }, modify: { kind: "modify", commandUsage: "/modify ", helpLine: " /modify Modify existing governed CAD workflow", commandDescription: "Start or resume CAD modify workflow for an existing governed model.", workflowFileName: "modify.md", fullSignature: "[Primus Environment: Loading CAD Modify Workflow Specification]", activeSignature: "[Primus Environment: CAD Modify Workflow Active]", startInstruction: "Begin with Stage 1 — Target Discovery & Plan Update before making code changes.", stageReminderLines: [ "1. Target Discovery & Plan Update — find the governed model, confirm it, inspect artifacts, then update the canonical plan.", "2. Research — reuse existing context and only research when the requested change introduces real uncertainty.", "3. Script Authoring — modify the existing model in place and update only affected tests.", "4. Review Loop — required before final preview handoff and completion.", ], }, }; function buildKnownTargetModifyGuidance(targetModelSlug: string | undefined): string[] { if (!targetModelSlug) { return []; } const planPath = `plans/${targetModelSlug}/plan.md`; return [ `Known target model: \`${targetModelSlug}\``, `Skip repository-wide target discovery and shortlist confirmation; inspect \`${planPath}\`, existing \`models/${targetModelSlug}/\`, and affected \`tests/${targetModelSlug}/\` artifacts before asking follow-up questions.`, `After inspection, ask only lean blocking questions and update \`${planPath}\` in place before research or implementation proceeds.`, ]; } function buildModifyDiscoveryGuidance(discovery: ModifyTargetDiscovery | undefined): string[] { if (!discovery) { return []; } if (discovery.status === "none") { return [ "Repo-only governed target discovery found no plausible governed models in current repository for this request.", "Do not guess or edit an arbitrary script.", "Tell the user no governed modify target was found and offer to start a new design workflow with `/design ` instead.", ]; } return [ "Repo-only governed target discovery found these plausible matches:", ...discovery.candidates.map( (candidate, index) => `${index + 1}. ${candidate.label} — ${candidate.summary} (slug \`${candidate.slug}\`)`, ), "Confirm the target explicitly with a short human-friendly shortlist through `npm:@juicesharp/rpiv-ask-user-question` when structured UI is available; fall back to normal prose if not.", "Stay lightweight: use repository names, slugs, and file structure first; inspect full plans or model/test artifacts only after the user confirms a candidate or the shortlist needs clarification.", "Do not guess between shortlist candidates.", ]; } function titleCaseSlug(slug: string): string { return slug .split(/[-_]+/) .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } function tokenizeForDiscovery(value: string): string[] { return value .toLowerCase() .split(/[^a-z0-9]+/i) .filter((token) => token.length > 1 && !MODIFY_TOKEN_STOP_WORDS.has(token)); } function stripFileExtension(fileName: string): string { return fileName.replace(/\.[^.]+$/, ""); } function summarizeCandidate(modelFiles: string[], testFiles: string[]): string { const scriptSummary = modelFiles.slice(0, 2).join(", "); const moreScripts = modelFiles.length > 2 ? ` +${modelFiles.length - 2} more` : ""; const testSummary = testFiles.length > 0 ? "tests present" : "tests absent"; return `governed model; scripts: ${scriptSummary}${moreScripts}; ${testSummary}`; } function scoreModifyCandidate( prompt: string, promptTokens: Set, slug: string, modelFiles: string[], testFiles: string[], ): number { const normalizedPrompt = prompt.toLowerCase(); const slugPhrase = slug.replace(/[-_]+/g, " ").toLowerCase(); let score = normalizedPrompt.includes(slugPhrase) ? 6 : 0; for (const token of tokenizeForDiscovery(slug)) { if (promptTokens.has(token)) { score += 4; } } for (const fileName of [...modelFiles, ...testFiles]) { for (const token of tokenizeForDiscovery(stripFileExtension(fileName))) { if (promptTokens.has(token)) { score += 2; } } } return score; } async function listImmediateFileNames(path: string): Promise { try { const entries = await readdir(path, { withFileTypes: true }); return entries.filter((entry) => entry.isFile()).map((entry) => entry.name).sort(); } catch (error) { if (error instanceof Error && "code" in error && error.code === "ENOENT") { return []; } throw error; } } async function readPlanLabel(planPath: string, slug: string): Promise { try { const planText = await readFile(planPath, "utf8"); for (const rawLine of planText.split(/\r?\n/)) { const line = rawLine.trim(); if (line.startsWith("# ")) { return line.slice(2).trim() || titleCaseSlug(slug); } if (line.length > 0) { return line.replace(/^[#>*\-\s]+/, "").trim() || titleCaseSlug(slug); } } } catch (error) { if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) { throw error; } } return titleCaseSlug(slug); } export async function discoverModifyTargets(options: { cwd: string; prompt: string; limit?: number; }): Promise { const { cwd, prompt, limit = MODIFY_SHORTLIST_LIMIT } = options; const promptTokens = new Set(tokenizeForDiscovery(prompt)); if (promptTokens.size === 0) { return { status: "none" }; } let planEntries; try { planEntries = await readdir(resolve(cwd, "plans"), { withFileTypes: true }); } catch (error) { if (error instanceof Error && "code" in error && error.code === "ENOENT") { return { status: "none" }; } throw error; } const scoredCandidates = await Promise.all( planEntries .filter((entry) => entry.isDirectory()) .map(async (entry) => { const slug = entry.name; const planPath = resolve(cwd, "plans", slug, "plan.md"); if (!(await pathExists(planPath))) { return undefined; } const modelFiles = await listImmediateFileNames(resolve(cwd, "models", slug)); if (modelFiles.length === 0) { return undefined; } const testFiles = await listImmediateFileNames(resolve(cwd, "tests", slug)); const score = scoreModifyCandidate(prompt, promptTokens, slug, modelFiles, testFiles); if (score <= 0) { return undefined; } return { slug, planPath, modelFiles, testFiles, score, }; }), ); const rankedCandidates = scoredCandidates .filter((candidate): candidate is NonNullable => candidate !== undefined) .sort((left, right) => right.score - left.score || left.slug.localeCompare(right.slug)) .slice(0, limit); if (rankedCandidates.length === 0) { return { status: "none" }; } return { status: "shortlist", candidates: await Promise.all( rankedCandidates.map(async (candidate) => ({ slug: candidate.slug, label: await readPlanLabel(candidate.planPath, candidate.slug), summary: summarizeCandidate(candidate.modelFiles, candidate.testFiles), score: candidate.score, })), ), }; } function getWorkflowStageReminderLines( workflowKind: PrimusWorkflowKind, targetModelSlug: string | undefined, ): string[] { const definition = WORKFLOW_DEFINITIONS[workflowKind]; if (workflowKind !== "modify" || !targetModelSlug) { return definition.stageReminderLines; } const planPath = `plans/${targetModelSlug}/plan.md`; return [ `1. Plan Inspection & Update — skip discovery for known target \`${targetModelSlug}\`; inspect \`${planPath}\`, existing \`models/${targetModelSlug}/\`, and affected \`tests/${targetModelSlug}/\` artifacts before lean follow-up questions and plan updates.`, ...definition.stageReminderLines.slice(1), ]; } function getWorkflowStartInstruction( workflowKind: PrimusWorkflowKind, targetModelSlug: string | undefined, ): string { const definition = WORKFLOW_DEFINITIONS[workflowKind]; if (workflowKind !== "modify" || !targetModelSlug) { return definition.startInstruction; } return `Begin with Stage 1 — Plan Inspection & Update for known target \`${targetModelSlug}\`; skip discovery, inspect artifacts first, then update the canonical plan before making code changes.`; } function buildHeaderLines(width: number, theme: Theme): string[] { if (width < NARROW_HEADER_WIDTH) { return [ truncateToWidth(theme.fg("accent", `${PRIMUS_WORDMARK} · ${PRIMUS_SUBTITLE}`), width, theme.fg("dim", "...")), truncateToWidth(theme.fg("muted", PRIMUS_HELP_HINT), width, theme.fg("dim", "...")), ]; } const lines: string[] = [""]; for (const raw of PRIMUS_ASCII_LOGO) { const colored = theme.fg("borderAccent", raw); lines.push(truncateToWidth(colored, width, "")); } lines.push(truncateToWidth(theme.fg("muted", PRIMUS_SUBTITLE), width, theme.fg("dim", "..."))); lines.push(truncateToWidth(theme.fg("muted", PRIMUS_HELP_HINT), width, theme.fg("dim", "..."))); lines.push(""); return lines; } function getPrimusHelpText(): string { return [ "Slash commands:", WORKFLOW_DEFINITIONS.design.helpLine, WORKFLOW_DEFINITIONS.modify.helpLine, " /help Show Primus command help", " /init Set up project folders for CAD work", " /primus-header-off Restore default header", "", "Workflow:", " goal alignment -> research -> authoring -> review", ].join("\n"); } async function pathExists(path: string): Promise { try { await stat(path); return true; } catch (error) { if (error instanceof Error && "code" in error && error.code === "ENOENT") { return false; } throw error; } } async function ensureGitignoreEntries(cwd: string): Promise<"created" | "updated" | "kept"> { const gitignorePath = resolve(cwd, ".gitignore"); const existed = await pathExists(gitignorePath); const original = existed ? await readFile(gitignorePath, "utf8") : ""; const entries = new Set(original.split(/\r?\n/).filter(Boolean)); let changed = !existed; for (const entry of GITIGNORE_ENTRIES) { if (!entries.has(entry)) { entries.add(entry); changed = true; } } if (!changed) { return "kept"; } await writeFile(gitignorePath, `${Array.from(entries).join("\n")}\n`, "utf8"); return existed ? "updated" : "created"; } async function ensureTrackedDirectoryPlaceholder( cwd: string, dir: (typeof PROJECT_DIRS)[number], ): Promise<"existing" | "created" | "skipped"> { const dirPath = resolve(cwd, dir); const entries = await readdir(dirPath); if (entries.includes(TRACKED_DIRECTORY_PLACEHOLDER)) { return "existing"; } if (entries.length > 0) { return "skipped"; } const placeholderPath = resolve(dirPath, TRACKED_DIRECTORY_PLACEHOLDER); await writeFile(placeholderPath, "", "utf8"); return "created"; } async function scaffoldProject(cwd: string): Promise { const created: string[] = []; const updated: string[] = []; const kept: string[] = []; for (const dir of PROJECT_DIRS) { const dirPath = resolve(cwd, dir); const existed = await pathExists(dirPath); await mkdir(dirPath, { recursive: true }); const placeholderStatus = await ensureTrackedDirectoryPlaceholder(cwd, dir); if (!existed) { created.push(dir); continue; } if (placeholderStatus === "created") { updated.push(dir); continue; } kept.push(dir); } const gitignoreStatus = await ensureGitignoreEntries(cwd); if (gitignoreStatus === "kept") { kept.push(".gitignore"); } else if (gitignoreStatus === "updated") { updated.push(".gitignore"); } else { created.push(".gitignore"); } return { created, updated, kept }; } function formatScaffoldSummary(result: ScaffoldResult): string { const createdSummary = result.created.length > 0 ? `created: ${result.created.join(", ")}` : "created: nothing"; const updatedSummary = result.updated.length > 0 ? `; updated: ${result.updated.join(", ")}` : ""; const keptSummary = result.kept.length > 0 ? `; kept existing: ${result.kept.join(", ")}` : ""; return `${createdSummary}${updatedSummary}${keptSummary}`; } function formatGitCommand(args: string[]): string { return ["git", ...args].join(" "); } function formatGitFailure(args: string[], result: { stdout: string; stderr: string }): string { const details = [result.stderr.trim(), result.stdout.trim()].filter((value) => value.length > 0).join("\n"); return details.length > 0 ? `${formatGitCommand(args)}\n${details}` : formatGitCommand(args); } async function runGit(pi: ExtensionAPI, cwd: string, args: string[], timeout = GIT_COMMAND_TIMEOUT_MS) { return pi.exec("git", args, { cwd, timeout }); } async function ensureGitRepository(pi: ExtensionAPI, cwd: string): Promise { const probeArgs = ["rev-parse", "--is-inside-work-tree"]; const probeResult = await runGit(pi, cwd, probeArgs); if (probeResult.code === 0 && probeResult.stdout.trim() === "true") { return "existing"; } const initArgs = ["init"]; const initResult = await runGit(pi, cwd, initArgs); if (initResult.code !== 0) { throw new Error(`Unable to initialize git repository.\n${formatGitFailure(initArgs, initResult)}`); } return "initialized"; } async function requireCleanScaffoldPaths(pi: ExtensionAPI, cwd: string, gitStatus: GitRepositoryStatus): Promise { const worktreeDiffArgs = ["diff", "--quiet", "--", ...SCAFFOLD_COMMIT_PATHS]; const worktreeDiffResult = await runGit(pi, cwd, worktreeDiffArgs); if (worktreeDiffResult.code !== 0 && worktreeDiffResult.code !== 1) { throw new Error(`Unable to inspect Primus scaffold paths.\n${formatGitFailure(worktreeDiffArgs, worktreeDiffResult)}`); } const cachedDiffArgs = ["diff", "--cached", "--quiet", "--", ...SCAFFOLD_COMMIT_PATHS]; const cachedDiffResult = await runGit(pi, cwd, cachedDiffArgs); if (cachedDiffResult.code !== 0 && cachedDiffResult.code !== 1) { throw new Error(`Unable to inspect Primus scaffold paths.\n${formatGitFailure(cachedDiffArgs, cachedDiffResult)}`); } const statusArgs = ["status", "--short", "--untracked-files=all", "--ignored=matching", "--", ...SCAFFOLD_COMMIT_PATHS]; const statusResult = await runGit(pi, cwd, statusArgs); if (statusResult.code !== 0) { throw new Error(`Unable to inspect Primus scaffold paths.\n${formatGitFailure(statusArgs, statusResult)}`); } const statusText = statusResult.stdout.trim(); const hasTrackedChanges = worktreeDiffResult.code === 1 || cachedDiffResult.code === 1; const hasUntrackedChanges = gitStatus === "existing" && statusText.length > 0; if (hasTrackedChanges || hasUntrackedChanges) { throw new Error( [ `Primus scaffold paths are dirty at ${cwd}.`, "Commit, stash, or discard local changes before running `/init`.", statusText, ].join("\n"), ); } } async function createScaffoldCommit(pi: ExtensionAPI, cwd: string): Promise { const existingScaffoldPaths: string[] = []; for (const path of SCAFFOLD_COMMIT_PATHS) { if (await pathExists(resolve(cwd, path))) { existingScaffoldPaths.push(path); } } if (existingScaffoldPaths.length === 0) { return "skipped"; } const addArgs = ["add", "-f", ...existingScaffoldPaths]; const addResult = await runGit(pi, cwd, addArgs); if (addResult.code !== 0) { throw new Error(`Unable to stage Primus scaffold.\n${formatGitFailure(addArgs, addResult)}`); } const diffArgs = ["diff", "--cached", "--quiet", "--", ...existingScaffoldPaths]; const diffResult = await runGit(pi, cwd, diffArgs); if (diffResult.code === 0) { return "skipped"; } if (diffResult.code !== 1) { throw new Error(`Unable to inspect staged Primus scaffold.\n${formatGitFailure(diffArgs, diffResult)}`); } const commitArgs = [ "-c", "user.name=Primus", "-c", "user.email=primus@example.invalid", "-c", "commit.gpgSign=false", "commit", "-m", SCAFFOLD_COMMIT_MESSAGE, "--", ...existingScaffoldPaths, ]; const commitResult = await runGit(pi, cwd, commitArgs, GIT_COMMIT_TIMEOUT_MS); if (commitResult.code !== 0) { throw new Error(`Unable to commit Primus scaffold.\n${formatGitFailure(commitArgs, commitResult)}`); } return "created"; } function formatInitSummary( result: ScaffoldResult, gitStatus: GitRepositoryStatus, commitStatus: ScaffoldCommitStatus, ): string { const commitSummary = commitStatus === "created" ? `created (${SCAFFOLD_COMMIT_MESSAGE})` : "skipped"; return `${formatScaffoldSummary(result)}; git: ${gitStatus}; commit: ${commitSummary}`; } /** * Extracted for unit testing. */ export function resolveWorkflowInjection(options: { workflowText: string; userPrompt: string; workflowActive: boolean; forceFullInject?: boolean; workflowKind?: PrimusWorkflowKind; targetModelSlug?: string; modifyDiscovery?: ModifyTargetDiscovery; }): string { const { workflowText, userPrompt, workflowActive, forceFullInject = false, workflowKind = "design", targetModelSlug, modifyDiscovery, } = options; const definition = WORKFLOW_DEFINITIONS[workflowKind]; const knownTargetGuidance = workflowKind === "modify" ? buildKnownTargetModifyGuidance(targetModelSlug) : []; const discoveryGuidance = workflowKind === "modify" && !targetModelSlug ? buildModifyDiscoveryGuidance(modifyDiscovery) : []; const stageReminderLines = getWorkflowStageReminderLines(workflowKind, targetModelSlug); const startInstruction = getWorkflowStartInstruction(workflowKind, targetModelSlug); if (!workflowActive || forceFullInject) { return [ definition.fullSignature, "", workflowText.trim(), "", "Hard rules remain active: Single Writer. Serena first. Verification-first. No completion without review.", "Final delivery also requires one successful preview handoff after the reviewed artifact is ready; if it fails, keep the final code and commit but report the workflow as incomplete until preview succeeds.", startInstruction, ...knownTargetGuidance, ...discoveryGuidance, "", `User Request: ${userPrompt}`, ].join("\n"); } return [ definition.activeSignature, "", "Required stages, in order:", ...stageReminderLines, "", "Hard rules: Single Writer. Serena first. Verification-first. No completion without review.", "Completion requires one successful final preview handoff after review fixes are committed; do not trigger it during exploratory spikes or intermediate runs.", "If preview handoff fails, keep the final code and commit intact but report the workflow as incomplete until preview succeeds.", "Outside Stage 1, keep research, authoring, and review moving; reuse structured questioning only for blocking ambiguity or changes that materially affect artifact shape, constraints, interfaces, or outputs.", startInstruction, ...knownTargetGuidance, ...discoveryGuidance, "", `User Request: ${userPrompt}`, ].join("\n"); } function sha256(text: string): string { return createHash("sha256").update(text).digest("hex"); } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isWorkflowKind(value: unknown): value is PrimusWorkflowKind { return typeof value === "string" && WORKFLOW_KINDS.includes(value as PrimusWorkflowKind); } function extractTextContent(content: unknown): string[] { if (typeof content === "string") { return [content]; } if (!Array.isArray(content)) { return []; } return content.flatMap((part) => (isRecord(part) && typeof part.text === "string" ? [part.text] : [])); } function detectWorkflowKindFromText(text: string): PrimusWorkflowKind | undefined { for (const kind of WORKFLOW_KINDS) { if (text.includes(WORKFLOW_DEFINITIONS[kind].fullSignature)) { return kind; } } return undefined; } function getWorkflowKindFromHistory(history: unknown[]): PrimusWorkflowKind | undefined { for (let index = history.length - 1; index >= 0; index -= 1) { const entry = history[index]; if (!isRecord(entry)) { continue; } let content: unknown; if (entry.type === "message" && isRecord(entry.message)) { content = entry.message.content; } else if (entry.type === "custom_message") { content = entry.content; } for (const text of extractTextContent(content)) { const workflowKind = detectWorkflowKindFromText(text); if (workflowKind) { return workflowKind; } } } return undefined; } export function hasWorkflowSignatureInHistory(history: unknown[]): boolean { return getWorkflowKindFromHistory(history) !== undefined; } function parseWorkflowState(value: unknown, fallbackKind?: PrimusWorkflowKind): PrimusWorkflowState | undefined { if (!isRecord(value) || typeof value.active !== "boolean") { return undefined; } const next: PrimusWorkflowState = { active: value.active }; const workflowKind = isWorkflowKind(value.kind) ? value.kind : fallbackKind; if (workflowKind) { next.kind = workflowKind; } if (next.active && next.kind === undefined) { return undefined; } if (typeof value.workflowHash === "string" && value.workflowHash.length > 0) { next.workflowHash = value.workflowHash; } if (value.source === "command" || value.source === "router" || value.source === "legacy") { next.source = value.source; } if ( workflowKind === "modify" && typeof value.targetModelSlug === "string" && value.targetModelSlug.trim().length > 0 ) { next.targetModelSlug = value.targetModelSlug.trim(); } return next; } export function getWorkflowState(history: unknown[]): PrimusWorkflowState { for (let index = history.length - 1; index >= 0; index -= 1) { const entry = history[index]; if (!isRecord(entry) || entry.type !== "custom") { continue; } const fallbackKind = entry.customType === LEGACY_WORKFLOW_STATE_TYPE ? "design" : undefined; if (entry.customType !== WORKFLOW_STATE_TYPE && fallbackKind === undefined) { continue; } const parsed = parseWorkflowState(entry.data, fallbackKind); if (parsed) { return parsed; } } const legacyKind = getWorkflowKindFromHistory(history); if (legacyKind) { return { active: true, kind: legacyKind, source: "legacy" }; } return { active: false }; } export function getDesignWorkflowState(history: unknown[]): PrimusDesignWorkflowState { const state = getWorkflowState(history); const next: PrimusDesignWorkflowState = { active: state.kind === "modify" ? false : state.active }; if (state.workflowHash) { next.workflowHash = state.workflowHash; } if (state.source) { next.source = state.source; } return next; } export function isDesignRequestPrompt(prompt: string): boolean { const normalized = prompt.trim(); if (normalized.length === 0 || NON_DESIGN_PROMPT_PATTERN.test(normalized)) { return false; } return CAD_REQUEST_PREFIX.test(normalized) || CAD_INTENT_PATTERN.test(normalized); } export function isModifyRequestPrompt(prompt: string): boolean { const normalized = prompt.trim(); if (normalized.length === 0 || NON_DESIGN_PROMPT_PATTERN.test(normalized) || !MODIFY_TARGET_NOUN_PATTERN.test(normalized)) { return false; } const startsWithCadRequest = CAD_REQUEST_PREFIX.test(normalized); const isCreationStyleComparativePrompt = startsWithCadRequest && CAD_REQUEST_WITH_LEADING_COMPARATIVE_PATTERN.test(normalized); if (MODIFY_EXISTING_CUE_PATTERN.test(normalized) && (MODIFY_ACTION_PATTERN.test(normalized) || MODIFY_COMPARATIVE_PATTERN.test(normalized))) { return true; } if (MODIFY_COMPARATIVE_PATTERN.test(normalized)) { return !isCreationStyleComparativePrompt; } return MODIFY_ACTION_PATTERN.test(normalized) && !startsWithCadRequest; } function resolvePrimusAgentDir(): string { return process.env.PRIMUS_CODING_AGENT_DIR ?? process.env.PI_CODING_AGENT_DIR ?? getAgentDir(); } function getWorkflowPath(workflowKind: PrimusWorkflowKind): string { return resolve(resolvePrimusAgentDir(), "workflows", WORKFLOW_DEFINITIONS[workflowKind].workflowFileName); } async function loadWorkflow( workflowKind: PrimusWorkflowKind, ctx: { ui: { notify(message: string, type?: "info" | "warning" | "error"): void } }, ): Promise<{ hash: string; text: string; } | undefined> { const workflowPath = getWorkflowPath(workflowKind); let workflowText = ""; try { workflowText = await readFile(workflowPath, "utf8"); } catch { ctx.ui.notify(`Failed to load workflow specification: ${workflowPath}`, "error"); return undefined; } const normalized = workflowText.trim(); if (normalized.length === 0) { ctx.ui.notify(`Workflow specification is empty: ${workflowPath}`, "error"); return undefined; } const missingMarkers = REQUIRED_WORKFLOW_STAGE_MARKERS.filter((marker) => !normalized.includes(marker)); if (missingMarkers.length > 0) { ctx.ui.notify(`Workflow specification is incomplete: ${workflowPath} (missing ${missingMarkers.join(", ")})`, "error"); return undefined; } return { hash: sha256(normalized), text: normalized, }; } export default function primusTools(pi: ExtensionAPI): void { let headerEnabled = true; let workflowState: PrimusWorkflowState = { active: false }; let suppressNextAutoWorkflowInjection = false; function refreshWorkflowState(history: unknown[]): PrimusWorkflowState { workflowState = getWorkflowState(history); return workflowState; } function persistWorkflowState(next: PrimusWorkflowState): void { workflowState = next; pi.appendEntry(WORKFLOW_STATE_TYPE, next); } function retainModifyTargetModel(workflowKind: PrimusWorkflowKind, previousState: PrimusWorkflowState): Partial { if (workflowKind !== "modify" || !previousState.targetModelSlug) { return {}; } return { targetModelSlug: previousState.targetModelSlug }; } pi.on("session_start", async (_event, ctx) => { refreshWorkflowState(ctx.sessionManager.getBranch()); if (!headerEnabled || !ctx.hasUI) { return; } ctx.ui.setHeader((_tui, theme) => ({ render(width: number): string[] { return buildHeaderLines(width, theme); }, invalidate(): void {}, })); }); pi.registerCommand("help", { description: "Show Primus-specific slash command help.", handler: async (_args, ctx) => { ctx.ui.notify(getPrimusHelpText(), "info"); }, }); function registerWorkflowCommand(workflowKind: PrimusWorkflowKind): void { const definition = WORKFLOW_DEFINITIONS[workflowKind]; pi.registerCommand(workflowKind, { description: definition.commandDescription, handler: async (args, ctx) => { const userPrompt = args.trim(); if (userPrompt.length === 0) { ctx.ui.notify(`Usage: ${definition.commandUsage}`, "warning"); return; } const previousState = refreshWorkflowState(ctx.sessionManager.getBranch()); const workflow = await loadWorkflow(workflowKind, ctx); if (!workflow) { return; } const workflowWasActive = previousState.active && previousState.kind === workflowKind; const forceFullInject = (previousState.kind !== undefined && previousState.kind !== workflowKind) || previousState.source === "legacy" || (workflowWasActive && previousState.workflowHash !== undefined && previousState.workflowHash !== workflow.hash); const modifyDiscovery = workflowKind === "modify" && !previousState.targetModelSlug ? await discoverModifyTargets({ cwd: ctx.cwd, prompt: userPrompt }) : undefined; persistWorkflowState({ active: true, kind: workflowKind, workflowHash: workflow.hash, source: "command", ...retainModifyTargetModel(workflowKind, previousState), }); suppressNextAutoWorkflowInjection = true; pi.sendMessage( { customType: WORKFLOW_MESSAGE_TYPE, content: resolveWorkflowInjection({ workflowKind, workflowActive: workflowWasActive, workflowText: workflow.text, userPrompt, forceFullInject, targetModelSlug: previousState.targetModelSlug, modifyDiscovery, }), display: false, details: { mode: forceFullInject || !workflowWasActive ? "full" : "resume" }, }, ctx.isIdle() ? { triggerTurn: true } : { triggerTurn: true, deliverAs: "followUp" }, ); }, }); } for (const workflowKind of WORKFLOW_KINDS) { registerWorkflowCommand(workflowKind); } pi.on("before_agent_start", async (event, ctx) => { const previousState = refreshWorkflowState(ctx.sessionManager.getBranch()); if (suppressNextAutoWorkflowInjection) { suppressNextAutoWorkflowInjection = false; return; } const shouldAutoRouteModify = !previousState.active && isModifyRequestPrompt(event.prompt); const shouldAutoRouteDesign = !shouldAutoRouteModify && !previousState.active && isDesignRequestPrompt(event.prompt); const shouldAutoRoute = shouldAutoRouteModify || shouldAutoRouteDesign; const workflowKind = previousState.active ? previousState.kind : shouldAutoRouteModify ? "modify" : shouldAutoRouteDesign ? "design" : undefined; if (!workflowKind) { return; } const workflow = await loadWorkflow(workflowKind, ctx); if (!workflow) { return; } const workflowWasActive = previousState.active && previousState.kind === workflowKind; const forceFullInject = shouldAutoRoute || previousState.source === "legacy" || (workflowWasActive && previousState.workflowHash !== undefined && previousState.workflowHash !== workflow.hash); const modifyDiscovery = workflowKind === "modify" && !previousState.targetModelSlug ? await discoverModifyTargets({ cwd: ctx.cwd, prompt: event.prompt }) : undefined; if (shouldAutoRoute || forceFullInject) { persistWorkflowState({ active: true, kind: workflowKind, workflowHash: workflow.hash, source: shouldAutoRoute ? "router" : previousState.source === "command" ? "command" : "router", ...retainModifyTargetModel(workflowKind, previousState), }); } return { message: { customType: WORKFLOW_MESSAGE_TYPE, content: resolveWorkflowInjection({ workflowKind, workflowActive: workflowWasActive, workflowText: workflow.text, userPrompt: event.prompt, forceFullInject, targetModelSlug: previousState.targetModelSlug, modifyDiscovery, }), display: false, details: { mode: forceFullInject || !workflowWasActive ? "full" : "resume" }, }, }; }); pi.registerCommand("init", { description: "Set up project folders for CAD work.", handler: async (_args, ctx) => { const gitStatus = await ensureGitRepository(pi, ctx.cwd); await requireCleanScaffoldPaths(pi, ctx.cwd, gitStatus); const result = await scaffoldProject(ctx.cwd); const commitStatus = await createScaffoldCommit(pi, ctx.cwd); ctx.ui.notify(formatInitSummary(result, gitStatus, commitStatus), "info"); }, }); pi.registerCommand("primus-header-off", { description: "Disable Primus custom header and restore default header.", handler: async (_args, ctx) => { headerEnabled = false; ctx.ui.setHeader(undefined); ctx.ui.notify("Primus header disabled", "info"); }, }); }