agent-shim 7.13.1 → 7.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/cli.cjs +95 -27
  2. package/dist/index.cjs +70 -7
  3. package/dist/index.mjs +70 -7
  4. package/dist/types/index.d.ts +2789 -40
  5. package/package.json +3 -1
  6. package/dist/types/checkReport.d.ts +0 -154
  7. package/dist/types/claudeShim.d.ts +0 -114
  8. package/dist/types/cli/bool.d.ts +0 -8
  9. package/dist/types/cli/commandDeps.d.ts +0 -39
  10. package/dist/types/cli/envBool.d.ts +0 -11
  11. package/dist/types/cli/pairs.d.ts +0 -19
  12. package/dist/types/cliError.d.ts +0 -27
  13. package/dist/types/codex/http.d.ts +0 -15
  14. package/dist/types/config/classify.d.ts +0 -49
  15. package/dist/types/config/load.d.ts +0 -42
  16. package/dist/types/config/schema.d.ts +0 -746
  17. package/dist/types/config/store.d.ts +0 -46
  18. package/dist/types/configProfilesStore.d.ts +0 -61
  19. package/dist/types/configure.d.ts +0 -170
  20. package/dist/types/credential.d.ts +0 -136
  21. package/dist/types/credentialCache.d.ts +0 -46
  22. package/dist/types/directoryRulesStore.d.ts +0 -43
  23. package/dist/types/doctorReport.d.ts +0 -117
  24. package/dist/types/frontdoor/capability.d.ts +0 -19
  25. package/dist/types/frontdoor/capture.d.ts +0 -58
  26. package/dist/types/frontdoor/connect.d.ts +0 -209
  27. package/dist/types/frontdoor/connectEffects.d.ts +0 -3
  28. package/dist/types/frontdoor/custody.d.ts +0 -32
  29. package/dist/types/frontdoor/ensure.d.ts +0 -36
  30. package/dist/types/frontdoor/headroomHop.d.ts +0 -22
  31. package/dist/types/frontdoor/pipeline.d.ts +0 -109
  32. package/dist/types/frontdoor/probe.d.ts +0 -13
  33. package/dist/types/frontdoor/realFrontDoorPort.d.ts +0 -5
  34. package/dist/types/frontdoor/route.d.ts +0 -112
  35. package/dist/types/frontdoor/server.d.ts +0 -44
  36. package/dist/types/frontdoor/state.d.ts +0 -57
  37. package/dist/types/frontdoor/supervisor.d.ts +0 -48
  38. package/dist/types/frontdoor/tap.d.ts +0 -9
  39. package/dist/types/frontdoor/trust.d.ts +0 -30
  40. package/dist/types/frontdoor/x509.d.ts +0 -11
  41. package/dist/types/headroom/ensure.d.ts +0 -31
  42. package/dist/types/headroom/headers.d.ts +0 -11
  43. package/dist/types/headroom/settings.d.ts +0 -13
  44. package/dist/types/headroom/source.d.ts +0 -8
  45. package/dist/types/headroom/state.d.ts +0 -77
  46. package/dist/types/headroom/supervisor.d.ts +0 -107
  47. package/dist/types/identityStore.d.ts +0 -85
  48. package/dist/types/launchWiring.d.ts +0 -49
  49. package/dist/types/launcher/argv.d.ts +0 -42
  50. package/dist/types/launcher/cascade.d.ts +0 -42
  51. package/dist/types/launcher/cliOverride.d.ts +0 -38
  52. package/dist/types/launcher/extraFlags.d.ts +0 -6
  53. package/dist/types/launcher/farm.d.ts +0 -168
  54. package/dist/types/launcher/flags.d.ts +0 -96
  55. package/dist/types/launcher/guard.d.ts +0 -57
  56. package/dist/types/launcher/identity.d.ts +0 -95
  57. package/dist/types/launcher/lock.d.ts +0 -41
  58. package/dist/types/launcher/pool.d.ts +0 -44
  59. package/dist/types/launcher/ports.d.ts +0 -135
  60. package/dist/types/launcher/spawn.d.ts +0 -20
  61. package/dist/types/launcher.d.ts +0 -98
  62. package/dist/types/legacy.d.ts +0 -29
  63. package/dist/types/pathNorm.d.ts +0 -14
  64. package/dist/types/paths.d.ts +0 -79
  65. package/dist/types/poolPickReport.d.ts +0 -44
  66. package/dist/types/poolStore.d.ts +0 -18
  67. package/dist/types/providersStore.d.ts +0 -137
  68. package/dist/types/realCredentialCache.d.ts +0 -11
  69. package/dist/types/realPorts.d.ts +0 -109
  70. package/dist/types/resolve/conditions.d.ts +0 -37
  71. package/dist/types/resolve/decide.d.ts +0 -45
  72. package/dist/types/resolve/extends.d.ts +0 -41
  73. package/dist/types/resolve/flatten.d.ts +0 -13
  74. package/dist/types/resolve/match.d.ts +0 -58
  75. package/dist/types/resolve/pipeline.d.ts +0 -33
  76. package/dist/types/resolve/plan.d.ts +0 -57
  77. package/dist/types/resolve/projects.d.ts +0 -61
  78. package/dist/types/resolve/reconcile.d.ts +0 -80
  79. package/dist/types/resolve/types.d.ts +0 -159
  80. package/dist/types/resolve/walk.d.ts +0 -85
  81. package/dist/types/resolve/walkPolicy.d.ts +0 -15
  82. package/dist/types/usage/account.d.ts +0 -20
  83. package/dist/types/usage/middleware.d.ts +0 -17
  84. package/dist/types/usage/pick.d.ts +0 -62
  85. package/dist/types/usage/plan.d.ts +0 -18
  86. package/dist/types/usage/poolPick.d.ts +0 -31
  87. package/dist/types/usage/preflight.d.ts +0 -20
  88. package/dist/types/usage/rateLimit.d.ts +0 -25
  89. package/dist/types/usage/read.d.ts +0 -70
  90. package/dist/types/usage/scan.d.ts +0 -24
  91. package/dist/types/usage/schema.d.ts +0 -318
  92. package/dist/types/usage/store.d.ts +0 -43
  93. package/dist/types/versionDiscovery.d.ts +0 -47
