@cockpit-ai/worktree 0.1.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.
@@ -0,0 +1,54 @@
1
+ interface WorktreeInfo {
2
+ path: string;
3
+ branch: string;
4
+ commit: string;
5
+ isMain: boolean;
6
+ assignedAgent?: string;
7
+ }
8
+ interface CreateWorktreeOptions {
9
+ repo: string;
10
+ branch: string;
11
+ path?: string;
12
+ createBranch?: boolean;
13
+ }
14
+ declare class WorktreeManager {
15
+ private readonly repoPath;
16
+ constructor(repoPath: string);
17
+ /**
18
+ * Create a new worktree.
19
+ * git worktree add -b <branch> <path> [base-branch]
20
+ */
21
+ create(opts: Omit<CreateWorktreeOptions, "repo">): WorktreeInfo;
22
+ /**
23
+ * List all worktrees in the repo.
24
+ * git worktree list --porcelain
25
+ */
26
+ list(): WorktreeInfo[];
27
+ /**
28
+ * Remove a worktree by path.
29
+ * git worktree remove <path> [--force]
30
+ */
31
+ remove(worktreePath: string, force?: boolean): void;
32
+ /**
33
+ * Prune stale worktree references.
34
+ * git worktree prune
35
+ */
36
+ prune(): void;
37
+ }
38
+
39
+ interface WorktreeState {
40
+ worktrees: Record<string, {
41
+ path: string;
42
+ branch: string;
43
+ assignedAgent?: string;
44
+ createdAt: string;
45
+ }>;
46
+ }
47
+ declare function readWorktreeState(): WorktreeState;
48
+ declare function writeWorktreeState(state: WorktreeState): void;
49
+ declare function registerWorktree(path: string, branch: string): void;
50
+ declare function unregisterWorktree(path: string): void;
51
+ declare function assignAgent(worktreePath: string, agentName: string): void;
52
+ declare function getWorktreeState(path: string): WorktreeState["worktrees"][string] | undefined;
53
+
54
+ export { type CreateWorktreeOptions, type WorktreeInfo, WorktreeManager, type WorktreeState, assignAgent, getWorktreeState, readWorktreeState, registerWorktree, unregisterWorktree, writeWorktreeState };
package/dist/index.js ADDED
@@ -0,0 +1,140 @@
1
+ // src/manager.ts
2
+ import { execSync } from "child_process";
3
+ import { join, dirname } from "path";
4
+ var WorktreeManager = class {
5
+ constructor(repoPath) {
6
+ this.repoPath = repoPath;
7
+ }
8
+ /**
9
+ * Create a new worktree.
10
+ * git worktree add -b <branch> <path> [base-branch]
11
+ */
12
+ create(opts) {
13
+ const targetPath = opts.path ?? join(dirname(this.repoPath), opts.branch.replace(/\//g, "-"));
14
+ const flag = opts.createBranch !== false ? "-b" : "";
15
+ execSync(`git worktree add ${flag} ${opts.branch} ${targetPath}`, {
16
+ cwd: this.repoPath,
17
+ stdio: "pipe"
18
+ });
19
+ return { path: targetPath, branch: opts.branch, commit: "", isMain: false };
20
+ }
21
+ /**
22
+ * List all worktrees in the repo.
23
+ * git worktree list --porcelain
24
+ */
25
+ list() {
26
+ const output = execSync("git worktree list --porcelain", {
27
+ cwd: this.repoPath,
28
+ stdio: "pipe"
29
+ }).toString("utf-8");
30
+ const worktrees = [];
31
+ const blocks = output.trim().split(/\n\n+/);
32
+ for (let i = 0; i < blocks.length; i++) {
33
+ const block = blocks[i];
34
+ if (!block) continue;
35
+ const lines = block.split("\n");
36
+ let path = "";
37
+ let commit = "";
38
+ let branch = "";
39
+ for (const line of lines) {
40
+ if (line.startsWith("worktree ")) {
41
+ path = line.slice("worktree ".length).trim();
42
+ } else if (line.startsWith("HEAD ")) {
43
+ commit = line.slice("HEAD ".length).trim();
44
+ } else if (line.startsWith("branch ")) {
45
+ const ref = line.slice("branch ".length).trim();
46
+ branch = ref.replace(/^refs\/heads\//, "");
47
+ }
48
+ }
49
+ if (path) {
50
+ worktrees.push({
51
+ path,
52
+ branch,
53
+ commit,
54
+ isMain: i === 0
55
+ });
56
+ }
57
+ }
58
+ return worktrees;
59
+ }
60
+ /**
61
+ * Remove a worktree by path.
62
+ * git worktree remove <path> [--force]
63
+ */
64
+ remove(worktreePath, force) {
65
+ const forceFlag = force ? " --force" : "";
66
+ execSync(`git worktree remove ${worktreePath}${forceFlag}`, {
67
+ cwd: this.repoPath,
68
+ stdio: "pipe"
69
+ });
70
+ }
71
+ /**
72
+ * Prune stale worktree references.
73
+ * git worktree prune
74
+ */
75
+ prune() {
76
+ execSync("git worktree prune", {
77
+ cwd: this.repoPath,
78
+ stdio: "pipe"
79
+ });
80
+ }
81
+ };
82
+
83
+ // src/registry.ts
84
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
85
+ import { join as join2, dirname as dirname2 } from "path";
86
+ import { homedir } from "os";
87
+ var STATE_FILE = join2(homedir(), ".cockpit", "worktree-state.json");
88
+ function readWorktreeState() {
89
+ if (!existsSync(STATE_FILE)) {
90
+ return { worktrees: {} };
91
+ }
92
+ try {
93
+ const raw = readFileSync(STATE_FILE, "utf-8");
94
+ return JSON.parse(raw);
95
+ } catch {
96
+ return { worktrees: {} };
97
+ }
98
+ }
99
+ function writeWorktreeState(state) {
100
+ const dir = dirname2(STATE_FILE);
101
+ mkdirSync(dir, { recursive: true });
102
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf-8");
103
+ }
104
+ function registerWorktree(path, branch) {
105
+ const state = readWorktreeState();
106
+ state.worktrees[path] = {
107
+ path,
108
+ branch,
109
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
110
+ };
111
+ writeWorktreeState(state);
112
+ }
113
+ function unregisterWorktree(path) {
114
+ const state = readWorktreeState();
115
+ delete state.worktrees[path];
116
+ writeWorktreeState(state);
117
+ }
118
+ function assignAgent(worktreePath, agentName) {
119
+ const state = readWorktreeState();
120
+ const entry = state.worktrees[worktreePath];
121
+ if (!entry) {
122
+ throw new Error(`Worktree not registered: ${worktreePath}`);
123
+ }
124
+ entry.assignedAgent = agentName;
125
+ writeWorktreeState(state);
126
+ }
127
+ function getWorktreeState(path) {
128
+ const state = readWorktreeState();
129
+ return state.worktrees[path];
130
+ }
131
+ export {
132
+ WorktreeManager,
133
+ assignAgent,
134
+ getWorktreeState,
135
+ readWorktreeState,
136
+ registerWorktree,
137
+ unregisterWorktree,
138
+ writeWorktreeState
139
+ };
140
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manager.ts","../src/registry.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { join, dirname } from \"node:path\";\n\n// ─── Types ─────────────────────────────────────────────────────────────────\n\nexport interface WorktreeInfo {\n path: string;\n branch: string;\n commit: string;\n isMain: boolean;\n assignedAgent?: string;\n}\n\nexport interface CreateWorktreeOptions {\n repo: string; // path to git repo (defaults to cwd)\n branch: string; // branch name to create/checkout\n path?: string; // where to create the worktree (defaults to ../<branch>)\n createBranch?: boolean; // -b flag, true by default\n}\n\n// ─── WorktreeManager ───────────────────────────────────────────────────────\n\nexport class WorktreeManager {\n constructor(private readonly repoPath: string) {}\n\n /**\n * Create a new worktree.\n * git worktree add -b <branch> <path> [base-branch]\n */\n create(opts: Omit<CreateWorktreeOptions, \"repo\">): WorktreeInfo {\n const targetPath =\n opts.path ?? join(dirname(this.repoPath), opts.branch.replace(/\\//g, \"-\"));\n const flag = opts.createBranch !== false ? \"-b\" : \"\";\n execSync(`git worktree add ${flag} ${opts.branch} ${targetPath}`, {\n cwd: this.repoPath,\n stdio: \"pipe\",\n });\n return { path: targetPath, branch: opts.branch, commit: \"\", isMain: false };\n }\n\n /**\n * List all worktrees in the repo.\n * git worktree list --porcelain\n */\n list(): WorktreeInfo[] {\n const output = execSync(\"git worktree list --porcelain\", {\n cwd: this.repoPath,\n stdio: \"pipe\",\n }).toString(\"utf-8\");\n\n const worktrees: WorktreeInfo[] = [];\n const blocks = output.trim().split(/\\n\\n+/);\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i];\n if (!block) continue;\n\n const lines = block.split(\"\\n\");\n let path = \"\";\n let commit = \"\";\n let branch = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"worktree \")) {\n path = line.slice(\"worktree \".length).trim();\n } else if (line.startsWith(\"HEAD \")) {\n commit = line.slice(\"HEAD \".length).trim();\n } else if (line.startsWith(\"branch \")) {\n // refs/heads/main -> main\n const ref = line.slice(\"branch \".length).trim();\n branch = ref.replace(/^refs\\/heads\\//, \"\");\n }\n }\n\n if (path) {\n worktrees.push({\n path,\n branch,\n commit,\n isMain: i === 0,\n });\n }\n }\n\n return worktrees;\n }\n\n /**\n * Remove a worktree by path.\n * git worktree remove <path> [--force]\n */\n remove(worktreePath: string, force?: boolean): void {\n const forceFlag = force ? \" --force\" : \"\";\n execSync(`git worktree remove ${worktreePath}${forceFlag}`, {\n cwd: this.repoPath,\n stdio: \"pipe\",\n });\n }\n\n /**\n * Prune stale worktree references.\n * git worktree prune\n */\n prune(): void {\n execSync(\"git worktree prune\", {\n cwd: this.repoPath,\n stdio: \"pipe\",\n });\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n// ─── Types ─────────────────────────────────────────────────────────────────\n\nexport interface WorktreeState {\n worktrees: Record<\n string,\n { path: string; branch: string; assignedAgent?: string; createdAt: string }\n >;\n}\n\n// ─── State file path ────────────────────────────────────────────────────────\n\nconst STATE_FILE = join(homedir(), \".cockpit\", \"worktree-state.json\");\n\n// ─── I/O helpers ───────────────────────────────────────────────────────────\n\nexport function readWorktreeState(): WorktreeState {\n if (!existsSync(STATE_FILE)) {\n return { worktrees: {} };\n }\n try {\n const raw = readFileSync(STATE_FILE, \"utf-8\");\n return JSON.parse(raw) as WorktreeState;\n } catch {\n return { worktrees: {} };\n }\n}\n\nexport function writeWorktreeState(state: WorktreeState): void {\n const dir = dirname(STATE_FILE);\n mkdirSync(dir, { recursive: true });\n writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), \"utf-8\");\n}\n\n// ─── Registry operations ────────────────────────────────────────────────────\n\nexport function registerWorktree(path: string, branch: string): void {\n const state = readWorktreeState();\n state.worktrees[path] = {\n path,\n branch,\n createdAt: new Date().toISOString(),\n };\n writeWorktreeState(state);\n}\n\nexport function unregisterWorktree(path: string): void {\n const state = readWorktreeState();\n delete state.worktrees[path];\n writeWorktreeState(state);\n}\n\nexport function assignAgent(worktreePath: string, agentName: string): void {\n const state = readWorktreeState();\n const entry = state.worktrees[worktreePath];\n if (!entry) {\n throw new Error(`Worktree not registered: ${worktreePath}`);\n }\n entry.assignedAgent = agentName;\n writeWorktreeState(state);\n}\n\nexport function getWorktreeState(\n path: string\n): WorktreeState[\"worktrees\"][string] | undefined {\n const state = readWorktreeState();\n return state.worktrees[path];\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,MAAM,eAAe;AAqBvB,IAAM,kBAAN,MAAsB;AAAA,EAC3B,YAA6B,UAAkB;AAAlB;AAAA,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhD,OAAO,MAAyD;AAC9D,UAAM,aACJ,KAAK,QAAQ,KAAK,QAAQ,KAAK,QAAQ,GAAG,KAAK,OAAO,QAAQ,OAAO,GAAG,CAAC;AAC3E,UAAM,OAAO,KAAK,iBAAiB,QAAQ,OAAO;AAClD,aAAS,oBAAoB,IAAI,IAAI,KAAK,MAAM,IAAI,UAAU,IAAI;AAAA,MAChE,KAAK,KAAK;AAAA,MACV,OAAO;AAAA,IACT,CAAC;AACD,WAAO,EAAE,MAAM,YAAY,QAAQ,KAAK,QAAQ,QAAQ,IAAI,QAAQ,MAAM;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAuB;AACrB,UAAM,SAAS,SAAS,iCAAiC;AAAA,MACvD,KAAK,KAAK;AAAA,MACV,OAAO;AAAA,IACT,CAAC,EAAE,SAAS,OAAO;AAEnB,UAAM,YAA4B,CAAC;AACnC,UAAM,SAAS,OAAO,KAAK,EAAE,MAAM,OAAO;AAE1C,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,QAAQ,OAAO,CAAC;AACtB,UAAI,CAAC,MAAO;AAEZ,YAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,UAAI,OAAO;AACX,UAAI,SAAS;AACb,UAAI,SAAS;AAEb,iBAAW,QAAQ,OAAO;AACxB,YAAI,KAAK,WAAW,WAAW,GAAG;AAChC,iBAAO,KAAK,MAAM,YAAY,MAAM,EAAE,KAAK;AAAA,QAC7C,WAAW,KAAK,WAAW,OAAO,GAAG;AACnC,mBAAS,KAAK,MAAM,QAAQ,MAAM,EAAE,KAAK;AAAA,QAC3C,WAAW,KAAK,WAAW,SAAS,GAAG;AAErC,gBAAM,MAAM,KAAK,MAAM,UAAU,MAAM,EAAE,KAAK;AAC9C,mBAAS,IAAI,QAAQ,kBAAkB,EAAE;AAAA,QAC3C;AAAA,MACF;AAEA,UAAI,MAAM;AACR,kBAAU,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ,MAAM;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,cAAsB,OAAuB;AAClD,UAAM,YAAY,QAAQ,aAAa;AACvC,aAAS,uBAAuB,YAAY,GAAG,SAAS,IAAI;AAAA,MAC1D,KAAK,KAAK;AAAA,MACV,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAc;AACZ,aAAS,sBAAsB;AAAA,MAC7B,KAAK,KAAK;AAAA,MACV,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;;;AC7GA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,QAAAA,OAAM,WAAAC,gBAAe;AAC9B,SAAS,eAAe;AAaxB,IAAM,aAAaD,MAAK,QAAQ,GAAG,YAAY,qBAAqB;AAI7D,SAAS,oBAAmC;AACjD,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,WAAO,EAAE,WAAW,CAAC,EAAE;AAAA,EACzB;AACA,MAAI;AACF,UAAM,MAAM,aAAa,YAAY,OAAO;AAC5C,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO,EAAE,WAAW,CAAC,EAAE;AAAA,EACzB;AACF;AAEO,SAAS,mBAAmB,OAA4B;AAC7D,QAAM,MAAMC,SAAQ,UAAU;AAC9B,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,gBAAc,YAAY,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;AACnE;AAIO,SAAS,iBAAiB,MAAc,QAAsB;AACnE,QAAM,QAAQ,kBAAkB;AAChC,QAAM,UAAU,IAAI,IAAI;AAAA,IACtB;AAAA,IACA;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACA,qBAAmB,KAAK;AAC1B;AAEO,SAAS,mBAAmB,MAAoB;AACrD,QAAM,QAAQ,kBAAkB;AAChC,SAAO,MAAM,UAAU,IAAI;AAC3B,qBAAmB,KAAK;AAC1B;AAEO,SAAS,YAAY,cAAsB,WAAyB;AACzE,QAAM,QAAQ,kBAAkB;AAChC,QAAM,QAAQ,MAAM,UAAU,YAAY;AAC1C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,4BAA4B,YAAY,EAAE;AAAA,EAC5D;AACA,QAAM,gBAAgB;AACtB,qBAAmB,KAAK;AAC1B;AAEO,SAAS,iBACd,MACgD;AAChD,QAAM,QAAQ,kBAAkB;AAChC,SAAO,MAAM,UAAU,IAAI;AAC7B;","names":["join","dirname"]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@cockpit-ai/worktree",
3
+ "version": "0.1.0",
4
+ "description": "Cockpit worktree orchestration",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "dependencies": {
24
+ "@cockpit-ai/core": "0.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.0.0",
28
+ "tsup": "^8.0.0",
29
+ "typescript": "^5.5.0",
30
+ "vitest": "^2.0.0"
31
+ },
32
+ "scripts": {
33
+ "build": "tsup",
34
+ "dev": "tsup --watch",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "clean": "rm -rf dist"
38
+ }
39
+ }