import type { IntakeApprovalMode } from "./issue-order.js";
export type { IntakeApprovalMode };
/** The accepted intake-approval modes (#448), in one place so the resolver's
* allowlist and the `IntakeApprovalMode` union cannot drift apart. */
export declare const INTAKE_APPROVAL_MODES: readonly ["code-org", "reporter", "off"];
/** The active profile name, or "" for the default (unprofiled) store. Selected
* by the global `--profile` flag (which exports SHIPFLOW_PROFILE) or the env
* var directly. */
export declare function activeProfile(): string;
/** The effective config directory. Each profile is an isolated
* credentials/projects/config store under `/profiles/`, so one
* machine can stay signed into several tenants at once. The default (no
* profile) keeps using `` so existing single-tenant setups are unchanged. */
export declare function configDir(): string;
/** Names of the profiles that have a config dir under `/profiles`. The
* default (unprofiled) store is not a profile and is reported separately. */
export declare function listProfiles(): string[];
/** Reads a profile's stored credentials without switching the active profile —
* for listing tenants across profiles. Pass "" for the default store. */
export declare function credentialsForProfile(name: string): StoredCredentials | null;
export interface StoredConfig {
apiUrl?: string;
defaultOrg?: string;
apiKey?: string;
autoIssue?: boolean;
liveReload?: boolean;
requireCi?: boolean;
mergePolicy?: MergePolicy;
maxFixAttempts?: number;
wipLimit?: number;
stalePrHours?: number;
bugHunt?: boolean;
bugHuntCap?: number;
requireReview?: boolean;
gitName?: string;
gitEmail?: string;
signoffOwner?: string;
conflictSweep?: boolean;
/** Intake gate (#448): who may release `needs-reporter-approval`.
* `code-org` (default) = the issue waits for someone with repo write access,
* which GitHub itself enforces — an outside account cannot remove a label.
* `reporter` additionally accepts the FILER's own confirmation, which is only
* meaningful for chat-filed issues where ShipFlow knows the exact reporter
* identity (bug_triage.go: "Only the original reporter decides").
* `off` disables the gate entirely — new arming stops AND issues already
* carrying the label become claimable again (the mode is threaded into
* `isActionableForPickup`), which is the only REPO-WIDE way back from a
* mass-arming event: no code path removes the label automatically (tracked
* in #473). Per issue, a maintainer removing the label is the release, and
* it sticks — arming is a one-time event (`decideIntakeGate`).
*
* Stored as a free string because config files are hand-edited; the resolver
* narrows it to `IntakeApprovalMode` and falls back to `code-org`. */
intakeApproval?: string;
loopWorkerModel?: string;
intentGate?: IntentGateMode;
pickupScope?: PickupScope;
cliDriftPollSeconds?: number;
appSlug?: string;
}
/** How far the loop may drive a PR toward merge without a human. */
export type MergePolicy = "manual" | "auto-on-green" | "auto-timeout";
/** The whole merge-policy vocabulary — exported (issue #669) so every surface
* that accepts a policy validates against THIS list instead of re-typing it:
* `resolveMergePolicy()` below, and the `--policy` guards on `pr ready` /
* `pr automerge`, which also render their `--policy` help text from it. */
export declare const MERGE_POLICIES: MergePolicy[];
/** Which issues the loop's `issue next` may claim (issue #600). */
export type PickupScope = "assigned" | "all";
export declare const PICKUP_SCOPES: PickupScope[];
/** Which body signals park a PR on the reporter (issue #471). */
export type IntentGateMode = "strict" | "trusted";
export declare const INTENT_GATE_MODES: IntentGateMode[];
export interface StoredCredentials {
jwt: string;
refreshToken: string;
tenantId: string;
org: string;
expiresAt: number;
}
export interface ProjectCacheEntry {
projectId: string;
projectName: string;
org: string;
tenantId: string;
}
type ProjectCache = Record;
export declare const loadConfig: () => StoredConfig;
export declare const saveConfig: (c: StoredConfig) => void;
export declare const clearConfig: () => void;
export declare const loadCredentials: () => StoredCredentials | null;
export declare const saveCredentials: (c: StoredCredentials) => void;
export declare const clearCredentials: () => void;
/** Auth options that wire transparent token refresh into a ShipFlowClient:
* the stored refresh token, plus a callback that persists rotated tokens back
* to credentials.json. Spread into `new ShipFlowClient({..., ...refreshOpts(creds)})`. */
export declare function refreshOpts(creds: StoredCredentials): {
refreshToken: string;
onRefreshed: (t: {
token: string;
refreshToken: string;
expiresAt: number;
}) => void;
};
export declare const loadProjectCache: () => ProjectCache;
export declare const saveProjectCache: (c: ProjectCache) => void;
/** Stable key for a repo path (resolves symlinks / paths to the git work tree). */
export declare function projectCacheKeyForRepoPath(absRepoRoot: string): string;
/** Parse a human boolean ("true"/"1"/"on"/"yes"). */
export declare function parseBool(v: string | undefined): boolean;
/** Whether to auto-create an issue for off-issue feature work (vs. asking).
* Env SHIPFLOW_AUTO_ISSUE wins over the stored config; defaults to false. */
export declare function resolveAutoIssue(): boolean;
/** Effective live-reload preference: env > stored config > undefined (undecided,
* so the SessionStart hook asks once). */
export declare function resolveLiveReload(): boolean | undefined;
/** Parse a non-negative integer; fall back when absent/invalid. */
export declare function parseIntOr(v: string | number | undefined, fallback: number): number;
/** Boolean spellings `config set` accepts, true then false. */
export declare const BOOL_TRUE_WORDS: string[];
export declare const BOOL_FALSE_WORDS: string[];
/** Parse a boolean typed at `config set`, or throw naming every accepted
* spelling. Accepts the same truthy words as `parseBool`; the difference is
* that a value matching neither list is an error, not a `false`. */
export declare function parseBoolStrict(key: string, v: string): boolean;
/** Parse a non-negative whole number typed at `config set`, or throw. Digits
* only: this rejects `abc` (which `parseIntOr` turns into the default), `1e3`
* and `3.7` (which `parseInt` truncates to `1` and `3`), and `-1`. */
export declare function parseIntStrict(key: string, v: string): number;
/** Require CI green before a PR is "advanced" (env > config > true). */
export declare function resolveRequireCi(): boolean;
/** Effective merge policy (env > config > "manual"). Invalid values fall back. */
export declare function resolveMergePolicy(): MergePolicy;
/** Max CI-fix attempts on one PR before escalating (env > config > 3). */
export declare function resolveMaxFixAttempts(): number;
/** Max concurrent open PRs before the loop stops admitting work (env > config > 10). */
export declare function resolveWipLimit(): number;
/** Hours before a parked (green, unreviewed) PR is "stale" (env > config > 48). */
export declare function resolveStalePrHours(): number;
/** Run the empty-queue bug sweep + auto-file issues (env > config > true). */
export declare function resolveBugHunt(): boolean;
/** Max new issues the bug sweep may file per loop run (env > config > 5). */
export declare function resolveBugHuntCap(): number;
/** Route every issue + PR through the reviewer first (env > config > true). */
export declare function resolveRequireReview(): boolean;
/** Pickup scope (env > config > **"assigned"**, issue #600): `assigned` — the
* loop claims only issues assigned to the account running it (assignment IS
* the queueing gesture); `all` — the pre-#600 repo-wide pickup. Unknown values
* fall back to `assigned` (the narrow direction). */
export declare function resolvePickupScope(): PickupScope;
/** Intent-gate mode (env > config > **"strict"**). Unknown values fall back
* to strict — the gate must never fail OPEN on a typo. */
export declare function resolveIntentGateMode(): IntentGateMode;
/** Repo-wide conflict sweep over OTHER authors' PRs (env > config > **false**).
* Defaults OFF: the sweep hands the loop a third-party branch to check out and
* run, so it must be chosen, never inherited from a CLI/plugin upgrade. */
export declare function resolveConflictSweep(): boolean;
/** Effective intake-approval mode. Env wins, then stored config, else `code-org`
* — the safe default: an unset knob must not disable a control.
*
* Only `off` and `code-org` change GitHub-intake behavior today. `reporter` is
* accepted (so the knob is forward-compatible) but the reporter-side clearing
* path is chat-only and has no GitHub-intake implementation yet (tracked in
* #473), so today it gates exactly like `code-org`. Kept in the allowlist so
* the knob is forward-compatible rather than rejecting a value it will later
* honor. */
export declare function resolveIntakeApproval(): IntakeApprovalMode;
/** Sign-off owner the loop names on escalations (env > config > undefined —
* the escalate command then falls back to the issue author). A leading @ is
* tolerated and stripped so both "gemscng" and "@gemscng" work. */
export declare function resolveSignoffOwner(): string | undefined;
/** Model for loop WORKER dispatches (env > config > undefined — the loop then
* keeps the host's default per dispatch). Freeform trimmed string; no
* allowlist because valid model names are host-specific. Blank values fall
* through, so `SHIPFLOW_LOOP_WORKER_MODEL=""` doesn't mask the stored config. */
export declare function resolveLoopWorkerModel(): string | undefined;
/** Seconds to poll npm for a just-published CLI version before continuing
* degraded (env > config > 180). Covers the measured ~62s publish lag with
* headroom; the loop NEVER halts on this timer — it warns loudly and runs on. */
export declare function resolveCliDriftPollSeconds(): number;
/** Where the effective trusted slug came from. Carried with the value so the
* reader's fail-stuck diagnostic can say WHY it expected what it expected —
* the difference between a diagnosable stuck gate and an inexplicable one. */
export type AuditAuthorSlugSource = "env SHIPFLOW_APP_SLUG" | "env GITHUB_APP_SLUG" | "config app-slug" | "contract default";
export interface ResolvedAuditAuthorSlug {
/** Normalised (trimmed, lower-cased, `[bot]` stripped) and never empty. */
slug: string;
source: AuditAuthorSlugSource;
/** Set when a configured value was REFUSED as malformed and the contract
* default was used instead — so the operator sees their typo, not silence. */
rejected?: string;
}
/** Is `v` usable as an App slug, once normalised? Exported for `config set`,
* which refuses a bad value at the point the operator can still fix it. */
export declare function isValidAppSlug(v: string | undefined): boolean;
/**
* The ONE bot identity trusted to author an intent-gate clearance record, for
* THIS deployment: `SHIPFLOW_APP_SLUG` > `GITHUB_APP_SLUG` (the server's own
* var, so a machine configured for the server needs nothing extra) > stored
* `app-slug` > the contract default.
*
* `GITHUB_APP_SLUG` is read second, not first, so an operator can pin the CLI
* independently on a host whose environment already names a *different* App.
*/
export declare function resolveIntentGateAuditAuthorSlug(): ResolvedAuditAuthorSlug;
export declare function resolveApiUrl(flagUrl?: string): string;
/** JWT first, API key fallback. */
export declare function resolveAuthToken(): {
token: string;
kind: "jwt" | "apiKey";
} | null;
/** Backward-compat: existing call sites that imported resolveApiKey still work. */
export declare function resolveApiKey(): string | undefined;
//# sourceMappingURL=config.d.ts.map