@edspencer/paddock 0.71.2 → 0.72.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.
- package/package.json +1 -1
- package/packages/server/dist/app.js +27 -0
- package/packages/server/dist/cli/args.d.ts +56 -5
- package/packages/server/dist/cli/args.js +193 -31
- package/packages/server/dist/cli/config.d.ts +157 -0
- package/packages/server/dist/cli/config.js +462 -0
- package/packages/server/dist/cli/paddock.js +53 -3
- package/packages/server/dist/config.d.ts +36 -1
- package/packages/server/dist/config.js +59 -44
- package/packages/server/dist/herdctl.d.ts +45 -0
- package/packages/server/dist/herdctl.js +58 -4
- package/packages/server/dist/instance-config.d.ts +149 -21
- package/packages/server/dist/instance-config.js +228 -5
- package/packages/server/dist/last-activity.d.ts +21 -0
- package/packages/server/dist/last-activity.js +7 -2
- package/packages/server/dist/profiles.d.ts +154 -0
- package/packages/server/dist/profiles.js +214 -0
- package/packages/server/dist/routes/transcripts.d.ts +61 -0
- package/packages/server/dist/routes/transcripts.js +676 -0
- package/packages/server/dist/routes.d.ts +3 -0
- package/packages/server/dist/routes.js +2 -0
- package/packages/server/dist/transcript-move.d.ts +62 -0
- package/packages/server/dist/transcript-move.js +179 -0
- package/packages/server/dist/transcripts-migration-execute.d.ts +144 -0
- package/packages/server/dist/transcripts-migration-execute.js +630 -0
- package/packages/server/dist/transcripts-migration.d.ts +336 -0
- package/packages/server/dist/transcripts-migration.js +917 -0
- package/packages/server/dist/transcripts.d.ts +46 -2
- package/packages/server/dist/transcripts.js +126 -3
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/DiscoverPage-DhrXvgkl.js +2 -0
- package/packages/web/dist/assets/DiscoverView-yPLCp_YE.js +2 -0
- package/packages/web/dist/assets/InstanceConfigPage-aOgiBFLq.js +2 -0
- package/packages/web/dist/assets/{MarkdownRenderer-DA5CpdGI.js → MarkdownRenderer-BjrB0TDO.js} +1 -1
- package/packages/web/dist/assets/ProjectMenu-PhwXTmgn.js +2 -0
- package/packages/web/dist/assets/{ProjectRedirect-k10uM_63.js → ProjectRedirect-N4g7WrZm.js} +1 -1
- package/packages/web/dist/assets/ProjectView-B-7AIt--.js +13 -0
- package/packages/web/dist/assets/{ProjectsGrid-DrzZ7R8M.js → ProjectsGrid-DZF5VHJF.js} +1 -1
- package/packages/web/dist/assets/RootHome-C9wEtWHu.js +2 -0
- package/packages/web/dist/assets/Surfaces-BidCkENw.js +2 -0
- package/packages/web/dist/assets/{arc-BKAVFR26.js → arc-BctfAqLY.js} +1 -1
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-BJgwSqOO.js +37 -0
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-Eu4BV7Ea.js → blockDiagram-GPEHLZMM-BD2skmST.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DR8PZaoa.js → c4Diagram-AAUBKEIU-DckMrhMv.js} +1 -1
- package/packages/web/dist/assets/channel-CsZOz8jm.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-CgvIIrI1.js → chunk-2J33WTMH-CQDUH7rp.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-CyJWrZqN.js → chunk-4BX2VUAB-Bae1J961.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-DXEnZN1d.js → chunk-55IACEB6-CXxS5wOa.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-Co89QTmp.js → chunk-727SXJPM-DSNFkPB3.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BRxTzfev.js → chunk-AQP2D5EJ-B2mqFyB7.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-DUj18CvF.js → chunk-FMBD7UC4-BfqEy3lu.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-BQMMJobN.js → chunk-ND2GUHAM-Bm6CBVK7.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-COx8LZCe.js → chunk-QZHKN3VN-NmnLtgYc.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DdALkhE9.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DdALkhE9.js +2 -0
- package/packages/web/dist/assets/{core-DnpcT0s6.js → core-YKeFSK1q.js} +1 -1
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-qf_mXpa9.js +2 -0
- package/packages/web/dist/assets/{dagre-BM42HDAG-CuaYJexd.js → dagre-BM42HDAG-C31v-j3s.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-C9_anxrA.js → diagram-2AECGRRQ-C_TiPHdS.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-Ckf3kFlY.js → diagram-5GNKFQAL-BC8gUFK-.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-BrpUuY5x.js → diagram-KO2AKTUF-vO_QYWxi.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-B9lyXZXW.js → diagram-LMA3HP47-vxJ-YZOg.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DT7ro32m.js → diagram-OG6HWLK6-BNIfJG6l.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-ZZn9bHQh.js → erDiagram-TEJ5UH35-B37HsOFM.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-BxQ82PY_.js → flowDiagram-I6XJVG4X-DqA5X81H.js} +1 -1
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BMP0zpIg.js +293 -0
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-BlwODdH9.js → gitGraphDiagram-PVQCEYII-P0nXaCzx.js} +1 -1
- package/packages/web/dist/assets/index-BkY_Nc_i.css +1 -0
- package/packages/web/dist/assets/index-C3MdluUb.js +71 -0
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-nanSBFyG.js → infoDiagram-5YYISTIA-DpKYQ7VK.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-yZHr_s28.js → ishikawaDiagram-YF4QCWOH-BZeRMg3C.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-BKjDqQEQ.js → journeyDiagram-JHISSGLW-Cg2O0qzv.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-QusvHqc6.js → kanban-definition-UN3LZRKU-DBwVxUvI.js} +1 -1
- package/packages/web/dist/assets/{linear-4xRoD38Z.js → linear-R8qRwYqx.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-CeulYvDv.js → mermaid.core-jctTs2en.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-DSjYt2Kp.js → mindmap-definition-RKZ34NQL-CtmsNfpb.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-Ci0gO3CA.js → pieDiagram-4H26LBE5-C-UiLFDv.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-y2qHkaHP.js → quadrantDiagram-W4KKPZXB-BS4SWa01.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-D9hiWbad.js → requirementDiagram-4Y6WPE33-LPPAC6Q1.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DA3Mvoze.js → sankeyDiagram-5OEKKPKP-tjF8KyS3.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DWACrljw.js → sequenceDiagram-3UESZ5HK-kmddkdk2.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-CFGEhoTb.js → stateDiagram-AJRCARHV-B07mykTY.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-r6WdX0dw.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-Cjygpd9e.js → timeline-definition-PNZ67QCA-DrUgi0cT.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-zQnLaxgv.js → vennDiagram-CIIHVFJN-Dz6p0m7l.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-DGmt-Lo2.js → wardley-L42UT6IY-DM16lA5x.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-Dj3vDukN.js → wardleyDiagram-YWT4CUSO-CCFQkFGB.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-C0bGbwbL.js → xychartDiagram-2RQKCTM6-DiUI5Dos.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/Chip-Cp32dDmz.js +0 -2
- package/packages/web/dist/assets/DiscoverPage-Cs439wTf.js +0 -2
- package/packages/web/dist/assets/DiscoverView-DoGAlUXT.js +0 -2
- package/packages/web/dist/assets/InstanceConfigPage-BDypeo_r.js +0 -2
- package/packages/web/dist/assets/ProjectMenu-DXXXtXeh.js +0 -2
- package/packages/web/dist/assets/ProjectView-BxjcMlSO.js +0 -13
- package/packages/web/dist/assets/RootHome-rWodzOC8.js +0 -2
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-UzIlxaCQ.js +0 -37
- package/packages/web/dist/assets/channel-C-sodtn7.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-D2eBPVdq.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-D2eBPVdq.js +0 -2
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-B-4NadKI.js +0 -2
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BxliloOH.js +0 -293
- package/packages/web/dist/assets/index-Cx9y6c3f.js +0 -71
- package/packages/web/dist/assets/index-bC5uCujx.css +0 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CLfbDXLX.js +0 -2
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { type TranscriptsMode } from "./transcripts.js";
|
|
2
|
+
/**
|
|
3
|
+
* The three-state classification the modal's table renders, plus the budget
|
|
4
|
+
* escape hatch.
|
|
5
|
+
*
|
|
6
|
+
* `unknown` is not a fourth kind of conflict — it is "we stopped looking", and
|
|
7
|
+
* it is treated as `diverged` (default unchecked) because that is the
|
|
8
|
+
* conservative direction.
|
|
9
|
+
*/
|
|
10
|
+
export type MigrationState = "new" | "fast-forward" | "diverged" | "unknown";
|
|
11
|
+
/** Why the banner is not offered. */
|
|
12
|
+
export type MigrationIneligibleReason = "already-host" | "env-shadowed" | "profile-paranoid" | "nothing-pending" | "scan-failed";
|
|
13
|
+
/** Non-fatal conditions the modal should surface. */
|
|
14
|
+
export type MigrationWarningCode = "host-store-unreadable" | "chats-dir-unreadable" | "env-shadowed" | "memory-collision" | "unexpected-entries";
|
|
15
|
+
/** The env var that shadows `claude.transcripts` — see `config.ts:1481`. */
|
|
16
|
+
export declare const TRANSCRIPTS_ENV_VAR = "PADDOCK_CLAUDE_TRANSCRIPTS";
|
|
17
|
+
/**
|
|
18
|
+
* Where unchecked chats are preserved: a SIBLING of `.chats/`, not a child.
|
|
19
|
+
*
|
|
20
|
+
* #882 specified `.chats/pre-migration/`, which cannot work — that directory is
|
|
21
|
+
* "anything", so `pointChatsDirAt` declines the redirect and the migration
|
|
22
|
+
* built to fix #708 ships #708's symptom. Design §5.1 verified it against the
|
|
23
|
+
* real `ensureProjectChats`. Named here because the plan reports the path and
|
|
24
|
+
* the execute half must use the same one.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PRESERVE_DIR_NAME = ".chats-pre-migration";
|
|
27
|
+
/**
|
|
28
|
+
* Bytes stage 3 (and the diverged rows' comparison columns) may read across one
|
|
29
|
+
* whole request. The worst case is a *product* of chat count and transcript
|
|
30
|
+
* size and neither is under our control, so it is bounded rather than trusted.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DIVERGENCE_SCAN_BUDGET_BYTES: number;
|
|
33
|
+
/** One copy of a chat, for a diverged row's comparison columns. */
|
|
34
|
+
export interface MigrationSide {
|
|
35
|
+
/** Absolute path of this copy. */
|
|
36
|
+
path: string;
|
|
37
|
+
sizeBytes: number;
|
|
38
|
+
/** Filesystem mtime. Present always; NOT a proxy for activity (#863). */
|
|
39
|
+
mtime: string;
|
|
40
|
+
/**
|
|
41
|
+
* Conversation records — `user`/`assistant`, excluding meta and
|
|
42
|
+
* task-notification records (the `last-activity.ts` rule, shared not copied).
|
|
43
|
+
*
|
|
44
|
+
* Present only where a decision needs it: on `diverged` rows, and only while
|
|
45
|
+
* the scan budget lasts. Counting it requires a full parse of the file, so
|
|
46
|
+
* populating it on every row would cost a complete read of every transcript
|
|
47
|
+
* and make the plan endpoint's realistic case ~60× its measured cost. See the
|
|
48
|
+
* PR body for why this deviates from the design's `required` list.
|
|
49
|
+
*/
|
|
50
|
+
messageCount?: number;
|
|
51
|
+
/** ISO 8601 timestamp of the last real message; absent when none is datable. */
|
|
52
|
+
lastMessageAt?: string;
|
|
53
|
+
}
|
|
54
|
+
/** One row of the modal's table.
|
|
55
|
+
*
|
|
56
|
+
* `identical` is deliberately NOT a {@link MigrationState}: the plan omits
|
|
57
|
+
* identical chats from its rows entirely, because there is no decision for the
|
|
58
|
+
* user to make about one, and widening the published enum would put a value in
|
|
59
|
+
* the OpenAPI contract that the table can never render. The EXECUTE half still
|
|
60
|
+
* needs to know — an identical chat has a copy on both sides and therefore has
|
|
61
|
+
* a disposition (host's survives, Paddock's is preserved) — so the scan carries
|
|
62
|
+
* it as a flag on the row and {@link buildMigrationPlan} filters those rows out.
|
|
63
|
+
*/
|
|
64
|
+
export interface MigrationChatRow {
|
|
65
|
+
sessionId: string;
|
|
66
|
+
/** The chat's set name, else its auto-name. Absent when neither exists. */
|
|
67
|
+
name?: string;
|
|
68
|
+
/** First user message, truncated. Absent when unreadable. */
|
|
69
|
+
preview?: string;
|
|
70
|
+
state: MigrationState;
|
|
71
|
+
/** How the checkbox starts: true for `new`/`fast-forward`. */
|
|
72
|
+
defaultSelected: boolean;
|
|
73
|
+
/** The copy in `.chats/`. Always present. */
|
|
74
|
+
own: MigrationSide;
|
|
75
|
+
/** The copy in the host store. Absent if and only if state is `new`. */
|
|
76
|
+
host?: MigrationSide;
|
|
77
|
+
/** For `fast-forward`, which side is the descendant and will survive. */
|
|
78
|
+
ahead?: "own" | "host";
|
|
79
|
+
/** Sidecars that move with this chat: `<id>/…` and `.reverts/<id>-*.jsonl`. */
|
|
80
|
+
extras: string[];
|
|
81
|
+
/**
|
|
82
|
+
* Byte-identical on both sides. Internal to the scan — stripped from the
|
|
83
|
+
* plan's rows and counted in `totals.identical` instead. See the doc above.
|
|
84
|
+
*/
|
|
85
|
+
identical?: boolean;
|
|
86
|
+
}
|
|
87
|
+
/** One project's slice of the plan. */
|
|
88
|
+
export interface MigrationProjectPlan {
|
|
89
|
+
/** Project slug; the empty string is the root workspace. */
|
|
90
|
+
slug: string;
|
|
91
|
+
name: string;
|
|
92
|
+
chatsDir: string;
|
|
93
|
+
hostStore: string;
|
|
94
|
+
preserveDir: string;
|
|
95
|
+
chats: MigrationChatRow[];
|
|
96
|
+
/**
|
|
97
|
+
* Entries of `.chats/` that move with the PROJECT rather than with any one
|
|
98
|
+
* chat — `memory/`, flat `agent-<hex>.jsonl` sidechain transcripts, an
|
|
99
|
+
* orphaned `<id>/` with no transcript.
|
|
100
|
+
*
|
|
101
|
+
* Not in the design's schema. Added because the postcondition moves them and
|
|
102
|
+
* the standing rule is that nothing moves unannounced: without this the
|
|
103
|
+
* completion summary could not say "and 132 memory files", which is the
|
|
104
|
+
* single sharpest item in the design's own inventory (§5).
|
|
105
|
+
*/
|
|
106
|
+
projectExtras: string[];
|
|
107
|
+
}
|
|
108
|
+
export interface MigrationWarning {
|
|
109
|
+
code: MigrationWarningCode;
|
|
110
|
+
/** The project it applies to. Absent for instance-wide warnings. */
|
|
111
|
+
slug?: string;
|
|
112
|
+
message: string;
|
|
113
|
+
paths?: string[];
|
|
114
|
+
}
|
|
115
|
+
export interface MigrationTotals {
|
|
116
|
+
chats: number;
|
|
117
|
+
new: number;
|
|
118
|
+
fastForward: number;
|
|
119
|
+
diverged: number;
|
|
120
|
+
unknown: number;
|
|
121
|
+
/** Identical on both sides, omitted from the rows. Reported so a count lower
|
|
122
|
+
* than the user's chat total always has an explanation. */
|
|
123
|
+
identical: number;
|
|
124
|
+
defaultSelected: number;
|
|
125
|
+
}
|
|
126
|
+
export interface MigrationPlan {
|
|
127
|
+
mode: TranscriptsMode;
|
|
128
|
+
configPath: string;
|
|
129
|
+
configVersion: string | null;
|
|
130
|
+
projects: MigrationProjectPlan[];
|
|
131
|
+
sweepers: {
|
|
132
|
+
stores: number;
|
|
133
|
+
chats: number;
|
|
134
|
+
};
|
|
135
|
+
totals: MigrationTotals;
|
|
136
|
+
scanBudgetExhausted: boolean;
|
|
137
|
+
warnings: MigrationWarning[];
|
|
138
|
+
}
|
|
139
|
+
export interface MigrationProbe {
|
|
140
|
+
mode: TranscriptsMode;
|
|
141
|
+
eligible: boolean;
|
|
142
|
+
reason?: MigrationIneligibleReason;
|
|
143
|
+
envVar?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Transcripts that would migrate. A LOWER BOUND by contract — see
|
|
146
|
+
* {@link probeMigration} for why this implementation can afford to make it
|
|
147
|
+
* exact, and for the one case where a pending project reports 0.
|
|
148
|
+
*/
|
|
149
|
+
pendingChats: number;
|
|
150
|
+
pendingProjects: number;
|
|
151
|
+
scannedProjects: number;
|
|
152
|
+
computedAt: string;
|
|
153
|
+
}
|
|
154
|
+
/** The subset of a project this module needs — kept minimal so it is testable
|
|
155
|
+
* against plain fixtures rather than a whole `ProjectStore`. */
|
|
156
|
+
export interface MigrationProjectRef {
|
|
157
|
+
slug: string;
|
|
158
|
+
name: string;
|
|
159
|
+
/** The METADATA dir — where `.chats/` lives. */
|
|
160
|
+
dir: string;
|
|
161
|
+
/** The agent's cwd — what the host store's encoded name is built from. NOT
|
|
162
|
+
* `dir` for a repo-backed or linked project (#187/#206). */
|
|
163
|
+
workingDir: string;
|
|
164
|
+
}
|
|
165
|
+
export interface MigrationInput {
|
|
166
|
+
/** The transcripts mode this process resolved at boot. */
|
|
167
|
+
mode: TranscriptsMode;
|
|
168
|
+
/** The resolved posture profile — `paranoid` suppresses the banner (§10.4). */
|
|
169
|
+
profile: string;
|
|
170
|
+
/** True when `PADDOCK_CLAUDE_TRANSCRIPTS` is set: the config write is inert. */
|
|
171
|
+
envShadowed: boolean;
|
|
172
|
+
projects: MigrationProjectRef[];
|
|
173
|
+
/** The USER's own `~/.claude` — the destination store's root. */
|
|
174
|
+
userHome: string;
|
|
175
|
+
/** Absolute path of the file the POST would write to. */
|
|
176
|
+
configPath: string;
|
|
177
|
+
/** Fingerprint of that file as read for this response. */
|
|
178
|
+
configVersion: string | null;
|
|
179
|
+
/** Sweeper working dirs (`<dataDir>/sweepers/<slug>/`), by slug. */
|
|
180
|
+
sweeperDirs?: Map<string, string>;
|
|
181
|
+
/** Per-session display name/preview, when the caller has them cheaply. */
|
|
182
|
+
names?: Map<string, {
|
|
183
|
+
name?: string;
|
|
184
|
+
preview?: string;
|
|
185
|
+
}>;
|
|
186
|
+
/**
|
|
187
|
+
* Override the per-request divergence-scan budget. Exists so a test can
|
|
188
|
+
* exhaust it with kilobytes instead of the 256 MB of pathological transcripts
|
|
189
|
+
* the real bound would need — the exhaustion path is the one that must not be
|
|
190
|
+
* silent, so it has to be exercised rather than reasoned about.
|
|
191
|
+
*/
|
|
192
|
+
scanBudgetBytes?: number;
|
|
193
|
+
}
|
|
194
|
+
interface ChatsDirListing {
|
|
195
|
+
/** Session ids with a `<id>.jsonl` in `.chats/`. */
|
|
196
|
+
chats: string[];
|
|
197
|
+
/** Session ids with a `<id>/` sidecar directory. */
|
|
198
|
+
chatDirs: Set<string>;
|
|
199
|
+
/** Entries that move with the project rather than with a chat. */
|
|
200
|
+
projectExtras: string[];
|
|
201
|
+
/** Entries matching no known pattern. */
|
|
202
|
+
unexpected: string[];
|
|
203
|
+
hasReverts: boolean;
|
|
204
|
+
hasMemory: boolean;
|
|
205
|
+
/** Total entries — the probe's whole question. */
|
|
206
|
+
entryCount: number;
|
|
207
|
+
}
|
|
208
|
+
/** Drop the memoised probe answers (tests; a config write). */
|
|
209
|
+
export declare function resetMigrationProbeCache(): void;
|
|
210
|
+
/**
|
|
211
|
+
* "Is there anything to migrate?" — one `readdir` per project, no transcript
|
|
212
|
+
* read and no host-store read at all (§10.1).
|
|
213
|
+
*
|
|
214
|
+
* Memoised per project on `mtimeMs:size` of `.chats/` plus the config version,
|
|
215
|
+
* which costs one `stat` to validate — that is what lets the banner be fetched
|
|
216
|
+
* on every page load. The host-store term the design's §3.4 cache key carried
|
|
217
|
+
* is gone with the host-store read it was there for.
|
|
218
|
+
*
|
|
219
|
+
* ### `pendingChats` is a lower bound that this implementation makes exact
|
|
220
|
+
*
|
|
221
|
+
* The design frames it as a lower bound because the probe "stops counting once
|
|
222
|
+
* the answer is known". `fs.readdir` hands back the whole list in one call, so
|
|
223
|
+
* there is no per-entry short-circuit to be had and counting the `*.jsonl`
|
|
224
|
+
* entries is free — the number is exact. It is still *reported* as a lower
|
|
225
|
+
* bound, because that is the contract clients were given and narrowing it later
|
|
226
|
+
* is a breaking change; but the field is `>= 0` rather than `∈ {0, 1}`.
|
|
227
|
+
*
|
|
228
|
+
* The one wrinkle the corrected predicate introduces: eligibility is about
|
|
229
|
+
* ENTRIES and `pendingChats` counts TRANSCRIPTS, so a `.chats/` holding only
|
|
230
|
+
* `memory/` is `eligible: true` with `pendingChats: 0`. That combination is
|
|
231
|
+
* correct, not a contradiction — there is real work to do and no chat to do it
|
|
232
|
+
* to — and it makes the schema's "exact only when 0" gloss wrong in one
|
|
233
|
+
* direction. Callers must key the banner off `eligible`, never off the count.
|
|
234
|
+
*
|
|
235
|
+
* ### `mode === "host"` is not on its own a reason to refuse
|
|
236
|
+
*
|
|
237
|
+
* The predicate is the same under both modes, which is the #882 ruling (§2)
|
|
238
|
+
* superseding the design's §7.2. `host` shipped as an unconditional refusal on
|
|
239
|
+
* the reasoning "already migrated, nothing to offer", and that is wrong for the
|
|
240
|
+
* one instance that most needs the offer: flipping to `host` while `.chats/`
|
|
241
|
+
* still held anything is #708's STRANDED state. `pointChatsDirAt` declines the
|
|
242
|
+
* redirect against a non-empty real directory (`transcripts.ts:145`), so the old
|
|
243
|
+
* transcripts sit in a real `.chats/` that nothing reads — and the probe told
|
|
244
|
+
* exactly those users there was nothing to do.
|
|
245
|
+
*
|
|
246
|
+
* The distinction is the DIRECTORY, and it is why {@link readChatsDir} `lstat`s:
|
|
247
|
+
*
|
|
248
|
+
* | `.chats/` under `host` | Eligible | Why |
|
|
249
|
+
* |---|---|---|
|
|
250
|
+
* | symlink into the host store | no — `already-host` | migrated; the redirect is planted |
|
|
251
|
+
* | empty real directory | no — `already-host` | the next boot plants it by itself |
|
|
252
|
+
* | non-empty real directory | **yes** | stranded: the redirect was declined |
|
|
253
|
+
*
|
|
254
|
+
* The first two are the overwhelmingly common case and must stay ineligible —
|
|
255
|
+
* showing a migration banner to everyone who has already migrated is worse than
|
|
256
|
+
* the bug this narrows. Under `host` the execute path moves the files and skips
|
|
257
|
+
* the config write (`configWritten: false`, `restartRequired: true`); the next
|
|
258
|
+
* boot then finds an empty `.chats/`, plants the redirect, and the stranded
|
|
259
|
+
* chats reappear. That closes the `own → host` half of #708.
|
|
260
|
+
*/
|
|
261
|
+
export declare function probeMigration(input: MigrationInput): Promise<MigrationProbe>;
|
|
262
|
+
/**
|
|
263
|
+
* One project, classified — everything both halves of the feature need, read
|
|
264
|
+
* from disk exactly once.
|
|
265
|
+
*
|
|
266
|
+
* Shared rather than duplicated because the execute half's step 4 is
|
|
267
|
+
* "re-enumerate from disk and reconcile the plan against reality" (§4.1): it
|
|
268
|
+
* needs the *same* classifier, not a similar one. A second enumeration written
|
|
269
|
+
* against the same directory shapes would drift, and the direction it would
|
|
270
|
+
* drift in is "execute moves a file the preview promised it would not".
|
|
271
|
+
*/
|
|
272
|
+
export interface MigrationProjectScan {
|
|
273
|
+
ref: MigrationProjectRef;
|
|
274
|
+
chatsDir: string;
|
|
275
|
+
hostStore: string;
|
|
276
|
+
preserveDir: string;
|
|
277
|
+
listing: ChatsDirListing;
|
|
278
|
+
/** False when the host store could not be read; every row is then `unknown`. */
|
|
279
|
+
hostReadable: boolean;
|
|
280
|
+
/** `.reverts/` BASE NAMES owned by each chat (not paths — the move helper
|
|
281
|
+
* rebuilds both sides from the store roots). */
|
|
282
|
+
revertsByChat: Map<string, string[]>;
|
|
283
|
+
/** Every chat, INCLUDING the identical ones the plan omits. */
|
|
284
|
+
rows: MigrationChatRow[];
|
|
285
|
+
projectExtras: string[];
|
|
286
|
+
warnings: MigrationWarning[];
|
|
287
|
+
candidates: ScanCandidate[];
|
|
288
|
+
}
|
|
289
|
+
/** A conflicted pair that stage 2 could not settle, deferred to stage 3. */
|
|
290
|
+
interface ScanCandidate {
|
|
291
|
+
row: MigrationChatRow;
|
|
292
|
+
/** Path of the longer side — the one that would be scanned. */
|
|
293
|
+
longerPath: string;
|
|
294
|
+
longerBytes: number;
|
|
295
|
+
shorterPath: string;
|
|
296
|
+
shorterBytes: number;
|
|
297
|
+
/** Which side is longer, i.e. which would be `ahead` on a hit. */
|
|
298
|
+
longerSide: "own" | "host";
|
|
299
|
+
/** The ancestor-candidate uuid, or undefined when the tail read found none. */
|
|
300
|
+
uuid?: string;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Build the modal's plan: every chat in every project's `.chats/`, classified
|
|
304
|
+
* against its host store.
|
|
305
|
+
*
|
|
306
|
+
* Chats identical on both sides are omitted from the rows — there is no
|
|
307
|
+
* decision to make — but they are counted in `totals.identical` so a row count
|
|
308
|
+
* lower than the user's chat total always has an explanation. They still
|
|
309
|
+
* migrate: the postcondition is about `.chats/` being empty, not about which
|
|
310
|
+
* chats the user picked.
|
|
311
|
+
*/
|
|
312
|
+
export declare function buildMigrationPlan(input: MigrationInput): Promise<MigrationPlan>;
|
|
313
|
+
/**
|
|
314
|
+
* Classify one project's `.chats/` against its host store.
|
|
315
|
+
*
|
|
316
|
+
* Returns `null` when there is nothing there to migrate: no `.chats/`, an
|
|
317
|
+
* already-planted symlink, or an empty directory. Everything else — including
|
|
318
|
+
* an unreadable store — comes back as a scan carrying its own warnings, because
|
|
319
|
+
* the execute half has to decide what to do about it rather than have it
|
|
320
|
+
* swallowed here.
|
|
321
|
+
*
|
|
322
|
+
* The stage-3 candidates are returned UNSETTLED. Settling them is one shared
|
|
323
|
+
* budget across the whole request, so the caller has to own it — see
|
|
324
|
+
* {@link settleCandidates}.
|
|
325
|
+
*/
|
|
326
|
+
export declare function scanProject(project: MigrationProjectRef, input: MigrationInput): Promise<MigrationProjectScan | null>;
|
|
327
|
+
/**
|
|
328
|
+
* Stage 3 over the deferred candidates, smallest-first against one shared
|
|
329
|
+
* budget. Ascending order means the budget settles as many rows as it can
|
|
330
|
+
* before it runs out, and — because it is ascending — the first row that does
|
|
331
|
+
* not fit proves every later one does not either.
|
|
332
|
+
*
|
|
333
|
+
* Returns whether any row came back `unknown`.
|
|
334
|
+
*/
|
|
335
|
+
export declare function settleCandidates(candidates: ScanCandidate[], budgetBytes: number): Promise<boolean>;
|
|
336
|
+
export {};
|