@fuzdev/fuz_gitops 0.78.0 → 0.79.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 (196) hide show
  1. package/README.md +91 -17
  2. package/dist/ModulesDetail.svelte +22 -27
  3. package/dist/ModulesDetail.svelte.d.ts.map +1 -1
  4. package/dist/ModulesNav.svelte +8 -5
  5. package/dist/ModulesNav.svelte.d.ts.map +1 -1
  6. package/dist/ModulesPage.svelte +3 -5
  7. package/dist/ModulesPage.svelte.d.ts.map +1 -1
  8. package/dist/PageFooter.svelte +2 -2
  9. package/dist/PageHeader.svelte +6 -6
  10. package/dist/PageHeader.svelte.d.ts +0 -3
  11. package/dist/PageHeader.svelte.d.ts.map +1 -1
  12. package/dist/PullRequestsDetail.svelte +29 -20
  13. package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
  14. package/dist/PullRequestsPage.svelte +4 -6
  15. package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
  16. package/dist/ReposTable.svelte +43 -55
  17. package/dist/ReposTable.svelte.d.ts.map +1 -1
  18. package/dist/ReposTree.svelte +9 -21
  19. package/dist/ReposTree.svelte.d.ts.map +1 -1
  20. package/dist/ReposTreeNav.svelte +9 -12
  21. package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
  22. package/dist/TablePage.svelte +3 -5
  23. package/dist/TablePage.svelte.d.ts.map +1 -1
  24. package/dist/TreeItemPage.svelte +3 -5
  25. package/dist/TreeItemPage.svelte.d.ts.map +1 -1
  26. package/dist/TreePage.svelte +3 -5
  27. package/dist/TreePage.svelte.d.ts.map +1 -1
  28. package/dist/changeset_generator.d.ts.map +1 -1
  29. package/dist/changeset_generator.js +2 -2
  30. package/dist/changeset_reader.d.ts.map +1 -1
  31. package/dist/changeset_reader.js +3 -3
  32. package/dist/ci_reconcile.d.ts +6 -10
  33. package/dist/ci_reconcile.d.ts.map +1 -1
  34. package/dist/ci_reconcile.js +6 -8
  35. package/dist/dependency_graph.d.ts +26 -49
  36. package/dist/dependency_graph.d.ts.map +1 -1
  37. package/dist/dependency_graph.js +40 -83
  38. package/dist/dependency_updater.d.ts +1 -1
  39. package/dist/dependency_updater.d.ts.map +1 -1
  40. package/dist/dependency_updater.js +14 -9
  41. package/dist/fetch_repo_data.d.ts +34 -8
  42. package/dist/fetch_repo_data.d.ts.map +1 -1
  43. package/dist/fetch_repo_data.js +44 -22
  44. package/dist/fs_fetch_value_cache.d.ts +1 -1
  45. package/dist/fs_fetch_value_cache.d.ts.map +1 -1
  46. package/dist/fs_fetch_value_cache.js +3 -3
  47. package/dist/git_operations.d.ts +6 -49
  48. package/dist/git_operations.d.ts.map +1 -1
  49. package/dist/git_operations.js +13 -126
  50. package/dist/github.d.ts +17 -2
  51. package/dist/github.d.ts.map +1 -1
  52. package/dist/github.js +17 -8
  53. package/dist/github_helpers.d.ts.map +1 -1
  54. package/dist/github_helpers.js +3 -11
  55. package/dist/gitops_analyze.task.d.ts +24 -2
  56. package/dist/gitops_analyze.task.d.ts.map +1 -1
  57. package/dist/gitops_analyze.task.js +47 -34
  58. package/dist/gitops_config.d.ts +34 -79
  59. package/dist/gitops_config.d.ts.map +1 -1
  60. package/dist/gitops_config.js +76 -53
  61. package/dist/gitops_constants.d.ts +6 -4
  62. package/dist/gitops_constants.d.ts.map +1 -1
  63. package/dist/gitops_constants.js +6 -4
  64. package/dist/gitops_plan.task.d.ts +29 -3
  65. package/dist/gitops_plan.task.d.ts.map +1 -1
  66. package/dist/gitops_plan.task.js +83 -61
  67. package/dist/gitops_publish.task.d.ts +62 -2
  68. package/dist/gitops_publish.task.d.ts.map +1 -1
  69. package/dist/gitops_publish.task.js +193 -105
  70. package/dist/gitops_run.task.d.ts +74 -0
  71. package/dist/gitops_run.task.d.ts.map +1 -1
  72. package/dist/gitops_run.task.js +176 -162
  73. package/dist/gitops_sync.task.d.ts +65 -3
  74. package/dist/gitops_sync.task.d.ts.map +1 -1
  75. package/dist/gitops_sync.task.js +163 -37
  76. package/dist/gitops_task_helpers.d.ts +86 -59
  77. package/dist/gitops_task_helpers.d.ts.map +1 -1
  78. package/dist/gitops_task_helpers.js +120 -79
  79. package/dist/gitops_validate.task.d.ts +1 -2
  80. package/dist/gitops_validate.task.d.ts.map +1 -1
  81. package/dist/gitops_validate.task.js +25 -32
  82. package/dist/graph_validation.d.ts +16 -27
  83. package/dist/graph_validation.d.ts.map +1 -1
  84. package/dist/graph_validation.js +24 -65
  85. package/dist/local_repo.d.ts +50 -62
  86. package/dist/local_repo.d.ts.map +1 -1
  87. package/dist/local_repo.js +83 -196
  88. package/dist/log_helpers.d.ts +8 -8
  89. package/dist/log_helpers.d.ts.map +1 -1
  90. package/dist/multi_repo_publisher.d.ts +10 -0
  91. package/dist/multi_repo_publisher.d.ts.map +1 -1
  92. package/dist/multi_repo_publisher.js +125 -61
  93. package/dist/npm_registry.d.ts +23 -19
  94. package/dist/npm_registry.d.ts.map +1 -1
  95. package/dist/npm_registry.js +21 -48
  96. package/dist/operations.d.ts +75 -180
  97. package/dist/operations.d.ts.map +1 -1
  98. package/dist/operations.js +15 -7
  99. package/dist/operations_defaults.d.ts +17 -2
  100. package/dist/operations_defaults.d.ts.map +1 -1
  101. package/dist/operations_defaults.js +111 -128
  102. package/dist/output_helpers.d.ts +36 -0
  103. package/dist/output_helpers.d.ts.map +1 -1
  104. package/dist/output_helpers.js +37 -3
  105. package/dist/preflight_checks.d.ts +20 -27
  106. package/dist/preflight_checks.d.ts.map +1 -1
  107. package/dist/preflight_checks.js +37 -126
  108. package/dist/publish_gate.d.ts.map +1 -1
  109. package/dist/publish_steps.d.ts +2 -4
  110. package/dist/publish_steps.d.ts.map +1 -1
  111. package/dist/publish_steps.js +4 -8
  112. package/dist/publishing_event.d.ts +2 -8
  113. package/dist/publishing_event.d.ts.map +1 -1
  114. package/dist/publishing_event.js +20 -17
  115. package/dist/publishing_event_handler.d.ts +10 -8
  116. package/dist/publishing_event_handler.d.ts.map +1 -1
  117. package/dist/publishing_event_handler.js +19 -17
  118. package/dist/publishing_plan.d.ts +16 -1
  119. package/dist/publishing_plan.d.ts.map +1 -1
  120. package/dist/publishing_plan.js +332 -254
  121. package/dist/publishing_plan_helpers.d.ts.map +1 -1
  122. package/dist/publishing_plan_helpers.js +3 -3
  123. package/dist/publishing_plan_logging.d.ts +1 -1
  124. package/dist/publishing_plan_logging.d.ts.map +1 -1
  125. package/dist/publishing_plan_logging.js +22 -15
  126. package/dist/repo.svelte.d.ts +7 -0
  127. package/dist/repo.svelte.d.ts.map +1 -1
  128. package/dist/repo.svelte.js +3 -0
  129. package/dist/repo_readiness.d.ts +210 -0
  130. package/dist/repo_readiness.d.ts.map +1 -0
  131. package/dist/repo_readiness.js +516 -0
  132. package/dist/repos_status.d.ts +3340 -0
  133. package/dist/repos_status.d.ts.map +1 -0
  134. package/dist/repos_status.js +555 -0
  135. package/dist/repos_status_load.d.ts +65 -0
  136. package/dist/repos_status_load.d.ts.map +1 -0
  137. package/dist/repos_status_load.js +131 -0
  138. package/dist/version_utils.js +1 -1
  139. package/package.json +15 -34
  140. package/src/lib/cargo_toml.ts +3 -3
  141. package/src/lib/changeset_generator.ts +19 -19
  142. package/src/lib/changeset_reader.ts +18 -18
  143. package/src/lib/ci_reconcile.ts +12 -16
  144. package/src/lib/dependency_graph.ts +61 -113
  145. package/src/lib/dependency_updater.ts +36 -30
  146. package/src/lib/fetch_repo_data.ts +71 -34
  147. package/src/lib/fs_fetch_value_cache.ts +15 -11
  148. package/src/lib/git_operations.ts +22 -194
  149. package/src/lib/github.ts +33 -21
  150. package/src/lib/github_helpers.ts +9 -16
  151. package/src/lib/gitops_analyze.task.ts +104 -66
  152. package/src/lib/gitops_config.ts +90 -139
  153. package/src/lib/gitops_constants.ts +6 -4
  154. package/src/lib/gitops_plan.task.ts +128 -76
  155. package/src/lib/gitops_publish.task.ts +280 -144
  156. package/src/lib/gitops_run.task.ts +265 -193
  157. package/src/lib/gitops_sync.task.ts +236 -52
  158. package/src/lib/gitops_task_helpers.ts +170 -114
  159. package/src/lib/gitops_validate.task.ts +44 -54
  160. package/src/lib/graph_validation.ts +28 -89
  161. package/src/lib/local_repo.ts +128 -335
  162. package/src/lib/log_helpers.ts +18 -28
  163. package/src/lib/multi_repo_publisher.ts +192 -110
  164. package/src/lib/npm_registry.ts +42 -67
  165. package/src/lib/operations.ts +99 -160
  166. package/src/lib/operations_defaults.ts +157 -201
  167. package/src/lib/output_helpers.ts +58 -6
  168. package/src/lib/preflight_checks.ts +52 -188
  169. package/src/lib/publish_gate.ts +7 -9
  170. package/src/lib/publish_steps.ts +27 -25
  171. package/src/lib/publishing_event.ts +26 -23
  172. package/src/lib/publishing_event_handler.ts +24 -22
  173. package/src/lib/publishing_plan.ts +474 -310
  174. package/src/lib/publishing_plan_helpers.ts +16 -12
  175. package/src/lib/publishing_plan_logging.ts +53 -44
  176. package/src/lib/repo.svelte.ts +16 -8
  177. package/src/lib/repo_readiness.ts +647 -0
  178. package/src/lib/repos_status.ts +671 -0
  179. package/src/lib/repos_status_load.ts +173 -0
  180. package/src/lib/version_utils.ts +3 -3
  181. package/dist/config_reconcile.d.ts +0 -46
  182. package/dist/config_reconcile.d.ts.map +0 -1
  183. package/dist/config_reconcile.js +0 -60
  184. package/dist/paths.d.ts +0 -7
  185. package/dist/paths.d.ts.map +0 -1
  186. package/dist/paths.js +0 -6
  187. package/dist/repo_ops.d.ts +0 -59
  188. package/dist/repo_ops.d.ts.map +0 -1
  189. package/dist/repo_ops.js +0 -170
  190. package/dist/resolved_gitops_config.d.ts +0 -9
  191. package/dist/resolved_gitops_config.d.ts.map +0 -1
  192. package/dist/resolved_gitops_config.js +0 -12
  193. package/src/lib/config_reconcile.ts +0 -94
  194. package/src/lib/paths.ts +0 -6
  195. package/src/lib/repo_ops.ts +0 -216
  196. package/src/lib/resolved_gitops_config.ts +0 -27
