import { AihError } from "../../errors.js"; import { type PlanResult } from "../../internals/execute.js"; import { type Action } from "../../internals/plan.js"; import type { Runner } from "../../internals/proc.js"; import { type FrameworkAdapter } from "../adapter.js"; import { type ClaudeDriftEntry, type PluginCacheLocator, type PluginScope } from "../hosts/claude/index.js"; import { type BindingOwnershipEntry } from "../lock.js"; /** * The Superpowers `FrameworkAdapter` (W4a) — the FIRST real D6 adapter, * `adapterType: "host-plugin"`. It composes the W3 Claude host services * (`bindPlugin`/`removePlugin`, the D18 managed-write engine, D7 plugin * identity, conservative removal) rather than re-implementing any of them; * this module is the ORCHESTRATION layer that decides what gets bound, in * what order, and how the extra (non-`bindPlugin`) telemetry field is owned * in the same lock. * * Locked decisions (orchestrator-pinned; do not re-derive or override): * - source: repository {@link SUPERPOWERS_REPOSITORY} at the exact commit * {@link SUPERPOWERS_PIN_COMMIT} (mirrors the maintainer-locked pin in * `src/internals/baseline-sources.ts`'s "obra"/"Superpowers" entry — see * the constant's own doc comment for why it is mirrored, not imported); * - plugin name {@link SUPERPOWERS_PLUGIN_NAME}, marketplace name * {@link SUPERPOWERS_MARKETPLACE_NAME} (AIH registers the scanned * checkout as this marketplace, per the W3b mechanism); * - telemetry disable is MANDATORY at bind time: `env.SUPERPOWERS_DISABLE_TELEMETRY` * is a SECOND D18-owned field in `.claude/settings.json`, applied and * reconciled in the exact same lock as `enabledPlugins` — `bindPlugin` * does not know about it, so this adapter owns it directly through its * own {@link ClaudeManagedWriteEngine} call; * - superpowers declares NO feature flags — `plan` calls * {@link assertKnownFeatureKeys} with an empty known-key list, so any * declared key fails closed. * * D6 method mapping: * - `inspect`: cheap static notes over a tree path (no network, no CLI). * - `resolve`: delegates to `resolveGitSource` with the declaration's git * source (exact commitSha input, skipping the ref round-trip). * - `plan`: PURE preview (D8 + feature-key checks, then a writes/ownership * preview mirroring what `bindPlugin` + the telemetry field will record — * no write ever lands on disk from `plan`). * - `provision`: `bindPlugin` (marketplace add -> install -> D7 verify -> * `enabledPlugins`) THEN the telemetry field THEN the assembled * `BindingLock` is written atomically. A D7 mismatch inside `bindPlugin` * throws before the telemetry field or the lock are ever touched (no * partial state). * - `verify`: reads the lock; reuses `planClaudeRemoval`'s drift computation * READ-ONLY (its `.actions` are never applied here) for the repo-relative * fields, plus an independent D7 re-check of the loaded plugin cache tree. * - `remove`: STAYS SYNCHRONOUS (the D6 contract) — it only PLANS the * teardown (partition ownership with `isHomeScopedTarget`, `planClaudeRemoval` * over the repo-relative subset). See {@link SuperpowersRemoveResult} for * exactly how a caller applies an "apply"-mode result. * - `report`: Framework Card input lines (framework, pin, D7 identity from * the lock, D18-owned + machine-scope surfaces, a labeled context-cost * estimate over the resolved checkout when its path is still recorded, * and the mandatory telemetry-disabled line). */ /** Adapter-local fail-closed error: wrong framework routed here, or a non-git source/resolution. */ export declare class SuperpowersBindingError extends AihError { constructor(message: string); } /** * obra/superpowers pin — the maintainer-locked commit this binding is pinned * to. MIRRORS (deliberately does not import) the "obra"/"Superpowers" entry * in `src/internals/baseline-sources.ts` — also reachable via * `baselineCatalogById("superpowers").pinnedSha` in * `src/baseline-evidence/catalogs.ts`, as consumed by `src/superpowers/verified.ts`. * Mirrored rather than imported for two reasons: (1) importing would pull the * whole ECC/Superpowers baseline-evidence catalog module (JSON component * manifests and all) into the binding path just to read one string; (2) the * binding pin is a DELIBERATE, independently-reviewed value — if the shared * baseline pin ever moves, this constant must be updated explicitly in the * same review, never silently follow. Do not set this to a different commit. */ export declare const SUPERPOWERS_PIN_COMMIT = "3dcbd5c4b48e02263fbf4a3c01e3fe4f81d584d9"; /** * The Superpowers adapter version (W7 §C.2) — bumped when this adapter's * provisioning / qualification logic changes. It is one of the fields keyed into the * runtime-qualification cache (`scan-cache-tiers.ts` `runtimeQualKey`), so a bump * re-keys every prior host qualification (a cache miss / recompute), never a served * stale one. Registered alongside the factory in `registry.ts` (`ADAPTER_VERSIONS`). */ export declare const ADAPTER_VERSION: 1; /** The pinned git source location (`owner/repo` shape; see `isPlausibleGitRepository`). */ export declare const SUPERPOWERS_REPOSITORY = "obra/superpowers"; /** The plugin name AIH installs. */ export declare const SUPERPOWERS_PLUGIN_NAME = "superpowers"; /** The marketplace name the PINNED checkout's own manifest declares. The claude * host registers a marketplace under the manifest's name — never a * registrar-chosen one — so this mirrors the manifest at * {@link SUPERPOWERS_PIN_COMMIT}; `bindPlugin` asserts the match before any * host mutation (W4 live-run correction). */ export declare const SUPERPOWERS_MARKETPLACE_NAME = "superpowers-dev"; export interface SuperpowersAdapterDeps { /** Project root the D18 fields (`enabledPlugins`, the telemetry env field) are owned under. */ root: string; /** The subprocess seam for the `claude plugin …` lifecycle — a fake in tests. */ runner: Runner; /** Environment (home-dir resolution for machine-scope targets). Defaults to `{}`. */ env?: NodeJS.ProcessEnv; /** Git checkout cache root for `resolve()`. Defaults to `bindingCacheHome(env)`. */ cacheHome?: string; /** Injectable plugin-cache locator; defaults to {@link defaultPluginCacheLocator}. */ locateCache?: PluginCacheLocator; /** Injectable apply seam for repo-relative writes; defaults to a real `executePlan` wrapper. */ applyActions?: (root: string, actions: Action[]) => Promise; /** Per-call `claude` CLI timeout override. */ timeoutMs?: number; } /** * `remove()` stays SYNCHRONOUS (the D6 `FrameworkAdapter.remove` contract), so * it can only PLAN the teardown — actually tearing it down means awaiting * `claude plugin …` calls, which a synchronous method cannot do. The caller * (a future CLI layer, or a test) applies an "apply"-mode result in EXACTLY * this order — REPO-RELATIVE FIRST, then machine-scope: * * 1. repo-relative restore: `executePlan(plan("...", ...repoRelativeActions), ctx)` * — this is what PRUNES/RESTORES the project's `enabledPlugins` entry, i.e. * disables the plugin at project scope; * 2. machine-scope teardown: `removePlugin({ownership: homeOwnership, plugin, marketplace}, deps)` * — this is what runs `claude plugin uninstall`. * * This order is a HARD HOST CONSTRAINT, not a style preference (empirically * verified on 2.1.214): `claude plugin uninstall` REFUSES while the plugin is * still enabled at project scope. Reversing the two steps would make removal * fail on a real host every time. * * Then, exactly like the W3 roundtrip precedent (`tests/binding/hosts/claude/roundtrip.test.ts`), * the caller deletes the binding lock itself — that is not this plan's or * `planClaudeRemoval`'s/`removePlugin`'s job. `"drift-report-only"` mode * mirrors {@link BindingRemovalPlan}: nothing to apply; `reason` explains why. */ export type SuperpowersRemoveResult = { mode: "drift-report-only"; reason: string; } | { mode: "apply"; repoRelativeActions: Action[]; repoRelativeDrift: ClaudeDriftEntry[]; homeOwnership: BindingOwnershipEntry[]; plugin: string; marketplace: string; scope: PluginScope; }; export declare function createSuperpowersAdapter(deps: SuperpowersAdapterDeps): FrameworkAdapter;