/** * Put the check in front of the agent that writes the access control. * * The distribution bet in GOAL.md is that Crossline becomes a default rather * than a decision, and the most direct mechanism is a line of text in a file * the coding agent already reads on every turn. Asking a human to copy that * line out of a README is a decision, and almost nobody makes it. So `init` * offers to write it. * * Two rules govern everything below, because this touches a file the developer * owns and we are only welcome as long as that stays true: * * - Never clobber. The block is delimited, updated in place when it is * already there, and never duplicated. Everything outside the markers is * returned byte for byte. * - Never write unasked. Interactive confirmation, or an explicit `--yes`. * Without either, print what would have been written and touch nothing. */ export declare const BEGIN = ""; export declare const END = ""; /** * The instruction itself. * * Short on purpose: a long block is a block that gets deleted, and the deleted * block protects nothing. It has to earn three lines — *when* to call, *how* to * call, and *what to do with the answer* — and nothing else goes in. * * The middle paragraph is the one that changes agent behaviour. An agent that * reads a finding as an opinion will argue with it or "fix" it by relaxing the * assertion; an agent that reads it as a fact fixes the hole. It is a fact: * Crossline seeded the row itself and matched it back by primary key. */ export declare const AGENT_INSTRUCTION: string; /** * Where coding agents read their standing instructions, in the order we prefer. * * Detected, never assumed. Creating a `CLAUDE.md` in a repo that uses Cursor is * the kind of presumption that gets a tool uninstalled. */ export declare const AGENT_FILES: readonly ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".github/copilot-instructions.md"]; /** * What we offer to create when a project has none of them. * * `AGENTS.md` is the convention-neutral choice — it is the cross-vendor * agreement rather than any one vendor's filename — so creating it does not * quietly pick a side on behalf of the repository. */ export declare const DEFAULT_AGENT_FILE = "AGENTS.md"; export type BlockState = "absent" | "present" | "malformed"; /** * Count non-overlapping occurrences. Exported only so the guard below can be * asserted directly — the markers are constants, so nothing else can reach it. */ export declare function occurrences(text: string, needle: string): number; /** * Whether this file already carries our block, and whether we dare touch it. * * `malformed` is the important one. A lone `begin` — left by a merge conflict, * or by someone who deleted half the block — makes "replace from begin to end" * ambiguous, and the wrong answer swallows however much of the developer's own * writing sits between the stray marker and the next end marker. There is no * safe guess available, so we make none and say so. */ export declare function blockState(text: string): BlockState; /** * The file as it should be: our block updated in place, or appended at the end. * * Everything outside the markers is preserved exactly, including the reader's * own edits above and below. Only valid on a file whose `blockState` is not * `malformed`. */ export declare function withBlock(text: string): string; export interface AgentHookTarget { /** Relative to the project root, as written and as displayed. */ path: string; exists: boolean; state: BlockState; /** The whole file as it should be, or null when we refuse to touch it. */ next: string | null; changed: boolean; } export interface AgentHookPlan { /** Agent instruction files that already exist, in detection order. */ found: string[]; targets: AgentHookTarget[]; /** True when nothing was found, so the plan is to create the default file. */ creating: boolean; } /** * Work out what would change, without changing anything. * * Every file that exists is a target. Two agent files in one repo means two * agents work on it, and the one that is not told is the one that ships the * hole — a duplicated paragraph costs nothing next to that. */ export declare function planAgentHook(cwd: string): AgentHookPlan; /** Write the plan. Returns the paths actually touched. */ export declare function applyAgentHook(cwd: string, plan: AgentHookPlan): string[]; /** * A default of yes, honoured for anything that is not a refusal. * * Enter means yes because the whole point is that this stops being a decision. * Anything starting with `n` is a no; so is an explicit `q`, because a person * reaching for quit is not reaching for consent. */ export declare function saysYes(answer: string): boolean; /** Oxford-comma-free list: "a", "a and b", "a, b and c". */ export declare function joinList(items: string[]): string; export interface OfferOptions { cwd: string; /** False when `--no-agent-hook` was passed. */ enabled: boolean; /** True when `--yes` was passed: no prompt, just write. */ assumeYes: boolean; /** Whether there is a human on the other end of stdin. */ interactive: boolean; out: (text: string) => void; /** Injected so the prompt is drivable from a test. */ ask: (question: string) => Promise; } /** * The whole interaction, from detection to the sentence naming what changed. * * Split out of the CLI so it can be driven from a test without a pseudo- * terminal, and so the decision table lives in one readable place. */ export declare function offerAgentHook(opts: OfferOptions): Promise; /** Ask on the real terminal. Kept out of `offerAgentHook` so tests need no pty. */ export declare function askOnTerminal(question: string): Promise;