gutterpress 0.10.0 → 0.10.1-beta.1

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 (54) 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-7a37g51p.js} +5 -5
  5. package/dist/{build-6r502chw.js → build-kdaet3pd.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-zfcryxg8.js → cli-ak1tagkr.js} +94 -57
  9. package/dist/{cli-eq5naw4m.js → cli-df7gcyns.js} +370 -1249
  10. package/dist/{cli-149edp6b.js → cli-dx6sxj2t.js} +1 -1
  11. package/dist/{cli-cqtggsng.js → cli-mcb484g3.js} +2 -47
  12. package/dist/{cli-k1065rkg.js → cli-w692g6dx.js} +75 -29
  13. package/dist/cli.js +14 -15
  14. package/dist/{doctor-akvxbtjb.js → doctor-vajd5h45.js} +2 -2
  15. package/dist/{engine-b159tbns.js → engine-0nt35svz.js} +2 -2
  16. package/dist/{engine-ft4cr3ep.js → engine-gkcqdye5.js} +1 -1
  17. package/dist/{gutterpress-viewer-te8g5grx.js → gutterpress-viewer-tma1qwz2.js} +57 -15
  18. package/dist/{index-ge7q9xj3.js → index-6g72h2n1.js} +771 -1482
  19. package/dist/{index-ycpvr0am.js → index-b2rqw41a.js} +1 -1
  20. package/dist/{index-mdefp0y5.js → index-dtrftayr.js} +75 -27
  21. package/dist/{index-wq3r5pj7.js → index-kvgq4q3q.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-p6a4bkvh.js} +5 -5
  38. package/dist/{new-hvq0x91q.js → new-4cdzfbsj.js} +5 -5
  39. package/dist/{plugin-pssmk0dx.js → plugin-a89tqspy.js} +5 -5
  40. package/dist/{preflight-4j00yd0g.js → preflight-r4y3f8f9.js} +5 -5
  41. package/dist/{preview-d4s6gk6p.js → preview-k8n2kew7.js} +5 -5
  42. package/dist/{publish-tkc26en7.js → publish-jnhjp2vd.js} +5 -5
  43. package/dist/{source-provider-3tcj6qg2.js → source-provider-2j5x2hyw.js} +1 -1
  44. package/dist/{source-provider-vanafrt9.js → source-provider-kn41jmbs.js} +1 -1
  45. package/dist/{validate-w9vfefrw.js → validate-prnybpkx.js} +5 -5
  46. package/package.json +1 -1
  47. package/dist/lib/app-heartbeat.d.ts +0 -102
  48. package/dist/lib/remote-auth/image-clash.d.ts +0 -17
  49. package/dist/lib/remote-auth/recovery/classify.d.ts +0 -97
  50. package/dist/lib/remote-auth/recovery/inspect.d.ts +0 -75
  51. package/dist/lib/remote-auth/recovery/locks.d.ts +0 -20
  52. package/dist/lib/remote-auth/recovery/repair.d.ts +0 -29
  53. package/dist/lib/remote-auth/recovery/types.d.ts +0 -39
  54. package/dist/repair-2va4w12t.js +0 -137
@@ -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-df7gcyns.js";
5
5
  import {
6
6
  log
7
- } from "./cli-cqtggsng.js";
7
+ } from "./cli-mcb484g3.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-dx6sxj2t.js";
13
+ import"./cli-ak1tagkr.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-df7gcyns.js";
9
9
  import {
10
10
  resolveGhostscript
11
- } from "./cli-cqtggsng.js";
11
+ } from "./cli-mcb484g3.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-dx6sxj2t.js";
17
+ import"./cli-ak1tagkr.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-df7gcyns.js";
5
+ import"./cli-mcb484g3.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-dx6sxj2t.js";
11
+ import"./cli-ak1tagkr.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-df7gcyns.js";
6
6
  import {
7
7
  log
8
- } from "./cli-cqtggsng.js";
8
+ } from "./cli-mcb484g3.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-dx6sxj2t.js";
14
+ import"./cli-ak1tagkr.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
 
