gsd-pi 2.78.1-dev.84a383f51 → 2.78.1-dev.8a893322c
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/README.md +1 -0
- package/dist/bundled-resource-path.d.ts +7 -0
- package/dist/bundled-resource-path.js +34 -2
- package/dist/claude-cli-check.js +18 -6
- package/dist/headless-query.js +21 -6
- package/dist/loader.js +2 -3
- package/dist/resource-loader.js +2 -8
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/readiness.js +19 -7
- package/dist/resources/extensions/google-search/index.js +2 -6
- package/dist/resources/extensions/gsd/auto/phases.js +3 -11
- package/dist/resources/extensions/gsd/auto/session.js +2 -6
- package/dist/resources/extensions/gsd/auto-dashboard.js +3 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +18 -6
- package/dist/resources/extensions/gsd/auto-prompts.js +63 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +30 -13
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +19 -1
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +22 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -0
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +84 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +8 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +8 -0
- package/dist/resources/extensions/gsd/commands-config.js +3 -2
- package/dist/resources/extensions/gsd/commands-extensions.js +46 -3
- package/dist/resources/extensions/gsd/commands-handlers.js +3 -2
- package/dist/resources/extensions/gsd/commands-worktree.js +309 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +6 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +2 -1
- package/dist/resources/extensions/gsd/forensics.js +8 -6
- package/dist/resources/extensions/gsd/guided-flow.js +2 -1
- package/dist/resources/extensions/gsd/home-dir.js +16 -0
- package/dist/resources/extensions/gsd/key-manager.js +2 -1
- package/dist/resources/extensions/gsd/migrate/command.js +3 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +10 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +10 -0
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +10 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +29 -4
- package/dist/resources/extensions/gsd/worktree-manager.js +20 -1
- package/dist/resources/extensions/gsd/worktree-resolver.js +4 -13
- package/dist/resources/extensions/gsd/worktree-root.js +124 -0
- package/dist/resources/extensions/gsd/worktree.js +4 -115
- package/dist/resources/extensions/mcp-client/index.js +0 -6
- package/dist/resources/extensions/ollama/index.js +15 -2
- package/dist/resources/extensions/ollama/model-capabilities.js +31 -0
- package/dist/resources/extensions/ollama/ollama-client.js +40 -4
- package/dist/resources/extensions/subagent/index.js +324 -178
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- 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/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 +17 -17
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- 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/welcome-screen.js +27 -1
- package/dist/worktree-cli.d.ts +1 -0
- package/dist/worktree-cli.js +9 -3
- package/package.json +1 -3
- package/packages/mcp-server/src/workflow-tools.test.ts +52 -0
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +20 -7
- package/src/resources/extensions/google-search/index.ts +2 -9
- package/src/resources/extensions/gsd/auto/phases.ts +3 -11
- package/src/resources/extensions/gsd/auto/session.ts +2 -6
- package/src/resources/extensions/gsd/auto-dashboard.ts +3 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -6
- package/src/resources/extensions/gsd/auto-prompts.ts +60 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +44 -12
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +19 -0
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +20 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +103 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +8 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
- package/src/resources/extensions/gsd/commands-config.ts +3 -2
- package/src/resources/extensions/gsd/commands-extensions.ts +43 -3
- package/src/resources/extensions/gsd/commands-handlers.ts +3 -2
- package/src/resources/extensions/gsd/commands-worktree.ts +383 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +6 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +2 -1
- package/src/resources/extensions/gsd/forensics.ts +10 -5
- package/src/resources/extensions/gsd/guided-flow.ts +2 -1
- package/src/resources/extensions/gsd/home-dir.ts +19 -0
- package/src/resources/extensions/gsd/journal.ts +4 -1
- package/src/resources/extensions/gsd/key-manager.ts +2 -1
- package/src/resources/extensions/gsd/migrate/command.ts +3 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +10 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +10 -0
- package/src/resources/extensions/gsd/prompts/refine-slice.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +50 -27
- package/src/resources/extensions/gsd/tests/commands-extensions-version-compare.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +25 -65
- package/src/resources/extensions/gsd/tests/home-dir.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +50 -1
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +17 -1
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +34 -33
- package/src/resources/extensions/gsd/tests/worktree.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +116 -1
- package/src/resources/extensions/gsd/unit-context-manifest.ts +36 -4
- package/src/resources/extensions/gsd/worktree-manager.ts +40 -1
- package/src/resources/extensions/gsd/worktree-resolver.ts +4 -14
- package/src/resources/extensions/gsd/worktree-root.ts +144 -0
- package/src/resources/extensions/gsd/worktree.ts +8 -119
- package/src/resources/extensions/mcp-client/index.ts +0 -7
- package/src/resources/extensions/ollama/index.ts +16 -2
- package/src/resources/extensions/ollama/model-capabilities.ts +34 -0
- package/src/resources/extensions/ollama/ollama-client.ts +41 -4
- package/src/resources/extensions/ollama/tests/model-capabilities.test.ts +96 -0
- package/src/resources/extensions/ollama/tests/ollama-client-timeout-env.test.ts +147 -0
- package/src/resources/extensions/subagent/index.ts +165 -7
- /package/dist/web/standalone/.next/static/{UF5VF4F1tB0miEtJS7LyX → QK8fABiGPmonfTgboN0Y9}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{UF5VF4F1tB0miEtJS7LyX → QK8fABiGPmonfTgboN0Y9}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -471,6 +471,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|
|
471
471
|
| `/gsd logs` | Browse activity, debug, and metrics logs |
|
|
472
472
|
| `/gsd export --html` | Generate HTML report for current or completed milestone |
|
|
473
473
|
| `/worktree` (`/wt`) | Git worktree lifecycle — create, switch, merge, remove |
|
|
474
|
+
| `/gsd worktree` (`/gsd wt`) | TUI worktree management — list, merge, clean, remove with safety checks |
|
|
474
475
|
| `/voice` | Toggle real-time speech-to-text (macOS, Linux) |
|
|
475
476
|
| `/exit` | Graceful shutdown — saves session state before exiting |
|
|
476
477
|
| `/kill` | Kill GSD process immediately |
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export type FileExists = (path: string) => boolean;
|
|
2
|
+
export declare function resolvePackageRoot(importUrl: string): string;
|
|
3
|
+
export declare function hasCompleteBundledResources(resourcesDir: string, fileExists?: FileExists): boolean;
|
|
4
|
+
export declare function resolveBundledResourcesDirFromPackageRoot(packageRoot: string, fileExists?: FileExists): string;
|
|
5
|
+
export declare function resolveBundledResourcesDir(importUrl: string, fileExists?: FileExists): string;
|
|
6
|
+
export declare function resolveBundledResource(importUrl: string, ...segments: string[]): string;
|
|
7
|
+
export declare function resolveBundledGsdExtensionModule(importUrl: string, moduleFile: string, fileExists?: FileExists): string;
|
|
1
8
|
/**
|
|
2
9
|
* Resolve bundled raw resource files from the package root.
|
|
3
10
|
*
|
|
@@ -1,5 +1,38 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { dirname, join, resolve } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
export function resolvePackageRoot(importUrl) {
|
|
5
|
+
const moduleDir = dirname(fileURLToPath(importUrl));
|
|
6
|
+
return resolve(moduleDir, "..");
|
|
7
|
+
}
|
|
8
|
+
export function hasCompleteBundledResources(resourcesDir, fileExists = existsSync) {
|
|
9
|
+
return fileExists(join(resourcesDir, "agents")) &&
|
|
10
|
+
fileExists(join(resourcesDir, "extensions"));
|
|
11
|
+
}
|
|
12
|
+
export function resolveBundledResourcesDirFromPackageRoot(packageRoot, fileExists = existsSync) {
|
|
13
|
+
const distResources = join(packageRoot, "dist", "resources");
|
|
14
|
+
const srcResources = join(packageRoot, "src", "resources");
|
|
15
|
+
return hasCompleteBundledResources(distResources, fileExists)
|
|
16
|
+
? distResources
|
|
17
|
+
: srcResources;
|
|
18
|
+
}
|
|
19
|
+
export function resolveBundledResourcesDir(importUrl, fileExists = existsSync) {
|
|
20
|
+
return resolveBundledResourcesDirFromPackageRoot(resolvePackageRoot(importUrl), fileExists);
|
|
21
|
+
}
|
|
22
|
+
export function resolveBundledResource(importUrl, ...segments) {
|
|
23
|
+
return join(resolveBundledResourcesDir(importUrl), ...segments);
|
|
24
|
+
}
|
|
25
|
+
export function resolveBundledGsdExtensionModule(importUrl, moduleFile, fileExists = existsSync) {
|
|
26
|
+
const packageRoot = resolvePackageRoot(importUrl);
|
|
27
|
+
const distResources = join(packageRoot, "dist", "resources");
|
|
28
|
+
const jsFile = moduleFile.replace(/\.ts$/, ".js");
|
|
29
|
+
const distModule = join(distResources, "extensions", "gsd", jsFile);
|
|
30
|
+
if (hasCompleteBundledResources(distResources, fileExists) && fileExists(distModule)) {
|
|
31
|
+
return distModule;
|
|
32
|
+
}
|
|
33
|
+
const tsFile = moduleFile.replace(/\.js$/, ".ts");
|
|
34
|
+
return join(packageRoot, "src", "resources", "extensions", "gsd", tsFile);
|
|
35
|
+
}
|
|
3
36
|
/**
|
|
4
37
|
* Resolve bundled raw resource files from the package root.
|
|
5
38
|
*
|
|
@@ -8,7 +41,6 @@ import { fileURLToPath } from "node:url";
|
|
|
8
41
|
* `src/resources/**`, not next to the compiled entry point.
|
|
9
42
|
*/
|
|
10
43
|
export function resolveBundledSourceResource(importUrl, ...segments) {
|
|
11
|
-
const
|
|
12
|
-
const packageRoot = resolve(moduleDir, "..");
|
|
44
|
+
const packageRoot = resolvePackageRoot(importUrl);
|
|
13
45
|
return join(packageRoot, "src", "resources", ...segments);
|
|
14
46
|
}
|
package/dist/claude-cli-check.js
CHANGED
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
// Set GSD_CLAUDE_DEBUG=1 to log probe output to stderr. Useful when
|
|
6
6
|
// diagnosing platform-specific detection failures (Issue #4997).
|
|
7
7
|
import { execFileSync } from 'node:child_process';
|
|
8
|
+
/**
|
|
9
|
+
* Spawn the Claude CLI without triggering Node's DEP0190.
|
|
10
|
+
*
|
|
11
|
+
* Passing `args` together with `shell: true` is deprecated in Node 22+
|
|
12
|
+
* because the args are concatenated into the command string without
|
|
13
|
+
* escaping. On Windows we still need a shell to resolve `.cmd` shims, so
|
|
14
|
+
* we invoke `cmd /c <command> <args...>` explicitly. On POSIX we don't
|
|
15
|
+
* need a shell at all.
|
|
16
|
+
*/
|
|
17
|
+
function spawnClaude(command, args, opts) {
|
|
18
|
+
if (process.platform === 'win32') {
|
|
19
|
+
return execFileSync('cmd', ['/c', command, ...args], opts);
|
|
20
|
+
}
|
|
21
|
+
return execFileSync(command, args, opts);
|
|
22
|
+
}
|
|
8
23
|
/**
|
|
9
24
|
* Platform-correct binary name for the Claude Code CLI.
|
|
10
25
|
*
|
|
@@ -44,10 +59,9 @@ function debugLog(...parts) {
|
|
|
44
59
|
function findWorkingCommand() {
|
|
45
60
|
for (const command of CLAUDE_COMMAND_CANDIDATES) {
|
|
46
61
|
try {
|
|
47
|
-
|
|
62
|
+
spawnClaude(command, ['--version'], {
|
|
48
63
|
timeout: VERSION_TIMEOUT_MS,
|
|
49
64
|
stdio: 'pipe',
|
|
50
|
-
shell: process.platform === 'win32',
|
|
51
65
|
});
|
|
52
66
|
debugLog('version probe ok via', command);
|
|
53
67
|
return command;
|
|
@@ -93,10 +107,9 @@ function parseAuthStatus(output) {
|
|
|
93
107
|
function probeAuth(command) {
|
|
94
108
|
// Try --json first (newer CLIs).
|
|
95
109
|
try {
|
|
96
|
-
const out =
|
|
110
|
+
const out = spawnClaude(command, ['auth', 'status', '--json'], {
|
|
97
111
|
timeout: AUTH_TIMEOUT_MS,
|
|
98
112
|
stdio: 'pipe',
|
|
99
|
-
shell: process.platform === 'win32',
|
|
100
113
|
}).toString();
|
|
101
114
|
debugLog('auth status --json output:', out.slice(0, 200));
|
|
102
115
|
const parsed = parseAuthStatus(out);
|
|
@@ -108,10 +121,9 @@ function probeAuth(command) {
|
|
|
108
121
|
}
|
|
109
122
|
// Fallback: plain `auth status` (older CLIs that don't accept --json).
|
|
110
123
|
try {
|
|
111
|
-
const out =
|
|
124
|
+
const out = spawnClaude(command, ['auth', 'status'], {
|
|
112
125
|
timeout: AUTH_TIMEOUT_MS,
|
|
113
126
|
stdio: 'pipe',
|
|
114
|
-
shell: process.platform === 'win32',
|
|
115
127
|
}).toString();
|
|
116
128
|
debugLog('auth status output:', out.slice(0, 200));
|
|
117
129
|
return parseAuthStatus(out);
|
package/dist/headless-query.js
CHANGED
|
@@ -17,7 +17,7 @@ import { createJiti } from '@mariozechner/jiti';
|
|
|
17
17
|
import { fileURLToPath } from 'node:url';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { homedir } from 'node:os';
|
|
20
|
-
import {
|
|
20
|
+
import { resolveBundledGsdExtensionModule } from './bundled-resource-path.js';
|
|
21
21
|
const jiti = createJiti(fileURLToPath(import.meta.url), { interopDefault: true, debug: false });
|
|
22
22
|
const { existsSync } = await import('node:fs');
|
|
23
23
|
/**
|
|
@@ -30,7 +30,8 @@ const { existsSync } = await import('node:fs');
|
|
|
30
30
|
* #3471 contract can be exercised in tests without spawning a subprocess.
|
|
31
31
|
*/
|
|
32
32
|
export function resolveGsdAgentExtensionsDir(env = process.env) {
|
|
33
|
-
|
|
33
|
+
const agentRoot = env.GSD_AGENT_DIR || join(env.GSD_HOME || join(homedir(), '.gsd'), 'agent');
|
|
34
|
+
return join(agentRoot, 'extensions', 'gsd');
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
37
|
* Decide whether headless-query should load extensions from the agent
|
|
@@ -41,13 +42,27 @@ export function shouldUseAgentExtensionsDir(opts) {
|
|
|
41
42
|
const env = opts.env ?? process.env;
|
|
42
43
|
const fileExists = opts.fileExists ?? existsSync;
|
|
43
44
|
const agentDir = resolveGsdAgentExtensionsDir(env);
|
|
44
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
agentDir,
|
|
47
|
+
useAgentDir: fileExists(join(agentDir, 'state.ts')) || fileExists(join(agentDir, 'state.js')),
|
|
48
|
+
};
|
|
45
49
|
}
|
|
46
50
|
const agentExtensionsDir = resolveGsdAgentExtensionsDir();
|
|
47
|
-
const useAgentDir =
|
|
51
|
+
const { useAgentDir } = shouldUseAgentExtensionsDir({ env: process.env });
|
|
48
52
|
const gsdExtensionPath = (...segments) => useAgentDir
|
|
49
|
-
?
|
|
50
|
-
:
|
|
53
|
+
? resolveAgentExtensionModule(agentExtensionsDir, segments)
|
|
54
|
+
: resolveBundledGsdExtensionModule(import.meta.url, segments.join('/'));
|
|
55
|
+
function resolveAgentExtensionModule(agentDir, segments) {
|
|
56
|
+
const requested = join(agentDir, ...segments);
|
|
57
|
+
if (existsSync(requested))
|
|
58
|
+
return requested;
|
|
59
|
+
if (segments.length === 1 && segments[0].endsWith('.ts')) {
|
|
60
|
+
const jsPath = join(agentDir, segments[0].replace(/\.ts$/, '.js'));
|
|
61
|
+
if (existsSync(jsPath))
|
|
62
|
+
return jsPath;
|
|
63
|
+
}
|
|
64
|
+
return requested;
|
|
65
|
+
}
|
|
51
66
|
async function loadExtensionModules() {
|
|
52
67
|
const stateModule = await jiti.import(gsdExtensionPath('state.ts'), {});
|
|
53
68
|
const dispatchModule = await jiti.import(gsdExtensionPath('auto-dispatch.ts'), {});
|
package/dist/loader.js
CHANGED
|
@@ -60,6 +60,7 @@ if (firstArg === '--help' || firstArg === '-h') {
|
|
|
60
60
|
import { agentDir, appRoot } from './app-paths.js';
|
|
61
61
|
import { applyRtkProcessEnv } from './rtk-shared.js';
|
|
62
62
|
import { serializeBundledExtensionPaths } from './bundled-extension-paths.js';
|
|
63
|
+
import { resolveBundledResourcesDirFromPackageRoot } from './bundled-resource-path.js';
|
|
63
64
|
import { discoverExtensionEntryPaths } from './extension-discovery.js';
|
|
64
65
|
import { loadRegistry, readManifestFromEntryPath, isExtensionEnabled } from './extension-registry.js';
|
|
65
66
|
import { renderLogo } from './logo.js';
|
|
@@ -118,9 +119,7 @@ process.env.GSD_BIN_PATH = process.argv[1];
|
|
|
118
119
|
// GSD_WORKFLOW_PATH — absolute path to bundled GSD-WORKFLOW.md, used by patched gsd extension
|
|
119
120
|
// when dispatching workflow prompts. Prefers dist/resources/ (stable, set at build time)
|
|
120
121
|
// over src/resources/ (live working tree) — see resource-loader.ts for rationale.
|
|
121
|
-
const
|
|
122
|
-
const srcRes = join(gsdRoot, 'src', 'resources');
|
|
123
|
-
const resourcesDir = existsSync(distRes) ? distRes : srcRes;
|
|
122
|
+
const resourcesDir = resolveBundledResourcesDirFromPackageRoot(gsdRoot);
|
|
124
123
|
process.env.GSD_WORKFLOW_PATH = join(resourcesDir, 'GSD-WORKFLOW.md');
|
|
125
124
|
// GSD_BUNDLED_EXTENSION_PATHS — dynamically discovered bundled extension entry points.
|
|
126
125
|
// Uses the shared discoverExtensionEntryPaths() to scan the bundled resources
|
package/dist/resource-loader.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { compareSemver } from './update-check.js';
|
|
7
7
|
import { discoverExtensionEntryPaths } from './extension-discovery.js';
|
|
8
8
|
import { loadRegistry, readManifestFromEntryPath, isExtensionEnabled, ensureRegistryEntries } from './extension-registry.js';
|
|
9
|
+
import { resolveBundledResourcesDirFromPackageRoot } from './bundled-resource-path.js';
|
|
9
10
|
let piCodingAgentModulePromise;
|
|
10
11
|
function loadPiCodingAgentModule() {
|
|
11
12
|
return (piCodingAgentModulePromise ??= import('@gsd/pi-coding-agent'));
|
|
@@ -19,14 +20,7 @@ function loadPiCodingAgentModule() {
|
|
|
19
20
|
// dist/resources/ is populated by the build step (`npm run copy-resources`) and
|
|
20
21
|
// reflects the built state, not the currently checked-out branch.
|
|
21
22
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
|
-
const
|
|
23
|
-
const srcResources = join(packageRoot, 'src', 'resources');
|
|
24
|
-
// Use dist/resources only if it has the full expected structure.
|
|
25
|
-
// A partial build (tsc without copy-resources) creates dist/resources/extensions/
|
|
26
|
-
// but not agents/ or skills/, causing initResources to sync from an incomplete source.
|
|
27
|
-
const resourcesDir = (existsSync(distResources) && existsSync(join(distResources, 'agents')))
|
|
28
|
-
? distResources
|
|
29
|
-
: srcResources;
|
|
23
|
+
const resourcesDir = resolveBundledResourcesDirFromPackageRoot(packageRoot);
|
|
30
24
|
const bundledExtensionsDir = join(resourcesDir, 'extensions');
|
|
31
25
|
const resourceVersionManifestName = 'managed-resources.json';
|
|
32
26
|
const resourceFingerprintFileName = '.managed-resources-content-hash';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
868d22f3f04d038e
|
|
@@ -14,6 +14,21 @@
|
|
|
14
14
|
* failures (Issue #4997).
|
|
15
15
|
*/
|
|
16
16
|
import { execFileSync } from "node:child_process";
|
|
17
|
+
/**
|
|
18
|
+
* Spawn the Claude CLI without triggering Node's DEP0190.
|
|
19
|
+
*
|
|
20
|
+
* Passing `args` together with `shell: true` is deprecated in Node 22+
|
|
21
|
+
* because the args are concatenated into the command string without
|
|
22
|
+
* escaping. On Windows we still need a shell to resolve `.cmd` shims, so
|
|
23
|
+
* we invoke `cmd /c <command> <args...>` explicitly. On POSIX we don't
|
|
24
|
+
* need a shell at all.
|
|
25
|
+
*/
|
|
26
|
+
function spawnClaude(command, args, opts) {
|
|
27
|
+
if (process.platform === "win32") {
|
|
28
|
+
return execFileSync("cmd", ["/c", command, ...args], opts);
|
|
29
|
+
}
|
|
30
|
+
return execFileSync(command, args, opts);
|
|
31
|
+
}
|
|
17
32
|
/**
|
|
18
33
|
* Candidate executable names for the Claude Code CLI.
|
|
19
34
|
*
|
|
@@ -43,7 +58,7 @@ function debugLog(...parts) {
|
|
|
43
58
|
* Find the first candidate that responds to `--version`. Returns the
|
|
44
59
|
* candidate name on success, null if none worked.
|
|
45
60
|
*
|
|
46
|
-
* On Windows with `
|
|
61
|
+
* On Windows with `cmd /c`, a missing candidate surfaces as a
|
|
47
62
|
* non-zero exit from cmd.exe rather than ENOENT — so we cannot rely on
|
|
48
63
|
* the error code to decide "try next". Treat any failure as "try next"
|
|
49
64
|
* for the version probe; the only thing that matters for binary
|
|
@@ -53,10 +68,9 @@ function debugLog(...parts) {
|
|
|
53
68
|
function findWorkingCommand() {
|
|
54
69
|
for (const command of CLAUDE_COMMAND_CANDIDATES) {
|
|
55
70
|
try {
|
|
56
|
-
|
|
71
|
+
spawnClaude(command, ["--version"], {
|
|
57
72
|
timeout: VERSION_TIMEOUT_MS,
|
|
58
73
|
stdio: "pipe",
|
|
59
|
-
shell: process.platform === "win32",
|
|
60
74
|
});
|
|
61
75
|
debugLog("version probe ok via", command);
|
|
62
76
|
return command;
|
|
@@ -103,10 +117,9 @@ function parseAuthStatus(output) {
|
|
|
103
117
|
function probeAuth(command) {
|
|
104
118
|
// Try --json first (newer CLIs).
|
|
105
119
|
try {
|
|
106
|
-
const out =
|
|
120
|
+
const out = spawnClaude(command, ["auth", "status", "--json"], {
|
|
107
121
|
timeout: AUTH_TIMEOUT_MS,
|
|
108
122
|
stdio: "pipe",
|
|
109
|
-
shell: process.platform === "win32",
|
|
110
123
|
}).toString();
|
|
111
124
|
debugLog("auth status --json output:", out.slice(0, 200));
|
|
112
125
|
const parsed = parseAuthStatus(out);
|
|
@@ -118,10 +131,9 @@ function probeAuth(command) {
|
|
|
118
131
|
}
|
|
119
132
|
// Fallback: plain `auth status` (older CLIs that don't accept --json).
|
|
120
133
|
try {
|
|
121
|
-
const out =
|
|
134
|
+
const out = spawnClaude(command, ["auth", "status"], {
|
|
122
135
|
timeout: AUTH_TIMEOUT_MS,
|
|
123
136
|
stdio: "pipe",
|
|
124
|
-
shell: process.platform === "win32",
|
|
125
137
|
}).toString();
|
|
126
138
|
debugLog("auth status output:", out.slice(0, 200));
|
|
127
139
|
return parseAuthStatus(out);
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
export default function (
|
|
2
|
-
|
|
3
|
-
ctx.ui.notify("google_search is being extracted to @gsd-extensions/google-search " +
|
|
4
|
-
"(not yet published to npm). This stub will be replaced once the " +
|
|
5
|
-
"package is available. No action needed for now.", "warning");
|
|
6
|
-
});
|
|
1
|
+
export default function (_pi) {
|
|
2
|
+
// Deprecation notice intentionally suppressed until @gsd-extensions/google-search ships.
|
|
7
3
|
}
|
|
@@ -11,6 +11,7 @@ import { MAX_RECOVERY_CHARS, BUDGET_THRESHOLDS, MAX_FINALIZE_TIMEOUTS, } from ".
|
|
|
11
11
|
import { detectStuck } from "./detect-stuck.js";
|
|
12
12
|
import { runUnit } from "./run-unit.js";
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
|
+
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
14
15
|
import { PROJECT_FILES, hasProjectFileInAncestor } from "../detection.js";
|
|
15
16
|
import { MergeConflictError } from "../git-service.js";
|
|
16
17
|
import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
|
|
@@ -48,11 +49,7 @@ export function resetSessionTimeoutState() {
|
|
|
48
49
|
* Exported for testing as _resolveReportBasePath.
|
|
49
50
|
*/
|
|
50
51
|
export function _resolveReportBasePath(s) {
|
|
51
|
-
|
|
52
|
-
// originalBasePath is falsy — prevents reports landing in the worktree (#3729).
|
|
53
|
-
const resolved = s.originalBasePath || s.basePath;
|
|
54
|
-
const markerIdx = resolved.indexOf("/.gsd/worktrees/");
|
|
55
|
-
return markerIdx !== -1 ? resolved.slice(0, markerIdx) : resolved;
|
|
52
|
+
return resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
|
|
56
53
|
}
|
|
57
54
|
/**
|
|
58
55
|
* Resolve the authoritative project base for dispatch guards.
|
|
@@ -60,12 +57,7 @@ export function _resolveReportBasePath(s) {
|
|
|
60
57
|
* unit is running inside an auto worktree.
|
|
61
58
|
*/
|
|
62
59
|
export function _resolveDispatchGuardBasePath(s) {
|
|
63
|
-
|
|
64
|
-
// originalBasePath is falsy — prevents guard checks running against the
|
|
65
|
-
// worktree instead of the project root (#3729).
|
|
66
|
-
const resolved = s.originalBasePath || s.basePath;
|
|
67
|
-
const markerIdx = resolved.indexOf("/.gsd/worktrees/");
|
|
68
|
-
return markerIdx !== -1 ? resolved.slice(0, markerIdx) : resolved;
|
|
60
|
+
return resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
|
|
69
61
|
}
|
|
70
62
|
const PLAN_V2_GATE_PHASES = new Set([
|
|
71
63
|
"executing",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* auto-session-encapsulation.test.ts enforce that auto.ts has no module-level
|
|
16
16
|
* `let` or `var` declarations.
|
|
17
17
|
*/
|
|
18
|
+
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
18
19
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
19
20
|
export const STUB_RECOVERY_THRESHOLD = 2;
|
|
20
21
|
export const NEW_SESSION_TIMEOUT_MS = 120_000;
|
|
@@ -155,12 +156,7 @@ export class AutoSession {
|
|
|
155
156
|
this.unitLifetimeDispatches.clear();
|
|
156
157
|
}
|
|
157
158
|
get lockBasePath() {
|
|
158
|
-
|
|
159
|
-
// Strip /.gsd/worktrees/ suffix if basePath is itself a worktree path
|
|
160
|
-
// to avoid reading/writing the lock inside the worktree (#3729).
|
|
161
|
-
const resolved = this.originalBasePath || this.basePath;
|
|
162
|
-
const markerIdx = resolved.indexOf("/.gsd/worktrees/");
|
|
163
|
-
return markerIdx !== -1 ? resolved.slice(0, markerIdx) : resolved;
|
|
159
|
+
return resolveWorktreeProjectRoot(this.basePath, this.originalBasePath);
|
|
164
160
|
}
|
|
165
161
|
reset() {
|
|
166
162
|
this.clearTimers();
|
|
@@ -10,6 +10,7 @@ import { getActiveHook } from "./post-unit-hooks.js";
|
|
|
10
10
|
import { getLedger, getProjectTotals } from "./metrics.js";
|
|
11
11
|
import { getErrorMessage } from "./error-utils.js";
|
|
12
12
|
import { nativeIsRepo } from "./native-git-bridge.js";
|
|
13
|
+
import { getHomeDir } from "./home-dir.js";
|
|
13
14
|
import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
|
|
14
15
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
15
16
|
import { execFileSync } from "node:child_process";
|
|
@@ -461,8 +462,8 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
461
462
|
let widgetPwd;
|
|
462
463
|
{
|
|
463
464
|
let fullPwd = process.cwd();
|
|
464
|
-
const widgetHome =
|
|
465
|
-
if (widgetHome && fullPwd.startsWith(widgetHome)) {
|
|
465
|
+
const widgetHome = getHomeDir();
|
|
466
|
+
if (widgetHome && (fullPwd === widgetHome || fullPwd.startsWith(widgetHome + "/") || fullPwd.startsWith(widgetHome + "\\"))) {
|
|
466
467
|
fullPwd = `~${fullPwd.slice(widgetHome.length)}`;
|
|
467
468
|
}
|
|
468
469
|
const parts = fullPwd.split("/");
|
|
@@ -655,14 +655,25 @@ export const DISPATCH_RULES = [
|
|
|
655
655
|
return null;
|
|
656
656
|
if (!state.activeSlice)
|
|
657
657
|
return null; // fall through
|
|
658
|
-
//
|
|
658
|
+
// Reactive dispatch is on by default when there are enough ready tasks to
|
|
659
|
+
// benefit from parallelism. Users opt out explicitly via
|
|
660
|
+
// `reactive_execution.enabled: false`. The downstream safety checks
|
|
661
|
+
// (graph ambiguity, ready-task count, conflict-free selection) still gate
|
|
662
|
+
// every actual dispatch, so the worst-case "default-on" outcome is the
|
|
663
|
+
// same fall-through to sequential execution as before.
|
|
659
664
|
const reactiveConfig = prefs?.reactive_execution;
|
|
660
|
-
if (
|
|
665
|
+
if (reactiveConfig?.enabled === false)
|
|
661
666
|
return null;
|
|
662
667
|
const sid = state.activeSlice.id;
|
|
663
668
|
const sTitle = state.activeSlice.title;
|
|
664
|
-
const maxParallel = reactiveConfig
|
|
665
|
-
const subagentModel = reactiveConfig
|
|
669
|
+
const maxParallel = reactiveConfig?.max_parallel ?? 2;
|
|
670
|
+
const subagentModel = reactiveConfig?.subagent_model ?? resolveModelWithFallbacksForUnit("subagent")?.primary;
|
|
671
|
+
// Default-on safety threshold: only activate reactive dispatch when at
|
|
672
|
+
// least N tasks are ready. Users who explicitly enabled reactive_execution
|
|
673
|
+
// keep the legacy threshold of 2 (matches the prior "any parallelism is
|
|
674
|
+
// better than none" intent). Default-on installs require >=3 to avoid
|
|
675
|
+
// surprising users with parallelism on small slices.
|
|
676
|
+
const minReadyTasksForReactive = reactiveConfig?.enabled === true ? 2 : 3;
|
|
666
677
|
// Dry-run mode: max_parallel=1 means graph is derived and logged but
|
|
667
678
|
// execution remains sequential
|
|
668
679
|
if (maxParallel <= 1)
|
|
@@ -678,8 +689,9 @@ export const DISPATCH_RULES = [
|
|
|
678
689
|
return null;
|
|
679
690
|
const completed = new Set(graph.filter((n) => n.done).map((n) => n.id));
|
|
680
691
|
const readyIds = getReadyTasks(graph, completed, new Set());
|
|
681
|
-
// Only activate reactive dispatch when
|
|
682
|
-
|
|
692
|
+
// Only activate reactive dispatch when enough tasks are ready.
|
|
693
|
+
// Threshold is 2 when explicitly opted in, 3 when default-on.
|
|
694
|
+
if (readyIds.length < minReadyTasksForReactive)
|
|
683
695
|
return null;
|
|
684
696
|
const uokFlags = resolveUokFlags(prefs);
|
|
685
697
|
const selected = uokFlags.executionGraph
|
|
@@ -23,7 +23,7 @@ import { composeInlinedContext } from "./unit-context-composer.js";
|
|
|
23
23
|
import { logWarning } from "./workflow-logger.js";
|
|
24
24
|
import { inlineGraphSubgraph } from "./graph-context.js";
|
|
25
25
|
import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
|
|
26
|
-
import { warnIfManifestHasMissingSkills } from "./skill-manifest.js";
|
|
26
|
+
import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
|
|
27
27
|
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
28
28
|
/**
|
|
29
29
|
* Historical static ceiling for the preamble cap. Kept as an upper bound even
|
|
@@ -675,6 +675,26 @@ function formatSkillActivationBlock(skillNames) {
|
|
|
675
675
|
const calls = safe.map(name => `Call Skill({ skill: '${name}' })`).join('. ');
|
|
676
676
|
return `<skill_activation>${calls}.</skill_activation>`;
|
|
677
677
|
}
|
|
678
|
+
/**
|
|
679
|
+
* Manifest-driven recommendations block — informational only, does NOT
|
|
680
|
+
* auto-invoke. Lists per-unit-type skills that are installed but not already
|
|
681
|
+
* activated by explicit user intent (always_use_skills / prefer_skills /
|
|
682
|
+
* skill_rules / task-plan skills_used). Surfaces relevant skills to the
|
|
683
|
+
* model so they can be invoked when the model judges them useful.
|
|
684
|
+
*
|
|
685
|
+
* This is the additive complement to the existing activation directive:
|
|
686
|
+
* activation force-invokes (explicit intent), recommendations remind
|
|
687
|
+
* (manifest defaults). User intent is preserved as the stronger signal
|
|
688
|
+
* (RFC #4779 design principle); this block only adds visibility.
|
|
689
|
+
*/
|
|
690
|
+
function formatSkillRecommendationsBlock(unitType, skillNames) {
|
|
691
|
+
if (!unitType)
|
|
692
|
+
return "";
|
|
693
|
+
const safe = skillNames.filter(name => SAFE_SKILL_NAME.test(name));
|
|
694
|
+
if (safe.length === 0)
|
|
695
|
+
return "";
|
|
696
|
+
return `<skill_recommendations unit="${unitType}">For this unit type, also consider invoking: ${safe.join(", ")}. Use Skill({ skill: 'name' }) when relevant — these are recommendations, not requirements.</skill_recommendations>`;
|
|
697
|
+
}
|
|
678
698
|
export function buildSkillActivationBlock(params) {
|
|
679
699
|
const prefs = params.preferences ?? loadEffectiveGSDPreferences(params.base)?.preferences;
|
|
680
700
|
const contextTokens = tokenizeSkillContext(params.milestoneId, params.milestoneTitle, params.sliceId, params.sliceTitle, params.taskId, params.taskTitle);
|
|
@@ -717,10 +737,51 @@ export function buildSkillActivationBlock(params) {
|
|
|
717
737
|
logWarning("prompt", `parseTaskPlanFile failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
718
738
|
}
|
|
719
739
|
}
|
|
740
|
+
// Heuristic auto-match (gated on skill_discovery: "auto").
|
|
741
|
+
// For each installed skill, check if its name or description appears in the
|
|
742
|
+
// unit's context tokens (milestone/slice/task titles). Only consider skills
|
|
743
|
+
// already on the unit-type manifest allowlist — this keeps the heuristic
|
|
744
|
+
// narrow and avoids wildly off-topic activations.
|
|
745
|
+
// Users who set `skill_discovery: "off"` or "suggest" do not get
|
|
746
|
+
// auto-matched skills (the recommendations block still surfaces manifest
|
|
747
|
+
// skills passively); only "auto" actually adds them to the activation
|
|
748
|
+
// directive set. Default `skill_discovery` is "suggest", so this is opt-in.
|
|
749
|
+
if ((prefs?.skill_discovery ?? "suggest") === "auto") {
|
|
750
|
+
const manifestAllow = resolveSkillManifest(params.unitType);
|
|
751
|
+
const allowSet = manifestAllow ? new Set(manifestAllow) : null;
|
|
752
|
+
for (const skill of visibleSkills) {
|
|
753
|
+
const normalized = normalizeSkillReference(skill.name);
|
|
754
|
+
if (matched.has(normalized) || avoided.has(normalized))
|
|
755
|
+
continue;
|
|
756
|
+
// Respect the manifest allowlist when present; wildcard (null) lets all
|
|
757
|
+
// installed skills compete for keyword match.
|
|
758
|
+
if (allowSet && !allowSet.has(normalized))
|
|
759
|
+
continue;
|
|
760
|
+
if (skillMatchesContext(skill, contextTokens)) {
|
|
761
|
+
matched.add(normalized);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
720
765
|
const ordered = [...matched]
|
|
721
766
|
.filter(name => installedNames.has(name) && !avoided.has(name))
|
|
722
767
|
.sort();
|
|
723
|
-
|
|
768
|
+
const activationBlock = formatSkillActivationBlock(ordered);
|
|
769
|
+
// Manifest-driven recommendations (additive, does not override explicit intent).
|
|
770
|
+
// Only surface skills the manifest declares for this unit type that are
|
|
771
|
+
// installed and not already in matched/avoided.
|
|
772
|
+
const matchedSet = new Set(ordered);
|
|
773
|
+
const manifestList = resolveSkillManifest(params.unitType);
|
|
774
|
+
const recommendations = (manifestList ?? [])
|
|
775
|
+
.filter(name => installedNames.has(name) && !avoided.has(name) && !matchedSet.has(name))
|
|
776
|
+
.sort();
|
|
777
|
+
const recommendationsBlock = formatSkillRecommendationsBlock(params.unitType, recommendations);
|
|
778
|
+
if (!activationBlock && !recommendationsBlock)
|
|
779
|
+
return "";
|
|
780
|
+
if (!activationBlock)
|
|
781
|
+
return recommendationsBlock;
|
|
782
|
+
if (!recommendationsBlock)
|
|
783
|
+
return activationBlock;
|
|
784
|
+
return `${activationBlock}\n${recommendationsBlock}`;
|
|
724
785
|
}
|
|
725
786
|
/**
|
|
726
787
|
* Build the skill discovery template variables for research prompts.
|
|
@@ -16,6 +16,7 @@ import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
|
16
16
|
import { gsdRoot } from "./paths.js";
|
|
17
17
|
import { createWorktree, removeWorktree, resolveGitDir, worktreePath, isInsideWorktreesDir, } from "./worktree-manager.js";
|
|
18
18
|
import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
|
|
19
|
+
import { isGsdWorktreePath, normalizeWorktreePathForCompare, resolveWorktreeProjectRoot, } from "./worktree-root.js";
|
|
19
20
|
import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
20
21
|
import { debugLog } from "./debug-logger.js";
|
|
21
22
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -1038,6 +1039,7 @@ function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
|
|
|
1038
1039
|
}
|
|
1039
1040
|
}
|
|
1040
1041
|
export function createAutoWorktree(basePath, milestoneId) {
|
|
1042
|
+
basePath = resolveWorktreeProjectRoot(basePath);
|
|
1041
1043
|
// Check if repo has commits — git worktree requires a valid HEAD
|
|
1042
1044
|
try {
|
|
1043
1045
|
execFileSync("git", ["rev-parse", "--verify", "HEAD"], { cwd: basePath, stdio: "pipe" });
|
|
@@ -1171,6 +1173,7 @@ function copyPlanningArtifacts(srcBase, wtPath) {
|
|
|
1171
1173
|
* the worktree and its branch.
|
|
1172
1174
|
*/
|
|
1173
1175
|
export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
|
|
1176
|
+
originalBasePath = resolveWorktreeProjectRoot(originalBasePath);
|
|
1174
1177
|
const branch = autoWorktreeBranch(milestoneId);
|
|
1175
1178
|
const { preserveBranch = false } = opts;
|
|
1176
1179
|
const previousCwd = process.cwd();
|
|
@@ -1212,18 +1215,26 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
|
|
|
1212
1215
|
}
|
|
1213
1216
|
/**
|
|
1214
1217
|
* Detect if the process is currently inside an auto-worktree.
|
|
1215
|
-
*
|
|
1218
|
+
* Uses the current directory structure plus git branch prefix so detection
|
|
1219
|
+
* still works after process restart when module state has been reset.
|
|
1216
1220
|
*/
|
|
1217
1221
|
export function isInAutoWorktree(basePath) {
|
|
1218
|
-
if (!originalBase)
|
|
1219
|
-
return false;
|
|
1220
1222
|
const cwd = process.cwd();
|
|
1221
|
-
|
|
1222
|
-
const wtDir = join(resolvedBase, ".gsd", "worktrees");
|
|
1223
|
-
if (!cwd.startsWith(wtDir))
|
|
1223
|
+
if (!isGsdWorktreePath(cwd))
|
|
1224
1224
|
return false;
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1225
|
+
const projectRoot = resolveWorktreeProjectRoot(basePath, originalBase);
|
|
1226
|
+
const cwdProjectRoot = resolveWorktreeProjectRoot(cwd, originalBase);
|
|
1227
|
+
if (normalizeWorktreePathForCompare(projectRoot) !==
|
|
1228
|
+
normalizeWorktreePathForCompare(cwdProjectRoot)) {
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
const branch = nativeGetCurrentBranch(cwd);
|
|
1233
|
+
return branch.startsWith("milestone/");
|
|
1234
|
+
}
|
|
1235
|
+
catch {
|
|
1236
|
+
return false;
|
|
1237
|
+
}
|
|
1227
1238
|
}
|
|
1228
1239
|
/**
|
|
1229
1240
|
* Get the filesystem path for an auto-worktree, or null if it doesn't exist
|
|
@@ -1234,6 +1245,7 @@ export function isInAutoWorktree(basePath) {
|
|
|
1234
1245
|
* mis-detection of leftover directories as active worktrees (#695).
|
|
1235
1246
|
*/
|
|
1236
1247
|
export function getAutoWorktreePath(basePath, milestoneId) {
|
|
1248
|
+
basePath = resolveWorktreeProjectRoot(basePath);
|
|
1237
1249
|
const p = worktreePath(basePath, milestoneId);
|
|
1238
1250
|
if (!existsSync(p))
|
|
1239
1251
|
return null;
|
|
@@ -1260,6 +1272,7 @@ export function getAutoWorktreePath(basePath, milestoneId) {
|
|
|
1260
1272
|
* Atomic: chdir + originalBase update in same try block.
|
|
1261
1273
|
*/
|
|
1262
1274
|
export function enterAutoWorktree(basePath, milestoneId) {
|
|
1275
|
+
basePath = resolveWorktreeProjectRoot(basePath);
|
|
1263
1276
|
const p = worktreePath(basePath, milestoneId);
|
|
1264
1277
|
if (!existsSync(p)) {
|
|
1265
1278
|
throw new GSDError(GSD_IO_ERROR, `Auto-worktree for ${milestoneId} does not exist at ${p}`);
|
|
@@ -1298,16 +1311,20 @@ export function enterAutoWorktree(basePath, milestoneId) {
|
|
|
1298
1311
|
export function getAutoWorktreeOriginalBase() {
|
|
1299
1312
|
return originalBase;
|
|
1300
1313
|
}
|
|
1314
|
+
export function _resetAutoWorktreeOriginalBaseForTests() {
|
|
1315
|
+
originalBase = null;
|
|
1316
|
+
}
|
|
1301
1317
|
export function getActiveAutoWorktreeContext() {
|
|
1302
1318
|
if (!originalBase)
|
|
1303
1319
|
return null;
|
|
1304
1320
|
const cwd = process.cwd();
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1321
|
+
if (!isGsdWorktreePath(cwd))
|
|
1322
|
+
return null;
|
|
1323
|
+
const cwdProjectRoot = resolveWorktreeProjectRoot(cwd, originalBase);
|
|
1324
|
+
if (normalizeWorktreePathForCompare(cwdProjectRoot) !==
|
|
1325
|
+
normalizeWorktreePathForCompare(originalBase)) {
|
|
1310
1326
|
return null;
|
|
1327
|
+
}
|
|
1311
1328
|
const worktreeName = detectWorktreeName(cwd);
|
|
1312
1329
|
if (!worktreeName)
|
|
1313
1330
|
return null;
|