@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.
- 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 +2 -1
- 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 +161 -3
- package/packages/server/dist/projects.js +466 -64
- package/packages/server/dist/routes/git.js +6 -3
- package/packages/server/dist/routes/projects.js +16 -2
- package/packages/server/dist/self-mcp-types.d.ts +6 -4
- package/packages/server/dist/sweep.js +10 -5
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-CCk2hVT9.js → InstanceConfigPage-Blr1a-hx.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-B6gST_0U.js → MarkdownRenderer-R6YzfI9Y.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-BCmKT4mf.js → ProjectRedirect-Cy-7Thc-.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-CCN98MWu.js → ProjectView-bMX4GEau.js} +9 -9
- package/packages/web/dist/assets/{ProjectsGrid-CYzuN_yD.js → ProjectsGrid-C9XHf4eS.js} +1 -1
- package/packages/web/dist/assets/{arc-BxvtMCZQ.js → arc-B_42F7Yk.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-Bp6ffJ_u.js → architectureDiagram-3BPJPVTR-BAXzSOyw.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-BIY2X8nW.js → blockDiagram-GPEHLZMM-BdYEdA-3.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-Br9stEJS.js → c4Diagram-AAUBKEIU-Cc4o15R5.js} +1 -1
- package/packages/web/dist/assets/channel-BZnRb_8C.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-Djc7uqMV.js → chunk-2J33WTMH-CeuxHjIm.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-yEVMLksS.js → chunk-4BX2VUAB-DnZfrAaV.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-tJaEdPZg.js → chunk-55IACEB6-DFE6UYOT.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-bWEqh672.js → chunk-727SXJPM-YVHQ5cHc.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-B8U9fB-T.js → chunk-AQP2D5EJ-5BYSbg-F.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-BeEITYig.js → chunk-FMBD7UC4-HBcelndR.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-L2rpLaoK.js → chunk-ND2GUHAM-2AwEsdxJ.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-qqzPMZL_.js → chunk-QZHKN3VN-BPpaZ6ct.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-BGCpL0-K.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-BGCpL0-K.js +2 -0
- package/packages/web/dist/assets/{core-CSaNbmLO.js → core-Cu_4vw1U.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BRF7Nech.js → cose-bilkent-S5V4N54A-DWkF0NrF.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-D10rJWOR.js → dagre-BM42HDAG-DTgNmO9I.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-DGjT5mAO.js → diagram-2AECGRRQ-DC28b7YS.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-DaH3bxHh.js → diagram-5GNKFQAL-_WvuLno6.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-QrLW9Wn6.js → diagram-KO2AKTUF-BEYxPfWq.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BX15wgYN.js → diagram-LMA3HP47-Biyev7x0.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DXQ_p63s.js → diagram-OG6HWLK6-u47NLW3S.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-C_ioRruE.js → erDiagram-TEJ5UH35-Bud2ISlK.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-7d-xLXLM.js → flowDiagram-I6XJVG4X-D-18vLQ1.js} +1 -1
- package/packages/web/dist/assets/{format-BSDZezwD.js → format-CQ2BDlby.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DZYx5xcj.js → ganttDiagram-6RSMTGT7-CzyRX39N.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-dimo-osm.js → gitGraphDiagram-PVQCEYII-DLogfrjg.js} +1 -1
- package/packages/web/dist/assets/{index-CUnFKMga.js → index-B0gJ0qWe.js} +14 -14
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BzApY25x.js → infoDiagram-5YYISTIA-OgNPt6RG.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DtX47_9p.js → ishikawaDiagram-YF4QCWOH-CrM0t0_X.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-C7fITKg1.js → journeyDiagram-JHISSGLW-xbxfHfwR.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BOrWLf_W.js → kanban-definition-UN3LZRKU-DpszBaZb.js} +1 -1
- package/packages/web/dist/assets/{linear-DuyAaBu6.js → linear-BLujdbjQ.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-BQlzuVvO.js → mermaid.core-DzEhy9Qq.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CmP748B5.js → mindmap-definition-RKZ34NQL-DaH3Lmoo.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CRd5Pdfq.js → pieDiagram-4H26LBE5-BxeW1kuf.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-DpJm0Yw_.js → quadrantDiagram-W4KKPZXB-BKs2CT0r.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-B0o2PRMC.js → requirementDiagram-4Y6WPE33-CVdO-hXD.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-CBi3Ek3v.js → sankeyDiagram-5OEKKPKP-yusj_kJC.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-C1stxfQg.js → sequenceDiagram-3UESZ5HK-C0h-9c8Y.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-lCkiETWK.js → stateDiagram-AJRCARHV-BSBY8Psb.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-4DGE0Z1g.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-D3DkWJMC.js → timeline-definition-PNZ67QCA-DEFUdNb8.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CO2-yC7K.js → vennDiagram-CIIHVFJN-DunhMqxK.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-Dfjp0kqX.js → wardley-L42UT6IY-CXJsBGKl.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CIl0OPwS.js → wardleyDiagram-YWT4CUSO-DfT_-dqX.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-CuvlQuup.js → xychartDiagram-2RQKCTM6-BysTtULW.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
package/package.json
CHANGED
|
@@ -123,6 +123,24 @@ export declare function isSweeperPrompt(preview: string): boolean;
|
|
|
123
123
|
* to a real remote — the caller does not have to special-case it.
|
|
124
124
|
*/
|
|
125
125
|
export declare function normalizeRemote(url: string): string;
|
|
126
|
+
/**
|
|
127
|
+
* Every remote URL configured in a git checkout, normalised.
|
|
128
|
+
*
|
|
129
|
+
* Reads `.git/config` directly rather than shelling out to git: this runs for
|
|
130
|
+
* candidate directories behind a count rendered in a header, and a subprocess per
|
|
131
|
+
* candidate is exactly the cost that made checking the remote look unaffordable
|
|
132
|
+
* in the first place.
|
|
133
|
+
*
|
|
134
|
+
* Handles a linked WORKTREE, where `.git` is a FILE holding `gitdir: <path>` and
|
|
135
|
+
* the config lives in the MAIN repository's git dir — found by resolving the
|
|
136
|
+
* `commondir` pointer. This repo is developed with worktrees, so a contributor's
|
|
137
|
+
* `wt-*` checkout is a real case, not a hypothetical.
|
|
138
|
+
*
|
|
139
|
+
* Returns `[]` for anything unreadable or not a checkout at all. An INI parse is
|
|
140
|
+
* enough here — `url = …` inside a `[remote "…"]` section — because the question
|
|
141
|
+
* is only "does any remote name this repo", not "what is the exact config".
|
|
142
|
+
*/
|
|
143
|
+
export declare function gitRemotes(cwd: string): Promise<string[]>;
|
|
126
144
|
/** The engine surface this module needs — narrowed so tests can fake it. */
|
|
127
145
|
export interface AdoptableFleet {
|
|
128
146
|
listAdoptableSessions(agentName: string, fromWorkingDir?: string): Promise<AdoptableSession[]>;
|
|
@@ -216,7 +216,7 @@ export function normalizeRemote(url) {
|
|
|
216
216
|
* enough here — `url = …` inside a `[remote "…"]` section — because the question
|
|
217
217
|
* is only "does any remote name this repo", not "what is the exact config".
|
|
218
218
|
*/
|
|
219
|
-
async function gitRemotes(cwd) {
|
|
219
|
+
export async function gitRemotes(cwd) {
|
|
220
220
|
const dot = path.join(cwd, ".git");
|
|
221
221
|
const st = await fs.stat(dot).catch(() => null);
|
|
222
222
|
if (st === null)
|
|
@@ -109,7 +109,9 @@ export async function buildApp(opts = {}) {
|
|
|
109
109
|
// on a fatal misconfig (e.g. jwt mode without a JWKS URL) — fail closed.
|
|
110
110
|
registerAuth(app, cfg.auth);
|
|
111
111
|
// --- project layer + herdctl ------------------------------------------
|
|
112
|
-
const projects = new ProjectStore(cfg.projectsRoot
|
|
112
|
+
const projects = new ProjectStore(cfg.projectsRoot, cfg.dataDir, {
|
|
113
|
+
warn: (message) => app.log.warn(message),
|
|
114
|
+
});
|
|
113
115
|
await projects.init();
|
|
114
116
|
// The user's own MCP servers, under `claude.mcpServers: host` (#691 step 5).
|
|
115
117
|
// Read BEFORE the service is constructed because every keeper's agent config
|
|
@@ -471,7 +471,15 @@ export async function ensureClaudeHome(cfg, env = process.env, probeKeychain = p
|
|
|
471
471
|
}
|
|
472
472
|
if (present.length > 0) {
|
|
473
473
|
report.notices.push({
|
|
474
|
-
|
|
474
|
+
// `warn`, not `info`, and deliberately (#706). `cli/paddock.ts` sets
|
|
475
|
+
// LOG_LEVEL=warn unless `--verbose`, so at `info` this was filtered out
|
|
476
|
+
// on the `npx` path — the documented install route, and the one
|
|
477
|
+
// population the notice exists for. It is not noise at `warn` because it
|
|
478
|
+
// is already inside `present.length > 0`: it fires only for someone who
|
|
479
|
+
// HAS ~/.claude instruction files that are not being loaded. An empty
|
|
480
|
+
// ~/.claude, or `instructions: host`, prints nothing at all. Do not
|
|
481
|
+
// "tidy" it back to `info` — that reintroduces #706.
|
|
482
|
+
level: "warn",
|
|
475
483
|
message: `Claude instructions: this instance's own only (\`claude.instructions: own\`). Your ` +
|
|
476
484
|
`~/.claude ${present.join(", ")} ${present.length === 1 ? "is" : "are"} NOT loaded — ` +
|
|
477
485
|
`set \`claude.instructions: host\` (or PADDOCK_CLAUDE_INSTRUCTIONS=host) to share ` +
|
|
@@ -126,7 +126,7 @@ async function main() {
|
|
|
126
126
|
if (args.dataDir !== undefined)
|
|
127
127
|
process.env.PADDOCK_DATA_DIR = args.dataDir;
|
|
128
128
|
const cfg = loadPaddockConfig();
|
|
129
|
-
const projects = new ProjectStore(cfg.projectsRoot);
|
|
129
|
+
const projects = new ProjectStore(cfg.projectsRoot, cfg.dataDir);
|
|
130
130
|
const herdctl = new HerdctlService(cfg);
|
|
131
131
|
const project = await projects.get(args.project);
|
|
132
132
|
// Register the keepers so the agent this imports INTO exists and its `.chats/`
|
|
@@ -46,6 +46,8 @@ export declare class GitService {
|
|
|
46
46
|
private readonly author;
|
|
47
47
|
/** Cached repo-detection result for `projectsRoot` (null = not yet checked). */
|
|
48
48
|
private repoFlag;
|
|
49
|
+
/** Cached per-directory repo detection, keyed by resolved path (#597 / #206). */
|
|
50
|
+
private readonly repoAt;
|
|
49
51
|
/**
|
|
50
52
|
* @param projectsRoot Absolute path to the projects root the service operates on.
|
|
51
53
|
* @param author Commit identity used when Paddock commits on a project's behalf.
|
|
@@ -62,9 +64,36 @@ export declare class GitService {
|
|
|
62
64
|
* Whether the backing store is a git working tree. Cached after the first
|
|
63
65
|
* successful check (repo-ness doesn't change under a running server); a thrown
|
|
64
66
|
* git / missing-binary error resolves to `false`.
|
|
67
|
+
*
|
|
68
|
+
* Store-wide, so it still gates the store-wide operations ({@link dirtyCounts},
|
|
69
|
+
* {@link remote}, {@link push}). The PER-PROJECT surface asks
|
|
70
|
+
* {@link isRepoAt} about the project's own working directory instead — see
|
|
71
|
+
* that method for why the two had to be separated.
|
|
65
72
|
*/
|
|
66
73
|
isRepo(): Promise<boolean>;
|
|
67
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Whether `dir` is inside a git working tree, cached per directory.
|
|
76
|
+
*
|
|
77
|
+
* The per-project git surface used to gate on {@link isRepo} — "is the PROJECTS
|
|
78
|
+
* ROOT a repo?" — which was right only while every project's tree lived under
|
|
79
|
+
* that root. It stopped being right twice over (issue #597, issue #206):
|
|
80
|
+
*
|
|
81
|
+
* - a project whose working directory is a linked checkout outside the root
|
|
82
|
+
* would report "not a repo" whenever the root itself wasn't one, hiding the
|
|
83
|
+
* Changes tab for the exact directory that IS a repo;
|
|
84
|
+
* - conversely a non-repo directory inside a repo root would claim to be one.
|
|
85
|
+
*
|
|
86
|
+
* Asking about the directory the project actually works in answers both. For
|
|
87
|
+
* every pre-existing shape it returns what {@link isRepo} did, because a
|
|
88
|
+
* project dir under a repo root is itself inside that work tree.
|
|
89
|
+
*
|
|
90
|
+
* Probing rather than requiring is the house style: nothing here refuses a
|
|
91
|
+
* directory for not being a repo, it just doesn't light up the git features.
|
|
92
|
+
*/
|
|
93
|
+
isRepoAt(dir: string): Promise<boolean>;
|
|
94
|
+
/** One `rev-parse`, false on any failure (missing binary, missing dir, not a repo). */
|
|
95
|
+
private probeRepo;
|
|
96
|
+
/** Force the next repo check to re-run (e.g. after `git init` at runtime). */
|
|
68
97
|
resetRepoCache(): void;
|
|
69
98
|
/**
|
|
70
99
|
* Status for one project's subtree. Returns `{ repo: false }` when the store
|
|
@@ -120,6 +149,20 @@ export declare class GitService {
|
|
|
120
149
|
* when the store isn't a repo.
|
|
121
150
|
*/
|
|
122
151
|
dirtyCounts(): Promise<Record<string, number>>;
|
|
152
|
+
/**
|
|
153
|
+
* Uncommitted-file count for ONE directory's own repository.
|
|
154
|
+
*
|
|
155
|
+
* The companion to {@link dirtyCounts} for a project whose working directory
|
|
156
|
+
* lives outside `projectsRoot` (issue #206). That method answers the whole grid
|
|
157
|
+
* in a single `git status` over the store, which is why it is cheap — and also
|
|
158
|
+
* why it structurally cannot see a linked checkout: it buckets paths by their
|
|
159
|
+
* first segment relative to the store, and a directory outside it has no such
|
|
160
|
+
* segment. This one costs a subprocess, so callers use it only for the projects
|
|
161
|
+
* the cheap sweep genuinely cannot cover.
|
|
162
|
+
*
|
|
163
|
+
* Returns 0 for a directory that isn't a repo, matching "nothing to report".
|
|
164
|
+
*/
|
|
165
|
+
dirtyCountAt(dir: string): Promise<number>;
|
|
123
166
|
/** Push the current branch to origin (sets upstream on first push). */
|
|
124
167
|
push(): Promise<{
|
|
125
168
|
pushed: boolean;
|
|
@@ -52,6 +52,8 @@ export class GitService {
|
|
|
52
52
|
author;
|
|
53
53
|
/** Cached repo-detection result for `projectsRoot` (null = not yet checked). */
|
|
54
54
|
repoFlag = null;
|
|
55
|
+
/** Cached per-directory repo detection, keyed by resolved path (#597 / #206). */
|
|
56
|
+
repoAt = new Map();
|
|
55
57
|
/**
|
|
56
58
|
* @param projectsRoot Absolute path to the projects root the service operates on.
|
|
57
59
|
* @param author Commit identity used when Paddock commits on a project's behalf.
|
|
@@ -74,25 +76,60 @@ export class GitService {
|
|
|
74
76
|
* Whether the backing store is a git working tree. Cached after the first
|
|
75
77
|
* successful check (repo-ness doesn't change under a running server); a thrown
|
|
76
78
|
* git / missing-binary error resolves to `false`.
|
|
79
|
+
*
|
|
80
|
+
* Store-wide, so it still gates the store-wide operations ({@link dirtyCounts},
|
|
81
|
+
* {@link remote}, {@link push}). The PER-PROJECT surface asks
|
|
82
|
+
* {@link isRepoAt} about the project's own working directory instead — see
|
|
83
|
+
* that method for why the two had to be separated.
|
|
77
84
|
*/
|
|
78
85
|
async isRepo() {
|
|
79
86
|
if (this.repoFlag !== null)
|
|
80
87
|
return this.repoFlag;
|
|
88
|
+
this.repoFlag = await this.probeRepo(this.projectsRoot);
|
|
89
|
+
return this.repoFlag;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether `dir` is inside a git working tree, cached per directory.
|
|
93
|
+
*
|
|
94
|
+
* The per-project git surface used to gate on {@link isRepo} — "is the PROJECTS
|
|
95
|
+
* ROOT a repo?" — which was right only while every project's tree lived under
|
|
96
|
+
* that root. It stopped being right twice over (issue #597, issue #206):
|
|
97
|
+
*
|
|
98
|
+
* - a project whose working directory is a linked checkout outside the root
|
|
99
|
+
* would report "not a repo" whenever the root itself wasn't one, hiding the
|
|
100
|
+
* Changes tab for the exact directory that IS a repo;
|
|
101
|
+
* - conversely a non-repo directory inside a repo root would claim to be one.
|
|
102
|
+
*
|
|
103
|
+
* Asking about the directory the project actually works in answers both. For
|
|
104
|
+
* every pre-existing shape it returns what {@link isRepo} did, because a
|
|
105
|
+
* project dir under a repo root is itself inside that work tree.
|
|
106
|
+
*
|
|
107
|
+
* Probing rather than requiring is the house style: nothing here refuses a
|
|
108
|
+
* directory for not being a repo, it just doesn't light up the git features.
|
|
109
|
+
*/
|
|
110
|
+
async isRepoAt(dir) {
|
|
111
|
+
const key = path.resolve(dir);
|
|
112
|
+
const cached = this.repoAt.get(key);
|
|
113
|
+
if (cached !== undefined)
|
|
114
|
+
return cached;
|
|
115
|
+
const found = await this.probeRepo(key);
|
|
116
|
+
this.repoAt.set(key, found);
|
|
117
|
+
return found;
|
|
118
|
+
}
|
|
119
|
+
/** One `rev-parse`, false on any failure (missing binary, missing dir, not a repo). */
|
|
120
|
+
async probeRepo(dir) {
|
|
81
121
|
try {
|
|
82
|
-
const out = await this.git(
|
|
83
|
-
|
|
84
|
-
"--is-inside-work-tree",
|
|
85
|
-
]);
|
|
86
|
-
this.repoFlag = out.trim() === "true";
|
|
122
|
+
const out = await this.git(dir, ["rev-parse", "--is-inside-work-tree"]);
|
|
123
|
+
return out.trim() === "true";
|
|
87
124
|
}
|
|
88
125
|
catch {
|
|
89
|
-
|
|
126
|
+
return false;
|
|
90
127
|
}
|
|
91
|
-
return this.repoFlag;
|
|
92
128
|
}
|
|
93
|
-
/** Force the next
|
|
129
|
+
/** Force the next repo check to re-run (e.g. after `git init` at runtime). */
|
|
94
130
|
resetRepoCache() {
|
|
95
131
|
this.repoFlag = null;
|
|
132
|
+
this.repoAt.clear();
|
|
96
133
|
}
|
|
97
134
|
/**
|
|
98
135
|
* Status for one project's subtree. Returns `{ repo: false }` when the store
|
|
@@ -100,7 +137,9 @@ export class GitService {
|
|
|
100
137
|
* `projectDir`.
|
|
101
138
|
*/
|
|
102
139
|
async projectStatus(projectDir) {
|
|
103
|
-
|
|
140
|
+
// Per-DIRECTORY, not store-wide: callers pass the project's workingDir, which
|
|
141
|
+
// may be a linked checkout outside `projectsRoot` entirely (#597 / #206).
|
|
142
|
+
if (!(await this.isRepoAt(projectDir))) {
|
|
104
143
|
return { repo: false, files: [], clean: true };
|
|
105
144
|
}
|
|
106
145
|
let branch;
|
|
@@ -188,7 +227,7 @@ export class GitService {
|
|
|
188
227
|
* (they're listed by `projectStatus`). Returns "" when not a repo / no diff.
|
|
189
228
|
*/
|
|
190
229
|
async projectDiff(projectDir, file) {
|
|
191
|
-
if (!(await this.
|
|
230
|
+
if (!(await this.isRepoAt(projectDir)))
|
|
192
231
|
return "";
|
|
193
232
|
const args = ["diff", "HEAD", "--", file ?? "."];
|
|
194
233
|
try {
|
|
@@ -259,7 +298,7 @@ export class GitService {
|
|
|
259
298
|
* Omitting `paths` keeps the legacy commit-the-whole-subtree behavior.
|
|
260
299
|
*/
|
|
261
300
|
async commitProject(projectDir, message, paths) {
|
|
262
|
-
if (!(await this.
|
|
301
|
+
if (!(await this.isRepoAt(projectDir)))
|
|
263
302
|
return { committed: false, error: "not a repo" };
|
|
264
303
|
const msg = message.trim() || "Update project";
|
|
265
304
|
let pathspec;
|
|
@@ -325,6 +364,35 @@ export class GitService {
|
|
|
325
364
|
return {};
|
|
326
365
|
}
|
|
327
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Uncommitted-file count for ONE directory's own repository.
|
|
369
|
+
*
|
|
370
|
+
* The companion to {@link dirtyCounts} for a project whose working directory
|
|
371
|
+
* lives outside `projectsRoot` (issue #206). That method answers the whole grid
|
|
372
|
+
* in a single `git status` over the store, which is why it is cheap — and also
|
|
373
|
+
* why it structurally cannot see a linked checkout: it buckets paths by their
|
|
374
|
+
* first segment relative to the store, and a directory outside it has no such
|
|
375
|
+
* segment. This one costs a subprocess, so callers use it only for the projects
|
|
376
|
+
* the cheap sweep genuinely cannot cover.
|
|
377
|
+
*
|
|
378
|
+
* Returns 0 for a directory that isn't a repo, matching "nothing to report".
|
|
379
|
+
*/
|
|
380
|
+
async dirtyCountAt(dir) {
|
|
381
|
+
if (!(await this.isRepoAt(dir)))
|
|
382
|
+
return 0;
|
|
383
|
+
try {
|
|
384
|
+
const out = await this.git(dir, [
|
|
385
|
+
"status",
|
|
386
|
+
"--porcelain=v1",
|
|
387
|
+
"-z",
|
|
388
|
+
"--untracked-files=all",
|
|
389
|
+
]);
|
|
390
|
+
return parsePorcelainZ(out).length;
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return 0;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
328
396
|
/** Push the current branch to origin (sets upstream on first push). */
|
|
329
397
|
async push() {
|
|
330
398
|
if (!(await this.isRepo()))
|
|
@@ -308,8 +308,9 @@ export function buildManagementOps(ctx, params) {
|
|
|
308
308
|
name: project.name,
|
|
309
309
|
dir: project.dir,
|
|
310
310
|
workingDir: project.workingDir,
|
|
311
|
-
|
|
311
|
+
managed: project.managed === true,
|
|
312
312
|
...(project.repo ? { repo: project.repo } : {}),
|
|
313
|
+
...(project.path ? { path: project.path } : {}),
|
|
313
314
|
agentRegistered,
|
|
314
315
|
};
|
|
315
316
|
},
|
|
@@ -66,11 +66,79 @@ export declare function isValidRepoUrl(url: string): boolean;
|
|
|
66
66
|
*/
|
|
67
67
|
export declare function repoCheckoutName(repo: string): string;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* Whether `child` is `parent` itself or lives underneath it. Both are resolved
|
|
70
|
+
* first, so this compares real path strings rather than the caller's spelling
|
|
71
|
+
* (it does NOT follow symlinks — callers that care resolve them beforehand).
|
|
72
|
+
*
|
|
73
|
+
* The `+ path.sep` is load-bearing: a plain `startsWith(parent)` would call
|
|
74
|
+
* `/data/projects-old` a child of `/data/projects`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function isPathInside(child: string, parent: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a project's working directory (keeper cwd) from its metadata dir plus
|
|
79
|
+
* whichever backing field is set:
|
|
80
|
+
*
|
|
81
|
+
* - **`linkedPath`** (issue #206) — the cwd IS that directory, returned
|
|
82
|
+
* verbatim, used in place with no copy. Applies on BOTH sides of the
|
|
83
|
+
* managed/unmanaged axis: an unmanaged project links a code checkout you
|
|
84
|
+
* already have, a managed one nominates where its notes should live.
|
|
85
|
+
* - **`repo`** (issue #187) — the nested checkout Paddock cloned.
|
|
86
|
+
* - **neither** — the metadata dir itself.
|
|
87
|
+
*
|
|
88
|
+
* `linkedPath` WINS over `repo`, which is why the two are not mutually exclusive:
|
|
89
|
+
* `repo` alongside a path is an ACQUISITION hint (clone here if the path is
|
|
90
|
+
* missing) plus a remote-match for adoption (`adoptable.ts`, #659) and a re-clone
|
|
91
|
+
* hint for disaster recovery. It never moves the cwd off the nominated path.
|
|
92
|
+
*/
|
|
93
|
+
export declare function workingDirFor(dir: string, repo?: string, linkedPath?: string): string;
|
|
94
|
+
/** The subset of `project.yaml` that decides a project's shape. */
|
|
95
|
+
export interface ProjectBacking {
|
|
96
|
+
managed?: boolean;
|
|
97
|
+
repo?: string;
|
|
98
|
+
path?: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Whether Paddock looks after this project's own files — the real axis, and the
|
|
102
|
+
* one that replaced the overloaded `repoBacked` boolean (issue #206).
|
|
103
|
+
*
|
|
104
|
+
* **managed** — Paddock curates `CLAUDE.md` / `OVERVIEW.md` / `CHANGELOG.md`
|
|
105
|
+
* here. This is what "notebook" used to mean.
|
|
106
|
+
* **unmanaged** — the content is code that you or your agents source-control
|
|
107
|
+
* OUTSIDE Paddock. Paddock never writes project files into it.
|
|
108
|
+
*
|
|
109
|
+
* Whether a git repo sits behind it is an INDEPENDENT question (just which of
|
|
110
|
+
* `path`/`repo` are set), not a type. See `docs/DESIGN-backing-store.md` §2 goal
|
|
111
|
+
* 4: the base store is a plain directory and git is an optional layer.
|
|
112
|
+
*
|
|
113
|
+
* ## The default is derived, never constant
|
|
114
|
+
*
|
|
115
|
+
* `managed ?? !(repo || path)` — an absent key means "whatever this project's
|
|
116
|
+
* backing implies". That is load-bearing for the upgrade path: every existing
|
|
117
|
+
* `project.yaml` predates the key, and if absent meant `true`, every existing
|
|
118
|
+
* repo-backed project would flip to managed on upgrade and the sweeper would
|
|
119
|
+
* start writing `CLAUDE.md` into somebody's checkout. New projects write the key
|
|
120
|
+
* explicitly, so the derivation only ever has to serve legacy files.
|
|
121
|
+
*/
|
|
122
|
+
export declare function isManaged(backing: ProjectBacking): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Where a project's CURATED CONTENT lives — `OVERVIEW.md`, `CHANGELOG.md` and
|
|
125
|
+
* `CLAUDE.md`, the three files the sweeper writes.
|
|
126
|
+
*
|
|
127
|
+
* Content follows the working directory; application state does not. So:
|
|
128
|
+
*
|
|
129
|
+
* - **managed** ⇒ the working dir. For a classic notebook that IS the metadata
|
|
130
|
+
* dir (unchanged). For a managed project with an external `path`, the curated
|
|
131
|
+
* files live out there with the content — an accepted consequence being that
|
|
132
|
+
* they then do NOT live in the Paddock data dir.
|
|
133
|
+
* - **unmanaged** ⇒ the metadata dir, always. The working dir is somebody
|
|
134
|
+
* else's tree and Paddock does not write project files into it; `OVERVIEW.md`
|
|
135
|
+
* and `CHANGELOG.md` are still curated, just sidecarred (issue #187), and
|
|
136
|
+
* `CLAUDE.md` is not curated at all (the checkout owns its own).
|
|
137
|
+
*
|
|
138
|
+
* `project.yaml` and `.chats/` are NOT content and never move — see
|
|
139
|
+
* {@link import("./projects.js").ProjectStore.dirFor}.
|
|
72
140
|
*/
|
|
73
|
-
export declare function
|
|
141
|
+
export declare function contentDirFor(dir: string, backing: ProjectBacking): string;
|
|
74
142
|
export declare class ProjectError extends Error {
|
|
75
143
|
readonly code: "not_found" | "exists" | "invalid" | "not_directory";
|
|
76
144
|
constructor(message: string, code: "not_found" | "exists" | "invalid" | "not_directory");
|
|
@@ -74,13 +74,85 @@ export function repoCheckoutName(repo) {
|
|
|
74
74
|
return safe || "repo";
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
77
|
+
* Whether `child` is `parent` itself or lives underneath it. Both are resolved
|
|
78
|
+
* first, so this compares real path strings rather than the caller's spelling
|
|
79
|
+
* (it does NOT follow symlinks — callers that care resolve them beforehand).
|
|
80
|
+
*
|
|
81
|
+
* The `+ path.sep` is load-bearing: a plain `startsWith(parent)` would call
|
|
82
|
+
* `/data/projects-old` a child of `/data/projects`.
|
|
83
|
+
*/
|
|
84
|
+
export function isPathInside(child, parent) {
|
|
85
|
+
const c = path.resolve(child);
|
|
86
|
+
const p = path.resolve(parent);
|
|
87
|
+
return c === p || c.startsWith(p + path.sep);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolve a project's working directory (keeper cwd) from its metadata dir plus
|
|
91
|
+
* whichever backing field is set:
|
|
92
|
+
*
|
|
93
|
+
* - **`linkedPath`** (issue #206) — the cwd IS that directory, returned
|
|
94
|
+
* verbatim, used in place with no copy. Applies on BOTH sides of the
|
|
95
|
+
* managed/unmanaged axis: an unmanaged project links a code checkout you
|
|
96
|
+
* already have, a managed one nominates where its notes should live.
|
|
97
|
+
* - **`repo`** (issue #187) — the nested checkout Paddock cloned.
|
|
98
|
+
* - **neither** — the metadata dir itself.
|
|
99
|
+
*
|
|
100
|
+
* `linkedPath` WINS over `repo`, which is why the two are not mutually exclusive:
|
|
101
|
+
* `repo` alongside a path is an ACQUISITION hint (clone here if the path is
|
|
102
|
+
* missing) plus a remote-match for adoption (`adoptable.ts`, #659) and a re-clone
|
|
103
|
+
* hint for disaster recovery. It never moves the cwd off the nominated path.
|
|
80
104
|
*/
|
|
81
|
-
export function workingDirFor(dir, repo) {
|
|
105
|
+
export function workingDirFor(dir, repo, linkedPath) {
|
|
106
|
+
if (linkedPath)
|
|
107
|
+
return linkedPath;
|
|
82
108
|
return repo ? path.join(dir, repoCheckoutName(repo)) : dir;
|
|
83
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Whether Paddock looks after this project's own files — the real axis, and the
|
|
112
|
+
* one that replaced the overloaded `repoBacked` boolean (issue #206).
|
|
113
|
+
*
|
|
114
|
+
* **managed** — Paddock curates `CLAUDE.md` / `OVERVIEW.md` / `CHANGELOG.md`
|
|
115
|
+
* here. This is what "notebook" used to mean.
|
|
116
|
+
* **unmanaged** — the content is code that you or your agents source-control
|
|
117
|
+
* OUTSIDE Paddock. Paddock never writes project files into it.
|
|
118
|
+
*
|
|
119
|
+
* Whether a git repo sits behind it is an INDEPENDENT question (just which of
|
|
120
|
+
* `path`/`repo` are set), not a type. See `docs/DESIGN-backing-store.md` §2 goal
|
|
121
|
+
* 4: the base store is a plain directory and git is an optional layer.
|
|
122
|
+
*
|
|
123
|
+
* ## The default is derived, never constant
|
|
124
|
+
*
|
|
125
|
+
* `managed ?? !(repo || path)` — an absent key means "whatever this project's
|
|
126
|
+
* backing implies". That is load-bearing for the upgrade path: every existing
|
|
127
|
+
* `project.yaml` predates the key, and if absent meant `true`, every existing
|
|
128
|
+
* repo-backed project would flip to managed on upgrade and the sweeper would
|
|
129
|
+
* start writing `CLAUDE.md` into somebody's checkout. New projects write the key
|
|
130
|
+
* explicitly, so the derivation only ever has to serve legacy files.
|
|
131
|
+
*/
|
|
132
|
+
export function isManaged(backing) {
|
|
133
|
+
return backing.managed ?? !(backing.repo || backing.path);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Where a project's CURATED CONTENT lives — `OVERVIEW.md`, `CHANGELOG.md` and
|
|
137
|
+
* `CLAUDE.md`, the three files the sweeper writes.
|
|
138
|
+
*
|
|
139
|
+
* Content follows the working directory; application state does not. So:
|
|
140
|
+
*
|
|
141
|
+
* - **managed** ⇒ the working dir. For a classic notebook that IS the metadata
|
|
142
|
+
* dir (unchanged). For a managed project with an external `path`, the curated
|
|
143
|
+
* files live out there with the content — an accepted consequence being that
|
|
144
|
+
* they then do NOT live in the Paddock data dir.
|
|
145
|
+
* - **unmanaged** ⇒ the metadata dir, always. The working dir is somebody
|
|
146
|
+
* else's tree and Paddock does not write project files into it; `OVERVIEW.md`
|
|
147
|
+
* and `CHANGELOG.md` are still curated, just sidecarred (issue #187), and
|
|
148
|
+
* `CLAUDE.md` is not curated at all (the checkout owns its own).
|
|
149
|
+
*
|
|
150
|
+
* `project.yaml` and `.chats/` are NOT content and never move — see
|
|
151
|
+
* {@link import("./projects.js").ProjectStore.dirFor}.
|
|
152
|
+
*/
|
|
153
|
+
export function contentDirFor(dir, backing) {
|
|
154
|
+
return isManaged(backing) ? workingDirFor(dir, backing.repo, backing.path) : dir;
|
|
155
|
+
}
|
|
84
156
|
export class ProjectError extends Error {
|
|
85
157
|
code;
|
|
86
158
|
constructor(message, code) {
|
|
@@ -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">> & {
|