@edspencer/paddock 0.63.0 → 0.64.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 (78) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/adoptable.d.ts +18 -0
  3. package/packages/server/dist/adoptable.js +1 -1
  4. package/packages/server/dist/app.js +3 -1
  5. package/packages/server/dist/claude-home.js +9 -1
  6. package/packages/server/dist/cli/import-chats.js +1 -1
  7. package/packages/server/dist/git.d.ts +44 -1
  8. package/packages/server/dist/git.js +79 -11
  9. package/packages/server/dist/management-ops.js +2 -1
  10. package/packages/server/dist/project-paths.d.ts +72 -4
  11. package/packages/server/dist/project-paths.js +76 -4
  12. package/packages/server/dist/project-types.d.ts +109 -11
  13. package/packages/server/dist/projects.d.ts +161 -3
  14. package/packages/server/dist/projects.js +466 -64
  15. package/packages/server/dist/routes/git.js +6 -3
  16. package/packages/server/dist/routes/projects.js +16 -2
  17. package/packages/server/dist/self-mcp-types.d.ts +6 -4
  18. package/packages/server/dist/sweep.js +10 -5
  19. package/packages/server/package.json +1 -1
  20. package/packages/web/dist/assets/{InstanceConfigPage-CCk2hVT9.js → InstanceConfigPage-Blr1a-hx.js} +1 -1
  21. package/packages/web/dist/assets/{MarkdownRenderer-B6gST_0U.js → MarkdownRenderer-R6YzfI9Y.js} +1 -1
  22. package/packages/web/dist/assets/{ProjectRedirect-BCmKT4mf.js → ProjectRedirect-Cy-7Thc-.js} +1 -1
  23. package/packages/web/dist/assets/{ProjectView-CCN98MWu.js → ProjectView-bMX4GEau.js} +9 -9
  24. package/packages/web/dist/assets/{ProjectsGrid-CYzuN_yD.js → ProjectsGrid-C9XHf4eS.js} +1 -1
  25. package/packages/web/dist/assets/{arc-BxvtMCZQ.js → arc-B_42F7Yk.js} +1 -1
  26. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-Bp6ffJ_u.js → architectureDiagram-3BPJPVTR-BAXzSOyw.js} +1 -1
  27. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-BIY2X8nW.js → blockDiagram-GPEHLZMM-BdYEdA-3.js} +1 -1
  28. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-Br9stEJS.js → c4Diagram-AAUBKEIU-Cc4o15R5.js} +1 -1
  29. package/packages/web/dist/assets/channel-BZnRb_8C.js +2 -0
  30. package/packages/web/dist/assets/{chunk-2J33WTMH-Djc7uqMV.js → chunk-2J33WTMH-CeuxHjIm.js} +1 -1
  31. package/packages/web/dist/assets/{chunk-4BX2VUAB-yEVMLksS.js → chunk-4BX2VUAB-DnZfrAaV.js} +1 -1
  32. package/packages/web/dist/assets/{chunk-55IACEB6-tJaEdPZg.js → chunk-55IACEB6-DFE6UYOT.js} +1 -1
  33. package/packages/web/dist/assets/{chunk-727SXJPM-bWEqh672.js → chunk-727SXJPM-YVHQ5cHc.js} +1 -1
  34. package/packages/web/dist/assets/{chunk-AQP2D5EJ-B8U9fB-T.js → chunk-AQP2D5EJ-5BYSbg-F.js} +1 -1
  35. package/packages/web/dist/assets/{chunk-FMBD7UC4-BeEITYig.js → chunk-FMBD7UC4-HBcelndR.js} +1 -1
  36. package/packages/web/dist/assets/{chunk-ND2GUHAM-L2rpLaoK.js → chunk-ND2GUHAM-2AwEsdxJ.js} +1 -1
  37. package/packages/web/dist/assets/{chunk-QZHKN3VN-qqzPMZL_.js → chunk-QZHKN3VN-BPpaZ6ct.js} +1 -1
  38. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-BGCpL0-K.js +2 -0
  39. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-BGCpL0-K.js +2 -0
  40. package/packages/web/dist/assets/{core-CSaNbmLO.js → core-Cu_4vw1U.js} +1 -1
  41. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BRF7Nech.js → cose-bilkent-S5V4N54A-DWkF0NrF.js} +1 -1
  42. package/packages/web/dist/assets/{dagre-BM42HDAG-D10rJWOR.js → dagre-BM42HDAG-DTgNmO9I.js} +1 -1
  43. package/packages/web/dist/assets/{diagram-2AECGRRQ-DGjT5mAO.js → diagram-2AECGRRQ-DC28b7YS.js} +1 -1
  44. package/packages/web/dist/assets/{diagram-5GNKFQAL-DaH3bxHh.js → diagram-5GNKFQAL-_WvuLno6.js} +1 -1
  45. package/packages/web/dist/assets/{diagram-KO2AKTUF-QrLW9Wn6.js → diagram-KO2AKTUF-BEYxPfWq.js} +1 -1
  46. package/packages/web/dist/assets/{diagram-LMA3HP47-BX15wgYN.js → diagram-LMA3HP47-Biyev7x0.js} +1 -1
  47. package/packages/web/dist/assets/{diagram-OG6HWLK6-DXQ_p63s.js → diagram-OG6HWLK6-u47NLW3S.js} +1 -1
  48. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-C_ioRruE.js → erDiagram-TEJ5UH35-Bud2ISlK.js} +1 -1
  49. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-7d-xLXLM.js → flowDiagram-I6XJVG4X-D-18vLQ1.js} +1 -1
  50. package/packages/web/dist/assets/{format-BSDZezwD.js → format-CQ2BDlby.js} +1 -1
  51. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DZYx5xcj.js → ganttDiagram-6RSMTGT7-CzyRX39N.js} +1 -1
  52. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-dimo-osm.js → gitGraphDiagram-PVQCEYII-DLogfrjg.js} +1 -1
  53. package/packages/web/dist/assets/{index-CUnFKMga.js → index-B0gJ0qWe.js} +14 -14
  54. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BzApY25x.js → infoDiagram-5YYISTIA-OgNPt6RG.js} +1 -1
  55. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DtX47_9p.js → ishikawaDiagram-YF4QCWOH-CrM0t0_X.js} +1 -1
  56. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-C7fITKg1.js → journeyDiagram-JHISSGLW-xbxfHfwR.js} +1 -1
  57. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BOrWLf_W.js → kanban-definition-UN3LZRKU-DpszBaZb.js} +1 -1
  58. package/packages/web/dist/assets/{linear-DuyAaBu6.js → linear-BLujdbjQ.js} +1 -1
  59. package/packages/web/dist/assets/{mermaid.core-BQlzuVvO.js → mermaid.core-DzEhy9Qq.js} +4 -4
  60. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CmP748B5.js → mindmap-definition-RKZ34NQL-DaH3Lmoo.js} +1 -1
  61. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CRd5Pdfq.js → pieDiagram-4H26LBE5-BxeW1kuf.js} +1 -1
  62. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-DpJm0Yw_.js → quadrantDiagram-W4KKPZXB-BKs2CT0r.js} +1 -1
  63. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-B0o2PRMC.js → requirementDiagram-4Y6WPE33-CVdO-hXD.js} +1 -1
  64. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-CBi3Ek3v.js → sankeyDiagram-5OEKKPKP-yusj_kJC.js} +1 -1
  65. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-C1stxfQg.js → sequenceDiagram-3UESZ5HK-C0h-9c8Y.js} +1 -1
  66. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-lCkiETWK.js → stateDiagram-AJRCARHV-BSBY8Psb.js} +1 -1
  67. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-4DGE0Z1g.js +2 -0
  68. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-D3DkWJMC.js → timeline-definition-PNZ67QCA-DEFUdNb8.js} +1 -1
  69. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CO2-yC7K.js → vennDiagram-CIIHVFJN-DunhMqxK.js} +1 -1
  70. package/packages/web/dist/assets/{wardley-L42UT6IY-Dfjp0kqX.js → wardley-L42UT6IY-CXJsBGKl.js} +1 -1
  71. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CIl0OPwS.js → wardleyDiagram-YWT4CUSO-DfT_-dqX.js} +1 -1
  72. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-CuvlQuup.js → xychartDiagram-2RQKCTM6-BysTtULW.js} +1 -1
  73. package/packages/web/dist/index.html +1 -1
  74. package/packages/web/dist/sw.js +1 -1
  75. package/packages/web/dist/assets/channel-CMdgNeFH.js +0 -2
  76. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DU4HSESS.js +0 -2
  77. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DU4HSESS.js +0 -2
  78. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-ZgEZRNqp.js +0 -2
