gutterpress 0.9.0-alpha.2 → 0.10.0-alpha.4

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 (138) hide show
  1. package/README.md +18 -4
  2. package/dist/api/index.d.ts +11 -5
  3. package/dist/api/index.js +20 -15
  4. package/dist/assets/preview/scripts/preview-bridge.d.ts +1 -0
  5. package/dist/assets/preview/scripts/preview-interface.d.ts +1 -0
  6. package/dist/{audit-nhn2pjz3.js → audit-k1vnfwvc.js} +10 -7
  7. package/dist/{build-san7fv2z.js → build-hqmwgvdw.js} +14 -8
  8. package/dist/checks/source/index.d.ts +1 -0
  9. package/dist/checks/source/layout-markers.d.ts +3 -0
  10. package/dist/checks/source/local-ref-parser.d.ts +28 -0
  11. package/dist/{cli-e5zhb0xs.js → cli-0r0tq16s.js} +17 -24
  12. package/dist/cli-46ycxe6r.js +18 -0
  13. package/dist/cli-c41yr7he.js +241 -0
  14. package/dist/cli-hp9r2pzt.js +2343 -0
  15. package/dist/{index-yzrh708h.js → cli-k1065rkg.js} +2 -5
  16. package/dist/{cli-k4bd06sd.js → cli-n25qycwz.js} +4986 -5889
  17. package/dist/{cli-najycadg.js → cli-ra0ed2xt.js} +69 -253
  18. package/dist/{cli-506tg37g.js → cli-revgt4pr.js} +2 -1
  19. package/dist/{cli-wchtvxvw.js → cli-v5mp7a6q.js} +14 -1
  20. package/dist/cli.js +20 -18
  21. package/dist/{doctor-hrk0kxxz.js → doctor-dxms7ehm.js} +4 -2
  22. package/dist/engine/compiler/build.d.ts +139 -0
  23. package/dist/engine/compiler/postprocess.d.ts +23 -0
  24. package/dist/engine/compiler/tier2.d.ts +78 -0
  25. package/dist/engine/shared/cdp.d.ts +104 -0
  26. package/dist/engine/shared/content-value.d.ts +64 -0
  27. package/dist/engine/shared/gcpm-extract.d.ts +119 -0
  28. package/dist/engine/shared/margin-box-support.d.ts +12 -0
  29. package/dist/engine/shared/pdf-inspect.d.ts +24 -0
  30. package/dist/engine/shared/synthesis.d.ts +155 -0
  31. package/dist/engine-wa7y9av9.js +41 -0
  32. package/dist/engine-z4p9sr4h.js +40 -0
  33. package/dist/gutterpress-agent-1ctgfz92.js +576 -0
  34. package/dist/gutterpress-viewer-cem7dmr5.js +2349 -0
  35. package/dist/{index-bynn850m.js → index-05y3dnxq.js} +3049 -4015
  36. package/dist/index-9tyq9kks.js +708 -0
  37. package/dist/{cli-yzrh708h.js → index-mdefp0y5.js} +1 -1
  38. package/dist/{index-wchtvxvw.js → index-v5mp7a6q.js} +14 -1
  39. package/dist/index-xxg4zfrg.js +1907 -0
  40. package/dist/index.d.ts +3 -1
  41. package/dist/index.js +24 -17
  42. package/dist/lib/asset-inline.d.ts +36 -0
  43. package/dist/lib/browser-pool.d.ts +18 -0
  44. package/dist/lib/build-error.d.ts +1 -1
  45. package/dist/lib/build-preflight.d.ts +35 -2
  46. package/dist/lib/build-runner.d.ts +45 -17
  47. package/dist/lib/build-staging.d.ts +9 -55
  48. package/dist/lib/cli-args.d.ts +8 -0
  49. package/dist/lib/desktop.d.ts +2 -2
  50. package/dist/lib/embedded-assets.d.ts +1 -1
  51. package/dist/lib/engine.d.ts +33 -0
  52. package/dist/lib/ghostscript.d.ts +46 -1
  53. package/dist/lib/markdown/assemble.d.ts +18 -10
  54. package/dist/lib/markdown/gp-pin-scope.d.ts +1 -0
  55. package/dist/lib/markdown/gutterpress-css.d.ts +125 -0
  56. package/dist/lib/markdown/images.d.ts +26 -0
  57. package/dist/lib/markdown/index.d.ts +4 -2
  58. package/dist/lib/markdown/inline-source.d.ts +10 -0
  59. package/dist/lib/markdown/markers.d.ts +32 -0
  60. package/dist/lib/markdown/renderer.d.ts +8 -3
  61. package/dist/lib/markdown/source-range.d.ts +61 -0
  62. package/dist/lib/missing-asset-placeholder.d.ts +52 -0
  63. package/dist/lib/presets.d.ts +1 -1
  64. package/dist/lib/printsafe.d.ts +2 -3
  65. package/dist/lib/remote-auth/converge-merge.d.ts +48 -0
  66. package/dist/lib/remote-auth/image-clash.d.ts +17 -0
  67. package/dist/lib/remote-auth/recovery/classify.d.ts +55 -67
  68. package/dist/lib/remote-auth/recovery/inspect.d.ts +14 -13
  69. package/dist/lib/remote-auth/recovery/locks.d.ts +20 -0
  70. package/dist/lib/remote-auth/recovery/repair.d.ts +29 -0
  71. package/dist/lib/remote-auth/recovery/types.d.ts +8 -204
  72. package/dist/lib/remote-auth/sync-messages.d.ts +2 -3
  73. package/dist/lib/remote-auth/sync-types.d.ts +35 -63
  74. package/dist/lib/remote-auth/sync.d.ts +11 -18
  75. package/dist/lib/remote-auth/transport.d.ts +14 -7
  76. package/dist/lib/theme-import.d.ts +4 -5
  77. package/dist/{lint-96j9hrj4.js → lint-xjwm5ep8.js} +10 -7
  78. package/dist/{manifest.schema-z61rzw44.json → manifest.schema-zxgxnbg7.json} +21 -0
  79. package/dist/{new-8p38wavc.js → new-kwdwpf0j.js} +12 -8
  80. package/dist/{plugin-ees6nhkc.js → plugin-rg4tnn96.js} +10 -7
  81. package/dist/{preflight-1q6c2edh.js → preflight-3127y25z.js} +10 -7
  82. package/dist/preview/file-watcher.d.ts +13 -17
  83. package/dist/preview/lifecycle.d.ts +1 -1
  84. package/dist/{pagedjs-bridge-vn4hk9fx.js → preview-bridge-fz7vpk8m.js} +8 -0
  85. package/dist/preview-interface-435cczt5.js +1059 -0
  86. package/dist/{preview-y5a2zen1.js → preview-ncgfhqmw.js} +16 -9
  87. package/dist/preview-shell-c5mfa3q0.js +346 -0
  88. package/dist/{project-source-p0gn1wd5.js → project-source-ekcyp63q.js} +1 -1
  89. package/dist/{publish-rm9yb3wh.js → publish-pr0rwh6p.js} +10 -7
  90. package/dist/render.d.ts +3 -4
  91. package/dist/render.js +728 -64
  92. package/dist/{repair-zgq7q2g6.js → repair-8270smfw.js} +45 -79
  93. package/dist/schema/manifest.types.d.ts +38 -0
  94. package/dist/{source-provider-c1rjm2c0.js → source-provider-3tcj6qg2.js} +2 -2
  95. package/dist/source-provider-vanafrt9.js +40 -0
  96. package/dist/{theme-zz2ktzqs.css → theme-h5recz6c.css} +8 -7
  97. package/dist/{theme-570zmh2t.css → theme-j2bagrfx.css} +8 -7
  98. package/dist/{theme-nya4nqh6.css → theme-nn6d53zy.css} +8 -7
  99. package/dist/types.d.ts +7 -0
  100. package/dist/{validate-nr0xa6sa.js → validate-54e17rae.js} +10 -7
  101. package/package.json +6 -6
  102. package/dist/cli-yja077f6.js +0 -92
  103. package/dist/git-http-yrb4ag6z.js +0 -17
  104. package/dist/index-yja077f6.js +0 -92
  105. package/dist/lib/markdown/markdown-it-paged.d.ts +0 -30
  106. package/dist/lib/pagedjs-marker.d.ts +0 -42
  107. package/dist/lib/pagedjs.d.ts +0 -26
  108. package/dist/lib/pagination.d.ts +0 -149
  109. package/dist/lib/remote-auth/conflict-resolution.d.ts +0 -29
  110. package/dist/lib/remote-auth/recovery/abort-interrupted-operation.d.ts +0 -103
  111. package/dist/lib/remote-auth/recovery/backup.d.ts +0 -113
  112. package/dist/lib/remote-auth/recovery/context.d.ts +0 -47
  113. package/dist/lib/remote-auth/recovery/dispatch.d.ts +0 -28
  114. package/dist/lib/remote-auth/recovery/failsafe.d.ts +0 -33
  115. package/dist/lib/remote-auth/recovery/manual-guidance.d.ts +0 -28
  116. package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +0 -59
  117. package/dist/lib/remote-auth/recovery/policy.d.ts +0 -47
  118. package/dist/lib/remote-auth/recovery/recover-auth.d.ts +0 -42
  119. package/dist/lib/remote-auth/recovery/recover-binary-conflict.d.ts +0 -37
  120. package/dist/lib/remote-auth/recovery/recover-corrupt-index.d.ts +0 -40
  121. package/dist/lib/remote-auth/recovery/recover-detached-head.d.ts +0 -70
  122. package/dist/lib/remote-auth/recovery/recover-interrupted-cherry-pick.d.ts +0 -23
  123. package/dist/lib/remote-auth/recovery/recover-interrupted-merge.d.ts +0 -28
  124. package/dist/lib/remote-auth/recovery/recover-interrupted-rebase.d.ts +0 -39
  125. package/dist/lib/remote-auth/recovery/recover-merge-conflict.d.ts +0 -34
  126. package/dist/lib/remote-auth/recovery/recover-missing-git-dir.d.ts +0 -37
  127. package/dist/lib/remote-auth/recovery/recover-missing-objects.d.ts +0 -56
  128. package/dist/lib/remote-auth/recovery/recover-network.d.ts +0 -34
  129. package/dist/lib/remote-auth/recovery/recover-non-fast-forward.d.ts +0 -27
  130. package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +0 -68
  131. package/dist/lib/remote-auth/recovery/recover-unrelated-histories.d.ts +0 -44
  132. package/dist/lib/remote-auth/recovery/recover-wrong-remote.d.ts +0 -35
  133. package/dist/lib/remote-auth/resolution-plan.d.ts +0 -64
  134. package/dist/paged.polyfill-n95pbxfn.js +0 -33288
  135. package/dist/pagedjs-interface-qxvzgwd7.js +0 -557
  136. package/dist/preview-shell-6dqexx1m.js +0 -581
  137. /package/dist/assets/{preview/scripts/pagedjs-bridge.d.ts → engine/gutterpress-agent.d.ts} +0 -0
  138. /package/dist/assets/{preview/scripts/pagedjs-interface.d.ts → engine/gutterpress-viewer.d.ts} +0 -0
