@opengsd/gsd-pi 1.1.1-dev.9bb7453 → 1.1.1-dev.9f86580
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/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
- package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
- package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/dist/resources/extensions/browser-tools/index.js +29 -2
- package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
- package/dist/resources/extensions/gsd/auto/phases.js +45 -3
- package/dist/resources/extensions/gsd/auto/session.js +2 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +21 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +26 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +4 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
- package/dist/resources/extensions/gsd/auto-timers.js +24 -10
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
- package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
- package/dist/resources/extensions/gsd/auto.js +26 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +16 -10
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +48 -29
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
- package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +1 -0
- package/dist/resources/extensions/gsd/context-masker.js +129 -5
- package/dist/resources/extensions/gsd/guided-flow.js +93 -108
- package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
- package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
- package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
- package/dist/resources/extensions/gsd/preferences-models.js +1 -0
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +5 -19
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
- package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
- package/dist/resources/extensions/gsd/tool-contract.js +6 -1
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +47 -7
- package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +113 -8
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +193 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -78
- package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
- package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
- 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/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/8357.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/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- 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/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -3
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +3 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +157 -18
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +159 -36
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- 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/handoff.js +16 -3
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
- package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
- package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +36 -5
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
- package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
- package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
- package/src/resources/extensions/gsd/auto/phases.ts +48 -6
- package/src/resources/extensions/gsd/auto/session.ts +2 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +26 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +4 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
- package/src/resources/extensions/gsd/auto-timers.ts +25 -9
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
- package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
- package/src/resources/extensions/gsd/auto.ts +28 -4
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +16 -10
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +63 -29
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
- package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +1 -0
- package/src/resources/extensions/gsd/context-masker.ts +152 -5
- package/src/resources/extensions/gsd/guided-flow.ts +128 -135
- package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
- package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
- package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
- package/src/resources/extensions/gsd/preferences-models.ts +1 -0
- package/src/resources/extensions/gsd/preferences-types.ts +8 -0
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +5 -19
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
- package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +147 -5
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +409 -0
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
- package/src/resources/extensions/gsd/tool-contract.ts +7 -1
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +82 -7
- package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +146 -9
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +210 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -78
- package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
- package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
- /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_ssgManifest.js +0 -0
|
@@ -26,6 +26,7 @@ import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
|
26
26
|
import { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
|
|
27
27
|
import { classifyProject } from "./detection.js";
|
|
28
28
|
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchForceReset, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor, nativeMergeAbort, nativeWorktreeList, nativeLsFiles, } from "./native-git-bridge.js";
|
|
29
|
+
import { CLOSEOUT_CONSISTENCY_BLOCKED_REASON, checkCloseoutConsistencyGate, formatCloseoutConsistencyBlock, } from "./closeout-consistency-gate.js";
|
|
29
30
|
import { gsdHome } from "./gsd-home.js";
|
|
30
31
|
import { createWorkspace } from "./workspace.js";
|
|
31
32
|
import { _finalizeProjectionForMergeImpl, _projectRootToWorktreeImpl, _projectWorktreeToRootImpl, } from "./worktree-state-projection.js";
|
|
@@ -1513,17 +1514,29 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
1513
1514
|
// symlink layout) — ATTACHing a WAL-mode file to itself corrupts the
|
|
1514
1515
|
// database (#2823).
|
|
1515
1516
|
if (isDbAvailable()) {
|
|
1517
|
+
const contract = resolveGsdPathContract(worktreeCwd, originalBasePath_);
|
|
1518
|
+
const worktreeDbPath = join(contract.worktreeGsd ?? join(worktreeCwd, ".gsd"), "gsd.db");
|
|
1519
|
+
const mainDbPath = contract.projectDb;
|
|
1516
1520
|
try {
|
|
1517
|
-
const
|
|
1518
|
-
|
|
1519
|
-
|
|
1521
|
+
const activeDbPath = getDbPath();
|
|
1522
|
+
if (activeDbPath && _shouldReconcileWorktreeDb(activeDbPath, mainDbPath)) {
|
|
1523
|
+
closeDatabase();
|
|
1524
|
+
if (!openDatabase(mainDbPath)) {
|
|
1525
|
+
throw new Error(`cannot open project DB at ${mainDbPath}`);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1520
1528
|
if (_shouldReconcileWorktreeDb(worktreeDbPath, mainDbPath)) {
|
|
1521
1529
|
reconcileWorktreeDb(mainDbPath, worktreeDbPath);
|
|
1522
1530
|
}
|
|
1523
1531
|
}
|
|
1524
1532
|
catch (err) {
|
|
1525
|
-
|
|
1526
|
-
logError("worktree",
|
|
1533
|
+
const message = `DB reconciliation failed before milestone ${milestoneId} merge: ${err instanceof Error ? err.message : String(err)}`;
|
|
1534
|
+
logError("worktree", message);
|
|
1535
|
+
throw new GSDError(GSD_GIT_ERROR, `${message}. Recovery reason: ${CLOSEOUT_CONSISTENCY_BLOCKED_REASON}.`);
|
|
1536
|
+
}
|
|
1537
|
+
const closeoutGate = checkCloseoutConsistencyGate(milestoneId);
|
|
1538
|
+
if (!closeoutGate.ok) {
|
|
1539
|
+
throw new GSDError(GSD_GIT_ERROR, formatCloseoutConsistencyBlock(closeoutGate));
|
|
1527
1540
|
}
|
|
1528
1541
|
}
|
|
1529
1542
|
// 2. Get completed slices for commit message
|
|
@@ -30,7 +30,7 @@ import { playNotificationBell, sendDesktopNotification } from "./notifications.j
|
|
|
30
30
|
import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, } from "./auto-budget.js";
|
|
31
31
|
import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools, isToolInvocationError, isQueuedUserMessageSkip, isDeterministicPolicyError, } from "./auto-tool-tracking.js";
|
|
32
32
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
33
|
-
import { selectAndApplyModel, resolveModelId, clearToolBaseline } from "./auto-model-selection.js";
|
|
33
|
+
import { selectAndApplyModel, resolveModelId, clearToolBaseline, getToolBaselineSnapshot } from "./auto-model-selection.js";
|
|
34
34
|
import { resetRoutingHistory, recordOutcome } from "./routing-history.js";
|
|
35
35
|
import { resetHookState, runPreDispatchHooks, restoreHookState, clearPersistedHookState, } from "./post-unit-hooks.js";
|
|
36
36
|
import { runGSDDoctor, rebuildState } from "./doctor.js";
|
|
@@ -287,8 +287,24 @@ export function _synthesizePausedSessionRecoveryForTest(basePath, unitType, unit
|
|
|
287
287
|
function handlePausedSessionResumeRecovery(basePath, state, notify) {
|
|
288
288
|
if (!state.pausedSessionFile)
|
|
289
289
|
return { skippedReplay: false };
|
|
290
|
-
const pausedRecoveryUnitType = state.currentUnit?.type ?? state.pausedUnitType ??
|
|
291
|
-
const pausedRecoveryUnitId = state.currentUnit?.id ?? state.pausedUnitId ??
|
|
290
|
+
const pausedRecoveryUnitType = state.currentUnit?.type ?? state.pausedUnitType ?? null;
|
|
291
|
+
const pausedRecoveryUnitId = state.currentUnit?.id ?? state.pausedUnitId ?? null;
|
|
292
|
+
// When the paused-session metadata never captured the unit identity (the
|
|
293
|
+
// pause happened between units, or the worker died before currentUnit was
|
|
294
|
+
// set), we have nothing to verify against and nothing correct to target. A
|
|
295
|
+
// replay synthesized with an "unknown" unit re-injects an unbounded,
|
|
296
|
+
// mis-identified tool-call blob into the fresh resume context — exactly the
|
|
297
|
+
// thrash that turns one stuck unit into several. Disk state has already been
|
|
298
|
+
// rebuilt (rebuildState + doctor) before this runs, so skip the replay and
|
|
299
|
+
// let the normal dispatcher recompute the next unit from disk.
|
|
300
|
+
if (!pausedRecoveryUnitType || !pausedRecoveryUnitId) {
|
|
301
|
+
state.pausedSessionFile = null;
|
|
302
|
+
state.pausedUnitType = null;
|
|
303
|
+
state.pausedUnitId = null;
|
|
304
|
+
state.pendingCrashRecovery = null;
|
|
305
|
+
notify("Paused session had no recorded unit identity. Skipping tool-call replay and resuming from disk state.");
|
|
306
|
+
return { skippedReplay: true };
|
|
307
|
+
}
|
|
292
308
|
const completedPausedUnit = verifyExpectedArtifact(pausedRecoveryUnitType, pausedRecoveryUnitId, basePath);
|
|
293
309
|
if (completedPausedUnit) {
|
|
294
310
|
state.pausedSessionFile = null;
|
|
@@ -1637,7 +1653,10 @@ export function createWiredDispatchAdapter(ctx, pi, dispatchBasePath, session) {
|
|
|
1637
1653
|
const authMode = sessionProvider && typeof ctx.modelRegistry?.getProviderAuthMode === "function"
|
|
1638
1654
|
? ctx.modelRegistry.getProviderAuthMode(sessionProvider)
|
|
1639
1655
|
: undefined;
|
|
1640
|
-
|
|
1656
|
+
// Use baseline snapshot — same reason as phases.ts:runDispatch: the live
|
|
1657
|
+
// active set may be narrowed by the prior unit before selectAndApplyModel
|
|
1658
|
+
// restores it, causing false transport-preflight failures (#477 follow-up).
|
|
1659
|
+
const activeTools = getToolBaselineSnapshot(pi);
|
|
1641
1660
|
// Mirrors runDispatch: deep-planning keeps approval gates in plain chat
|
|
1642
1661
|
// because structured questions can be cancelled outside the chat turn on
|
|
1643
1662
|
// some transports.
|
|
@@ -1678,6 +1697,9 @@ export function createWiredDispatchAdapter(ctx, pi, dispatchBasePath, session) {
|
|
|
1678
1697
|
sessionContextWindow,
|
|
1679
1698
|
sessionProvider,
|
|
1680
1699
|
modelRegistry,
|
|
1700
|
+
activeTools,
|
|
1701
|
+
sessionAuthMode: authMode,
|
|
1702
|
+
sessionBaseUrl: ctx.model?.baseUrl,
|
|
1681
1703
|
});
|
|
1682
1704
|
if (action.action === "stop") {
|
|
1683
1705
|
if (session)
|
|
@@ -65,6 +65,9 @@ function readDetails(result) {
|
|
|
65
65
|
}
|
|
66
66
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- result shape varies by tool
|
|
67
67
|
function formatToolErrorText(result, details) {
|
|
68
|
+
if (typeof details?.displayReason === "string" && details.displayReason) {
|
|
69
|
+
return details.displayReason;
|
|
70
|
+
}
|
|
68
71
|
const message = details?.error
|
|
69
72
|
?? result?.content?.find((entry) => entry.type === "text")?.text
|
|
70
73
|
?? "unknown";
|
|
@@ -405,6 +408,9 @@ export function registerDbTools(pi) {
|
|
|
405
408
|
kind: StringEnum(["gsd_uat_exec", "gsd_exec", "screenshot", "log", "url", "browser"], { description: "Evidence kind" }),
|
|
406
409
|
ref: Type.String({ description: "Evidence ID, approved .gsd path, or URL" }),
|
|
407
410
|
note: Type.Optional(Type.String({ description: "Short evidence note" })),
|
|
411
|
+
unitType: Type.Optional(Type.String({ description: "Unit that produced the evidence" })),
|
|
412
|
+
tool: Type.Optional(Type.String({ description: "Tool that produced the evidence" })),
|
|
413
|
+
executionId: Type.Optional(Type.String({ description: "Stable execution or artifact id" })),
|
|
408
414
|
});
|
|
409
415
|
const uatCheck = Type.Object({
|
|
410
416
|
id: Type.String({ description: "Stable check ID from the UAT spec" }),
|
|
@@ -416,17 +422,17 @@ export function registerDbTools(pi) {
|
|
|
416
422
|
nonAutomatable: Type.Optional(Type.Boolean({ description: "True when the check is explicitly non-automatable" })),
|
|
417
423
|
});
|
|
418
424
|
const toolPresentationBlock = Type.Object({
|
|
419
|
-
surface: StringEnum(["provider-tools", "claude-code-sdk", "mcp", "hybrid"], { description: "Tool presentation surface" }),
|
|
425
|
+
surface: Type.Optional(StringEnum(["provider-tools", "claude-code-sdk", "mcp", "hybrid"], { description: "Tool presentation surface" })),
|
|
420
426
|
model: Type.Optional(Type.Object({
|
|
421
427
|
provider: Type.Optional(Type.String()),
|
|
422
428
|
api: Type.Optional(Type.String()),
|
|
423
429
|
id: Type.Optional(Type.String()),
|
|
424
430
|
})),
|
|
425
|
-
presentedTools: Type.Array(Type.String(), { description: "Tool names actually presented to the model" }),
|
|
426
|
-
blockedTools: Type.Array(Type.Object({
|
|
431
|
+
presentedTools: Type.Optional(Type.Array(Type.String(), { description: "Tool names actually presented to the model" })),
|
|
432
|
+
blockedTools: Type.Optional(Type.Array(Type.Object({
|
|
427
433
|
name: Type.String(),
|
|
428
434
|
reason: Type.String(),
|
|
429
|
-
}), { description: "Tool names blocked from the model with reasons" }),
|
|
435
|
+
}), { description: "Tool names blocked from the model with reasons" })),
|
|
430
436
|
aliases: Type.Optional(Type.Array(Type.Object({
|
|
431
437
|
requested: Type.String(),
|
|
432
438
|
canonical: Type.String(),
|
|
@@ -448,12 +454,12 @@ export function registerDbTools(pi) {
|
|
|
448
454
|
"Do not use raw gsd_summary_save as a substitute for UAT results.",
|
|
449
455
|
],
|
|
450
456
|
parameters: Type.Object({
|
|
451
|
-
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
452
|
-
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
453
|
-
uatType:
|
|
454
|
-
verdict:
|
|
455
|
-
checks: Type.Array(uatCheck, { description: "Structured check results" }),
|
|
456
|
-
presentation: toolPresentationBlock,
|
|
457
|
+
milestoneId: Type.Optional(Type.String({ description: "Milestone ID (e.g. M001)" })),
|
|
458
|
+
sliceId: Type.Optional(Type.String({ description: "Slice ID (e.g. S01)" })),
|
|
459
|
+
uatType: Type.Optional(Type.String({ description: "Declared UAT mode" })),
|
|
460
|
+
verdict: Type.Optional(Type.String({ description: "Overall UAT verdict: PASS, FAIL, or PARTIAL" })),
|
|
461
|
+
checks: Type.Optional(Type.Array(uatCheck, { description: "Structured check results" })),
|
|
462
|
+
presentation: Type.Optional(toolPresentationBlock),
|
|
457
463
|
notes: Type.Optional(Type.String({ description: "Overall verdict rationale" })),
|
|
458
464
|
attempt: Type.Optional(Type.String({ description: "Attempt number or auto" })),
|
|
459
465
|
previousAttemptId: Type.Optional(Type.String({ description: "Prior attempt ID, when retrying" })),
|
|
@@ -30,7 +30,7 @@ import { getGuidedUnitContext } from "../guided-unit-context.js";
|
|
|
30
30
|
import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-recovery.js";
|
|
31
31
|
import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
|
|
32
32
|
import { filterToolsForProvider } from "../model-router.js";
|
|
33
|
-
import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
|
|
33
|
+
import { RUN_UAT_READ_ONLY_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
|
|
34
34
|
let approvalQuestionAbortInFlight = false;
|
|
35
35
|
async function loadWelcomeScreenModule() {
|
|
36
36
|
const candidates = [];
|
|
@@ -204,8 +204,18 @@ export function buildMinimalAutoGsdToolSet(activeToolNames, unitType, registered
|
|
|
204
204
|
return withPreservedShimTools([...new Set([...preserved, ...scoped])]);
|
|
205
205
|
}
|
|
206
206
|
export function buildRunUatGsdToolSet(activeToolNames, registeredToolNames = activeToolNames) {
|
|
207
|
-
const scoped = resolveScopedToolNames([...activeToolNames, ...registeredToolNames], [
|
|
208
|
-
|
|
207
|
+
const scoped = resolveScopedToolNames([...activeToolNames, ...registeredToolNames], [
|
|
208
|
+
...RUN_UAT_WORKFLOW_TOOL_NAMES,
|
|
209
|
+
...RUN_UAT_READ_ONLY_TOOL_NAMES,
|
|
210
|
+
"subagent",
|
|
211
|
+
...RUN_UAT_BROWSER_TOOL_NAMES,
|
|
212
|
+
]);
|
|
213
|
+
const resolved = [...new Set(scoped)];
|
|
214
|
+
const unresolved = RUN_UAT_WORKFLOW_TOOL_NAMES.filter((tool) => !resolved.some((name) => name === tool || (name.startsWith("mcp__") && name.endsWith(`__${tool}`))));
|
|
215
|
+
if (unresolved.length > 0) {
|
|
216
|
+
safetyLogWarning("bootstrap", `buildRunUatGsdToolSet: required run-uat workflow tool(s) not found in active/registered surface: ${unresolved.join(", ")}. Session may lack gsd-workflow MCP connection.`);
|
|
217
|
+
}
|
|
218
|
+
return resolved;
|
|
209
219
|
}
|
|
210
220
|
export function buildMinimalGsdWorkflowToolSet(activeToolNames, registeredToolNames = activeToolNames) {
|
|
211
221
|
const autoBaseTools = new Set(MINIMAL_AUTO_BASE_TOOL_NAMES);
|
|
@@ -382,6 +392,11 @@ function isContextDraftSummarySave(toolName, input) {
|
|
|
382
392
|
return false;
|
|
383
393
|
return input.artifact_type === "CONTEXT-DRAFT";
|
|
384
394
|
}
|
|
395
|
+
function withDepthGateDisplayReason(result, displayReason = "Depth confirmation is waiting for your answer.") {
|
|
396
|
+
if (!result.block)
|
|
397
|
+
return result;
|
|
398
|
+
return { ...result, displayReason };
|
|
399
|
+
}
|
|
385
400
|
function shouldBlockDeferredApprovalTool(toolName, input, basePath) {
|
|
386
401
|
if (deferredApprovalGate?.basePath !== basePath)
|
|
387
402
|
return { block: false };
|
|
@@ -389,14 +404,14 @@ function shouldBlockDeferredApprovalTool(toolName, input, basePath) {
|
|
|
389
404
|
return { block: false };
|
|
390
405
|
if (isContextDraftSummarySave(toolName, input))
|
|
391
406
|
return { block: false };
|
|
392
|
-
return {
|
|
407
|
+
return withDepthGateDisplayReason({
|
|
393
408
|
block: true,
|
|
394
409
|
reason: [
|
|
395
410
|
`HARD BLOCK: Approval question "${deferredApprovalGate.gateId}" has been shown to the user.`,
|
|
396
411
|
`Only CONTEXT-DRAFT persistence may finish in this same assistant turn.`,
|
|
397
412
|
`Wait for the user's answer before calling additional tools.`,
|
|
398
413
|
].join(" "),
|
|
399
|
-
};
|
|
414
|
+
});
|
|
400
415
|
}
|
|
401
416
|
export function resolveNotificationStoreBasePath(basePath) {
|
|
402
417
|
return resolveWorktreeProjectRoot(basePath);
|
|
@@ -453,6 +468,18 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
453
468
|
if (isAutoActive() || preserveCloseoutSurface) {
|
|
454
469
|
ctx.ui.setWidget("gsd-health", undefined);
|
|
455
470
|
}
|
|
471
|
+
// Cold start after /quit relaunches with cwd at the project root. When
|
|
472
|
+
// auto-mode is neither active nor paused (its own resume path re-enters the
|
|
473
|
+
// worktree with a lease check — auto.ts:3032), proactively chdir back into
|
|
474
|
+
// the active milestone's worktree so subsequent work isn't stranded at the
|
|
475
|
+
// root. Best-effort and a no-op when already inside a worktree.
|
|
476
|
+
if (!isAutoActive() && !isAutoPaused() && !preserveCloseoutSurface) {
|
|
477
|
+
try {
|
|
478
|
+
const { reenterActiveWorktreeIfNeeded } = await import("../worktree-reentry.js");
|
|
479
|
+
await reenterActiveWorktreeIfNeeded(basePath);
|
|
480
|
+
}
|
|
481
|
+
catch { /* non-fatal */ }
|
|
482
|
+
}
|
|
456
483
|
});
|
|
457
484
|
pi.on("session_switch", async (_event, ctx) => {
|
|
458
485
|
const basePath = contextBasePath(ctx);
|
|
@@ -754,7 +781,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
754
781
|
if (ctx) {
|
|
755
782
|
await maybePauseAutoForApprovalGate(ctx, pi, true, "Depth confirmation is waiting for your answer — pausing auto-mode.");
|
|
756
783
|
}
|
|
757
|
-
return bashGuard;
|
|
784
|
+
return withDepthGateDisplayReason(bashGuard);
|
|
758
785
|
}
|
|
759
786
|
}
|
|
760
787
|
else {
|
|
@@ -763,7 +790,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
763
790
|
if (ctx) {
|
|
764
791
|
await maybePauseAutoForApprovalGate(ctx, pi, true, "Depth confirmation is waiting for your answer — pausing auto-mode.");
|
|
765
792
|
}
|
|
766
|
-
return gateGuard;
|
|
793
|
+
return withDepthGateDisplayReason(gateGuard);
|
|
767
794
|
}
|
|
768
795
|
}
|
|
769
796
|
}
|
|
@@ -847,8 +874,9 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
847
874
|
if (!isToolCallEventType("write", event))
|
|
848
875
|
return;
|
|
849
876
|
const result = shouldBlockContextWrite(event.toolName, event.input.path, await getDiscussionMilestoneIdFor(discussionBasePath), isQueuePhaseActive(discussionBasePath), discussionBasePath);
|
|
850
|
-
if (result.block)
|
|
851
|
-
return result;
|
|
877
|
+
if (result.block) {
|
|
878
|
+
return withDepthGateDisplayReason(result, "Depth check required before writing milestone context.");
|
|
879
|
+
}
|
|
852
880
|
});
|
|
853
881
|
// ── Safety harness: evidence collection + destructive command blocking ──
|
|
854
882
|
pi.on("tool_call", async (event, ctx) => {
|
|
@@ -1073,16 +1101,19 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1073
1101
|
if (isAutoActive()) {
|
|
1074
1102
|
try {
|
|
1075
1103
|
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
1104
|
+
const { createObservationMask, createResponsesInputObservationMask, truncateContextResultMessages, truncateResponsesInputResultItems, } = await import("../context-masker.js");
|
|
1076
1105
|
const prefs = loadEffectiveGSDPreferences();
|
|
1077
1106
|
const cmConfig = prefs?.preferences.context_management;
|
|
1078
1107
|
// Observation masking: replace old tool results with placeholders
|
|
1079
1108
|
if (cmConfig?.observation_masking !== false) {
|
|
1080
1109
|
const keepTurns = cmConfig?.observation_mask_turns ?? 8;
|
|
1081
|
-
const { createObservationMask } = await import("../context-masker.js");
|
|
1082
|
-
const mask = createObservationMask(keepTurns);
|
|
1083
1110
|
const messages = payload.messages;
|
|
1084
1111
|
if (Array.isArray(messages)) {
|
|
1085
|
-
payload.messages =
|
|
1112
|
+
payload.messages = createObservationMask(keepTurns)(messages);
|
|
1113
|
+
}
|
|
1114
|
+
const input = payload.input;
|
|
1115
|
+
if (Array.isArray(input)) {
|
|
1116
|
+
payload.input = createResponsesInputObservationMask(keepTurns)(input);
|
|
1086
1117
|
}
|
|
1087
1118
|
}
|
|
1088
1119
|
// Tool result truncation: cap individual tool result content length.
|
|
@@ -1091,23 +1122,11 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1091
1122
|
const maxChars = cmConfig?.tool_result_max_chars ?? 800;
|
|
1092
1123
|
const msgs = payload.messages;
|
|
1093
1124
|
if (Array.isArray(msgs)) {
|
|
1094
|
-
payload.messages = msgs
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
if (totalLen > maxChars) {
|
|
1100
|
-
const truncated = blocks.map(b => {
|
|
1101
|
-
if (typeof b.text === "string" && b.text.length > maxChars) {
|
|
1102
|
-
return { ...b, text: b.text.slice(0, maxChars) + "\n…[truncated]" };
|
|
1103
|
-
}
|
|
1104
|
-
return b;
|
|
1105
|
-
});
|
|
1106
|
-
return { ...msg, content: truncated };
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
return msg;
|
|
1110
|
-
});
|
|
1125
|
+
payload.messages = truncateContextResultMessages(msgs, maxChars);
|
|
1126
|
+
}
|
|
1127
|
+
const input = payload.input;
|
|
1128
|
+
if (Array.isArray(input)) {
|
|
1129
|
+
payload.input = truncateResponsesInputResultItems(input, maxChars);
|
|
1111
1130
|
}
|
|
1112
1131
|
}
|
|
1113
1132
|
catch { /* non-fatal */ }
|
|
@@ -56,7 +56,7 @@ export const BUNDLED_SKILL_TRIGGERS = [
|
|
|
56
56
|
{ trigger: "Core Web Vitals — fix LCP, CLS, INP; layout shifts; page experience optimization", skill: "core-web-vitals" },
|
|
57
57
|
{ trigger: "GitHub Actions CI/CD — write, run, and debug workflow files; live syntax and run monitoring", skill: "github-workflows" },
|
|
58
58
|
{ trigger: "Comprehensive web quality audit — performance, accessibility, SEO, and best-practices (Lighthouse-style)", skill: "web-quality-audit" },
|
|
59
|
-
{ trigger: "gsd-browser
|
|
59
|
+
{ trigger: "gsd-browser opt-in and External MCP UAT — screenshots, assertions, console/network diagnostics", skill: "gsd-browser" },
|
|
60
60
|
{ trigger: "Browser automation — open sites, fill forms, click, screenshot, scrape, or test web apps programmatically", skill: "agent-browser" },
|
|
61
61
|
{ trigger: "Review UI code for Web Interface Guidelines compliance — UX, design, and accessibility patterns", skill: "web-design-guidelines" },
|
|
62
62
|
{ trigger: "UI/UX patterns reference — animations, CSS, typography, prefetching, icons (file:line findings)", skill: "userinterface-wiki" },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { minimatch } from "minimatch";
|
|
5
|
-
import { shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.js";
|
|
5
|
+
import { GSD_PHASE_SCOPE_DISPLAY_REASON, shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.js";
|
|
6
6
|
import { getIsolationMode } from "../preferences.js";
|
|
7
7
|
import { compileSubagentPermissionContract } from "../unit-context-manifest.js";
|
|
8
8
|
import { logWarning } from "../workflow-logger.js";
|
|
@@ -643,6 +643,13 @@ function blockReason(unitType, mode, what) {
|
|
|
643
643
|
`the work belongs in execute-task, not in a planning unit.`,
|
|
644
644
|
].join(" ");
|
|
645
645
|
}
|
|
646
|
+
function planningBlock(unitType, mode, what) {
|
|
647
|
+
return {
|
|
648
|
+
block: true,
|
|
649
|
+
reason: blockReason(unitType, mode, what),
|
|
650
|
+
displayReason: GSD_PHASE_SCOPE_DISPLAY_REASON,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
646
653
|
/**
|
|
647
654
|
* Planning-unit tool-policy enforcement. Returns { block } per the policy
|
|
648
655
|
* resolved from the active unit's manifest:
|
|
@@ -690,10 +697,10 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
|
|
|
690
697
|
if (tool.startsWith("gsd_"))
|
|
691
698
|
return { block: false };
|
|
692
699
|
if (PLANNING_WRITE_TOOLS.has(tool) || tool === "bash" || PLANNING_SUBAGENT_TOOLS.has(tool)) {
|
|
693
|
-
return
|
|
700
|
+
return planningBlock(unitType, policy.mode, `${tool} is not permitted (read-only)`);
|
|
694
701
|
}
|
|
695
702
|
// Unknown tool in read-only mode — block by default.
|
|
696
|
-
return
|
|
703
|
+
return planningBlock(unitType, policy.mode, `tool "${tool}" is not on the read-only allowlist`);
|
|
697
704
|
}
|
|
698
705
|
// planning / planning-dispatch / docs / verification modes share the same surface for safe tools, bash, and subagent.
|
|
699
706
|
if (PLANNING_SAFE_TOOLS.has(tool))
|
|
@@ -711,10 +718,7 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
|
|
|
711
718
|
// instead of silently bypassing the gate.
|
|
712
719
|
if (agentClasses === undefined) {
|
|
713
720
|
warnMissingControlledDispatchAgentClasses(unitType, policy.mode, tool);
|
|
714
|
-
return {
|
|
715
|
-
block: true,
|
|
716
|
-
reason: blockReason(unitType, policy.mode, `subagent dispatch blocked: stale caller did not supply agent identities for "${tool}"; update extractSubagentAgentClasses to handle this input shape`),
|
|
717
|
-
};
|
|
721
|
+
return planningBlock(unitType, policy.mode, `subagent dispatch blocked: stale caller did not supply agent identities for "${tool}"; update extractSubagentAgentClasses to handle this input shape`);
|
|
718
722
|
}
|
|
719
723
|
// agentClasses was explicitly provided but resolved to an empty list (for
|
|
720
724
|
// example, a bare tool call with no agent field). Pass through; no agents
|
|
@@ -724,41 +728,29 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
|
|
|
724
728
|
}
|
|
725
729
|
const globallyDisallowed = requested.find(a => !isReadOnlySpecialist(a));
|
|
726
730
|
if (globallyDisallowed) {
|
|
727
|
-
return {
|
|
728
|
-
block: true,
|
|
729
|
-
reason: blockReason(unitType, policy.mode, `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`),
|
|
730
|
-
};
|
|
731
|
+
return planningBlock(unitType, policy.mode, `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`);
|
|
731
732
|
}
|
|
732
733
|
const disallowedByPolicy = requested.find(a => !allowed.has(a));
|
|
733
734
|
if (disallowedByPolicy) {
|
|
734
|
-
return {
|
|
735
|
-
block: true,
|
|
736
|
-
reason: blockReason(unitType, policy.mode, `subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`),
|
|
737
|
-
};
|
|
735
|
+
return planningBlock(unitType, policy.mode, `subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`);
|
|
738
736
|
}
|
|
739
737
|
return { block: false };
|
|
740
738
|
}
|
|
741
|
-
return
|
|
739
|
+
return planningBlock(unitType, policy.mode, "subagent dispatch is not permitted in planning units");
|
|
742
740
|
}
|
|
743
741
|
if (tool === "bash") {
|
|
744
742
|
if (policy.mode === "verification") {
|
|
745
743
|
if (BASH_VERIFICATION_RE.test(pathOrCommand) || BASH_READ_ONLY_RE.test(pathOrCommand))
|
|
746
744
|
return { block: false };
|
|
747
|
-
return {
|
|
748
|
-
block: true,
|
|
749
|
-
reason: blockReason(unitType, policy.mode, `bash is restricted to build/test verification commands (npm run build, npm test, etc.); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`),
|
|
750
|
-
};
|
|
745
|
+
return planningBlock(unitType, policy.mode, `bash is restricted to build/test verification commands (npm run build, npm test, etc.); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`);
|
|
751
746
|
}
|
|
752
747
|
if (BASH_READ_ONLY_RE.test(pathOrCommand))
|
|
753
748
|
return { block: false };
|
|
754
|
-
return {
|
|
755
|
-
block: true,
|
|
756
|
-
reason: blockReason(unitType, policy.mode, `bash is restricted to read-only commands (cat/grep/git log/etc); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`),
|
|
757
|
-
};
|
|
749
|
+
return planningBlock(unitType, policy.mode, `bash is restricted to read-only commands (cat/grep/git log/etc); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`);
|
|
758
750
|
}
|
|
759
751
|
if (PLANNING_WRITE_TOOLS.has(tool)) {
|
|
760
752
|
if (!pathOrCommand) {
|
|
761
|
-
return
|
|
753
|
+
return planningBlock(unitType, policy.mode, `${tool} called with empty path`);
|
|
762
754
|
}
|
|
763
755
|
const absPath = isAbsolute(pathOrCommand) ? pathOrCommand : resolve(basePath, pathOrCommand);
|
|
764
756
|
// Always allow .gsd/ writes — that's where planning artifacts live.
|
|
@@ -768,10 +760,7 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
|
|
|
768
760
|
if (policy.mode === "docs" && matchesAllowedGlob(absPath, basePath, policy.allowedPathGlobs)) {
|
|
769
761
|
return { block: false };
|
|
770
762
|
}
|
|
771
|
-
return {
|
|
772
|
-
block: true,
|
|
773
|
-
reason: blockReason(unitType, policy.mode, `cannot ${tool} "${pathOrCommand}" — writes are restricted to .gsd/${policy.mode === "docs" ? " and " + policy.allowedPathGlobs.join(", ") : ""}`),
|
|
774
|
-
};
|
|
763
|
+
return planningBlock(unitType, policy.mode, `cannot ${tool} "${pathOrCommand}" — writes are restricted to .gsd/${policy.mode === "docs" ? " and " + policy.allowedPathGlobs.join(", ") : ""}`);
|
|
775
764
|
}
|
|
776
765
|
// Unknown tool name — pass through. Other layers (queue, pending-gate,
|
|
777
766
|
// CONTEXT.md write) catch known mutating shapes; defaulting to allow here
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Shared DB-backed guard for milestone closeout finalization.
|
|
3
|
+
import { getDbPath, getLatestAssessmentByScope, getMilestone, getMilestoneSlices, getPendingGates, getSliceTasks, isDbAvailable, refreshOpenDatabaseFromDisk, } from "./gsd-db.js";
|
|
4
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
5
|
+
export const CLOSEOUT_CONSISTENCY_BLOCKED_REASON = "closeout-consistency-blocked";
|
|
6
|
+
function blocked(reason, message) {
|
|
7
|
+
return {
|
|
8
|
+
ok: false,
|
|
9
|
+
reason,
|
|
10
|
+
recoveryReason: CLOSEOUT_CONSISTENCY_BLOCKED_REASON,
|
|
11
|
+
message,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function isFileBackedDbPath(path) {
|
|
15
|
+
return Boolean(path && path !== ":memory:");
|
|
16
|
+
}
|
|
17
|
+
export function checkCloseoutConsistencyGate(milestoneId, options = {}) {
|
|
18
|
+
if (!isDbAvailable()) {
|
|
19
|
+
return blocked("db-unavailable", `Closeout consistency blocked for ${milestoneId}: canonical DB is unavailable.`);
|
|
20
|
+
}
|
|
21
|
+
if (options.refreshFromDisk && isFileBackedDbPath(getDbPath()) && !refreshOpenDatabaseFromDisk()) {
|
|
22
|
+
return blocked("db-refresh-failed", `Closeout consistency blocked for ${milestoneId}: canonical DB refresh failed.`);
|
|
23
|
+
}
|
|
24
|
+
const milestone = getMilestone(milestoneId);
|
|
25
|
+
if (!milestone) {
|
|
26
|
+
return blocked("milestone-missing", `Closeout consistency blocked for ${milestoneId}: milestone is missing from canonical DB.`);
|
|
27
|
+
}
|
|
28
|
+
if (!isClosedStatus(milestone.status)) {
|
|
29
|
+
return blocked("milestone-open", `Closeout consistency blocked for ${milestoneId}: canonical DB milestone status is "${milestone.status}".`);
|
|
30
|
+
}
|
|
31
|
+
if (milestone.status !== "skipped") {
|
|
32
|
+
const validation = getLatestAssessmentByScope(milestoneId, "milestone-validation");
|
|
33
|
+
if (validation?.status !== "pass") {
|
|
34
|
+
return blocked("validation-not-pass", `Closeout consistency blocked for ${milestoneId}: latest milestone validation is "${validation?.status ?? "absent"}".`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const slices = getMilestoneSlices(milestoneId);
|
|
38
|
+
if (slices.length === 0 && milestone.status !== "skipped") {
|
|
39
|
+
return blocked("slice-missing", `Closeout consistency blocked for ${milestoneId}: no slices exist in canonical DB.`);
|
|
40
|
+
}
|
|
41
|
+
for (const slice of slices) {
|
|
42
|
+
if (!isClosedStatus(slice.status)) {
|
|
43
|
+
return blocked("slice-open", `Closeout consistency blocked for ${milestoneId}: slice ${slice.id} status is "${slice.status}".`);
|
|
44
|
+
}
|
|
45
|
+
for (const task of getSliceTasks(milestoneId, slice.id)) {
|
|
46
|
+
if (!isClosedStatus(task.status)) {
|
|
47
|
+
return blocked("task-open", `Closeout consistency blocked for ${milestoneId}: task ${slice.id}/${task.id} status is "${task.status}".`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const pendingGate = getPendingGates(milestoneId, slice.id)[0];
|
|
51
|
+
if (pendingGate) {
|
|
52
|
+
return blocked("quality-gate-pending", `Closeout consistency blocked for ${milestoneId}: quality gate ${pendingGate.gate_id} is still pending for ${slice.id}.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { ok: true };
|
|
56
|
+
}
|
|
57
|
+
export function formatCloseoutConsistencyBlock(result) {
|
|
58
|
+
if (result.ok)
|
|
59
|
+
return "";
|
|
60
|
+
return `${result.message} Recovery reason: ${result.recoveryReason}. Resolve the canonical DB state and run /gsd auto to retry.`;
|
|
61
|
+
}
|
|
@@ -198,6 +198,16 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
|
|
|
198
198
|
if (!(await guardRemoteSession(ctx, pi)))
|
|
199
199
|
return true;
|
|
200
200
|
const basePath = projectRoot();
|
|
201
|
+
// Cold start after /quit lands at the project root, not the worktree. If the
|
|
202
|
+
// active milestone has a live worktree, chdir back into it now so the agent
|
|
203
|
+
// doesn't have to search for it. Best-effort; resolves to a no-op otherwise.
|
|
204
|
+
try {
|
|
205
|
+
const { reenterActiveWorktreeIfNeeded } = await import("../../worktree-reentry.js");
|
|
206
|
+
await reenterActiveWorktreeIfNeeded(basePath, {
|
|
207
|
+
notify: (message) => ctx.ui.notify(message, "info"),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch { /* non-fatal */ }
|
|
201
211
|
const { hasGsdBootstrapArtifacts } = await import("../../detection.js");
|
|
202
212
|
const { gsdRoot } = await import("../../paths.js");
|
|
203
213
|
if (!hasGsdBootstrapArtifacts(gsdRoot(basePath))) {
|
|
@@ -34,7 +34,7 @@ export function formatMcpInitResult(status, configPath, targetPath) {
|
|
|
34
34
|
`Config: ${configPath}`,
|
|
35
35
|
"",
|
|
36
36
|
"MCP-capable clients can now load the GSD workflow and gsd-browser MCP servers from this folder.",
|
|
37
|
-
"Pi Providers use
|
|
37
|
+
"Pi Providers use built-in browser tools directly; this project config is for External MCP Clients.",
|
|
38
38
|
"Restart or reconnect any client that already has this project open.",
|
|
39
39
|
].join("\n");
|
|
40
40
|
}
|
|
@@ -122,6 +122,7 @@ function collectConfigSections() {
|
|
|
122
122
|
supRows.push({ label: "Model", value: sup.model });
|
|
123
123
|
supRows.push({ label: "Soft timeout", value: `${sup.soft_timeout_minutes}m` });
|
|
124
124
|
supRows.push({ label: "Idle timeout", value: `${sup.idle_timeout_minutes}m` });
|
|
125
|
+
supRows.push({ label: "Stalled tool timeout", value: `${sup.stalled_tool_timeout_minutes}m` });
|
|
125
126
|
supRows.push({ label: "Hard timeout", value: `${sup.hard_timeout_minutes}m` });
|
|
126
127
|
sections.push({ title: "Auto Supervisor", rows: supRows });
|
|
127
128
|
}
|