/** * The Layer-2 placeholder keys in `text`, in document order (duplicates kept — * callers dedupe when they need to). matchAll clones the global regex, so no * lastIndex state leaks between calls. * @param {string} text * @returns {string[]} */ export function layer2Keys(text: string): string[]; /** * Every distinct Layer-2 placeholder key anywhere in `value` — the deep-walk * twin of {@link layer2Keys}, with the same depth cap (fail OPEN: an advisory * miss costs one line, never a mangled input) as {@link containsPlaceholder}. * @param {unknown} value * @param {number} [depth] * @returns {string[]} */ export function layer2KeysIn(value: unknown, depth?: number): string[]; /** * Depth-capped walk: does any string in `value` carry secret-placeholder-shaped * text? The cap fails OPEN (deeper content is unseen) — every caller feeds a * context-only advisory, so a miss costs one line, never a mangled input. * * Kept alongside {@link collectPlaceholders} rather than expressed in terms of * it: this one short-circuits on the first hit and ignores the Layer-2 * grammar, which is what the PostToolUse on-disk tripwire wants on every Read. * @param {unknown} value * @param {number} [depth] * @returns {boolean} */ export function containsPlaceholder(value: unknown, depth?: number): boolean; /** * One found token: the exact placeholder text and the dotted field path of the * FIRST input field carrying it (empty for a bare string input). * @typedef {{ token: string, path: string }} FoundPlaceholder */ /** * Depth-capped walk collecting every distinct placeholder token in `value`, * split by grammar: `secret` for the redaction grammar (PLACEHOLDER_RE), * `layer2` for the keyed splice placeholders plus the un-keyed unparseable * marker. Same cap and fail-OPEN posture as {@link containsPlaceholder} — the * consumers are context-only advisories. * @param {unknown} value * @returns {{ secret: FoundPlaceholder[], layer2: FoundPlaceholder[] }} */ export function collectPlaceholders(value: unknown): { secret: FoundPlaceholder[]; layer2: FoundPlaceholder[]; }; /** * Advisory context for a tool call OUTSIDE the rehydrated set (Bash, MCP * tools, anything unknown) whose input carries SECRET placeholder text, or * null. Rehydration only re-anchors Edit/Write; every other write path — a * shell heredoc, `sed -i`, an MCP body field — persists the literal * placeholder and destroys the secret it stands for. The advisory names each * exact token, the field carrying it, and the recovery path. It cannot tell a * write from a read (`grep` for a placeholder is legitimate), so it is * deliberately a NOTE, not a verdict: a false positive costs a few sentences * of context, never a blocked call or a mangled input. * * Direct substitution into non-shell tool inputs was evaluated and rejected: * substituting the real secret into an MCP body field (a PR body, a comment) * would PUBLISH the secret to an external service — exfiltration by * construction — and PreToolUse has no placeholder→secret map without a named * owning file anyway. * @param {string} tool * @param {unknown} toolInput * @returns {string | null} */ export function placeholderNotice(tool: string, toolInput: unknown): string | null; /** * The Layer-2 twin of {@link placeholderNotice}: advisory context for a tool * call OUTSIDE the rehydrated set whose input carries Layer-2 splice * placeholders, or null. Rehydration restores keyed placeholders to the stored * (already-redacted) original only on the Edit/Write path; a shell heredoc, * `sed -i`, or an MCP body field persists the placeholder text literally. * Deliberately a NOTE, not a verdict, for the same cannot-tell-write-from-read * reason as the secret advisory — and it names the span file path(s) where the * original bytes live so the model can Read one instead of guessing. * * Kept separate from {@link placeholderNotice}, rather than folded into it, so * that the call site's secret-opt-in gate (with secrets off, `[REDACTED]`- * shaped text is ordinary prose) cannot also suppress the Layer-2 advisory: * Layer 2 splices regardless of the secret opt-in. * @param {string} tool * @param {unknown} toolInput * @returns {string | null} */ export function layer2PlaceholderNotice(tool: string, toolInput: unknown): string | null; export const PLACEHOLDER_LABEL_CHARS: "A-Za-z0-9 ()._-"; /** * Matches exactly the placeholder text the canonical redactor can emit: * `[REDACTED]` or `[REDACTED: