/** * Repo-level docs subscription config: `/.auden/config.json`. * * This is a DIFFERENT file from the machine-level `~/.auden/config.json` that * `src/config.ts` owns. That one lives in the home directory, is chmod `0600`, * and holds the bearer **token**. This one is repo-relative, meant to be * committed to git, and holds a repo's doc-bundle subscription * (`docs.bundles`) and its portable project identity (`project.id`). They share * a basename by design-doc convention (cross-repo-docs-plan.md) but never the * same path — this reader is built so a token can never be read from, or * written into, the committed repo file: it only ever touches the `docs` and * `project` keys, strips a `token` on every write, and is never routed through * `writeConfigToken`/the home-config writers. * * Kept free of citty/console so it unit-tests directly against a temp dir. */ /** Absolute path of a repo's `.auden/config.json`, rooted at `cwd`. */ export declare function repoConfigPath(cwd: string): string; /** * True when `cwd`'s repo config path resolves to the *same file* as the * machine-level `~/.auden/config.json` token store — i.e. `cwd` is the home * directory. Reading/writing there would treat the token file as a repo config * and (on write) strip the bearer token, breaking future authenticated * commands. Callers must refuse the repo-config path in that case. */ export declare function collidesWithHomeConfig(cwd: string): boolean; /** Why a repo config path must not be read or written, or `null` when it is safe. */ export type RepoConfigEscape = 'home-token-store' | 'outside-repo'; /** * `collidesWithHomeConfig` compares *lexical* paths, so it cannot see a config * that only looks repo-local: a repo can commit `.auden/config.json` (or the * whole `.auden/`) as a **symlink**, and `readFile`/`writeFile` follow it. A * checkout at `$HOME/repo` carrying `.auden/config.json -> ../../.auden/config.json` * would otherwise let a writer strip the bearer token out of the real machine * token store. So resolve symlinks on the deepest component that exists and * refuse anything landing on the token store or outside the repo — the same * containment posture `guide-projection.ts` already applies to guide writes. */ export declare function repoConfigEscape(cwd: string): Promise; /** * One subscribed bundle, as the committed config carries it. * * **The id is the identity and the slug is the label** (`unified-item-sync-plan.md` * → "A subscription names a bundle id, not a slug"). A slug is unique per *user* * — the server's index is `(user_id, slug)` — so a committed subscription naming * only a slug never referred to a specific bundle at all: a teammate who clones * the repo resolves it against their own account and gets a different bundle that * happens to share the name. The bundle id is globally unique, so it resolves the * same for everyone. The git-submodule shape: commit the URL and the SHA, not the * branch name. * * `id` is nullable because the on-disk format predates it and the file is * committed — every existing checkout carries bare slugs, and a clone made before * the migration ran is a legitimate reader of this type. A null id means "slug * only, unmigrated", never "no bundle". */ export type BundleSubscription = { /** Globally-unique bundle id, or null for a legacy slug-only entry. */ id: string | null; /** Human-readable label; what `auden status` prints. */ slug: string; }; export type RepoDocsConfigResult = { /** * Subscribed bundles (trimmed, de-duplicated by slug, empties dropped). * * Deliberately not accompanied by a parallel `bundles: string[]` of slugs. * Callers that only need slugs map for them at the point of use, which keeps * one shape of this fact on the read side — a second, id-less view is exactly * what a future call site would reach for and then silently lose the id * through (`CLAUDE.md` → Simplicity). */ subscriptions: BundleSubscription[]; /** True when the file did not exist on disk. */ missing: boolean; /** True when the file existed but was not a JSON object. */ malformed: boolean; /** True when a stray top-level `token` key is present (must be stripped on write). */ hasToken: boolean; }; /** * Normalize an arbitrary value into a clean subscription list: trim, drop * empties, de-duplicate by slug while preserving first-seen order. Shared by the * reader and the merge/write path so a hand-edited config and a `--bundle` flag * are cleaned identically. * * **Two accepted entry shapes, and both stay readable forever.** A bare string * is a legacy slug-only subscription; `{ id, slug }` is the current form. This * file is committed and travels by `git clone`, so a reader that rejected the old * shape would break every checkout made before the migration — including a * teammate's, who never ran it. Tolerant read, structured write. * * De-duplication is by **slug**, not by id, because that is the key the old * format had: two entries naming the same slug are one subscription written * twice, and dropping the later one would discard an id the earlier one lacks. So * the first entry wins on order and the first *id* seen for a slug wins on * identity — which is what lets an id be added to an existing entry by appending * rather than by rewriting in place. */ export declare function normalizeBundleSubscriptions(value: unknown): BundleSubscription[]; export type RepoProjectIdResult = { /** * The declared portable project identity (`project.id`), trimmed. `null` when * the key is absent, empty, or not a string — callers derive a default rather * than treating a malformed declaration as an id. */ projectId: string | null; /** True when the file did not exist on disk. */ missing: boolean; /** True when the file existed but was not a JSON object. */ malformed: boolean; /** True when a stray top-level `token` key is present (must be stripped on write). */ hasToken: boolean; }; /** * Read the repo's declared `projectId` — the committed, portable identity every * clone and teammate agrees on (`guide-write-side-identity` slice 1). Same * guards as the docs reader: never touches the file when `cwd` is the home * directory, where that path is the bearer-token store. */ export declare function readRepoProjectId(cwd: string): Promise; /** * Read a repo's docs-bundle subscription. Returns an empty list on a missing or * malformed config so callers can treat "not subscribed" uniformly; `missing` * / `malformed` distinguish the two for messaging. */ export declare function readRepoDocsConfig(cwd: string): Promise; export type WriteRepoDocsResult = { /** * True when a `token` key was found in the existing file and stripped before * writing. The committed repo config must never carry a bearer token; if one * was there (e.g. the file was copied from `~/.auden/config.json` — the exact * basename-collision this slice guards against), we remove it rather than * re-serialize it back into a file the user commits. The caller should warn * so the user can rotate a token that may already be in git history. */ removedToken: boolean; }; /** * Persist a repo's docs-bundle subscription, preserving every other key already * in the file (`project.id`, hand-added keys) — but never a `token`, which is * stripped defensively so this committed file can't leak a credential. Refuses * to overwrite a file that exists but is malformed, rather than silently * discarding it. */ export declare function writeRepoDocsBundles(cwd: string, subscriptions: BundleSubscription[]): Promise; /** * Persist the repo's declared `projectId` under `project.id`, preserving every * other key (including `docs.bundles`) and stripping a `token` on the same * terms as the docs writer. This is the one field a teammate is expected to * commit, so it must never be written into the home token store. */ export declare function writeRepoProjectId(cwd: string, projectId: string): Promise; /** * Union an existing subscription list with newly-selected bundles, cleaned and * de-duplicated. `docs enable` is additive: re-running it to add one bundle * must never drop the others already subscribed. * * **Add, never replace — and that includes learning an id for a slug already * there.** `added` goes second, so `normalizeBundleSubscriptions` keeps the * existing entry's position while adopting an id it did not have. That is the * whole mechanism behind the enrollment migration being safe to re-run and safe * on a repo that already subscribes (`unified-item-sync-plan.md` → Slice 0). */ export declare function mergeBundleSubscriptions(existing: BundleSubscription[], added: BundleSubscription[]): BundleSubscription[]; /** * Turn bare slugs (a `--bundle` flag, a `auden pull ` argument) into * subscriptions. The id is unknown at the command line — the user typed a name — * so it is null until a sync learns it from the server. */ export declare function subscriptionsFromSlugs(slugs: string[]): BundleSubscription[]; //# sourceMappingURL=repo-config.d.ts.map