@opengsd/gsd-pi 1.0.2-dev.235ebf3 → 1.0.2-dev.29398d2
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 +63 -12
- package/dist/onboarding.js +22 -3
- package/dist/resource-loader.d.ts +7 -0
- package/dist/resource-loader.js +42 -9
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/context7/index.js +12 -2
- package/dist/resources/extensions/google-cli/index.js +30 -0
- package/dist/resources/extensions/google-cli/models.js +55 -0
- package/dist/resources/extensions/google-cli/package.json +11 -0
- package/dist/resources/extensions/google-cli/readiness.js +12 -0
- package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
- package/dist/resources/extensions/gsd/auto/loop.js +19 -0
- package/dist/resources/extensions/gsd/auto/phases.js +1 -1
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +232 -49
- package/dist/resources/extensions/gsd/auto-worktree.js +2 -54
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -15
- package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
- package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
- package/dist/resources/extensions/gsd/key-manager.js +45 -13
- package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
- package/dist/resources/extensions/gsd/worktree-post-create-hook.js +117 -0
- package/dist/resources/extensions/search-the-web/native-search.js +57 -8
- package/dist/resources/shared/package-manager-detection.js +36 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +7 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- 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/api/update/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 +5 -5
- package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
- 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/web/standalone/package.json +0 -1
- package/dist/worktree-cli.d.ts +0 -2
- package/dist/worktree-cli.js +21 -9
- package/package.json +5 -2
- 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/dist/modes/interactive/components/login-dialog.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +2 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/bin/gsd-mcp-server.js +14 -0
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +13 -13
- package/packages/pi-agent-core/dist/agent-loop.js.map +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.d.ts +40 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +49 -30
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +50 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/read.js +5 -3
- package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
- package/packages/pi-coding-agent/package.json +8 -8
- 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/scripts/install/detect-existing.js +17 -3
- package/scripts/install/npm-global.js +103 -33
- package/scripts/install.js +1 -0
- package/src/resources/extensions/context7/index.ts +15 -2
- package/src/resources/extensions/google-cli/index.ts +34 -0
- package/src/resources/extensions/google-cli/models.ts +57 -0
- package/src/resources/extensions/google-cli/package.json +11 -0
- package/src/resources/extensions/google-cli/readiness.ts +15 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
- package/src/resources/extensions/gsd/auto/loop.ts +22 -0
- package/src/resources/extensions/gsd/auto/phases.ts +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-start.ts +307 -56
- package/src/resources/extensions/gsd/auto-worktree.ts +2 -56
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -15
- package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
- package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
- package/src/resources/extensions/gsd/key-manager.ts +57 -14
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +141 -1
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
- package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +56 -4
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
- package/src/resources/extensions/gsd/worktree-post-create-hook.ts +127 -0
- package/src/resources/extensions/search-the-web/native-search.ts +60 -8
- package/src/resources/shared/package-manager-detection.ts +39 -0
- package/dist/tsconfig.extensions.tsbuildinfo +0 -1
- /package/dist/web/standalone/.next/static/{-P554bKh56nzavKUmvFM2 → bukT6Ux1YchPm2XqjaexX}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{-P554bKh56nzavKUmvFM2 → bukT6Ux1YchPm2XqjaexX}/_ssgManifest.js +0 -0
|
@@ -418,6 +418,17 @@ function initSessionNotifications(ctx) {
|
|
|
418
418
|
installNotifyInterceptor(ctx);
|
|
419
419
|
initNotificationWidget(ctx);
|
|
420
420
|
}
|
|
421
|
+
async function prepareWorkflowMcpForHookContext(ctx, basePath) {
|
|
422
|
+
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
423
|
+
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
424
|
+
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
425
|
+
// CLI path resolution), dirtying the tree and breaking the milestone merge.
|
|
426
|
+
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
427
|
+
if (isInAutoWorktree(basePath))
|
|
428
|
+
return;
|
|
429
|
+
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
430
|
+
prepareWorkflowMcpForProject(ctx, basePath);
|
|
431
|
+
}
|
|
421
432
|
export function registerHooks(pi, ecosystemHandlers) {
|
|
422
433
|
// ADR-005 Phase 3b: surface pi-ai ProviderSwitchReport via audit, notification, and counter.
|
|
423
434
|
// Idempotent — only the first registerHooks call installs.
|
|
@@ -438,12 +449,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
438
449
|
await syncServiceTierStatus(ctx);
|
|
439
450
|
await applyDisabledModelProviderPolicy(ctx);
|
|
440
451
|
await applyCompactionThresholdOverride(ctx);
|
|
441
|
-
|
|
442
|
-
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
443
|
-
if (!isInAutoWorktree(basePath)) {
|
|
444
|
-
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
445
|
-
prepareWorkflowMcpForProject(ctx, basePath);
|
|
446
|
-
}
|
|
452
|
+
await prepareWorkflowMcpForHookContext(ctx, basePath);
|
|
447
453
|
// Apply show_token_cost preference (#1515)
|
|
448
454
|
try {
|
|
449
455
|
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
@@ -468,15 +474,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
468
474
|
await syncServiceTierStatus(ctx);
|
|
469
475
|
await applyDisabledModelProviderPolicy(ctx);
|
|
470
476
|
await applyCompactionThresholdOverride(ctx);
|
|
471
|
-
|
|
472
|
-
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
473
|
-
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
474
|
-
// CLI path resolution), dirtying the tree and breaking the milestone merge.
|
|
475
|
-
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
476
|
-
if (!isInAutoWorktree(basePath)) {
|
|
477
|
-
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
478
|
-
prepareWorkflowMcpForProject(ctx, basePath);
|
|
479
|
-
}
|
|
477
|
+
await prepareWorkflowMcpForHookContext(ctx, basePath);
|
|
480
478
|
await loadToolApiKeysForSession();
|
|
481
479
|
if (!isAutoActive()) {
|
|
482
480
|
ctx.ui.setWidget("gsd-progress", undefined);
|
|
@@ -511,6 +509,10 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
511
509
|
}
|
|
512
510
|
}
|
|
513
511
|
clearDeferredApprovalGate(beforeAgentBasePath);
|
|
512
|
+
// session_start can fire before the active provider has settled. By
|
|
513
|
+
// before_agent_start, Claude Code CLI sessions should get the same
|
|
514
|
+
// project MCP config that /gsd mcp init would write.
|
|
515
|
+
await prepareWorkflowMcpForHookContext(ctx, beforeAgentBasePath);
|
|
514
516
|
// GSD's own context injection (existing behavior — unchanged).
|
|
515
517
|
const { buildBeforeAgentStartResult } = await import("./system-context.js");
|
|
516
518
|
const gsdResult = await buildBeforeAgentStartResult(event, ctx);
|
|
@@ -167,7 +167,13 @@ export function getCloseoutManualResolveBlocker(basePath) {
|
|
|
167
167
|
if (conflictProbe.status === "dirty" && conflictProbe.unmerged.length > 0) {
|
|
168
168
|
return `Unmerged paths remain in ${basePath}: ${conflictProbe.unmerged.slice(0, 5).join(", ")}`;
|
|
169
169
|
}
|
|
170
|
-
|
|
170
|
+
let status;
|
|
171
|
+
try {
|
|
172
|
+
status = runGit(basePath, ["status", "--porcelain"]);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return `Could not inspect git status in ${basePath}.`;
|
|
176
|
+
}
|
|
171
177
|
if (status) {
|
|
172
178
|
return `Working tree still has uncommitted changes in ${basePath}. Commit, stash, or run /gsd closeout retry first.`;
|
|
173
179
|
}
|
|
@@ -197,7 +197,15 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
|
|
|
197
197
|
if (trimmed === "") {
|
|
198
198
|
if (!(await guardRemoteSession(ctx, pi)))
|
|
199
199
|
return true;
|
|
200
|
-
|
|
200
|
+
const basePath = projectRoot();
|
|
201
|
+
const { hasGsdBootstrapArtifacts } = await import("../../detection.js");
|
|
202
|
+
const { gsdRoot } = await import("../../paths.js");
|
|
203
|
+
if (!hasGsdBootstrapArtifacts(gsdRoot(basePath))) {
|
|
204
|
+
const { showSmartEntry } = await import("../../guided-flow.js");
|
|
205
|
+
await showSmartEntry(ctx, pi, basePath, { step: true });
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
if (await hasUnresolvedCloseoutBlocker(ctx, basePath))
|
|
201
209
|
return true;
|
|
202
210
|
const { showGsdHome } = await import("../../gsd-command-home.js");
|
|
203
211
|
await showGsdHome(ctx, pi, projectRoot());
|
|
@@ -17,6 +17,7 @@ import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
|
|
|
17
17
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
18
18
|
import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
|
|
19
19
|
import { loadPrompt } from "./prompt-loader.js";
|
|
20
|
+
import { isPnpmInstall } from "../../shared/package-manager-detection.js";
|
|
20
21
|
import { buildDoctorHealIssuePayload, buildDoctorHealSummary, buildWorkflowDispatchContent, } from "./workflow-protocol.js";
|
|
21
22
|
import { restoreGsdWorkflowTools, scopeGsdWorkflowToolsForDispatch, } from "./bootstrap/register-hooks.js";
|
|
22
23
|
const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/@opengsd%2fgsd-pi/latest";
|
|
@@ -42,6 +43,8 @@ function isBunInstall(argv1 = process.argv[1]) {
|
|
|
42
43
|
function resolveInstallCommand(pkg) {
|
|
43
44
|
if (isBunInstall())
|
|
44
45
|
return `bun add -g ${pkg}`;
|
|
46
|
+
if (isPnpmInstall())
|
|
47
|
+
return `pnpm add -g ${pkg}`;
|
|
45
48
|
return `npm install -g ${pkg}`;
|
|
46
49
|
}
|
|
47
50
|
async function fetchLatestVersionForCommand() {
|
|
@@ -15,16 +15,15 @@ import { delimiter, join } from "node:path";
|
|
|
15
15
|
import { AuthStorage } from "@gsd/pi-coding-agent";
|
|
16
16
|
import { getEnvApiKey } from "@gsd/pi-ai";
|
|
17
17
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
18
|
-
import { getAuthPath, PROVIDER_REGISTRY } from "./key-manager.js";
|
|
18
|
+
import { getAuthPath, PROVIDER_REGISTRY, supportsBrowserOAuth } from "./key-manager.js";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
// ── Provider routing constants ────────────────────────────────────────────────
|
|
21
21
|
/**
|
|
22
22
|
* Providers that use external CLI authentication (not API keys).
|
|
23
|
-
*
|
|
23
|
+
* When explicitly selected, the provider's own CLI/session owns auth.
|
|
24
24
|
*/
|
|
25
25
|
const CLI_AUTH_PROVIDERS = new Set([
|
|
26
26
|
"claude-code",
|
|
27
|
-
"openai-codex",
|
|
28
27
|
"google-gemini-cli",
|
|
29
28
|
"google-antigravity",
|
|
30
29
|
]);
|
|
@@ -126,23 +125,26 @@ function collectConfiguredModelProviders() {
|
|
|
126
125
|
* Used for lightweight binary-presence checks (PATH scan, no subprocess).
|
|
127
126
|
*/
|
|
128
127
|
const CLI_BINARY_MAP = {
|
|
129
|
-
"claude-code": "claude",
|
|
130
|
-
"
|
|
131
|
-
"google-
|
|
132
|
-
"google-antigravity": "antigravity",
|
|
128
|
+
"claude-code": ["claude", "claude-code"],
|
|
129
|
+
"google-gemini-cli": ["gemini"],
|
|
130
|
+
"google-antigravity": ["agy"],
|
|
133
131
|
};
|
|
132
|
+
const CLI_AUTH_PATH_CHECK_PROVIDERS = new Set([
|
|
133
|
+
"google-gemini-cli",
|
|
134
|
+
"google-antigravity",
|
|
135
|
+
]);
|
|
134
136
|
/**
|
|
135
137
|
* Check if a CLI provider's binary exists anywhere in PATH.
|
|
136
138
|
* Fast filesystem scan — no subprocess, no network, sub-1ms.
|
|
137
139
|
*/
|
|
138
140
|
function isCliBinaryInPath(providerId) {
|
|
139
|
-
const
|
|
140
|
-
if (!
|
|
141
|
+
const binaries = CLI_BINARY_MAP[providerId];
|
|
142
|
+
if (!binaries)
|
|
141
143
|
return false;
|
|
142
144
|
const pathDirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
143
145
|
// On Windows, command shims are commonly installed as .cmd/.exe/.bat/.com.
|
|
144
146
|
// Scan PATHEXT candidates in addition to the bare binary name.
|
|
145
|
-
const executableNames = [
|
|
147
|
+
const executableNames = [...binaries];
|
|
146
148
|
if (process.platform === "win32") {
|
|
147
149
|
const rawPathExt = process.env.PATHEXT
|
|
148
150
|
?.split(";")
|
|
@@ -150,10 +152,12 @@ function isCliBinaryInPath(providerId) {
|
|
|
150
152
|
.filter(Boolean) ?? [];
|
|
151
153
|
const normalizedPathExt = rawPathExt.map(ext => ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`);
|
|
152
154
|
const defaultExt = [".exe", ".cmd", ".bat", ".com"];
|
|
153
|
-
for (const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
executableNames.
|
|
155
|
+
for (const binary of binaries) {
|
|
156
|
+
for (const ext of [...normalizedPathExt, ...defaultExt]) {
|
|
157
|
+
const candidate = `${binary}${ext}`;
|
|
158
|
+
if (!executableNames.includes(candidate))
|
|
159
|
+
executableNames.push(candidate);
|
|
160
|
+
}
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
163
|
return pathDirs.some(dir => executableNames.some(name => existsSync(join(dir, name))));
|
|
@@ -185,11 +189,6 @@ function hasModelsJsonApiKey(providerId) {
|
|
|
185
189
|
}
|
|
186
190
|
function resolveKey(providerId) {
|
|
187
191
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
188
|
-
// claude-code never stores credentials in auth.json — GSD delegates entirely to
|
|
189
|
-
// the local CLI binary. Presence of the binary in PATH is the only signal.
|
|
190
|
-
if (providerId === "claude-code") {
|
|
191
|
-
return { found: isCliBinaryInPath("claude-code"), source: "env", backedOff: false };
|
|
192
|
-
}
|
|
193
192
|
if (providerId === "anthropic-vertex" && process.env.ANTHROPIC_VERTEX_PROJECT_ID) {
|
|
194
193
|
return { found: true, source: "env", backedOff: false };
|
|
195
194
|
}
|
|
@@ -201,7 +200,16 @@ function resolveKey(providerId) {
|
|
|
201
200
|
const creds = auth.getCredentialsForProvider(providerId);
|
|
202
201
|
if (creds.length > 0) {
|
|
203
202
|
// Filter out empty placeholder keys (from skipped onboarding)
|
|
204
|
-
const hasRealKey = creds.some(c =>
|
|
203
|
+
const hasRealKey = creds.some(c => {
|
|
204
|
+
if (c.type === "oauth")
|
|
205
|
+
return true;
|
|
206
|
+
if (c.type !== "api_key")
|
|
207
|
+
return false;
|
|
208
|
+
const key = c.key?.trim();
|
|
209
|
+
if (!key)
|
|
210
|
+
return false;
|
|
211
|
+
return !(CLI_AUTH_PROVIDERS.has(providerId) && key === "cli");
|
|
212
|
+
});
|
|
205
213
|
if (hasRealKey) {
|
|
206
214
|
return {
|
|
207
215
|
found: true,
|
|
@@ -229,6 +237,11 @@ function resolveKey(providerId) {
|
|
|
229
237
|
if (hasModelsJsonApiKey(providerId)) {
|
|
230
238
|
return { found: true, source: "models.json", backedOff: false };
|
|
231
239
|
}
|
|
240
|
+
// Cross-provider routes can use a local CLI when it is installed. Explicit
|
|
241
|
+
// external CLI provider selections are handled in checkLlmProviders() below.
|
|
242
|
+
if (CLI_AUTH_PROVIDERS.has(providerId) && isCliBinaryInPath(providerId)) {
|
|
243
|
+
return { found: true, source: "env", backedOff: false };
|
|
244
|
+
}
|
|
232
245
|
return { found: false, source: "none", backedOff: false };
|
|
233
246
|
}
|
|
234
247
|
// ── Individual check groups ────────────────────────────────────────────────────
|
|
@@ -236,15 +249,32 @@ function checkLlmProviders() {
|
|
|
236
249
|
const required = collectConfiguredModelProviders();
|
|
237
250
|
const results = [];
|
|
238
251
|
for (const providerId of required) {
|
|
239
|
-
// CLI-authenticated providers don't need API keys
|
|
252
|
+
// CLI-authenticated providers don't need API keys. The provider's own
|
|
253
|
+
// request path validates CLI sessions when it is used.
|
|
240
254
|
if (CLI_AUTH_PROVIDERS.has(providerId)) {
|
|
241
255
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
256
|
+
const label = info?.label ?? providerId;
|
|
257
|
+
if (CLI_AUTH_PATH_CHECK_PROVIDERS.has(providerId) && !isCliBinaryInPath(providerId)) {
|
|
258
|
+
const binaries = CLI_BINARY_MAP[providerId]?.map(binary => `\`${binary}\``).join(" or ");
|
|
259
|
+
results.push({
|
|
260
|
+
name: providerId,
|
|
261
|
+
label,
|
|
262
|
+
category: "llm",
|
|
263
|
+
status: "error",
|
|
264
|
+
message: `${label} — CLI not found`,
|
|
265
|
+
detail: binaries
|
|
266
|
+
? `Install ${label} and ensure ${binaries} is on PATH`
|
|
267
|
+
: `Install ${label} and ensure its CLI is on PATH`,
|
|
268
|
+
required: true,
|
|
269
|
+
});
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
242
272
|
results.push({
|
|
243
273
|
name: providerId,
|
|
244
|
-
label
|
|
274
|
+
label,
|
|
245
275
|
category: "llm",
|
|
246
276
|
status: "ok",
|
|
247
|
-
message: `${
|
|
277
|
+
message: `${label} — CLI auth (no key needed)`,
|
|
248
278
|
required: true,
|
|
249
279
|
});
|
|
250
280
|
continue;
|
|
@@ -282,7 +312,7 @@ function checkLlmProviders() {
|
|
|
282
312
|
message: `${label} — not configured`,
|
|
283
313
|
detail: providerId === "anthropic-vertex"
|
|
284
314
|
? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
|
|
285
|
-
: info
|
|
315
|
+
: info && supportsBrowserOAuth(info)
|
|
286
316
|
? `Run /gsd keys to authenticate`
|
|
287
317
|
: `Set ${envVar} or run /gsd keys`,
|
|
288
318
|
required: true,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// File Purpose: Detect and reconcile unresolved Git conflict state before automation runs.
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { autoResolveSafeConflictPaths } from "./git-conflict-resolve.js";
|
|
7
7
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
8
8
|
import { abortAndReset } from "./git-self-heal.js";
|
|
@@ -10,6 +10,23 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
10
10
|
function splitZeroDelimited(output) {
|
|
11
11
|
return output.split("\0").filter(Boolean);
|
|
12
12
|
}
|
|
13
|
+
function hasGitMarker(basePath) {
|
|
14
|
+
try {
|
|
15
|
+
let dir = resolve(basePath);
|
|
16
|
+
for (let i = 0; i < 30; i++) {
|
|
17
|
+
if (existsSync(join(dir, ".git")))
|
|
18
|
+
return true;
|
|
19
|
+
const parent = dirname(dir);
|
|
20
|
+
if (parent === dir)
|
|
21
|
+
break;
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Fall through to the git probes, which will report unknown on failure.
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
13
30
|
export function listUnmergedGitPaths(basePath) {
|
|
14
31
|
try {
|
|
15
32
|
const output = spawnSync("git", ["diff", "--name-only", "--diff-filter=U", "-z"], {
|
|
@@ -57,6 +74,14 @@ export function listMergeStateBlockers(basePath) {
|
|
|
57
74
|
return MERGE_STATE_MARKERS.filter((marker) => existsSync(join(gitDir, marker)));
|
|
58
75
|
}
|
|
59
76
|
export function probeGitConflictState(basePath) {
|
|
77
|
+
if (!hasGitMarker(basePath)) {
|
|
78
|
+
return {
|
|
79
|
+
status: "clean",
|
|
80
|
+
unmerged: [],
|
|
81
|
+
checkFailures: [],
|
|
82
|
+
mergeStateBlockers: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
60
85
|
const unmerged = listUnmergedGitPaths(basePath);
|
|
61
86
|
if (unmerged === null) {
|
|
62
87
|
return {
|
|
@@ -12,18 +12,18 @@ import { getErrorMessage } from "./error-utils.js";
|
|
|
12
12
|
import { gsdHome } from "./gsd-home.js";
|
|
13
13
|
export const PROVIDER_REGISTRY = [
|
|
14
14
|
// LLM Providers
|
|
15
|
-
{ id: "anthropic", label: "Anthropic (Claude)", category: "llm", envVar: "ANTHROPIC_API_KEY", prefixes: ["sk-ant-"],
|
|
15
|
+
{ id: "anthropic", label: "Anthropic (Claude)", category: "llm", envVar: "ANTHROPIC_API_KEY", prefixes: ["sk-ant-"], authMode: "apiKey", dashboardUrl: "console.anthropic.com" },
|
|
16
16
|
// Claude Code CLI: routes through the local `claude` binary — no API key,
|
|
17
17
|
// authentication is handled by the CLI's own OAuth flow.
|
|
18
18
|
// Referenced by doctor-providers.ts, auto-model-selection.ts, and others;
|
|
19
19
|
// must be in the canonical registry so all consumers see the same catalog.
|
|
20
20
|
// See: https://github.com/open-gsd/gsd-pi/issues/4541
|
|
21
|
-
{ id: "claude-code", label: "Claude Code CLI", category: "llm",
|
|
21
|
+
{ id: "claude-code", label: "Claude Code CLI", category: "llm", authMode: "externalCli" },
|
|
22
22
|
{ id: "openai", label: "OpenAI", category: "llm", envVar: "OPENAI_API_KEY", prefixes: ["sk-"], dashboardUrl: "platform.openai.com/api-keys" },
|
|
23
|
-
{ id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN",
|
|
24
|
-
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)", category: "llm",
|
|
25
|
-
{ id: "google-gemini-cli", label: "Google Gemini CLI", category: "llm",
|
|
26
|
-
{ id: "google-antigravity", label: "Antigravity", category: "llm",
|
|
23
|
+
{ id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN", authMode: "browserOAuth" },
|
|
24
|
+
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)", category: "llm", authMode: "browserOAuth" },
|
|
25
|
+
{ id: "google-gemini-cli", label: "Google Gemini CLI", category: "llm", authMode: "externalCli" },
|
|
26
|
+
{ id: "google-antigravity", label: "Antigravity", category: "llm", authMode: "externalCli" },
|
|
27
27
|
{ id: "google", label: "Google (Gemini)", category: "llm", envVar: "GEMINI_API_KEY", dashboardUrl: "aistudio.google.com/apikey" },
|
|
28
28
|
{ id: "groq", label: "Groq", category: "llm", envVar: "GROQ_API_KEY", dashboardUrl: "console.groq.com" },
|
|
29
29
|
{ id: "xai", label: "xAI (Grok)", category: "llm", envVar: "XAI_API_KEY", dashboardUrl: "console.x.ai" },
|
|
@@ -49,6 +49,20 @@ export const PROVIDER_REGISTRY = [
|
|
|
49
49
|
{ id: "telegram_bot", label: "Telegram Bot", category: "remote", envVar: "TELEGRAM_BOT_TOKEN" },
|
|
50
50
|
];
|
|
51
51
|
// ─── Utilities ──────────────────────────────────────────────────────────────────
|
|
52
|
+
export function getProviderAuthMode(provider) {
|
|
53
|
+
return provider.authMode ?? "apiKey";
|
|
54
|
+
}
|
|
55
|
+
export function supportsBrowserOAuth(provider) {
|
|
56
|
+
return getProviderAuthMode(provider) === "browserOAuth";
|
|
57
|
+
}
|
|
58
|
+
export function supportsStoredApiKey(provider) {
|
|
59
|
+
const mode = getProviderAuthMode(provider);
|
|
60
|
+
if (mode === "externalCli" || mode === "cloudIdentity" || mode === "none")
|
|
61
|
+
return false;
|
|
62
|
+
if (mode === "browserOAuth")
|
|
63
|
+
return Boolean(provider.envVar || provider.prefixes?.length);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
52
66
|
/**
|
|
53
67
|
* Mask an API key for display: show first 4 + last 4 chars.
|
|
54
68
|
* Keys shorter than 12 chars show only first 2 + last 2.
|
|
@@ -79,11 +93,14 @@ export function formatDuration(ms) {
|
|
|
79
93
|
/**
|
|
80
94
|
* Describe a credential's type and status.
|
|
81
95
|
*/
|
|
82
|
-
export function describeCredential(cred) {
|
|
96
|
+
export function describeCredential(cred, provider) {
|
|
83
97
|
if (cred.type === "api_key") {
|
|
84
98
|
const apiCred = cred;
|
|
85
99
|
if (!apiCred.key)
|
|
86
100
|
return "empty key";
|
|
101
|
+
if (apiCred.key === "cli" && provider && getProviderAuthMode(provider) === "externalCli") {
|
|
102
|
+
return "external CLI";
|
|
103
|
+
}
|
|
87
104
|
return `API key (${maskKey(apiCred.key)})`;
|
|
88
105
|
}
|
|
89
106
|
if (cred.type === "oauth") {
|
|
@@ -129,7 +146,7 @@ export function getAllKeyStatuses(auth) {
|
|
|
129
146
|
const firstCred = creds[0];
|
|
130
147
|
const desc = creds.length > 1
|
|
131
148
|
? `${creds.length} keys (round-robin)`
|
|
132
|
-
: describeCredential(firstCred);
|
|
149
|
+
: describeCredential(firstCred, provider);
|
|
133
150
|
return {
|
|
134
151
|
provider,
|
|
135
152
|
configured: true,
|
|
@@ -236,9 +253,20 @@ export async function handleAddKey(providerArg, ctx, auth) {
|
|
|
236
253
|
return false;
|
|
237
254
|
provider = PROVIDER_REGISTRY[idx];
|
|
238
255
|
}
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
256
|
+
const authMode = getProviderAuthMode(provider);
|
|
257
|
+
if (authMode === "externalCli") {
|
|
258
|
+
ctx.ui.notify(`${provider.label} is authenticated by its own local CLI. ` +
|
|
259
|
+
`Sign in with that CLI first, then run /login to activate it in GSD.`, "info");
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (authMode === "cloudIdentity") {
|
|
263
|
+
ctx.ui.notify(`${provider.label} uses cloud identity credentials. Configure the provider's cloud CLI or environment, then restart GSD.`, "info");
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (supportsBrowserOAuth(provider)) {
|
|
267
|
+
const methods = supportsStoredApiKey(provider)
|
|
268
|
+
? ["API token/key", "Browser login (OAuth)"]
|
|
269
|
+
: ["Browser login (OAuth)"];
|
|
242
270
|
const method = await ctx.ui.select(`${provider.label} — how do you want to authenticate?`, methods);
|
|
243
271
|
if (!method || typeof method !== "string")
|
|
244
272
|
return false;
|
|
@@ -248,6 +276,10 @@ export async function handleAddKey(providerArg, ctx, auth) {
|
|
|
248
276
|
return false;
|
|
249
277
|
}
|
|
250
278
|
}
|
|
279
|
+
if (!supportsStoredApiKey(provider)) {
|
|
280
|
+
ctx.ui.notify(`${provider.label} does not accept a GSD-stored API key. Use /login.`, "info");
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
251
283
|
// API key input
|
|
252
284
|
const input = await ctx.ui.input(`API key for ${provider.label}:`, provider.envVar ? `or set ${provider.envVar} env var` : "paste your key here");
|
|
253
285
|
if (input === null || input === undefined)
|
|
@@ -309,7 +341,7 @@ export async function handleRemoveKey(providerArg, ctx, auth) {
|
|
|
309
341
|
}
|
|
310
342
|
// Multi-key handling
|
|
311
343
|
if (creds.length > 1) {
|
|
312
|
-
const options = creds.map((c, i) => `[${i + 1}] ${describeCredential(c)}`);
|
|
344
|
+
const options = creds.map((c, i) => `[${i + 1}] ${describeCredential(c, provider)}`);
|
|
313
345
|
options.push("Remove all");
|
|
314
346
|
const choice = await ctx.ui.select(`${provider.label} has ${creds.length} keys. Remove which?`, options);
|
|
315
347
|
if (!choice || typeof choice !== "string")
|
|
@@ -330,7 +362,7 @@ export async function handleRemoveKey(providerArg, ctx, auth) {
|
|
|
330
362
|
}
|
|
331
363
|
}
|
|
332
364
|
else {
|
|
333
|
-
const confirmed = await ctx.ui.confirm("Remove key?", `Remove ${describeCredential(creds[0])} for ${provider.label}?`);
|
|
365
|
+
const confirmed = await ctx.ui.confirm("Remove key?", `Remove ${describeCredential(creds[0], provider)} for ${provider.label}?`);
|
|
334
366
|
if (!confirmed)
|
|
335
367
|
return false;
|
|
336
368
|
auth.remove(provider.id);
|
|
@@ -116,6 +116,15 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
116
116
|
if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
|
|
117
117
|
return { error: "milestoneId is required and must be a non-empty string" };
|
|
118
118
|
}
|
|
119
|
+
if (!params.oneLiner || typeof params.oneLiner !== "string" || params.oneLiner.trim() === "") {
|
|
120
|
+
return { error: "oneLiner is required and must be a non-empty string" };
|
|
121
|
+
}
|
|
122
|
+
if (!params.narrative || typeof params.narrative !== "string" || params.narrative.trim() === "") {
|
|
123
|
+
return { error: "narrative is required and must be a non-empty string" };
|
|
124
|
+
}
|
|
125
|
+
if (!params.verification || typeof params.verification !== "string" || params.verification.trim() === "") {
|
|
126
|
+
return { error: "verification is required and must be a non-empty string" };
|
|
127
|
+
}
|
|
119
128
|
const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, params.milestoneId);
|
|
120
129
|
// ── Ownership check (opt-in: only enforced when claim file exists) ──────
|
|
121
130
|
const ownershipErr = checkOwnership(artifactBasePath, taskUnitKey(params.milestoneId, params.sliceId, params.taskId), params.actorName);
|
|
@@ -238,6 +238,24 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
|
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
+
function normalizeVerificationEvidence(evidence) {
|
|
242
|
+
return (evidence ?? []).map((entry) => typeof entry === "string"
|
|
243
|
+
? { command: entry, exitCode: -1, verdict: "unknown (coerced from string)", durationMs: 0 }
|
|
244
|
+
: entry);
|
|
245
|
+
}
|
|
246
|
+
function deriveVerificationSummary(evidence) {
|
|
247
|
+
if (evidence.length === 0)
|
|
248
|
+
return null;
|
|
249
|
+
const rendered = evidence.slice(0, 3).map((entry) => {
|
|
250
|
+
const command = entry.command.trim() || "(unspecified command)";
|
|
251
|
+
const verdict = entry.verdict.trim() || "recorded";
|
|
252
|
+
return `\`${command}\` exited ${entry.exitCode} (${verdict})`;
|
|
253
|
+
});
|
|
254
|
+
const suffix = evidence.length > rendered.length
|
|
255
|
+
? `; ${evidence.length - rendered.length} more check(s) recorded`
|
|
256
|
+
: "";
|
|
257
|
+
return `Verification evidence recorded: ${rendered.join("; ")}${suffix}.`;
|
|
258
|
+
}
|
|
241
259
|
export async function executeTaskComplete(params, basePath = process.cwd()) {
|
|
242
260
|
const dbAvailable = await ensureDbOpen(basePath);
|
|
243
261
|
if (!dbAvailable) {
|
|
@@ -249,7 +267,28 @@ export async function executeTaskComplete(params, basePath = process.cwd()) {
|
|
|
249
267
|
}
|
|
250
268
|
try {
|
|
251
269
|
const coerced = { ...params };
|
|
252
|
-
|
|
270
|
+
const verificationEvidence = normalizeVerificationEvidence(params.verificationEvidence);
|
|
271
|
+
coerced.verificationEvidence = verificationEvidence;
|
|
272
|
+
const verification = typeof params.verification === "string" ? params.verification.trim() : "";
|
|
273
|
+
if (verification.length === 0) {
|
|
274
|
+
const derived = deriveVerificationSummary(verificationEvidence);
|
|
275
|
+
if (derived) {
|
|
276
|
+
coerced.verification = derived;
|
|
277
|
+
}
|
|
278
|
+
else if (params.blockerDiscovered === true) {
|
|
279
|
+
coerced.verification = "Not run: blocker discovered before verification.";
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
return {
|
|
283
|
+
content: [{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: "Error completing task: verification is required unless verificationEvidence is provided or blockerDiscovered is true.",
|
|
286
|
+
}],
|
|
287
|
+
details: { operation: "complete_task", error: "verification_required" },
|
|
288
|
+
isError: true,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
253
292
|
const result = await handleCompleteTask(coerced, basePath);
|
|
254
293
|
if ("error" in result) {
|
|
255
294
|
return {
|
|
@@ -203,7 +203,7 @@ function validateMilestoneId(milestoneId) {
|
|
|
203
203
|
* - emits worktree-created telemetry on successful entry
|
|
204
204
|
* - notifies the caller via `ctx.notify` for every user-visible outcome
|
|
205
205
|
*/
|
|
206
|
-
export function _enterMilestoneCore(s, deps, milestoneId, ctx) {
|
|
206
|
+
export function _enterMilestoneCore(s, deps, milestoneId, ctx, opts = {}) {
|
|
207
207
|
if (!isValidMilestoneId(milestoneId)) {
|
|
208
208
|
debugLog("WorktreeLifecycle", {
|
|
209
209
|
action: "enterMilestone",
|
|
@@ -305,7 +305,7 @@ export function _enterMilestoneCore(s, deps, milestoneId, ctx) {
|
|
|
305
305
|
// Handles the case where originalBasePath is falsy and basePath is itself
|
|
306
306
|
// a worktree path — prevents double-nested worktree paths (#3729).
|
|
307
307
|
const basePath = resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
|
|
308
|
-
const mode = getIsolationMode(basePath);
|
|
308
|
+
const mode = opts.modeOverride ?? getIsolationMode(basePath);
|
|
309
309
|
if (s.isolationDegraded) {
|
|
310
310
|
if (mode === "worktree") {
|
|
311
311
|
try {
|
|
@@ -841,7 +841,8 @@ export function mergeMilestoneStandalone(deps, mctx) {
|
|
|
841
841
|
pushed: false,
|
|
842
842
|
};
|
|
843
843
|
}
|
|
844
|
-
const mode =
|
|
844
|
+
const mode = mctx.isolationModeOverride ??
|
|
845
|
+
getIsolationMode(originalBasePath || worktreeBasePath);
|
|
845
846
|
debugLog("WorktreeLifecycle", {
|
|
846
847
|
action: "mergeAndExit",
|
|
847
848
|
milestoneId,
|
|
@@ -1137,6 +1138,7 @@ export class WorktreeLifecycle {
|
|
|
1137
1138
|
originalBasePath: this.s.originalBasePath,
|
|
1138
1139
|
worktreeBasePath: this.s.basePath,
|
|
1139
1140
|
milestoneId,
|
|
1141
|
+
isolationModeOverride: this.s.strandedRecoveryIsolationMode ?? undefined,
|
|
1140
1142
|
isolationDegraded: this.s.isolationDegraded,
|
|
1141
1143
|
notify: ctx.notify,
|
|
1142
1144
|
});
|
|
@@ -1223,6 +1225,7 @@ export class WorktreeLifecycle {
|
|
|
1223
1225
|
// Rebuild GitService after merge (branch HEAD changed)
|
|
1224
1226
|
rebuildGitService(this.s, this.deps);
|
|
1225
1227
|
}
|
|
1228
|
+
this.s.strandedRecoveryIsolationMode = null;
|
|
1226
1229
|
return result;
|
|
1227
1230
|
}
|
|
1228
1231
|
// ── Removed: _mergeWorktreeMode / _mergeBranchMode bodies ────────────
|
|
@@ -1345,6 +1348,24 @@ export class WorktreeLifecycle {
|
|
|
1345
1348
|
resumeFromPausedSession(base, persistedWorktreePath) {
|
|
1346
1349
|
this.s.basePath = resolvePausedResumeBasePath(base, persistedWorktreePath);
|
|
1347
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Adopt in-progress stranded work during bootstrap.
|
|
1353
|
+
*
|
|
1354
|
+
* Unlike completed-orphan recovery, this does not merge, delete, or commit.
|
|
1355
|
+
* It only moves the live session onto the branch/worktree proven by the
|
|
1356
|
+
* audit evidence, while preserving that mode for the eventual merge.
|
|
1357
|
+
*/
|
|
1358
|
+
adoptStrandedMilestone(milestoneId, base, ctx, opts) {
|
|
1359
|
+
this.adoptSessionRoot(base);
|
|
1360
|
+
this.s.strandedRecoveryIsolationMode = opts.mode;
|
|
1361
|
+
const result = _enterMilestoneCore(this.s, this.deps, milestoneId, ctx, {
|
|
1362
|
+
modeOverride: opts.mode,
|
|
1363
|
+
});
|
|
1364
|
+
if (!result.ok) {
|
|
1365
|
+
this.s.strandedRecoveryIsolationMode = null;
|
|
1366
|
+
}
|
|
1367
|
+
return result;
|
|
1368
|
+
}
|
|
1348
1369
|
/**
|
|
1349
1370
|
* Adopt an orphan worktree for a bootstrap-time merge (ADR-016 phase 2 / B4,
|
|
1350
1371
|
* issue #5622).
|