gutterpress 0.10.0 → 0.10.1-beta.2

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 (57) hide show
  1. package/README.md +3 -16
  2. package/dist/api/index.d.ts +10 -19
  3. package/dist/api/index.js +8 -64
  4. package/dist/{audit-cvarpa72.js → audit-dce99css.js} +5 -5
  5. package/dist/{build-6r502chw.js → build-bj4x0727.js} +5 -5
  6. package/dist/checks/source/index.d.ts +1 -0
  7. package/dist/checks/source/merge-markers.d.ts +3 -0
  8. package/dist/{cli-cqtggsng.js → cli-0z6zf98d.js} +2 -47
  9. package/dist/{cli-eq5naw4m.js → cli-7n848af2.js} +370 -1249
  10. package/dist/{cli-zfcryxg8.js → cli-eewynen9.js} +97 -60
  11. package/dist/{cli-k1065rkg.js → cli-w692g6dx.js} +75 -29
  12. package/dist/{cli-149edp6b.js → cli-ydpf09d9.js} +1 -1
  13. package/dist/cli.js +14 -15
  14. package/dist/{doctor-akvxbtjb.js → doctor-7nd6n2aa.js} +2 -2
  15. package/dist/{engine-ft4cr3ep.js → engine-57vw8zpd.js} +1 -1
  16. package/dist/{engine-b159tbns.js → engine-8kdwy0jz.js} +2 -2
  17. package/dist/{gutterpress-viewer-te8g5grx.js → gutterpress-viewer-tma1qwz2.js} +57 -15
  18. package/dist/{index-ycpvr0am.js → index-7hrvksrb.js} +4 -4
  19. package/dist/{index-mdefp0y5.js → index-dtrftayr.js} +75 -27
  20. package/dist/{index-ge7q9xj3.js → index-h1x7krkg.js} +771 -1482
  21. package/dist/{index-wq3r5pj7.js → index-p0ebr7dh.js} +94 -57
  22. package/dist/index.js +9 -65
  23. package/dist/lib/git-fs.d.ts +43 -0
  24. package/dist/lib/host-policy.d.ts +14 -2
  25. package/dist/lib/remote-auth/clone.d.ts +0 -21
  26. package/dist/lib/remote-auth/converge-merge.d.ts +24 -7
  27. package/dist/lib/remote-auth/generic-auth.d.ts +0 -22
  28. package/dist/lib/remote-auth/git-http.d.ts +1 -1
  29. package/dist/lib/remote-auth/github-auth.d.ts +2 -22
  30. package/dist/lib/remote-auth/operation-log.d.ts +4 -9
  31. package/dist/lib/remote-auth/sync-messages.d.ts +35 -12
  32. package/dist/lib/remote-auth/sync-types.d.ts +30 -101
  33. package/dist/lib/remote-auth/sync.d.ts +19 -21
  34. package/dist/lib/remote-auth/token-store.d.ts +0 -10
  35. package/dist/lib/remote-auth/transport.d.ts +42 -40
  36. package/dist/lib/source-provider.d.ts +12 -0
  37. package/dist/{lint-p2sw53d9.js → lint-c8ze8sqw.js} +5 -5
  38. package/dist/{new-hvq0x91q.js → new-xy2qc300.js} +5 -5
  39. package/dist/{plugin-pssmk0dx.js → plugin-g9hwzjsm.js} +5 -5
  40. package/dist/{preflight-4j00yd0g.js → preflight-prcvdvmk.js} +5 -5
  41. package/dist/{preview-bridge-fz7vpk8m.js → preview-bridge-2nnrs2hy.js} +15 -0
  42. package/dist/{preview-d4s6gk6p.js → preview-h479apj9.js} +5 -5
  43. package/dist/{preview-interface-0ssk8bmm.js → preview-interface-2f5v96hd.js} +411 -88
  44. package/dist/{preview-shell-c5mfa3q0.js → preview-shell-kyjq59d9.js} +80 -25
  45. package/dist/{publish-tkc26en7.js → publish-emk644a8.js} +5 -5
  46. package/dist/{source-provider-3tcj6qg2.js → source-provider-2j5x2hyw.js} +1 -1
  47. package/dist/{source-provider-vanafrt9.js → source-provider-kn41jmbs.js} +1 -1
  48. package/dist/{validate-w9vfefrw.js → validate-be54bdc6.js} +5 -5
  49. package/package.json +1 -1
  50. package/dist/lib/app-heartbeat.d.ts +0 -102
  51. package/dist/lib/remote-auth/image-clash.d.ts +0 -17
  52. package/dist/lib/remote-auth/recovery/classify.d.ts +0 -97
  53. package/dist/lib/remote-auth/recovery/inspect.d.ts +0 -75
  54. package/dist/lib/remote-auth/recovery/locks.d.ts +0 -20
  55. package/dist/lib/remote-auth/recovery/repair.d.ts +0 -29
  56. package/dist/lib/remote-auth/recovery/types.d.ts +0 -39
  57. package/dist/repair-2va4w12t.js +0 -137
