import { type HostId } from "./host-mcp.js"; import type { MigrationTarget } from "./migration-target.js"; import { type BootstrapSource } from "./project-config-bootstrapper.js"; import type { SetupState } from "./setup-state.js"; export type SetupStatus = "already_configured" | "ready_to_apply" | "user_action_required"; export type SetupCode = "JAM_PROJECT_SELECTION_REQUIRED" | "JAM_BINDINGS_UNREADABLE" | "JAM_AUTH_REQUIRED" | "JAM_RUNTIME_CONFIG_MISSING" | "JAM_PROJECT_CONFIG_INVALID" | "JAM_MCP_CONFIG_UNREADABLE" | "JAM_MIGRATION_TARGET_UNAVAILABLE" | "JAM_PROJECT_KEY_CONFLICT"; /** * Where a project key came from. `repository` is the team's committed * `.jira-agent/project.yaml`; the rest are this user's own settings, in the * precedence order `decideProjectKey` applies. */ export type KeySource = BootstrapSource | "repository"; export type KeyOrigin = { key: string; source: KeySource; }; export type SetupChange = { type: "create"; target: "project-config"; path: string; key: string; keySource: KeySource; } | { type: "create"; target: "mcp-config"; path: string; } | { type: "merge"; target: "mcp-config"; path: string; preserveExisting: string[]; } | { type: "replace"; target: "mcp-config"; path: string; reason: "migrate"; } | { type: "create" | "replace"; target: "personal-binding"; path: string; workspaceId: string; key: string; keySource: KeySource; /** Present on a rebind, so the preview shows what is being replaced. */ previousKey?: string; } | { type: "create" | "replace"; target: "host-mcp"; host: HostId; /** * The exact argv apply will run, decided here and never recomputed - * so `plan --json` shows what is about to happen, and running a host's * CLI stays on the apply side of the line. */ command: string; args: string[]; /** * Run before the registration, on a repair. The host CLI refuses to add an * entry that already exists, so the stale one has to go first - and which * command does that is decided here, not worked out during apply. */ precede?: { command: string; args: string[]; }; /** On a repair: the launcher pin the entry runs today, so the preview names it. */ previousVersion?: string; reason?: "stale-registration"; }; export type SetupPlan = { status: SetupStatus; code?: SetupCode; changes: SetupChange[]; requiresUserAction: boolean; /** Populated by the caller when status is JAM_PROJECT_SELECTION_REQUIRED. */ projects?: { key: string; name: string; }[]; /** Why a requested migration was refused, when status is JAM_MIGRATION_TARGET_UNAVAILABLE. */ migrationTarget?: MigrationTarget; /** * What a person or an agent has to do next. * * `command` is executable on a machine with nothing installed and no runtime * configured - so it is an `npx` bootstrap invocation, never a bare `jam`. * A human interface is free to render the short form; this field is the one * a script runs, and a script has no PATH to rely on. * * `userCommand` is the opposite: a command for the person, which the agent * relays and never runs. Authentication is the only step of that shape, and * it carries no `command` precisely so that no caller can execute it. The * separation is the point - one field is for running, the other for showing. * * `env` names the variables that would satisfy the same requirement without * the interactive command, so an agent that cannot show a prompt still knows * what the person has to provide - never their values. */ nextAction?: { type: "authenticate" | "select_project" | "configure_runtime"; command?: string; userCommand?: string; env?: string[]; }; project?: { root: string; key?: string; keySource?: KeySource; }; /** On JAM_PROJECT_KEY_CONFLICT: what was asked for, and what already stands. */ requested?: KeyOrigin; existing?: KeyOrigin; }; export type PlanOptions = { /** * `--shared`: adopt JAM for the team, writing `.jira-agent/project.yaml` and * `.mcp.json` into the repository. Without it setup is personal and the * repository is left alone - discovery is allowed, adoption is asked for. */ shared?: boolean; /** `--project KEY`. */ explicitKey?: string; /** `--migrate`: rewrite a legacy jam entry instead of leaving it alone. */ migrate?: boolean; env?: NodeJS.ProcessEnv; presetsPath?: string; /** True when the existing jam entry does not match the current canonical form. */ jamEntryIsLegacy?: boolean; /** * Whether the package a `--migrate` rewrite would point at can be resolved. * Observed by the caller, like `jamEntryIsLegacy` - the planner never probes. * Absent means not verified, and an unverified target refuses the rewrite. */ migrationTarget?: MigrationTarget; }; /** * Decide what setup would do, changing nothing. * * Pure by construction: every input arrives through `state` and `options`, and * the result is a list of changes for apply to execute verbatim. Keeping the * decision separate from the mutation is what lets a human preview it, an * agent reason about it, and both go through identical logic. * * Safe Bootstrap is preserved here: a project key comes from an explicit * source or not at all. JAM never infers one from a repo or directory name. */ export declare function computeSetupPlan(state: SetupState, options?: PlanOptions): SetupPlan;