@@ -6,10 +6,10 @@ import {
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-eq5naw4m.js";
9
+ } from "./cli-df7gcyns.js";
10
10
  import {
11
11
  log
12
- } from "./cli-cqtggsng.js";
12
+ } from "./cli-mcb484g3.js";
13
13
  import {
14
14
  UsageError,
15
15
  parseEngine,
@@ -18,13 +18,13 @@ import {
18
18
  rejectExtraPositionals,
19
19
  rejectUnknownFlags,
20
20
  resolvePort
21
- } from "./cli-149edp6b.js";
22
- import"./cli-zfcryxg8.js";
21
+ } from "./cli-dx6sxj2t.js";
22
+ import"./cli-ak1tagkr.js";
23
23
  import"./cli-c41yr7he.js";
24
24
  import {
25
25
  BuildError
26
26
  } from "./cli-46ycxe6r.js";
27
- import"./cli-k1065rkg.js";
27
+ import"./cli-w692g6dx.js";
28
28
  import"./cli-v5mp7a6q.js";
29
29
  import"./cli-37x76zdn.js";
30
30
 
@@ -7,22 +7,22 @@ import {
7
7
  publishCredentialKey,
8
8
  publishProviderFor,
9
9
  runPublish
10
- } from "./cli-eq5naw4m.js";
10
+ } from "./cli-df7gcyns.js";
11
11
  import {
12
12
  FileTokenStore,
13
13
  log
14
- } from "./cli-cqtggsng.js";
14
+ } from "./cli-mcb484g3.js";
15
15
  import {
16
16
  UsageError,
17
17
  rejectExtraPositionals,
18
18
  rejectUnknownFlags
19
- } from "./cli-149edp6b.js";
20
- import"./cli-zfcryxg8.js";
19
+ } from "./cli-dx6sxj2t.js";
20
+ import"./cli-ak1tagkr.js";
21
21
  import"./cli-c41yr7he.js";
22
22
  import {
23
23
  EXIT_CODES
24
24
  } from "./cli-46ycxe6r.js";
25
- import"./cli-k1065rkg.js";
25
+ import"./cli-w692g6dx.js";
26
26
  import"./cli-v5mp7a6q.js";
27
27
  import"./cli-37x76zdn.js";
28
28
 
@@ -16,7 +16,7 @@ import {
16
16
  snapshotWorkingTreeUnlocked,
17
17
  stageChanges,
18
18
  withRepoLock
19
- } from "./cli-k1065rkg.js";
19
+ } from "./cli-w692g6dx.js";
20
20
  import"./cli-v5mp7a6q.js";
21
21
  import"./cli-37x76zdn.js";
22
22
  export {
@@ -16,7 +16,7 @@ import {
16
16
  snapshotWorkingTreeUnlocked,
17
17
  stageChanges,
18
18
  withRepoLock
19
- } from "./index-mdefp0y5.js";
19
+ } from "./index-dtrftayr.js";
20
20
  import"./index-v5mp7a6q.js";
21
21
  import"./index-37x76zdn.js";
22
22
  export {
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-eq5naw4m.js";
3
+ } from "./cli-df7gcyns.js";
4
4
  import {
5
5
  log
6
- } from "./cli-cqtggsng.js";
6
+ } from "./cli-mcb484g3.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-149edp6b.js";
12
- import"./cli-zfcryxg8.js";
11
+ } from "./cli-dx6sxj2t.js";
12
+ import"./cli-ak1tagkr.js";
13
13
  import"./cli-c41yr7he.js";
14
14
  import {
15
15
  EXIT_CODES
16
16
  } from "./cli-46ycxe6r.js";
17
- import"./cli-k1065rkg.js";
17
+ import"./cli-w692g6dx.js";
18
18
  import"./cli-v5mp7a6q.js";
19
19
  import"./cli-37x76zdn.js";
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.10.0",
3
+ "version": "0.10.1-beta.1",
4
4
  "description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5
5
  "author": "itlackey",
6
6
  "license": "MPL-2.0",