@@ -0,0 +1,671 @@
1
+ /**
2
+ * Zod schemas for the `repos status --json` document, the report the Rust
3
+ * `repos` tool (`crates/fuz_repos`) prints, and the error document it prints
4
+ * in place of the report on a fatal error.
5
+ *
6
+ * The schemas mirror the Rust types by hand, each named as its Rust type
7
+ * prefixed `Repos` (`EntryStatus` is `ReposEntryStatus`), with fields as
8
+ * serde writes them: snake_case, a tagged enum as an object whose `kind`
9
+ * names its variant, an `Option` as `null` (never omitted), counts as
10
+ * integers, and times as unix seconds. A flattened enum's `kind`
11
+ * and payload sit beside its struct's own fields, so those structs are
12
+ * unions here (`ReposUnregisteredClone`, `ReposStatusErrorBody`) — or, for
13
+ * an enum with no payloads, an object whose `kind` is an enum
14
+ * (`ReposProbeError`). Where a
15
+ * Rust enum holds variants the status document never carries, the mirror
16
+ * leaves them out: `ReposFetchFailure` has no push rejection, and
17
+ * `ReposStatusErrorBody` holds only the errors `repos status --json` can
18
+ * print.
19
+ *
20
+ * Every object is strict and every union closed, so a field or variant the
21
+ * mirror doesn't know fails the parse. The Rust side bumps `version` on any
22
+ * change to the document's shape; a consumer checks it first, and on a
23
+ * mismatch reports both versions and how to install the binary this repo
24
+ * builds (`cargo install --path crates/fuz_repos --locked`) rather than a
25
+ * parse error.
26
+ *
27
+ * Drift is caught by the golden fixtures: a Rust test writes every status
28
+ * document shape to `src/test/fixtures/repos_status/`, covering every
29
+ * variant the status report and its error document can hold, and a TS test
30
+ * parses each with these schemas. The field semantics live in the Rust
31
+ * rustdoc and the JSON contract in `docs/repos.md`. The `repos sync --json`
32
+ * and `repos push --json` documents have no TS consumer and aren't
33
+ * mirrored here.
34
+ *
35
+ * @module
36
+ */
37
+
38
+ import { z } from 'zod';
39
+
40
+ /**
41
+ * The `repos status --json` document's format version, Rust's
42
+ * `STATUS_FORMAT_VERSION`: the one these schemas parse.
43
+ */
44
+ export const REPOS_STATUS_FORMAT_VERSION = 18;
45
+
46
+ // u32 and u64 on the Rust side
47
+ const Count = z.number().int().nonnegative();
48
+ const UnixSeconds = z.number().int().nonnegative().meta({ description: 'a time in unix seconds' });
49
+
50
+ // --- sessions ---
51
+
52
+ /** Where a live Claude Code session was recorded. */
53
+ export const ReposSessionSource = z.enum(['session_file', 'roster_worker']);
54
+ export type ReposSessionSource = z.infer<typeof ReposSessionSource>;
55
+
56
+ /** A live Claude Code session: its process, and where it works. */
57
+ export const ReposSession = z.strictObject({
58
+ pid: Count,
59
+ cwd: z.string(),
60
+ worktree: z.string().nullable(),
61
+ process_cwd: z.string().nullable(),
62
+ source: ReposSessionSource
63
+ });
64
+ export type ReposSession = z.infer<typeof ReposSession>;
65
+
66
+ /** Why busy detection can't vouch for every live session. */
67
+ export const ReposUnavailable = z.discriminatedUnion('kind', [
68
+ z.strictObject({ kind: z.literal('home_unknown') }),
69
+ z.strictObject({ kind: z.literal('relative_config_dir'), path: z.string() }),
70
+ z.strictObject({ kind: z.literal('unreadable'), path: z.string(), error: z.string() }),
71
+ z.strictObject({ kind: z.literal('unparseable'), path: z.string(), error: z.string() }),
72
+ z.strictObject({
73
+ kind: z.literal('foreign_pid_domain'),
74
+ path: z.string(),
75
+ pid_domain: z.string(),
76
+ source: ReposSessionSource
77
+ })
78
+ ]);
79
+ export type ReposUnavailable = z.infer<typeof ReposUnavailable>;
80
+
81
+ /** Busy detection as the report carries it. */
82
+ export const ReposSessions = z.discriminatedUnion('kind', [
83
+ z.strictObject({ kind: z.literal('available'), unscoped: z.array(ReposSession) }),
84
+ z.strictObject({ kind: z.literal('unavailable'), reason: ReposUnavailable })
85
+ ]);
86
+ export type ReposSessions = z.infer<typeof ReposSessions>;
87
+
88
+ // --- registry ---
89
+
90
+ /** Which registry table an entry comes from. */
91
+ export const ReposEntryKind = z.enum(['repo', 'reference']);
92
+ export type ReposEntryKind = z.infer<typeof ReposEntryKind>;
93
+
94
+ /** A repo's declared visibility on its host. */
95
+ export const ReposVisibility = z.enum(['public', 'private']);
96
+ export type ReposVisibility = z.infer<typeof ReposVisibility>;
97
+
98
+ /** A repo's list of the sibling checkouts it uses. */
99
+ export const ReposCheckoutList = z.enum(['requires', 'consults']);
100
+ export type ReposCheckoutList = z.infer<typeof ReposCheckoutList>;
101
+
102
+ /** An entry by table and key. */
103
+ export const ReposEntryName = z.strictObject({ kind: ReposEntryKind, key: z.string() });
104
+ export type ReposEntryName = z.infer<typeof ReposEntryName>;
105
+
106
+ /** A registry integrity rule broken. */
107
+ export const ReposRegistryIssue = z.discriminatedUnion('kind', [
108
+ z.strictObject({ kind: z.literal('repo_not_owned'), key: z.string(), account: z.string() }),
109
+ z.strictObject({ kind: z.literal('fork_not_owned'), key: z.string() }),
110
+ z.strictObject({ kind: z.literal('dir_not_a_name'), entry: ReposEntryName, dir: z.string() }),
111
+ z.strictObject({
112
+ kind: z.literal('dir_claimed_twice'),
113
+ dir: z.string(),
114
+ first: ReposEntryName,
115
+ second: ReposEntryName
116
+ }),
117
+ z.strictObject({ kind: z.literal('key_in_both'), key: z.string() }),
118
+ z.strictObject({ kind: z.literal('key_is_other_dir'), key: z.string(), entry: ReposEntryName }),
119
+ z.strictObject({
120
+ kind: z.literal('unknown_checkout_ref'),
121
+ key: z.string(),
122
+ field: ReposCheckoutList,
123
+ target: z.string()
124
+ }),
125
+ z.strictObject({ kind: z.literal('self_ref'), key: z.string(), field: ReposCheckoutList }),
126
+ z.strictObject({ kind: z.literal('requires_and_consults'), key: z.string(), target: z.string() })
127
+ ]);
128
+ export type ReposRegistryIssue = z.infer<typeof ReposRegistryIssue>;
129
+
130
+ // --- verdicts ---
131
+
132
+ /** What holds a branch's action back. */
133
+ export const ReposBranchHold = z.enum([
134
+ 'pinned',
135
+ 'entry',
136
+ 'push_url',
137
+ 'fetch_failed',
138
+ 'dirty_checkout',
139
+ 'unprobed_worktree',
140
+ 'several_checkouts',
141
+ 'busy',
142
+ 'busy_unknown'
143
+ ]);
144
+ export type ReposBranchHold = z.infer<typeof ReposBranchHold>;
145
+
146
+ /** What holds a reference's refresh back. */
147
+ export const ReposRefreshHold = z.enum(['pinned', 'entry', 'origin_not_https']);
148
+ export type ReposRefreshHold = z.infer<typeof ReposRefreshHold>;
149
+
150
+ /** What holds a missing entry's clone back. */
151
+ export const ReposCloneHold = z.enum(['entry', 'busy', 'unprobed_worktree']);
152
+ export type ReposCloneHold = z.infer<typeof ReposCloneHold>;
153
+
154
+ /** What a run does about refreshing a third-party reference or a pin. */
155
+ export const ReposRefreshVerdict = z.discriminatedUnion('kind', [
156
+ z.strictObject({ kind: z.literal('act') }),
157
+ z.strictObject({ kind: z.literal('held'), by: ReposRefreshHold })
158
+ ]);
159
+ export type ReposRefreshVerdict = z.infer<typeof ReposRefreshVerdict>;
160
+
161
+ /** Whether a repo is at the entry's dir. */
162
+ export const ReposPresence = z.discriminatedUnion('kind', [
163
+ z.strictObject({ kind: z.literal('present') }),
164
+ z.strictObject({ kind: z.literal('missing') }),
165
+ z.strictObject({ kind: z.literal('not_a_repo') })
166
+ ]);
167
+ export type ReposPresence = z.infer<typeof ReposPresence>;
168
+
169
+ /** How sync would clone a missing entry. */
170
+ export const ReposCloneRecipe = z.strictObject({
171
+ url: z.string(),
172
+ branch: z.string().nullable(),
173
+ shallow: z.boolean(),
174
+ sparse: z.string().nullable()
175
+ });
176
+ export type ReposCloneRecipe = z.infer<typeof ReposCloneRecipe>;
177
+
178
+ /** What sync does about a missing entry: clone it, or why not yet. */
179
+ export const ReposCloneVerdict = z.discriminatedUnion('kind', [
180
+ z.strictObject({ kind: z.literal('act'), recipe: ReposCloneRecipe }),
181
+ z.strictObject({ kind: z.literal('held'), recipe: ReposCloneRecipe, by: ReposCloneHold })
182
+ ]);
183
+ export type ReposCloneVerdict = z.infer<typeof ReposCloneVerdict>;
184
+
185
+ /** A sync action on a branch. */
186
+ export const ReposSyncAction = z.discriminatedUnion('kind', [
187
+ z.strictObject({ kind: z.literal('push'), commits: Count }),
188
+ z.strictObject({ kind: z.literal('fast_forward'), commits: Count }),
189
+ z.strictObject({ kind: z.literal('move') })
190
+ ]);
191
+ export type ReposSyncAction = z.infer<typeof ReposSyncAction>;
192
+
193
+ /** Why a branch is left to a person. */
194
+ export const ReposBranchNeedsHuman = z.enum([
195
+ 'diverged',
196
+ 'unmapped',
197
+ 'archived_ahead',
198
+ 'shallow_local_work',
199
+ 'upstream_not_a_branch'
200
+ ]);
201
+ export type ReposBranchNeedsHuman = z.infer<typeof ReposBranchNeedsHuman>;
202
+
203
+ /** Why a branch reads as cleanup. */
204
+ export const ReposCleanupReason = z.enum(['merged', 'upstream_gone']);
205
+ export type ReposCleanupReason = z.infer<typeof ReposCleanupReason>;
206
+
207
+ /** What sync does about a branch. */
208
+ export const ReposVerdict = z.discriminatedUnion('kind', [
209
+ z.strictObject({ kind: z.literal('quiet') }),
210
+ z.strictObject({ kind: z.literal('act'), action: ReposSyncAction }),
211
+ z.strictObject({ kind: z.literal('held'), action: ReposSyncAction, by: ReposBranchHold }),
212
+ z.strictObject({ kind: z.literal('needs_human'), reason: ReposBranchNeedsHuman }),
213
+ z.strictObject({ kind: z.literal('local_only') }),
214
+ z.strictObject({
215
+ kind: z.literal('cleanup'),
216
+ reason: ReposCleanupReason,
217
+ removable_worktree: z.string().nullable()
218
+ })
219
+ ]);
220
+ export type ReposVerdict = z.infer<typeof ReposVerdict>;
221
+
222
+ // --- checkouts and branches ---
223
+
224
+ /** A clone's shape: shallow, sparse, or partial. */
225
+ export const ReposLayout = z.strictObject({
226
+ shallow: z.boolean(),
227
+ sparse: z.boolean(),
228
+ partial_filter: z.string().nullable()
229
+ });
230
+ export type ReposLayout = z.infer<typeof ReposLayout>;
231
+
232
+ /**
233
+ * A checkout's HEAD: a probed checkout's, an unprobed worktree's, or an
234
+ * unlisted git dir's — the last two `null` when it can't be read.
235
+ */
236
+ export const ReposHead = z.discriminatedUnion('kind', [
237
+ z.strictObject({ kind: z.literal('branch'), name: z.string() }),
238
+ z.strictObject({ kind: z.literal('detached'), commit: z.string() })
239
+ ]);
240
+ export type ReposHead = z.infer<typeof ReposHead>;
241
+
242
+ /** A checkout's uncommitted work, by kind. */
243
+ export const ReposUncommitted = z.strictObject({
244
+ staged: Count,
245
+ unstaged: Count,
246
+ untracked: Count,
247
+ conflicted: Count
248
+ });
249
+ export type ReposUncommitted = z.infer<typeof ReposUncommitted>;
250
+
251
+ /** A git operation in progress. */
252
+ export const ReposInProgressOp = z.enum([
253
+ 'rebase',
254
+ 'merge',
255
+ 'cherry_pick',
256
+ 'revert',
257
+ 'bisect',
258
+ 'sequencer',
259
+ 'am'
260
+ ]);
261
+ export type ReposInProgressOp = z.infer<typeof ReposInProgressOp>;
262
+
263
+ /** One checkout of a repo: its primary, or a linked worktree probed. */
264
+ export const ReposCheckout = z.strictObject({
265
+ path: z.string(),
266
+ primary: z.boolean(),
267
+ head: ReposHead,
268
+ uncommitted: ReposUncommitted,
269
+ in_progress: ReposInProgressOp.nullable(),
270
+ locked: z.boolean(),
271
+ linked: z.boolean(),
272
+ submodules: z.boolean().nullable(),
273
+ busy: z.array(ReposSession)
274
+ });
275
+ export type ReposCheckout = z.infer<typeof ReposCheckout>;
276
+
277
+ /** A branch's relation to its upstream. */
278
+ export const ReposRelation = z.discriminatedUnion('kind', [
279
+ z.strictObject({ kind: z.literal('in_sync') }),
280
+ z.strictObject({ kind: z.literal('ahead'), commits: Count }),
281
+ z.strictObject({ kind: z.literal('behind'), commits: Count }),
282
+ z.strictObject({ kind: z.literal('diverged'), ahead: Count, behind: Count }),
283
+ z.strictObject({ kind: z.literal('shallow') }),
284
+ z.strictObject({ kind: z.literal('gone') }),
285
+ z.strictObject({ kind: z.literal('unmapped') }),
286
+ z.strictObject({ kind: z.literal('untracked') })
287
+ ]);
288
+ export type ReposRelation = z.infer<typeof ReposRelation>;
289
+
290
+ /** One local branch and its verdict. */
291
+ export const ReposBranchStatus = z.strictObject({
292
+ name: z.string(),
293
+ upstream: z.string().nullable(),
294
+ worktree: z.string().nullable(),
295
+ symref: z.string().nullable(),
296
+ unique_commits: Count,
297
+ newest_commit_at: UnixSeconds,
298
+ relation: ReposRelation,
299
+ verdict: ReposVerdict
300
+ });
301
+ export type ReposBranchStatus = z.infer<typeof ReposBranchStatus>;
302
+
303
+ /** Whether an entry's primary checkout sits where the registry puts it. */
304
+ export const ReposAtRest = z.strictObject({
305
+ on_branch: z.boolean().nullable(),
306
+ clean: z.boolean(),
307
+ idle: z.boolean(),
308
+ followed: ReposRelation.nullable()
309
+ });
310
+ export type ReposAtRest = z.infer<typeof ReposAtRest>;
311
+
312
+ // --- worktrees not probed ---
313
+
314
+ /** Why a worktree wasn't probed. */
315
+ export const ReposUnprobedWhy = z.discriminatedUnion('kind', [
316
+ z.strictObject({ kind: z.literal('prunable') }),
317
+ z.strictObject({ kind: z.literal('missing') }),
318
+ z.strictObject({ kind: z.literal('failed'), error: z.string() })
319
+ ]);
320
+ export type ReposUnprobedWhy = z.infer<typeof ReposUnprobedWhy>;
321
+
322
+ /** What an unprobed worktree's git dir holds that a prune would lose. */
323
+ export const ReposGitDirHolds = z.strictObject({
324
+ submodules: z.boolean(),
325
+ worktree_refs: z.boolean(),
326
+ staged: z.boolean().nullable()
327
+ });
328
+ export type ReposGitDirHolds = z.infer<typeof ReposGitDirHolds>;
329
+
330
+ /** What pruning an unprobed worktree would lose. */
331
+ export const ReposPruneLoss = z.discriminatedUnion('kind', [
332
+ z.strictObject({ kind: z.literal('operation'), op: ReposInProgressOp }),
333
+ z.strictObject({ kind: z.literal('detached_head') }),
334
+ z.strictObject({ kind: z.literal('unknown_head') }),
335
+ z.strictObject({ kind: z.literal('missing_branch'), name: z.string() }),
336
+ z.strictObject({ kind: z.literal('submodules') }),
337
+ z.strictObject({ kind: z.literal('worktree_refs') }),
338
+ z.strictObject({ kind: z.literal('staged_changes') }),
339
+ z.strictObject({ kind: z.literal('unmatched_git_dir') }),
340
+ z.strictObject({ kind: z.literal('relative_gitdir'), git_dir: z.string() })
341
+ ]);
342
+ export type ReposPruneLoss = z.infer<typeof ReposPruneLoss>;
343
+
344
+ /** Whether pruning an unprobed worktree is safe. */
345
+ export const ReposPrune = z.discriminatedUnion('kind', [
346
+ z.strictObject({ kind: z.literal('safe') }),
347
+ z.strictObject({ kind: z.literal('loses'), losses: z.array(ReposPruneLoss) }),
348
+ z.strictObject({ kind: z.literal('moved'), to: z.array(z.string()) })
349
+ ]);
350
+ export type ReposPrune = z.infer<typeof ReposPrune>;
351
+
352
+ /** A worktree of the repo that couldn't be probed. */
353
+ export const ReposUnprobedWorktree = z.strictObject({
354
+ path: z.string(),
355
+ git_dir: z.string().nullable(),
356
+ head: ReposHead.nullable().meta({ description: 'null when its HEAD cannot be read' }),
357
+ locked: z.boolean(),
358
+ in_progress: ReposInProgressOp.nullable(),
359
+ why: ReposUnprobedWhy,
360
+ holds: ReposGitDirHolds.nullable()
361
+ });
362
+ export type ReposUnprobedWorktree = z.infer<typeof ReposUnprobedWorktree>;
363
+
364
+ /** An unprobed worktree (flattened) with its prune verdict and live sessions. */
365
+ export const ReposUnprobedWorktreeStatus = ReposUnprobedWorktree.extend({
366
+ prune: ReposPrune.nullable(),
367
+ busy: z.array(ReposSession)
368
+ });
369
+ export type ReposUnprobedWorktreeStatus = z.infer<typeof ReposUnprobedWorktreeStatus>;
370
+
371
+ // --- remotes ---
372
+
373
+ /** The `origin` remote as git sees it, when it isn't the registry's `url`. */
374
+ export const ReposOriginRemote = z.discriminatedUnion('kind', [
375
+ z.strictObject({ kind: z.literal('url'), url: z.string() }),
376
+ z.strictObject({ kind: z.literal('no_url') }),
377
+ z.strictObject({ kind: z.literal('missing') })
378
+ ]);
379
+ export type ReposOriginRemote = z.infer<typeof ReposOriginRemote>;
380
+
381
+ /** Why no `git remote` command fits an origin's fix. */
382
+ export const ReposOriginByHand = z.enum([
383
+ 'outside_repo_file',
384
+ 'valueless_url',
385
+ 'empty_value',
386
+ 'several_urls'
387
+ ]);
388
+ export type ReposOriginByHand = z.infer<typeof ReposOriginByHand>;
389
+
390
+ /** How to point `origin` at the registry's URL. */
391
+ export const ReposOriginFix = z.discriminatedUnion('kind', [
392
+ z.strictObject({ kind: z.literal('add') }),
393
+ z.strictObject({ kind: z.literal('set_url') }),
394
+ z.strictObject({ kind: z.literal('by_hand'), reason: ReposOriginByHand })
395
+ ]);
396
+ export type ReposOriginFix = z.infer<typeof ReposOriginFix>;
397
+
398
+ /** Why a remote couldn't be reached. */
399
+ export const ReposUnreachableCause = z.enum(['dns', 'connection', 'host_key', 'auth']);
400
+ export type ReposUnreachableCause = z.infer<typeof ReposUnreachableCause>;
401
+
402
+ /** How to repair a fetch refspec naming a ref gone from origin. */
403
+ export const ReposRefGoneFix = z.discriminatedUnion('kind', [
404
+ z.strictObject({ kind: z.literal('unset_refspec'), pattern: z.string() }),
405
+ z.strictObject({ kind: z.literal('set_branches'), branch: z.string().nullable() }),
406
+ z.strictObject({ kind: z.literal('by_hand') })
407
+ ]);
408
+ export type ReposRefGoneFix = z.infer<typeof ReposRefGoneFix>;
409
+
410
+ /**
411
+ * Why a fetch or the visibility check failed, or why the tool refused to
412
+ * fetch: Rust's `RemoteFailure` without `rejected`, which only a push meets.
413
+ */
414
+ export const ReposFetchFailure = z.discriminatedUnion('kind', [
415
+ z.strictObject({ kind: z.literal('ref_gone'), refname: z.string(), fix: ReposRefGoneFix }),
416
+ z.strictObject({
417
+ kind: z.literal('unreachable'),
418
+ cause: ReposUnreachableCause,
419
+ message: z.string()
420
+ }),
421
+ z.strictObject({ kind: z.literal('repo_not_found'), message: z.string() }),
422
+ z.strictObject({
423
+ kind: z.literal('timed_out'),
424
+ after_secs: Count.meta({ description: "the runner's timeout in seconds" })
425
+ }),
426
+ z.strictObject({ kind: z.literal('failed'), message: z.string() }),
427
+ z.strictObject({ kind: z.literal('refspec_outside_origin'), refspec: z.string() }),
428
+ z.strictObject({
429
+ kind: z.literal('origin_refs_shared'),
430
+ remote: z.string(),
431
+ refspec: z.string()
432
+ }),
433
+ z.strictObject({ kind: z.literal('legacy_remotes_unreadable'), path: z.string() })
434
+ ]);
435
+ export type ReposFetchFailure = z.infer<typeof ReposFetchFailure>;
436
+
437
+ /** What an anonymous read of a repo declared private found. */
438
+ export const ReposVisibilityCheck = z.discriminatedUnion('kind', [
439
+ z.strictObject({ kind: z.literal('leak') }),
440
+ z.strictObject({ kind: z.literal('private') }),
441
+ z.strictObject({ kind: z.literal('unknown'), failure: ReposFetchFailure })
442
+ ]);
443
+ export type ReposVisibilityCheck = z.infer<typeof ReposVisibilityCheck>;
444
+
445
+ // --- entries ---
446
+
447
+ /** What kind of failure stopped an entry's probe. */
448
+ export const ReposProbeErrorKind = z.enum([
449
+ 'path_unreadable',
450
+ 'non_utf8_path',
451
+ 'config_unreadable',
452
+ 'fetch_url_unreadable',
453
+ 'push_urls_unreadable',
454
+ 'git_not_run',
455
+ 'git_timed_out',
456
+ 'git_failed',
457
+ 'unexpected_output'
458
+ ]);
459
+ export type ReposProbeErrorKind = z.infer<typeof ReposProbeErrorKind>;
460
+
461
+ /** Why an entry's probe failed: its kind, and a message for display. */
462
+ export const ReposProbeError = z.strictObject({
463
+ kind: ReposProbeErrorKind,
464
+ message: z.string()
465
+ });
466
+ export type ReposProbeError = z.infer<typeof ReposProbeError>;
467
+
468
+ /** Why sync would stop on an entry and leave it to a person. */
469
+ export const ReposNeedsHuman = z.discriminatedUnion('kind', [
470
+ z.strictObject({ kind: z.literal('not_a_repo'), detail: z.string() }),
471
+ z.strictObject({
472
+ kind: z.literal('operation_in_progress'),
473
+ checkout: z.string(),
474
+ op: ReposInProgressOp
475
+ }),
476
+ z.strictObject({
477
+ kind: z.literal('origin_mismatch'),
478
+ origin: ReposOriginRemote,
479
+ expected: z.string(),
480
+ fix: ReposOriginFix
481
+ }),
482
+ z.strictObject({
483
+ kind: z.literal('origin_not_https'),
484
+ fetch_url: z.string(),
485
+ expected: z.string(),
486
+ fix: ReposOriginFix.nullable()
487
+ }),
488
+ z.strictObject({
489
+ kind: z.literal('fetch_url_mismatch'),
490
+ fetch_url: z.string(),
491
+ expected: z.string(),
492
+ fix: ReposOriginFix.nullable()
493
+ }),
494
+ z.strictObject({ kind: z.literal('worktree_unreadable'), path: z.string() }),
495
+ z.strictObject({ kind: z.literal('default_branch_missing'), branch: z.string() }),
496
+ z.strictObject({ kind: z.literal('default_branch_no_upstream'), branch: z.string() }),
497
+ z.strictObject({ kind: z.literal('default_branch_gone'), branch: z.string() }),
498
+ z.strictObject({ kind: z.literal('unexpected_detached'), checkout: z.string() }),
499
+ z.strictObject({
500
+ kind: z.literal('checkout_unresolvable'),
501
+ checkout: z.string(),
502
+ path: z.string(),
503
+ error: z.string()
504
+ }),
505
+ z.strictObject({
506
+ kind: z.literal('unlisted_git_dir'),
507
+ git_dir: z.string(),
508
+ head: ReposHead.nullable().meta({ description: 'null when its HEAD cannot be read' }),
509
+ busy: z.array(ReposSession)
510
+ }),
511
+ z.strictObject({
512
+ kind: z.literal('push_url_mismatch'),
513
+ push_urls: z.array(z.string()),
514
+ expected: z.string()
515
+ }),
516
+ z.strictObject({ kind: z.literal('clone_shares_repo'), with: z.string() }),
517
+ z.strictObject({ kind: z.literal('cloned_unregistered'), dir: z.string() })
518
+ ]);
519
+ export type ReposNeedsHuman = z.infer<typeof ReposNeedsHuman>;
520
+
521
+ /** One registry entry's state. */
522
+ export const ReposEntryStatus = z.strictObject({
523
+ key: z.string(),
524
+ kind: ReposEntryKind,
525
+ dir: z.string(),
526
+ url: z.string(),
527
+ writable: z.boolean(),
528
+ archived: z.boolean(),
529
+ visibility: ReposVisibility.nullable(),
530
+ ci: z.boolean(),
531
+ branch: z.string().nullable(),
532
+ pinned: z.boolean(),
533
+ refresh: ReposRefreshVerdict.nullable(),
534
+ presence: ReposPresence,
535
+ clone: ReposCloneVerdict.nullable(),
536
+ layout: ReposLayout.nullable(),
537
+ checkouts: z.array(ReposCheckout),
538
+ branches: z.array(ReposBranchStatus),
539
+ at_rest: ReposAtRest.nullable(),
540
+ stashes: Count,
541
+ fetched_at: UnixSeconds.nullable(),
542
+ needs_human: z.array(ReposNeedsHuman),
543
+ probe_error: ReposProbeError.nullable(),
544
+ unprobed_worktrees: z.array(ReposUnprobedWorktreeStatus),
545
+ fetch_error: ReposFetchFailure.nullable(),
546
+ visibility_check: ReposVisibilityCheck.nullable()
547
+ });
548
+ export type ReposEntryStatus = z.infer<typeof ReposEntryStatus>;
549
+
550
+ // --- unregistered clones ---
551
+
552
+ /** What keeps a moved worktree's repair from being offered. */
553
+ export const ReposRepairBlock = z.discriminatedUnion('kind', [
554
+ z.strictObject({ kind: z.literal('rewrites'), path: z.string(), git_dir: z.string() }),
555
+ z.strictObject({ kind: z.literal('claimed_dir'), git_dir: z.string() }),
556
+ z.strictObject({ kind: z.literal('swapped'), git_dir: z.string(), with: z.string() }),
557
+ z.strictObject({ kind: z.literal('relative_gitdir'), git_dir: z.string() }),
558
+ z.strictObject({ kind: z.literal('unreadable_gitdir'), git_dir: z.string() }),
559
+ z.strictObject({ kind: z.literal('non_utf8_path') }),
560
+ z.strictObject({ kind: z.literal('nul_in_gitdir'), git_dir: z.string() })
561
+ ]);
562
+ export type ReposRepairBlock = z.infer<typeof ReposRepairBlock>;
563
+
564
+ // `UnregisteredClone`'s own fields, beside its flattened `UnregisteredKind`
565
+ const unregistered_clone_fields = {
566
+ dir: z.string(),
567
+ origin: z.string().nullable(),
568
+ owned: z.boolean()
569
+ };
570
+
571
+ /**
572
+ * A child of the workspace root holding a `.git` that no registry entry
573
+ * claims, its `kind` flattened beside its own fields.
574
+ */
575
+ export const ReposUnregisteredClone = z.discriminatedUnion('kind', [
576
+ z.strictObject({ ...unregistered_clone_fields, kind: z.literal('clone') }),
577
+ z.strictObject({ ...unregistered_clone_fields, kind: z.literal('worktree') }),
578
+ z.strictObject({
579
+ ...unregistered_clone_fields,
580
+ kind: z.literal('moved_worktree'),
581
+ entry: z.string(),
582
+ blocked_by: ReposRepairBlock.nullable(),
583
+ exit_noise: z.string().nullable()
584
+ }),
585
+ z.strictObject({
586
+ ...unregistered_clone_fields,
587
+ kind: z.literal('orphaned_worktree'),
588
+ entry: z.string()
589
+ }),
590
+ z.strictObject({
591
+ ...unregistered_clone_fields,
592
+ kind: z.literal('shared_git_dir'),
593
+ entry: z.string(),
594
+ with: z.string().nullable()
595
+ }),
596
+ z.strictObject({ ...unregistered_clone_fields, kind: z.literal('unfinished_clone') })
597
+ ]);
598
+ export type ReposUnregisteredClone = z.infer<typeof ReposUnregisteredClone>;
599
+
600
+ // --- the documents ---
601
+
602
+ /** The `repos status --json` report. */
603
+ export const ReposStatusReport = z.strictObject({
604
+ version: z.literal(REPOS_STATUS_FORMAT_VERSION),
605
+ workspace: z.string(),
606
+ registry: z.string(),
607
+ fetched: z.boolean(),
608
+ sessions: ReposSessions,
609
+ entries: z.array(ReposEntryStatus),
610
+ unregistered: z
611
+ .array(ReposUnregisteredClone)
612
+ .nullable()
613
+ .meta({ description: 'null when the scan did not run (a run with targets)' })
614
+ });
615
+ export type ReposStatusReport = z.infer<typeof ReposStatusReport>;
616
+
617
+ // `ErrorBody`'s own fields, beside its flattened `ErrorKind`
618
+ const error_body_fields = {
619
+ message: z.string(),
620
+ hint: z.string().nullable()
621
+ };
622
+
623
+ /**
624
+ * A fatal error as `repos status --json` prints it, its `kind` flattened
625
+ * beside `message` and `hint`. Only the kinds `status` can print: the rest
626
+ * of Rust's `ErrorKind` are `repos push`'s, or precede knowing `--json`.
627
+ */
628
+ export const ReposStatusErrorBody = z.discriminatedUnion('kind', [
629
+ z.strictObject({ ...error_body_fields, kind: z.literal('references_with_targets') }),
630
+ z.strictObject({ ...error_body_fields, kind: z.literal('root_not_found') }),
631
+ z.strictObject({ ...error_body_fields, kind: z.literal('registry_not_found') }),
632
+ z.strictObject({ ...error_body_fields, kind: z.literal('root_in_entry'), key: z.string() }),
633
+ z.strictObject({ ...error_body_fields, kind: z.literal('registry_read') }),
634
+ z.strictObject({ ...error_body_fields, kind: z.literal('registry_parse') }),
635
+ z.strictObject({
636
+ ...error_body_fields,
637
+ kind: z.literal('registry_invalid'),
638
+ issues: z.array(ReposRegistryIssue)
639
+ }),
640
+ z.strictObject({ ...error_body_fields, kind: z.literal('git_not_found') }),
641
+ z.strictObject({
642
+ ...error_body_fields,
643
+ kind: z.literal('git_too_old'),
644
+ found: z.string(),
645
+ required: z.string()
646
+ }),
647
+ z.strictObject({
648
+ ...error_body_fields,
649
+ kind: z.literal('unknown_entry'),
650
+ name: z.string(),
651
+ suggestions: z.array(z.string())
652
+ }),
653
+ z.strictObject({ ...error_body_fields, kind: z.literal('io') })
654
+ ]);
655
+ export type ReposStatusErrorBody = z.infer<typeof ReposStatusErrorBody>;
656
+
657
+ /** The document `repos status --json` prints on a fatal error, in place of the report. */
658
+ export const ReposStatusErrorReport = z.strictObject({
659
+ version: z.literal(REPOS_STATUS_FORMAT_VERSION),
660
+ error: ReposStatusErrorBody
661
+ });
662
+ export type ReposStatusErrorReport = z.infer<typeof ReposStatusErrorReport>;
663
+
664
+ /**
665
+ * Either document `repos status --json` prints. No tag is needed: `error`
666
+ * is the error document's alone, and each side's strictness refuses the
667
+ * other's fields. A consumer wanting a sharper parse error branches on
668
+ * `error` first and parses with that side's schema.
669
+ */
670
+ export const ReposStatusDocument = z.union([ReposStatusReport, ReposStatusErrorReport]);
671
+ export type ReposStatusDocument = z.infer<typeof ReposStatusDocument>;