import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import { type AppError } from "../app-error/index.js"; import { type InstructionsConfig } from "../settings/index.js"; import type { WorkspaceScope } from "../workspace/scope.js"; import type { AgentId, AgentInstructionsDescriptor } from "./types.js"; export interface ResolvedInstructionsConfig { readonly fileName: string; readonly gitignoreAliases: boolean; } /** * How AXM realizes an instruction target. `none` marks a configured agent * with no projectable convention, so nothing is written or inspected for it. */ export type InstructionMechanism = "native" | "symlink" | "copy" | "adapter" | "none"; export type InstructionHealth = "ok" | "missing-source" | "missing-target" | "drift" | "broken-link" | "unsupported" | "stale"; /** * Ownership of whatever occupies an instruction target path, proven by * inspection alone: a symlink that resolves to the canonical source, or an * `axm:file` banner. Nothing is remembered between commands. `unowned` is a * collision with content AXM did not produce; it is reported and never * modified. */ export type InstructionTargetOwnership = "absent" | "owned-current" | "owned-drift" | "unowned"; /** The form present at a target path — what is on disk, not what sync would choose. */ export type ObservedInstructionForm = "none" | "symlink" | "broken-link" | "copy" | "file" | "directory"; export interface InstructionStatusItem { readonly root: string; readonly agentId: AgentId; readonly agentName: string; readonly sourceFile: string; readonly targetFile: string; readonly mechanism: InstructionMechanism; readonly health: InstructionHealth; readonly ownership: InstructionTargetOwnership; readonly observedForm: ObservedInstructionForm; readonly details: string; } export interface InstructionsStatus { readonly enabled: boolean; readonly sourceFileName: string; readonly gitignoreAliases: boolean; readonly roots: ReadonlyArray; /** Canonical source paths the plan expects but the workspace lacks. */ readonly missingSources: ReadonlyArray; /** One row per configured agent at every discovered root. */ readonly items: ReadonlyArray; /** * AXM-owned targets no current plan item desires — residue of a removed * source root, a removed agent, or a changed source filename. Always * `health: "stale"` and owned; unowned files outside the plan are not AXM's * concern and are never listed. */ readonly staleTargets: ReadonlyArray; } export interface InstructionsGitignoreStatus { readonly file: string; readonly present: boolean; readonly managed: boolean; readonly desired: boolean; readonly current: boolean; readonly trackedAliases: ReadonlyArray; } export interface InstructionProjectionEffect { readonly path: string; readonly change: "created" | "updated" | "removed"; } /** * One command-scoped observation of the instruction projection: the expected * plan plus the filesystem and Git facts read against it at one moment. Status, * lint, preflight, cleanup, and sync all consume this value instead of * rediscovering roots separately. It is plain data, not a cache or a service. */ export interface InstructionProjectionSnapshot { readonly plan: InstructionProjectionPlan; readonly symlinkSupported: boolean; readonly status: InstructionsStatus; readonly gitignore: InstructionsGitignoreStatus; } export interface InstructionsSyncResult { /** * Observed after the writes, never the pre-write snapshot — except for a dry * run, which mutates nothing and reports the observation it planned from. */ readonly snapshot: InstructionProjectionSnapshot; readonly written: ReadonlyArray; readonly removed: ReadonlyArray; readonly skipped: ReadonlyArray; } export declare const resolveInstructionsConfig: (config: InstructionsConfig | undefined) => ResolvedInstructionsConfig; export declare const resolveInstructionMechanism: (descriptor: AgentInstructionsDescriptor, symlinkSupported: boolean) => InstructionMechanism; /** Reason an agent is excluded from instruction-file sync. */ export type InstructionSkipReason = "no-convention"; /** * A single, branch-exhaustive answer to the three questions both the sync * engine and the `axm setup` plan preview ask about a configured agent: * is it syncable, what file/dir does it target, and by what mechanism. * * `relativeTarget` is relative to an instruction root so this stays free of * the FileSystem/Path services and is trivially fixture-testable across the * whole registry. The `action` discriminant maps 1:1 to a plan-preview row: * * - `native` — agent reads the source file itself (`AGENTS.md`); no write. * - `write` — propagate the source to a distinct native file via symlink or * copy (e.g. `CLAUDE.md`, `GEMINI.md`). * - `adapter` — convert the source into a native rules directory. * - `skip` — agent has no encoded instruction-file convention. */ export type InstructionTargetResolution = { readonly action: "native" | "write" | "adapter"; readonly mechanism: InstructionMechanism; readonly relativeTarget: string; } | { readonly action: "skip"; readonly reason: InstructionSkipReason; }; export type InstructionTargetShape = { readonly action: "native" | "write" | "adapter"; readonly relativeTarget: string; } | { readonly action: "skip"; readonly reason: InstructionSkipReason; }; export declare const resolveInstructionTargetShape: (args: { readonly instructions: AgentInstructionsDescriptor | undefined; readonly sourceFileName: string; }) => InstructionTargetShape; export declare const resolveInstructionTarget: (args: { readonly instructions: AgentInstructionsDescriptor | undefined; readonly sourceFileName: string; readonly symlinkSupported: boolean; }) => InstructionTargetResolution; export declare const probeSymlinkSupport: (workspaceRoot: string) => Effect.Effect; export type PlannedInstructionItem = { readonly action: "skip"; readonly reason: "unknown-agent"; readonly root: string; readonly agentId: string; readonly agentName: string; readonly sourcePath: string; } | { readonly action: "skip"; readonly reason: InstructionSkipReason; readonly root: string; readonly agentId: AgentId; readonly agentName: string; readonly sourcePath: string; } | { readonly action: "native" | "write" | "adapter"; readonly root: string; readonly agentId: AgentId; readonly agentName: string; readonly sourcePath: string; readonly targetPath: string; readonly relativeTarget: string; readonly instructions: AgentInstructionsDescriptor; }; export interface InstructionProjectionPlan { readonly roots: ReadonlyArray; readonly items: ReadonlyArray; } export declare const buildInstructionProjectionPlan: (args: { readonly roots: ReadonlyArray; readonly configuredAgents: ReadonlyArray; readonly sourceFileName: string; readonly path: Path.Path; }) => InstructionProjectionPlan; export declare const assertInstructionsGitignoreSafe: (workspaceRoot: string) => Effect.Effect; /** Exact durable paths a reconciliation from this observation will touch. */ export declare const instructionProjectionEffects: (snapshot: InstructionProjectionSnapshot) => ReadonlyArray; /** Exact durable paths disabling this observed projection will touch. */ export declare const instructionProjectionRemovalEffects: (snapshot: InstructionProjectionSnapshot) => ReadonlyArray; export interface ObserveInstructionProjectionArgs { readonly workspaceRoot: string; readonly scope: WorkspaceScope; readonly configuredAgents: ReadonlyArray; readonly config: ResolvedInstructionsConfig; readonly symlinkSupported?: boolean; /** True only when the supplied filesystem is a snapshot of the Git index. */ readonly gitIndexView?: boolean; } /** * Build the command-scoped snapshot: discover roots and alias candidates in * one walk, expand the plan, then read target, stale-candidate, and * `.gitignore` facts against it. Call it once per command and derive every * view from the result; call it again after writing to read the outcome back. */ export declare const observeInstructionProjection: (args: ObserveInstructionProjectionArgs) => Effect.Effect; /** * Whether every projected target with a canonical source is current, no owned * residue remains, and the managed `.gitignore` region matches. Rows whose * source is missing are not judged here: nothing can be written for them, and * the caller decides whether a missing canonical file is its concern — the * sync plan treats it as work, `axm lint --fix` leaves it to its author. */ export declare const instructionProjectionIsCurrent: (snapshot: InstructionProjectionSnapshot) => boolean; /** * The hard blocker every mutation shares: an unowned file at a planned target * stops the whole operation before any write, so no path can claim authority * over content AXM cannot prove it produced. */ export declare const assertInstructionTargetsSafe: (status: InstructionsStatus) => Effect.Effect; /** * Remove every target AXM owns — the current plan's owned aliases and all * stale residue — refusing the whole operation if any planned target is * unowned. Returns the removable paths in plan order; with `dryRun` nothing * is touched and the same list is returned. */ export declare const removeManagedInstructionTargets: (args: { readonly snapshot: InstructionProjectionSnapshot; readonly dryRun: boolean; }) => Effect.Effect; export declare const removeInstructionsGitignore: (args: { readonly workspaceRoot: string; readonly dryRun: boolean; }) => Effect.Effect, AppError, FileSystem.FileSystem | Path.Path>; export interface SyncInstructionsArgs extends ObserveInstructionProjectionArgs { readonly dryRun: boolean; } /** * Observe, apply, and read back. Unowned planned targets are skipped, not * failed; callers that must refuse on them run `assertInstructionTargetsSafe` * against their own snapshot first. A dry run returns the paths a real run * would write and remove and reports the pre-write observation unchanged. */ export declare const syncInstructions: (args: SyncInstructionsArgs) => Effect.Effect; /** * The reconciliation `axm sync`, `axm lint --fix`, and instruction-file * transitions share: refuse on any unowned planned target or unsafe * `.gitignore` region before touching anything, apply the desired state, then * prove from a fresh observation that it was reached. */ export declare const reconcileInstructionTargets: (args: ObserveInstructionProjectionArgs) => Effect.Effect; //# sourceMappingURL=instructions.d.ts.map