@@ -17,19 +17,6 @@ export interface HostCallbacks {
17
17
  /** Optional cancellation (user closed the dialog). */
18
18
  signal?: AbortSignal;
19
19
  }
20
- /**
21
- * Per-host auth-acquisition plugin contract (ADR 0006 D3 layer 3). github.com
22
- * gets the device flow below; every other host gets the generic token flow
23
- * (#14, not in this change).
24
- */
25
- export interface RemoteAuthProvider {
26
- /** Does this provider handle the given remote host? */
27
- matches(origin: URL): boolean;
28
- /** Interactive flow producing a credential for the host. */
29
- connect(callbacks: HostCallbacks): Promise<HostCredential>;
30
- /** Cheap revalidation for stored credentials. */
31
- validate(credential: HostCredential): Promise<boolean>;
32
- }
33
20
  /**
34
21
  * Client id resolution: explicit option → env var → registered default.
35
22
  * Empty/whitespace-only values at any layer are treated as unset (the packaged
@@ -48,20 +35,13 @@ export interface GitHubAuthProviderOptions {
48
35
  /** Injectable sleep for tests (so polling tests run instantly). */
49
36
  sleepImpl?: (ms: number) => Promise<void>;
50
37
  }
51
- /** GitHub device-flow {@link RemoteAuthProvider} (ADR 0006 D1). */
52
- export declare class GitHubAuthProvider implements RemoteAuthProvider {
38
+ /** GitHub device-flow credential acquisition (ADR 0006 D1). */
39
+ export declare class GitHubAuthProvider {
53
40
  private readonly clientId;
54
41
  private readonly fetchImpl;
55
42
  private readonly sleep;
56
43
  constructor(options?: GitHubAuthProviderOptions);
57
- matches(origin: URL): boolean;
58
44
  connect(callbacks: HostCallbacks): Promise<HostCredential>;
59
- /**
60
- * Cheap credential revalidation against `GET /user`. Returns `false` on any
61
- * non-200 (401 revoked/expired, 403 forbidden/rate-limit-blocked, …);
62
- * network failures are treated as "can't tell" → `true`.
63
- */
64
- validate(credential: HostCredential): Promise<boolean>;
65
45
  /** Best-effort login lookup after auth — failure is non-fatal. */
66
46
  private fetchUsername;
67
47
  }
@@ -20,10 +20,10 @@
20
20
  * [ISO-timestamp] LEVEL operation: step=<step> key=value ... | <message>
21
21
  *
22
22
  * Example:
23
- * [2026-06-19T12:34:56.789Z] INFO recovery: kind=unrelated_histories repo=my-book branch=main | starting recovery
24
- * [2026-06-19T12:34:56.890Z] INFO recovery: step=backup | backup created at /tmp/print-sync-recovery/my-book/...zip
25
- * [2026-06-19T12:34:57.789Z] WARN recovery: step=merge | merge conflicted files=manifest.yaml,notes.md
26
- * [2026-06-19T12:34:57.890Z] INFO recovery: result=needs_user | surfaced 2 conflicted files to user
23
+ * [2026-06-19T12:34:56.789Z] INFO sync: repo=my-book branch=main | starting sync
24
+ * [2026-06-19T12:34:56.890Z] INFO sync: step=snapshot | committed 3 changed files
25
+ * [2026-06-19T12:34:57.789Z] WARN sync: step=merge | combined files=manifest.yaml,notes.md
26
+ * [2026-06-19T12:34:57.890Z] INFO sync: result=synced | pushed to the online copy
27
27
  *
28
28
  * Cross-platform: uses `node:fs` appendFileSync + `node:path`. The caller
29
29
  * is responsible for providing a valid directory (the logger creates the
@@ -59,8 +59,3 @@ export declare function createFileLogger(logFile: string, operation: string, min
59
59
  * don't pass `logFile` see zero behavior change and zero file I/O).
60
60
  */
61
61
  export declare function resolveLogger(logFile: string | undefined, operation: string): OperationLogger;
62
- /**
63
- * Shorten a 40-char OID to the first 7 characters for compact log lines.
64
- * Same convention as `git log --oneline`.
65
- */
66
- export declare function shortOid(oid: string): string;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Author-language copy for sync / pull / push / conflict-resolution (never raw
3
- * git words). Extracted from sync.ts so the transport, conflict-resolution and
4
- * orchestrator modules share ONE source of these strings their exact wording
5
- * is part of the observable contract (the host renders them verbatim).
2
+ * Author-language copy for sync (never raw git words). Extracted from sync.ts
3
+ * so the transport and orchestrator modules share ONE source of these
4
+ * strings their exact wording is part of the observable contract (the host
5
+ * renders them verbatim).
6
6
  */
7
7
  export declare const MSG_UP_TO_DATE = "Everything is in sync.";
8
8
  export declare const MSG_UP_TO_DATE_PULLED = "Everything is in sync. The latest online changes were downloaded to this computer.";
@@ -12,14 +12,37 @@ export declare const MSG_OFFLINE = "Your changes are saved on this computer. gut
12
12
  export declare const MSG_AUTH = "The online repository didn't accept the saved connection. Reconnect and try again.";
13
13
  export declare const MSG_INSECURE_TRANSPORT = "This project's online address isn't secure, so the saved connection wasn't sent \u2014 connections are never sent over an insecure address. Switch the address to a secure one (starting with https), or to a local loopback address for a server on this computer, to sync.";
14
14
  export declare const MSG_BUSY = "The online copy is changing very quickly right now. Your work is saved on this computer \u2014 try Sync again in a moment.";
15
- export declare const MSG_UNRELATED = "The online address points at a different project's files, so the two can't be combined. Check the project's online address.";
15
+ /**
16
+ * The project's own version history is unreadable — a damaged or missing
17
+ * `.git`. Says the three things that are actually true and useful: the writing
18
+ * is safe, the HISTORY is what broke, and (sync always has a remote, so this
19
+ * always applies here) a fresh copy from online is the way back.
20
+ */
21
+ export declare const MSG_HISTORY_UNREADABLE = "This project's version history can't be read, so syncing can't continue. Your writing is safe \u2014 every file is still here on this computer. To get history and syncing working again, download a fresh copy of the project from online, then move any recent changes into it.";
22
+ export declare const MSG_UNRELATED = "This project and the online copy have no history in common, so they can't be combined. Either the online address points at a different project, or this project's own history was lost and started over. Your writing is safe \u2014 every file is still here on this computer. Check the online address; if it is correct, download a fresh copy from online and move any recent changes into it.";
16
23
  export declare const MSG_NO_REMOTE = "This project isn't connected to an online repository yet.";
17
24
  export declare const MSG_SSH_REMOTE = "This project's online address uses SSH (git@\u2026), which gutterpress can't sync to. Switch it to the web (HTTPS) address to sync from here.";
18
25
  export declare const MSG_NO_BRANCH = "This project's version history isn't on a named branch, so it can't be synced right now.";
19
- export declare const MSG_PULLED = "The latest online changes were downloaded to this computer.";
20
- export declare const MSG_PULLED_MERGED = "The latest online changes were combined with your changes on this computer.";
21
- export declare const MSG_PULL_UP_TO_DATE = "You already have the latest online changes.";
22
- export declare const MSG_PUSH_UP_TO_DATE = "There's nothing new to send \u2014 everything is already online.";
23
- export declare const MSG_PULL_FIRST = "The online copy has changes you don't have yet. Get the latest changes first, then send yours.";
24
- /** Message recorded on the automatic pre-sync snapshot (D5 invariant). */
25
- export declare const SYNC_SNAPSHOT_MESSAGE = "Snapshot before syncing";
26
+ /** Message recorded on the automatic pre-sync snapshot (D5 invariant).
27
+ * Pre-0.10.1 history carries the old spelling "Snapshot before syncing". */
28
+ export declare const SYNC_SNAPSHOT_MESSAGE = "Automatic backup of your work";
29
+ /**
30
+ * Message recorded when an edit reached disk while the fetch was in flight
31
+ * after the pre-sync snapshot, before the merge (see the re-snapshot in
32
+ * `syncProject`). Distinct wording so the race is legible in history; the
33
+ * copy is already writer-voiced, so it deliberately stays unchanged (every
34
+ * rename permanently grows the timeline's superseded-spelling list).
35
+ */
36
+ export declare const SYNC_LATE_EDIT_MESSAGE = "Saved the edit you made while syncing";
37
+ /**
38
+ * The keep-both sibling contract, kept HERE rather than in converge-merge.ts
39
+ * because every consumer needs it and converge-merge pulls in isomorphic-git:
40
+ * the markdown file resolver must not render a sibling as a chapter, and the
41
+ * merge-marker check must report it. This file imports nothing, so any of
42
+ * them can depend on it.
43
+ *
44
+ * `art/cover.png` -> `art/cover.online.png`; extensionless `NOTES` -> `NOTES.online`.
45
+ */
46
+ export declare function onlineSiblingPath(filepath: string): string;
47
+ /** True for a path `onlineSiblingPath` produced — Gutterpress's artifact, not the author's. */
48
+ export declare function isOnlineSibling(filepath: string): boolean;
@@ -16,26 +16,21 @@ import type { HostCredential, TokenStore } from "./token-store.ts";
16
16
  */
17
17
  export type GitCache = Record<string, unknown>;
18
18
  /**
19
- * One image file that changed on both sides. Sync already converged (the
20
- * NEWER side's bytes are on disk and committed the safe default); this
21
- * record lets a host offer a non-blocking side-by-side picker afterwards.
22
- * Both blob oids are pinned by the merge commit's two parents, so the picker
23
- * can never go stale.
19
+ * One file that changed on both sides and cannot carry conflict markers (a
20
+ * binary, or an SVG whose XML markers would break). Both versions are on
21
+ * disk and committed: ours stayed at `path`, the online one was written
22
+ * beside it. The host names the pair so the writer can fix it by hand.
24
23
  */
25
- export interface ImageClash {
26
- /** Repo-relative path of the image. */
24
+ export interface KeptBothFile {
25
+ /** Repo-relative path holding OUR version (unchanged). */
27
26
  path: string;
28
- /** Blob oid of the local version. */
29
- localOid: string;
30
- /** Blob oid of the online version. */
31
- remoteOid: string;
32
- /** Which side the automatic newer-wins policy kept on disk. */
33
- kept: "local" | "online";
27
+ /** Repo-relative path holding the ONLINE version (`name.online.ext`). */
28
+ onlinePath: string;
34
29
  }
35
30
  /** Outcome of a sync attempt. Sync always converges — there is no "conflict"
36
31
  * arm: overlapping text edits land in the file inside standard git conflict
37
- * markers (`combinedFiles`), clashing binaries keep the newer side
38
- * (`imageClashes` for images), and the other version is always reachable in
32
+ * markers (`combinedFiles`), clashing binaries keep BOTH versions as two
33
+ * files (`keptBothFiles`), and every version is always reachable in
39
34
  * history. Owner ruling 2026-08-14. */
40
35
  export type SyncOutcome = {
41
36
  status: "synced";
@@ -48,13 +43,17 @@ export type SyncOutcome = {
48
43
  filesChanged?: boolean;
49
44
  /** Files whose text now holds BOTH versions inside git conflict markers. */
50
45
  combinedFiles?: string[];
51
- /** Clashing images (newer kept) for the host's non-blocking picker. */
52
- imageClashes?: ImageClash[];
46
+ /** Files kept as a pair (ours at `path`, theirs at `onlinePath`). */
47
+ keptBothFiles?: KeptBothFile[];
53
48
  } | {
54
49
  status: "up-to-date";
55
50
  message: string;
56
51
  snapshotId?: string;
57
52
  filesChanged?: boolean;
53
+ /** A pull-merge-only pass (`push: false`) can combine overlapping edits
54
+ * and then hold the push — the converge report still surfaces here. */
55
+ combinedFiles?: string[];
56
+ keptBothFiles?: KeptBothFile[];
58
57
  } | {
59
58
  status: "auth";
60
59
  message: string;
@@ -68,90 +67,9 @@ export type SyncOutcome = {
68
67
  } | {
69
68
  status: "error";
70
69
  message: string;
71
- /**
72
- * Stable machine-readable signal so a host UI can route without
73
- * string-matching `message` (which stays free to reword):
74
- * "needs-connection-setup" — the project isn't set up right (no
75
- * remote / SSH remote / no named branch); route to the connect/setup
76
- * surface.
77
- */
78
- code?: "needs-connection-setup";
79
70
  snapshotId?: string;
80
71
  filesChanged?: boolean;
81
72
  };
82
- /**
83
- * Outcome of a pull-only attempt ({@link pullChanges}): fetch + fast-forward/
84
- * converge-merge of the online changes, NEVER a push. The merge always lands
85
- * (see converge-merge.ts) — there is no "conflict" arm.
86
- */
87
- export type PullOutcome = {
88
- status: "pulled";
89
- message: string;
90
- /** Snapshot taken of unsaved work before pulling, if any. */
91
- snapshotId?: string;
92
- /**
93
- * True when the pull created a combine (merge) commit — local commits
94
- * existed alongside the online ones. False for a plain fast-forward.
95
- */
96
- merged: boolean;
97
- /**
98
- * True when the working tree CONTENT changed (the tip's tree differs
99
- * from before) — the host should refresh its preview.
100
- */
101
- filesChanged: boolean;
102
- /** Files whose text now holds BOTH versions inside git conflict markers. */
103
- combinedFiles?: string[];
104
- /** Clashing images (newer kept) for the host's non-blocking picker. */
105
- imageClashes?: ImageClash[];
106
- } | {
107
- status: "up-to-date";
108
- message: string;
109
- snapshotId?: string;
110
- } | {
111
- status: "auth";
112
- message: string;
113
- snapshotId?: string;
114
- } | {
115
- status: "offline";
116
- message: string;
117
- snapshotId?: string;
118
- } | {
119
- status: "error";
120
- message: string;
121
- snapshotId?: string;
122
- };
123
- /**
124
- * Outcome of a push-only attempt ({@link pushChanges}): snapshot-if-needed,
125
- * then push — NEVER a merge. When the online copy has commits this computer
126
- * doesn't have, the result is the typed `"pull-first"` status (the host shows
127
- * a plain-language "get the latest changes first" message) — pushChanges
128
- * never auto-merges.
129
- */
130
- export type PushOutcome = {
131
- status: "pushed";
132
- message: string;
133
- snapshotId?: string;
134
- } | {
135
- status: "up-to-date";
136
- message: string;
137
- snapshotId?: string;
138
- } | {
139
- status: "pull-first";
140
- message: string;
141
- snapshotId?: string;
142
- } | {
143
- status: "auth";
144
- message: string;
145
- snapshotId?: string;
146
- } | {
147
- status: "offline";
148
- message: string;
149
- snapshotId?: string;
150
- } | {
151
- status: "error";
152
- message: string;
153
- snapshotId?: string;
154
- };
155
73
  export interface SyncProjectOptions {
156
74
  projectDir: string;
157
75
  /** Explicit credential; wins over the token store. */
@@ -162,12 +80,23 @@ export interface SyncProjectOptions {
162
80
  message?: string;
163
81
  authorName?: string;
164
82
  authorEmail?: string;
83
+ /**
84
+ * When `false`, run a pull-merge-only pass: fetch + converge-merge exactly
85
+ * as always (remote work still arrives promptly), but hold the network push
86
+ * — and mint no snapshot unless the remote moved and the merge needs one
87
+ * (D5), so a quiet pass creates no commit at all. Local work stays safely
88
+ * committed/on disk for the next push-enabled pass to send. Default `true`:
89
+ * the full snapshot-first → fetch → merge → push pass. Owner decision
90
+ * 2026-08-23 (push cadence): the desktop's 2-minute tick pulls every time
91
+ * and enables the push only every ~15 minutes and on app exit.
92
+ */
93
+ push?: boolean;
165
94
  /** Injectable git HTTP transport for tests. */
166
95
  httpClient?: typeof httpNode;
167
96
  /**
168
- * Bounded retry policy for {@link syncProject}'s pull→push race loop. The
169
- * loop is ALWAYS bounded (never infinite) and the snapshot-first guarantee
170
- * holds on every path. Defaults to {@link DEFAULT_SYNC_RETRY}. `sleep` is
97
+ * Bounded retry policy for {@link syncProject}'s fetchmerge→push race
98
+ * loop. The loop is ALWAYS bounded (never infinite) and the snapshot-first
99
+ * guarantee holds on every path. Defaults to {@link DEFAULT_SYNC_RETRY}. `sleep` is
171
100
  * injectable so tests can drive backoff deterministically.
172
101
  */
173
102
  retry?: SyncRetryOptions;
@@ -1,30 +1,28 @@
1
1
  import { onAuthFor } from "./transport.ts";
2
- import type { PullOutcome, PushOutcome, SyncOutcome, SyncProjectOptions } from "./sync-types.ts";
2
+ import type { SyncOutcome, SyncProjectOptions } from "./sync-types.ts";
3
3
  export { onAuthFor };
4
4
  export { SYNC_SNAPSHOT_MESSAGE } from "./sync-messages.ts";
5
- export type { ImageClash, PullOutcome, PushOutcome, SyncOutcome, SyncProjectOptions, } from "./sync-types.ts";
5
+ export type { KeptBothFile, SyncOutcome, SyncProjectOptions, } from "./sync-types.ts";
6
6
  /**
7
- * Snapshot-first sync (ADR 0006 D5) the composition of {@link pullChanges}
8
- * then {@link pushChanges}. If someone pushes between our pull and our push,
9
- * the push reports pull-first and the pair re-runs (their commits converge in
10
- * on the next pass), with a short backoff between passes. The loop is ALWAYS
11
- * bounded by `retry.attempts`; a remote that races every pass surfaces a
12
- * friendly "try again in a moment" rather than looping forever. Never throws
13
- * for expected outcomes — everything is reported through {@link SyncOutcome}.
7
+ * A push the remote refused because our branch is behind — the pull-first
8
+ * situation the retry loop exists for.
14
9
  */
15
- export declare function syncProject(options: SyncProjectOptions): Promise<SyncOutcome>;
10
+ export declare function isPushRejected(e: unknown): boolean;
16
11
  /**
17
- * Pull-only operation (the History tab's "Pull"): snapshot-if-needed
18
- * fetch fast-forward or CONVERGE-merge of the online changes NEVER a
19
- * push, never a question. Serialized on the per-repo lock; one
20
- * function-scoped object cache, released on return.
12
+ * Unrelated histories the local project and the configured online project
13
+ * share no common starting point. Sync surfaces this as a plain setup error
14
+ * (a wrong online address must never be silently spliced into the book).
21
15
  */
22
- export declare function pullChanges(options: SyncProjectOptions): Promise<PullOutcome>;
16
+ export declare function isUnrelatedHistories(e: unknown): boolean;
23
17
  /**
24
- * Push-only operation (the History tab's "Push"): snapshot-if-needed push.
25
- * If the online copy has commits this computer doesn't have (non-fast-forward),
26
- * it does NOT auto-merge — the typed `"pull-first"` result tells the host to
27
- * show a plain-language "get the latest changes first" message. Serialized on
28
- * the per-repo lock; one function-scoped object cache, released on return.
18
+ * Snapshot-first sync (ADR 0006 D5). Serialized on the per-repo lock; one
19
+ * function-scoped object cache, released on return.
20
+ *
21
+ * If someone pushes between our fetch and our push, the push is rejected and
22
+ * the fetch→merge→push pass re-runs (their commits converge in on the next
23
+ * fetch), with a short backoff between passes. The loop is ALWAYS bounded by
24
+ * `retry.attempts`; a remote that races every pass surfaces a friendly "try
25
+ * again in a moment" rather than looping forever. Never throws for expected
26
+ * outcomes — everything is reported through {@link SyncOutcome}.
29
27
  */
30
- export declare function pushChanges(options: SyncProjectOptions): Promise<PushOutcome>;
28
+ export declare function syncProject(options: SyncProjectOptions): Promise<SyncOutcome>;
@@ -25,10 +25,6 @@ export interface TokenStore {
25
25
  /** All stored credentials (used by "connected accounts" UIs). */
26
26
  list(): Promise<HostCredential[]>;
27
27
  }
28
- /** A credential with the token value masked — safe for logs/diagnostics. */
29
- export declare function redactCredential(cred: HostCredential): Omit<HostCredential, "token"> & {
30
- token: string;
31
- };
32
28
  /**
33
29
  * THE canonical credential-store key for a remote host — the ONE derivation
34
30
  * every writer (GitHub device flow, generic connect, embedded-URL migration)
@@ -87,9 +83,3 @@ export interface UrlCredentialExtraction {
87
83
  * display. Non-HTTP(S) or unparseable URLs pass through unchanged.
88
84
  */
89
85
  export declare function extractUrlCredential(url: string): UrlCredentialExtraction;
90
- /**
91
- * Migrate any credential embedded in `url` into `store` (only when the store
92
- * has no existing credential for that host — a stored credential is fresher
93
- * than one fossilized in a clone URL) and return the sanitized URL.
94
- */
95
- export declare function migrateUrlCredential(url: string, store: TokenStore): Promise<string>;
@@ -1,6 +1,5 @@
1
1
  import httpNode from "isomorphic-git/http/node";
2
2
  import { type HostCredential, type TokenStore } from "./token-store.ts";
3
- import type { OperationLogger } from "./operation-log.ts";
4
3
  import type { GitCache, RemoteTransport } from "./sync-types.ts";
5
4
  /**
6
5
  * The git repo directory for a project dir. A project IS its git repo, so this
@@ -17,6 +16,19 @@ export declare function repoDirFor(projectDir: string): Promise<string>;
17
16
  * http carries no network exposure, so it stays allowed.
18
17
  */
19
18
  export declare function isCredentialTransmissionSafe(url: string): boolean;
19
+ /**
20
+ * Thrown by {@link onAuthFor} when a stored credential EXISTS but the remote
21
+ * URL fails {@link isCredentialTransmissionSafe} (non-loopback http). Loud and
22
+ * typed on purpose: the old behavior (silently withholding the credential)
23
+ * surfaced as a 401 → "auth" → "reconnect" loop. The `code` string is the
24
+ * STABLE contract (matchable across dynamic-import boundaries).
25
+ */
26
+ export declare class InsecureTransportError extends Error {
27
+ readonly code = "InsecureTransport";
28
+ constructor();
29
+ }
30
+ /** Type guard for {@link InsecureTransportError} (matches on the stable code). */
31
+ export declare function isInsecureTransportError(e: unknown): e is InsecureTransportError;
20
32
  export declare function onAuthFor(credential: HostCredential | undefined): {
21
33
  onAuth?: undefined;
22
34
  } | {
@@ -35,13 +47,19 @@ export declare function resolveTransport(dir: string, options: {
35
47
  tokenStore?: TokenStore;
36
48
  }): Promise<RemoteTransport>;
37
49
  /**
38
- * The failure arms shared verbatim by {@link SyncOutcome}, {@link PullOutcome}
39
- * and {@link PushOutcome} so one classifier serves all three operations.
40
- * Decoding delegates to the shared recovery classifier
41
- * (classifyTransportFailure): auth_required → "auth", network_unavailable →
42
- * "offline", insecure_transport → "error" with its dedicated message (NEVER
43
- * "auth" reconnecting can't fix an http:// address, and the auth recovery
44
- * path deletes the stored credential), anything else → the generic "error" arm.
50
+ * Decode a thrown transport error into the outcome it maps to, or null when it
51
+ * is not a transport failure at all. The single source of truth for transport
52
+ * error decoding sync.ts and clone.ts consume it rather than keeping
53
+ * parallel copies.
54
+ */
55
+ export declare function classifyTransportFailure(e: unknown): "auth_required" | "network_unavailable" | "insecure_transport" | null;
56
+ /**
57
+ * The failure arms of {@link SyncOutcome}. Decoding delegates to
58
+ * {@link classifyTransportFailure}: auth_required → "auth",
59
+ * network_unavailable → "offline", insecure_transport → "error" with its
60
+ * dedicated message (NEVER "auth" — reconnecting can't fix an http:// address,
61
+ * and the auth recovery path deletes the stored credential), anything else →
62
+ * the generic "error" arm.
45
63
  */
46
64
  export declare function failureOutcome(e: unknown, snapshotId?: string): {
47
65
  status: "auth" | "offline" | "error";
@@ -51,7 +69,7 @@ export declare function failureOutcome(e: unknown, snapshotId?: string): {
51
69
  /** Friendly setup-problem message for the expected gate errors, else null. */
52
70
  export declare function setupErrorMessage(e: unknown): string | null;
53
71
  /**
54
- * Snapshot-first step shared by syncProject / pullChanges / pushChanges
72
+ * Snapshot-first step used by syncProject
55
73
  * (ADR 0006 D5): commit any unsaved work in the WHOLE repo BEFORE any network
56
74
  * or merge step, so a forced post-merge checkout can never discard it. The
57
75
  * working-tree check runs lazily at action time on the caller's function-scoped
@@ -65,44 +83,28 @@ export declare function snapshotBeforeAction(args: {
65
83
  authorEmail?: string;
66
84
  cache: GitCache;
67
85
  }): Promise<string | undefined>;
68
- /**
69
- * Structural preflight — never touch the tree of a damaged repo. An interrupted
70
- * merge/rebase/cherry-pick, detached HEAD, stale lock, or missing `.git` must be
71
- * REPAIRED before any sync work: snapshot-first would otherwise commit whatever
72
- * is on disk (e.g. the literal conflict markers a half-done native-git merge
73
- * leaves in tracked files) and push it to every collaborator. Throwing the
74
- * typed error routes the caller through the recover() path.
75
- * `checkLocalChanges: false` — only the structural flags matter here.
76
- *
77
- * Runs INSIDE the caller's repo lock. Shared by pullChanges and pushChanges.
78
- */
79
- export declare function assertNoStructuralDamage(projectDir: string, logger: OperationLogger): Promise<void>;
80
86
  export declare function currentBranchOrThrow(dir: string): Promise<string>;
81
87
  /**
82
- * Run `fn` (a fetch that moves remote-tracking refs) with a rollback guard
83
- * (deep-analysis R15): isomorphic-git updates refs/remotes/<remote>/* from
84
- * the ref advertisement BEFORE collecting the packfile, so an abort
85
- * mid-transfer (e.g. the defaultGitHttp idle timeout) leaves refs pointing
86
- * at oids with no local object. Such a dangling ref poisons the next fetch —
88
+ * Run `fn` (a fetch that moves ONE remote-tracking ref) with a rollback guard
89
+ * (deep-analysis R15): isomorphic-git updates `refs/remotes/<remote>/<branch>`
90
+ * from the ref advertisement BEFORE collecting the packfile, so an abort
91
+ * mid-transfer (e.g. the defaultGitHttp idle timeout) leaves the ref pointing
92
+ * at an oid with no local object. Such a dangling ref poisons the next fetch —
87
93
  * zero `have`s → the server streams the ENTIRE repository (the OOM
88
- * fetchRemoteTip's `ref` choice exists to prevent) — and resolving it reports
94
+ * `fetchRemoteTip`'s `ref` choice exists to prevent) — and resolving it reports
89
95
  * missing-object "corruption" on a never-corrupt repo.
90
96
  *
91
- * `listRefs` names the refs at risk; it runs again after a throw so refs
92
- * CREATED by `fn` are covered too. If `fn` throws, every ref that moved to an
93
- * oid whose object is MISSING locally is restored to its previous oid (or
94
- * deleted if it didn't exist); refs whose objects DID land are kept — the
95
- * pack made it. On success no ref is touched. Restoration is best-effort,
96
- * per ref, and never masks `fn`'s error.
97
+ * If `fn` throws and the ref moved to an oid whose object is MISSING locally,
98
+ * it is restored to its previous oid (or DELETED if it did not exist before).
99
+ * A ref whose object DID land is kept the pack made it. On success no ref is
100
+ * touched.
101
+ *
102
+ * Every read here is best-effort and never masks `fn`'s error: a damaged ref
103
+ * store must not block the guarded fetch, because the recovery handlers run on
104
+ * exactly such repos and skipping `fn` would skip the repair itself. An
105
+ * unreadable pre-scan simply degrades to the delete-if-dangling arm.
97
106
  */
98
- export declare function guardRefs<T>(dir: string, listRefs: () => Promise<string[]>, cache: GitCache, fn: () => Promise<T>): Promise<T>;
99
- /** Single-ref form of {@link guardRefs} — guards one remote-tracking ref. */
100
107
  export declare function guardTrackingRef<T>(dir: string, ref: string, cache: GitCache, fn: () => Promise<T>): Promise<T>;
101
- /**
102
- * Remote-wide form of {@link guardRefs} for a fetch that may move or create
103
- * ANY refs/remotes/<remote>/* ref (e.g. `singleBranch: false`).
104
- */
105
- export declare function guardRemoteRefs<T>(dir: string, remote: string, cache: GitCache, fn: () => Promise<T>): Promise<T>;
106
108
  /**
107
109
  * Fetch the tracked branch's online tip. Returns `null` when the online
108
110
  * repository has no such branch yet (a freshly created empty repo).
@@ -38,6 +38,18 @@ export interface SnapshotOptions {
38
38
  repoRoot?: string;
39
39
  /** Optional log file for debugging snapshot operations. */
40
40
  logFile?: string;
41
+ /**
42
+ * The CALLER's object cache, when the snapshot is one step of a longer
43
+ * locked operation (sync). Committing rewrites `.git/index`, and
44
+ * isomorphic-git's index cache invalidates on a stat comparison that can
45
+ * miss a same-second rewrite of the same size — so a caller that keeps
46
+ * using its own cache afterwards would keep reading the PRE-snapshot index.
47
+ * That is not cosmetic: `git.checkout` derives STAGE from it, and a stale
48
+ * STAGE makes an unmodified file look locally modified. Pass the cache and
49
+ * the snapshot's own `git.add`/`git.commit` refresh it in place. Omit it
50
+ * for standalone snapshots — they get a private cache, released on return.
51
+ */
52
+ cache?: GitCache;
41
53
  }
42
54
  /**
43
55
  * The git repo directory a project's operations run against. A project IS its
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  MANIFEST_FILENAMES,
3
3
  runLint
4
- } from "./cli-eq5naw4m.js";
4
+ } from "./cli-7n848af2.js";
5
5
  import {
6
6
  log
7
- } from "./cli-cqtggsng.js";
7
+ } from "./cli-0z6zf98d.js";
8
8
  import {
9
9
  UsageError,
10
10
  rejectExtraPositionals,
11
11
  rejectUnknownFlags
12
- } from "./cli-149edp6b.js";
13
- import"./cli-zfcryxg8.js";
12
+ } from "./cli-ydpf09d9.js";
13
+ import"./cli-eewynen9.js";
14
14
  import"./cli-c41yr7he.js";
15
15
  import {
16
16
  EXIT_CODES
17
17
  } from "./cli-46ycxe6r.js";
18
- import"./cli-k1065rkg.js";
18
+ import"./cli-w692g6dx.js";
19
19
  import"./cli-v5mp7a6q.js";
20
20
  import"./cli-37x76zdn.js";
21
21
 
@@ -5,23 +5,23 @@ import {
5
5
  TARGETS,
6
6
  TARGET_IDS,
7
7
  scaffoldProject
8
- } from "./cli-eq5naw4m.js";
8
+ } from "./cli-7n848af2.js";
9
9
  import {
10
10
  resolveGhostscript
11
- } from "./cli-cqtggsng.js";
11
+ } from "./cli-0z6zf98d.js";
12
12
  import {
13
13
  UsageError,
14
14
  rejectExtraPositionals,
15
15
  rejectUnknownFlags
16
- } from "./cli-149edp6b.js";
17
- import"./cli-zfcryxg8.js";
16
+ } from "./cli-ydpf09d9.js";
17
+ import"./cli-eewynen9.js";
18
18
  import {
19
19
  isToolAvailable
20
20
  } from "./cli-c41yr7he.js";
21
21
  import {
22
22
  EXIT_CODES
23
23
  } from "./cli-46ycxe6r.js";
24
- import"./cli-k1065rkg.js";
24
+ import"./cli-w692g6dx.js";
25
25
  import"./cli-v5mp7a6q.js";
26
26
  import"./cli-37x76zdn.js";
27
27
 
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  addNpmPlugin,
3
3
  parseNpmPluginSpec
4
- } from "./cli-eq5naw4m.js";
5
- import"./cli-cqtggsng.js";
4
+ } from "./cli-7n848af2.js";
5
+ import"./cli-0z6zf98d.js";
6
6
  import {
7
7
  UsageError,
8
8
  rejectExtraPositionals,
9
9
  rejectUnknownFlags
10
- } from "./cli-149edp6b.js";
11
- import"./cli-zfcryxg8.js";
10
+ } from "./cli-ydpf09d9.js";
11
+ import"./cli-eewynen9.js";
12
12
  import"./cli-c41yr7he.js";
13
13
  import {
14
14
  EXIT_CODES
15
15
  } from "./cli-46ycxe6r.js";
16
- import"./cli-k1065rkg.js";
16
+ import"./cli-w692g6dx.js";
17
17
  import"./cli-v5mp7a6q.js";
18
18
  import"./cli-37x76zdn.js";
19
19
 
@@ -2,21 +2,21 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-eq5naw4m.js";
5
+ } from "./cli-7n848af2.js";
6
6
  import {
7
7
  log
8
- } from "./cli-cqtggsng.js";
8
+ } from "./cli-0z6zf98d.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-149edp6b.js";
14
- import"./cli-zfcryxg8.js";
13
+ } from "./cli-ydpf09d9.js";
14
+ import"./cli-eewynen9.js";
15
15
  import"./cli-c41yr7he.js";
16
16
  import {
17
17
  EXIT_CODES
18
18
  } from "./cli-46ycxe6r.js";
19
- import"./cli-k1065rkg.js";
19
+ import"./cli-w692g6dx.js";
20
20
  import"./cli-v5mp7a6q.js";
21
21
  import"./cli-37x76zdn.js";
22
22