@@ -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
- constructor(root: string);
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,130 @@ 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. Both
71
+ * current callers already only pass in-root paths — this exists so that stays
72
+ * true when someone later adds a third one.
73
+ */
74
+ private rmInsideRoot;
75
+ /**
76
+ * Undo a failed {@link create} by removing the directories it created — and
77
+ * ONLY those.
78
+ *
79
+ * This is the non-negotiable half of the acquisition rules (issue #206). The
80
+ * previous rollback was an unconditional `fs.rm(dir, {recursive, force})` on the
81
+ * slug dir, safe only because everything a project owned was nested inside it.
82
+ * Once `path:` lets a user nominate a clone TARGET, an unconditional recursive
83
+ * delete is a footgun aimed at whatever they typed — a home directory, say. So
84
+ * the caller records what it brought into being and this removes exactly that
85
+ * list; a pre-existing directory is never in it and therefore can never be
86
+ * deleted, no matter how the create failed.
87
+ *
88
+ * Removed deepest-first so a nested pair (slug dir + its checkout) unwinds
89
+ * cleanly. Failures are swallowed: the original error is the one worth
90
+ * reporting, and a rollback that can't finish must not mask it.
91
+ */
92
+ private rollback;
93
+ /**
94
+ * Validate a `path:` (issue #206) and return it canonicalised, along with
95
+ * whether it already exists.
96
+ *
97
+ * This is the ONLY gate between a user-supplied string and a directory Paddock
98
+ * will hand an agent as its cwd (and may `git clone` into), so it is
99
+ * deliberately strict and ordered cheapest-first. It rejects:
100
+ *
101
+ * - a relative path (a cwd must be unambiguous — it is baked into every
102
+ * transcript path, and resolving it against the server's cwd is a footgun);
103
+ * - a path that exists but isn't a directory;
104
+ * - a path inside `projectsRoot` or the data dir — that is Paddock's own
105
+ * state, and pointing at it re-creates the nesting this feature exists to
106
+ * avoid (plus `remove()` would then really delete it);
107
+ * - a path that is, contains, or sits inside another project's working
108
+ * directory — two keepers sharing one cwd collide on transcripts (they are
109
+ * keyed by cwd) and on the working tree itself.
110
+ *
111
+ * It deliberately does NOT reject a directory for not being a git repository.
112
+ * That would be the only mandatory git check in the codebase: everywhere else
113
+ * paddock PROBES and degrades (`GitService.isRepo()` → false, the Changes tab
114
+ * removes itself, commit/push return `{error: "not a repo"}`), and the default
115
+ * E2E server runs on a non-git directory on purpose.
116
+ *
117
+ * A MISSING path is not an error here — it is an acquisition instruction, which
118
+ * {@link acquirePath} resolves. Only the existing-path branch can be
119
+ * canonicalised by `realpath`, so for a missing one the containment checks run
120
+ * against the resolved-but-not-real path plus its nearest existing ancestor —
121
+ * otherwise `/tmp/link -> <projectsRoot>` with a missing leaf would slip past.
122
+ */
123
+ private validatePath;
124
+ /**
125
+ * Canonicalise a path that does not exist yet, by realpath-ing the deepest
126
+ * ancestor that DOES exist and re-appending the missing segments. Used only by
127
+ * {@link validatePath}, so a clone target under a symlinked parent still gets
128
+ * containment-checked against its real location.
129
+ */
130
+ private resolveViaExistingAncestor;
131
+ /**
132
+ * Bring a project's `path` into existence, returning the directories THIS call
133
+ * created so a failure can roll back exactly those and nothing else.
134
+ *
135
+ * The acquisition rules (issue #206):
136
+ *
137
+ * - path exists ⇒ use it. With `repo` also set, verify the remote matches and
138
+ * WARN on a mismatch rather than silently ignoring `repo` — that is the #659
139
+ * bug class (a directory that is a clone of a *different* repo).
140
+ * - path missing + `repo` ⇒ clone the repo to that path.
141
+ * - path missing, managed ⇒ create the directory; the user is nominating where
142
+ * their notes should live, and an empty dir is a valid notebook.
143
+ * - path missing, unmanaged, no `repo` ⇒ error. There is nothing to acquire
144
+ * from and an empty directory is not a codebase.
145
+ *
146
+ * **Cleanup rule:** `created` only ever gains directories this call brought into
147
+ * being. A pre-existing directory is never appended, so no rollback can delete
148
+ * one — the target is user-nominated and may be a home directory.
149
+ *
150
+ * `created` is an OUT-parameter rather than a return value on purpose. Returning
151
+ * the list would mean the caller only learns what to clean up on the success
152
+ * path: an `acquirePath` that threw would never hand back the directory it had
153
+ * already made, so a clone that died partway through would orphan it. Recording
154
+ * before the risky step is what makes the rollback complete.
155
+ */
156
+ private acquirePath;
157
+ /**
158
+ * Warn when a directory's git remotes don't include `repo`.
159
+ *
160
+ * Deliberately a warning, not a rejection: the directory the user named is the
161
+ * one they meant, and plenty of legitimate setups (a fork, a mirror, an
162
+ * ssh-vs-https spelling) won't match exactly. But silently ignoring `repo` is
163
+ * how #659 happened, so the mismatch has to be said out loud. A directory with
164
+ * no git at all is not a mismatch — it is just not a repo, which is allowed.
165
+ *
166
+ * Reuses `adoptable.ts`'s reader, which parses `.git/config` directly (no
167
+ * subprocess) and already follows a linked worktree's `gitdir:`/`commondir`
168
+ * pointers, and returns URLs already through {@link normalizeRemote}.
169
+ */
170
+ private warnOnRemoteMismatch;
43
171
  /**
44
172
  * Create a project: mkdir + write project.yaml (from template) + seed
45
173
  * CHANGELOG.md. Idempotency: throws ProjectError("exists") if the slug
46
174
  * directory already holds a project.yaml.
47
175
  */
