import { TheokitAgentError } from '@theokit/sdk/errors'; import { z } from 'zod'; import { TrustPosture, LayerValues, PermissionRule } from '@theokit/sdk'; export { DeclaredLayer, LayerValues, PermissionAction, PermissionRule } from '@theokit/sdk'; import { R as ResolvedCompatSource } from './setting-sources-gate-BJJiKq33.js'; export { resolveEffectiveContextWindow as effectiveContextWindow } from '@theokit/sdk/compaction'; /** * M73 — layered configuration, as a parameterised machine. * * ## The gap this closes * * The config module answered "load my framework's config file". It published no layering engine, did * not let the SDK's through, and said nothing about directory trust. The evidence that this was a * real gap and not a scope decision: a repository whose README forbids importing `@theokit/sdk` * directly broke its own rule **six times**, and all six reach for config/trust/wiring primitives. A * team that breaks its own rule rather than reimplement is the strongest signal that the door, not * the willingness, was what was missing. * * ## What is policy and what is machine * * The VOCABULARY — which keys exist, which capabilities they grant, TOML or TS — is legitimately * policy and stays with the product. The chain machine, the profile merge, the precedence report and * the floor are identical in every agent product. * * The milestone's named risk was generalising too early and freezing another product's vocabulary. * The mitigation is structural: **the layer chain is a parameter, never a constant.** No layer name * from any consumer appears in this file, and a test asserts it by resolving a chain of names this * repository has never heard of. * * ## Why it composes rather than implements * * `foldLayers` and `verifyLayerOrdering` are the SDK's, crossed by M67. Re-deriving the fold here * would give two answers to "which layer wins", and the two would diverge silently — the exact * defect this whole initiative exists to remove. What this adds is what the SDK deliberately does * not: provenance per key, and the measured-vs-declared precedence report. */ /** One layer of configuration, with the values it contributes. */ interface ConfigLayer { readonly layer: string; /** Higher wins. Omit across the whole chain to mean "this array is already the order". */ readonly precedence?: number; readonly values: Readonly>; } /** * Where each resolved key came from. * * For an overwritten key this is the winning layer's name. For an ACCUMULATED key it is every * contributing layer, comma-separated — a union has no single winner, and naming only the last one * would be a lie about where the other members came from. */ type ProvenancePerKey = Readonly>; /** * Declared order versus the order the values actually proved. * * The consumer wrote this check by hand (`measuredPrecedenceChain`) because the engine did not offer * it. A layer that declares high precedence and never wins a key is a config nobody is reading — * usually a path that does not exist. Silence there is how a broken override survives for months. */ interface PrecedenceReport { /** The chain as the caller wrote it. */ readonly declared: readonly string[]; /** * The layers that actually contributed at least one key, in chain order. * * PARTICIPATION, not victory. The first draft measured which layers WON a key, and a base * `defaults` layer whose every value is later overridden then reported as absent from the measured * chain — flagging the most normal arrangement in layered config as a divergence. Being * overridden is what a defaults layer is for. */ readonly measured: readonly string[]; /** * Layers declared in the chain that contributed NO key at all. * * Not the same as "was overridden": being superseded is the normal fate of a defaults layer and * says nothing is wrong. Contributing nothing usually means a path that does not exist — a config * file nobody is reading — and that is the silence this report exists to break. */ readonly declaredButSilent: readonly string[]; readonly diverges: boolean; } interface LayeredConfigInput { readonly layers: readonly ConfigLayer[]; /** Validated AFTER folding — see {@link LayeredConfig.resolve}. */ readonly schema: TSchema; /** * Keys whose values UNION across layers instead of being overwritten. * * Opt-in per key, deliberately: some keys are lists a layer adds to (tools, plugins, allowlists) * and overwriting them discards what a lower layer contributed — but defaulting to union would * silently merge something the caller meant to replace. */ readonly accumulatingKeys?: readonly string[]; } interface LayeredConfigResult { readonly value: TValue; readonly provenancePerKey: ProvenancePerKey; readonly precedenceReport: PrecedenceReport; } /** * Raised when a chain is built out of order. * * Refusing beats sorting silently: a caller who wrote the chain in the wrong order holds a belief * about precedence that is wrong, and sorting it for them leaves the belief intact until it produces * a surprise somewhere else. */ declare class LayerOutOfOrderError extends TheokitAgentError { readonly name = "LayerOutOfOrderError"; constructor(message: string); } /** * The layering engine. * * A class with one static rather than a free function: it is the namespace the config surface hangs * from (`LayeredConfig.resolve`), matching the `X.create()` shape the rest of this codebase uses, and * it leaves room for the trust-store companion to join the same namespace without a second import. */ declare const LayeredConfig: { /** * Fold the chain, validate the result, and report where everything came from. * * The schema is applied AFTER folding, never per layer. Validating each layer separately would * force every file to be complete, which defeats layering: a project override that sets one key * would have to restate the whole config. * * @throws {LayerOutOfOrderError} when a layer does not outrank the one before it. */ resolve(input: LayeredConfigInput): LayeredConfigResult>; }; /** * M73 — the per-directory trust store: a trust decision that survives the process. * * ## Why persisting it matters * * M68 made `settingSources`' `project` root require a `TrustPosture` — evidence, not a claim. But a * posture computed fresh on every run is a question asked over and over, and a question asked every * run is a question users learn to answer without reading. * * Persisting turns the stamp into a DECISION: recorded once, auditable afterwards, and answerable by * "who trusted this directory, when, and on what basis" rather than by re-deriving it. * * ## Why the file permission is checked on READ * * This file decides whether a directory may run shell hooks. A store any other user can write is a * store any other user can use to grant themselves that. Checking at write time only would leave a * file whose mode was loosened afterwards looking fine — so the check is where the value is * consumed, and a loose mode is REFUSED rather than repaired: silently tightening it hides that * something changed the mode, which is the fact worth knowing. */ /** What was decided about one directory. */ interface TrustRecord { /** Absolute path of the trusted directory. */ readonly path: string; /** ISO-8601 stamp of the decision. Injected by the caller — see {@link TrustStore.trust}. */ readonly decidedAt: string; /** Free-form provenance: who or what decided (a username, a CI job, `--trust` on the CLI). */ readonly decidedBy: string; readonly trusted: boolean; } /** Raised when the store's file mode would let another user grant themselves trust. */ declare class TrustStorePermissionsError extends TheokitAgentError { readonly file: string; readonly mode: number; readonly name = "TrustStorePermissionsError"; constructor(file: string, mode: number); } declare class TrustStore { private readonly file; constructor(file: string); /** * Whether `path` carries a recorded decision to trust it. * * Denies on anything else — never recorded, recorded as refused, or unresolvable. A refusal on * record (`trusted: false`) is a different fact from "never asked", and neither is trust. */ isTrusted(path: string): boolean; /** * Read the store, refusing a file other users can write. * * A missing store is not an error — it is a machine that has trusted nothing yet, which is the * correct starting state and the safe one. */ read(): readonly TrustRecord[]; /** * Record a decision about `path`, replacing any previous one for it. * * `decidedAt` and `decidedBy` are ARGUMENTS, not derived here (DIP): the clock and the identity * belong to the caller, and baking `new Date()` in would make every assertion about the record * depend on when the test ran. * * ASYNC because both `withFileLock` and `atomicWriteJson` are. Measured, not assumed: the first * draft called them synchronously and `trust()` returned before the bytes landed, so an immediate * `read()` saw an empty store. Same shape as the M71 pointer bug, and same cause — the SDK's * `.d.ts` does not declare these, so nothing at compile time says they return a Promise * (usetheokit/theokit-sdk#280). */ trust(record: TrustRecord): Promise; /** * The recorded posture for `path`, or an UNTRUSTED posture when nothing was recorded. * * Absence resolves to untrusted, never to "unknown, proceed". A store that answered "I do not * know" would push the decision back to the caller, and the caller asking is what the store * exists to answer. */ postureFor(path: string, capabilities: readonly K[]): TrustPosture; private assertSafePermissions; } interface ExpandImportsInput { readonly text: string; /** Absolute path of the file `text` came from — imports resolve relative to its directory. */ readonly filePath: string; /** Containment boundary. An import resolving outside it is kept literal. */ readonly rootDir: string; readonly onWarn: (message: string) => void; /** * Frame imported content — e.g. `--- import: x ---` markers around it. * * Presentation belongs to the caller: these markers end up in the model's prompt, and a loader * that dictated them would silently change what a product sends. Absent means the content is * inlined bare, which is what every caller saw before this seam existed. * * @param name the reference as written, without the `@` * @param content the imported file's content, already expanded */ readonly wrap?: (name: string, content: string) => string; /** * Files the CALLER already read, which must not be inlined again. * * A walk that collects its files first and expands second — the ancestor-chain convention, where a * product climbs from the working directory to the repository root — has already loaded some of * the files an import may name. Without this, such a file lands in the prompt twice. */ readonly alreadyLoaded?: readonly string[]; } /** * Replace every `@file.md` reference with that file's content, recursively. * * A reference that cannot be expanded — missing, outside the root, too deep, already visited — is * left exactly as written. Keeping it literal rather than dropping it is what lets a user SEE that * something did not expand; a silently deleted line reads as content nobody wrote. */ declare function expandInstructionImports(input: ExpandImportsInput): string; /** * M74 — load a tree of project instruction files, with explicit ceilings. * * ## Why `compileProjectContext` does not cover this * * It reads ONE fixed file through the SDK: no depth or file budget, no frontmatter, no cycle guard, * no truncation policy, no warning channel. A product that wants project-scoped instructions writes * roughly 720 lines of mechanism — all of it identical between products, none of it about their * domain. * * ## The containment check is a security control * * A symlink inside a project directory can point anywhere. Following one lets a repository the user * just cloned inject the contents of `~/.ssh/config`, or any other readable file, straight into the * model's system prompt. That is prompt injection with the filesystem as the vector, and every * consumer that writes this loader by hand reintroduces it. * * `assertNoSymlinkEscape` is the SDK's, crossed in M67. Composing it is what makes the check the * same one everywhere instead of four subtly different `realpath` comparisons. * * ## Failure is per FILE, not per tree * * A malformed frontmatter skips that file and warns. Failing the whole load would let one bad file * in a deep tree silently disable every instruction the user wrote — the loudest possible failure * producing the quietest possible outcome. */ /** One loaded instruction file. */ interface InstructionBlock { /** Path relative to `cwd`, for messages a human can act on. */ readonly path: string; /** File body with the frontmatter removed. */ readonly content: string; /** `paths:` from the frontmatter — the scopes this block applies to. Empty means unscoped. */ readonly scopes: readonly string[]; /** * A `paths:` key was declared and nothing readable came out of it. * * Without this, that case is indistinguishable from a file that declared no scope: both yield * `scopes: []`. A consumer rendering the block would then apply a rule written for one subtree * EVERYWHERE — the one frontmatter failure with a consequence, and a silent one. Products with a * fail-closed policy drop the block on this flag instead of publishing it unscoped. */ readonly scopesUnreadable: boolean; } /** * Does a block's `paths:` scope cover this file? * * `scopes` shipped without this, and the gap was not cosmetic: the field's own docblock names the * consequence of getting it wrong — a rule written for one subtree applied EVERYWHERE, silently — * and then hands the decision to the product. Delegating the DECISION is deliberate and unchanged. * What was missing is the MEANS: no glob matcher existed anywhere in this package, so a consumer who * wanted to honour a scope had to invent the semantics, and two consumers would invent two. * * `scopesUnreadable` answers FALSE, whatever the path. That is the fail-closed half the flag was * invented for: a `paths:` that was declared and yielded nothing must not read as "no scope * declared", because those two are indistinguishable in `scopes` alone and only one of them is safe * to publish everywhere. * * ## What this supports, and what it refuses to guess * * `**` (crosses separators), `*` (does not), and `?` (one character) — the same three the SDK's own * rule activation implements. Brace expansion `{a,b}` and character classes `[abc]` are NOT * supported: a pattern using them matches literally and therefore almost certainly not at all. * * That is a deliberate floor rather than a step toward a glob library. Three wildcards are a dozen * lines; a dependency for them carries a transitive tree into a package whose direct dependencies * number three. If a caller needs the fuller grammar, that is a decision to make out loud — adding * it quietly here would leave two half-grammars in one ecosystem. */ declare function blockAppliesTo(block: Pick, filePath: string): boolean; interface InstructionTreeBudget { /** How deep below each root to descend. */ readonly maxDepth: number; /** How many files to load in total. */ readonly maxFiles: number; /** Total characters across all blocks. */ readonly maxChars: number; } interface LoadInstructionTreeInput { readonly cwd: string; /** Directories to walk, relative to `cwd` or absolute. Order is the caller's. */ readonly roots: readonly string[]; readonly budget: InstructionTreeBudget; /** * Where a skipped file, a refused symlink or an exhausted budget is reported. * * A channel rather than a throw: none of these should stop a load, and none of them should be * silent either. Silence here is how a user's instruction file stops being read without anybody * noticing. */ readonly onWarn?: (message: string) => void; /** * Which files to load. Defaults to the conventional two. * * A list matches basenames exactly; a predicate answers the question a list cannot. A rules * DIRECTORY —, `.cursor/rules/`, `.theokit/rules/` — holds files the caller * cannot name in advance, because the user chooses the names. With only a list on offer, the * closest consumer wrote its own 112-line walk (budget, depth ceiling, cycle guard and all) to * ask `entry.endsWith('.md')`. The walk was ours; only the question was theirs. */ readonly fileNames?: readonly string[] | ((entry: string) => boolean); /** * The order blocks come back in. Defaults to `'outward-in'`. * * `'outward-in'` reads every file at a level before descending: an instruction TREE means the * outer file states the general rule and the inner one refines it, so the composer needs the * general one first. * * `'lexicographic'` is one alphabetical pass, directories interleaved where their names fall. A * rules FOLDER means the opposite of a tree: the files are peers, and the contract its users * depend on is that the same directory assembles the same prompt on any machine. * * Both are deterministic. Only one is right per shape, and only the caller knows which shape it * is looking at — which is why offering the walk without offering the order left the capability * half-built. */ readonly order?: 'outward-in' | 'lexicographic'; } interface InstructionTree { readonly blocks: readonly InstructionBlock[]; /** True when a ceiling stopped the walk — the caller is seeing a partial tree. */ readonly truncated: boolean; readonly count: number; } /** * Walk `roots` and load the instruction files found, stopping at the declared ceilings. * * Cycles are broken by INODE, not by path: a symlink loop produces infinitely many distinct paths * for the same file, so a path-keyed `seen` set never terminates. The inode is what identifies the * file the OS would actually read. */ declare function loadInstructionTree(input: LoadInstructionTreeInput): InstructionTree; /** * M74 — compose a base prompt with instruction sources under a character ceiling. * * ## Why the truncation LADDER is mechanism and the ORDER is policy * * When the composed text does not fit, something has to go — and which thing goes is a product * decision. A coding agent drops repository conventions before user rules; a support agent may do * the opposite. Baking that preference into the framework would be absorbing one product's taste as * everyone's law, which is the milestone's named risk. * * So the caller passes `sources` ALREADY ORDERED, most important first, and this function walks that * order backwards when it needs room. The framework supplies the cutting mechanism; the product * supplies the preference. No source NAME appears in this file. * * ## Why it truncates rather than refusing * * A prompt that does not fit is a run that cannot start, and refusing outright would make a long * instruction file a hard failure at the worst moment. Dropping the least important source and * SAYING SO is the behaviour that keeps the agent usable while keeping the user informed — which is * why `onWarn` is not optional in practice even though it is optional in the signature. */ /** One named block of instructions the caller wants composed. */ interface InstructionSource { /** The product's own label, used only in warnings. Never interpreted here. */ readonly name: string; readonly content: string; } interface ComposeInstructionsOptions { /** Ceiling for the whole composed string, base included. */ readonly maxChars: number; /** Where a dropped or trimmed source is reported. Silence here loses the user's instructions. */ readonly onWarn?: (message: string) => void; /** Separator between blocks. */ readonly separator?: string; } interface ComposedInstructions { readonly text: string; /** Names of sources that were dropped entirely, in the order they were dropped. */ readonly dropped: readonly string[]; /** Name of the source that was cut in half to fit, when one was. */ readonly trimmed?: string; } /** * Compose `base` with `sources`, cutting from the END of the list until it fits. * * The base is never dropped: it is the agent's own identity, and an agent without it is a different * agent. If the base alone exceeds the ceiling the result is the base, TRUNCATED, with a warning — * returning an empty string would be a silent lobotomy, and throwing would make a long system prompt * an unrecoverable configuration error. */ declare function composeInstructions(base: string, sources: readonly InstructionSource[], options: ComposeInstructionsOptions): ComposedInstructions; /** * The narrowed half of `ResolvedCompatSource`, split out because the union's other half is the * string literal `'claude-code'` — and `string | 'claude-code'` collapses to `string`, which would * make the object arm unreachable to the type system. * * Its `kind` is that one literal today, so the loader below does not re-check it. A second foreign * dialect would widen it, and would have to revisit `COMPAT_COMMANDS_DIR` in the same breath: the * directory this reads is `.claude/`, not "whichever dialect asked". */ type NarrowedCompatSource = Exclude; /** * M76 — load custom commands from `.theokit/commands/`. * * ## Why this gap was worse than a missing feature * * The framework OWNS the `.theokit/` convention and already loads `skills/`, `agents/` and * `hooks.json` from it. `commands/` — the one directory every product-facing agent surface wants — * had no loader at all. So a consumer wrote markdown-with-frontmatter scanning **against the * framework's own directory**: reimplementing the reading of a convention the framework defines. * * A convention with a hole in it is worse than no convention. It teaches the reader that `.theokit/` * is the framework's, then makes them write the loader themselves for one subdirectory, and their * loader inevitably disagrees with ours about frontmatter, precedence and trust. * * ## Trust, and why project commands are gated * * A command is a prompt the user can invoke by name. A project-level one comes from the working * directory — which, for an agent pointed at a repository the user just cloned, is * attacker-controlled content. Same decision as M68: `projectTrusted` false means project commands * do not load. User-level commands under `homeDir` need no gate — that is the operator's own * machine. * * ## Warns, never decides * * On a name collision this loader REPORTS and moves on. Which command wins when a custom one shadows * a builtin is the product's router to decide (M83) — it is the only layer that knows what its * builtins are. A loader that silently dropped one would make that decision invisibly, on behalf of * a product it cannot see. */ /** One command loaded from disk. */ interface CustomCommand { /** Invocation name, derived from the file name without its extension. */ readonly name: string; /** One-line summary from `description:` in the frontmatter, when present. */ readonly description?: string; /** The prompt body, frontmatter removed. */ readonly body: string; /** Which layer it came from. `project` beats `user` — see {@link loadCustomCommands}. */ readonly source: 'project' | 'user'; /** Absolute path, for messages a human can act on. */ readonly path: string; /** * The frontmatter lines, verbatim — read your own keys with {@link frontmatterValue}. * * This loader knows two keys (`description`, and the fence). A product's commands declare more, * and the sets do not agree: the closest consumer reads `model`, `agent`, `subtask` and `hints`, * while Claude Code's custom commands declare `model` and `argument-hint`. Two vocabularies * already, and neither is the framework's to adopt. * * Carrying the lines is what stops that from forcing a second loader. Measured: the consumer * wrote 122 lines — the same directories, the same trust gate, the same precedence — because the * result gave it nowhere to read its own keys from. */ readonly frontmatter: readonly string[]; } interface LoadCustomCommandsInput { /** Project root. Its `.theokit/commands/` is read only when `projectTrusted`. */ readonly projectDir?: string; /** Operator home. Its `.theokit/commands/` needs no trust gate. */ readonly homeDir?: string; /** * Whether the project directory is trusted (M68/M73). * * REQUIRED, like `approved` on the hook engine and for the same reason: an optional security gate * is a gate somebody forgets, and forgetting this one loads prompts written by whoever wrote the * repository. */ readonly projectTrusted: boolean; /** Names the product already uses, so a shadow can be reported rather than discovered later. */ /** * Foreign dialects whose command directory the caller wants read, in the SDK's `compatSources` * vocabulary. `['claude-code']` adds `/.claude/commands/`. * * Declaration AND trust, exactly as `resolveCompatSources` requires for the other surfaces: a * command is a prompt that runs on the operator's behalf, and this directory is usually written * for a different product and arrives with the repository. */ readonly compatSources?: readonly (string | NarrowedCompatSource)[]; readonly builtinNames?: readonly string[]; /** Where a shadow, a duplicate, or a malformed file is reported. */ readonly onWarn?: (message: string) => void; } interface CustomCommandsResult { /** Loaded commands, project-level first. Names are unique — see the precedence note. */ readonly commands: readonly CustomCommand[]; /** Names that also exist as builtins. Reported, never resolved here. */ readonly shadowedBuiltins: readonly string[]; } declare function loadCustomCommands(input: LoadCustomCommandsInput): CustomCommandsResult; /** * The ceiling on inlined file content. A larger file is TRUNCATED with a warning — never inlined * whole (which would blow the context the command was meant to shape) and never silently dropped * (which reads to the model as an empty file). */ declare const FILE_INLINE_CAP: number; interface ShellResult { /** What the command wrote. Substituted whether or not it succeeded — see {@link TemplateDeps.warn}. */ readonly text: string; readonly ok: boolean; } interface TemplateDeps { /** Runs a shell segment. Injected: the trust decision is the caller's, not this module's. */ shell: (cmd: string) => Promise; /** Resolves a `@reference`. Injected: containment is the caller's. `undefined` = not found. */ readFile: (name: string) => string | undefined; /** Reports anything that did not resolve as asked. Never throws — expansion is best-effort. */ warn: (message: string) => void; } /** * The placeholders a template declares, sorted and de-duplicated — what a command palette shows so * the user knows what the command wants before running it. * * ESCAPED placeholders are excluded. `\$1` is prose about a placeholder, not a request for one, and * listing it asks the user to supply an argument the template will never substitute. It also broke * the sort outright: the match carries the backslash, so `slice(1)` produced `"$1"` and `Number` * produced `NaN`, and a comparator returning `NaN` leaves the order unspecified. * * `$ARGUMENTS` sorts first because it is the whole string — reading it after `$3` suggests it is a * fourth position. */ declare function templateHints(template: string): string[]; /** * Expands a command body against its arguments. * * Order is placeholders → shell → files, and it is not arbitrary: arguments are the user's direct * input for THIS invocation, so they are the only thing allowed to influence which command runs or * which file is read. Running the other two first would let a file's contents decide what to * execute. * * Throws in exactly two cases, both of which produce NO prompt rather than a wrong one: a fenced * ```` ```! ```` block (never executed, previously passed through as markdown) and an injected * command that failed (the spec aborts the invocation). Everything else — a missing `@file`, an * out-of-range `$N`, an oversized file — still produces a usable prompt with a warning naming what * did not resolve, which is more useful than discarding the whole body. * * The line between the two is what the template CAUSED versus what it POINTED at. */ declare function expandCommandTemplate(template: string, rawArgs: string, deps: TemplateDeps): Promise; /** * B-067 — the settings precedence stack, NAMED. * * `@theokit/sdk` ships the mechanism and deliberately not the vocabulary: `foldLayers` folds an * array of `{ layer, precedence?, values }`, `verifyLayerOrdering` refuses a chain that contradicts * itself, and `DeclaredLayer.layer` is a free-form `string` whose `precedence` is optional. That is * the right shape for a library — it cannot know what layers a consumer has — and it leaves one * thing undecided that two consumers must agree on: WHICH layers exist, and in what order. * * Until this file, they did not agree, and nothing could tell. Two products could each invent their * own names and numbers, fold in opposite orders, and both pass `verifyLayerOrdering`, because a * chain is only checked against itself. B-026 named three levels — managed settings above the * project file above `defineAgent()` — for the four keys the operator policy carries; a consumer * implementing a fifth key had nothing to consult and invented an order that type-checked. * * ## The order, and where it comes from * * The five file levels and their order are the format's, measured against * on 2026-09-11 (highest first there, lowest first here): * managed settings, command line, project local, shared project, user. `code` is added below all of * them and is ours: a value passed to `defineAgent()` is the weakest thing in the system, because * every file above it is editable by a human who did not write the code and is answerable for what * the agent does on their machine. * * That ordering is the whole operator tier in one line. Reversing any pair of it — letting a project * file outrank managed settings, or code outrank a file — turns the tier into a suggestion. * * ## Why this is here and not in the SDK * * Same reason as `operator-policy.ts` beside it: this package depends on a PUBLISHED * `@theokit/sdk`, so a symbol added to that package's source is not importable here until somebody * cuts a release. The names and the order are the contract, and they are stated where the consumer * that needs them can reach them today. What must not drift is the ORDER, which is the format's and * not ours to change. */ /** * Every layer that can supply a setting, named. * * A union rather than a string, so adding one is a compile-time event everywhere a layer is handled * — the property `DeclaredLayer.layer: string` cannot give, and the reason this file exists. */ type SettingsLayer = /** Values passed to `defineAgent()` / the builder. The weakest: code loses to every file. */ 'code' /** `~/.claude/settings.json` — you, in every project. */ | 'user' /** `.claude/settings.json` — everyone in the project. */ | 'project-shared' /** `.claude/settings.local.json` — you, in this project. */ | 'project-local' /** `claude --settings` — you, in this session. */ | 'command-line' /** `managed-settings.json`, MDM, or the console. Your organisation. A project cannot widen it. */ | 'managed'; /** * The FILE layers, lowest precedence first — the array `foldLayers` is given. * * `code` is deliberately absent: it is not a settings file, it is the value the caller passed, and a * chain that folded it as a peer would invite somebody to place it above one. {@link settingsLayerChain} * puts it at the bottom, which is the only position it has. * * The numbers are spaced by ten so a level can be inserted between two without renumbering the * stack — a renumber is exactly the kind of edit that silently reorders a fold somewhere else. */ declare const SETTINGS_LAYERS: readonly [{ readonly layer: "user"; readonly precedence: 10; }, { readonly layer: "project-shared"; readonly precedence: 20; }, { readonly layer: "project-local"; readonly precedence: 30; }, { readonly layer: "command-line"; readonly precedence: 40; }, { readonly layer: "managed"; readonly precedence: 50; }]; /** * Compile-time exhaustiveness: a layer added to {@link SettingsLayer} without a position here makes * this line fail to compile. * * The same gate `capability-zero-behavior.test.ts` puts on the compiled-options waist, for the same * reason — a new member that nobody placed is a member somebody will place at random, once, in the * file where they happened to need it. */ type Positioned = (typeof SETTINGS_LAYERS)[number]['layer'] | 'code'; type _Exhaustive = Exclude extends never ? true : ['unpositioned settings layer']; declare const LAYERS_ARE_POSITIONED: _Exhaustive; /** The precedence of a layer. Higher wins. */ declare function layerPrecedence(layer: SettingsLayer): number; /** * Build the `foldLayers` input from values keyed by layer name. * * Sorted by declared precedence rather than by the object's key order, because an object literal's * order is the author's typing order and folding by it would make precedence depend on where * somebody happened to add a line. Layers with no values are omitted: a layer that supplies nothing * and a layer that does not exist fold identically, and inventing an empty entry for the first would * make the chain longer without making it truer. */ declare function settingsLayerChain(values: Partial>>>): LayerValues[]; /** * A style was named and could not be read. * * Typed, and carrying the directories it searched, because the whole point of the refusal is that * the author learns WHICH lookup failed. `error-handling.md` § 2: fail fast, fail clear. */ declare class OutputStyleError extends TheokitAgentError { readonly name = "OutputStyleError"; constructor(message: string); } interface OutputStyle { /** The filename without `.md` — what `outputStyle` in settings refers to. */ readonly name: string; /** The body, which becomes a section of the system prompt. */ readonly content: string; /** The `description` frontmatter key, when present. */ readonly description?: string; /** * `keep-coding-instructions: true` in frontmatter. * * The field with teeth. A style REPLACES the built-in software-engineering instructions by * default, so a consumer who does not know that loses them without being told. Carried here so * the decision belongs to the caller rather than to whoever wrote the style file. */ readonly keepCodingInstructions: boolean; } interface LoadOutputStyleInput { /** The style to load, from `settings.json`'s `outputStyle`. `undefined` means none was asked for. */ readonly name: string | undefined; /** Project root. Its `.claude/output-styles/` wins over the home directory's. */ readonly cwd: string; /** Home directory, for styles that apply across every project. Omit to search the project only. */ readonly homeDir?: string; } /** * The named style, or `undefined` when none was requested. * * @throws OutputStyleError when a style IS named and no file backs it, listing the directories that * were searched. */ declare function loadOutputStyle(input: LoadOutputStyleInput): OutputStyle | undefined; /** * The style the project's settings name, read from the project's style files. * * The two halves of the documented mechanism, joined. `loadSettings` reads the `outputStyle` key and * {@link loadOutputStyle} reads the file; until this existed nothing connected them, so a consumer * had to find both modules and know that one feeds the other. Two halves each individually correct * and jointly unreachable is the same defect as a capability with no caller — it just takes two * files to make. * * Deliberately does NOT soften the loader: a style named in settings with no file behind it still * throws {@link OutputStyleError}. Returning `undefined` there would restore the exact silence this * work removed — configured, not applied, and no way to tell which. * * @throws OutputStyleError when settings name a style and no file backs it. */ declare function resolveOutputStyle(input: { readonly cwd: string; readonly homeDir?: string; readonly onWarn?: (message: string) => void; }): OutputStyle | undefined; /** What an operator may impose on THIS layer. */ interface OperatorPolicy { /** * Refuse to execute the `` !`command` `` form in a skill or command body. * * A skill is a markdown file a repository can carry, and its body runs shell at expansion time. * Without this the only way to decline was to stop reading skills at all. */ disableSkillShellExecution?: boolean; /** * Refuse every hook declared under a foreign configuration root. * * B-042. `.claude/hooks.json` runs shell commands out of a working directory that usually arrived * with the clone, and until this key existed an operator who inherited an untrusted checkout could * not decline hook execution without editing files in it. `resolveCompatSources` reads this and * drops the `hooks` surface from what it grants. * * ## It fails CLOSED, unlike every other key here * * The others ignore a value of the wrong shape and warn. For this one that is fail-OPEN: an * operator who writes `"true"` as a string would get hooks running while believing they were off. * The asymmetry decides it — fail-open is silent and unsafe, fail-closed is loud and recoverable: * hooks stop, somebody notices, the typo is fixed. For a control whose purpose is refusing, * failing toward the refusal is the only direction where the failure announces itself. * * An ABSENT key is not a malformed one and still means "hooks run": most machines have no operator * tier, and refusing by default would remove hooks nobody asked to remove. */ disableAllHooks?: boolean; /** * Servers from a project `.mcp.json` that may NOT start, by name. * * Deny wins over {@link OperatorPolicy.allowedMcpServers}. A server named in both is a * contradiction, and the safe reading of a contradiction is the restrictive one — resolving it the * other way would let an allow entry re-enable something an operator explicitly refused. */ deniedMcpServers?: readonly string[]; /** * When present, the ONLY servers from a project `.mcp.json` that may start. * * Absent means "no allow list", not "allow nothing": the file is the project's own declaration and * the default stays permissive by CHOICE, because refusing it outright would break every existing * consumer to protect against a file they wrote themselves. An empty ARRAY is a real decision and * refuses everything. */ allowedMcpServers?: readonly string[]; /** * B-069 — a command that prints a credential on stdout, re-run whenever one is needed. * * An operator whose tokens rotate had no seam at all: a long-running agent failed mid-run and the * only answer was a restart with a fresh environment variable. This is declared HERE rather than * on `defineAgent` for the reason B-065 records — the person answerable for what runs on a machine * is frequently not the person who wrote the code, and a command that mints a secret is the most * operator-shaped thing in the system. * * Run by {@link runCredentialHelper}, which bounds it with a timeout and refuses an empty result. */ apiKeyHelper?: string; } declare function currentOperatorPolicy(warn: (message: string) => void): OperatorPolicy; /** * Whether a server declared in a project `.mcp.json` may start. * * Deny first: a server in both lists is refused. An allow list, once present, is EXHAUSTIVE — * anything unnamed is refused — while an absent one means "no allow list", not "allow nothing". */ declare function mcpServerAdmitted(name: string, policy: OperatorPolicy): boolean; /** * B-024 — read the operator's roots when `user: true`, or say why not. * * `SettingSourcesSelection.user` was forwarded and consulted by nothing: a flag a consumer could set * that changed no behaviour and produced no complaint. That is the accepted-and-ignored failure * `rules/foreign-config-surfaces.md` removes for `.claude/` file surfaces — *a surface is read, or it * is refused with a reason; never accepted and ignored*. * * ## Two roots, one grant, one label * * `~/.theokit/` is ours: `user: true` is the whole permission. `~/.claude/` belongs to another * product under a shared home, and it passes the SAME `claude-code` dialect grant that gates the * project's `.claude/` surfaces — never a looser one. `user` says WHICH MACHINE; the dialect says * WHOSE FORMAT, and collapsing them would admit a foreign inventory on a flag that never mentioned * it. * * Every definition carries the root it came from, because an operator has to be able to tell one * they wrote for this product from one they wrote for another — at the point of use, not by * remembering which directory they put it in. */ /** Which root a definition was loaded from. */ type OperatorOrigin = 'theokit' | 'claude-code'; /** A definition found under an operator root. */ interface OperatorDefinition { readonly name: string; readonly path: string; readonly origin: OperatorOrigin; } /** A root that exists and was NOT read, with the grant that would admit it. */ interface WithheldRoot { readonly root: string; readonly reason: string; } /** A file under a read root that could not be taken as a definition. */ interface SkippedDefinition { readonly path: string; readonly reason: string; } interface OperatorRootsResult { readonly definitions: readonly OperatorDefinition[]; /** Roots actually walked. Empty when `user` is absent — the proof of NFR-002, not a claim. */ readonly rootsRead: readonly string[]; /** Roots present and withheld, each NAMED. A count would tell an operator they lost something and not what. */ readonly withheld: readonly WithheldRoot[]; readonly skipped: readonly SkippedDefinition[]; } interface OperatorRootsOptions { /** `SettingSourcesSelection.user`. When false or absent, nothing is read at all. */ readonly user?: boolean; /** The dialects the consumer granted, e.g. `['claude-code']`. */ readonly grants?: readonly string[]; /** Overridable for tests; defaults to the real home. */ readonly homeDir?: string; } /** An operator root exists and cannot be read. Refused loudly rather than reported as empty. */ declare class OperatorRootUnreadableError extends TheokitAgentError { readonly name = "OperatorRootUnreadableError"; constructor(root: string, cause: unknown); } /** * Resolve the operator's definitions for this run. * * With `user` absent or false nothing is walked — `rootsRead` is empty, and that is what the test * asserts rather than the empty result, because a read-everything-then-filter implementation * produces the same result at the cost of a walk on every run. */ declare function resolveOperatorRoots(options: OperatorRootsOptions): OperatorRootsResult; /** Lines of `MEMORY.md` loaded, matching the reference. */ declare const MEMORY_LINE_CAP = 200; /** Bytes loaded, matching the reference. Applied with the line cap, not instead of it. */ declare const MEMORY_BYTE_CAP = 25000; /** * `memory:` is parsed by `@theokit/sdk` from 5.9.0, and REFUSED before it. * * Measured 2026-09-16 by loading a real subagent out of `.theokit/agents/` against the published * tarballs, with a control (a second subagent declaring no `memory:`, which loads on both): * * 5.3.0 ConfigurationError: Subagent note-taker.md: unknown frontmatter field "memory" * (accepted: name, description, model, tools, reasoning_effort, mcp, sandbox) * 5.9.0 loads both; `memory: "project"` survives the parse * * The failure is loud. What misleads is where it POINTS: naming the field and the accepted keys * reads as a typo, so the line an author meant to write gets deleted instead of the SDK upgraded. * * ## Why there is no runtime guard for this, and the number that decided it * * One was written and removed. `listSubagentNames` is the only place this package touches that * parse, it lives in the ROOT BARREL, and wiring it to a version check cost **161 bytes** — the * budget's headroom is 72. Attributed by removing the wiring and rebuilding: 38 789 back to * 38 628. The cost is the coupling rather than the code: four different bodies for the same * function produced byte-identical bundles, because importing `sdk-adapter-create-options` at all * pulls it into the barrel's chunk. A dedicated module was worse (40 403) — it duplicated * `createRequire` instead of sharing it. * * So the choice was a message every consumer pays 161 bytes for against one that no consumer pays * for. `bundle-size.test.ts` argues the rest: the root barrel is what every consumer carries * unconditionally, and raising a ceiling to fit one change approves the next kilobyte without * being asked. The fact lives here and in `the-sdk-version-that-parses-memory.test.ts`, which * fails if it stops being true. * * `applySubagentMemory` itself needs NO particular SDK: it reads `node:fs`, takes an object, and * works on any version. The requirement belongs to the parser, which is why it is documented at * the parse rather than guarded at the function. */ /** Where a subagent's memory lives. The values a `memory:` frontmatter key may take. */ type AgentMemoryScope = 'project' | 'local' | 'user'; /** A memory root could not be resolved. Typed, per `error-handling.md` § 2. */ declare class AgentMemoryError extends TheokitAgentError { readonly name = "AgentMemoryError"; constructor(message: string); } interface ResolveAgentMemoryInput { /** The subagent's name — the directory it owns under the root. */ readonly agent: string; /** * Which root, from the subagent's `memory:` frontmatter. * * Typed as the union WIDENED with `string`, because that is what the boundary actually carries: the * value is read out of a markdown file nobody type-checked. Declaring it as the narrow union would * make the refusal below unreachable to the type system and reachable in production — the shape * where a lint is right about the type and wrong about the world. */ readonly scope: AgentMemoryScope | (string & {}); /** Project root, for `project` and `local`. */ readonly cwd: string; /** Home directory, required for `user`. */ readonly homeDir?: string; } interface AgentMemory { /** The directory this subagent reads and writes. Resolves even when nothing is written yet. */ readonly root: string; /** `MEMORY.md`, under both caps. Absent when the subagent has written none. */ readonly memory?: string; /** Whether a cap bit. Reported because silent truncation is the silence this surface removes. */ readonly truncated: boolean; } declare function resolveAgentMemory(input: ResolveAgentMemoryInput): AgentMemory; /** * A subagent definition with its declared memory appended to the prompt it will receive. * * `@theokit/sdk` carries `memory:` on the definition and stops there, deliberately: which of three * roots a note lives under is a decision about who can see it, and a second copy of that rule in * that package is how the two drift into disagreeing about privacy. {@link resolveAgentMemory} owns * it here — and until this function existed, it owned it with no caller. The reader and the * declaration were one call apart for as long as both existed, which is the capability-with-no-route * shape this repository keeps finding. * * The reference implementation loads a subagent's `MEMORY.md` into its system prompt. A subagent's * prompt here is `definition.prompt`, so that is where the notes go. * * Three things it refuses to do quietly: * * - **An unrecognised scope throws**, because {@link resolveAgentMemory} throws, and softening * that into a no-op would publish on the next commit something written expecting privacy. * - **No notes means no change.** An agent that declares `memory:` before writing its first note * is an ordinary first run, not a misconfiguration. * - **The notes are marked as notes.** Appending them bare would hand the model somebody's * scratchpad as though it were instruction. * * The input is never mutated: a caller mapping over a loaded record should not find the record * changed underneath it. */ declare function applySubagentMemory(definition: T, agent: string, cwd: string, home?: string): T; /** * Translate a settings `permissions` block into rules the SDK's engine evaluates. * * The SDK ships `PermissionEngine(rules, { defaultAction })` and it is a CODE surface: you construct * it with `PermissionRule[]`. What was missing was the path from the FILE to those rules. Measured * 2026-09-11: `permissions` appears in 13 SDK files, and the SDK reads `settings.json` in three, of * which two are sourcemaps and the third describes the hooks shape. An operator writing * `{ "permissions": { "deny": ["Bash(curl:*)"] } }` got a file nothing translated. * * ## Partial fidelity would be worse than absence * * A `deny` that silently fails to match is a control the operator believes is in force — strictly * more dangerous than no rule, because without one they would have written the guard themselves. So * every entry this module cannot render faithfully is RETURNED as unsupported, with a reason, and * excluded from the rules. Nothing is quietly turned into a matcher that does not bite. * * ## What it deliberately does not attempt * * The reference's specifier language is larger than this: path globs with `~` expansion, per-tool * argument names, and matching semantics that differ by tool. Implementing a lookalike would produce * rules that match *almost* the right calls, which is the failure above. Two forms are translated — * a bare tool name, and `Tool(prefix:*)` / `Tool(exact)` — and everything else is reported. */ /** An entry that was understood well enough to refuse, but not well enough to translate. */ interface UnsupportedPermissionEntry { /** The entry as written, so the operator can find it in their file. */ readonly entry: string; /** Why it was not translated. */ readonly reason: string; } interface PermissionTranslation { readonly rules: readonly PermissionRule[]; readonly unsupported: readonly UnsupportedPermissionEntry[]; } /** The `permissions` block as a settings file may carry it. */ interface PermissionsBlock { readonly allow?: readonly string[]; readonly deny?: readonly string[]; readonly ask?: readonly string[]; } declare function permissionRulesFromSettings(block: PermissionsBlock | undefined, opts?: { /** * Which argument each tool's specifier addresses — `{ Bash: 'command', Read: 'path' }`. * * Required per tool rather than guessed. A specifier rendered against the wrong field produces a * matcher that can never fire, and a rule that looks translated is worse than one reported as * untranslatable: the operator believes a control is in force. A tool absent from this map has * its specifier entries REPORTED, never rendered. * * The default keeps the shell-shaped behaviour this function shipped with, so a caller that * passes nothing is unchanged. */ readonly specifierArg?: Readonly>; /** * The directory a path specifier is relative TO — normally the one holding the settings file. * * Without it, `./x` matches only its relative spellings; a tool called with an absolute path * would slip past a rule the operator believes covers that file. With it, the absolute form is * anchored to this directory and nowhere else. */ readonly baseDir?: string; }): PermissionTranslation; /** * The keys this layer can act on today, plus everything else untouched. * * Typed narrowly on purpose: a key is listed here when something in this package reads it, not * because the reference documents it. Declaring a field the code ignores would publish a control * that does nothing, which is the shape every item in this batch had. */ declare const SETTINGS_SCHEMA: z.ZodObject<{ outputStyle: z.ZodOptional; env: z.ZodOptional>; model: z.ZodOptional; cleanupPeriodDays: z.ZodOptional; permissions: z.ZodOptional>; deny: z.ZodOptional>; ask: z.ZodOptional>; }, z.core.$loose>>; }, z.core.$loose>; type Settings = z.infer; interface LoadSettingsInput { /** Project root. Its `.claude/settings.json` and `.claude/settings.local.json` are read. */ readonly cwd: string; /** Home directory, for the user-level file. Omit to read the project only. */ readonly homeDir?: string; /** Where a file that exists and cannot be parsed is reported. */ readonly onWarn?: (message: string) => void; } interface LoadSettingsResult { readonly values: Settings; /** Which layer each resolved key came from. */ readonly provenancePerKey: Readonly>; /** Which declared layers contributed nothing — a file nobody is reading is named, not hidden. */ readonly precedenceReport: PrecedenceReport; } /** * The settings as the declared layers resolve them. * * Never throws for a missing or malformed file; see {@link readLayer}. */ declare function loadSettings(input: LoadSettingsInput): LoadSettingsResult; /** * Split a markdown file into frontmatter lines and body. * * ## Why this is shared rather than duplicated * * Two loaders need it — the M74 instruction tree and the M76 command loader — and they need the SAME * answer to the same question: where does the metadata stop and the content begin, and what happens * when the fence never closes. That is one piece of knowledge (`G12`), not two similar-looking * functions. * * What is NOT shared is which KEYS each loader reads. `paths:` matters to instructions and * `description:` matters to commands, and folding those together would build a vocabulary neither * one asked for. */ interface ParsedFrontmatter { /** Lines between the fences, excluding them. Empty when the file has no frontmatter. */ readonly frontmatter: readonly string[]; /** Everything after the closing fence — or the whole file when there is no frontmatter. */ readonly body: string; } /** * Split `raw`, or return `undefined` when the frontmatter opens and never closes. * * `undefined` rather than a best guess: a file whose fence never closes is malformed in a way that * makes its metadata unknowable, and guessing whether the rest is body or metadata feeds the caller * either the wrong text or the wrong settings. Both callers treat that as "skip this file, warn, * keep going" — failure is per file, never per tree. */ declare function splitFrontmatter(raw: string): ParsedFrontmatter | undefined; /** * Read one scalar key from frontmatter lines. * * Deliberately not a YAML parser. Both callers read a handful of known keys, and pulling in a parser * to do that would be a dependency for a feature nobody asked for (parsimony rungs 4 → 1). An * unrecognised key is ignored rather than rejected — a product may put its own metadata there. */ declare function frontmatterValue(frontmatter: readonly string[], key: string): string | undefined; /** * M74 — context pressure: the fraction of the window a run has consumed. * * ## Why this had no counterpart * * The framework shipped the DENOMINATOR (`resolveEffectiveContextWindow`, crossed in M67) and the * NUMERATOR (token usage, on every `done` event) and never put them together. So every product that * wanted to warn a user before a run hit the wall computed the ratio itself, and each picked its own * thresholds. * * The arithmetic is trivial; publishing it is not about saving a division. It is about there being * ONE answer to "is this run in trouble", so a warning in the CLI and a badge in a dashboard agree. */ /** How close a run is to its context limit. */ type ContextPressure = 'ok' | 'warn' | 'critical'; /** * Fractions of the effective window at which each level begins. * * Defaults chosen to leave room to act: `warn` at 75% is early enough that compacting still helps, * `critical` at 90% is where the next turn may not fit. Configurable because a product with long * tool outputs legitimately wants an earlier warning than one with short chat turns. */ interface ContextPressureThresholds { readonly warn: number; readonly critical: number; } declare const DEFAULT_CONTEXT_PRESSURE_THRESHOLDS: ContextPressureThresholds; /** Raised when thresholds are ordered in a way that makes a level unreachable. */ declare class ContextPressureThresholdError extends TheokitAgentError { readonly name = "ContextPressureThresholdError"; constructor(message: string); } /** * Classify a run's context pressure. * * @param usedTokens tokens the run has consumed * @param effectiveWindow the window it is consuming from — typically * `resolveEffectiveContextWindow(...)`, re-exported here so a caller reaches both through one import * (see {@link effectiveContextWindow}). * * An `effectiveWindow` of zero or less returns `'ok'` rather than dividing: an unknown window is not * evidence of pressure, and `Infinity`/`NaN` reaching a UI as a percentage is worse than saying * nothing. Missing evidence is not evidence — the same rule the transcript collector applies to a * missing mtime. * * @throws {ContextPressureThresholdError} when `warn` is not below `critical` — a caller who * inverted them holds a belief about their own thresholds that is wrong, and silently sorting them * would leave that belief intact. */ declare function contextPressure(usedTokens: number, effectiveWindow: number, thresholds?: ContextPressureThresholds): ContextPressure; export { type AgentMemory, AgentMemoryError, type AgentMemoryScope, type ComposeInstructionsOptions, type ComposedInstructions, type ConfigLayer, type ContextPressure, ContextPressureThresholdError, type ContextPressureThresholds, type CustomCommand, type CustomCommandsResult, DEFAULT_CONTEXT_PRESSURE_THRESHOLDS, type ExpandImportsInput, FILE_INLINE_CAP, type InstructionBlock, type InstructionSource, type InstructionTree, type InstructionTreeBudget, LAYERS_ARE_POSITIONED, LayerOutOfOrderError, LayeredConfig, type LayeredConfigInput, type LayeredConfigResult, type LoadCustomCommandsInput, type LoadInstructionTreeInput, type LoadOutputStyleInput, type LoadSettingsInput, type LoadSettingsResult, MEMORY_BYTE_CAP, MEMORY_LINE_CAP, type OperatorDefinition, type OperatorOrigin, type OperatorPolicy, OperatorRootUnreadableError, type OperatorRootsOptions, type OperatorRootsResult, type OutputStyle, OutputStyleError, type ParsedFrontmatter, type PermissionTranslation, type PermissionsBlock, type PrecedenceReport, type ProvenancePerKey, type ResolveAgentMemoryInput, SETTINGS_LAYERS, type Settings, type SettingsLayer, type ShellResult, type SkippedDefinition, type TemplateDeps, type TrustRecord, TrustStore, TrustStorePermissionsError, type UnsupportedPermissionEntry, type WithheldRoot, applySubagentMemory, blockAppliesTo, composeInstructions, contextPressure, currentOperatorPolicy, expandCommandTemplate, expandInstructionImports, frontmatterValue, layerPrecedence, loadCustomCommands, loadInstructionTree, loadOutputStyle, loadSettings, mcpServerAdmitted, permissionRulesFromSettings, resolveAgentMemory, resolveOperatorRoots, resolveOutputStyle, settingsLayerChain, splitFrontmatter, templateHints };