@@ -1,102 +0,0 @@
1
- /**
2
- * app-heartbeat.ts — detect a running Gutterpress app before a CLI repair
3
- * mutates the repo.
4
- *
5
- * WHY: `gutterpress repair` and the desktop's own recovery path both call into
6
- * the recovery subsystem, but each runs in its own OS process. The per-repo
7
- * FIFO lock (source-provider.ts) only serializes operations WITHIN a process,
8
- * so `gutterpress repair` run from a terminal while the desktop has the same
9
- * project open can race a live sync/snapshot. Rather than build a
10
- * cross-process lock manager, the desktop leaves a small liveness marker
11
- * behind while a project is open, and `repair` checks it before mutating.
12
- *
13
- * The marker lives at `<repoDir>/.git/gutterpress-app-heartbeat` (NOT in
14
- * userData) so `repair`, given only a repo directory, can find it without any
15
- * knowledge of the desktop's install. It is deliberately NOT a lock:
16
- * - It never blocks the desktop itself, or any other repair run with --force.
17
- * - It carries no locking semantics — just "an app touched this repo
18
- * recently, maybe check before you assume it's idle".
19
- * - It is written best-effort (a failed write must never surprise the
20
- * author with a crash) and read best-effort (a missing/corrupt file
21
- * means "no app appears to have this open", not an error).
22
- *
23
- * Two invariants that keep this file inert to the rest of the recovery
24
- * subsystem:
25
- * - Filename does not match any pattern `findLockCandidates` scans
26
- * (recover-stale-lock.ts: fixed top-level `*.lock` names + `refs/**`) —
27
- * so stale-lock recovery can never see or remove it.
28
- * - It lives under `.git`, which `hasPendingChanges`/`listWorkdirChanges`
29
- * (source-provider.ts) always ignores (`git.isIgnored` treats a `.git`
30
- * path segment as ignored unconditionally) — so it can never look like an
31
- * uncommitted author change.
32
- */
33
- /**
34
- * Fallback freshness window used only when a heartbeat carries no `ttlMs`
35
- * (an older writer, or one with no cadence to report). Prefer a
36
- * cadence-derived TTL — see {@link heartbeatTtlMs} — since the actual
37
- * refresh cadence ranges 1 min–24 h (AUTO_SYNC_MIN/MAX_MINUTES,
38
- * host-policy.ts) and a fixed 2-minute window reads a live app as "closed"
39
- * for most of any longer cadence.
40
- */
41
- export declare const APP_HEARTBEAT_FRESH_MS: number;
42
- export interface AppHeartbeat {
43
- /** PID of the process that wrote the heartbeat (diagnostic only — never
44
- * used to `kill`/signal anything). */
45
- pid: number;
46
- /** Epoch ms the heartbeat was last (re)written. */
47
- timestamp: number;
48
- /**
49
- * Freshness window (ms) the writer says applies to THIS heartbeat, derived
50
- * from its own refresh cadence (see {@link heartbeatTtlMs}). When present,
51
- * `isAppHeartbeatFresh` honors it instead of its `maxAgeMs` fallback param —
52
- * the writer knows how often it actually refreshes; the reader doesn't.
53
- */
54
- ttlMs?: number;
55
- }
56
- /**
57
- * The freshness window a heartbeat writer should stamp for a given refresh
58
- * cadence (the actual interval this writer refreshes the marker on — e.g.
59
- * `autoSyncDelayMs(settings.versionHistory)`). `null` (feature disabled, no
60
- * periodic refresh at all) falls back to {@link APP_HEARTBEAT_FRESH_MS}.
61
- *
62
- * Standard heartbeat rule: TTL should comfortably exceed the refresh period
63
- * so one missed/delayed tick doesn't read as "closed". Exported so both the
64
- * desktop (the writer, which knows its own cadence) and tests can compute it
65
- * consistently.
66
- */
67
- export declare function heartbeatTtlMs(periodicMs: number | null): number;
68
- /** Absolute path to the heartbeat marker for a repo. */
69
- export declare function appHeartbeatPath(repoDir: string): string;
70
- /**
71
- * Write/refresh the heartbeat marker. Best-effort: a failed write (missing
72
- * `.git` dir, read-only filesystem, …) is swallowed — it must never surface
73
- * as an error to the author, since it is only ever a background liveness
74
- * signal, not a required operation.
75
- *
76
- * `ttlMs`, when supplied, should be {@link heartbeatTtlMs} applied to this
77
- * writer's OWN actual refresh cadence — it is stamped into the marker so a
78
- * reader with no knowledge of that cadence still judges freshness correctly.
79
- * Omit it (or pass `undefined`) when the caller has no cadence to report; the
80
- * reader then falls back to its own `maxAgeMs` default.
81
- */
82
- export declare function writeAppHeartbeat(repoDir: string, now?: number, pid?: number, ttlMs?: number): Promise<void>;
83
- /**
84
- * Remove the heartbeat marker (project close / app quit). Best-effort: an
85
- * already-missing or unreadable file is not an error.
86
- */
87
- export declare function removeAppHeartbeat(repoDir: string): Promise<void>;
88
- /**
89
- * Read + parse the heartbeat marker. Returns `null` when absent, unreadable,
90
- * or corrupt (never throws — a damaged heartbeat is just "no signal").
91
- */
92
- export declare function readAppHeartbeat(repoDir: string): Promise<AppHeartbeat | null>;
93
- /**
94
- * True when a FRESH heartbeat exists for this repo — i.e. the gutterpress app
95
- * appears to have this project open right now. Absent/stale/corrupt all read
96
- * as "not open" (fail open: `repair` should not block on ambiguous signal).
97
- *
98
- * Freshness window: the heartbeat's own stamped `ttlMs` (derived from the
99
- * writer's actual refresh cadence — see {@link heartbeatTtlMs}) wins when
100
- * present; `maxAgeMs` is only a fallback for a heartbeat with none.
101
- */
102
- export declare function isAppHeartbeatFresh(repoDir: string, now?: number, maxAgeMs?: number): Promise<boolean>;
@@ -1,17 +0,0 @@
1
- /** Read one version's exact bytes by BLOB oid. */
2
- export declare function readImageVersion(options: {
3
- projectDir: string;
4
- oid: string;
5
- }): Promise<Uint8Array>;
6
- /**
7
- * Keep a specific version of a clashing image: write the blob's exact bytes
8
- * to the file and snapshot. Serialized on the per-repo lock like every other
9
- * mutating operation.
10
- */
11
- export declare function keepImageVersion(options: {
12
- projectDir: string;
13
- path: string;
14
- oid: string;
15
- authorName?: string;
16
- authorEmail?: string;
17
- }): Promise<void>;
@@ -1,97 +0,0 @@
1
- /**
2
- * Error/health classification for sync + repair — the SINGLE source of truth.
3
- *
4
- * 2026-08-14 simplification (owner directive): the 17-kind SyncErrorKind
5
- * taxonomy and its 16 per-kind handlers are gone. Every structural problem a
6
- * repo can have now has ONE answer — `repairRepo()` (repair.ts) — so
7
- * classification collapses to three health verdicts:
8
- *
9
- * - null — healthy, nothing to do
10
- * - "stale_lock" — a leftover lock old enough to sweep
11
- * - "needs_repair" — anything structural (missing/corrupt `.git`, broken
12
- * ref store, detached HEAD, interrupted merge/rebase/
13
- * cherry-pick left by an external tool)
14
- *
15
- * The transport decoders (auth/offline/insecure) and the merge/push guards
16
- * used by sync.ts remain here unchanged — outcome mapping, not repair.
17
- * This module is pure — no I/O, no side effects.
18
- */
19
- import type { RepoHealth } from "./types.ts";
20
- /**
21
- * Minimum age before a leftover git lock counts as STALE. locks.ts imports
22
- * this same constant as its sweep threshold, so preflight and sweep can never
23
- * disagree: a lock young enough to pass preflight is exactly a lock the sweep
24
- * would defer ("a live process may still hold it").
25
- */
26
- export declare const STALE_LOCK_MIN_AGE_MS: number;
27
- /** The collapsed repair taxonomy — see the module header. */
28
- export type RepairNeed = "stale_lock" | "needs_repair";
29
- /**
30
- * Thrown by sync's structural preflight when the repo must be repaired before
31
- * any sync work can safely run. The `code` string is the STABLE contract
32
- * hosts may match on across the dynamic-import boundary (where `instanceof`
33
- * is unreliable); `kind` says whether a lock sweep suffices or the full
34
- * repair pipeline is needed.
35
- */
36
- export declare class RepoNeedsRecoveryError extends Error {
37
- readonly kind: RepairNeed;
38
- readonly code = "RepoNeedsRecovery";
39
- constructor(kind: RepairNeed);
40
- }
41
- /** Type guard for {@link RepoNeedsRecoveryError} (matches on the stable code). */
42
- export declare function isRepoNeedsRecoveryError(e: unknown): e is RepoNeedsRecoveryError;
43
- /**
44
- * Thrown by transport.ts's onAuth when a stored credential EXISTS but the
45
- * remote URL fails isCredentialTransmissionSafe (non-loopback http). Loud and
46
- * typed on purpose: the old behavior (silently withholding the credential)
47
- * surfaced as a 401 → "auth" → "reconnect" loop. The `code` string is the
48
- * STABLE contract (matchable across dynamic-import boundaries).
49
- */
50
- export declare class InsecureTransportError extends Error {
51
- readonly code = "InsecureTransport";
52
- constructor();
53
- }
54
- /** Type guard for {@link InsecureTransportError} (matches on the stable code). */
55
- export declare function isInsecureTransportError(e: unknown): e is InsecureTransportError;
56
- export declare function isPushRejected(e: unknown): boolean;
57
- /** Type guard exposing MergeConflictError's per-file payload (converge-merge). */
58
- export declare function isMergeConflictError(e: unknown): e is {
59
- data: {
60
- filepaths: string[];
61
- bothModified: string[];
62
- deleteByUs: string[];
63
- deleteByTheirs: string[];
64
- };
65
- };
66
- /**
67
- * Unrelated histories — the local project and the configured online project
68
- * share no common starting point. Sync surfaces this as a plain setup error
69
- * (a wrong online address must never be silently spliced into the book).
70
- */
71
- export declare function isUnrelatedHistories(e: unknown): boolean;
72
- export declare function classifyTransportFailure(e: unknown): "auth_required" | "network_unavailable" | "insecure_transport" | null;
73
- /**
74
- * True when a thrown error smells like LOCAL repo corruption (unreadable
75
- * objects/refs/index) rather than a transport or logic failure — the signal
76
- * for a host's mid-sync catch to run `repairRepo()`. NOTE on NotFoundError
77
- * ambiguity: a transport 404 also surfaces as NotFoundError, but
78
- * transport.ts's fetchRemoteTip rewrites those to an HttpError(401) BEFORE
79
- * they can reach this heuristic, so a raw NotFoundError here is a LOCAL
80
- * ref-resolution failure.
81
- */
82
- export declare function isLikelyRepoCorruption(e: unknown): boolean;
83
- /**
84
- * Classify a repo's structural condition from a RepoHealth snapshot.
85
- * Returns null for a healthy repo, "stale_lock" when the only problem is a
86
- * sweepable lock, and "needs_repair" for everything structural — the repair
87
- * pipeline (repair.ts) handles every structural case in one ordered pass, so
88
- * finer distinctions buy nothing.
89
- *
90
- * `minLockAgeMs` gates the stale-lock verdict: at preflight (the default) a
91
- * younger lock is treated as healthy because a live process may still hold
92
- * it. Error-path callers pass 0: a lock that just made a sync THROW is worth
93
- * routing regardless of age (the sweep still defers while it is fresh).
94
- */
95
- export declare function classifyFromHealth(health: RepoHealth, opts?: {
96
- minLockAgeMs?: number;
97
- }): RepairNeed | null;