import { Type } from "@sinclair/typebox";
import { StringEnum } from "@singularity-forge/ai";
import { Text } from "@singularity-forge/tui";
import {
	AUTONOMOUS_SOLVER_OUTCOMES,
	appendAutonomousSolverCheckpoint,
} from "../autonomous-solver.js";
import { getErrorMessage } from "../error-utils.js";
import {
	claimReservedId,
	findMilestoneIds,
	getReservedMilestoneIds,
	nextMilestoneId,
} from "../milestone-ids.js";
import { loadEffectiveSFPreferences } from "../preferences.js";
import { ALL_SCHEDULE_KINDS } from "../schedule/schedule-types.js";
import { markResolved, recordSelfFeedback } from "../self-feedback.js";
import {
	executeCompleteMilestone,
	executePlanMilestone,
	executePlanSlice,
	executeReassessRoadmap,
	executeReplanSlice,
	executeSaveGateResult,
	executeSliceComplete,
	executeSummarySave,
	executeTaskComplete,
	executeValidateMilestone,
} from "../tools/workflow-tool-executors.js";
import { logError } from "../workflow-logger.js";
import { ensureDbOpen } from "./dynamic-tools.js";
export function registerDbTools(pi) {
	// ─── save_decision ─────────────────────────────────────────────────
	const decisionSaveExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		const dbAvailable = await ensureDbOpen();
		if (!dbAvailable) {
			return {
				content: [
					{
						type: "text",
						text: "Error: SF database is not available. Cannot save decision.",
					},
				],
				details: { operation: "save_decision", error: "db_unavailable" },
			};
		}
		try {
			const { saveDecisionToDb } = await import("../db-writer.js");
			const { id } = await saveDecisionToDb(
				{
					scope: params.scope,
					decision: params.decision,
					choice: params.choice,
					rationale: params.rationale,
					revisable: params.revisable,
					when_context: params.when_context,
					made_by: params.made_by,
				},
				process.cwd(),
			);
			return {
				content: [{ type: "text", text: `Saved decision ${id}` }],
				details: { operation: "save_decision", id },
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `save_decision tool failed: ${msg}`, {
				tool: "save_decision",
				error: String(err),
			});
			return {
				content: [{ type: "text", text: `Error saving decision: ${msg}` }],
				details: { operation: "save_decision", error: msg },
			};
		}
	};
	const decisionSaveTool = {
		name: "save_decision",
		label: "Save Decision",
		description:
			"Record an architectural or technical decision and return its auto-assigned ID (e.g. D001). " +
			"Call this whenever a non-trivial choice is made about architecture, libraries, patterns, or observability so the rationale is durable and reviewable.",
		promptSnippet:
			"Record a project decision (auto-assigns ID, persists to DB)",
		promptGuidelines: [
			"Call save_decision for architectural, library, pattern, or observability choices — not for task-level implementation details.",
			"Decision IDs are auto-assigned — never guess or provide one.",
			"scope, decision, choice, and rationale are required; revisable, when_context, and made_by are optional.",
			"Set made_by to 'human' when the user directed it, 'agent' when you chose autonomously, or 'collaborative' when agreed together.",
		],
		parameters: Type.Object({
			scope: Type.String({
				description:
					"Scope of the decision (e.g. 'architecture', 'library', 'observability')",
			}),
			decision: Type.String({ description: "What is being decided" }),
			choice: Type.String({ description: "The choice made" }),
			rationale: Type.String({ description: "Why this choice was made" }),
			revisable: Type.Optional(
				Type.String({
					description: "Whether this can be revisited (default: 'Yes')",
				}),
			),
			when_context: Type.Optional(
				Type.String({
					description: "When/context for the decision (e.g. milestone ID)",
				}),
			),
			made_by: Type.Optional(
				Type.Union(
					[
						Type.Literal("human"),
						Type.Literal("agent"),
						Type.Literal("collaborative"),
					],
					{
						description:
							"Who made this decision: 'human' (user directed), 'agent' (LLM decided autonomously), or 'collaborative' (discussed and agreed). Default: 'agent'",
					},
				),
			),
		}),
		execute: decisionSaveExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("save_decision "));
			if (args.scope) text += theme.fg("accent", `[${args.scope}] `);
			if (args.decision) text += theme.fg("muted", args.decision);
			if (args.choice) text += theme.fg("dim", ` — ${args.choice}`);
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				const textContent = result.content?.find?.(
					(item) => item?.type === "text",
				)?.text;
				const message = d?.reason ?? textContent ?? d?.error ?? "unknown";
				return new Text(theme.fg("error", `Error: ${message}`), 0, 0);
			}
			const text = theme.fg("success", `Decision ${d?.id ?? ""} saved`);
			return new Text(text, 0, 0);
		},
	};
	pi.registerTool(decisionSaveTool);
	// ─── update_requirement ────────────────────────────────────────────
	const requirementUpdateExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		const dbAvailable = await ensureDbOpen();
		if (!dbAvailable) {
			return {
				content: [
					{
						type: "text",
						text: "Error: SF database is not available. Cannot update requirement.",
					},
				],
				details: {
					operation: "update_requirement",
					id: params.id,
					error: "db_unavailable",
				},
			};
		}
		try {
			const { updateRequirementInDb } = await import("../db-writer.js");
			const updates = {};
			if (params.status !== undefined) updates.status = params.status;
			if (params.validation !== undefined)
				updates.validation = params.validation;
			if (params.notes !== undefined) updates.notes = params.notes;
			if (params.description !== undefined)
				updates.description = params.description;
			if (params.primary_owner !== undefined)
				updates.primary_owner = params.primary_owner;
			if (params.supporting_slices !== undefined)
				updates.supporting_slices = params.supporting_slices;
			await updateRequirementInDb(params.id, updates, process.cwd());
			return {
				content: [{ type: "text", text: `Updated requirement ${params.id}` }],
				details: { operation: "update_requirement", id: params.id },
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `update_requirement tool failed: ${msg}`, {
				tool: "update_requirement",
				error: String(err),
			});
			return {
				content: [{ type: "text", text: `Error updating requirement: ${msg}` }],
				details: {
					operation: "update_requirement",
					id: params.id,
					error: msg,
				},
			};
		}
	};
	const requirementUpdateTool = {
		name: "update_requirement",
		label: "Update Requirement",
		description:
			"Update an existing requirement by ID and return confirmation — only fields you provide are changed. " +
			"Call this when a requirement's status, validation evidence, description, or owning slice changes after it was first recorded.",
		promptSnippet:
			"Update an existing requirement by ID (only provided fields are changed)",
		promptGuidelines: [
			"id is required and must be an existing requirement identifier (e.g. R001).",
			"All other fields are optional — only the fields you provide are updated.",
			"Use this to mark a requirement validated, deferred, or to correct its description after new evidence.",
		],
		parameters: Type.Object({
			id: Type.String({ description: "The requirement ID (e.g. R001, R014)" }),
			status: Type.Optional(
				Type.String({
					description: "New status (e.g. 'active', 'validated', 'deferred')",
				}),
			),
			validation: Type.Optional(
				Type.String({ description: "Validation criteria or proof" }),
			),
			notes: Type.Optional(Type.String({ description: "Additional notes" })),
			description: Type.Optional(
				Type.String({ description: "Updated description" }),
			),
			primary_owner: Type.Optional(
				Type.String({ description: "Primary owning slice" }),
			),
			supporting_slices: Type.Optional(
				Type.String({ description: "Supporting slices" }),
			),
		}),
		execute: requirementUpdateExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("update_requirement "));
			if (args.id) text += theme.fg("accent", args.id);
			const fields = ["status", "validation", "notes", "description"].filter(
				(f) => args[f],
			);
			if (fields.length > 0) text += theme.fg("dim", ` (${fields.join(", ")})`);
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? "unknown"}`),
					0,
					0,
				);
			}
			const text = theme.fg("success", `Requirement ${d?.id ?? ""} updated`);
			return new Text(text, 0, 0);
		},
	};
	pi.registerTool(requirementUpdateTool);
	// ─── save_requirement ─────────────────────────────────────────────
	const requirementSaveExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		const dbAvailable = await ensureDbOpen();
		if (!dbAvailable) {
			return {
				content: [
					{
						type: "text",
						text: "Error: SF database is not available. Cannot save requirement.",
					},
				],
				details: {
					operation: "save_requirement",
					error: "db_unavailable",
				},
			};
		}
		try {
			const { saveRequirementToDb } = await import("../db-writer.js");
			const result = await saveRequirementToDb(
				{
					class: params.class,
					status: params.status,
					description: params.description,
					why: params.why,
					source: params.source,
					primary_owner: params.primary_owner,
					supporting_slices: params.supporting_slices,
					validation: params.validation,
					notes: params.notes,
				},
				process.cwd(),
			);
			return {
				content: [{ type: "text", text: `Saved requirement ${result.id}` }],
				details: { operation: "save_requirement", id: result.id },
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `save_requirement tool failed: ${msg}`, {
				tool: "save_requirement",
				error: String(err),
			});
			return {
				content: [{ type: "text", text: `Error saving requirement: ${msg}` }],
				details: { operation: "save_requirement", error: msg },
			};
		}
	};
	const requirementSaveTool = {
		name: "save_requirement",
		label: "Save Requirement",
		description:
			"Record a new requirement and return its auto-assigned ID (e.g. R001). " +
			"Call this when a functional, non-functional, or operational requirement is identified that the project must satisfy.",
		promptSnippet: "Record a new requirement (auto-assigns ID, persists to DB)",
		promptGuidelines: [
			"Requirement IDs are auto-assigned — never guess or provide one.",
			"class, description, why, and source are required; all other fields are optional.",
			"Use update_requirement to change an existing requirement rather than saving a duplicate.",
		],
		parameters: Type.Object({
			class: Type.String({
				description:
					"Requirement class (e.g. 'functional', 'non-functional', 'operational')",
			}),
			description: Type.String({
				description: "Short description of the requirement",
			}),
			why: Type.String({ description: "Why this requirement matters" }),
			source: Type.String({
				description:
					"Origin of the requirement (e.g. 'user-research', 'design', 'M001')",
			}),
			status: Type.Optional(
				Type.String({ description: "Status (default: 'active')" }),
			),
			primary_owner: Type.Optional(
				Type.String({ description: "Primary owning slice" }),
			),
			supporting_slices: Type.Optional(
				Type.String({ description: "Supporting slices" }),
			),
			validation: Type.Optional(
				Type.String({ description: "Validation criteria" }),
			),
			notes: Type.Optional(Type.String({ description: "Additional notes" })),
		}),
		execute: requirementSaveExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("save_requirement "));
			if (args.class) text += theme.fg("accent", `[${args.class}] `);
			if (args.description) text += theme.fg("muted", args.description);
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? "unknown"}`),
					0,
					0,
				);
			}
			const text = theme.fg("success", `Requirement ${d?.id ?? ""} saved`);
			return new Text(text, 0, 0);
		},
	};
	pi.registerTool(requirementSaveTool);
	// ─── save_summary ──────────────────────────────────────────────────
	const summarySaveExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executeSummarySave(params, process.cwd());
	};
	const summarySaveTool = {
		name: "save_summary",
		label: "Save Summary",
		description:
			"Save a research, summary, context, or assessment artifact to disk with an auto-computed path. " +
			"Call this to persist planning or research output (e.g. a research brief, context doc, or summary) for a milestone, slice, or task.",
		promptSnippet:
			"Save a planning artifact (SUMMARY/RESEARCH/CONTEXT/ASSESSMENT) to disk",
		promptGuidelines: [
			"milestone_id is required; slice_id and task_id are optional and determine the file path.",
			"artifact_type must be one of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT.",
			"Use CONTEXT-DRAFT for incremental saves; use CONTEXT only for the final milestone context after verification.",
		],
		parameters: Type.Object({
			milestone_id: Type.String({ description: "Milestone ID (e.g. M001)" }),
			slice_id: Type.Optional(
				Type.String({ description: "Slice ID (e.g. S01)" }),
			),
			task_id: Type.Optional(
				Type.String({ description: "Task ID (e.g. T01)" }),
			),
			artifact_type: Type.String({
				description:
					"One of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT",
			}),
			content: Type.String({
				description: "The full markdown content of the artifact",
			}),
		}),
		execute: summarySaveExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("save_summary "));
			if (args.artifact_type) text += theme.fg("accent", args.artifact_type);
			const path = [args.milestone_id, args.slice_id, args.task_id]
				.filter(Boolean)
				.join("/");
			if (path) text += theme.fg("dim", ` ${path}`);
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? "unknown"}`),
					0,
					0,
				);
			}
			let text = theme.fg("success", `${d?.artifact_type ?? "Artifact"} saved`);
			if (d?.path) text += theme.fg("dim", ` → ${d.path}`);
			return new Text(text, 0, 0);
		},
	};
	pi.registerTool(summarySaveTool);
	// ─── new_milestone_id ────────────────────────────────────────
	const milestoneGenerateIdExecute = async (
		_toolCallId,
		_params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		try {
			// Claim a reserved ID if the guided-flow already previewed one to the user.
			// This guarantees the ID shown in the UI matches the one materialised on disk.
			const reserved = claimReservedId();
			if (reserved) {
				await ensureMilestoneDbRow(reserved);
				return {
					content: [{ type: "text", text: reserved }],
					details: {
						operation: "new_milestone_id",
						id: reserved,
						source: "reserved",
					},
				};
			}
			const basePath = process.cwd();
			const existingIds = findMilestoneIds(basePath);
			const uniqueEnabled =
				!!loadEffectiveSFPreferences()?.preferences?.unique_milestone_ids;
			const allIds = [
				...new Set([...existingIds, ...getReservedMilestoneIds()]),
			];
			const newId = nextMilestoneId(allIds, uniqueEnabled);
			await ensureMilestoneDbRow(newId);
			return {
				content: [{ type: "text", text: newId }],
				details: {
					operation: "new_milestone_id",
					id: newId,
					existingCount: existingIds.length,
					uniqueEnabled,
				},
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			return {
				content: [
					{
						type: "text",
						text: `Error generating milestone ID: ${msg}`,
					},
				],
				details: { operation: "new_milestone_id", error: msg },
			};
		}
	};
	/**
	 * Insert a minimal DB row for a milestone ID so it's visible to the state
	 * machine. Uses INSERT OR IGNORE — safe to call even if plan_milestone
	 * later writes the full row. Silently skips if the DB isn't available yet
	 * (pre-migration).
	 */
	async function ensureMilestoneDbRow(milestoneId) {
		const dbAvailable = await ensureDbOpen();
		if (!dbAvailable) return;
		try {
			const { insertMilestone } = await import("../sf-db.js");
			insertMilestone({ id: milestoneId, status: "queued" });
		} catch (e) {
			logError(
				"tool",
				`insertMilestone failed for ${milestoneId}: ${e.message}`,
			);
		}
	}
	const milestoneGenerateIdTool = {
		name: "new_milestone_id",
		label: "Generate Milestone ID",
		description:
			"Generate and reserve the next milestone ID (e.g. M001) and return it as a string. " +
			"Always call this before creating a new milestone — never invent or hardcode a milestone ID.",
		promptSnippet:
			"Generate a valid milestone ID before creating a new milestone",
		promptGuidelines: [
			"Call new_milestone_id once per new milestone, before any other milestone creation steps.",
			"Never hardcode milestone IDs like M001 or M002 — always use this tool.",
			"For multiple milestones, call it once per milestone in sequence.",
		],
		parameters: Type.Object({}),
		execute: milestoneGenerateIdExecute,
		renderCall(_args, theme) {
			return new Text(
				theme.fg("toolTitle", theme.bold("new_milestone_id")),
				0,
				0,
			);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? "unknown"}`),
					0,
					0,
				);
			}
			let text = theme.fg("success", `Generated ${d?.id ?? "ID"}`);
			if (d?.source === "reserved") text += theme.fg("dim", " (reserved)");
			return new Text(text, 0, 0);
		},
	};
	pi.registerTool(milestoneGenerateIdTool);
	// ─── report_issue ─────────────────────────────────────────────────
	// Agent-callable bug-report channel. Records anomalies the agent observes
	// in sf's own behavior so they accumulate in self-feedback (forge's own
	// .sf/SELF-FEEDBACK.md when running on forge itself, ~/.sf/agent/upstream-feedback.jsonl
	// otherwise). Severity drives whether the originating unit is also blocked
	// pending an sf version bump.
	const selfReportExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		try {
			const result = recordSelfFeedback(
				{
					kind: params.kind,
					severity: params.severity,
					summary: params.summary,
					evidence: params.evidence,
					suggestedFix: params.suggested_fix,
					acceptanceCriteria: params.acceptance_criteria,
					occurredIn: params.occurred_in,
					source: "agent",
				},
				process.cwd(),
			);
			if (!result) {
				return {
					content: [
						{
							type: "text",
							text: "Error: failed to write self-feedback entry",
						},
					],
					details: {
						operation: "self_report",
						error: "write_failed",
					},
				};
			}
			const e = result.entry;
			const blockNote = result.blocking
				? ` (BLOCKING — unit will be held until sf is bumped past ${e.sfVersion} or entry ${e.id} is resolved)`
				: "";
			return {
				content: [
					{
						type: "text",
						text: `Recorded self-feedback ${e.id} [${e.severity}] ${e.kind}${blockNote}`,
					},
				],
				details: {
					operation: "self_report",
					id: e.id,
					blocking: e.blocking,
					repoIdentity: e.repoIdentity,
					sfVersion: e.sfVersion,
				},
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `report_issue tool failed: ${msg}`, {
				tool: "report_issue",
				error: String(err),
			});
			return {
				content: [{ type: "text", text: `Error in report_issue: ${msg}` }],
				details: { operation: "self_report", error: msg },
			};
		}
	};
	const selfReportTool = {
		name: "report_issue",
		label: "Self Report",
		description:
			"File an observation about the agent tooling itself — a bug, confusing prompt, missing feature, friction, or improvement idea — and return the new entry ID. " +
			"Use this whenever you notice something wrong or suboptimal about how the agent tooling behaves, not for bugs in the user's project.",
		promptSnippet:
			"Report any agent-tooling observation: bug, prompt issue, missing feature, or improvement idea",
		promptGuidelines: [
			"Use report_issue for agent-tooling observations only — not for bugs in the user's project or your own task work.",
			"Acceptable kinds: prompt-quality-issue, improvement-idea, agent-friction, design-thought, missing-feature, brittle-predicate, git-empty-pathspec.",
			"Severity: low = cosmetic, medium = recurring friction, high = blocked a task unit, critical = needs immediate fix.",
			"Include concrete evidence (log excerpt, command, file path, error message) and a suggested_fix if you have one.",
			"For high/critical entries, include acceptance_criteria so the resolver has a falsifiable bar to meet.",
			"Over-reporting is preferred — dedup and cleanup happen separately.",
		],
		parameters: Type.Object({
			kind: Type.String({
				description:
					"Short stable identifier for the anomaly class (e.g. 'git-empty-pathspec', 'brittle-predicate', 'advisory-downgrade'). Reuse existing kinds when applicable.",
			}),
			severity: Type.Union(
				[
					Type.Literal("low"),
					Type.Literal("medium"),
					Type.Literal("high"),
					Type.Literal("critical"),
				],
				{
					description:
						"low/medium = log and continue. high/critical = block this unit until sf is bumped or the entry is resolved.",
				},
			),
			summary: Type.String({
				description: "One-line description of the anomaly",
			}),
			evidence: Type.Optional(
				Type.String({
					description:
						"Concrete artifact: log excerpt, command, file path, error message, etc.",
				}),
			),
			suggested_fix: Type.Optional(
				Type.String({
					description: "Optional hypothesis about how to fix this in sf source",
				}),
			),
			acceptance_criteria: Type.Optional(
				Type.String({
					description:
						"Optional reporter-written list of conditions a future resolver must satisfy before marking this resolved. Phrase as bullet points or a short numbered list. Example: '1. plan-quality.ts rejects grep -c predicates with a clear error. 2. existing predicates of that shape are flagged in BACKLOG. 3. test in plan-quality.test.ts covers the rejection.' Without this, resolution is just trust — with it, the resolver has a falsifiable bar to meet.",
				}),
			),
			occurred_in: Type.Optional(
				Type.Object(
					{
						milestone: Type.Optional(Type.String()),
						slice: Type.Optional(Type.String()),
						task: Type.Optional(Type.String()),
						unitType: Type.Optional(Type.String()),
					},
					{
						description:
							"Override the auto-detected current unit. Usually leave unset — the tool reads .sf/auto.lock by default.",
					},
				),
			),
		}),
		execute: selfReportExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("report_issue "));
			if (args.severity)
				text += theme.fg(
					args.severity === "critical" || args.severity === "high"
						? "error"
						: "accent",
					`[${args.severity}] `,
				);
			if (args.kind) text += theme.fg("muted", args.kind);
			if (args.summary) text += theme.fg("dim", ` — ${args.summary}`);
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? "unknown"}`),
					0,
					0,
				);
			}
			const blocking = d?.blocking ? " · BLOCKING" : "";
			let text = theme.fg("success", `Recorded ${d?.id ?? ""}`);
			text += theme.fg("dim", `${blocking}`);
			return new Text(text, 0, 0);
		},
	};
	pi.registerTool(selfReportTool);
	// ─── save_knowledge ───────────────────────────────────────────────
	const knowledgeSaveExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		const dbAvailable = await ensureDbOpen();
		if (!dbAvailable) {
			return {
				content: [
					{
						type: "text",
						text: "Error: SF database is not available. Cannot save knowledge entry.",
					},
				],
				details: { operation: "save_knowledge", error: "db_unavailable" },
			};
		}
		try {
			const { randomUUID } = await import("node:crypto");
			const { insertMemoryRow } = await import("../sf-db.js");
			const id = `K-${randomUUID()}`;
			const now = new Date().toISOString();
			const confidenceScore =
				params.confidence === "medium"
					? 0.6
					: params.confidence === "low"
						? 0.3
						: 0.9;
			insertMemoryRow({
				id,
				category: params.category ?? "knowledge",
				content: params.content,
				confidence: confidenceScore,
				sourceUnitType: "agent",
				sourceUnitId: null,
				createdAt: now,
				updatedAt: now,
				tags: params.tags ?? [],
			});
			return {
				content: [{ type: "text", text: `Saved knowledge entry ${id}` }],
				details: { operation: "save_knowledge", id },
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `save_knowledge tool failed: ${msg}`, {
				tool: "save_knowledge",
				error: String(err),
			});
			return {
				content: [{ type: "text", text: `Error saving knowledge: ${msg}` }],
				details: { operation: "save_knowledge", error: msg },
			};
		}
	};
	const knowledgeSaveTool = {
		name: "save_knowledge",
		label: "Save Knowledge",
		description:
			"Persist a non-obvious rule, recurring gotcha, or useful pattern discovered during execution to the project knowledge base. " +
			"Only call this when the insight would save future agents from repeating your investigation — not for obvious observations.",
		promptSnippet: "Save a knowledge entry to the project knowledge base (DB)",
		promptGuidelines: [
			"content is required. Write a complete, self-contained insight — future agents won't have your context.",
			"category defaults to 'knowledge'. Use 'architecture', 'tooling', or 'process' for other categories.",
			"confidence defaults to 'high'. Use 'medium' for tentative observations.",
			"tags is an optional array of strings for filtering (e.g. ['mcp', 'transport']).",
			"Do NOT call this for task-level implementation details or obvious observations.",
		],
		parameters: Type.Object({
			content: Type.String({
				description:
					"The knowledge insight to persist — complete and self-contained",
			}),
			category: Type.Optional(
				Type.String({
					description:
						"Category (default: 'knowledge'). E.g. 'architecture', 'tooling', 'process'",
				}),
			),
			confidence: Type.Optional(
				Type.String({
					description: "Confidence level: 'high' (default) or 'medium'",
				}),
			),
			tags: Type.Optional(
				Type.Array(Type.String(), {
					description:
						"Optional string tags for filtering (e.g. ['mcp', 'transport'])",
				}),
			),
		}),
		execute: knowledgeSaveExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("save_knowledge "));
			if (args.category) text += theme.fg("accent", `[${args.category}] `);
			if (args.content)
				text += theme.fg(
					"muted",
					args.content.slice(0, 60) + (args.content.length > 60 ? "…" : ""),
				);
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				const textContent = result.content?.find?.(
					(item) => item?.type === "text",
				)?.text;
				const message = d?.reason ?? textContent ?? d?.error ?? "unknown";
				return new Text(theme.fg("error", `Error: ${message}`), 0, 0);
			}
			return new Text(
				theme.fg("success", `Knowledge ${d?.id ?? ""} saved`),
				0,
				0,
			);
		},
	};
	pi.registerTool(knowledgeSaveTool);
	// ─── resolve_issue ────────────────────────────────────────
	// Agent-callable resolver for inline self-feedback repair turns. The
	// inline-fix prompt must not rely on hand-editing JSONL: the tool updates
	// the structured source of truth and regenerates the markdown view.
	const selfFeedbackResolveExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		try {
			const evidenceKind = params.evidence_kind ?? "agent-fix";
			if (
				evidenceKind === "promoted-to-requirement" &&
				!(
					typeof params.requirement_id === "string" &&
					params.requirement_id.trim()
				)
			) {
				return {
					content: [
						{
							type: "text",
							text: "Error in resolve_issue: requirement_id is required for promoted-to-requirement evidence",
						},
					],
					details: {
						operation: "self_feedback_resolve",
						id: params.id,
						error: "missing_requirement_id",
					},
				};
			}
			const ok = markResolved(
				params.id,
				{
					reason: params.reason,
					evidence: {
						kind: evidenceKind,
						commitSha: params.commit_sha,
						testPath: params.test_path,
						summaryNarrative: params.summary_narrative,
						requirementId: params.requirement_id,
					},
					criteriaMet: params.criteria_met,
				},
				process.cwd(),
			);
			if (!ok) {
				return {
					content: [
						{
							type: "text",
							text: `Error: unresolved self-feedback entry not found: ${params.id}`,
						},
					],
					details: {
						operation: "self_feedback_resolve",
						id: params.id,
						error: "not_found_or_already_resolved",
					},
				};
			}
			return {
				content: [
					{
						type: "text",
						text: `Resolved self-feedback ${params.id}`,
					},
				],
				details: {
					operation: "self_feedback_resolve",
					id: params.id,
					resolved: true,
				},
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `resolve_issue tool failed: ${msg}`, {
				tool: "resolve_issue",
				error: String(err),
			});
			return {
				content: [
					{
						type: "text",
						text: `Error in resolve_issue: ${msg}`,
					},
				],
				details: {
					operation: "self_feedback_resolve",
					id: params.id,
					error: msg,
				},
			};
		}
	};
	pi.registerTool({
		name: "resolve_issue",
		label: "Resolve Self Feedback",
		description:
			"Mark a previously reported agent-tooling issue as resolved and record canonical evidence. " +
			"Use agent-fix only after a fix is implemented and verified; use human-clear or promoted-to-requirement for triage closures.",
		promptSnippet:
			"Resolve a filed agent-tooling issue with canonical evidence",
		promptGuidelines: [
			"Use evidence_kind=agent-fix after implementing and verifying the fix, not before.",
			"Use evidence_kind=human-clear for stale, superseded, false-positive, or intentionally closed entries.",
			"Use evidence_kind=promoted-to-requirement with requirement_id when an entry has been moved into requirements.",
			"Pass commit_sha when an agent-fix commit exists; use summary_narrative and test_path when a commit/test path is not enough.",
			"If the entry had acceptance criteria, pass criteria_met with the criteria you satisfied.",
		],
		parameters: Type.Object({
			id: Type.String({
				description: "Self-feedback entry id, e.g. sf-moocz9so-4ffov2",
			}),
			reason: Type.String({
				description: "Short explanation of why the entry is resolved",
			}),
			evidence_kind: Type.Optional(
				Type.Union(
					[
						Type.Literal("agent-fix"),
						Type.Literal("human-clear"),
						Type.Literal("promoted-to-requirement"),
						Type.Literal("agent-fix-unverified"),
					],
					{
						description:
							"Canonical resolution evidence kind. Defaults to agent-fix for backward compatibility.",
					},
				),
			),
			commit_sha: Type.Optional(
				Type.String({ description: "Commit SHA containing the fix" }),
			),
			requirement_id: Type.Optional(
				Type.String({
					description:
						"Requirement id required when evidence_kind is promoted-to-requirement",
				}),
			),
			test_path: Type.Optional(
				Type.String({ description: "Focused test or verification path" }),
			),
			summary_narrative: Type.Optional(
				Type.String({
					description:
						"Concise verification summary when a commit/test path alone is not enough",
				}),
			),
			criteria_met: Type.Optional(
				Type.Array(Type.String(), {
					description:
						"Acceptance criteria satisfied by this fix, if the entry provided criteria",
				}),
			),
		}),
		execute: selfFeedbackResolveExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("resolve_issue "));
			if (args.id) text += theme.fg("muted", args.id);
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? "unknown"}`),
					0,
					0,
				);
			}
			return new Text(
				theme.fg("success", `Resolved ${d?.id ?? "self-feedback"}`),
				0,
				0,
			);
		},
	});
	// ─── checkpoint ───────────────────────────────────────
	const autonomousCheckpointExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		try {
			const checkpoint = appendAutonomousSolverCheckpoint(
				process.cwd(),
				params,
			);
			return {
				content: [
					{
						type: "text",
						text: `Recorded autonomous checkpoint for ${checkpoint.unitType} ${checkpoint.unitId}: ${checkpoint.outcome}`,
					},
				],
				details: {
					operation: "autonomous_checkpoint",
					unitType: checkpoint.unitType,
					unitId: checkpoint.unitId,
					iteration: checkpoint.iteration,
					outcome: checkpoint.outcome,
				},
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `checkpoint tool failed: ${msg}`, {
				tool: "checkpoint",
				error: String(err),
			});
			return {
				content: [
					{
						type: "text",
						text: `Error in checkpoint: ${msg}`,
					},
				],
				details: { operation: "autonomous_checkpoint", error: msg },
			};
		}
	};
	pi.registerTool({
		name: "checkpoint",
		label: "Autonomous Checkpoint",
		description:
			"Save a structured progress snapshot for the current task iteration — what was done, what's blocked, and what remains — so progress is visible and recoverable. " +
			"Call this before ending every autonomous turn.",
		promptSnippet:
			"Save a progress checkpoint before ending an autonomous turn",
		promptGuidelines: [
			"Call checkpoint before ending any autonomous task turn.",
			"outcome=complete only when the unit's normal completion tool also succeeded.",
			"outcome=continue when real progress was made but more iterations are needed.",
			"outcome=blocked for missing facts, broken environment, or impossible next steps.",
			"outcome=decide for material product or architecture choices that must not be made silently.",
			"Fill all eight structured fields: purpose, consumer, contract, failureBoundary, evidence, nonGoals, invariants, assumptions.",
		],
		parameters: Type.Object({
			unitType: Type.String({
				description: "Current autonomous unit type, e.g. execute-task",
			}),
			unitId: Type.String({
				description: "Current autonomous unit id, e.g. M010/S05/T02",
			}),
			outcome: Type.Union(
				AUTONOMOUS_SOLVER_OUTCOMES.map((outcome) => Type.Literal(outcome)),
				{
					description:
						"Semantic result for this iteration: continue, complete, blocked, or decide",
				},
			),
			summary: Type.String({
				description: "Concise statement of what happened this iteration",
			}),
			completedItems: Type.Array(Type.String(), {
				description: "Concrete items completed in this iteration",
			}),
			remainingItems: Type.Array(Type.String(), {
				description: "Exact remaining work for the next autonomous iteration",
			}),
			verificationEvidence: Type.Array(Type.String(), {
				description:
					"Commands, files, tests, screenshots, or observations that prove progress",
			}),
			blockerReason: Type.Optional(
				Type.String({
					description: "Required when outcome=blocked",
				}),
			),
			decisionQuestion: Type.Optional(
				Type.String({
					description: "Required when outcome=decide",
				}),
			),
			pdd: Type.Object({
				purpose: Type.String({ description: "Why this behavior exists" }),
				consumer: Type.String({
					description: "Who or what uses it in production",
				}),
				contract: Type.String({
					description: "Observable behavior or artifact boundary",
				}),
				failureBoundary: Type.String({
					description: "Failures that must be contained or surfaced",
				}),
				evidence: Type.String({
					description: "Proof gathered this iteration",
				}),
				nonGoals: Type.String({
					description: "What is intentionally not solved here",
				}),
				invariants: Type.String({
					description: "Rules that must remain true across iterations",
				}),
				assumptions: Type.String({
					description: "Uncertain facts and how to falsify them",
				}),
			}),
		}),
		execute: autonomousCheckpointExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("checkpoint "));
			if (args.outcome) text += theme.fg("accent", `[${args.outcome}] `);
			if (args.unitType || args.unitId) {
				text += theme.fg(
					"muted",
					`${args.unitType ?? "unit"} ${args.unitId ?? ""}`.trim(),
				);
			}
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? "unknown"}`),
					0,
					0,
				);
			}
			return new Text(
				theme.fg(
					"success",
					`Checkpoint ${d?.outcome ?? "recorded"} · ${d?.unitType ?? ""} ${d?.unitId ?? ""}`.trim(),
				),
				0,
				0,
			);
		},
	});
	// ─── plan_milestone ────────────────────────────────────────────────
	const planMilestoneExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executePlanMilestone(params, process.cwd());
	};
	const planMilestoneTool = {
		name: "plan_milestone",
		label: "Plan Milestone",
		description:
			"Write milestone planning state to the SF database, render ROADMAP.md from DB, and clear caches after a successful render.",
		promptSnippet:
			"Plan a milestone via DB write + roadmap render + cache invalidation",
		promptGuidelines: [
			"Use plan_milestone for milestone planning instead of writing ROADMAP.md directly.",
			"Keep parameters flat and provide the full milestone planning payload. Use either explicit slices or templateId-based scaffolding for common feat/fix/refactor patterns.",
			"Use productResearch for product/category/competitor research; do not hide those findings inside visionMeeting.researcher.",
			"The tool validates input, writes milestone and slice planning data transactionally, renders ROADMAP.md from DB, and clears both state and parse caches after success.",
		],
		parameters: Type.Object({
			// ── Core identification + content (required) ──────────────────────
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			title: Type.String({ description: "Milestone title" }),
			vision: Type.String({
				description:
					"Milestone purpose/vision. Start with why this milestone exists before implementation detail.",
			}),
			slices: Type.Optional(
				Type.Array(
					Type.Object({
						sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
						title: Type.String({ description: "Slice title" }),
						risk: Type.String({ description: "Slice risk" }),
						depends: Type.Array(Type.String(), {
							description: "Slice dependency IDs",
						}),
						demo: Type.String({
							description: "Roadmap demo text / After this",
						}),
						goal: Type.String({
							description:
								"Slice purpose/goal. Start with the capability this slice makes true before implementation detail.",
						}),
						successCriteria: Type.String({
							description: "Slice success criteria block",
						}),
						proofLevel: Type.String({ description: "Slice proof level" }),
						integrationClosure: Type.String({
							description: "Slice integration closure",
						}),
						observabilityImpact: Type.String({
							description: "Slice observability impact",
						}),
					}),
					{
						description:
							"Planned slices for the milestone. Optional when templateId is used for scaffolding.",
					},
				),
			),
			templateId: Type.Optional(
				Type.String({
					description:
						"Optional milestone template scaffold (e.g. bugfix, small-feature, refactor)",
				}),
			),
			// ── Enrichment metadata (optional — defaults to empty) ────────────
			status: Type.Optional(
				Type.String({ description: "Milestone status (defaults to active)" }),
			),
			dependsOn: Type.Optional(
				Type.Array(Type.String(), { description: "Milestone dependencies" }),
			),
			successCriteria: Type.Optional(
				Type.Array(Type.String(), {
					description: "Top-level success criteria bullets",
				}),
			),
			keyRisks: Type.Optional(
				Type.Array(
					Type.Object({
						risk: Type.String({ description: "Risk statement" }),
						whyItMatters: Type.String({ description: "Why the risk matters" }),
					}),
					{ description: "Structured risk entries" },
				),
			),
			proofStrategy: Type.Optional(
				Type.Array(
					Type.Object({
						riskOrUnknown: Type.String({
							description: "Risk or unknown to retire",
						}),
						retireIn: Type.String({ description: "Where it will be retired" }),
						whatWillBeProven: Type.String({
							description: "What proof will be produced",
						}),
					}),
					{ description: "Structured proof strategy entries" },
				),
			),
			verificationContract: Type.Optional(
				Type.String({ description: "Verification contract text" }),
			),
			verificationIntegration: Type.Optional(
				Type.String({ description: "Integration verification text" }),
			),
			verificationOperational: Type.Optional(
				Type.String({ description: "Operational verification text" }),
			),
			verificationUat: Type.Optional(
				Type.String({ description: "UAT verification text" }),
			),
			definitionOfDone: Type.Optional(
				Type.Array(Type.String(), {
					description: "Definition of done bullets",
				}),
			),
			requirementCoverage: Type.Optional(
				Type.String({ description: "Requirement coverage text" }),
			),
			boundaryMapMarkdown: Type.Optional(
				Type.String({ description: "Boundary map markdown block" }),
			),
			productResearch: Type.Optional(
				Type.Object(
					{
						purpose: Type.String({
							description:
								"Why this product/category research exists for this milestone",
						}),
						consumer: Type.String({
							description:
								"Who consumes the research and what decision it informs",
						}),
						contract: Type.String({
							description:
								"What the research must answer before requirements or slices are locked",
						}),
						failureBoundary: Type.String({
							description:
								"What must happen if research is weak, stale, contradictory, or not applicable",
						}),
						evidence: Type.String({
							description:
								"Concrete sources/comparables inspected and what evidence was accepted",
						}),
						nonGoals: Type.String({
							description:
								"What this research intentionally does not decide or copy",
						}),
						invariants: Type.String({
							description:
								"Rules that remain true regardless of competitor findings",
						}),
						assumptions: Type.String({
							description:
								"Assumptions made where research could not fully resolve uncertainty",
						}),
						category: Type.String({
							description: "Product category or workflow category",
						}),
						targetJob: Type.String({
							description: "Target user job-to-be-done",
						}),
						comparables: Type.Array(Type.String(), {
							description:
								"3-5 competitors, comparable OSS tools, or production teams with specific findings",
						}),
						tableStakes: Type.Array(Type.String(), {
							description: "Expected category behaviors users will assume",
						}),
						differentiators: Type.Array(Type.String(), {
							description:
								"Differentiators this roadmap should preserve or create",
						}),
						antiPatterns: Type.Array(Type.String(), {
							description: "Failure modes or competitor patterns not to copy",
						}),
					},
					{
						description:
							"Purpose-shaped product and competitor research. Required when the milestone is product-facing, workflow-facing, developer-experience, or market-positioning; omit only for purely internal mechanical fixes.",
					},
				),
			),
			schedule: Type.Optional(
				Type.Array(
					Type.Object({
						in: Type.Optional(
							Type.String({
								description:
									"Duration string (e.g. '2w', '1d') — fires at milestone creation",
							}),
						),
						on_complete: Type.Optional(
							Type.Object({
								in: Type.String({
									description: "Duration after milestone completion",
								}),
							}),
						),
						kind: Type.Union(
							ALL_SCHEDULE_KINDS.map((k) => Type.Literal(k)),
							{
								description: `Entry kind (${ALL_SCHEDULE_KINDS.join(", ")})`,
							},
						),
						title: Type.String({
							description: "Entry title / reminder message",
						}),
						payload: Type.Optional(
							Type.Record(Type.String(), Type.Unknown(), {
								description: "Extra kind-specific data",
							}),
						),
					}),
					{ description: "Scheduled follow-ups to create with this milestone" },
				),
			),
			visionMeeting: Type.Optional(
				Type.Object(
					{
						trigger: Type.String({
							description: "Why a top-level roadmap meeting was needed",
						}),
						pm: Type.String({
							description:
								"Product manager framing of the milestone and roadmap",
						}),
						userAdvocate: Type.String({
							description:
								"User advocate view of what must matter for the end user",
						}),
						customerPanel: Type.String({
							description:
								"Nuanced customer panel summary across multiple likely customer viewpoints",
						}),
						business: Type.String({
							description:
								"Business view on viability, wedge, retention, or monetizable direction",
						}),
						researcher: Type.String({
							description:
								"Product category, competitors/comparable OSS tools, market expectations, and external research with specific findings",
						}),
						deliveryLead: Type.String({
							description:
								"Sequencing and scope-cut view from a delivery perspective",
						}),
						partner: Type.String({
							description: "Strengthened best-case roadmap proposal",
						}),
						combatant: Type.String({
							description:
								"Strongest objection, overbuild warning, or alternative framing",
						}),
						architect: Type.String({
							description: "System-fit and architecture synthesis",
						}),
						moderator: Type.String({
							description:
								"Final moderator decision after weighing the participants",
						}),
						weightedSynthesis: Type.String({
							description:
								"Weighted synthesis of the strongest claims, additions, cuts, and sequencing changes",
						}),
						confidenceByArea: Type.String({
							description: "Confidence by area, not one fake overall score",
						}),
						recommendedRoute: Type.Union(
							[
								Type.Literal("discussing"),
								Type.Literal("researching"),
								Type.Literal("planning"),
							],
							{
								description:
									"Where the system should route next after weighing the meeting",
							},
						),
					},
					{
						description:
							"Structured vision and roadmap alignment meeting for top-level milestone planning",
					},
				),
			),
		}),
		execute: planMilestoneExecute,
		renderCall(args, theme) {
			const milestoneId = args?.milestoneId ? String(args.milestoneId) : "";
			const title = args?.title ? String(args.title) : "";
			const slices = Array.isArray(args?.slices) ? args.slices : [];
			let text = theme.fg("toolTitle", theme.bold("plan_milestone"));
			if (milestoneId || title) {
				text += theme.fg(
					"muted",
					` ${[milestoneId, title].filter(Boolean).join(": ")}`,
				);
			}
			if (slices.length > 0) {
				text += theme.fg(
					"dim",
					` — ${slices.length} slice${slices.length === 1 ? "" : "s"}`,
				);
			}
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				const textContent = result.content?.find?.(
					(item) => item?.type === "text",
				)?.text;
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? textContent ?? "unknown"}`),
					0,
					0,
				);
			}
			const milestoneId = d?.milestoneId ? String(d.milestoneId) : "milestone";
			const title = d?.title ? String(d.title) : "";
			const sliceCount =
				typeof d?.sliceCount === "number" ? d.sliceCount : undefined;
			let text = theme.fg(
				"success",
				`${milestoneId} planned${title ? `: ${title}` : ""}`,
			);
			if (sliceCount !== undefined) {
				text += theme.fg(
					"dim",
					` · ${sliceCount} slice${sliceCount === 1 ? "" : "s"}`,
				);
			}
			if (d?.firstSliceId || d?.firstSliceTitle) {
				text += theme.fg(
					"dim",
					` · next ${[d.firstSliceId, d.firstSliceTitle]
						.filter(Boolean)
						.join(": ")}`,
				);
			}
			return new Text(text, 0, 0);
		},
	};
	pi.registerTool(planMilestoneTool);
	// ─── plan_slice ────────────────────────────────────────────────────
	const planSliceExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executePlanSlice(params, process.cwd());
	};
	const planSliceTool = {
		name: "plan_slice",
		label: "Plan Slice",
		description:
			"Write slice planning state to the SF database, render S##-PLAN.md plus task PLAN artifacts from DB, and clear caches after a successful render.",
		promptSnippet:
			"Plan a slice via DB write + PLAN render + cache invalidation",
		promptGuidelines: [
			"Use plan_slice for slice planning instead of writing S##-PLAN.md or task PLAN files directly.",
			"Keep parameters flat and provide the full slice planning payload, including tasks.",
			"The tool validates input, requires an existing parent slice, writes slice/task planning data, renders PLAN.md and task plan files from DB, and clears both state and parse caches after success.",
		],
		parameters: Type.Object({
			// ── Core identification + content (required) ──────────────────────
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
			goal: Type.String({
				description:
					"Slice purpose/goal. Start with the capability this slice makes true before implementation detail.",
			}),
			adversarialReview: Type.Optional(
				Type.Object(
					{
						partner: Type.String({
							description:
								"Strongest case for the plan and confirmed mechanism",
						}),
						combatant: Type.String({
							description:
								"Attacks the premise first, then the proposal and alternatives",
						}),
						architect: Type.String({
							description:
								"System-fit review after partner and combatant passes",
						}),
					},
					{ description: "Adversarial review summary for this slice plan" },
				),
			),
			planningMeeting: Type.Object(
				{
					trigger: Type.String({
						description: "Why a planning meeting was needed",
					}),
					pm: Type.String({ description: "PM/product framing and scope cut" }),
					userAdvocate: Type.Optional(
						Type.String({
							description:
								"User advocate view of what must matter for the end user",
						}),
					),
					customerPanel: Type.Optional(
						Type.String({
							description:
								"Nuanced customer panel summary across multiple likely customer viewpoints",
						}),
					),
					business: Type.Optional(
						Type.String({
							description: "Business or viability perspective when relevant",
						}),
					),
					researcher: Type.String({
						description:
							"Research and evidence summary, including docs/code findings",
					}),
					deliveryLead: Type.Optional(
						Type.String({
							description:
								"Sequencing, scope cut, and delivery risk perspective",
						}),
					),
					partner: Type.String({ description: "Strengthened best-case plan" }),
					combatant: Type.String({
						description:
							"Strongest objection or alternative root cause/approach",
					}),
					architect: Type.String({
						description: "System-fit and sequencing resolution",
					}),
					moderator: Type.String({
						description: "Moderator synthesis and decision",
					}),
					recommendedRoute: Type.Union(
						[
							Type.Literal("discussing"),
							Type.Literal("researching"),
							Type.Literal("planning"),
						],
						{
							description: "Where the workflow should route after the meeting",
						},
					),
					confidenceSummary: Type.String({
						description: "Confidence rationale after the meeting",
					}),
				},
				{
					description:
						"Required populated planning meeting artifact. Empty, null, or missing planningMeeting is not acceptable.",
				},
			),
			tasks: Type.Array(
				Type.Object({
					taskId: Type.String({ description: "Task ID (e.g. T01)" }),
					title: Type.String({ description: "Task title" }),
					description: Type.String({
						description:
							"Task description / steps block. Start with Purpose: why this task exists and which part of the slice contract it closes.",
					}),
					estimate: Type.String({ description: "Task estimate string" }),
					files: Type.Array(Type.String(), {
						description: "Files likely touched",
					}),
					verify: Type.String({ description: "Verification command or block" }),
					inputs: Type.Array(Type.String(), {
						description: "Input files or references",
					}),
					expectedOutput: Type.Array(Type.String(), {
						description: "Expected output files or artifacts",
					}),
					observabilityImpact: Type.Optional(
						Type.String({ description: "Task observability impact" }),
					),
					risk: Type.Optional(
						Type.String({
							description:
								"Task risk level: none, low, medium, high, or critical",
						}),
					),
					mutationScope: Type.Optional(
						Type.String({
							description:
								"Declared mutation scope: none, docs-only, config, test-only, isolated, bounded, cross-cutting, or systemic",
						}),
					),
					verification: Type.Optional(
						Type.String({
							description:
								"Verification requirement type: none, self-check, review, test, integration, or manual-qa",
						}),
					),
					planApproval: Type.Optional(
						Type.String({
							description:
								"Approval state: not-required, pending, approved, rejected, or auto-approved",
						}),
					),
					estimatedEffort: Type.Optional(
						Type.Number({
							description: "Estimated effort in minutes when known",
						}),
					),
					dependencies: Type.Optional(
						Type.Array(Type.String(), {
							description: "Task IDs this task depends on",
						}),
					),
					blocksParallel: Type.Optional(
						Type.Boolean({
							description: "True when no other task should run concurrently",
						}),
					),
					requiresUserInput: Type.Optional(
						Type.Boolean({
							description: "True when execution is expected to need user input",
						}),
					),
					autoRetry: Type.Optional(
						Type.Boolean({
							description: "Whether transient failures may retry automatically",
						}),
					),
					maxRetries: Type.Optional(
						Type.Number({
							description: "Maximum automatic retry attempts, 0-10",
						}),
					),
				}),
				{ description: "Planned tasks for the slice" },
			),
			// ── Enrichment metadata (optional — defaults to empty) ────────────
			successCriteria: Type.Optional(
				Type.String({ description: "Slice success criteria block" }),
			),
			proofLevel: Type.Optional(
				Type.String({ description: "Slice proof level" }),
			),
			integrationClosure: Type.Optional(
				Type.String({ description: "Slice integration closure" }),
			),
			observabilityImpact: Type.Optional(
				Type.String({ description: "Slice observability impact" }),
			),
		}),
		execute: planSliceExecute,
	};
	pi.registerTool(planSliceTool);
	// ─── plan_task ─────────────────────────────────────────────────────
	const planTaskExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		const dbAvailable = await ensureDbOpen();
		if (!dbAvailable) {
			return {
				content: [
					{
						type: "text",
						text: "Error: SF database is not available. Cannot plan task.",
					},
				],
				details: { operation: "plan_task", error: "db_unavailable" },
			};
		}
		try {
			const { handlePlanTask } = await import("../tools/plan-task.js");
			const result = await handlePlanTask(params, process.cwd());
			if ("error" in result) {
				return {
					content: [
						{
							type: "text",
							text: `Error planning task: ${result.error}`,
						},
					],
					details: { operation: "plan_task", error: result.error },
				};
			}
			return {
				content: [
					{
						type: "text",
						text: `Planned task ${result.taskId} (${result.sliceId}/${result.milestoneId})`,
					},
				],
				details: {
					operation: "plan_task",
					milestoneId: result.milestoneId,
					sliceId: result.sliceId,
					taskId: result.taskId,
					taskPlanPath: result.taskPlanPath,
				},
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `plan_task tool failed: ${msg}`, {
				tool: "plan_task",
				error: String(err),
			});
			return {
				content: [{ type: "text", text: `Error planning task: ${msg}` }],
				details: { operation: "plan_task", error: msg },
			};
		}
	};
	const planTaskTool = {
		name: "plan_task",
		label: "Plan Task",
		description:
			"Write task planning state to the SF database, render tasks/T##-PLAN.md from DB, and clear caches after a successful render.",
		promptSnippet:
			"Plan a task via DB write + task PLAN render + cache invalidation",
		promptGuidelines: [
			"Use plan_task for task planning instead of writing tasks/T##-PLAN.md directly.",
			"Keep parameters flat and provide the full task planning payload.",
			"The tool validates input, requires an existing parent slice, writes task planning data, renders the task PLAN file from DB, and clears both state and parse caches after success.",
		],
		parameters: Type.Object({
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
			taskId: Type.String({ description: "Task ID (e.g. T01)" }),
			title: Type.String({ description: "Task title" }),
			description: Type.String({
				description:
					"Task description / steps block. Start with Purpose: why this task exists and which part of the slice contract it closes.",
			}),
			estimate: Type.String({ description: "Task estimate string" }),
			files: Type.Array(Type.String(), { description: "Files likely touched" }),
			verify: Type.String({ description: "Verification command or block" }),
			inputs: Type.Array(Type.String(), {
				description: "Input files or references",
			}),
			expectedOutput: Type.Array(Type.String(), {
				description: "Expected output files or artifacts",
			}),
			observabilityImpact: Type.Optional(
				Type.String({ description: "Task observability impact" }),
			),
		}),
		execute: planTaskExecute,
	};
	pi.registerTool(planTaskTool);
	// ─── complete_task ─────────────────────────────────────────────────
	const taskCompleteExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executeTaskComplete(params, process.cwd());
	};
	const taskCompleteTool = {
		name: "complete_task",
		label: "Complete Task",
		description:
			"Record a completed task to the SF database, render a SUMMARY.md to disk, and toggle the plan checkbox — all in one atomic operation. " +
			"Writes the task row inside a transaction, then performs filesystem writes outside the transaction.",
		promptSnippet:
			"Complete a SF task (DB write + summary render + checkbox toggle)",
		promptGuidelines: [
			"Use complete_task when a task is finished and needs to be recorded.",
			"All string fields are required. verificationEvidence is an array of objects with command, exitCode, verdict, durationMs.",
			"The tool validates required fields and returns an error message if any are missing.",
			"On success, returns the summaryPath where the SUMMARY.md was written.",
			"Idempotent — calling with the same params twice will upsert (INSERT OR REPLACE) without error.",
		],
		parameters: Type.Object({
			// ── Core identification + content (required) ──────────────────────
			taskId: Type.String({ description: "Task ID (e.g. T01)" }),
			sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			oneLiner: Type.String({
				description: "One-line summary of what was accomplished",
			}),
			narrative: Type.String({
				description: "Detailed narrative of what happened during the task",
			}),
			verification: Type.String({
				description:
					"What was verified and how — commands run, tests passed, behavior confirmed",
			}),
			// ── ADR-0000 (SF v70): purpose trace ─────────────────────────────
			// Required. The sentence or clause of the slice goal this change
			// served. complete-slice refuses to flip status while any task
			// has a NULL trace, so omitting this field will fail the call.
			purposeTrace: Type.String({
				description:
					"REQUIRED (ADR-0000): the sentence or clause from the slice goal that this change served. " +
					"Quote or paraphrase the slice.goal clause in 1-3 sentences. complete-slice will refuse to close " +
					"the slice if any task is missing this field.",
			}),
			// ── Enrichment metadata (optional — defaults to empty) ────────────
			deviations: Type.Optional(
				Type.String({
					description: "Deviations from the task plan, or 'None.'",
				}),
			),
			knownIssues: Type.Optional(
				Type.String({
					description: "Known issues discovered but not fixed, or 'None.'",
				}),
			),
			keyFiles: Type.Optional(
				Type.Array(Type.String(), {
					description: "List of key files created or modified",
				}),
			),
			keyDecisions: Type.Optional(
				Type.Array(Type.String(), {
					description: "List of key decisions made during this task",
				}),
			),
			blockerDiscovered: Type.Optional(
				Type.Boolean({
					description: "Whether a plan-invalidating blocker was discovered",
				}),
			),
			// SF ADR-011 Phase 2: mid-execution escalation — agent flags an ambiguity
			// for the user. Only honored when phases.mid_execution_escalation=true.
			escalation: Type.Optional(
				Type.Object(
					{
						question: Type.String({
							description:
								"The question the user needs to answer — one clear sentence.",
						}),
						options: Type.Array(
							Type.Object({
								id: Type.String({
									description:
										"Short id (e.g. 'A', 'B') used by /escalate resolve.",
								}),
								label: Type.String({ description: "One-line label." }),
								tradeoffs: Type.String({
									description: "1-2 sentences on the tradeoffs of this option.",
								}),
							}),
							{
								minItems: 2,
								maxItems: 4,
								description: "2–4 options the user can choose between.",
							},
						),
						recommendation: Type.String({
							description: "Option id the executor recommends.",
						}),
						recommendationRationale: Type.String({
							description: "Why the recommendation — 1–2 sentences.",
						}),
						continueWithDefault: Type.Boolean({
							description:
								"When true, loop continues (artifact logged for later review). When false, autonomous mode pauses until the user resolves via /escalate resolve.",
						}),
					},
					{
						description:
							"SF ADR-011 P2: optional escalation payload. Only honored when phases.mid_execution_escalation is true.",
					},
				),
			),
			verificationEvidence: Type.Optional(
				Type.Array(
					Type.Union([
						Type.Object({
							command: Type.String({
								description: "Verification command that was run",
							}),
							exitCode: Type.Number({
								description: "Exit code of the command",
							}),
							verdict: Type.String({
								description: "Pass/fail verdict (e.g. '✅ pass', '❌ fail')",
							}),
							durationMs: Type.Number({
								description: "Duration of the command in milliseconds",
							}),
						}),
						Type.String({
							description: "Fallback: verification summary string",
						}),
					]),
					{ description: "Array of verification evidence entries" },
				),
			),
		}),
		execute: taskCompleteExecute,
	};
	pi.registerTool(taskCompleteTool);
	// ─── complete_slice ────────────────────────────────────────────────
	const sliceCompleteExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executeSliceComplete(params, process.cwd());
	};
	const sliceCompleteTool = {
		name: "complete_slice",
		label: "Complete Slice",
		description:
			"Record a completed slice to the SF database, render SUMMARY.md + UAT.md to disk, and toggle the roadmap checkbox — all in one atomic operation. " +
			"Validates all tasks are complete before proceeding. Writes the slice row inside a transaction, then performs filesystem writes outside the transaction.",
		promptSnippet:
			"Complete a SF slice (DB write + summary/UAT render + roadmap checkbox toggle)",
		promptGuidelines: [
			"Use complete_slice when all tasks in a slice are finished and the slice needs to be recorded.",
			"All tasks in the slice must have status 'complete' — the handler validates this before proceeding.",
			"On success, returns summaryPath and uatPath where the files were written.",
			"Idempotent — calling with the same params twice will not crash.",
		],
		parameters: Type.Object({
			// ── Core identification + content (required) ──────────────────────
			sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			sliceTitle: Type.String({ description: "Title of the slice" }),
			oneLiner: Type.String({
				description: "One-line summary of what the slice accomplished",
			}),
			narrative: Type.String({
				description: "Detailed narrative of what happened across all tasks",
			}),
			verification: Type.String({
				description: "What was verified across all tasks",
			}),
			uatContent: Type.String({
				description: "UAT test content (markdown body)",
			}),
			// ── Enrichment metadata (optional — defaults to empty) ────────────
			deviations: Type.Optional(
				Type.String({
					description: "Deviations from the slice plan, or 'None.'",
				}),
			),
			knownLimitations: Type.Optional(
				Type.String({ description: "Known limitations or gaps, or 'None.'" }),
			),
			followUps: Type.Optional(
				Type.String({
					description: "Follow-up work discovered during execution, or 'None.'",
				}),
			),
			keyFiles: Type.Optional(
				Type.Union([Type.Array(Type.String()), Type.String()], {
					description: "Key files created or modified",
				}),
			),
			keyDecisions: Type.Optional(
				Type.Union([Type.Array(Type.String()), Type.String()], {
					description: "Key decisions made during this slice",
				}),
			),
			patternsEstablished: Type.Optional(
				Type.Union([Type.Array(Type.String()), Type.String()], {
					description: "Patterns established by this slice",
				}),
			),
			observabilitySurfaces: Type.Optional(
				Type.Union([Type.Array(Type.String()), Type.String()], {
					description: "Observability surfaces added",
				}),
			),
			provides: Type.Optional(
				Type.Union([Type.Array(Type.String()), Type.String()], {
					description: "What this slice provides to downstream slices",
				}),
			),
			requirementsSurfaced: Type.Optional(
				Type.Union([Type.Array(Type.String()), Type.String()], {
					description: "New requirements surfaced",
				}),
			),
			drillDownPaths: Type.Optional(
				Type.Union([Type.Array(Type.String()), Type.String()], {
					description: "Paths to task summaries for drill-down",
				}),
			),
			affects: Type.Optional(
				Type.Union([Type.Array(Type.String()), Type.String()], {
					description: "Downstream slices affected",
				}),
			),
			requirementsAdvanced: Type.Optional(
				Type.Array(
					Type.Union([
						Type.Object({
							id: Type.String({ description: "Requirement ID" }),
							how: Type.String({ description: "How it was advanced" }),
						}),
						Type.String({ description: "Fallback: 'ID — how' string" }),
					]),
					{ description: "Requirements advanced by this slice" },
				),
			),
			requirementsValidated: Type.Optional(
				Type.Array(
					Type.Union([
						Type.Object({
							id: Type.String({ description: "Requirement ID" }),
							proof: Type.String({ description: "What proof validates it" }),
						}),
						Type.String({ description: "Fallback: 'ID — proof' string" }),
					]),
					{ description: "Requirements validated by this slice" },
				),
			),
			requirementsInvalidated: Type.Optional(
				Type.Array(
					Type.Union([
						Type.Object({
							id: Type.String({ description: "Requirement ID" }),
							what: Type.String({ description: "What changed" }),
						}),
						Type.String({ description: "Fallback: 'ID — what' string" }),
					]),
					{ description: "Requirements invalidated or re-scoped" },
				),
			),
			filesModified: Type.Optional(
				Type.Array(
					Type.Union([
						Type.Object({
							path: Type.String({ description: "File path" }),
							description: Type.String({ description: "What changed" }),
						}),
						Type.String({ description: "Fallback: file path string" }),
					]),
					{ description: "Files modified with descriptions" },
				),
			),
			requires: Type.Optional(
				Type.Array(
					Type.Union([
						Type.Object({
							slice: Type.String({ description: "Dependency slice ID" }),
							provides: Type.String({
								description: "What was consumed from it",
							}),
						}),
						Type.String({ description: "Fallback: slice ID string" }),
					]),
					{ description: "Upstream slice dependencies consumed" },
				),
			),
		}),
		execute: sliceCompleteExecute,
	};
	pi.registerTool(sliceCompleteTool);
	// ─── skip_slice (#3477 / #3487) ───────────────────────────────────
	const skipSliceExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		const dbAvailable = await ensureDbOpen();
		if (!dbAvailable) {
			return {
				content: [
					{
						type: "text",
						text: "Error: SF database is not available. Cannot skip slice.",
					},
				],
				details: { operation: "skip_slice", error: "db_unavailable" },
			};
		}
		try {
			const { getSlice, updateSliceStatus } = await import("../sf-db.js");
			const { invalidateStateCache } = await import("../state.js");
			const slice = getSlice(params.milestoneId, params.sliceId);
			if (!slice) {
				return {
					content: [
						{
							type: "text",
							text: `Error: Slice ${params.sliceId} not found in milestone ${params.milestoneId}`,
						},
					],
					details: { operation: "skip_slice", error: "slice_not_found" },
				};
			}
			if (slice.status === "complete" || slice.status === "done") {
				return {
					content: [
						{
							type: "text",
							text: `Error: Slice ${params.sliceId} is already complete — cannot skip.`,
						},
					],
					details: {
						operation: "skip_slice",
						error: "already_complete",
					},
				};
			}
			if (slice.status === "skipped") {
				return {
					content: [
						{
							type: "text",
							text: `Slice ${params.sliceId} is already skipped.`,
						},
					],
					details: {
						operation: "skip_slice",
						sliceId: params.sliceId,
						milestoneId: params.milestoneId,
					},
				};
			}
			updateSliceStatus(params.milestoneId, params.sliceId, "skipped");
			invalidateStateCache();
			// Rebuild STATE.md so it reflects the skip immediately (#3477).
			// Without this, /autonomous reads stale STATE.md and resumes the skipped slice.
			try {
				const basePath = process.cwd();
				const { rebuildState } = await import("../doctor.js");
				await rebuildState(basePath);
			} catch (err) {
				logError("tool", `skip_slice rebuildState failed: ${err.message}`, {
					tool: "skip_slice",
				});
			}
			return {
				content: [
					{
						type: "text",
						text: `Skipped slice ${params.sliceId} (${params.milestoneId}). Reason: ${params.reason ?? "User-directed skip"}. Autonomous mode will advance past this slice.`,
					},
				],
				details: {
					operation: "skip_slice",
					sliceId: params.sliceId,
					milestoneId: params.milestoneId,
					reason: params.reason,
				},
			};
		} catch (err) {
			const msg = getErrorMessage(err);
			logError("tool", `skip_slice tool failed: ${msg}`, {
				tool: "skip_slice",
				error: String(err),
			});
			return {
				content: [{ type: "text", text: `Error skipping slice: ${msg}` }],
				details: { operation: "skip_slice", error: msg },
			};
		}
	};
	pi.registerTool({
		name: "skip_slice",
		label: "Skip Slice",
		description:
			"Mark a slice as skipped so autonomous mode advances past it without executing. " +
			"The slice data is preserved for reference. The state machine treats skipped slices like completed ones for dependency satisfaction.",
		promptSnippet:
			"Skip a SF slice (mark as skipped, autonomous mode will advance past it)",
		promptGuidelines: [
			"Use skip_slice when a slice should be bypassed — descoped, superseded, or no longer relevant.",
			"Cannot skip a slice that is already complete.",
			"Skipped slices satisfy downstream dependencies just like completed slices.",
		],
		parameters: Type.Object({
			sliceId: Type.String({ description: "Slice ID (e.g. S02)" }),
			milestoneId: Type.String({ description: "Milestone ID (e.g. M003)" }),
			reason: Type.Optional(
				Type.String({ description: "Reason for skipping this slice" }),
			),
		}),
		execute: skipSliceExecute,
	});
	// ─── complete_milestone ────────────────────────────────────────────
	const milestoneCompleteExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executeCompleteMilestone(params, process.cwd());
	};
	const milestoneCompleteTool = {
		name: "complete_milestone",
		label: "Complete Milestone",
		description:
			"Record a completed milestone to the SF database, render MILESTONE-SUMMARY.md to disk — all in one atomic operation. " +
			"Validates all slices are complete before proceeding.",
		promptSnippet: "Complete a SF milestone (DB write + summary render)",
		promptGuidelines: [
			"Use complete_milestone when all slices in a milestone are finished and the milestone needs to be recorded.",
			"All slices in the milestone must have status 'complete' — the handler validates this before proceeding.",
			"verificationPassed must be explicitly set to true — the handler rejects completion if verification did not pass.",
			"On success, returns summaryPath where the MILESTONE-SUMMARY.md was written.",
		],
		parameters: Type.Object({
			// ── Core identification + content (required) ──────────────────────
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			title: Type.String({ description: "Milestone title" }),
			oneLiner: Type.String({
				description: "One-sentence summary of what the milestone achieved",
			}),
			narrative: Type.String({
				description: "Detailed narrative of what happened during the milestone",
			}),
			verificationPassed: Type.Boolean({
				description:
					"Must be true — confirms that code change verification, success criteria, and definition of done checks all passed before completion",
			}),
			// ── Enrichment metadata (optional — defaults to empty) ────────────
			successCriteriaResults: Type.Optional(
				Type.String({
					description:
						"Markdown detailing how each success criterion was met or not met",
				}),
			),
			definitionOfDoneResults: Type.Optional(
				Type.String({
					description:
						"Markdown detailing how each definition-of-done item was met",
				}),
			),
			requirementOutcomes: Type.Optional(
				Type.String({
					description:
						"Markdown detailing requirement status transitions with evidence",
				}),
			),
			keyDecisions: Type.Optional(
				Type.Array(Type.String(), {
					description:
						"Key architectural/pattern decisions made during the milestone",
				}),
			),
			keyFiles: Type.Optional(
				Type.Array(Type.String(), {
					description: "Key files created or modified during the milestone",
				}),
			),
			lessonsLearned: Type.Optional(
				Type.Array(Type.String(), {
					description: "Lessons learned during the milestone",
				}),
			),
			followUps: Type.Optional(
				Type.String({ description: "Follow-up items for future milestones" }),
			),
			deviations: Type.Optional(
				Type.String({ description: "Deviations from the original plan" }),
			),
			schedule: Type.Optional(
				Type.Array(
					Type.Object({
						in: Type.Optional(
							Type.String({
								description:
									"Duration string (e.g. '2w', '1d') — fires at milestone creation",
							}),
						),
						on_complete: Type.Optional(
							Type.Object({
								in: Type.String({
									description: "Duration after milestone completion",
								}),
							}),
						),
						kind: Type.Union(
							ALL_SCHEDULE_KINDS.map((k) => Type.Literal(k)),
							{
								description: `Entry kind (${ALL_SCHEDULE_KINDS.join(", ")})`,
							},
						),
						title: Type.String({
							description: "Entry title / reminder message",
						}),
						payload: Type.Optional(
							Type.Record(Type.String(), Type.Unknown(), {
								description: "Extra kind-specific data",
							}),
						),
					}),
					{
						description:
							"Scheduled follow-ups to create on milestone completion",
					},
				),
			),
		}),
		execute: milestoneCompleteExecute,
	};
	pi.registerTool(milestoneCompleteTool);
	// ─── validate_milestone ────────────────────────────────────────────
	const milestoneValidateExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executeValidateMilestone(params, process.cwd());
	};
	const milestoneValidateTool = {
		name: "validate_milestone",
		label: "Validate Milestone",
		description:
			"Validate a milestone before completion — persist validation results to the DB, render VALIDATION.md to disk. " +
			"Records verdict (pass/needs-attention/needs-remediation) and rationale.",
		promptSnippet: "Validate a SF milestone (DB write + VALIDATION.md render)",
		promptGuidelines: [
			"Use validate_milestone when all slices are done and the milestone needs validation before completion.",
			"Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verificationClasses (optional), verdictRationale, remediationPlan (optional).",
			"If verdict is 'needs-remediation', also provide remediationPlan and use reassess_roadmap to add remediation slices to the roadmap.",
			"On success, returns validationPath where VALIDATION.md was written.",
		],
		parameters: Type.Object({
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			verdict: StringEnum(["pass", "needs-attention", "needs-remediation"], {
				description: "Validation verdict",
			}),
			remediationRound: Type.Number({
				description: "Remediation round (0 for first validation)",
			}),
			successCriteriaChecklist: Type.String({
				description:
					"Markdown checklist of success criteria with pass/fail and evidence",
			}),
			sliceDeliveryAudit: Type.String({
				description:
					"Markdown table auditing each slice's claimed vs delivered output",
			}),
			crossSliceIntegration: Type.String({
				description: "Markdown describing any cross-slice boundary mismatches",
			}),
			requirementCoverage: Type.String({
				description: "Markdown describing any unaddressed requirements",
			}),
			verificationClasses: Type.Optional(
				Type.String({
					description:
						"Markdown describing verification class compliance and gaps",
				}),
			),
			verdictRationale: Type.String({
				description: "Why this verdict was chosen",
			}),
			remediationPlan: Type.Optional(
				Type.String({
					description:
						"Remediation plan (required if verdict is needs-remediation)",
				}),
			),
		}),
		execute: milestoneValidateExecute,
	};
	pi.registerTool(milestoneValidateTool);
	// ─── replan_slice ──────────────────────────────────────────────────
	const replanSliceExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executeReplanSlice(params, process.cwd());
	};
	const replanSliceTool = {
		name: "replan_slice",
		label: "Replan Slice",
		description:
			"Replan a slice after a blocker is discovered. Structurally enforces preservation of completed tasks — " +
			"mutations to completed task IDs are rejected with actionable error payloads. Writes replan history to DB, " +
			"applies task mutations, re-renders PLAN.md, and renders REPLAN.md.",
		promptSnippet:
			"Replan a SF slice with structural enforcement of completed tasks",
		promptGuidelines: [
			"Use replan_slice when a blocker is discovered and the slice plan needs rewriting.",
			"The tool structurally enforces that completed tasks cannot be updated or removed — violations return specific error payloads naming the blocked task ID.",
			"Parameters: milestoneId, sliceId, blockerTaskId, blockerDescription, whatChanged, optional slice-level planning/ceremony updates, updatedTasks (array), removedTaskIds (array).",
			"updatedTasks items: taskId, title, description, estimate, files, verify, inputs, expectedOutput.",
			"When the blocker changes the slice-level rationale or execution readiness, update adversarialReview and planningMeeting as part of the same replan.",
		],
		parameters: Type.Object({
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
			blockerTaskId: Type.String({
				description: "Task ID that discovered the blocker",
			}),
			blockerDescription: Type.String({
				description: "Description of the blocker",
			}),
			whatChanged: Type.String({
				description: "Summary of what changed in the plan",
			}),
			goal: Type.Optional(
				Type.String({
					description:
						"Updated slice goal when the replan changes the slice contract",
				}),
			),
			successCriteria: Type.Optional(
				Type.String({ description: "Updated slice success criteria block" }),
			),
			proofLevel: Type.Optional(
				Type.String({ description: "Updated slice proof level" }),
			),
			integrationClosure: Type.Optional(
				Type.String({ description: "Updated slice integration closure" }),
			),
			observabilityImpact: Type.Optional(
				Type.String({ description: "Updated slice observability impact" }),
			),
			adversarialReview: Type.Optional(
				Type.Object(
					{
						partner: Type.String({
							description: "Updated strongest case for the replanned slice",
						}),
						combatant: Type.String({
							description:
								"Updated strongest objection or alternative cause/path",
						}),
						architect: Type.String({
							description: "Updated system-fit review after the replan",
						}),
					},
					{ description: "Updated adversarial review for the replanned slice" },
				),
			),
			planningMeeting: Type.Optional(
				Type.Object(
					{
						trigger: Type.String({
							description: "Why a planning meeting was needed during replan",
						}),
						pm: Type.String({
							description: "PM/product framing and scope cut",
						}),
						researcher: Type.String({
							description: "Updated evidence summary for the replan",
						}),
						partner: Type.String({
							description: "Updated strengthened best-case plan",
						}),
						combatant: Type.String({
							description: "Updated strongest objection or alternative",
						}),
						architect: Type.String({
							description: "Updated system-fit and sequencing resolution",
						}),
						moderator: Type.String({
							description: "Moderator synthesis and route after replan",
						}),
						recommendedRoute: Type.Union(
							[
								Type.Literal("discussing"),
								Type.Literal("researching"),
								Type.Literal("planning"),
							],
							{
								description:
									"Where the workflow should route after the replanning meeting",
							},
						),
						confidenceSummary: Type.String({
							description: "Confidence rationale after the replanning meeting",
						}),
					},
					{
						description:
							"Updated planning meeting artifact for the replanned slice",
					},
				),
			),
			updatedTasks: Type.Array(
				Type.Object({
					taskId: Type.String({ description: "Task ID (e.g. T01)" }),
					title: Type.String({ description: "Task title" }),
					description: Type.String({
						description: "Task description / steps block",
					}),
					estimate: Type.String({ description: "Task estimate string" }),
					files: Type.Array(Type.String(), {
						description: "Files likely touched",
					}),
					verify: Type.String({ description: "Verification command or block" }),
					inputs: Type.Array(Type.String(), {
						description: "Input files or references",
					}),
					expectedOutput: Type.Array(Type.String(), {
						description: "Expected output files or artifacts",
					}),
				}),
				{ description: "Tasks to upsert (update existing or insert new)" },
			),
			removedTaskIds: Type.Array(Type.String(), {
				description: "Task IDs to remove from the slice",
			}),
		}),
		execute: replanSliceExecute,
	};
	pi.registerTool(replanSliceTool);
	// ─── reassess_roadmap ──────────────────────────────────────────────
	const reassessRoadmapExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executeReassessRoadmap(params, process.cwd());
	};
	const reassessRoadmapTool = {
		name: "reassess_roadmap",
		label: "Reassess Roadmap",
		description:
			"Reassess the milestone roadmap after a slice completes. Structurally enforces preservation of completed slices — " +
			"mutations to completed slice IDs are rejected with actionable error payloads. Writes assessment to DB, " +
			"applies slice mutations, re-renders ROADMAP.md, and renders ASSESSMENT.md.",
		promptSnippet:
			"Reassess a SF roadmap with structural enforcement of completed slices",
		promptGuidelines: [
			"Use reassess_roadmap after a slice completes to reassess the roadmap.",
			"The tool structurally enforces that completed slices cannot be modified or removed — violations return specific error payloads naming the blocked slice ID.",
			"Parameters: milestoneId, completedSliceId, verdict, assessment, sliceChanges (object with modified, added, removed arrays).",
			"sliceChanges.modified items: sliceId, title, risk (optional), depends (optional), demo (optional).",
		],
		parameters: Type.Object({
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			completedSliceId: Type.String({
				description: "Slice ID that just completed",
			}),
			verdict: Type.String({
				description:
					"Assessment verdict (e.g. 'roadmap-confirmed', 'roadmap-adjusted')",
			}),
			assessment: Type.String({
				description: "Assessment text explaining the decision",
			}),
			sliceChanges: Type.Object(
				{
					modified: Type.Array(
						Type.Object({
							sliceId: Type.String({ description: "Slice ID to modify" }),
							title: Type.String({ description: "Updated slice title" }),
							risk: Type.Optional(
								Type.String({ description: "Updated risk level" }),
							),
							depends: Type.Optional(
								Type.Array(Type.String(), {
									description: "Updated dependencies",
								}),
							),
							demo: Type.Optional(
								Type.String({ description: "Updated demo text" }),
							),
						}),
						{ description: "Slices to modify" },
					),
					added: Type.Array(
						Type.Object({
							sliceId: Type.String({ description: "New slice ID" }),
							title: Type.String({ description: "New slice title" }),
							risk: Type.Optional(Type.String({ description: "Risk level" })),
							depends: Type.Optional(
								Type.Array(Type.String(), { description: "Dependencies" }),
							),
							demo: Type.Optional(Type.String({ description: "Demo text" })),
						}),
						{ description: "New slices to add" },
					),
					removed: Type.Array(Type.String(), {
						description: "Slice IDs to remove",
					}),
				},
				{ description: "Slice changes to apply" },
			),
		}),
		execute: reassessRoadmapExecute,
	};
	pi.registerTool(reassessRoadmapTool);
	// ─── record_gate ──────────────────────────────────────────────
	const saveGateResultExecute = async (
		_toolCallId,
		params,
		_signal,
		_onUpdate,
		_ctx,
	) => {
		return executeSaveGateResult(params, process.cwd());
	};
	const saveGateResultTool = {
		name: "record_gate",
		label: "Save Gate Result",
		description:
			"Save the result of a quality gate evaluation — verdict, rationale, and findings — to the database. " +
			"Call this after evaluating a quality gate question; one call per gate per evaluation run.",
		promptSnippet:
			"Save quality gate evaluation result (verdict, rationale, findings)",
		promptGuidelines: [
			"Use record_gate after evaluating a quality gate question.",
			"gateId must be one of: Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, MV04.",
			"verdict must be: pass (no concerns), flag (concerns found), or omitted (not applicable).",
			"rationale should be a one-sentence justification for the verdict.",
			"findings should contain detailed markdown analysis (or empty string if omitted).",
		],
		parameters: Type.Object({
			milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
			sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
			gateId: Type.String({
				description:
					"Gate ID: Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, or MV04",
			}),
			taskId: Type.Optional(
				Type.String({
					description: "Task ID for task-scoped gates (Q5/Q6/Q7)",
				}),
			),
			verdict: Type.String({ description: "pass, flag, or omitted" }),
			rationale: Type.String({ description: "One-sentence justification" }),
			findings: Type.Optional(
				Type.String({ description: "Detailed markdown findings" }),
			),
		}),
		execute: saveGateResultExecute,
		renderCall(args, theme) {
			let text = theme.fg("toolTitle", theme.bold("record_gate "));
			text += theme.fg("accent", args.gateId ?? "");
			text += theme.fg("dim", ` → ${args.verdict ?? ""}`);
			return new Text(text, 0, 0);
		},
		renderResult(result, _options, theme) {
			const d = result.details;
			if (result.isError || d?.error) {
				return new Text(
					theme.fg("error", `Error: ${d?.error ?? "unknown"}`),
					0,
					0,
				);
			}
			const color = d?.verdict === "flag" ? "warning" : "success";
			return new Text(theme.fg(color, `${d?.gateId}: ${d?.verdict}`), 0, 0);
		},
	};
	pi.registerTool(saveGateResultTool);

	// ─── chapter_open / chapter_close ────────────────────────────────────────
	// Intent chapters: crash-resume context. The agent calls chapter_open at
	// the start of each meaningful work block and chapter_close when it finishes.
	// On crash-resume, open chapters are surfaced in the system prompt so the
	// agent knows where it left off without replaying the full transcript.

	pi.registerTool({
		name: "chapter_open",
		label: "Open Intent Chapter",
		description:
			"Record the agent's intent at the start of a work block so crash-resume can surface it. " +
			"Call at the top of each autonomous work block with a clear one-sentence intent.",
		promptSnippet:
			"Open an intent chapter before starting a significant block of work",
		promptGuidelines: [
			"Call chapter_open before starting any significant work block (a task, a multi-step investigation, a refactor).",
			"Keep the intent concise — one sentence stating what you are about to accomplish.",
			"Pair every chapter_open with a chapter_close when the block completes (even on failure).",
		],
		parameters: {
			type: "object",
			properties: {
				intent: {
					type: "string",
					description:
						"One-sentence description of what this work block will accomplish. " +
						"Example: 'Implement the retry handler in packages/pi-ai/src/retry.ts'",
				},
				unit_type: {
					type: "string",
					description:
						"UOK unit type (e.g. 'execute-task', 'plan-slice'). Optional — defaults to current unit.",
				},
				unit_id: {
					type: "string",
					description:
						"UOK unit ID (e.g. 'M001/S01/T02'). Optional — defaults to current unit.",
				},
			},
			required: ["intent"],
		},
		execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
			const dbAvailable = await ensureDbOpen();
			if (!dbAvailable) {
				return {
					content: [
						{
							type: "text",
							text: "Error: SF database unavailable — chapter not opened.",
						},
					],
					details: { operation: "chapter_open", error: "db_unavailable" },
				};
			}
			try {
				const { openIntentChapter } = await import("../sf-db.js");
				const { randomUUID } = await import("node:crypto");
				const id = randomUUID();
				openIntentChapter({
					id,
					unitType: params.unit_type ?? "manual",
					unitId: params.unit_id ?? "manual",
					intent: params.intent,
				});
				return {
					content: [{ type: "text", text: `Chapter opened: ${id}` }],
					details: { operation: "chapter_open", id },
				};
			} catch (err) {
				const msg = getErrorMessage(err);
				return {
					content: [{ type: "text", text: `Error opening chapter: ${msg}` }],
					details: { operation: "chapter_open", error: msg },
				};
			}
		},
		renderToolCall: (_params, theme) => {
			const { Text } = theme;
			return new Text(theme.fg("info", "chapter_open"), 0, 0);
		},
		renderToolResult: (d, theme) => {
			const { Text } = theme;
			if (d?.error)
				return new Text(
					theme.fg("error", `chapter_open error: ${d.error}`),
					0,
					0,
				);
			return new Text(
				theme.fg("success", `chapter opened: ${d?.id ?? ""}`),
				0,
				0,
			);
		},
	});

	pi.registerTool({
		name: "chapter_close",
		label: "Close Intent Chapter",
		description:
			"Close a previously opened intent chapter when the work block completes. " +
			"Pass the id returned by chapter_open and an outcome (done|failed|skipped|blocked).",
		promptSnippet: "Close an intent chapter when a work block finishes",
		promptGuidelines: [
			"Call chapter_close after every chapter_open, regardless of outcome.",
			"Use outcome='done' for successful completion, 'failed' for errors, 'blocked' for dependencies.",
		],
		parameters: {
			type: "object",
			properties: {
				id: {
					type: "string",
					description: "Chapter ID returned by chapter_open.",
				},
				outcome: {
					type: "string",
					enum: ["done", "failed", "skipped", "blocked", "cancelled"],
					description: "Result of the work block.",
				},
			},
			required: ["id", "outcome"],
		},
		execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
			const dbAvailable = await ensureDbOpen();
			if (!dbAvailable) {
				return {
					content: [
						{
							type: "text",
							text: "Error: SF database unavailable — chapter not closed.",
						},
					],
					details: { operation: "chapter_close", error: "db_unavailable" },
				};
			}
			try {
				const { closeIntentChapter } = await import("../sf-db.js");
				const closed = closeIntentChapter(params.id, params.outcome);
				return {
					content: [
						{
							type: "text",
							text: closed
								? `Chapter ${params.id} closed (${params.outcome}).`
								: `Chapter ${params.id} not found or already closed.`,
						},
					],
					details: { operation: "chapter_close", id: params.id, closed },
				};
			} catch (err) {
				const msg = getErrorMessage(err);
				return {
					content: [{ type: "text", text: `Error closing chapter: ${msg}` }],
					details: { operation: "chapter_close", error: msg },
				};
			}
		},
		renderToolCall: (_params, theme) => {
			const { Text } = theme;
			return new Text(theme.fg("info", "chapter_close"), 0, 0);
		},
		renderToolResult: (d, theme) => {
			const { Text } = theme;
			if (d?.error)
				return new Text(
					theme.fg("error", `chapter_close error: ${d.error}`),
					0,
					0,
				);
			return new Text(
				theme.fg("success", `chapter closed: ${d?.id ?? ""}`),
				0,
				0,
			);
		},
	});
}