@@ -1,38 +1,42 @@
1
1
  /**
2
- * Error-to-kind classifier for the sync-recovery subsystem — the SINGLE
3
- * source of truth for git error classification.
2
+ * Error/health classification for sync + repair — the SINGLE source of truth.
4
3
  *
5
- * Maps a thrown error (from isomorphic-git or sync.ts) plus an optional
6
- * RepoHealth preflight to a SyncErrorKind. The building blocks
7
- * (isPushRejected, isMergeConflictError, classifyTransportFailure) are
8
- * exported and consumed by sync.ts — there is exactly ONE implementation of
9
- * each decoder, not parallel copies "kept in sync by spec".
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:
10
8
  *
11
- * classifyFromHealth() is the health-only classifier used by preflight
12
- * callers (no thrown error yet — e.g. the desktop at project-open): it returns
13
- * null for a healthy repo so the caller can skip recovery entirely.
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
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.
15
17
  * This module is pure — no I/O, no side effects.
16
18
  */
17
- import type { RepoHealth, SyncErrorKind } from "./types.ts";
19
+ import type { RepoHealth } from "./types.ts";
18
20
  /**
19
- * Minimum age before a leftover git lock counts as STALE for a preflight
20
- * classification. recover-stale-lock.ts imports this same constant as its
21
- * act-or-retry threshold, so preflight and handler can never disagree: a lock
22
- * young enough to pass preflight is exactly a lock the handler would defer
23
- * with retry_later ("a live process may still hold it").
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").
24
25
  */
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";
26
29
  /**
27
- * Thrown by syncProject's structural preflight when the repo must be repaired
28
- * before any sync work can safely run. The `code` string is the STABLE
29
- * contract hosts may match on across the dynamic-import boundary (where
30
- * `instanceof` is unreliable); `kind` names the repair to dispatch.
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.
31
35
  */
