@opengsd/gsd-pi 1.4.0-dev.c18009cd → 1.5.0-dev.553dc518
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 +10 -10
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -3
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
- package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +4 -5
- package/dist/resources/extensions/gsd/auto-start.js +3 -2
- package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
- package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
- package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
- package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
- package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
- package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
- package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
- package/dist/resources/extensions/gsd/auto.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +19 -1
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +118 -21
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
- package/dist/resources/extensions/gsd/db/queries.js +9 -9
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
- package/dist/resources/extensions/gsd/flat-phase-migration.js +14 -3
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
- package/dist/resources/extensions/gsd/git-service.js +45 -0
- package/dist/resources/extensions/gsd/gsd-db.js +11 -0
- package/dist/resources/extensions/gsd/guided-flow.js +6 -10
- package/dist/resources/extensions/gsd/markdown-renderer.js +15 -3
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +82 -0
- package/dist/resources/extensions/gsd/prompts/system.md +6 -6
- package/dist/resources/extensions/gsd/quick.js +12 -3
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
- package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/unit-registry.js +12 -2
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
- package/dist/resources/extensions/gsd/worktree-command.js +1 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
- package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- 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 +8 -8
- 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/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +24 -35
- package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
- package/package.json +6 -6
- 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/dist/export-html/template.js +53 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +14 -9
- 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/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.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 +7 -3
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
- package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
- package/packages/pi-ai/dist/tool-result-content.js +62 -0
- package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
- package/packages/pi-ai/dist/types.d.ts +8 -20
- 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 +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/core/export-html/template.js +53 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
- package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -3
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
- package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +4 -5
- package/src/resources/extensions/gsd/auto-start.ts +4 -2
- package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
- package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
- package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
- package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
- package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
- package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
- package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
- package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
- package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
- package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
- package/src/resources/extensions/gsd/auto.ts +11 -17
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +19 -1
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +152 -21
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
- package/src/resources/extensions/gsd/db/queries.ts +9 -9
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +53 -0
- package/src/resources/extensions/gsd/gsd-db.ts +13 -0
- package/src/resources/extensions/gsd/guided-flow.ts +7 -11
- package/src/resources/extensions/gsd/markdown-renderer.ts +15 -3
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/preferences-types.ts +30 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +69 -0
- package/src/resources/extensions/gsd/prompts/system.md +6 -6
- package/src/resources/extensions/gsd/quick.ts +11 -3
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +44 -1
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
- package/src/resources/extensions/gsd/tool-contract.ts +29 -0
- package/src/resources/extensions/gsd/unit-registry.ts +16 -2
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
- package/src/resources/extensions/gsd/worktree-command.ts +1 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
- package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
- /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → ZpFDazP-38PXBPiI_LdZf}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → ZpFDazP-38PXBPiI_LdZf}/_ssgManifest.js +0 -0
|
@@ -51,10 +51,16 @@ import { atomicWriteSync } from "./atomic-write.js";
|
|
|
51
51
|
import { captureIntegrationBranch, detectWorktreeName, getCurrentBranch, getMainBranch, setActiveMilestoneId, } from "./worktree.js";
|
|
52
52
|
import { GitServiceImpl } from "./git-service.js";
|
|
53
53
|
import { getPriorSliceCompletionBlocker } from "./dispatch-guard.js";
|
|
54
|
-
import {
|
|
54
|
+
import { autoWorktreeBranch } from "./auto-worktree-branch-lifecycle.js";
|
|
55
|
+
import { createAutoWorktree } from "./auto-worktree-creation.js";
|
|
56
|
+
import { isInAutoWorktree } from "./auto-worktree-entry.js";
|
|
57
|
+
import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
|
|
58
|
+
import { checkResourcesStale } from "./auto-worktree-resource-version.js";
|
|
59
|
+
import { escapeStaleWorktree } from "./auto-worktree-runtime-cleanup.js";
|
|
60
|
+
import { teardownAutoWorktree } from "./auto-worktree-teardown.js";
|
|
55
61
|
import { pruneQueueOrder } from "./queue-order.js";
|
|
56
62
|
import { startCommandPolling as _startCommandPolling, isRemoteConfigured } from "../remote-questions/manager.js";
|
|
57
|
-
import {
|
|
63
|
+
import { createDefaultMilestoneMergeTransaction } from "./milestone-merge-transaction.js";
|
|
58
64
|
import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
|
|
59
65
|
import { reconcileMergeState, verifyExpectedArtifact, } from "./auto-recovery.js";
|
|
60
66
|
import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
|
|
@@ -1746,7 +1752,7 @@ export function buildWorktreeLifecycleDeps() {
|
|
|
1746
1752
|
return new GitServiceImpl(basePath, gitConfig);
|
|
1747
1753
|
},
|
|
1748
1754
|
worktreeProjection: new WorktreeStateProjection(),
|
|
1749
|
-
mergeMilestone:
|
|
1755
|
+
mergeMilestone: createDefaultMilestoneMergeTransaction(),
|
|
1750
1756
|
};
|
|
1751
1757
|
}
|
|
1752
1758
|
function buildLifecycle() {
|
|
@@ -6,7 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@gsd/pi-coding-agent";
|
|
7
7
|
import { logWarning } from "../workflow-logger.js";
|
|
8
8
|
import { getWorkflowDatabaseStatus, openWorkflowDatabase, } from "../db-workspace.js";
|
|
9
|
-
import { getAutoWorktreePath } from "../auto-worktree.js";
|
|
9
|
+
import { getAutoWorktreePath } from "../auto-worktree-path-resolution.js";
|
|
10
10
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
11
11
|
import { worktreesDirs } from "../worktree-placement.js";
|
|
12
12
|
export function safeWorkspaceCwd() {
|
|
@@ -15,7 +15,7 @@ import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
|
15
15
|
import { clearAutoCompletionStopInProgress, clearToolInvocationError, getAutoRuntimeSnapshot, getSourceObservationStore, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, isInteractiveElicitationInFlight, markToolEnd, markToolStart, recordAutoToolSurfaceSnapshot, recordToolInvocationError, } from "../auto-runtime-state.js";
|
|
16
16
|
import { isDeterministicPolicyError, isQueuedUserMessageSkip, isToolInvocationError, isToolUnavailableError, } from "../auto-tool-tracking.js";
|
|
17
17
|
import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
|
|
18
|
-
import { checkToolCallLoop, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
18
|
+
import { checkToolCallLoop, configureToolCallLoopGuard, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
19
19
|
import { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
|
|
20
20
|
import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
|
|
21
21
|
import { saveActivityLog } from "../activity-log.js";
|
|
@@ -432,6 +432,22 @@ async function applyCompactionThresholdOverride(ctx) {
|
|
|
432
432
|
ctx.setCompactionThresholdOverride?.(0.6);
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* Apply user-tunable tool-call loop guard thresholds (#1198) from GSD
|
|
437
|
+
* preferences plus GSD_TOOL_LOOP_* env overrides. Runs at session boundaries
|
|
438
|
+
* so both interactive sessions and `/gsd auto` pick up the configuration.
|
|
439
|
+
* Non-fatal: falls back to built-in defaults when preferences cannot be loaded.
|
|
440
|
+
*/
|
|
441
|
+
async function applyToolCallLoopGuardConfig(basePath) {
|
|
442
|
+
try {
|
|
443
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
444
|
+
const prefs = loadEffectiveGSDPreferences(basePath);
|
|
445
|
+
configureToolCallLoopGuard(prefs?.preferences.tool_call_loop_guard);
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
configureToolCallLoopGuard(null);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
435
451
|
function clearDeferredApprovalGate(basePath) {
|
|
436
452
|
if (!basePath) {
|
|
437
453
|
deferredApprovalGates.clear();
|
|
@@ -779,6 +795,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
779
795
|
}
|
|
780
796
|
resetWriteGateState(basePath);
|
|
781
797
|
resetToolCallLoopGuard();
|
|
798
|
+
await applyToolCallLoopGuardConfig(basePath);
|
|
782
799
|
approvalQuestionAbortInFlight = false;
|
|
783
800
|
clearDeferredApprovalGate();
|
|
784
801
|
await resetAskUserQuestionsTurnCache();
|
|
@@ -864,6 +881,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
864
881
|
initSessionNotifications(ctx);
|
|
865
882
|
resetWriteGateState(basePath);
|
|
866
883
|
resetToolCallLoopGuard();
|
|
884
|
+
await applyToolCallLoopGuardConfig(basePath);
|
|
867
885
|
clearDeferredApprovalGate();
|
|
868
886
|
await resetAskUserQuestionsTurnCache();
|
|
869
887
|
clearDiscussionFlowState(basePath);
|
|
@@ -11,7 +11,7 @@ import { resolveModelWithFallbacksForUnit } from "../preferences-models.js";
|
|
|
11
11
|
import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
|
|
12
12
|
import { extractIntroAndRules } from "../knowledge-parser.js";
|
|
13
13
|
import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
|
|
14
|
-
import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
|
|
14
|
+
import { getActiveAutoWorktreeContext } from "../auto-worktree-session-registry.js";
|
|
15
15
|
import { getActiveWorktreeName, getWorktreeOriginalCwd } from "../worktree-session-state.js";
|
|
16
16
|
import { deriveState } from "../state.js";
|
|
17
17
|
import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
|
|
@@ -21,29 +21,117 @@
|
|
|
21
21
|
* signature, so the identical-args streak never trips, while allowing
|
|
22
22
|
* tool-heavy turns that are making file-mutation progress. Whichever guard
|
|
23
23
|
* trips first blocks.
|
|
24
|
+
*
|
|
25
|
+
* Thresholds, exempt tools, and enable flags are user-tunable (#1198) via the
|
|
26
|
+
* `tool_call_loop_guard` key in `.gsd/PREFERENCES.md` and `GSD_TOOL_LOOP_*`
|
|
27
|
+
* environment variables, applied through {@link configureToolCallLoopGuard}.
|
|
28
|
+
* Defaults preserve the original hardcoded behavior.
|
|
24
29
|
*/
|
|
25
30
|
import { createHash } from "node:crypto";
|
|
26
31
|
import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
|
|
27
32
|
import { hasBrowserContractPrefix } from "../../shared/browser-contract.js";
|
|
28
33
|
import { canonicalToolName } from "../engine-hook-contract.js";
|
|
29
|
-
|
|
34
|
+
/** Built-in defaults. Preserved when preferences/env do not override them. */
|
|
35
|
+
const DEFAULT_MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
|
|
36
|
+
const DEFAULT_PER_TOOL_DEFAULT_CAP = 6;
|
|
37
|
+
const DEFAULT_PER_TOOL_REPEATABLE_CAP = 15;
|
|
38
|
+
const DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS = ["find", "glob", "grep", "ls", "read", "search_and_read"];
|
|
30
39
|
/** Interactive/user-facing tools where even 1 duplicate is confusing. */
|
|
31
40
|
const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]);
|
|
32
41
|
const MAX_CONSECUTIVE_STRICT = 1;
|
|
42
|
+
const STATE_MUTATING_TOOL_SET = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
|
|
43
|
+
function defaultGuardConfig() {
|
|
44
|
+
return {
|
|
45
|
+
guardEnabled: true,
|
|
46
|
+
identicalEnabled: true,
|
|
47
|
+
maxConsecutiveIdentical: DEFAULT_MAX_CONSECUTIVE_IDENTICAL_CALLS,
|
|
48
|
+
repeatedEnabled: true,
|
|
49
|
+
perToolDefaultCap: DEFAULT_PER_TOOL_DEFAULT_CAP,
|
|
50
|
+
perToolRepeatableCap: DEFAULT_PER_TOOL_REPEATABLE_CAP,
|
|
51
|
+
perToolExempt: new Set(DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
let config = defaultGuardConfig();
|
|
55
|
+
/** Parse a positive-integer env var, returning undefined when unset/invalid. */
|
|
56
|
+
function envPositiveInt(name) {
|
|
57
|
+
const raw = process.env[name];
|
|
58
|
+
if (raw === undefined || raw.trim() === "")
|
|
59
|
+
return undefined;
|
|
60
|
+
const n = Number(raw);
|
|
61
|
+
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Parse a boolean env var (`true`/`1` vs `false`/`0`), undefined when unset. */
|
|
64
|
+
function envBool(name) {
|
|
65
|
+
const raw = process.env[name];
|
|
66
|
+
if (raw === undefined || raw.trim() === "")
|
|
67
|
+
return undefined;
|
|
68
|
+
const v = raw.trim().toLowerCase();
|
|
69
|
+
if (v === "true" || v === "1" || v === "yes" || v === "on")
|
|
70
|
+
return true;
|
|
71
|
+
if (v === "false" || v === "0" || v === "no" || v === "off")
|
|
72
|
+
return false;
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/** Parse a comma-separated tool-name list env var. */
|
|
76
|
+
function envToolList(name) {
|
|
77
|
+
const raw = process.env[name];
|
|
78
|
+
if (!raw)
|
|
79
|
+
return [];
|
|
80
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
81
|
+
}
|
|
33
82
|
/**
|
|
34
|
-
*
|
|
83
|
+
* Resolve preferences + environment overrides into the active configuration.
|
|
84
|
+
* Environment variables win over preferences; preferences win over built-in
|
|
85
|
+
* defaults. User-supplied exempt tools are additive to the built-in exempt
|
|
86
|
+
* set so defaults are always preserved.
|
|
35
87
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* ceiling. Everything else — workflow one-shot tools (e.g. gsd_complete_milestone)
|
|
39
|
-
* and any non-allowlisted tool — gets the default cap. The default is generous
|
|
40
|
-
* enough to absorb legitimate retries but catches the reported improvisation
|
|
41
|
-
* loop (~51 calls) well before a cost spike.
|
|
88
|
+
* Applied to both interactive sessions and `/gsd auto` (called from the
|
|
89
|
+
* session_start / session_switch hooks).
|
|
42
90
|
*/
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
91
|
+
export function configureToolCallLoopGuard(prefs) {
|
|
92
|
+
const next = defaultGuardConfig();
|
|
93
|
+
if (prefs) {
|
|
94
|
+
if (typeof prefs.enabled === "boolean")
|
|
95
|
+
next.guardEnabled = prefs.enabled;
|
|
96
|
+
if (prefs.identical_args) {
|
|
97
|
+
if (typeof prefs.identical_args.enabled === "boolean")
|
|
98
|
+
next.identicalEnabled = prefs.identical_args.enabled;
|
|
99
|
+
if (typeof prefs.identical_args.max_consecutive_calls === "number" && prefs.identical_args.max_consecutive_calls >= 1) {
|
|
100
|
+
next.maxConsecutiveIdentical = Math.floor(prefs.identical_args.max_consecutive_calls);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (prefs.repeated_tool) {
|
|
104
|
+
if (typeof prefs.repeated_tool.enabled === "boolean")
|
|
105
|
+
next.repeatedEnabled = prefs.repeated_tool.enabled;
|
|
106
|
+
if (typeof prefs.repeated_tool.default_cap === "number" && prefs.repeated_tool.default_cap >= 1) {
|
|
107
|
+
next.perToolDefaultCap = Math.floor(prefs.repeated_tool.default_cap);
|
|
108
|
+
}
|
|
109
|
+
if (typeof prefs.repeated_tool.repeatable_cap === "number" && prefs.repeated_tool.repeatable_cap >= 1) {
|
|
110
|
+
next.perToolRepeatableCap = Math.floor(prefs.repeated_tool.repeatable_cap);
|
|
111
|
+
}
|
|
112
|
+
for (const tool of prefs.repeated_tool.exempt_tools ?? []) {
|
|
113
|
+
if (typeof tool === "string" && tool.trim())
|
|
114
|
+
next.perToolExempt.add(tool.trim());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Environment overrides (win over preferences).
|
|
119
|
+
const envEnabled = envBool("GSD_TOOL_LOOP_GUARD_ENABLED");
|
|
120
|
+
if (envEnabled !== undefined)
|
|
121
|
+
next.guardEnabled = envEnabled;
|
|
122
|
+
const envIdenticalMax = envPositiveInt("GSD_TOOL_LOOP_IDENTICAL_MAX");
|
|
123
|
+
if (envIdenticalMax !== undefined)
|
|
124
|
+
next.maxConsecutiveIdentical = envIdenticalMax;
|
|
125
|
+
const envDefaultCap = envPositiveInt("GSD_TOOL_LOOP_REPEATED_DEFAULT_CAP");
|
|
126
|
+
if (envDefaultCap !== undefined)
|
|
127
|
+
next.perToolDefaultCap = envDefaultCap;
|
|
128
|
+
const envRepeatableCap = envPositiveInt("GSD_TOOL_LOOP_REPEATED_REPEATABLE_CAP");
|
|
129
|
+
if (envRepeatableCap !== undefined)
|
|
130
|
+
next.perToolRepeatableCap = envRepeatableCap;
|
|
131
|
+
for (const tool of envToolList("GSD_TOOL_LOOP_EXEMPT_TOOLS"))
|
|
132
|
+
next.perToolExempt.add(tool);
|
|
133
|
+
config = next;
|
|
134
|
+
}
|
|
47
135
|
let consecutiveCount = 0;
|
|
48
136
|
let lastSignature = "";
|
|
49
137
|
let lastToolName = "";
|
|
@@ -72,14 +160,17 @@ function hashToolCall(toolName, args) {
|
|
|
72
160
|
* Returns `{ block: true, reason }` when the loop threshold is exceeded.
|
|
73
161
|
*
|
|
74
162
|
* Two independent guards run; whichever trips first blocks:
|
|
75
|
-
* 1. Identical-signature streak (
|
|
163
|
+
* 1. Identical-signature streak (config.maxConsecutiveIdentical, strict for
|
|
76
164
|
* ask_user_questions).
|
|
77
|
-
* 2. Per-tool-name cap (
|
|
165
|
+
* 2. Per-tool-name cap (config.perToolDefaultCap / config.perToolRepeatableCap),
|
|
78
166
|
* independent of args, reset after file-mutation progress — catches
|
|
79
167
|
* improvisation loops (#783).
|
|
168
|
+
*
|
|
169
|
+
* Both guards, their thresholds, and the per-tool exempt set are user-tunable
|
|
170
|
+
* via {@link configureToolCallLoopGuard} (#1198).
|
|
80
171
|
*/
|
|
81
172
|
export function checkToolCallLoop(toolName, args) {
|
|
82
|
-
if (!enabled)
|
|
173
|
+
if (!enabled || !config.guardEnabled)
|
|
83
174
|
return { block: false, count: 0 };
|
|
84
175
|
const sig = hashToolCall(toolName, args);
|
|
85
176
|
if (sig === lastSignature) {
|
|
@@ -93,12 +184,14 @@ export function checkToolCallLoop(toolName, args) {
|
|
|
93
184
|
// ── Guard 1: identical-signature streak ──
|
|
94
185
|
const threshold = STRICT_LOOP_TOOLS.has(toolName)
|
|
95
186
|
? MAX_CONSECUTIVE_STRICT
|
|
96
|
-
:
|
|
97
|
-
if (consecutiveCount > threshold) {
|
|
187
|
+
: config.maxConsecutiveIdentical;
|
|
188
|
+
if (config.identicalEnabled && consecutiveCount > threshold) {
|
|
98
189
|
return {
|
|
99
190
|
block: true,
|
|
100
191
|
reason: `Tool loop detected (identical args): ${toolName} called ${consecutiveCount} times ` +
|
|
101
|
-
`with identical arguments. Blocking to prevent infinite loop. ` +
|
|
192
|
+
`with identical arguments (max ${threshold}). Blocking to prevent infinite loop. ` +
|
|
193
|
+
`Raise tool_call_loop_guard.identical_args.max_consecutive_calls in .gsd/PREFERENCES.md ` +
|
|
194
|
+
`(or GSD_TOOL_LOOP_IDENTICAL_MAX) if this is expected. ` +
|
|
102
195
|
`Do not retry this tool or call other tools this turn — stop and respond to the user in text.`,
|
|
103
196
|
count: consecutiveCount,
|
|
104
197
|
};
|
|
@@ -119,19 +212,23 @@ export function checkToolCallLoop(toolName, args) {
|
|
|
119
212
|
// so the arg-independent per-tool cap misfires on legitimate verification;
|
|
120
213
|
// Guard 1's identical-signature streak still catches a genuinely stuck
|
|
121
214
|
// browser loop.
|
|
122
|
-
if (
|
|
215
|
+
if (!config.repeatedEnabled ||
|
|
216
|
+
config.perToolExempt.has(toolName) ||
|
|
123
217
|
hasBrowserContractPrefix(canonicalToolName(toolName))) {
|
|
124
218
|
return { block: false, count: consecutiveCount };
|
|
125
219
|
}
|
|
126
220
|
const perToolCap = INHERENTLY_REPEATABLE_TOOL_SET.has(toolName)
|
|
127
|
-
?
|
|
128
|
-
:
|
|
221
|
+
? config.perToolRepeatableCap
|
|
222
|
+
: config.perToolDefaultCap;
|
|
129
223
|
if (perToolCount > perToolCap) {
|
|
130
224
|
return {
|
|
131
225
|
block: true,
|
|
132
226
|
reason: `Tool loop detected (repeated tool): ${toolName} called ${perToolCount} times ` +
|
|
133
227
|
`this turn (cap ${perToolCap}). Blocking to prevent infinite loop. ` +
|
|
134
228
|
`The tool may be unavailable or failing repeatedly. ` +
|
|
229
|
+
`Raise tool_call_loop_guard.repeated_tool.default_cap/repeatable_cap or add "${toolName}" ` +
|
|
230
|
+
`to tool_call_loop_guard.repeated_tool.exempt_tools in .gsd/PREFERENCES.md ` +
|
|
231
|
+
`(or GSD_TOOL_LOOP_REPEATED_* / GSD_TOOL_LOOP_EXEMPT_TOOLS) if this is expected. ` +
|
|
135
232
|
`Do not retry this tool or pivot to other tools this turn — stop and respond to the user in text.`,
|
|
136
233
|
count: perToolCount,
|
|
137
234
|
};
|
|
@@ -16,7 +16,7 @@ import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captur
|
|
|
16
16
|
import { appendOverride, appendKnowledge } from "./files.js";
|
|
17
17
|
import { formatDoctorIssuesForPrompt, formatDoctorReport, formatDoctorReportJson, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
|
|
18
18
|
import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
|
|
19
|
-
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
19
|
+
import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
|
|
20
20
|
import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
|
|
21
21
|
import { loadPrompt } from "./prompt-loader.js";
|
|
22
22
|
import { buildClaudeRuntimeFloorAdvisory } from "../../shared/claude-runtime-floor.js";
|
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { resolveByName, autoDetect, listTemplates, getTemplateInfo, loadWorkflowTemplate, loadRegistry, isLegacyWorkflowMode, } from "./workflow-templates.js";
|
|
6
6
|
import { loadPrompt } from "./prompt-loader.js";
|
|
7
7
|
import { gsdRoot } from "./paths.js";
|
|
8
|
-
import { createGitService, runGit } from "./git-service.js";
|
|
8
|
+
import { createGitService, runGit, taskBranchArgs } from "./git-service.js";
|
|
9
9
|
import { isAutoActive, isAutoPaused } from "./auto.js";
|
|
10
10
|
import { getErrorMessage } from "./error-utils.js";
|
|
11
11
|
import { resolvePlugin } from "./workflow-plugins.js";
|
|
@@ -349,7 +349,7 @@ export async function handleStart(args, ctx, pi) {
|
|
|
349
349
|
git.autoCommit("workflow-template", templateId, []);
|
|
350
350
|
}
|
|
351
351
|
catch { /* nothing to commit */ }
|
|
352
|
-
runGit(basePath,
|
|
352
|
+
runGit(basePath, taskBranchArgs(basePath, branchName, current, git.getMainBranch()));
|
|
353
353
|
branchCreated = true;
|
|
354
354
|
}
|
|
355
355
|
}
|
|
@@ -481,7 +481,7 @@ export function dispatchMarkdownPhasePlugin(plugin, description, ctx, pi) {
|
|
|
481
481
|
git.autoCommit("workflow-template", templateId, []);
|
|
482
482
|
}
|
|
483
483
|
catch { /* nothing to commit */ }
|
|
484
|
-
runGit(basePath,
|
|
484
|
+
runGit(basePath, taskBranchArgs(basePath, branchName, current, git.getMainBranch()));
|
|
485
485
|
branchCreated = true;
|
|
486
486
|
}
|
|
487
487
|
}
|
|
@@ -218,7 +218,7 @@ export function getMilestone(id) {
|
|
|
218
218
|
export function getActiveMilestoneFromDb() {
|
|
219
219
|
if (!getDbOrNull())
|
|
220
220
|
return null;
|
|
221
|
-
const row = getDbOrNull().prepare(
|
|
221
|
+
const row = getDbOrNull().prepare(`SELECT * FROM milestones WHERE status NOT IN (${TERMINAL_STATUS_SQL}, 'parked') ORDER BY id LIMIT 1`).get();
|
|
222
222
|
if (!row)
|
|
223
223
|
return null;
|
|
224
224
|
return rowToMilestone(row);
|
|
@@ -230,11 +230,11 @@ export function getActiveSliceFromDb(milestoneId) {
|
|
|
230
230
|
// Uses json_each() to expand the JSON depends array and checks each dep is complete.
|
|
231
231
|
const row = getDbOrNull().prepare(`SELECT s.* FROM slices s
|
|
232
232
|
WHERE s.milestone_id = :mid
|
|
233
|
-
AND s.status NOT IN (
|
|
233
|
+
AND s.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
234
234
|
AND NOT EXISTS (
|
|
235
235
|
SELECT 1 FROM json_each(s.depends) AS dep
|
|
236
236
|
WHERE dep.value NOT IN (
|
|
237
|
-
SELECT id FROM slices WHERE milestone_id = :mid AND status IN (
|
|
237
|
+
SELECT id FROM slices WHERE milestone_id = :mid AND status IN (${TERMINAL_STATUS_SQL})
|
|
238
238
|
)
|
|
239
239
|
)
|
|
240
240
|
ORDER BY s.sequence, s.id
|
|
@@ -246,7 +246,7 @@ export function getActiveSliceFromDb(milestoneId) {
|
|
|
246
246
|
export function getActiveTaskFromDb(milestoneId, sliceId) {
|
|
247
247
|
if (!getDbOrNull())
|
|
248
248
|
return null;
|
|
249
|
-
const row = getDbOrNull().prepare(
|
|
249
|
+
const row = getDbOrNull().prepare(`SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN (${TERMINAL_STATUS_SQL}) ORDER BY sequence, id LIMIT 1`).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
250
250
|
if (!row)
|
|
251
251
|
return null;
|
|
252
252
|
return rowToTask(row);
|
|
@@ -265,7 +265,7 @@ export function getParallelMonitorSliceProgress(milestoneId) {
|
|
|
265
265
|
s.id AS id,
|
|
266
266
|
s.status AS status,
|
|
267
267
|
COUNT(t.id) AS total,
|
|
268
|
-
COALESCE(SUM(CASE WHEN t.status
|
|
268
|
+
COALESCE(SUM(CASE WHEN t.status IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END), 0) AS done
|
|
269
269
|
FROM slices s
|
|
270
270
|
LEFT JOIN tasks t ON s.milestone_id=t.milestone_id AND s.id=t.slice_id
|
|
271
271
|
WHERE s.milestone_id=:mid
|
|
@@ -417,7 +417,7 @@ export function getSliceScopedArtifacts(milestoneId, sliceId) {
|
|
|
417
417
|
export function getActiveMilestoneIdFromDb() {
|
|
418
418
|
if (!getDbOrNull())
|
|
419
419
|
return null;
|
|
420
|
-
const row = getDbOrNull().prepare(
|
|
420
|
+
const row = getDbOrNull().prepare(`SELECT id, status FROM milestones WHERE status NOT IN (${TERMINAL_STATUS_SQL}, 'parked') ORDER BY id LIMIT 1`).get();
|
|
421
421
|
if (!row)
|
|
422
422
|
return null;
|
|
423
423
|
return rowToIdStatusSummary(row);
|
|
@@ -432,7 +432,7 @@ export function getSliceStatusSummary(milestoneId) {
|
|
|
432
432
|
export function getActiveTaskIdFromDb(milestoneId, sliceId) {
|
|
433
433
|
if (!getDbOrNull())
|
|
434
434
|
return null;
|
|
435
|
-
const row = getDbOrNull().prepare(
|
|
435
|
+
const row = getDbOrNull().prepare(`SELECT id, status, title FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN (${TERMINAL_STATUS_SQL}) ORDER BY sequence, id LIMIT 1`).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
436
436
|
if (!row)
|
|
437
437
|
return null;
|
|
438
438
|
return rowToActiveTaskSummary(row);
|
|
@@ -443,8 +443,8 @@ export function getSliceTaskCounts(milestoneId, sliceId) {
|
|
|
443
443
|
return emptyTaskStatusCounts();
|
|
444
444
|
const row = getDbOrNull().prepare(`SELECT
|
|
445
445
|
COUNT(*) as total,
|
|
446
|
-
SUM(CASE WHEN status IN (
|
|
447
|
-
SUM(CASE WHEN status NOT IN (
|
|
446
|
+
SUM(CASE WHEN status IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END) as done,
|
|
447
|
+
SUM(CASE WHEN status NOT IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END) as pending
|
|
448
448
|
FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
449
449
|
return rowToTaskStatusCounts(row);
|
|
450
450
|
}
|
|
@@ -3,7 +3,7 @@ import { isAbsolute, join, relative, sep } from "node:path";
|
|
|
3
3
|
import { deleteArtifactByPath, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, repairTaskCompletionFromSummary, _getAdapter, } from "./gsd-db.js";
|
|
4
4
|
import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
|
|
5
5
|
import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
|
|
6
|
-
import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "./paths.js";
|
|
6
|
+
import { buildTaskFileName, gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, } from "./paths.js";
|
|
7
7
|
import { deriveState } from "./state.js";
|
|
8
8
|
import { isClosedStatus } from "./status-guards.js";
|
|
9
9
|
import { workflowEventLogPath } from "./workflow-event-ledger.js";
|
|
@@ -12,6 +12,7 @@ import { flushWorkflowProjections } from "./projection-flush.js";
|
|
|
12
12
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
13
13
|
import { parsePlan } from "./parsers-legacy.js";
|
|
14
14
|
import { parseSummary } from "./files.js";
|
|
15
|
+
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
15
16
|
const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
|
|
16
17
|
function escapeRegExp(value) {
|
|
17
18
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -113,10 +114,10 @@ function isClearedByMilestoneShellProjectionFlush(basePath, issue, reRenderedMil
|
|
|
113
114
|
return false;
|
|
114
115
|
return issue.file === relativeFile(basePath, roadmapPath);
|
|
115
116
|
}
|
|
116
|
-
function artifactExistsOnDisk(basePath, artifactPath) {
|
|
117
|
-
return resolveArtifactDiskPath(basePath, artifactPath) !== null;
|
|
117
|
+
function artifactExistsOnDisk(basePath, artifactPath, row) {
|
|
118
|
+
return resolveArtifactDiskPath(basePath, artifactPath, row) !== null;
|
|
118
119
|
}
|
|
119
|
-
function
|
|
120
|
+
function resolveLiteralArtifactDiskPath(basePath, artifactPath) {
|
|
120
121
|
const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
|
|
121
122
|
if (isAbsolute(relativeArtifactPath)) {
|
|
122
123
|
return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
|
|
@@ -128,6 +129,48 @@ function resolveArtifactDiskPath(basePath, artifactPath) {
|
|
|
128
129
|
}
|
|
129
130
|
return null;
|
|
130
131
|
}
|
|
132
|
+
function relativeToGsdRoot(basePath, diskPath) {
|
|
133
|
+
for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
|
|
134
|
+
if (!isPathInside(root, diskPath))
|
|
135
|
+
continue;
|
|
136
|
+
return relative(root, diskPath).split("\\").join("/");
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function isFlatPhaseDiskPath(basePath, diskPath) {
|
|
141
|
+
return relativeToGsdRoot(basePath, diskPath)?.startsWith(`${LAYOUT_SEGMENTS.level1}/`) ?? false;
|
|
142
|
+
}
|
|
143
|
+
function hasFlatPhaseLayout(basePath) {
|
|
144
|
+
return existsSync(join(gsdProjectionRoot(basePath), LAYOUT_SEGMENTS.level1));
|
|
145
|
+
}
|
|
146
|
+
function resolveFlatPhaseArtifactDiskPath(basePath, row) {
|
|
147
|
+
if (!hasFlatPhaseLayout(basePath))
|
|
148
|
+
return null;
|
|
149
|
+
if (!row.milestone_id)
|
|
150
|
+
return null;
|
|
151
|
+
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
152
|
+
if (!artifactType)
|
|
153
|
+
return null;
|
|
154
|
+
if (row.slice_id && row.task_id) {
|
|
155
|
+
const phaseDir = resolveMilestonePath(basePath, row.milestone_id);
|
|
156
|
+
if (!phaseDir || !isFlatPhaseDiskPath(basePath, phaseDir))
|
|
157
|
+
return null;
|
|
158
|
+
const candidate = join(phaseDir, buildTaskFileName(row.task_id, artifactType));
|
|
159
|
+
return existsSync(candidate) ? candidate : null;
|
|
160
|
+
}
|
|
161
|
+
const candidate = row.slice_id
|
|
162
|
+
? resolveSliceFile(basePath, row.milestone_id, row.slice_id, artifactType)
|
|
163
|
+
: resolveMilestoneFile(basePath, row.milestone_id, artifactType);
|
|
164
|
+
return candidate && isFlatPhaseDiskPath(basePath, candidate) ? candidate : null;
|
|
165
|
+
}
|
|
166
|
+
function resolveArtifactDiskPath(basePath, artifactPath, row) {
|
|
167
|
+
if (row && isMilestonesArtifactPath(row.path)) {
|
|
168
|
+
const flatPath = resolveFlatPhaseArtifactDiskPath(basePath, row);
|
|
169
|
+
if (flatPath)
|
|
170
|
+
return flatPath;
|
|
171
|
+
}
|
|
172
|
+
return resolveLiteralArtifactDiskPath(basePath, artifactPath);
|
|
173
|
+
}
|
|
131
174
|
function taskExistsInPlan(basePath, milestoneId, sliceId, taskId) {
|
|
132
175
|
const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
|
|
133
176
|
if (!planPath || !existsSync(planPath))
|
|
@@ -156,7 +199,7 @@ function readTaskCompletionEvidenceFromSummary(basePath, row) {
|
|
|
156
199
|
if (!row.task_status && Number(row.task_count) > 0 && !taskExistsInPlan(basePath, row.milestone_id, row.slice_id, row.task_id)) {
|
|
157
200
|
return null;
|
|
158
201
|
}
|
|
159
|
-
const diskPath = resolveArtifactDiskPath(basePath, row.path);
|
|
202
|
+
const diskPath = resolveArtifactDiskPath(basePath, row.path, { ...row, artifact_type: "SUMMARY" });
|
|
160
203
|
if (!diskPath)
|
|
161
204
|
return null;
|
|
162
205
|
try {
|
|
@@ -256,6 +299,43 @@ function hasPresentMilestonesReplacement(basePath, row, artifactRows) {
|
|
|
256
299
|
sameArtifactIdentity(row, other) &&
|
|
257
300
|
artifactExistsOnDisk(basePath, other.path));
|
|
258
301
|
}
|
|
302
|
+
function hasPresentFlatPhaseReplacement(basePath, row, artifactRows) {
|
|
303
|
+
if (resolveFlatPhaseArtifactDiskPath(basePath, row))
|
|
304
|
+
return true;
|
|
305
|
+
return artifactRows.some((other) => other.path !== row.path &&
|
|
306
|
+
!isMilestonesArtifactPath(other.path) &&
|
|
307
|
+
sameArtifactIdentity(row, other) &&
|
|
308
|
+
artifactExistsOnDisk(basePath, other.path, other));
|
|
309
|
+
}
|
|
310
|
+
function hasNoFlatPhaseFileEquivalent(row) {
|
|
311
|
+
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
312
|
+
if (row.slice_id && row.task_id) {
|
|
313
|
+
return artifactType === "PLAN" || artifactType === "SUMMARY";
|
|
314
|
+
}
|
|
315
|
+
return Boolean(row.slice_id && !row.task_id && artifactType === "SUMMARY");
|
|
316
|
+
}
|
|
317
|
+
function staleMilestonesArtifactRowFixable(basePath, row, artifactRows) {
|
|
318
|
+
if (!isMilestonesArtifactPath(row.path))
|
|
319
|
+
return false;
|
|
320
|
+
if (!hasFlatPhaseLayout(basePath))
|
|
321
|
+
return false;
|
|
322
|
+
if (hasPresentFlatPhaseReplacement(basePath, row, artifactRows))
|
|
323
|
+
return true;
|
|
324
|
+
return hasNoFlatPhaseFileEquivalent(row) && !resolveLiteralArtifactDiskPath(basePath, row.path);
|
|
325
|
+
}
|
|
326
|
+
function stalePhasesArtifactRowFixable(basePath, row, artifactRows) {
|
|
327
|
+
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
328
|
+
return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
|
|
329
|
+
}
|
|
330
|
+
function staleArtifactRowFixable(basePath, row, artifactRows) {
|
|
331
|
+
return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
|
|
332
|
+
stalePhasesArtifactRowFixable(basePath, row, artifactRows);
|
|
333
|
+
}
|
|
334
|
+
function staleArtifactPruneMessage(row) {
|
|
335
|
+
return isMilestonesArtifactPath(row.path)
|
|
336
|
+
? `pruned stale legacy artifact row ${row.path}`
|
|
337
|
+
: `pruned stale flat-phase artifact row ${row.path}`;
|
|
338
|
+
}
|
|
259
339
|
export async function checkEngineHealth(basePath, issues, fixesApplied, options) {
|
|
260
340
|
const dbPath = resolveGsdPathContract(basePath).projectDb;
|
|
261
341
|
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
@@ -456,18 +536,20 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
|
|
|
456
536
|
ORDER BY path`)
|
|
457
537
|
.all();
|
|
458
538
|
for (const row of artifactRows) {
|
|
459
|
-
if (artifactExistsOnDisk(basePath, row.path))
|
|
460
|
-
continue;
|
|
461
539
|
const unitId = artifactUnitId(row);
|
|
462
540
|
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
463
|
-
if (
|
|
541
|
+
if (artifactExistsOnDisk(basePath, row.path))
|
|
542
|
+
continue;
|
|
543
|
+
if (options?.repair && staleArtifactRowFixable(basePath, row, artifactRows)) {
|
|
464
544
|
// Route the write through the Single Writer owner (gsd-db.ts) instead
|
|
465
545
|
// of issuing raw DELETE SQL here — doctor is a read-only consumer and
|
|
466
546
|
// the single-writer invariant forbids write SQL outside the allowlist.
|
|
467
547
|
deleteArtifactByPath(row.path);
|
|
468
|
-
fixesApplied.push(
|
|
548
|
+
fixesApplied.push(staleArtifactPruneMessage(row));
|
|
469
549
|
continue;
|
|
470
550
|
}
|
|
551
|
+
if (artifactExistsOnDisk(basePath, row.path, row))
|
|
552
|
+
continue;
|
|
471
553
|
if (isUserAuthoredArtifactType(row.artifact_type)) {
|
|
472
554
|
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
473
555
|
missingUserContentArtifacts.push({ path: issuePath, artifactType });
|
|
@@ -489,7 +571,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
|
|
|
489
571
|
unitId,
|
|
490
572
|
message: `Artifact ${issuePath} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
|
|
491
573
|
file: issuePath,
|
|
492
|
-
fixable:
|
|
574
|
+
fixable: staleArtifactRowFixable(basePath, row, artifactRows),
|
|
493
575
|
});
|
|
494
576
|
}
|
|
495
577
|
}
|
|
@@ -518,7 +600,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
|
|
|
518
600
|
.all();
|
|
519
601
|
const seen = new Set();
|
|
520
602
|
for (const row of rows) {
|
|
521
|
-
if (!artifactExistsOnDisk(basePath, row.path))
|
|
603
|
+
if (!artifactExistsOnDisk(basePath, row.path, row))
|
|
522
604
|
continue;
|
|
523
605
|
const reopenAt = latestExplicitReopenAt(basePath, row.milestone_id);
|
|
524
606
|
if (!isAfter(row.imported_at, reopenAt))
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { renderAllFromDb, renderRoadmapFromDb } from "./markdown-renderer.js";
|
|
7
|
-
import { getAllMilestones, getMilestoneSlices } from "./gsd-db.js";
|
|
7
|
+
import { deleteArtifactsByPathPrefix, getAllMilestones, getMilestoneSlices } from "./gsd-db.js";
|
|
8
8
|
import { migrateFromMarkdown } from "./md-importer.js";
|
|
9
9
|
import { countDbHierarchy } from "./migration-auto-check.js";
|
|
10
10
|
import { logWarning } from "./workflow-logger.js";
|
|
@@ -163,7 +163,8 @@ export function pruneStaleFlatPhaseBackups(basePath) {
|
|
|
163
163
|
* 2. Rename .gsd/milestones/ aside so path resolvers target phases/
|
|
164
164
|
* 3. Render flat-phase from the DB (which already has the data)
|
|
165
165
|
* 4. Verify counts match
|
|
166
|
-
* 5.
|
|
166
|
+
* 5. Prune legacy milestones/ artifact rows
|
|
167
|
+
* 6. Remove the renamed legacy tree
|
|
167
168
|
*
|
|
168
169
|
* Idempotent: if .gsd/milestones/ doesn't exist, returns immediately.
|
|
169
170
|
*/
|
|
@@ -262,7 +263,17 @@ export async function migrateToFlatPhase(basePath) {
|
|
|
262
263
|
rollbackPartialMigration(basePath, backupDir, migratingPath);
|
|
263
264
|
throw new Error("flat-phase migration verification failed: no artifacts rendered");
|
|
264
265
|
}
|
|
265
|
-
// 6. Verified —
|
|
266
|
+
// 6. Verified — prune legacy artifact rows now that renderAllFromDb has
|
|
267
|
+
// re-inserted flat-phase rows for artifacts that still have files.
|
|
268
|
+
try {
|
|
269
|
+
deleteArtifactsByPathPrefix("milestones/");
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
logWarning("migration", `flat-phase migration could not prune legacy artifact rows: ${err.message}`);
|
|
273
|
+
rollbackPartialMigration(basePath, backupDir, migratingPath);
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
276
|
+
// 7. Remove the renamed legacy tree (backup already on disk).
|
|
266
277
|
try {
|
|
267
278
|
removePathWithRetries(migratingPath);
|
|
268
279
|
}
|
|
@@ -24,7 +24,7 @@ import { loadPrompt } from "./prompt-loader.js";
|
|
|
24
24
|
import { gsdRoot } from "./paths.js";
|
|
25
25
|
import { isDbAvailable, getHierarchyCompletionCounts } from "./gsd-db.js";
|
|
26
26
|
import { formatDuration } from "../shared/format-utils.js";
|
|
27
|
-
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
27
|
+
import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
|
|
28
28
|
import { clearGSDPreferencesCache, loadEffectiveGSDPreferences, loadGlobalGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
|
|
29
29
|
import { showNextAction } from "../shared/tui.js";
|
|
30
30
|
import { isInteractiveCommandContext, notifyForensicsNeedsInteractiveMenu, } from "./command-feedback.js";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// File Purpose: Shared auto-resolution of safe merge conflict paths.
|
|
3
3
|
import { logWarning } from "./workflow-logger.js";
|
|
4
4
|
import { nativeAddPaths, nativeCheckoutTheirs, nativeRmForce, } from "./native-git-bridge.js";
|
|
5
|
-
import { isSafeToAutoResolve } from "./auto-worktree.js";
|
|
6
|
-
export { isSafeToAutoResolve } from "./auto-worktree.js";
|
|
5
|
+
import { isSafeToAutoResolve } from "./auto-worktree-conflict-auto-resolve.js";
|
|
6
|
+
export { isSafeToAutoResolve } from "./auto-worktree-conflict-auto-resolve.js";
|
|
7
7
|
/**
|
|
8
8
|
* Auto-resolve paths that are safe to accept from the milestone side
|
|
9
9
|
* (.gsd/ state and build artifacts).
|