@edspencer/paddock 0.63.0 → 0.65.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.
- package/package.json +1 -1
- package/packages/server/dist/adoptable.d.ts +18 -0
- package/packages/server/dist/adoptable.js +1 -1
- package/packages/server/dist/app.js +3 -1
- package/packages/server/dist/claude-home.js +9 -1
- package/packages/server/dist/cli/import-chats.js +1 -1
- package/packages/server/dist/git.d.ts +44 -1
- package/packages/server/dist/git.js +79 -11
- package/packages/server/dist/management-ops.js +42 -2
- package/packages/server/dist/management-policy.d.ts +4 -3
- package/packages/server/dist/management-policy.js +5 -2
- package/packages/server/dist/project-paths.d.ts +72 -4
- package/packages/server/dist/project-paths.js +76 -4
- package/packages/server/dist/project-types.d.ts +109 -11
- package/packages/server/dist/projects.d.ts +170 -3
- package/packages/server/dist/projects.js +477 -66
- package/packages/server/dist/routes/git.js +6 -3
- package/packages/server/dist/routes/projects.js +16 -2
- package/packages/server/dist/self-mcp-descriptions.d.ts +1 -0
- package/packages/server/dist/self-mcp-descriptions.js +14 -1
- package/packages/server/dist/self-mcp-types.d.ts +32 -8
- package/packages/server/dist/self-mcp-write.d.ts +21 -0
- package/packages/server/dist/self-mcp-write.js +44 -0
- package/packages/server/dist/self-mcp.d.ts +7 -6
- package/packages/server/dist/self-mcp.js +36 -10
- package/packages/server/dist/sweep.js +10 -5
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-CCk2hVT9.js → InstanceConfigPage-DcprTH2W.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-B6gST_0U.js → MarkdownRenderer-Ri1LwAUN.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-BCmKT4mf.js → ProjectRedirect-Dze5H8Xy.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-CCN98MWu.js → ProjectView-PrA8purE.js} +9 -9
- package/packages/web/dist/assets/{ProjectsGrid-CYzuN_yD.js → ProjectsGrid-BidsM7ah.js} +1 -1
- package/packages/web/dist/assets/{arc-BxvtMCZQ.js → arc-eQ3ryE9c.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-Bp6ffJ_u.js → architectureDiagram-3BPJPVTR-Dc6Tj2Ai.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-BIY2X8nW.js → blockDiagram-GPEHLZMM-DBX5pma_.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-Br9stEJS.js → c4Diagram-AAUBKEIU-CnWrtTpa.js} +1 -1
- package/packages/web/dist/assets/channel-B4sED4OP.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-Djc7uqMV.js → chunk-2J33WTMH-2qmLvtsr.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-yEVMLksS.js → chunk-4BX2VUAB-Dn-LUDMV.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-tJaEdPZg.js → chunk-55IACEB6-DB0augUY.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-bWEqh672.js → chunk-727SXJPM-BPQQM0i8.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-B8U9fB-T.js → chunk-AQP2D5EJ-DOhfwA-x.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-BeEITYig.js → chunk-FMBD7UC4-CatHHdFD.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-L2rpLaoK.js → chunk-ND2GUHAM-B645aAlG.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-qqzPMZL_.js → chunk-QZHKN3VN-D1fAaPSZ.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-BlVeHFir.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-BlVeHFir.js +2 -0
- package/packages/web/dist/assets/{core-CSaNbmLO.js → core-B4t7D8Om.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BRF7Nech.js → cose-bilkent-S5V4N54A-BLdKDAwa.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-D10rJWOR.js → dagre-BM42HDAG-CbTbTVKv.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-DGjT5mAO.js → diagram-2AECGRRQ-CorSS29_.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-DaH3bxHh.js → diagram-5GNKFQAL-PLKivts0.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-QrLW9Wn6.js → diagram-KO2AKTUF-CUSIBacZ.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BX15wgYN.js → diagram-LMA3HP47-CZ69mAZs.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DXQ_p63s.js → diagram-OG6HWLK6-Cmf042Rh.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-C_ioRruE.js → erDiagram-TEJ5UH35-DyIKGj6e.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-7d-xLXLM.js → flowDiagram-I6XJVG4X-B79pLdLD.js} +1 -1
- package/packages/web/dist/assets/{format-BSDZezwD.js → format-C5nhFL8q.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DZYx5xcj.js → ganttDiagram-6RSMTGT7-BFTR_oNA.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-dimo-osm.js → gitGraphDiagram-PVQCEYII-DdGUlJxS.js} +1 -1
- package/packages/web/dist/assets/{index-CUnFKMga.js → index-DVSsta3B.js} +14 -14
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BzApY25x.js → infoDiagram-5YYISTIA-B80FyUOy.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DtX47_9p.js → ishikawaDiagram-YF4QCWOH-_wRlphn9.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-C7fITKg1.js → journeyDiagram-JHISSGLW-qu4yvjW8.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BOrWLf_W.js → kanban-definition-UN3LZRKU-D2DEUp2-.js} +1 -1
- package/packages/web/dist/assets/{linear-DuyAaBu6.js → linear-D-CLRUEW.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-BQlzuVvO.js → mermaid.core-DdihBJl4.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CmP748B5.js → mindmap-definition-RKZ34NQL-BEh_TDBf.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CRd5Pdfq.js → pieDiagram-4H26LBE5-BbrmLVaZ.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-DpJm0Yw_.js → quadrantDiagram-W4KKPZXB-DfVuygAv.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-B0o2PRMC.js → requirementDiagram-4Y6WPE33-BHp2QpZt.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-CBi3Ek3v.js → sankeyDiagram-5OEKKPKP-D2G6lRMY.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-C1stxfQg.js → sequenceDiagram-3UESZ5HK-Dj-jzR-c.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-lCkiETWK.js → stateDiagram-AJRCARHV-C5D6bT13.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-C8NDOqTq.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-D3DkWJMC.js → timeline-definition-PNZ67QCA-DE4gGjhP.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CO2-yC7K.js → vennDiagram-CIIHVFJN-C9QNrl92.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-Dfjp0kqX.js → wardley-L42UT6IY-U2fSGy-d.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CIl0OPwS.js → wardleyDiagram-YWT4CUSO-B9RhObdH.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-CuvlQuup.js → xychartDiagram-2RQKCTM6-BnlPrtQ9.js} +1 -1
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/channel-CMdgNeFH.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DU4HSESS.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DU4HSESS.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-ZgEZRNqp.js +0 -2
|
@@ -175,6 +175,61 @@ export interface ProjectYaml {
|
|
|
175
175
|
* (like `slug`/`started`).
|
|
176
176
|
*/
|
|
177
177
|
repo?: string;
|
|
178
|
+
/**
|
|
179
|
+
* Whether Paddock looks after this project's own files (issue #206).
|
|
180
|
+
*
|
|
181
|
+
* `true` — Paddock curates `CLAUDE.md` / `OVERVIEW.md` / `CHANGELOG.md` here;
|
|
182
|
+
* this is what "notebook" used to mean. `false` — the content is code that you
|
|
183
|
+
* or your agents source-control OUTSIDE Paddock, and Paddock never writes
|
|
184
|
+
* project files into it.
|
|
185
|
+
*
|
|
186
|
+
* This is the real axis. It replaced the derived `repoBacked` boolean, which
|
|
187
|
+
* was one flag doing four jobs and broke the moment a fifth backing shape
|
|
188
|
+
* appeared. Whether a git repo sits behind the project is INDEPENDENT of this
|
|
189
|
+
* and is not a type — it is just which of `path`/`repo` are set.
|
|
190
|
+
*
|
|
191
|
+
* **Optional on disk, and the default is DERIVED rather than constant:**
|
|
192
|
+
* `managed ?? !(repo || path)` (see
|
|
193
|
+
* {@link import("./project-paths.js").isManaged}). Every `project.yaml` written
|
|
194
|
+
* before this key existed relies on that: were an absent value to mean `true`,
|
|
195
|
+
* every existing repo-backed project would flip to managed on upgrade and the
|
|
196
|
+
* sweeper would begin writing `CLAUDE.md` into its checkout. `create()` always
|
|
197
|
+
* writes the key explicitly, so the derivation only ever serves legacy files.
|
|
198
|
+
*
|
|
199
|
+
* `managed: true` together with `repo` is REJECTED at creation rather than
|
|
200
|
+
* silently ignored — Paddock curating files into a repo it also clones is not a
|
|
201
|
+
* combination with a sensible meaning.
|
|
202
|
+
*/
|
|
203
|
+
managed?: boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Absolute path to the directory holding this project's content (issue #206).
|
|
206
|
+
*
|
|
207
|
+
* Applies on BOTH sides of the managed axis, which is what makes it a location
|
|
208
|
+
* rather than a type:
|
|
209
|
+
*
|
|
210
|
+
* - **unmanaged + `path`** — LINK a code checkout you already have
|
|
211
|
+
* (`~/Code/foo`), used in place with no copy: its real history, branches and
|
|
212
|
+
* remotes. Paddock writes nothing into it (no `.chats/`, no sidecar
|
|
213
|
+
* `.gitignore`, no `CLAUDE.md`); transcripts stay in `<slug>/.chats/` via
|
|
214
|
+
* `ensureProjectChats`'s two-argument split. Transcripts key on this cwd, so
|
|
215
|
+
* prior `claude` sessions run there by hand are offered for adoption, and
|
|
216
|
+
* `claude.transcripts: host` / per-directory `~/.claude.json` MCP servers
|
|
217
|
+
* finally mean something for a code project.
|
|
218
|
+
* - **managed + `path`** — nominate where your NOTES live. The curated trio
|
|
219
|
+
* (`OVERVIEW.md`/`CHANGELOG.md`/`CLAUDE.md`) follows the path, so those files
|
|
220
|
+
* end up outside the Paddock data dir.
|
|
221
|
+
*
|
|
222
|
+
* Either way `project.yaml` (the registry entry Paddock discovers projects by
|
|
223
|
+
* scanning for) and `.chats/` (Paddock's own state) stay in
|
|
224
|
+
* `<projectsRoot>/<slug>/`.
|
|
225
|
+
*
|
|
226
|
+
* Acquisition when the path does NOT exist: with `repo`, Paddock clones into it;
|
|
227
|
+
* for a managed project, Paddock creates it; otherwise the create is rejected.
|
|
228
|
+
*
|
|
229
|
+
* Set at creation and immutable thereafter (like `slug`/`started`/`repo`) — the
|
|
230
|
+
* cwd is baked into every transcript path, so moving it would strand history.
|
|
231
|
+
*/
|
|
232
|
+
path?: string;
|
|
178
233
|
/**
|
|
179
234
|
* Scheduled chat sessions for this project (issue #265 / DD-2), keyed by a
|
|
180
235
|
* stable schedule name. Each value is herdctl's `ScheduleSchema` shape (plus the
|
|
@@ -211,22 +266,46 @@ export interface Project extends ProjectYaml {
|
|
|
211
266
|
* empty string means "Unsorted". */
|
|
212
267
|
group: string;
|
|
213
268
|
/**
|
|
214
|
-
* Absolute path to the project's METADATA directory — the per-slug dir
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
269
|
+
* Absolute path to the project's METADATA directory — the per-slug dir under
|
|
270
|
+
* `projectsRoot`. Always holds `project.yaml` (the registry entry Paddock
|
|
271
|
+
* discovers projects by scanning for, so it can never move) and `.chats/`
|
|
272
|
+
* (Paddock's own state, and what backup tooling points at).
|
|
273
|
+
*
|
|
274
|
+
* It ALSO holds the curated trio (`OVERVIEW.md`/`CHANGELOG.md`/`CLAUDE.md`)
|
|
275
|
+
* unless the project is managed with an external `path`, in which case those
|
|
276
|
+
* follow the content — see {@link contentDir}.
|
|
219
277
|
*/
|
|
220
278
|
dir: string;
|
|
221
279
|
/**
|
|
222
|
-
* The keeper agent's working directory (cwd)
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
280
|
+
* The keeper agent's working directory (cwd): {@link ProjectYaml.path} verbatim
|
|
281
|
+
* when set, else the nested checkout for a `repo`-backed project (#187), else
|
|
282
|
+
* {@link dir}. Always concrete in the DTO.
|
|
283
|
+
*
|
|
284
|
+
* This — not {@link dir} — is the directory the git surface reports on, which
|
|
285
|
+
* is what issue #597 was: the Changes tab read `dir`, so it showed the metadata
|
|
286
|
+
* directory rather than the code the project is actually about.
|
|
226
287
|
*/
|
|
227
288
|
workingDir: string;
|
|
228
|
-
/**
|
|
229
|
-
|
|
289
|
+
/**
|
|
290
|
+
* Where the sweeper's curated trio lives — {@link workingDir} for a managed
|
|
291
|
+
* project, {@link dir} for an unmanaged one (issue #206). Always concrete.
|
|
292
|
+
*
|
|
293
|
+
* Equals {@link dir} in every pre-#206 shape; it differs only for a managed
|
|
294
|
+
* project with an external `path`.
|
|
295
|
+
*/
|
|
296
|
+
contentDir: string;
|
|
297
|
+
/**
|
|
298
|
+
* Whether Paddock curates this project's own files — ALWAYS concrete in the
|
|
299
|
+
* DTO, derived via `managed ?? !(repo || path)` so legacy files keep their
|
|
300
|
+
* meaning (issue #206).
|
|
301
|
+
*
|
|
302
|
+
* This replaced `repoBacked`, which conflated four separate questions into one
|
|
303
|
+
* boolean and broke as soon as a fifth backing shape appeared. Each consumer
|
|
304
|
+
* now takes the fact it actually needs: the sweeper's `CLAUDE.md` suppression
|
|
305
|
+
* and the promote guard key on `managed`; the git surface keys on whether
|
|
306
|
+
* {@link workingDir} is a git working tree, asked per directory.
|
|
307
|
+
*/
|
|
308
|
+
managed: boolean;
|
|
230
309
|
/** Whether OVERVIEW.md exists in the project dir (sweep-curated context). */
|
|
231
310
|
hasOverview: boolean;
|
|
232
311
|
/** Always present in the DTO (defaults to []). */
|
|
@@ -259,6 +338,25 @@ export interface CreateProjectInput {
|
|
|
259
338
|
* a notebook project (the classic type).
|
|
260
339
|
*/
|
|
261
340
|
repo?: string;
|
|
341
|
+
/**
|
|
342
|
+
* Whether Paddock should curate this project's own files (issue #206). Absent ⇒
|
|
343
|
+
* derived as `!(repo || path)`, so a plain create is managed and a create that
|
|
344
|
+
* names a backing is not. `managed: true` together with `repo` is REJECTED.
|
|
345
|
+
*/
|
|
346
|
+
managed?: boolean;
|
|
347
|
+
/**
|
|
348
|
+
* Absolute path to the directory this project's content lives in (issue #206).
|
|
349
|
+
*
|
|
350
|
+
* Validated at create time: absolute, and outside `projectsRoot` / the data dir
|
|
351
|
+
* / every other project's working directory. Immutable thereafter. If it does
|
|
352
|
+
* not exist, Paddock clones `repo` into it when one is given, creates it for a
|
|
353
|
+
* managed project, and otherwise rejects the create.
|
|
354
|
+
*
|
|
355
|
+
* There is deliberately NO requirement that it be a git repository — Paddock
|
|
356
|
+
* probes for git and lights up the git features when it finds one, exactly as
|
|
357
|
+
* it already does for the projects root.
|
|
358
|
+
*/
|
|
359
|
+
path?: string;
|
|
262
360
|
}
|
|
263
361
|
/** Partial metadata update (slug + started are immutable). */
|
|
264
362
|
export type UpdateProjectInput = Partial<Pick<ProjectYaml, "name" | "status" | "domain" | "group" | "visibility" | "summary" | "links" | "model" | "permissionMode" | "maxTurns" | "docker">> & {
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import { IMAGE_MIME, VIDEO_MIME, DOCUMENT_MIME, fileKind, contentTypeFor } from "./project-mime.js";
|
|
2
2
|
export { IMAGE_MIME, VIDEO_MIME, DOCUMENT_MIME, fileKind, contentTypeFor };
|
|
3
|
-
import { ROOT_KEY, isRootKey, isValidRepoUrl, repoCheckoutName, workingDirFor, slugify, ProjectError } from "./project-paths.js";
|
|
4
|
-
export { ROOT_KEY, isRootKey, isValidRepoUrl, repoCheckoutName, workingDirFor, slugify, ProjectError, };
|
|
3
|
+
import { ROOT_KEY, isRootKey, isPathInside, isManaged, contentDirFor, isValidRepoUrl, repoCheckoutName, workingDirFor, slugify, ProjectError } from "./project-paths.js";
|
|
4
|
+
export { ROOT_KEY, isRootKey, isPathInside, isManaged, contentDirFor, isValidRepoUrl, repoCheckoutName, workingDirFor, slugify, ProjectError, };
|
|
5
5
|
import { type Project, type FileKind, type FileEntry, type CreateProjectInput, type UpdateProjectInput } from "./project-types.js";
|
|
6
6
|
export * from "./project-types.js";
|
|
7
7
|
export declare class ProjectStore {
|
|
8
8
|
private readonly root;
|
|
9
|
-
|
|
9
|
+
private readonly dataDir?;
|
|
10
|
+
private readonly log;
|
|
11
|
+
/**
|
|
12
|
+
* @param root The projects root (`cfg.projectsRoot`) — every project's metadata dir.
|
|
13
|
+
* @param dataDir The instance data dir (`cfg.dataDir`), used ONLY to reject a
|
|
14
|
+
* `path:` that points inside Paddock's own state (issue #206). Optional so the
|
|
15
|
+
* many tests that construct a bare store keep compiling; when omitted, `root`
|
|
16
|
+
* alone is the containment check (in the default layout `projectsRoot` is
|
|
17
|
+
* `<dataDir>/projects`, so it is the tighter of the two anyway).
|
|
18
|
+
* @param log Where non-fatal advisories go — currently only the declared-repo /
|
|
19
|
+
* actual-remote mismatch on an existing `path` (issue #206), which must be
|
|
20
|
+
* said out loud rather than silently ignored (#659) but must not fail the
|
|
21
|
+
* create. Defaults to a no-op so a bare `new ProjectStore(root)` is unchanged.
|
|
22
|
+
*/
|
|
23
|
+
constructor(root: string, dataDir?: string | undefined, log?: {
|
|
24
|
+
warn: (message: string) => void;
|
|
25
|
+
});
|
|
10
26
|
/** Ensure the projects root exists. Call once at startup. */
|
|
11
27
|
init(): Promise<void>;
|
|
12
28
|
/**
|
|
@@ -40,12 +56,139 @@ export declare class ProjectStore {
|
|
|
40
56
|
* record; the root workspace always exists, record or not.
|
|
41
57
|
*/
|
|
42
58
|
exists(slug: string): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Recursively delete `target`, but ONLY if it is strictly inside `projectsRoot`.
|
|
61
|
+
*
|
|
62
|
+
* The single choke point for every recursive delete in this store. Before issue
|
|
63
|
+
* #206 the containment was implicit: everything a project owned — including a
|
|
64
|
+
* repo-backed project's checkout — was nested under `<projectsRoot>/<slug>/`, so
|
|
65
|
+
* `fs.rm(dir, {recursive: true})` could not reach anything that wasn't ours.
|
|
66
|
+
* A LINKED project's working directory is the user's real work repo, outside the
|
|
67
|
+
* root, and that implicit safety is gone.
|
|
68
|
+
*
|
|
69
|
+
* So the check is made explicit and centralised here rather than re-derived at
|
|
70
|
+
* each call site: an `fs.rm` that escapes throws instead of deleting. Every
|
|
71
|
+
* caller already only passes in-root paths — this exists so that stays true
|
|
72
|
+
* when someone later adds another one.
|
|
73
|
+
*
|
|
74
|
+
* `promote()`'s two rollbacks route through here as well. Their target is
|
|
75
|
+
* `<projectsRoot>/<slug>/<checkoutName>`, which is in-root by construction, so
|
|
76
|
+
* the guard is inert there today — that is the point. This is the choke point
|
|
77
|
+
* the comment above promised, and a recursive delete that skips it is exactly
|
|
78
|
+
* the drift it exists to prevent. {@link rollback} is the deliberate exception:
|
|
79
|
+
* it deletes paths that may legitimately sit OUTSIDE the root (a user-nominated
|
|
80
|
+
* clone target), and is safe by a different mechanism — it only ever removes
|
|
81
|
+
* directories this operation created.
|
|
82
|
+
*/
|
|
83
|
+
private rmInsideRoot;
|
|
84
|
+
/**
|
|
85
|
+
* Undo a failed {@link create} by removing the directories it created — and
|
|
86
|
+
* ONLY those.
|
|
87
|
+
*
|
|
88
|
+
* This is the non-negotiable half of the acquisition rules (issue #206). The
|
|
89
|
+
* previous rollback was an unconditional `fs.rm(dir, {recursive, force})` on the
|
|
90
|
+
* slug dir, safe only because everything a project owned was nested inside it.
|
|
91
|
+
* Once `path:` lets a user nominate a clone TARGET, an unconditional recursive
|
|
92
|
+
* delete is a footgun aimed at whatever they typed — a home directory, say. So
|
|
93
|
+
* the caller records what it brought into being and this removes exactly that
|
|
94
|
+
* list; a pre-existing directory is never in it and therefore can never be
|
|
95
|
+
* deleted, no matter how the create failed.
|
|
96
|
+
*
|
|
97
|
+
* Removed deepest-first so a nested pair (slug dir + its checkout) unwinds
|
|
98
|
+
* cleanly. Failures are swallowed: the original error is the one worth
|
|
99
|
+
* reporting, and a rollback that can't finish must not mask it.
|
|
100
|
+
*/
|
|
101
|
+
private rollback;
|
|
102
|
+
/**
|
|
103
|
+
* Validate a `path:` (issue #206) and return it canonicalised, along with
|
|
104
|
+
* whether it already exists.
|
|
105
|
+
*
|
|
106
|
+
* This is the ONLY gate between a user-supplied string and a directory Paddock
|
|
107
|
+
* will hand an agent as its cwd (and may `git clone` into), so it is
|
|
108
|
+
* deliberately strict and ordered cheapest-first. It rejects:
|
|
109
|
+
*
|
|
110
|
+
* - a relative path (a cwd must be unambiguous — it is baked into every
|
|
111
|
+
* transcript path, and resolving it against the server's cwd is a footgun);
|
|
112
|
+
* - a path that exists but isn't a directory;
|
|
113
|
+
* - a path inside `projectsRoot` or the data dir — that is Paddock's own
|
|
114
|
+
* state, and pointing at it re-creates the nesting this feature exists to
|
|
115
|
+
* avoid (plus `remove()` would then really delete it);
|
|
116
|
+
* - a path that is, contains, or sits inside another project's working
|
|
117
|
+
* directory — two keepers sharing one cwd collide on transcripts (they are
|
|
118
|
+
* keyed by cwd) and on the working tree itself.
|
|
119
|
+
*
|
|
120
|
+
* It deliberately does NOT reject a directory for not being a git repository.
|
|
121
|
+
* That would be the only mandatory git check in the codebase: everywhere else
|
|
122
|
+
* paddock PROBES and degrades (`GitService.isRepo()` → false, the Changes tab
|
|
123
|
+
* removes itself, commit/push return `{error: "not a repo"}`), and the default
|
|
124
|
+
* E2E server runs on a non-git directory on purpose.
|
|
125
|
+
*
|
|
126
|
+
* A MISSING path is not an error here — it is an acquisition instruction, which
|
|
127
|
+
* {@link acquirePath} resolves. Only the existing-path branch can be
|
|
128
|
+
* canonicalised by `realpath`, so for a missing one the containment checks run
|
|
129
|
+
* against the resolved-but-not-real path plus its nearest existing ancestor —
|
|
130
|
+
* otherwise `/tmp/link -> <projectsRoot>` with a missing leaf would slip past.
|
|
131
|
+
*/
|
|
132
|
+
private validatePath;
|
|
133
|
+
/**
|
|
134
|
+
* Canonicalise a path that does not exist yet, by realpath-ing the deepest
|
|
135
|
+
* ancestor that DOES exist and re-appending the missing segments. Used only by
|
|
136
|
+
* {@link validatePath}, so a clone target under a symlinked parent still gets
|
|
137
|
+
* containment-checked against its real location.
|
|
138
|
+
*/
|
|
139
|
+
private resolveViaExistingAncestor;
|
|
140
|
+
/**
|
|
141
|
+
* Bring a project's `path` into existence, returning the directories THIS call
|
|
142
|
+
* created so a failure can roll back exactly those and nothing else.
|
|
143
|
+
*
|
|
144
|
+
* The acquisition rules (issue #206):
|
|
145
|
+
*
|
|
146
|
+
* - path exists ⇒ use it. With `repo` also set, verify the remote matches and
|
|
147
|
+
* WARN on a mismatch rather than silently ignoring `repo` — that is the #659
|
|
148
|
+
* bug class (a directory that is a clone of a *different* repo).
|
|
149
|
+
* - path missing + `repo` ⇒ clone the repo to that path.
|
|
150
|
+
* - path missing, managed ⇒ create the directory; the user is nominating where
|
|
151
|
+
* their notes should live, and an empty dir is a valid notebook.
|
|
152
|
+
* - path missing, unmanaged, no `repo` ⇒ error. There is nothing to acquire
|
|
153
|
+
* from and an empty directory is not a codebase.
|
|
154
|
+
*
|
|
155
|
+
* **Cleanup rule:** `created` only ever gains directories this call brought into
|
|
156
|
+
* being. A pre-existing directory is never appended, so no rollback can delete
|
|
157
|
+
* one — the target is user-nominated and may be a home directory.
|
|
158
|
+
*
|
|
159
|
+
* `created` is an OUT-parameter rather than a return value on purpose. Returning
|
|
160
|
+
* the list would mean the caller only learns what to clean up on the success
|
|
161
|
+
* path: an `acquirePath` that threw would never hand back the directory it had
|
|
162
|
+
* already made, so a clone that died partway through would orphan it. Recording
|
|
163
|
+
* before the risky step is what makes the rollback complete.
|
|
164
|
+
*/
|
|
165
|
+
private acquirePath;
|
|
166
|
+
/**
|
|
167
|
+
* Warn when a directory's git remotes don't include `repo`.
|
|
168
|
+
*
|
|
169
|
+
* Deliberately a warning, not a rejection: the directory the user named is the
|
|
170
|
+
* one they meant, and plenty of legitimate setups (a fork, a mirror, an
|
|
171
|
+
* ssh-vs-https spelling) won't match exactly. But silently ignoring `repo` is
|
|
172
|
+
* how #659 happened, so the mismatch has to be said out loud. A directory with
|
|
173
|
+
* no git at all is not a mismatch — it is just not a repo, which is allowed.
|
|
174
|
+
*
|
|
175
|
+
* Reuses `adoptable.ts`'s reader, which parses `.git/config` directly (no
|
|
176
|
+
* subprocess) and already follows a linked worktree's `gitdir:`/`commondir`
|
|
177
|
+
* pointers, and returns URLs already through {@link normalizeRemote}.
|
|
178
|
+
*/
|
|
179
|
+
private warnOnRemoteMismatch;
|
|
43
180
|
/**
|
|
44
181
|
* Create a project: mkdir + write project.yaml (from template) + seed
|
|
45
182
|
* CHANGELOG.md. Idempotency: throws ProjectError("exists") if the slug
|
|
46
183
|
* directory already holds a project.yaml.
|
|
47
184
|
*/
|
|
48
185
|
create(input: CreateProjectInput): Promise<Project>;
|
|
186
|
+
/**
|
|
187
|
+
* Write a new project's `project.yaml` and seed its content files. Split out of
|
|
188
|
+
* {@link create} so the whole sequence sits inside that method's single
|
|
189
|
+
* try/rollback rather than trailing off the end of it.
|
|
190
|
+
*/
|
|
191
|
+
private finishCreate;
|
|
49
192
|
/**
|
|
50
193
|
* Promote an existing NOTEBOOK project into a REPO-BACKED one IN PLACE (issue
|
|
51
194
|
* #213), preserving its chats + sidecar metadata. This relaxes #187's create-time
|
|
@@ -87,6 +230,21 @@ export declare class ProjectStore {
|
|
|
87
230
|
readChangelog(slug: string): Promise<string>;
|
|
88
231
|
/** Whether OVERVIEW.md exists for this project. */
|
|
89
232
|
overviewExists(slug: string): Promise<boolean>;
|
|
233
|
+
/**
|
|
234
|
+
* {@link overviewExists} for a content dir already in hand.
|
|
235
|
+
*
|
|
236
|
+
* `readSafe` needs this: it has just parsed the yaml, so it can derive the
|
|
237
|
+
* content dir for free, whereas going through the slug would re-read
|
|
238
|
+
* `project.yaml` on every project of every `list()`.
|
|
239
|
+
*/
|
|
240
|
+
private overviewExistsIn;
|
|
241
|
+
/**
|
|
242
|
+
* Where this project's curated trio lives (issue #206) — see
|
|
243
|
+
* {@link contentDirFor}. Falls back to the metadata dir for a slug that names
|
|
244
|
+
* no project, so a caller reading a missing project still gets "" rather than
|
|
245
|
+
* throwing.
|
|
246
|
+
*/
|
|
247
|
+
private contentDirOf;
|
|
90
248
|
/** Read CLAUDE.md, or "" if it doesn't exist yet (issue #177). */
|
|
91
249
|
readClaudeMd(slug: string): Promise<string>;
|
|
92
250
|
/**
|
|
@@ -198,5 +356,14 @@ export declare class ProjectStore {
|
|
|
198
356
|
private ensureSidecarGitignore;
|
|
199
357
|
private writeYaml;
|
|
200
358
|
private toDto;
|
|
359
|
+
/**
|
|
360
|
+
* Drop the derived DTO-only fields, leaving the on-disk shape.
|
|
361
|
+
*
|
|
362
|
+
* `managed` deliberately SURVIVES: it is a real `project.yaml` key, and because
|
|
363
|
+
* the DTO's value is always concrete, the first `update()` of a legacy project
|
|
364
|
+
* pins its derived value explicitly on disk. That is the desired direction —
|
|
365
|
+
* the file stops depending on a default — and it is safe precisely because the
|
|
366
|
+
* value written is the one the derivation already produced.
|
|
367
|
+
*/
|
|
201
368
|
private stripDto;
|
|
202
369
|
}
|