48
176
  create(input: CreateProjectInput): Promise<Project>;
177
+ /**
178
+ * Write a new project's `project.yaml` and seed its content files. Split out of
179
+ * {@link create} so the whole sequence sits inside that method's single
180
+ * try/rollback rather than trailing off the end of it.
181
+ */
182
+ private finishCreate;
49
183
  /**
50
184
  * Promote an existing NOTEBOOK project into a REPO-BACKED one IN PLACE (issue
51
185
  * #213), preserving its chats + sidecar metadata. This relaxes #187's create-time
@@ -87,6 +221,21 @@ export declare class ProjectStore {
87
221
  readChangelog(slug: string): Promise<string>;
88
222
  /** Whether OVERVIEW.md exists for this project. */
89
223
  overviewExists(slug: string): Promise<boolean>;
224
+ /**
225
+ * {@link overviewExists} for a content dir already in hand.
226
+ *
227
+ * `readSafe` needs this: it has just parsed the yaml, so it can derive the
228
+ * content dir for free, whereas going through the slug would re-read
229
+ * `project.yaml` on every project of every `list()`.
230
+ */
231
+ private overviewExistsIn;
232
+ /**
233
+ * Where this project's curated trio lives (issue #206) — see
234
+ * {@link contentDirFor}. Falls back to the metadata dir for a slug that names
235
+ * no project, so a caller reading a missing project still gets "" rather than
236
+ * throwing.
237
+ */
238
+ private contentDirOf;
90
239
  /** Read CLAUDE.md, or "" if it doesn't exist yet (issue #177). */
91
240
  readClaudeMd(slug: string): Promise<string>;
92
241
  /**
@@ -198,5 +347,14 @@ export declare class ProjectStore {
198
347
  private ensureSidecarGitignore;
199
348
  private writeYaml;
200
349
  private toDto;
350
+ /**
351
+ * Drop the derived DTO-only fields, leaving the on-disk shape.
352
+ *
353
+ * `managed` deliberately SURVIVES: it is a real `project.yaml` key, and because
354
+ * the DTO's value is always concrete, the first `update()` of a legacy project
355
+ * pins its derived value explicitly on disk. That is the desired direction —
356
+ * the file stops depending on a default — and it is safe precisely because the
357
+ * value written is the one the derivation already produced.
358
+ */
201
359
  private stripDto;
202
360
  }