gutterpress 0.0.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 (230) hide show
  1. package/README.md +422 -0
  2. package/dist/CGATS21_CRPC1-g0e3k7kr.icc +0 -0
  3. package/dist/api/index.d.ts +76 -0
  4. package/dist/api/index.js +272 -0
  5. package/dist/assets/preview/scripts/pagedjs-bridge.d.ts +1 -0
  6. package/dist/assets/preview/scripts/pagedjs-interface.d.ts +1 -0
  7. package/dist/assets/preview/scripts/preview-shell.d.ts +1 -0
  8. package/dist/audit-1jrwwacz.js +66 -0
  9. package/dist/build-5s92kgpc.js +77 -0
  10. package/dist/chapter-01-6fn9pvd0.md +31 -0
  11. package/dist/chapter-01-7jwqg1ct.md +20 -0
  12. package/dist/chapter-01-jj8ftn4c.md +29 -0
  13. package/dist/chapter-01-rkpyvjna.md +26 -0
  14. package/dist/checks/asset/approved-fonts.d.ts +3 -0
  15. package/dist/checks/asset/extensions.d.ts +41 -0
  16. package/dist/checks/asset/font-license.d.ts +3 -0
  17. package/dist/checks/asset/image-alpha.d.ts +3 -0
  18. package/dist/checks/asset/image-color-space.d.ts +3 -0
  19. package/dist/checks/asset/image-file-size.d.ts +3 -0
  20. package/dist/checks/asset/image-resolution.d.ts +3 -0
  21. package/dist/checks/asset/image-tac.d.ts +3 -0
  22. package/dist/checks/asset/index.d.ts +7 -0
  23. package/dist/checks/formatter.d.ts +3 -0
  24. package/dist/checks/heuristic/index.d.ts +4 -0
  25. package/dist/checks/heuristic/layer-count.d.ts +3 -0
  26. package/dist/checks/heuristic/placement-variance.d.ts +3 -0
  27. package/dist/checks/heuristic/section-density.d.ts +3 -0
  28. package/dist/checks/heuristic/text-density.d.ts +3 -0
  29. package/dist/checks/pdf/bleed.d.ts +3 -0
  30. package/dist/checks/pdf/bookmarks.d.ts +3 -0
  31. package/dist/checks/pdf/color-spaces.d.ts +3 -0
  32. package/dist/checks/pdf/cross-refs.d.ts +3 -0
  33. package/dist/checks/pdf/embedded-fonts.d.ts +3 -0
  34. package/dist/checks/pdf/image-resolution.d.ts +3 -0
  35. package/dist/checks/pdf/index.d.ts +15 -0
  36. package/dist/checks/pdf/ink-coverage.d.ts +3 -0
  37. package/dist/checks/pdf/page-labels.d.ts +3 -0
  38. package/dist/checks/pdf/page-size.d.ts +3 -0
  39. package/dist/checks/pdf/pdfx-markers.d.ts +3 -0
  40. package/dist/checks/pdf/pdfx-metadata.d.ts +3 -0
  41. package/dist/checks/pdf/pdfx-structure.d.ts +6 -0
  42. package/dist/checks/pdf/qpdf-structure.d.ts +3 -0
  43. package/dist/checks/pdf/rasterized-pages.d.ts +3 -0
  44. package/dist/checks/pdf/toc-links.d.ts +3 -0
  45. package/dist/checks/pdf/transparency.d.ts +3 -0
  46. package/dist/checks/policy.d.ts +61 -0
  47. package/dist/checks/register-builtins.d.ts +17 -0
  48. package/dist/checks/registry.d.ts +40 -0
  49. package/dist/checks/runner.d.ts +25 -0
  50. package/dist/checks/source/accessibility-alt-text.d.ts +3 -0
  51. package/dist/checks/source/accessibility-heading-order.d.ts +3 -0
  52. package/dist/checks/source/config-file.d.ts +8 -0
  53. package/dist/checks/source/htmlhint.d.ts +3 -0
  54. package/dist/checks/source/index.d.ts +6 -0
  55. package/dist/checks/source/local-refs.d.ts +3 -0
  56. package/dist/checks/source/markdownlint.d.ts +3 -0
  57. package/dist/checks/source/stylelint.d.ts +3 -0
  58. package/dist/checks/tool-check.d.ts +23 -0
  59. package/dist/checks/types.d.ts +52 -0
  60. package/dist/cli-1tyj4bk3.js +124 -0
  61. package/dist/cli-37x76zdn.js +4 -0
  62. package/dist/cli-506tg37g.js +26 -0
  63. package/dist/cli-5bxx0c4y.js +464 -0
  64. package/dist/cli-7ykx7pp0.js +12378 -0
  65. package/dist/cli-h7phg3ka.js +746 -0
  66. package/dist/cli-hvqjs342.js +284 -0
  67. package/dist/cli-yja077f6.js +92 -0
  68. package/dist/cli.js +157 -0
  69. package/dist/constants.d.ts +17 -0
  70. package/dist/doctor-25zjt8dz.js +57 -0
  71. package/dist/favicon-wkbm9cjn.ico +0 -0
  72. package/dist/git-http-yrb4ag6z.js +17 -0
  73. package/dist/index-1tyj4bk3.js +124 -0
  74. package/dist/index-37x76zdn.js +4 -0
  75. package/dist/index-3h3fef7r.js +15218 -0
  76. package/dist/index-5bxx0c4y.js +464 -0
  77. package/dist/index-yja077f6.js +92 -0
  78. package/dist/index.d.ts +32 -0
  79. package/dist/index.js +310 -0
  80. package/dist/lib/app-heartbeat.d.ts +102 -0
  81. package/dist/lib/asset-inline.d.ts +73 -0
  82. package/dist/lib/browser-pool.d.ts +17 -0
  83. package/dist/lib/build-error.d.ts +52 -0
  84. package/dist/lib/build-fingerprint.d.ts +15 -0
  85. package/dist/lib/build-preflight.d.ts +43 -0
  86. package/dist/lib/build-runner.d.ts +161 -0
  87. package/dist/lib/build-staging.d.ts +64 -0
  88. package/dist/lib/chromium.d.ts +20 -0
  89. package/dist/lib/cli-args.d.ts +45 -0
  90. package/dist/lib/desktop.d.ts +14 -0
  91. package/dist/lib/diagnostics.d.ts +64 -0
  92. package/dist/lib/embedded-assets.d.ts +29 -0
  93. package/dist/lib/exec.d.ts +84 -0
  94. package/dist/lib/fetch-timeout.d.ts +41 -0
  95. package/dist/lib/ghostscript.d.ts +48 -0
  96. package/dist/lib/host-policy.d.ts +75 -0
  97. package/dist/lib/image-inspect.d.ts +32 -0
  98. package/dist/lib/install-hints.d.ts +21 -0
  99. package/dist/lib/lint-runner.d.ts +10 -0
  100. package/dist/lib/manifest-config.d.ts +31 -0
  101. package/dist/lib/manifest-doc.d.ts +25 -0
  102. package/dist/lib/manifest.d.ts +41 -0
  103. package/dist/lib/markdown/assemble.d.ts +78 -0
  104. package/dist/lib/markdown/chapter-id.d.ts +24 -0
  105. package/dist/lib/markdown/images.d.ts +34 -0
  106. package/dist/lib/markdown/index.d.ts +78 -0
  107. package/dist/lib/markdown/markdown-it-paged.d.ts +30 -0
  108. package/dist/lib/markdown/plugins.d.ts +69 -0
  109. package/dist/lib/markdown/renderer.d.ts +111 -0
  110. package/dist/lib/npm-plugin-installer.d.ts +33 -0
  111. package/dist/lib/open-path.d.ts +5 -0
  112. package/dist/lib/output-paths.d.ts +23 -0
  113. package/dist/lib/pagedjs-marker.d.ts +42 -0
  114. package/dist/lib/pagedjs.d.ts +26 -0
  115. package/dist/lib/pagination.d.ts +169 -0
  116. package/dist/lib/pdf-inspect.d.ts +123 -0
  117. package/dist/lib/pdf-parse.d.ts +57 -0
  118. package/dist/lib/plugin-manager.d.ts +108 -0
  119. package/dist/lib/plugin-vendor.d.ts +107 -0
  120. package/dist/lib/presets.d.ts +45 -0
  121. package/dist/lib/printsafe.d.ts +17 -0
  122. package/dist/lib/project-scaffold.d.ts +116 -0
  123. package/dist/lib/project-source.d.ts +140 -0
  124. package/dist/lib/project-templates.d.ts +49 -0
  125. package/dist/lib/publish/butler.d.ts +10 -0
  126. package/dist/lib/publish/command-runner.d.ts +18 -0
  127. package/dist/lib/publish/connect.d.ts +23 -0
  128. package/dist/lib/publish/manifest-publish.d.ts +9 -0
  129. package/dist/lib/publish/providers/azure-swa.d.ts +3 -0
  130. package/dist/lib/publish/providers/drivethrurpg.d.ts +10 -0
  131. package/dist/lib/publish/providers/guided.d.ts +3 -0
  132. package/dist/lib/publish/providers/itch.d.ts +5 -0
  133. package/dist/lib/publish/providers/kdp.d.ts +9 -0
  134. package/dist/lib/publish/providers/shopify.d.ts +5 -0
  135. package/dist/lib/publish/registry.d.ts +5 -0
  136. package/dist/lib/publish/run-publish.d.ts +30 -0
  137. package/dist/lib/publish/selections.d.ts +25 -0
  138. package/dist/lib/publish/types.d.ts +258 -0
  139. package/dist/lib/remote-auth/clone.d.ts +87 -0
  140. package/dist/lib/remote-auth/conflict-resolution.d.ts +29 -0
  141. package/dist/lib/remote-auth/diagnose.d.ts +77 -0
  142. package/dist/lib/remote-auth/generic-auth.d.ts +97 -0
  143. package/dist/lib/remote-auth/git-http.d.ts +85 -0
  144. package/dist/lib/remote-auth/github-auth.d.ts +69 -0
  145. package/dist/lib/remote-auth/github-repos.d.ts +64 -0
  146. package/dist/lib/remote-auth/operation-log.d.ts +66 -0
  147. package/dist/lib/remote-auth/recovery/abort-interrupted-operation.d.ts +103 -0
  148. package/dist/lib/remote-auth/recovery/backup.d.ts +113 -0
  149. package/dist/lib/remote-auth/recovery/classify.d.ts +109 -0
  150. package/dist/lib/remote-auth/recovery/context.d.ts +47 -0
  151. package/dist/lib/remote-auth/recovery/dispatch.d.ts +33 -0
  152. package/dist/lib/remote-auth/recovery/failsafe.d.ts +33 -0
  153. package/dist/lib/remote-auth/recovery/inspect.d.ts +74 -0
  154. package/dist/lib/remote-auth/recovery/manual-guidance.d.ts +28 -0
  155. package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +58 -0
  156. package/dist/lib/remote-auth/recovery/policy.d.ts +47 -0
  157. package/dist/lib/remote-auth/recovery/recover-auth.d.ts +42 -0
  158. package/dist/lib/remote-auth/recovery/recover-binary-conflict.d.ts +37 -0
  159. package/dist/lib/remote-auth/recovery/recover-corrupt-index.d.ts +40 -0
  160. package/dist/lib/remote-auth/recovery/recover-detached-head.d.ts +70 -0
  161. package/dist/lib/remote-auth/recovery/recover-interrupted-cherry-pick.d.ts +23 -0
  162. package/dist/lib/remote-auth/recovery/recover-interrupted-merge.d.ts +28 -0
  163. package/dist/lib/remote-auth/recovery/recover-interrupted-rebase.d.ts +39 -0
  164. package/dist/lib/remote-auth/recovery/recover-merge-conflict.d.ts +34 -0
  165. package/dist/lib/remote-auth/recovery/recover-missing-git-dir.d.ts +37 -0
  166. package/dist/lib/remote-auth/recovery/recover-missing-objects.d.ts +56 -0
  167. package/dist/lib/remote-auth/recovery/recover-network.d.ts +34 -0
  168. package/dist/lib/remote-auth/recovery/recover-non-fast-forward.d.ts +27 -0
  169. package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +76 -0
  170. package/dist/lib/remote-auth/recovery/recover-unrelated-histories.d.ts +44 -0
  171. package/dist/lib/remote-auth/recovery/recover-wrong-remote.d.ts +35 -0
  172. package/dist/lib/remote-auth/recovery/types.d.ts +235 -0
  173. package/dist/lib/remote-auth/resolution-plan.d.ts +64 -0
  174. package/dist/lib/remote-auth/sync-messages.d.ts +26 -0
  175. package/dist/lib/remote-auth/sync-types.d.ts +225 -0
  176. package/dist/lib/remote-auth/sync.d.ts +45 -0
  177. package/dist/lib/remote-auth/test-access.d.ts +47 -0
  178. package/dist/lib/remote-auth/test-support/git-http-server.d.ts +71 -0
  179. package/dist/lib/remote-auth/token-store.d.ts +95 -0
  180. package/dist/lib/remote-auth/transport.d.ts +105 -0
  181. package/dist/lib/slug.d.ts +28 -0
  182. package/dist/lib/snippets.d.ts +39 -0
  183. package/dist/lib/source-provider.d.ts +244 -0
  184. package/dist/lib/static-serve.d.ts +54 -0
  185. package/dist/lib/style-resolver.d.ts +30 -0
  186. package/dist/lib/theme-import.d.ts +70 -0
  187. package/dist/lib/theme-manager.d.ts +128 -0
  188. package/dist/lib/tool-probe.d.ts +37 -0
  189. package/dist/lib/validation-exec.d.ts +47 -0
  190. package/dist/lib/validation-profile.d.ts +18 -0
  191. package/dist/lib/version.d.ts +9 -0
  192. package/dist/lint-f63nxjfj.js +65 -0
  193. package/dist/manifest-n1gh3qw5.yaml +9 -0
  194. package/dist/manifest-ryyg71nf.yaml +9 -0
  195. package/dist/manifest.schema-rn1dpdpn.json +564 -0
  196. package/dist/new-7b08a42h.js +105 -0
  197. package/dist/paged.polyfill-n95pbxfn.js +33288 -0
  198. package/dist/pagedjs-bridge-vn4hk9fx.js +111 -0
  199. package/dist/pagedjs-interface-80g3xa8p.js +498 -0
  200. package/dist/platform.d.ts +100 -0
  201. package/dist/plugin-wf2cbmwc.js +129 -0
  202. package/dist/preflight-tg4mpz1h.js +222 -0
  203. package/dist/preview/file-watcher.d.ts +174 -0
  204. package/dist/preview/http-server.d.ts +45 -0
  205. package/dist/preview/lifecycle.d.ts +67 -0
  206. package/dist/preview/server-context.d.ts +36 -0
  207. package/dist/preview-h2bwbb2x.js +102 -0
  208. package/dist/preview-shell-rghtfmc8.js +155 -0
  209. package/dist/project-source-0tg0r6vm.js +17 -0
  210. package/dist/publish-ztaet53r.js +233 -0
  211. package/dist/render.d.ts +20 -0
  212. package/dist/render.js +682 -0
  213. package/dist/repair-jkp4n3j8.js +171 -0
  214. package/dist/schema/manifest.types.d.ts +218 -0
  215. package/dist/server.d.ts +34 -0
  216. package/dist/source-provider-hp96b5jf.js +46 -0
  217. package/dist/test-helpers/testkit.d.ts +91 -0
  218. package/dist/theme-0yzvthe8.css +65 -0
  219. package/dist/theme-570zmh2t.css +58 -0
  220. package/dist/theme-6a2fngc8.json +6 -0
  221. package/dist/theme-cd6efzjw.json +6 -0
  222. package/dist/theme-nya4nqh6.css +70 -0
  223. package/dist/theme-tqfypc1w.json +6 -0
  224. package/dist/theme-xt0hjb1b.json +6 -0
  225. package/dist/theme-zz2ktzqs.css +60 -0
  226. package/dist/types.d.ts +31 -0
  227. package/dist/utils/file-utils.d.ts +53 -0
  228. package/dist/utils/logger.d.ts +59 -0
  229. package/dist/validate-k2drs19j.js +99 -0
  230. package/package.json +102 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Type/interface declarations for snapshot-first sync + conflict resolution