@@ -1,18 +0,0 @@
1
- import { CliError } from "./cliError";
2
- import { type Pool } from "./config/schema";
3
- import type { LayoutPaths } from "./paths";
4
- /** Raised when a command names a pool that is not defined in the global config. */
5
- export declare class PoolNotFoundError extends CliError {
6
- readonly poolName: string;
7
- constructor(poolName: string);
8
- }
9
- /** The pools defined in `~/.agent-shim/config.json`, by name. */
10
- export declare function readPools(paths: LayoutPaths): Readonly<Record<string, Pool>>;
11
- /** The pool named `name`; throws `PoolNotFoundError` when none is defined. */
12
- export declare function requirePool(paths: LayoutPaths, name: string): Pool;
13
- /** Defines a new pool. Throws `InvalidPoolNameError` for a name `PoolNameSchema` rejects and `PoolAlreadyExistsError` for one already defined. */
14
- export declare function addPool(paths: LayoutPaths, name: string, identities: readonly string[]): Pool;
15
- /** Replaces an existing pool's members. Throws `PoolNotFoundError` when it is not defined. */
16
- export declare function setPool(paths: LayoutPaths, name: string, identities: readonly string[]): Pool;
17
- /** Removes a pool. Throws `PoolNotFoundError` when it is not defined. Anything that selected it (an active-identity file, a directory rule) is left for `doctor` to report, as with a removed identity. */
18
- export declare function removePool(paths: LayoutPaths, name: string): void;
@@ -1,137 +0,0 @@
1
- import { type PROVIDER_CREDENTIAL_TARGETS, type PROVIDER_KINDS, type CodexProviderConfig, type CredentialCache, type CredentialSource, type Provider } from "./config/schema";
2
- import { CliError, UsageError } from "./cliError";
3
- import { type CredentialPort } from "./credential";
4
- import type { ResolvedProvider } from "./launcher/flags";
5
- import type { FsPort } from "./launcher/ports";
6
- import { type CascadeInput } from "./resolve/walk";
7
- import type { LayoutPaths } from "./paths";
8
- /** Raised by any operation that requires a provider to already exist, when it does not. */
9
- export declare class ProviderNotFoundError extends CliError {
10
- readonly name: string;
11
- constructor(name: string);
12
- }
13
- /** Raised by `addProvider` when a provider with the given name already has a file. */
14
- export declare class ProviderAlreadyExistsError extends CliError {
15
- readonly providerName: string;
16
- constructor(providerName: string);
17
- }
18
- /**
19
- * Raised by `addProvider` when `name` fails the provider naming rule: the same rule `IdentitySchema` applies to identity names (start with a letter or number, then letters, numbers, dots, hyphens, underscores, and at signs). Keeping the two rules identical means a name that works for one works for the other, and neither vocabulary can ever produce a path segment that escapes `providers/` or `identities/`.
20
- */
21
- export declare class InvalidProviderNameError extends CliError {
22
- readonly attemptedName: string;
23
- constructor(attemptedName: string);
24
- }
25
- /** True when a provider file exists for `name`, regardless of whether it validates. */
26
- export declare function providerExists(paths: LayoutPaths, name: string): boolean;
27
- /** Written in a replacement in place of an old fixed `env.ANTHROPIC_AUTH_TOKEN` value, which is never printed: the person converting the file copies the value across themselves. */
28
- export declare const LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
29
- /** What converting one old-format provider file involves: the old fields it uses, and the whole file rewritten in the current format. */
30
- export interface LegacyProviderConversion {
31
- readonly fields: readonly string[];
32
- readonly replacement: Record<string, unknown>;
33
- }
34
- /**
35
- * Recognises a provider file written in the format before the credential block (a `tokenEnv`, `tokenCommand` or `authScheme` field, or a credential variable in `env`, and no `credential`) and builds its exact replacement: `tokenEnv` becomes an `env` source, `tokenCommand` a `command` source, a non-empty fixed `env.ANTHROPIC_AUTH_TOKEN` a `literal` source (its value replaced by `LEGACY_LITERAL_PLACEHOLDER`, never printed), `authScheme` the `target`, and the credential variables leave `env`. Undefined for anything else, including a file that is merely invalid.
36
- */
37
- export declare function legacyProviderConversion(raw: unknown): LegacyProviderConversion | undefined;
38
- /** Raised when a provider file is still in the format before the credential block, naming the old fields and giving the exact replacement. */
39
- export declare class LegacyProviderFileError extends CliError {
40
- readonly filePath: string;
41
- readonly conversion: LegacyProviderConversion;
42
- constructor(filePath: string, conversion: LegacyProviderConversion);
43
- }
44
- /** Reads and validates one provider definition, or undefined when it does not exist. */
45
- export declare function readProvider(paths: LayoutPaths, name: string): Provider | undefined;
46
- /**
47
- * Reads and validates one provider definition through an injected `FsPort`, the same way `loadIdentity` reads an `identity.json`: the launcher never touches the real filesystem directly, so provider loading must flow through the port it is already handed.
48
- */
49
- export declare function loadProvider(providersDir: string, name: string, port: FsPort): Provider | undefined;
50
- /** One provider definition as reported by `listProviders`. */
51
- export interface ProviderListEntry {
52
- readonly name: string;
53
- readonly provider: Provider;
54
- }
55
- /** Lists every provider under `providersDir` that has a valid `<name>.json`. A file that fails validation is skipped here; `provider list` is the place a broken definition gets surfaced, not a launch. */
56
- export declare function listProviders(paths: LayoutPaths): readonly ProviderListEntry[];
57
- /** The credential targets a provider may use. */
58
- export type ProviderCredentialTarget = (typeof PROVIDER_CREDENTIAL_TARGETS)[number];
59
- /** The kinds of provider. */
60
- export type ProviderKind = (typeof PROVIDER_KINDS)[number];
61
- /** Where a provider sends its sessions, for display: an `http` provider's base URL, or the front door for a codex provider (whose address exists only at launch). */
62
- export declare function describeProviderEndpoint(provider: Provider): string;
63
- /** Everything `addProvider` writes into a fresh provider file. `baseUrl` is required for an `http` provider and refused for a codex one; `codex` applies only to a codex provider. */
64
- export interface AddProviderInput {
65
- readonly kind?: ProviderKind;
66
- readonly displayName: string;
67
- readonly baseUrl?: string;
68
- readonly sources: readonly CredentialSource[];
69
- readonly target?: ProviderCredentialTarget;
70
- readonly env?: Readonly<Record<string, string>>;
71
- readonly codex?: CodexProviderConfig;
72
- }
73
- /** Raised when a provider option does not fit the provider's kind: a base URL for a codex provider, a missing one for an http provider, or codex settings for an http provider. */
74
- export declare class ProviderKindMismatchError extends UsageError {
75
- constructor(message: string);
76
- }
77
- /**
78
- * Creates a new provider definition. Throws `ProviderAlreadyExistsError` when a provider with this name already has a file, `InvalidProviderNameError` when `name` fails the naming rule, and `ConfigValidationError` when the assembled definition fails `ProviderSchema` (e.g. a `baseUrl` that is not a URL) rather than letting the underlying `ZodError` escape as an unhandled crash.
79
- */
80
- export declare function addProvider(paths: LayoutPaths, name: string, input: AddProviderInput): Provider;
81
- /**
82
- * The fields `updateProvider` changes. `sources` replaces the whole ordered source list (a list is set, not patched, since its order is its meaning); `target` changes where the token goes and keeps the sources. `env` entries merge over the existing ones and `unsetEnv` keys are then deleted.
83
- */
84
- export interface UpdateProviderInput {
85
- readonly displayName?: string;
86
- readonly baseUrl?: string;
87
- readonly sources?: readonly CredentialSource[];
88
- readonly target?: ProviderCredentialTarget;
89
- /** A new cache block, or false to remove it; undefined leaves the existing one. */
90
- readonly cache?: CredentialCache | false;
91
- readonly env?: Readonly<Record<string, string>>;
92
- readonly unsetEnv?: readonly string[];
93
- /** Merged over a codex provider's existing settings: a field given here replaces that field, and `models` entries merge per tier. */
94
- readonly codex?: CodexProviderConfig;
95
- }
96
- /**
97
- * Updates an existing provider definition in place. Throws `ProviderNotFoundError` when it does not exist, and `ConfigValidationError` when the updated definition fails `ProviderSchema`, leaving the file untouched.
98
- */
99
- export declare function updateProvider(paths: LayoutPaths, name: string, input: UpdateProviderInput): Provider;
100
- /** Deletes a provider definition. Throws `ProviderNotFoundError` when it does not exist. */
101
- export declare function removeProvider(paths: LayoutPaths, name: string): void;
102
- /**
103
- * The provider name the cascade selects through `launch.provider`, if any layer sets one. Last layer wins, the same rule `flattenLayers` applies to every other launch flag.
104
- */
105
- export declare function cascadeProviderName(cascade: CascadeInput): string | undefined;
106
- /** Inputs to `resolveProvider`. */
107
- export interface ResolveProviderParams {
108
- readonly paths: LayoutPaths;
109
- readonly port: FsPort;
110
- readonly env: Readonly<Record<string, string | undefined>>;
111
- /** Resolves the provider's credential sources. Omitted by a caller that cannot read secret files or run commands; a selected provider is then refused rather than launched without its credential. */
112
- readonly credentials?: CredentialPort;
113
- /** The `--provider <name>` flag's value, when one was given. It outranks any cascade layer, matching how the cliOverride layer composes last everywhere else. */
114
- readonly cliProvider?: string;
115
- /** The assembled-but-unflattened cascade for this launch, when one was loaded. Its layers are scanned for a `launch.provider` selection so a profile, directory rule, portable file, or the global config can pin a provider exactly the way they pin the other launch flags. */
116
- readonly cascade?: CascadeInput;
117
- }
118
- /**
119
- * The outcome of resolving this launch's provider: either a fully-resolved provider (definition plus its resolved credential), or a refusal the launcher turns into a stderr line and a non-zero exit.
120
- *
121
- * A provider whose credential block yields no token exits `CREDENTIAL_UNAVAILABLE_EXIT` (64), because the invocation cannot authenticate. An unknown provider name exits 1, matching every other "named thing not found" refusal in the launcher.
122
- */
123
- export type ProviderResolution = {
124
- readonly ok: true;
125
- readonly provider: ResolvedProvider;
126
- readonly warnings: readonly string[];
127
- } | {
128
- readonly ok: false;
129
- readonly status: number;
130
- readonly message: string;
131
- };
132
- /**
133
- * Resolves which API provider this launch routes through, if any: the `--provider` flag first, then the cascade's `launch.provider` selection. Returns undefined when nothing selected a provider at all.
134
- *
135
- * Pure over its injected ports, so the launcher's tests exercise refusals without touching a real providers directory, secret store or terminal: this function decides, and the caller owns the log/exit side effects.
136
- */
137
- export declare function resolveProvider(params: ResolveProviderParams): ProviderResolution | undefined;
@@ -1,11 +0,0 @@
1
- import type { CredentialCacheEnv } from "./credential";
2
- import type { LayoutPaths } from "./paths";
3
- import { type CredentialCachePort } from "./credentialCache";
4
- /** A file name for an owner such as `identity work`: every character outside letters, digits, `.`, `_` and `-` becomes `_`, so no owner can name a path outside the cache directory. */
5
- export declare function cacheFileNameFor(owner: string): string;
6
- /**
7
- * The real `CredentialCachePort`. The file store keeps one JSON file per owner under `<root>/credential-cache`, directory 0700 and file 0600. The Keychain store keeps the entry (base64 of its JSON, which has no spaces or quotes to escape) as a generic password under `KEYCHAIN_CACHE_SERVICE`, written through `security -i` on standard input so the token is never an argument. Both treat a missing or malformed entry as no entry.
8
- */
9
- export declare function createRealCredentialCache(root: string): CredentialCachePort;
10
- /** The cache environment the real launcher hands the resolver: the real stores under this layout's root, this platform, and the wall clock. */
11
- export declare function realCredentialCacheEnv(paths: LayoutPaths): CredentialCacheEnv;
@@ -1,109 +0,0 @@
1
- import type { CredentialPort } from "./credential";
2
- import { type DiscoveredClaudeBinary } from "./versionDiscovery";
3
- import type { LayoutPaths } from "./paths";
4
- import type { FarmFs, FsPort, HeadroomPort, LogPort, ProcPort, RunPort, SpawnPort } from "./launcher/ports";
5
- /** The real, `node:fs`-backed `FsPort` used by the actual `claude` launcher — never used in tests. */
6
- export declare const realFsPort: FsPort;
7
- export declare const realFarmFs: FarmFs;
8
- /**
9
- * Blocks the current thread for `ms` milliseconds.
10
- *
11
- * The launcher is synchronous end to end, right through to `spawnSync`, so waiting on another process's identity lock cannot be done with a promise. `Atomics.wait` on a private buffer is the one way to sleep synchronously without burning the CPU in a spin loop.
12
- */
13
- export declare function realSleepSync(ms: number): void;
14
- /**
15
- * Whether `pid` is a live, schedulable process: signal-0 alive AND not a zombie. This is the liveness notion every headroom coordination decision must use, because a defunct daemon holds no port and a defunct supervisor will never write state, yet both still "exist" as far as signal 0 is concerned.
16
- */
17
- export declare function realIsProcessRunning(pid: number): boolean;
18
- /** The real `RunPort`, used for auxiliary commands whose output this process needs to read — git branch detection for `when: { branch }` conditions, and `check.ts`'s macOS Keychain lookup. */
19
- export declare const realRunPort: RunPort;
20
- /**
21
- * The real `CredentialPort`. A secret file's mode is reported only where POSIX permission bits exist (not on Windows, whose ACLs Node's `stat` does not expose). A command runs with no shell under its timeout; a non-interactive one gets no stdin and has its stderr captured for the error message, while an interactive one inherits the terminal's stdin and stderr so its prompt reaches a person. stdout is always captured, since it is the token.
22
- */
23
- export declare const realCredentialPort: CredentialPort;
24
- /** Reads the git branch checked out at `cwd`, and whether the repository is in detached-HEAD state. Both undefined when `cwd` is not in a repository at all. */
25
- export declare function resolveGitBranch(run: RunPort, cwd: string): {
26
- branch?: string;
27
- branchDetached?: boolean;
28
- };
29
- /**
30
- * The spawn options every background daemon agent-shim starts shares. `detached: true` puts the child in a new session with no controlling terminal, so closing the terminal a launch started from (SIGHUP to that terminal's session) never reaches it: a second session still using the daemon keeps working after the first one's terminal is gone. Standard input is closed and both output streams go to the daemon's log file.
31
- */
32
- export declare function detachedDaemonSpawnOptions(logFd: number, env: NodeJS.ProcessEnv): {
33
- readonly detached: true;
34
- readonly stdio: ["ignore", number, number];
35
- readonly env: NodeJS.ProcessEnv;
36
- };
37
- /**
38
- * Spawns a detached supervisor: a background copy of this very executable running a hidden internal subcommand, its output appended to `logPath`, unref'd so the launcher never waits on it. `AGENT_SHIM_HOME` is passed explicitly so the supervisor lands on the same root as its spawner even when the launcher was started with the variable set only for itself.
39
- */
40
- export declare function spawnDetachedSupervisor(paths: LayoutPaths, subcommand: string, logPath: string): number;
41
- /** Starts a background daemon: `paths`, `subcommand` and `logPath` as for `spawnDetachedSupervisor`, which a caller that is not the agent-shim command line process supplies in place of it. */
42
- export type DaemonSpawner = (paths: LayoutPaths, subcommand: string, logPath: string) => number;
43
- /** A `DaemonSpawner` that starts daemons through the agent-shim executable at `executable`, for a caller (a library host) whose own process is not it. */
44
- export declare function spawnDaemonThrough(executable: string): DaemonSpawner;
45
- /**
46
- * Whether nothing is currently listening on one specific loopback port: binds it on 127.0.0.1 and releases, so the headroom supervisor can decide whether its sticky `lastPort` is still reusable. The same bind-and-release technique the supervisor's `freePort` uses, applied to a number the caller already cares about rather than asking the OS to pick one.
47
- */
48
- export declare function realIsPortFree(port: number): Promise<boolean>;
49
- /**
50
- * The real `HeadroomPort` for one launch: `ensure` runs the lock-and-poll coordination against the real filesystem and process table and derives the per-project identity from the real git repository containing the working directory; `release` removes this launcher's session-registry entry.
51
- */
52
- export declare function realHeadroomPort(paths: LayoutPaths, options: {
53
- readonly spawnDaemon: DaemonSpawner;
54
- readonly cwd: string;
55
- }): HeadroomPort;
56
- /** The real `SpawnPort`, backed by `node:child_process`'s `spawnSync` — the Node equivalent of the legacy bash tool's `exec`. */
57
- export declare const realSpawnPort: SpawnPort;
58
- /** The real `ProcPort`, reading the actual process environment and logical CLI argv (`process.argv.slice(2)`). */
59
- export declare const realProcPort: ProcPort;
60
- /** The real `LogPort`, writing info/warn to stdout and errors to stderr. */
61
- export declare const realLogPort: LogPort;
62
- /**
63
- * The pure decision logic behind `findExecutableInDir`, taking `platform`/`pathext`/the mode-stat as explicit parameters so it's unit-testable against fake Windows/POSIX environments without touching real process globals or a real filesystem — this is exactly the kind of platform-dependent logic that's easy to get subtly wrong (confirmed: it was, twice, across two failed release attempts before this was extracted and actually tested).
64
- *
65
- * `statFileMode` returns the file's POSIX mode bits if `candidate` exists as a regular file, or `undefined` otherwise (ENOENT collapsed to `undefined`, every other error still propagated by the real implementation).
66
- *
67
- * On Windows, this can't rely on `fs.Stats.mode`'s execute bits at all (see the comment above `windowsPathExtensions`), so it instead mirrors what a real shell does resolving a bare command name: try `name` as given if it already carries a recognised extension (e.g. a `.exe` filename passed in explicitly, as `resolveOwnExecutablePath`'s Scoop-shim redirect does), otherwise try appending each `PATHEXT` extension in turn and return the first that exists as a file. Everywhere else, a file counts as executable only when its POSIX mode bits actually say so.
68
- */
69
- export declare function resolveExecutableCandidate(dir: string, name: string, env: {
70
- readonly platform: string;
71
- readonly pathext: string | undefined;
72
- readonly statFileMode: (candidate: string) => number | undefined;
73
- }): string | undefined;
74
- /** Looks for a file literally named `name` inside `dir` and confirms it's runnable. Exported so `src/claudeShim.ts`'s PATH-shadow and shim-redirect checks can reuse the exact same semantics `discoverClaudeBinary`'s own PATH fallback already uses, rather than shelling out to a shell builtin like `command -v`. Real-wired convenience over `resolveExecutableCandidate` — see that function for the actual decision logic and its own tests. */
75
- export declare function findExecutableInDir(dir: string, name: string): string | undefined;
76
- /**
77
- * Resolves the absolute path to the currently-running agent-shim/claude executable from the raw process/env facts a call site would otherwise read directly off `process` — passed explicitly so this is unit-testable without touching real process globals.
78
- *
79
- * This exists because `process.argv[1]` is not a reliable "path to my own executable" for a Node single-executable-application (SEA) binary the way it is for an ordinary Node script. Per Node's own SEA documentation, a SEA binary's `argv[1]` merely echoes back whatever string appeared on the command line — verbatim, with no path or PATH resolution applied by Node or the OS — whereas a shebang-invoked script (the npm/plain-Node case) genuinely receives its resolved script path there, because the kernel's shebang handling substitutes the shell's already-PATH-resolved pathname before Node ever starts. Concretely: typing the bare word `agent-shim` at a shell prompt produces `argv[1] === "agent-shim"` for a SEA binary (no directory component at all), which previously got fed straight into `fs.realpathSync`/`path.dirname` as if it were a real path — resolving relative to `process.cwd()` instead of wherever PATH actually found the binary, and throwing ENOENT the moment cwd didn't happen to contain a same-named file. Confirmed via a minimal instrumented SEA binary reproducing exactly this, and matching the exact failure every release verification job hit once invoking `agent-shim`/`claude` as a bare PATH-resolved command rather than by absolute path.
80
- *
81
- * A second, separate wrinkle: some install channels invoke this binary through a re-exec wrapper rather than running it directly. Scoop's own shim (`~/scoop/shims/agent-shim.exe`) is a compiled proxy (confirmed from its source: https://github.com/ScoopInstaller/Shim/blob/main/cs/shim.cs) that calls `CreateProcessW(null, cmd, ...)` with the real target's absolute path as the first token of `cmd` — per the Win32 `CreateProcess` contract, passing `lpApplicationName = null` means that first token becomes both the module launched *and* what the child receives as its own command line — so this process ends up seeing the real target's absolute path, living in a versioned app directory Scoop deliberately keeps off PATH, not the shim's own PATH-visible location. Placing the new `claude` alongside that path (as the Homebrew/direct-invocation case correctly does) would create a file PATH can never find. Confirmed against a real Scoop install: `agent-shim shim enable` placed `claude.exe` next to the real target in the app directory, and the immediately following `claude --version` failed with "not recognized" because that directory was never on PATH.
82
- *
83
- * The redirect-if-not-on-PATH check below applies uniformly to whichever candidate we end up with — whether derived from a path-shaped `argv1`, or from the `execPath` fallback (`argv1` undefined) — since it's unconfirmed whether a Windows SEA binary duplicates `argv[0]` into `argv[1]` the same way the POSIX build does; treating both sources identically is strictly more robust either way and regresses nothing already confirmed working.
84
- *
85
- * The algorithm: if the raw candidate is a bare word with no path separator, it can only have been found via PATH lookup in the first place (Node/the OS applies no resolution to it at all), so search PATH ourselves for the first directory containing an executable of that name, reconstructing exactly what the shell already did. Otherwise, resolve it (or the `execPath` fallback) against cwd, then check whether the *resulting directory* is itself on PATH: if so, use it directly (the Homebrew/direct-invocation case — never realpath'd, since a package manager's PATH-visible entry is often a symlink elsewhere, e.g. Homebrew's `/opt/homebrew/bin/agent-shim` pointing at its own Cellar keg, and callers need that PATH-visible location, not the dereferenced target). If that directory is *not* on PATH (the Scoop re-exec case above), search PATH for a separately-installed entry sharing our own invoked basename before falling back to the direct candidate.
86
- */
87
- export declare function resolveOwnExecutablePath(env: {
88
- readonly argv1: string | undefined;
89
- readonly execPath: string;
90
- readonly cwd: string;
91
- readonly pathDirs: readonly string[];
92
- readonly findExecutableInDir: (dir: string, name: string) => string | undefined;
93
- }): string;
94
- /** Real-wired convenience over `resolveOwnExecutablePath`, reading the actual process globals — this is what every real call site (`cli.ts`, `doctor.ts`, `claudeShim.ts`'s command wiring) should use instead of reading `process.argv[1]`/`process.execPath` directly. */
95
- export declare function realOwnExecutablePath(): string;
96
- /**
97
- * Resolves the path to the actual file whose *content* is this running process — never a PATH-visible location, which can differ from the real content entirely (Scoop's own shim, a compiled proxy binary, sits at the PATH-visible location while the genuine agent-shim binary lives elsewhere; see `resolveOwnExecutablePath`'s own doc comment for the full mechanism). This is what `enableClaudeShim`/`disableClaudeShim` must hardlink/copy from and compare inodes against — using `resolveOwnExecutablePath`'s redirected PATH-visible location for this would hardlink Scoop's generic shim proxy itself, not agent-shim's real content, which is exactly the bug this function exists to avoid (confirmed against a real Scoop install: `shim enable` correctly placed `claude.exe` in `shims/`, but invoking it failed with Scoop's own "Cannot open shim file for read" error, because the hardlink's source was Scoop's proxy, paired via a `.shim` config file keyed to its *original* filename).
98
- *
99
- * `node:sea`'s `isSea()` is the correct, documented way to distinguish the two operating modes this tool ships in, rather than inferring it from `argv1`'s shape: for a single-executable-application build, `process.execPath` is always the real, fully-resolved running binary (confirmed: Windows `CreateProcess` with `lpApplicationName = null` still resolves to the genuine target file, the same one `execPath` reports, regardless of Scoop's separate PATH-visible proxy). For a plain Node script (the npm-published bundle), `execPath` would instead point at the Node interpreter itself — useless — so `argv1` (the shebang-resolved script path, reliable in this mode) is used instead.
100
- */
101
- export declare function resolveContentSourcePath(env: {
102
- readonly isSea: boolean;
103
- readonly execPath: string;
104
- readonly argv1: string | undefined;
105
- }): string;
106
- /** Real-wired convenience over `resolveContentSourcePath`. */
107
- export declare function realContentSourcePath(): string;
108
- /** Builds the real `resolveClaudeBinary` callback `runLauncher` needs: scans the real versions directory, then falls back to a real PATH search, excluding this tool's own install directory. */
109
- export declare function realResolveClaudeBinary(ownInstallDirs: readonly string[]): () => DiscoveredClaudeBinary;
@@ -1,37 +0,0 @@
1
- import { type WhenCondition } from "../config/schema";
2
- import type { EntryFact } from "./types";
3
- /** Parses a duration literal like `90d` or `500ms` into milliseconds. Throws on anything the schema's own regex would already have rejected, so a malformed value can never be silently treated as zero. */
4
- export declare function parseDuration(value: string): number;
5
- /** True when the value would satisfy the schema's duration regex. */
6
- export declare function isDuration(value: string): boolean;
7
- /** Everything a `when` condition can be evaluated against, injected rather than read. */
8
- export interface ConditionContext {
9
- readonly nowMs: number;
10
- /** Facts about the specific entry being decided. Absent when evaluating a rule-level condition that has no single entry (a directory rule's own `when`). */
11
- readonly fact?: EntryFact;
12
- /** The branch checked out at `cwd`, or undefined when `cwd` is not in a repository. */
13
- readonly branch?: string;
14
- /** True when the repository is in detached-HEAD state, in which case no `branch` condition can match. */
15
- readonly branchDetached?: boolean;
16
- readonly env: Readonly<Record<string, string | undefined>>;
17
- }
18
- /** The result of evaluating one `when` object. */
19
- export interface WhenEvaluation {
20
- readonly passed: boolean;
21
- /** Which condition fields were present and evaluated. */
22
- readonly checked: readonly string[];
23
- /** Which of those fields did not hold. Empty when `passed` is true. */
24
- readonly failed: readonly string[];
25
- }
26
- /** True when `branch` matches `pattern`. The pattern is glob-capable (`client/*`); a detached HEAD or a non-repository directory never matches. */
27
- export declare function matchBranch(pattern: string, branch: string | undefined, detached?: boolean): boolean;
28
- /**
29
- * Evaluates a `when` object. Every present field must hold — conditions AND together within one object.
30
- *
31
- * An absent condition is vacuously true, so `when: {}` passes; `agent-shim check` warns about that rather than erroring, since an empty object is more likely a half-finished edit than an intentional statement.
32
- *
33
- * `newerThan`, `olderThan`, and `maxSizeBytes` read the subtree-aggregated facts (`latestMtimeMs`, `totalSizeBytes`), never the entry's own inode stat: a directory's own mtime does not change when a file three levels beneath it is rewritten, and its own size is a ~4KB inode figure that says nothing about what it contains.
34
- */
35
- export declare function evaluateWhen(when: WhenCondition | undefined, context: ConditionContext): WhenEvaluation;
36
- /** True when the `when` object is present but empty, which is vacuously true and therefore has no effect. */
37
- export declare function isVacuousWhen(when: WhenCondition | undefined): boolean;
@@ -1,45 +0,0 @@
1
- import { type CategoryName } from "../config/schema";
2
- import type { CompiledRule, Decision, Diagnostic, EliminatedRule, EntryFact, EntryFacts, FlattenedCascade } from "./types";
3
- /** Everything phase two needs, all of it injected. */
4
- export interface DecideParams {
5
- readonly flattened: FlattenedCascade;
6
- readonly facts: EntryFacts;
7
- /** Every top-level `~/.claude` entry name mapped to its category, or to null when nothing recognises it. */
8
- readonly classification: ReadonlyMap<string, CategoryName | null>;
9
- }
10
- /** A rule chosen by `selectRule`, alongside the candidates eliminated on the way to it. */
11
- export interface RuleSelection {
12
- readonly rule?: CompiledRule;
13
- readonly eliminated: readonly EliminatedRule[];
14
- }
15
- /**
16
- * Picks the winning entries rule for one path.
17
- *
18
- * Candidates are ranked by `compareSpecificity` and taken in order. A candidate whose `when` condition **fails is eliminated** and resolution continues to the next-most-specific matching rule — it never falls straight through to the category default, and a failed condition never inverts the rule's boolean value, which would not have a coherent meaning anyway. Only when no candidate survives does the caller fall back to the category.
19
- */
20
- export declare function selectRule(relPath: string, flattened: FlattenedCascade, context: {
21
- nowMs: number;
22
- branch?: string;
23
- branchDetached?: boolean;
24
- env: Readonly<Record<string, string | undefined>>;
25
- fact?: EntryFact;
26
- }): RuleSelection;
27
- /**
28
- * Resolves one path to a sharing decision, in exactly this order:
29
- *
30
- * 1. **The secret floor.** If the path's top-level entry classifies as `secret`, the answer is `false` unconditionally. This check sits *above* the entries lookup, not merely at the least-specific end of the cascade, and it fires on the path's **real classification**, never on a lexical reading of the key that matched it — so a glob written under a `runtime/` prefix that happens to reach `.credentials.json` is neutralised exactly like a deliberate `secret/` key would be.
31
- * 2. **Unclassified.** A top-level entry nothing recognises is not shared, and is reported rather than silently guessed at.
32
- * 3. **Entries rules**, most specific first, with a failing condition eliminating a candidate rather than ending the search.
33
- * 4. **The category**: a layer's own toggle if any layer set one, otherwise the shipped default.
34
- */
35
- export declare function resolveEntry(relPath: string, params: DecideParams): {
36
- decision: Decision;
37
- diagnostics: Diagnostic[];
38
- };
39
- /** Every decision the resolver reaches, plus every diagnostic raised along the way (phase-one diagnostics included, and de-duplicated). */
40
- export interface ResolveAllResult {
41
- readonly decisions: ReadonlyMap<string, Decision>;
42
- readonly diagnostics: readonly Diagnostic[];
43
- }
44
- /** Resolves every entry in the fact manifest. */
45
- export declare function resolveAll(params: DecideParams): ResolveAllResult;
@@ -1,41 +0,0 @@
1
- import type { ConfigProfile } from "../config/schema";
2
- import type { Diagnostic, Layer, LayerId } from "./types";
3
- /** One configuration profile as loaded from disk, with its entries key order captured at load time. */
4
- export interface ProfileSource {
5
- readonly name: string;
6
- readonly profile: ConfigProfile;
7
- readonly entryOrder?: readonly string[];
8
- /** Where it was loaded from, for diagnostics. Defaults to the profile name. */
9
- readonly filepath?: string;
10
- }
11
- /** Loads a profile by name. Injected, so nothing in the resolver reads a file. Returns undefined when no profile of that name exists. */
12
- export type ProfileLoader = (name: string) => ProfileSource | undefined;
13
- /** The linearised `extends` chain plus anything that went wrong resolving it. */
14
- export interface LinearisedProfiles {
15
- /** Profile names, base-first: every profile appears exactly once, after everything it extends. */
16
- readonly order: readonly string[];
17
- readonly diagnostics: readonly Diagnostic[];
18
- }
19
- /**
20
- * Linearises a profile's `extends` graph into a base-first order.
21
- *
22
- * The walker keeps **two separate structures**, and the distinction is load-bearing rather than stylistic:
23
- *
24
- * - `stack` is the current depth-first path. A name already on it means the graph genuinely loops back on itself, which is the only real cycle.
25
- * - `visited` is every name already emitted anywhere in the walk. A name already in it is a diamond — two branches sharing a base — and must simply be skipped, not re-emitted.
26
- *
27
- * Collapsing the two into one shared set produces a silent wrong answer in either direction. Using `stack` alone re-emits a shared base a second time in a diamond, so the base's values get re-applied on top of an intermediate profile that deliberately overrode them — the override silently disappears. Using `visited` alone false-flags every legitimate diamond as a cycle. Neither failure crashes; both just resolve to the wrong configuration.
28
- *
29
- * Emission is **post-order**: a profile is appended only after everything it extends, so `c extends [a, b]` where both extend `base` linearises to `[base, a, b, c]` — never `[base, a, base, b, c]`.
30
- */
31
- export declare function lineariseProfile(rootName: string, load: ProfileLoader): LinearisedProfiles;
32
- /**
33
- * Turns a profile's `extends` chain into cascade layers, base-first, starting at `startId`.
34
- *
35
- * A resolved profile is not a separate mechanism from the outer cascade: its chain flattens through the same ordered-layer sequence everything else uses, so a profile's resolved patch is simply one more run of inputs to the same algorithm.
36
- */
37
- export declare function profileLayers(rootName: string, load: ProfileLoader, startId: LayerId): {
38
- layers: Layer[];
39
- nextId: LayerId;
40
- diagnostics: readonly Diagnostic[];
41
- };
@@ -1,13 +0,0 @@
1
- import type { CompiledRule, FlattenedCascade, Layer } from "./types";
2
- /**
3
- * Cascade phase one: walk the ordered layer sequence once, spreading each layer's `categories` and `entries` over an accumulator. A later layer's value for the exact same category name, or the exact same key, replaces an earlier layer's value for that identical key. No path-specificity reasoning happens here — that is phase two's job.
4
- *
5
- * The entries accumulator is keyed by the **canonical** pattern, not the raw written key, so two differently-spelled but equivalent keys (`history/projects/~/work/x` and `history/projects/$HOME/work/x` after expansion) collapse into one rule here instead of racing as two separate candidates in phase two. Every surviving rule carries its own `{ layer, ordinal }`, which is what lets phase two tell a same-layer conflict from a cross-layer one without a separate provenance side-table.
6
- *
7
- * A key written under the `secret/` prefix is rejected outright with its own diagnostic and contributes no rule at all: `secret` is the one category no layer may open, and a deliberate attempt to name a secret path deserves a clearer error than the silent neutralisation the resolve-time floor check applies to a glob that reaches one incidentally.
8
- */
9
- export declare function flattenLayers(layers: readonly Layer[], options: Readonly<{
10
- home: string;
11
- }>): FlattenedCascade;
12
- /** Every compiled rule that matches `relPath`, ranked most-specific first. */
13
- export declare function matchingRules(flattened: FlattenedCascade, relPath: string): CompiledRule[];
@@ -1,58 +0,0 @@
1
- import { type CategoryName } from "../config/schema";
2
- import { CliError } from "../cliError";
3
- import type { CompiledRule } from "./types";
4
- /** True when `pattern` contains no glob metacharacter, so it can only ever match one literal path. */
5
- export declare function isExactPattern(pattern: string): boolean;
6
- /** The pattern's leading run of characters before the first glob metacharacter — the part that is definitely literal. */
7
- export declare function literalPrefixOf(pattern: string): string;
8
- /** Normalises a relative path fragment: collapses repeated slashes, drops `./` segments, and strips a trailing slash. Deliberately case-preserving and case-sensitive. */
9
- export declare function normaliseRelative(fragment: string): string;
10
- /** The outcome of canonicalising one entries key. */
11
- export interface CanonicalKey {
12
- readonly declaredCategory: CategoryName;
13
- /** The pattern with its category prefix stripped, relative to `~/.claude`, with any `history/projects/` path fragment already encoded. */
14
- readonly canonicalPattern: string;
15
- /** The `history/projects/` path fragment as written, before encoding — retained so ambiguity reporting can name what the author actually typed. */
16
- readonly projectFragment?: string;
17
- }
18
- /** Raised when an entries key cannot be canonicalised. Carries the offending key so the diagnostic can name it. */
19
- export declare class EntryKeyError extends CliError {
20
- readonly key: string;
21
- readonly reason: "unrooted-project-path" | "malformed";
22
- constructor(key: string, message: string, reason: "unrooted-project-path" | "malformed");
23
- }
24
- /**
25
- * Canonicalises one entries key into a pattern over paths relative to `~/.claude`.
26
- *
27
- * Every key is `<category>/<real-relative-path>`, so the category prefix is stripped and the remainder kept as written — with one deliberately narrow exception. Anything written after the literal `history/projects/` prefix is a real absolute working directory (optionally globbed), not a literal child directory name, because that directory's only real children are Claude Code's own encoded names and there is nothing else meaningful to reference there. Those fragments get `~`-expanded and forward-encoded; every other key in the whole design is a plain literal path or an ordinary glob over one, matched exactly as written.
28
- */
29
- export declare function canonicaliseEntryKey(key: string, options: Readonly<{
30
- home: string;
31
- }>): CanonicalKey;
32
- /**
33
- * Compiles a canonical pattern into a matcher that covers the pattern itself *and* everything beneath it, so a rule written as `knowledge/skills` decides `skills/commit/SKILL.md` too. Two picomatch matchers are needed rather than one: `skills/**` alone covers the subtree but `projects/*` does not match `projects/x/y`, and `projects/*` alone does not cover the files inside a matched project directory.
34
- *
35
- * Matching is deliberately case-sensitive (`nocase: false`) whatever the host filesystem does, so the same config behaves identically on a case-insensitive APFS volume and a case-sensitive one. `dot: true` so a glob can match a leading-dot entry.
36
- */
37
- export declare function compileMatcher(canonicalPattern: string): (relPath: string) => boolean;
38
- /**
39
- * True when some path *underneath* `dir` could match `canonicalPattern`, whether or not such a path exists today.
40
- *
41
- * This is what makes a conditional rule force materialisation: a directory Claude Code will write new children into cannot be symlinked wholesale if a rule might have something to say about those not-yet-existing children. Answering that from the pattern's structure rather than from the current file listing is the whole point — asking "does any existing child match" would pass the obvious test and fail the real one.
42
- */
43
- export declare function patternCouldReachUnder(canonicalPattern: string, dir: string): boolean;
44
- /**
45
- * Ranks two compiled rules that both match the same path. A positive result means `a` wins.
46
- *
47
- * The order, exactly:
48
- *
49
- * 1. **Different layers — the later layer wins, period.** This rank sits deliberately *above* exactness. Ranking exactness first would let an untrusted committed `.agent-shim.json`'s exact key beat your own later, personal glob override, breaking the design's stated trust property that a directory-scoped local rule can only ever tighten what a committed file opened, never the reverse. Nothing in the design asserts that an earlier exact key beats a later glob, so nothing is lost by closing that hole.
50
- * 2. Same layer: an exact literal beats a glob.
51
- * 3. Same layer: the longer literal prefix wins.
52
- * 4. Same layer: more path segments wins, which separates a one-wildcard pattern from a two-wildcard one at the same depth.
53
- * 5. Same layer: the later ordinal — source order within the file — wins.
54
- * 6. Canonical pattern, compared as a string, as a last-resort determinism guarantee.
55
- *
56
- * The order must be **total**: returning 0 for two distinct rules would make the winner depend on iteration order, which tests would only catch intermittently.
57
- */
58
- export declare function compareSpecificity(a: CompiledRule, b: CompiledRule): number;
@@ -1,33 +0,0 @@
1
- import { type ClassifyResult } from "../config/classify";
2
- import type { CategoryClassification, CategoryClassificationOverlay } from "../config/schema";
3
- import { type ResolveAllResult } from "./decide";
4
- import { type FarmPlan } from "./plan";
5
- import { type DescendPolicy } from "./walkPolicy";
6
- import { type AssembledCascade, type CascadeInput } from "./walk";
7
- import type { Diagnostic, EntryFacts, FlattenedCascade } from "./types";
8
- /** Everything one launch needs resolved, in one call. */
9
- export interface ResolveDecisionsInput {
10
- readonly facts: EntryFacts;
11
- readonly cascade: CascadeInput;
12
- readonly classification: {
13
- readonly defaults: CategoryClassification;
14
- readonly overlay?: CategoryClassificationOverlay;
15
- };
16
- }
17
- /** The fully resolved state of one launch: the layers that composed it, every entry's decision, the farm layout, and everything worth reporting. */
18
- export interface ResolvedState {
19
- readonly assembled: AssembledCascade;
20
- readonly flattened: FlattenedCascade;
21
- readonly classification: ClassifyResult;
22
- readonly decisions: ResolveAllResult["decisions"];
23
- readonly farm: FarmPlan;
24
- readonly diagnostics: readonly Diagnostic[];
25
- }
26
- /** The top-level `~/.claude` entry names present in a fact manifest, in sorted order. */
27
- export declare function topLevelNames(facts: EntryFacts): string[];
28
- /** The walk policy `buildEntryFacts` needs for `cascade`: which directories of `~/.claude` the fact walk must enter, derived from the flattened rules (see `descendPolicy`). */
29
- export declare function descendPolicyFor(cascade: CascadeInput, home: string): DescendPolicy;
30
- /**
31
- * Runs the whole pipeline for one launch: assemble the ordered layer sequence, flatten it, classify the real `~/.claude` entries, decide every path, and plan the resulting farm.
32
- */
33
- export declare function resolveDecisions(input: ResolveDecisionsInput): ResolvedState;
@@ -1,57 +0,0 @@
1
- import type { Decision, EntryFacts, FlattenedCascade } from "./types";
2
- /** What the farm should contain at one path. */
3
- type FarmPlanEntry =
4
- /** A single symlink standing in for this whole path — always absolute, always under `claudeHome`. */
5
- {
6
- readonly kind: "link";
7
- readonly rel: string;
8
- readonly target: string;
9
- }
10
- /** A real local directory the farm builds itself, because the decision beneath it is not uniform or because a conditional rule could touch it. */
11
- | {
12
- readonly kind: "materialise";
13
- readonly rel: string;
14
- readonly reason: MaterialiseReason;
15
- }
16
- /** Nothing at all in the farm for this path. */
17
- | {
18
- readonly kind: "omit";
19
- readonly rel: string;
20
- };
21
- /** Why a directory has to be built rather than symlinked. */
22
- type MaterialiseReason =
23
- /** Descendants of this directory resolve to different decisions, so a single symlink cannot express the outcome. */
24
- "split-decision"
25
- /** A conditional entries rule could match something under this directory, so the decision has to stay live. */
26
- | "conditional-rule";
27
- /** The full farm plan for one resolved cascade. */
28
- export interface FarmPlan {
29
- /** Every planned path, parents before children. */
30
- readonly entries: readonly FarmPlanEntry[];
31
- /** Directories the farm builds itself, in plan order. Written into the farm manifest so the next resync can tell them apart from directories Claude Code created. */
32
- readonly materialised: readonly string[];
33
- readonly links: readonly {
34
- readonly rel: string;
35
- readonly target: string;
36
- }[];
37
- }
38
- /** Everything `planFarm` needs, all of it already resolved. */
39
- export interface PlanFarmParams {
40
- readonly facts: EntryFacts;
41
- readonly decisions: ReadonlyMap<string, Decision>;
42
- readonly flattened: FlattenedCascade;
43
- }
44
- /** Indexes the fact manifest by parent path, so the plan can descend the tree without touching a filesystem. */
45
- export declare function buildChildIndex(facts: EntryFacts): Map<string, string[]>;
46
- /**
47
- * Turns a resolved set of decisions into a concrete farm layout.
48
- *
49
- * A directory becomes one symlink when the decision is uniform across its whole subtree, which is what keeps the common resync cheap — a few dozen top-level symlinks rather than a rebuilt tree. It is materialised, and recursed into, when either:
50
- *
51
- * - its subtree's decisions genuinely disagree, so no single symlink can express the outcome; or
52
- * - **any** conditional entries rule could reach something under it, even when today's evaluation happens to be uniform. This second case is the one a naive "is the decision uniform right now" check gets wrong: a symlinked directory freezes the decision for every file Claude Code creates *after* the resync, so a `newerThan` condition meant to share recent sessions would never see a session written five minutes from now. The bug only surfaces for files that do not exist yet, which is precisely why the check has to be structural rather than observational.
53
- *
54
- * Every symlink target is `<claudeHome>/<rel>`, unconditionally. That matters for the entries inside a materialised directory that are themselves relative symlinks escaping `~/.claude` (a real condition, not a hypothetical one — a skills directory linked out to a separate dotfiles repository, for instance). Copying such a link's own relative target verbatim would resolve to a different place from the farm's own depth, and copying its resolved absolute target would pin the farm to wherever that link pointed at resync time. Linking at `<claudeHome>/<rel>` and letting the OS follow the remaining hops from there is the only form that stays correct.
55
- */
56
- export declare function planFarm(params: PlanFarmParams): FarmPlan;
57
- export {};