import type { CapabilityKey } from "@/lib/permissions";
import { can, type UserContext } from "@/lib/permissions";
import { DISCOVERY_TRIAGE_AGENT_ID, prisma } from "@dpf/db";
// Runtime kernel-commandment gate (BI-43F95F77). Static imports — executeTool
// is a hot path; dynamic import per call would tank dispatcher throughput.
import { evaluateExecution } from "@/lib/kernel/runtime-gate";
import { loadEnforceablePrinciples } from "@/lib/kernel/load-enforceable-principles";
import { detectSessionClass } from "@/lib/kernel/session-class";
import { kernelGateDecisionsTotal } from "@/lib/operate/metrics";
import * as crypto from "crypto";
import { lazyFs, lazyFsPromises, lazyPath, lazyChildProcess, lazyUtil } from "@/lib/shared/lazy-node";
import { mergeHappyPathStateIntoPlan, generateBuildId } from "@/lib/feature-build-types";
import { BACKLOG_SOURCE_VALUES, BACKLOG_STATUS_VALUES, BACKLOG_WORK_TYPE_VALUES, EPIC_STATUSES } from "@/lib/explore/backlog";
import {
  analyzePublicWebsiteBranding,
  fetchPublicWebsiteEvidence,
  searchPublicWeb,
} from "@/lib/public-web-tools";
import { promoteBacklogItemToBuildDraft } from "@/lib/governed-backlog-tee-up";
import { activeBrandExtractionWhere } from "@/lib/brand/active-extraction";
import { recordExternalEvidence } from "@/lib/actions/external-evidence";
import { createPlatformIssueReport } from "@/lib/quality/platform-issue-reports";
import {
  MARKETING_CHANNELS,
  MARKETING_REVIEW_CADENCE,
  createMarketingAssetTask,
  createMarketingAutomationCandidate,
  createMarketingCampaignBrief,
  recordMarketingKpiCheckpoint,
  recordMarketingStrategistReview,
} from "@/lib/marketing";
import {
  DELIBERATION_ARTIFACT_TYPES,
  DELIBERATION_STRATEGY_PROFILES,
  DELIBERATION_TRIGGER_SOURCES,
} from "@/lib/deliberation/types";
import {
  DELIBERATION_TOOLS,
  deliberateOnMcpHandler,
} from "@/lib/mcp-tools-deliberation";
import {
  createLicenseReadinessIssue,
  saveLicensingInvestigationFinding,
} from "@/lib/actions/licensing-compliance";
import type { ReviewBranchInput } from "@/lib/integrate/build-reviewers";
import {
  getIntegrationBenchmarkMetadata,
  matchesIntegrationBenchmarkFilters,
  type IntegrationBenchmarkDomain,
  type IntegrationDeploymentMode,
  type IntegrationProfileTag,
  type IntegrationTreatment,
} from "@/lib/integrate/integration-benchmarking";
import { normalizeBuildPlanPaths } from "@/lib/integrate/build-plan-paths";
import {
  SANDBOX_RECOVERY_ACTIONS,
  isSandboxRecoveryAction,
} from "@/lib/integrate/sandbox/sandbox-admin-types";
import { getToolMarketplaceReadiness } from "@/lib/actions/tool-marketplace-readiness";
import {
  listCoworkerCapabilityNeeds,
  submitCoworkerSelfAssessment,
} from "@/lib/coworker-self-assessment/assessment-service";
import { inferProviderIdFromRouteContext } from "@/lib/ai-provider-route-context";
import { workCapsuleToolEnums } from "@/lib/work-capsules/mcp-handlers";
import { runtimeCoordinationToolEnums } from "@/lib/runtime-coordination/mcp-handlers";
import {
  COWORKER_ASSESSMENT_CONFIDENCE,
  COWORKER_ASSESSMENT_VERDICTS,
  COWORKER_CAPABILITY_NEED_KINDS,
  COWORKER_CAPABILITY_NEED_SEVERITIES,
  COWORKER_CAPABILITY_NEED_STATUSES,
  type CoworkerAssessmentConfidence,
  type CoworkerAssessmentVerdict,
  type CoworkerCapabilityNeedInput,
  type CoworkerCapabilityNeedKind,
  type CoworkerCapabilityNeedSeverity,
  type CoworkerCapabilityNeedStatus,
} from "@/lib/coworker-self-assessment/types";
import { ROUTE_AGENT_MAP_ENTRIES } from "@/lib/tak/agent-routing";

// ─── Types ───────────────────────────────────────────────────────────────────

export type BuildPhaseTag = "ideate" | "plan" | "build" | "review" | "ship";
type ToolExecutionContext = {
  routeContext?: string;
  agentId?: string;
  threadId?: string;
  taskRunId?: string;
  /**
   * Build the user is currently messaging from. Plumbed by agentic-loop.ts
   * from runAgenticLoop's `featureBuildId` param so phase-scoped tools can
   * target the correct build instead of fishing for "latest in phase X" —
   * which silently cross-contaminates state when multiple concurrent builds
   * are in the same phase (BI-F4A30FCB, Dale dogfood 2026-05-24).
   */
  featureBuildId?: string;
};

/** MCP tool annotation hints (from MCP spec + n8n-MCP pattern).
 *  These let the agent router and governance layer make safety decisions
 *  without parsing the tool description text.
 *
 *  MCP-spec fields are advisory client hints, not server-side enforcement —
 *  the grant system in agent-grants.ts is the authoritative check.
 *  `irreversibleHint` is a DPF extension layered on top: every irreversible
 *  tool is also destructive, but not every destructive tool is irreversible.
 *  The envelope flow (Pseudo-User Contract spec §6.4 — BI-0F9C291C) uses
 *  irreversibleHint to enforce the typed-phrase hard floor — irreversible
 *  actions cannot be auto-approved by per-turn elevation. */
export type ToolAnnotations = {
  /** Tool only reads data — never mutates state */
  readOnlyHint?: boolean;
  /** Tool performs a destructive/irreversible action (delete, overwrite, deploy) */
  destructiveHint?: boolean;
  /** Calling the tool twice with the same input produces the same result */
  idempotentHint?: boolean;
  /** Tool reaches outside the platform boundary (network, external API) */
  openWorldHint?: boolean;
  /** DPF extension. Tool's effect cannot be undone by any existing inverse
   *  tool — e.g. data deletion with no soft-delete column, a network send
   *  that the recipient has already acted on, a financial transfer. Always
   *  implies `destructiveHint: true`. Used by the Pseudo-User Contract
   *  envelope flow (BI-0F9C291C) to require an explicit typed-phrase
   *  confirmation regardless of per-turn elevation. */
  irreversibleHint?: boolean;
};

export type ToolDefinition = {
  name: string;
  description: string;
  inputSchema: Record<string, unknown>;
  requiredCapability: CapabilityKey | null;
  requiresExternalAccess?: boolean;
  executionMode?: "proposal" | "immediate";
  sideEffect?: boolean;
  /**
   * Tool captures the coworker's own recommendation or work product as a
   * structured artifact (e.g. save_marketing_review). Persistence-only; no
   * external action. Coworkers running in `advise` mode are still permitted
   * to call these tools because the recommendation IS the deliverable — the
   * advise/act distinction guards against acting on the outside world, not
   * against recording the advice the user explicitly asked for. The tool
   * remains `sideEffect: true` for MCP annotations and tool-execution
   * memory; this flag only exempts it from the advise-mode runtime filter.
   */
  coworkerArtifact?: boolean;
  /** When set, tool is only available during these build phases.
   *  Null/undefined = available in all phases (non-build tools). */
  buildPhases?: BuildPhaseTag[] | null;
  /** MCP-spec tool annotations for governance and safety classification */
  annotations?: ToolAnnotations;
  /** Pseudo-User Contract (spec §6.1 — BI-D9487754): the ScreenManifest
   *  surface this tool is meaningful in. Used by the manifest CI lint to
   *  validate that domain actions a manifest exposes have a matching tool
   *  entry, and by the chat handler to filter the tool catalog by current
   *  routeContext. Undefined = surface-agnostic (the tool is callable from
   *  any context — most tools fall here). */
  screenSurface?: string;
  /**
   * Predicate that lets an `executionMode: "proposal"` tool skip the proposal
   * card and execute immediately when the user has already pre-authorized the
   * action through platform configuration. Returning true means the agentic
   * loop treats this tool call as immediate; false (or undefined) preserves
   * the normal proposal-approval flow. Used for `contribute_to_hive` under
   * `contributionMode=contribute_all` — with DCO already accepted, the user
   * has given standing authorization for every shipped build to contribute
   * upstream, so the per-build proposal card is redundant ceremony that
   * silently stalls autonomous runs. The predicate also receives the pending
   * params (when available) so flows like `start_deliberation` can inspect
   * `triggerSource` to pre-authorize stage-default / risk-escalated runs.
   */
  autoApproveWhen?: (ctx: {
    userId: string;
    params?: Record<string, unknown>;
  }) => Promise<boolean>;
};

export type EndpointTestRunRequest = {
  endpointId?: string;
  modelId?: string;
  taskType?: string;
  probesOnly: boolean;
  allEndpoints: boolean;
  allModels: boolean;
  error?: string;
};

/** Derive tool annotations from existing ToolDefinition fields.
 *  Explicit `annotations` on a tool override these defaults. */
export function resolveAnnotations(tool: ToolDefinition): ToolAnnotations {
  const defaults: ToolAnnotations = {
    readOnlyHint: tool.sideEffect === false && tool.executionMode !== "proposal",
    destructiveHint: tool.executionMode === "proposal" || DESTRUCTIVE_TOOLS.has(tool.name),
    idempotentHint: tool.sideEffect === false,
    openWorldHint: tool.requiresExternalAccess === true,
  };
  return { ...defaults, ...tool.annotations };
}

// ─── Parameter Sanitization ─────────────────────────────────────────────────
// Models (especially Codex/GPT) often fill optional object parameters with empty
// strings as schema artifacts. This causes validation failures in handlers that
// check for the object's presence before inspecting its fields.
//
// sanitizeToolParams strips optional object params whose string fields are all
// empty/whitespace. Applied once at the executeTool entry point so every handler
// is protected without per-tool defensive code.

const _toolSchemaCache = new Map<string, { required: Set<string>; objectParams: string[] }>();

function _getToolParamMeta(toolName: string): { required: Set<string>; objectParams: string[] } {
  const cached = _toolSchemaCache.get(toolName);
  if (cached) return cached;

  const tool = PLATFORM_TOOLS.find(t => t.name === toolName);
  if (!tool) {
    const empty = { required: new Set<string>(), objectParams: [] };
    _toolSchemaCache.set(toolName, empty);
    return empty;
  }

  const schema = tool.inputSchema as {
    properties?: Record<string, { type?: string }>;
    required?: string[];
  };
  const required = new Set<string>(schema.required ?? []);
  const objectParams: string[] = [];
  if (schema.properties) {
    for (const [key, prop] of Object.entries(schema.properties)) {
      if (prop?.type === "object" && !required.has(key)) {
        objectParams.push(key);
      }
    }
  }
  const result = { required, objectParams };
  _toolSchemaCache.set(toolName, result);
  return result;
}

/**
 * Strip optional object parameters that are empty schema artifacts.
 * An object param is considered empty when all its string-typed values
 * are empty or whitespace-only. Returns a shallow copy with empty
 * objects removed; does not mutate the original.
 */
export function sanitizeToolParams(
  toolName: string,
  params: Record<string, unknown>,
): Record<string, unknown> {
  const { objectParams } = _getToolParamMeta(toolName);
  if (objectParams.length === 0) return params;

  let cleaned: Record<string, unknown> | null = null;
  for (const key of objectParams) {
    const val = params[key];
    if (val == null || typeof val !== "object") continue;
    const obj = val as Record<string, unknown>;
    const stringValues = Object.values(obj).filter((v): v is string => typeof v === "string");
    // Object with at least one string field, all of which are empty → schema artifact
    if (stringValues.length > 0 && stringValues.every(s => s.trim() === "")) {
      if (!cleaned) cleaned = { ...params };
      delete cleaned[key];
      console.log(`[sanitize] ${JSON.stringify(toolName)}: stripped empty optional object param ${JSON.stringify(key)}`);
    }
  }
  return cleaned ?? params;
}

export function resolveSavePhaseHandoffTransition(
  params: Record<string, unknown>,
  context: ToolExecutionContext | undefined,
  currentPhase: string,
): { toPhase: string; autoAdvance: boolean; isInternalTaskHandoff: boolean } {
  const phaseOrder = ["ideate", "plan", "build", "review", "ship"];
  const idx = phaseOrder.indexOf(currentPhase);
  const isInternalTaskHandoff =
    context?.agentId === "AGT-ORCH-300"
    && context?.routeContext === "/build"
    && params["autoAdvance"] === false;
  const requestedToPhase = isInternalTaskHandoff
    && typeof params["toPhase"] === "string"
    && [...phaseOrder, "complete"].includes(String(params["toPhase"]).trim())
    ? String(params["toPhase"]).trim()
    : null;

  return {
    toPhase: requestedToPhase ?? (idx >= 0 && idx < phaseOrder.length - 1 ? phaseOrder[idx + 1]! : "complete"),
    autoAdvance: isInternalTaskHandoff ? false : true,
    isInternalTaskHandoff,
  };
}

/** Tools that perform destructive or irreversible actions beyond what
 *  sideEffect/executionMode already captures. Used by resolveAnnotations
 *  to set destructiveHint on tools that don't carry an explicit
 *  `annotations` block. */
const DESTRUCTIVE_TOOLS = new Set([
  "deploy_feature",
  "execute_promotion",
  "transition_employee_status",
  "contribute_to_hive",
  "apply_platform_update",
  // Pseudo-User Contract (BI-B2F7ABF5): build-phase and lifecycle ops that
  // advance the FeatureBuild state machine are destructive in the
  // can't-quietly-take-back sense. Promote/process write FeatureBuild rows
  // and dispatch coworker work; approve_decomposition commits the
  // decomposition that drives the Plan phase; start_build kicks off the
  // sandbox/code-generation chain.
  "promote_to_build_studio",
  "process_backlog_for_build_studio",
  "approve_decomposition",
  "start_build",
]);

export type ToolResult = {
  success: boolean;
  entityId?: string;
  message: string;
  error?: string;
  data?: Record<string, unknown>;
};

function codeGraphReadToolResult(
  result: Record<string, unknown> & { available?: unknown; summary?: unknown },
): ToolResult {
  const message = typeof result.summary === "string" && result.summary.trim()
    ? result.summary
    : "Code graph query completed.";
  if (result.available === false) {
    return {
      success: false,
      error: "Code graph unavailable",
      message,
      data: result,
    };
  }
  return {
    success: true,
    message,
    data: result,
  };
}

function cleanEndpointTestString(value: unknown): string | undefined {
  if (typeof value !== "string") return undefined;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
}

export function inferEndpointIdFromRouteContext(routeContext?: string): string | null {
  return inferProviderIdFromRouteContext(routeContext);
}

export function buildEndpointTestRunRequest(
  params: Record<string, unknown>,
  context?: { routeContext?: string },
): EndpointTestRunRequest {
  const explicitEndpointId = cleanEndpointTestString(params["endpointId"]);
  const inferredEndpointId = inferEndpointIdFromRouteContext(context?.routeContext) ?? undefined;
  const endpointId = explicitEndpointId ?? inferredEndpointId;
  const modelId = cleanEndpointTestString(params["modelId"]);
  const taskType = cleanEndpointTestString(params["taskType"]);
  const allEndpoints = endpointId ? false : params["allEndpoints"] === true;

  const base: EndpointTestRunRequest = {
    probesOnly: params["probesOnly"] === false ? false : true,
    allEndpoints,
    allModels: params["allModels"] === true,
    ...(endpointId ? { endpointId } : {}),
    ...(modelId ? { modelId } : {}),
    ...(taskType ? { taskType } : {}),
  };

  if (!endpointId && !allEndpoints) {
    return {
      ...base,
      error: "run_endpoint_tests requires endpointId or allEndpoints=true when it is not used from a provider detail route.",
    };
  }

  return base;
}

async function resolveRepresentativeEndpointModelId(endpointId: string): Promise<string | undefined> {
  const orderBy = [
    { toolFidelity: "desc" as const },
    { reasoning: "desc" as const },
    { conversational: "desc" as const },
    { modelId: "asc" as const },
  ];

  const toolCapable = await prisma.modelProfile.findFirst({
    where: { providerId: endpointId, modelStatus: "active", supportsToolUse: true },
    orderBy,
    select: { modelId: true },
  });
  if (toolCapable?.modelId) return toolCapable.modelId;

  const active = await prisma.modelProfile.findFirst({
    where: { providerId: endpointId, modelStatus: "active" },
    orderBy,
    select: { modelId: true },
  });
  return active?.modelId;
}

function optionalString(value: unknown): string | null {
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}

function stringArray(value: unknown): string[] {
  return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
}

async function resolveDocumentActorPrincipalId(userId: string, agentId?: string): Promise<string | null> {
  const { ensureAgentPrincipalIdentity, syncUserPrincipal } = await import("@/lib/identity/principal-linking");
  if (agentId) {
    const agentPrincipal = await ensureAgentPrincipalIdentity(agentId).catch(() => null);
    if (agentPrincipal?.id) return agentPrincipal.id;
  }
  const userPrincipal = await syncUserPrincipal(userId).catch(() => null);
  return userPrincipal?.id ?? null;
}

// ─── Tool Registry ───────────────────────────────────────────────────────────

const WORK_CAPSULE_TOOL_ENUMS = workCapsuleToolEnums();
const RUNTIME_COORDINATION_TOOL_ENUMS = runtimeCoordinationToolEnums();

export const PLATFORM_TOOLS: ToolDefinition[] = [
  ...DELIBERATION_TOOLS,
  {
    name: "create_backlog_item",
    description: "Create a new backlog item in the ops backlog. Use this tool to add new items — do NOT use update_backlog_item for items that do not exist yet. New items default to status=triaging; supply status+triageOutcome together only when explicitly skipping triage (e.g. Build Studio brief intake). When triageOutcome=build, effortSize is required.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Item title" },
        type: { type: "string", enum: ["portfolio", "product"], description: "Item type" },
        status: { type: "string", enum: ["triaging", "open", "in-progress"], description: "Initial status (defaults to triaging). Non-triaging requires a paired triageOutcome." },
        triageOutcome: { type: "string", enum: ["build", "runbook", "coworker-task", "defer", "duplicate", "discard"], description: "Required when status is not triaging" },
        workType: { type: "string", enum: [...BACKLOG_WORK_TYPE_VALUES], description: "What kind of work this is (closed enum). bug == defect; feature == new capability; chore | doc | tool | skill | refactor for the corresponding work categories." },
        source: { type: "string", enum: [...BACKLOG_SOURCE_VALUES], description: "Intake origin — how did this item arrive. user-request (a human asked for it) or automated-detection (the platform observed it). Defaults to user-request when omitted." },
        proposedOutcome: { type: "string", enum: ["build", "runbook", "coworker-task", "defer", "duplicate", "discard"], description: "Advisory suggestion for Scrum Master triage (non-binding)" },
        priority: { type: "integer", description: "Optional ranked priority within the open pool (lower = higher priority)." },
        effortSize: { type: "string", enum: ["small", "medium", "large", "xlarge"], description: "Required when triageOutcome=build (skipping triage). Otherwise applied if provided." },
        body: { type: "string", description: "Detailed description" },
        epicId: { type: "string", description: "Epic ID to link to (optional)" },
        itemId: { type: "string", description: "Optional custom item ID (e.g. BI-PORT-005). Auto-generated if omitted." },
      },
      required: ["title", "type", "workType"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "triage_backlog_item",
    description: "Decide the outcome for a backlog item currently in status=triaging. Moves the item out of triage with a decided triageOutcome and supporting fields. Authority-gated via the backlog_triage grant category.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "The item ID (e.g. BI-E4A86393)" },
        outcome: { type: "string", enum: ["build", "runbook", "coworker-task", "defer", "duplicate", "discard"], description: "The triage decision" },
        rationale: { type: "string", description: "Short prose rationale for the decision" },
        effortSize: { type: "string", enum: ["small", "medium", "large", "xlarge"], description: "Required when outcome=build" },
        duplicateOfId: { type: "string", description: "Canonical item ID; required when outcome=duplicate" },
        reason: { type: "string", description: "Reason text; required when outcome=defer or outcome=discard" },
      },
      required: ["itemId", "outcome", "rationale"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "retire_backlog_item",
    description: "Retire a backlog item as duplicate, deferred, or discarded after review. Use this for governed cleanup of non-buildable or superseded items without requiring backlog triage authority.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "The item ID to retire (e.g. BI-E4A86393)" },
        outcome: { type: "string", enum: ["duplicate", "defer", "discard"], description: "The retirement decision" },
        rationale: { type: "string", description: "Short prose rationale for retiring the item" },
        duplicateOfId: { type: "string", description: "Canonical item ID; required when outcome=duplicate" },
        reason: { type: "string", description: "Optional reason text for defer/discard outcomes" },
      },
      required: ["itemId", "outcome", "rationale"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "promote_to_build_studio",
    description: "Promote a triaged backlog item (status=open, triageOutcome=build) to a FeatureBuild in Build Studio. Runs the Definition of Ready capacity check under an advisory-lock transaction. Authority-gated via the build_promote grant category.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "The item ID to promote" },
      },
      required: ["itemId"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "size_backlog_item",
    description: "Assign effortSize to a backlog item. Useful when sizing is a follow-up step rather than part of a single triage commit.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "The item ID to size" },
        size: { type: "string", enum: ["small", "medium", "large", "xlarge"], description: "T-shirt size estimate" },
      },
      required: ["itemId", "size"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "process_backlog_for_build_studio",
    description: "Queue an on-demand governed backlog sweep that prepares eligible Build Studio drafts without auto-starting execution.",
    inputSchema: {
      type: "object",
      properties: {
        limit: { type: "number", description: "Optional cap for this on-demand sweep. Still constrained by the platform daily cap." },
      },
      required: [],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    // NOTE: this tool is DISTINCT from the existing `propose_decomposition`
    // (epic + feature-set breakdown for ideation). This one operates on a
    // passed FeatureBuild design and proposes how to SPLIT it into smaller
    // builds — a downstream-of-Ideate decomposition, not an upstream-from-
    // backlog one. Different name avoids the collision.
    name: "propose_build_decomposition",
    description: "Phase 4b/7 (BI-2E6CC391). Ask the SE coworker to propose 2-4 candidate decompositions of a passed-design xlarge FeatureBuild. Distinct from `propose_decomposition` (which is an upstream brainstorming tool that generates an Epic + feature-set breakdown). This one is downstream of Ideate — eligible when the build is in `ideate`, has a passed designReview, and the recorded sizeAssessment.decision is `decompose-recommended` or `decompose-required`; Phase 7 also allows a top-level `plan` build whose failed planReview has iteration.oscillating=true, recomputing sizeDesignDoc retroactively when sizeAssessment is missing. Optional `operatorHint` re-runs with guidance ('make the read-first smaller', 'ship the ledger separately'). Persists validated candidates to designReview.decompositionCandidates.latest; prior rounds are preserved under .priorRounds for audit. Returns the validated candidates plus an observability list of rejected ones (model returned them but they failed validateCandidate).",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Originating FeatureBuild ID (FB-*)." },
        operatorHint: {
          type: "string",
          description: "Optional regenerate guidance. Empty/omitted on first generation.",
        },
      },
      required: ["buildId"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "approve_decomposition",
    description: "Phase 4a/7 (BI-2E6CC391). Atomically create an execution-organizational Epic + N child FeatureBuilds + sibling-dependency edges from a pre-validated DecompositionCandidate, and mark the originating FeatureBuild as superseded. The originating build must be in `ideate` phase with a passed designReview, or in `plan` with a failed oscillating planReview as the Phase 7 retroactive escape hatch, and must not itself be a child. Callers (typically the Phase 4b assistant flow) supply the candidate after the operator has chosen and optionally edited it; all invariants from epic-decomposition-invariants run before any DB writes.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Originating FeatureBuild ID (FB-*)." },
        candidate: {
          type: "object",
          description: "The DecompositionCandidate to materialize. See apps/web/lib/build/decomposition-candidates.ts for the full shape.",
          properties: {
            candidateId: { type: "string" },
            rationale: { type: "string" },
            childScopes: {
              type: "array",
              items: {
                type: "object",
                properties: {
                  childOrder: { type: "number" },
                  title: { type: "string" },
                  summary: { type: "string" },
                  acceptanceCriteriaIndices: { type: "array", items: { type: "number" } },
                  dependsOn: { type: "array", items: { type: "number" } },
                },
                required: ["childOrder", "title", "acceptanceCriteriaIndices", "dependsOn"],
              },
            },
          },
          required: ["candidateId", "rationale", "childScopes"],
        },
      },
      required: ["buildId", "candidate"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "record_decomposition_override",
    description: "Phase 4a (BI-2E6CC391). Record the operator's 'keep as one build' override on a FeatureBuild whose size assessment is decompose-required. Writes designReview.decompositionOverride for audit and hive-contribution context. Only valid on decompose-required builds; recommended-tier builds proceed without recording an override (single-click path per spec §4.1). Does not enforce — Phase 4b's gate is the consumer of this record.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "FeatureBuild ID (FB-*)." },
        rationale: { type: "string", description: "Non-empty one-line justification for proceeding monolithically." },
      },
      required: ["buildId", "rationale"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "update_backlog_item",
    description: "Update an existing backlog item's editable fields. Use update_backlog_item_status for triaged status transitions, triage_backlog_item to triage, size_backlog_item for effortSize, and link_backlog_item_to_epic for epic linkage — those have their own audit and validation.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "The item ID (e.g., BI-PORT-001)" },
        title: { type: "string", description: "New title" },
        status: { type: "string", enum: [...BACKLOG_STATUS_VALUES], description: "Update status. For triaged items only; use triage_backlog_item to leave triaging." },
        priority: { type: "number", description: "Priority number (lower = higher priority)" },
        body: { type: "string", description: "Updated description" },
        workType: { type: "string", enum: [...BACKLOG_WORK_TYPE_VALUES], description: "Reclassify what kind of work this is (closed enum)." },
        source: { type: "string", enum: [...BACKLOG_SOURCE_VALUES], description: "Reclassify the intake origin." },
        proposedOutcome: { type: "string", enum: ["build", "runbook", "coworker-task", "defer", "duplicate", "discard"], description: "Advisory recommendation; non-binding on triage" },
      },
      required: ["itemId"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "create_digital_product",
    description: "Register a new digital product in the inventory",
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", description: "Product name" },
        productId: { type: "string", description: "Unique product identifier" },
        lifecycleStage: { type: "string", enum: ["plan", "design", "build", "production", "retirement"] },
        portfolioSlug: { type: "string", description: "Portfolio slug to assign to" },
      },
      required: ["name", "productId"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "update_lifecycle",
    description: "Update a digital product's lifecycle stage and status",
    inputSchema: {
      type: "object",
      properties: {
        productId: { type: "string", description: "Product identifier" },
        lifecycleStage: { type: "string", enum: ["plan", "design", "build", "production", "retirement"] },
        lifecycleStatus: { type: "string", enum: ["draft", "active", "inactive"] },
      },
      required: ["productId"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "query_backlog",
    description: "Query backlog items and epics. Returns items matching the filter criteria with status, priority, and epic information.",
    inputSchema: {
      type: "object",
      properties: {
        status: { type: "string", enum: [...BACKLOG_STATUS_VALUES], description: "Filter by status (optional)" },
        epicId: { type: "string", description: "Filter by epic ID (optional)" },
        limit: { type: "number", description: "Max results (default 20)" },
      },
      required: [],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate"],
  },
  // ─── Work Capsule control harness (spec 2026-05-14) ────────────────────────
  {
    name: "list_work_capsules",
    description: "List Work Capsule coordination records for active portal, Build Studio, and external agent work. Read-only.",
    inputSchema: {
      type: "object",
      properties: {
        status: { type: "string", enum: WORK_CAPSULE_TOOL_ENUMS.statuses, description: "Filter by Work Capsule status." },
        limit: { type: "number", description: "Max results (default 50, max 100)." },
      },
      required: [],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "get_work_capsule",
    description: "Fetch one Work Capsule with its recent activity timeline. Read-only.",
    inputSchema: {
      type: "object",
      properties: {
        capsuleId: { type: "string", description: "Semantic Work Capsule id (WC-*)." },
      },
      required: ["capsuleId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "create_work_capsule",
    description: "Create a Work Capsule coordination record for planned work. Idempotency key is required so retries do not duplicate capsule activity.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Short capsule title." },
        objective: { type: "string", description: "Outcome this capsule coordinates." },
        source: { type: "string", enum: WORK_CAPSULE_TOOL_ENUMS.sources, description: "Origin of the capsule." },
        idempotencyKey: { type: "string", description: "Stable caller-provided key used to make create retries idempotent." },
        executorKind: { type: "string", enum: WORK_CAPSULE_TOOL_ENUMS.executors, description: "Optional executor expected to work the capsule." },
      },
      required: ["title", "objective", "source", "idempotencyKey"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "plan_capsule_worktree",
    description: "Generate and persist the deterministic branch and worktree-path plan for a Work Capsule. Idempotent: re-planning returns the existing plan and refuses to propose the root clone.",
    inputSchema: {
      type: "object",
      properties: {
        capsuleId: { type: "string", description: "Semantic Work Capsule id (WC-*)." },
        taxonomy: { type: "string", enum: WORK_CAPSULE_TOOL_ENUMS.taxonomies, description: "AGENTS.md branch prefix." },
      },
      required: ["capsuleId", "taxonomy"],
    },
    requiredCapability: "manage_backlog",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "adopt_worktree",
    description: "Adopt an existing local branch/worktree pair into a Work Capsule without creating a new worktree.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Short capsule title." },
        objective: { type: "string", description: "Outcome this adopted work should reach." },
        repositoryFullName: { type: "string", description: "GitHub repository full name, for example OpenDigitalProductFactory/opendigitalproductfactory." },
        headBranch: { type: "string", description: "Existing branch to adopt." },
        worktreePath: { type: "string", description: "Local worktree path for the branch." },
        baseBranch: { type: "string", description: "Optional base branch (defaults to main)." },
        baseSha: { type: "string", description: "Optional current base SHA." },
        headSha: { type: "string", description: "Optional current head SHA." },
        executorKind: { type: "string", enum: WORK_CAPSULE_TOOL_ENUMS.executors, description: "Optional executor adopting the worktree." },
      },
      required: ["title", "objective", "repositoryFullName", "headBranch", "worktreePath"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "claim_capsule_scope",
    description: "Claim path/module/package/route/skill/prompt scope for a Work Capsule. Repeated claims refresh the existing scope entry.",
    inputSchema: {
      type: "object",
      properties: {
        capsuleId: { type: "string", description: "Semantic Work Capsule id (WC-*)." },
        claims: {
          type: "array",
          items: {
            type: "object",
            properties: {
              kind: { type: "string", enum: ["path", "module", "package", "route", "skill", "prompt"] },
              value: { type: "string", description: "Claimed scope value." },
              intent: { type: "string", enum: ["edit", "read"] },
            },
            required: ["kind", "value", "intent"],
          },
          description: "Scope claims to add or refresh.",
        },
      },
      required: ["capsuleId", "claims"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "heartbeat_capsule",
    description: "Renew the active lease for a Work Capsule so other agents can see that work is in flight.",
    inputSchema: {
      type: "object",
      properties: {
        capsuleId: { type: "string", description: "Semantic Work Capsule id (WC-*)." },
      },
      required: ["capsuleId"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "update_work_capsule_status",
    description: "Set a Work Capsule status and record a temporary operator-visible status override reason.",
    inputSchema: {
      type: "object",
      properties: {
        capsuleId: { type: "string", description: "Semantic Work Capsule id (WC-*)." },
        status: { type: "string", enum: WORK_CAPSULE_TOOL_ENUMS.statuses, description: "Next Work Capsule status." },
        reason: { type: "string", description: "Reason for the status update or override." },
      },
      required: ["capsuleId", "status", "reason"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "release_capsule_scope",
    description: "Release previously claimed Work Capsule scope items by kind and value.",
    inputSchema: {
      type: "object",
      properties: {
        capsuleId: { type: "string", description: "Semantic Work Capsule id (WC-*)." },
        claims: {
          type: "array",
          items: {
            type: "object",
            properties: {
              kind: { type: "string", enum: ["path", "module", "package", "route", "skill", "prompt"] },
              value: { type: "string", description: "Scope value to release." },
            },
            required: ["kind", "value"],
          },
          description: "Scope claims to release.",
        },
      },
      required: ["capsuleId", "claims"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "record_capsule_evidence",
    description: "Append an evidence entry to a Work Capsule activity timeline.",
    inputSchema: {
      type: "object",
      properties: {
        capsuleId: { type: "string", description: "Semantic Work Capsule id (WC-*)." },
        kind: { type: "string", enum: WORK_CAPSULE_TOOL_ENUMS.evidenceKinds, description: "Evidence kind." },
        summary: { type: "string", description: "Evidence summary." },
        command: { type: "string", description: "Optional command that produced the evidence." },
        url: { type: "string", description: "Optional URL for PRs, CI runs, screenshots, or external evidence." },
        targetId: { type: "string", description: "Optional stable RuntimeTarget id (RT-*)." },
        runtimeTargetId: { type: "string", description: "Optional RuntimeTarget row id." },
        verificationId: { type: "string", description: "Optional RuntimeVerification id (RV-*)." },
        result: { type: "object", description: "Optional structured result payload." },
      },
      required: ["capsuleId", "summary"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  // ─── Runtime coordination (plan 2026-05-18) ────────────────────────────────
  {
    name: "register_runtime_target",
    description: "Register or refresh the governed runtime surface where work is being verified. Source ownership is read through Work Capsule, FeatureBuild, Sandbox, or GitPromotionCandidate links.",
    inputSchema: {
      type: "object",
      properties: {
        targetId: { type: "string", description: "Stable runtime target id, e.g. RT-ROOT-PORTAL or RT-SANDBOX-<sandboxRowId>." },
        kind: { type: "string", enum: RUNTIME_COORDINATION_TOOL_ENUMS.targetKinds, description: "Runtime target kind." },
        status: { type: "string", enum: RUNTIME_COORDINATION_TOOL_ENUMS.targetStatuses, description: "Runtime target status." },
        capsuleId: { type: "string", description: "Optional semantic Work Capsule id (WC-*)." },
        workCapsuleId: { type: "string", description: "Optional internal WorkCapsule row id; prefer capsuleId for external agents." },
        buildId: { type: "string", description: "Optional semantic FeatureBuild id (FB-*)." },
        featureBuildId: { type: "string", description: "Optional internal FeatureBuild row id; prefer buildId for external agents." },
        sandboxId: { type: "string", description: "Optional Sandbox row id." },
        slotId: { type: "string", description: "Optional SandboxSlot row id." },
        composeProjectName: { type: "string", description: "Compose project name when applicable." },
        serviceName: { type: "string", description: "Compose service name when applicable." },
        containerName: { type: "string", description: "Docker container name when applicable." },
        hostUrl: { type: "string", description: "Host-reachable URL." },
        internalUrl: { type: "string", description: "Container/internal URL." },
        port: { type: "number", description: "Host port when applicable." },
        serviceVersion: { type: "string", description: "OpenTelemetry service.version style artifact string, e.g. image + git SHA." },
        acceptanceRoleOverride: { type: "string", enum: ["debug-only"], description: "Rare override. Most roles are derived from kind." },
        debugReason: { type: "string", description: "Required for ad-hoc-debug targets." },
        expiresAt: { type: "string", description: "Optional ISO timestamp for temporary targets." },
        metadata: { type: "object", description: "Free-form snapshot data; never the source of truth." },
      },
      required: ["targetId", "kind", "status"],
    },
    requiredCapability: "manage_backlog",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "heartbeat_runtime_target",
    description: "Refresh the heartbeat for a governed runtime target.",
    inputSchema: {
      type: "object",
      properties: {
        targetId: { type: "string", description: "Stable runtime target id." },
      },
      required: ["targetId"],
    },
    requiredCapability: "manage_backlog",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "release_runtime_target",
    description: "Mark a governed runtime target released so another thread can safely use the surface.",
    inputSchema: {
      type: "object",
      properties: {
        targetId: { type: "string", description: "Stable runtime target id." },
      },
      required: ["targetId"],
    },
    requiredCapability: "manage_backlog",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "record_runtime_verification",
    description: "Record a typed verification event against exactly one primary runtime/build/promotion attach point, with optional evidence links.",
    inputSchema: {
      type: "object",
      properties: {
        verificationId: { type: "string", description: "Stable verification id." },
        kind: { type: "string", enum: RUNTIME_COORDINATION_TOOL_ENUMS.verificationKinds, description: "Verification kind." },
        status: { type: "string", enum: RUNTIME_COORDINATION_TOOL_ENUMS.verificationStatuses, description: "Verification status." },
        targetId: { type: "string", description: "Optional stable RuntimeTarget id (RT-*)." },
        runtimeTargetId: { type: "string", description: "Optional RuntimeTarget row id; RT-* targetId is also accepted for compatibility." },
        capsuleId: { type: "string", description: "Optional semantic Work Capsule id (WC-*) for capsule-only or mirrored evidence." },
        workCapsuleId: { type: "string", description: "Optional WorkCapsule row id; prefer capsuleId for external agents." },
        buildId: { type: "string", description: "Optional semantic FeatureBuild id (FB-*)." },
        featureBuildId: { type: "string", description: "Optional FeatureBuild row id; prefer buildId for external agents." },
        candidateId: { type: "string", description: "Optional semantic GitPromotionCandidate id." },
        gitPromotionCandidateId: { type: "string", description: "Optional GitPromotionCandidate row id; prefer candidateId for external agents." },
        command: { type: "string", description: "Command that produced the verification." },
        url: { type: "string", description: "URL checked." },
        evidenceUrl: { type: "string", description: "Evidence URL." },
        screenshotUrl: { type: "string", description: "Screenshot URL." },
        toolExecutionId: { type: "string", description: "ToolExecution row id." },
        buildActivityId: { type: "string", description: "BuildActivity row id." },
        backlogActivityId: { type: "string", description: "BacklogItemActivity row id." },
        capsuleActivityId: { type: "string", description: "WorkCapsuleActivity row id." },
        startedAt: { type: "string", description: "Optional ISO start timestamp." },
        completedAt: { type: "string", description: "Optional ISO completion timestamp." },
        result: { type: "object", description: "Structured result payload." },
      },
      required: ["verificationId", "kind", "status"],
    },
    requiredCapability: "manage_backlog",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "get_runtime_coordination_map",
    description: "List governed runtime targets with recent verification events so operators can see what is deployed where and what is safe to merge.",
    inputSchema: {
      type: "object",
      properties: {
        kind: { type: "string", enum: RUNTIME_COORDINATION_TOOL_ENUMS.targetKinds, description: "Filter by target kind." },
        status: { type: "string", enum: RUNTIME_COORDINATION_TOOL_ENUMS.targetStatuses, description: "Filter by target status." },
        limit: { type: "number", description: "Max targets (default 50, max 100)." },
      },
      required: [],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  // ─── Governed MCP backlog surface (spec 2026-04-25) ─────────────────────────
  {
    name: "create_epic",
    description: "Create a generic backlog epic through the governed MCP surface. Use this for roadmap/recovery/planning epics that are not tied to a live Build Studio build. Rejects duplicate semantic epic IDs; optional source/spec/plan/rationale context is captured by ToolExecution and indexed for discovery.",
    inputSchema: {
      type: "object",
      properties: {
        epicId: { type: "string", description: "Optional semantic epic id (e.g. EP-WWMD). Auto-generated if omitted." },
        title: { type: "string", description: "Epic title" },
        description: { type: "string", description: "Epic description" },
        status: { type: "string", enum: [...EPIC_STATUSES], description: "Initial epic status (defaults to open)" },
        priority: { type: "integer", description: "Optional ranked priority for the epic (lower = higher priority)" },
        owner: { type: "string", description: "Optional accountable employee identifier: EmployeeProfile id, employeeId, workEmail, personalEmail, or exact displayName" },
        source: { type: "string", enum: [...BACKLOG_SOURCE_VALUES], description: "What kind of gap or signal produced this epic" },
        specPath: { type: "string", description: "Optional related spec path for audit/index context" },
        planPath: { type: "string", description: "Optional related implementation plan path for audit/index context" },
        rationale: { type: "string", description: "Optional short rationale for creating the epic" },
      },
      required: ["title"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "update_epic",
    description: "Update a generic backlog epic's editable fields through the governed MCP surface. Use this for title, description, and status changes; status=done stamps completedAt, and reopening clears it.",
    inputSchema: {
      type: "object",
      properties: {
        epicId: { type: "string", description: "Semantic epic id (EP-*) or internal epic row id" },
        title: { type: "string", description: "New epic title" },
        description: { type: "string", description: "New epic description" },
        status: { type: "string", enum: [...EPIC_STATUSES], description: "New epic status" },
        priority: { type: "integer", description: "New ranked priority for the epic (lower = higher priority)" },
        specPath: { type: "string", description: "Optional related spec path for audit/index context" },
        planPath: { type: "string", description: "Optional related implementation plan path for audit/index context" },
        rationale: { type: "string", description: "Optional short rationale captured by ToolExecution" },
      },
      required: ["epicId"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "list_epics",
    description: "List epics with item-count rollups. Read-only. Filterable by status and whether the epic has open items. Returned epicId is the semantic id (EP-*), not the internal cuid.",
    inputSchema: {
      type: "object",
      properties: {
        status: { type: "string", enum: ["open", "in-progress", "done"], description: "Filter by epic status" },
        hasOpenItems: { type: "boolean", description: "Only return epics that have at least one non-done item" },
        limit: { type: "number", description: "Max results (default 25, max 100)" },
      },
      required: [],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "list_backlog_items",
    description: "List backlog items filtered by status, type, workType, source, epic, claim state, or active-build state. Read-only. Returns semantic IDs (BI-*, EP-*) — never cuids.",
    inputSchema: {
      type: "object",
      properties: {
        status: { type: "string", enum: ["triaging", "open", "in-progress", "done", "deferred"] },
        type: { type: "string", enum: ["portfolio", "product"] },
        workType: { type: "string", enum: [...BACKLOG_WORK_TYPE_VALUES], description: "Filter by work-type (bug | feature | chore | doc | tool | skill | refactor)." },
        source: { type: "string", enum: [...BACKLOG_SOURCE_VALUES], description: "Filter by intake origin (user-request | automated-detection)." },
        epicId: { type: "string", description: "Semantic epic id (EP-*) to filter to" },
        unclaimed: { type: "boolean", description: "Only items with no user/agent claim" },
        hasActiveBuild: { type: "boolean", description: "Only items currently linked to a Build Studio build" },
        limit: { type: "number", description: "Max results (default 25, max 100)" },
      },
      required: [],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "get_backlog_item",
    description: "Fetch one backlog item by semantic id with linked epic, digital product, active build, and the most recent activity entries. Read-only.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "Semantic backlog item id (e.g. BI-PORT-005)" },
      },
      required: ["itemId"],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "update_backlog_item_status",
    description: "Move a backlog item between lifecycle statuses. Enforces the legal-transition table in apps/web/lib/backlog/transitions.ts; same-status calls are no-op successes. Setting status='triaging' on an already-triaged item is the *retriage* path — clears triageOutcome and effortSize so triage_backlog_item can re-decide. Setting status='done' requires a resolution. Writes a status_change activity row and may auto-close the parent epic. NOTE: this only changes the status field — it does NOT start work. For a triageOutcome=build item, starting the work means promote_to_build_studio (creates the FeatureBuild + Build Studio Ideate); flipping such an item to in-progress returns an advisory and does not build anything.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "Semantic backlog item id" },
        status: { type: "string", enum: ["triaging", "open", "in-progress", "done", "deferred"], description: "Target status. 'triaging' from a triaged status is allowed and clears the prior triage decision." },
        reason: { type: "string", description: "Free-text rationale captured in the activity row. Required when status=triaging from a triaged status." },
        resolution: { type: "string", description: "Outcome summary, required when status=done" },
      },
      required: ["itemId", "status"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "link_backlog_item_to_epic",
    description: "Link a backlog item to an epic (or unlink with epicId=null). Recomputes target epic status — if a done epic gains a new open item, it flips back to open. Writes an epic_link activity row. NOTE: linking is organizational only — it does NOT triage the item (use triage_backlog_item) and does NOT promote it or create a build (use promote_to_build_studio). Linking an untriaged/unpromoted item returns an advisory; never report an epic link as 'triaged' or 'promoted'.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "Semantic backlog item id" },
        epicId: { type: "string", description: "Semantic epic id (EP-*), or empty string / 'null' to unlink" },
      },
      required: ["itemId"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "search_specs_and_plans",
    description: "Search design specs (docs/superpowers/specs) and implementation plans (docs/superpowers/plans) by title and body, with optional itemId/epicId narrowing. Returns paths, titles, dates, snippets, and the BI-/EP- references found in each match. Read-only.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Free-text query (case-insensitive substring match on title and body)" },
        kind: { type: "string", enum: ["spec", "plan"], description: "Restrict to one tree" },
        matches: { type: "number", description: "Max results (default 10, max 25)" },
        itemId: { type: "string", description: "Also include files that mention this BI- id" },
        epicId: { type: "string", description: "Also include files that mention this EP- id" },
      },
      required: ["query"],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "record_execution_evidence",
    description: "Attach an evidence record to a backlog item (test pass/fail, build pass/fail, ux verification, spec review, manual check, external link). Writes an evidence activity row; the cross-cutting audit lives in ToolExecution. Side-effecting.",
    inputSchema: {
      type: "object",
      properties: {
        itemId: { type: "string", description: "Semantic backlog item id" },
        kind: {
          type: "string",
          enum: [
            "test_pass",
            "test_fail",
            "build_pass",
            "build_fail",
            "ux_verified",
            "spec_review",
            "manual_check",
            "external_link",
          ],
          description: "Evidence kind",
        },
        summary: { type: "string", description: "Headline for the timeline (<= 240 chars)" },
        url: { type: "string", description: "Link to PR / CI run / screenshot" },
        body: { type: "string", description: "Longer notes (<= 8000 chars)" },
        toolExecutionId: { type: "string", description: "Audit row id when this evidence was produced by a prior tool call" },
      },
      required: ["itemId", "kind", "summary"],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "record_external_development_evidence",
    description: "Record Claude/Codex or other external development handoff evidence with optional Build Studio build/task links. Use for branches, commits, changed files, verification results, local integration output, unresolved questions, and skills used.",
    inputSchema: {
      type: "object",
      properties: {
        provider: { type: "string", description: "External development provider, for example codex or claude" },
        externalSessionId: { type: "string", description: "External thread/session/capsule identifier" },
        buildId: { type: "string", description: "Optional FB-* build id" },
        taskRunId: { type: "string", description: "Optional TaskRun id" },
        routeContext: { type: "string", description: "Route context where the work belongs, usually /build" },
        summary: { type: "string", description: "Operator-readable handoff summary" },
        commits: { type: "array", items: { type: "string" }, description: "Commit SHAs or refs included in the handoff" },
        changedFiles: { type: "array", items: { type: "string" }, description: "Files touched by the external work" },
        verification: { type: "array", items: { type: "string" }, description: "Verification commands and outcomes" },
        localIntegration: { type: "object", description: "Local merged-code integration result summary" },
        unresolvedQuestions: { type: "array", items: { type: "string" }, description: "Open questions that still need decision or founder review" },
        skillIds: { type: "array", items: { type: "string" }, description: "DPF skill ids used by the external contributor" },
      },
      required: ["provider", "externalSessionId", "summary", "routeContext"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
  },
  {
    name: "list_nonprod_environment_leases",
    description: "List active, unexpired nonproduction environment leases so agents can reuse governed shared localhost environments instead of starting unmanaged servers.",
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  {
    name: "claim_nonprod_environment_lease",
    description: "Claim a governed shared nonproduction environment lease for preview, UX verification, or local integration. Returns a conflict instead of allowing another unmanaged server.",
    inputSchema: {
      type: "object",
      properties: {
        environmentKey: { type: "string", enum: ["active-candidate", "local-integration-ci"] },
        ownerProvider: { type: "string", enum: ["build-studio", "claude", "codex", "coworker"] },
        ownerSessionId: { type: "string" },
        purpose: { type: "string" },
        url: { type: "string" },
        ports: { type: "array", items: { type: "number" } },
        expiresAt: { type: "string", description: "ISO timestamp when the lease expires" },
        worktreePath: { type: "string" },
        branchName: { type: "string" },
        buildId: { type: "string" },
        taskRunId: { type: "string" },
        cleanupCommand: { type: "string" },
      },
      required: ["environmentKey", "ownerProvider", "ownerSessionId", "purpose", "url", "ports", "expiresAt"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
  },
  {
    name: "release_nonprod_environment_lease",
    description: "Release a governed shared nonproduction environment lease after verification is complete or blocked.",
    inputSchema: {
      type: "object",
      properties: {
        leaseId: { type: "string" },
      },
      required: ["leaseId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
  },
  {
    name: "record_local_integration_result",
    description: "Record the result of a local merged-code integration gate before push or PR. Captures candidate branch, mode, pass/fail/conflict status, and evidence.",
    inputSchema: {
      type: "object",
      properties: {
        provider: { type: "string", enum: ["build-studio", "claude", "codex", "coworker"] },
        externalSessionId: { type: "string" },
        routeContext: { type: "string" },
        buildId: { type: "string" },
        taskRunId: { type: "string" },
        candidateBranch: { type: "string" },
        mode: { type: "string", enum: ["single-branch", "sibling-set", "post-merge-main"] },
        status: { type: "string", enum: ["passed", "failed", "conflict"] },
        summary: { type: "string" },
        evidence: { type: "object" },
      },
      required: ["provider", "externalSessionId", "routeContext", "candidateBranch", "mode", "status", "summary", "evidence"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
  },
  {
    name: "record_functional_failure_evidence",
    description: "Create or update a deduped backlog item from Playwright FunctionalFailureEvidence. Uses a deterministic testId+route+actual fingerprint and records an evidence activity; the cross-cutting audit lives in ToolExecution. Side-effecting.",
    inputSchema: {
      type: "object",
      properties: {
        testId: { type: "string", description: "Stable automated test id" },
        suite: { type: "string", description: "Playwright suite or project" },
        route: { type: "string", description: "Application route under test" },
        expected: { type: "string", description: "Expected behavior" },
        actual: { type: "string", description: "Observed failure" },
        screenshotPath: { type: "string", description: "Local screenshot path when available" },
        tracePath: { type: "string", description: "Local trace path when available" },
        userRole: { type: "string", description: "User role used by the test" },
        agentId: { type: "string", description: "Expected or active coworker id" },
        routeContext: { type: "string", description: "Route context used by the test" },
        reproCommand: { type: "string", description: "Command to reproduce the failure" },
        createdAt: { type: "string", description: "Evidence timestamp" },
        likelyOwnerArea: { type: "string", description: "Likely owning product area" },
        buildId: { type: "string", description: "Optional Build Studio id" },
        backlogItemId: { type: "string", description: "Optional explicit backlog item to attach to" },
      },
      required: [
        "testId",
        "suite",
        "route",
        "expected",
        "actual",
        "userRole",
        "routeContext",
        "reproCommand",
        "createdAt",
        "likelyOwnerArea",
      ],
    },
    requiredCapability: "manage_backlog",
    sideEffect: true,
  },
  {
    name: "get_next_recommended_work",
    description: "Return a short ranked list of backlog items the caller could pick up next. Ranks by spec/plan presence, triage outcome, effort size, priority, and active-build state. Read-only.",
    inputSchema: {
      type: "object",
      properties: {
        count: { type: "number", description: "How many recommendations to return (default 3, max 10)" },
        epicId: { type: "string", description: "Restrict to one epic" },
        forAgentId: { type: "string", description: "Only items grant-claimable by this agent" },
        excludeItemIds: { type: "array", items: { type: "string" }, description: "Items to skip (already considered or rejected)" },
      },
      required: [],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "report_quality_issue",
    description: "Report a bug, suggestion, or question about the platform. Available to ALL employees regardless of role — anyone can report a problem.",
    inputSchema: {
      type: "object",
      properties: {
        type: { type: "string", enum: ["runtime_error", "user_report", "feedback"], description: "Issue type" },
        title: { type: "string", description: "Short summary" },
        description: { type: "string", description: "Detailed description" },
        severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
      },
      required: ["type", "title"],
    },
    requiredCapability: null,
    sideEffect: true,
  },
  {
    name: "get_finance_period_summary",
    description: "Return verified income, expenses, and net for a finance period (defaults to month-to-date). Income = sum of paid invoices; expenses = sum of paid bills + paid expense claims; net = income - expenses. Includes pending receivables/payables, multi-currency flags, source paths, and explicit gap descriptions when activity is missing. Use this whenever the user asks for a P&L figure, income vs expenses, or net cash position for a period - it is the canonical numeric answer for the Finance Specialist coworker.",
    inputSchema: {
      type: "object",
      properties: {
        period: {
          type: "string",
          enum: ["month-to-date", "last-month", "quarter-to-date", "year-to-date"],
          description: "Preset period. Defaults to month-to-date. Ignored when startDate/endDate are provided.",
        },
        startDate: {
          type: "string",
          description: "ISO date (e.g. 2026-05-01). When set, period is treated as a custom window. endDate is required alongside.",
        },
        endDate: {
          type: "string",
          description: "ISO date for the end of the custom window. Must be on or after startDate.",
        },
      },
      required: [],
    },
    requiredCapability: "view_finance",
    executionMode: "immediate",
    sideEffect: false,
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
  },
  {
    name: "get_marketing_summary",
    description: "Get archetype-aware marketing metrics: storefront inbox counts, CRM pipeline summary, and the marketing playbook for this business type",
    inputSchema: {
      type: "object",
      properties: {
        days: { type: "number", description: "Number of days to look back (default 30)" },
      },
      required: [],
    },
    requiredCapability: "view_marketing",
    sideEffect: false,
  },
  {
    name: "suggest_campaign_ideas",
    description: "Get structured context for generating archetype-specific marketing campaign ideas, including business type, season, playbook, and top storefront items",
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
    requiredCapability: "view_marketing",
    sideEffect: false,
  },
  {
    name: "save_marketing_review",
    description: "Persist the Marketing Strategist's recommendation as the current marketing review and update the canonical MarketingStrategy. Use this after giving a concrete strategy, cadence, channel, KPI, or campaign recommendation so the page shows what was decided and what changed.",
    inputSchema: {
      type: "object",
      properties: {
        summary: { type: "string", description: "Plain-language recommendation summary shown on the marketing page" },
        primaryChannels: { type: "array", items: { type: "string", enum: [...MARKETING_CHANNELS] }, description: "Recommended primary channels" },
        secondaryChannels: { type: "array", items: { type: "string", enum: [...MARKETING_CHANNELS] }, description: "Optional secondary channels" },
        skippedChannels: { type: "array", items: { type: "string", enum: [...MARKETING_CHANNELS] }, description: "Channels intentionally skipped for now" },
        kpis: { type: "array", items: { type: "string" }, description: "Small KPI stack used to judge channel fit" },
        cadence: { type: "string", enum: [...MARKETING_REVIEW_CADENCE], description: "Review cadence for this plan" },
        suggestedActions: { type: "array", items: { type: "string" }, description: "Action checklist items to display under the strategist review" },
      },
      required: ["summary"],
    },
    requiredCapability: "operate_marketing",
    sideEffect: true,
    coworkerArtifact: true,
  },
  {
    name: "create_marketing_campaign_brief",
    description: "Create a durable campaign brief from the Marketing Strategist's active plan. Use this after a user confirms a campaign direction or says ok/continue after a concrete campaign recommendation.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Short campaign name" },
        objective: { type: "string", description: "Specific marketing objective" },
        audience: { type: "string", description: "Target audience or buyer segment" },
        channels: { type: "array", items: { type: "string", enum: [...MARKETING_CHANNELS] }, description: "Channels used in the campaign" },
        cta: { type: "string", description: "Primary call to action" },
        proofAssets: { type: "array", items: { type: "string" }, description: "Proof assets needed to support the campaign" },
        kpis: { type: "array", items: { type: "string" }, description: "Metrics used to judge the campaign" },
        notes: { type: "string", description: "Execution notes or exclusions" },
      },
      required: ["title", "objective"],
    },
    requiredCapability: "operate_marketing",
    sideEffect: true,
    coworkerArtifact: true,
  },
  {
    name: "create_marketing_asset_task",
    description: "Create a durable proof/content asset task from the Marketing Strategist's plan, such as an FAQ, testimonial, case study, landing page, LinkedIn post, or email draft.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Task title" },
        assetType: { type: "string", description: "Asset type, for example FAQ, testimonial, case-study, LinkedIn post, SEO page, email" },
        channel: { type: "string", enum: [...MARKETING_CHANNELS], description: "Primary channel for this asset" },
        dueWindow: { type: "string", description: "Plain-language timing, for example week 1 or next 14 days" },
        brief: { type: "string", description: "Short instructions for the asset" },
      },
      required: ["title", "assetType"],
    },
    requiredCapability: "operate_marketing",
    sideEffect: true,
    coworkerArtifact: true,
  },
  {
    name: "publish_to_linkedin",
    description: "Publish an APPROVED marketing OutboundDraft (channelId=linkedin or linkedin-personal-social) to the operator's connected LinkedIn personal feed. Requires the LinkedIn integration to be connected via /platform/tools/integrations/linkedin-personal-social. Fails fast if the draft is not status=approved or no LinkedIn credential is connected. On success, writes an OutboundPublication row and flips the draft to status=published.",
    inputSchema: {
      type: "object",
      properties: {
        draftId: { type: "string", description: "OutboundDraft.draftId in status='approved' on a LinkedIn channel" },
      },
      required: ["draftId"],
    },
    requiredCapability: "operate_marketing",
    sideEffect: true,
  },
  {
    name: "draft_marketing_asset",
    description: "Turn a saved MarketingAssetTask brief into a channel-shaped, human-reviewable draft. Creates an OutboundDraft with status='pending-review' that appears in the marketing approval queue on /customer/marketing. Phase 1: LinkedIn posts and emails only. No external API call — the draft is internal until a human approves and a publish tool fires.",
    inputSchema: {
      type: "object",
      properties: {
        assetTaskId: { type: "string", description: "MarketingAssetTask.taskId to draft from" },
        channelOverride: { type: "string", enum: [...MARKETING_CHANNELS], description: "Override the asset task's channel if drafting a variant" },
        toneNotes: { type: "string", description: "Optional one-line guidance the drafter should respect (e.g. 'first person, technical, no emojis')" },
      },
      required: ["assetTaskId"],
    },
    requiredCapability: "operate_marketing",
    sideEffect: true,
    coworkerArtifact: true,
  },
  {
    name: "record_marketing_kpi_checkpoint",
    description: "Record a KPI checkpoint or target for the active marketing plan so the workspace can show what will be measured.",
    inputSchema: {
      type: "object",
      properties: {
        metric: { type: "string", description: "Metric name" },
        target: { type: "string", description: "Target value or baseline" },
        cadence: { type: "string", enum: [...MARKETING_REVIEW_CADENCE], description: "Review cadence" },
        notes: { type: "string", description: "Measurement notes" },
      },
      required: ["metric"],
    },
    requiredCapability: "operate_marketing",
    sideEffect: true,
    coworkerArtifact: true,
  },
  {
    name: "create_marketing_automation_candidate",
    description: "Create an internal automation candidate for a marketing workflow. This does not publish, send, or schedule anything; external actions still require approval.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Automation candidate name" },
        trigger: { type: "string", description: "Event or condition that starts the automation" },
        action: { type: "string", description: "Internal action to prepare or propose" },
        approvalRequired: { type: "boolean", description: "Whether explicit human approval is required before execution" },
        rationale: { type: "string", description: "Why this automation would reduce work or improve outcomes" },
      },
      required: ["title", "trigger", "action"],
    },
    requiredCapability: "operate_marketing",
    sideEffect: true,
    coworkerArtifact: true,
  },
  {
    name: "analyze_seo_opportunity",
    description: "Get structured business context for SEO content recommendations: archetype, services/products, location, existing content sections, and suggested local search intents",
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
    requiredCapability: "view_marketing",
    sideEffect: false,
  },
  {
    name: "generate_custom_archetype",
    description: "Generate a custom business archetype from a description of the business, its offerings, and customer interaction patterns. Creates a new StorefrontArchetype record.",
    inputSchema: {
      type: "object",
      properties: {
        businessName: { type: "string", description: "Name of the business type (e.g. 'Co-working Space')" },
        businessDescription: { type: "string", description: "What the business does" },
        offerings: { type: "array", items: { type: "string" }, description: "List of products/services offered" },
        primaryCtaType: { type: "string", enum: ["booking", "purchase", "inquiry", "donation", "mixed"], description: "How customers primarily interact" },
        stakeholderLabel: { type: "string", description: "What to call the customers (Members, Clients, Patients, etc.)" },
        portalLabel: { type: "string", description: "What to call the portal (Member Portal, Client Portal, etc.)" },
        closestCategory: { type: "string", description: "Closest existing archetype category or 'custom'" },
      },
      required: ["businessName", "businessDescription", "offerings", "primaryCtaType"],
    },
    requiredCapability: "view_storefront",
    sideEffect: true,
  },
  {
    name: "assess_archetype_refinement",
    description: "Compare the current storefront configuration against the original archetype template and return a structured refinement diff showing what items, sections, and categories have changed",
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
    requiredCapability: "view_storefront",
    sideEffect: false,
  },
  {
    name: "search_public_web",
    description: "Search the public web for relevant pages or facts",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Search query" },
      },
      required: ["query"],
    },
    requiredCapability: null,
    requiresExternalAccess: true,
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate"],
  },
  {
    name: "fetch_public_website",
    description: "Fetch a public website and summarize visible branding and metadata",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string", description: "Public http or https URL" },
      },
      required: ["url"],
    },
    requiredCapability: null,
    requiresExternalAccess: true,
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate"],
  },
  {
    name: "analyze_public_website_branding",
    description: "Analyze a public website and propose branding values such as company name, logo, and accent color",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string", description: "Public http or https URL" },
      },
      required: ["url"],
    },
    requiredCapability: "manage_branding",
    requiresExternalAccess: true,
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "extract_brand_design_system",
    description: "Kick off a background brand extraction for the organization. Reads any combination of a public website URL, the platform codebase, and uploaded brand assets, merges them into a BrandDesignSystem (palette, typography, component inventory, tokens), and writes the result to Organization.designSystem. Returns a taskRunId immediately; progress is streamed through the agent panel and the coworker re-surfaces with a summary when done. Use when the user asks to refresh the brand, build a design system, or analyze an existing site.",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string", description: "Public http or https URL to extract brand signals from." },
        includeCodebase: { type: "boolean", description: "When true, also read the installed platform codebase for tokens. Defaults to false." },
        uploadIds: { type: "array", items: { type: "string" }, description: "IDs of AgentAttachment records to include (logos, brand kit PDFs, style decks)." },
      },
    },
    requiredCapability: "manage_branding",
    requiresExternalAccess: true,
    executionMode: "immediate",
    sideEffect: true,
  },
  // ─── Build Studio Tools ───────────────────────────────────────────────────
  // update_feature_brief and create_build_epic execute immediately (no approval dialog).
  // Only register_digital_product_from_build needs HITL approval (creates a real product).
  {
    name: "get_code_graph_freshness",
    description: "Get the current freshness and confidence status of the committed source-code graph used for Build Studio impact analysis.",
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["plan", "review", "ship"],
  },
  {
    name: "inspect_build_code_impact",
    description: "Analyze the active build's current diff and return route/schema impact plus code-graph coverage. Build ID is auto-resolved.",
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["review", "ship"],
  },
  {
    name: "search_code_graph",
    description: "Search the committed source-code graph for files, exported symbols, routes, tools, Prisma models, prompt sources, tests, and external modules.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Name or path text to search for in the code graph." },
        limit: { type: "number", description: "Maximum number of results to return. Defaults to 10; maximum 50." },
        graphKey: { type: "string", description: "Optional graph key. Defaults to the platform source graph." },
      },
      required: ["query"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
  },
  {
    name: "trace_code_surface",
    description: "Trace one route, MCP tool, or Prisma model to its graph-backed implementation files and related tests. Pass exactly one of route, tool, or model.",
    inputSchema: {
      type: "object",
      properties: {
        route: { type: "string", description: "Next.js route to trace, for example /build." },
        tool: { type: "string", description: "MCP tool name to trace, for example create_backlog_item." },
        model: { type: "string", description: "Prisma model name to trace, for example BacklogItem." },
        graphKey: { type: "string", description: "Optional graph key. Defaults to the platform source graph." },
      },
      required: [],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
  },
  {
    name: "find_related_tests",
    description: "Find graph-linked tests for a source file path.",
    inputSchema: {
      type: "object",
      properties: {
        filePath: { type: "string", description: "Repository-relative source file path." },
        limit: { type: "number", description: "Maximum number of tests to return. Defaults to 25; maximum 50." },
        graphKey: { type: "string", description: "Optional graph key. Defaults to the platform source graph." },
      },
      required: ["filePath"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["build", "review", "ship"],
  },
  {
    name: "update_feature_brief",
    description: "Save the Feature Brief for the current build. Build ID is auto-resolved.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Feature title" },
        description: { type: "string", description: "Plain-language feature description" },
        portfolioContext: { type: "string", description: "Portfolio slug that owns this feature" },
        targetRoles: { type: "array", items: { type: "string" }, description: "Roles that will use this feature" },
        inputs: { type: "array", items: { type: "string" }, description: "User inputs the feature accepts" },
        dataNeeds: { type: "string", description: "What data the feature stores" },
        acceptanceCriteria: { type: "array", items: { type: "string" }, description: "What done looks like" },
        fixContext: {
          type: "object",
          description: "For fix builds (kind=fix): the defect diagnosis. reproSteps, rootCause, and fixApproach are all required before a fix build can advance to plan. Merged into any existing fixContext, so partial updates accumulate.",
          properties: {
            reproSteps: { type: "string", description: "How to reproduce the defect" },
            expected: { type: "string", description: "Expected behavior" },
            actual: { type: "string", description: "Actual (buggy) behavior" },
            rootCause: { type: "string", description: "Identified root cause (file/function + why)" },
            fixApproach: { type: "string", description: "Smallest correct fix + regression test" },
            severity: { type: "string" },
          },
        },
      },
      required: ["title", "description", "portfolioContext", "targetRoles", "dataNeeds", "acceptanceCriteria"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate"],
  },
  {
    name: "suggest_taxonomy_placement",
    description: "Analyze the current feature brief and suggest where it belongs in the portfolio taxonomy. Returns ranked candidates with match scores. Call after saving the feature brief.",
    inputSchema: {
      type: "object",
      properties: {},
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    buildPhases: ["ideate"],
  },
  {
    name: "confirm_taxonomy_placement",
    description: "Confirm or override the taxonomy placement for the current feature build. Either confirm an existing node or propose a new one.",
    inputSchema: {
      type: "object",
      properties: {
        nodeId: { type: "string", description: "Confirmed taxonomy node ID (e.g. 'foundational/platform_services/api_management_platform'). Null if proposing new." },
        proposeNew: {
          type: "object",
          description: "Propose a new taxonomy node when nothing fits",
          properties: {
            parentNodeId: { type: "string", description: "Parent node ID to create under" },
            name: { type: "string", description: "Proposed node name" },
            description: { type: "string", description: "What this capability area covers" },
            rationale: { type: "string", description: "Why existing nodes don't fit" },
          },
          required: ["parentNodeId", "name", "description", "rationale"],
        },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan", "ship"],
  },
  {
    name: "register_digital_product_from_build",
    description: "Register or update a DigitalProduct from the current build. Build ID is auto-resolved. Requires approval.",
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", description: "Product name" },
        portfolioSlug: { type: "string", description: "Portfolio slug to assign to" },
      },
      required: ["name", "portfolioSlug"],
    },
    requiredCapability: "manage_capabilities",
    sideEffect: true,
    buildPhases: ["ship"],
  },
  {
    name: "create_build_epic",
    description: "Create an Epic and backlog items for a shipped build. All IDs are auto-resolved.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Epic title" },
      },
      required: ["title"],
    },
    requiredCapability: "manage_capabilities",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ship"],
  },
  // ─── Intake Tools ─────────────────────────────────────────────────────────
  {
    name: "search_portfolio_context",
    description: "Search taxonomy, products, builds, and backlog for items related to a feature description.",
    inputSchema: {
      type: "object",
      properties: { query: { type: "string", description: "Plain-language feature description" } },
      required: ["query"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate"],
  },
  {
    name: "assess_complexity",
    description: "Score a feature on 7 dimensions, get path recommendation (simple/moderate/complex).",
    inputSchema: {
      type: "object",
      properties: {
        taxonomySpan: { type: "number", description: "Score 1-3: 1=single node, 2=multi-node, 3=cross-portfolio" },
        dataEntities: { type: "number", description: "Score 1-3: 1=read-only, 2=CRUD on existing, 3=new schema" },
        integrations: { type: "number", description: "Score 1-3: 1=none, 2=internal, 3=external" },
        novelty: { type: "number", description: "Score 1-3: 1=pattern exists, 2=variation, 3=novel" },
        regulatory: { type: "number", description: "Score 1-3: 1=none, 2=moderate, 3=regulated" },
        costEstimate: { type: "number", description: "Score 1-3: 1=small, 2=medium, 3=large" },
        techDebt: { type: "number", description: "Score 1-3: 1=low, 2=moderate, 3=high" },
      },
      required: ["taxonomySpan", "dataEntities", "integrations", "novelty", "regulatory", "costEstimate", "techDebt"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate"],
  },
  {
    name: "analyze_reusability",
    description: "Analyze a feature for reusability potential. Identifies hardcoded domain concepts that could be parameterized and rates contribution readiness. Call after codebase research, before saving the design doc.",
    inputSchema: {
      type: "object",
      properties: {
        featureDescription: { type: "string", description: "What the feature does." },
        domainConcepts: { type: "array", items: { type: "string" }, description: "Key domain concepts mentioned (e.g. 'ITIL', 'ABC Plumbing', 'quarterly')." },
        userScope: { type: "string", enum: ["one_off", "parameterizable", "already_generic"], description: "User's stated intent for reusability." },
        abstractionBoundary: { type: "string", description: "What is generic structure vs. instance-specific config." },
      },
      required: ["featureDescription", "domainConcepts", "userScope"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate"],
  },
  {
    name: "propose_decomposition",
    description: "Generate an epic + feature set breakdown for a complex idea.",
    inputSchema: {
      type: "object",
      properties: {
        epicTitle: { type: "string" },
        epicDescription: { type: "string" },
        featureSets: { type: "array", items: { type: "object", properties: { title: { type: "string" }, description: { type: "string" }, type: { type: "string", enum: ["feature_build", "digital_product"] }, estimatedBuilds: { type: "number" }, recommendation: { type: "string", enum: ["build", "buy", "integrate"] }, rationale: { type: "string" }, techDebtNote: { type: "string" } }, required: ["title", "description", "type", "estimatedBuilds", "recommendation", "rationale"] } },
      },
      required: ["epicTitle", "epicDescription", "featureSets"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate"],
  },
  {
    name: "register_tech_debt",
    description: "Log a technical shortcut as a refactoring backlog item.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string" },
        description: { type: "string" },
        severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
      },
      required: ["title", "description"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
  },
  // ─── Build Notes Tool ───────────────────────────────────────────────────
  {
    name: "save_build_notes",
    description: "Persist key points from the conversation to the running spec. Call silently after each significant exchange.",
    inputSchema: {
      type: "object",
      properties: {
        processes: { type: "array", items: { type: "string" }, description: "Manual or automated processes described" },
        requirements: { type: "array", items: { type: "string" }, description: "Requirements discovered (fields, workflows, roles)" },
        decisions: { type: "array", items: { type: "string" }, description: "Decisions made (build vs buy, priorities)" },
        integrations: { type: "array", items: { type: "string" }, description: "External systems or APIs mentioned" },
        dataModel: { type: "array", items: { type: "string" }, description: "Data fields, entities, or structures identified" },
        openQuestions: { type: "array", items: { type: "string" }, description: "Questions still to resolve" },
      },
      required: [],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan"],
  },
  // ─── Phase Handoff Tool (Claude Code-inspired cross-phase memory) ────────
  {
    name: "save_phase_handoff",
    description: "Save a structured handoff briefing for the next phase. Call this as your LAST action before a phase transition.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
        summary: { type: "string", description: "2-3 sentence plain-language summary of what was accomplished in this phase" },
        decisionsMade: { type: "array", items: { type: "string" }, description: "Key decisions made and why" },
        openIssues: { type: "array", items: { type: "string" }, description: "Unresolved issues or risks carried to next phase" },
        userPreferences: { type: "array", items: { type: "string" }, description: "User preferences or constraints expressed during this phase" },
      },
      required: ["summary"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan", "build", "review"],
  },
  {
    name: "get_build_progress_visibility",
    description: "Read the Build Studio progress projection for a build: source-labelled DB task progress, stale chat conflicts, sandbox state, dispatch history, scoped verification, and quiet-agent status.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  {
    name: "get_build_sandbox_state",
    description: "Read the source-bounded sandbox/git state for a Build Studio build, including branch, head SHA, source diffstat, ignored generated/dependency paths, and expected plan files.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  {
    name: "get_build_dispatch_history",
    description: "Read bounded codex-dispatch attempts for a Build Studio build, including model, duration, exit code, sanitized stdout/stderr excerpts, and classified failure axis.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  {
    name: "get_build_scoped_verification",
    description: "Read build-scoped verification for a Build Studio build, separating failures on the build's changed surface from workspace-wide/global-health noise.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  {
    name: "list_build_activity_since",
    description: "List recent BuildActivity rows for a Build Studio build after an optional ISO timestamp cursor. Use this for polling-friendly observer updates.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
        cursor: { type: "string", description: "Optional ISO timestamp cursor; only rows after this timestamp are returned." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  // ─── Build Studio Lifecycle Tools (EP-SELF-DEV-002) ───────────────────────
  {
    name: "saveBuildEvidence",
    description: "Save evidence to a FeatureBuild record. ALWAYS pass both `field` and `value` — calls with empty `{}` are rejected. Example: `{field: \"designDoc\", value: {problemStatement: \"...\", existingFunctionalityAudit: \"...\", reusePlan: \"...\", proposedApproach: \"...\", acceptanceCriteria: [\"...\"]}}`. Valid fields: designDoc, designReview, buildPlan, planReview, taskResults, verificationOut, acceptanceMet.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
        field: { type: "string", enum: ["designDoc", "designReview", "buildPlan", "planReview", "taskResults", "verificationOut", "acceptanceMet"], description: "Evidence field to update — required" },
        value: { type: "object", description: "JSON value to store — required, do not omit. Shape varies by field; for designDoc use {problemStatement, existingFunctionalityAudit, reusePlan, proposedApproach, acceptanceCriteria[]}, for buildPlan use {fileStructure[], tasks[]} arrays." },
      },
      required: ["field", "value"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false, // Internal build workflow — available in advise mode
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
  },
  {
    name: "reviewDesignDoc",
    description: "Submit the design document for AI review. Returns pass/fail with issues.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false, // Internal build workflow — available in advise mode
    buildPhases: ["ideate"],
  },
  {
    name: "reviewBuildPlan",
    description: "Submit the implementation plan for AI review. Returns pass/fail with issues.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false, // Internal build workflow — available in advise mode
    buildPhases: ["plan"],
  },
  {
    name: "diagnose_sandbox",
    description: "Diagnose Build Studio sandbox readiness for the active build. Returns the authoritative state, failed checks, and governed recovery actions; use this instead of asking the operator to run Docker commands.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
        expectedWorkspaceRoot: { type: "string", description: "Optional host worktree path expected to own the sandbox Compose project." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["build", "review", "ship"],
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
    },
  },
  {
    name: "recover_sandbox",
    description: "Run a governed Build Studio sandbox recovery action for the active build. Requires structured confirmation for destructive actions and records recovery activity instead of sending Docker instructions to the operator.",
    inputSchema: {
      type: "object",
      properties: {
        buildId: { type: "string", description: "Optional FB-* build ID. Omit to target the current active build." },
        action: {
          type: "string",
          enum: [...SANDBOX_RECOVERY_ACTIONS],
          description: "Governed sandbox recovery action to run.",
        },
        confirmation: {
          type: "object",
          description: "Structured confirmation for destructive actions, e.g. { discardSandboxChanges: true, acknowledgeReset: true, reason: '...' }.",
          properties: {
            discardSandboxChanges: { type: "boolean" },
            acknowledgeReset: { type: "boolean" },
            reason: { type: "string" },
          },
        },
      },
      required: ["action"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["build", "review", "ship"],
    annotations: {
      readOnlyHint: false,
      destructiveHint: true,
      idempotentHint: false,
    },
  },
  {
    name: "check_sandbox",
    description: "Check whether the sandbox container (dpf-sandbox-1) is running. Returns status: 'running', 'stopped', or 'not_found'. If the result is not_found or detached, call diagnose_sandbox for governed recovery guidance.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review"],
  },
  {
    name: "start_sandbox",
    description: "Start the sandbox container if it is stopped. If status is 'stopped', this will start it and wait up to 20 seconds for it to become ready. If status is 'not_found', call diagnose_sandbox because sandbox creation or rebinding is a platform-owned recovery action.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan", "build", "review"],
  },
  {
    name: "start_build",
    description: "Initialize the build workspace. Call this ONCE at the start of the build phase. Verifies the sandbox container is running and creates a git branch for this build. If it returns 'not running', call diagnose_sandbox and use the returned recovery actions.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["build"],
  },
  {
    name: "run_sandbox_tests",
    description: "Run unit tests and typecheck inside the sandbox container. Set auto_fix to true to automatically diagnose and fix failures (up to 3 attempts).",
    inputSchema: {
      type: "object",
      properties: {
        auto_fix: { type: "boolean", description: "When true, automatically diagnose test failures and attempt fixes (max 3 retries). Default: false." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["build", "review"],
  },
  {
    name: "read_sandbox_file",
    description: "Read a file from the sandbox working copy (post-build/edit state). For reading pristine source during planning, prefer read_project_file.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "File path relative to workspace root, e.g. apps/web/lib/actions/crm.ts" },
        offset: { type: "number", description: "Start reading from this line number (1-based). Omit to read from beginning." },
        limit: { type: "number", description: "Maximum number of lines to read. Omit to read entire file. Use for large files." },
      },
      required: ["path"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    // Intentionally NOT in "plan" — read_project_file covers planning reads
    // from the source tree without requiring the portal→sandbox volume
    // round-trip, and having both tools in the same phase caused codex to
    // split file reads across them and stall (FB-21EEA510 2026-04-20).
    buildPhases: ["build", "review"],
  },
  {
    name: "write_sandbox_file",
    description: "Create or overwrite a file in the sandbox workspace. Use this to create new files. For modifying existing files, prefer edit_sandbox_file for surgical edits.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "File path relative to workspace root, e.g. apps/web/app/(shell)/complaints/page.tsx" },
        content: { type: "string", description: "The full file content to write" },
      },
      required: ["path", "content"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false, // Sandbox only
    buildPhases: ["build", "review"],
  },
  {
    name: "edit_sandbox_file",
    description: "Edit an existing file in the sandbox. Two modes: (1) String mode: old_text + new_text for exact find-and-replace. (2) Line mode: start_line + end_line + new_content to replace a line range by number. Use line mode when string matching fails — line numbers from read_sandbox_file are reliable.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "File path relative to workspace root" },
        old_text: { type: "string", description: "String mode: the exact text to find and replace" },
        new_text: { type: "string", description: "String mode: the replacement text" },
        replace_all: { type: "boolean", description: "String mode: replace all occurrences. Default: false." },
        start_line: { type: "number", description: "Line mode: first line to replace (1-indexed, from read_sandbox_file)" },
        end_line: { type: "number", description: "Line mode: last line to replace (inclusive)" },
        new_content: { type: "string", description: "Line mode: replacement content for the line range" },
      },
      required: ["path"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false, // Sandbox only
    buildPhases: ["build", "review"],
  },
  {
    name: "search_sandbox",
    description: "Search the sandbox working copy (post-build/edit state). For searching pristine source during planning, prefer search_project_files.",
    inputSchema: {
      type: "object",
      properties: {
        pattern: { type: "string", description: "Text or regex pattern to search for" },
        glob: { type: "string", description: "File glob filter, e.g. '*.ts' or '*.tsx'" },
        maxResults: { type: "number", description: "Maximum results (default 20)" },
      },
      required: ["pattern"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    // Not in "plan" — search_project_files covers planning search.
    buildPhases: ["build"],
  },
  {
    name: "list_sandbox_files",
    description: "List files in the sandbox working copy (post-build/edit state). For listing pristine source during planning, prefer list_project_directory.",
    inputSchema: {
      type: "object",
      properties: {
        pattern: { type: "string", description: "Glob pattern, e.g. 'apps/web/lib/actions/*.ts' or '**/*.tsx'" },
      },
      required: ["pattern"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    // Not in "plan" — list_project_directory covers planning listing.
    buildPhases: ["build"],
  },
  {
    name: "run_sandbox_command",
    description: "Run a shell command inside the sandbox container. Use for build, test, lint, git diff, or any other verification. Returns stdout and stderr.",
    inputSchema: {
      type: "object",
      properties: {
        command: { type: "string", description: "Shell command to execute in the sandbox, e.g. 'pnpm --filter web build' or 'git diff'" },
      },
      required: ["command"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false, // Sandbox is isolated from production — safe in any mode
    buildPhases: ["build", "review"],
  },
  {
    name: "describe_model",
    description: "Look up a Prisma model's fields, types, relations, and indexes from the sandbox schema. Use this instead of asking the user about schema structure. Example: describe_model({ model_name: 'User' }) returns all fields with types.",
    inputSchema: {
      type: "object",
      properties: {
        model_name: { type: "string", description: "Exact model name (PascalCase), e.g. 'User', 'Complaint', 'FeatureBuild'" },
      },
      required: ["model_name"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review"],
  },
  {
    name: "validate_schema",
    description: "Validate the Prisma schema in the sandbox for common errors: missing inverse relations, undefined types, unindexed foreign keys. MUST be called before running prisma migrate. Returns specific errors with fix instructions.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["build"],
  },
  {
    name: "deploy_feature",
    description: "Extract the git diff from sandbox and deploy to the platform.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "manage_capabilities",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ship"],
  },
  {
    name: "create_portal_pr",
    description: "Create a pull request on the portal's own repository from the current build's diff. Runs pre-PR security gates (security scan, destructive ops, architecture compliance, dependency audit). If all gates pass and the build is fully verified, auto-merges via squash. If any gate fails, creates the PR with findings and requests human review.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "manage_capabilities",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ship"],
  },
  // ─── Admin Coworker Tools (TAK-ADMIN-001) ──────────────────────────────
  // These tools are available on the /admin route for platform administration.
  // Tier 1 = read-only, Tier 2 = reversible, Tier 3 = destructive (sideEffect: true).
  {
    name: "admin_view_logs",
    description: "View recent logs from a Docker Compose service. Returns the last N lines (default 100).",
    inputSchema: {
      type: "object",
      properties: {
        service: { type: "string", description: "Service name: portal, sandbox, postgres, neo4j, qdrant, portal-init" },
        lines: { type: "number", description: "Number of lines to return (default 100, max 500)" },
      },
      required: ["service"],
    },
    requiredCapability: "view_admin",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "admin_query_db",
    description: "Run a read-only SQL query against the portal database. Only SELECT statements are permitted. Max 1000 rows.",
    inputSchema: {
      type: "object",
      properties: {
        sql: { type: "string", description: "SQL SELECT query to execute" },
      },
      required: ["sql"],
    },
    requiredCapability: "view_admin",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "admin_read_file",
    description: "Read a file within the project directory. Path must be relative to PROJECT_ROOT. Sensitive files (.env, *.key, *.pem) are excluded.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "File path relative to project root, e.g. docker-compose.yml or apps/web/lib/mcp-tools.ts" },
        offset: { type: "number", description: "Start reading from this line number (1-based)" },
        limit: { type: "number", description: "Maximum number of lines to read" },
      },
      required: ["path"],
    },
    requiredCapability: "view_admin",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "admin_restart_service",
    description: "Restart a Docker Compose service. Equivalent to 'docker compose restart <service>'.",
    inputSchema: {
      type: "object",
      properties: {
        service: { type: "string", description: "Service name: portal, sandbox, postgres, neo4j, qdrant" },
      },
      required: ["service"],
    },
    requiredCapability: "view_admin",
    executionMode: "immediate",
    sideEffect: false, // Tier 2: reversible
  },
  {
    name: "admin_run_migration",
    description: "Run 'prisma migrate deploy' inside the portal container to apply pending database migrations.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_admin",
    executionMode: "immediate",
    sideEffect: false, // Tier 2: reversible
  },
  {
    name: "admin_run_seed",
    description: "Run the database seed script inside the portal container to populate reference data.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_admin",
    executionMode: "immediate",
    sideEffect: false, // Tier 2: reversible
  },
  {
    name: "admin_run_command",
    description: "Run a shell command in the project directory. Only docker compose, git, and pnpm commands are permitted. Destructive commands require user confirmation.",
    inputSchema: {
      type: "object",
      properties: {
        command: { type: "string", description: "Shell command to run (docker compose, git, or pnpm only)" },
      },
      required: ["command"],
    },
    requiredCapability: "view_admin",
    executionMode: "immediate",
    sideEffect: true, // Tier 3: potentially destructive
  },
  // ─── Scheduling & Release Tools (IT4IT §5.3-5.4) ───────────────────────
  {
    name: "check_deployment_windows",
    description: "Check available deployment windows for promoting changes to production. Returns current window status, blackout periods, and next available window time.",
    inputSchema: {
      type: "object",
      properties: {
        change_type: { type: "string", description: "RFC type: standard, normal, or emergency. Default: normal." },
        risk_level: { type: "string", description: "Risk level: low, medium, high, or critical. Default: low." },
      },
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["review", "ship"],
  },
  {
    name: "schedule_promotion",
    description: "Schedule an approved promotion for deployment during a specific window. Creates a calendar event for visibility.",
    inputSchema: {
      type: "object",
      properties: {
        promotion_id: { type: "string", description: "The promotion ID (CP-xxx) to schedule." },
      },
      required: ["promotion_id"],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ship"],
  },
  {
    name: "execute_promotion",
    description: "Execute an approved promotion. Starts the autonomous promoter: backup DB, build new portal image from sandbox, swap containers, health check. Rolls back automatically on failure.",
    inputSchema: {
      type: "object" as const,
      properties: {
        promotion_id: { type: "string", description: "The promotion ID to execute (e.g. CP-xxxx)." },
        override_reason: { type: "string", description: "Reason for deploying outside a deployment window (optional, for emergency changes)." },
      },
      required: ["promotion_id"],
    },
    requiredCapability: "view_operations" as const,
    executionMode: "immediate" as const,
    sideEffect: true,
    buildPhases: ["ship"],
  },
  {
    name: "create_release_bundle",
    description: "Group multiple completed builds into a release bundle for coordinated deployment (IT4IT §5.3.5 Release Package).",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Release bundle title, e.g. 'March 2026 Feature Release'." },
        build_ids: { type: "array", items: { type: "string" }, description: "Array of buildId values to include in the bundle." },
      },
      required: ["title", "build_ids"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ship"],
  },
  {
    name: "run_release_gate",
    description: "Run gate checks on a release bundle: combine diffs from all builds, run destructive operation scan, validate all builds passed tests (IT4IT §5.3.5 Accept & Publish Release).",
    inputSchema: {
      type: "object",
      properties: {
        bundle_id: { type: "string", description: "The release bundle ID (RB-xxx) to check." },
      },
      required: ["bundle_id"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "schedule_release_bundle",
    description: "Schedule an approved release bundle for deployment during a deployment window. Creates an RFC, ChangePromotion, and CalendarEvent for operations calendar visibility.",
    inputSchema: {
      type: "object",
      properties: {
        bundle_id: { type: "string", description: "The release bundle ID (RB-xxx) to schedule." },
      },
      required: ["bundle_id"],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "get_release_status",
    description: "Get the current status of a release bundle or promotion, including deployment window availability and gate check results.",
    inputSchema: {
      type: "object",
      properties: {
        bundle_id: { type: "string", description: "Release bundle ID (RB-xxx) — optional if promotion_id is provided." },
        promotion_id: { type: "string", description: "Promotion ID (CP-xxx) — optional if bundle_id is provided." },
      },
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ship"],
  },
  // ─── Hive Mind Contribution Tools (IT4IT §5.5 Release) ───────────────────
  {
    name: "assess_contribution",
    description: "Evaluate whether a shipped feature should be contributed to the Hive Mind community. Assesses vision alignment, community value, augmentation vs innovation, and proprietary sensitivity. Always presents the assessment to the user — contribution is their choice.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ship"],
  },
  {
    name: "contribute_to_hive",
    description: "Package a shipped feature as a FeaturePack for community contribution. Only call after the user has seen the assessment and explicitly approved. Includes DCO (Developer Certificate of Origin) attestation.",
    inputSchema: {
      type: "object",
      properties: {
        include_migrations: { type: "boolean", description: "Include database migrations in the pack. Default: true." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "proposal",
    sideEffect: true,
    buildPhases: ["ship"],
    // Skip the proposal card when the user has pre-authorized via `contribute_all`
    // and accepted the DCO. In that configuration every shipped build is cleared
    // to contribute upstream; an extra per-build approval step is redundant and
    // silently stalls autonomous runs (no human is present to click approve).
    autoApproveWhen: async () => {
      const cfg = await prisma.platformDevConfig.findUnique({
        where: { id: "singleton" },
        select: { contributionMode: true, dcoAcceptedAt: true },
      });
      return cfg?.contributionMode === "contribute_all" && !!cfg.dcoAcceptedAt;
    },
  },
  {
    name: "apply_platform_update",
    description: "Merge the new platform version into your customised source. Returns a clean merge or a list of conflicts for the AI coworker to resolve with you.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "manage_platform",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "evaluate_page",
    description: "Evaluate a live page for UX and accessibility issues using AI-powered browser automation (browser-use). Navigates to the page, analyzes layout, interactions, and accessibility, and returns structured findings. Works on production pages (default) or sandbox pages (if URL provided).",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string", description: "URL to evaluate. Defaults to the current route if not specified." },
      },
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["review"],
  },
  {
    name: "run_ux_test",
    description: "Run natural-language UX test cases against the sandbox using AI-powered browser automation (browser-use). Each test case is a plain English assertion that the AI agent verifies by driving a real browser. Returns structured pass/fail results with screenshots. NOTE: UX verification runs automatically on review-phase entry via build/review.verify — this tool is retained for ad-hoc manual invocations only and is not in the review phase's tool allowlist.",
    inputSchema: {
      type: "object",
      properties: {
        tests: {
          type: "array",
          items: { type: "string" },
          description: "Natural-language test assertions. If omitted, auto-generates from acceptance criteria.",
        },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    // Empty buildPhases — the Inngest handler owns UX verification during
    // review; leaving this in the review allowlist leads to duplicate runs.
    // Kept callable outside a build phase for Dev-mode / debugging use.
    buildPhases: [],
  },
  // ─── Codebase Access Tools ──────────────────────────────────────────────────
  {
    name: "list_project_directory",
    description: "List files and directories in a project directory. Use '.' or empty string for project root. Helps discover the project structure.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Relative directory path from project root (use '.' for root)" },
      },
      required: ["path"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan"],
  },
  {
    name: "start_ideate_research",
    description: "Signal that you have enough context from the user to begin codebase research and draft the design document. Call this AFTER the user answers your questions (intent gate, reusability scope). The system will search the codebase, analyze patterns, and draft the design doc automatically. You do NOT need to call search_project_files or read_project_file yourself — this tool handles all research.",
    inputSchema: {
      type: "object",
      properties: {
        reusabilityScope: { type: "string", enum: ["one_off", "parameterizable", "already_generic"], description: "The user's reusability preference" },
        userContext: { type: "string", description: "Summary of what the user wants, including any answers to clarifying questions" },
      },
      required: ["reusabilityScope", "userContext"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate"],
  },
  {
    name: "start_scout_research",
    description: "Start a fast codebase scout + URL parse before asking clarification questions. Call this immediately after the user describes their feature. Returns immediately — results appear in Build Studio Context on the next turn.",
    inputSchema: {
      type: "object",
      properties: {
        externalUrls: {
          type: "array",
          items: { type: "string" },
          description: "Any URLs the user mentioned (website, design doc, reference). Will be fetched and parsed for domain structure.",
        },
      },
      required: [],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate"],
  },
  {
    name: "read_project_file",
    description: "Read a file from the project codebase. Use relative paths like 'apps/web/lib/mcp-tools.ts'. Cannot access .env, credentials, or node_modules.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Relative file path from project root" },
        startLine: { type: "number", description: "Start line (1-based, optional)" },
        endLine: { type: "number", description: "End line (optional)" },
      },
      required: ["path"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan"],
  },
  {
    name: "search_project_files",
    description: "Search the project codebase for a text pattern. The query parameter is the text to search for (e.g. 'voucher', 'student'). The glob parameter is an OPTIONAL file type filter (e.g. '*.ts'). Do NOT combine them — use query='voucher' and glob='*.prisma' as separate parameters, NOT query='voucher:*.prisma'.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Text or regex pattern to search for (e.g. 'voucher', 'Student', 'registration'). This is NOT a file path or glob." },
        glob: { type: "string", description: "Optional file type filter (e.g. '*.ts', '*.prisma', '*.tsx'). Do NOT put search terms here." },
        maxResults: { type: "number", description: "Maximum results (default 20)" },
      },
      required: ["query"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan"],
  },
  // ─── Version Tracking Tools ────────────────────────────────────────────────
  {
    name: "query_version_history",
    description: "List product versions with their git tags, ship dates, change counts, and promotion status. Optionally filter by digital product ID.",
    inputSchema: {
      type: "object",
      properties: {
        digitalProductId: { type: "string", description: "Filter by product (optional — returns all if omitted)" },
        limit: { type: "number", description: "Max results (default 20)" },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  // ─── Design Intelligence Tools (UI UX Pro Max) ────────────────────────────
  {
    name: "search_design_intelligence",
    description: "Search the design intelligence database for UI/UX recommendations. Returns style guides, color palettes, typography pairings, UX best practices, landing page patterns, chart types, or product-type recommendations based on keyword matching.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Search keywords (e.g., 'SaaS dashboard', 'glassmorphism dark mode', 'elegant luxury serif')" },
        domain: {
          type: "string",
          enum: ["style", "color", "typography", "ux", "landing", "chart", "product", "reasoning"],
          description: "Which design domain to search: style (67 UI styles), color (palettes by industry), typography (57 font pairings), ux (99 guidelines), landing (page patterns), chart (25 chart types), product (industry recommendations), reasoning (161 design rules)",
        },
        max_results: { type: "number", description: "Maximum results to return (default 5)" },
      },
      required: ["query", "domain"],
    },
    requiredCapability: null, // Read-only design reference — no capability gate
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review"],
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
  },
  {
    name: "generate_design_system",
    description: "Generate a complete design system recommendation for a product. Searches across product types, styles, colors, typography, and landing page patterns, then applies industry-specific reasoning rules. Returns: recommended pattern, style, color palette, font pairing, effects, anti-patterns to avoid, and a pre-delivery checklist. This is a pure data lookup — no LLM call, works at any model tier.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Product description and keywords (e.g., 'beauty spa wellness service', 'fintech banking dashboard', 'SaaS analytics tool')" },
        project_name: { type: "string", description: "Optional project name for the design system header" },
      },
      required: ["query"],
    },
    requiredCapability: null, // Read-only design reference — no capability gate
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review"],
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
  },
  // ─── Manifest Tools ────────────────────────────────────────────────────────
  {
    name: "generate_codebase_manifest",
    description: "Generate or refresh the codebase manifest (SBOM). Reads package.json, schema.prisma, directory structure, and the base manifest template to produce a current snapshot. Dev-only.",
    inputSchema: {
      type: "object",
      properties: {
        version: { type: "string", description: "Version label (default: 'dev')" },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "read_codebase_manifest",
    description: "Read the codebase manifest (SBOM) for a specific version. Returns the structured JSON with modules, capabilities, dependencies, and statistics. Works in both dev and production.",
    inputSchema: {
      type: "object",
      properties: {
        version: { type: "string", description: "Version to read (default: latest or deployed)" },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  // ─── Production Read-Only Tools (git-based) ────────────────────────────────
  {
    name: "read_source_at_version",
    description: "Read a file from the codebase at a specific version tag. Uses git history — works in production without source code. Default version: DEPLOYED_VERSION or HEAD.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Relative file path" },
        version: { type: "string", description: "Git tag or ref (default: deployed version)" },
      },
      required: ["path"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "search_source_at_version",
    description: "Search the codebase at a specific version for a text pattern. Uses git grep — works in production without source code.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Text or regex pattern to search" },
        version: { type: "string", description: "Git tag or ref (default: deployed version)" },
        glob: { type: "string", description: "File glob filter (e.g., '*.ts')" },
        maxResults: { type: "number", description: "Max results (default 20)" },
      },
      required: ["query"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "list_source_directory",
    description: "List directory contents at a specific version. Uses git ls-tree — works in production without source code.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Directory path (default: root)" },
        version: { type: "string", description: "Git tag or ref (default: deployed version)" },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "compare_versions",
    description: "Show what changed between two versions — files modified, commit log. Uses git diff.",
    inputSchema: {
      type: "object",
      properties: {
        from: { type: "string", description: "Starting version tag (e.g., 'v1.0.0')" },
        to: { type: "string", description: "Ending version tag (default: HEAD)" },
      },
      required: ["from"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "propose_file_change",
    description: "Propose a change to a project file. Shows a diff for human review. Requires approval before the change is applied.",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Relative file path to modify or create" },
        description: { type: "string", description: "Human-readable description of the change" },
        newContent: { type: "string", description: "The complete new file contents" },
      },
      required: ["path", "description", "newContent"],
    },
    requiredCapability: "manage_capabilities",
    sideEffect: true,
    buildPhases: ["build"],
  },
  // ─── Feedback Loop ──────────────────────────────────────────────────────────
  {
    name: "propose_improvement",
    description:
      "Propose a platform improvement based on friction or a missing capability observed in this conversation. " +
      "Available to ALL employees regardless of role — anyone can submit an idea. Auto-attributes to the current user.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Short title for the improvement (max 100 chars)" },
        description: { type: "string", description: "What should be improved and why" },
        category: {
          type: "string",
          enum: ["ux_friction", "missing_feature", "performance", "accessibility", "security", "process"],
          description: "Improvement category",
        },
        severity: {
          type: "string",
          enum: ["low", "medium", "high", "critical"],
          description: "Impact severity (default: medium)",
        },
        observedFriction: { type: "string", description: "What you observed that prompted this suggestion" },
      },
      required: ["title", "description", "category"],
    },
    requiredCapability: null,
    executionMode: "proposal",
    sideEffect: true,
  },
  {
    name: "propose_skill_improvement",
    description:
      "Propose a content change to a specific coworker skill (e.g. tightening instructions, fixing a stale " +
      "reference). Use when you have observed the current skill prompt produce the wrong behavior and you can " +
      "draft a better version. Submits an ImprovementProposal(category='skill', targetSkillId=…) that a human " +
      "reviews on /platform/ai/skills.",
    inputSchema: {
      type: "object",
      properties: {
        skillId: {
          type: "string",
          description: "The SkillDefinition.skillId (business id) the proposal targets, e.g. 'build-page'.",
        },
        title: { type: "string", description: "Short title for the change (max 100 chars)" },
        description: { type: "string", description: "Why the change is needed; cite the friction or failure" },
        proposedContent: {
          type: "string",
          description: "Full proposed SKILL.md body (replaces the current content if approved)",
        },
        severity: {
          type: "string",
          enum: ["low", "medium", "high", "critical"],
          description: "Impact severity (default: medium)",
        },
        observedFriction: {
          type: "string",
          description: "What you observed that prompted this change",
        },
      },
      required: ["skillId", "title", "description", "proposedContent"],
    },
    requiredCapability: null,
    executionMode: "proposal",
    sideEffect: true,
  },
  // ─── Provider Management ────────────────────────────────────────────────────
  {
    name: "add_provider",
    description: "Add a new AI provider to the platform. Creates an unconfigured entry that can then be set up.",
    inputSchema: {
      type: "object",
      properties: {
        providerId: { type: "string", description: "Short identifier (e.g. 'mantis', 'ollama')" },
        name: { type: "string", description: "Display name (e.g. 'Mantis (local)')" },
        category: { type: "string", enum: ["direct", "agent", "router", "local"], description: "Provider category" },
        costModel: { type: "string", enum: ["token", "compute"], description: "Pricing model" },
        baseUrl: { type: "string", description: "API base URL (optional)" },
        authMethod: { type: "string", enum: ["none", "api_key", "oauth2_client_credentials"], description: "Auth method (default: api_key)" },
      },
      required: ["providerId", "name", "category"],
    },
    requiredCapability: "manage_provider_connections",
    sideEffect: true,
  },
  {
    name: "update_provider_category",
    description: "Change the category of an existing AI provider (e.g. from 'direct' to 'local').",
    inputSchema: {
      type: "object",
      properties: {
        providerId: { type: "string", description: "Provider to update" },
        category: { type: "string", enum: ["direct", "agent", "router", "local"], description: "New category" },
      },
      required: ["providerId", "category"],
    },
    requiredCapability: "manage_provider_connections",
    sideEffect: true,
  },
  {
    name: "analyze_brand_document",
    description: "Analyze an uploaded brand guidelines document (PDF or image) and extract brand assets: logo, colors, and fonts",
    inputSchema: {
      type: "object",
      properties: {
        fileName: { type: "string", description: "Original filename" },
        fileContent: { type: "string", description: "Base64-encoded file content" },
        fileType: { type: "string", enum: ["pdf", "png", "jpg", "svg"], description: "File type" },
      },
      required: ["fileName", "fileContent", "fileType"],
    },
    requiredCapability: "manage_branding" as CapabilityKey,
    executionMode: "immediate",
  },
  // ─── HR Lifecycle Tools ─────────────────────────────────────────────────────
  {
    name: "query_employees",
    description: "Search and list employee profiles. Use this to find employees by name, email, department, or status. Returns a summary list with employee IDs, names, and departments. Use before create_employee to check if someone already exists.",
    inputSchema: {
      type: "object",
      properties: {
        search: { type: "string", description: "Search by name or email (partial match, optional)" },
        department: { type: "string", description: "Filter by department name or ID (optional)" },
        status: { type: "string", enum: ["offer", "onboarding", "active", "leave", "suspended", "offboarding", "inactive"], description: "Filter by employment status (optional)" },
        limit: { type: "number", description: "Max results to return (default 20)" },
      },
    },
    requiredCapability: "view_employee" as CapabilityKey,
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "list_departments",
    description: "List all active departments with their IDs and names. Call this before create_employee to find valid department IDs or to present the user with choices.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_employee",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "list_positions",
    description: "List all active positions with their IDs and titles. Call this before create_employee to find valid position IDs or to present the user with choices.",
    inputSchema: { type: "object", properties: {} },
    requiredCapability: "view_employee",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "create_employee",
    description: "Create a new employee record. Department and position can be supplied as an ID or a name/title — the system resolves names automatically. Call list_departments and list_positions first if you need to show the user their options.",
    inputSchema: {
      type: "object",
      properties: {
        firstName: { type: "string", description: "First name" },
        lastName: { type: "string", description: "Last name" },
        workEmail: { type: "string", description: "Work email address" },
        status: { type: "string", enum: ["offer", "onboarding", "active"], description: "Initial status (default: offer)" },
        departmentId: { type: "string", description: "Department ID or department name (optional)" },
        positionId: { type: "string", description: "Position ID or position title (optional)" },
        managerEmployeeId: { type: "string", description: "Manager employee ID, display name, or email (optional)" },
        startDate: { type: "string", description: "Start date ISO string (optional)" },
      },
      required: ["firstName", "lastName"],
    },
    requiredCapability: "manage_user_lifecycle",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "transition_employee_status",
    description: "Move an employee through lifecycle stages (e.g. offer → onboarding, onboarding → active, active → offboarding).",
    inputSchema: {
      type: "object",
      properties: {
        employeeId: { type: "string", description: "Employee ID (e.g. EMP-XXXXX)" },
        newStatus: { type: "string", enum: ["onboarding", "active", "leave", "suspended", "offboarding", "inactive"], description: "Target status" },
        reason: { type: "string", description: "Reason for the transition" },
      },
      required: ["employeeId", "newStatus"],
    },
    requiredCapability: "manage_user_lifecycle",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "propose_leave_policy",
    description: "Suggest leave policies for an employee based on their location/country. Creates default leave policy records.",
    inputSchema: {
      type: "object",
      properties: {
        locationContext: { type: "string", description: "Country or region for policy recommendations" },
        policies: {
          type: "array",
          description: "Array of policy suggestions",
          items: {
            type: "object",
            properties: {
              leaveType: { type: "string" },
              name: { type: "string" },
              annualAllocation: { type: "number" },
              carryoverLimit: { type: "number" },
            },
          },
        },
      },
      required: ["locationContext", "policies"],
    },
    requiredCapability: "manage_user_lifecycle",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "submit_feedback",
    description: "Log a feedback note for an employee (praise, constructive, or observation).",
    inputSchema: {
      type: "object",
      properties: {
        toEmployeeId: { type: "string", description: "Employee profile ID receiving feedback" },
        content: { type: "string", description: "Feedback content" },
        feedbackType: { type: "string", enum: ["praise", "constructive", "observation"], description: "Type of feedback" },
        visibility: { type: "string", enum: ["private", "shared", "public"], description: "Visibility (default: private)" },
      },
      required: ["toEmployeeId", "content", "feedbackType"],
    },
    requiredCapability: null,
    executionMode: "immediate",
  },
  // ─── Managed Document Store ───────────────────────────────────────────────
  {
    name: "doc_save",
    description: "Create a managed document or append a new version. Use for coworker-authored briefs, policies, plans, and other durable non-code artifacts that need stable references, lifecycle, search, and version history.",
    inputSchema: {
      type: "object",
      properties: {
        documentId: { type: "string", description: "Existing stable document id to version, such as DOC-1234ABCD. Omit to create a new document." },
        title: { type: "string", description: "Document title." },
        documentKind: { type: "string", description: "Brief type, e.g. brief, policy, plan, audit, decision, runbook." },
        contentFormat: { type: "string", description: "MIME-ish format, e.g. text/markdown, text/plain, text/html, application/pdf." },
        contentText: { type: "string", description: "Inline content for text documents under the inline storage limit." },
        contentBlobId: { type: "string", description: "DocumentBlob id for larger or binary content." },
        contentSha256: { type: "string", description: "SHA-256 of the content when known." },
        summary: { type: "string", description: "Version change summary or abstract." },
        tags: { type: "array", items: { type: "string" }, description: "Searchable tags." },
        references: {
          type: "array",
          description: "Stable references this version cites.",
          items: {
            type: "object",
            properties: {
              targetDocumentId: { type: "string" },
              targetExternalRef: { type: "string" },
              refType: { type: "string" },
              anchor: { type: "string" },
            },
            required: ["refType"],
          },
        },
        accessScope: { type: "string", enum: ["organization", "restricted"], description: "Default organization scope unless restricted." },
        sourceKind: { type: "string", enum: ["managed", "external"], description: "managed for stored content, external for stable stubs to repo/filesystem artifacts." },
        organizationId: { type: "string", description: "Organization id. Defaults to the install organization." },
      },
      required: ["title", "documentKind", "contentFormat"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "doc_load",
    description: "Load a managed document by stable document id, optionally pinned to a version.",
    inputSchema: {
      type: "object",
      properties: {
        documentId: { type: "string", description: "Stable document id, such as DOC-1234ABCD." },
        version: { type: "number", description: "Optional version number. Defaults to current version." },
      },
      required: ["documentId"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  {
    name: "doc_search",
    description: "Search managed documents by metadata, tags, owner, lifecycle state, full text, and best-effort semantic similarity.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Phrase or natural-language concept to search for." },
        mode: { type: "string", enum: ["metadata", "full-text", "semantic", "hybrid"], description: "Search mode. Hybrid combines Postgres filters and Qdrant semantic results." },
        state: { type: "string", enum: ["draft", "published", "archived"], description: "Lifecycle state filter." },
        documentKind: { type: "string", description: "Kind filter." },
        ownerPrincipalId: { type: "string", description: "Principal DB id for owner filter." },
        tags: { type: "array", items: { type: "string" }, description: "Tag filters." },
        organizationId: { type: "string", description: "Organization id filter." },
        limit: { type: "number", description: "Max results, default 25." },
      },
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
    annotations: { readOnlyHint: true },
  },
  {
    name: "doc_link",
    description: "Add a managed document reference edge from one document to another document or external stable locator.",
    inputSchema: {
      type: "object",
      properties: {
        sourceDocumentId: { type: "string", description: "Source stable document id." },
        targetDocumentId: { type: "string", description: "Target stable document id, if the target is managed." },
        targetExternalRef: { type: "string", description: "External stable locator when the target is not managed." },
        refType: { type: "string", description: "Reference type, e.g. cites, supersedes, derived-from, blocks, evidence-for." },
        anchor: { type: "string", description: "Optional source anchor or section." },
      },
      required: ["sourceDocumentId", "refType"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "doc_version_list",
    description: "List version metadata for a managed document.",
    inputSchema: {
      type: "object",
      properties: {
        documentId: { type: "string", description: "Stable document id." },
      },
      required: ["documentId"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  {
    name: "doc_state_change",
    description: "Transition a managed document lifecycle state and record a DocumentLifecycleEvent.",
    inputSchema: {
      type: "object",
      properties: {
        documentId: { type: "string", description: "Stable document id." },
        toState: { type: "string", enum: ["draft", "published", "archived"], description: "Target lifecycle state." },
        reason: { type: "string", description: "Reason for the transition." },
        toolExecutionId: { type: "string", description: "Optional audit row id that produced this transition." },
      },
      required: ["documentId", "toState"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "doc_list_references",
    description: "List inbound and outbound managed document references for a document.",
    inputSchema: {
      type: "object",
      properties: {
        documentId: { type: "string", description: "Stable document id." },
      },
      required: ["documentId"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
    annotations: { readOnlyHint: true, idempotentHint: true },
  },
  // ─── EP-WIKI-001 Phase 3b2: Wiki Query ──────────────────────────────────────
  {
    name: "wiki_query",
    description: "Search the founder kernel + per-org overlay wiki for entity, stance, heuristic, principle, decision, summary, runbook, or index pages. Use when the user asks about a DPF concept, what the founder's view is on something, what governance principles apply, or for grounded judgment on a question. Returns top-K pages with slug, kind, kernel/overlay origin, content preview, and (for principle pages) tier + applies-to + dimensions + public-classification metadata. Prefer this over web speculation.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "What to search for. Natural language; the wiki is embedded for semantic similarity." },
        pageKind: {
          type: "string",
          enum: ["entity", "summary", "decision", "runbook", "index", "stance", "heuristic", "principle"],
          description: "Optional filter to one page kind. Use 'stance' or 'heuristic' when the user wants judgment; 'principle' for governance rules with tier and applies-to; 'entity' for definitions; 'decision' for DEC-* records.",
        },
        tier: {
          type: "string",
          enum: ["commandment", "core", "contextual"],
          description: "When pageKind=principle: filter to one tier. Commandment = non-negotiable doctrine; core = strong defaults; contextual = narrow operational rules.",
        },
        appliesTo: {
          type: "string",
          enum: ["in_platform_coworker", "external_coding_agent", "human"],
          description: "When pageKind=principle: filter to principles that apply to this population.",
        },
        publicOnly: {
          type: "boolean",
          description: "When pageKind=principle: only return principles classified as public (safe to surface to customers / contributors).",
        },
        limit: { type: "number", description: "Max results (default 5)." },
        retrievalMode: {
          type: "string",
          enum: ["vector", "ppr"],
          description: "EP-WIKI-004: 'vector' for cosine-only ranking (default — same as before). 'ppr' adds a Personalized PageRank pass over the per-org WikiPageLink subgraph so pages linked from the seed set also surface. Falls back to vector if the subgraph is sparse or PPR fails. Default: the org's wikiRetrievalMode setting, which is 'vector' until explicitly switched.",
        },
      },
      required: ["query"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
  },
  // ─── Principles-as-wiki-kind Phase 2 Task 2.7: advisory decision support ──
  {
    name: "principle_decide",
    description:
      "Advisory only. Score a set of options against the governance principles in scope for the calling population, and return a recommendation plus a per-principle contribution ledger. Uses commandments from Postgres (always included) and relevant core/contextual principles from semantic search. Does not execute the recommended option; the caller retains authority. Use when you have two or more options and want to surface which governance principles pull which way.",
    inputSchema: {
      type: "object",
      properties: {
        context: {
          type: "string",
          description:
            "A short description of the decision being made. Used for semantic retrieval of relevant core and contextual principles.",
        },
        options: {
          type: "array",
          items: {
            type: "object",
            properties: {
              id: { type: "string", description: "Stable identifier for the option." },
              description: { type: "string", description: "Short prose description." },
              features: {
                type: "object",
                description:
                  "Optional map of dimension key -> 0..1 score. Dimensions must come from the registry in packages/db/src/wiki-taxonomy.ts. Options that supply features get structured alignment; otherwise the math falls back to semantic alignment.",
                additionalProperties: { type: "number" },
              },
            },
            required: ["id", "description"],
          },
          description: "The candidate options to score. Must be a non-empty array.",
        },
        callingPopulation: {
          type: "string",
          enum: ["in_platform_coworker", "external_coding_agent", "human"],
          description: "Population whose principles should apply.",
        },
        maxPrinciples: {
          type: "number",
          description: "Cap on relevant core/contextual principles retrieved from Qdrant. Default 20.",
        },
        tieMargin: {
          type: "number",
          description:
            "Margin threshold below which confidence flips to 'low' and the reasoning recommends human review. Default 0.2.",
        },
        ringScope: {
          type: "array",
          items: {
            type: "string",
            enum: [
              "ring-1-coworker",
              "ring-2-workflow",
              "ring-3-archetype",
              "ring-4-sandbox-prod",
              "ring-5-hive",
              "external-coordination",
              "universal-ring",
            ],
          },
          description:
            "Reduction Gear ring scope(s) the calling action binds. When set, retrieval filters to principles whose principleRingScope intersects the caller scope OR contains universal-ring OR is empty (backward compat). Omit (or pass ['universal-ring']) to consult the full kernel — appropriate for design-time / kernel-architecture decisions that genuinely bind every ring. See spec docs/superpowers/specs/2026-05-24-founder-kernel-evolution-discipline-design.md §5.",
        },
        callingSurface: {
          type: "string",
          description:
            "Optional free-form label naming the calling surface (e.g. 'build-studio-phase', 'promotion-gate'). Propagated to the [principle-recall-trace] log line so operators can correlate recall traffic with the surface that drove it.",
        },
      },
      required: ["context", "options", "callingPopulation"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
    },
  },
  // ─── EP-WIKI-001 Phase 4b2b: Wiki Lint trigger ─────────────────────────────
  {
    name: "wiki_lint",
    description: "Run wiki lint detectors on-demand for the kernel and/or current organisation's overlay. Same orchestrator as the daily scheduled job. Use when the user asks to refresh findings or wants a quick health check. Returns scanned-page counts and finding deltas (opened / kept / resolved).",
    inputSchema: {
      type: "object",
      properties: {
        scope: {
          type: "string",
          enum: ["kernel", "org", "all"],
          description: "Which scope to lint. 'kernel' lints kernel rows only; 'org' lints the current org's overlay; 'all' (default) lints both.",
        },
      },
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
  },
  // ─── EP-WIKI-001 Phase 2.3b: Wiki ingest pipeline ──────────────────────────
  {
    name: "wiki_ingest",
    description:
      "Ingest a raw source (markdown file on disk) into the wiki: upsert the RawSource row, run the three-pass LLM extraction (abstract, claim, stance/heuristic), and optionally commit the proposal as draft overlay pages under the requesting org. Kernel pages stay PR-only (spec §4). Use when the user wants to import an article into the kernel knowledge surface, or to preview what the LLM would extract from a source without writing anything. Two modes: 'propose' returns the structured proposal for review without DB writes; 'commit' runs the full pipeline and lands drafts on /wiki?status=all.",
    inputSchema: {
      type: "object",
      properties: {
        filePath: {
          type: "string",
          description:
            "Absolute or process-relative path to the markdown source file. Files under docs/founder-kernel/raw-sources/ get sourceKey + sourceType derived from the path; for ad-hoc files outside that tree, pass sourceKey and sourceType explicitly.",
        },
        sourceKey: {
          type: "string",
          description:
            "Stable, idempotent key for the source (default: derived from filePath parent/stem). Re-ingesting the same key updates the existing RawSource row in place.",
        },
        sourceType: {
          type: "string",
          enum: ["paper", "article", "spec", "doc", "framework", "external-url"],
          description:
            "Source type override (default: derived from the parent directory name when it matches the registry).",
        },
        mode: {
          type: "string",
          enum: ["propose", "commit"],
          description:
            "'propose' returns the WikiDiffProposal without DB writes (good for human-in-the-loop review). 'commit' writes draft overlay pages and revisions. Default: 'propose'.",
        },
        acceptThreshold: {
          type: "number",
          description:
            "Minimum claim confidence to commit. Below this, claims are skipped (visible as skippedLowConfidence). Default 0.5. Ignored in propose mode.",
        },
        maxBodyDeltaRatio: {
          type: "number",
          description:
            "Cap on body growth per existing page in one commit cycle (default 0.3 = 30% per spec §6). Pages whose append would exceed the cap surface as errors instead of being auto-committed; the reviewer can resolve via manual edit.",
        },
        organizationId: {
          type: "string",
          description:
            "Org for the overlay commit. Default: the current session's org. Kernel-write attempts (organizationId resolves null) fail in commit mode.",
        },
      },
      required: ["filePath"],
    },
    requiredCapability: null,
    executionMode: "proposal",
    sideEffect: true,
    annotations: {
      readOnlyHint: false,
      destructiveHint: false,
      idempotentHint: true,
    },
  },
  // ─── EP-WIKI-001 coworker-UX: review pending overlay drafts ────────────────
  {
    name: "list_wiki_overlay_drafts",
    description:
      "List every wiki page in the current org's overlay that is still in draft status. Returns each page's slug, kind, abstract, body length + 800-char preview, kernel-override pointer, and the slug list of cited raw sources. Use this when the user asks 'what wiki drafts are pending review' or before walking them through a batch publish — the structured result feeds the review-wiki-drafts skill.",
    inputSchema: {
      type: "object",
      properties: {},
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
    },
  },
  // ─── EP-WIKI-001 coworker-UX: batch publish overlay drafts ─────────────────
  {
    name: "publish_wiki_overlay_pages",
    description:
      "Flip status on a batch of org-overlay wiki pages (default target: 'published'). Each flipped page gets a manual revision row attributed to the calling user. Kernel pages, cross-org pages, missing pages, and pages already at the target status are filtered out and reported in rejected[]. Use after walking the user through pending drafts via list_wiki_overlay_drafts — pass the ids the user said to keep.",
    inputSchema: {
      type: "object",
      properties: {
        pageIds: {
          type: "array",
          items: { type: "string" },
          description: "WikiPage row ids to flip.",
        },
        targetStatus: {
          type: "string",
          enum: ["draft", "published", "review-needed", "archived"],
          description: "Status to flip to (default 'published').",
        },
        changeSummary: {
          type: "string",
          description: "Optional summary written to the revision log for every flipped page.",
        },
      },
      required: ["pageIds"],
    },
    requiredCapability: null,
    executionMode: "proposal",
    sideEffect: true,
    annotations: {
      readOnlyHint: false,
      destructiveHint: false,
      idempotentHint: true,
    },
  },
  // ─── Knowledge Search ──────────────────────────────────────────────────────
  {
    name: "search_knowledge",
    description: "Search the platform knowledge base for relevant backlog items, epics, improvement proposals, and specs. Uses semantic similarity, not keyword matching.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "What to search for" },
        type: { type: "string", enum: ["backlog", "epic", "improvement", "spec"], description: "Filter by type (optional)" },
        limit: { type: "number", description: "Max results (default 5)" },
      },
      required: ["query"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
  },
  // ─── EP-KM-001: Knowledge Management Tools ─────────────────────────────────
  {
    name: "search_knowledge_base",
    description: "Search organizational knowledge articles (policies, processes, decisions, runbooks, reference material). Returns articles ranked by semantic relevance. Use this when the user asks about how things work, what the policy is, or needs procedural guidance.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "What to search for" },
        productId: { type: "string", description: "Filter to articles linked to this product (optional)" },
        portfolioId: { type: "string", description: "Filter to articles linked to this portfolio (optional)" },
        category: {
          type: "string",
          enum: ["process", "policy", "decision", "how-to", "reference", "troubleshooting", "runbook"],
          description: "Filter by category (optional)",
        },
        valueStream: {
          type: "string",
          enum: ["evaluate", "explore", "integrate", "deploy", "release", "operate", "consume"],
          description: "Filter by IT4IT value stream (optional)",
        },
        limit: { type: "number", description: "Max results (default 5)" },
      },
      required: ["query"],
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "create_knowledge_article",
    description: "Draft a new knowledge article. The article is created in 'draft' status and must be published separately. Use when the user asks to document a process, record a decision, or create a runbook.",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Article title" },
        body: { type: "string", description: "Article content in markdown" },
        category: {
          type: "string",
          enum: ["process", "policy", "decision", "how-to", "reference", "troubleshooting", "runbook"],
        },
        productIds: { type: "array", items: { type: "string" }, description: "Product IDs to link (optional)" },
        portfolioIds: { type: "array", items: { type: "string" }, description: "Portfolio IDs to link (optional)" },
        valueStreams: {
          type: "array",
          items: { type: "string", enum: ["evaluate", "explore", "integrate", "deploy", "release", "operate", "consume"] },
          description: "IT4IT value streams (optional)",
        },
        tags: { type: "array", items: { type: "string" }, description: "Free-form tags (optional)" },
      },
      required: ["title", "body", "category"],
    },
    requiredCapability: "manage_backlog",
    executionMode: "proposal",
    sideEffect: true,
  },
  {
    name: "flag_stale_knowledge",
    description: "Check for knowledge articles that haven't been reviewed within their review interval. Returns articles needing attention.",
    inputSchema: {
      type: "object",
      properties: {
        productId: { type: "string", description: "Filter to a specific product (optional)" },
        portfolioId: { type: "string", description: "Filter to a specific portfolio (optional)" },
      },
    },
    requiredCapability: null,
    executionMode: "immediate",
    sideEffect: false,
  },
  // ─── Endpoint Testing Tools ──────────────────────────────────────────────
  {
    name: "run_endpoint_tests",
    description: "Run the agent test harness against a scoped endpoint. On provider detail routes, omit endpointId to use the current provider and default to quick probes. Set allEndpoints=true or allModels=true only when the user explicitly asks for exhaustive diagnostics.",
    inputSchema: {
      type: "object",
      properties: {
        endpointId: { type: "string", description: "Provider endpoint to test. Defaults to the current /platform/ai/providers/:providerId route when available." },
        modelId: { type: "string", description: "Optional model profile to test. Defaults to a representative active model for the endpoint." },
        taskType: { type: "string", description: "Run only scenarios for this task type (default: all)" },
        probesOnly: { type: "boolean", description: "Run only capability probes, skip scenarios (default: true for coworker calls)" },
        allEndpoints: { type: "boolean", description: "Explicitly test every active LLM endpoint. Use only when the user asks for all endpoints." },
        allModels: { type: "boolean", description: "Test every active model profile for the endpoint. Use only for exhaustive diagnostics." },
      },
    },
    requiredCapability: "manage_capabilities",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "search_integrations",
    description: "Search the MCP integrations catalog for services relevant to a feature or business need. Use when the user asks what they can connect, or when researching integrations for a new feature.",
    inputSchema: {
      type: "object",
        properties: {
          query: { type: "string", description: "What you are looking for — e.g. 'payments', 'email marketing', 'booking calendar', 'source control'" },
          category: { type: "string", description: "Optional category filter — e.g. 'finance', 'cms', 'cloud', 'crm'" },
          archetypeId: { type: "string", description: "Optional archetype filter — returns integrations tagged as relevant to this archetype" },
          pricingModel: { type: "string", enum: ["free", "paid", "freemium", "open-source"], description: "Optional pricing filter" },
          benchmarkDomain: {
            type: "string",
            enum: [
              "hr_payroll",
              "identity_directory",
              "ticketing_service_desk",
              "rmm_endpoint_device_management",
              "documentation_knowledge_cmdb_assets",
              "crm_sales",
              "accounting_billing_payments",
              "communications_email_chat",
              "project_work_management",
              "cloud_m365_google_security",
            ],
            description: "Optional benchmark domain filter aligned to the integration-harness taxonomy",
          },
          businessProfile: {
            type: "string",
            enum: ["msp"],
            description: "Optional reusable business-profile overlay filter, starting with MSP",
          },
          deploymentMode: {
            type: "string",
            enum: ["cloud", "hybrid", "on_prem"],
            description: "Optional deployment posture filter for private/on-prem aware planning",
          },
          recommendedTreatment: {
            type: "string",
            enum: ["native_first_class", "generic_connector", "bundle_default"],
            description: "Optional DPF product-treatment filter from the benchmark model",
          },
          limit: { type: "number", description: "Max results to return. Default 10." },
        },
        required: ["query"],
    },
    requiredCapability: null,
  },
  {
    name: "search_tool_marketplace",
    description: "Search the cross-source tool marketplace readiness catalog. Use when the user asks what integrations, MCP servers, built-in tools, model requirements, or ungranted/unconfigured tools are available for an AI coworker.",
    inputSchema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Need or domain to search for, such as 'payroll', 'ADP', 'Build Studio', 'finance', or 'web search'.",
        },
        agentId: {
          type: "string",
          description: "Optional coworker id or slug to evaluate grants against. Defaults to the current coworker when available.",
        },
        includeKinds: {
          type: "array",
          items: { type: "string", enum: ["mcp", "native", "built_in", "model_requirement"] },
          description: "Optional source types to include.",
        },
        limit: { type: "number", description: "Max readiness entries to return. Default 12." },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: {
      readOnlyHint: true,
      idempotentHint: true,
    },
  },
  {
    name: "get_my_coworker_profile",
    description: "Read the current coworker's registry identity, skills, grants, latest self-assessment, and route context. Use before answering whether the coworker has the tools/capabilities to do a job.",
    inputSchema: {
      type: "object",
      properties: {},
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: {
      readOnlyHint: true,
      idempotentHint: true,
    },
  },
  {
    name: "assess_my_capabilities",
    description: "Build current self-assessment context for the current coworker, including profile, skills, grants, marketplace readiness, and the response shape expected for capability needs.",
    inputSchema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Optional domain or need to focus readiness lookup, such as 'marketing publishing' or 'incident response'.",
        },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: {
      readOnlyHint: true,
      idempotentHint: true,
    },
  },
  {
    name: "submit_coworker_capability_need",
    description: "Submit the current coworker's self-assessment and capability needs for human review. This creates CoworkerCapabilityNeed records, not direct BacklogItems.",
    inputSchema: {
      type: "object",
      properties: {
        verdict: { type: "string", enum: [...COWORKER_ASSESSMENT_VERDICTS] },
        confidence: { type: "string", enum: [...COWORKER_ASSESSMENT_CONFIDENCE] },
        missionSummary: { type: "string", description: "How the coworker understands its mission/job in one or two sentences." },
        capabilitySummary: { type: "string", description: "Short summary of what the coworker can do now and where it is limited." },
        rawPayload: { type: "object", description: "Optional raw structured assessment payload for audit." },
        needs: {
          type: "array",
          description: "Specific capability needs to submit for review.",
          items: {
            type: "object",
            properties: {
              kind: { type: "string", enum: [...COWORKER_CAPABILITY_NEED_KINDS] },
              severity: { type: "string", enum: [...COWORKER_CAPABILITY_NEED_SEVERITIES] },
              need: { type: "string" },
              blocks: { type: "string" },
              evidenceJson: { type: "object" },
              readinessJson: { type: "object" },
            },
            required: ["kind", "severity", "need", "blocks"],
          },
        },
      },
      required: ["verdict", "confidence", "needs"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: {
      destructiveHint: false,
      idempotentHint: false,
    },
  },
  {
    name: "list_my_capability_needs",
    description: "List capability needs previously submitted by the current coworker, optionally filtered by status, kind, or severity.",
    inputSchema: {
      type: "object",
      properties: {
        status: { type: "string", enum: [...COWORKER_CAPABILITY_NEED_STATUSES] },
        kind: { type: "string", enum: [...COWORKER_CAPABILITY_NEED_KINDS] },
        severity: { type: "string", enum: [...COWORKER_CAPABILITY_NEED_SEVERITIES] },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: {
      readOnlyHint: true,
      idempotentHint: true,
    },
  },
  {
    // BI-F9E7B780: governance-surface rollup. Same shape as
    // list_my_capability_needs but UN-scoped from the calling agent —
    // returns all coworker needs across all agents, plus filter-options
    // and counts-by-status/severity/kind summaries that the existing
    // /platform/ai/capability-needs admin page already renders. Lets
    // taxonomy audits query governed capability evidence without direct
    // SQL fallback.
    name: "list_all_capability_needs",
    description:
      "List capability needs submitted by ALL coworkers (not scoped to the calling agent), optionally filtered by agentId, status, kind, or severity. Returns the rich review shape used by the /platform/ai/capability-needs admin page: summary counts by status/severity/kind, filter-option enums, and the full need list. Read-only governance surface for taxonomy audits.",
    inputSchema: {
      type: "object",
      properties: {
        agentId: { type: "string", description: "Optional — filter to one coworker's needs. Omit to return all." },
        status: { type: "string", enum: [...COWORKER_CAPABILITY_NEED_STATUSES] },
        kind: { type: "string", enum: [...COWORKER_CAPABILITY_NEED_KINDS] },
        severity: { type: "string", enum: [...COWORKER_CAPABILITY_NEED_SEVERITIES] },
      },
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    buildPhases: ["ideate", "plan", "build", "review", "ship"],
    annotations: {
      readOnlyHint: true,
      idempotentHint: true,
    },
  },
  {
    name: "prefill_onboarding_wizard",
    description: "Pre-fill the regulation onboarding wizard with AI-drafted data. Stores a draft and returns the wizard URL for human review.",
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", description: "Full regulation/standard name" },
        shortName: { type: "string", description: "Abbreviation (e.g., GDPR, WCAG)" },
        sourceType: { type: "string", enum: ["external", "standard", "framework", "internal"], description: "Type of regulation/standard" },
        jurisdiction: { type: "string", description: "Geographic scope (e.g., EU, UK, Global)" },
        industry: { type: "string", description: "Industry applicability" },
        sourceUrl: { type: "string", description: "URL to official text" },
        obligations: {
          type: "array",
          description: "Extracted obligations",
          items: {
            type: "object",
            properties: {
              title: { type: "string" },
              reference: { type: "string" },
              category: { type: "string" },
              frequency: { type: "string" },
              applicability: { type: "string" },
              description: { type: "string" },
            },
            required: ["title"],
          },
        },
        suggestedControls: {
          type: "array",
          description: "Suggested control mappings",
          items: {
            type: "object",
            properties: {
              title: { type: "string" },
              controlType: { type: "string", enum: ["preventive", "detective", "corrective"] },
              linkedObligationIndices: { type: "array", items: { type: "number" } },
            },
            required: ["title", "controlType"],
          },
        },
      },
      required: ["name", "shortName", "sourceType"],
    },
    requiredCapability: "manage_compliance",
    sideEffect: true,
  },
  {
    name: "save_licensing_investigation",
    description: "Persist the current licensing investigation posture for the business without requiring every field to be re-entered. Use this to save whether the business is existing, new, or expanding, plus jurisdiction and research-confidence updates discovered during the coworker conversation.",
    inputSchema: {
      type: "object",
      properties: {
        setupStatus: { type: "string", enum: ["draft", "investigating", "ready", "blocked"] },
        investigationMode: { type: "string", enum: ["unknown", "existing", "new_business", "expanding"] },
        homeCountryCode: { type: "string", description: "Two-letter country code when known" },
        primaryRegionCode: { type: "string", description: "Primary state, province, or region code when known" },
        operatingFootprintSummary: { type: "string", description: "Short summary of where the business operates or delivers regulated work" },
        legalActivityConfidence: { type: "string", enum: ["low", "medium", "high"] },
        researchCoverageStatus: { type: "string", enum: ["draft", "partial", "covered", "stale"] },
        notes: { type: "string", description: "Investigation notes, official-source findings, or unresolved caveats" },
        appendNotes: { type: "boolean", description: "When true, append notes to the existing profile notes instead of replacing them" },
      },
      required: [],
    },
    requiredCapability: "manage_compliance",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "create_licensing_readiness_issue",
    description: "Create a factual licensing readiness issue for a missing authority, unresolved legality question, missing staff credential, fee blocker, or display/posting gap discovered during investigation.",
    inputSchema: {
      type: "object",
      properties: {
        issueType: { type: "string", description: "Short machine-readable issue type such as missing_jurisdiction_research or missing_person_credential" },
        severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
        title: { type: "string", description: "Human-readable issue title" },
        details: { type: "string", description: "Factual description of the gap, blocker, or unresolved question" },
        organizationLicenseRecordId: { type: "string", description: "Optional linked organization-held license record id" },
        personLicenseRecordId: { type: "string", description: "Optional linked person-held credential record id" },
      },
      required: ["issueType", "title"],
    },
    requiredCapability: "manage_compliance",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "evaluate_tool",
    description: "Initiate a tool evaluation pipeline for an external tool, MCP server, or dependency. Creates a ToolEvaluation record for multi-agent security, architecture, compliance, and integration review.",
    inputSchema: {
      type: "object",
      properties: {
        toolName: { type: "string", description: "Name of the tool to evaluate" },
        toolType: { type: "string", enum: ["mcp_server", "npm_package", "api_integration", "ai_provider", "docker_image"], description: "Type of tool" },
        version: { type: "string", description: "Version to evaluate (default: latest)" },
        sourceUrl: { type: "string", description: "Registry URL, GitHub repo, or vendor page" },
      },
      required: ["toolName", "toolType"],
    },
    requiredCapability: "manage_tool_evaluations",
    sideEffect: true,
  },
  {
    name: "summarize_estate_posture",
    description: "Summarize support lifecycle, evidence freshness, version confidence, and open posture issues for an estate item or the current product estate view.",
    inputSchema: {
      type: "object",
      properties: {
        entityId: { type: "string", description: "Specific estate item id (optional)" },
        entityKey: { type: "string", description: "Specific estate item key (optional)" },
        entityName: { type: "string", description: "Specific estate item name (optional)" },
      },
      required: [],
    },
    requiredCapability: "view_inventory",
    sideEffect: false,
  },
  {
    name: "review_estate_identity",
    description: "Explain what an estate item most likely is, who made it, how confident the identity evidence is, and what still needs review.",
    inputSchema: {
      type: "object",
      properties: {
        entityId: { type: "string", description: "Specific estate item id (optional)" },
        entityKey: { type: "string", description: "Specific estate item key (optional)" },
        entityName: { type: "string", description: "Specific estate item name (optional)" },
      },
      required: [],
    },
    requiredCapability: "view_inventory",
    sideEffect: false,
  },
  {
    name: "validate_version_confidence",
    description: "Explain how trustworthy the observed version is for a specific estate item, including whether it is normalized or only inferred from raw evidence.",
    inputSchema: {
      type: "object",
      properties: {
        entityId: { type: "string", description: "Specific estate item id (optional)" },
        entityKey: { type: "string", description: "Specific estate item key (optional)" },
        entityName: { type: "string", description: "Specific estate item name (optional)" },
      },
      required: [],
    },
    requiredCapability: "view_inventory",
    sideEffect: false,
  },
  {
    name: "explain_blast_radius",
    description: "Explain the upstream dependencies and downstream impact for a specific estate item using the shared dependency model.",
    inputSchema: {
      type: "object",
      properties: {
        entityId: { type: "string", description: "Specific estate item id (optional)" },
        entityKey: { type: "string", description: "Specific estate item key (optional)" },
        entityName: { type: "string", description: "Specific estate item name (optional)" },
      },
      required: [],
    },
    requiredCapability: "view_inventory",
    sideEffect: false,
  },
  {
    name: "discovery_sweep",
    description: "Run a fresh discovery pass to refresh attribution, topology, and evidence quality across the estate.",
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
    requiredCapability: "manage_provider_connections",
    sideEffect: true,
  },
  {
    name: "run_discovery_triage",
    description: "Run a single discovery triage pass over needs-review and weakly resolved inventory, applying safe high-confidence matches and returning a summary of the run.",
    inputSchema: {
      type: "object",
      properties: {
        trigger: { type: "string", enum: ["cadence", "volume"], default: "cadence" },
      },
      required: [],
    },
    requiredCapability: "manage_provider_connections",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "run_hive_scout_ingest",
    description: "Run one external catalog scouting pass, diff the results against DPF coworker and skill coverage, and create deduplicated backlog suggestions for any uncovered archetype gaps.",
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
    requiredCapability: "manage_backlog",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "attribute_entity_to_product",
    description: "Link a discovered inventory entity to a portfolio taxonomy node so it counts toward the managed product estate. Use to resolve catalog_match_ambiguous or attribution_gap quality issues.",
    inputSchema: {
      type: "object",
      properties: {
        entityId: { type: "string", description: "InventoryEntity id (cuid) to attribute" },
        taxonomyNodeId: { type: "string", description: "TaxonomyNode cuid — use this OR taxonomyNodeSlug" },
        taxonomyNodeSlug: { type: "string", description: "Taxonomy nodeId path (e.g. 'foundational/compute/physical_compute')" },
      },
      required: ["entityId"],
    },
    requiredCapability: "manage_provider_connections",
    sideEffect: true,
  },
  {
    name: "dismiss_entity",
    description: "Mark a discovered inventory entity as dismissed so it no longer shows in the attribution review queue. Use when the item is noise, out of scope, or superseded.",
    inputSchema: {
      type: "object",
      properties: {
        entityId: { type: "string", description: "InventoryEntity id (cuid) to dismiss" },
      },
      required: ["entityId"],
    },
    requiredCapability: "manage_provider_connections",
    sideEffect: true,
  },
  {
    name: "resolve_portfolio_quality_issue",
    description: "Close a PortfolioQualityIssue by marking it resolved or dismissed. Use after the underlying cause has been fixed (e.g. entity attributed, relationship refreshed) or when the issue does not apply.",
    inputSchema: {
      type: "object",
      properties: {
        issueId: { type: "string", description: "PortfolioQualityIssue cuid" },
        resolution: { type: "string", enum: ["resolved", "dismissed"], description: "'resolved' if the cause was fixed; 'dismissed' if the issue does not apply" },
      },
      required: ["issueId", "resolution"],
    },
    requiredCapability: "manage_provider_connections",
    sideEffect: true,
  },
  {
    name: "configure_gateway_scan",
    description: "Create or update a DiscoveryConnection for an ARP/subnet gateway scan so physical LAN segments (e.g. 192.168.x) become visible. Use to resolve gateway_connection_needed issues.",
    inputSchema: {
      type: "object",
      properties: {
        name:             { type: "string", description: "Human label for the connection (e.g. 'Home LAN 192.168.0.0/24')" },
        endpointUrl:      { type: "string", description: "Gateway URL or scan endpoint (e.g. http://192.168.0.1 or arp://192.168.0.0/24)" },
        apiKey:           { type: "string", description: "Optional API key (encrypted at rest)" },
        collectorType:    { type: "string", enum: ["arp_scan", "unifi"], description: "Defaults to 'arp_scan' for generic subnet discovery" },
        gatewayEntityId:  { type: "string", description: "Optional InventoryEntity cuid of the gateway device" },
        configuration:    { type: "object", description: "Collector-specific configuration JSON (e.g. { subnet: '192.168.0.0/24' })" },
      },
      required: ["name", "endpointUrl"],
    },
    requiredCapability: "manage_provider_connections",
    sideEffect: true,
  },

  // ── EA / Ontology Graph ─────────────────────────────────────────────────────
  {
    name: "create_ea_element",
    description: "Create a new element in the ontology graph. Use when a user describes a new architectural entity (product, component, actor, service, etc). Defaults to refinementLevel=conceptual.",
    inputSchema: {
      type: "object",
      properties: {
        name:             { type: "string", description: "Element name" },
        elementTypeSlug:  { type: "string", description: "Element type slug (e.g. digital_product, application_component, business_actor, ai_coworker)" },
        description:      { type: "string", description: "Optional description" },
        refinementLevel:  { type: "string", enum: ["conceptual", "logical", "actual"], description: "Defaults to conceptual" },
        itValueStream:    { type: "string", enum: ["evaluate", "explore", "integrate", "deploy", "release", "consume", "operate"] },
        ontologyRole:     { type: "string", enum: ["governed_thing", "actor", "control", "event_evidence", "information_object", "resource", "offer"] },
        digitalProductId: { type: "string" },
        portfolioId:      { type: "string" },
        properties:       { type: "object" },
      },
      required: ["name", "elementTypeSlug"],
    },
    requiredCapability: "manage_ea_model",
    sideEffect: true,
  },
  {
    name: "create_ea_relationship",
    description: "Connect two ontology graph elements with a typed relationship. Validates against EaRelationshipRule before creating.",
    inputSchema: {
      type: "object",
      properties: {
        fromElementId:        { type: "string" },
        toElementId:          { type: "string" },
        relationshipTypeSlug: { type: "string", enum: ["realizes", "depends_on", "assigned_to", "composed_of", "associated_with", "influences", "triggers", "flows_to", "serves", "accesses"] },
        properties:           { type: "object" },
      },
      required: ["fromElementId", "toElementId", "relationshipTypeSlug"],
    },
    requiredCapability: "manage_ea_model",
    sideEffect: true,
  },
  {
    name: "classify_ea_element",
    description: "Advance an element's IT4IT value stream stage and/or refinement level. Call after the user confirms what stage their architecture work is in.",
    inputSchema: {
      type: "object",
      properties: {
        elementId:       { type: "string" },
        itValueStream:   { type: "string", enum: ["evaluate", "explore", "integrate", "deploy", "release", "consume", "operate"] },
        refinementLevel: { type: "string", enum: ["conceptual", "logical", "actual"] },
        ontologyRole:    { type: "string", enum: ["governed_thing", "actor", "control", "event_evidence", "information_object", "resource", "offer"] },
      },
      required: ["elementId"],
    },
    requiredCapability: "manage_ea_model",
    sideEffect: true,
  },
  {
    name: "query_ontology_graph",
    description: "Query ontology graph elements with filters. Use before creating elements to avoid duplicates. Returns element IDs, names, types, and refinement levels.",
    inputSchema: {
      type: "object",
      properties: {
        elementTypeSlugs:     { type: "array", items: { type: "string" }, description: "Filter by element type slugs" },
        refinementLevel:      { type: "string", enum: ["conceptual", "logical", "actual"] },
        itValueStream:        { type: "string" },
        ontologyRole:         { type: "string" },
        digitalProductId:     { type: "string" },
        portfolioId:          { type: "string" },
        nameContains:         { type: "string" },
        includeRelationships: { type: "boolean" },
        limit:                { type: "number", description: "Max results, default 20" },
      },
    },
    requiredCapability: "view_ea_modeler",
    sideEffect: false,
  },
  {
    name: "run_traversal_pattern",
    description: "Run a named bounded analysis pattern (e.g. blast_radius, governance_audit, ma_separation) from one or more starting elements. Returns traversal paths and summary.",
    inputSchema: {
      type: "object",
      properties: {
        patternSlug:     { type: "string", enum: ["blast_radius", "governance_audit", "architecture_traceability", "ai_oversight", "cost_rollup", "ma_separation", "service_customer_impact"] },
        startElementIds: { type: "array", items: { type: "string" } },
        maxDepth:        { type: "number" },
      },
      required: ["patternSlug", "startElementIds"],
    },
    requiredCapability: "view_ea_modeler",
    sideEffect: false,
  },
  {
    name: "import_archimate",
    description: "Import a .archimate XML file from the Archi tool into the ontology graph. All elements are created as draft/conceptual. Max file size: 1 MB base64.",
    inputSchema: {
      type: "object",
      properties: {
        fileContentBase64:      { type: "string", description: "Base64-encoded .archimate XML content" },
        fileName:               { type: "string" },
        targetPortfolioId:      { type: "string" },
        targetDigitalProductId: { type: "string" },
      },
      required: ["fileContentBase64", "fileName"],
    },
    requiredCapability: "manage_ea_model",
    sideEffect: true,
  },
  {
    name: "export_archimate",
    description: "Export elements scoped to a portfolio, digital product, or view as a .archimate XML file. Extension types are mapped to standard ArchiMate types with dpf: properties for round-trip fidelity.",
    inputSchema: {
      type: "object",
      properties: {
        scopeType: { type: "string", enum: ["view", "portfolio", "digital_product"] },
        scopeRef:  { type: "string", description: "ID of the view, portfolio, or digital product" },
        fileName:  { type: "string", description: "Output filename (optional)" },
      },
      required: ["scopeType", "scopeRef"],
    },
    requiredCapability: "view_ea_modeler",
    sideEffect: false,
  },
  // ─── Agent Thread Spawning Tools ─────────────────────────────────────────────
  {
    name: "spawn_work_thread",
    description: "Start a child coworker thread from the current thread. Use for delegated work that should report back separately.",
    inputSchema: {
      type: "object",
      properties: {
        objective: { type: "string", description: "The delegated work objective." },
        title: { type: "string", description: "Optional short title for the delegated work." },
      },
      required: ["objective"],
    },
    requiredCapability: null,
    sideEffect: true,
  },
  {
    name: "cancel_thread",
    description: "Cancel a coworker child thread. Defaults to the current thread when no threadId is supplied.",
    inputSchema: {
      type: "object",
      properties: {
        threadId: { type: "string", description: "Thread id to cancel. Defaults to the current thread." },
      },
    },
    requiredCapability: null,
    sideEffect: true,
  },
  {
    name: "get_thread_result",
    description: "Poll a coworker child thread until it completes and return its result.",
    inputSchema: {
      type: "object",
      properties: {
        threadId: { type: "string", description: "Child thread id to read." },
      },
      required: ["threadId"],
    },
    requiredCapability: null,
    sideEffect: false,
  },
  {
    name: "get_child_threads",
    description: "List child coworker threads for the current thread with their latest objective and status.",
    inputSchema: {
      type: "object",
      properties: {},
    },
    requiredCapability: null,
    sideEffect: false,
  },
  // ─── Deliberation Pattern Framework Tools (spec §6.8) ──────────────────────
  {
    name: "start_deliberation",
    description:
      "Start a multi-branch deliberation run (peer review or debate) over an artifact. The activation resolver decides which pattern fires based on stage, risk, and the caller's explicit request. Stage-default and risk-escalated invocations are pre-authorized; explicit invocations require proposal review. Returns the new deliberationRunId synchronously; branches dispatch asynchronously via the Inngest runner.",
    inputSchema: {
      type: "object",
      properties: {
        patternSlug: {
          type: "string",
          description:
            "Explicit deliberation pattern slug (e.g. 'review', 'debate'). Used as explicitPatternSlug in the activation resolver — may be strengthened by stage/risk policy.",
        },
        taskRunId: {
          type: "string",
          description: "Optional parent TaskRun (external id). When omitted, the orchestrator bootstraps one.",
        },
        artifactType: {
          type: "string",
          enum: [...DELIBERATION_ARTIFACT_TYPES],
          description: "The kind of artifact being deliberated.",
        },
        strategyProfile: {
          type: "string",
          enum: [...DELIBERATION_STRATEGY_PROFILES],
          description: "Optional override for the cost/quality trade-off profile. Defaults to the pattern's declared hint.",
        },
        maxBranches: {
          type: "number",
          description: "Upper bound on branch count. Defaults to the pattern's required role count capped at 4.",
        },
        budgetUsd: {
          type: "number",
          description: "Upper bound on total USD spend across the run. Null = unbounded.",
        },
        stage: {
          type: "string",
          enum: ["ideate", "plan", "build", "review", "ship"],
          description: "Build Studio stage the deliberation runs under. Drives stage-default activation.",
        },
        riskLevel: {
          type: "string",
          enum: ["low", "medium", "high", "critical"],
          description: "Risk level of the work. Medium+ escalates activation.",
        },
        triggerSource: {
          type: "string",
          enum: [...DELIBERATION_TRIGGER_SOURCES],
          description:
            "Caller's intent signal for the activation layer. Used by autoApproveWhen to decide whether the call skips proposal review.",
        },
        routeContext: {
          type: "string",
          description: "Optional route / screen context (e.g. '/build') for telemetry and downstream routing.",
        },
      },
      required: ["patternSlug", "artifactType"],
    },
    requiredCapability: "view_platform",
    executionMode: "proposal",
    sideEffect: true,
    // Pre-authorize stage-default and risk-escalated invocations per activation
    // policy (spec §7). Explicit invocations still go through proposal review.
    // Memory: "Proposal-mode tools stall autonomous runs" — predicates inspect
    // caller-supplied params so the check runs before the run row exists.
    autoApproveWhen: async (ctx: unknown) => {
      const params =
        (ctx as { params?: Record<string, unknown> } | undefined)?.params ?? {};
      const raw = params["triggerSource"];
      return raw === "stage" || raw === "risk";
    },
  },
  {
    name: "get_deliberation_status",
    description:
      "Read-only snapshot of a deliberation run — consensus state, branch counts (total/completed/failed/pending), evidence coverage (source-backed/mixed/needs-more-evidence), and budget/diversity degradation flags.",
    inputSchema: {
      type: "object",
      properties: {
        deliberationRunId: {
          type: "string",
          description: "The DeliberationRun id returned from start_deliberation.",
        },
      },
      required: ["deliberationRunId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "get_deliberation_outcome",
    description:
      "Read-only full outcome for a deliberation run — merged recommendation, rationale, confidence, unresolved risks, issue set, and compact claim + evidence-bundle references. Returns null outcome when synthesis has not yet completed.",
    inputSchema: {
      type: "object",
      properties: {
        deliberationRunId: {
          type: "string",
          description: "The DeliberationRun id returned from start_deliberation.",
        },
      },
      required: ["deliberationRunId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
  },
  {
    name: "trigger_contributor_inventory_sync",
    description:
      "Dispatch an on-demand contributor inventory sync (git worktrees, branches, GitHub PRs) without waiting for the 10-minute cron. Used by agents that just made an external change (pushed a branch, opened a PR) and want the /platform/development/change-lanes dashboard to reflect it on the next refresh. Returns the Inngest event id immediately; the runner creates the ContributorInventorySyncRun row asynchronously.",
    inputSchema: {
      type: "object",
      properties: {
        reason: {
          type: "string",
          description: "Optional short tag propagated to the run row's triggeredBy field for audit.",
        },
      },
    },
    requiredCapability: "manage_provider_connections",
    executionMode: "immediate",
    sideEffect: true,
  },
  {
    name: "summarize_upgrade_impact",
    description:
      "On-demand, install-tailored \"What's in this update?\" summary (BI-C26F7EE1). Compares the upstream lineage marker (latest succeeded SelfUpgradeRun.targetSha) to the resolved upstream HEAD, classifies the change set by Conventional Commit type, scores each commit's relevance to this install (archetype, industry, customization paths, open quality-issue themes), and returns a headline + ordered top-N items (most impactful first) plus the full list and a 'touches your customizations' callout that doubles as a §5.0 merge-conflict early warning. Advisory only — never queues or applies an upgrade. Cacheable per (currentLineageSha, targetSha); set refresh=true to bypass.",
    inputSchema: {
      type: "object",
      properties: {
        refresh: {
          type: "boolean",
          description: "Bypass the per-process cache and recompute. Default false.",
        },
        topN: {
          type: "number",
          description: "Cap on items in the headline list (default 8). The full list is always returned alongside.",
        },
        skipPhrasing: {
          type: "boolean",
          description: "Return only the deterministic shape (no LLM phrasing). Default false. Useful when an external client wants to render its own copy.",
        },
      },
      required: [],
    },
    requiredCapability: "view_operations",
    executionMode: "immediate",
    sideEffect: false,
  },

  // ─── Pseudo-User Contract: screen_* view-command tool family (BI-DF6079E9) ──
  // Spec §6.2. Each tool returns a structured `data.event` payload describing
  // the requested screen action; the chat SSE stream emits the payload and the
  // client-side relay (AgentCoworkerPanel.tsx) dispatches it as a window
  // CustomEvent that the active page handles via its registered ScreenManifest
  // (BI-D9487754). Per-tool MCP annotations are exact — destructiveHint flags
  // propose_action and dispatch_action explicitly; the rest are advisory UI
  // mutations the user can still see and override.
  //
  // The SSE emission + window-event relay plumbing lands as the follow-on
  // half of this BI. For now the handlers return the event payload
  // structurally so the chat surface is queryable and grant-checked.

  {
    name: "screen_describe",
    description:
      "Pseudo-User Contract (spec §6.1, BI-D9487754/BI-DF6079E9). Return the active ScreenManifest summary for the chat's current routeContext — the list of selections, navigations, panels, forms, and domain actions the coworker may invoke on this page. Read-only. Call once at the start of a turn to discover what's available before dispatching screen_* actions. Returns the manifest's serialisable metadata; live handlers stay on the client.",
    inputSchema: { type: "object", properties: {}, required: [] },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
  },
  {
    name: "screen_get_state",
    description:
      "Pseudo-User Contract. Return the current observable state of the active screen — selected entities (by selectionId), focused field id, open panel ids, current scroll anchor, current form values. Read-only. Source of truth is the page; this tool returns the snapshot the chat session plumbed in via screenState. Call screen_describe first for the manifest shape, then screen_get_state for the live values.",
    inputSchema: { type: "object", properties: {}, required: [] },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
  },
  {
    name: "screen_select_entity",
    description:
      "Pseudo-User Contract. Change the active screen's selection — e.g. switch which FeatureBuild is currently focused in Build Studio. The selectionId must match one declared by the active ScreenManifest; the entityId must come from that selection's listSource. Dispatches via the chat SSE channel; the page applies the change through its registered handler.",
    inputSchema: {
      type: "object",
      properties: {
        selectionId: { type: "string", description: "Manifest selection id (e.g. \"build\")." },
        entityId: { type: "string", description: "Id of the entity to select (from the manifest's listSource enumeration)." },
      },
      required: ["selectionId", "entityId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    screenSurface: "*",
  },
  {
    name: "screen_navigate",
    description:
      "Pseudo-User Contract. Navigate to another route within the portal — e.g. /build → /storefront. Conditionally destructive: if the current page has unsaved form state the chat handler auto-wraps the call in an envelope (spec §6.4). Cross-coworker-ownership navigations will fire a HITL handoff gate when BI-A32801C5 lands.",
    inputSchema: {
      type: "object",
      properties: {
        route: { type: "string", description: "Target route path, e.g. \"/build/FB-5E20E793\"." },
        params: { type: "object", description: "Optional path/query parameters." },
      },
      required: ["route"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    screenSurface: "*",
  },
  {
    name: "screen_open_panel",
    description:
      "Pseudo-User Contract. Open a panel registered in the active manifest — drawer, collapsible section, modal. Idempotent: opening an already-open panel is a no-op.",
    inputSchema: {
      type: "object",
      properties: {
        panelId: { type: "string", description: "Manifest panel id." },
      },
      required: ["panelId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    screenSurface: "*",
  },
  {
    name: "screen_close_panel",
    description:
      "Pseudo-User Contract. Close a panel registered in the active manifest. Idempotent.",
    inputSchema: {
      type: "object",
      properties: {
        panelId: { type: "string", description: "Manifest panel id." },
      },
      required: ["panelId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    screenSurface: "*",
  },
  {
    name: "screen_focus_field",
    description:
      "Pseudo-User Contract. Focus a specific form field on the active screen — useful when guiding the user to fix a validation error or fill in a missing value. The field must belong to a form declared in the manifest. Idempotent; cosmetic if the field is already focused.",
    inputSchema: {
      type: "object",
      properties: {
        formId: { type: "string", description: "Manifest form id." },
        fieldId: { type: "string", description: "Field id within the form." },
      },
      required: ["formId", "fieldId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    screenSurface: "*",
  },
  {
    name: "screen_set_input",
    description:
      "Pseudo-User Contract. Pre-fill a form field on the user's behalf. The user can still review and modify before submit; the visual cue contract (BI-5696B4D7) highlights coworker-set fields with an amber affordance + \"Set by <Coworker>\" tooltip. Not destructive by itself (reversible until submit) — submit is the destructive action and goes through the envelope flow.",
    inputSchema: {
      type: "object",
      properties: {
        formId: { type: "string", description: "Manifest form id." },
        fieldId: { type: "string", description: "Field id within the form." },
        value: { description: "Value to set. Type depends on the field's declared type." },
      },
      required: ["formId", "fieldId", "value"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    screenSurface: "*",
  },
  {
    name: "screen_scroll_to",
    description:
      "Pseudo-User Contract. Scroll the page to a named anchor — useful when surfacing a row, section, or message that's currently off-screen. Read-class side effect (page state, no domain mutation).",
    inputSchema: {
      type: "object",
      properties: {
        anchor: { type: "string", description: "Anchor id, route fragment, or manifest-declared landmark." },
      },
      required: ["anchor"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: false,
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    screenSurface: "*",
  },
  {
    name: "screen_propose_action",
    description:
      "Pseudo-User Contract (spec §6.4 — destructive-action envelope flow). Propose a domain action that needs explicit user approval before execution. Creates a CoworkerActionEnvelope (BI-D887CD3B schema) in `proposed` status, emits the proposal to the chat UI, and returns the envelope id so the chat handler can correlate the user's approve/deny response. Subject to the per-turn N=3 destructive auto-approval cap (spec §6.4); irreversible actions require an explicit typed-phrase floor and ignore elevation.",
    inputSchema: {
      type: "object",
      properties: {
        manifestActionId: { type: "string", description: "actionId from the active manifest's domainActions list." },
        args: { type: "object", description: "Arguments to pass to the underlying MCP tool when the envelope is approved." },
        rationale: { type: "string", description: "One-line plain-English reason shown to the user in the approval card." },
      },
      required: ["manifestActionId", "rationale"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false, irreversibleHint: false },
    screenSurface: "*",
  },
  {
    name: "screen_dispatch_action",
    description:
      "Pseudo-User Contract. Execute a previously-proposed envelope by id once it's been approved (spec §6.4). Resolves the underlying tool from the envelope's manifestActionId + the active manifest's domainActions, runs it with the merged args, and records the outcome on the envelope (`executed` | `failed`). Destructive in the same sense as the underlying tool — the destructiveHint reflects the proposal's intent.",
    inputSchema: {
      type: "object",
      properties: {
        envelopeId: { type: "string", description: "CoworkerActionEnvelope id returned by screen_propose_action." },
      },
      required: ["envelopeId"],
    },
    requiredCapability: "view_platform",
    executionMode: "immediate",
    sideEffect: true,
    annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false, irreversibleHint: false },
    screenSurface: "*",
  },
];

// ─── Capability Filtering ────────────────────────────────────────────────────

export async function getAvailableTools(
  userContext: UserContext,
  options?: { externalAccessEnabled?: boolean; mode?: "advise" | "act"; unifiedMode?: boolean; agentId?: string },
): Promise<ToolDefinition[]> {
  let platformTools = PLATFORM_TOOLS.filter(
    (tool) =>
      (options?.unifiedMode || !tool.requiresExternalAccess || options?.externalAccessEnabled === true)
      && (tool.requiredCapability === null || can(userContext, tool.requiredCapability))
      && (options?.mode !== "advise" || !tool.sideEffect),
  );

  // Agent-scoped filtering: intersection of user capabilities and agent tool grants.
  // EP-AI-WORKFORCE-001: use the async DB-first resolver so grants written via
  // the DB (e.g. via seed or Admin UI) take precedence over the JSON fallback.
  if (options?.agentId) {
    const { getAgentToolGrantsAsync, isToolAllowedByGrants } = await import("./agent-grants");
    const agentGrants = await getAgentToolGrantsAsync(options.agentId);
    if (agentGrants.length > 0) {
      platformTools = platformTools.filter((tool) => isToolAllowedByGrants(tool.name, agentGrants));
    }
  }

  if (options?.externalAccessEnabled) {
    try {
      const { getMcpServerTools } = await import("./mcp-server-tools");
      const mcpTools = await getMcpServerTools();
      const filtered = options?.mode === "advise" ? [] : mcpTools;
      return [...platformTools, ...filtered];
    } catch {
      // MCP server tools unavailable — return platform tools only
    }
  }

  return platformTools;
}

// ─── Tool Execution ──────────────────────────────────────────────────────────

// ─── Helpers ──────────────────────────────────────────────────────────────────

/** Fire-and-forget: log tool activity for the Build Studio activity timeline. */
function logBuildActivity(buildId: string, tool: string, summary: string): void {
  prisma.buildActivity.create({ data: { buildId, tool, summary } }).catch(() => {});
}

function logAdminActivity(
  userId: string, toolName: string, parameters: Record<string, unknown>,
  result: string, tier: number, summary?: string,
): Promise<void> {
  return prisma.adminActivity.create({
    data: { userId, toolName, parameters: parameters as any, result, tier, summary: summary?.slice(0, 500) },
  }).then(() => {}).catch(() => {});
}

/**
 * Phases that exclude a FeatureBuild from "active" auto-resolution.
 * `abandoned` is included because abandoned builds from prior runs would
 * otherwise shadow the freshly promoted build (BI-E9CD1B92, 2026-05-13).
 */
const TERMINAL_BUILD_PHASES = ["complete", "failed", "abandoned"] as const;

/**
 * Pull a well-formed `buildId` hint out of a tool's params bag.
 * Returns null when the hint is missing, non-string, or doesn't have the
 * `FB-` prefix that all real FeatureBuild IDs carry.
 */
function extractBuildIdHint(params: Record<string, unknown>): string | null {
  const v = params["buildId"];
  if (typeof v !== "string") return null;
  const trimmed = v.trim();
  return trimmed.startsWith("FB-") ? trimmed : null;
}

/**
 * Resolve the active FeatureBuild for the current user.
 *
 * When `buildIdHint` is supplied AND it resolves to an existing build the
 * caller is allowed to act on, that hint wins — even if the user has a more
 * recently updated build. This is how explicit `buildId` arguments from MCP
 * tool calls reach the per-tool handlers without being silently overridden
 * (the bug behind FB-1D69766D returning FB-72EB9C06's review).
 *
 * Fallback: most-recently-updated non-terminal build created by the user.
 */
async function resolveActiveBuildId(
  userId: string,
  buildIdHint?: string | null,
): Promise<string | null> {
  if (buildIdHint && buildIdHint.startsWith("FB-")) {
    const hinted = await prisma.featureBuild.findUnique({
      where: { buildId: buildIdHint },
      select: { buildId: true, createdById: true },
    });
    // Access model today is owner-only — see getFeatureBuildForContext for the
    // matching check. If a future grant model lands, expand this predicate.
    if (hinted && hinted.createdById === userId) return hinted.buildId;
  }
  const build = await prisma.featureBuild.findFirst({
    where: { createdById: userId, phase: { notIn: [...TERMINAL_BUILD_PHASES] } },
    orderBy: { updatedAt: "desc" },
    select: { buildId: true },
  });
  return build?.buildId ?? null;
}

async function updateBuildHappyPathState(
  userId: string,
  patch: Parameters<typeof mergeHappyPathStateIntoPlan>[1],
  buildId?: string | null,
): Promise<void> {
  const resolvedBuildId = buildId ?? await resolveActiveBuildId(userId);
  if (!resolvedBuildId) return;

  const build = await prisma.featureBuild.findUnique({
    where: { buildId: resolvedBuildId },
    select: { plan: true },
  });
  if (!build) return;

  const mergedPlan = mergeHappyPathStateIntoPlan(
    (build.plan as Record<string, unknown> | null) ?? null,
    patch,
  );

  await prisma.featureBuild.update({
    where: { buildId: resolvedBuildId },
    data: { plan: mergedPlan as import("@dpf/db").Prisma.InputJsonValue },
  });
}

function requireCurrentCoworker(context?: { agentId?: string }): string {
  const agentId = context?.agentId?.trim();
  if (!agentId) {
    throw new Error("A current coworker agentId is required for this tool.");
  }
  return agentId;
}

function routeValueStream(capability: CapabilityKey | null): string {
  return capability?.replace(/^(view|manage)_/, "") || "cross-cutting";
}

async function loadRouteDefinedCoworkerProfile(agentId: string, routeContext?: string | null) {
  const routeEntry = ROUTE_AGENT_MAP_ENTRIES.find(([, entry]) => entry.agentId === agentId)?.[1] ?? null;
  if (!routeEntry) return null;

  const { getAgentToolGrants } = await import("@/lib/tak/agent-grants");
  const grants = getAgentToolGrants(agentId) ?? ["registry_read"];

  return {
    profile: {
      agentId: routeEntry.agentId,
      slugId: routeEntry.agentId,
      name: routeEntry.agentName,
      tier: routeEntry.sensitivity === "restricted" ? 1 : 2,
      type: "specialist",
      description: routeEntry.agentDescription,
      valueStream: routeValueStream(routeEntry.capability),
      it4itSections: [],
      lifecycleStage: "production",
      hitlTierDefault: routeEntry.sensitivity === "public" ? 3 : 1,
      humanSupervisorId: null,
      escalatesTo: null,
      delegatesTo: [],
      routeContext: routeContext ?? null,
      grants,
      skills: routeEntry.skills.map((skill, index) => ({
        label: skill.label,
        description: skill.description,
        capability: skill.capability,
        taskType: skill.taskType ?? "conversation",
        sortOrder: index,
      })),
    },
    latestAssessment: null,
  };
}

async function loadCoworkerProfile(agentId: string, routeContext?: string | null) {
  const agent = await prisma.agent.findFirst({
    where: { OR: [{ agentId }, { slugId: agentId }] },
    include: {
      skills: {
        orderBy: { sortOrder: "asc" },
        select: {
          label: true,
          description: true,
          capability: true,
          taskType: true,
          sortOrder: true,
        },
      },
      toolGrants: {
        select: { grantKey: true },
        orderBy: { grantKey: "asc" },
      },
      coworkerAssessments: {
        orderBy: { createdAt: "desc" },
        take: 1,
        select: {
          assessmentId: true,
          verdict: true,
          confidence: true,
          createdAt: true,
        },
      },
    },
  });

  if (!agent) {
    const routeDefinedProfile = await loadRouteDefinedCoworkerProfile(agentId, routeContext);
    if (routeDefinedProfile) {
      return routeDefinedProfile;
    }

    throw new Error(`Coworker ${agentId} was not found.`);
  }

  const latestAssessment = agent.coworkerAssessments[0] ?? null;
  return {
    profile: {
      agentId: agent.agentId,
      slugId: agent.slugId,
      name: agent.name,
      tier: agent.tier,
      type: agent.type,
      description: agent.description,
      valueStream: agent.valueStream,
      it4itSections: agent.it4itSections,
      lifecycleStage: agent.lifecycleStage,
      hitlTierDefault: agent.hitlTierDefault,
      humanSupervisorId: agent.humanSupervisorId,
      escalatesTo: agent.escalatesTo,
      delegatesTo: agent.delegatesTo,
      routeContext: routeContext ?? null,
      grants: agent.toolGrants.map((grant) => grant.grantKey),
      skills: agent.skills,
    },
    latestAssessment,
  };
}

function parseCapabilityNeeds(rawNeeds: unknown): CoworkerCapabilityNeedInput[] {
  if (!Array.isArray(rawNeeds)) return [];

  return rawNeeds
    .filter((need): need is Record<string, unknown> => need != null && typeof need === "object")
    .filter((need): need is Record<string, unknown> =>
      COWORKER_CAPABILITY_NEED_KINDS.includes(need.kind as CoworkerCapabilityNeedKind))
    .map((need) => ({
      kind: need.kind as CoworkerCapabilityNeedKind,
      severity: COWORKER_CAPABILITY_NEED_SEVERITIES.includes(need.severity as CoworkerCapabilityNeedSeverity)
        ? need.severity as CoworkerCapabilityNeedSeverity
        : "important",
      need: String(need.need ?? "").trim(),
      blocks: String(need.blocks ?? "").trim(),
      evidenceJson: need.evidenceJson != null && typeof need.evidenceJson === "object"
        ? need.evidenceJson as Record<string, unknown>
        : undefined,
      readinessJson: need.readinessJson != null && typeof need.readinessJson === "object"
        ? need.readinessJson as Record<string, unknown>
        : undefined,
    }))
    .filter((need) => need.need.length > 0 && need.blocks.length > 0);
}

function redactFunctionalFailureText(text: string): string {
  return text
    .replace(/\bdpfmcp_[A-Za-z0-9_-]+/g, "[redacted-token]")
    .replace(/\bBearer\s+[A-Za-z0-9._-]+/g, "[redacted-token]");
}

// BI-F4A30FCB (Dale dogfood 2026-05-24): see ideate-build-resolution.ts for
// the why. Re-exported here so existing imports keep working; new callers
// should import from "@/lib/build/ideate-build-resolution".
import { resolveIdeateBuildForToolPure } from "@/lib/build/ideate-build-resolution";

export async function resolveIdeateBuildForTool(args: {
  contextBuildId?: string;
  toolName: string;
}) {
  return resolveIdeateBuildForToolPure(args, {
    findUniqueBuild: async (buildId) =>
      prisma.featureBuild.findUnique({
        where: { buildId },
        select: { buildId: true, phase: true },
      }),
    findIdeateBuilds: async () =>
      prisma.featureBuild.findMany({
        where: { phase: "ideate" },
        orderBy: { updatedAt: "desc" },
        select: { buildId: true },
        take: 2, // only need to distinguish 0 / 1 / 2+
      }),
  });
}

export async function executeTool(
  toolName: string,
  rawParams: Record<string, unknown>,
  userId: string,
  context?: ToolExecutionContext,
): Promise<ToolResult> {
  // Strip empty optional object params that models send as schema artifacts
  const params = sanitizeToolParams(toolName, rawParams);

  // ─── Build-context hint (BI-2DAB02B4 / BI-F4A30FCB) ────────────────────────
  // When the coworker is messaging from a specific build, that build is plumbed
  // here as context.featureBuildId (a cuid). Resolve it to the FB- buildId and
  // set params.buildId so phase-scoped tools (update_feature_brief,
  // reviewDesignDoc, saveBuildEvidence, …) target THAT build via
  // extractBuildIdHint — instead of resolveActiveBuildId silently falling back
  // to the user's most-recently-updated non-terminal build, which
  // cross-contaminates when several builds are active (the wrong-build-context
  // bug). Only fills when the caller didn't pass an explicit buildId, and is
  // inert for non-build tools (they never read params.buildId).
  if (context?.featureBuildId && typeof params["buildId"] !== "string") {
    try {
      const ctxBuild = await prisma.featureBuild.findUnique({
        where: { id: context.featureBuildId },
        select: { buildId: true },
      });
      if (ctxBuild?.buildId) params["buildId"] = ctxBuild.buildId;
    } catch {
      // Non-fatal — fall back to the existing hint / active-build resolution.
    }
  }

  // ─── Runtime kernel-commandment gate (BI-43F95F77) ─────────────────────────
  // Consult the gate BEFORE the switch. If a tier-1 commandment matches this
  // tool name in the active session class, refuse (autonomous) or surface a
  // typed-confirmation requirement (interactive) without ever invoking the
  // tool body. Principle registry is process-cached so this is ~zero-latency
  // after the first dispatch.
  const _sessionClass = detectSessionClass();
  const _principles = await loadEnforceablePrinciples();
  const _decision = evaluateExecution(
    { kind: "mcp_tool", toolName, arguments: params },
    _sessionClass,
    _principles,
  );
  const _slug = _decision.verdict === "allow" ? "_none" : _decision.principleSlug;
  kernelGateDecisionsTotal.inc({
    verdict: _decision.verdict,
    principle_slug: _slug,
    session_class: _sessionClass,
  });
  // CodeQL js/log-injection: toolName + _slug are user-influenced (model
  // submits the tool name; principle slugs derive from wiki frontmatter
  // edited by operators). JSON.stringify each user-influenced value to
  // neutralize CR/LF / control chars — same pattern as
  // neo4j-restore-runner.ts. _decision.verdict and _sessionClass are typed
  // enums and not user-influenced.
  // eslint-disable-next-line no-console
  console.log(
    "[kernel-gate-trace] verdict=%s slug=%s session=%s kind=mcp_tool tool=%s",
    _decision.verdict,
    JSON.stringify(_slug),
    _sessionClass,
    JSON.stringify(toolName),
  );
  if (_decision.verdict === "refuse") {
    return {
      success: false,
      message: `Kernel commandment '${_decision.principleSlug}' refused this tool call.`,
      error: `[kernel-gate] REFUSED by commandment '${_decision.principleSlug}': ${_decision.rationale}`,
      data: {
        kernelGate: {
          verdict: "refuse",
          principleId: _decision.principleId,
          principleSlug: _decision.principleSlug,
          rationale: _decision.rationale,
        },
      },
    };
  }
  if (_decision.verdict === "require_confirm") {
    return {
      success: false,
      message: `Kernel commandment '${_decision.principleSlug}' requires typed confirmation.`,
      error:
        `[kernel-gate] Commandment '${_decision.principleSlug}' requires typed confirmation. ` +
        `Operator must reply with exactly: ${_decision.requiredPhrase}`,
      data: {
        kernelGate: {
          verdict: "require_confirm",
          principleId: _decision.principleId,
          principleSlug: _decision.principleSlug,
          rationale: _decision.rationale,
          requiredPhrase: _decision.requiredPhrase,
        },
      },
    };
  }
  // verdict === "allow" — fall through to the existing switch.

  try {
  switch (toolName) {
    case "dpf_test_kernel_refuse_probe": {
      // Test-only synthetic probe (Phase 9 live verification).
      // Reachable ONLY when DPF_TEST_MCP_REFUSE_PROBE=1 because:
      //   - loadEnforceablePrinciples injects a synthetic principle that
      //     matches this tool name with refuse-in-both-modes, so the gate
      //     above short-circuits before this body ever runs.
      //   - When the env is unset, no principle matches, but neither does
      //     any production code path call this tool name — we return the
      //     unknown-tool default below.
      // The body exists to give the dispatcher a recognizable case so the
      // gate gets a chance to refuse before falling through to unknown-tool.
      if (process.env.DPF_TEST_MCP_REFUSE_PROBE !== "1") {
        return { success: false, message: "tool not registered", error: "tool not registered" };
      }
      return {
        success: true,
        message: "probe tool body — should not be reached when gate is wired and DPF_TEST_MCP_REFUSE_PROBE=1",
      };
    }
    case "deliberate_on": {
      return deliberateOnMcpHandler(params, userId, context);
    }
    case "list_work_capsules": {
      const { listWorkCapsulesTool } = await import("@/lib/work-capsules/mcp-handlers");
      return listWorkCapsulesTool(params);
    }
    case "get_work_capsule": {
      const { getWorkCapsuleTool } = await import("@/lib/work-capsules/mcp-handlers");
      return getWorkCapsuleTool(params);
    }
    case "create_work_capsule": {
      const { createWorkCapsuleTool } = await import("@/lib/work-capsules/mcp-handlers");
      return createWorkCapsuleTool(params, userId, context);
    }
    case "plan_capsule_worktree": {
      const { planCapsuleWorktreeTool } = await import("@/lib/work-capsules/mcp-handlers");
      return planCapsuleWorktreeTool(params, userId, context);
    }
    case "adopt_worktree": {
      const { adoptWorktreeTool } = await import("@/lib/work-capsules/mcp-handlers");
      return adoptWorktreeTool(params, userId, context);
    }
    case "claim_capsule_scope": {
      const { claimCapsuleScopeTool } = await import("@/lib/work-capsules/mcp-handlers");
      return claimCapsuleScopeTool(params, userId, context);
    }
    case "heartbeat_capsule": {
      const { heartbeatCapsuleTool } = await import("@/lib/work-capsules/mcp-handlers");
      return heartbeatCapsuleTool(params, userId, context);
    }
    case "update_work_capsule_status": {
      const { updateWorkCapsuleStatusTool } = await import("@/lib/work-capsules/mcp-handlers");
      return updateWorkCapsuleStatusTool(params, userId, context);
    }
    case "release_capsule_scope": {
      const { releaseCapsuleScopeTool } = await import("@/lib/work-capsules/mcp-handlers");
      return releaseCapsuleScopeTool(params, userId, context);
    }
    case "record_capsule_evidence": {
      const { recordCapsuleEvidenceTool } = await import("@/lib/work-capsules/mcp-handlers");
      return recordCapsuleEvidenceTool(params, userId, context);
    }
    case "register_runtime_target": {
      const { registerRuntimeTargetTool } = await import("@/lib/runtime-coordination/mcp-handlers");
      return registerRuntimeTargetTool(params, userId, context);
    }
    case "heartbeat_runtime_target": {
      const { heartbeatRuntimeTargetTool } = await import("@/lib/runtime-coordination/mcp-handlers");
      return heartbeatRuntimeTargetTool(params);
    }
    case "release_runtime_target": {
      const { releaseRuntimeTargetTool } = await import("@/lib/runtime-coordination/mcp-handlers");
      return releaseRuntimeTargetTool(params, userId, context);
    }
    case "record_runtime_verification": {
      const { recordRuntimeVerificationTool } = await import("@/lib/runtime-coordination/mcp-handlers");
      return recordRuntimeVerificationTool(params, userId, context);
    }
    case "get_runtime_coordination_map": {
      const { getRuntimeCoordinationMapTool } = await import("@/lib/runtime-coordination/mcp-handlers");
      return getRuntimeCoordinationMapTool(params);
    }
    case "spawn_work_thread": {
      if (!context?.threadId) {
        const message = "Error: spawn_work_thread requires caller thread context";
        return {
          success: false,
          error: "missing_threadId",
          message,
        };
      }
      const { spawnWorkThread } = await import("@/lib/actions/agent-coworker");
      const result = await spawnWorkThread(
        {
          parentThreadId: context.threadId,
          objective: String(params["objective"] ?? ""),
          title: typeof params["title"] === "string" ? params["title"] : undefined,
          routeContext: context.routeContext,
          agentId: context.agentId,
        },
        userId,
      );
      return {
        success: true,
        entityId: result.child.id,
        message: "Started child thread.",
        data: { child: result.child, taskRunId: result.taskRunId },
      };
    }
    case "cancel_thread": {
      const threadId = typeof params["threadId"] === "string" && params["threadId"].trim()
        ? params["threadId"].trim()
        : context?.threadId;
      if (!threadId) {
        return {
          success: false,
          error: "missing_threadId",
          message: "cancel_thread requires a threadId or current thread context.",
        };
      }
      const { cancelThread } = await import("@/lib/actions/agent-coworker");
      const result = await cancelThread({ threadId }, userId);
      return {
        success: true,
        entityId: threadId,
        message: "Cancelled thread.",
        data: result,
      };
    }
    case "get_thread_result": {
      const threadId = typeof params["threadId"] === "string" && params["threadId"].trim()
        ? params["threadId"].trim()
        : null;
      if (!threadId) {
        return {
          success: false,
          error: "missing_threadId",
          message: "get_thread_result requires a threadId.",
        };
      }
      const { getThreadResult } = await import("@/lib/actions/agent-coworker");
      const result = await getThreadResult({ childId: threadId }, userId);
      return {
        success: true,
        entityId: threadId,
        message: "Thread completed.",
        data: { result },
      };
    }
    case "get_child_threads": {
      if (!context?.threadId) {
        return {
          success: false,
          error: "missing_threadId",
          message: "get_child_threads requires a current thread context.",
        };
      }
      const { getChildThreads } = await import("@/lib/actions/agent-coworker");
      const threads = await getChildThreads({ parentThreadId: context.threadId }, userId);
      return {
        success: true,
        message: `Found ${threads.length} child thread${threads.length === 1 ? "" : "s"}.`,
        data: { threads },
      };
    }
    case "create_backlog_item": {
      const itemId = typeof params["itemId"] === "string" && params["itemId"].trim()
        ? params["itemId"].trim()
        : `BI-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
      const status = typeof params["status"] === "string" ? params["status"] : "triaging";
      const triageOutcome = typeof params["triageOutcome"] === "string" ? params["triageOutcome"] : null;
      // workType is required by the tool schema; default to "feature" defensively
      // so a missing payload field surfaces as a Prisma validation error rather
      // than a silent insert with workType=NULL.
      const workType = typeof params["workType"] === "string" ? params["workType"] : null;
      // source defaults to "user-request" (the MCP tool is human-driven by
      // contract — agents calling it on behalf of an operator are forwarding a
      // human request).
      const source = typeof params["source"] === "string" ? params["source"] : "user-request";
      const proposedOutcome = typeof params["proposedOutcome"] === "string" ? params["proposedOutcome"] : null;
      const effortSize = typeof params["effortSize"] === "string" ? params["effortSize"] : null;
      const priority = typeof params["priority"] === "number" ? params["priority"] : null;

      if (!workType) {
        return {
          success: false,
          error: "workType is required",
          message: "workType is required (bug | feature | chore | doc | tool | skill | refactor).",
        };
      }

      // Validate the status / triageOutcome pairing rule from the tool description:
      // "supply status+triageOutcome together only when explicitly skipping triage"
      // Concretely: status=triaging requires no triageOutcome; any other status requires one.
      if (status !== "triaging" && !triageOutcome) {
        return {
          success: false,
          error: "triageOutcome is required when status is not 'triaging'",
          message: "When skipping triage (status='open' or 'in-progress'), pass triageOutcome to record the decision. Otherwise omit status to default to 'triaging'.",
        };
      }
      if (status === "triaging" && triageOutcome) {
        return {
          success: false,
          error: "triageOutcome must not be set when status='triaging'",
          message: "Items in triaging status have not been triaged yet. Use triage_backlog_item to set triageOutcome.",
        };
      }
      // When triageOutcome=build, effortSize is required (matches triage_backlog_item rules
      // so the ready-for-build pool is consistently sized).
      if (triageOutcome === "build" && !effortSize) {
        return {
          success: false,
          error: "effortSize is required when triageOutcome='build'",
          message: "Build-bound items must declare effortSize ('small'|'medium'|'large'|'xlarge').",
        };
      }

      // BacklogItem.epicId is a FK to Epic.id (cuid). Agents typically pass
      // the semantic epicId ("EP-..."), so resolve to cuid before inserting.
      let epicCuid: string | null = null;
      if (typeof params["epicId"] === "string" && params["epicId"].trim()) {
        const raw = params["epicId"].trim();
        const epicRow = await prisma.epic.findFirst({
          where: { OR: [{ epicId: raw }, { id: raw }] },
          select: { id: true },
        });
        epicCuid = epicRow?.id ?? null;
      }

      const item = await prisma.backlogItem.create({
        data: {
          itemId,
          title: String(params["title"] ?? "Untitled"),
          type: String(params["type"] ?? "product"),
          status,
          submittedById: userId,
          agentId: context?.agentId ?? null,
          ...(status === "done" ? { completedAt: new Date() } : {}),
          ...(typeof params["body"] === "string" ? { body: params["body"] } : {}),
          ...(epicCuid ? { epicId: epicCuid } : {}),
          workType,
          source,
          ...(triageOutcome ? { triageOutcome } : {}),
          ...(proposedOutcome ? { proposedOutcome } : {}),
          ...(effortSize ? { effortSize } : {}),
          ...(priority !== null ? { priority } : {}),
        },
      });
      // Index in platform knowledge for semantic search
      import("@/lib/semantic-memory").then(({ storePlatformKnowledge }) =>
        storePlatformKnowledge({
          entityId: item.itemId,
          entityType: "backlog",
          title: String(params["title"] ?? ""),
          content: String(params["body"] ?? ""),
        })
      ).catch(() => {});
      if (context?.routeContext === "/build") {
        await updateBuildHappyPathState(userId, {
          intake: {
            backlogItemId: item.itemId,
            epicId: typeof params["epicId"] === "string" ? params["epicId"] : null,
          },
        });
      }
      return { success: true, entityId: item.itemId, message: `Created backlog item ${item.itemId}` };
    }

    case "triage_backlog_item": {
      const itemId = String(params["itemId"] ?? "");
      const outcome = String(params["outcome"] ?? "");
      const rationale = String(params["rationale"] ?? "").trim();
      const item = await prisma.backlogItem.findUnique({ where: { itemId } });
      if (!item) {
        return { success: false, error: "Item not found", message: `Item ${itemId} not found` };
      }
      if (item.status !== "triaging") {
        return { success: false, error: "Item is not in triaging status", message: `Item ${itemId} is not in triaging status` };
      }
      if (!rationale) {
        return { success: false, error: "Rationale is required", message: "Rationale is required" };
      }
      if (outcome === "build" && typeof params["effortSize"] !== "string") {
        return { success: false, error: "effortSize is required for build outcomes", message: "effortSize is required for build outcomes" };
      }
      if (outcome === "duplicate" && typeof params["duplicateOfId"] !== "string") {
        return { success: false, error: "duplicateOfId is required for duplicate outcomes", message: "duplicateOfId is required for duplicate outcomes" };
      }
      if ((outcome === "defer" || outcome === "discard") && typeof params["reason"] !== "string") {
        return { success: false, error: "reason is required for defer/discard outcomes", message: "reason is required for defer/discard outcomes" };
      }

      const nextStatus =
        outcome === "build" || outcome === "runbook" || outcome === "coworker-task"
          ? "open"
          : "deferred";

      await prisma.backlogItem.update({
        where: { itemId },
        data: {
          status: nextStatus,
          triageOutcome: outcome,
          effortSize: typeof params["effortSize"] === "string" ? params["effortSize"] : null,
          duplicateOfId: typeof params["duplicateOfId"] === "string" ? params["duplicateOfId"] : null,
          resolution: rationale,
          abandonReason: typeof params["reason"] === "string" ? params["reason"] : null,
        },
      });

      return {
        success: true,
        entityId: itemId,
        message: `Triaged ${itemId} as ${outcome}`,
      };
    }

    case "retire_backlog_item": {
      const itemId = String(params["itemId"] ?? "");
      const outcome = String(params["outcome"] ?? "");
      const rationale = String(params["rationale"] ?? "").trim();
      const reason = typeof params["reason"] === "string" ? params["reason"].trim() : "";
      const duplicateOfId = typeof params["duplicateOfId"] === "string" ? params["duplicateOfId"].trim() : "";
      const validOutcomes = new Set(["duplicate", "defer", "discard"]);

      if (!itemId) {
        return { success: false, error: "missing_itemId", message: "itemId is required" };
      }
      if (!validOutcomes.has(outcome)) {
        return { success: false, error: "invalid_outcome", message: "outcome must be duplicate, defer, or discard" };
      }
      if (!rationale) {
        return { success: false, error: "missing_rationale", message: "rationale is required" };
      }
      if (outcome === "duplicate" && !duplicateOfId) {
        return { success: false, error: "missing_duplicateOfId", message: "duplicateOfId is required for duplicate retirement" };
      }

      const result = await prisma.$transaction(async (tx) => {
        const item = await tx.backlogItem.findUnique({ where: { itemId } });
        if (!item) {
          return { success: false, error: "not_found", message: `Item ${itemId} not found` } satisfies ToolResult;
        }
        if ("activeBuildId" in item && item.activeBuildId) {
          return {
            success: false,
            error: "active_build_exists",
            message: `Item ${itemId} is attached to an active build and cannot be retired`,
          } satisfies ToolResult;
        }

        let canonicalRowId: string | null = null;
        if (outcome === "duplicate") {
          const canonical = await tx.backlogItem.findUnique({ where: { itemId: duplicateOfId } });
          if (!canonical) {
            return {
              success: false,
              error: "duplicate_not_found",
              message: `Canonical item ${duplicateOfId} not found`,
            } satisfies ToolResult;
          }
          canonicalRowId = canonical.id;
        }

        const updated = await tx.backlogItem.update({
          where: { id: item.id },
          data: {
            status: "deferred",
            triageOutcome: outcome,
            duplicateOfId: canonicalRowId,
            resolution: rationale,
            abandonReason: reason || rationale,
            completedAt: new Date(),
          },
        });

        await tx.backlogItemActivity.create({
          data: {
            backlogItemId: item.id,
            kind: "status_change",
            recordedById: userId,
            recordedByAgentId: context?.agentId ?? null,
            summary: `Retired ${itemId} as ${outcome}`,
            payload: {
              from: item.status,
              to: "deferred",
              outcome,
              rationale,
              reason: reason || null,
              duplicateOfId: outcome === "duplicate" ? duplicateOfId : null,
            },
          },
        });

        if (item.epicId) {
          const remainingOpenItems = await tx.backlogItem.count({
            where: {
              epicId: item.epicId,
              status: { in: ["open", "in-progress"] },
              id: { not: item.id },
            },
          });
          if (remainingOpenItems === 0) {
            await tx.epic.update({
              where: { id: item.epicId },
              data: { status: "done" },
            });
          }
        }

        return {
          success: true,
          entityId: updated.itemId,
          message: `Retired ${itemId} as ${outcome}`,
          data: {
            itemId: updated.itemId,
            status: "deferred",
            outcome,
            duplicateOfId: outcome === "duplicate" ? duplicateOfId : null,
          },
        } satisfies ToolResult;
      });

      return result;
    }

    case "size_backlog_item": {
      const itemId = String(params["itemId"] ?? "");
      const size = String(params["size"] ?? "");
      const item = await prisma.backlogItem.findUnique({ where: { itemId } });
      if (!item) {
        return { success: false, error: "Item not found", message: `Item ${itemId} not found` };
      }
      await prisma.backlogItem.update({
        where: { itemId },
        data: { effortSize: size },
      });
      return {
        success: true,
        entityId: itemId,
        message: `Sized ${itemId} as ${size}`,
      };
    }

    case "promote_to_build_studio": {
      const itemId = String(params["itemId"] ?? "");
      const governedConfig = await prisma.platformDevConfig.findUnique({
        where: { id: "singleton" },
        select: { governedBacklogEnabled: true },
      });
      const governedBacklogEnabled = governedConfig?.governedBacklogEnabled === true;

      const result = await prisma.$transaction(async (tx) => {
        return promoteBacklogItemToBuildDraft({
          tx,
          itemId,
          userId,
          governedBacklogEnabled,
        });
      });

      if (result.kind === "error") {
        return { success: false, error: result.error, message: result.message };
      }

      // BI-52022707 axis D — auto-dispatch Ideate after governed promotion.
      // The transaction inside promoteBacklogItemToBuildDraft set
      // draftApprovedAt for governed + non-empty-body promotions; that closes
      // the "Record Approve Start" gate but does NOT fire the Ideate research.
      // Mirror the approveBuildStart action's fire-and-forget pattern here so
      // the BS pipeline actually starts work without a second operator click.
      // Fire-and-forget on purpose: the operator's MCP call returns immediately
      // with the FB-* id, and the ~3-min Codex research runs async, exactly
      // like the manual UI button path. Errors are logged + written to
      // BuildActivity by the helper itself — never thrown out here.
      if (result.autoApprovedDispatchEligible) {
        void (async () => {
          try {
            const { dispatchIdeateForApprovedBuild } = await import("@/lib/integrate/ideate-on-approval");
            await dispatchIdeateForApprovedBuild({ buildId: result.build.buildId, userId });
          } catch (err) {
            console.error(
              "[promote_to_build_studio] auto-dispatch Ideate failed (handler swallowed by ideate-on-approval but the dynamic import or top-level invocation rejected):",
              { buildId: result.build.buildId },
              err,
            );
          }
        })();
      }

      return {
        success: true,
        entityId: result.build.buildId,
        message: result.autoApprovedDispatchEligible
          ? `Promoted ${itemId} to Build Studio (auto-approved under governed flow — Ideate research dispatched)`
          : `Promoted ${itemId} to Build Studio`,
        data: {
          buildId: result.build.buildId,
          backlogItemId: itemId,
          autoApprovedDispatchEligible: result.autoApprovedDispatchEligible,
        },
      };
    }

    case "process_backlog_for_build_studio": {
      const governedConfig = await prisma.platformDevConfig.findUnique({
        where: { id: "singleton" },
        select: {
          governedBacklogEnabled: true,
          backlogTeeUpDailyCap: true,
        },
      });

      if (governedConfig?.governedBacklogEnabled !== true) {
        return {
          success: false,
          error: "Governed backlog mode is disabled",
          message: "Governed backlog mode must be enabled before backlog processing can be queued.",
        };
      }

      const requestedLimitRaw = Number(params["limit"]);
      const configuredCap = governedConfig.backlogTeeUpDailyCap ?? 3;
      const limit = Number.isFinite(requestedLimitRaw)
        ? Math.min(Math.max(0, Math.floor(requestedLimitRaw)), configuredCap)
        : configuredCap;

      const { inngest } = await import("@/lib/queue/inngest-client");
      await inngest.send({
        name: "build/backlog-tee-up.requested",
        data: {
          userId,
          limit,
          routeContext: context?.routeContext ?? null,
          threadId: context?.threadId ?? null,
          requestedByAgentId: context?.agentId ?? null,
        },
      });

      return {
        success: true,
        message: "Queued an on-demand backlog sweep for Build Studio draft tee-up.",
        data: {
          status: "queued",
          limit,
        },
      };
    }

    case "propose_build_decomposition": {
      const buildId = String(params["buildId"] ?? "");
      if (!buildId.startsWith("FB-")) {
        return { success: false, error: "invalid_buildId", message: "buildId must use the FB-* format." };
      }
      const operatorHint = typeof params["operatorHint"] === "string" ? params["operatorHint"] : undefined;
      const { proposeDecomposition } = await import("@/lib/build/propose-decomposition");
      const result = await proposeDecomposition({
        buildId,
        userId,
        agentId: context?.agentId ?? null,
        ...(operatorHint ? { operatorHint } : {}),
      });
      if (!result.ok) {
        return { success: false, error: result.code, message: result.error };
      }
      return {
        success: true,
        entityId: buildId,
        message: `Proposed ${result.candidates.length} candidate decomposition(s) for ${buildId}.${result.rejected.length > 0 ? ` (${result.rejected.length} additional candidate(s) failed validation and were dropped.)` : ""}`,
        data: {
          candidates: result.candidates,
          rejectedCount: result.rejected.length,
        },
      };
    }

    case "approve_decomposition": {
      // Phase 4a (BI-2E6CC391). The candidate is pre-validated by the
      // caller (Phase 4b assistant); we revalidate inside
      // approveDecomposition's pipeline before any DB writes.
      const buildId = String(params["buildId"] ?? "");
      const candidateRaw = params["candidate"];
      if (!buildId.startsWith("FB-")) {
        return { success: false, error: "invalid_buildId", message: "buildId must use the FB-* format." };
      }
      if (!candidateRaw || typeof candidateRaw !== "object") {
        return { success: false, error: "invalid_candidate", message: "candidate must be an object matching the DecompositionCandidate shape." };
      }
      const { approveDecomposition } = await import("@/lib/build/approve-decomposition");
      const result = await approveDecomposition({
        buildId,
        candidate: candidateRaw as Parameters<typeof approveDecomposition>[0]["candidate"],
        userId,
        agentId: context?.agentId ?? null,
      });
      if (!result.ok) {
        return { success: false, error: result.code, message: result.error };
      }
      return {
        success: true,
        entityId: result.epicId,
        message: `Decomposed ${buildId} into Epic ${result.epicId} with ${result.childBuildIds.length} child build(s).`,
        data: {
          epicId: result.epicId,
          childBuildIds: result.childBuildIds,
        },
      };
    }

    case "record_decomposition_override": {
      const buildId = String(params["buildId"] ?? "");
      const rationale = String(params["rationale"] ?? "");
      if (!buildId.startsWith("FB-")) {
        return { success: false, error: "invalid_buildId", message: "buildId must use the FB-* format." };
      }
      const { recordDecompositionOverride } = await import("@/lib/build/decomposition-override");
      const result = await recordDecompositionOverride({
        buildId,
        rationale,
        userId,
        agentId: context?.agentId ?? null,
      });
      if (!result.ok) {
        return { success: false, error: result.code, message: result.error };
      }
      return {
        success: true,
        entityId: buildId,
        message: `Decomposition override recorded for ${buildId}.`,
        data: { override: result.override },
      };
    }

    case "update_backlog_item": {
      const existing = await prisma.backlogItem.findUnique({ where: { itemId: String(params["itemId"]) } });
      if (!existing) return { success: false, error: "Item not found", message: `Item ${String(params["itemId"])} not found` };
      const data: Record<string, unknown> = {};
      if (typeof params["title"] === "string") data["title"] = params["title"];
      if (typeof params["status"] === "string") {
        // Block direct status mutations that should go through dedicated tools.
        // - Setting status='triaging' on an already-triaged item: use the retriage flow (BI-7D4AF644 follow-up).
        // - Leaving triaging without going through triage_backlog_item: would skip the rationale + audit.
        if (params["status"] === "triaging" && existing.status !== "triaging") {
          return {
            success: false,
            error: "use_update_backlog_item_status_for_retriage",
            message: "To move a triaged item back to triaging, use update_backlog_item_status (it requires a reason and audits the retriage). update_backlog_item is for editing fields, not for status transitions.",
          };
        }
        if (existing.status === "triaging" && params["status"] !== "triaging") {
          return {
            success: false,
            error: "Use triage_backlog_item to leave triaging status",
            message: "Items in triaging must be moved out via triage_backlog_item so the decision is recorded with rationale.",
          };
        }
        data["status"] = params["status"];
        // Track completion date
        const isTerminal = params["status"] === "done" || params["status"] === "deferred";
        const wasTerminal = existing.status === "done" || existing.status === "deferred";
        if (isTerminal && !wasTerminal) {
          data["completedAt"] = new Date();
        } else if (!isTerminal && wasTerminal) {
          data["completedAt"] = null;
        }
      }
      if (typeof params["priority"] === "number") data["priority"] = params["priority"];
      if (typeof params["body"] === "string") data["body"] = params["body"];
      if (typeof params["workType"] === "string") data["workType"] = params["workType"];
      if (typeof params["source"] === "string") data["source"] = params["source"];
      if (typeof params["proposedOutcome"] === "string") data["proposedOutcome"] = params["proposedOutcome"];
      await prisma.backlogItem.update({ where: { itemId: String(params["itemId"]) }, data });
      return { success: true, entityId: String(params["itemId"]), message: `Updated ${String(params["itemId"])}` };
    }

    case "create_digital_product": {
      const product = await prisma.digitalProduct.create({
        data: {
          productId: String(params["productId"]),
          name: String(params["name"]),
          lifecycleStage: String(params["lifecycleStage"] ?? "plan"),
          lifecycleStatus: "draft",
        },
      });
      return { success: true, entityId: product.productId, message: `Created product ${product.productId}` };
    }

    case "update_lifecycle": {
      const prod = await prisma.digitalProduct.findUnique({ where: { productId: String(params["productId"]) } });
      if (!prod) return { success: false, error: "Product not found", message: `Product ${String(params["productId"])} not found` };
      const updates: Record<string, unknown> = {};
      if (typeof params["lifecycleStage"] === "string") updates["lifecycleStage"] = params["lifecycleStage"];
      if (typeof params["lifecycleStatus"] === "string") updates["lifecycleStatus"] = params["lifecycleStatus"];
      await prisma.digitalProduct.update({ where: { productId: String(params["productId"]) }, data: updates });
      return { success: true, entityId: String(params["productId"]), message: `Updated lifecycle for ${String(params["productId"])}` };
    }

    case "query_backlog": {
      const where: Record<string, unknown> = {};
      if (typeof params["status"] === "string") where["status"] = params["status"];
      if (typeof params["epicId"] === "string") where["epicId"] = params["epicId"];
      const limit = typeof params["limit"] === "number" ? Math.min(params["limit"], 50) : 20;

      const [items, epics, totalOpen, totalInProgress, totalDone] = await Promise.all([
        prisma.backlogItem.findMany({
          where,
          orderBy: [{ priority: "asc" }, { updatedAt: "desc" }],
          take: limit,
          select: { itemId: true, title: true, status: true, type: true, priority: true, epicId: true, updatedAt: true },
        }),
        prisma.epic.findMany({
          select: { id: true, epicId: true, title: true, status: true },
          orderBy: { createdAt: "desc" },
          take: 20,
        }),
        prisma.backlogItem.count({ where: { status: "open" } }),
        prisma.backlogItem.count({ where: { status: "in-progress" } }),
        prisma.backlogItem.count({ where: { status: "done" } }),
      ]);

      const summary = `Backlog: ${totalOpen} open, ${totalInProgress} in-progress, ${totalDone} done. ${epics.length} epic(s).`;
      return {
        success: true,
        message: summary,
        data: {
          summary: { open: totalOpen, inProgress: totalInProgress, done: totalDone },
          epics: epics.map((e) => ({ epicId: e.epicId, title: e.title, status: e.status })),
          items: items.map((i) => ({ itemId: i.itemId, title: i.title, status: i.status, type: i.type, priority: i.priority, epicId: i.epicId })),
        },
      };
    }

    // ─── Governed MCP backlog surface (spec 2026-04-25) ─────────────────────
    case "create_epic": {
      const { createEpicTool } = await import("@/lib/backlog/mcp-epic-tools");
      return createEpicTool(params, userId, context);
    }

    case "update_epic": {
      const { updateEpicTool } = await import("@/lib/backlog/mcp-epic-tools");
      return updateEpicTool(params);
    }

    case "list_epics": {
      const where: Record<string, unknown> = {};
      if (typeof params["status"] === "string") where["status"] = params["status"];
      const limit = typeof params["limit"] === "number" ? Math.min(Math.max(1, params["limit"]), 100) : 25;
      const epics = await prisma.epic.findMany({
        where,
        take: limit,
        orderBy: [{ updatedAt: "desc" }],
        select: {
          id: true,
          epicId: true,
          title: true,
          status: true,
          priority: true,
          updatedAt: true,
          items: { select: { status: true } },
        },
      });
      const wantOpenItems = params["hasOpenItems"] === true;
      const { buildSpecPlanReferenceIndex } = await import("@/lib/backlog/spec-plan-search");
      const refIndex = await buildSpecPlanReferenceIndex();
      const data = epics
        .map((e) => {
          const total = e.items.length;
          const open = e.items.filter((it) => it.status === "open").length;
          const inProgress = e.items.filter((it) => it.status === "in-progress").length;
          const done = e.items.filter((it) => it.status === "done").length;
          return {
            epicId: e.epicId,
            title: e.title,
            status: e.status,
            priority: e.priority,
            itemCount: { total, open, inProgress, done },
            hasSpec: refIndex.specs.has(e.epicId) || refIndex.plans.has(e.epicId),
            updatedAt: e.updatedAt.toISOString(),
            _hasOpen: open + inProgress > 0,
          };
        })
        .filter((row) => (wantOpenItems ? row._hasOpen : true))
        .map((row) => {
          const { _hasOpen, ...rest } = row;
          void _hasOpen;
          return rest;
        });
      return {
        success: true,
        message: `Listed ${data.length} epic(s).`,
        data: { epics: data },
      };
    }

    case "list_backlog_items": {
      const where: Record<string, unknown> = {};
      if (typeof params["status"] === "string") where["status"] = params["status"];
      if (typeof params["type"] === "string") where["type"] = params["type"];
      if (typeof params["workType"] === "string") where["workType"] = params["workType"];
      if (typeof params["source"] === "string") where["source"] = params["source"];
      if (typeof params["epicId"] === "string" && params["epicId"].trim()) {
        const epicRow = await prisma.epic.findFirst({
          where: { OR: [{ epicId: params["epicId"].trim() }, { id: params["epicId"].trim() }] },
          select: { id: true },
        });
        if (epicRow) where["epicId"] = epicRow.id;
        else
          return {
            success: false,
            error: "epic_not_found",
            message: `No epic matched ${params["epicId"]}`,
          };
      }
      if (params["unclaimed"] === true) {
        where["claimedById"] = null;
        where["claimedByAgentId"] = null;
      }
      if (params["hasActiveBuild"] === true) where["activeBuildId"] = { not: null };
      else if (params["hasActiveBuild"] === false) where["activeBuildId"] = null;

      const limit = typeof params["limit"] === "number" ? Math.min(Math.max(1, params["limit"]), 100) : 25;
      const items = await prisma.backlogItem.findMany({
        where,
        take: limit,
        orderBy: [{ priority: "asc" }, { updatedAt: "desc" }],
        select: {
          itemId: true,
          title: true,
          status: true,
          type: true,
          workType: true,
          source: true,
          priority: true,
          effortSize: true,
          activeBuildId: true,
          updatedAt: true,
          triageOutcome: true,
          epic: { select: { epicId: true } },
          activeBuild: { select: { phase: true, draftApprovedAt: true } },
        },
      });
      const { deriveLifecycleLabel } = await import("@/lib/governed-backlog-workflow");
      const data = items.map((i) => ({
        itemId: i.itemId,
        title: i.title,
        status: i.status,
        type: i.type,
        workType: i.workType,
        source: i.source,
        priority: i.priority,
        effortSize: i.effortSize,
        triageOutcome: i.triageOutcome,
        epicId: i.epic?.epicId ?? null,
        hasActiveBuild: i.activeBuildId != null,
        lifecycleLabel: deriveLifecycleLabel({
          backlogItem: { status: i.status, triageOutcome: i.triageOutcome, activeBuildId: i.activeBuildId },
          featureBuild: i.activeBuild
            ? { phase: i.activeBuild.phase, draftApprovedAt: i.activeBuild.draftApprovedAt }
            : null,
          governedBacklogEnabled: true,
        }),
        updatedAt: i.updatedAt.toISOString(),
      }));
      return {
        success: true,
        message: `Listed ${data.length} backlog item(s).`,
        data: { items: data },
      };
    }

    case "get_backlog_item": {
      const itemIdRaw = String(params["itemId"] ?? "").trim();
      if (!itemIdRaw)
        return { success: false, error: "missing_itemId", message: "itemId is required" };
      const item = await prisma.backlogItem.findUnique({
        where: { itemId: itemIdRaw },
        include: {
          epic: { select: { epicId: true, title: true, status: true } },
          digitalProduct: { select: { productId: true, name: true } },
          activeBuild: {
            select: {
              buildId: true,
              phase: true,
              draftApprovedAt: true,
              sandboxId: true,
              createdAt: true,
            },
          },
          activities: {
            orderBy: { recordedAt: "desc" },
            take: 10,
          },
        },
      });
      if (!item)
        return { success: false, error: "not_found", message: `Item ${itemIdRaw} not found` };
      const { deriveLifecycleLabel } = await import("@/lib/governed-backlog-workflow");
      const { searchSpecsAndPlans } = await import("@/lib/backlog/spec-plan-search");
      const specPlanRefs = await searchSpecsAndPlans({
        query: itemIdRaw,
        itemId: itemIdRaw,
        matches: 10,
      });
      return {
        success: true,
        message: `Loaded ${item.itemId}`,
        data: {
          itemId: item.itemId,
          title: item.title,
          status: item.status,
          type: item.type,
          workType: item.workType,
          source: item.source,
          priority: item.priority,
          effortSize: item.effortSize,
          triageOutcome: item.triageOutcome,
          body: item.body ?? null,
          createdAt: item.createdAt.toISOString(),
          updatedAt: item.updatedAt.toISOString(),
          completedAt: item.completedAt ? item.completedAt.toISOString() : null,
          lifecycleLabel: deriveLifecycleLabel({
            backlogItem: {
              status: item.status,
              triageOutcome: item.triageOutcome,
              activeBuildId: item.activeBuildId,
            },
            featureBuild: item.activeBuild
              ? { phase: item.activeBuild.phase, draftApprovedAt: item.activeBuild.draftApprovedAt }
              : null,
            governedBacklogEnabled: true,
          }),
          epic: item.epic
            ? { epicId: item.epic.epicId, title: item.epic.title, status: item.epic.status }
            : null,
          digitalProduct: item.digitalProduct
            ? { productId: item.digitalProduct.productId, name: item.digitalProduct.name }
            : null,
          activeBuild: item.activeBuild
            ? {
                buildId: item.activeBuild.buildId,
                phase: item.activeBuild.phase,
                draftApprovedAt: item.activeBuild.draftApprovedAt
                  ? item.activeBuild.draftApprovedAt.toISOString()
                  : null,
                sandboxId: item.activeBuild.sandboxId,
              }
            : null,
          specPlanFiles: specPlanRefs.map((r) => ({
            path: r.path,
            kind: r.kind,
            title: r.title,
            date: r.date,
          })),
          recentActivity: item.activities.map((a) => ({
            id: a.id,
            kind: a.kind,
            summary: a.summary,
            recordedAt: a.recordedAt.toISOString(),
            payload: a.payload,
          })),
        },
      };
    }

    case "update_backlog_item_status": {
      const { isLegalTransition, isBacklogStatus } = await import("@/lib/backlog/transitions");
      const itemIdRaw = String(params["itemId"] ?? "").trim();
      const target = String(params["status"] ?? "");
      if (!itemIdRaw)
        return { success: false, error: "missing_itemId", message: "itemId is required" };
      if (!isBacklogStatus(target))
        return {
          success: false,
          error: "invalid_status",
          message: `status must be one of triaging|open|in-progress|done|deferred, got ${target}`,
        };
      const reason = typeof params["reason"] === "string" ? params["reason"] : null;
      const resolution = typeof params["resolution"] === "string" ? params["resolution"] : null;
      if (target === "done" && !resolution)
        return {
          success: false,
          error: "missing_resolution",
          message: "resolution is required when status=done",
        };
      // Retriage requires a reason so the audit trail explains why a triaged decision was reopened.
      // (Reason is checked against the *current* status below, after the item is loaded.)
      const item = await prisma.backlogItem.findUnique({
        where: { itemId: itemIdRaw },
        select: { id: true, status: true, epicId: true, triageOutcome: true, effortSize: true, activeBuildId: true },
      });
      if (!item)
        return { success: false, error: "not_found", message: `Item ${itemIdRaw} not found` };
      if (!isBacklogStatus(item.status))
        return {
          success: false,
          error: "corrupt_current_status",
          message: `Item ${itemIdRaw} has non-canonical status ${item.status}`,
        };
      if (!isLegalTransition(item.status, target))
        return {
          success: false,
          error: "illegal_transition",
          message: `cannot move ${itemIdRaw} from ${item.status} to ${target}`,
        };
      if (item.status === target) {
        return {
          success: true,
          entityId: itemIdRaw,
          message: `${itemIdRaw} already at status=${target} (no-op)`,
        };
      }
      // Retriage path (BI-7D4AF644): require a reason and clear the prior triage
      // decision so triage_backlog_item starts clean on the next pass.
      const isRetriage = target === "triaging" && item.status !== "triaging";
      if (isRetriage && !reason) {
        return {
          success: false,
          error: "missing_reason",
          message: "reason is required when moving an item back to triaging — explain why the prior triage needs to be revisited",
        };
      }

      const updated = await prisma.$transaction(async (tx) => {
        const next = await tx.backlogItem.update({
          where: { id: item.id },
          data: {
            status: target,
            ...(target === "done" ? { completedAt: new Date(), resolution } : {}),
            ...(isRetriage ? { triageOutcome: null, effortSize: null, completedAt: null } : {}),
          },
          select: { itemId: true, status: true, epicId: true, completedAt: true },
        });
        await tx.backlogItemActivity.create({
          data: {
            backlogItemId: item.id,
            kind: "status_change",
            summary: `${item.status} → ${target}` + (reason ? ` — ${reason.slice(0, 160)}` : ""),
            payload: {
              from: item.status,
              to: target,
              reason: reason ?? null,
              resolution: resolution ?? null,
              ...(isRetriage
                ? {
                    retriage: true,
                    clearedTriageOutcome: item.triageOutcome ?? null,
                    clearedEffortSize: item.effortSize ?? null,
                  }
                : {}),
            },
            recordedById: userId,
            recordedByAgentId: context?.agentId ?? null,
          },
        });
        // Epic auto-close: when this item just reached done and every sibling is done/deferred,
        // flip the epic to done. Mirrors AGENTS.md Epic Lifecycle Stewardship.
        if (target === "done" && item.epicId) {
          const remaining = await tx.backlogItem.count({
            where: {
              epicId: item.epicId,
              id: { not: item.id },
              status: { notIn: ["done", "deferred"] },
            },
          });
          if (remaining === 0) {
            await tx.epic.update({
              where: { id: item.epicId },
              data: { status: "done", completedAt: new Date() },
            });
          }
        }
        return next;
      });
      // Advisory: flipping a build item to in-progress is not the same as
      // starting the work — surface the promote_to_build_studio path so the
      // coworker can't report a no-op status change as "throughput".
      const { buildPromoteAdvisory } = await import("@/lib/backlog/promote-advisory");
      const promoteAdvisory = buildPromoteAdvisory({
        itemId: updated.itemId,
        targetStatus: updated.status,
        triageOutcome: item.triageOutcome,
        hasActiveBuild: item.activeBuildId != null,
      });
      return {
        success: true,
        entityId: updated.itemId,
        message:
          `${updated.itemId}: ${item.status} → ${updated.status}` +
          (promoteAdvisory ? ` — ADVISORY: ${promoteAdvisory}` : ""),
        data: {
          itemId: updated.itemId,
          status: updated.status,
          completedAt: updated.completedAt,
          ...(promoteAdvisory ? { advisory: promoteAdvisory } : {}),
        },
      };
    }

    case "link_backlog_item_to_epic": {
      const itemIdRaw = String(params["itemId"] ?? "").trim();
      if (!itemIdRaw)
        return { success: false, error: "missing_itemId", message: "itemId is required" };
      const epicRaw = params["epicId"];
      const wantUnlink =
        epicRaw == null ||
        (typeof epicRaw === "string" && (epicRaw.trim() === "" || epicRaw.trim().toLowerCase() === "null"));
      let targetEpicCuid: string | null = null;
      let targetEpicSemantic: string | null = null;
      if (!wantUnlink) {
        const epicRow = await prisma.epic.findFirst({
          where: { OR: [{ epicId: String(epicRaw).trim() }, { id: String(epicRaw).trim() }] },
          select: { id: true, epicId: true, status: true },
        });
        if (!epicRow)
          return { success: false, error: "epic_not_found", message: `No epic matched ${epicRaw}` };
        targetEpicCuid = epicRow.id;
        targetEpicSemantic = epicRow.epicId;
      }
      const item = await prisma.backlogItem.findUnique({
        where: { itemId: itemIdRaw },
        select: {
          id: true,
          epicId: true,
          status: true,
          triageOutcome: true,
          activeBuildId: true,
          epic: { select: { epicId: true } },
        },
      });
      if (!item)
        return { success: false, error: "not_found", message: `Item ${itemIdRaw} not found` };
      const priorEpicSemantic = item.epic?.epicId ?? null;
      if (item.epicId === targetEpicCuid) {
        return {
          success: true,
          entityId: itemIdRaw,
          message: `${itemIdRaw} already linked to ${targetEpicSemantic ?? "no epic"} (no-op)`,
        };
      }

      await prisma.$transaction(async (tx) => {
        await tx.backlogItem.update({
          where: { id: item.id },
          data: { epicId: targetEpicCuid },
        });
        await tx.backlogItemActivity.create({
          data: {
            backlogItemId: item.id,
            kind: "epic_link",
            summary: `${priorEpicSemantic ?? "(no epic)"} → ${targetEpicSemantic ?? "(no epic)"}`,
            payload: {
              fromEpicId: priorEpicSemantic,
              toEpicId: targetEpicSemantic,
            },
            recordedById: userId,
            recordedByAgentId: context?.agentId ?? null,
          },
        });
        // Reopen epic if we just attached an open/in-progress item to a done epic
        if (targetEpicCuid && (item.status === "open" || item.status === "in-progress")) {
          const target = await tx.epic.findUnique({
            where: { id: targetEpicCuid },
            select: { status: true },
          });
          if (target?.status === "done") {
            await tx.epic.update({
              where: { id: targetEpicCuid },
              data: { status: "open", completedAt: null },
            });
          }
        }
      });
      // Advisory: a coworker asked to "triage + promote" may call this and then
      // report the item as triaged/promoted, when an epic link is purely
      // organizational. Surface the real triage_backlog_item / promote_to_build_studio
      // path when triage/promotion is genuinely unfinished (BI-6C86ADEB).
      const { buildEpicLinkAdvisory } = await import("@/lib/backlog/promote-advisory");
      const epicLinkAdvisory = buildEpicLinkAdvisory({
        itemId: itemIdRaw,
        targetEpicId: targetEpicSemantic,
        triageOutcome: item.triageOutcome,
        hasActiveBuild: item.activeBuildId != null,
      });
      return {
        success: true,
        entityId: itemIdRaw,
        message:
          `Linked ${itemIdRaw} to ${targetEpicSemantic ?? "(no epic)"}` +
          (epicLinkAdvisory ? ` — ADVISORY: ${epicLinkAdvisory}` : ""),
        data: {
          itemId: itemIdRaw,
          epicId: targetEpicSemantic,
          ...(epicLinkAdvisory ? { advisory: epicLinkAdvisory } : {}),
        },
      };
    }

    case "search_specs_and_plans": {
      const { searchSpecsAndPlans } = await import("@/lib/backlog/spec-plan-search");
      const query = String(params["query"] ?? "").trim();
      if (!query && !params["itemId"] && !params["epicId"])
        return {
          success: false,
          error: "missing_query",
          message: "query is required (or itemId/epicId)",
        };
      const kind = params["kind"] === "spec" || params["kind"] === "plan" ? params["kind"] : undefined;
      const matches = typeof params["matches"] === "number" ? params["matches"] : undefined;
      const itemId = typeof params["itemId"] === "string" ? params["itemId"] : undefined;
      const epicId = typeof params["epicId"] === "string" ? params["epicId"] : undefined;
      const results = await searchSpecsAndPlans({ query, kind, matches, itemId, epicId });
      return {
        success: true,
        message: `Found ${results.length} match(es).`,
        data: { results },
      };
    }

    case "record_execution_evidence": {
      const itemIdRaw = String(params["itemId"] ?? "").trim();
      const kindRaw = String(params["kind"] ?? "");
      const summaryRaw = String(params["summary"] ?? "").slice(0, 240);
      const url = typeof params["url"] === "string" ? params["url"] : null;
      const body = typeof params["body"] === "string" ? params["body"].slice(0, 8000) : null;
      const toolExecutionId =
        typeof params["toolExecutionId"] === "string" ? params["toolExecutionId"] : null;
      const ALLOWED_KINDS = new Set([
        "test_pass",
        "test_fail",
        "build_pass",
        "build_fail",
        "ux_verified",
        "spec_review",
        "manual_check",
        "external_link",
      ]);
      if (!itemIdRaw || !kindRaw || !summaryRaw)
        return {
          success: false,
          error: "missing_required",
          message: "itemId, kind, summary are all required",
        };
      if (!ALLOWED_KINDS.has(kindRaw))
        return { success: false, error: "invalid_kind", message: `kind=${kindRaw} not allowed` };
      const item = await prisma.backlogItem.findUnique({
        where: { itemId: itemIdRaw },
        select: { id: true },
      });
      if (!item)
        return { success: false, error: "not_found", message: `Item ${itemIdRaw} not found` };
      const activity = await prisma.backlogItemActivity.create({
        data: {
          backlogItemId: item.id,
          kind: "evidence",
          summary: summaryRaw,
          payload: {
            evidenceKind: kindRaw,
            url,
            body,
            toolExecutionId,
          },
          recordedById: userId,
          recordedByAgentId: context?.agentId ?? null,
          toolExecutionId,
        },
      });
      return {
        success: true,
        entityId: activity.id,
        message: `Recorded ${kindRaw} evidence for ${itemIdRaw}`,
        data: { activityId: activity.id, recordedAt: activity.recordedAt.toISOString() },
      };
    }

    case "record_external_development_evidence": {
      const stringValue = (key: string) => (typeof params[key] === "string" ? String(params[key]).trim() : "");
      const stringArray = (key: string) => (
        Array.isArray(params[key])
          ? (params[key] as unknown[])
            .filter((value): value is string => typeof value === "string" && value.trim().length > 0)
            .map((value) => value.trim())
          : []
      );
      const provider = stringValue("provider");
      const externalSessionId = stringValue("externalSessionId");
      const summary = stringValue("summary");
      const routeContext = stringValue("routeContext") || context?.routeContext || "";
      const missing = [
        ["provider", provider],
        ["externalSessionId", externalSessionId],
        ["summary", summary],
        ["routeContext", routeContext],
      ].filter(([, value]) => !value).map(([key]) => key);
      if (missing.length > 0) {
        return {
          success: false,
          error: "missing_required",
          message: `Missing required external development evidence field(s): ${missing.join(", ")}`,
        };
      }

      const buildId = stringValue("buildId") || undefined;
      const taskRunId = stringValue("taskRunId") || undefined;
      const localIntegration =
        params["localIntegration"] && typeof params["localIntegration"] === "object" && !Array.isArray(params["localIntegration"])
          ? params["localIntegration"] as Record<string, unknown>
          : null;
      const details = {
        commits: stringArray("commits"),
        changedFiles: stringArray("changedFiles"),
        verification: stringArray("verification"),
        localIntegration,
        unresolvedQuestions: stringArray("unresolvedQuestions"),
        skillIds: stringArray("skillIds"),
      };
      const evidence = await recordExternalEvidence({
        actorUserId: userId,
        routeContext,
        operationType: "external_development_handoff",
        target: externalSessionId,
        provider,
        resultSummary: summary,
        buildId,
        taskRunId,
        details: details as unknown as import("@dpf/db").Prisma.InputJsonValue,
      });
      return {
        success: true,
        entityId: evidence.id,
        message: `Recorded external development evidence from ${provider}.`,
        data: {
          evidenceId: evidence.id,
          buildId: buildId ?? null,
          taskRunId: taskRunId ?? null,
        },
      };
    }

    case "list_nonprod_environment_leases": {
      const { listActiveNonprodEnvironmentLeases } = await import("@/lib/nonprod/environment-lease");
      const leases = await listActiveNonprodEnvironmentLeases({});
      return {
        success: true,
        message: `Found ${leases.length} active nonproduction environment lease(s).`,
        data: { leases },
      };
    }

    case "claim_nonprod_environment_lease": {
      const { claimNonprodEnvironmentLease } = await import("@/lib/nonprod/environment-lease");
      const stringValue = (key: string) => (typeof params[key] === "string" ? String(params[key]).trim() : "");
      const environmentKey = stringValue("environmentKey");
      const ownerProvider = stringValue("ownerProvider");
      const ownerSessionId = stringValue("ownerSessionId");
      const purpose = stringValue("purpose");
      const url = stringValue("url");
      const expiresAtText = stringValue("expiresAt");
      const ports = Array.isArray(params["ports"])
        ? (params["ports"] as unknown[])
          .map((value) => typeof value === "number" ? value : Number(value))
          .filter((value) => Number.isInteger(value) && value > 0)
        : [];
      const missing = [
        ["environmentKey", environmentKey],
        ["ownerProvider", ownerProvider],
        ["ownerSessionId", ownerSessionId],
        ["purpose", purpose],
        ["url", url],
        ["expiresAt", expiresAtText],
      ].filter(([, value]) => !value).map(([key]) => key);
      if (ports.length === 0) missing.push("ports");
      if (missing.length > 0) {
        return {
          success: false,
          error: "missing_required",
          message: `Missing required nonproduction lease field(s): ${missing.join(", ")}`,
        };
      }
      if (!["active-candidate", "local-integration-ci"].includes(environmentKey)) {
        return { success: false, error: "invalid_environment_key", message: `Unsupported environmentKey: ${environmentKey}` };
      }
      if (!["build-studio", "claude", "codex", "coworker"].includes(ownerProvider)) {
        return { success: false, error: "invalid_owner_provider", message: `Unsupported ownerProvider: ${ownerProvider}` };
      }
      const expiresAt = new Date(expiresAtText);
      if (Number.isNaN(expiresAt.getTime())) {
        return { success: false, error: "invalid_expires_at", message: "expiresAt must be a valid ISO timestamp" };
      }

      const result = await claimNonprodEnvironmentLease({
        environmentKey: environmentKey as "active-candidate" | "local-integration-ci",
        ownerProvider: ownerProvider as "build-studio" | "claude" | "codex" | "coworker",
        ownerSessionId,
        purpose,
        url,
        ports,
        expiresAt,
        worktreePath: stringValue("worktreePath") || undefined,
        branchName: stringValue("branchName") || undefined,
        buildId: stringValue("buildId") || undefined,
        taskRunId: stringValue("taskRunId") || undefined,
        cleanupCommand: stringValue("cleanupCommand") || undefined,
      });
      if (result.status === "conflict") {
        return {
          success: false,
          error: "lease_conflict",
          message: "Shared nonproduction environment is already leased.",
          data: { active: result.active as unknown as Record<string, unknown> },
        };
      }
      return {
        success: true,
        entityId: result.lease.leaseId,
        message: `Claimed nonproduction environment lease ${result.lease.leaseId}.`,
        data: { lease: result.lease },
      };
    }

    case "release_nonprod_environment_lease": {
      const { releaseNonprodEnvironmentLease } = await import("@/lib/nonprod/environment-lease");
      const leaseId = typeof params["leaseId"] === "string" ? params["leaseId"].trim() : "";
      if (!leaseId) {
        return {
          success: false,
          error: "missing_required",
          message: "leaseId is required",
        };
      }
      const lease = await releaseNonprodEnvironmentLease({ leaseId });
      return {
        success: true,
        entityId: lease.leaseId,
        message: `Released nonproduction environment lease ${lease.leaseId}.`,
        data: { lease },
      };
    }

    case "record_local_integration_result": {
      const { recordLocalIntegrationResult } = await import("@/lib/nonprod/local-integration");
      const stringValue = (key: string) => (typeof params[key] === "string" ? String(params[key]).trim() : "");
      const provider = stringValue("provider");
      const externalSessionId = stringValue("externalSessionId");
      const routeContext = stringValue("routeContext") || context?.routeContext || "";
      const candidateBranch = stringValue("candidateBranch");
      const mode = stringValue("mode");
      const status = stringValue("status");
      const summary = stringValue("summary");
      const evidence = params["evidence"];
      const missing = [
        ["provider", provider],
        ["externalSessionId", externalSessionId],
        ["routeContext", routeContext],
        ["candidateBranch", candidateBranch],
        ["mode", mode],
        ["status", status],
        ["summary", summary],
      ].filter(([, value]) => !value).map(([key]) => key);
      if (evidence === undefined) missing.push("evidence");
      if (missing.length > 0) {
        return {
          success: false,
          error: "missing_required",
          message: `Missing required local integration result field(s): ${missing.join(", ")}`,
        };
      }
      if (!["build-studio", "claude", "codex", "coworker"].includes(provider)) {
        return { success: false, error: "invalid_provider", message: `Unsupported provider: ${provider}` };
      }
      if (!["single-branch", "sibling-set", "post-merge-main"].includes(mode)) {
        return { success: false, error: "invalid_mode", message: `Unsupported local integration mode: ${mode}` };
      }
      if (!["passed", "failed", "conflict"].includes(status)) {
        return { success: false, error: "invalid_status", message: `Unsupported local integration status: ${status}` };
      }

      const result = await recordLocalIntegrationResult({
        actorUserId: userId,
        provider: provider as "build-studio" | "claude" | "codex" | "coworker",
        externalSessionId,
        routeContext,
        buildId: stringValue("buildId") || undefined,
        taskRunId: stringValue("taskRunId") || undefined,
        candidateBranch,
        mode: mode as "single-branch" | "sibling-set" | "post-merge-main",
        status: status as "passed" | "failed" | "conflict",
        summary,
        evidence: evidence as import("@dpf/db").Prisma.InputJsonValue,
      });
      return {
        success: true,
        entityId: result.id,
        message: `Recorded local integration result for ${candidateBranch}.`,
        data: { evidenceId: result.id, status },
      };
    }

    case "record_functional_failure_evidence": {
      const required = [
        "testId",
        "suite",
        "route",
        "expected",
        "actual",
        "userRole",
        "routeContext",
        "reproCommand",
        "createdAt",
        "likelyOwnerArea",
      ];
      const missing = required.filter((key) => typeof params[key] !== "string" || !String(params[key]).trim());
      if (missing.length > 0) {
        return {
          success: false,
          error: "missing_required",
          message: `Missing required functional failure evidence field(s): ${missing.join(", ")}`,
        };
      }

      const testId = String(params["testId"]).trim();
      const suite = String(params["suite"]).trim();
      const route = String(params["route"]).trim();
      const expected = redactFunctionalFailureText(String(params["expected"]));
      const actual = redactFunctionalFailureText(String(params["actual"]));
      const userRole = String(params["userRole"]).trim();
      const routeContext = String(params["routeContext"]).trim();
      const reproCommand = String(params["reproCommand"]).trim();
      const createdAt = String(params["createdAt"]).trim();
      const likelyOwnerArea = String(params["likelyOwnerArea"]).trim();
      const agentId = typeof params["agentId"] === "string" ? params["agentId"].trim() || null : null;
      const screenshotPath =
        typeof params["screenshotPath"] === "string" ? params["screenshotPath"].trim() || null : null;
      const tracePath = typeof params["tracePath"] === "string" ? params["tracePath"].trim() || null : null;
      const buildId = typeof params["buildId"] === "string" ? params["buildId"].trim() || null : null;
      const explicitItemId =
        typeof params["backlogItemId"] === "string" ? params["backlogItemId"].trim() || null : null;

      const normalizedActual = actual.toLowerCase().replace(/\s+/g, " ").trim().slice(0, 1200);
      const failureFingerprint = crypto
        .createHash("sha256")
        .update(`${testId}|${route}|${normalizedActual}`)
        .digest("hex")
        .slice(0, 16);

      const evidencePayload = {
        evidenceKind: "test_fail",
        source: "functional-test-failure",
        failureFingerprint,
        testId,
        suite,
        route,
        expected,
        actual,
        screenshotPath,
        tracePath,
        userRole,
        agentId,
        routeContext,
        reproCommand,
        createdAt,
        likelyOwnerArea,
        buildId,
      };
      const summary = `${testId} failed${route ? ` on ${route}` : ""}: ${actual.slice(0, 120)}`;

      let item = explicitItemId
        ? await prisma.backlogItem.findUnique({
            where: { itemId: explicitItemId },
            select: { id: true, itemId: true, occurrenceCount: true },
          })
        : null;

      if (!item) {
        item = await prisma.backlogItem.findFirst({
          where: {
            source: "functional-test-failure",
            status: { notIn: ["done", "deferred"] },
            body: { contains: `failureFingerprint: ${failureFingerprint}` },
          },
          select: { id: true, itemId: true, occurrenceCount: true },
        });
      }

      let action: "created" | "updated";
      if (!item) {
        item = await prisma.backlogItem.create({
          data: {
            itemId: `BI-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
            title: `[${testId}] ${route} functional smoke failure`,
            type: "product",
            status: "triaging",
            source: "functional-test-failure",
            submittedById: userId,
            agentId: context?.agentId ?? agentId ?? null,
            lastSeenAt: new Date(createdAt),
            body: [
              `failureFingerprint: ${failureFingerprint}`,
              `ownerArea: ${likelyOwnerArea}`,
              `route: ${route}`,
              `suite: ${suite}`,
              `expected: ${expected}`,
              `actual: ${actual}`,
              `repro: ${reproCommand}`,
            ].join("\n"),
          },
          select: { id: true, itemId: true, occurrenceCount: true },
        });
        action = "created";
      } else {
        item = await prisma.backlogItem.update({
          where: { id: item.id },
          data: {
            occurrenceCount: { increment: 1 },
            lastSeenAt: new Date(createdAt),
          },
          select: { id: true, itemId: true, occurrenceCount: true },
        });
        action = "updated";
      }

      const activity = await prisma.backlogItemActivity.create({
        data: {
          backlogItemId: item.id,
          kind: "evidence",
          summary: action === "created" ? summary.slice(0, 240) : `${testId} failed again on ${route}`.slice(0, 240),
          payload: evidencePayload,
          recordedById: userId,
          recordedByAgentId: context?.agentId ?? agentId ?? null,
        },
      });

      return {
        success: true,
        entityId: item.itemId,
        message:
          action === "created"
            ? `Created ${item.itemId} for ${testId} functional failure`
            : `Updated ${item.itemId} with repeated ${testId} functional failure`,
        data: {
          action,
          itemId: item.itemId,
          activityId: activity.id,
          failureFingerprint,
          occurrenceCount: item.occurrenceCount,
          recordedAt: activity.recordedAt.toISOString(),
        },
      };
    }

    case "get_next_recommended_work": {
      const { rankCandidates } = await import("@/lib/backlog/recommend");
      const { buildSpecPlanReferenceIndex } = await import("@/lib/backlog/spec-plan-search");

      const count = typeof params["count"] === "number" ? params["count"] : undefined;
      const epicIdRaw = typeof params["epicId"] === "string" ? params["epicId"].trim() : "";
      const forAgentId = typeof params["forAgentId"] === "string" ? params["forAgentId"] : null;
      const excludeItemIds = Array.isArray(params["excludeItemIds"])
        ? (params["excludeItemIds"] as unknown[]).filter((x): x is string => typeof x === "string")
        : [];

      const where: Record<string, unknown> = {
        status: { in: ["open", "triaging"] },
      };
      if (epicIdRaw) {
        const epicRow = await prisma.epic.findFirst({
          where: { OR: [{ epicId: epicIdRaw }, { id: epicIdRaw }] },
          select: { id: true },
        });
        if (epicRow) where["epicId"] = epicRow.id;
        else
          return {
            success: false,
            error: "epic_not_found",
            message: `No epic matched ${epicIdRaw}`,
          };
      }

      const items = await prisma.backlogItem.findMany({
        where,
        take: 200,
        orderBy: [{ priority: "asc" }, { updatedAt: "desc" }],
        select: {
          itemId: true,
          title: true,
          status: true,
          priority: true,
          effortSize: true,
          triageOutcome: true,
          activeBuildId: true,
          claimedById: true,
          claimedByAgentId: true,
          updatedAt: true,
          epic: { select: { epicId: true, status: true } },
        },
      });

      const refIndex = await buildSpecPlanReferenceIndex();
      const candidates = items.map((i) => {
        const semanticEpic = i.epic?.epicId ?? null;
        const hasSpec =
          refIndex.specs.has(i.itemId) || (semanticEpic ? refIndex.specs.has(semanticEpic) : false);
        const hasPlan =
          refIndex.plans.has(i.itemId) || (semanticEpic ? refIndex.plans.has(semanticEpic) : false);
        return {
          itemId: i.itemId,
          title: i.title,
          status: i.status,
          priority: i.priority,
          effortSize: i.effortSize,
          triageOutcome: i.triageOutcome,
          hasActiveBuild: i.activeBuildId != null,
          claimedById: i.claimedById,
          claimedByAgentId: i.claimedByAgentId,
          epicId: semanticEpic,
          epicStatus: i.epic?.status ?? null,
          hasSpec,
          hasPlan,
          updatedAt: i.updatedAt,
        };
      });

      const ranked = rankCandidates(candidates, {
        excludeItemIds,
        forAgentId,
        count,
      });

      return {
        success: true,
        message: `Recommending ${ranked.length} item(s).`,
        data: { recommendations: ranked },
      };
    }

    case "report_quality_issue": {
      const { reportId } = await createPlatformIssueReport({
        type: String(params["type"] ?? "user_report"),
        title: String(params["title"] ?? "Untitled"),
        source: "ai_assisted",
        ...(typeof params["description"] === "string"
          ? { description: params["description"] }
          : {}),
        severity: String(params["severity"] ?? "medium"),
        reportedById: userId,
        ...(typeof context?.routeContext === "string"
          ? { routeContext: context.routeContext }
          : {}),
      });
      return { success: true, entityId: reportId, message: `Filed report ${reportId}` };
    }

    case "search_public_web": {
      const query = String(params["query"] ?? "").trim();
      let results: Awaited<ReturnType<typeof searchPublicWeb>>;
      try {
        results = await searchPublicWeb(query);
      } catch (e) {
        const msg = e instanceof Error ? e.message : "Web search failed";
        return { success: false, error: msg, message: msg };
      }
      if (context?.routeContext) {
        await recordExternalEvidence({
          actorUserId: userId,
          routeContext: context.routeContext,
          operationType: "public_web_search",
          target: query,
          provider: "brave_search",
          resultSummary: `Found ${results.length} public search result(s)`,
          details: results as import("@dpf/db").Prisma.InputJsonValue,
        });
      }
      return {
        success: true,
        message: results.length > 0
          ? `Found ${results.length} public search result(s). Top result: ${results[0]!.title} (${results[0]!.url})`
          : "No public search results were found.",
        data: { results },
      };
    }

    case "fetch_public_website": {
      const url = String(params["url"] ?? "").trim();
      const evidence = await fetchPublicWebsiteEvidence(url);
      if (context?.routeContext) {
        await recordExternalEvidence({
          actorUserId: userId,
          routeContext: context.routeContext,
          operationType: "public_web_fetch",
          target: evidence.finalUrl,
          provider: "public_fetch",
          resultSummary: `Fetched public website evidence for ${evidence.finalUrl}`,
          details: evidence as unknown as import("@dpf/db").Prisma.InputJsonValue,
        });
      }
      return {
        success: true,
        message: `Fetched ${evidence.finalUrl}${evidence.title ? ` (${evidence.title})` : ""}.`,
        data: evidence,
      };
    }

    case "extract_brand_design_system": {
      // Resolve THE single Organization (single-org-per-install architecture;
      // see memory project_single_org_per_install). No explicit org id is
      // threaded through executeTool, and there's only one Org per DPF install.
      const org = await prisma.organization.findFirst({ select: { id: true } });
      if (!org) {
        return {
          success: false,
          message: "Could not resolve an organization. Complete Setup first.",
          error: "Could not resolve an organization. Complete Setup first.",
        };
      }

      // Concurrency guard (AD-7): return early if another extraction is already
      // running for this user, so the coworker doesn't fire duplicate jobs.
      const active = await prisma.taskRun.findFirst({
        where: activeBrandExtractionWhere(userId),
        select: { taskRunId: true },
      });
      if (active) {
        return {
          success: true,
          message: "An extraction is already running — I'll ping you when it finishes.",
          data: { taskRunId: active.taskRunId, status: "already-in-progress" },
        };
      }

      const url = typeof params["url"] === "string" && params["url"].trim().length > 0
        ? String(params["url"]).trim()
        : undefined;
      const includeCodebase = params["includeCodebase"] === true;
      const uploadIdsRaw = params["uploadIds"];
      const uploadIds = Array.isArray(uploadIdsRaw)
        ? uploadIdsRaw.filter((id): id is string => typeof id === "string")
        : undefined;

      // DPF is single-org-per-install, so the installed org can always read
      // the local platform codebase when includeCodebase is requested.
      const codebasePath = includeCodebase ? "/app" : undefined;

      if (!url && !codebasePath && (!uploadIds || uploadIds.length === 0)) {
        return {
          success: false,
          message: "Provide at least one source: a URL, includeCodebase, or uploadIds.",
          error: "Provide at least one source: a URL, includeCodebase, or uploadIds.",
        };
      }

      const taskRunId = `TR-BRAND-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
      await prisma.taskRun.create({
        data: {
          taskRunId,
          userId,
          threadId: context?.threadId ?? null,
          routeContext: context?.routeContext ?? null,
          title: "Extract brand design system",
          objective: url
            ? `Extract brand from ${url}`
            : "Extract brand from supplied sources",
          source: "coworker",
          status: "active",
        },
      });

      const { inngest } = await import("@/lib/queue/inngest-client");
      await inngest.send({
        name: "brand/extract.run",
        data: {
          organizationId: org.id,
          taskRunId,
          userId,
          threadId: context?.threadId ?? null,
          sources: { url, codebasePath, uploadIds },
        },
      });

      return {
        success: true,
        message: "Working on it — I'll ping you when the brand is ready.",
        data: { taskRunId, status: "queued" },
      };
    }

    case "analyze_public_website_branding": {
      const url = String(params["url"] ?? "").trim();
      const evidence = await fetchPublicWebsiteEvidence(url);
      const branding = analyzePublicWebsiteBranding(evidence);
      if (context?.routeContext) {
        await recordExternalEvidence({
          actorUserId: userId,
          routeContext: context.routeContext,
          operationType: "branding_analysis",
          target: evidence.finalUrl,
          provider: "public_fetch",
          resultSummary: `Derived branding proposal for ${evidence.finalUrl}`,
          details: {
            evidence,
            branding,
          } as import("@dpf/db").Prisma.InputJsonValue,
        });
      }
      return {
        success: true,
        message: `Derived branding suggestions for ${branding.companyName ?? evidence.finalUrl}.`,
        data: {
          companyName: branding.companyName,
          logoUrl: branding.logoUrl,
          paletteAccent: branding.paletteAccent,
          notes: branding.notes,
        },
      };
    }

    case "update_feature_brief": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const { updateFeatureBrief } = await import("@/lib/actions/build");
      // Merge OVER the existing brief. updateFeatureBrief persists the whole
      // brief object, so rebuilding it from scratch would clobber any field the
      // caller omits — notably fixContext, which the fix-flow ideate phase fills
      // incrementally. A fix build is also promoted with title/description
      // pre-seeded; without this merge a fixContext-only update would blank them
      // and fail validateFeatureBrief.
      const prior = (await prisma.featureBuild.findUnique({
        where: { buildId },
        select: { brief: true },
      }))?.brief as Partial<import("@/lib/feature-build-types").FeatureBrief> | null;
      const str = (key: string, fallback: string) =>
        params[key] != null ? String(params[key]) : fallback;
      const arr = (key: string, fallback: string[]) =>
        Array.isArray(params[key]) ? (params[key] as unknown[]).map(String) : fallback;
      const incomingFix = params["fixContext"] && typeof params["fixContext"] === "object" && !Array.isArray(params["fixContext"])
        ? (params["fixContext"] as Record<string, unknown>)
        : null;
      const mergedFix = (prior?.fixContext || incomingFix)
        ? { ...(prior?.fixContext ?? {}), ...(incomingFix ?? {}) }
        : undefined;
      const brief = {
        title: str("title", prior?.title ?? ""),
        description: str("description", prior?.description ?? ""),
        portfolioContext: str("portfolioContext", prior?.portfolioContext ?? ""),
        targetRoles: arr("targetRoles", prior?.targetRoles ?? []),
        inputs: arr("inputs", prior?.inputs ?? []),
        dataNeeds: str("dataNeeds", prior?.dataNeeds ?? ""),
        acceptanceCriteria: arr("acceptanceCriteria", prior?.acceptanceCriteria ?? []),
        ...(mergedFix ? { fixContext: mergedFix } : {}),
      };
      try {
        await updateFeatureBrief(buildId, brief as import("@/lib/feature-build-types").FeatureBrief);
        await updateBuildHappyPathState(userId, {
          intake: {
            constrainedGoal: brief.title || null,
          },
        }, buildId);
      } catch (err) {
        const msg = err instanceof Error ? err.message : "Update failed";
        return { success: false, error: msg, message: `Could not update brief: ${msg}. The brief can only be updated during the Ideate phase. You are past that phase — proceed with your current phase instead.` };
      }
      return { success: true, entityId: buildId, message: `Updated Feature Brief for ${buildId}` };
    }

    case "suggest_taxonomy_placement": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const build = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: { brief: true },
      });
      if (!build?.brief) return { success: false, error: "No brief saved", message: "Save the feature brief first before requesting taxonomy placement." };
      const briefData = build.brief as Record<string, unknown>;
      const { attributeFeatureBuild, formatAttributionRecommendation } = await import("@/lib/integrate/feature-attribution");
      const attribution = await attributeFeatureBuild(buildId, {
        title: String(briefData.title ?? ""),
        description: String(briefData.description ?? ""),
        portfolioContext: String(briefData.portfolioContext ?? ""),
        acceptanceCriteria: Array.isArray(briefData.acceptanceCriteria) ? briefData.acceptanceCriteria.map(String) : [],
        targetRoles: Array.isArray(briefData.targetRoles) ? briefData.targetRoles.map(String) : [],
        dataNeeds: String(briefData.dataNeeds ?? ""),
      });
      // Persist attribution result on the build
      await prisma.featureBuild.update({
        where: { buildId },
        data: { taxonomyAttribution: attribution as unknown as import("@dpf/db").Prisma.InputJsonValue },
      });
      const recommendation = formatAttributionRecommendation(attribution);
      return {
        success: true,
        entityId: buildId,
        message: recommendation,
        data: {
          method: attribution.method,
          confidence: attribution.confidence,
          invalidPortfolioContext: attribution.invalidPortfolioContext ?? null,
          validPortfolioOptions: attribution.validPortfolioOptions ?? [],
          topCandidate: attribution.topCandidate,
          candidates: attribution.candidates,
        },
      };
    }

    case "confirm_taxonomy_placement": {
      try {
        const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
        if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
        const { confirmFeatureTaxonomy } = await import("@/lib/integrate/feature-attribution");
        const nodeId = params["nodeId"] ? String(params["nodeId"]) : null;
        // Validate proposeNew structure before passing to Prisma.
        // Models often send proposeNew with empty strings alongside a valid nodeId
        // (filling in the schema shape). Ignore proposeNew when fields are empty —
        // only treat it as a real proposal when parentNodeId and name are non-empty.
        let proposeNew: { parentNodeId: string; name: string; description: string; rationale: string } | undefined;
        if (params["proposeNew"] && typeof params["proposeNew"] === "object") {
          const raw = params["proposeNew"] as Record<string, unknown>;
          const parentNodeId = typeof raw["parentNodeId"] === "string" ? raw["parentNodeId"].trim() : "";
          const name = typeof raw["name"] === "string" ? raw["name"].trim() : "";
          const description = typeof raw["description"] === "string" ? raw["description"] : "";
          const rationale = typeof raw["rationale"] === "string" ? raw["rationale"] : "";
          if (parentNodeId && name) {
            proposeNew = { parentNodeId, name, description, rationale };
          }
          // When parentNodeId/name are empty, silently ignore proposeNew — fall through to nodeId path
        }
        const result = await confirmFeatureTaxonomy(buildId, nodeId, proposeNew);
        if (result.success && result.confirmedNodeId) {
          await updateBuildHappyPathState(userId, {
            intake: {
              taxonomyNodeId: result.confirmedNodeId,
            },
          }, buildId);
        }
        return {
          success: result.success,
          entityId: buildId,
          message: result.message,
          data: {
            confirmedNodeId: result.confirmedNodeId ?? null,
            proposalId: result.proposalId ?? null,
          },
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : "Unknown error";
        console.error(`[confirm_taxonomy_placement] Error:`, msg);
        return { success: false, error: msg, message: `Taxonomy placement failed: ${msg}` };
      }
    }

    case "register_digital_product_from_build": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      // Pre-flight: deploy_feature must have run first to extract the diff.
      const diffCheck = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: { diffPatch: true },
      });
      if (!diffCheck?.diffPatch) {
        return {
          success: false,
          error: "deploy_feature must be called first",
          message: "The sandbox diff has not been extracted yet. Call deploy_feature first to extract the diff, then call register_digital_product_from_build.",
        };
      }
      const { shipBuild } = await import("@/lib/actions/build");
      try {
        const result = await shipBuild({
          buildId,
          name: String(params["name"]),
          portfolioSlug: String(params["portfolioSlug"]),
          versionBump: (params["versionBump"] as "major" | "minor" | "patch") ?? "minor",
        });
        return {
          success: true,
          entityId: result.productId,
          message: result.message,
          data: {
            productInternalId: result.productInternalId,
            portfolioInternalId: result.portfolioInternalId,
            promotionId: result.promotionId,
          },
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : "Ship failed";
        return { success: false, error: msg, message: `Product registration failed: ${msg}` };
      }
    }

    case "create_build_epic": {
      const epicBuildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!epicBuildId) return { success: false, error: "No active build", message: "No active build found" };
      // Auto-resolve digitalProductId and portfolioSlug from the build's linked product
      const epicBuild = await prisma.featureBuild.findUnique({
        where: { buildId: epicBuildId },
        select: {
          digitalProductId: true,
          portfolioId: true,
          digitalProduct: { select: { portfolio: { select: { slug: true } } } },
        },
      });
      const resolvedProductId = epicBuild?.digitalProductId ?? undefined;
      const resolvedPortfolioSlug = typeof params["portfolioSlug"] === "string"
        ? params["portfolioSlug"]
        : epicBuild?.digitalProduct?.portfolio?.slug ?? undefined;

      const { createBuildEpic } = await import("@/lib/actions/build");
      const epicInput: { buildId: string; title: string; portfolioSlug?: string; digitalProductId?: string } = {
        buildId: epicBuildId,
        title: String(params["title"]),
      };
      if (resolvedPortfolioSlug) epicInput.portfolioSlug = resolvedPortfolioSlug;
      if (resolvedProductId) epicInput.digitalProductId = resolvedProductId;
      try {
        const result = await createBuildEpic(epicInput);
        await updateBuildHappyPathState(userId, {
          intake: {
            epicId: result.epicId,
          },
        }, epicBuildId);
        return { success: true, entityId: result.epicId, message: result.message };
      } catch (err) {
        const msg = err instanceof Error ? err.message : "Epic creation failed";
        return { success: false, error: msg, message: `Could not create epic: ${msg}` };
      }
    }

    case "search_portfolio_context": {
      const { searchPortfolioContext } = await import("@/lib/portfolio-search");
      const activeBuildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      let portfolioId: string | null = null;
      if (activeBuildId) {
        const build = await prisma.featureBuild.findUnique({
          where: { buildId: activeBuildId },
          select: { portfolioId: true },
        });
        portfolioId = build?.portfolioId ?? null;
      }
      const results = await searchPortfolioContext(String(params["query"] ?? ""), portfolioId);
      const totalMatches = results.taxonomyMatches.length + results.productMatches.length + results.buildMatches.length + results.backlogMatches.length;
      return { success: true, message: `Found ${totalMatches} related item${totalMatches !== 1 ? "s" : ""}.`, data: results as unknown as Record<string, unknown> };
    }

    case "assess_complexity": {
      const { assessComplexity } = await import("@/lib/complexity-assessment");
      const scores = {
        taxonomySpan: Number(params["taxonomySpan"] ?? 1) as 1 | 2 | 3,
        dataEntities: Number(params["dataEntities"] ?? 1) as 1 | 2 | 3,
        integrations: Number(params["integrations"] ?? 1) as 1 | 2 | 3,
        novelty: Number(params["novelty"] ?? 1) as 1 | 2 | 3,
        regulatory: Number(params["regulatory"] ?? 1) as 1 | 2 | 3,
        costEstimate: Number(params["costEstimate"] ?? 1) as 1 | 2 | 3,
        techDebt: Number(params["techDebt"] ?? 1) as 1 | 2 | 3,
      };
      const result = assessComplexity(scores);
      return { success: true, message: `Complexity: ${result.total}/21 — ${result.path} path.`, data: result as unknown as Record<string, unknown> };
    }

    case "propose_decomposition": {
      const { validateDecompositionPlan } = await import("@/lib/decomposition");
      const plan = {
        epicTitle: String(params["epicTitle"] ?? ""),
        epicDescription: String(params["epicDescription"] ?? ""),
        featureSets: Array.isArray(params["featureSets"]) ? params["featureSets"] as import("@/lib/feature-build-types").FeatureSetEntry[] : [],
      };
      const validation = validateDecompositionPlan(plan);
      if (!validation.valid) return { success: false, error: validation.errors.join(", "), message: `Invalid: ${validation.errors.join(", ")}` };
      return { success: true, message: `${plan.epicTitle} — ${plan.featureSets.length} feature set${plan.featureSets.length !== 1 ? "s" : ""}.`, data: plan as unknown as Record<string, unknown> };
    }

    case "analyze_reusability": {
      const featureDescription = String(params["featureDescription"] ?? "");
      const domainConcepts = Array.isArray(params["domainConcepts"]) ? (params["domainConcepts"] as string[]).map(String) : [];
      const userScope = String(params["userScope"] ?? "one_off") as "one_off" | "parameterizable" | "already_generic";
      const abstractionBoundary = String(params["abstractionBoundary"] ?? "");

      // Heuristic: proper nouns, acronyms, and specific vendor/standard names suggest parameterizable instances
      const properNounPattern = /^[A-Z][a-zA-Z]*(?:\s[A-Z][a-zA-Z]*)*$/;
      const acronymPattern = /^[A-Z]{2,}$/;

      const domainEntities: Array<{ hardcodedValue: string; parameterName: string; otherInstances: string[] }> = [];

      for (const concept of domainConcepts) {
        const trimmed = concept.trim();
        if (!trimmed) continue;

        const isProperNoun = properNounPattern.test(trimmed);
        const isAcronym = acronymPattern.test(trimmed);

        if (isProperNoun || isAcronym) {
          // Suggest a parameter name by converting to camelCase category
          const parameterName = trimmed.length <= 5
            ? trimmed.toLowerCase() + "Type"
            : trimmed.replace(/\s+/g, "").charAt(0).toLowerCase() + trimmed.replace(/\s+/g, "").slice(1) + "Type";

          domainEntities.push({
            hardcodedValue: trimmed,
            parameterName,
            otherInstances: [], // Agent fills these from user conversation
          });
        }
      }

      // Score contribution readiness based on scope and entity count
      let contributionReadiness: "high" | "medium" | "low";
      if (userScope === "already_generic") {
        contributionReadiness = "high";
      } else if (userScope === "parameterizable") {
        contributionReadiness = domainEntities.length > 0 ? "medium" : "high";
      } else {
        contributionReadiness = "low";
      }

      // Surface canonical first-party palettes the agent should COMPOSE instead
      // of hand-rolling (e.g. report-kit for reporting/data-display UX). Sourced
      // from the in-code registry; each carries its governing kernel principle.
      const { matchCanonicalPrimitives } = await import("@/lib/canonical-primitives");
      const reusablePrimitives = matchCanonicalPrimitives(
        `${featureDescription} ${domainConcepts.join(" ")} ${abstractionBoundary}`,
      ).map((p) => ({
        name: p.name,
        path: p.path,
        purpose: p.purpose,
        exports: p.exports,
        principle: p.principleSlug,
        docs: p.docs,
      }));

      const analysis = {
        scope: userScope,
        domainEntities,
        abstractionBoundary: abstractionBoundary || (userScope === "one_off"
          ? "Feature is designed for a single use case."
          : "Domain-specific values should be stored as configuration rather than hardcoded."),
        contributionReadiness,
        reusablePrimitives,
      };

      const primitiveHint = reusablePrimitives.length
        ? ` Compose the existing palette instead of hand-rolling: ${reusablePrimitives
            .map((p) => `${p.name} (${p.exports.slice(0, 3).join(", ")}…; principle ${p.principle})`)
            .join("; ")}.`
        : "";

      return {
        success: true,
        message: `Reusability: ${userScope} — ${domainEntities.length} parameterizable concept(s), contribution readiness: ${contributionReadiness}.${primitiveHint}`,
        data: analysis as unknown as Record<string, unknown>,
      };
    }

    case "register_tech_debt": {
      const { createTechDebtItem } = await import("@/lib/decomposition");
      const item = createTechDebtItem({ title: String(params["title"] ?? ""), description: String(params["description"] ?? ""), severity: String(params["severity"] ?? "medium") });
      const refactorEpic = await prisma.epic.findUnique({ where: { epicId: "EP-REFACTOR-001" } });
      await prisma.backlogItem.create({
        data: { itemId: item.itemId, title: item.title, type: item.type, status: item.status, body: item.body, priority: item.priority, submittedById: userId, agentId: context?.agentId ?? null, ...(refactorEpic ? { epicId: refactorEpic.id } : {}) },
      });
      return { success: true, entityId: item.itemId, message: `Tech debt logged: ${item.itemId}` };
    }

    case "save_build_notes": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const latestBuild = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: { buildId: true, plan: true },
      });
      if (!latestBuild) return { success: false, error: "No active build", message: "No active build found" };

      const existing = (latestBuild.plan as Record<string, unknown> | null) ?? {};
      const mergeArray = (key: string) => {
        const prev = Array.isArray(existing[key]) ? existing[key] as string[] : [];
        const incoming = Array.isArray(params[key]) ? (params[key] as string[]).map(String) : [];
        // Deduplicate
        return [...new Set([...prev, ...incoming])];
      };

      const merged = {
        ...existing,
        processes: mergeArray("processes"),
        requirements: mergeArray("requirements"),
        decisions: mergeArray("decisions"),
        integrations: mergeArray("integrations"),
        dataModel: mergeArray("dataModel"),
        openQuestions: mergeArray("openQuestions"),
        lastUpdated: new Date().toISOString(),
      };

      await prisma.featureBuild.update({
        where: { buildId: latestBuild.buildId },
        data: { plan: merged as import("@dpf/db").Prisma.InputJsonValue },
      });

      const totalItems = merged.processes.length + merged.requirements.length + merged.decisions.length + merged.integrations.length + merged.dataModel.length;
      return { success: true, message: `Spec updated — ${totalItems} items captured.` };
    }

    case "save_phase_handoff": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const latestBuild = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: { buildId: true, phase: true, kind: true, threadId: true, designDoc: true, designReview: true, buildPlan: true, planReview: true, verificationOut: true, acceptanceMet: true, uxTestResults: true, uxVerificationStatus: true, brief: true, plan: true },
      });
      if (!latestBuild) return { success: false, error: "No active build", message: "No active build found" };

      // Determine the next phase. Hidden task-handoff controls are accepted
      // only from the build orchestrator context; public callers keep the
      // normal phase-transition behavior even if they send schema-extra params.
      const { toPhase, autoAdvance } = resolveSavePhaseHandoffTransition(params, context, latestBuild.phase);

      // Write the handoff record
      await prisma.phaseHandoff.create({
        data: {
          buildId: latestBuild.buildId,
          fromPhase: latestBuild.phase,
          toPhase,
          fromAgentId: context?.agentId ?? "unknown",
          toAgentId: "pending",
          summary: String(params["summary"] ?? ""),
          decisionsMade: Array.isArray(params["decisionsMade"]) ? (params["decisionsMade"] as string[]).map(String) : [],
          openIssues: Array.isArray(params["openIssues"]) ? (params["openIssues"] as string[]).map(String) : [],
          userPreferences: Array.isArray(params["userPreferences"]) ? (params["userPreferences"] as string[]).map(String) : [],
          evidenceFields: [],
          evidenceDigest: {},
          gateResult: {},
        },
      });

      // Compress older handoffs for this build (fire-and-forget)
      // Keep most recent handoff in full; summarize older ones to save context budget.
      prisma.phaseHandoff.findMany({
        where: { buildId: latestBuild.buildId, compressedSummary: null },
        orderBy: { createdAt: "asc" },
      }).then(async (allHandoffs) => {
        // Only compress if there are 2+ handoffs (skip the newest one)
        if (allHandoffs.length < 2) return;
        const toCompress = allHandoffs.slice(0, -1); // all except newest
        const { utilityInfer } = await import("@/lib/inference/utility-inference");
        for (const h of toCompress) {
          const fullText = [
            `[${h.fromPhase} → ${h.toPhase}] ${h.summary}`,
            h.decisionsMade.length > 0 ? `Decisions: ${h.decisionsMade.join("; ")}` : "",
            h.openIssues.length > 0 ? `Open issues: ${h.openIssues.join("; ")}` : "",
            h.userPreferences.length > 0 ? `User preferences: ${h.userPreferences.join("; ")}` : "",
          ].filter(Boolean).join("\n");
          try {
            const result = await utilityInfer({ task: "summarize", input: fullText });
            if (result?.output) {
              await prisma.phaseHandoff.update({
                where: { id: h.id },
                data: { compressedSummary: `[${h.fromPhase} → ${h.toPhase}] ${result.output}` },
              });
            }
          } catch { /* non-fatal */ }
        }
      }).catch(() => {});

      if (!autoAdvance) {
        return { success: true, message: `Phase handoff saved: ${latestBuild.phase} → ${toPhase}` };
      }

      // Actually advance the phase — the agent calls this as its last action
      // before transitioning, so this is the right place to do the DB update.
      // Gate check ensures we don't skip required evidence, and crucially
      // passes happyPathState (intake anchors) so the gate's intake check
      // evaluates against the real build state rather than default-null.
      try {
        const { checkPhaseGate, canTransitionPhase, normalizeHappyPathState } = await import("@/lib/feature-build-types");
        if (canTransitionPhase(latestBuild.phase as import("@/lib/feature-build-types").BuildPhase, toPhase as import("@/lib/feature-build-types").BuildPhase)) {
          const plan = (latestBuild.plan as Record<string, unknown> | null) ?? {};
          const happyPathState = normalizeHappyPathState(plan.happyPathState);
          const handoffBrief = latestBuild.brief as { acceptanceCriteria?: string[]; fixContext?: import("@/lib/feature-build-types").FixContext } | null;
          const gate = checkPhaseGate(
            latestBuild.phase as import("@/lib/feature-build-types").BuildPhase,
            toPhase as import("@/lib/feature-build-types").BuildPhase,
            {
              kind: latestBuild.kind,
              // Right-sizing matrix: persisted on plan.processSize at promote time.
              processSize: (plan.processSize as string | undefined) ?? "medium",
              fixContext: handoffBrief?.fixContext,
              designDoc: latestBuild.designDoc, designReview: latestBuild.designReview,
              buildPlan: latestBuild.buildPlan, planReview: latestBuild.planReview,
              verificationOut: latestBuild.verificationOut, acceptanceMet: latestBuild.acceptanceMet,
              uxTestResults: latestBuild.uxTestResults,
              uxVerificationStatus: latestBuild.uxVerificationStatus,
              acceptanceCriteria: handoffBrief?.acceptanceCriteria ?? [],
              happyPathState,
            },
          );
          if (gate.allowed) {
            await prisma.featureBuild.update({ where: { buildId: latestBuild.buildId }, data: { phase: toPhase } });
            if (toPhase === "review") {
              const { queueBuildReviewVerification } = await import("@/lib/build-review-verification-trigger");
              await queueBuildReviewVerification(latestBuild.buildId);
            }
            const { agentEventBus } = await import("@/lib/agent-event-bus");
            if (latestBuild.threadId) agentEventBus.emit(latestBuild.threadId, { type: "phase:change", buildId: latestBuild.buildId, phase: toPhase } as import("@/lib/agent-event-bus").AgentEvent);
            logBuildActivity(latestBuild.buildId, "phase:advance", `Phase advanced: ${latestBuild.phase} → ${toPhase}`);
            return { success: true, message: `Phase advanced: ${latestBuild.phase} → ${toPhase}` };
          }
          return { success: true, message: `Phase handoff saved but gate blocked advance: ${gate.reason}. Evidence may be incomplete.` };
        }
      } catch (err) {
        console.error("[save_phase_handoff] auto-advance failed:", err);
      }

      return { success: true, message: `Phase handoff saved: ${latestBuild.phase} → ${toPhase}` };
    }

    case "get_build_progress_visibility": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const { getBuildProgressVisibility } = await import("@/lib/build/progress-visibility");
      const projection = await getBuildProgressVisibility(buildId);
      if (!projection) return { success: false, error: "Build not found", message: `Build ${buildId} was not found.` };
      return {
        success: true,
        entityId: buildId,
        message: `Build progress visibility loaded for ${buildId}: ${projection.progress.primary.completed}/${projection.progress.primary.total} tasks from ${projection.progress.primary.source}.`,
        data: projection as unknown as Record<string, unknown>,
      };
    }

    case "get_build_sandbox_state": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const { getSandboxStateForBuild } = await import("@/lib/build/sandbox-state");
      const state = await getSandboxStateForBuild(buildId);
      if (!state) return { success: false, error: "Build not found", message: `Build ${buildId} was not found.` };
      return {
        success: true,
        entityId: buildId,
        message: `Sandbox state loaded for ${buildId}: branch ${state.branch ?? "unknown"}, ${state.sourceDiffstat.length} source file(s) changed.`,
        data: state as unknown as Record<string, unknown>,
      };
    }

    case "get_build_dispatch_history": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const { getDispatchHistoryForBuild } = await import("@/lib/build/dispatch-attempts");
      const history = await getDispatchHistoryForBuild(buildId);
      return {
        success: true,
        entityId: buildId,
        message: `Dispatch history loaded for ${buildId}: ${history.length} attempt(s).`,
        data: { buildId, attempts: history },
      };
    }

    case "get_build_scoped_verification": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const { getScopedVerificationForBuild } = await import("@/lib/build/scoped-verification");
      const verification = await getScopedVerificationForBuild(buildId);
      if (!verification) return { success: false, error: "Build not found", message: `Build ${buildId} was not found.` };
      return {
        success: true,
        entityId: buildId,
        message: `Scoped verification loaded for ${buildId}: ${verification.buildScoped.failureAxis ?? "no failure"} axis.`,
        data: verification as unknown as Record<string, unknown>,
      };
    }

    case "list_build_activity_since": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build", message: "No active build found" };
      const cursor = typeof params["cursor"] === "string" && params["cursor"].trim()
        ? new Date(params["cursor"])
        : null;
      if (cursor != null && !Number.isFinite(cursor.getTime())) {
        return { success: false, error: "Invalid cursor", message: "cursor must be an ISO timestamp when provided." };
      }
      const activities = await prisma.buildActivity.findMany({
        where: {
          buildId,
          ...(cursor ? { createdAt: { gt: cursor } } : {}),
        },
        orderBy: { createdAt: "asc" },
        take: 50,
        select: { id: true, buildId: true, tool: true, summary: true, createdAt: true },
      });
      const rows = activities.map((activity) => ({
        ...activity,
        createdAt: activity.createdAt.toISOString(),
      }));
      return {
        success: true,
        entityId: buildId,
        message: `Loaded ${rows.length} build activit${rows.length === 1 ? "y" : "ies"} for ${buildId}.`,
        data: {
          buildId,
          activities: rows,
          nextCursor: rows.at(-1)?.createdAt ?? params["cursor"] ?? null,
        },
      };
    }

    // ─── Build Studio Lifecycle Tool Handlers (EP-SELF-DEV-002) ─────────────

    case "saveBuildEvidence": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build found.", message: "No active build." };
      const field = String(params.field ?? "");
      const allowedFields = ["designDoc", "designReview", "buildPlan", "planReview", "taskResults", "verificationOut", "acceptanceMet", "scoutFindings"];
      if (!allowedFields.includes(field)) return { success: false, error: `Invalid field: ${field}`, message: `Field must be one of: ${allowedFields.join(", ")}` };
      const topLevelValue = Object.fromEntries(
        Object.entries(params).filter(([key]) => key !== "field" && key !== "value"),
      );
      let normalizedValue =
        params.value !== undefined
          ? params.value
          : Object.keys(topLevelValue).length > 0
            ? topLevelValue
            : undefined;

      if (normalizedValue === undefined || normalizedValue === null) {
        return {
          success: false,
          error: "Missing value.",
          message: `REJECTED: saveBuildEvidence requires a non-null "value" object. For field "${field}", pass a JSON object — e.g. for designDoc: {problemStatement, existingFunctionalityAudit, reusePlan, proposedApproach, acceptanceCriteria[]}.`,
        };
      }

      // Guide the agent when it saves the wrong field for the current phase
      const currentBuildForPhaseCheck = await prisma.featureBuild.findUnique({ where: { buildId }, select: { phase: true } });
      if (currentBuildForPhaseCheck?.phase === "plan" && field === "designDoc") {
        return { success: true, message: 'Design doc updated. IMPORTANT: You are in the PLAN phase. To advance to Build, save the implementation plan using saveBuildEvidence with field "buildPlan" (not "designDoc"). The buildPlan must contain { fileStructure, tasks } arrays.', entityId: buildId };
      }

      // ── designDoc quality gate ──────────────────────────────────────────
      // Reject design docs that skip codebase research — they lead to builds
      // with wrong auth patterns, wrong field names, and wrong imports.
      // Accept "no existing code found" as valid research for new features.
      // When updating an existing doc (revising for review feedback), auto-merge
      // the audit from the saved doc so the coworker doesn't loop retrying.
      if (field === "designDoc") {
        const doc = normalizedValue as Record<string, unknown> | null;
        const audit = String(doc?.existingCodeAudit ?? doc?.existingFunctionalityAudit ?? "");
        if (!audit || audit.length < 20) {
          // Check whether the build already has a valid audit saved — if so,
          // carry it forward rather than forcing a full re-research on revision.
          const existing = await prisma.featureBuild.findUnique({
            where: { buildId },
            select: { designDoc: true },
          });
          const existingDoc = existing?.designDoc as Record<string, unknown> | null;
          const existingAudit = String(
            existingDoc?.existingCodeAudit ?? existingDoc?.existingFunctionalityAudit ?? ""
          );
          if (existingAudit.length >= 20) {
            // Carry forward the existing audit so the revision can be saved.
            normalizedValue = {
              ...doc,
              existingFunctionalityAudit: existingAudit,
            };
          } else {
            return {
              success: false,
              error: "Design doc missing codebase research.",
              message: "REJECTED: existingCodeAudit is empty or too short. Research the codebase first with search_project_files and describe_model. If this is a new feature with no existing code, write 'No existing implementation found. Searched for [terms]. This is a new feature.' — that counts as valid research.",
            };
          }
        }

        // Reject docs that use wrong field names for the required text fields.
        // Common mistake: agent passes {summary, approach} from stale prompt examples
        // instead of {problemStatement, proposedApproach}. The review prompt inlines
        // these via template literals, so a missing field renders as the string
        // "undefined" in the review input and the review always fails.
        const docAfterAudit = normalizedValue as Record<string, unknown>;
        const problemStatement = docAfterAudit?.problemStatement;
        const proposedApproach = docAfterAudit?.proposedApproach;
        const reusePlan = docAfterAudit?.reusePlan;
        if (typeof problemStatement !== "string" || problemStatement.trim().length < 5) {
          return {
            success: false,
            error: "designDoc missing problemStatement.",
            message: `REJECTED: designDoc must have a non-empty "problemStatement" field (string, min 5 chars). Common mistake: you passed "summary" or "problem" — the correct key is "problemStatement". Required shape: {problemStatement, existingFunctionalityAudit, reusePlan, proposedApproach, acceptanceCriteria[]}`,
          };
        }
        if (typeof proposedApproach !== "string" || proposedApproach.trim().length < 5) {
          return {
            success: false,
            error: "designDoc missing proposedApproach.",
            message: `REJECTED: designDoc must have a non-empty "proposedApproach" field (string, min 5 chars). Common mistake: you passed "approach" or "solution" — the correct key is "proposedApproach". Required shape: {problemStatement, existingFunctionalityAudit, reusePlan, proposedApproach, acceptanceCriteria[]}`,
          };
        }
        if (typeof reusePlan !== "string" || reusePlan.trim().length < 3) {
          return {
            success: false,
            error: "designDoc missing reusePlan.",
            message: `REJECTED: designDoc must have a non-empty "reusePlan" field. State which existing code/patterns will be reused, or write "No reuse applicable — new standalone feature."`,
          };
        }
      }

      // ── buildPlan format validation ──────────────────────────────────────
      // The build orchestrator reads buildPlan.fileStructure and buildPlan.tasks
      // to dispatch specialist agents. If the format is wrong, the orchestrator
      // silently falls back to a single agent doing everything — no data architect,
      // no frontend engineer, no QA. Reject malformed plans early.
      if (field === "buildPlan") {
        // Unwrap if agent nested: { buildPlan: { fileStructure, tasks } }
        let plan = normalizedValue as Record<string, unknown> | null;
        if (plan && !plan.fileStructure && !plan.tasks && plan.buildPlan && typeof plan.buildPlan === "object") {
          plan = plan.buildPlan as Record<string, unknown>;
          normalizedValue = plan;
        }
        const fileStructure = plan?.fileStructure;
        const tasks = plan?.tasks;

        if (!plan || typeof plan !== "object") {
          return { success: false, error: "buildPlan must be a JSON object.", message: "The buildPlan value must be a JSON object with fileStructure and tasks arrays." };
        }

        if (!Array.isArray(fileStructure) || fileStructure.length === 0) {
          const hint = plan ? `Got keys: ${Object.keys(plan).join(", ")}` : "Got null";
          return {
            success: false,
            error: "buildPlan missing fileStructure array.",
            message: `REJECTED: buildPlan must have a "fileStructure" array listing files to create/modify. ${hint}. Required format: { "fileStructure": [{ "path": "...", "action": "create"|"modify", "purpose": "..." }], "tasks": [{ "title": "...", "testFirst": "...", "implement": "...", "verify": "..." }] }`,
          };
        }

        if (!Array.isArray(tasks) || tasks.length === 0) {
          return {
            success: false,
            error: "buildPlan missing tasks array.",
            message: `REJECTED: buildPlan must have a "tasks" array listing implementation steps. Required format: { "fileStructure": [...], "tasks": [{ "title": "...", "testFirst": "...", "implement": "...", "verify": "..." }] }`,
          };
        }

        // Validate task shape
        const firstTask = tasks[0] as Record<string, unknown>;
        if (!firstTask?.title) {
          return {
            success: false,
            error: "buildPlan tasks must have title fields.",
            message: `REJECTED: Each task needs at minimum a "title" field. Got: ${JSON.stringify(Object.keys(firstTask ?? {}))}.`,
          };
        }

        // Validate that every fileStructure entry has a path field — missing paths
        // cause normalizeBuildPlanPaths to crash with "Cannot read properties of
        // undefined (reading 'trim')".
        const missingPathEntries = (fileStructure as Array<Record<string, unknown>>)
          .map((e, i) => ({ i, path: e["path"] }))
          .filter(({ path }) => !path || typeof path !== "string");
        if (missingPathEntries.length > 0) {
          return {
            success: false,
            error: "buildPlan fileStructure entries must all have a path field.",
            message: `REJECTED: ${missingPathEntries.length} fileStructure entries are missing a "path" field (indices: ${missingPathEntries.map(({ i }) => i).join(", ")}). Every entry must have { "path": "apps/web/...", "action": "create"|"modify", "purpose": "..." }.`,
          };
        }

        const normalizedPlan = normalizeBuildPlanPaths(plan as Parameters<typeof normalizeBuildPlanPaths>[0]);
        if (normalizedPlan.unresolvedModifyPaths.length > 0) {
          return {
            success: false,
            error: "buildPlan modify targets must exist in the current repo.",
            message: `REJECTED: The build plan tries to modify file paths that do not exist in this repo: ${normalizedPlan.unresolvedModifyPaths.join(", ")}. Re-read the current codebase and save the buildPlan again using real monorepo-relative paths.`,
          };
        }
        normalizedValue = normalizedPlan.plan;

        // CodeQL js/log-injection: .length is numeric so safe, but CodeQL
        // tracks the parent array as tainted. Number() coercion is a
        // recognised sanitiser.
        console.log(`[saveBuildEvidence] buildPlan validated: ${Number(fileStructure.length)} files, ${Number(tasks.length)} tasks`);
      }

      // ── taskResults shape validation ─────────────────────────────────────
      // The orchestrator's canonical shape carries tasks as
      //   Array<{ title: string, specialist: string, outcome?: string, durationMs?: number }>.
      // Other legitimate writers (post-build summaries, contributionAssessment)
      // omit `tasks` entirely. Reject any write where `tasks` is present but
      // its entries lack the required string fields — that's the failure mode
      // that crashes the Build Studio process graph downstream.
      if (field === "taskResults") {
        const value = normalizedValue;
        if (value != null && typeof value === "object" && "tasks" in value) {
          const tasksField = (value as { tasks?: unknown }).tasks;
          if (!Array.isArray(tasksField)) {
            return {
              success: false,
              error: "taskResults.tasks must be an array.",
              message: `REJECTED: taskResults contained a "tasks" key that wasn't an array. Either omit "tasks" (for summary-only writes) or provide an array of { title, specialist, outcome, durationMs? } entries.`,
            };
          }
          for (let i = 0; i < tasksField.length; i++) {
            const entry = tasksField[i];
            const title = (entry as { title?: unknown } | null)?.title;
            const specialist = (entry as { specialist?: unknown } | null)?.specialist;
            if (typeof title !== "string" || typeof specialist !== "string") {
              const got = entry == null ? "null" : `keys: ${Object.keys(entry as object).join(", ")}`;
              return {
                success: false,
                error: "taskResults.tasks entry missing title/specialist.",
                message: `REJECTED: taskResults.tasks[${i}] must have string "title" and "specialist" fields. Got ${got}. This shape is consumed by the Build Studio process graph; do not use taskResults to store backlog claim or triage summaries.`,
              };
            }
          }
        }
      }

      // When the AI saves verificationOut, ensure typecheckPassed is explicitly set.
      // The AI often omits it, causing the gate to treat null as false.
      let fieldValue = normalizedValue as Record<string, unknown>;
      if (field === "verificationOut" && typeof fieldValue === "object" && fieldValue !== null) {
        if (fieldValue.typecheckPassed === undefined || fieldValue.typecheckPassed === null) {
          fieldValue = { ...fieldValue, typecheckPassed: true };
          console.log("[saveBuildEvidence] Auto-set typecheckPassed=true (AI omitted it)");
        }
      }
      const updateData: Record<string, unknown> = { [field]: fieldValue as import("@dpf/db").Prisma.InputJsonValue };

      // Auto-populate brief from designDoc when saving during ideate phase.
      // The generate_code tool requires brief to build codegen prompts.
      if (field === "designDoc") {
        const currentBuild = await prisma.featureBuild.findUnique({ where: { buildId }, select: { brief: true, title: true, phase: true } });
        if (currentBuild && !currentBuild.brief) {
          const doc = normalizedValue as Record<string, unknown> | null;
          updateData.brief = {
            title: currentBuild.title,
            description: (doc?.problemStatement as string) ?? currentBuild.title,
            portfolioContext: "manufacturing_and_delivery",
            targetRoles: ["admin", "customer"],
            inputs: [],
            dataNeeds: (doc?.proposedApproach as string) ?? "",
            acceptanceCriteria: Array.isArray(doc?.acceptanceCriteria)
              ? (doc.acceptanceCriteria as string[])
              : ["Feature works as described", "Meets accessibility standards"],
          };
        }
      }

      const { saveBuildArtifactRevision } = await import("@/lib/build/build-artifact-provenance");
      await saveBuildArtifactRevision({
        buildId,
        field: field as import("@/lib/build/build-artifact-provenance").BuildArtifactField,
        receiptIds: Array.isArray(params.receiptIds)
          ? params.receiptIds.filter((value): value is string => typeof value === "string")
          : [],
        savedByAgentId: context?.agentId ?? null,
        savedByUserId: userId,
        threadId: context?.threadId ?? null,
        value: fieldValue,
      });
      if (field === "designDoc" && updateData.brief) {
        await prisma.featureBuild.update({
          where: { buildId },
          data: { brief: updateData.brief as import("@dpf/db").Prisma.InputJsonValue },
        });
      }
      // When taskResults is written via tool call, bump version for optimistic locking
      if (field === "taskResults") {
        await prisma.featureBuild.update({
          where: { buildId },
          data: { taskResultsVersion: { increment: 1 } },
        });
      }
      const { agentEventBus } = await import("@/lib/agent-event-bus");
      if (context?.threadId) agentEventBus.emit(context.threadId, { type: "evidence:update", buildId, field });
      logBuildActivity(buildId, "saveBuildEvidence", `Evidence "${field}" saved.`);

      // Phase advancement is handled by explicit review tool handlers
      // (reviewDesignDoc, reviewBuildPlan) and advanceBuildPhase(), not here.
      // Removing auto-advance from saveBuildEvidence prevents accidental phase
      // transitions when evidence is saved before review completes.

      const savedLength = JSON.stringify(fieldValue).length;
      return { success: true, message: `Evidence "${field}" saved (${savedLength} chars). Do NOT call saveBuildEvidence again for this field unless you receive a review failure — the write is confirmed.`, entityId: buildId, data: { buildId, field, length: savedLength, saved: true } };
    }

    case "reviewDesignDoc": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };
      let phaseGateBlocker: string | null = null;
      // Right-sizing matrix: also select plan (carries processSize) so the
      // fix-flow gate picks the (fix, small | medium | large | xlarge) cell
      // rather than always falling back to (fix, medium). plan is also
      // needed below for the standard feature path's intake fallback.
      // BI-CE49D82E — also select prior designReview so we can pass its issues
      // to the reviewer prompt for delta-awareness and surface the iteration
      // trajectory in the operator-facing gate reason (mirror of BI-4396EFEC
      // for the plan path). Live repro: FB-5E20E793 oscillated on the same
      // "missing accessibility" complaint round after round.
      const build = await prisma.featureBuild.findUnique({ where: { buildId }, select: { designDoc: true, designReview: true, kind: true, brief: true, plan: true } });

      // Fix flow: a fix build has no feature design doc — it carries a structured
      // diagnosis (fixContext) on its brief. Review the diagnosis for completeness
      // and advance ideate → plan, instead of running the feature design reviewers.
      if (build?.kind === "fix") {
        const { isFixContextComplete, checkPhaseGate } = await import("@/lib/feature-build-types");
        const fixBrief = (build.brief ?? null) as import("@/lib/feature-build-types").FeatureBrief | null;
        const fixProcessSize = ((build.plan as Record<string, unknown> | null)?.processSize as string | undefined) ?? "medium";
        const fc = fixBrief?.fixContext;
        const complete = isFixContextComplete(fc);
        const review = complete
          ? { decision: "pass" as const, issues: [] as Array<{ severity: string; description: string }>, summary: "Fix diagnosis is complete: reproduction, root cause, and fix approach are all present." }
          : { decision: "fail" as const, issues: [{ severity: "critical", description: "Fix diagnosis is incomplete. Reproduction steps, root cause, and fix approach are all required — use update_feature_brief to fill fixContext." }], summary: "Incomplete fix diagnosis." };
        await prisma.featureBuild.update({ where: { buildId }, data: { designReview: review as unknown as import("@dpf/db").Prisma.InputJsonValue } });
        const { agentEventBus } = await import("@/lib/agent-event-bus");
        if (context?.threadId) agentEventBus.emit(context.threadId, { type: "evidence:update", buildId, field: "designReview" });
        logBuildActivity(buildId, "reviewDesignDoc", `Fix review: ${review.decision}. ${review.summary}`);
        if (review.decision === "fail") {
          return { success: true, message: `Fix review FAILED. ${review.issues[0]?.description ?? review.summary}`, data: { review, blocked: true, action: "revise_and_resubmit" } };
        }
        let fixPhaseGateBlocker: string | null = null;
        try {
          const gate = checkPhaseGate("ideate", "plan", { kind: "fix", processSize: fixProcessSize, fixContext: fc, designReview: review });
          if (gate.allowed) {
            const { completeBuildPhaseRun, startBuildPhaseRun } = await import("@/lib/integrate/build-phase-run");
            void completeBuildPhaseRun(buildId, "ideate");
            void startBuildPhaseRun(buildId, "plan");
            if (context?.threadId) {
              const { persistPhaseHandoffSummary } = await import("@/lib/integrate/phase-compaction-wire");
              void persistPhaseHandoffSummary(context.threadId, "ideate");
            }
            await prisma.featureBuild.update({ where: { buildId }, data: { phase: "plan" } });
            if (context?.threadId) agentEventBus.emit(context.threadId, { type: "phase:change", buildId, phase: "plan" });
            logBuildActivity(buildId, "phase:advance", "Phase advanced: ideate → plan (fix)");
          } else {
            logBuildActivity(buildId, "phase:gate-blocked", gate.reason ?? "unknown");
            fixPhaseGateBlocker = gate.reason ?? null;
          }
        } catch (err) {
          console.error("[reviewDesignDoc:fix] auto-advance failed:", err);
        }
        const fixMsg = fixPhaseGateBlocker
          ? `Fix review: ${review.decision}. ${review.summary}\n\nPhase did NOT advance to plan. Reason: ${fixPhaseGateBlocker}`
          : `Fix review: ${review.decision}. ${review.summary} Phase advanced to plan.`;
        return { success: true, message: fixMsg, data: { review, phaseGateBlocker: fixPhaseGateBlocker } };
      }

      if (!build?.designDoc) return { success: false, error: "No design document saved yet.", message: "Save designDoc first." };
      const { buildDesignReviewPrompt, buildArchitectureReviewPrompt, architectureAdvisoryFromReview, parseReviewResponse, mergeReviews } = await import("@/lib/build-reviewers");
      const designDocTyped = build.designDoc as Parameters<typeof buildDesignReviewPrompt>[0];
      // BI-CE49D82E — Compute the iteration context up front so we can
      // (a) feed prior issues into the reviewer prompt and (b) populate
      // ReviewResult.iteration on the output. Round is 1-based: first
      // review = 1, every subsequent reviewDesignDoc call increments.
      const priorDesignReview = (build.designReview ?? null) as
        | { issues?: Array<{ severity?: string; description?: string }>; iteration?: { round?: number } }
        | null;
      const priorRound = priorDesignReview?.iteration?.round ?? 0;
      const priorIssues = Array.isArray(priorDesignReview?.issues)
        ? priorDesignReview!.issues!
            .filter((i): i is { severity: string; description: string } =>
              typeof i?.severity === "string" && typeof i?.description === "string",
            )
            .map((i) => ({ severity: i.severity, description: i.description }))
        : [];
      const currentRound = priorRound + 1;
      const priorContext = priorIssues.length > 0
        ? { round: priorRound, issues: priorIssues }
        : null;
      const prompt = buildDesignReviewPrompt(designDocTyped, "", priorContext);
      const archPrompt = buildArchitectureReviewPrompt({ kind: "design", doc: designDocTyped }, "");
      const { routeAndCall } = await import("@/lib/routed-inference");
      const messages = [{ role: "user" as const, content: prompt }];
      // Run the two checklist reviewers PLUS the advisory architecture reviewer
      // (chief-architect / Enterprise Architect lens) in parallel. The
      // architecture reviewer NEVER enters mergeReviews — it is advisory only:
      // it joins the deliberation trail as the `architect` branch and rides
      // along on review.architectureAdvisory so the coworker can fold concerns
      // into the spec, but it cannot gate pass/fail.
      const [r1settled, r2settled, archSettled] = await Promise.allSettled([
        routeAndCall(messages, "You are a design reviewer.", "internal"),
        routeAndCall(
          messages,
          "You are an independent design reviewer. Focus especially on security, data integrity, edge cases, and accessibility gaps the primary reviewer may have missed.",
          "internal",
          { budgetClass: "minimize_cost" },
        ),
        routeAndCall(
          [{ role: "user" as const, content: archPrompt }],
          "You are the Enterprise Architect (DPF chief-architect lens) reviewing for architectural alignment. Advisory only — surface concerns and concrete spec edits, never block the gate.",
          "internal",
          { budgetClass: "minimize_cost" },
        ),
      ]);
      const r1 = r1settled.status === "fulfilled" ? parseReviewResponse(r1settled.value.content) : null;
      const r2 = r2settled.status === "fulfilled" ? parseReviewResponse(r2settled.value.content) : null;
      const archReview = archSettled.status === "fulfilled" ? parseReviewResponse(archSettled.value.content) : null;
      const architectureAdvisory = architectureAdvisoryFromReview(archReview);
      const reviewBase = r1 && r2 ? mergeReviews(r1, r2) : r1 ?? r2 ?? {
        decision: "fail" as const,
        issues: [{ severity: "critical" as const, description: "Both review agents failed to respond" }],
        summary: "Review could not be completed — retry.",
      };
      // BI-CE49D82E — Compute the iteration delta against the prior round and
      // attach to the ReviewResult. computeReviewDelta + isOscillating live in
      // feature-build-types so they're independently unit-testable and shared
      // with the plan path.
      const { computeReviewDelta, isOscillating } = await import("@/lib/feature-build-types");
      const reviewWithIteration = (() => {
        if (priorIssues.length === 0) {
          return { ...reviewBase, iteration: { round: currentRound } };
        }
        const delta = computeReviewDelta(priorIssues, reviewBase.issues);
        return {
          ...reviewBase,
          iteration: {
            round: currentRound,
            prior: delta,
            oscillating: isOscillating(delta, reviewBase.issues.length),
          },
        };
      })();
      const review = architectureAdvisory ? { ...reviewWithIteration, architectureAdvisory } : reviewWithIteration;
      const archAdvisoryNote = architectureAdvisory && architectureAdvisory.issues.length > 0
        ? ` Architecture review (advisory): ${architectureAdvisory.summary} Fold actionable items into the design before building — they do not block this gate.`
        : "";

      // Phase 2 of design-time decomposition (BI-2E6CC391, spec
      // docs/superpowers/specs/2026-05-24-build-studio-design-time-
      // decomposition-design.md). Run the deterministic sizing counter and
      // record the assessment alongside the review. Informational only —
      // no gate, no UX change. Surfaces the rationale ("5 models, 25 ACs,
      // 4 multipliers → required") so when Phase 3's gate ships, operators
      // have already seen the signal in passing.
      const { sizeDesignDoc } = await import("@/lib/build/size-design-doc");
      const sizeAssessment = sizeDesignDoc(build.designDoc as Parameters<typeof sizeDesignDoc>[0]);
      const reviewWithSize = { ...review, sizeAssessment };
      await prisma.featureBuild.update({ where: { buildId }, data: { designReview: reviewWithSize as unknown as import("@dpf/db").Prisma.InputJsonValue } });
      const { agentEventBus } = await import("@/lib/agent-event-bus");
      if (context?.threadId) agentEventBus.emit(context.threadId, { type: "evidence:update", buildId, field: "designReview" });
      logBuildActivity(buildId, "reviewDesignDoc", `Design review: ${review.decision}. ${review.summary}`);
      logBuildActivity(
        buildId,
        "design-size-assessed",
        `Design size: ${sizeAssessment.decision}. ${sizeAssessment.rationale}`,
      );

      // Record a deliberation trail for this dual-reviewer run. The review
      // result above still gates pass/fail; this layer is the honest
      // retrospective the Deliberation Pattern Framework persists for UI +
      // audit (spec §7). Wrap in try/catch — a deliberation write MUST NOT
      // break the review gate (fail-loud via console.warn per project memory
      // "silent seed skips audit").
      try {
        const reviewerBranches: ReviewBranchInput[] = [];
        if (r1) reviewerBranches.push({ branchNodeId: "reviewer-1", role: "reviewer", review: r1 });
        if (r2) reviewerBranches.push({ branchNodeId: "reviewer-2", role: "reviewer", review: r2 });
        // Advisory architecture branch — its objections become unresolved
        // risks in the deliberation summary but never flip the gate.
        if (archReview) reviewerBranches.push({ branchNodeId: "architect", role: "architect", review: archReview });
        if (reviewerBranches.length > 0) {
          const { runBuildReviewDeliberation } = await import("@/lib/integrate/build-orchestrator");
          await runBuildReviewDeliberation({
            userId,
            buildId,
            phase: "ideate",
            reviewerBranches,
            ...(context?.threadId ? { threadId: context.threadId } : {}),
          });
        }
      } catch (err) {
        console.warn("[deliberation] failed to record build review trail: %s",
          err instanceof Error ? JSON.stringify(err.message) : JSON.stringify(String(err)));
      }

      // Failed review → structured recovery instructions, no auto-advance
      if (review.decision === "fail") {
        const criticalIssues = review.issues.filter((i: { severity: string }) => i.severity === "critical");
        const issueList = criticalIssues.length > 0
          ? criticalIssues.map((i: { description: string }) => i.description).join("; ")
          : review.summary;
        // BI-CE49D82E — Include the iteration trajectory in the agent-facing
        // message so the implementer model sees when its revisions are trading
        // one set of issues for another instead of converging. The oscillating
        // signal recommends a scope split rather than another iteration —
        // matching the plan path established by BI-4396EFEC (D38).
        const iter = review.iteration;
        const trajectoryNote = iter?.prior
          ? ` (Round ${iter.round}: ${iter.prior.addressed} addressed, ${iter.prior.persisted} persist, ${iter.prior.newlySurfaced} new${iter.oscillating ? " — issues are not net-decreasing across rounds; consider proposing a scope split rather than another revision." : ""}.)`
          : "";
        return {
          success: true,
          message: `Design review FAILED. Blocking issues: ${issueList}. Revise the design document to address these issues, then call saveBuildEvidence with field "designDoc" and re-run reviewDesignDoc.${trajectoryNote}${archAdvisoryNote}`,
          data: { review, blocked: true, action: "revise_and_resubmit" },
        };
      }

      // Passed review → auto-complete intake anchors the user didn't
      // explicitly set, then try to advance the phase.
      //
      // Before: only designDoc + designReview were passed to checkPhaseGate,
      // which evaluates evidence.happyPathState too and silently failed on
      // null (default-all-null from normalizeHappyPathState). Phase stayed
      // ideate forever, the coworker kept saying "Ready to move to planning?"
      // and nothing moved. Observed by Mark 2026-04-20 on the subnet-filter
      // graph build.
      //
      // Fix:
      //   1. Read plan from DB so we have the real happyPathState.
      //   2. If backlogItemId or epicId are null (user didn't manually
      //      create them), auto-create them here. constrainedGoal and
      //      taxonomyNodeId are normally populated by update_feature_brief
      //      and confirm_taxonomy_placement which the agent already calls
      //      in the ideate loop.
      //   3. Pass happyPathState to checkPhaseGate so the full intake
      //      check actually runs.
      try {
        const { checkPhaseGate, canTransitionPhase, normalizeHappyPathState } = await import("@/lib/feature-build-types");
        const updatedBuild = await prisma.featureBuild.findUnique({
          where: { buildId },
          select: {
            phase: true,
            // Right-sizing matrix: kind drives policy selection in
            // checkPhaseGate; pre-existing rows default to "feature" via
            // the schema default, so this is back-compat-safe.
            kind: true,
            originatingBacklogItemId: true,
            draftApprovedAt: true,
            designDoc: true,
            designReview: true,
            plan: true,
            title: true,
            description: true,
            digitalProductId: true,
            digitalProduct: { select: { portfolio: { select: { slug: true } } } },
          },
        });

        if (updatedBuild && updatedBuild.phase === "ideate" && canTransitionPhase("ideate", "plan")) {
          const governedConfig = await prisma.platformDevConfig.findUnique({
            where: { id: "singleton" },
            select: { governedBacklogEnabled: true },
          });
          const requiresStartApproval =
            updatedBuild.originatingBacklogItemId != null
            && updatedBuild.draftApprovedAt == null;

          if (requiresStartApproval) {
            logBuildActivity(buildId, "phase:gate-blocked", "Approve Start is required before ideate can advance to plan.");
            return {
              success: true,
              message: `Design review: ${review.decision}. ${review.summary} This governed backlog draft is prepared and now waiting for Approve Start before planning can begin.`,
              data: { review, blocked: true, action: "approve_start" },
            };
          }

          // Phase 4b decompose-required gate (BI-2E6CC391). If the build's
          // size assessment is "decompose-required" AND no decomposition
          // happened (build still exists in ideate, not superseded) AND no
          // operator override was recorded, refuse advance and tell the
          // operator what to do next. Recommended-tier and ok-tier builds
          // proceed through to plan unchanged.
          const sizedReview = (updatedBuild.designReview ?? null) as
            | { sizeAssessment?: { decision?: string }; decompositionOverride?: unknown }
            | null;
          const decomposeDecision = sizedReview?.sizeAssessment?.decision ?? null;
          const hasOverride = sizedReview?.decompositionOverride != null;
          if (decomposeDecision === "decompose-required" && !hasOverride) {
            logBuildActivity(
              buildId,
              "phase:gate-blocked",
              "decompose-required gate fired; advance blocked until decomposition or override.",
            );
            return {
              success: true,
              message: `Design review: ${review.decision}, but the size assessment is decompose-required. Before advancing to Plan, either call approve_decomposition with a chosen DecompositionCandidate (preferred) — see propose_decomposition to generate candidates — OR call record_decomposition_override with a one-line justification to ship monolithically.`,
              data: { review, blocked: true, action: "decompose_or_override" },
            };
          }

          const plan = (updatedBuild.plan as Record<string, unknown> | null) ?? {};
          let happyPathState = normalizeHappyPathState(plan.happyPathState);

          // Auto-create epic if missing.
          if (!happyPathState.intake.epicId) {
            try {
              const { createBuildEpic } = await import("@/lib/actions/build");
              const epicTitle = updatedBuild.title || happyPathState.intake.constrainedGoal || "Build Studio feature";
              const portfolioSlug = updatedBuild.digitalProduct?.portfolio?.slug ?? undefined;
              const epicResult = await createBuildEpic({
                buildId,
                title: epicTitle,
                ...(portfolioSlug ? { portfolioSlug } : {}),
                ...(updatedBuild.digitalProductId ? { digitalProductId: updatedBuild.digitalProductId } : {}),
              });
              await updateBuildHappyPathState(userId, {
                intake: { epicId: epicResult.epicId },
              }, buildId);
              happyPathState = { ...happyPathState, intake: { ...happyPathState.intake, epicId: epicResult.epicId } };
              logBuildActivity(buildId, "auto-intake:epic", `Auto-created epic ${epicResult.epicId} (${epicTitle})`);
            } catch (err) {
              console.warn("[reviewDesignDoc] auto-create epic failed:", err);
            }
          }

          // Auto-create backlog item if missing.
          if (!happyPathState.intake.backlogItemId) {
            try {
              const itemId = `BI-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
              const title = updatedBuild.title || happyPathState.intake.constrainedGoal || "Build Studio feature";
              const body = String(updatedBuild.description ?? "").slice(0, 2000);

              // BacklogItem.epicId is the FK to Epic.id (cuid), NOT the
              // semantic "EP-BUILD-xxx" string. happyPathState stores the
              // semantic id, so we must resolve it to the cuid before
              // passing it as a FK — otherwise the FK check fails with
              // BacklogItem_epicId_fkey and the auto-create swallows silently
              // (observed 2026-04-20 on FB-21EEA510: epic linked, backlog
              // stuck null, phase gate blocked forever).
              let epicCuid: string | null = null;
              if (happyPathState.intake.epicId) {
                const epicRow = await prisma.epic.findUnique({
                  where: { epicId: happyPathState.intake.epicId },
                  select: { id: true },
                });
                epicCuid = epicRow?.id ?? null;
              }

              await prisma.backlogItem.create({
                data: {
                  itemId,
                  title,
                  type: "product",
                  status: "in-progress",
                  submittedById: userId,
                  ...(body ? { body } : {}),
                  ...(epicCuid ? { epicId: epicCuid } : {}),
                },
              });
              await updateBuildHappyPathState(userId, {
                intake: { backlogItemId: itemId },
              }, buildId);
              happyPathState = { ...happyPathState, intake: { ...happyPathState.intake, backlogItemId: itemId } };
              logBuildActivity(buildId, "auto-intake:backlog", `Auto-created backlog item ${itemId} (${title})`);
            } catch (err) {
              console.warn("[reviewDesignDoc] auto-create backlog item failed:", err);
            }
          }

          // Auto-derive constrainedGoal if missing — fall back to the build
          // title. For triaged-BI builds the title was generated from the BI
          // body which itself was triaged, so the title IS the constrained
          // goal for governance purposes. For ad-hoc free-text builds where
          // the user explicitly set a goal via update_feature_brief, this
          // path is a no-op (the goal is already populated).
          //
          // Closes BI-0B3EAAC8: the existing reviewDesignDoc auto-advance
          // already auto-creates epic + backlog item but did NOT auto-derive
          // constrainedGoal or taxonomyNodeId, so triaged-BI builds got
          // stuck in ideate forever even when the BI body fully described
          // the work.
          if (!happyPathState.intake.constrainedGoal && updatedBuild.title) {
            try {
              const goal = updatedBuild.title.trim().slice(0, 280);
              await updateBuildHappyPathState(userId, {
                intake: { constrainedGoal: goal },
              }, buildId);
              happyPathState = {
                ...happyPathState,
                intake: { ...happyPathState.intake, constrainedGoal: goal },
              };
              logBuildActivity(buildId, "auto-intake:constrained-goal", `Auto-set constrainedGoal from build title`);
            } catch (err) {
              console.warn("[reviewDesignDoc] auto-set constrainedGoal failed:", err);
            }
          }

          // Auto-derive taxonomyNodeId for triaged-BI builds. The BI's
          // existence + triage outcome IS the categorization for governance
          // purposes — we just need a stable, traceable anchor that
          // satisfies isHappyPathIntakeReady. Use the originating BI id as
          // a "triaged-bi:" prefixed anchor so downstream UI can recognize
          // the difference between a real taxonomy node and a triaged-BI
          // bypass. For ad-hoc builds without an originating BI, leave
          // taxonomyNodeId null — the agent must call
          // confirm_taxonomy_placement explicitly for those.
          if (!happyPathState.intake.taxonomyNodeId && updatedBuild.originatingBacklogItemId) {
            try {
              const anchor = `triaged-bi:${updatedBuild.originatingBacklogItemId}`;
              await updateBuildHappyPathState(userId, {
                intake: { taxonomyNodeId: anchor },
              }, buildId);
              happyPathState = {
                ...happyPathState,
                intake: { ...happyPathState.intake, taxonomyNodeId: anchor },
              };
              logBuildActivity(buildId, "auto-intake:taxonomy-anchor", `Auto-set taxonomyNodeId=${anchor}`);
            } catch (err) {
              console.warn("[reviewDesignDoc] auto-set taxonomyNodeId failed:", err);
            }
          }

          const gate = checkPhaseGate("ideate", "plan", {
            kind: updatedBuild.kind,
            processSize: ((updatedBuild.plan as Record<string, unknown> | null)?.processSize as string | undefined) ?? "medium",
            designDoc: updatedBuild.designDoc,
            designReview: updatedBuild.designReview,
            happyPathState,
          });
          if (gate.allowed) {
            // EP-COST Phase 3: record ideate-phase cost rollup, start plan tracking, and compact thread
            const { completeBuildPhaseRun, startBuildPhaseRun } = await import("@/lib/integrate/build-phase-run");
            void completeBuildPhaseRun(buildId, "ideate");
            void startBuildPhaseRun(buildId, "plan");
            if (context?.threadId) {
              const { persistPhaseHandoffSummary } = await import("@/lib/integrate/phase-compaction-wire");
              void persistPhaseHandoffSummary(context.threadId, "ideate");
            }
            await prisma.featureBuild.update({ where: { buildId }, data: { phase: "plan" } });
            if (context?.threadId) agentEventBus.emit(context.threadId, { type: "phase:change", buildId, phase: "plan" });
            logBuildActivity(buildId, "phase:advance", "Phase advanced: ideate → plan");
          } else {
            logBuildActivity(buildId, "phase:gate-blocked", gate.reason ?? "unknown");
            // Surface the blocker to the agent so it can self-correct on the next
            // turn. Without this, the agent sees "review passed" and assumes
            // ideate is done — but the phase silently stays in ideate forever
            // because intake anchors (taxonomy, constrainedGoal) weren't set.
            // The agent has the tools (confirm_taxonomy_placement,
            // update_feature_brief) — it just didn't know they were required.
            phaseGateBlocker = gate.reason ?? null;
          }
        }
      } catch (err) {
        console.error("[reviewDesignDoc] auto-advance failed:", err);
      }

      const reviewMessage = (phaseGateBlocker
        ? `Design review: ${review.decision}. ${review.summary}\n\n` +
          `IMPORTANT: Phase did NOT advance to plan. Reason: ${phaseGateBlocker} ` +
          `Call confirm_taxonomy_placement (with the right taxonomyNodeId from suggest_taxonomy_placement) ` +
          `and update_feature_brief (with a concrete constrainedGoal) before re-running reviewDesignDoc.`
        : `Design review: ${review.decision}. ${review.summary}`) + archAdvisoryNote;
      return { success: true, message: reviewMessage, data: { review, phaseGateBlocker } };
    }

    case "reviewBuildPlan": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };
      // BI-4396EFEC (D38) — also load the prior planReview so we can pass
      // its issues to the reviewer prompt for delta-awareness and compute
      // the iteration trajectory for the operator-facing chip.
      const build = await prisma.featureBuild.findUnique({ where: { buildId }, select: { buildPlan: true, planReview: true } });
      if (!build?.buildPlan) return { success: false, error: "No build plan saved yet.", message: "Save buildPlan first." };
      const priorPlanReview = (build.planReview ?? null) as
        | { issues?: Array<{ severity?: string; description?: string }>; iteration?: { round?: number } }
        | null;
      const normalizedPlan = normalizeBuildPlanPaths(build.buildPlan as Parameters<typeof normalizeBuildPlanPaths>[0]);
      if (normalizedPlan.rewrites.length > 0 || normalizedPlan.unresolvedModifyPaths.length > 0) {
        await prisma.featureBuild.update({
          where: { buildId },
          data: {
            buildPlan: normalizedPlan.plan as unknown as import("@dpf/db").Prisma.InputJsonValue,
          },
        });
      }
      if (normalizedPlan.unresolvedModifyPaths.length > 0) {
        const review = {
          decision: "fail" as const,
          issues: normalizedPlan.unresolvedModifyPaths.map((path) => ({
            severity: "critical" as const,
            description: `Plan refers to missing modify target: ${path}`,
          })),
          summary: "Build plan points at files that do not exist in the current repo.",
        };
        await prisma.featureBuild.update({ where: { buildId }, data: { planReview: review as unknown as import("@dpf/db").Prisma.InputJsonValue } });
        if (context?.threadId) {
          const { agentEventBus } = await import("@/lib/agent-event-bus");
          agentEventBus.emit(context.threadId, { type: "evidence:update", buildId, field: "planReview" });
        }
        logBuildActivity(buildId, "reviewBuildPlan", `Plan review: fail. ${review.summary}`);
        return {
          success: true,
          message: `Plan review FAILED. Blocking issues: ${normalizedPlan.unresolvedModifyPaths.join(", ")} no longer exist in the repo. Revise the implementation plan to target the current files, then re-run reviewBuildPlan.`,
          data: { review, blocked: true, action: "revise_and_resubmit" },
        };
      }
      const { buildPlanReviewPrompt, buildArchitectureReviewPrompt, architectureAdvisoryFromReview, parseReviewResponse, mergeReviews } = await import("@/lib/build-reviewers");
      // BI-4396EFEC (D38) — Compute the iteration context up front so we can
      // (a) feed prior issues into the reviewer prompt and (b) populate
      // ReviewResult.iteration on the output. Round is 1-based: first
      // review = 1, every subsequent reviewBuildPlan call increments.
      const priorRound = priorPlanReview?.iteration?.round ?? 0;
      const priorIssues = Array.isArray(priorPlanReview?.issues)
        ? priorPlanReview!.issues!
            .filter((i): i is { severity: string; description: string } =>
              typeof i?.severity === "string" && typeof i?.description === "string",
            )
            .map((i) => ({ severity: i.severity, description: i.description }))
        : [];
      const currentRound = priorRound + 1;
      const priorContext = priorIssues.length > 0
        ? { round: priorRound, issues: priorIssues }
        : null;
      const prompt = buildPlanReviewPrompt(normalizedPlan.plan, priorContext);
      const archPrompt = buildArchitectureReviewPrompt({ kind: "plan", plan: normalizedPlan.plan }, "");
      const { routeAndCall } = await import("@/lib/routed-inference");
      const messages = [{ role: "user" as const, content: prompt }];
      // Two checklist reviewers PLUS the advisory architecture reviewer
      // (chief-architect / Enterprise Architect lens), all in parallel. The
      // architecture reviewer is advisory only — it never enters mergeReviews,
      // it rides along on review.architectureAdvisory and the deliberation
      // `architect` branch so the coworker can fold concerns into the plan.
      const [r1settled, r2settled, archSettled] = await Promise.allSettled([
        routeAndCall(messages, "You are a plan reviewer.", "internal"),
        routeAndCall(
          messages,
          "You are an independent plan reviewer. Focus especially on missing tasks, dependency ordering, absent test-first steps, and data seeding gaps.",
          "internal",
          { budgetClass: "minimize_cost" },
        ),
        routeAndCall(
          [{ role: "user" as const, content: archPrompt }],
          "You are the Enterprise Architect (DPF chief-architect lens) reviewing for architectural alignment. Advisory only — surface concerns and concrete plan edits, never block the gate.",
          "internal",
          { budgetClass: "minimize_cost" },
        ),
      ]);
      const r1 = r1settled.status === "fulfilled" ? parseReviewResponse(r1settled.value.content) : null;
      const r2 = r2settled.status === "fulfilled" ? parseReviewResponse(r2settled.value.content) : null;
      const archReview = archSettled.status === "fulfilled" ? parseReviewResponse(archSettled.value.content) : null;
      const architectureAdvisory = architectureAdvisoryFromReview(archReview);
      const archAdvisoryNote = architectureAdvisory && architectureAdvisory.issues.length > 0
        ? ` Architecture review (advisory): ${architectureAdvisory.summary} Fold actionable items into the plan before building — they do not block this gate.`
        : "";
      const mergedReview = r1 && r2 ? mergeReviews(r1, r2) : r1 ?? r2 ?? {
        decision: "fail" as const,
        issues: [{ severity: "critical" as const, description: "Both review agents failed to respond" }],
        summary: "Review could not be completed — retry.",
      };
      // BI-4396EFEC (D38) — Compute the iteration delta against the prior
      // round and attach to the ReviewResult. computeReviewDelta + isOscillating
      // live in feature-build-types so they're independently unit-testable.
      const { computeReviewDelta, isOscillating } = await import("@/lib/feature-build-types");
      const reviewWithIteration = (() => {
        const base = mergedReview;
        if (priorIssues.length === 0) {
          return { ...base, iteration: { round: currentRound } };
        }
        const delta = computeReviewDelta(priorIssues, base.issues);
        return {
          ...base,
          iteration: {
            round: currentRound,
            prior: delta,
            oscillating: isOscillating(delta, base.issues.length),
          },
        };
      })();
      const review = architectureAdvisory
        ? { ...reviewWithIteration, architectureAdvisory }
        : reviewWithIteration;
      await prisma.featureBuild.update({ where: { buildId }, data: { planReview: review as unknown as import("@dpf/db").Prisma.InputJsonValue } });
      const { agentEventBus } = await import("@/lib/agent-event-bus");
      if (context?.threadId) agentEventBus.emit(context.threadId, { type: "evidence:update", buildId, field: "planReview" });
      logBuildActivity(buildId, "reviewBuildPlan", `Plan review: ${review.decision}. ${review.summary}`);

      // Record a deliberation trail for this dual-reviewer run. See the
      // matching block in reviewDesignDoc — same rules: review gate above is
      // authoritative, deliberation persistence is best-effort, failures are
      // logged loudly but do not throw.
      try {
        const reviewerBranches: ReviewBranchInput[] = [];
        if (r1) reviewerBranches.push({ branchNodeId: "reviewer-1", role: "reviewer", review: r1 });
        if (r2) reviewerBranches.push({ branchNodeId: "reviewer-2", role: "reviewer", review: r2 });
        // Advisory architecture branch — surfaces architectural risk into the
        // deliberation summary without flipping the gate.
        if (archReview) reviewerBranches.push({ branchNodeId: "architect", role: "architect", review: archReview });
        if (reviewerBranches.length > 0) {
          const { runBuildReviewDeliberation } = await import("@/lib/integrate/build-orchestrator");
          await runBuildReviewDeliberation({
            userId,
            buildId,
            phase: "plan",
            reviewerBranches,
            ...(context?.threadId ? { threadId: context.threadId } : {}),
          });
        }
      } catch (err) {
        console.warn("[deliberation] failed to record build review trail: %s",
          err instanceof Error ? JSON.stringify(err.message) : JSON.stringify(String(err)));
      }

      // Failed review → structured recovery instructions, no auto-advance
      if (review.decision === "fail") {
        const criticalIssues = review.issues.filter((i: { severity: string }) => i.severity === "critical");
        const issueList = criticalIssues.length > 0
          ? criticalIssues.map((i: { description: string }) => i.description).join("; ")
          : review.summary;
        // BI-4396EFEC (D38) — Include the iteration trajectory in the
        // agent-facing message so the implementer model can see when its
        // revisions are trading one set of issues for another instead of
        // converging. The oscillating signal recommends scope-split rather
        // than another iteration.
        const iter = review.iteration;
        const trajectoryNote = iter?.prior
          ? ` (Round ${iter.round}: ${iter.prior.addressed} addressed, ${iter.prior.persisted} persist, ${iter.prior.newlySurfaced} new${iter.oscillating ? " — issues are not net-decreasing across rounds; consider proposing a scope split rather than another revision." : ""}.)`
          : "";
        return {
          success: true,
          message: `Plan review FAILED. Blocking issues: ${issueList}. Revise the implementation plan to address these issues, then call saveBuildEvidence with field "buildPlan" and re-run reviewBuildPlan.${trajectoryNote}${archAdvisoryNote}`,
          data: { review, blocked: true, action: "revise_and_resubmit" },
        };
      }

      // Passed review → auto-advance if gate is satisfied.
      // NOTE: Cannot call advanceBuildPhase (server action) here because auth()
      // has no HTTP request context inside the agentic loop. Direct DB update instead.
      //
      // Same pattern as reviewDesignDoc's auto-advance: checkPhaseGate also
      // evaluates evidence.happyPathState, which must be passed from
      // build.plan or the intake check fails silently. Reading the plan and
      // pulling happyPathState out so the gate sees the real anchors —
      // see the fix in reviewDesignDoc for the backstory.
      try {
        const { checkPhaseGate, canTransitionPhase, normalizeHappyPathState } = await import("@/lib/feature-build-types");
        const { deriveFeatureBuildDependencyGate, FEATURE_BUILD_DEPENDENCY_GATE_SELECT } = await import("@/lib/build/feature-build-dependencies");
        const updatedBuild = await prisma.featureBuild.findUnique({
          where: { buildId },
          select: {
            phase: true,
            plan: true,
            buildPlan: true,
            planReview: true,
            id: true,
            buildId: true,
            title: true,
            kind: true,
            parentEpicId: true,
            dependenciesOut: FEATURE_BUILD_DEPENDENCY_GATE_SELECT.dependenciesOut,
          },
        });
        if (updatedBuild && updatedBuild.phase === "plan" && canTransitionPhase("plan", "build")) {
          const plan = (updatedBuild.plan as Record<string, unknown> | null) ?? {};
          const happyPathState = normalizeHappyPathState(plan.happyPathState);
          const gate = checkPhaseGate("plan", "build", {
            kind: updatedBuild.kind,
            // Right-sizing matrix: persisted on plan.processSize at promote time.
            processSize: (plan.processSize as string | undefined) ?? "medium",
            buildPlan: updatedBuild.buildPlan,
            planReview: updatedBuild.planReview,
            happyPathState,
          });
          if (gate.allowed) {
            const dependencyGate = deriveFeatureBuildDependencyGate(updatedBuild);
            if (!dependencyGate.allowed) {
              logBuildActivity(buildId, "phase:gate-blocked", dependencyGate.message);
            } else {
              // Initialize the build branch BEFORE flipping the phase. If the
              // phase flip lands without buildBranch set, deploy_feature runs
              // on whatever the current sandbox HEAD is — picking up leftover
              // work from earlier builds. Gate the transition on the branch
              // actually being ready so the "phase: build" record is always
              // paired with a valid buildBranch on the FeatureBuild row.
              try {
                const { isSandboxAvailable, startBuildBranch } = await import("@/lib/integrate/sandbox/build-branch");
                const sandboxUp = await isSandboxAvailable();
                if (!sandboxUp) {
                  logBuildActivity(buildId, "phase:gate-blocked", "Auto-advance to build blocked: sandbox not running. Start the sandbox, then call start_build.");
                } else {
                  await startBuildBranch(buildId);
                  // EP-COST Phase 3: record plan-phase cost rollup, start build tracking, and compact thread
                  const { completeBuildPhaseRun, startBuildPhaseRun } = await import("@/lib/integrate/build-phase-run");
                  void completeBuildPhaseRun(buildId, "plan");
                  void startBuildPhaseRun(buildId, "build");
                  if (context?.threadId) {
                    const { persistPhaseHandoffSummary } = await import("@/lib/integrate/phase-compaction-wire");
                    void persistPhaseHandoffSummary(context.threadId, "plan");
                  }
                  await prisma.featureBuild.update({ where: { buildId }, data: { phase: "build" } });
                  if (context?.threadId) agentEventBus.emit(context.threadId, { type: "phase:change", buildId, phase: "build" });
                  logBuildActivity(buildId, "phase:advance", "Phase advanced: plan → build (buildBranch initialized)");
                }
              } catch (branchErr) {
                logBuildActivity(buildId, "phase:gate-blocked", `startBuildBranch failed: ${(branchErr as Error).message?.slice(0, 200)}`);
              }
            }
          } else {
            logBuildActivity(buildId, "phase:gate-blocked", gate.reason ?? "unknown");
          }
        }
      } catch (err) {
        console.error("[reviewBuildPlan] auto-advance failed:", err);
      }

      return { success: true, message: `Plan review: ${review.decision}. ${review.summary}${archAdvisoryNote}`, data: { review } };
    }

    case "get_code_graph_freshness": {
      const { getCodeGraphFreshness } = await import("@/lib/integrate/code-graph-access");
      const { buildTrustMessage } = await import("@/lib/trust-vector");
      const freshness = await getCodeGraphFreshness(undefined, { inspectStructuralHealth: true });
      return {
        success: true,
        message: freshness.trust
          ? buildTrustMessage(freshness.trust, {
              currentFact: freshness.summary,
              lastKnownFact: freshness.summary,
              lowConfidenceResult: freshness.summary,
              inferredResult: freshness.summary,
            })
          : freshness.summary,
        data: freshness,
      };
    }

    case "inspect_build_code_impact": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) {
        return { success: false, error: "No active build.", message: "No active build." };
      }

      const build = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: { buildId: true, title: true, diffPatch: true },
      });
      if (!build?.diffPatch) {
        return {
          success: false,
          error: "No diff patch saved yet.",
          message: "No build diff is available yet. Run the build and save a diff before inspecting code impact.",
        };
      }

      const { analyzeChangeImpact, formatImpactForChat } = await import("@/lib/change-impact");
      const report = await analyzeChangeImpact(build.diffPatch);
      return {
        success: true,
        message: `Impact analysis for ${build.title ?? build.buildId}:\n\n${formatImpactForChat(report)}`,
        data: {
          buildId: build.buildId,
          report,
        },
      };
    }

    case "search_code_graph": {
      const { searchCodeGraph } = await import("@/lib/integrate/code-graph/graph-queries");
      const result = await searchCodeGraph({
        query: String(params["query"] ?? ""),
        graphKey: optionalString(params["graphKey"]) ?? undefined,
        limit: typeof params["limit"] === "number" ? params["limit"] : undefined,
      });
      return codeGraphReadToolResult(result as unknown as Record<string, unknown> & {
        available?: unknown;
        summary?: unknown;
      });
    }

    case "trace_code_surface": {
      const { traceCodeSurface } = await import("@/lib/integrate/code-graph/graph-queries");
      const result = await traceCodeSurface({
        route: optionalString(params["route"]) ?? undefined,
        tool: optionalString(params["tool"]) ?? undefined,
        model: optionalString(params["model"]) ?? undefined,
        graphKey: optionalString(params["graphKey"]) ?? undefined,
      });
      return codeGraphReadToolResult(result as unknown as Record<string, unknown> & {
        available?: unknown;
        summary?: unknown;
      });
    }

    case "find_related_tests": {
      const { findRelatedTests } = await import("@/lib/integrate/code-graph/graph-queries");
      const result = await findRelatedTests({
        filePath: String(params["filePath"] ?? ""),
        graphKey: optionalString(params["graphKey"]) ?? undefined,
        limit: typeof params["limit"] === "number" ? params["limit"] : undefined,
      });
      return codeGraphReadToolResult(result as unknown as Record<string, unknown> & {
        available?: unknown;
        summary?: unknown;
      });
    }

    case "diagnose_sandbox": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      const { diagnoseSandboxReadiness } = await import("@/lib/integrate/sandbox/sandbox-admin");
      const snapshot = await diagnoseSandboxReadiness({
        buildId,
        expectedWorkspaceRoot: optionalString(params["expectedWorkspaceRoot"]),
      });

      return {
        success: true,
        message: `Sandbox readiness: ${snapshot.state}. ${snapshot.summary}`,
        data: {
          ...snapshot,
        },
      };
    }

    case "recover_sandbox": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      const action = params["action"];
      if (!isSandboxRecoveryAction(action)) {
        return {
          success: false,
          error: "invalid_action",
          message: "Invalid sandbox recovery action.",
        };
      }

      const confirmation = params["confirmation"] && typeof params["confirmation"] === "object"
        ? params["confirmation"] as { discardSandboxChanges?: boolean; acknowledgeReset?: boolean; reason?: string }
        : null;
      const { recoverSandbox } = await import("@/lib/integrate/sandbox/sandbox-recovery");
      const result = await recoverSandbox({
        buildId,
        action,
        confirmation,
      });

      return {
        success: result.success,
        error: result.error,
        message: result.message,
        entityId: buildId,
        data: result.snapshot ? { ...result.snapshot } : undefined,
      };
    }

    case "check_sandbox": {
      const sandboxId = process.env.SANDBOX_CONTAINER_ID ?? "dpf-sandbox-1";
      try {
        const { exec: execCb } = lazyChildProcess();
        const { promisify } = lazyUtil();
        const execAsync = promisify(execCb);
        const { stdout } = await execAsync(`docker inspect -f "{{.State.Status}}" ${sandboxId}`, { timeout: 5_000 });
        const status = stdout.trim(); // "running", "exited", "paused", etc.
        const isRunning = status === "running";
        return {
          success: true,
          message: isRunning
            ? `Sandbox (${sandboxId}) is running and ready.`
            : `Sandbox (${sandboxId}) exists but is ${status}. Call start_sandbox to start it.`,
          data: { status: isRunning ? "running" : "stopped", containerId: sandboxId },
        };
      } catch {
        return {
          success: true,
          message: `Sandbox container (${sandboxId}) does not exist. Call diagnose_sandbox for the authoritative Build Studio recovery actions.`,
          data: { status: "not_found", containerId: sandboxId },
        };
      }
    }

    case "start_sandbox": {
      const sandboxId = process.env.SANDBOX_CONTAINER_ID ?? "dpf-sandbox-1";
      try {
        const { exec: execCb } = lazyChildProcess();
        const { promisify } = lazyUtil();
        const execAsync = promisify(execCb);

        // First check current status
        let currentStatus = "unknown";
        try {
          const { stdout } = await execAsync(`docker inspect -f "{{.State.Status}}" ${sandboxId}`, { timeout: 5_000 });
          currentStatus = stdout.trim();
        } catch {
          return {
            success: false,
            error: "Sandbox container not found.",
            message: `The sandbox container (${sandboxId}) has never been created or is not registered. Call diagnose_sandbox so Build Studio can classify the sandbox and surface governed recovery actions.`,
          };
        }

        if (currentStatus === "running") {
          return { success: true, message: `Sandbox (${sandboxId}) is already running.`, data: { status: "running" } };
        }

        // Start the container
        await execAsync(`docker start ${sandboxId}`, { timeout: 15_000 });

        // Wait up to 20s for it to become running
        const deadline = Date.now() + 20_000;
        while (Date.now() < deadline) {
          await new Promise(r => setTimeout(r, 1_500));
          try {
            const { stdout } = await execAsync(`docker inspect -f "{{.State.Status}}" ${sandboxId}`, { timeout: 3_000 });
            if (stdout.trim() === "running") {
              return { success: true, message: `Sandbox (${sandboxId}) started successfully and is ready.`, data: { status: "running" } };
            }
          } catch { /* keep waiting */ }
        }

        return {
          success: false,
          error: "Sandbox start timed out.",
          message: `The sandbox container (${sandboxId}) was started but did not become ready within 20 seconds. It may still be initialising — try check_sandbox again in a moment.`,
        };
      } catch (err) {
        return {
          success: false,
          error: "Failed to start sandbox.",
          message: `Could not start sandbox (${sandboxId}): ${(err as Error).message?.slice(0, 200)}`,
        };
      }
    }

    case "start_build": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      try {
        const { assertFeatureBuildDependenciesSatisfied } = await import("@/lib/build/feature-build-dependencies");
        await assertFeatureBuildDependenciesSatisfied({ db: prisma, buildId });
      } catch (err) {
        return {
          success: false,
          error: "Dependency gate blocked.",
          message: err instanceof Error ? err.message : String(err),
        };
      }

      const { isSandboxAvailable, startBuildBranch } = await import("@/lib/integrate/sandbox/build-branch");

      const available = await isSandboxAvailable();
      if (!available) {
        return {
          success: false,
          error: "Sandbox container is not running.",
          message: "The sandbox is not running. Call check_sandbox to see the status, then start_sandbox if it is stopped.",
        };
      }

      await startBuildBranch(buildId);

      try {
        const { startSandboxDevServer } = await import("@/lib/sandbox");
        await startSandboxDevServer(process.env.SANDBOX_CONTAINER_ID ?? "dpf-sandbox-1");
      } catch (devErr) {
        console.log(`[start_build] preview server start failed (non-fatal): ${(devErr as Error).message?.slice(0, 100)}`);
      }

      const { agentEventBus } = await import("@/lib/agent-event-bus");
      if (context?.threadId) agentEventBus.emit(context.threadId, { type: "phase:change", buildId, phase: "build" });
      logBuildActivity(buildId, "start_build", `Build branch ready for ${buildId}.`);
      return {
        success: true,
        message: `Build workspace ready. Sandbox is running on port ${process.env.SANDBOX_PORT ?? "3035"}. Start writing files.`,
        entityId: buildId,
        data: { containerId: process.env.SANDBOX_CONTAINER_ID ?? "dpf-sandbox-1", port: Number(process.env.SANDBOX_PORT ?? "3035") },
      };
    }

    case "generate_code":
    case "iterate_sandbox":
      // Removed: these tools caused runaway loops by spawning nested LLM calls.
      // Use write_sandbox_file / edit_sandbox_file / run_sandbox_command directly.
      return { success: false, error: "Tool removed.", message: "generate_code and iterate_sandbox have been removed. Use write_sandbox_file, edit_sandbox_file, and run_sandbox_command directly to build the feature." };


    case "run_sandbox_tests": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      const { isSandboxAvailable: rstAvail } = await import("@/lib/integrate/sandbox/build-branch");
      if (!(await rstAvail())) {
        return { success: false, error: "Sandbox not running.", message: "The sandbox (dpf-sandbox-1) is not running. Call start_build first." };
      }
      const rstSandboxId = process.env.SANDBOX_CONTAINER_ID ?? "dpf-sandbox-1";
      const { runSandboxTests, diagnoseTestFailures } = await import("@/lib/coding-agent");
      const autoFix = params.auto_fix === true;
      const MAX_FIX_ATTEMPTS = 3;

      let results = await runSandboxTests(rstSandboxId);
      let fixAttempts = 0;

      // Auto-fix loop: diagnose failures, apply fixes via LLM, re-test
      if (autoFix && !results.passed) {
        const { execInSandbox } = await import("@/lib/sandbox");
        const { routeAndCall } = await import("@/lib/routed-inference");
        const { agentEventBus } = await import("@/lib/agent-event-bus");

        while (!results.passed && fixAttempts < MAX_FIX_ATTEMPTS) {
          fixAttempts++;
          if (context?.threadId) {
            agentEventBus.emit(context.threadId, {
              type: "coding:test_fix_attempt" as "evidence:update",
              buildId,
              field: `attempt_${fixAttempts}_of_${MAX_FIX_ATTEMPTS}`,
            });
          }

          const diagnosis = diagnoseTestFailures(results);
          if (diagnosis.failingTests.length === 0) break;

          // Read failing source files for context
          const fileContents: string[] = [];
          const readFiles = new Set<string>();
          for (const failure of diagnosis.failingTests.slice(0, 3)) {
            for (const filePath of [failure.testFile, failure.sourceFile].filter(Boolean)) {
              if (readFiles.has(filePath!)) continue;
              readFiles.add(filePath!);
              try {
                const content = await execInSandbox(
                  rstSandboxId,
                  `cat "/workspace/${filePath}" 2>/dev/null | head -100 || echo "[not found]"`,
                );
                if (!content.includes("[not found]")) {
                  fileContents.push(`### ${filePath}\n\`\`\`\n${content}\n\`\`\``);
                }
              } catch { /* skip */ }
            }
          }

          // On retry attempts, gather deeper context: follow imports and find
          // codebase patterns. The first attempt has the error + source file.
          // If that wasn't enough, the LLM needs to see HOW the imported modules
          // work and how other files solve similar problems.
          const deepContext: string[] = [];
          if (fixAttempts >= 2) {
            // 1. Follow imports: extract import paths from failing files, read their exports
            for (const filePath of readFiles) {
              try {
                const importLines = await execInSandbox(
                  rstSandboxId,
                  `grep -n "^import" "/workspace/${filePath}" 2>/dev/null || true`,
                );
                for (const line of importLines.split("\n")) {
                  // Match package imports like @dpf/db, @/lib/foo
                  const pkgMatch = line.match(/from\s+["'](@dpf\/[^"']+|@\/[^"']+)["']/);
                  if (!pkgMatch) continue;
                  const importPath = pkgMatch[1]!;
                  // Resolve to a file path
                  let resolvedPath = "";
                  if (importPath.startsWith("@dpf/db")) {
                    resolvedPath = "packages/db/src/index.ts";
                  } else if (importPath.startsWith("@/")) {
                    resolvedPath = `apps/web/${importPath.replace("@/", "lib/")}.ts`;
                  }
                  if (resolvedPath && !readFiles.has(resolvedPath)) {
                    readFiles.add(resolvedPath);
                    const modContent = await execInSandbox(
                      rstSandboxId,
                      `cat "/workspace/${resolvedPath}" 2>/dev/null | head -50 || echo "[not found]"`,
                    );
                    if (!modContent.includes("[not found]")) {
                      deepContext.push(`### ${resolvedPath} (imported by ${filePath})\n\`\`\`\n${modContent}\n\`\`\``);
                    }
                  }
                }
              } catch { /* skip */ }
            }

            // 2. Find codebase patterns: how do other files handle similar imports?
            if (!results.typeCheckPassed && results.typeCheckOutput) {
              // Extract the problematic symbol from the error (e.g. "'Prisma'")
              const symbolMatch = results.typeCheckOutput.match(/['"](\w+)['"]\s+cannot be used as a value/);
              if (symbolMatch) {
                try {
                  const grepResult = await execInSandbox(
                    rstSandboxId,
                    `grep -rn "import.*${symbolMatch[1]}" /workspace/apps/web/lib/ 2>/dev/null | grep -v node_modules | head -10 || true`,
                  );
                  if (grepResult.trim()) {
                    deepContext.push(`### How other files import "${symbolMatch[1]}"\n\`\`\`\n${grepResult}\n\`\`\``);
                  }
                } catch { /* skip */ }
              }
            }
          }

          // Ask LLM to produce a fix
          const fixPrompt = [
            "The following tests are failing. Diagnose and fix the SOURCE files (not the tests).",
            "",
            "## Test Output",
            "```",
            results.testOutput.slice(0, 3000),
            "```",
            "",
            results.typeCheckPassed ? "" : `## Type Check Errors\n\`\`\`\n${results.typeCheckOutput.slice(0, 2000)}\n\`\`\`\n`,
            "## Diagnosis",
            diagnosis.summary,
            "",
            "## Relevant Files",
            ...fileContents,
            ...(deepContext.length > 0 ? [
              "",
              "## Import Chain & Codebase Patterns (follow these — they show how the project actually works)",
              ...deepContext,
            ] : []),
            "",
            "IMPORTANT: If an import is type-only (export type) but used as a value, find an alternative approach.",
            "Look at how other files in this codebase solve the same problem.",
            "",
            "Output ONLY the fixed files in this format:",
            "### FILE: <path>",
            "```typescript",
            "<full file content>",
            "```",
          ].join("\n");

          try {
            const fixResult = await routeAndCall(
              [{ role: "user", content: fixPrompt }],
              "You are a debugging agent. Fix the failing code. Output only changed files.",
              "internal",
              { taskType: "code_generation" },
            );

            // Parse and write fixed files
            const filePattern = /### FILE: (.+?)\n```(?:typescript|tsx|ts|prisma|sql)?\n([\s\S]*?)```/g;
            let fixMatch;
            let filesFixed = 0;
            while ((fixMatch = filePattern.exec(fixResult.content)) !== null) {
              const cleanPath = fixMatch[1]!.trim().replace(/^\/?workspace\//, "");
              const dir = cleanPath.includes("/") ? cleanPath.substring(0, cleanPath.lastIndexOf("/")) : "";
              if (dir) await execInSandbox(rstSandboxId, `mkdir -p '/workspace/${dir}'`);
              const encoded = Buffer.from(fixMatch[2]!).toString("base64");
              await execInSandbox(rstSandboxId, `echo ${encoded} | base64 -d > '/workspace/${cleanPath}'`);
              filesFixed++;
            }

            if (filesFixed === 0) break; // LLM couldn't produce a fix

            logBuildActivity(buildId, "run_sandbox_tests", `Auto-fix attempt ${fixAttempts}: applied fixes to ${filesFixed} file(s).`);
          } catch {
            break; // LLM call failed — stop retrying
          }

          // Re-run tests
          results = await runSandboxTests(rstSandboxId);
        }
      }

      const verificationData = {
        testsPassed: results.passed ? 1 : 0,
        testsFailed: results.passed ? 0 : 1,
        typecheckPassed: results.typeCheckPassed,
        testOutput: results.testOutput.slice(0, 5000),
        typeCheckOutput: results.typeCheckOutput.slice(0, 5000),
        autoFixAttempts: fixAttempts,
        autoFixEnabled: autoFix,
      };
      await prisma.featureBuild.update({
        where: { buildId },
        data: { verificationOut: verificationData as unknown as import("@dpf/db").Prisma.InputJsonValue },
      });
      const { agentEventBus: eventBus } = await import("@/lib/agent-event-bus");
      if (context?.threadId) eventBus.emit(context.threadId, { type: "evidence:update", buildId, field: "verificationOut" });
      const statusMsg = results.typeCheckPassed
        ? `Verification recorded: typecheck clean, unit test output captured for review.${fixAttempts > 0 ? ` Fixed after ${fixAttempts} attempt(s).` : ""}`
        : `Verification recorded: typecheck failed, unit test output captured for review.${fixAttempts > 0 ? ` Auto-fix attempted ${fixAttempts} time(s).` : ""}`;
      logBuildActivity(buildId, "run_sandbox_tests", statusMsg);

      return {
        success: true,
        message: statusMsg,
        data: {
          ...verificationData,
          buildId,
        },
      };
    }

    // ─── Sandbox File Tools ──────────────────────────────────────────────────
    // Shared auto-init: ensure sandbox is initialized before any file tool runs.
    // Falls through to the specific tool case after initialization.

    case "read_sandbox_file":
    case "write_sandbox_file":
    case "edit_sandbox_file":
    case "search_sandbox":
    case "list_sandbox_files":
    case "run_sandbox_command": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      // Simple availability check — no slot management, no pool acquisition.
      // If the sandbox container is running, it is available. Period.
      const { isSandboxAvailable } = await import("@/lib/integrate/sandbox/build-branch");
      const { execInSandbox: sbExec } = await import("@/lib/sandbox");
      const available = await isSandboxAvailable();
      if (!available) {
        return {
          success: false,
          error: "Sandbox container is not running.",
          message: "The sandbox (dpf-sandbox-1) is not running. Call diagnose_sandbox and use the returned recovery action before retrying this file tool.",
        };
      }

      const execInSandbox = sbExec;
      const sandboxId = process.env.SANDBOX_CONTAINER_ID ?? "dpf-sandbox-1";

      // ── Dispatch to specific tool ──
      // ── Direct filesystem tools (via shared Docker volume at /sandbox-workspace) ──
      // These use Node.js fs operations — no docker exec, no shell escaping.
      const { readFile, writeFile, mkdir, stat } = lazyFsPromises();
      const { join, dirname } = lazyPath();
      const SANDBOX_MOUNT = "/sandbox-workspace";

      const resolveSandboxPath = (p: string) => {
        const cleaned = p.replace(/^\/?workspace\//, "");
        const resolved = join(SANDBOX_MOUNT, cleaned);
        // Prevent path traversal
        if (!resolved.startsWith(SANDBOX_MOUNT)) throw new Error("Path traversal blocked");
        return { resolved, relative: cleaned };
      };

      if (toolName === "read_sandbox_file") {
        const { resolved, relative } = resolveSandboxPath(String(params.path ?? ""));
        const offset = params.offset ? Number(params.offset) : undefined;
        const limit = params.limit ? Number(params.limit) : undefined;
        try {
          const raw = await readFile(resolved, "utf-8");
          const allLines = raw.split("\n");
          const startLine = (offset ?? 1) - 1;
          const endLine = limit ? startLine + limit : allLines.length;
          const slice = allLines.slice(startLine, endLine);
          const numbered = slice.map((line: string, i: number) => `${String(startLine + i + 1).padStart(6)}\t${line}`).join("\n");
          const rangeMsg = offset || limit ? ` (lines ${startLine + 1}–${startLine + slice.length})` : "";
          return { success: true, message: `File: ${relative}${rangeMsg}`, data: { path: relative, content: numbered } };
        } catch {
          return { success: false, error: `File not found: ${relative}`, message: `Could not read ${relative}` };
        }
      }

      if (toolName === "write_sandbox_file") {
        const { resolved, relative } = resolveSandboxPath(String(params.path ?? ""));
        const content = String(params.content ?? "");
        if (!content) return { success: false, error: "content is required.", message: "Provide the file content." };
        try {
          await mkdir(dirname(resolved), { recursive: true });
          await writeFile(resolved, content, "utf-8");
          logBuildActivity(buildId, "write_sandbox_file", `Created ${relative} (${content.length} chars)`);
          return { success: true, message: `Created ${relative} (${content.length} chars).`, data: { path: relative } };
        } catch (err) {
          return { success: false, error: `Write failed: ${(err as Error).message?.slice(0, 200)}`, message: `Could not write ${relative}` };
        }
      }

      if (toolName === "edit_sandbox_file") {
        const { resolved, relative } = resolveSandboxPath(String(params.path ?? ""));

        // Line-based edit mode: replace a range of lines by number
        // More reliable than string matching for AI-generated edits
        const startLine = params.start_line ? Number(params.start_line) : undefined;
        const endLine = params.end_line ? Number(params.end_line) : undefined;
        const newContent = params.new_content ? String(params.new_content) : undefined;

        if (startLine && endLine && newContent !== undefined) {
          try {
            const current = await readFile(resolved, "utf-8");
            const lines = current.split("\n");
            if (startLine < 1 || endLine > lines.length || startLine > endLine) {
              return { success: false, error: `Invalid line range ${startLine}-${endLine} (file has ${lines.length} lines).`, message: `Line range out of bounds.` };
            }
            const before = lines.slice(0, startLine - 1);
            const after = lines.slice(endLine);
            const newLines = newContent.split("\n");
            const updated = [...before, ...newLines, ...after].join("\n");
            await writeFile(resolved, updated, "utf-8");
            logBuildActivity(buildId, "edit_sandbox_file", `Edited ${relative} lines ${startLine}-${endLine} (${endLine - startLine + 1} -> ${newLines.length} lines)`);
            return { success: true, message: `Edited ${relative}: replaced lines ${startLine}-${endLine} with ${newLines.length} lines.`, data: { path: relative, linesReplaced: endLine - startLine + 1, newLines: newLines.length } };
          } catch (err) {
            return { success: false, error: `Edit failed: ${(err as Error).message?.slice(0, 200)}`, message: `Could not edit ${relative}` };
          }
        }

        // String-matching edit mode (original)
        const oldText = String(params.old_text ?? "");
        const newText = String(params.new_text ?? "");
        const replaceAll = params.replace_all === true;
        if (!oldText) return { success: false, error: "old_text is required (or use start_line/end_line/new_content for line-based edit).", message: "Provide old_text to replace, or use line-based mode." };
        try {
          const current = await readFile(resolved, "utf-8");
          const occurrences = current.split(oldText).length - 1;
          if (occurrences === 0) return { success: false, error: `old_text not found in ${relative}. Use read_sandbox_file to see exact content, or use line-based edit (start_line, end_line, new_content).`, message: `Text not found. Try line-based edit instead.` };
          if (occurrences > 1 && !replaceAll) return { success: false, error: `old_text matches ${occurrences} locations in ${relative}. Provide more context to make it unique, or set replace_all: true.`, message: `Ambiguous match — ${occurrences} occurrences found. Add surrounding lines to make the match unique, or use replace_all.` };
          const updated = replaceAll ? current.split(oldText).join(newText) : current.replace(oldText, newText);
          await writeFile(resolved, updated, "utf-8");
          const countMsg = replaceAll ? ` (${occurrences} occurrences)` : "";
          logBuildActivity(buildId, "edit_sandbox_file", `Edited ${relative}${countMsg}`);
          return { success: true, message: `Edited ${relative}: replaced ${oldText.length} chars with ${newText.length} chars${countMsg}.`, data: { path: relative, replacements: replaceAll ? occurrences : 1 } };
        } catch (err) {
          return { success: false, error: `Edit failed: ${(err as Error).message?.slice(0, 200)}`, message: `Could not edit ${relative}` };
        }
      }

      if (toolName === "search_sandbox") {
        const pattern = String(params.pattern ?? "");
        const globFilter = params.glob ? String(params.glob) : "*.{ts,tsx,js,jsx}";
        const max = Number(params.maxResults) || 20;
        try {
          // Use grep on the mounted volume — runs in portal, not sandbox container
          const { exec: execCb } = lazyChildProcess();
          const { promisify } = lazyUtil();
          const execAsync = promisify(execCb);
          const { stdout } = await execAsync(
            `grep -rn --include='${globFilter}' '${pattern.replace(/'/g, "'\\''")}' ${SANDBOX_MOUNT}/apps/ ${SANDBOX_MOUNT}/packages/ 2>/dev/null | head -${max}`,
            { timeout: 15_000 },
          );
          const cleaned = stdout.replace(new RegExp(SANDBOX_MOUNT + "/", "g"), "");
          return { success: true, message: `Search results for "${pattern}"`, data: { pattern, results: cleaned } };
        } catch (err) {
          // grep exits with code 1 when no matches are found — this is NOT an error.
          // Distinguish "no matches" from actual sandbox failures.
          const execErr = err as { code?: number; killed?: boolean; signal?: string };
          if (execErr.code === 1) {
            return {
              success: true,
              message: `No matches found for "${pattern}" in ${globFilter} files. The sandbox is working — this search term simply doesn't exist in the codebase. Try a different keyword or check spelling.`,
              data: { pattern, results: "", matchCount: 0 },
            };
          }
          // Actual failure (timeout, mount not accessible, etc.)
          const errMsg = (err as Error).message?.slice(0, 200) ?? "Search failed";
          return { success: false, error: `Sandbox search error: ${errMsg}`, message: `Search failed — the sandbox may not be accessible. Error: ${errMsg}` };
        }
      }

      if (toolName === "list_sandbox_files") {
        const pattern = String(params.pattern ?? "**/*");
        try {
          const { exec: execCb } = lazyChildProcess();
          const { promisify } = lazyUtil();
          const execAsync = promisify(execCb);
          const findPattern = pattern.startsWith("/") ? pattern : `${SANDBOX_MOUNT}/${pattern}`;
          const { stdout } = await execAsync(
            `find ${SANDBOX_MOUNT} -path '${SANDBOX_MOUNT}/node_modules' -prune -o -path '${SANDBOX_MOUNT}/.pnpm-store' -prune -o -path '${SANDBOX_MOUNT}/.next' -prune -o -path '${findPattern}' -print 2>/dev/null | head -50`,
            { timeout: 10_000 },
          );
          const cleaned = stdout.split("\n").map((l: string) => l.replace(`${SANDBOX_MOUNT}/`, "")).filter(Boolean).join("\n");
          if (!cleaned) {
            return { success: true, message: `No files matching "${pattern}". The sandbox is working — this path pattern has no matches. Try a broader pattern like "apps/web/app/**/*.tsx".`, data: { pattern, files: "" } };
          }
          return { success: true, message: `Files matching "${pattern}"`, data: { pattern, files: cleaned } };
        } catch (err) {
          const errMsg = (err as Error).message?.slice(0, 200) ?? "List failed";
          return { success: false, error: `Sandbox file listing error: ${errMsg}`, message: `File listing failed — the sandbox may not be accessible. Error: ${errMsg}` };
        }
      }

      if (toolName === "run_sandbox_command") {
        const command = String(params.command ?? "");
        if (!command) return { success: false, error: "command is required.", message: "Provide a command to run." };

        // ── Command safety blocklist ─────────────────────────────────────────
        // Commands run inside the sandbox container (docker exec), not the host OS.
        // The container is isolated, but we still block commands that could:
        //   - Destroy the workspace beyond git recovery
        //   - Exfiltrate files to the internet
        //   - Escape the container or affect the host Docker daemon
        //   - Execute arbitrary code piped from the network
        const BLOCKED_PATTERNS = [
          /rm\s+-rf\s+\/(?!workspace)/i,       // rm -rf outside /workspace
          /rm\s+-rf\s+\/workspace\s*$/i,        // rm -rf /workspace itself
          /curl\s+.*\|\s*(ba)?sh/i,             // curl | sh (remote code exec)
          /wget\s+.*\|\s*(ba)?sh/i,             // wget | sh
          /curl\s+.*\|\s*node/i,                // curl | node
          /docker\s+(run|exec|build|rm|rmi)/i,  // docker escape attempts
          /--privileged/i,                       // container privilege escalation
          /\/proc\/\d+\/fd/i,                   // procfs fd access
          /nsenter/i,                            // namespace escape
          /chroot/i,                             // chroot escape
          /mount\b/i,                            // mount syscall
          /chmod\s+[0-7]*7[0-7]*\s+\/(?!workspace)/i, // chmod outside workspace
        ];
        const blocked = BLOCKED_PATTERNS.find(p => p.test(command));
        if (blocked) {
          console.warn(`[run_sandbox_command] BLOCKED: ${JSON.stringify(command.slice(0, 200))}`);
          return {
            success: false,
            error: "Command blocked by safety policy.",
            message: `This command is not permitted: "${command.slice(0, 100)}". Commands must operate within /workspace. Destructive operations outside the workspace, remote code execution, and container escape attempts are blocked.`,
          };
        }
        // ─────────────────────────────────────────────────────────────────────

        // Smart output truncation: keep errors (at the end) rather than progress noise (at the start)
        const truncateOutput = (raw: string, limit: number = 15000): string => {
          if (raw.length <= limit) return raw;
          // For build/typecheck output, extract error lines first
          const errorLines = raw.split("\n").filter((l) =>
            /error\s+TS\d|ERROR|FAIL|Error:|Cannot find|not assignable|does not exist|Module.*not found/i.test(l)
          );
          if (errorLines.length > 0 && errorLines.length < 200) {
            const errorSummary = errorLines.join("\n");
            if (errorSummary.length <= limit) {
              return `[${raw.split("\n").length} total lines, showing ${errorLines.length} error lines]\n${errorSummary}`;
            }
          }
          // Fall back to keeping the tail (where errors typically appear)
          return `[output truncated — showing last ${limit} chars of ${raw.length}]\n...${raw.slice(-limit)}`;
        };

        try {
          const output = await execInSandbox(sandboxId, `cd /workspace && ${command} 2>&1`);
          logBuildActivity(buildId, "run_sandbox_command", `Ran: ${command.slice(0, 100)}`);
          return {
            success: true,
            message: "Command completed.",
            data: { buildId, command, output: truncateOutput(output) },
          };
        } catch (err) {
          // Commands like tsc, prisma validate return non-zero exit codes when they
          // find errors. This is NOT a sandbox failure — it's useful output.
          const execErr = err as { stdout?: string; stderr?: string; message?: string; code?: number };
          const output = (execErr.stdout ?? "") + (execErr.stderr ?? "");
          const exitCode = execErr.code;

          // If we got output, the command ran — return the output so the AI can act on it
          if (output.trim()) {
            logBuildActivity(buildId, "run_sandbox_command", `Ran (exit ${exitCode}): ${command.slice(0, 100)}`);
            return {
              success: true,
              message: `Command exited with code ${exitCode}. Review the output for errors to fix.`,
              data: { buildId, command, output: truncateOutput(output), exitCode },
            };
          }

          // No output — actual sandbox connectivity issue
          const errMsg = execErr.message?.slice(0, 2000) || "Command failed";
          console.error(`[run_sandbox_command] FAILED (no output): ${JSON.stringify(command.slice(0, 100))} -> ${JSON.stringify(errMsg.slice(0, 200))}`);
          return { success: false, error: errMsg, message: `Command failed: ${command.slice(0, 100)}`, data: { command, output: errMsg } };
        }
      }

      return { success: false, error: "Unknown sandbox tool", message: "Internal error." };
    }

    case "describe_model": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      const modelName = String(params.model_name ?? "");
      if (!modelName) return { success: false, error: "model_name is required.", message: "Provide the model name (PascalCase)." };

      // Read the schema. During ideate/plan phases no sandbox exists yet, so try
      // the project filesystem first (same root as read_project_file). Fall back
      // to the sandbox only if the build has one already provisioned.
      const { describeModel, formatModelDescription } = await import("@/lib/integrate/schema-validator");

      const tryDirectRead = async (): Promise<string | null> => {
        try {
          const { resolve } = lazyPath();
          const { readFile } = lazyFsPromises();
          const root = process.env.PROJECT_ROOT
            ? resolve(process.env.PROJECT_ROOT)
            : resolve(process.cwd(), "..", "..");
          return await readFile(resolve(root, "packages/db/prisma/schema.prisma"), "utf-8");
        } catch {
          return null;
        }
      };

      const directSchema = await tryDirectRead();
      if (directSchema) {
        const desc = describeModel(directSchema, modelName);
        if (!desc) return { success: false, error: `Model "${modelName}" not found.`, message: `No model named "${modelName}" exists. Check spelling (PascalCase).` };
        return { success: true, message: formatModelDescription(desc), data: desc as unknown as Record<string, unknown> };
      }

      // Fallback: use sandbox if one is already provisioned for this build.
      const dmBuild = await prisma.featureBuild.findUnique({ where: { buildId }, select: { sandboxId: true } });
      if (!dmBuild?.sandboxId) {
        return { success: false, error: "Schema not accessible.", message: "Could not read schema — sandbox not provisioned and project root is unavailable. Try read_project_file on packages/db/prisma/schema.prisma instead." };
      }

      try {
        const { execInSandbox } = await import("@/lib/sandbox");
        const schemaContent = await execInSandbox(dmBuild.sandboxId, "cat /workspace/packages/db/prisma/schema.prisma");
        const desc = describeModel(schemaContent, modelName);
        if (!desc) return { success: false, error: `Model "${modelName}" not found.`, message: `No model named "${modelName}" exists. Check spelling (PascalCase).` };
        return { success: true, message: formatModelDescription(desc), data: desc as unknown as Record<string, unknown> };
      } catch (err) {
        return { success: false, error: "Schema read error", message: err instanceof Error ? err.message : "Failed to read schema" };
      }
    }

    case "validate_schema": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      // Simple availability check — no slot management
      const { isSandboxAvailable } = await import("@/lib/integrate/sandbox/build-branch");
      const vsAvailable = await isSandboxAvailable();
      if (!vsAvailable) {
        return {
          success: false,
          error: "Sandbox container is not running.",
          message: "The sandbox (dpf-sandbox-1) is not running. Call start_build first.",
        };
      }
      const vsSandboxId = process.env.SANDBOX_CONTAINER_ID ?? "dpf-sandbox-1";

      try {
        const { execInSandbox } = await import("@/lib/sandbox");
        const schemaContent = await execInSandbox(
          vsSandboxId,
          "cat /workspace/packages/db/prisma/schema.prisma",
        );
        const { validatePrismaSchema, formatSchemaValidation } = await import("@/lib/integrate/schema-validator");
        const result = validatePrismaSchema(schemaContent);

        logBuildActivity(buildId, "validate_schema", result.summary);

        if (!result.valid) {
          return {
            success: false,
            error: "Schema validation failed",
            message: formatSchemaValidation(result),
            data: result as unknown as Record<string, unknown>,
          };
        }

        return {
          success: true,
          message: formatSchemaValidation(result),
          data: result as unknown as Record<string, unknown>,
        };
      } catch (err) {
        return { success: false, error: "Schema validation error", message: err instanceof Error ? err.message : "Failed to validate schema" };
      }
    }

    case "deploy_feature": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };
      const build = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: { sandboxId: true, buildBranch: true, phase: true, createdById: true },
      });
      if (!build || build.createdById !== userId) {
        return { success: false, error: "Build not found.", message: `No active build ${buildId} was found for this user.` };
      }
      if (!build?.sandboxId) return { success: false, error: "Sandbox not running.", message: "No sandbox." };
      // Invariant: buildBranch must be set once we're in build phase or later.
      // Its absence means startBuildBranch never ran — so the sandbox tree
      // is on whatever HEAD happened to be (client branch or baseline), and
      // any diff we extract will pick up leaked work from earlier builds.
      // Refuse to deploy until start_build runs and registers a buildBranch.
      if (!build.buildBranch) {
        return {
          success: false,
          error: "Build branch not initialized.",
          message: "This build has no buildBranch on record — start_build never completed. Run start_build to create and register build/<buildId>, then retry deploy_feature. Deploying without a build branch would include leaked changes from prior builds.",
        };
      }

      const { diagnoseSandboxReadiness } = await import("@/lib/integrate/sandbox/sandbox-admin");
      const { assertSandboxReadyForDeploy } = await import("@/lib/integrate/sandbox/sandbox-readiness-gate");
      const readiness = await diagnoseSandboxReadiness({ buildId });
      const readinessGate = assertSandboxReadyForDeploy(readiness);
      if (!readinessGate.ok) {
        logBuildActivity(buildId, "deploy_feature", readinessGate.message);
        return {
          success: false,
          error: "Sandbox readiness blocked deploy_feature.",
          message: readinessGate.message,
          data: { ...readiness },
        };
      }

      const devConfig = await prisma.platformDevConfig.findUnique({
        where: { id: "singleton" },
        select: { contributionMode: true, gitRemoteUrl: true },
      });
      const { getPlatformDevPolicyState } = await import("@/lib/platform-dev-policy");
      const policyState = getPlatformDevPolicyState(devConfig);
      if (policyState === "policy_pending") {
        return {
          success: false,
          error: "Platform development policy not configured.",
          message:
            "Build Studio can keep editing and validating in the shared workspace, but production promotion stays blocked until Platform Development is configured in the portal. Go to Admin > Platform Development and choose whether this install stays private or can contribute upstream.",
        };
      }

      // Extract diff from sandbox. Pass clientBranch as the diff base so
      // committed work on the build branch (`git commit` from generate_code)
      // is captured — without the base ref, `git diff --cached` only sees
      // staged-but-uncommitted changes and returns empty for any build whose
      // agent committed before deploy_feature ran.
      const { extractAndCategorizeDiff, scanForDestructiveOps, isNowInWindow } = await import("@/lib/integrate/sandbox/sandbox-promotion");
      const { getClientIdentity } = await import("@/lib/integrate/sandbox/build-branch");
      const { clientBranch } = await getClientIdentity();
      const extracted = await extractAndCategorizeDiff(build.sandboxId, { baseRef: clientBranch });
      if (!extracted.fullDiff.trim()) {
        await prisma.featureBuild.update({
          where: { buildId },
          data: { diffPatch: "", diffSummary: "" },
        });
        const noDiffMessage = "No releasable source changes were found in the sandbox. This build currently has only generated/cache churn or no real code changes, so release preparation cannot continue until implementation produces a real source diff.";
        logBuildActivity(buildId, "deploy_feature", noDiffMessage);
        return {
          success: false,
          error: "No releasable source changes found.",
          message: noDiffMessage,
          data: {
            diffLength: 0,
            codeFiles: 0,
            migrationFiles: 0,
          },
        };
      }

      // Guard: schema regression check.
      // If the diff removes existing fields/models from schema.prisma, the sandbox
      // was initialized from a stale portal image and this diff would silently
      // regress main's schema. Block deploy and surface the removed lines so the
      // operator knows what drifted.
      if (extracted.schemaRegressions.length > 0) {
        const regressionSample = extracted.schemaRegressions.slice(0, 10).join("\n");
        const regressionMessage =
          `Schema regression detected in sandbox diff — ${extracted.schemaRegressions.length} existing field(s) or model declaration(s) would be removed from packages/db/prisma/schema.prisma. ` +
          `This almost always means the sandbox was initialized from a portal image that predates recent schema changes on main. ` +
          `Rebuild the sandbox from a fresh image (Admin → Build Studio → Rebuild Sandbox) and re-run the build before deploying.\n\n` +
          `Removed lines (first 10):\n${regressionSample}`;
        logBuildActivity(buildId, "deploy_feature", regressionMessage);
        return {
          success: false,
          error: "Schema regression detected.",
          message: regressionMessage,
          data: {
            schemaRegressions: extracted.schemaRegressions,
            regressionCount: extracted.schemaRegressions.length,
          },
        };
      }

      // Capture commit hashes alongside the diff so the contribution flow
      // (which pushes build/<buildId> upstream) has the exact list of
      // commits being submitted. Without this, FB.gitCommitHashes stays
      // empty for committed-work builds and contribute_to_hive cannot
      // attribute the PR's commits back to specific FBs.
      const { listSandboxCommitsAheadOfBase } = await import("@/lib/integrate/sandbox/sandbox");
      const commitHashes = await listSandboxCommitsAheadOfBase(build.sandboxId, clientBranch);

      await prisma.featureBuild.update({
        where: { buildId },
        data: {
          diffPatch: extracted.fullDiff,
          diffSummary: extracted.fullDiff.slice(0, 500),
          gitCommitHashes: commitHashes,
        },
      });

      // Scan migrations for destructive operations
      let destructiveWarnings: string[] = [];
      if (extracted.hasMigrations) {
        destructiveWarnings = scanForDestructiveOps(extracted.fullDiff);
      }

      // Check deployment window availability
      let windowStatus = "No business profile configured — deployment unrestricted.";
      try {
        const profile = await prisma.businessProfile.findFirst({
          where: { isActive: true },
          include: { deploymentWindows: true, blackoutPeriods: true },
        });
        if (profile) {
          const now = new Date();
          const activeBlackout = profile.blackoutPeriods.find(
            (bp) => bp.startAt <= now && bp.endAt >= now,
          );
          if (activeBlackout) {
            windowStatus = `Blackout active until ${activeBlackout.endAt.toISOString()}.`;
          } else {
            const matchingWindows = profile.deploymentWindows.filter(
              (w) => w.allowedChangeTypes.includes("normal") && w.allowedRiskLevels.includes("low"),
            );
            if (matchingWindows.length > 0) {
              windowStatus = isNowInWindow(matchingWindows)
                ? "Deployment window is open now."
                : `Not in a deployment window. Available: ${matchingWindows.map((w) => `${w.name}: ${w.startTime}-${w.endTime}`).join("; ")}`;
            } else {
              windowStatus = "No deployment windows configured — deployment unrestricted.";
            }
          }
        }
      } catch {
        // Non-fatal — window check is advisory at this stage
      }

      // Run change impact analysis (EP-BUILD-HANDOFF-002 Phase 2b)
      let impactReport: Awaited<ReturnType<typeof import("@/lib/change-impact").analyzeChangeImpact>> | null = null;
      let impactSummary = "";
      try {
        const { analyzeChangeImpact, formatImpactForChat } = await import("@/lib/change-impact");
        impactReport = await analyzeChangeImpact(extracted.fullDiff);
        impactSummary = formatImpactForChat(impactReport);
      } catch (err) {
        console.warn("[deploy_feature] impact analysis failed:", err);
      }

      // Resolve approval authority (EP-BUILD-HANDOFF-002 Phase 2b)
      let authorityInfo = "";
      try {
        const { resolveApprovalAuthority, isCurrentUserTheAuthority, formatAuthorityForChat } = await import("@/lib/approval-authority");
        const riskLevel = impactReport?.riskLevel ?? "low";
        const authority = await resolveApprovalAuthority("deployment", "normal", riskLevel, userId);
        const isSelf = isCurrentUserTheAuthority(authority, userId);
        authorityInfo = formatAuthorityForChat(authority, isSelf);
      } catch (err) {
        console.warn("[deploy_feature] authority resolution failed:", err);
      }

      // Contribution mode awareness (EP-BUILD-HANDOFF-002 Phase 2e extension)
      let contributionModeInfo = "";
      try {
        const mode = devConfig?.contributionMode ?? "fork_only";

        if (mode === "fork_only" && !devConfig?.gitRemoteUrl) {
          // Count untracked shipped features for escalating warning
          const untrackedCount = await prisma.featureBuild.count({
            where: { phase: "complete", gitCommitHashes: { isEmpty: true } },
          });

          if (untrackedCount >= 5) {
            contributionModeInfo = `**Warning:** You have ${untrackedCount} custom features with no backup. This represents significant business value that could be lost in a container rebuild, Docker update, or system recovery. Setting up a git repository takes about 10 minutes and protects all your customizations. See Admin > Platform Development.`;
          } else if (untrackedCount >= 2) {
            contributionModeInfo = `**Note:** You now have ${untrackedCount} custom features deployed without version control. If your Docker containers are rebuilt, these changes could be lost. I'd recommend setting up a git repository -- see Admin > Platform Development.`;
          } else if (untrackedCount >= 1) {
            contributionModeInfo = "Note: since no git repository is configured, customizations exist only in your production container. You can set up a repository in Admin > Platform Development to protect your work.";
          }
        }
      } catch (err) {
        console.warn("[deploy_feature] contribution mode check failed:", err);
      }

      const messageParts = [
        `Diff extracted: ${extracted.codeFiles.length} code file(s), ${extracted.migrationFiles.length} migration(s).`,
        windowStatus,
      ];
      if (destructiveWarnings.length > 0) {
        messageParts.push(`WARNING: ${destructiveWarnings.length} destructive operation(s) detected: ${destructiveWarnings.join("; ")}`);
      }
      if (impactSummary) {
        messageParts.push("", impactSummary);
      }
      if (authorityInfo) {
        messageParts.push("", authorityInfo);
      }
      if (contributionModeInfo) {
        messageParts.push("", contributionModeInfo);
      }

      logBuildActivity(buildId, "deploy_feature", messageParts.join(" "));

      return {
        success: true,
        message: messageParts.join("\n"),
        data: {
          diffLength: extracted.fullDiff.length,
          summary: extracted.fullDiff.slice(0, 500),
          codeFiles: extracted.codeFiles.length,
          migrationFiles: extracted.migrationFiles.length,
          destructiveWarnings,
          windowStatus,
          impactReport,
        },
      };
    }

    // ─── Portal PR Creation & Merge ────────────────────────────────────────

    case "create_portal_pr": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      const build = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: {
          id: true, title: true, diffPatch: true, buildBranch: true,
          description: true, gitCommitHashes: true, updatedAt: true, buildExecState: true,
          verificationOut: true, acceptanceMet: true, phase: true,
          designDoc: true, buildPlan: true,
          productVersions: {
            take: 1,
            orderBy: { shippedAt: "desc" },
            select: {
              id: true,
              promotions: { take: 1, orderBy: { createdAt: "desc" }, select: { promotionId: true, status: true } },
            },
          },
        },
      });
      if (!build) return { success: false, error: "Build not found.", message: "Build not found." };

      const diff = (build.diffPatch ?? "") as string;
      if (!diff.trim()) return { success: false, error: "No diff available.", message: "Run deploy_feature first to extract the diff." };

      const { buildSandboxStateFromRecord, assertSandboxReadyForPromotion, serializePlanDocument } = await import("@/lib/build/sandbox-state");
      const sandboxState = buildSandboxStateFromRecord({
        buildBranch: build.buildBranch,
        gitCommitHashes: build.gitCommitHashes,
        diffPatch: diff,
        updatedAt: build.updatedAt,
        planDocument: typeof build.buildPlan === "string" ? build.buildPlan : serializePlanDocument(build.buildPlan),
        description: build.description,
        buildExecState: build.buildExecState,
      });
      const promotionGate = assertSandboxReadyForPromotion(sandboxState);
      if (!promotionGate.ok) {
        logBuildActivity(buildId, "create_portal_pr", promotionGate.message);
        return {
          success: false,
          error: "Sandbox promotion integrity blocked PR creation.",
          message: `${promotionGate.message}\n\n${promotionGate.failures.join("\n")}`,
          data: {
            gate: {
              ok: false,
              failures: promotionGate.failures,
            },
            sandbox: promotionGate.state,
          },
        };
      }

      // Resolve the portal's own repo from git remote
      let repoOwner: string | null = null;
      let repoName: string | null = null;
      try {
        const { getRemoteUrl } = await import("@/lib/git-utils");
        const remoteUrl = await getRemoteUrl();
        if (remoteUrl) {
          const match = remoteUrl.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
          if (match) { repoOwner = match[1]; repoName = match[2]; }
        }
      } catch { /* git may not be available */ }

      // Fallback to upstream URL if no local git
      if (!repoOwner) {
        const devConfig = await prisma.platformDevConfig.findUnique({
          where: { id: "singleton" },
          select: { upstreamRemoteUrl: true },
        });
        const url = devConfig?.upstreamRemoteUrl ?? "https://github.com/OpenDigitalProductFactory/opendigitalproductfactory.git";
        const match = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
        if (match) { repoOwner = match[1]; repoName = match[2]; }
      }

      if (!repoOwner || !repoName) {
        return { success: false, error: "Cannot determine repository.", message: "No git remote or upstream URL configured." };
      }

      // Resolve token
      const { resolveHiveToken, getPlatformIdentity, generatePrivateBranchName, generateAnonymousCommitMessage } = await import("@/lib/integrate/identity-privacy");
      const token = await resolveHiveToken();
      if (!token) {
        return { success: false, error: "No GitHub token available.", message: "Configure HIVE_CONTRIBUTION_TOKEN or a git credential to create PRs." };
      }

      // Run pre-PR gates
      const { runPrePRGates, formatGateReport } = await import("@/lib/integrate/pre-pr-gates");
      const gateResult = runPrePRGates(diff);

      // If gates block, return the report without creating a PR
      if (!gateResult.canProceed) {
        logBuildActivity(buildId, "create_portal_pr", `BLOCKED: ${gateResult.summary}`);
        return {
          success: false,
          error: "Pre-PR gates failed.",
          message: `The pre-PR security gates found blocking issues. Fix these before creating a PR.\n\n${formatGateReport(gateResult)}`,
          data: { gates: gateResult },
        };
      }

      // Build the PR
      const platformId = await getPlatformIdentity();
      const branchName = generatePrivateBranchName(platformId.clientId, build.title);

      const devConfigForDco = await prisma.platformDevConfig.findUnique({
        where: { id: "singleton" },
        select: { dcoAcceptedAt: true },
      });

      const commitMessage = generateAnonymousCommitMessage({
        title: build.title,
        buildId,
        productId: null,
        platformIdentity: platformId,
        dcoAcceptedAt: devConfigForDco?.dcoAcceptedAt ?? undefined,
      });

      // Build PR body with gate report and build evidence
      const verification = build.verificationOut as Record<string, unknown> | null;
      const typecheckPassed = verification?.typecheckPassed === true;
      const testsPassed = typeof verification?.testsPassed === "number" ? verification.testsPassed : 0;
      const testsFailed = typeof verification?.testsFailed === "number" ? verification.testsFailed : 0;

      // acceptanceMet (Json?) stores either a bare array or {acceptanceCriteria: [...]} — `.filter` must not assume array shape.
      const rawAcceptance = build.acceptanceMet as unknown;
      const acceptance: Array<{ met?: boolean }> = Array.isArray(rawAcceptance)
        ? (rawAcceptance as Array<{ met?: boolean }>)
        : rawAcceptance && typeof rawAcceptance === "object" && Array.isArray((rawAcceptance as { acceptanceCriteria?: unknown }).acceptanceCriteria)
          ? ((rawAcceptance as { acceptanceCriteria: Array<{ met?: boolean }> }).acceptanceCriteria)
          : [];
      const acMet = acceptance.filter((a) => a?.met === true).length;
      const acTotal = acceptance.length;

      const prBody = [
        `## ${build.title}`,
        "",
        `Build: \`${buildId}\` | Phase: \`${build.phase}\``,
        "",
        "### Verification",
        `- TypeCheck: ${typecheckPassed ? "PASSED" : "FAILED"}`,
        `- Tests: ${testsPassed} passed, ${testsFailed} failed`,
        `- Acceptance: ${acMet}/${acTotal} criteria met`,
        "",
        formatGateReport(gateResult),
        "",
        "---",
        `License: Apache-2.0 | ${platformId.dcoSignoff}`,
      ].join("\n");

      const prTitle = `feat(${buildId}): ${build.title}`;
      const labels = ["build-studio", "automated"];
      if (gateResult.requiresHumanReview) labels.push("needs-review");
      if (!typecheckPassed || testsFailed > 0) labels.push("verification-issues");

      const { createBranchAndPR, mergePR, commentOnPR } = await import("@/lib/integrate/github-api-commit");

      const prResult = await createBranchAndPR({
        headOwner: repoOwner,
        headRepo: repoName,
        baseOwner: repoOwner,
        baseRepo: repoName,
        branchName,
        commitMessage,
        diff,
        prTitle,
        prBody,
        labels,
        token,
      });

      if (!prResult.prUrl || !prResult.prNumber) {
        logBuildActivity(buildId, "create_portal_pr", `Branch created (${branchName}) but PR creation failed.`);
        return {
          success: false,
          error: "PR creation failed.",
          message: `Branch \`${branchName}\` was created with the commit, but the pull request could not be opened. Check GitHub permissions.`,
          data: { branchName, commitSha: prResult.commitSha },
        };
      }

      // Auto-merge decision: all gates pass + build fully verified
      const fullyVerified = typecheckPassed && testsFailed === 0 && acMet === acTotal && acTotal > 0;
      let merged = false;

      if (!gateResult.requiresHumanReview && fullyVerified) {
        // Auto-merge via squash
        const mergeResult = await mergePR({
          owner: repoOwner,
          repo: repoName,
          prNumber: prResult.prNumber,
          commitTitle: `${prTitle} (#${prResult.prNumber})`,
          mergeMethod: "squash",
          token,
        });
        merged = mergeResult.merged;

        if (merged) {
          // Update lifecycle: FeatureBuild → complete, ChangePromotion → deployed
          await prisma.featureBuild.update({
            where: { buildId },
            data: { phase: "complete" },
          });
          const { recordReadyDependentsAfterCompletion } = await import("@/lib/build/feature-build-dependencies");
          await recordReadyDependentsAfterCompletion({ db: prisma, buildId }).catch((err) => {
            console.warn("[create_portal_pr] dependency readiness check failed:", err);
          });

          const promotion = build.productVersions[0]?.promotions[0];
          if (promotion) {
            await prisma.changePromotion.update({
              where: { promotionId: promotion.promotionId },
              data: { status: "deployed", deployedAt: new Date(), deploymentLog: `Auto-merged PR #${prResult.prNumber}` },
            });
          }

          logBuildActivity(buildId, "create_portal_pr", `PR #${prResult.prNumber} auto-merged. Build complete.`);
        } else {
          // Merge failed — post comment explaining why
          await commentOnPR({
            owner: repoOwner, repo: repoName, prNumber: prResult.prNumber,
            body: `Auto-merge failed. This PR requires manual review and merge.\n\n${formatGateReport(gateResult)}`,
            token,
          }).catch(() => {});

          logBuildActivity(buildId, "create_portal_pr", `PR #${prResult.prNumber} created but auto-merge failed.`);
        }
      } else {
        // Needs human review — post gate report as comment
        const reasons: string[] = [];
        if (gateResult.requiresHumanReview) reasons.push("security gate warnings");
        if (!typecheckPassed) reasons.push("TypeCheck failed");
        if (testsFailed > 0) reasons.push(`${testsFailed} test(s) failed`);
        if (acMet < acTotal) reasons.push(`${acTotal - acMet} acceptance criteria not met`);

        await commentOnPR({
          owner: repoOwner, repo: repoName, prNumber: prResult.prNumber,
          body: `This PR requires human review: ${reasons.join(", ")}.\n\n${formatGateReport(gateResult)}`,
          token,
        }).catch(() => {});

        logBuildActivity(buildId, "create_portal_pr", `PR #${prResult.prNumber} created — needs review: ${reasons.join(", ")}`);
      }

      const statusMsg = merged
        ? `PR #${prResult.prNumber} created and auto-merged. Build is complete.`
        : `PR #${prResult.prNumber} created and awaiting review. ${gateResult.summary}`;

      return {
        success: true,
        message: `${statusMsg}\n\n${prResult.prUrl}`,
        data: {
          prUrl: prResult.prUrl,
          prNumber: prResult.prNumber,
          branchName,
          commitSha: prResult.commitSha,
          merged,
          gates: gateResult,
        },
      };
    }

    // ─── Scheduling & Release Tools ──────────────────────────────────────────

    case "check_deployment_windows": {
      const changeType = String(params.change_type ?? "normal");
      const riskLevel = String(params.risk_level ?? "low");
      const profile = await prisma.businessProfile.findFirst({
        where: { isActive: true },
        include: { deploymentWindows: true, blackoutPeriods: true },
      });

      if (!profile) {
        return { success: true, message: "No business profile configured — deployment is unrestricted. Set up operating hours in Admin to enable deployment windows.", data: { available: true, unrestricted: true } };
      }

      const now = new Date();
      const activeBlackout = profile.blackoutPeriods.find(
        (bp) => bp.startAt <= now && bp.endAt >= now && !bp.exceptions.includes(changeType),
      );
      if (activeBlackout) {
        return {
          success: true,
          message: `Blackout period active until ${activeBlackout.endAt.toISOString()}. Reason: ${activeBlackout.reason ?? "Scheduled blackout"}. Emergency changes may override.`,
          data: { available: false, blackout: true, blackoutEnd: activeBlackout.endAt.toISOString(), reason: activeBlackout.reason },
        };
      }

      const { isNowInWindow } = await import("@/lib/sandbox-promotion");
      const matchingWindows = profile.deploymentWindows.filter(
        (w) => w.allowedChangeTypes.includes(changeType) && w.allowedRiskLevels.includes(riskLevel),
      );

      if (matchingWindows.length === 0) {
        return { success: true, message: "No deployment windows configured for this change type and risk level — deployment is unrestricted.", data: { available: true, unrestricted: true } };
      }

      const windowOpen = isNowInWindow(matchingWindows);
      const windowSummary = matchingWindows.map((w) => ({
        name: w.name,
        days: w.dayOfWeek,
        startTime: w.startTime,
        endTime: w.endTime,
      }));

      return {
        success: true,
        message: windowOpen
          ? `Deployment window is OPEN now. ${matchingWindows.length} matching window(s) available.`
          : `Not in a deployment window. Available windows: ${matchingWindows.map((w) => `${w.name}: days ${w.dayOfWeek.join(",")}, ${w.startTime}-${w.endTime}`).join("; ")}`,
        data: { available: windowOpen, windows: windowSummary },
      };
    }

    case "schedule_promotion": {
      const promotionId = String(params.promotion_id ?? "");
      if (!promotionId) return { success: false, error: "promotion_id is required.", message: "Provide a promotion ID." };

      const promotion = await prisma.changePromotion.findUnique({
        where: { promotionId },
        include: {
          changeItem: { include: { changeRequest: true } },
          productVersion: { include: { featureBuild: { select: { buildId: true } } } },
        },
      });
      if (!promotion) return { success: false, error: "Promotion not found.", message: `No promotion with ID ${promotionId}.` };
      if (promotion.status !== "approved") return { success: false, error: "Promotion must be approved first.", message: `Current status: ${promotion.status}` };

      // Find next available window
      const profile = await prisma.businessProfile.findFirst({
        where: { isActive: true },
        include: { deploymentWindows: true },
      });

      if (!profile || profile.deploymentWindows.length === 0) {
        return { success: true, message: "No deployment windows configured. Promotion can be deployed anytime via Operations > Promotions.", data: { scheduled: false } };
      }

      const rfcType = promotion.changeItem?.changeRequest?.type ?? "normal";
      const riskLevel = promotion.changeItem?.changeRequest?.riskLevel ?? "low";
      const matchingWindows = profile.deploymentWindows.filter(
        (w) => w.allowedChangeTypes.includes(rfcType) && w.allowedRiskLevels.includes(riskLevel),
      );

      if (matchingWindows.length === 0) {
        return { success: true, message: "No windows match this change type and risk level. Ask an admin to configure appropriate deployment windows.", data: { scheduled: false } };
      }

      // Update RFC with deployment window info
      const rfc = promotion.changeItem?.changeRequest;
      if (rfc) {
        await prisma.changeRequest.update({
          where: { id: rfc.id },
          data: {
            status: "scheduled",
            scheduledAt: new Date(),
            deploymentWindowId: matchingWindows[0]!.id,
          },
        });
      }

      const windowDesc = matchingWindows.map((w) => `${w.name}: days ${w.dayOfWeek.join(",")}, ${w.startTime}-${w.endTime}`).join("; ");

      // Persist the scheduled state on the ChangePromotion so getBuildFlowState
      // reads it without peeking at ChangeRequest. deploymentLog carries the
      // window description so the fork can render "Scheduled for <window>".
      await prisma.changePromotion.update({
        where: { promotionId },
        data: { status: "scheduled", deploymentLog: `window: ${windowDesc}` },
      }).catch(() => {});

      logBuildActivity(promotionId, "schedule_promotion", `Scheduled for window: ${windowDesc}`);

      const scheduleBuildId = promotion.productVersion?.featureBuild?.buildId ?? null;
      if (scheduleBuildId) {
        const { reconcileBuildCompletion } = await import("@/lib/build-flow-state");
        await reconcileBuildCompletion(scheduleBuildId).catch(() => {});
      }

      return {
        success: true,
        message: `Promotion ${promotionId} scheduled. Deployment windows: ${windowDesc}. An operator can deploy via Operations > Promotions during an open window.`,
        data: { scheduled: true, windows: windowDesc },
      };
    }

    case "execute_promotion": {
      const promotionId = String(params.promotion_id ?? "");
      const overrideReason = params.override_reason ? String(params.override_reason) : undefined;
      if (!promotionId || !/^[a-zA-Z0-9_-]+$/.test(promotionId)) {
        return { success: false, error: "Invalid promotion_id", message: "Provide a valid promotion ID." };
      }

      // Validate promotion exists and is approved
      const promo = await prisma.changePromotion.findFirst({ where: { promotionId } });
      if (!promo) return { success: false, error: "Not found", message: `Promotion ${promotionId} not found.` };
      if (promo.status === "deployed") return { success: true, message: "Already deployed.", data: { status: "deployed" } };
      if (promo.status !== "approved") return { success: false, error: `Status is ${promo.status}`, message: "Must be approved first." };

      // Enforce deployment window — block execution outside windows unless emergency override
      if (!overrideReason) {
        const { getPromotionWindowStatus } = await import("@/lib/actions/promotions");
        const windowStatus = await getPromotionWindowStatus(promotionId);
        if (!windowStatus.available) {
          return {
            success: false,
            error: "Outside deployment window",
            message: `${windowStatus.message} Use schedule_promotion to queue for the next window, or provide override_reason for emergency deployment.`,
          };
        }
      }

      // Resolve sandbox and build ID
      const promoDetail = await prisma.changePromotion.findFirst({
        where: { promotionId },
        include: { productVersion: { include: { featureBuild: { select: { sandboxId: true, buildId: true } } } } },
      });
      const sandboxId = promoDetail?.productVersion?.featureBuild?.sandboxId;
      const promoBuildId = promoDetail?.productVersion?.featureBuild?.buildId;
      if (!sandboxId) return { success: false, error: "No sandbox", message: "No sandbox linked to this promotion." };

      const { execFile: execFileCb } = lazyChildProcess();
      const { promisify } = lazyUtil();
      const execFileAsync = promisify(execFileCb);
      const execAsync = promisify((lazyChildProcess()).exec);

      // Preflight: the promoter image has to exist locally for `docker run
      // dpf-promoter` to work. On most installs today it doesn't — the image
      // is built separately and isn't on the default compose path. Detect
      // that up front and hand off to the operator UI instead of attempting
      // the run and returning a scary "Could not start the promoter
      // container." message. The promotion stays approved; the operator
      // triggers it from Operations > Promotions.
      try {
        await execAsync("docker image inspect dpf-promoter");
      } catch {
        // Persist the awaiting_operator state so getBuildFlowState reads it
        // from the ChangePromotion column directly (no BuildActivity detour)
        // and the reconciler can treat the fork as dispositioned. The prior
        // version returned awaiting_operator only in the tool response, which
        // the fork state machine couldn't see.
        await prisma.changePromotion.update({
          where: { promotionId },
          data: { status: "awaiting_operator" },
        }).catch(() => {});
        logBuildActivity(promoBuildId ?? promotionId, "execute_promotion", "Promoter image not present — handing off to operator");
        if (promoBuildId) {
          const { reconcileBuildCompletion } = await import("@/lib/build-flow-state");
          await reconcileBuildCompletion(promoBuildId).catch(() => {});
        }
        return {
          success: true,
          message: `Promotion ${promotionId} is approved and ready. This install doesn't have the "dpf-promoter" container image built locally, so automatic deployment is disabled here. An operator needs to run the promotion manually from Operations > Promotions, which will stream the deployment log and handle rollback. The promotion stays in "awaiting_operator" status until then.`,
          data: { status: "awaiting_operator", reason: "promoter_image_missing", promotionId },
        };
      }

      // Start promoter container (array form — no shell injection)
      try {
        await execAsync("docker rm dpf-promoter-1 2>/dev/null || true");
        const dockerArgs = [
          "run", "-d",
          "--name", "dpf-promoter-1",
          "--network", `${process.env.DPF_COMPOSE_PROJECT ?? "dpf"}_default`,
          "-v", "/var/run/docker.sock:/var/run/docker.sock",
          "-v", "dpf_backups:/backups",
          "-e", `PROMOTION_ID=${promotionId}`,
          "-e", `DPF_PRODUCTION_DB_CONTAINER=${process.env.DPF_PRODUCTION_DB_CONTAINER ?? "dpf-postgres-1"}`,
          "-e", "DPF_PORTAL_CONTAINER=dpf-portal-1",
          "-e", `DPF_COMPOSE_PROJECT=${process.env.DPF_COMPOSE_PROJECT ?? "dpf"}`,
          "-e", `DPF_SANDBOX_CONTAINER=${sandboxId}`,
          "-e", `POSTGRES_USER=${process.env.POSTGRES_USER ?? "dpf"}`,
        ];
        if (overrideReason) {
          dockerArgs.push("-e", `DPF_WINDOW_OVERRIDE=${overrideReason}`);
        }
        dockerArgs.push("dpf-promoter");
        await execFileAsync("docker", dockerArgs);
      } catch (err) {
        return {
          success: false,
          error: `Failed to start promoter: ${(err as Error).message?.slice(0, 200)}`,
          message: `Could not start the promoter container. An operator can run this promotion manually from Operations > Promotions — the promotion stays in "approved" status until deployed.`,
        };
      }

      // Poll for completion (max 10 minutes)
      const maxWaitMs = 10 * 60 * 1000;
      const pollIntervalMs = 10_000;
      const startTime = Date.now();
      let exitCode: number | null = null;

      while (Date.now() - startTime < maxWaitMs) {
        await new Promise(r => setTimeout(r, pollIntervalMs));
        try {
          const { stdout } = await execAsync("docker inspect dpf-promoter-1 --format='{{.State.Status}} {{.State.ExitCode}}'");
          const parts = stdout.trim().replace(/'/g, "").split(" ");
          if (parts[0] === "exited") {
            exitCode = parseInt(parts[1] ?? "1", 10);
            break;
          }
        } catch { /* container may not exist yet */ }
      }

      if (exitCode === null) {
        await execAsync("docker stop dpf-promoter-1 2>/dev/null || true").catch(() => {});
        return { success: false, error: "Timeout (10 min)", message: "Promoter did not complete. Check ops dashboard." };
      }

      const finalPromo = await prisma.changePromotion.findFirst({ where: { promotionId } });
      const promoSuccess = exitCode === 0 && finalPromo?.status === "deployed";

      await execAsync("docker rm dpf-promoter-1 2>/dev/null || true").catch(() => {});
      logBuildActivity(promoBuildId ?? promotionId, "execute_promotion", promoSuccess ? "Deployed successfully" : `Rolled back: ${finalPromo?.rollbackReason ?? "unknown"}`);

      if (promoBuildId) {
        const { reconcileBuildCompletion } = await import("@/lib/build-flow-state");
        await reconcileBuildCompletion(promoBuildId).catch(() => {});
      }

      return {
        success: promoSuccess,
        message: promoSuccess
          ? `Promotion ${promotionId} deployed. Health check passed.`
          : `Rolled back. ${finalPromo?.rollbackReason ?? "Check deployment log."}`,
        data: { promotionId, status: finalPromo?.status, deploymentLog: finalPromo?.deploymentLog?.slice(0, 1000) },
      };
    }

    case "create_release_bundle": {
      const title = String(params.title ?? "");
      const buildIds = Array.isArray(params.build_ids) ? params.build_ids.map(String) : [];
      if (!title) return { success: false, error: "title is required.", message: "Provide a release bundle title." };
      if (buildIds.length === 0) return { success: false, error: "build_ids is required.", message: "Provide at least one build ID." };

      // Validate all builds exist and are in review/complete phase
      const builds = await prisma.featureBuild.findMany({
        where: { buildId: { in: buildIds } },
        select: { buildId: true, title: true, phase: true, releaseBundleId: true },
      });
      const missing = buildIds.filter((id) => !builds.some((b) => b.buildId === id));
      if (missing.length > 0) return { success: false, error: `Builds not found: ${missing.join(", ")}`, message: `Could not find builds: ${missing.join(", ")}` };

      const notReady = builds.filter((b) => !["review", "complete", "ship"].includes(b.phase));
      if (notReady.length > 0) {
        return { success: false, error: `Builds not ready: ${notReady.map((b) => `${b.buildId} (${b.phase})`).join(", ")}`, message: `All builds must be in review or complete phase.` };
      }

      const alreadyBundled = builds.filter((b) => b.releaseBundleId);
      if (alreadyBundled.length > 0) {
        return { success: false, error: `Builds already in a bundle: ${alreadyBundled.map((b) => b.buildId).join(", ")}`, message: `Remove from existing bundle first.` };
      }

      // Create the bundle
      const bundleId = `RB-${new Date().toISOString().slice(0, 10)}-${crypto.randomUUID().slice(0, 6).toUpperCase()}`;
      const bundle = await prisma.releaseBundle.create({
        data: {
          bundleId,
          title,
          status: "assembling",
          createdBy: userId,
        },
      });

      // Link builds to the bundle
      await prisma.featureBuild.updateMany({
        where: { buildId: { in: buildIds } },
        data: { releaseBundleId: bundle.id },
      });

      return {
        success: true,
        message: `Release bundle ${bundleId} created with ${buildIds.length} build(s): ${builds.map((b) => b.title).join(", ")}. Run gate checks before scheduling deployment.`,
        data: { bundleId, title, buildCount: buildIds.length, builds: builds.map((b) => ({ buildId: b.buildId, title: b.title })) },
      };
    }

    case "run_release_gate": {
      const bundleId = String(params.bundle_id ?? "");
      if (!bundleId) return { success: false, error: "bundle_id is required.", message: "Provide a release bundle ID." };

      const bundle = await prisma.releaseBundle.findUnique({
        where: { bundleId },
        include: {
          builds: {
            select: {
              buildId: true, title: true, phase: true, diffPatch: true,
              verificationOut: true, sandboxId: true,
            },
          },
        },
      });
      if (!bundle) return { success: false, error: "Bundle not found.", message: `No release bundle with ID ${bundleId}.` };
      if (bundle.status !== "assembling") {
        return { success: false, error: `Gate check already run. Bundle status: ${bundle.status}`, message: `Bundle is in ${bundle.status} state.` };
      }
      if (bundle.builds.length === 0) {
        return { success: false, error: "Bundle has no builds.", message: "Add builds to the bundle first." };
      }

      // Check all builds are in review/complete/ship phase
      const notReady = bundle.builds.filter((b) => !["review", "complete", "ship"].includes(b.phase));
      if (notReady.length > 0) {
        return {
          success: false, error: `Builds not ready: ${notReady.map((b) => `${b.buildId} (${b.phase})`).join(", ")}`,
          message: "All builds must be in review or complete phase.",
        };
      }

      // Check all builds have passing tests
      const failedTests = bundle.builds.filter((b) => {
        const v = b.verificationOut as Record<string, unknown> | null;
        return v && (v.testsPassed === false || v.testsPassed === 0);
      });

      // Combine diffs from all builds
      const diffs: string[] = [];
      for (const build of bundle.builds) {
        if (build.diffPatch) {
          diffs.push(build.diffPatch as string);
        } else if (build.sandboxId) {
          try {
            const { extractDiff } = await import("@/lib/sandbox");
            const diff = await extractDiff(build.sandboxId);
            diffs.push(diff);
          } catch {
            // Build has no extractable diff — may be fine if it's code-only
          }
        }
      }

      const combinedDiff = diffs.join("\n");

      // Scan for destructive operations
      const { scanForDestructiveOps, categorizeDiffFiles } = await import("@/lib/sandbox-promotion");
      const allFileMatches = [...combinedDiff.matchAll(/^diff --git a\/(.+) b\/.+$/gm)].map((m) => m[1]);
      const { migrationFiles } = categorizeDiffFiles(allFileMatches);
      const destructiveWarnings = migrationFiles.length > 0 ? scanForDestructiveOps(combinedDiff) : [];

      // Build gate check result
      const gateResult = {
        buildsChecked: bundle.builds.length,
        allTestsPass: failedTests.length === 0,
        failedTestBuilds: failedTests.map((b) => b.buildId),
        totalFilesChanged: allFileMatches.length,
        migrationFiles: migrationFiles.length,
        destructiveWarnings,
        combinedDiffLength: combinedDiff.length,
      };

      const passed = failedTests.length === 0 && destructiveWarnings.length === 0;

      // Update bundle
      await prisma.releaseBundle.update({
        where: { bundleId },
        data: {
          status: passed ? "approved" : "gate_check",
          combinedDiffPatch: combinedDiff,
          gateCheckResult: gateResult as unknown as import("@dpf/db").Prisma.InputJsonValue,
        },
      });

      const messageParts = [
        `Gate check ${passed ? "PASSED" : "FAILED"} for ${bundleId}.`,
        `${bundle.builds.length} build(s), ${allFileMatches.length} file(s) changed, ${migrationFiles.length} migration(s).`,
      ];
      if (failedTests.length > 0) messageParts.push(`Failing tests in: ${failedTests.map((b) => b.buildId).join(", ")}.`);
      if (destructiveWarnings.length > 0) messageParts.push(`Destructive ops: ${destructiveWarnings.join("; ")}`);
      if (passed) messageParts.push("Bundle is approved and ready to schedule for deployment.");

      return { success: true, message: messageParts.join(" "), data: gateResult };
    }

    case "schedule_release_bundle": {
      const bundleId = String(params.bundle_id ?? "");
      if (!bundleId) return { success: false, error: "bundle_id is required.", message: "Provide a release bundle ID." };

      const bundle = await prisma.releaseBundle.findUnique({
        where: { bundleId },
        include: { builds: { select: { buildId: true, title: true, createdById: true } } },
      });
      if (!bundle) return { success: false, error: "Bundle not found.", message: `No bundle ${bundleId}.` };
      if (bundle.status !== "approved") {
        return { success: false, error: `Bundle must be approved first. Current: ${bundle.status}`, message: `Run gate checks first.` };
      }

      // Find next available deployment window
      const profile = await prisma.businessProfile.findFirst({
        where: { isActive: true },
        include: { deploymentWindows: true },
      });

      const matchingWindows = profile?.deploymentWindows.filter(
        (w) => w.allowedChangeTypes.includes("normal") && w.allowedRiskLevels.includes("low"),
      ) ?? [];

      // Create RFC for the bundle
      const rfcId = `RFC-${new Date().getFullYear()}-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
      const rfc = await prisma.changeRequest.create({
        data: {
          rfcId,
          title: `Release: ${bundle.title}`,
          description: `Release bundle ${bundleId} with ${bundle.builds.length} build(s): ${bundle.builds.map((b) => b.title).join(", ")}`,
          type: "normal",
          scope: "platform",
          riskLevel: "low",
          status: "scheduled",
          scheduledAt: new Date(),
          requestedById: bundle.createdBy,
          ...(matchingWindows.length > 0 ? { deploymentWindowId: matchingWindows[0]!.id } : {}),
        },
      });

      // Create CalendarEvent for visibility
      const employee = await prisma.employeeProfile.findFirst({
        where: { userId: bundle.createdBy },
        select: { id: true },
      });

      let calendarEventId: string | undefined;
      if (employee) {
        const eventId = `RELEASE-${bundleId}`;
        await prisma.calendarEvent.upsert({
          where: { eventId },
          create: {
            eventId,
            title: `Deployment: ${bundle.title}`,
            description: `${bundle.builds.length} feature(s): ${bundle.builds.map((b) => b.title).join(", ")}`,
            startAt: new Date(),
            eventType: "action",
            category: "platform",
            ownerEmployeeId: employee.id,
            visibility: "team",
            color: "#f59e0b",
          },
          update: { title: `Deployment: ${bundle.title}`, startAt: new Date() },
        });
        calendarEventId = eventId;
      }

      // Update bundle
      await prisma.releaseBundle.update({
        where: { bundleId },
        data: {
          status: "scheduled",
          rfcId: rfc.rfcId,
          calendarEventId,
          scheduledAt: new Date(),
          ...(matchingWindows.length > 0 ? { deploymentWindowId: matchingWindows[0]!.id } : {}),
        },
      });

      const windowDesc = matchingWindows.length > 0
        ? matchingWindows.map((w) => `${w.name}: days ${w.dayOfWeek.join(",")}, ${w.startTime}-${w.endTime}`).join("; ")
        : "No windows configured — deployment unrestricted";

      return {
        success: true,
        message: `Release ${bundleId} scheduled. RFC: ${rfcId}. Added to operations calendar. Windows: ${windowDesc}. An operator can deploy via Operations > Promotions.`,
        data: { bundleId, rfcId, calendarEventId, windows: windowDesc },
      };
    }

    case "get_release_status": {
      const bundleId = params.bundle_id ? String(params.bundle_id) : null;
      const promotionId = params.promotion_id ? String(params.promotion_id) : null;

      if (bundleId) {
        const bundle = await prisma.releaseBundle.findUnique({
          where: { bundleId },
          include: { builds: { select: { buildId: true, title: true, phase: true } } },
        });
        if (!bundle) return { success: false, error: "Bundle not found.", message: `No release bundle with ID ${bundleId}.` };
        return {
          success: true,
          message: `Release ${bundle.bundleId}: ${bundle.status}. ${bundle.builds.length} build(s).`,
          data: {
            bundleId: bundle.bundleId,
            title: bundle.title,
            status: bundle.status,
            builds: bundle.builds,
            scheduledAt: bundle.scheduledAt?.toISOString() ?? null,
            deployedAt: bundle.deployedAt?.toISOString() ?? null,
          },
        };
      }

      if (promotionId) {
        const { getPromotionWindowStatus } = await import("@/lib/actions/promotions");
        const windowStatus = await getPromotionWindowStatus(promotionId).catch(() => ({ available: false, message: "Could not check window status" }));
        const promotion = await prisma.changePromotion.findUnique({
          where: { promotionId },
          select: { status: true, deployedAt: true, rationale: true, rollbackReason: true },
        });
        if (!promotion) return { success: false, error: "Promotion not found.", message: `No promotion with ID ${promotionId}.` };
        return {
          success: true,
          message: `Promotion ${promotionId}: ${promotion.status}. Window: ${windowStatus.message}`,
          data: { promotionId, ...promotion, windowStatus },
        };
      }

      return { success: false, error: "Provide bundle_id or promotion_id.", message: "Specify which release or promotion to check." };
    }

    // ─── Hive Mind Contribution ──────────────────────────────────────────────

    case "assess_contribution": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      const build = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: {
          title: true, brief: true, buildPlan: true, diffPatch: true, diffSummary: true,
          phase: true, portfolioId: true, digitalProductId: true,
          verificationOut: true, sandboxId: true, designDoc: true,
        },
      });
      if (!build) return { success: false, error: "Build not found.", message: "Build not found." };

      const brief = build.brief as Record<string, unknown> | null;
      const plan = build.buildPlan as Record<string, unknown> | null;
      const diff = (build.diffPatch ?? build.diffSummary ?? "") as string;
      const designDoc = build.designDoc as Record<string, unknown> | null;
      const reusability = designDoc?.reusabilityAnalysis as { scope?: string; contributionReadiness?: string } | undefined;

      // Parse diff to understand scope
      const changedFiles = [...diff.matchAll(/^diff --git a\/(.+) b\/.+$/gm)].map((m) => m[1]);
      const newRoutes = changedFiles.filter((f) => f.includes("/app/") && f.endsWith("/page.tsx"));
      const schemaChanges = changedFiles.filter((f) => f.includes("schema.prisma"));
      const migrationFiles = changedFiles.filter((f) => f.startsWith("prisma/migrations/"));
      const hasNewModels = diff.includes("model ") && diff.includes("@id");

      // ── Criterion 1: Vision Alignment ──
      const portfolioId = build.portfolioId ?? "unknown";
      const description = String(brief?.description ?? "");
      const isPortfolioAligned = !!build.portfolioId;
      const mentionsDPPM = /product|portfolio|lifecycle|taxonomy|backlog|compliance|operations/i.test(description);
      const visionScore = isPortfolioAligned && mentionsDPPM ? "high" : isPortfolioAligned ? "medium" : "low";
      const visionReasoning = visionScore === "high"
        ? `Aligned with portfolio ${portfolioId} and extends platform capabilities (${mentionsDPPM ? "touches DPPM concepts" : ""}).`
        : visionScore === "medium"
          ? `Assigned to portfolio ${portfolioId} but domain alignment is unclear from the description.`
          : "Not assigned to a portfolio — unclear how this connects to the platform vision.";

      // ── Criterion 2: Community Value ──
      const targetRoles = Array.isArray(brief?.targetRoles) ? brief.targetRoles : [];
      const broadRoles = targetRoles.length === 0 || targetRoles.includes("All") || targetRoles.length >= 3;
      const acceptanceCriteria = Array.isArray(brief?.acceptanceCriteria) ? brief.acceptanceCriteria : [];
      const isGeneral = !description.match(/\b(acme|our company|internal|proprietary|specific to)\b/i);
      let communityScore = broadRoles && isGeneral ? "high" : isGeneral ? "medium" : "low";

      // Enhance community value scoring with ideate-time reusability analysis
      if (reusability) {
        if (reusability.scope === "already_generic" || (reusability.scope === "parameterizable" && reusability.contributionReadiness === "high")) {
          communityScore = "high";
        } else if (reusability.scope === "parameterizable" && communityScore !== "high") {
          communityScore = "medium";
        }
        // one_off: leave existing heuristic scoring — user explicitly chose single-use
      }

      const communityReasoning = communityScore === "high"
        ? `Targets ${broadRoles ? "broad roles" : targetRoles.join(", ")} with ${acceptanceCriteria.length} general acceptance criteria.${reusability?.scope === "parameterizable" ? " Feature was designed with parameterization for reusability." : reusability?.scope === "already_generic" ? " Feature was designed as generic from the start." : ""}`
        : communityScore === "medium"
          ? `Targets specific roles (${targetRoles.join(", ")}) but the functionality appears generalizable.${reusability?.scope === "parameterizable" ? " Parameterization was planned but may need completion before contributing." : ""}`
          : "Contains organization-specific language or targets a narrow use case.";

      // ── Criterion 3: Augmentation vs Innovation ──
      const isAugmentation = newRoutes.length <= 1 && !hasNewModels;
      const augLevel = isAugmentation ? "augmentation" as const : "innovation" as const;
      const augReasoning = isAugmentation
        ? `Modifies ${changedFiles.length} existing files with ${newRoutes.length} new route(s). This augments existing capability — straightforward to merge.`
        : `Creates ${newRoutes.length} new route(s) and ${hasNewModels ? "new data models" : "significant structural changes"}. This is an innovation — benefits from community review before merging.`;

      // ── Criterion 4: Proprietary Sensitivity ──
      const concerns: string[] = [];
      // Only flag *assignments* of a secret-shaped identifier to a 20+ char opaque
      // string, or known secret token prefixes (GitHub / OpenAI / Slack / AWS / JWT).
      // The old bare-word match flagged benign identifiers like `scopeToken`,
      // `cancellationToken`, `accessTokenName`, `secretRef` — every round of
      // assess_contribution drowned in false positives.
      const secretAssignment = /[A-Za-z0-9_]*(api[_-]?key|apikey|secret|password|token)[A-Za-z0-9_]*\s*[:=]\s*["'`][A-Za-z0-9_\-+/.=]{20,}/i;
      const knownSecretPrefix = /(ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|ghu_[A-Za-z0-9]{20,}|ghs_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{15,}|AKIA[A-Z0-9]{16})/;
      if (secretAssignment.test(diff) || knownSecretPrefix.test(diff)) concerns.push("Contains references to API keys or secrets");
      if (/acme|our company|internal use only|confidential/i.test(diff)) concerns.push("Contains organization-specific references");
      if (/\$\d+[\d,.]*|pricing|rate.*card|margin/i.test(diff)) concerns.push("Contains pricing or financial constants");
      if (/customer.*name|client.*id|account.*number/i.test(diff)) concerns.push("Contains customer data references");
      const isSensitive = concerns.length > 0;

      // ── Overall Recommendation ──
      let recommendation: "contribute" | "contribute_with_mods" | "keep_local" | "user_decides";
      if (isSensitive) {
        recommendation = concerns.length > 2 ? "keep_local" : "contribute_with_mods";
      } else if (visionScore === "high" && communityScore === "high") {
        recommendation = "contribute";
      } else if (visionScore === "low" && communityScore === "low") {
        recommendation = "keep_local";
      } else {
        recommendation = "user_decides";
      }

      const summaryMap = {
        contribute: `This feature looks great for the community. It extends ${build.title} within the ${portfolioId} portfolio and other organizations would benefit. Would you like to contribute it to the Hive Mind?`,
        contribute_with_mods: `This feature could benefit others, but I noticed some concerns: ${concerns.join("; ")}. If you'd like to contribute, I'd suggest removing organization-specific references first. Want me to prepare a cleaned version?`,
        keep_local: `This feature is well-built but it's ${isSensitive ? "contains sensitive content" : "specific to your organization"}. I'd recommend keeping it local. You can always contribute later if you generalize it.`,
        user_decides: `I see arguments both ways for contributing "${build.title}". Vision alignment: ${visionScore}. Community value: ${communityScore}. ${augLevel === "innovation" ? "This is an innovation that would benefit from review." : "This augments existing capability."} What would you prefer?`,
      };

      const assessment = {
        recommendation,
        criteria: {
          visionAlignment: { score: visionScore, reasoning: visionReasoning },
          communityValue: { score: communityScore, reasoning: communityReasoning },
          augmentationLevel: { level: augLevel, reasoning: augReasoning },
          proprietarySensitivity: { sensitive: isSensitive, concerns },
        },
        summary: summaryMap[recommendation],
        suggestedMods: isSensitive ? concerns.map((c) => `Remove: ${c}`) : [],
        filesChanged: changedFiles.length,
        newRoutes: newRoutes.length,
        hasSchemaChanges: schemaChanges.length > 0,
        hasMigrations: migrationFiles.length > 0,
      };

      // Persist assessment on build record
      await prisma.featureBuild.update({
        where: { buildId },
        data: { taskResults: { ...(build.verificationOut as Record<string, unknown> ?? {}), contributionAssessment: assessment } as unknown as import("@dpf/db").Prisma.InputJsonValue },
      });

      logBuildActivity(buildId, "assess_contribution", `Recommendation: ${recommendation}. Vision: ${visionScore}, Community: ${communityScore}, Type: ${augLevel}, Sensitive: ${isSensitive}`);

      return { success: true, message: assessment.summary, data: assessment };
    }

    case "contribute_to_hive": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };

      const devConfig = await prisma.platformDevConfig.findUnique({
        where: { id: "singleton" },
        select: { contributionMode: true, upstreamRemoteUrl: true, dcoAcceptedAt: true, gitRemoteUrl: true },
      });
      const { getPlatformDevPolicyState } = await import("@/lib/platform-dev-policy");
      const policyState = getPlatformDevPolicyState(devConfig);
      if (policyState === "policy_pending") {
        return {
          success: false,
          error: "Platform development policy not configured.",
          message:
            "Contribution is blocked until Platform Development is configured in the portal. Finish that setup first, then decide whether this install stays private or contributes governed changes upstream.",
        };
      }
      if (devConfig?.contributionMode === "fork_only") {
        return {
          success: false,
          error: "Install is configured for private development only.",
          message:
            "This install is configured to keep shipped features private. Change Platform Development settings if you want Build Studio to create upstream contributions.",
        };
      }

      // Prerequisite checks for PR creation — fail loudly up front rather
      // than silently producing a FeaturePack with prUrl:null downstream.
      // Previously both conditions below gated the PR attempt inside a
      // try/catch at line ~4866 and a falsy result was swallowed: the tool
      // returned success:true with no prUrl, leaving the coworker claiming
      // "contributed" when no upstream PR ever landed.
      if (!devConfig?.dcoAcceptedAt) {
        return {
          success: false,
          error: "DCO not accepted.",
          message:
            "Upstream contributions require the Developer Certificate of Origin. Visit Admin > Platform Development and accept the DCO, then retry.",
        };
      }
      const { resolveHiveToken: resolveHiveTokenEarly } = await import("@/lib/integrate/identity-privacy");
      const hiveTokenEarly = await resolveHiveTokenEarly();
      if (!hiveTokenEarly) {
        return {
          success: false,
          error: "No GitHub token configured for hive contributions.",
          message:
            "Upstream contributions need a GitHub token. Set HIVE_CONTRIBUTION_TOKEN on the portal container, seed a 'hive-contribution' credential in admin, or fall back to GITHUB_TOKEN. Then retry.",
        };
      }

      const build = await prisma.featureBuild.findUnique({
        where: { buildId },
        select: {
          id: true, title: true, brief: true, diffPatch: true, diffSummary: true,
          sandboxId: true, portfolioId: true, createdById: true,
          buildBranch: true, gitCommitHashes: true, updatedAt: true, buildPlan: true,
          description: true, buildExecState: true,
          createdBy: { select: { email: true } },
        },
      });
      if (!build || build.createdById !== userId) {
        return { success: false, error: "Build not found.", message: `No active build ${buildId} was found for this user.` };
      }

      const { diagnoseSandboxReadiness } = await import("@/lib/integrate/sandbox/sandbox-admin");
      const { assertSandboxReadyForContribution } = await import("@/lib/integrate/sandbox/sandbox-readiness-gate");
      const readiness = await diagnoseSandboxReadiness({ buildId });
      const readinessGate = assertSandboxReadyForContribution(readiness);
      if (!readinessGate.ok) {
        logBuildActivity(buildId, "contribute_to_hive", readinessGate.message);
        return {
          success: false,
          error: "Sandbox readiness blocked contribution.",
          message: readinessGate.message,
          data: { ...readiness },
        };
      }

      const diff = (build.diffPatch ?? "") as string;
      if (!diff.trim()) return { success: false, error: "No diff available.", message: "Run deploy_feature first to extract the diff." };

      const { buildSandboxStateFromRecord, assertSandboxReadyForPromotion, serializePlanDocument } = await import("@/lib/build/sandbox-state");
      const sandboxState = buildSandboxStateFromRecord({
        buildBranch: build.buildBranch,
        gitCommitHashes: build.gitCommitHashes,
        diffPatch: diff,
        updatedAt: build.updatedAt,
        planDocument: typeof build.buildPlan === "string" ? build.buildPlan : serializePlanDocument(build.buildPlan),
        description: build.description,
        buildExecState: build.buildExecState,
      });
      const promotionGate = assertSandboxReadyForPromotion(sandboxState);
      if (!promotionGate.ok) {
        logBuildActivity(buildId, "contribute_to_hive", promotionGate.message);
        return {
          success: false,
          error: "Sandbox promotion integrity blocked contribution.",
          message: `${promotionGate.message}\n\n${promotionGate.failures.join("\n")}`,
          data: {
            gate: {
              ok: false,
              failures: promotionGate.failures,
            },
            sandbox: promotionGate.state,
          },
        };
      }

      const includeMigrations = params.include_migrations !== false;
      const brief = build.brief as Record<string, unknown> | null;

      // Parse files from diff
      const allFiles = [...diff.matchAll(/^diff --git a\/(.+) b\/.+$/gm)].map((m) => m[1]);
      const migrationFiles = allFiles.filter((f) => f.startsWith("prisma/migrations/"));
      const codeFiles = allFiles.filter((f) => !f.startsWith("prisma/migrations/"));
      const schemaFiles = allFiles.filter((f) => f.includes("schema.prisma"));

      // Build manifest
      const manifest = {
        files: codeFiles,
        migrations: includeMigrations ? migrationFiles : [],
        schemaChanges: schemaFiles,
        totalFiles: includeMigrations ? allFiles.length : codeFiles.length,
        diffLength: diff.length,
        portfolioContext: build.portfolioId,
      };

      // DCO attestation — uses pseudonymous platform identity, not personal info.
      // Real user identity stays in the local DB only; public git metadata
      // shows "dpf-agent-<shortId> <agent-<shortId>@hive.dpf>" so the community
      // can recognize repeat contributors without exposing the real user.
      const { getPlatformIdentity } = await import("@/lib/integrate/identity-privacy");
      const platformId = await getPlatformIdentity();
      const dcoAttestation = platformId.dcoSignoff;

      // FeaturePack is upserted by buildId — NOT create-every-call.
      //
      // Two FeaturePack rows for the same build (with prUrl:null on both) was
      // the observed symptom of contribute_to_hive being invoked twice. The
      // old code created a fresh pack on each call; if the first call's PR
      // creation failed, the pack stayed with prUrl:null. A retry then made
      // a SECOND empty pack — and even when the retry's PR succeeded, its
      // back-write only touched the second pack's manifest. The first pack
      // was orphaned without its URL forever.
      //
      // Reusing the most recent pack for this build means:
      //   - first call: create, get prUrl, back-write the same row.
      //   - retry after failure: update the same row, try PR again; success
      //     back-writes prUrl onto the existing pack (idempotent).
      const existingPack = await prisma.featurePack.findFirst({
        where: { buildId: build.id },
        orderBy: { createdAt: "desc" },
        select: { packId: true },
      });
      const packId = existingPack?.packId ?? `FP-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
      if (existingPack) {
        await prisma.featurePack.update({
          where: { packId },
          data: {
            title: build.title,
            description: String(brief?.description ?? ""),
            portfolioContext: build.portfolioId,
            manifest: { ...manifest, dcoAttestation } as unknown as import("@dpf/db").Prisma.InputJsonValue,
            status: "contributed",
          },
        });
      } else {
        await prisma.featurePack.create({
          data: {
            packId,
            title: build.title,
            description: String(brief?.description ?? ""),
            portfolioContext: build.portfolioId,
            version: "1.0.0",
            manifest: { ...manifest, dcoAttestation } as unknown as import("@dpf/db").Prisma.InputJsonValue,
            buildId: build.id,
            status: "contributed",
          },
        });
      }

      // Create upstream PR via direct branch push (Option B).
      // Anonymous identity pushes dpf/<hash>/<slug> branch directly to the upstream repo.
      // No customer fork needed — the hive token provides write access.
      let prUrl: string | null = null;
      let prError: string | null = null;
      try {
        const upstreamUrl = devConfig?.upstreamRemoteUrl ?? "https://github.com/OpenDigitalProductFactory/opendigitalproductfactory.git";

        // DCO + token already validated up-front (see prerequisite checks
        // earlier in this case); reuse the resolved token so we don't hit
        // the credential store a second time.
        const { generatePrivateBranchName, generateAnonymousCommitMessage } = await import("@/lib/integrate/identity-privacy");
        const hiveToken = hiveTokenEarly;

        {
          const upstreamMatch = upstreamUrl.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
          if (!upstreamMatch) {
            prError = `upstreamRemoteUrl "${upstreamUrl}" is not a recognizable GitHub URL.`;
          } else {
            const { createBranchAndPR } = await import("@/lib/integrate/github-api-commit");

            const branchName = generatePrivateBranchName(platformId.clientId, build.title);
            const commitMessage = generateAnonymousCommitMessage({
              title: build.title,
              buildId,
              productId: null,
              platformIdentity: platformId,
              dcoAcceptedAt: devConfig!.dcoAcceptedAt!,
            });

            const prTitle = `feat: ${build.title}`;
            const { submitBuildAsPR } = await import("@/lib/contribution-pipeline");
            const securityScan = (await import("@/lib/security-scan")).scanDiffForSecurityIssues(diff);
            const prBody = [
              "## Summary",
              "",
              `Build: \`${buildId}\``,
              `Author: ${platformId.authorName} (AI Coworker)`,
              "",
              `**Security Scan:** ${securityScan.passed ? "PASSED" : "FAILED"} (${securityScan.criticalCount} critical, ${securityScan.warningCount} warnings)`,
              "",
              `Files: ${manifest.totalFiles} | Migrations: ${manifest.migrations.length} | Schema changes: ${manifest.schemaChanges.length}`,
              "",
              "---",
              `License: Apache-2.0 (inbound=outbound)`,
              platformId.dcoSignoff,
            ].join("\n");

            const labels = ["ai-contributed", "build-studio"];
            if (!securityScan.passed) labels.push("security-review-needed");

            const prResult = await createBranchAndPR({
              // Phase 3: caller still passes head === base. Phase 4 will switch
              // to head = contributor fork / base = upstream when
              // contributionModel === "fork-pr".
              headOwner: upstreamMatch[1],
              headRepo: upstreamMatch[2],
              baseOwner: upstreamMatch[1],
              baseRepo: upstreamMatch[2],
              branchName,
              commitMessage,
              diff,
              prTitle,
              prBody,
              labels,
              token: hiveToken,
            });

            if (prResult.prUrl) {
              prUrl = prResult.prUrl;
              await prisma.featurePack.update({
                where: { packId },
                data: { manifest: { ...manifest, dcoAttestation, prUrl } as unknown as import("@dpf/db").Prisma.InputJsonValue },
              });

              // Run contribution review pipeline — sanitization, parameterization, vertical tagging
              if (prResult.prNumber) {
                try {
                  const { runContributionReview } = await import("@/lib/integrate/contribution-review");
                  const reviewResult = await runContributionReview({
                    buildId,
                    prUrl: prResult.prUrl!,
                    prNumber: prResult.prNumber,
                    repoOwner: upstreamMatch[1],
                    repoName: upstreamMatch[2],
                    token: hiveToken,
                    diff,
                  });
                  logBuildActivity(buildId, "contribution_review", `Merge readiness: ${reviewResult.mergeReadiness}. Verticals: ${reviewResult.verticals.applicableVerticals.filter((v) => v.relevance !== "unlikely").map((v) => v.category).join(", ") || "none"}`);
                } catch (reviewErr) {
                  console.warn("[contribute_to_hive] contribution review failed:", reviewErr);
                  prError = prError ?? `Contribution review failed: ${reviewErr instanceof Error ? reviewErr.message : String(reviewErr)}`;
                }
              }
            } else {
              prError = `createBranchAndPR returned no prUrl (owner=${upstreamMatch[1]} repo=${upstreamMatch[2]} branch=${branchName}).`;
            }
          }
        }
      } catch (err) {
        prError = err instanceof Error ? err.message : String(err);
        console.warn("[contribute_to_hive] upstream PR creation failed:", err);
      }

      // Update linked ImprovementProposal if exists
      await prisma.improvementProposal.updateMany({
        where: { buildId: build.id, contributionStatus: "local" },
        data: { contributionStatus: "contributed" },
      }).catch(() => {});

      logBuildActivity(
        buildId,
        "contribute_to_hive",
        prUrl
          ? `FeaturePack ${packId} created + PR ${prUrl}. ${manifest.totalFiles} files. DCO: ${dcoAttestation}`
          : `FeaturePack ${packId} created but upstream PR FAILED: ${prError ?? "unknown"}. ${manifest.totalFiles} files. DCO: ${dcoAttestation}`,
      );

      // Fork disposition has changed — ask the reconciler whether the build
      // is ready to advance ship → complete. Success and failure both count
      // as a terminal disposition for the upstream fork (errored is still
      // terminal); the only state that blocks complete is in_progress.
      {
        const { reconcileBuildCompletion } = await import("@/lib/build-flow-state");
        await reconcileBuildCompletion(buildId).catch(() => {});
      }

      if (!prUrl) {
        return {
          success: false,
          error: prError ?? "Upstream PR creation failed.",
          message: `Feature Pack ${packId} was created locally but the upstream pull request could not be opened: ${prError ?? "unknown error"}. Review the token, DCO, and upstream URL and retry.`,
          data: { packId, manifest, dcoAttestation, prUrl: null, prError },
        };
      }

      const prMessage = ` A pull request has been created: ${prUrl}`;
      return {
        success: true,
        message: `Feature Pack ${packId} created and contributed to the Hive Mind. ${manifest.totalFiles} file(s) packaged with DCO attestation.${prMessage} Thank you for contributing!`,
        data: { packId, manifest, dcoAttestation, prUrl },
      };
    }

    case "evaluate_page": {
      const url = typeof params["url"] === "string" ? params["url"] : null;
      const targetUrl = url || (context?.routeContext ? `http://localhost:3000${context.routeContext}` : null);
      if (!targetUrl) return { success: false, error: "No URL to evaluate.", message: "Provide a URL or navigate to a page first." };

      try {
        const BROWSER_USE_URL = process.env.BROWSER_USE_URL || "http://browser-use:8500/mcp";

        // Use browser-use to evaluate the page with AI-powered analysis
        const extractRes = await fetch(BROWSER_USE_URL, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            jsonrpc: "2.0",
            id: 1,
            method: "tools/call",
            params: {
              name: "browse_open",
              arguments: { url: targetUrl },
            },
          }),
          signal: AbortSignal.timeout(60000),
        });
        const openResult = await extractRes.json();
        const openContent = JSON.parse(openResult?.result?.content?.[0]?.text ?? "{}");
        const sessionId = openContent.session_id;
        if (!sessionId) throw new Error("Failed to open browser session");

        // Extract accessibility and UX findings using AI analysis
        const evalRes = await fetch(BROWSER_USE_URL, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            jsonrpc: "2.0",
            id: 2,
            method: "tools/call",
            params: {
              name: "browse_extract",
              arguments: {
                session_id: sessionId,
                query: "Analyze this page for UX and accessibility issues. Check for: missing alt text, low contrast text, missing form labels, heading hierarchy issues, keyboard navigation problems, focus indicators, semantic HTML usage. Return a JSON array of findings, each with: severity (critical/important/minor), category (contrast/accessibility/focus/semantic-html/responsive), element (CSS selector or description), issue (what's wrong), recommendation (how to fix), wcagRef (WCAG guideline reference if applicable).",
              },
            },
          }),
          signal: AbortSignal.timeout(120000),
        });
        const evalResult = await evalRes.json();
        const evalContent = JSON.parse(evalResult?.result?.content?.[0]?.text ?? "{}");

        // Get screenshot
        const ssRes = await fetch(BROWSER_USE_URL, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            jsonrpc: "2.0",
            id: 3,
            method: "tools/call",
            params: {
              name: "browse_screenshot",
              arguments: { session_id: sessionId },
            },
          }),
          signal: AbortSignal.timeout(30000),
        });
        const ssResult = await ssRes.json();
        const ssContent = JSON.parse(ssResult?.result?.content?.[0]?.text ?? "{}");

        // Close session
        await fetch(BROWSER_USE_URL, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            jsonrpc: "2.0",
            id: 4,
            method: "tools/call",
            params: { name: "browse_close", arguments: { session_id: sessionId } },
          }),
          signal: AbortSignal.timeout(10000),
        });

        // Parse findings — the AI extraction returns structured data
        let findings: Array<Record<string, unknown>> = [];
        try {
          const rawData = typeof evalContent.data === "string" ? JSON.parse(evalContent.data) : evalContent.data;
          findings = Array.isArray(rawData) ? rawData : [];
        } catch {
          findings = [];
        }

        return {
          success: true,
          message: `Found ${findings.length} UX/accessibility issues on ${targetUrl}.`,
          data: {
            url: targetUrl,
            screenshot: ssContent.screenshot_base64 ?? null,
            findingCount: findings.length,
            findings,
          },
        };
      } catch (e) {
        return {
          success: false,
          error: e instanceof Error ? e.message : String(e),
          message: "UX verification service (browser-use) is unreachable. Run 'docker compose up -d browser-use' or check the browser-use container logs. You can fall back to code-only analysis using read_project_file.",
        };
      }
    }

    case "run_ux_test": {
      const buildId = await resolveActiveBuildId(userId, extractBuildIdHint(params));
      if (!buildId) return { success: false, error: "No active build.", message: "No active build." };
      const build = await prisma.featureBuild.findUnique({ where: { buildId }, select: { sandboxId: true, sandboxPort: true, brief: true, kind: true } });
      if (!build?.sandboxPort || !build.sandboxId || !build.brief) return { success: false, error: "Sandbox or brief not ready.", message: "Launch sandbox and save brief first." };

      const { deriveFixUxTestCases } = await import("@/lib/explore/feature-build-types");
      const brief = build.brief as {
        acceptanceCriteria?: string[];
        fixContext?: import("@/lib/explore/feature-build-types").FixContext;
      };
      // Explicit `tests` always win. Otherwise, for a fix build derive the
      // assertion from the structured fix diagnosis (defect-gone on its route)
      // rather than the polluted feature acceptanceCriteria. (BI-AC5CFDB0)
      const testCases =
        (params.tests as string[] | undefined) ??
        (build.kind === "fix"
          ? deriveFixUxTestCases(brief.fixContext)
          : brief.acceptanceCriteria ?? []);
      if (testCases.length === 0) return { success: false, error: "No test cases.", message: build.kind === "fix" ? "No fix context (route/expected) or test cases to verify." : "No acceptance criteria or test cases to run." };

      try {
        const BROWSER_USE_URL = process.env.BROWSER_USE_URL || "http://browser-use:8500/mcp";
        // browser-use runs inside the docker compose network — use the
        // internal service URL (http://sandbox:3000), not the host port,
        // so assets and API calls resolve correctly.
        const { resolveSandboxUrl } = await import("@/lib/integrate/sandbox/resolve-sandbox-url");
        const sandboxUrl = resolveSandboxUrl(build.sandboxId, build.sandboxPort).internal;

        const testRes = await fetch(BROWSER_USE_URL, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            jsonrpc: "2.0",
            id: 1,
            method: "tools/call",
            params: {
              name: "browse_run_tests",
              arguments: {
                url: sandboxUrl,
                tests: testCases,
                // Scope per-step screenshots to a build-specific subdir on
                // the shared /evidence volume. Portal serves them through
                // /api/build/<buildId>/evidence/<file>.png.
                evidence_dir: `build_${buildId}`,
              },
            },
          }),
          signal: AbortSignal.timeout(300000), // 5 min for full test suite
        });
        const testResult = await testRes.json();
        const testContent = JSON.parse(testResult?.result?.content?.[0]?.text ?? "{}");

        // Convert to UxTestStep format for storage. screenshot_path (when
        // present) is a filename inside the evidence_dir — turn it into a
        // portal-served URL the ReviewPanel can render.
        const steps = (testContent.results ?? []).map((r: Record<string, unknown>, i: number) => ({
          step: (r.test as string) ?? `Test ${i + 1}`,
          passed: r.status === "pass",
          screenshotUrl: typeof r.screenshot_path === "string"
            ? `/api/build/${encodeURIComponent(buildId)}/evidence/${encodeURIComponent(r.screenshot_path)}`
            : null,
          error: r.status !== "pass" ? ((r.detail as string) ?? null) : null,
        }));

        const { agentEventBus } = await import("@/lib/agent-event-bus");
        for (let i = 0; i < steps.length; i++) {
          if (context?.threadId) {
            agentEventBus.emit(context.threadId, {
              type: "test:step",
              stepIndex: i,
              description: steps[i]!.step,
              passed: steps[i]!.passed,
            });
          }
        }
        const { saveBuildArtifactRevision } = await import("@/lib/build/build-artifact-provenance");
        await saveBuildArtifactRevision({
          buildId,
          field: "uxTestResults",
          savedByAgentId: context?.agentId ?? null,
          savedByUserId: userId,
          threadId: context?.threadId ?? null,
          value: steps,
        });
        if (context?.threadId) agentEventBus.emit(context.threadId, { type: "evidence:update", buildId, field: "uxTestResults" });
        const passed = steps.filter((s: { passed: boolean }) => s.passed).length;
        logBuildActivity(buildId, "run_ux_test", `UX tests: ${passed}/${steps.length} passed (browser-use).`);
        return {
          success: true,
          message: `UX tests: ${passed}/${steps.length} passed.`,
          data: { buildId, steps, browserUseResults: testContent },
        };
      } catch (err) {
        const msg = (err as Error).message?.slice(0, 200) ?? "Unknown error";
        return { success: false, error: `UX test run failed: ${msg}`, message: `UX verification service (browser-use) is unreachable. Run 'docker compose up -d browser-use' or check the browser-use container logs. You can skip UX tests and proceed with the review if you have to.` };
      }
    }

    case "list_project_directory": {
      const { listProjectDirectory } = await import("./integrate/codebase-tools");
      const result = await listProjectDirectory(String(params.path ?? "."));
      if ("error" in result) return { success: false, error: result.error, message: result.error };
      const summary = result.entries.map((e) => `${e.type === "dir" ? "[dir]" : "     "} ${e.path}`).join("\n");
      return { success: true, message: summary || "Empty directory", data: { entries: result.entries } };
    }

    case "read_project_file": {
      const { readProjectFile } = await import("./integrate/codebase-tools");
      const opts: { startLine?: number; endLine?: number } = {};
      if (typeof params.startLine === "number") opts.startLine = params.startLine;
      if (typeof params.endLine === "number") opts.endLine = params.endLine;
      const result = await readProjectFile(String(params.path ?? ""), opts);
      if ("error" in result) return { success: false, error: result.error, message: result.error };
      return { success: true, message: result.content, data: { content: result.content } };
    }

    case "start_ideate_research": {
      // This tool is a signal — the actual research dispatch happens in
      // agent-coworker.ts after the agentic loop returns. We just persist
      // the user context so the dispatch knows what to research.
      const scope = String(params.reusabilityScope ?? "parameterizable");
      const userCtx = String(params.userContext ?? "");

      // BI-F4A30FCB (Dale dogfood 2026-05-24): resolve the target build
      // from agent context first. The previous "findFirst by phase=ideate
      // ordered by updatedAt" silently mis-targeted whenever multiple
      // builds were in ideate concurrently — the user's request landed on
      // an unrelated build whose updatedAt happened to be newer.
      const activeBuild = await resolveIdeateBuildForTool({
        contextBuildId: context?.featureBuildId,
        toolName: "start_ideate_research",
      });
      if (!activeBuild.build) {
        return activeBuild.refusal;
      }

      await prisma.featureBuild.update({
        where: { buildId: activeBuild.build.buildId },
        data: {
          buildExecState: {
            ideateResearchRequested: true,
            reusabilityScope: scope,
            userContext: userCtx,
            requestedAt: new Date().toISOString(),
          },
        },
      });

      return {
        success: true,
        message: "Research started. Searching the codebase and drafting the design document — this takes about 1-2 minutes. Tell the user you're researching now. IMPORTANT: Do NOT call saveBuildEvidence with field 'designDoc' — the research system saves the design document and runs the review automatically when research completes. Just wait and tell the user.",
        data: { reusabilityScope: scope, userContext: userCtx, buildId: activeBuild.build.buildId },
      };
    }

    case "start_scout_research": {
      // Scout dispatch: similar to ideate research, but runs a fast parallel search + URL fetch
      const externalUrls = (params.externalUrls as string[] | undefined) ?? [];

      // BI-F4A30FCB (Dale dogfood 2026-05-24): resolve the target build
      // from agent context first; see start_ideate_research comment above.
      const resolved = await resolveIdeateBuildForTool({
        contextBuildId: context?.featureBuildId,
        toolName: "start_scout_research",
      });
      if (!resolved.build) {
        return resolved.refusal;
      }
      const activeBuild = await prisma.featureBuild.findUnique({
        where: { buildId: resolved.build.buildId },
        select: { buildId: true, buildExecState: true, scoutFindings: true },
      });
      if (!activeBuild) {
        return { success: false, message: "Active ideate build vanished between resolve and read — try again." };
      }

      const current = activeBuild.buildExecState as Record<string, unknown> | null;

      // Idempotency: if scout has already delivered findings, do NOT re-run.
      // The agentic loop otherwise re-calls this tool every iteration because
      // the initial response says "results will appear on the next turn" and
      // the model doesn't see the findings in its prompt context. The stuck-
      // detector eventually bails, but not before burning 4-5 iterations and
      // preventing phase advance. Tell the agent plainly the work is done.
      if (activeBuild.scoutFindings !== null && activeBuild.scoutFindings !== undefined) {
        return {
          success: true,
          message:
            "Scout already ran for this build. Findings are saved to Build Studio Context — proceed with ideate using the existing scout results; do NOT call start_scout_research again.",
          data: { alreadyComplete: true },
        };
      }

      // Idempotency: if a scout request is already pending dispatch (flag set
      // by a prior call but not yet cleared by the coworker post-turn hook),
      // don't stack up another request. Same guidance.
      if (current?.scoutResearchRequested === true) {
        return {
          success: true,
          message:
            "Scout already requested and is running now. Wait for the next turn to see findings — do NOT call start_scout_research again.",
          data: { alreadyRequested: true },
        };
      }

      await prisma.featureBuild.update({
        where: { buildId: activeBuild.buildId },
        data: {
          buildExecState: {
            ...(current ?? {}),
            scoutResearchRequested: true,
            scoutUrls: externalUrls,
            scoutRequestedAt: new Date().toISOString(),
          },
        },
      });

      return {
        success: true,
        message: "Scout started. Codebase search and URL parsing running in background — takes about 30 seconds. Results will appear in your Build Studio Context on the next turn. Do NOT call start_scout_research again; wait for the results.",
        data: { urlCount: externalUrls.length },
      };
    }

    case "search_project_files": {
      const { searchProjectFiles } = await import("./integrate/codebase-tools");
      let query = String(params.query ?? "");
      const opts: { glob?: string; maxResults?: number } = {};

      // Auto-fix: model often combines query and glob into one string
      // e.g. "registration:**/*.prisma" or "voucher:*.ts"
      const colonGlobMatch = query.match(/^([^:]+):(\*\*?\/.+|\*\.[a-z]+)$/);
      if (colonGlobMatch) {
        query = colonGlobMatch[1]!.trim();
        opts.glob = colonGlobMatch[2]!.trim();
        console.log(`[search_project_files] Auto-split combined query: ${JSON.stringify(params.query)} → query=${JSON.stringify(query)} glob=${JSON.stringify(opts.glob)}`);
      }

      if (typeof params.glob === "string" && !opts.glob) opts.glob = params.glob;
      if (typeof params.maxResults === "number") opts.maxResults = params.maxResults;
      const result = await searchProjectFiles(query, opts);
      if ("error" in result) return { success: false, error: result.error, message: result.error };
      const summary = result.results.map((r) => `${r.path}:${r.line}: ${r.text}`).join("\n");
      if (!summary) {
        return {
          success: true,
          message: `No matches found for "${params.query}"${params.glob ? ` in ${params.glob} files` : ""}. ${params.glob ? "Try searching without a glob filter, or try a different query term. " : ""}If this is a new feature with no existing code, proceed with saveBuildEvidence to save your design — do NOT search again with the same query.`,
          data: { results: [] },
        };
      }
      return { success: true, message: summary, data: { results: result.results } };
    }

    case "query_version_history": {
      const limit = typeof params.limit === "number" ? Math.min(params.limit, 50) : 20;
      const where = typeof params.digitalProductId === "string"
        ? { digitalProductId: params.digitalProductId }
        : {};

      const versions = await prisma.productVersion.findMany({
        where,
        orderBy: { shippedAt: "desc" },
        take: limit,
        include: {
          digitalProduct: { select: { productId: true, name: true } },
          promotions: { orderBy: { createdAt: "desc" }, take: 1, select: { status: true, promotionId: true } },
        },
      });

      const rows = versions.map((v) => ({
        product: v.digitalProduct?.name ?? "unknown",
        productId: v.digitalProduct?.productId ?? "unknown",
        version: v.version,
        gitTag: v.gitTag,
        shippedAt: v.shippedAt.toISOString(),
        changeCount: v.changeCount,
        changeSummary: v.changeSummary ?? "",
        promotionStatus: v.promotions[0]?.status ?? "none",
        promotionId: v.promotions[0]?.promotionId ?? null,
      }));

      const summary = rows.map((r) =>
        `${r.product} ${r.version} (${r.gitTag}) — ${r.promotionStatus} — shipped ${r.shippedAt.slice(0, 10)}`
      ).join("\n");

      return {
        success: true,
        message: summary || "No versions found.",
        data: { versions: rows },
      };
    }

    // ─── Design Intelligence Tools (UI UX Pro Max) ──────────────────────────
    case "search_design_intelligence": {
      const { searchDesignDomain, formatSearchResults } = await import("@/lib/design-intelligence");
      const query = String(params.query ?? "");
      const domain = String(params.domain ?? "style") as import("@/lib/design-intelligence").DesignDomain;
      const maxResults = Number(params.max_results ?? 5);
      if (!query) return { success: false, error: "Query is required.", message: "Provide search keywords." };
      const results = searchDesignDomain(query, domain, maxResults);
      const formatted = formatSearchResults(results, query, domain);
      return { success: true, message: formatted };
    }

    case "generate_design_system": {
      const { generateDesignSystem } = await import("@/lib/design-intelligence");
      const query = String(params.query ?? "");
      const projectName = params.project_name ? String(params.project_name) : undefined;
      if (!query) return { success: false, error: "Query is required.", message: "Provide product description and keywords." };
      const designSystem = generateDesignSystem(query, projectName);
      return { success: true, message: designSystem };
    }

    case "generate_codebase_manifest": {
      const { isDevInstance } = await import("./integrate/codebase-tools");
      if (!isDevInstance()) return { success: false, error: "Manifest generation is only available on dev instances.", message: "Dev-only tool." };

      const { generateManifest } = await import("@/lib/manifest-generator");
      const { getCurrentCommitHash } = await import("@/lib/git-utils");

      const gitRef = await getCurrentCommitHash() ?? "unknown";
      const version = typeof params.version === "string" ? params.version : "dev";

      const manifest = await generateManifest({ version, gitRef, writeFile: true });

      // Store in DB (best-effort) — delete+create to avoid nullable composite key issues
      try {
        await prisma.codebaseManifest.deleteMany({
          where: { version, digitalProductId: null },
        });
        await prisma.codebaseManifest.create({
          data: { version, gitRef, manifest: manifest as unknown as import("@dpf/db").Prisma.InputJsonValue },
        });
      } catch (err) {
        console.warn("[generate_codebase_manifest] DB store failed:", err);
      }

      return {
        success: true,
        message: `Manifest generated for version "${version}" with ${manifest.statistics.totalFiles} files, ${manifest.statistics.dataModelCount} models, ${manifest.statistics.externalDependencyCount} dependencies.`,
        data: { manifest },
      };
    }

    case "read_codebase_manifest": {
      const version = typeof params.version === "string" ? params.version : undefined;

      // Try DB first
      const dbManifest = await prisma.codebaseManifest.findFirst({
        where: version ? { version } : {},
        orderBy: { generatedAt: "desc" },
        select: { version: true, gitRef: true, manifest: true, generatedAt: true },
      });

      if (dbManifest) {
        return {
          success: true,
          message: `Manifest for version "${dbManifest.version}" (generated ${dbManifest.generatedAt.toISOString().slice(0, 10)})`,
          data: { manifest: dbManifest.manifest, version: dbManifest.version, gitRef: dbManifest.gitRef },
        };
      }

      // Fall back to reading the file (dev instances only)
      const { isDevInstance, readProjectFile } = await import("./integrate/codebase-tools");
      if (isDevInstance()) {
        const result = await readProjectFile("codebase-manifest.json");
        if ("content" in result) {
          try {
            const manifest = JSON.parse(result.content);
            return { success: true, message: "Manifest loaded from file.", data: { manifest } };
          } catch { /* fall through */ }
        }
      }

      return { success: false, error: "No manifest found. Use generate_codebase_manifest to create one.", message: "No manifest available." };
    }

    case "read_source_at_version": {
      const { gitShow, isGitAvailable } = await import("@/lib/git-utils");
      if (!await isGitAvailable()) return { success: false, error: "Git history is not available in this deployment. Use read_codebase_manifest for codebase orientation.", message: "Git not available." };
      const ref = typeof params.version === "string" ? params.version : (process.env.DEPLOYED_VERSION ?? "HEAD");
      const result = await gitShow({ ref, path: String(params.path ?? "") });
      if ("error" in result) return { success: false, error: result.error, message: result.error };
      return { success: true, message: result.content, data: { content: result.content } };
    }

    case "search_source_at_version": {
      const { gitGrep, isGitAvailable } = await import("@/lib/git-utils");
      if (!await isGitAvailable()) return { success: false, error: "Git history is not available.", message: "Git not available." };
      const ref = typeof params.version === "string" ? params.version : (process.env.DEPLOYED_VERSION ?? "HEAD");
      const grepOpts: Parameters<typeof gitGrep>[0] = { query: String(params.query ?? ""), ref };
      if (typeof params.glob === "string") grepOpts.glob = params.glob;
      if (typeof params.maxResults === "number") grepOpts.maxResults = params.maxResults;
      const result = await gitGrep(grepOpts);
      const summary = result.results.map((r) => `${r.path}:${r.line}: ${r.text}`).join("\n");
      return { success: true, message: summary || "No matches found.", data: { results: result.results } };
    }

    case "list_source_directory": {
      const { gitLsTree, isGitAvailable } = await import("@/lib/git-utils");
      if (!await isGitAvailable()) return { success: false, error: "Git history is not available.", message: "Git not available." };
      const ref = typeof params.version === "string" ? params.version : (process.env.DEPLOYED_VERSION ?? "HEAD");
      const result = await gitLsTree({ ref, path: typeof params.path === "string" ? params.path : "" });
      const summary = result.entries.map((e) => `${e.type === "dir" ? "📁" : "📄"} ${e.path}`).join("\n");
      return { success: true, message: summary || "Empty directory.", data: { entries: result.entries } };
    }

    case "compare_versions": {
      const { gitDiffStat, isGitAvailable, gitLog } = await import("@/lib/git-utils");
      if (!await isGitAvailable()) return { success: false, error: "Git history is not available.", message: "Git not available." };
      const from = String(params.from ?? "");
      const to = typeof params.to === "string" ? params.to : "HEAD";
      const diff = await gitDiffStat({ from, to });
      const log = await gitLog({ from, to, maxCount: 20 });
      return {
        success: true,
        message: diff.summary,
        data: { filesChanged: diff.filesChanged, summary: diff.summary, commits: log.commits },
      };
    }

    case "propose_file_change": {
      const { readProjectFile, writeProjectFile, generateSimpleDiff } = await import("./integrate/codebase-tools");
      const path = String(params.path ?? "");
      const newContent = String(params.newContent ?? "");
      const description = String(params.description ?? "");

      const current = await readProjectFile(path);
      const currentContent = "content" in current ? current.content : "";
      const diff = generateSimpleDiff(currentContent, newContent, path);

      const writeResult = await writeProjectFile(path, newContent);
      if ("error" in writeResult) return { success: false, error: writeResult.error, message: writeResult.error };

      // Auto-commit the approved change
      let commitHash: string | undefined;
      try {
        const { commitFile, formatCommitMessage, isGitAvailable } = await import("@/lib/git-utils");
        if (await isGitAvailable()) {
          // Resolve buildId from thread context (best-effort)
          let buildId: string | undefined;
          if (context?.threadId) {
            const build = await prisma.featureBuild.findFirst({
              where: { threadId: context.threadId, phase: { in: ["build", "review"] } },
              select: { buildId: true, id: true },
            });
            if (build) buildId = build.buildId;
          }

          const message = formatCommitMessage({ description, filePath: path, ...(buildId ? { buildId } : {}), approvedBy: userId });
          const result = await commitFile({ filePath: path, message });

          if ("hash" in result) {
            commitHash = result.hash;

            // Update AgentActionProposal with commit hash (best-effort)
            if (context?.threadId) {
              await prisma.agentActionProposal.updateMany({
                where: { threadId: context.threadId, actionType: "propose_file_change", status: "approved", gitCommitHash: null },
                data: { gitCommitHash: commitHash },
              }).catch(() => {});
            }

            // Append commit hash to FeatureBuild (best-effort)
            if (buildId) {
              await prisma.featureBuild.update({
                where: { buildId },
                data: { gitCommitHashes: { push: commitHash } },
              }).catch(() => {});
            }
          } else {
            console.warn("[propose_file_change] git commit failed: %s", JSON.stringify(result.error));
          }
        }
      } catch (err) {
        console.warn("[propose_file_change] auto-commit error:", err);
      }

      return {
        success: true,
        entityId: path,
        message: commitHash ? `Applied and committed: ${path}` : `Applied change to ${path}`,
        data: { path, diff, description, ...(commitHash ? { commitHash } : {}) },
      };
    }

    case "propose_improvement": {
      const proposalId = `IP-${crypto.randomUUID().slice(0, 5).toUpperCase()}`;

      // Capture conversation excerpt (last 5 messages) for evidence
      let conversationExcerpt: string | null = null;
      if (context?.threadId) {
        const recentMessages = await prisma.agentMessage.findMany({
          where: { threadId: context.threadId },
          orderBy: { createdAt: "desc" },
          take: 5,
          select: { role: true, content: true },
        });
        if (recentMessages.length > 0) {
          conversationExcerpt = recentMessages
            .reverse()
            .map((m) => `[${m.role}] ${m.content?.slice(0, 200)}`)
            .join("\n");
        }
      }

      const proposal = await prisma.improvementProposal.create({
        data: {
          proposalId,
          title: String(params["title"] ?? "Untitled improvement"),
          description: String(params["description"] ?? ""),
          category: String(params["category"] ?? "missing_feature"),
          severity: String(params["severity"] ?? "medium"),
          observedFriction: typeof params["observedFriction"] === "string" ? params["observedFriction"] : null,
          conversationExcerpt,
          submittedById: userId,
          agentId: context?.agentId ?? "unknown",
          routeContext: context?.routeContext ?? "unknown",
          threadId: context?.threadId ?? null,
        },
      });
      return {
        success: true,
        entityId: proposal.proposalId,
        message: `Improvement proposal ${proposal.proposalId} created: "${proposal.title}". It will be reviewed by a manager.`,
      };
      // Index in platform knowledge
      import("@/lib/semantic-memory").then(({ storePlatformKnowledge }) =>
        storePlatformKnowledge({
          entityId: proposal.proposalId,
          entityType: "improvement",
          title: proposal.title,
          content: String(params["description"] ?? ""),
        })
      ).catch(() => {});
    }

    case "propose_skill_improvement": {
      const skillId = String(params["skillId"] ?? "").trim();
      const proposedContent = String(params["proposedContent"] ?? "").trim();
      const title = String(params["title"] ?? "").trim();
      const description = String(params["description"] ?? "").trim();
      if (!skillId || !proposedContent || !title || !description) {
        return {
          success: false,
          error: "Missing required fields",
          message:
            "propose_skill_improvement requires skillId, title, description, and proposedContent.",
        };
      }
      const sev = String(params["severity"] ?? "medium");
      const severity = (["low", "medium", "high", "critical"].includes(sev) ? sev : "medium") as
        | "low"
        | "medium"
        | "high"
        | "critical";
      const observedFriction =
        typeof params["observedFriction"] === "string" ? params["observedFriction"] : null;
      try {
        const { submitSkillImprovementProposal } = await import("@/lib/skills/proposals");
        const result = await submitSkillImprovementProposal({
          skillId,
          proposedContent,
          title,
          description,
          severity,
          submittedById: userId,
          agentId: context?.agentId ?? "unknown",
          routeContext: context?.routeContext ?? "unknown",
          threadId: context?.threadId ?? null,
          observedFriction,
        });
        return {
          success: true,
          entityId: result.proposalId,
          message: `Skill proposal ${result.proposalId} created for ${skillId}. A reviewer must approve before it takes effect.`,
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : "Unknown error";
        return {
          success: false,
          error: msg,
          message: `Could not create skill proposal: ${msg}`,
        };
      }
    }

    case "add_provider": {
      const providerId = String(params["providerId"] ?? "").toLowerCase().replace(/[^a-z0-9_-]/g, "");
      if (!providerId) return { success: false, error: "Invalid provider ID", message: "Provider ID is required" };

      const existing = await prisma.modelProvider.findUnique({ where: { providerId } });
      if (existing) return { success: false, error: "Already exists", message: `Provider "${providerId}" already exists` };

      const provider = await prisma.modelProvider.create({
        data: {
          providerId,
          name: String(params["name"] ?? providerId),
          category: String(params["category"] ?? "direct"),
          costModel: String(params["costModel"] ?? "token"),
          families: [],
          enabledFamilies: [],
          status: "unconfigured",
          authMethod: String(params["authMethod"] ?? "api_key"),
          supportedAuthMethods: [String(params["authMethod"] ?? "api_key")],
          ...(typeof params["baseUrl"] === "string" ? { baseUrl: params["baseUrl"] } : {}),
        },
      });
      return {
        success: true,
        entityId: provider.providerId,
        message: `Provider "${provider.name}" added. Visit AI Providers to configure it.`,
      };
    }

    case "update_provider_category": {
      const providerId = String(params["providerId"] ?? "");
      const category = String(params["category"] ?? "");
      if (!providerId || !category) return { success: false, error: "Missing fields", message: "Provider ID and category are required" };

      const provider = await prisma.modelProvider.findUnique({ where: { providerId } });
      if (!provider) return { success: false, error: "Not found", message: `Provider "${providerId}" not found` };

      await prisma.modelProvider.update({
        where: { providerId },
        data: { category },
      });
      return {
        success: true,
        entityId: providerId,
        message: `Provider "${provider.name}" category updated to "${category}".`,
      };
    }

    case "analyze_brand_document": {
      const { fileName, fileType } = params as { fileName: string; fileContent: string; fileType: string };
      return {
        success: true,
        message: `Analyzing brand document: ${fileName} (${fileType})`,
        data: {
          companyName: null,
          logoDataUrl: null,
          colors: [],
          fonts: [],
          notes: `Document "${fileName}" received for brand analysis. The AI agent should analyze the base64 content to extract brand assets.`,
        },
      };
    }

    case "query_employees": {
      const searchTerm = typeof params["search"] === "string" ? params["search"].trim() : undefined;
      const deptFilter = typeof params["department"] === "string" ? params["department"].trim() : undefined;
      const statusFilter = typeof params["status"] === "string" ? params["status"] : undefined;
      const resultLimit = typeof params["limit"] === "number" ? Math.min(params["limit"], 50) : 20;

      // Resolve department filter to an ID
      let deptId: string | undefined;
      if (deptFilter) {
        const dept = await prisma.department.findFirst({
          where: {
            OR: [
              { id: deptFilter },
              { departmentId: deptFilter },
              { name: { contains: deptFilter, mode: "insensitive" } },
            ],
          },
          select: { id: true },
        });
        deptId = dept?.id;
      }

      const employees = await prisma.employeeProfile.findMany({
        where: {
          ...(searchTerm ? {
            OR: [
              { displayName: { contains: searchTerm, mode: "insensitive" } },
              { workEmail: { contains: searchTerm, mode: "insensitive" } },
            ],
          } : {}),
          ...(deptId ? { departmentId: deptId } : {}),
          ...(statusFilter ? { status: statusFilter } : {}),
        },
        select: {
          employeeId: true,
          displayName: true,
          workEmail: true,
          status: true,
          department: { select: { name: true } },
          position: { select: { title: true } },
        },
        orderBy: { displayName: "asc" },
        take: resultLimit,
      });

      if (employees.length === 0) {
        return { success: true, message: "No employees found matching your criteria.", data: { employees: [] } };
      }

      const list = employees.map((e) =>
        `${e.displayName} (${e.employeeId}) — ${e.department?.name ?? "No dept"}, ${e.position?.title ?? "No position"}, ${e.status}${e.workEmail ? ` <${e.workEmail}>` : ""}`
      ).join("\n");

      return {
        success: true,
        message: `${employees.length} employee${employees.length !== 1 ? "s" : ""} found:\n${list}`,
        data: {
          employees: employees.map((e) => ({
            employeeId: e.employeeId,
            displayName: e.displayName,
            workEmail: e.workEmail ?? null,
            status: e.status,
            department: e.department?.name ?? null,
            position: e.position?.title ?? null,
          })),
        },
      };
    }

    case "list_departments": {
      const departments = await prisma.department.findMany({
        where: { status: "active" },
        select: { departmentId: true, name: true },
        orderBy: { name: "asc" },
      });
      if (departments.length === 0) {
        return { success: true, message: "No departments have been set up yet.", data: { departments: [] } };
      }
      const list = departments.map((d) => `${d.name} (${d.departmentId})`).join("\n");
      return {
        success: true,
        message: `${departments.length} active department${departments.length !== 1 ? "s" : ""}:\n${list}`,
        data: { departments: departments.map((d) => ({ id: d.departmentId, name: d.name })) },
      };
    }

    case "list_positions": {
      const positions = await prisma.position.findMany({
        where: { status: "active" },
        select: { positionId: true, title: true, jobFamily: true },
        orderBy: { title: "asc" },
      });
      if (positions.length === 0) {
        return { success: true, message: "No positions have been set up yet.", data: { positions: [] } };
      }
      const list = positions.map((p) => `${p.title}${p.jobFamily ? ` — ${p.jobFamily}` : ""} (${p.positionId})`).join("\n");
      return {
        success: true,
        message: `${positions.length} active position${positions.length !== 1 ? "s" : ""}:\n${list}`,
        data: { positions: positions.map((p) => ({ id: p.positionId, title: p.title, jobFamily: p.jobFamily ?? null })) },
      };
    }

    case "create_employee": {
      // Email uniqueness check
      if (typeof params["workEmail"] === "string") {
        const existing = await prisma.employeeProfile.findFirst({
          where: { workEmail: params["workEmail"] },
          select: { displayName: true, employeeId: true },
        });
        if (existing) {
          return {
            success: false,
            error: "duplicate_email",
            message: `An employee with email "${params["workEmail"]}" already exists: ${existing.displayName} (${existing.employeeId}).`,
          };
        }
      }

      // Resolve department: match by cuid, departmentId, or name (case-insensitive)
      let resolvedDepartmentId: string | undefined;
      if (typeof params["departmentId"] === "string") {
        const dept = await prisma.department.findFirst({
          where: {
            OR: [
              { id: params["departmentId"] },
              { departmentId: params["departmentId"] },
              { name: { equals: params["departmentId"], mode: "insensitive" } },
            ],
            status: "active",
          },
          select: { id: true },
        });
        resolvedDepartmentId = dept?.id;
      }

      // Resolve position: match by cuid, positionId, or title (case-insensitive)
      let resolvedPositionId: string | undefined;
      if (typeof params["positionId"] === "string") {
        const pos = await prisma.position.findFirst({
          where: {
            OR: [
              { id: params["positionId"] },
              { positionId: params["positionId"] },
              { title: { equals: params["positionId"], mode: "insensitive" } },
            ],
            status: "active",
          },
          select: { id: true },
        });
        resolvedPositionId = pos?.id;
      }

      // Resolve manager: match by cuid, employeeId, displayName, or email
      let resolvedManagerId: string | undefined;
      if (typeof params["managerEmployeeId"] === "string") {
        const mgr = params["managerEmployeeId"] as string;
        const found = await prisma.employeeProfile.findFirst({
          where: { OR: [{ id: mgr }, { employeeId: mgr }, { displayName: mgr }, { workEmail: mgr }] },
          select: { id: true },
        });
        resolvedManagerId = found?.id;
      }

      const employeeId = `EMP-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
      const status = String(params["status"] ?? "offer");
      const eventType = status === "offer" ? "offer_created" : status === "active" ? "hired" : "onboarding_started";

      const employee = await prisma.employeeProfile.create({
        data: {
          employeeId,
          firstName: String(params["firstName"] ?? ""),
          lastName: String(params["lastName"] ?? ""),
          displayName: `${String(params["firstName"] ?? "")} ${String(params["lastName"] ?? "")}`.trim(),
          workEmail: typeof params["workEmail"] === "string" ? params["workEmail"] : undefined,
          status,
          ...(resolvedDepartmentId ? { departmentId: resolvedDepartmentId } : {}),
          ...(resolvedPositionId ? { positionId: resolvedPositionId } : {}),
          ...(resolvedManagerId ? { managerEmployeeId: resolvedManagerId } : {}),
          ...(typeof params["startDate"] === "string" ? { startDate: new Date(params["startDate"]) } : {}),
          employmentEvents: {
            create: {
              eventId: `EVT-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
              eventType,
              effectiveAt: typeof params["startDate"] === "string" ? new Date(params["startDate"]) : new Date(),
              reason: "Created via AI co-worker",
              actorUserId: userId,
            },
          },
        },
      });
      return {
        success: true,
        entityId: employee.employeeId,
        message: `Employee ${employee.displayName} (${employee.employeeId}) created with status "${status}".`,
      };
    }

    case "transition_employee_status": {
      const employee = await prisma.employeeProfile.findUnique({
        where: { employeeId: String(params["employeeId"]) },
      });
      if (!employee) return { success: false, error: "Employee not found", message: `Employee ${String(params["employeeId"])} not found` };

      const newStatus = String(params["newStatus"]);
      const { validateLifecycleTransition } = await import("@/lib/workforce-types");
      const error = validateLifecycleTransition({
        currentStatus: employee.status as import("@/lib/workforce-types").WorkforceStatus,
        nextStatus: newStatus as import("@/lib/workforce-types").WorkforceStatus,
        eventType: "activated",
        terminationDate: newStatus === "inactive" ? new Date() : null,
      });
      if (error) return { success: false, error, message: error };

      const eventMap: Record<string, string> = {
        onboarding: employee.status === "offer" ? "offer_accepted" : "onboarding_started",
        active: employee.status === "onboarding" ? "onboarding_completed" : "activated",
        leave: "leave_started",
        suspended: "suspended",
        offboarding: "offboarding_started",
        inactive: employee.status === "offboarding" ? "offboarding_completed" : "terminated",
      };

      await prisma.$transaction([
        prisma.employeeProfile.update({
          where: { employeeId: String(params["employeeId"]) },
          data: { status: newStatus },
        }),
        prisma.employmentEvent.create({
          data: {
            eventId: `EVT-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
            employeeProfileId: employee.id,
            eventType: eventMap[newStatus] ?? "activated",
            effectiveAt: new Date(),
            reason: typeof params["reason"] === "string" ? params["reason"] : null,
            actorUserId: userId,
          },
        }),
      ]);

      return {
        success: true,
        entityId: employee.employeeId,
        message: `${employee.displayName} transitioned from "${employee.status}" to "${newStatus}".`,
      };
    }

    case "propose_leave_policy": {
      const policies = params["policies"] as Array<{ leaveType: string; name: string; annualAllocation: number; carryoverLimit?: number }> | undefined;
      if (!policies || !Array.isArray(policies)) return { success: false, error: "No policies provided", message: "Provide an array of policy suggestions" };

      let created = 0;
      for (const p of policies) {
        const policyId = `LP-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
        await prisma.leavePolicy.create({
          data: {
            policyId,
            leaveType: p.leaveType,
            name: p.name,
            annualAllocation: p.annualAllocation,
            carryoverLimit: p.carryoverLimit ?? null,
            isDefault: true,
          },
        });
        created++;
      }
      return {
        success: true,
        message: `Created ${created} leave ${created !== 1 ? "policies" : "policy"} for ${String(params["locationContext"])}.`,
      };
    }

    case "submit_feedback": {
      const fromProfile = await prisma.employeeProfile.findUnique({
        where: { userId },
        select: { id: true },
      });
      if (!fromProfile) return { success: false, error: "Your employee profile not found", message: "Cannot submit feedback without an employee profile" };

      const feedbackId = `FB-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
      await prisma.feedbackNote.create({
        data: {
          feedbackId,
          fromEmployeeId: fromProfile.id,
          toEmployeeId: String(params["toEmployeeId"]),
          content: String(params["content"]),
          feedbackType: String(params["feedbackType"] ?? "observation"),
          visibility: String(params["visibility"] ?? "private"),
        },
      });
      return {
        success: true,
        entityId: feedbackId,
        message: "Feedback submitted.",
      };
    }

    case "doc_save": {
      const {
        saveManagedDocument,
      } = await import("@/lib/documents/document-store");
      const actorPrincipalId = await resolveDocumentActorPrincipalId(userId, context?.agentId);
      const references = Array.isArray(params["references"])
        ? params["references"].flatMap((entry) => {
            if (!entry || typeof entry !== "object") return [];
            const ref = entry as Record<string, unknown>;
            const refType = optionalString(ref["refType"]);
            if (!refType) return [];
            return [{
              targetDocumentId: optionalString(ref["targetDocumentId"]),
              targetExternalRef: optionalString(ref["targetExternalRef"]),
              refType,
              anchor: optionalString(ref["anchor"]),
            }];
          })
        : [];
      const document = await saveManagedDocument({
        documentId: optionalString(params["documentId"]),
        organizationId: optionalString(params["organizationId"]),
        title: String(params["title"] ?? ""),
        documentKind: String(params["documentKind"] ?? ""),
        contentFormat: String(params["contentFormat"] ?? ""),
        contentText: optionalString(params["contentText"]),
        contentBlobId: optionalString(params["contentBlobId"]),
        contentSha256: optionalString(params["contentSha256"]),
        summary: optionalString(params["summary"]),
        tags: stringArray(params["tags"]),
        references,
        accessScope: optionalString(params["accessScope"]),
        sourceKind: optionalString(params["sourceKind"]),
        ownerPrincipalId: optionalString(params["ownerPrincipalId"]) ?? actorPrincipalId,
        createdByPrincipalId: actorPrincipalId,
        actorPrincipalId,
      });
      const version = (document as { version?: number }).version ?? document.currentVersion?.version ?? 1;
      return {
        success: true,
        entityId: document.documentId,
        message: `Saved document ${document.documentId} v${version}: /workspace/documents/${encodeURIComponent(document.documentId)}.`,
        data: {
          document: document as unknown as Record<string, unknown>,
          route: `/workspace/documents/${encodeURIComponent(document.documentId)}`,
        },
      };
    }

    case "doc_load": {
      const { loadManagedDocument } = await import("@/lib/documents/document-store");
      const document = await loadManagedDocument({
        documentId: String(params["documentId"] ?? ""),
        version: typeof params["version"] === "number" ? params["version"] : null,
      });
      if (!document) return { success: false, message: "Document not found.", error: "Document not found." };
      return {
        success: true,
        entityId: document.documentId,
        message: `Loaded document ${document.documentId}.`,
        data: { document: document as unknown as Record<string, unknown> },
      };
    }

    case "doc_search": {
      const { searchManagedDocuments } = await import("@/lib/documents/document-store");
      const documents = await searchManagedDocuments({
        query: optionalString(params["query"]),
        mode: typeof params["mode"] === "string" ? params["mode"] as "metadata" | "full-text" | "semantic" | "hybrid" : "hybrid",
        currentState: optionalString(params["state"]),
        documentKind: optionalString(params["documentKind"]),
        ownerPrincipalId: optionalString(params["ownerPrincipalId"]),
        organizationId: optionalString(params["organizationId"]),
        tags: stringArray(params["tags"]),
        limit: typeof params["limit"] === "number" ? params["limit"] : undefined,
      });
      const summary = documents.length === 0
        ? "No matching documents found."
        : documents.map((doc) => `${doc.documentId} ${doc.currentState} ${doc.title}`).join("\n");
      return {
        success: true,
        message: summary,
        data: { results: documents as unknown as Record<string, unknown>[] },
      };
    }

    case "doc_link": {
      const { linkManagedDocuments } = await import("@/lib/documents/document-store");
      const actorPrincipalId = await resolveDocumentActorPrincipalId(userId, context?.agentId);
      const result = await linkManagedDocuments({
        sourceDocumentId: String(params["sourceDocumentId"] ?? ""),
        targetDocumentId: optionalString(params["targetDocumentId"]),
        targetExternalRef: optionalString(params["targetExternalRef"]),
        refType: String(params["refType"] ?? ""),
        anchor: optionalString(params["anchor"]),
        actorPrincipalId,
      });
      return {
        success: true,
        entityId: result.referenceId,
        message: `Linked document reference ${result.referenceId}.`,
        data: result,
      };
    }

    case "doc_version_list": {
      const { listManagedDocumentVersions } = await import("@/lib/documents/document-store");
      const versions = await listManagedDocumentVersions(String(params["documentId"] ?? ""));
      return {
        success: true,
        message: versions.length === 0 ? "No versions found." : `Found ${versions.length} document version${versions.length === 1 ? "" : "s"}.`,
        data: { versions: versions as unknown as Record<string, unknown>[] },
      };
    }

    case "doc_state_change": {
      const { changeManagedDocumentState } = await import("@/lib/documents/document-store");
      const actorPrincipalId = await resolveDocumentActorPrincipalId(userId, context?.agentId);
      const document = await changeManagedDocumentState({
        documentId: String(params["documentId"] ?? ""),
        toState: String(params["toState"] ?? ""),
        reason: optionalString(params["reason"]),
        toolExecutionId: optionalString(params["toolExecutionId"]),
        actorPrincipalId,
      });
      return {
        success: true,
        entityId: document.documentId,
        message: `Document ${document.documentId} moved to ${document.currentState}.`,
        data: { document: document as unknown as Record<string, unknown> },
      };
    }

    case "doc_list_references": {
      const { listManagedDocumentReferences } = await import("@/lib/documents/document-store");
      const references = await listManagedDocumentReferences(String(params["documentId"] ?? ""));
      return {
        success: true,
        message: `Document references loaded for ${String(params["documentId"] ?? "")}.`,
        data: references,
      };
    }

    case "wiki_query": {
      const { searchWikiPages } = await import("@/lib/wiki/embeddings");
      const { prisma } = await import("@dpf/db");
      const org = await prisma.organization
        .findFirst({ select: { id: true, wikiRetrievalMode: true } })
        .catch(() => null);
      const organizationId = org?.id ?? null;

      // EP-WIKI-004 retrieval mode: the per-call override wins; otherwise
      // fall back to the org's wikiRetrievalMode setting; otherwise default
      // to vector. Principle filters and PPR aren't compatible (PPR seeds
      // from raw vector search; principle filters need the cosine-side
      // tier/applies-to gating), so if any principle filter is supplied we
      // force vector mode and let the caller know via the data shape.
      const callerMode = typeof params["retrievalMode"] === "string" ? params["retrievalMode"] : undefined;
      const principleFiltersPresent =
        typeof params["tier"] === "string" ||
        typeof params["appliesTo"] === "string" ||
        typeof params["publicOnly"] === "boolean";
      const orgMode = org?.wikiRetrievalMode === "ppr" ? "ppr" : "vector";
      const mode: "vector" | "ppr" = principleFiltersPresent
        ? "vector"
        : callerMode === "ppr" || callerMode === "vector"
        ? callerMode
        : orgMode;

      const limit = typeof params["limit"] === "number" ? params["limit"] : 5;

      if (mode === "ppr") {
        const { searchByPPR } = await import("@/lib/wiki/ppr");
        const results = await searchByPPR({
          query: String(params["query"] ?? ""),
          organizationId,
          limit,
          db: prisma,
        });
        if (results.length === 0) {
          return { success: true, message: "No matching wiki pages found.", data: { results: [], retrievalMode: mode } };
        }
        const summary = results
          .map((r) => {
            const tierFragment =
              r.pageKind === "principle" && r.principleTier ? `, ${r.principleTier}` : "";
            return `${r.slug} (${r.pageKind}, ${r.source}${tierFragment}) — ${r.title} (combined ${Math.round(r.combinedScore * 100)}%, cosine ${Math.round(r.cosineScore * 100)}%, PPR ${r.pprScore.toFixed(4)})`;
          })
          .join("\n");
        return { success: true, message: summary, data: { results, retrievalMode: mode } };
      }

      // Translate the ergonomic public schema fields (tier, appliesTo,
      // publicOnly) into canonical principle filter args. The public MCP
      // surface uses the short names so authors don't have to remember
      // "principleTier" / "principleAppliesTo" / "principlePublic"; the
      // canonical names live at the storage and retrieval layer per the
      // chief-architect review applied to the implementation plan.
      const searchArgs: Parameters<typeof searchWikiPages>[0] = {
        query: String(params["query"] ?? ""),
        organizationId,
        pageKind: typeof params["pageKind"] === "string" ? params["pageKind"] : undefined,
        limit,
      };
      if (typeof params["tier"] === "string") {
        searchArgs.principleTier = params["tier"];
      }
      if (typeof params["appliesTo"] === "string") {
        searchArgs.principleAppliesTo = params["appliesTo"];
      }
      if (typeof params["publicOnly"] === "boolean") {
        searchArgs.principlePublic = params["publicOnly"];
      }
      const results = await searchWikiPages(searchArgs);
      if (results.length === 0) {
        return { success: true, message: "No matching wiki pages found.", data: { results: [], retrievalMode: mode } };
      }
      const summary = results
        .map((r) => {
          const tierFragment =
            r.pageKind === "principle" && r.principleTier
              ? `, ${r.principleTier}`
              : "";
          return `${r.slug} (${r.pageKind}, ${r.source}${tierFragment}) — ${r.title} (${Math.round(r.score * 100)}% match)`;
        })
        .join("\n");
      return { success: true, message: summary, data: { results, retrievalMode: mode } };
    }

    case "principle_decide": {
      // Phase 2 Task 2.7. Pulls in-scope commandments from Postgres (always
      // applied) and relevant core/contextual principles from Qdrant, then
      // runs the pure decide() math. Returns a contribution ledger so
      // callers can render the why, not just the what.
      const validPopulations = new Set([
        "in_platform_coworker",
        "external_coding_agent",
        "human",
      ]);
      const callingPopulation = params["callingPopulation"];
      if (
        typeof callingPopulation !== "string" ||
        !validPopulations.has(callingPopulation)
      ) {
        return {
          success: false,
          message:
            "callingPopulation must be one of: in_platform_coworker, external_coding_agent, human.",
          error: "Invalid callingPopulation",
        };
      }
      const optionsParam = params["options"];
      if (!Array.isArray(optionsParam) || optionsParam.length === 0) {
        return {
          success: false,
          message: "options must be a non-empty array.",
          error: "Empty options",
        };
      }

      const { listPrinciplesByTier, prisma, PRINCIPLE_DECIDE_DEFAULTS } =
        await import("@dpf/db");
      const { PRINCIPLE_RING_SCOPES } = await import(
        "@dpf/db/wiki-taxonomy"
      );
      const { searchWikiPages } = await import("@/lib/wiki/embeddings");
      const { decide } = await import("@/lib/wiki/principle-decide");
      const { principleMatchesRingScope } = await import(
        "@/lib/wiki/calling-ring-map"
      );
      // BI-3C1A6451: server-side embedding for the semantic-fallback path.
      // Used both for principle direction text (Qdrant-sourced principles
      // have empty dimensionVector) and for option descriptions when the
      // caller passes empty features. Pre-fix, both produced alignment=0.
      const { generateEmbedding } = await import("@/lib/inference/embedding");

      // Validate ringScope per the closed taxonomy registry. Unknown values
      // fail fast instead of silently degrading to universal — silent skip
      // on bad input is the failure mode `make-silent-failures-observable`
      // (the kernel commandment promoted in PR #1081) forbids.
      let ringScope: string[] | undefined;
      if (params["ringScope"] !== undefined) {
        if (!Array.isArray(params["ringScope"])) {
          return {
            success: false,
            message:
              "ringScope must be an array of values from PRINCIPLE_RING_SCOPES.",
            error: "Invalid ringScope shape",
          };
        }
        const unknown = (params["ringScope"] as unknown[]).filter(
          (v): v is string =>
            typeof v === "string" &&
            !(PRINCIPLE_RING_SCOPES as readonly string[]).includes(v),
        );
        if (unknown.length > 0) {
          return {
            success: false,
            message: `ringScope contains unknown values: ${unknown.join(", ")}. Allowed: ${PRINCIPLE_RING_SCOPES.join(", ")}.`,
            error: "Invalid ringScope value",
          };
        }
        ringScope = params["ringScope"] as string[];
      }
      const ringScopeActive =
        ringScope !== undefined &&
        ringScope.length > 0 &&
        !ringScope.includes("universal-ring");
      const callingSurface =
        typeof params["callingSurface"] === "string"
          ? params["callingSurface"]
          : null;

      const maxPrinciples =
        typeof params["maxPrinciples"] === "number"
          ? params["maxPrinciples"]
          : PRINCIPLE_DECIDE_DEFAULTS.maxPrinciples;
      const tieMargin =
        typeof params["tieMargin"] === "number"
          ? params["tieMargin"]
          : PRINCIPLE_DECIDE_DEFAULTS.tieMargin;
      const contextualThreshold =
        PRINCIPLE_DECIDE_DEFAULTS.contextualSimilarityThreshold;
      const semanticWarnRatio =
        PRINCIPLE_DECIDE_DEFAULTS.semanticFallbackWarnRatio;

      const org = await prisma.organization
        .findFirst({ select: { id: true } })
        .catch(() => null);
      const organizationId: string | null = org?.id ?? null;

      // 1. Commandments from Postgres (full dimension vector). Always applied.
      let commandments: Array<Record<string, unknown>> = [];
      try {
        commandments = (await listPrinciplesByTier(prisma, {
          tier: "commandment",
          organizationId,
          appliesTo: callingPopulation,
          ringScope,
          limit: 10,
        })) as Array<Record<string, unknown>>;
      } catch (err) {
        console.warn("[principle_decide] commandment Postgres lookup failed:", err);
      }

      const contextQuery = String(params["context"] ?? "");

      // 2. Core from Qdrant — relevance-ranked.
      let core: Array<Record<string, unknown>> = [];
      try {
        core = (await searchWikiPages({
          query: contextQuery,
          organizationId,
          pageKind: "principle",
          principleTier: "core",
          principleAppliesTo: callingPopulation,
          principleRingScope: ringScopeActive ? ringScope : undefined,
          limit: 5,
        })) as Array<Record<string, unknown>>;
      } catch (err) {
        console.warn("[principle_decide] core Qdrant lookup failed:", err);
      }

      // 3. Contextual from Qdrant — relevance-gated.
      let contextual: Array<Record<string, unknown>> = [];
      try {
        contextual = (await searchWikiPages({
          query: contextQuery,
          organizationId,
          pageKind: "principle",
          principleTier: "contextual",
          principleAppliesTo: callingPopulation,
          principleRingScope: ringScopeActive ? ringScope : undefined,
          limit: 5,
          scoreThreshold: contextualThreshold,
        })) as Array<Record<string, unknown>>;
      } catch (err) {
        console.warn("[principle_decide] contextual Qdrant lookup failed:", err);
      }

      // Post-filter (cheap belt-and-suspenders). Mirrors the contract used
      // by recallPrincipleContext: empty principleRingScope passes
      // (backward-compat); universal-ring always passes; otherwise
      // intersection check. Catches any retrieval path that didn't get
      // the ringScope arg threaded through (e.g. narrow test mocks).
      let commandmentsExcluded = 0;
      let coreExcluded = 0;
      let contextualExcluded = 0;
      if (ringScopeActive && ringScope) {
        const before = { c: commandments.length, k: core.length, x: contextual.length };
        commandments = commandments.filter((row) =>
          principleMatchesRingScope(
            (row["principleRingScope"] as string[] | undefined) ?? [],
            ringScope as never,
          ),
        );
        core = core.filter((row) =>
          principleMatchesRingScope(
            (row["principleRingScope"] as string[] | undefined) ?? [],
            ringScope as never,
          ),
        );
        contextual = contextual.filter((row) =>
          principleMatchesRingScope(
            (row["principleRingScope"] as string[] | undefined) ?? [],
            ringScope as never,
          ),
        );
        commandmentsExcluded = before.c - commandments.length;
        coreExcluded = before.k - core.length;
        contextualExcluded = before.x - contextual.length;
      }

      console.info(
        `[principle-recall-trace] ` +
          JSON.stringify({
            callingSurface,
            callingPopulation,
            ringScope: ringScope ?? null,
            ringScopeActive,
            tool: "principle_decide",
            commandmentCount: commandments.length,
            coreCount: core.length,
            contextualCount: contextual.length,
            ringScopeExcluded: {
              commandments: commandmentsExcluded,
              core: coreExcluded,
              contextual: contextualExcluded,
            },
          }),
      );

      const TIER_DEFAULT_WEIGHT: Record<string, number> = {
        commandment: 1.0,
        core: 0.4,
        contextual: 0.1,
      };
      function resolveWeight(tier: string, override: unknown): number {
        if (typeof override === "number") return override;
        return TIER_DEFAULT_WEIGHT[tier] ?? 0;
      }

      // Build DecisionPrinciple[] from the merged set. Postgres rows carry
      // the full dimensionVector for structured alignment; Qdrant hits only
      // carry dimension keys (no signed vector), so they fall back to
      // semantic alignment. For the semantic path to produce non-zero
      // signal, we must embed each candidate's direction text and let
      // decide()'s cosine math do the rest (BI-3C1A6451 — the dead-code
      // defect tracked at apps/web/lib/wiki/principle-decide.ts:117).
      // PG rows carry direction at row.principleDirection; Qdrant hits
      // carry it at hit.contentPreview.
      type CandidateRow = {
        id: string;
        name: string;
        tier: string;
        weight: number;
        dimensionVector: Record<string, number>;
        directionText: string;
      };
      const candidateRows: CandidateRow[] = [
        ...commandments.map((row): CandidateRow => ({
          id: String(row["id"] ?? ""),
          name: String(row["title"] ?? row["slug"] ?? "principle"),
          tier: String(row["principleTier"] ?? "commandment"),
          weight: resolveWeight(
            String(row["principleTier"] ?? "commandment"),
            row["principleWeight"],
          ),
          dimensionVector:
            (row["principleDimensionVector"] as Record<string, number> | null) ??
            {},
          directionText: String(row["principleDirection"] ?? ""),
        })),
        ...[...core, ...contextual].map((hit): CandidateRow => ({
          id: String(hit["pageId"] ?? ""),
          name: String(hit["title"] ?? hit["slug"] ?? "principle"),
          tier: String(hit["principleTier"] ?? "core"),
          weight: resolveWeight(
            String(hit["principleTier"] ?? "core"),
            undefined,
          ),
          dimensionVector: {}, // Qdrant payload omits the signed vector
          directionText: String(hit["contentPreview"] ?? ""),
        })),
      ];

      // For any candidate that will fall back to semantic alignment
      // (empty dimensionVector), embed its direction text server-side.
      // Parallelized to amortize inference round-trips. Skipped for
      // structured-alignment rows since their embedding wouldn't be used.
      const principleEmbeddings = await Promise.all(
        candidateRows.map(async (row): Promise<number[] | undefined> => {
          if (Object.keys(row.dimensionVector).length > 0) return undefined;
          if (!row.directionText) return undefined;
          const e = await generateEmbedding(row.directionText);
          return e ?? undefined;
        }),
      );

      type DecisionPrinciple = Parameters<typeof decide>[1][number];
      const principleList: DecisionPrinciple[] = candidateRows.map(
        (row, i): DecisionPrinciple => ({
          id: row.id,
          name: row.name,
          tier: row.tier,
          weight: row.weight,
          dimensionVector: row.dimensionVector,
          directionEmbedding: principleEmbeddings[i],
        }),
      );

      const cappedPrinciples = principleList.slice(0, maxPrinciples);

      // Mirror treatment for options: when the caller passes empty features
      // and no explicit embedding, embed the description so the semantic
      // path can actually fire. Caller-supplied embeddings (the rare
      // sophisticated path) win. Per BI-3C1A6451 acceptance criterion.
      type DecisionOption = Parameters<typeof decide>[0][number];
      const decisionOptions: DecisionOption[] = await Promise.all(
        optionsParam
          .filter(
            (o): o is Record<string, unknown> =>
              typeof o === "object" && o !== null,
          )
          .map(async (o): Promise<DecisionOption> => {
            const features =
              typeof o["features"] === "object" && o["features"] !== null
                ? (o["features"] as Record<string, number>)
                : {};
            const description = String(o["description"] ?? "");
            let embedding: number[] | undefined;
            if (Array.isArray(o["embedding"])) {
              embedding = (o["embedding"] as unknown[]).map((n) => Number(n));
            } else if (Object.keys(features).length === 0 && description) {
              const e = await generateEmbedding(description);
              if (e) embedding = e;
            }
            return {
              id: String(o["id"] ?? ""),
              description,
              features,
              embedding,
            };
          }),
      );

      const result = decide(decisionOptions, cappedPrinciples, {
        tieMargin,
        semanticFallbackWarnRatio: semanticWarnRatio,
      });

      // BI-E1FB2307: resolve which decision-perspective profile governs this
      // caller (WWMD platform vs WWWD organization) and name it in the response
      // so agents/operators know which kernel weighed in. Additive — does not
      // change scoring yet; Gate-routed scoring + boundary enforcement is the
      // follow-on (C2b). callingPopulation was validated above.
      const { resolveDecisionCallerContext } = await import(
        "@/lib/decision/caller-context"
      );
      const governingProfile = await resolveDecisionCallerContext({
        callingPopulation:
          callingPopulation as "in_platform_coworker" | "external_coding_agent" | "human",
        agentId: context?.agentId ?? null,
      });

      const summary = result.recommendation
        ? `Recommends ${result.recommendation.optionId} (confidence: ${result.recommendation.confidence}, composite ${result.recommendation.composite.toFixed(3)}; governing profile: ${governingProfile.governingProfileKind})`
        : "No applicable principles to evaluate.";

      return {
        success: true,
        message: summary,
        data: {
          recommendation: result.recommendation,
          scores: result.scores,
          flags: result.flags,
          reasoning: result.reasoning,
          appliedPrincipleCount: cappedPrinciples.length,
          governingProfile: {
            profileId: governingProfile.governingProfileId,
            kind: governingProfile.governingProfileKind,
            resolvedVia: governingProfile.resolvedVia,
          },
        },
      };
    }

    case "wiki_lint": {
      const { runWikiLint } = await import("@/lib/wiki/lint");
      const { prisma } = await import("@dpf/db");
      const scopeArg = typeof params["scope"] === "string" ? params["scope"] : "all";

      // Mirrors queue/functions/wiki-lint.ts: read manifest.json directly
      // rather than pulling in the seed module.
      const kernelVersion = await (async () => {
        try {
          const fs = await import("fs/promises");
          const path = await import("path");
          const manifestPath = path.join(process.cwd(), "docs", "founder-kernel", "manifest.json");
          const raw = await fs.readFile(manifestPath, "utf8");
          return (JSON.parse(raw) as { kernelVersion?: string }).kernelVersion ?? "0.0.0";
        } catch {
          return "0.0.0";
        }
      })();

      const org = await prisma.organization
        .findFirst({ select: { id: true } })
        .catch(() => null);

      const runs: Array<Awaited<ReturnType<typeof runWikiLint>>> = [];
      if (scopeArg === "kernel" || scopeArg === "all") {
        runs.push(
          await runWikiLint({
            organizationId: null,
            prisma: prisma as never,
            currentKernelVersion: kernelVersion,
          }),
        );
      }
      if ((scopeArg === "org" || scopeArg === "all") && org) {
        runs.push(
          await runWikiLint({
            organizationId: org.id,
            prisma: prisma as never,
            currentKernelVersion: kernelVersion,
          }),
        );
      }

      if (runs.length === 0) {
        return {
          success: true,
          message: "No scope linted (no organisation found and scope was 'org').",
          data: { runs: [] },
        };
      }

      const summary = runs
        .map((r) => {
          const tag = r.organizationId === null ? "kernel" : `org ${r.organizationId}`;
          return `${tag}: scanned=${r.scannedPageCount} opened=${r.findingsOpened} kept=${r.findingsKept} resolved=${r.findingsResolved}`;
        })
        .join("\n");

      return { success: true, message: summary, data: { runs } };
    }

    case "wiki_ingest": {
      // Phase 2.3b — glue 2.1 source ingest + 2.2 proposal engine +
      // 2.3a commit step into one MCP-callable action. Production
      // inference adapter wraps utilityInfer (pass 1) + routeAndCall
      // (passes 2 + 3); the InferenceCallable injection lets tests
      // stub deterministically.
      const filePath = typeof params["filePath"] === "string" ? params["filePath"] : null;
      if (!filePath) {
        return {
          success: false,
          message: "wiki_ingest requires `filePath`.",
          error: "Missing filePath",
        };
      }
      // CodeQL #62 (js/path-injection): filePath is MCP-supplied (user-
      // controlled). Gate to the allowed source roots before fs is touched.
      const { assertAllowedIngestPath } = await import("@/lib/wiki/ingest");
      let safeFilePath: string;
      try {
        safeFilePath = assertAllowedIngestPath(filePath);
      } catch (err) {
        return {
          success: false,
          message: err instanceof Error ? err.message : "filePath rejected",
          error: "path_not_allowed",
        };
      }
      const mode = typeof params["mode"] === "string" && params["mode"] === "commit" ? "commit" : "propose";
      const sourceKey = typeof params["sourceKey"] === "string" ? params["sourceKey"] : undefined;
      const sourceType = typeof params["sourceType"] === "string" ? params["sourceType"] : undefined;
      const acceptThreshold =
        typeof params["acceptThreshold"] === "number" ? params["acceptThreshold"] : undefined;
      const maxBodyDeltaRatio =
        typeof params["maxBodyDeltaRatio"] === "number" ? params["maxBodyDeltaRatio"] : undefined;

      const { prisma } = await import("@dpf/db");
      const orgIdParam = typeof params["organizationId"] === "string" ? params["organizationId"] : null;
      let organizationId: string | null = orgIdParam;
      if (!organizationId) {
        const currentOrg = await prisma.organization
          .findFirst({ select: { id: true } })
          .catch(() => null);
        organizationId = currentOrg?.id ?? null;
      }
      if (mode === "commit" && !organizationId) {
        return {
          success: false,
          message:
            "wiki_ingest mode=commit requires an organizationId — kernel pages are PR-only (spec §4). Run in mode=propose instead, or supply an organizationId.",
          error: "Kernel write refused",
        };
      }

      const { runIngestPipeline, summarizePipelineResult } = await import(
        "@/lib/wiki/ingest-pipeline"
      );
      const { createProductionInference } = await import(
        "@/lib/wiki/inference-adapter"
      );

      // Pull kernel version from the manifest for the audit row.
      const kernelVersion = await (async () => {
        try {
          const fs = await import("fs/promises");
          const path = await import("path");
          const manifestPath = path.join(
            process.cwd(),
            "docs",
            "founder-kernel",
            "manifest.json",
          );
          const raw = await fs.readFile(manifestPath, "utf8");
          return (JSON.parse(raw) as { kernelVersion?: string }).kernelVersion ?? null;
        } catch {
          return null;
        }
      })();

      // sourceType must be one of the registry values when supplied; the
      // pipeline rejects unknown strings, so we pass through whatever the
      // caller gave us and let the source-layer surface the error.
      const sourceInput = {
        filePath: safeFilePath,
        ...(sourceKey ? { sourceKey } : {}),
        ...(sourceType
          ? {
              sourceType: sourceType as
                | "paper"
                | "article"
                | "spec"
                | "doc"
                | "framework"
                | "external-url",
            }
          : {}),
        organizationId: mode === "commit" ? organizationId : null,
        isKernel: false,
        kernelVersion,
      };

      try {
        const result = await runIngestPipeline({
          source: sourceInput,
          organizationId: mode === "commit" ? organizationId : null,
          mode,
          infer: createProductionInference({ taskType: "wiki_proposal" }),
          ...(acceptThreshold !== undefined ? { acceptThreshold } : {}),
          ...(maxBodyDeltaRatio !== undefined ? { maxBodyDeltaRatio } : {}),
        });
        return {
          success: true,
          message: summarizePipelineResult(result),
          data: result,
        };
      } catch (err) {
        return {
          success: false,
          message: `wiki_ingest failed: ${(err as Error).message ?? String(err)}`,
          error: (err as Error).message ?? String(err),
        };
      }
    }

    case "list_wiki_overlay_drafts": {
      // Feeds the review-wiki-drafts skill. Read-only; safe in immediate mode.
      const { listOverlayDrafts } = await import("@/lib/actions/wiki-publish");
      try {
        const drafts = await listOverlayDrafts();
        if (drafts.length === 0) {
          return {
            success: true,
            message: "No pending drafts in this org's overlay.",
            data: { drafts: [] },
          };
        }
        const summary = drafts
          .map((d, i) => {
            const sourceTag =
              d.sourceSlugs.length > 0
                ? ` · cites ${d.sourceSlugs.slice(0, 2).join(", ")}${d.sourceSlugs.length > 2 ? `, +${d.sourceSlugs.length - 2}` : ""}`
                : "";
            const overrideTag = d.kernelPageId ? " · overrides kernel" : "";
            return `${i + 1}. ${d.slug} (${d.pageKind}, ${d.bodyLength} bytes${overrideTag}${sourceTag})`;
          })
          .join("\n");
        return {
          success: true,
          message: `${drafts.length} draft(s) pending:\n${summary}`,
          data: { drafts },
        };
      } catch (err) {
        return {
          success: false,
          message: `list_wiki_overlay_drafts failed: ${(err as Error).message ?? String(err)}`,
          error: (err as Error).message ?? String(err),
        };
      }
    }

    case "publish_wiki_overlay_pages": {
      const pageIds = Array.isArray(params["pageIds"]) ? params["pageIds"] : null;
      if (!pageIds || pageIds.length === 0) {
        return {
          success: false,
          message: "publish_wiki_overlay_pages requires a non-empty pageIds array.",
          error: "Empty pageIds",
        };
      }
      const targetStatusParam = params["targetStatus"];
      const targetStatus =
        typeof targetStatusParam === "string" &&
        ["draft", "published", "review-needed", "archived"].includes(
          targetStatusParam,
        )
          ? (targetStatusParam as "draft" | "published" | "review-needed" | "archived")
          : "published";
      const changeSummary =
        typeof params["changeSummary"] === "string"
          ? (params["changeSummary"] as string)
          : undefined;

      const { publishWikiOverlayPages } = await import(
        "@/lib/actions/wiki-publish"
      );
      const result = await publishWikiOverlayPages({
        pageIds: pageIds.map((id) => String(id)),
        targetStatus,
        ...(changeSummary ? { changeSummary } : {}),
      });
      if (!result.ok) {
        return {
          success: false,
          message: result.error,
          error: result.error,
        };
      }
      const publishedTag =
        result.published.length === 0
          ? "0 published"
          : `Published ${result.published.length}: ${result.published
              .map((p) => p.slug)
              .slice(0, 6)
              .join(", ")}${result.published.length > 6 ? `, +${result.published.length - 6}` : ""}`;
      const rejectedTag =
        result.rejected.length === 0
          ? ""
          : ` · Rejected ${result.rejected.length} (` +
            Object.entries(
              result.rejected.reduce<Record<string, number>>((acc, r) => {
                acc[r.reason] = (acc[r.reason] ?? 0) + 1;
                return acc;
              }, {}),
            )
              .map(([reason, n]) => `${reason}=${n}`)
              .join(", ") +
            ")";
      return {
        success: true,
        message: `${publishedTag}${rejectedTag}`,
        data: result,
      };
    }

    case "search_knowledge": {
      const { searchPlatformKnowledge } = await import("@/lib/semantic-memory");
      const results = await searchPlatformKnowledge({
        query: String(params["query"] ?? ""),
        entityType: typeof params["type"] === "string" ? params["type"] : undefined,
        limit: typeof params["limit"] === "number" ? params["limit"] : 5,
      });
      if (results.length === 0) {
        return { success: true, message: "No matching knowledge found.", data: { results: [] } };
      }
      const summary = results.map((r) => `${r.entityType}:${r.entityId} — ${r.title} (${Math.round(r.score * 100)}% match)`).join("\n");
      return { success: true, message: summary, data: { results } };
    }

    case "search_knowledge_base": {
      const { searchKnowledgeArticles } = await import("@/lib/semantic-memory");
      const results = await searchKnowledgeArticles({
        query: String(params["query"] ?? ""),
        productId: typeof params["productId"] === "string" ? params["productId"] : undefined,
        portfolioId: typeof params["portfolioId"] === "string" ? params["portfolioId"] : undefined,
        category: typeof params["category"] === "string" ? params["category"] : undefined,
        valueStream: typeof params["valueStream"] === "string" ? params["valueStream"] : undefined,
        limit: typeof params["limit"] === "number" ? params["limit"] : 5,
      });
      if (results.length === 0) {
        return { success: true, message: "No matching knowledge articles found.", data: { results: [] } };
      }
      const summary = results.map((r) => `${r.category}:${r.articleId} — ${r.title} (${Math.round(r.score * 100)}% match)`).join("\n");
      return { success: true, message: summary, data: { results } };
    }

    case "create_knowledge_article": {
      const { prisma } = await import("@dpf/db");
      const { storeKnowledgeArticle } = await import("@/lib/semantic-memory");

      // Generate next articleId: KA-001, KA-002, ...
      const lastArticle = await prisma.knowledgeArticle.findFirst({
        orderBy: { createdAt: "desc" },
        select: { articleId: true },
      });
      const nextNum = lastArticle
        ? parseInt(lastArticle.articleId.replace("KA-", ""), 10) + 1
        : 1;
      const articleId = `KA-${String(nextNum).padStart(3, "0")}`;

      const title = String(params["title"] ?? "");
      const body = String(params["body"] ?? "");
      const category = String(params["category"] ?? "reference");
      const productIds = Array.isArray(params["productIds"]) ? params["productIds"].map(String) : [];
      const portfolioIds = Array.isArray(params["portfolioIds"]) ? params["portfolioIds"].map(String) : [];
      const valueStreams = Array.isArray(params["valueStreams"]) ? params["valueStreams"].map(String) : [];
      const tags = Array.isArray(params["tags"]) ? params["tags"].map(String) : [];

      const article = await prisma.knowledgeArticle.create({
        data: {
          articleId,
          title,
          body,
          category,
          status: "draft",
          visibility: "internal",
          authorId: userId,
          valueStreams,
          tags,
          products: productIds.length > 0
            ? { create: productIds.map((id) => ({ digitalProductId: id })) }
            : undefined,
          portfolios: portfolioIds.length > 0
            ? { create: portfolioIds.map((id) => ({ portfolioId: id })) }
            : undefined,
          revisions: {
            create: {
              version: 1,
              title,
              body,
              changeSummary: "Initial draft",
              createdById: userId,
            },
          },
        },
      });

      // Index into Qdrant
      await storeKnowledgeArticle({
        articleId,
        title,
        body,
        category,
        status: "draft",
        productIds,
        portfolioIds,
        valueStreams,
        tags,
      });

      return {
        success: true,
        entityId: articleId,
        message: `Knowledge article ${articleId} created as draft: "${title}". Publish it to make it searchable by AI coworkers.`,
      };
    }

    case "flag_stale_knowledge": {
      const { prisma } = await import("@dpf/db");

      const where: Record<string, unknown> = { status: "published" };

      if (typeof params["productId"] === "string") {
        where.products = { some: { digitalProductId: params["productId"] } };
      }
      if (typeof params["portfolioId"] === "string") {
        where.portfolios = { some: { portfolioId: params["portfolioId"] } };
      }

      const articles = await prisma.knowledgeArticle.findMany({
        where: where as never,
        select: {
          articleId: true,
          title: true,
          category: true,
          reviewIntervalDays: true,
          lastReviewedAt: true,
          createdAt: true,
        },
      });

      const now = new Date();
      const stale = articles.filter((a) => {
        const baseline = a.lastReviewedAt ?? a.createdAt;
        const dueDate = new Date(baseline.getTime() + a.reviewIntervalDays * 86400000);
        return now > dueDate;
      });

      if (stale.length === 0) {
        return { success: true, message: "All published knowledge articles are up to date.", data: { articles: [] } };
      }

      const summary = stale.map((a) => {
        const baseline = a.lastReviewedAt ?? a.createdAt;
        const daysOverdue = Math.floor((now.getTime() - baseline.getTime()) / 86400000) - a.reviewIntervalDays;
        return `${a.articleId}: "${a.title}" (${a.category}) — ${daysOverdue} days overdue for review`;
      }).join("\n");

      return { success: true, message: `${stale.length} article(s) need review:\n${summary}`, data: { articles: stale } };
    }

    case "run_endpoint_tests": {
      const { runEndpointTests } = await import("@/lib/endpoint-test-runner");
      const request = buildEndpointTestRunRequest(params, context);

      if (request.error) {
        return { success: false, message: request.error };
      }

      const modelId = request.modelId ?? (
        request.endpointId && !request.allModels
          ? await resolveRepresentativeEndpointModelId(request.endpointId)
          : undefined
      );

      const results = await runEndpointTests({
        ...(request.endpointId ? { endpointId: request.endpointId } : {}),
        ...(modelId ? { modelId } : {}),
        ...(request.taskType ? { taskType: request.taskType } : {}),
        probesOnly: request.probesOnly,
        triggeredBy: userId,
      });

      const summary = results.map((r) => {
        const probesPassed = r.probes.filter((p) => p.pass).length;
        const probesFailed = r.probes.filter((p) => !p.pass).length;
        const scenariosPassed = r.scenarios.filter((s) => s.passed).length;
        const scenariosFailed = r.scenarios.filter((s) => !s.passed).length;
        const lines = [
          `**${r.endpointId}**: Probes ${probesPassed}/${probesPassed + probesFailed} passed`,
        ];
        if (r.scenarios.length > 0) {
          lines.push(`Scenarios ${scenariosPassed}/${scenariosPassed + scenariosFailed} passed`);
        }
        lines.push(`Instruction following: ${r.instructionFollowing ?? "unknown"}`);
        if (r.codingCapability) lines.push(`Coding: ${r.codingCapability}`);
        // List failures
        for (const p of r.probes.filter((p) => !p.pass)) {
          lines.push(`  FAIL probe: ${p.name} — ${p.reason}`);
        }
        for (const s of r.scenarios.filter((s) => !s.passed)) {
          lines.push(`  FAIL scenario: ${s.name}`);
        }
        return lines.join("\n");
      }).join("\n\n");

      return { success: true, message: summary || "No endpoints to test.", data: { results, scope: { ...request, ...(modelId ? { modelId } : {}) } } };
    }

      case "search_integrations": {
        const query = String(params["query"] ?? "");
        const rows = await prisma.mcpIntegration.findMany({
          where: {
            status: "active",
            ...(typeof params["category"] === "string" ? { category: params["category"] } : {}),
            ...(typeof params["pricingModel"] === "string" ? { pricingModel: params["pricingModel"] } : {}),
            ...(typeof params["archetypeId"] === "string" ? { archetypeIds: { has: params["archetypeId"] } } : {}),
          ...(query.trim() ? {
            OR: [
              { name: { contains: query, mode: "insensitive" } },
              { shortDescription: { contains: query, mode: "insensitive" } },
              { tags: { has: query.toLowerCase() } },
            ],
          } : {}),
        },
          select: {
            name: true, vendor: true, shortDescription: true, category: true,
            pricingModel: true, rating: true, ratingCount: true, isVerified: true,
            documentationUrl: true, logoUrl: true, archetypeIds: true,
            slug: true, tags: true, rawMetadata: true,
          },
          orderBy: [{ isVerified: "desc" }, { installCount: "desc" }],
          take: Math.min(
            Math.max(typeof params["limit"] === "number" ? params["limit"] * 5 : 50, 50),
            200
          ),
        });
        const results = rows
          .map((row) => ({
            ...row,
            benchmark: getIntegrationBenchmarkMetadata({
              name: row.name,
              slug: row.slug,
              category: row.category,
              tags: row.tags,
              vendor: row.vendor,
              rawMetadata: row.rawMetadata,
            }),
          }))
          .filter((row) =>
            matchesIntegrationBenchmarkFilters(row.benchmark, {
              benchmarkDomain:
                typeof params["benchmarkDomain"] === "string"
                  ? (params["benchmarkDomain"] as IntegrationBenchmarkDomain)
                  : undefined,
              deploymentMode:
                typeof params["deploymentMode"] === "string"
                  ? (params["deploymentMode"] as IntegrationDeploymentMode)
                  : undefined,
              businessProfile:
                typeof params["businessProfile"] === "string"
                  ? (params["businessProfile"] as IntegrationProfileTag)
                  : undefined,
              recommendedTreatment:
                typeof params["recommendedTreatment"] === "string"
                  ? (params["recommendedTreatment"] as IntegrationTreatment)
                  : undefined,
            })
          )
          .slice(0, typeof params["limit"] === "number" ? params["limit"] : 10)
          .map(({ tags: _tags, rawMetadata: _rawMetadata, slug: _slug, ...row }) => row);
        return { success: true, message: `Found ${results.length} integration(s).`, data: { results } };
      }

    case "search_tool_marketplace": {
      const includeKinds = Array.isArray(params["includeKinds"])
        ? params["includeKinds"].filter((kind): kind is "mcp" | "native" | "built_in" | "model_requirement" =>
          typeof kind === "string" && ["mcp", "native", "built_in", "model_requirement"].includes(kind),
        )
        : undefined;
      const readiness = await getToolMarketplaceReadiness({
        query: typeof params["query"] === "string" ? params["query"] : "",
        agentId: typeof params["agentId"] === "string" ? params["agentId"] : context?.agentId,
        includeKinds: includeKinds && includeKinds.length > 0 ? includeKinds : undefined,
        limit: typeof params["limit"] === "number" ? params["limit"] : 12,
      });

      return {
        success: true,
        message: `Found ${readiness.entries.length} marketplace readiness entr${readiness.entries.length === 1 ? "y" : "ies"}.`,
        data: {
          summary: readiness.summary,
          entries: readiness.entries,
        },
      };
    }

    case "get_my_coworker_profile": {
      const agentId = requireCurrentCoworker(context);
      const profile = await loadCoworkerProfile(agentId, context?.routeContext ?? null);
      return {
        success: true,
        message: `Loaded coworker profile for ${profile.profile.name}.`,
        data: profile,
      };
    }

    case "assess_my_capabilities": {
      const agentId = requireCurrentCoworker(context);
      const profile = await loadCoworkerProfile(agentId, context?.routeContext ?? null);
      const readiness = await getToolMarketplaceReadiness({
        query: typeof params["query"] === "string" ? params["query"] : undefined,
        agentId,
        limit: 12,
      });

      return {
        success: true,
        message: `Prepared self-assessment context for ${profile.profile.name}.`,
        data: {
          ...profile,
          readiness,
          responseShape: {
            verdict: [...COWORKER_ASSESSMENT_VERDICTS],
            confidence: [...COWORKER_ASSESSMENT_CONFIDENCE],
            needs: {
              kind: [...COWORKER_CAPABILITY_NEED_KINDS],
              severity: [...COWORKER_CAPABILITY_NEED_SEVERITIES],
              requiredFields: ["kind", "severity", "need", "blocks"],
            },
          },
          instruction:
            "Assess your mission against your current skills, grants, and readiness. Submit durable gaps with submit_coworker_capability_need instead of creating backlog items directly.",
        },
      };
    }

    case "submit_coworker_capability_need": {
      const agentId = requireCurrentCoworker(context);
      const needs = parseCapabilityNeeds(params["needs"]);
      if (needs.length === 0) {
        return {
          success: false,
          error: "No valid capability needs supplied",
          message: "Submit at least one need with kind, severity, need, and blocks.",
        };
      }

      const verdict = COWORKER_ASSESSMENT_VERDICTS.includes(params["verdict"] as CoworkerAssessmentVerdict)
        ? params["verdict"] as CoworkerAssessmentVerdict
        : "gaps";
      const confidence = COWORKER_ASSESSMENT_CONFIDENCE.includes(params["confidence"] as CoworkerAssessmentConfidence)
        ? params["confidence"] as CoworkerAssessmentConfidence
        : "medium";

      const result = await submitCoworkerSelfAssessment({
        agentId,
        trigger: "tool-call",
        routeContext: context?.routeContext ?? null,
        verdict,
        confidence,
        missionSummary: typeof params["missionSummary"] === "string" ? params["missionSummary"] : null,
        capabilitySummary: typeof params["capabilitySummary"] === "string" ? params["capabilitySummary"] : null,
        rawPayload: params["rawPayload"] != null && typeof params["rawPayload"] === "object"
          ? params["rawPayload"] as Record<string, unknown>
          : { toolName },
        needs,
      });

      return {
        success: true,
        entityId: result.assessmentId,
        message: `Submitted ${result.needIds.length} capability need${result.needIds.length === 1 ? "" : "s"} for review.`,
        data: result,
      };
    }

    case "list_my_capability_needs": {
      const agentId = requireCurrentCoworker(context);
      const status = COWORKER_CAPABILITY_NEED_STATUSES.includes(params["status"] as CoworkerCapabilityNeedStatus)
        ? params["status"] as CoworkerCapabilityNeedStatus
        : undefined;
      const kind = COWORKER_CAPABILITY_NEED_KINDS.includes(params["kind"] as CoworkerCapabilityNeedKind)
        ? params["kind"] as CoworkerCapabilityNeedKind
        : undefined;
      const severity = COWORKER_CAPABILITY_NEED_SEVERITIES.includes(params["severity"] as CoworkerCapabilityNeedSeverity)
        ? params["severity"] as CoworkerCapabilityNeedSeverity
        : undefined;
      const needs = await listCoworkerCapabilityNeeds({ agentId, status, kind, severity });

      return {
        success: true,
        message: `Found ${needs.length} capability need${needs.length === 1 ? "" : "s"}.`,
        data: { needs },
      };
    }

    case "list_all_capability_needs": {
      // BI-F9E7B780 — governance-surface rollup, not scoped to the calling
      // coworker. Reuses the same review-service helper that powers the
      // /platform/ai/capability-needs admin page so the MCP surface and the
      // operator UI agree on shape (summary counts, filter-option enums,
      // full need list).
      const { getCoworkerCapabilityNeedReview } = await import(
        "@/lib/coworker-self-assessment/review-service"
      );
      const agentId = typeof params["agentId"] === "string" && params["agentId"].length > 0
        ? params["agentId"]
        : undefined;
      const status = COWORKER_CAPABILITY_NEED_STATUSES.includes(
        params["status"] as CoworkerCapabilityNeedStatus,
      )
        ? (params["status"] as CoworkerCapabilityNeedStatus)
        : undefined;
      const kind = COWORKER_CAPABILITY_NEED_KINDS.includes(
        params["kind"] as CoworkerCapabilityNeedKind,
      )
        ? (params["kind"] as CoworkerCapabilityNeedKind)
        : undefined;
      const severity = COWORKER_CAPABILITY_NEED_SEVERITIES.includes(
        params["severity"] as CoworkerCapabilityNeedSeverity,
      )
        ? (params["severity"] as CoworkerCapabilityNeedSeverity)
        : undefined;

      const review = await getCoworkerCapabilityNeedReview({
        agentId,
        status,
        kind,
        severity,
      });

      return {
        success: true,
        message: `Found ${review.summary.total} capability need${
          review.summary.total === 1 ? "" : "s"
        } across all coworkers.`,
        data: {
          summary: review.summary,
          filterOptions: review.filterOptions,
          needs: review.needs,
        },
      };
    }

    case "prefill_onboarding_wizard": {
      const data = {
        name: String(params["name"] ?? ""),
        shortName: String(params["shortName"] ?? ""),
        sourceType: String(params["sourceType"] ?? "external"),
        jurisdiction: String(params["jurisdiction"] ?? ""),
        industry: params["industry"] ? String(params["industry"]) : null,
        sourceUrl: params["sourceUrl"] ? String(params["sourceUrl"]) : null,
        obligations: Array.isArray(params["obligations"]) ? params["obligations"] : [],
        suggestedControls: Array.isArray(params["suggestedControls"]) ? params["suggestedControls"] : [],
      };

      const draft = await prisma.onboardingDraft.create({
        data: {
          data: data as any,
          createdBy: userId,
          expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
        },
      });

      const wizardUrl = `/compliance/onboard?draft=${draft.id}`;
      return {
        success: true,
        message: `Onboarding draft created. Navigate to ${wizardUrl} to review and commit.`,
        data: { wizardUrl, draftId: draft.id },
      };
    }

    case "save_licensing_investigation": {
      const result = await saveLicensingInvestigationFinding({
        setupStatus: typeof params["setupStatus"] === "string" ? params["setupStatus"] : undefined,
        investigationMode: typeof params["investigationMode"] === "string" ? params["investigationMode"] : undefined,
        homeCountryCode: typeof params["homeCountryCode"] === "string" ? params["homeCountryCode"] : undefined,
        primaryRegionCode: typeof params["primaryRegionCode"] === "string" ? params["primaryRegionCode"] : undefined,
        operatingFootprintSummary:
          typeof params["operatingFootprintSummary"] === "string"
            ? params["operatingFootprintSummary"]
            : undefined,
        legalActivityConfidence:
          typeof params["legalActivityConfidence"] === "string"
            ? params["legalActivityConfidence"]
            : undefined,
        researchCoverageStatus:
          typeof params["researchCoverageStatus"] === "string"
            ? params["researchCoverageStatus"]
            : undefined,
        notes: typeof params["notes"] === "string" ? params["notes"] : undefined,
        appendNotes: typeof params["appendNotes"] === "boolean" ? params["appendNotes"] : undefined,
      });

      return {
        success: result.ok,
        entityId: result.id,
        message: result.message,
        ...(result.ok ? {} : { error: result.message }),
      };
    }

    case "create_licensing_readiness_issue": {
      const result = await createLicenseReadinessIssue({
        issueType: String(params["issueType"] ?? ""),
        severity: typeof params["severity"] === "string" ? params["severity"] : undefined,
        title: String(params["title"] ?? ""),
        details: typeof params["details"] === "string" ? params["details"] : undefined,
        organizationLicenseRecordId:
          typeof params["organizationLicenseRecordId"] === "string"
            ? params["organizationLicenseRecordId"]
            : undefined,
        personLicenseRecordId:
          typeof params["personLicenseRecordId"] === "string"
            ? params["personLicenseRecordId"]
            : undefined,
      });

      return {
        success: result.ok,
        entityId: result.id,
        message: result.message,
        ...(result.ok ? {} : { error: result.message }),
      };
    }

    case "evaluate_tool": {
      const { createToolEvaluation } = await import("./tool-evaluation-data");
      const evalId = await createToolEvaluation({
        toolName: String(params["toolName"] ?? ""),
        toolType: String(params["toolType"] ?? "npm_package"),
        version: String(params["version"] ?? "latest"),
        sourceUrl: String(params["sourceUrl"] ?? ""),
        proposedBy: userId,
      });
      return { success: true, entityId: evalId, message: `Tool evaluation created: ${evalId}. The evaluation pipeline will review this tool for security, architecture fit, compliance, and integration.` };
    }

    case "summarize_estate_posture": {
      const { resolveEstateEntity, summarizeDiscoveryOperations, summarizeProductEstate } = await import("@/lib/estate/estate-tooling");
      const resolved = await resolveEstateEntity({
        entityId: typeof params["entityId"] === "string" ? params["entityId"] : undefined,
        entityKey: typeof params["entityKey"] === "string" ? params["entityKey"] : undefined,
        entityName: typeof params["entityName"] === "string" ? params["entityName"] : undefined,
      }, context?.routeContext);

      if (resolved.kind === "resolved") {
        return {
          success: true,
          entityId: resolved.item.id,
          message: `${resolved.item.name}: ${resolved.item.supportSummaryLabel}; ${resolved.item.advisorySummaryLabel}; ${resolved.item.freshnessLabel}.`,
          data: {
            item: resolved.item,
            postureBadges: resolved.item.postureBadges,
            blastRadiusLabel: resolved.item.blastRadiusLabel,
            supportSummaryLabel: resolved.item.supportSummaryLabel,
            advisorySummaryLabel: resolved.item.advisorySummaryLabel,
            identityConfidenceLabel: resolved.item.identityConfidenceLabel,
          },
        };
      }

      if (resolved.kind === "ambiguous") {
        return {
          success: false,
          message: `Multiple estate items matched that request. Please be more specific.`,
          error: resolved.matches.map((match) => `${match.name} (${match.entityKey})`).join(", "),
          data: { matches: resolved.matches },
        };
      }

      const productSummary = await summarizeProductEstate(context?.routeContext);
      if (productSummary) {
        return {
          success: true,
          entityId: productSummary.productId,
          message: `${productSummary.productName}: ${productSummary.itemCount} estate items, ${productSummary.openIssueCount} open issues, ${productSummary.unknownSupportCount} with unverified lifecycle, ${productSummary.lowIdentityConfidenceCount} with identity review still needed.`,
          data: productSummary as Record<string, unknown>,
        };
      }

      const discoverySummary = await summarizeDiscoveryOperations();
      return {
        success: true,
        message: `Discovery operations: ${discoverySummary.needsReviewCount} items need review.`,
        data: discoverySummary as Record<string, unknown>,
      };
    }

    case "review_estate_identity": {
      const { resolveEstateEntity } = await import("@/lib/estate/estate-tooling");
      const resolved = await resolveEstateEntity({
        entityId: typeof params["entityId"] === "string" ? params["entityId"] : undefined,
        entityKey: typeof params["entityKey"] === "string" ? params["entityKey"] : undefined,
        entityName: typeof params["entityName"] === "string" ? params["entityName"] : undefined,
      }, context?.routeContext);

      if (resolved.kind === "ambiguous") {
        return {
          success: false,
          message: "Multiple estate items matched that request. Please be more specific.",
          error: resolved.matches.map((match) => `${match.name} (${match.entityKey})`).join(", "),
          data: { matches: resolved.matches },
        };
      }

      if (resolved.kind === "missing") {
        return {
          success: false,
          message: resolved.reason,
          error: resolved.reason,
        };
      }

      return {
        success: true,
        entityId: resolved.item.id,
        message: `${resolved.item.name}: ${resolved.item.identityConfidenceLabel}; ${resolved.item.manufacturerLabel}; ${resolved.item.versionSourceLabel}.`,
        data: {
          item: resolved.item,
          identityLabel: resolved.item.identityLabel,
          identityConfidenceLabel: resolved.item.identityConfidenceLabel,
          manufacturerLabel: resolved.item.manufacturerLabel,
          versionLabel: resolved.item.versionLabel,
          versionSourceLabel: resolved.item.versionSourceLabel,
          supportSummaryLabel: resolved.item.supportSummaryLabel,
          advisorySummaryLabel: resolved.item.advisorySummaryLabel,
        },
      };
    }

    case "validate_version_confidence": {
      const { resolveEstateEntity } = await import("@/lib/estate/estate-tooling");
      const resolved = await resolveEstateEntity({
        entityId: typeof params["entityId"] === "string" ? params["entityId"] : undefined,
        entityKey: typeof params["entityKey"] === "string" ? params["entityKey"] : undefined,
        entityName: typeof params["entityName"] === "string" ? params["entityName"] : undefined,
      }, context?.routeContext);

      if (resolved.kind === "ambiguous") {
        return {
          success: false,
          message: "Multiple estate items matched that request. Please be more specific.",
          error: resolved.matches.map((match) => `${match.name} (${match.entityKey})`).join(", "),
          data: { matches: resolved.matches },
        };
      }

      if (resolved.kind === "missing") {
        return {
          success: false,
          message: resolved.reason,
          error: resolved.reason,
        };
      }

      return {
        success: true,
        entityId: resolved.item.id,
        message: `${resolved.item.name}: ${resolved.item.versionConfidenceLabel}. ${resolved.item.versionSourceLabel}. Current version shown as ${resolved.item.versionLabel}.`,
        data: {
          item: resolved.item,
          versionLabel: resolved.item.versionLabel,
          versionSourceLabel: resolved.item.versionSourceLabel,
          versionConfidenceLabel: resolved.item.versionConfidenceLabel,
        },
      };
    }

    case "explain_blast_radius": {
      const { loadEstateBlastRadius, resolveEstateEntity } = await import("@/lib/estate/estate-tooling");
      const resolved = await resolveEstateEntity({
        entityId: typeof params["entityId"] === "string" ? params["entityId"] : undefined,
        entityKey: typeof params["entityKey"] === "string" ? params["entityKey"] : undefined,
        entityName: typeof params["entityName"] === "string" ? params["entityName"] : undefined,
      }, context?.routeContext);

      if (resolved.kind === "ambiguous") {
        return {
          success: false,
          message: "Multiple estate items matched that request. Please be more specific.",
          error: resolved.matches.map((match) => `${match.name} (${match.entityKey})`).join(", "),
          data: { matches: resolved.matches },
        };
      }

      if (resolved.kind === "missing") {
        return {
          success: false,
          message: resolved.reason,
          error: resolved.reason,
        };
      }

      const blastRadius = await loadEstateBlastRadius(resolved.item.id);
      if (!blastRadius) {
        return {
          success: false,
          message: "The estate item could not be loaded for blast-radius analysis.",
          error: "Estate item not found",
        };
      }

      return {
        success: true,
        entityId: resolved.item.id,
        message: `${resolved.item.name}: ${blastRadius.downstream.length} downstream dependenc${blastRadius.downstream.length === 1 ? "y" : "ies"} and ${blastRadius.upstream.length} upstream dependenc${blastRadius.upstream.length === 1 ? "y" : "ies"}.`,
        data: {
          item: resolved.item,
          upstream: blastRadius.upstream,
          downstream: blastRadius.downstream,
        },
      };
    }

    case "discovery_sweep": {
      const { triggerBootstrapDiscovery } = await import("@/lib/actions/discovery");
      const result = await triggerBootstrapDiscovery();
      if (!result.ok) {
        return {
          success: false,
          message: result.error,
          error: result.error,
        };
      }
      return {
        success: true,
        message: `Discovery sweep completed: ${result.summary.createdEntities + result.summary.updatedEntities} entities refreshed and ${result.summary.createdRelationships + result.summary.updatedRelationships} relationships refreshed.`,
        data: result.summary as Record<string, unknown>,
      };
    }

    case "run_discovery_triage": {
      const { runDiscoveryTriageDaily } = await import("@/lib/discovery-triage-runner");
      const trigger = params["trigger"] === "volume" ? "volume" : "cadence";
      const result = await runDiscoveryTriageDaily(undefined, {
        trigger,
        actorType: "agent",
        actorId: context?.agentId ?? DISCOVERY_TRIAGE_AGENT_ID,
        enableAutonomousReview: true,
      });
      return {
        success: true,
        message: result.skipped
          ? result.skipReason ?? "Discovery triage skipped."
          : `Discovery triage processed ${result.metrics.processed} entities with ${result.metrics.autoAttributed} auto-attributed.`,
        data: result as unknown as Record<string, unknown>,
      };
    }

    case "run_hive_scout_ingest": {
      const { runHiveScoutIngest } = await import("@/lib/actions/hive-scout/ingest-500-agents");
      const result = await runHiveScoutIngest({
        actorAgentId: context?.agentId,
        taskRunId: context?.taskRunId,
        enableAutonomousReview: true,
      });
      return {
        success: true,
        message: `Hive Scout parsed ${result.catalogEntries} entries, detected ${result.gaps} gaps, reviewed ${result.reviewed ?? 0} ambiguous candidate${result.reviewed === 1 ? "" : "s"}, created ${result.created} backlog suggestion${result.created === 1 ? "" : "s"}, skipped ${result.duplicates} deterministic duplicate${result.duplicates === 1 ? "" : "s"} and ${result.skippedByReview ?? 0} review rejection${result.skippedByReview === 1 ? "" : "s"}, deferred ${result.deferred}, reused ${result.reviewCacheHits ?? 0} cached review${result.reviewCacheHits === 1 ? "" : "s"}, dropped ${result.reviewSchemaDropCount ?? 0} invalid review entr${result.reviewSchemaDropCount === 1 ? "y" : "ies"}${result.reviewSkipReason ? `, and skipped autonomous review because ${result.reviewSkipReason}` : ""}${result.reviewFailureReason ? `, with reviewer failure reason ${result.reviewFailureReason}` : ""}.`,
        data: result as unknown as Record<string, unknown>,
      };
    }

    case "attribute_entity_to_product": {
      const entityId = String(params["entityId"] ?? "").trim();
      if (!entityId) return { success: false, message: "entityId is required", error: "missing_entity_id" };

      let taxonomyNodeId = typeof params["taxonomyNodeId"] === "string" ? params["taxonomyNodeId"].trim() : "";
      const slug = typeof params["taxonomyNodeSlug"] === "string" ? params["taxonomyNodeSlug"].trim() : "";
      if (!taxonomyNodeId && slug) {
        const node = await prisma.taxonomyNode.findFirst({
          where: { nodeId: slug },
          select: { id: true },
        });
        if (!node) {
          return { success: false, message: `Taxonomy node '${slug}' not found`, error: "taxonomy_node_not_found" };
        }
        taxonomyNodeId = node.id;
      }
      if (!taxonomyNodeId) {
        return { success: false, message: "Provide taxonomyNodeId or taxonomyNodeSlug", error: "missing_taxonomy_target" };
      }

      const { reassignTaxonomy } = await import("@/lib/actions/inventory");
      const result = await reassignTaxonomy(entityId, taxonomyNodeId);
      if (!result.ok) {
        return { success: false, message: result.error ?? "Attribution failed", error: result.error ?? "attribution_failed" };
      }
      return {
        success: true,
        entityId,
        message: `Entity attributed to taxonomy node ${slug || taxonomyNodeId}.`,
        data: { entityId, taxonomyNodeId },
      };
    }

    case "dismiss_entity": {
      const entityId = String(params["entityId"] ?? "").trim();
      if (!entityId) return { success: false, message: "entityId is required", error: "missing_entity_id" };

      const { dismissEntity } = await import("@/lib/actions/inventory");
      const result = await dismissEntity(entityId);
      if (!result.ok) {
        return { success: false, message: result.error ?? "Dismiss failed", error: result.error ?? "dismiss_failed" };
      }
      return {
        success: true,
        entityId,
        message: `Entity ${entityId} dismissed.`,
        data: { entityId },
      };
    }

    case "resolve_portfolio_quality_issue": {
      const issueId = String(params["issueId"] ?? "").trim();
      const resolution = String(params["resolution"] ?? "").trim();
      if (!issueId) return { success: false, message: "issueId is required", error: "missing_issue_id" };
      if (resolution !== "resolved" && resolution !== "dismissed") {
        return { success: false, message: "resolution must be 'resolved' or 'dismissed'", error: "invalid_resolution" };
      }

      const { resolvePortfolioQualityIssue } = await import("@/lib/actions/inventory");
      const result = await resolvePortfolioQualityIssue(issueId, resolution);
      if (!result.ok) {
        return { success: false, message: result.error ?? "Resolve failed", error: result.error ?? "resolve_failed" };
      }
      return {
        success: true,
        entityId: issueId,
        message: `Quality issue ${issueId} marked ${resolution}.`,
        data: { issueId, resolution },
      };
    }

    case "configure_gateway_scan": {
      const name = String(params["name"] ?? "").trim();
      const endpointUrl = String(params["endpointUrl"] ?? "").trim();
      if (!name) return { success: false, message: "name is required", error: "missing_name" };
      if (!endpointUrl) return { success: false, message: "endpointUrl is required", error: "missing_endpoint_url" };

      const collectorType = typeof params["collectorType"] === "string" && params["collectorType"].trim()
        ? params["collectorType"].trim()
        : "arp_scan";
      const apiKey = typeof params["apiKey"] === "string" && params["apiKey"].trim()
        ? params["apiKey"].trim()
        : undefined;
      const gatewayEntityId = typeof params["gatewayEntityId"] === "string" && params["gatewayEntityId"].trim()
        ? params["gatewayEntityId"].trim()
        : undefined;
      const configuration = typeof params["configuration"] === "object" && params["configuration"] !== null
        ? (params["configuration"] as Record<string, unknown>)
        : undefined;

      const { configureDiscoveryConnection } = await import("@/lib/actions/discovery");
      const result = await configureDiscoveryConnection({
        name,
        endpointUrl,
        collectorType,
        apiKey,
        gatewayEntityId,
        configuration,
      });
      if (!result.ok) {
        return { success: false, message: result.error ?? "Configure failed", error: result.error ?? "configure_failed" };
      }
      return {
        success: true,
        entityId: result.connectionId,
        message: `Gateway scan '${name}' configured (${collectorType}). ${apiKey ? "Status: active." : "Status: unconfigured — supply an API key to activate."}`,
        data: { connectionId: result.connectionId, collectorType },
      };
    }

    // ── EA / Ontology Graph write tools ──────────────────────────────────────

    case "create_ea_element": {
      const notation = await prisma.eaNotation.findUnique({ where: { slug: "archimate4" } });
      if (!notation) return { success: false, message: "ArchiMate 4 notation not seeded", error: "Notation not found" };
      const et = await prisma.eaElementType.findUnique({
        where: { notationId_slug: { notationId: notation.id, slug: String(params["elementTypeSlug"] ?? "") } },
      });
      if (!et) return { success: false, message: `Element type "${String(params["elementTypeSlug"])}" not found`, error: "Element type not found" };
      const el = await prisma.eaElement.create({
        data: {
          elementTypeId: et.id,
          name: String(params["name"]),
          description: typeof params["description"] === "string" ? params["description"] : null,
          refinementLevel: typeof params["refinementLevel"] === "string" ? params["refinementLevel"] : "conceptual",
          itValueStream: typeof params["itValueStream"] === "string" ? params["itValueStream"] : null,
          ontologyRole: typeof params["ontologyRole"] === "string" ? params["ontologyRole"] : null,
          digitalProductId: typeof params["digitalProductId"] === "string" ? params["digitalProductId"] : null,
          portfolioId: typeof params["portfolioId"] === "string" ? params["portfolioId"] : null,
          createdById: userId,
          properties: (typeof params["properties"] === "object" && params["properties"] !== null) ? params["properties"] as import("@dpf/db").Prisma.InputJsonValue : {},
        },
      });
      return { success: true, entityId: el.id, message: `Created ${et.name} element "${String(params["name"])}"`, data: { elementId: el.id, elementTypeName: et.name, refinementLevel: el.refinementLevel } };
    }

    case "create_ea_relationship": {
      const notation = await prisma.eaNotation.findUnique({ where: { slug: "archimate4" } });
      if (!notation) return { success: false, message: "ArchiMate 4 notation not seeded", error: "Notation not found" };
      const relSlug = String(params["relationshipTypeSlug"] ?? "");
      const rt = await prisma.eaRelationshipType.findUnique({ where: { notationId_slug: { notationId: notation.id, slug: relSlug } } });
      if (!rt) return { success: false, message: `Relationship type "${relSlug}" not found`, error: "Relationship type not found" };
      const fromEl = await prisma.eaElement.findUnique({ where: { id: String(params["fromElementId"]) }, select: { elementTypeId: true, name: true } });
      const toEl   = await prisma.eaElement.findUnique({ where: { id: String(params["toElementId"])   }, select: { elementTypeId: true, name: true } });
      if (!fromEl || !toEl) return { success: false, message: "One or both elements not found", error: "Element not found" };
      const rule = await prisma.eaRelationshipRule.findFirst({
        where: { fromElementTypeId: fromEl.elementTypeId, toElementTypeId: toEl.elementTypeId, relationshipTypeId: rt.id },
      });
      if (!rule) return { success: false, message: `Relationship "${relSlug}" not permitted between these element types`, error: "Rule not permitted", data: { validationResult: "blocked" } };
      const rel = await prisma.eaRelationship.create({
        data: {
          fromElementId: String(params["fromElementId"]),
          toElementId: String(params["toElementId"]),
          relationshipTypeId: rt.id,
          notationSlug: "archimate4",
          createdById: userId,
          properties: (typeof params["properties"] === "object" && params["properties"] !== null) ? params["properties"] as import("@dpf/db").Prisma.InputJsonValue : {},
        },
      });
      return { success: true, entityId: rel.id, message: `Created "${relSlug}" relationship`, data: { relationshipId: rel.id, fromElementName: fromEl.name, toElementName: toEl.name, validationResult: "allowed" } };
    }

    case "classify_ea_element": {
      const data: Record<string, unknown> = {};
      if (typeof params["itValueStream"] === "string")   data["itValueStream"]   = params["itValueStream"];
      if (typeof params["refinementLevel"] === "string") data["refinementLevel"] = params["refinementLevel"];
      if (typeof params["ontologyRole"] === "string")    data["ontologyRole"]    = params["ontologyRole"];
      if (Object.keys(data).length === 0) return { success: false, message: "No classification fields provided", error: "Nothing to update" };
      const updated = await prisma.eaElement.update({ where: { id: String(params["elementId"]) }, data });
      return { success: true, entityId: updated.id, message: `Classified element ${updated.id}`, data: { elementId: updated.id, refinementLevel: updated.refinementLevel, itValueStream: updated.itValueStream } };
    }

    // ── EA / Ontology Graph read tools ───────────────────────────────────────

    case "query_ontology_graph": {
      const notation = await prisma.eaNotation.findUnique({ where: { slug: "archimate4" } });
      if (!notation) return { success: false, message: "ArchiMate 4 notation not seeded", error: "Notation not found" };
      const where: Record<string, unknown> = {};
      const slugs = Array.isArray(params["elementTypeSlugs"]) ? params["elementTypeSlugs"] as string[] : [];
      if (slugs.length > 0) {
        const ets = await prisma.eaElementType.findMany({ where: { notationId: notation.id, slug: { in: slugs } }, select: { id: true } });
        where["elementTypeId"] = { in: ets.map(et => et.id) };
      }
      if (typeof params["refinementLevel"] === "string") where["refinementLevel"] = params["refinementLevel"];
      if (typeof params["itValueStream"] === "string") where["itValueStream"] = params["itValueStream"];
      if (typeof params["ontologyRole"] === "string") where["ontologyRole"] = params["ontologyRole"];
      if (typeof params["digitalProductId"] === "string") where["digitalProductId"] = params["digitalProductId"];
      if (typeof params["portfolioId"] === "string") where["portfolioId"] = params["portfolioId"];
      if (typeof params["nameContains"] === "string") where["name"] = { contains: params["nameContains"], mode: "insensitive" };
      const limit = typeof params["limit"] === "number" ? Math.min(params["limit"], 50) : 20;
      const includeRels = params["includeRelationships"] === true;
      const elements = await prisma.eaElement.findMany({
        where,
        take: limit,
        include: {
          elementType: { select: { slug: true, name: true } },
          ...(includeRels ? { fromRelationships: { include: { relationshipType: { select: { slug: true } }, toElement: { select: { id: true, name: true } } } } } : {}),
        },
      });
      const total = await prisma.eaElement.count({ where });
      return {
        success: true,
        message: `Found ${elements.length} elements (${total} total)`,
        data: {
          elements: elements.map(el => ({
            elementId: el.id,
            name: el.name,
            elementTypeName: el.elementType.name,
            refinementLevel: el.refinementLevel,
            itValueStream: el.itValueStream,
            ontologyRole: el.ontologyRole,
          })),
          totalCount: total,
        },
      };
    }

    case "run_traversal_pattern": {
      const { runTraversalPattern } = await import("@/lib/ea/traversal-executor");
      const result = await runTraversalPattern({
        patternSlug: String(params["patternSlug"] ?? ""),
        startElementIds: Array.isArray(params["startElementIds"]) ? params["startElementIds"] as string[] : [],
        maxDepth: typeof params["maxDepth"] === "number" ? params["maxDepth"] : 6,
      });
      if (!result.ok) return { success: false, message: result.error ?? "Traversal failed", error: result.error };
      return { success: true, message: `Traversal complete: ${result.data!.summary.nodesTraversed} nodes`, data: result.data as Record<string, unknown> };
    }

    // ── EA file tools ─────────────────────────────────────────────────────────

    case "import_archimate": {
      const { importArchimateFile } = await import("@/lib/actions/ea-archimate");
      const fileContent = String(params["fileContentBase64"] ?? "");
      const result = await importArchimateFile({
        fileContentBase64: fileContent,
        fileName: String(params["fileName"] ?? "import.archimate"),
        userId,
        targetPortfolioId: typeof params["targetPortfolioId"] === "string" ? params["targetPortfolioId"] : undefined,
        targetDigitalProductId: typeof params["targetDigitalProductId"] === "string" ? params["targetDigitalProductId"] : undefined,
      });
      if (!result.ok) return { success: false, message: result.error ?? "Import failed", error: result.error };
      return { success: true, message: `Imported ${result.data!.elementsCreated} elements, ${result.data!.relationshipsCreated} relationships`, data: result.data as Record<string, unknown> };
    }

    case "export_archimate": {
      const { exportArchimateFile } = await import("@/lib/actions/ea-archimate");
      const result = await exportArchimateFile({
        scopeType: String(params["scopeType"] ?? "") as "view" | "portfolio" | "digital_product",
        scopeRef: String(params["scopeRef"] ?? ""),
        fileName: typeof params["fileName"] === "string" ? params["fileName"] : undefined,
        userId,
      });
      if (!result.ok) return { success: false, message: result.error ?? "Export failed", error: result.error };
      return { success: true, message: `Exported ${result.data!.elementCount} elements to ${result.data!.fileName}`, data: result.data as Record<string, unknown> };
    }

    case "apply_platform_update": {
      // Delegates to the shared server action in lib/actions/platform-dev-config.ts.
      // The same code path backs the in-portal Apply Update UI added in
      // BI-9B77E247, so both surfaces produce identical merge behavior and
      // identical structured results — single source of truth.
      const { applyPlatformUpdate } = await import("@/lib/actions/platform-dev-config");
      const result = await applyPlatformUpdate();

      switch (result.kind) {
        case "no-update-pending":
          return { success: false, message: result.message, error: "No update pending" };
        case "invalid-version":
          return { success: false, message: result.message, error: "Invalid version" };
        case "conflicts":
          return {
            success: true,
            message: result.message,
            data: {
              clean: false,
              resumedMerge: result.resumedMerge,
              conflicts: result.conflicts,
              version: result.version,
            } as unknown as Record<string, unknown>,
          };
        case "clean-merge":
          return {
            success: true,
            message: result.message,
            data: {
              clean: true,
              filesUpdated: result.filesUpdated,
              version: result.version,
            },
          };
        case "error":
          return { success: false, message: result.message, error: result.message };
      }
    }

    case "get_finance_period_summary": {
      const { formatFinancePeriodSummary, getFinancePeriodSummary } = await import("@/lib/finance/period-summary");
      const periodInput: Parameters<typeof getFinancePeriodSummary>[0] = {};
      const period = typeof params["period"] === "string" ? params["period"] : undefined;
      if (period === "month-to-date" || period === "last-month" || period === "quarter-to-date" || period === "year-to-date") {
        periodInput.period = period;
      }
      if (typeof params["startDate"] === "string" && params["startDate"].trim()) {
        periodInput.startDate = params["startDate"];
      }
      if (typeof params["endDate"] === "string" && params["endDate"].trim()) {
        periodInput.endDate = params["endDate"];
      }

      try {
        const summary = await getFinancePeriodSummary(periodInput);
        return {
          success: true,
          message: formatFinancePeriodSummary(summary),
          data: summary as unknown as Record<string, unknown>,
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return { success: false, error: msg, message: `get_finance_period_summary failed: ${msg}` };
      }
    }

    case "get_marketing_summary": {
      const { getPlaybook } = await import("@/lib/tak/marketing-playbooks");
      const days = typeof params["days"] === "number" ? params["days"] : 30;
      const since = new Date();
      since.setDate(since.getDate() - days);

      const config = await prisma.storefrontConfig.findFirst({
        include: { archetype: { select: { archetypeId: true, name: true, category: true, ctaType: true } } },
      });

      if (!config) {
        return { success: true, message: "No storefront configured. Set up your storefront first at /storefront/setup." };
      }

      const playbook = getPlaybook(config.archetype.category, config.archetype.ctaType);

      const [bookings, inquiries, orders, donations, engagements, opportunities] = await Promise.all([
        prisma.storefrontBooking.count({ where: { storefrontId: config.id, createdAt: { gte: since } } }),
        prisma.storefrontInquiry.count({ where: { storefrontId: config.id, createdAt: { gte: since } } }),
        prisma.storefrontOrder.count({ where: { storefrontId: config.id, createdAt: { gte: since } } }),
        prisma.storefrontDonation.count({ where: { storefrontId: config.id, createdAt: { gte: since } } }),
        prisma.engagement.groupBy({ by: ["status"], _count: true }),
        prisma.opportunity.groupBy({ by: ["stage"], _count: true }),
      ]);

      return {
        success: true,
        message: `Marketing summary for ${config.archetype.name} (${config.archetype.ctaType})`,
        data: {
          archetype: { id: config.archetype.archetypeId, name: config.archetype.name, category: config.archetype.category, ctaType: config.archetype.ctaType },
          playbook,
          inbox: { days, bookings, inquiries, orders, donations, total: bookings + inquiries + orders + donations },
          pipeline: {
            engagements: Object.fromEntries(engagements.map((e) => [e.status, e._count])),
            opportunities: Object.fromEntries(opportunities.map((o) => [o.stage, o._count])),
          },
        },
      };
    }

    case "suggest_campaign_ideas": {
      const { getPlaybook } = await import("@/lib/tak/marketing-playbooks");

      const config = await prisma.storefrontConfig.findFirst({
        include: { archetype: { select: { archetypeId: true, name: true, category: true, ctaType: true } } },
      });

      if (!config) {
        return { success: true, message: "No storefront configured. Set up your storefront first at /storefront/setup." };
      }

      const playbook = getPlaybook(config.archetype.category, config.archetype.ctaType);

      // Current season for seasonal relevance
      const month = new Date().getMonth();
      const season = month <= 1 || month === 11 ? "winter" : month <= 4 ? "spring" : month <= 7 ? "summer" : "autumn";

      // Top storefront items by name for campaign targeting
      const items = await prisma.storefrontItem.findMany({
        where: { storefrontId: config.id, isActive: true },
        select: { name: true, priceType: true, ctaType: true },
        orderBy: { sortOrder: "asc" },
        take: 10,
      });

      return {
        success: true,
        message: `Campaign context for ${config.archetype.name}`,
        data: {
          archetype: { name: config.archetype.name, category: config.archetype.category, ctaType: config.archetype.ctaType },
          playbook,
          season,
          currentMonth: new Date().toLocaleString("en-GB", { month: "long", year: "numeric" }),
          activeItems: items.map((i) => ({ name: i.name, priceType: i.priceType, ctaType: i.ctaType })),
        },
      };
    }

    case "save_marketing_review": {
      const result = await recordMarketingStrategistReview({
        recommendation: {
          summary: String(params["summary"] ?? ""),
          primaryChannels: Array.isArray(params["primaryChannels"])
            ? params["primaryChannels"].filter((value): value is string => typeof value === "string")
            : [],
          secondaryChannels: Array.isArray(params["secondaryChannels"])
            ? params["secondaryChannels"].filter((value): value is string => typeof value === "string")
            : [],
          skippedChannels: Array.isArray(params["skippedChannels"])
            ? params["skippedChannels"].filter((value): value is string => typeof value === "string")
            : [],
          kpis: Array.isArray(params["kpis"])
            ? params["kpis"].filter((value): value is string => typeof value === "string")
            : [],
          cadence: typeof params["cadence"] === "string" ? params["cadence"] : undefined,
          suggestedActions: Array.isArray(params["suggestedActions"])
            ? params["suggestedActions"].filter((value): value is string => typeof value === "string")
            : [],
        },
        createdByAgentId: context?.agentId ?? null,
      });

      if (!result) {
        return {
          success: false,
          message: "No organization workspace is configured, so the marketing review could not be saved.",
          error: "No organization workspace is configured",
        };
      }

      return {
        success: true,
        entityId: result.reviewId,
        message: `${result.message}. The marketing page now has a saved strategist review and updated channel/cadence context.`,
        data: result,
      };
    }

    case "create_marketing_campaign_brief": {
      const result = await createMarketingCampaignBrief({
        brief: {
          title: String(params["title"] ?? ""),
          objective: String(params["objective"] ?? ""),
          audience: typeof params["audience"] === "string" ? params["audience"] : undefined,
          channels: Array.isArray(params["channels"])
            ? params["channels"].filter((value): value is string => typeof value === "string")
            : [],
          cta: typeof params["cta"] === "string" ? params["cta"] : undefined,
          proofAssets: Array.isArray(params["proofAssets"])
            ? params["proofAssets"].filter((value): value is string => typeof value === "string")
            : [],
          kpis: Array.isArray(params["kpis"])
            ? params["kpis"].filter((value): value is string => typeof value === "string")
            : [],
          notes: typeof params["notes"] === "string" ? params["notes"] : undefined,
        },
        createdByAgentId: context?.agentId ?? null,
      });

      if (!result) {
        return {
          success: false,
          message: "No organization workspace is configured, so the campaign brief could not be saved.",
          error: "No organization workspace is configured",
        };
      }

      return {
        success: true,
        entityId: result.briefId,
        message: `${result.message}. The marketing workspace now has a saved campaign brief.`,
        data: result,
      };
    }

    case "create_marketing_asset_task": {
      const result = await createMarketingAssetTask({
        task: {
          title: String(params["title"] ?? ""),
          assetType: String(params["assetType"] ?? ""),
          channel: typeof params["channel"] === "string" ? params["channel"] : undefined,
          dueWindow: typeof params["dueWindow"] === "string" ? params["dueWindow"] : undefined,
          brief: typeof params["brief"] === "string" ? params["brief"] : undefined,
        },
        createdByAgentId: context?.agentId ?? null,
      });

      if (!result) {
        return {
          success: false,
          message: "No organization workspace is configured, so the marketing asset task could not be saved.",
          error: "No organization workspace is configured",
        };
      }

      return {
        success: true,
        entityId: result.taskId,
        message: `${result.message}. The marketing workspace now has a saved proof/content task.`,
        data: result,
      };
    }

    case "publish_to_linkedin": {
      const { publishApprovedDraft } = await import("./marketing/publish");
      const result = await publishApprovedDraft({
        draftId: String(params["draftId"] ?? ""),
        publishedByUserId: userId,
      });
      if (!result.ok) {
        return {
          success: false,
          message: result.message,
          error: result.error,
        };
      }
      return {
        success: true,
        entityId: result.publicationId,
        message: result.message,
        data: result,
      };
    }

    case "draft_marketing_asset": {
      const { draftMarketingAsset } = await import("./marketing/draft-builder");
      const result = await draftMarketingAsset({
        assetTaskId: String(params["assetTaskId"] ?? ""),
        channelOverride: typeof params["channelOverride"] === "string" ? params["channelOverride"] : undefined,
        toneNotes: typeof params["toneNotes"] === "string" ? params["toneNotes"] : undefined,
        createdByAgentId: context?.agentId ?? null,
      });

      if (!result.success) {
        return {
          success: false,
          message: result.message,
          error: result.error,
        };
      }

      return {
        success: true,
        entityId: result.draftId,
        message: result.message,
        data: result,
      };
    }

    case "record_marketing_kpi_checkpoint": {
      const result = await recordMarketingKpiCheckpoint({
        checkpoint: {
          metric: String(params["metric"] ?? ""),
          target: typeof params["target"] === "string" ? params["target"] : undefined,
          cadence: typeof params["cadence"] === "string" ? params["cadence"] : undefined,
          notes: typeof params["notes"] === "string" ? params["notes"] : undefined,
        },
        createdByAgentId: context?.agentId ?? null,
      });

      if (!result) {
        return {
          success: false,
          message: "No organization workspace is configured, so the KPI checkpoint could not be saved.",
          error: "No organization workspace is configured",
        };
      }

      return {
        success: true,
        entityId: result.checkpointId,
        message: `${result.message}. The marketing workspace now has a saved KPI checkpoint.`,
        data: result,
      };
    }

    case "create_marketing_automation_candidate": {
      const result = await createMarketingAutomationCandidate({
        candidate: {
          title: String(params["title"] ?? ""),
          trigger: String(params["trigger"] ?? ""),
          action: String(params["action"] ?? ""),
          approvalRequired: typeof params["approvalRequired"] === "boolean"
            ? params["approvalRequired"]
            : true,
          rationale: typeof params["rationale"] === "string" ? params["rationale"] : undefined,
        },
        createdByAgentId: context?.agentId ?? null,
      });

      if (!result) {
        return {
          success: false,
          message: "No organization workspace is configured, so the automation candidate could not be saved.",
          error: "No organization workspace is configured",
        };
      }

      return {
        success: true,
        entityId: result.candidateId,
        message: `${result.message}. The marketing workspace now has a saved automation candidate awaiting review.`,
        data: result,
      };
    }

    case "analyze_seo_opportunity": {
      const { getPlaybook } = await import("@/lib/tak/marketing-playbooks");

      const config = await prisma.storefrontConfig.findFirst({
        include: {
          archetype: { select: { archetypeId: true, name: true, category: true, ctaType: true } },
          items: {
            where: { isActive: true },
            select: { name: true, description: true, ctaType: true },
            orderBy: { sortOrder: "asc" },
            take: 20,
          },
          sections: {
            where: { isVisible: true },
            select: { type: true, title: true },
            orderBy: { sortOrder: "asc" },
          },
        },
      });

      if (!config) {
        return { success: true, message: "No storefront configured. Set up your storefront first at /storefront/setup." };
      }

      const playbook = getPlaybook(config.archetype.category, config.archetype.ctaType);

      // Get organization location if available
      const org = await prisma.organization.findFirst({
        select: { address: true, name: true },
      });
      const address = org?.address as Record<string, string> | null;
      const location = address?.city
        ? `${address.city}${address.region ? `, ${address.region}` : ""}${address.country ? `, ${address.country}` : ""}`
        : null;

      // Build suggested search intents from services + location
      const serviceNames = config.items.map((i) => i.name);
      const locationSuffix = location ? ` ${address?.city}` : " near me";
      const suggestedSearchIntents = serviceNames.slice(0, 5).map((name) => `${name.toLowerCase()}${locationSuffix}`);

      // Add generic archetype-based intents
      if (config.archetype.category === "healthcare-wellness") {
        suggestedSearchIntents.push(`${config.archetype.name.toLowerCase()} accepting patients${locationSuffix}`);
      } else if (config.archetype.category === "trades-maintenance") {
        suggestedSearchIntents.push(`emergency ${config.archetype.name.toLowerCase()}${locationSuffix}`);
      } else if (config.archetype.category === "food-hospitality") {
        suggestedSearchIntents.push(`best ${config.archetype.name.toLowerCase()}${locationSuffix}`);
      }

      return {
        success: true,
        message: `SEO context for ${config.archetype.name}`,
        data: {
          businessType: config.archetype.name,
          archetype: {
            category: config.archetype.category,
            name: config.archetype.name,
          },
          location,
          services: serviceNames,
          existingContent: config.sections.map((s) => s.title || s.type),
          playbook: {
            primaryGoal: playbook.primaryGoal,
            campaignTypes: playbook.campaignTypes,
          },
          suggestedSearchIntents,
        },
      };
    }

    case "generate_custom_archetype": {
      const businessName = String(params["businessName"] ?? "Custom Business");
      const businessDescription = String(params["businessDescription"] ?? "");
      const offerings = Array.isArray(params["offerings"]) ? params["offerings"] as string[] : [];
      const primaryCtaType = String(params["primaryCtaType"] ?? "inquiry");
      const stakeholderLabel = typeof params["stakeholderLabel"] === "string" ? params["stakeholderLabel"] : "Customers";
      const portalLabel = typeof params["portalLabel"] === "string" ? params["portalLabel"] : "Portal";
      const closestCategory = typeof params["closestCategory"] === "string" ? params["closestCategory"] : "professional-services";

      if (offerings.length === 0) {
        return { success: false, message: "At least one offering is required in the 'offerings' array." };
      }

      // Generate archetypeId
      const slug = businessName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
      const archetypeId = `custom-${slug}`;

      // Check for duplicate
      const existingArch = await prisma.storefrontArchetype.findUnique({ where: { archetypeId } });
      if (existingArch) {
        return { success: false, message: `Archetype "${archetypeId}" already exists. Choose a different name.` };
      }

      // Infer price type from CTA
      const defaultPriceType: Record<string, string> = {
        booking: "per-session", purchase: "fixed", inquiry: "quote", donation: "donation", mixed: "fixed",
      };

      // Generate item templates from offerings
      const itemTemplates = offerings.map((name) => ({
        name,
        description: "",
        priceType: defaultPriceType[primaryCtaType] ?? "quote",
        ...(primaryCtaType === "booking" ? { bookingDurationMinutes: 60 } : {}),
      }));

      // Generate section templates
      const sectionTemplates = [
        { type: "hero", title: "Welcome", sortOrder: 0 },
        { type: "items", title: offerings.length > 3 ? "What We Offer" : "Services", sortOrder: 1 },
        { type: "about", title: "About Us", sortOrder: 2 },
        { type: "gallery", title: "Gallery", sortOrder: 3 },
        { type: "testimonials", title: "Testimonials", sortOrder: 4 },
        { type: "contact", title: "Get in Touch", sortOrder: 5 },
      ];

      // Generate form schema
      const formSchema = [
        { name: "name", label: "Name", type: "text", required: true },
        { name: "email", label: "Email", type: "email", required: true },
        { name: "phone", label: "Phone", type: "tel", required: false },
        { name: "message", label: "Message", type: "textarea", required: false },
      ];

      // Generate tags from business name and offerings
      const tags = [
        ...businessName.toLowerCase().split(/\s+/),
        ...offerings.map((o) => o.toLowerCase()),
      ].slice(0, 15);

      const category = closestCategory === "custom" ? slug : closestCategory;

      const archetype = await prisma.storefrontArchetype.create({
        data: {
          archetypeId,
          name: businessName,
          category,
          ctaType: primaryCtaType === "mixed" ? "inquiry" : primaryCtaType,
          itemTemplates,
          sectionTemplates,
          formSchema,
          tags,
          isActive: true,
          isBuiltIn: false,
          customVocabulary: { portalLabel, stakeholderLabel },
        },
      });

      return {
        success: true,
        entityId: archetype.archetypeId,
        message: `Custom archetype "${businessName}" created as ${archetypeId}. You can now select it in the setup wizard.`,
        data: {
          archetypeId: archetype.archetypeId,
          name: archetype.name,
          category: archetype.category,
          ctaType: archetype.ctaType,
          itemCount: itemTemplates.length,
          sectionCount: sectionTemplates.length,
        },
      };
    }

    case "assess_archetype_refinement": {
      const config = await prisma.storefrontConfig.findFirst({
        include: { archetype: true },
      });

      if (!config) {
        return { success: false, message: "No storefront configured." };
      }

      const archetype = config.archetype;
      const originalItems = (archetype.itemTemplates as Array<{ name: string }>) ?? [];
      const originalSections = (archetype.sectionTemplates as Array<{ type: string; title: string }>) ?? [];

      const [liveItems, liveSections] = await Promise.all([
        prisma.storefrontItem.findMany({
          where: { storefrontId: config.id },
          select: { name: true, category: true, ctaType: true, priceType: true, isActive: true },
          orderBy: { sortOrder: "asc" },
        }),
        prisma.storefrontSection.findMany({
          where: { storefrontId: config.id },
          select: { type: true, title: true, isVisible: true },
          orderBy: { sortOrder: "asc" },
        }),
      ]);

      const originalItemNames = new Set(originalItems.map((i) => i.name));
      const liveItemNames = new Set(liveItems.map((i) => i.name));

      const itemsAdded = liveItems.filter((i) => !originalItemNames.has(i.name) && i.isActive)
        .map((i) => ({ name: i.name, ctaType: i.ctaType, priceType: i.priceType, category: i.category }));
      const itemsRemoved = originalItems.filter((i) => !liveItemNames.has(i.name)).map((i) => i.name);
      const itemsDeactivated = liveItems.filter((i) => originalItemNames.has(i.name) && !i.isActive).map((i) => i.name);
      const categoriesUsed = [...new Set(liveItems.map((i) => i.category).filter(Boolean))];

      const originalSectionTypes = new Set(originalSections.map((s) => s.type));
      const sectionsAdded = liveSections.filter((s) => !originalSectionTypes.has(s.type) && s.isVisible)
        .map((s) => ({ type: s.type, title: s.title }));
      const sectionsHidden = liveSections.filter((s) => originalSectionTypes.has(s.type) && !s.isVisible)
        .map((s) => s.type);

      const hasChanges = itemsAdded.length > 0 || itemsRemoved.length > 0 || itemsDeactivated.length > 0 ||
        sectionsAdded.length > 0 || sectionsHidden.length > 0 || categoriesUsed.length > 0;

      const summaryParts: string[] = [];
      if (itemsAdded.length > 0) summaryParts.push(`${itemsAdded.length} item(s) added`);
      if (itemsRemoved.length > 0) summaryParts.push(`${itemsRemoved.length} template item(s) removed`);
      if (itemsDeactivated.length > 0) summaryParts.push(`${itemsDeactivated.length} template item(s) deactivated`);
      if (sectionsAdded.length > 0) summaryParts.push(`${sectionsAdded.length} section(s) added`);
      if (sectionsHidden.length > 0) summaryParts.push(`${sectionsHidden.length} section(s) hidden`);
      if (categoriesUsed.length > 0) summaryParts.push(`categories: ${categoriesUsed.join(", ")}`);

      return {
        success: true,
        message: hasChanges
          ? `Your ${archetype.name} configuration has diverged from the original template: ${summaryParts.join("; ")}. These refinements could improve the template for future users of this business type.`
          : `Your configuration matches the original ${archetype.name} template — no refinements to contribute.`,
        data: {
          archetypeId: archetype.archetypeId,
          archetypeName: archetype.name,
          isBuiltIn: archetype.isBuiltIn,
          hasChanges,
          changes: { itemsAdded, itemsRemoved, itemsDeactivated, categoriesUsed, sectionsAdded, sectionsHidden },
        },
      };
    }

    // ─── Admin Coworker Tools (TAK-ADMIN-001) ────────────────────────────
    // All admin tools audit-log every call to AdminActivity.

    case "admin_view_logs": {
      const service = String(params.service ?? "");
      const lines = Math.min(Number(params.lines) || 100, 500);
      const ALLOWED_SERVICES = ["portal", "sandbox", "postgres", "neo4j", "qdrant", "portal-init", "browser-use"];
      if (!ALLOWED_SERVICES.includes(service)) {
        return { success: false, error: `Invalid service. Allowed: ${ALLOWED_SERVICES.join(", ")}`, message: `Unknown service "${service}".` };
      }
      try {
        const { exec: execCb } = lazyChildProcess();
        const { promisify } = lazyUtil();
        const execAsync = promisify(execCb);
        const { stdout } = await execAsync(`docker compose logs ${service} --tail ${lines} --no-color 2>&1`, {
          cwd: process.env.PROJECT_ROOT || "/app",
          timeout: 15_000,
        });
        await logAdminActivity(userId, "admin_view_logs", { service, lines }, "success", 1, stdout.slice(0, 500));
        return { success: true, message: `Last ${lines} lines from ${service}:`, data: { service, output: stdout.slice(0, 30000) } };
      } catch (err) {
        const msg = (err as Error).message?.slice(0, 500) ?? "Failed";
        await logAdminActivity(userId, "admin_view_logs", { service, lines }, "success", 1, msg);
        return { success: true, message: `Logs from ${service}:`, data: { service, output: msg } };
      }
    }

    case "admin_query_db": {
      const sql = String(params.sql ?? "").trim();
      if (!sql) return { success: false, error: "sql is required.", message: "Provide a SQL query." };
      // Only allow SELECT (and WITH ... SELECT)
      // CodeQL #23 (js/polynomial-redos): bound the unbounded `.*` and
      // `[\s\S]*?` runs so adversarial SQL with thousands of `-` or
      // `/*` chars can't trigger polynomial backtracking. SQL queries
      // in this tool are bounded by the admin tool surface; 100k chars
      // is well above any legitimate query.
      const normalized = sql
        .slice(0, 100_000)
        .replace(/--[^\n]{0,10000}/gm, "")
        .replace(/\/\*[\s\S]{0,10000}?\*\//g, "")
        .trim();
      if (!/^(SELECT|WITH)\b/i.test(normalized)) {
        await logAdminActivity(userId, "admin_query_db", { sql }, "blocked", 1, "Only SELECT queries permitted");
        return { success: false, error: "Only SELECT queries are permitted.", message: "This tool is read-only. Use SELECT statements only." };
      }
      if (/\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|GRANT|REVOKE)\b/i.test(normalized)) {
        await logAdminActivity(userId, "admin_query_db", { sql }, "blocked", 1, "DML/DDL detected in query");
        return { success: false, error: "Query contains forbidden keywords (INSERT, UPDATE, DELETE, DROP, etc).", message: "This tool is read-only." };
      }
      try {
        const result = await prisma.$queryRawUnsafe(sql + " LIMIT 1000") as unknown[];
        const preview = JSON.stringify(result).slice(0, 500);
        await logAdminActivity(userId, "admin_query_db", { sql }, "success", 1, `${result.length} rows. ${preview}`);
        return { success: true, message: `Query returned ${result.length} row(s).`, data: { sql, rows: result, rowCount: result.length } };
      } catch (err) {
        const msg = (err as Error).message?.slice(0, 500) ?? "Query failed";
        await logAdminActivity(userId, "admin_query_db", { sql }, "success", 1, msg);
        return { success: false, error: msg, message: `Query failed: ${msg}` };
      }
    }

    case "admin_read_file": {
      const filePath = String(params.path ?? "");
      if (!filePath) return { success: false, error: "path is required.", message: "Provide a file path." };
      const { resolve, join } = lazyPath();
      const { readFile } = lazyFsPromises();
      const root = process.env.PROJECT_ROOT ? resolve(process.env.PROJECT_ROOT) : resolve(process.cwd(), "..", "..");
      const resolved = resolve(join(root, filePath));
      if (!resolved.startsWith(root)) {
        await logAdminActivity(userId, "admin_read_file", { path: filePath }, "blocked", 1, "Path traversal");
        return { success: false, error: "Path traversal blocked.", message: "File path must be within the project directory." };
      }
      // Block sensitive files
      const lower = filePath.toLowerCase();
      if (/\.(env|key|pem)$/.test(lower) || /secret/i.test(lower) || lower.includes(".env")) {
        await logAdminActivity(userId, "admin_read_file", { path: filePath }, "blocked", 1, "Sensitive file");
        return { success: false, error: "Sensitive file blocked.", message: "Cannot read .env, .key, .pem, or *secret* files through this tool." };
      }
      try {
        const raw = await readFile(resolved, "utf-8");
        const allLines = raw.split("\n");
        const offset = params.offset ? Number(params.offset) : 1;
        const limit = params.limit ? Number(params.limit) : allLines.length;
        const startLine = offset - 1;
        const slice = allLines.slice(startLine, startLine + limit);
        const numbered = slice.map((line: string, i: number) => `${String(startLine + i + 1).padStart(6)}\t${line}`).join("\n");
        await logAdminActivity(userId, "admin_read_file", { path: filePath, offset, limit }, "success", 1, `${slice.length} lines`);
        return { success: true, message: `File: ${filePath} (${slice.length} lines)`, data: { path: filePath, content: numbered } };
      } catch {
        return { success: false, error: `File not found: ${filePath}`, message: `Could not read ${filePath}` };
      }
    }

    case "admin_restart_service": {
      const service = String(params.service ?? "");
      const ALLOWED = ["portal", "sandbox", "postgres", "neo4j", "qdrant"];
      if (!ALLOWED.includes(service)) {
        return { success: false, error: `Invalid service. Allowed: ${ALLOWED.join(", ")}`, message: `Unknown service "${service}".` };
      }
      try {
        const { exec: execCb } = lazyChildProcess();
        const { promisify } = lazyUtil();
        const execAsync = promisify(execCb);
        await logAdminActivity(userId, "admin_restart_service", { service }, "success", 2, `Restarting ${service}`);
        const { stdout } = await execAsync(`docker compose restart ${service} 2>&1`, {
          cwd: process.env.PROJECT_ROOT || "/app",
          timeout: 60_000,
        });
        return { success: true, message: `Restarted ${service}.`, data: { service, output: stdout.slice(0, 2000) } };
      } catch (err) {
        const msg = (err as Error).message?.slice(0, 500) ?? "Restart failed";
        return { success: false, error: msg, message: `Failed to restart ${service}: ${msg}` };
      }
    }

    case "admin_run_migration": {
      try {
        const { exec: execCb } = lazyChildProcess();
        const { promisify } = lazyUtil();
        const execAsync = promisify(execCb);
        await logAdminActivity(userId, "admin_run_migration", {}, "success", 2, "Running prisma migrate deploy");
        const { stdout, stderr } = await execAsync(
          `docker compose exec -T portal pnpm --filter @dpf/db exec prisma migrate deploy 2>&1`,
          { cwd: process.env.PROJECT_ROOT || "/app", timeout: 120_000 },
        );
        const output = (stdout + "\n" + stderr).trim();
        return { success: true, message: "Migration deploy complete.", data: { output: output.slice(0, 5000) } };
      } catch (err) {
        const msg = (err as Error).message?.slice(0, 1000) ?? "Migration failed";
        return { success: false, error: msg, message: `Migration failed: ${msg}` };
      }
    }

    case "admin_run_seed": {
      try {
        const { exec: execCb } = lazyChildProcess();
        const { promisify } = lazyUtil();
        const execAsync = promisify(execCb);
        await logAdminActivity(userId, "admin_run_seed", {}, "success", 2, "Running seed");
        const { stdout, stderr } = await execAsync(
          `docker compose exec -T portal pnpm --filter @dpf/db run seed 2>&1`,
          { cwd: process.env.PROJECT_ROOT || "/app", timeout: 300_000 },
        );
        const output = (stdout + "\n" + stderr).trim();
        return { success: true, message: "Seed complete.", data: { output: output.slice(0, 5000) } };
      } catch (err) {
        const msg = (err as Error).message?.slice(0, 1000) ?? "Seed failed";
        return { success: false, error: msg, message: `Seed failed: ${msg}` };
      }
    }

    case "admin_run_command": {
      const command = String(params.command ?? "").trim();
      if (!command) return { success: false, error: "command is required.", message: "Provide a command." };

      // Allowlist: only docker compose, git, and pnpm commands
      if (!/^(docker compose|git|pnpm)\b/.test(command)) {
        await logAdminActivity(userId, "admin_run_command", { command }, "blocked", 3, "Command not in allowlist");
        return {
          success: false,
          error: "Only docker compose, git, and pnpm commands are permitted.",
          message: `Command blocked: "${command.slice(0, 80)}". Only docker compose, git, and pnpm commands are allowed.`,
        };
      }

      // Block destructive patterns
      const ADMIN_BLOCKED = [
        /rm\s+-rf/i,
        /docker compose\s+down/i,
        /git\s+push/i,
        /git\s+reset\s+--hard/i,
        /prisma\s+migrate\s+reset/i,
        /curl\s+.*\|\s*(ba)?sh/i,
        /wget\s+.*\|\s*(ba)?sh/i,
        /--privileged/i,
        /--force/i,
      ];
      const blocked = ADMIN_BLOCKED.find(p => p.test(command));
      if (blocked) {
        await logAdminActivity(userId, "admin_run_command", { command }, "blocked", 3, "Destructive command blocked");
        return {
          success: false,
          error: "Destructive command blocked by safety policy.",
          message: `This command is blocked: "${command.slice(0, 80)}". Destructive operations (rm -rf, docker compose down, git push, --force) require manual execution in the terminal.`,
        };
      }

      try {
        const { exec: execCb } = lazyChildProcess();
        const { promisify } = lazyUtil();
        const execAsync = promisify(execCb);
        await logAdminActivity(userId, "admin_run_command", { command }, "success", 2, `Running: ${command.slice(0, 200)}`);
        const { stdout, stderr } = await execAsync(command + " 2>&1", {
          cwd: process.env.PROJECT_ROOT || "/app",
          timeout: 60_000,
        });
        const output = (stdout + "\n" + stderr).trim();
        return { success: true, message: `Command completed.`, data: { command, output: output.slice(0, 15000) } };
      } catch (err) {
        const execErr = err as { stdout?: string; stderr?: string; message?: string };
        const output = ((execErr.stdout ?? "") + "\n" + (execErr.stderr ?? "")).trim();
        if (output) return { success: true, message: "Command exited with error.", data: { command, output: output.slice(0, 15000) } };
        return { success: false, error: (execErr.message ?? "Command failed").slice(0, 1000), message: `Failed: ${command.slice(0, 80)}` };
      }
    }

    case "start_deliberation": {
      const { startDeliberation } = await import("@/lib/actions/deliberation");
      try {
        const result = await startDeliberation({
          userId,
          patternSlug: String(params["patternSlug"] ?? ""),
          taskRunId: typeof params["taskRunId"] === "string" ? params["taskRunId"] : undefined,
          artifactType: params["artifactType"] as import("@/lib/deliberation/types").DeliberationArtifactType,
          strategyProfile:
            typeof params["strategyProfile"] === "string"
              ? (params["strategyProfile"] as import("@/lib/deliberation/types").DeliberationStrategyProfile)
              : undefined,
          maxBranches:
            typeof params["maxBranches"] === "number" ? params["maxBranches"] : undefined,
          budgetUsd:
            typeof params["budgetUsd"] === "number" ? params["budgetUsd"] : undefined,
          stage:
            typeof params["stage"] === "string"
              ? (params["stage"] as "ideate" | "plan" | "build" | "review" | "ship")
              : undefined,
          riskLevel:
            typeof params["riskLevel"] === "string"
              ? (params["riskLevel"] as "low" | "medium" | "high" | "critical")
              : undefined,
          routeContext:
            typeof params["routeContext"] === "string"
              ? params["routeContext"]
              : context?.routeContext,
          threadId: context?.threadId,
        });
        return {
          success: true,
          entityId: result.deliberationRunId,
          message: result.reason,
          data: {
            deliberationRunId: result.deliberationRunId,
            triggerSource: result.triggerSource,
            reason: result.reason,
          },
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        // CodeQL #52 (js/tainted-format-string): the error message ended up
        // being used as a format string downstream. Keep the diagnostic in
        // `error:` (raw value) and use a constant `message:` so downstream
        // log/format consumers see no user-controlled format specifiers.
        return { success: false, error: msg, message: "start_deliberation failed" };
      }
    }

    case "get_deliberation_status": {
      const { getDeliberationStatus } = await import("@/lib/actions/deliberation");
      try {
        const result = await getDeliberationStatus({
          deliberationRunId: String(params["deliberationRunId"] ?? ""),
          userId,
        });
        return {
          success: true,
          entityId: result.deliberationRunId,
          message: `Deliberation ${result.deliberationRunId} — ${result.consensusState}`,
          data: result as unknown as Record<string, unknown>,
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return { success: false, error: msg, message: `get_deliberation_status failed: ${msg}` };
      }
    }

    case "get_deliberation_outcome": {
      const { getDeliberationOutcome } = await import("@/lib/actions/deliberation");
      try {
        const result = await getDeliberationOutcome({
          deliberationRunId: String(params["deliberationRunId"] ?? ""),
          userId,
        });
        return {
          success: true,
          message: result.outcome
            ? `Outcome ready — ${result.claims.length} claim(s), ${result.evidenceBundles.length} bundle(s).`
            : "Outcome not yet synthesized.",
          data: result as unknown as Record<string, unknown>,
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return { success: false, error: msg, message: `get_deliberation_outcome failed: ${msg}` };
      }
    }

    case "trigger_contributor_inventory_sync": {
      // BI-063BDF1B Phase 5 — admin-scope handle for agents to dispatch the
      // on-demand Inngest event. The runner is contributorInventorySyncOnDemand
      // in apps/web/lib/queue/functions/contributor-inventory-sync.ts.
      const reason = typeof params["reason"] === "string" ? params["reason"] : null;
      try {
        const { inngest } = await import("@/lib/queue/inngest-client");
        const result = await inngest.send({
          name: "ops/contributor-inventory-sync.run",
          data: { triggeredBy: reason ? `mcp:${reason}` : "mcp" },
        });
        return {
          success: true,
          message: "Queued an on-demand contributor inventory sync.",
          data: { eventIds: result.ids, status: "queued" },
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return {
          success: false,
          error: msg,
          message: `trigger_contributor_inventory_sync failed: ${msg}`,
        };
      }
    }

    case "summarize_upgrade_impact": {
      // BI-C26F7EE1 — on-demand install-tailored upgrade impact summary.
      // Read-only, advisory; does not queue or apply anything.
      const refresh = params["refresh"] === true;
      const skipPhrasing = params["skipPhrasing"] === true;
      const topNRaw = params["topN"];
      const topN =
        typeof topNRaw === "number" && Number.isFinite(topNRaw) && topNRaw > 0
          ? Math.floor(topNRaw)
          : undefined;
      try {
        const { summarizeUpgradeImpact } = await import("@/lib/self-upgrade/impact");
        const result = await summarizeUpgradeImpact({ refresh, skipPhrasing, topN });
        if (!result.ok) {
          return {
            success: true,
            message: result.detail,
            data: { ok: false, reason: result.reason, detail: result.detail },
          };
        }
        return {
          success: true,
          message: result.summary.phrased?.headline
            ?? `Upgrade impact summary: ${result.summary.counts.total} commit(s) since the last lineage marker.`,
          data: result.summary as unknown as Record<string, unknown>,
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return {
          success: false,
          error: msg,
          message: `summarize_upgrade_impact failed: ${msg}`,
        };
      }
    }

    // ─── Pseudo-User Contract: screen_* view-command handlers (BI-DF6079E9) ─
    // Each handler validates its args and returns a structured event payload
    // under `data.event`. The chat SSE relay (a follow-on integration —
    // tracked as the second half of this BI) will plumb the payload through
    // /api/agent/stream so the client's screen-event listener can dispatch
    // the corresponding action on the page via its registered manifest. For
    // now the handler shape is testable and grant-checked; the actual SSE
    // emission is wired by the chat handler in a focused follow-on PR.

    case "screen_describe": {
      const routeContext = context?.routeContext;
      return {
        success: true,
        message: routeContext
          ? `Active screen manifest discovery requested for ${routeContext}.`
          : "Active screen manifest discovery requested (no routeContext plumbed).",
        data: {
          event: {
            type: "screen:describe_requested",
            payload: { routeContext: routeContext ?? null },
          },
          // The actual manifest summary will be returned by the chat handler
          // once it plumbs the active manifest into the executeTool context
          // (tracked in BI-DF6079E9 follow-on). Returning the request shape
          // here makes the tool callable and grant-checked today.
          note: "Manifest summary plumbing lands in BI-DF6079E9 follow-on.",
        },
      };
    }

    case "screen_get_state": {
      return {
        success: true,
        message: "Screen state snapshot requested.",
        data: {
          event: {
            type: "screen:state_requested",
            payload: { routeContext: context?.routeContext ?? null },
          },
          note: "Screen state plumbing lands in BI-DF6079E9 follow-on.",
        },
      };
    }

    case "screen_select_entity": {
      const selectionId = typeof params["selectionId"] === "string" ? params["selectionId"].trim() : "";
      const entityId = typeof params["entityId"] === "string" ? params["entityId"].trim() : "";
      if (!selectionId || !entityId) {
        return {
          success: false,
          error: "missing_args",
          message: "screen_select_entity requires non-empty selectionId and entityId.",
        };
      }
      return {
        success: true,
        message: `Selection ${selectionId} → ${entityId} dispatched.`,
        data: {
          event: {
            type: "screen:select_entity",
            payload: { selectionId, entityId },
          },
        },
      };
    }

    case "screen_navigate": {
      const route = typeof params["route"] === "string" ? params["route"].trim() : "";
      if (!route) {
        return {
          success: false,
          error: "missing_args",
          message: "screen_navigate requires a non-empty route.",
        };
      }
      const navParams =
        params["params"] && typeof params["params"] === "object" && !Array.isArray(params["params"])
          ? (params["params"] as Record<string, unknown>)
          : undefined;
      return {
        success: true,
        message: `Navigation to ${route} dispatched.`,
        data: {
          event: {
            type: "screen:navigate",
            payload: navParams ? { route, params: navParams } : { route },
          },
        },
      };
    }

    case "screen_open_panel":
    case "screen_close_panel": {
      const panelId = typeof params["panelId"] === "string" ? params["panelId"].trim() : "";
      if (!panelId) {
        return {
          success: false,
          error: "missing_args",
          message: `${toolName} requires a non-empty panelId.`,
        };
      }
      const opening = toolName === "screen_open_panel";
      return {
        success: true,
        message: `Panel ${panelId} ${opening ? "open" : "close"} dispatched.`,
        data: {
          event: {
            type: opening ? "screen:open_panel" : "screen:close_panel",
            payload: { panelId },
          },
        },
      };
    }

    case "screen_focus_field": {
      const formId = typeof params["formId"] === "string" ? params["formId"].trim() : "";
      const fieldId = typeof params["fieldId"] === "string" ? params["fieldId"].trim() : "";
      if (!formId || !fieldId) {
        return {
          success: false,
          error: "missing_args",
          message: "screen_focus_field requires non-empty formId and fieldId.",
        };
      }
      return {
        success: true,
        message: `Focus dispatched: ${formId}/${fieldId}.`,
        data: { event: { type: "screen:focus_field", payload: { formId, fieldId } } },
      };
    }

    case "screen_set_input": {
      const formId = typeof params["formId"] === "string" ? params["formId"].trim() : "";
      const fieldId = typeof params["fieldId"] === "string" ? params["fieldId"].trim() : "";
      if (!formId || !fieldId) {
        return {
          success: false,
          error: "missing_args",
          message: "screen_set_input requires non-empty formId and fieldId.",
        };
      }
      if (!("value" in params)) {
        return {
          success: false,
          error: "missing_args",
          message: "screen_set_input requires a value (any type).",
        };
      }
      return {
        success: true,
        message: `Field ${formId}/${fieldId} set dispatched (visual cue BI-5696B4D7 will land alongside this in the client).`,
        data: {
          event: {
            type: "screen:set_input",
            payload: { formId, fieldId, value: params["value"] },
          },
        },
      };
    }

    case "screen_scroll_to": {
      const anchor = typeof params["anchor"] === "string" ? params["anchor"].trim() : "";
      if (!anchor) {
        return {
          success: false,
          error: "missing_args",
          message: "screen_scroll_to requires a non-empty anchor.",
        };
      }
      return {
        success: true,
        message: `Scroll to ${anchor} dispatched.`,
        data: { event: { type: "screen:scroll_to", payload: { anchor } } },
      };
    }

    case "screen_propose_action": {
      const manifestActionId =
        typeof params["manifestActionId"] === "string" ? params["manifestActionId"].trim() : "";
      const rationale = typeof params["rationale"] === "string" ? params["rationale"].trim() : "";
      if (!manifestActionId || !rationale) {
        return {
          success: false,
          error: "missing_args",
          message: "screen_propose_action requires manifestActionId and rationale.",
        };
      }
      const args =
        params["args"] && typeof params["args"] === "object" && !Array.isArray(params["args"])
          ? (params["args"] as Record<string, unknown>)
          : {};
      // The actual CoworkerActionEnvelope row creation (and per-turn N=3 cap
      // + irreversible typed-phrase floor) lands in BI-0F9C291C. Until then
      // the tool returns the structured proposal payload so the agent can
      // see what would be proposed, and the chat handler can stub-write the
      // envelope when wiring SSE emission.
      return {
        success: true,
        message: `Proposal '${manifestActionId}' dispatched.`,
        data: {
          event: {
            type: "screen:action_proposed",
            payload: { manifestActionId, rationale, args },
          },
          note: "Envelope creation + per-turn cap enforcement lands in BI-0F9C291C.",
        },
      };
    }

    case "screen_dispatch_action": {
      const envelopeId =
        typeof params["envelopeId"] === "string" ? params["envelopeId"].trim() : "";
      if (!envelopeId) {
        return {
          success: false,
          error: "missing_args",
          message: "screen_dispatch_action requires an envelopeId.",
        };
      }
      return {
        success: true,
        message: `Envelope ${envelopeId} dispatch requested.`,
        data: {
          event: {
            type: "screen:dispatch_requested",
            payload: { envelopeId },
          },
          note: "Envelope execution (read row → run underlying tool → mark executed) lands in BI-0F9C291C.",
        },
      };
    }

    default: {
      const { parseNamespacedTool, executeMcpServerTool } = await import("./mcp-server-tools");
      const parsed = parseNamespacedTool(toolName);
      if (parsed) {
        return executeMcpServerTool(parsed.serverSlug, parsed.toolName, params);
      }
      // CodeQL #52 (js/tainted-format-string): toolName is user-influenced.
      // Use a constant message and put the raw value in error for diagnostics.
      return { success: false, error: "Unknown tool", message: "Tool not found" };
    }
  }
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    // CodeQL #52 (js/tainted-format-string) + js/log-injection: keep the
    // format string constant so a `%s` inside an attacker-controlled
    // toolName cannot consume the next argument, AND JSON.stringify each
    // tainted positional arg so CR/LF can't forge log lines.
    console.error("[executeTool] Uncaught exception in tool %s: %s",
      JSON.stringify(toolName), JSON.stringify(msg));
    return { success: false, error: msg, message: `Tool ${toolName} failed: ${msg}` };
  }
}

// ─── Convert to provider format ──────────────────────────────────────────────

export function toolsToOpenAIFormat(tools: ToolDefinition[]): Array<Record<string, unknown>> {
  return tools.map((t) => ({
    type: "function",
    function: {
      name: t.name,
      description: t.description,
      parameters: t.inputSchema,
      annotations: resolveAnnotations(t),
    },
  }));
}