3
+ * (#15, ADR 0006 D5). Extracted from sync.ts so the orchestrator, transport and
4
+ * conflict-resolution modules share ONE definition of each result/option shape.
5
+ * Pure type surface — no runtime code.
6
+ */
7
+ import type httpNode from "isomorphic-git/http/node";
8
+ import type { HostCredential, TokenStore } from "./token-store.ts";
9
+ /**
10
+ * isomorphic-git object cache, scoped to ONE operation (one function call)
11
+ * and released with it. NEVER share these across operations or hold them in
12
+ * module state: reading any object from a packfile makes isomorphic-git load
13
+ * the ENTIRE pack into the cache (measured ~1.3–3.8 GB RSS on a 2 GB repo),
14
+ * so a long-lived shared cache pins that memory for the life of the process —
15
+ * the root cause of the 0.5.0 "sync uses 2 GB" report.
16
+ */
17
+ export type GitCache = Record<string, unknown>;
18
+ /** How one conflicted file differs between the two copies. */
19
+ export type ConflictKind =
20
+ /** Edited in both copies. */
21
+ "both-edited"
22
+ /** The author deleted it; the online copy edited it. */
23
+ | "you-deleted"
24
+ /** The online copy deleted it; the author edited it. */
25
+ | "online-deleted";
26
+ /** One file that changed in both the local and the online copy. */
27
+ export interface ConflictFile {
28
+ path: string;
29
+ kind: ConflictKind;
30
+ }
31
+ /** Author's per-file decision for a conflicted file (ADR 0006 D5). */
32
+ export interface ConflictResolution {
33
+ path: string;
34
+ choice: "mine" | "theirs" | "both";
35
+ }
36
+ /** Outcome of a sync (or conflict-resolution) attempt. */
37
+ export type SyncOutcome = {
38
+ status: "synced";
39
+ message: string;
40
+ /** Snapshot taken of unsaved work before syncing, if any. */
41
+ snapshotId?: string;
42
+ /** True when online changes were merged into the local copy. */
43
+ mergedRemoteChanges: boolean;
44
+ /** True when pulling online changes changed the local working tree. */
45
+ filesChanged?: boolean;
46
+ } | {
47
+ status: "up-to-date";
48
+ message: string;
49
+ snapshotId?: string;
50
+ filesChanged?: boolean;
51
+ } | {
52
+ status: "conflict";
53
+ message: string;
54
+ files: ConflictFile[];
55
+ /** Local branch tip the conflict was computed against. */
56
+ localId: string;
57
+ /** Online tip the conflict was computed against. */
58
+ remoteId: string;
59
+ snapshotId?: string;
60
+ } | {
61
+ status: "auth";
62
+ message: string;
63
+ snapshotId?: string;
64
+ filesChanged?: boolean;
65
+ } | {
66
+ status: "offline";
67
+ message: string;
68
+ snapshotId?: string;
69
+ filesChanged?: boolean;
70
+ } | {
71
+ status: "error";
72
+ message: string;
73
+ /**
74
+ * Stable machine-readable signal for the small set of known "the project
75
+ * isn't set up right" failures (no remote / SSH remote / no named
76
+ * branch) — set by conflict-resolution.ts's setupErrorMessage() check.
77
+ * Lets a host UI route to its connect/setup surface without
78
+ * string-matching `message` (which stays free to reword).
79
+ */
80
+ code?: "needs-connection-setup";
81
+ snapshotId?: string;
82
+ filesChanged?: boolean;
83
+ };
84
+ /**
85
+ * Outcome of a pull-only attempt ({@link pullChanges}): fetch + fast-forward/
86
+ * merge of the online changes, NEVER a push. Conflict semantics are identical
87
+ * to {@link syncProject} (abortOnConflict — the working tree is never left
88
+ * with conflict markers).
89
+ */
90
+ export type PullOutcome = {
91
+ status: "pulled";
92
+ message: string;
93
+ /** Snapshot taken of unsaved work before pulling, if any. */
94
+ snapshotId?: string;
95
+ /**
96
+ * True when the pull created a combine (merge) commit — local commits
97
+ * existed alongside the online ones. False for a plain fast-forward.
98
+ */
99
+ merged: boolean;
100
+ /**
101
+ * True when the working tree CONTENT changed (the tip's tree differs
102
+ * from before) — the host should refresh its preview.
103
+ */
104
+ filesChanged: boolean;
105
+ } | {
106
+ status: "up-to-date";
107
+ message: string;
108
+ snapshotId?: string;
109
+ } | {
110
+ status: "conflict";
111
+ message: string;
112
+ files: ConflictFile[];
113
+ localId: string;
114
+ remoteId: string;
115
+ snapshotId?: string;
116
+ } | {
117
+ status: "auth";
118
+ message: string;
119
+ snapshotId?: string;
120
+ } | {
121
+ status: "offline";
122
+ message: string;
123
+ snapshotId?: string;
124
+ } | {
125
+ status: "error";
126
+ message: string;
127
+ snapshotId?: string;
128
+ };
129
+ /**
130
+ * Outcome of a push-only attempt ({@link pushChanges}): snapshot-if-needed,
131
+ * then push — NEVER a merge. When the online copy has commits this computer
132
+ * doesn't have, the result is the typed `"pull-first"` status (the host shows
133
+ * a plain-language "get the latest changes first" message) — pushChanges
134
+ * never auto-merges.
135
+ */
136
+ export type PushOutcome = {
137
+ status: "pushed";
138
+ message: string;
139
+ snapshotId?: string;
140
+ } | {
141
+ status: "up-to-date";
142
+ message: string;
143
+ snapshotId?: string;
144
+ } | {
145
+ status: "pull-first";
146
+ message: string;
147
+ snapshotId?: string;
148
+ } | {
149
+ status: "auth";
150
+ message: string;
151
+ snapshotId?: string;
152
+ } | {
153
+ status: "offline";
154
+ message: string;
155
+ snapshotId?: string;
156
+ } | {
157
+ status: "error";
158
+ message: string;
159
+ snapshotId?: string;
160
+ };
161
+ export interface SyncProjectOptions {
162
+ projectDir: string;
163
+ /** Explicit credential; wins over the token store. */
164
+ credential?: HostCredential;
165
+ /** Host-keyed store used to resolve the credential for the remote's host. */
166
+ tokenStore?: TokenStore;
167
+ /** Snapshot message for unsaved work (defaults to a friendly one). */
168
+ message?: string;
169
+ authorName?: string;
170
+ authorEmail?: string;
171
+ /** Injectable git HTTP transport for tests. */
172
+ httpClient?: typeof httpNode;
173
+ /**
174
+ * Bounded retry policy for {@link syncProject}'s pull→push race loop. The
175
+ * loop is ALWAYS bounded (never infinite) and the snapshot-first guarantee
176
+ * holds on every path. Defaults to {@link DEFAULT_SYNC_RETRY}. `sleep` is
177
+ * injectable so tests can drive backoff deterministically.
178
+ */
179
+ retry?: SyncRetryOptions;
180
+ /**
181
+ * Optional path to a log file for debugging sync/recovery operations.
182
+ * When set, each step (snapshot, fetch, merge, push, conflict) is appended
183
+ * as a timestamped line. Never logs secrets.
184
+ */
185
+ logFile?: string;
186
+ }
187
+ /** Bounded retry policy for the sync race loop (BUG 6). */
188
+ export interface SyncRetryOptions {
189
+ /** Max pull→push passes before giving up. Clamped to ≥ 1. Default 3. */
190
+ attempts?: number;
191
+ /** Delay between passes, in ms. Clamped to ≥ 0. Default 150. */
192
+ backoffMs?: number;
193
+ /** Injectable delay (tests only); defaults to a real timer. */
194
+ sleep?: (ms: number) => Promise<void>;
195
+ }
196
+ export interface ResolveConflictsOptions {
197
+ projectDir: string;
198
+ resolutions: ConflictResolution[];
199
+ /** The `localId` from the conflict outcome. */
200
+ localId: string;
201
+ /** The `remoteId` from the conflict outcome. */
202
+ remoteId: string;
203
+ credential?: HostCredential;
204
+ tokenStore?: TokenStore;
205
+ authorName?: string;
206
+ authorEmail?: string;
207
+ httpClient?: typeof httpNode;
208
+ /**
209
+ * When true, the merge is allowed to combine two commits that share no
210
+ * common ancestor (unrelated histories). Set by the unrelated-histories
211
+ * recovery path; regular merge conflicts leave this false (the local and
212
+ * remote share a common base, so `allowUnrelatedHistories` is unnecessary).
213
+ */
214
+ allowUnrelatedHistories?: boolean;
215
+ /** Optional log file for debugging conflict resolution steps. */
216
+ logFile?: string;
217
+ }
218
+ /** Resolved project remote + credential used by every transport call. */
219
+ export interface RemoteTransport {
220
+ remote: string;
221
+ /** Sanitized HTTPS URL (no embedded credentials). */
222
+ url: string;
223
+ host: string;
224
+ credential?: HostCredential;
225
+ }
@@ -0,0 +1,45 @@
1
+ import { conflictFilesFrom, onAuthFor } from "./transport.ts";
2
+ import type { PullOutcome, PushOutcome, SyncOutcome, SyncProjectOptions, SyncRetryOptions } from "./sync-types.ts";
3
+ export { onAuthFor, conflictFilesFrom };
4
+ export { onlineCopyPath, resolveConflicts } from "./conflict-resolution.ts";
5
+ export { SYNC_SNAPSHOT_MESSAGE } from "./sync-messages.ts";
6
+ export type { ConflictFile, ConflictKind, ConflictResolution, PullOutcome, PushOutcome, ResolveConflictsOptions, SyncOutcome, SyncProjectOptions, SyncRetryOptions, } from "./sync-types.ts";
7
+ /**
8
+ * Default sync retry budget (BUG 6): 3 bounded passes with a short backoff.
9
+ * A fast-moving remote can race the push once or twice without the user seeing
10
+ * the false "Someone else synced at the same moment" message; a remote that
11
+ * genuinely races EVERY pass still terminates with that friendly message and
12
+ * the work safely snapshotted.
13
+ */
14
+ export declare const DEFAULT_SYNC_RETRY: Required<Omit<SyncRetryOptions, "sleep">>;
15
+ /**
16
+ * Snapshot-first sync (ADR 0006 D5) — the composition of {@link pullChanges}
17
+ * then {@link pushChanges}. If someone pushes between our pull and our push,
18
+ * the push reports pull-first and the pair re-runs (their commits merge in on
19
+ * the next pass), with a short backoff between passes. The loop is ALWAYS
20
+ * bounded by `retry.attempts` (BUG 6 — a fast-moving remote no longer triggers
21
+ * a FALSE race message after only two attempts); a remote that genuinely races
22
+ * every pass surfaces a friendly "try again" rather than looping forever. The
23
+ * snapshot-first guarantee holds on every path (the work is saved locally
24
+ * before any network step). Never throws for expected outcomes — everything is
25
+ * reported through the {@link SyncOutcome} union.
26
+ */
27
+ export declare function syncProject(options: SyncProjectOptions): Promise<SyncOutcome>;
28
+ /**
29
+ * Pull-only operation (the History tab's "Pull"): snapshot-if-needed →
30
+ * fetch → fast-forward or clean merge of the online changes — NEVER a push.
31
+ *
32
+ * Same conflict semantics as {@link syncProject}: `abortOnConflict` keeps the
33
+ * working tree completely untouched and the conflict comes back as
34
+ * `{ status: "conflict", files }` for the per-file choices dialog. Serialized
35
+ * on the per-repo lock; one function-scoped object cache, released on return.
36
+ */
37
+ export declare function pullChanges(options: SyncProjectOptions): Promise<PullOutcome>;
38
+ /**
39
+ * Push-only operation (the History tab's "Push"): snapshot-if-needed → push.
40
+ * If the online copy has commits this computer doesn't have (non-fast-forward),
41
+ * it does NOT auto-merge — the typed `"pull-first"` result tells the host to
42
+ * show a plain-language "get the latest changes first" message. Serialized on
43
+ * the per-repo lock; one function-scoped object cache, released on return.
44
+ */
45
+ export declare function pushChanges(options: SyncProjectOptions): Promise<PushOutcome>;
@@ -0,0 +1,47 @@
1
+ import httpNode from "isomorphic-git/http/node";
2
+ import { type HostCredential } from "./token-store.ts";
3
+ /** Why a remote-access probe failed, in machine-readable form. */
4
+ export type RemoteAccessFailureReason = "auth" | "not-found" | "unreachable" | "ssh-unsupported" | "insecure-transport" | "tls" | "unknown";
5
+ export type RemoteAccessResult = {
6
+ ok: true;
7
+ defaultBranch?: string;
8
+ refCount: number;
9
+ } | {
10
+ ok: false;
11
+ reason: RemoteAccessFailureReason;
12
+ message: string;
13
+ };
14
+ export interface TestRemoteAccessOptions {
15
+ /** The remote URL to probe (HTTPS; SSH is classified, never contacted). */
16
+ url: string;
17
+ /** Credential for the remote's host, if one is stored. */
18
+ credential?: HostCredential;
19
+ /** Injectable git HTTP transport for tests. Defaults to the node client. */
20
+ httpClient?: typeof httpNode;
21
+ /** Probe timeout. The probe resolves `unreachable` when it elapses. */
22
+ timeoutMs?: number;
23
+ }
24
+ /**
25
+ * True for any remote URL gutterpress's HTTPS-only transport cannot use:
26
+ * `ssh://…` and the scp-like `git@host:owner/repo.git` shorthand.
27
+ */
28
+ export declare function isSshRemoteUrl(url: string): boolean;
29
+ /**
30
+ * Author-friendly messages per failure reason (no URLs, no tokens — and no
31
+ * literal scheme tokens like "http://": the desktop's Advanced Setup dialog
32
+ * redacts anything matching /https?:\/\/\S+/, which would garble the copy).
33
+ * Exported for the wording guard in test-access.test.ts.
34
+ */
35
+ export declare const FAILURE_MESSAGES: Record<RemoteAccessFailureReason, string>;
36
+ /** Map a raw transport error to a classified failure. Never echoes the URL. */
37
+ export declare function classifyRemoteAccessError(e: unknown): RemoteAccessResult;
38
+ /**
39
+ * Probe a remote with a single refs listing (the `git ls-remote` equivalent).
40
+ *
41
+ * - Never throws — every outcome is a classified {@link RemoteAccessResult}.
42
+ * - SSH URLs return `ssh-unsupported` with zero network traffic.
43
+ * - Credentials embedded in the URL are stripped and used for auth when no
44
+ * explicit credential is supplied (ADR 0006 D7) — they never leak onward.
45
+ * - `defaultBranch` comes from the server's `HEAD` symref when advertised.
46
+ */
47
+ export declare function testRemoteAccess(options: TestRemoteAccessOptions): Promise<RemoteAccessResult>;
@@ -0,0 +1,71 @@
1
+ import type httpNode from "isomorphic-git/http/node";
2
+ type HttpClient = typeof httpNode;
3
+ export declare function pkt(data: string | Buffer): Buffer;
4
+ export declare const FLUSH: Buffer<ArrayBuffer>;
5
+ export declare function parsePktLines(body: Buffer): string[];
6
+ /**
7
+ * isomorphic-git location options: a worktree repo (`dir`) or a bare repo
8
+ * (`gitdir`). Bare support exists so the server can serve a real `--bare`
9
+ * clone (used by the large-repo sync repro scripts).
10
+ */
11
+ type RepoLoc = {
12
+ dir: string;
13
+ } | {
14
+ gitdir: string;
15
+ };
16
+ export declare function createFixtureRepo(dir: string): Promise<{
17
+ head: string;
18
+ first: string;
19
+ }>;
20
+ /**
21
+ * Collect commit+tree+blob oids reachable from `commit`, to `depth` commits.
22
+ *
23
+ * `stopCommits` (the client's `have` lines) bounds the walk like a real
24
+ * upload-pack: commits the client already has are neither read nor included.
25
+ * `skipTrees` (the root trees of those have-commits) prunes tree recursion so
26
+ * a fetch of message-only commits on a multi-GB repo packs ONLY the new
27
+ * commit objects instead of re-walking the whole project tree.
28
+ */
29
+ export declare function collectOids(repo: RepoLoc, commit: string, depth: number, opts?: {
30
+ stopCommits?: Set<string>;
31
+ skipTrees?: Set<string>;
32
+ }): Promise<string[]>;
33
+ export interface GitServer {
34
+ url: string;
35
+ /** Authorization headers the server saw, in order. */
36
+ authHeaders: Array<string | undefined>;
37
+ close(): Promise<void>;
38
+ }
39
+ export interface GitServerOptions {
40
+ requireAuth?: {
41
+ username: string;
42
+ password: string;
43
+ };
44
+ /** Answer every request with 404 (a host with no repo at that path). */
45
+ notFound?: boolean;
46
+ }
47
+ export declare function startGitServer(repoDir: string, opts?: GitServerOptions): Promise<GitServer>;
48
+ export declare function tempDir(prefix: string): Promise<string>;
49
+ /**
50
+ * An http client that delivers the git-upload-pack response but ERRORS the
51
+ * body instead of completing it — the shape a defaultGitHttp idle-timeout trip
52
+ * takes mid-pack. isomorphic-git 1.38.4 updates refs/remotes/<remote>/* from
53
+ * the ref advertisement BEFORE the packfile is collected/persisted, so this
54
+ * reproduces "refs moved, objects never landed" deterministically (the R15
55
+ * dangling-tracking-ref failure). Shared by the transport and recovery tests.
56
+ */
57
+ export declare function packDroppingClient(inner: HttpClient): HttpClient;
58
+ /**
59
+ * Binary-safe pkt-line command parser for a receive-pack request body:
60
+ * returns the `<old> <new> <ref>` commands and the byte offset where the raw
61
+ * packfile starts (right after the flush-pkt).
62
+ */
63
+ export declare function parseReceivePackRequest(body: Buffer): {
64
+ commands: Array<{
65
+ oldOid: string;
66
+ newOid: string;
67
+ ref: string;
68
+ }>;
69
+ packfile: Buffer;
70
+ };
71
+ export {};
@@ -0,0 +1,95 @@
1
+ /** An opaque stored credential for one remote host. */
2
+ export interface HostCredential {
3
+ /** Remote host the credential authenticates against, e.g. "github.com". */
4
+ host: string;
5
+ /** How the credential was acquired (drives re-auth UX, not transport). */
6
+ kind: "github-oauth" | "token";
7
+ /** The secret token value. NEVER log this. */
8
+ token: string;
9
+ /** Login/username associated with the token, when known. */
10
+ username?: string;
11
+ /** Optional human label ("GitHub — @octocat"). */
12
+ label?: string;
13
+ /** Epoch ms the credential was stored. */
14
+ createdAt: number;
15
+ }
16
+ /**
17
+ * Host-keyed credential vault contract (ADR 0006 D3 layer 2). Implementations
18
+ * are provided by the HOST APP (Electron safeStorage, CLI 0600 file); the lib
19
+ * only consumes this interface.
20
+ */
21
+ export interface TokenStore {
22
+ get(host: string): Promise<HostCredential | null>;
23
+ set(host: string, credential: HostCredential): Promise<void>;
24
+ delete(host: string): Promise<void>;
25
+ /** All stored credentials (used by "connected accounts" UIs). */
26
+ list(): Promise<HostCredential[]>;
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
+ /**
33
+ * THE canonical credential-store key for a remote host — the ONE derivation
34
+ * every writer (GitHub device flow, generic connect, embedded-URL migration)
35
+ * and every reader (diagnose, transport) must share. Historically each site
36
+ * derived its own key (the device flow hardcoded `github.com`, the URL
37
+ * migration dropped `:port`, generic connect kept `www.`), so a credential
38
+ * stored by one flow could be invisible to the lookup of another — the
39
+ * "connected successfully but never syncable" defect class.
40
+ *
41
+ * Accepts a bare hostname ("Git.Example.com"), a host:port pair
42
+ * ("git.example.com:3000"), any URL on the host, or an scp-like SSH address
43
+ * ("git@host:owner/repo.git"). Returns `hostname[:port]` lower-cased with any
44
+ * leading `www.` stripped (so `www.github.com` remotes find the `github.com`
45
+ * device-flow credential). The port is kept ONLY when explicit and
46
+ * non-default — `new URL` already drops :443/:80 for https/http. Returns ""
47
+ * when nothing usable remains.
48
+ */
49
+ export declare function credentialHostKey(hostOrUrl: string): string;
50
+ /**
51
+ * Resolve the Gutterpress user config directory (where the CLI token store
52
+ * lives). There is no pre-existing lib config-dir mechanism to follow (the CLI
53
+ * config cascade is per-project manifest based), so this establishes the
54
+ * standard one: `$GUTTERPRESS_CONFIG_DIR` override → `%APPDATA%/gutterpress` on
55
+ * Windows → `$XDG_CONFIG_HOME/gutterpress` → `~/.config/gutterpress`.
56
+ */
57
+ export declare function defaultConfigDir(): string;
58
+ /**
59
+ * The CLI's {@link TokenStore}: a JSON file with `0600` permissions under the
60
+ * user config dir. Plaintext-at-rest by design (the `gh`/`npm` model) — the
61
+ * desktop uses an OS-keychain-backed store instead.
62
+ */
63
+ export declare class FileTokenStore implements TokenStore {
64
+ readonly filePath: string;
65
+ private queue;
66
+ constructor(filePath?: string);
67
+ private read;
68
+ private write;
69
+ private enqueue;
70
+ get(host: string): Promise<HostCredential | null>;
71
+ set(host: string, credential: HostCredential): Promise<void>;
72
+ delete(host: string): Promise<void>;
73
+ list(): Promise<HostCredential[]>;
74
+ }
75
+ /** Result of {@link extractUrlCredential}. */
76
+ export interface UrlCredentialExtraction {
77
+ /** The URL with any userinfo (user:token@) stripped. */
78
+ cleanUrl: string;
79
+ /** Credential recovered from the URL's userinfo, if any. */
80
+ credential?: HostCredential;
81
+ }
82
+ /**
83
+ * Detect a token embedded in a remote URL (`https://user:tok@host/…` — common
84
+ * in the wild), returning the sanitized URL plus the recovered credential so
85
+ * callers can migrate it into the {@link TokenStore}. The token value must
86
+ * never be echoed into logs or diagnostics — only the clean URL is safe to
87
+ * display. Non-HTTP(S) or unparseable URLs pass through unchanged.
88
+ */
89
+ 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>;
@@ -0,0 +1,105 @@
1
+ import httpNode from "isomorphic-git/http/node";
2
+ import { type HostCredential, type TokenStore } from "./token-store.ts";
3
+ import type { ConflictFile, GitCache, RemoteTransport } from "./sync-types.ts";
4
+ /**
5
+ * The git repo directory for a project dir. A project IS its git repo, so this
6
+ * walks up to the enclosing repo root (opening a subfolder syncs the whole
7
+ * repo — plain git, no per-book scoping). Anything unclassifiable is itself.
8
+ */
9
+ export declare function repoDirFor(projectDir: string): Promise<string>;
10
+ /**
11
+ * True when a stored credential may be transmitted to `url`: over https to
12
+ * anywhere, or over http ONLY to loopback (a local git daemon or the in-memory
13
+ * test server). Deep-analysis SECURITY fix: the protocol gates elsewhere accept
14
+ * http:// too, so without this a repo-scoped account token was sent as cleartext
15
+ * Basic auth to a remote http host — harvestable by anyone on the path. Loopback
16
+ * http carries no network exposure, so it stays allowed.
17
+ */
18
+ export declare function isCredentialTransmissionSafe(url: string): boolean;
19
+ export declare function onAuthFor(credential: HostCredential | undefined): {
20
+ onAuth?: undefined;
21
+ } | {
22
+ onAuth: (url: string) => {
23
+ username: string;
24
+ password: string;
25
+ };
26
+ };
27
+ /**
28
+ * Resolve the project's tracked remote + credential. Throws friendly errors
29
+ * for the no-remote / SSH cases (the UI should have gated on diagnose, but
30
+ * the lib must stay safe to call directly).
31
+ */
32
+ export declare function resolveTransport(dir: string, options: {
33
+ credential?: HostCredential;
34
+ tokenStore?: TokenStore;
35
+ }): Promise<RemoteTransport>;
36
+ /**
37
+ * The failure arms shared verbatim by {@link SyncOutcome}, {@link PullOutcome}
38
+ * and {@link PushOutcome} — so one classifier serves all three operations.
39
+ * Decoding delegates to the shared recovery classifier
40
+ * (classifyTransportFailure): auth_required → "auth", network_unavailable →
41
+ * "offline", insecure_transport → "error" with its dedicated message (NEVER
42
+ * "auth" — reconnecting can't fix an http:// address, and the auth recovery
43
+ * path deletes the stored credential), anything else → the generic "error" arm.
44
+ */
45
+ export declare function failureOutcome(e: unknown, snapshotId?: string): {
46
+ status: "auth" | "offline" | "error";
47
+ message: string;
48
+ snapshotId?: string;
49
+ };
50
+ export declare function conflictFilesFrom(data: {
51
+ filepaths: string[];
52
+ bothModified: string[];
53
+ deleteByUs: string[];
54
+ deleteByTheirs: string[];
55
+ }): ConflictFile[];
56
+ /** Friendly setup-problem message for the expected gate errors, else null. */
57
+ export declare function setupErrorMessage(e: unknown): string | null;
58
+ /**
59
+ * Snapshot-first step shared by syncProject / pullChanges / pushChanges
60
+ * (ADR 0006 D5): commit any unsaved work in the WHOLE repo BEFORE any network
61
+ * or merge step, so a forced post-merge checkout can never discard it. The
62
+ * working-tree check runs lazily at action time on the caller's function-scoped
63
+ * object cache (released with the operation).
64
+ */
65
+ export declare function snapshotBeforeAction(args: {
66
+ projectDir: string;
67
+ dir: string;
68
+ message?: string;
69
+ authorName?: string;
70
+ authorEmail?: string;
71
+ cache: GitCache;
72
+ }): Promise<string | undefined>;
73
+ export declare function currentBranchOrThrow(dir: string): Promise<string>;
74
+ /**
75
+ * Run `fn` (a fetch that moves remote-tracking refs) with a rollback guard
76
+ * (deep-analysis R15): isomorphic-git updates refs/remotes/<remote>/* from
77
+ * the ref advertisement BEFORE collecting the packfile, so an abort
78
+ * mid-transfer (e.g. the defaultGitHttp idle timeout) leaves refs pointing
79
+ * at oids with no local object. Such a dangling ref poisons the next fetch —
80
+ * zero `have`s → the server streams the ENTIRE repository (the OOM
81
+ * fetchRemoteTip's `ref` choice exists to prevent) — and resolving it reports
82
+ * missing-object "corruption" on a never-corrupt repo.
83
+ *
84
+ * `listRefs` names the refs at risk; it runs again after a throw so refs
85
+ * CREATED by `fn` are covered too. If `fn` throws, every ref that moved to an
86
+ * oid whose object is MISSING locally is restored to its previous oid (or
87
+ * deleted if it didn't exist); refs whose objects DID land are kept — the
88
+ * pack made it. On success no ref is touched. Restoration is best-effort,
89
+ * per ref, and never masks `fn`'s error.
90
+ */
91
+ export declare function guardRefs<T>(dir: string, listRefs: () => Promise<string[]>, cache: GitCache, fn: () => Promise<T>): Promise<T>;
92
+ /** Single-ref form of {@link guardRefs} — guards one remote-tracking ref. */
93
+ export declare function guardTrackingRef<T>(dir: string, ref: string, cache: GitCache, fn: () => Promise<T>): Promise<T>;
94
+ /**
95
+ * Remote-wide form of {@link guardRefs} for a fetch that may move or create
96
+ * ANY refs/remotes/<remote>/* ref (e.g. `singleBranch: false`).
97
+ */
98
+ export declare function guardRemoteRefs<T>(dir: string, remote: string, cache: GitCache, fn: () => Promise<T>): Promise<T>;
99
+ /**
100
+ * Fetch the tracked branch's online tip. Returns `null` when the online
101
+ * repository has no such branch yet (a freshly created empty repo).
102
+ */
103
+ export declare function fetchRemoteTip(dir: string, branch: string, transport: RemoteTransport, http: typeof httpNode, cache: GitCache): Promise<string | null>;
104
+ /** First 8 chars of an oid for diagnostic log lines (null-safe). */
105
+ export declare function short(oid: string | null | undefined): string;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared slug helper — turn a human name / URL into a safe, lowercase,
3
+ * hyphen-separated id usable as a folder or filename stem.
4
+ *
5
+ * One implementation, two historical call sites (DRY): the theme manager
6
+ * (`themes/<id>/`) and project scaffolding (project folder / output filename).
7
+ * They differed only in the empty-input fallback, so that is the single
8
+ * parameter — everything else (NFKD normalise, diacritic strip, collapse
9
+ * non-alphanumerics to single hyphens, trim edge hyphens) is identical.
10
+ *
11
+ * Bundle-safe (CLAUDE.md §1/§3): pure string work, no deps.
12
+ */
13
+ /**
14
+ * Slugify `name`. When the input contains no usable characters the result is
15
+ * `fallback` (defaults to `""` — callers that need a placeholder pass one, e.g.
16
+ * theme ids pass `"theme"`).
17
+ */
18
+ export declare function slugify(name: string, fallback?: string): string;
19
+ /**
20
+ * Prettify a slug / filename stem into a display name: `-`/`_` runs become
21
+ * spaces and the first character is upper-cased ("stat-block" → "Stat block",
22
+ * "my_cool-theme" → "My cool theme"). The rough inverse of {@link slugify} used
23
+ * to label themes, snippets and custom templates that carry only a slug id.
24
+ *
25
+ * One implementation, several historical call sites (theme manager, snippets,
26
+ * project templates) — bundle-safe pure string work, no deps.
27
+ */
28
+ export declare function prettify(slug: string): string;
@@ -0,0 +1,39 @@
1
+ /** Folder (relative to the project root) snippets live in. */
2
+ export declare const SNIPPETS_DIR = "snippets";
3
+ /** One snippet's metadata for the picker (no body — read lazily). */
4
+ export interface SnippetEntry {
5
+ /** Display name (derived from the `.md` filename stem, prettified). */
6
+ name: string;
7
+ /** The on-disk filename, e.g. `callout.md`. Stable id for read/delete. */
8
+ fileName: string;
9
+ /** Distinct `{{variable}}` names parsed from the body, in first-seen order. */
10
+ variables: string[];
11
+ }
12
+ /**
13
+ * Parse the distinct `{{variable}}` placeholder names from a template, in the
14
+ * order they first appear. Whitespace inside the braces is ignored. Pure.
15
+ */
16
+ export declare function extractVariables(template: string): string[];
17
+ /**
18
+ * Replace every `{{name}}` placeholder with `values[name]`. A name with no
19
+ * provided value becomes the empty string (the caller prompts for values, so an
20
+ * unanswered field simply collapses). Non-placeholder braces are left intact.
21
+ * Pure.
22
+ */
23
+ export declare function substituteVariables(template: string, values: Record<string, string>): string;
24
+ /**
25
+ * List the project's snippets (newest filesystem order is not guaranteed; sort
26
+ * for the picker). Returns `[]` when the `snippets/` folder doesn't exist.
27
+ */
28
+ export declare function listSnippets(projectDir: string): Promise<SnippetEntry[]>;
29
+ /** Read one snippet's raw body. Refuses path traversal. */
30
+ export declare function readSnippet(projectDir: string, fileName: string): Promise<string>;
31
+ /**
32
+ * Save a snippet body under `snippets/<slug(name)>.md`, creating the folder when
33
+ * absent. Returns the stored entry (with its filename + parsed variables). The
34
+ * returned `name` echoes the author-supplied name, while `fileName` is the
35
+ * slugified storage name.
36
+ */
37
+ export declare function saveSnippet(projectDir: string, name: string, body: string): Promise<SnippetEntry>;
38
+ /** Delete a snippet by filename. Refuses path traversal. */
39
+ export declare function deleteSnippet(projectDir: string, fileName: string): Promise<void>;