32
36
  export declare class RepoNeedsRecoveryError extends Error {
33
- readonly kind: SyncErrorKind;
37
+ readonly kind: RepairNeed;
34
38
  readonly code = "RepoNeedsRecovery";
35
- constructor(kind: SyncErrorKind);
39
+ constructor(kind: RepairNeed);
36
40
  }
37
41
  /** Type guard for {@link RepoNeedsRecoveryError} (matches on the stable code). */
38
42
  export declare function isRepoNeedsRecoveryError(e: unknown): e is RepoNeedsRecoveryError;
@@ -40,9 +44,8 @@ export declare function isRepoNeedsRecoveryError(e: unknown): e is RepoNeedsReco
40
44
  * Thrown by transport.ts's onAuth when a stored credential EXISTS but the
41
45
  * remote URL fails isCredentialTransmissionSafe (non-loopback http). Loud and
42
46
  * typed on purpose: the old behavior (silently withholding the credential)
43
- * surfaced as a 401 → "auth" → "reconnect" loop, and recover-auth then deleted
44
- * the credential for the whole host. The `code` string is the STABLE contract
45
- * (matchable across dynamic-import boundaries where `instanceof` fails).
47
+ * surfaced as a 401 → "auth" → "reconnect" loop. The `code` string is the
48
+ * STABLE contract (matchable across dynamic-import boundaries).
46
49
  */
