@opengsd/gsd-pi 1.0.2-dev.d456457 → 1.0.2-dev.d55d5c9
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/dist/resource-loader.d.ts +5 -0
- package/dist/resource-loader.js +23 -7
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +2 -54
- package/dist/resources/extensions/gsd/worktree-post-create-hook.js +117 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
- package/dist/web/standalone/.next/server/chunks/1834.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/worktree-cli.d.ts +0 -2
- package/dist/worktree-cli.js +21 -9
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/bin/gsd-cloud-mcp-gateway.js +14 -0
- package/packages/cloud-mcp-gateway/package.json +4 -3
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/bin/gsd-mcp-server.js +14 -0
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/bin/pi-ai.js +14 -0
- package/packages/pi-ai/dist/models.generated.js +6 -6
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +3 -2
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/deps.js +10 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +2 -56
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +141 -1
- package/src/resources/extensions/gsd/worktree-post-create-hook.ts +127 -0
- package/dist/tsconfig.extensions.tsbuildinfo +0 -1
- /package/dist/web/standalone/.next/static/{4NVKiVx4C-8FUT9A7DZdq → l2HoM3Fqnb0IDfjVs_Umt}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{4NVKiVx4C-8FUT9A7DZdq → l2HoM3Fqnb0IDfjVs_Umt}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gsd/pi-ai",
|
|
3
|
-
"version": "1.0.2-dev.
|
|
3
|
+
"version": "1.0.2-dev.d55d5c9",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"gsd": {
|
|
@@ -57,9 +57,10 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"bin": {
|
|
60
|
-
"pi-ai": "./
|
|
60
|
+
"pi-ai": "./bin/pi-ai.js"
|
|
61
61
|
},
|
|
62
62
|
"files": [
|
|
63
|
+
"bin",
|
|
63
64
|
"dist",
|
|
64
65
|
"README.md"
|
|
65
66
|
],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gsd/pi-coding-agent",
|
|
3
|
-
"version": "1.0.2-dev.
|
|
3
|
+
"version": "1.0.2-dev.d55d5c9",
|
|
4
4
|
"description": "Coding agent CLI (vendored from earendil-works/pi)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"gsd": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"copy-assets": "node scripts/copy-assets.cjs"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@opengsd/contracts": "^1.0.2-dev.
|
|
36
|
+
"@opengsd/contracts": "^1.0.2-dev.d55d5c9",
|
|
37
37
|
"@mariozechner/jiti": "^2.6.2",
|
|
38
38
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
39
39
|
"chalk": "5.6.2",
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"typebox": "1.1.38",
|
|
54
54
|
"undici": "8.3.0",
|
|
55
55
|
"yaml": "2.9.0",
|
|
56
|
-
"@gsd/agent-core": "^1.0.2-dev.
|
|
57
|
-
"@gsd/native": "^1.0.2-dev.
|
|
58
|
-
"@gsd/pi-agent-core": "^1.0.2-dev.
|
|
59
|
-
"@gsd/pi-ai": "^1.0.2-dev.
|
|
60
|
-
"@gsd/pi-tui": "^1.0.2-dev.
|
|
56
|
+
"@gsd/agent-core": "^1.0.2-dev.d55d5c9",
|
|
57
|
+
"@gsd/native": "^1.0.2-dev.d55d5c9",
|
|
58
|
+
"@gsd/pi-agent-core": "^1.0.2-dev.d55d5c9",
|
|
59
|
+
"@gsd/pi-ai": "^1.0.2-dev.d55d5c9",
|
|
60
|
+
"@gsd/pi-tui": "^1.0.2-dev.d55d5c9",
|
|
61
61
|
"@sinclair/typebox": "^0.34.41"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengsd/rpc-client",
|
|
3
|
-
"version": "1.0.2-dev.
|
|
3
|
+
"version": "1.0.2-dev.d55d5c9",
|
|
4
4
|
"description": "Standalone RPC client SDK for GSD — zero internal dependencies",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"gsd": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test": "node --test dist/rpc-client.test.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@opengsd/contracts": "^1.0.2-dev.
|
|
37
|
+
"@opengsd/contracts": "^1.0.2-dev.d55d5c9"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=22.0.0"
|
package/pkg/package.json
CHANGED
package/scripts/install/deps.js
CHANGED
|
@@ -164,6 +164,16 @@ function execCommand(command, opts = {}) {
|
|
|
164
164
|
* to materialize the real packages without re-running lifecycle scripts.
|
|
165
165
|
*/
|
|
166
166
|
export async function repairPackageDependencies(packageRoot, { ui, quiet = false } = {}) {
|
|
167
|
+
if (
|
|
168
|
+
process.env.GSD_SKIP_DEP_REPAIR === '1' ||
|
|
169
|
+
process.env.GSD_SKIP_DEP_REPAIR === 'true'
|
|
170
|
+
) {
|
|
171
|
+
if (!quiet) {
|
|
172
|
+
ui?.skip?.('Dependencies', 'skipped by GSD_SKIP_DEP_REPAIR')
|
|
173
|
+
}
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
167
177
|
const pkgJson = join(packageRoot, 'package.json')
|
|
168
178
|
if (!existsSync(pkgJson)) return
|
|
169
179
|
|
|
@@ -59,6 +59,7 @@ import { debugLog } from "./debug-logger.js";
|
|
|
59
59
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
60
60
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
61
61
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
62
|
+
import { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
|
|
62
63
|
import {
|
|
63
64
|
nativeGetCurrentBranch,
|
|
64
65
|
nativeDetectMainBranch,
|
|
@@ -912,62 +913,7 @@ export function syncWorktreeStateBack(
|
|
|
912
913
|
): { synced: string[] } {
|
|
913
914
|
return _finalizeProjectionForMergeImpl(mainBasePath, worktreePath, milestoneId);
|
|
914
915
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
/**
|
|
918
|
-
* Run the user-configured post-create hook script after worktree creation.
|
|
919
|
-
* The script receives SOURCE_DIR and WORKTREE_DIR as environment variables.
|
|
920
|
-
* Failure is non-fatal — returns the error message or null on success.
|
|
921
|
-
*
|
|
922
|
-
* Reads the hook path from git.worktree_post_create in preferences.
|
|
923
|
-
* Pass hookPath directly to bypass preference loading (useful for testing).
|
|
924
|
-
*/
|
|
925
|
-
export function runWorktreePostCreateHook(
|
|
926
|
-
sourceDir: string,
|
|
927
|
-
worktreeDir: string,
|
|
928
|
-
hookPath?: string,
|
|
929
|
-
): string | null {
|
|
930
|
-
if (hookPath === undefined) {
|
|
931
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
932
|
-
hookPath = prefs?.worktree_post_create;
|
|
933
|
-
}
|
|
934
|
-
if (!hookPath) return null;
|
|
935
|
-
|
|
936
|
-
// Resolve relative paths against the source project root.
|
|
937
|
-
// On Windows, convert 8.3 short paths (e.g. RUNNER~1) to long paths
|
|
938
|
-
// so execFileSync can locate the file correctly.
|
|
939
|
-
let resolved = isAbsolute(hookPath) ? hookPath : join(sourceDir, hookPath);
|
|
940
|
-
if (!existsSync(resolved)) {
|
|
941
|
-
return `Worktree post-create hook not found: ${resolved}`;
|
|
942
|
-
}
|
|
943
|
-
if (process.platform === "win32") {
|
|
944
|
-
try { resolved = realpathSync.native(resolved); } catch (err) { /* keep original */
|
|
945
|
-
logWarning("worktree", `realpath failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
try {
|
|
950
|
-
// .bat/.cmd files on Windows require shell mode — execFileSync cannot
|
|
951
|
-
// spawn them directly (EINVAL).
|
|
952
|
-
const needsShell = process.platform === "win32" && /\.(bat|cmd)$/i.test(resolved);
|
|
953
|
-
execFileSync(resolved, [], {
|
|
954
|
-
cwd: worktreeDir,
|
|
955
|
-
env: {
|
|
956
|
-
...process.env,
|
|
957
|
-
SOURCE_DIR: sourceDir,
|
|
958
|
-
WORKTREE_DIR: worktreeDir,
|
|
959
|
-
},
|
|
960
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
961
|
-
encoding: "utf-8",
|
|
962
|
-
timeout: 30_000, // 30 second timeout
|
|
963
|
-
shell: needsShell,
|
|
964
|
-
});
|
|
965
|
-
return null;
|
|
966
|
-
} catch (err) {
|
|
967
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
968
|
-
return `Worktree post-create hook failed: ${msg}`;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
916
|
+
export { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
|
|
971
917
|
|
|
972
918
|
// ─── Auto-Worktree Branch Naming ───────────────────────────────────────────
|
|
973
919
|
|
|
@@ -13,7 +13,7 @@ import { mkdtempSync, mkdirSync, rmSync, existsSync, writeFileSync, readFileSync
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
15
|
|
|
16
|
-
import { runWorktreePostCreateHook } from "../
|
|
16
|
+
import { runWorktreePostCreateHook } from "../worktree-post-create-hook.ts";
|
|
17
17
|
|
|
18
18
|
function makeTmpDir(): string {
|
|
19
19
|
return mkdtempSync(join(tmpdir(), "gsd-wt-hook-test-"));
|
|
@@ -45,10 +45,150 @@ function writeNodeHookScript(filePath: string, code: string): void {
|
|
|
45
45
|
test("returns null when no hook path is provided", () => {
|
|
46
46
|
const src = makeTmpDir();
|
|
47
47
|
const wt = makeTmpDir();
|
|
48
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
48
49
|
try {
|
|
50
|
+
process.env.GSD_HOME = join(src, "empty-gsd-home");
|
|
49
51
|
const result = runWorktreePostCreateHook(src, wt, undefined);
|
|
50
52
|
assert.equal(result, null);
|
|
51
53
|
} finally {
|
|
54
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
55
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
56
|
+
rmSync(src, { recursive: true, force: true });
|
|
57
|
+
rmSync(wt, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("reads git.worktree_post_create from project preferences without loading auto-worktree", () => {
|
|
62
|
+
const src = makeTmpDir();
|
|
63
|
+
const wt = makeTmpDir();
|
|
64
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
65
|
+
try {
|
|
66
|
+
process.env.GSD_HOME = join(src, "empty-gsd-home");
|
|
67
|
+
const hooksDir = join(src, ".gsd", "hooks");
|
|
68
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
69
|
+
writeFileSync(
|
|
70
|
+
join(src, ".gsd", "PREFERENCES.md"),
|
|
71
|
+
[
|
|
72
|
+
"---",
|
|
73
|
+
"git:",
|
|
74
|
+
` worktree_post_create: ${hookPath(".gsd/hooks/post-create")}`,
|
|
75
|
+
"---",
|
|
76
|
+
"",
|
|
77
|
+
].join("\n"),
|
|
78
|
+
);
|
|
79
|
+
const hookFile = hookPath(join(hooksDir, "post-create"));
|
|
80
|
+
writeNodeHookScript(
|
|
81
|
+
hookFile,
|
|
82
|
+
`require("fs").writeFileSync(require("path").join(process.env.WORKTREE_DIR, "configured-hook-ran"), "ok");`,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const result = runWorktreePostCreateHook(src, wt);
|
|
86
|
+
|
|
87
|
+
assert.equal(result, null);
|
|
88
|
+
assert.ok(existsSync(join(wt, "configured-hook-ran")), "configured hook should run");
|
|
89
|
+
} finally {
|
|
90
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
91
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
92
|
+
rmSync(src, { recursive: true, force: true });
|
|
93
|
+
rmSync(wt, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("prefers canonical project PREFERENCES.md over legacy lowercase preferences.md", () => {
|
|
98
|
+
const src = makeTmpDir();
|
|
99
|
+
const wt = makeTmpDir();
|
|
100
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
101
|
+
try {
|
|
102
|
+
process.env.GSD_HOME = join(src, "empty-gsd-home");
|
|
103
|
+
const hooksDir = join(src, ".gsd", "hooks");
|
|
104
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
105
|
+
writeFileSync(
|
|
106
|
+
join(src, ".gsd", "preferences.md"),
|
|
107
|
+
[
|
|
108
|
+
"---",
|
|
109
|
+
"git:",
|
|
110
|
+
` worktree_post_create: ${hookPath(".gsd/hooks/legacy")}`,
|
|
111
|
+
"---",
|
|
112
|
+
"",
|
|
113
|
+
].join("\n"),
|
|
114
|
+
);
|
|
115
|
+
writeFileSync(
|
|
116
|
+
join(src, ".gsd", "PREFERENCES.md"),
|
|
117
|
+
[
|
|
118
|
+
"---",
|
|
119
|
+
"git:",
|
|
120
|
+
` worktree_post_create: ${hookPath(".gsd/hooks/canonical")}`,
|
|
121
|
+
"---",
|
|
122
|
+
"",
|
|
123
|
+
].join("\n"),
|
|
124
|
+
);
|
|
125
|
+
writeNodeHookScript(
|
|
126
|
+
hookPath(join(hooksDir, "canonical")),
|
|
127
|
+
`require("fs").writeFileSync(require("path").join(process.env.WORKTREE_DIR, "configured-hook-ran"), "canonical");`,
|
|
128
|
+
);
|
|
129
|
+
writeNodeHookScript(
|
|
130
|
+
hookPath(join(hooksDir, "legacy")),
|
|
131
|
+
`require("fs").writeFileSync(require("path").join(process.env.WORKTREE_DIR, "configured-hook-ran"), "legacy");`,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const result = runWorktreePostCreateHook(src, wt);
|
|
135
|
+
|
|
136
|
+
assert.equal(result, null);
|
|
137
|
+
assert.equal(readFileSync(join(wt, "configured-hook-ran"), "utf-8"), "canonical");
|
|
138
|
+
} finally {
|
|
139
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
140
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
141
|
+
rmSync(src, { recursive: true, force: true });
|
|
142
|
+
rmSync(wt, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("prefers canonical global PREFERENCES.md over legacy lowercase preferences.md", () => {
|
|
147
|
+
const src = makeTmpDir();
|
|
148
|
+
const wt = makeTmpDir();
|
|
149
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
150
|
+
try {
|
|
151
|
+
const gsdHomeDir = join(src, "global-gsd-home");
|
|
152
|
+
process.env.GSD_HOME = gsdHomeDir;
|
|
153
|
+
const hooksDir = join(src, ".gsd", "hooks");
|
|
154
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
155
|
+
mkdirSync(gsdHomeDir, { recursive: true });
|
|
156
|
+
writeFileSync(
|
|
157
|
+
join(gsdHomeDir, "preferences.md"),
|
|
158
|
+
[
|
|
159
|
+
"---",
|
|
160
|
+
"git:",
|
|
161
|
+
` worktree_post_create: ${hookPath(".gsd/hooks/legacy")}`,
|
|
162
|
+
"---",
|
|
163
|
+
"",
|
|
164
|
+
].join("\n"),
|
|
165
|
+
);
|
|
166
|
+
writeFileSync(
|
|
167
|
+
join(gsdHomeDir, "PREFERENCES.md"),
|
|
168
|
+
[
|
|
169
|
+
"---",
|
|
170
|
+
"git:",
|
|
171
|
+
` worktree_post_create: ${hookPath(".gsd/hooks/canonical")}`,
|
|
172
|
+
"---",
|
|
173
|
+
"",
|
|
174
|
+
].join("\n"),
|
|
175
|
+
);
|
|
176
|
+
writeNodeHookScript(
|
|
177
|
+
hookPath(join(hooksDir, "canonical")),
|
|
178
|
+
`require("fs").writeFileSync(require("path").join(process.env.WORKTREE_DIR, "configured-hook-ran"), "canonical");`,
|
|
179
|
+
);
|
|
180
|
+
writeNodeHookScript(
|
|
181
|
+
hookPath(join(hooksDir, "legacy")),
|
|
182
|
+
`require("fs").writeFileSync(require("path").join(process.env.WORKTREE_DIR, "configured-hook-ran"), "legacy");`,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const result = runWorktreePostCreateHook(src, wt);
|
|
186
|
+
|
|
187
|
+
assert.equal(result, null);
|
|
188
|
+
assert.equal(readFileSync(join(wt, "configured-hook-ran"), "utf-8"), "canonical");
|
|
189
|
+
} finally {
|
|
190
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
191
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
52
192
|
rmSync(src, { recursive: true, force: true });
|
|
53
193
|
rmSync(wt, { recursive: true, force: true });
|
|
54
194
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Lightweight worktree post-create hook runner.
|
|
3
|
+
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { isAbsolute, join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
10
|
+
|
|
11
|
+
import { gsdHome } from "./gsd-home.js";
|
|
12
|
+
import { gsdRoot } from "./paths.js";
|
|
13
|
+
|
|
14
|
+
function readPreferencesObject(path: string): Record<string, unknown> | null {
|
|
15
|
+
if (!existsSync(path)) return null;
|
|
16
|
+
|
|
17
|
+
const content = readFileSync(path, "utf-8");
|
|
18
|
+
try {
|
|
19
|
+
const startMarker = content.startsWith("---\r\n") ? "---\r\n" : "---\n";
|
|
20
|
+
if (content.startsWith(startMarker)) {
|
|
21
|
+
const searchStart = startMarker.length;
|
|
22
|
+
const endIdx = content.indexOf("\n---", searchStart);
|
|
23
|
+
if (endIdx === -1) return null;
|
|
24
|
+
|
|
25
|
+
const parsed = parseYaml(content.slice(searchStart, endIdx).replace(/\r/g, ""));
|
|
26
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
27
|
+
? parsed as Record<string, unknown>
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const gitLines: string[] = [];
|
|
32
|
+
let inGitSection = false;
|
|
33
|
+
for (const rawLine of content.split("\n")) {
|
|
34
|
+
const line = rawLine.replace(/\r$/, "");
|
|
35
|
+
const heading = line.match(/^##\s+(.+)$/);
|
|
36
|
+
if (heading) {
|
|
37
|
+
inGitSection = heading[1].trim().toLowerCase().replace(/\s+/g, "_") === "git";
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (inGitSection && line.trim() && !line.trimStart().startsWith("#")) {
|
|
41
|
+
gitLines.push(line.replace(/^\s*-\s*/, ""));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (gitLines.length === 0) return null;
|
|
45
|
+
|
|
46
|
+
const parsed = parseYaml(gitLines.join("\n"));
|
|
47
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
48
|
+
? { git: parsed as Record<string, unknown> }
|
|
49
|
+
: null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extractHookPath(preferences: Record<string, unknown> | null): string | null {
|
|
56
|
+
const git = preferences?.git;
|
|
57
|
+
if (!git || typeof git !== "object" || Array.isArray(git)) return null;
|
|
58
|
+
const hookPath = (git as Record<string, unknown>).worktree_post_create;
|
|
59
|
+
return typeof hookPath === "string" && hookPath.trim() ? hookPath : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveConfiguredHookPath(sourceDir: string): string | null {
|
|
63
|
+
const paths = [
|
|
64
|
+
join(homedir(), ".pi", "agent", "gsd-preferences.md"),
|
|
65
|
+
join(gsdHome(), "preferences.md"),
|
|
66
|
+
join(gsdHome(), "PREFERENCES.md"),
|
|
67
|
+
join(gsdRoot(sourceDir), "preferences.md"),
|
|
68
|
+
join(gsdRoot(sourceDir), "PREFERENCES.md"),
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
let hookPath: string | null = null;
|
|
72
|
+
for (const path of paths) {
|
|
73
|
+
hookPath = extractHookPath(readPreferencesObject(path)) ?? hookPath;
|
|
74
|
+
}
|
|
75
|
+
return hookPath;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run the user-configured post-create hook script after worktree creation.
|
|
80
|
+
* The script receives SOURCE_DIR and WORKTREE_DIR as environment variables.
|
|
81
|
+
* Failure is non-fatal -- returns the error message or null on success.
|
|
82
|
+
*
|
|
83
|
+
* Reads git.worktree_post_create from effective global/project preferences
|
|
84
|
+
* unless hookPath is provided directly.
|
|
85
|
+
*/
|
|
86
|
+
export function runWorktreePostCreateHook(
|
|
87
|
+
sourceDir: string,
|
|
88
|
+
worktreeDir: string,
|
|
89
|
+
hookPath?: string,
|
|
90
|
+
): string | null {
|
|
91
|
+
if (hookPath === undefined) {
|
|
92
|
+
hookPath = resolveConfiguredHookPath(sourceDir) ?? undefined;
|
|
93
|
+
}
|
|
94
|
+
if (!hookPath) return null;
|
|
95
|
+
|
|
96
|
+
let resolved = isAbsolute(hookPath) ? hookPath : join(sourceDir, hookPath);
|
|
97
|
+
if (!existsSync(resolved)) {
|
|
98
|
+
return `Worktree post-create hook not found: ${resolved}`;
|
|
99
|
+
}
|
|
100
|
+
if (process.platform === "win32") {
|
|
101
|
+
try {
|
|
102
|
+
resolved = realpathSync.native(resolved);
|
|
103
|
+
} catch {
|
|
104
|
+
// Keep the original path; the exec error below will include the failure.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const needsShell = process.platform === "win32" && /\.(bat|cmd)$/i.test(resolved);
|
|
110
|
+
execFileSync(resolved, [], {
|
|
111
|
+
cwd: worktreeDir,
|
|
112
|
+
env: {
|
|
113
|
+
...process.env,
|
|
114
|
+
SOURCE_DIR: sourceDir,
|
|
115
|
+
WORKTREE_DIR: worktreeDir,
|
|
116
|
+
},
|
|
117
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
118
|
+
encoding: "utf-8",
|
|
119
|
+
timeout: 30_000,
|
|
120
|
+
shell: needsShell,
|
|
121
|
+
});
|
|
122
|
+
return null;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
125
|
+
return `Worktree post-create hook failed: ${msg}`;
|
|
126
|
+
}
|
|
127
|
+
}
|