import type { EstateExecFn, TotemRegistry } from '@mmnto/totem'; export type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip'; export interface DiagnosticResult { name: string; status: CheckStatus; message: string; remediation?: string; /** * Sensor-class row: its ADVISORY statuses never gate, under any `--strict` * tier (ruled scope, mmnto-ai/totem#2580). The exemption is a property of the * row rather than of the tier, so a sensor cannot acquire teeth by someone * widening the tier later — and it deliberately does NOT cover `fail`, which * always gates (see `doctorGateFailed`). */ gateExempt?: true; } /** * Gating tiers for `totem doctor --strict [tier]`: * - `fail` (bare `--strict`): exit non-zero on fail-class diagnostics only — * the pre-#2385 boolean contract. * - `warn`: exit non-zero on warn- OR fail-class diagnostics — the * machine-checkable all-wiring oracle for CI / agent consumers. * * `skip` never gates in either tier: it marks checks that are intentionally * inapplicable (e.g. unconfigured optional wiring), not gaps. */ export type StrictTier = 'fail' | 'warn'; export declare const STRICT_TIERS: readonly StrictTier[]; /** * Resolve the raw commander `--strict [tier]` value into a StrictTier. * `true` (bare flag) maps to `'fail'`. Returns `undefined` when strict mode * is off. Throws on an unknown tier (fail-loud, Tenet 4 — a silently ignored * tier would let a consumer believe it gated on more than it did). * Async solely so the error class rides the dynamic-import convention (no * static `@mmnto/totem` import in command handlers — CLI startup latency). */ export declare function resolveStrictTier(strict: boolean | string | undefined): Promise; /** * The gate predicate the CLI edge applies to `doctorCommand` results. Kept * pure and exported so the edge stays thin and the semantics stay unit-tested * (the exit-code decision itself lives at the CLI edge — see DoctorOptions). * * The exemption is scoped to ADVISORY statuses. A `fail` gates regardless of * `gateExempt`: a fail-class diagnostic is a wiring failure, and no row may be * allowed to hide one. Sensor rows never emit `fail` in the first place, so the * narrowing costs them nothing and closes the hole where a mislabelled row * could suppress a real gate. */ export declare function doctorGateFailed(results: readonly DiagnosticResult[], tier: StrictTier): boolean; export declare function checkConfig(cwd: string): DiagnosticResult; export declare function checkCompiledRules(cwd: string, totemDir?: string): DiagnosticResult; export declare function checkGitHooks(cwd: string): DiagnosticResult; /** * Sense the init-distributed prepare wrapper (mmnto-ai/totem#2410 PR-B): whether * `.totem/prepare.cjs` is present + marker-headed + canonical AND the consumer's * `package.json` `prepare` invokes it. A SENSOR, never a gate — every non-pass state * is `warn`/`skip` (doctor `--strict` gates only on `fail`), so this can never block CI. * * Remedies follow the PR-A/PR-B semantics: * - absent / wired-but-file-missing → `totem init` (adoption / scaffolding). * - present, marker-headed, but DRIFTED from canonical → `totem hook install` * (bare — the wrapper is a bounded roster member, so bare self-repair suffices). * - present + canonical but `prepare` not wired → `totem init`. * * Honest-absent (Tenet 14), so it stays quiet where absence is legitimate: * - no `package.json` → `skip`. * - wrapper absent AND a DIFFERENT user-managed `prepare` exists (the owner-repo * exception — totem itself runs `tools/install-hooks.js`) → `skip`, never a nudge. * - a user-owned `.totem/prepare.cjs` with no totem marker → `skip` (left as-is). */ export declare function checkPrepareWrapper(cwd: string): Promise; export declare function checkEmbeddingConfig(cwd: string): DiagnosticResult; /** * Probe whether the Ollama daemon is reachable. Surfaces the floor-embedder * expectation diagnostically (mmnto-ai/totem#1851) so consumers don't perceive * the well-formed `LazyEmbedder` `TotemConfigError` as a crash and reach for * a vendor-coupling workaround. Always probes regardless of configured * provider — Ollama IS the floor per Tenet 16. * * Reads `embedding.baseUrl` from config when `provider: 'ollama'` is * configured with a custom URL; otherwise probes the default * (`http://localhost:11434`). The configured-but-unreachable case for * `provider: 'ollama'` produces the same `warn` here as it does for any * other provider; the false-`pass` in `checkEmbeddingConfig` for that * exact scenario is tracked separately and intentionally left untouched * to keep this PR additive. */ export declare function checkOllama(config?: { embedding?: { provider?: string; baseUrl?: string; }; }): Promise; export declare function checkIndex(cwd: string, lanceDir?: string): DiagnosticResult; export declare function checkLinkedIndexes(cwd: string): DiagnosticResult; /** * Strategy-root resolver diagnostic (mmnto-ai/totem#1710). * * Runs `resolveStrategyRoot` and reports which precedence layer matched. * Advisory only: `warn` (not `fail`) on unresolved so a freshly-cloned * project without a strategy repo doesn't fail the doctor pass. * * Affected consumer surfaces if unresolved: MCP `describe_project` * rich-state pointer, `totem proposal new` / `totem adr new`, federated * search via the auto-injected strategy linkedIndex, the bench scripts * under `scripts/`. * * Async + dynamic import to keep `@mmnto/totem` off the CLI cold-start * graph (matches `checkSecretLeaks` and the rest of the diagnostics that * need core). */ export declare function checkStrategyRoot(cwd: string, config?: { strategyRoot?: string; }): Promise; export declare function checkSecretLeaks(cwd: string, totemDir?: string): Promise; export declare function checkSecretsFileTracked(cwd: string, totemDir?: string): DiagnosticResult; /** * Maximum byte size for a `CLAUDE.md` that's purely a thin redirect to * `AGENTS.md`. Anchored to Proposal 272 § 6.7. Empirical headroom: * the largest post-migration cohort redirect is 558 bytes. */ export declare const CLAUDE_MD_REDIRECT_MAX_BYTES = 600; /** * Minimal shape signature for a `CLAUDE.md` redirect to `AGENTS.md` * per ADR-038. The canonical phrase plus the link target is load-bearing; * surrounding wording is intentionally unconstrained so future template * tweaks survive without breaking every consumer. */ export declare const AGENTS_MD_REDIRECT_PATTERN: RegExp; export declare function checkAgentsMdCanonical(cwd: string): DiagnosticResult; /** * Pure helper: scan compiled rules + metrics and return structured upgrade candidates. * Used by both `checkUpgradeCandidates` (read-only diagnostic) and `runSelfHealing` * (auto-recompile phase). Returns null if rules/metrics cannot be loaded. * * IMPORTANT: Uses `contextCounts` (per-context match buckets), NOT `triggerCount` * (the rolled-up total). `triggerCount` includes ALL matches, not just code matches. */ export declare function findUpgradeCandidates(cwd: string, totemDir?: string): Promise; /** * Find regex/ast rules whose telemetry shows >NON_CODE_THRESHOLD of matches landing * in non-code contexts (strings, comments, regex literals). These are good candidates * for being upgraded to structural ast-grep patterns via `totem lesson compile --upgrade`. */ export declare function checkUpgradeCandidates(cwd: string, totemDir?: string): Promise; /** Bypass rate above which a rule is considered "struggling" and eligible for downgrade. */ export declare const BYPASS_THRESHOLD = 0.3; /** Minimum total events (triggers + bypasses) required before acting on a rule. */ export declare const MIN_EVENTS = 5; /** Non-code match ratio above which a regex/ast rule is flagged for ast-grep upgrade. */ export declare const NON_CODE_THRESHOLD = 0.2; /** Minimum total context events required before flagging a rule as an upgrade candidate. */ export declare const MIN_CONTEXT_EVENTS = 5; export interface UpgradeCandidate { lessonHash: string; heading: string; /** * Always `'regex'` — `findUpgradeCandidates` filters to regex rules only * because only they carry trustworthy non-code telemetry. Narrowed from * the broader engine union so the type matches the implementation. */ engine: 'regex'; total: number; codeCount: number; nonCodeRatio: number; } /** * Pure helper signature: a staleness candidate as returned by * `findStaleRules`. The `severity` distinction lets the formatter label * security rules visually distinct from standard rules without the caller * needing to re-derive the category from the compiled rule. */ export interface StaleRuleCandidate { lessonHash: string; heading: string; evaluationCount: number; severity: 'standard' | 'security'; /** The recommended next step surfaced in the advisory text. */ recommendation: string; /** Compile-metadata flags relevant to the advisory. */ flags: { unverified?: boolean; immutable?: boolean; category?: string; }; } /** * Pure helper: scan compiled rules + metrics and return structured * stale-rule candidates. A rule is stale when it has accrued at least * `staleRuleWindow` evaluations over its lifetime and has never landed a * match in code context (`contextCounts.code === 0`). * * Security rules (`category === 'security'` OR `immutable === true`) get * flagged with the `security` severity so the formatter can mark them * with a higher-severity label. Per the design doc, doctor never * recommends archival for security rules. */ export declare function findStaleRules(cwd: string, totemDir?: string, thresholds?: { staleRuleWindow: number; }): Promise; /** * Stale-rule advisory diagnostic. Returns a single DiagnosticResult * regardless of how many rules were flagged; the details list is * serialized into the `message` + `remediation` fields. Per the design * doc, this is advisory-only — no auto-archive, no side effects on the * rules file. */ export declare function checkStaleRules(cwd: string, totemDir?: string, thresholds?: { staleRuleWindow: number; }): Promise; /** * ISO timestamp for the 1.13.0 ship date. Rules whose vintage timestamp * precedes this never saw the ADR-088 Phase 1 substrate fields * (`badExample`, `goodExample`, `unverified`) during their compile. Used * by `findLegacyGrandfatheredRules` to categorize the pre-zero-trust * cohort the 2026-04-20 audit measured at 357 of 378 active rules. */ export declare const V_1_13_0_SHIP_DATE_ISO = "2026-04-07T00:00:00.000Z"; export type GrandfatheredReasonCode = 'vintage-pre-1.13.0' | 'no-badExample' | 'no-goodExample'; export interface GrandfatheredRuleCandidate { lessonHash: string; heading: string; /** Non-empty: rules with zero applicable reasons are not returned. */ reasons: GrandfatheredReasonCode[]; /** `createdAt` when present, `compiledAt` otherwise; used for the vintage check. */ vintage: string; } /** * Pure helper: scan compiled rules and return the grandfathered * pre-zero-trust cohort categorized by reason. A rule is a candidate * when it is active (`status !== 'archived'`) and lacks the `unverified` * flag from ADR-089 part 1 (mmnto-ai/totem#1581). Each candidate gets * every reason that applies: * * - `vintage-pre-1.13.0`: vintage timestamp precedes the 1.13.0 ship date. * - `no-badExample`: the rule has no authored defect preimage. * - `no-goodExample`: the rule has no authored fixed postimage. * * The two exemplar reasons read through the single-homed `ruleBadExampleLines` / * `ruleGoodExampleLines` (Prop 310 slice 3), so each one asks the rule's OWN home: * a RECORD-path rule (one carrying `examples`) is read from `examples[i].bad` / * `examples[i].good` — the § Design 10 editable home, which the lowering never * mirrors onto the legacy fields — and a legacy rule from `badExample` / * `goodExample`. A whitespace-only value counts as ABSENT on either path, which is * what keeps the legacy verdict byte-identical to the pre-slice-3 reads. * * Rules with at least one reason are returned; rules that satisfy all * three substrate checks are omitted. * * Returns `null` when `compiled-rules.json` is missing or unreadable, * matching the fallback convention used by `findStaleRules` so the * caller can render a `skip` diagnostic rather than fail the pipeline. */ export declare function findLegacyGrandfatheredRules(cwd: string, totemDir?: string): Promise; /** * Grandfathered-rule advisory diagnostic. Summarizes the pre-zero-trust * cohort by reason code. Advisory-only (`warn`): ADR-091 Stage 4 * Codebase Verifier (1.16.0, mmnto-ai/totem#1504) is the empirical * audit path; this check gives users a triage-able surface until that * ships. */ export declare function checkGrandfatheredRules(cwd: string, totemDir?: string): Promise; export interface DoctorOptions { pr?: boolean; /** * Raw commander `--strict [tier]` value (`true` for the bare flag, a string * for `--strict=`). When set, callers should treat gate-class * diagnostics as a gating condition (exit non-zero) — resolve via * `resolveStrictTier` and apply `doctorGateFailed` at the CLI edge. The flag * itself doesn't change what `doctorCommand` returns — the exit-code * decision lives at the CLI edge so this function stays composable and free * of process-exit side effects. * * Reference: mmnto-ai/totem#1908 (Proposal 273 § 6 Q2 / § 7 routing matrix * row 5); mmnto-ai/totem#2385 (warn tier — the all-wiring oracle). */ strict?: boolean | string; /** * Test seam for the ambient `Estate` row. Production callers omit it and the * row reads the real user-level registry; tests pass an empty registry so the * suite never shells git at whatever repos this machine happens to have * synced (same hermeticity reason as the mocked `fetch` for the Ollama probe). */ estateSeamsForTest?: Parameters[0]; } export declare function runSelfHealing(cwd: string): Promise; /** * Sensor-only freeze surfacing (strategy#584 read half, mmnto-ai/totem#2167). Renders the * effective freeze union (repo-local ∪ distributed cohort) with per-source * channel status — absent-package / absent-file / corrupt / genuinely-none * stay distinct (codex W1). NEVER emits 'fail': freezes are sensed state, not * drift, and doctor `--strict` gates on fail — a freeze (or a broken channel) * must report loudly without gating (Tenet 13; the gate consumer is * verify-manifest, not doctor). */ export declare function checkFreezes(cwd: string, totemDir?: string): Promise; /** * Ambient worktree-estate row (mmnto-ai/totem#2580, open question 1 ruled (a)). * Quiet when the estate is clean, a named SKIP when nothing is registered, and * a `warn` — never a `fail` — when husks or stale worktrees exist: this is a * sensor, and the detail lives behind `totem doctor --estate`. * * `@mmnto/totem` is dynamic-imported INSIDE the check, per the ruling's * constraint: a static core-barrel import here would pull core onto the CLI * cold-start path for every command. * * Every return carries `gateExempt: true`: this row is sensor-class and must * not gate under any `--strict` tier (mmnto-ai/totem#2580 ruled scope — * removal verbs and gating are later slices). */ export declare function checkEstate(seams?: { registry?: TotemRegistry; safeExec?: EstateExecFn; now?: number; /** * Test seam — bypasses the user-level `~/.totem/worktrees.json` read that * supplies the wt-registry's recorded container roots (mmnto-ai/totem#2580 * slice 2). Pinned in tests so a developer machine that has actually used * `totem wt create` cannot drag its real roots into an assertion. */ wtRoots?: string[]; }): Promise; export declare function doctorCommand(options?: DoctorOptions): Promise; //# sourceMappingURL=doctor.d.ts.map