47
50
  export declare class InsecureTransportError extends Error {
48
51
  readonly code = "InsecureTransport";
@@ -51,7 +54,7 @@ export declare class InsecureTransportError extends Error {
51
54
  /** Type guard for {@link InsecureTransportError} (matches on the stable code). */
52
55
  export declare function isInsecureTransportError(e: unknown): e is InsecureTransportError;
53
56
  export declare function isPushRejected(e: unknown): boolean;
54
- /** Type guard exposing MergeConflictError's per-file payload (used by sync.ts). */
57
+ /** Type guard exposing MergeConflictError's per-file payload (converge-merge). */
55
58
  export declare function isMergeConflictError(e: unknown): e is {
56
59
  data: {
57
60
  filepaths: string[];
@@ -60,50 +63,35 @@ export declare function isMergeConflictError(e: unknown): e is {
60
63
  deleteByTheirs: string[];
61
64
  };
62
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;
63
72
  export declare function classifyTransportFailure(e: unknown): "auth_required" | "network_unavailable" | "insecure_transport" | null;
64
73
  /**
65
- * Classify a structural repo condition from a RepoHealth snapshot alone.
66
- * Used by preflight callers (no thrown error — e.g. project-open), and by
67
- * classifyGitError's structural step so there is ONE ordering, not two.
68
- *
69
- * Returns null for a healthy repo (nothing to recover).
70
- *
71
- * ORDERING: interrupted-operation checks MUST precede the detached-head check
72
- * (an in-progress rebase usually detaches HEAD — the abort repair must win over
73
- * the rescue-branch repair), and specific interrupted-op repairs precede the
74
- * generic stale-lock cleanup.
75
- *
76
- * `minLockAgeMs` gates the stale-lock classification: at preflight (the
77
- * default, STALE_LOCK_MIN_AGE_MS) a younger lock is treated as healthy because
78
- * a live process may still hold it — the same rule recover-stale-lock.ts
79
- * applies before acting. Error-path callers pass 0: a lock that just made a
80
- * sync THROW is worth routing regardless of age (the handler still re-checks
81
- * and returns retry_later while it is fresh).
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.
82
81
  */
83
- export declare function classifyFromHealth(health: RepoHealth, opts?: {
84
- minLockAgeMs?: number;
85
- }): SyncErrorKind | null;
82
+ export declare function isLikelyRepoCorruption(e: unknown): boolean;
86
83
  /**
87
- * Map a thrown error plus optional repo-health facts to a SyncErrorKind.
88
- *
89
- * Called by the recovery dispatcher BEFORE invoking the per-kind handler.
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.
90
89
  *
91
- * ORDERING (BUG 1 — transient transport beats structural health):
92
- * 1. missing_git_dir — when there is genuinely no repo, a transport error is
93
- * meaningless (nothing to talk to a remote ABOUT), so the missing-repo
94
- * guidance must win even over an auth/network error.
95
- * 2. Clearly transient/transport errors (auth, network) — these are decoded
96
- * from the THROWN ERROR and win over the remaining structural health
97
- * flags (detached HEAD, stale lock). Rationale: you cannot repair repo
98
- * STRUCTURE while you are offline or signed out, and the scary
99
- * backup+rescue-branch+confirm repair is the wrong first response to a
100
- * blip — the friendly "reconnect" / "try later" is correct. Once the user
101
- * is back online, the next sync re-runs preflight (no thrown error) and
102
- * the structural kind surfaces then (step 3).
103
- * 3. Structural health flags (detached HEAD, stale lock) — applied when the
104
- * failure is NOT a transient transport error (e.g. a preflight with no
105
- * error, or a non-transport error).
106
- * 4. Remaining error-code/message heuristics (conflicts, corrupt index,
107
- * unrelated histories, missing objects/ref-store, wrong remote/branch).
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).
108
94
  */
109
- export declare function classifyGitError(err: unknown, health?: RepoHealth): SyncErrorKind;
95
+ export declare function classifyFromHealth(health: RepoHealth, opts?: {
96
+ minLockAgeMs?: number;
97
+ }): RepairNeed | null;
@@ -12,15 +12,16 @@
12
12
  * Notes on two health facts:
13
13
  * - hasGitDir is true whenever `.git/` EXISTS, even with a missing/corrupt
14
14
  * HEAD (a damaged repo is still a repo — see the inline note at the check).
15
- * - hasStaleLock uses the stale-lock handler's OWN lock scanner
16
- * (findLockCandidates in recover-stale-lock.ts) — one implementation, so
15
+ * - hasStaleLock uses the lock sweep's OWN scanner
16
+ * (findLockCandidates in locks.ts) — one implementation, so
17
17
  * health and handler can never disagree about which locks exist.
18
18
  *
19
19
  * All probes are best-effort and throw-free (the caller must never see an
20
20
  * exception from a preflight probe).
21
21
  */
22
22
  import type { LogData } from "../operation-log.ts";
23
- import type { RepoHealth, RecoveryContext, SyncErrorKind } from "./types.ts";
23
+ import type { RepairNeed } from "./classify.ts";
24
+ import type { RepoHealth } from "./types.ts";
24
25
  /**
25
26
  * Probe the local repository and return a RepoHealth snapshot.
26
27
  * Never throws — on any error the relevant flag is set conservatively.
@@ -31,7 +32,10 @@ import type { RepoHealth, RecoveryContext, SyncErrorKind } from "./types.ts";
31
32
  * pull step immediately performs the same walk anyway (sync-simplicity
32
33
  * mandate: no redundant walks on the hot path).
33
34
  */
34
- export declare function inspectRepo(ctx: Pick<RecoveryContext, "repoDir" | "source">, opts?: {
35
+ export declare function inspectRepo(ctx: {
36
+ repoDir: string;
37
+ source?: import("../../project-source.ts").ProjectSource;
38
+ }, opts?: {
35
39
  checkLocalChanges?: boolean;
36
40
  }): Promise<RepoHealth>;
37
41
  /**
@@ -39,15 +43,12 @@ export declare function inspectRepo(ctx: Pick<RecoveryContext, "repoDir" | "sour
39
43
  * its commit, and read its root tree. Throws when any step fails (missing or
40
44
  * corrupt object/ref); resolves when the repo is fully readable.
41
45
  *
42
- * This is the SAME verification recover-missing-objects.ts performs to check
43
- * whether a fetch actually repaired the object store — it is exported here so
44
- * that check and the repair command's diagnosis step share ONE implementation
45
- * rather than two copies that could drift. `inspectRepo`'s health flags are
46
+ * Exported so the repair
47
+ * command's diagnosis step and any host check share ONE implementation. `inspectRepo`'s health flags are
46
48
  * all filesystem-presence checks (no object is ever read), so they cannot
47
49
  * detect object-store corruption on their own — callers that need to catch
48
- * `corrupt_index` / `missing_or_corrupt_objects` / `unrelated_histories` /
49
- * `wrong_remote_or_branch` must run this probe and feed a caught error
50
- * through `classifyGitError`.
50
+ * unreadable objects/refs must run this probe and feed a caught error through
51
+ * `isLikelyRepoCorruption` (classify.ts) to decide whether to repairRepo().
51
52
  */
52
53
  export declare function verifyRepoReadable(dir: string): Promise<void>;
53
54
  /**
@@ -64,11 +65,11 @@ export declare function isUnbornRepo(repoDir: string): boolean;
64
65
  * classifyFromHealth returned (a pure mapping — it cannot drift from the
65
66
  * classifier's decision order, because it never re-implements it).
66
67
  */
67
- export declare function preflightStructuralReason(kind: SyncErrorKind | null): string;
68
+ export declare function preflightStructuralReason(kind: RepairNeed | null): string;
68
69
  /**
69
70
  * Build a flat, secret-free record of the preflight decision inputs for the
70
71
  * operation log. Every health boolean is recorded (so support can see the FULL
71
72
  * picture, not just the one-word kind), plus the opened dir vs repo root, the
72
73
  * chosen kind, and the single reason that drove it.
73
74
  */
74
- export declare function buildPreflightDiagnostics(openedDir: string, repoDir: string, health: RepoHealth, kind: SyncErrorKind | null): LogData;
75
+ export declare function buildPreflightDiagnostics(openedDir: string, repoDir: string, health: RepoHealth, kind: RepairNeed | null): LogData;
@@ -0,0 +1,20 @@
1
+ export interface LockCandidate {
2
+ /** Absolute path to the lock file. */
3
+ path: string;
4
+ /** Age in ms relative to `now` (now - mtime). */
5
+ ageMs: number;
6
+ }
7
+ /**
8
+ * Gather every candidate lock file (top-level + refs/**) with its age.
9
+ * Locks that vanish between discovery and stat are simply dropped (a racing
10
+ * delete is fine to ignore). Never throws.
11
+ */
12
+ export declare function findLockCandidates(gitDir: string, now: number): Promise<LockCandidate[]>;
13
+ /**
14
+ * Delete every stale lock under `gitDir` — but ONLY when every candidate is
15
+ * stale (one fresh lock defers the whole sweep; a live process may hold it).
16
+ *
17
+ * Returns "clean" (nothing found), "swept" (all removed), or "fresh"
18
+ * (deferred — the caller should retry later).
19
+ */
20
+ export declare function sweepStaleLocks(gitDir: string, now?: number, minAgeMs?: number): Promise<"clean" | "swept" | "fresh">;
@@ -0,0 +1,29 @@
1
+ import type { HostCredential, TokenStore } from "../token-store.ts";
2
+ export interface RepairOptions {
3
+ projectDir: string;
4
+ /** Explicit credential; wins over the token store (nuclear re-clone only). */
5
+ credential?: HostCredential;
6
+ /** Host-keyed store used to resolve the credential for the remote's host. */
7
+ tokenStore?: TokenStore;
8
+ authorName?: string;
9
+ authorEmail?: string;
10
+ /** Operation log (same file sync writes to). */
11
+ logFile?: string;
12
+ }
13
+ export interface RepairResult {
14
+ status: "repaired" | "retry_later" | "failed";
15
+ /** One author-language sentence describing the outcome. */
16
+ message: string;
17
+ /** Author-language lines describing what was done (for the log/details). */
18
+ actions: string[];
19
+ /** Present when the re-clone ran: on-disk backup of the old `.git`. */
20
+ damagedGitBackupPath?: string;
21
+ /** Suggested delay before retrying (present on "retry_later"). */
22
+ retryAfterMs?: number;
23
+ }
24
+ /**
25
+ * Repair a damaged repository. See the module header for the pipeline and
26
+ * invariants. Never throws for expected outcomes — everything is reported
27
+ * through {@link RepairResult}.
28
+ */
29
+ export declare function repairRepo(options: RepairOptions): Promise<RepairResult>;
@@ -1,131 +1,11 @@
1
1
  /**
2
- * Shared types and dispatcher contract for the sync-recovery subsystem.
2
+ * Shared types for the repo health probe + repair pipeline.
3
3
  *
4
- * Every recover-<x>.ts handler imports from here. This module contains ZERO
5
- * logic — it is a pure type surface. The dispatcher (dispatch.ts) maps a
6
- * SyncErrorKind to its handler and is wired in the Integrate phase.
7
- *
8
- * Author-facing copy rules: no git words, no tokens, no internal paths visible
9
- * to users. Every user-visible string lives in manual-guidance.ts.
10
- */
11
- import type httpNode from "isomorphic-git/http/node";
12
- import type { ProjectSource } from "../../project-source.ts";
13
- import type { ConflictFile } from "../sync.ts";
14
- import type { HostCredential, TokenStore } from "../token-store.ts";
15
- /**
16
- * Every failure mode the recovery subsystem can handle. The classifier
17
- * (classify.ts) maps a thrown error + repo health facts to one of these.
18
- */
19
- export type SyncErrorKind = "non_fast_forward" | "merge_conflict" | "binary_conflict" | "auth_required" | "network_unavailable" | "insecure_transport" | "detached_head" | "stale_lock" | "corrupt_index" | "missing_git_dir" | "missing_or_corrupt_objects" | "unrelated_histories" | "wrong_remote_or_branch" | "interrupted_rebase" | "interrupted_cherry_pick" | "interrupted_merge" | "unknown";
20
- /**
21
- * Machine token for the primary CTA in the guidance dialog. The host switches
22
- * on this to route the button to the right flow. The human-readable label the
23
- * button shows lives in `ManualGuidance.recommendedAction` (never this token).
24
- */
25
- export type RecoveryActionKey = "sync" | "reconnect" | "resolve_conflict" | "restore_repo" | "check_connection";
26
- /**
27
- * How much irreversible change a repair makes. The confirmation gate shows
28
- * a plain-language summary appropriate to the risk level.
29
- */
30
- export type RecoveryRisk = "none" | "low" | "medium" | "high";
31
- /**
32
- * Jargon-free guidance the host shows when a repair is blocked or fails.
33
- * No git words, no tokens, no internal paths.
34
- */
35
- export interface ManualGuidance {
36
- /** One-sentence plain-language summary of what went wrong. */
37
- userSummary: string;
38
- /** The single most important action the user should take next. */
39
- recommendedNextStep: string;
40
- /** Action label for the primary button in the UI. */
41
- recommendedAction: string;
42
- /** Machine token the host switches on to route the primary button's action. */
43
- recommendedActionKey: RecoveryActionKey;
44
- /** Optional secondary steps (shown as a list). */
45
- safeNextSteps?: string[];
46
- /** Details for a support ticket — may contain technical info. */
47
- supportDetails?: string;
48
- /** Path to the backup zip, when one was created before the failure. */
49
- backupZipPath?: string;
50
- }
51
- /** The backup zip created before a risky repair. */
52
- export interface RecoveryBackup {
53
- /** Absolute path to the zip file under os.tmpdir()/print-sync-recovery/. */
54
- zipPath: string;
55
- /** ISO timestamp the backup was created. */
56
- createdAt: string;
57
- /** Files included in the backup (relative paths). */
58
- entries: string[];
59
- }
60
- /**
61
- * What a confirmation gate shows the user before a risky repair.
62
- * The UI must render this information — never silently start a risky repair.
63
- */
64
- export interface RepairConfirmation {
65
- repair: SyncErrorKind;
66
- risk: RecoveryRisk;
67
- /** Plain-language summary of what the repair will do. */
68
- summary: string;
69
- /** Path to the backup zip (already created and verified). */
70
- backupZipPath: string;
71
- willChangeLocalFiles: boolean;
72
- willChangeGitMetadata: boolean;
73
- willChangeRemote: boolean;
74
- /** True when the backup alone is enough to undo the repair. */
75
- canBeUndoneFromBackup: boolean;
76
- }
77
- /** Gate the host must satisfy: show the user what will happen and ask. */
78
- export interface ConfirmationGate {
79
- confirmRepair(req: RepairConfirmation): Promise<boolean>;
80
- }
81
- /**
82
- * Outcome of a recovery attempt. Each status maps to a host-side action:
83
- * - recovered → refresh the project; show a success toast.
84
- * - retry_later → schedule a retry after retryAfterMs; show a soft message.
85
- * - needs_user → show the conflict chooser / reconnect dialog.
86
- * - blocked → show guidance and a "what do I do?" panel.
87
- * - failed_no_changes_made → show guidance; nothing was changed locally or remotely.
88
- * - failed_backup_available → show guidance + "open backup folder" button.
89
- */
90
- export type RecoveryResult = {
91
- status: "recovered";
92
- message: string;
93
- backupZipPath?: string;
94
- } | {
95
- status: "retry_later";
96
- message: string;
97
- retryAfterMs: number;
98
- } | {
99
- status: "needs_user";
100
- message: string;
101
- guidance: ManualGuidance;
102
- files?: ConflictFile[];
103
- backupZipPath?: string;
104
- /**
105
- * Conflict tip OIDs threaded through to the host so it can call
106
- * resolveConflicts with the correct local/remote versions after the user
107
- * chooses. Present on the per-file conflict-chooser paths only.
108
- */
109
- localId?: string;
110
- remoteId?: string;
111
- } | {
112
- status: "blocked";
113
- message: string;
114
- guidance: ManualGuidance;
115
- backupZipPath?: string;
116
- } | {
117
- status: "failed_no_changes_made";
118
- message: string;
119
- guidance: ManualGuidance;
120
- } | {
121
- status: "failed_backup_available";
122
- message: string;
123
- backupZipPath: string;
124
- guidance: ManualGuidance;
125
- };
126
- /**
127
- * Preflight health snapshot of a local repo. Populated by inspectRepo()
128
- * (inspect.ts) before any recovery decision is made.
4
+ * 2026-08-14 simplification (owner directive): the old handler-dispatch type
5
+ * surface (SyncErrorKind taxonomy, RecoveryContext, RecoveryResult,
6
+ * ManualGuidance, confirmation gates, fault injection) is gone — repair is
7
+ * one automatic pipeline (repair.ts) and needs none of it. What remains is
8
+ * the health snapshot inspect.ts produces.
129
9
  */
130
10
  export interface RepoHealth {
131
11
  /** True when .git/ exists and looks like a valid git dir. */
@@ -137,15 +17,13 @@ export interface RepoHealth {
137
17
  /**
138
18
  * True when HEAD (or the ref store) could not even be READ — distinct from
139
19
  * a clean detached HEAD, where HEAD resolves fine but points at a commit
140
- * instead of a branch. This means the repo's ref store or HEAD file is
141
- * missing/corrupt, and routes to `missing_or_corrupt_objects` instead of
142
- * `detached_head` (see classifyFromHealth).
20
+ * instead of a branch. Routes to the re-clone step of repairRepo.
143
21
  */
144
22
  headUnreadable: boolean;
145
23
  /**
146
24
  * True when a leftover git lock file exists (a previous operation may have
147
25
  * died). Detects any of index.lock, HEAD.lock, config.lock, packed-refs.lock,
148
- * or a per-ref refs/**\/*.lock — the same set the stale-lock handler scans.
26
+ * or a per-ref refs/**\/*.lock — the same set the lock sweep scans.
149
27
  */
150
28
  hasStaleLock: boolean;
151
29
  /** How old the YOUNGEST detected lock file is in milliseconds, when present. */
@@ -159,77 +37,3 @@ export interface RepoHealth {
159
37
  /** True when the working tree has uncommitted changes. */
160
38
  hasLocalChanges: boolean;
161
39
  }
162
- /**
163
- * Named fault injection points every risky repair MUST call via
164
- * `ctx.faults?.before(point)`. Throwing from this hook simulates a failure
165
- * at that exact point so failsafe invariants can be asserted.
166
- */
167
- export type FaultPoint = "backup_create" | "backup_verify" | "after_backup_before_repair" | "fetch" | "merge" | "checkout_branch" | "abort_interrupted_operation" | "remove_operation_state" | "create_recovery_branch" | "commit_recovery_snapshot" | "remove_index_lock" | "remove_index" | "rebuild_index" | "clone_temp_repo" | "replace_git_dir" | "push" | "write_conflict_snapshot";
168
- export interface FaultInjector {
169
- before(point: FaultPoint): Promise<void>;
170
- }
171
- /**
172
- * Everything a recover-<x>.ts handler needs. Extends SyncProjectOptions with
173
- * the repo-level facts that the dispatcher resolves once and passes down.
174
- */
175
- export interface RecoveryContext {
176
- /** The project directory the user opened (may be a repo subfolder). */
177
- projectDir: string;
178
- /** Absolute path to the git repository root. */
179
- repoDir: string;
180
- /**
181
- * The projectDir's classification, resolved ONCE by buildRecoveryContext and
182
- * threaded to every consumer (inspectRepo, diagnoseProjectRemote) so the
183
- * recovery path never re-walks parent dirs to re-classify the same folder.
184
- * `null` records that classification failed; omitted means "not resolved" —
185
- * consumers classify themselves in both cases.
186
- */
187
- source?: ProjectSource | null;
188
- /** Current branch name (may be empty on detached HEAD). */
189
- branch: string;
190
- /** Sanitized HTTPS remote URL (no embedded credentials). */
191
- remoteUrl?: string;
192
- /** Short identifier for the repo used in backup paths. */
193
- repoSlug: string;
194
- /** Resolved credential for the remote host. */
195
- credential?: HostCredential;
196
- /** Host-keyed credential store (for clearing stale tokens). */
197
- tokenStore?: TokenStore;
198
- /** Injectable HTTP transport (real or test mock). */
199
- httpClient?: typeof httpNode;
200
- /** The user's display name for snapshot commits. */
201
- authorName?: string;
202
- /** The user's email for snapshot commits. Paired with `authorName` — a
203
- * recovery commit must carry the SAME identity a normal snapshot does. */
204
- authorEmail?: string;
205
- /** Gate the host must satisfy before a risky repair starts. */
206
- confirmation: ConfirmationGate;
207
- /** Fault injection hooks (tests only — omit in production). */
208
- faults?: FaultInjector;
209
- /** Clock override (tests only). Returns epoch ms. */
210
- now?: () => number;
211
- /**
212
- * Optional path to a log file for debugging. When set, each recovery step
213
- * (backup, fetch, merge, checkout, etc.) is appended as a timestamped line.
214
- * Never logs secrets — only repo slug, branch, short OIDs, outcome status.
215
- */
216
- logFile?: string;
217
- }
218
- /**
219
- * The signature every recover-<x>.ts module MUST export.
220
- * The dispatcher (dispatch.ts) maps SyncErrorKind → RecoverFn.
221
- */
222
- export type RecoverFn = (ctx: RecoveryContext, error?: unknown) => Promise<RecoveryResult>;
223
- /**
224
- * Optional per-kind precondition probe a recover-<x>.ts module MAY export as
225
- * `stillApplies`. The dispatcher calls it — for serializeRepo:true kinds only,
226
- * INSIDE withRepoLock, immediately before invoking the handler body — to catch
227
- * the case where the condition that justified this kind was already resolved
228
- * (externally, or by a previous attempt) between classification and dispatch.
229
- * Returning false means there is nothing left to repair; the dispatcher
230
- * returns a benign no-op RecoveryResult without running the handler at all.
231
- * Must be read-only and throw-free from the caller's perspective (probes
232
- * should catch their own errors and resolve `true` — "assume it still
233
- * applies" — rather than reject, so a probe failure never blocks recovery).
234
- */
235
- export type StillAppliesFn = (ctx: RecoveryContext) => Promise<boolean>;
@@ -11,8 +11,8 @@ export declare const MSG_SYNCED_MERGED = "Your changes are online, combined with
11
11
  export declare const MSG_OFFLINE = "Your changes are saved on this computer. gutterpress couldn't reach the online repository \u2014 try syncing again when you're back online.";
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
- export declare const MSG_RACE = "Someone else synced changes at the same moment. Your work is saved on this computer \u2014 please try Sync again.";
15
- export declare const MSG_CONFLICT = "Your copy and the online copy both changed. Choose which version to keep for each file \u2014 a safety snapshot of your work was taken first.";
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.";
16
16
  export declare const MSG_NO_REMOTE = "This project isn't connected to an online repository yet.";
17
17
  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
18
  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.";
@@ -21,6 +21,5 @@ export declare const MSG_PULLED_MERGED = "The latest online changes were combine
21
21
  export declare const MSG_PULL_UP_TO_DATE = "You already have the latest online changes.";
22
22
  export declare const MSG_PUSH_UP_TO_DATE = "There's nothing new to send \u2014 everything is already online.";
23
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
- export declare const MSG_EXPIRED_CHOICES = "Those combine choices have expired. Please run Sync again.";
25
24
  /** Message recorded on the automatic pre-sync snapshot (D5 invariant). */
26
25
  export declare const SYNC_SNAPSHOT_MESSAGE = "Snapshot before syncing";