@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
|
@@ -177,24 +177,18 @@ import {
|
|
|
177
177
|
import { GitServiceImpl } from "./git-service.js";
|
|
178
178
|
import { nativeCheckoutBranch } from "./native-git-bridge.js";
|
|
179
179
|
import { getPriorSliceCompletionBlocker } from "./dispatch-guard.js";
|
|
180
|
-
import {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
autoWorktreeBranch,
|
|
190
|
-
syncWorktreeStateBack,
|
|
191
|
-
readResourceVersion,
|
|
192
|
-
checkResourcesStale,
|
|
193
|
-
escapeStaleWorktree,
|
|
194
|
-
} from "./auto-worktree.js";
|
|
180
|
+
import { autoWorktreeBranch, enterBranchModeForMilestone } from "./auto-worktree-branch-lifecycle.js";
|
|
181
|
+
import { createAutoWorktree } from "./auto-worktree-creation.js";
|
|
182
|
+
import { enterAutoWorktree, isInAutoWorktree } from "./auto-worktree-entry.js";
|
|
183
|
+
import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
|
|
184
|
+
import { checkResourcesStale, readResourceVersion } from "./auto-worktree-resource-version.js";
|
|
185
|
+
import { escapeStaleWorktree } from "./auto-worktree-runtime-cleanup.js";
|
|
186
|
+
import { getAutoWorktreeOriginalBase } from "./auto-worktree-session-registry.js";
|
|
187
|
+
import { syncWorktreeStateBack } from "./auto-worktree-sync.js";
|
|
188
|
+
import { teardownAutoWorktree } from "./auto-worktree-teardown.js";
|
|
195
189
|
import { pruneQueueOrder } from "./queue-order.js";
|
|
196
190
|
import { startCommandPolling as _startCommandPolling, isRemoteConfigured } from "../remote-questions/manager.js";
|
|
197
|
-
import {
|
|
191
|
+
import { createDefaultMilestoneMergeTransaction } from "./milestone-merge-transaction.js";
|
|
198
192
|
|
|
199
193
|
import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
|
|
200
194
|
import {
|
|
@@ -2326,7 +2320,7 @@ export function buildWorktreeLifecycleDeps(): WorktreeLifecycleDeps {
|
|
|
2326
2320
|
return new GitServiceImpl(basePath, gitConfig);
|
|
2327
2321
|
},
|
|
2328
2322
|
worktreeProjection: new WorktreeStateProjection(),
|
|
2329
|
-
mergeMilestone:
|
|
2323
|
+
mergeMilestone: createDefaultMilestoneMergeTransaction(),
|
|
2330
2324
|
};
|
|
2331
2325
|
}
|
|
2332
2326
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type WorkflowDatabaseOpenResult,
|
|
15
15
|
type WorkflowDatabaseStatus,
|
|
16
16
|
} from "../db-workspace.js";
|
|
17
|
-
import { getAutoWorktreePath } from "../auto-worktree.js";
|
|
17
|
+
import { getAutoWorktreePath } from "../auto-worktree-path-resolution.js";
|
|
18
18
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
19
19
|
import { worktreesDirs } from "../worktree-placement.js";
|
|
20
20
|
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
} from "../auto-tool-tracking.js";
|
|
41
41
|
import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
|
|
42
42
|
|
|
43
|
-
import { checkToolCallLoop, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
43
|
+
import { checkToolCallLoop, configureToolCallLoopGuard, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
44
44
|
import { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
|
|
45
45
|
import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
|
|
46
46
|
import { saveActivityLog } from "../activity-log.js";
|
|
@@ -594,6 +594,22 @@ async function applyCompactionThresholdOverride(ctx: ExtensionContext): Promise<
|
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
+
/**
|
|
598
|
+
* Apply user-tunable tool-call loop guard thresholds (#1198) from GSD
|
|
599
|
+
* preferences plus GSD_TOOL_LOOP_* env overrides. Runs at session boundaries
|
|
600
|
+
* so both interactive sessions and `/gsd auto` pick up the configuration.
|
|
601
|
+
* Non-fatal: falls back to built-in defaults when preferences cannot be loaded.
|
|
602
|
+
*/
|
|
603
|
+
async function applyToolCallLoopGuardConfig(basePath: string): Promise<void> {
|
|
604
|
+
try {
|
|
605
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
606
|
+
const prefs = loadEffectiveGSDPreferences(basePath);
|
|
607
|
+
configureToolCallLoopGuard(prefs?.preferences.tool_call_loop_guard);
|
|
608
|
+
} catch {
|
|
609
|
+
configureToolCallLoopGuard(null);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
597
613
|
function clearDeferredApprovalGate(basePath?: string): void {
|
|
598
614
|
if (!basePath) {
|
|
599
615
|
deferredApprovalGates.clear();
|
|
@@ -996,6 +1012,7 @@ export function registerHooks(
|
|
|
996
1012
|
}
|
|
997
1013
|
resetWriteGateState(basePath);
|
|
998
1014
|
resetToolCallLoopGuard();
|
|
1015
|
+
await applyToolCallLoopGuardConfig(basePath);
|
|
999
1016
|
approvalQuestionAbortInFlight = false;
|
|
1000
1017
|
clearDeferredApprovalGate();
|
|
1001
1018
|
await resetAskUserQuestionsTurnCache();
|
|
@@ -1087,6 +1104,7 @@ export function registerHooks(
|
|
|
1087
1104
|
initSessionNotifications(ctx);
|
|
1088
1105
|
resetWriteGateState(basePath);
|
|
1089
1106
|
resetToolCallLoopGuard();
|
|
1107
|
+
await applyToolCallLoopGuardConfig(basePath);
|
|
1090
1108
|
clearDeferredApprovalGate();
|
|
1091
1109
|
await resetAskUserQuestionsTurnCache();
|
|
1092
1110
|
clearDiscussionFlowState(basePath);
|
|
@@ -14,7 +14,7 @@ import { resolveModelWithFallbacksForUnit } from "../preferences-models.js";
|
|
|
14
14
|
import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
|
|
15
15
|
import { extractIntroAndRules } from "../knowledge-parser.js";
|
|
16
16
|
import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
|
|
17
|
-
import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
|
|
17
|
+
import { getActiveAutoWorktreeContext } from "../auto-worktree-session-registry.js";
|
|
18
18
|
import { getActiveWorktreeName, getWorktreeOriginalCwd } from "../worktree-session-state.js";
|
|
19
19
|
import { deriveState } from "../state.js";
|
|
20
20
|
import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
|
|
@@ -21,6 +21,11 @@
|
|
|
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
|
|
|
26
31
|
import { createHash } from "node:crypto";
|
|
@@ -28,26 +33,143 @@ import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
|
|
|
28
33
|
import { hasBrowserContractPrefix } from "../../shared/browser-contract.js";
|
|
29
34
|
import { canonicalToolName } from "../engine-hook-contract.js";
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
/** Built-in defaults. Preserved when preferences/env do not override them. */
|
|
37
|
+
const DEFAULT_MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
|
|
38
|
+
const DEFAULT_PER_TOOL_DEFAULT_CAP = 6;
|
|
39
|
+
const DEFAULT_PER_TOOL_REPEATABLE_CAP = 15;
|
|
40
|
+
const DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS = ["find", "glob", "grep", "ls", "read", "search_and_read"] as const;
|
|
32
41
|
|
|
33
42
|
/** Interactive/user-facing tools where even 1 duplicate is confusing. */
|
|
34
43
|
const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]);
|
|
35
44
|
const MAX_CONSECUTIVE_STRICT = 1;
|
|
36
45
|
|
|
46
|
+
const STATE_MUTATING_TOOL_SET = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
|
|
47
|
+
|
|
37
48
|
/**
|
|
38
|
-
*
|
|
49
|
+
* User-tunable configuration shape for the loop guard (#1198).
|
|
39
50
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* and any non-allowlisted tool — gets the default cap. The default is generous
|
|
44
|
-
* enough to absorb legitimate retries but catches the reported improvisation
|
|
45
|
-
* loop (~51 calls) well before a cost spike.
|
|
51
|
+
* Mirrors the `tool_call_loop_guard` key in `.gsd/PREFERENCES.md`. All fields
|
|
52
|
+
* are optional; anything omitted falls back to the built-in defaults so that
|
|
53
|
+
* existing installs keep their current behavior.
|
|
46
54
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
export interface ToolCallLoopGuardConfig {
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
identical_args?: {
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
max_consecutive_calls?: number;
|
|
60
|
+
};
|
|
61
|
+
repeated_tool?: {
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
default_cap?: number;
|
|
64
|
+
repeatable_cap?: number;
|
|
65
|
+
exempt_tools?: string[];
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Active, resolved configuration. Set by {@link configureToolCallLoopGuard}
|
|
71
|
+
* from preferences + environment overrides, and deliberately NOT touched by
|
|
72
|
+
* {@link resetToolCallLoopGuard} so a session's tuning survives turn
|
|
73
|
+
* boundaries.
|
|
74
|
+
*/
|
|
75
|
+
interface ResolvedGuardConfig {
|
|
76
|
+
/** Master switch for both guards. */
|
|
77
|
+
guardEnabled: boolean;
|
|
78
|
+
identicalEnabled: boolean;
|
|
79
|
+
maxConsecutiveIdentical: number;
|
|
80
|
+
repeatedEnabled: boolean;
|
|
81
|
+
perToolDefaultCap: number;
|
|
82
|
+
perToolRepeatableCap: number;
|
|
83
|
+
/** Tool names exempt from Guard 2 (per-tool-name cap). */
|
|
84
|
+
perToolExempt: Set<string>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function defaultGuardConfig(): ResolvedGuardConfig {
|
|
88
|
+
return {
|
|
89
|
+
guardEnabled: true,
|
|
90
|
+
identicalEnabled: true,
|
|
91
|
+
maxConsecutiveIdentical: DEFAULT_MAX_CONSECUTIVE_IDENTICAL_CALLS,
|
|
92
|
+
repeatedEnabled: true,
|
|
93
|
+
perToolDefaultCap: DEFAULT_PER_TOOL_DEFAULT_CAP,
|
|
94
|
+
perToolRepeatableCap: DEFAULT_PER_TOOL_REPEATABLE_CAP,
|
|
95
|
+
perToolExempt: new Set<string>(DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let config: ResolvedGuardConfig = defaultGuardConfig();
|
|
100
|
+
|
|
101
|
+
/** Parse a positive-integer env var, returning undefined when unset/invalid. */
|
|
102
|
+
function envPositiveInt(name: string): number | undefined {
|
|
103
|
+
const raw = process.env[name];
|
|
104
|
+
if (raw === undefined || raw.trim() === "") return undefined;
|
|
105
|
+
const n = Number(raw);
|
|
106
|
+
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Parse a boolean env var (`true`/`1` vs `false`/`0`), undefined when unset. */
|
|
110
|
+
function envBool(name: string): boolean | undefined {
|
|
111
|
+
const raw = process.env[name];
|
|
112
|
+
if (raw === undefined || raw.trim() === "") return undefined;
|
|
113
|
+
const v = raw.trim().toLowerCase();
|
|
114
|
+
if (v === "true" || v === "1" || v === "yes" || v === "on") return true;
|
|
115
|
+
if (v === "false" || v === "0" || v === "no" || v === "off") return false;
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Parse a comma-separated tool-name list env var. */
|
|
120
|
+
function envToolList(name: string): string[] {
|
|
121
|
+
const raw = process.env[name];
|
|
122
|
+
if (!raw) return [];
|
|
123
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Resolve preferences + environment overrides into the active configuration.
|
|
128
|
+
* Environment variables win over preferences; preferences win over built-in
|
|
129
|
+
* defaults. User-supplied exempt tools are additive to the built-in exempt
|
|
130
|
+
* set so defaults are always preserved.
|
|
131
|
+
*
|
|
132
|
+
* Applied to both interactive sessions and `/gsd auto` (called from the
|
|
133
|
+
* session_start / session_switch hooks).
|
|
134
|
+
*/
|
|
135
|
+
export function configureToolCallLoopGuard(prefs?: ToolCallLoopGuardConfig | null): void {
|
|
136
|
+
const next = defaultGuardConfig();
|
|
137
|
+
|
|
138
|
+
if (prefs) {
|
|
139
|
+
if (typeof prefs.enabled === "boolean") next.guardEnabled = prefs.enabled;
|
|
140
|
+
if (prefs.identical_args) {
|
|
141
|
+
if (typeof prefs.identical_args.enabled === "boolean") next.identicalEnabled = prefs.identical_args.enabled;
|
|
142
|
+
if (typeof prefs.identical_args.max_consecutive_calls === "number" && prefs.identical_args.max_consecutive_calls >= 1) {
|
|
143
|
+
next.maxConsecutiveIdentical = Math.floor(prefs.identical_args.max_consecutive_calls);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (prefs.repeated_tool) {
|
|
147
|
+
if (typeof prefs.repeated_tool.enabled === "boolean") next.repeatedEnabled = prefs.repeated_tool.enabled;
|
|
148
|
+
if (typeof prefs.repeated_tool.default_cap === "number" && prefs.repeated_tool.default_cap >= 1) {
|
|
149
|
+
next.perToolDefaultCap = Math.floor(prefs.repeated_tool.default_cap);
|
|
150
|
+
}
|
|
151
|
+
if (typeof prefs.repeated_tool.repeatable_cap === "number" && prefs.repeated_tool.repeatable_cap >= 1) {
|
|
152
|
+
next.perToolRepeatableCap = Math.floor(prefs.repeated_tool.repeatable_cap);
|
|
153
|
+
}
|
|
154
|
+
for (const tool of prefs.repeated_tool.exempt_tools ?? []) {
|
|
155
|
+
if (typeof tool === "string" && tool.trim()) next.perToolExempt.add(tool.trim());
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Environment overrides (win over preferences).
|
|
161
|
+
const envEnabled = envBool("GSD_TOOL_LOOP_GUARD_ENABLED");
|
|
162
|
+
if (envEnabled !== undefined) next.guardEnabled = envEnabled;
|
|
163
|
+
const envIdenticalMax = envPositiveInt("GSD_TOOL_LOOP_IDENTICAL_MAX");
|
|
164
|
+
if (envIdenticalMax !== undefined) next.maxConsecutiveIdentical = envIdenticalMax;
|
|
165
|
+
const envDefaultCap = envPositiveInt("GSD_TOOL_LOOP_REPEATED_DEFAULT_CAP");
|
|
166
|
+
if (envDefaultCap !== undefined) next.perToolDefaultCap = envDefaultCap;
|
|
167
|
+
const envRepeatableCap = envPositiveInt("GSD_TOOL_LOOP_REPEATED_REPEATABLE_CAP");
|
|
168
|
+
if (envRepeatableCap !== undefined) next.perToolRepeatableCap = envRepeatableCap;
|
|
169
|
+
for (const tool of envToolList("GSD_TOOL_LOOP_EXEMPT_TOOLS")) next.perToolExempt.add(tool);
|
|
170
|
+
|
|
171
|
+
config = next;
|
|
172
|
+
}
|
|
51
173
|
|
|
52
174
|
let consecutiveCount = 0;
|
|
53
175
|
let lastSignature = "";
|
|
@@ -82,17 +204,20 @@ function hashToolCall(toolName: string, args: Record<string, unknown>): string {
|
|
|
82
204
|
* Returns `{ block: true, reason }` when the loop threshold is exceeded.
|
|
83
205
|
*
|
|
84
206
|
* Two independent guards run; whichever trips first blocks:
|
|
85
|
-
* 1. Identical-signature streak (
|
|
207
|
+
* 1. Identical-signature streak (config.maxConsecutiveIdentical, strict for
|
|
86
208
|
* ask_user_questions).
|
|
87
|
-
* 2. Per-tool-name cap (
|
|
209
|
+
* 2. Per-tool-name cap (config.perToolDefaultCap / config.perToolRepeatableCap),
|
|
88
210
|
* independent of args, reset after file-mutation progress — catches
|
|
89
211
|
* improvisation loops (#783).
|
|
212
|
+
*
|
|
213
|
+
* Both guards, their thresholds, and the per-tool exempt set are user-tunable
|
|
214
|
+
* via {@link configureToolCallLoopGuard} (#1198).
|
|
90
215
|
*/
|
|
91
216
|
export function checkToolCallLoop(
|
|
92
217
|
toolName: string,
|
|
93
218
|
args: Record<string, unknown>,
|
|
94
219
|
): { block: boolean; reason?: string; count?: number } {
|
|
95
|
-
if (!enabled) return { block: false, count: 0 };
|
|
220
|
+
if (!enabled || !config.guardEnabled) return { block: false, count: 0 };
|
|
96
221
|
|
|
97
222
|
const sig = hashToolCall(toolName, args);
|
|
98
223
|
|
|
@@ -107,14 +232,16 @@ export function checkToolCallLoop(
|
|
|
107
232
|
// ── Guard 1: identical-signature streak ──
|
|
108
233
|
const threshold = STRICT_LOOP_TOOLS.has(toolName)
|
|
109
234
|
? MAX_CONSECUTIVE_STRICT
|
|
110
|
-
:
|
|
235
|
+
: config.maxConsecutiveIdentical;
|
|
111
236
|
|
|
112
|
-
if (consecutiveCount > threshold) {
|
|
237
|
+
if (config.identicalEnabled && consecutiveCount > threshold) {
|
|
113
238
|
return {
|
|
114
239
|
block: true,
|
|
115
240
|
reason:
|
|
116
241
|
`Tool loop detected (identical args): ${toolName} called ${consecutiveCount} times ` +
|
|
117
|
-
`with identical arguments. Blocking to prevent infinite loop. ` +
|
|
242
|
+
`with identical arguments (max ${threshold}). Blocking to prevent infinite loop. ` +
|
|
243
|
+
`Raise tool_call_loop_guard.identical_args.max_consecutive_calls in .gsd/PREFERENCES.md ` +
|
|
244
|
+
`(or GSD_TOOL_LOOP_IDENTICAL_MAX) if this is expected. ` +
|
|
118
245
|
`Do not retry this tool or call other tools this turn — stop and respond to the user in text.`,
|
|
119
246
|
count: consecutiveCount,
|
|
120
247
|
};
|
|
@@ -139,15 +266,16 @@ export function checkToolCallLoop(
|
|
|
139
266
|
// Guard 1's identical-signature streak still catches a genuinely stuck
|
|
140
267
|
// browser loop.
|
|
141
268
|
if (
|
|
142
|
-
|
|
269
|
+
!config.repeatedEnabled ||
|
|
270
|
+
config.perToolExempt.has(toolName) ||
|
|
143
271
|
hasBrowserContractPrefix(canonicalToolName(toolName))
|
|
144
272
|
) {
|
|
145
273
|
return { block: false, count: consecutiveCount };
|
|
146
274
|
}
|
|
147
275
|
|
|
148
276
|
const perToolCap = INHERENTLY_REPEATABLE_TOOL_SET.has(toolName)
|
|
149
|
-
?
|
|
150
|
-
:
|
|
277
|
+
? config.perToolRepeatableCap
|
|
278
|
+
: config.perToolDefaultCap;
|
|
151
279
|
|
|
152
280
|
if (perToolCount > perToolCap) {
|
|
153
281
|
return {
|
|
@@ -156,6 +284,9 @@ export function checkToolCallLoop(
|
|
|
156
284
|
`Tool loop detected (repeated tool): ${toolName} called ${perToolCount} times ` +
|
|
157
285
|
`this turn (cap ${perToolCap}). Blocking to prevent infinite loop. ` +
|
|
158
286
|
`The tool may be unavailable or failing repeatedly. ` +
|
|
287
|
+
`Raise tool_call_loop_guard.repeated_tool.default_cap/repeatable_cap or add "${toolName}" ` +
|
|
288
|
+
`to tool_call_loop_guard.repeated_tool.exempt_tools in .gsd/PREFERENCES.md ` +
|
|
289
|
+
`(or GSD_TOOL_LOOP_REPEATED_* / GSD_TOOL_LOOP_EXEMPT_TOOLS) if this is expected. ` +
|
|
159
290
|
`Do not retry this tool or pivot to other tools this turn — stop and respond to the user in text.`,
|
|
160
291
|
count: perToolCount,
|
|
161
292
|
};
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
filterDoctorIssues,
|
|
26
26
|
} from "./doctor.js";
|
|
27
27
|
import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
|
|
28
|
-
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
28
|
+
import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
|
|
29
29
|
import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
|
|
30
30
|
import { loadPrompt } from "./prompt-loader.js";
|
|
31
31
|
import { buildClaudeRuntimeFloorAdvisory } from "../../shared/claude-runtime-floor.js";
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
} from "./workflow-templates.js";
|
|
24
24
|
import { loadPrompt } from "./prompt-loader.js";
|
|
25
25
|
import { gsdRoot } from "./paths.js";
|
|
26
|
-
import { createGitService, runGit } from "./git-service.js";
|
|
26
|
+
import { createGitService, runGit, taskBranchArgs } from "./git-service.js";
|
|
27
27
|
import { isAutoActive, isAutoPaused } from "./auto.js";
|
|
28
28
|
import { getErrorMessage } from "./error-utils.js";
|
|
29
29
|
import { resolvePlugin, type WorkflowPlugin } from "./workflow-plugins.js";
|
|
@@ -464,7 +464,7 @@ export async function handleStart(
|
|
|
464
464
|
try {
|
|
465
465
|
git.autoCommit("workflow-template", templateId, []);
|
|
466
466
|
} catch { /* nothing to commit */ }
|
|
467
|
-
runGit(basePath,
|
|
467
|
+
runGit(basePath, taskBranchArgs(basePath, branchName, current, git.getMainBranch()));
|
|
468
468
|
branchCreated = true;
|
|
469
469
|
}
|
|
470
470
|
} catch (err) {
|
|
@@ -636,7 +636,7 @@ export function dispatchMarkdownPhasePlugin(
|
|
|
636
636
|
const current = git.getCurrentBranch();
|
|
637
637
|
if (current !== branchName) {
|
|
638
638
|
try { git.autoCommit("workflow-template", templateId, []); } catch { /* nothing to commit */ }
|
|
639
|
-
runGit(basePath,
|
|
639
|
+
runGit(basePath, taskBranchArgs(basePath, branchName, current, git.getMainBranch()));
|
|
640
640
|
branchCreated = true;
|
|
641
641
|
}
|
|
642
642
|
} catch (err) {
|
|
@@ -281,7 +281,7 @@ export function getMilestone(id: string): MilestoneRow | null {
|
|
|
281
281
|
export function getActiveMilestoneFromDb(): MilestoneRow | null {
|
|
282
282
|
if (!getDbOrNull()!) return null;
|
|
283
283
|
const row = getDbOrNull()!.prepare(
|
|
284
|
-
|
|
284
|
+
`SELECT * FROM milestones WHERE status NOT IN (${TERMINAL_STATUS_SQL}, 'parked') ORDER BY id LIMIT 1`,
|
|
285
285
|
).get();
|
|
286
286
|
if (!row) return null;
|
|
287
287
|
return rowToMilestone(row);
|
|
@@ -295,11 +295,11 @@ export function getActiveSliceFromDb(milestoneId: string): SliceRow | null {
|
|
|
295
295
|
const row = getDbOrNull()!.prepare(
|
|
296
296
|
`SELECT s.* FROM slices s
|
|
297
297
|
WHERE s.milestone_id = :mid
|
|
298
|
-
AND s.status NOT IN (
|
|
298
|
+
AND s.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
299
299
|
AND NOT EXISTS (
|
|
300
300
|
SELECT 1 FROM json_each(s.depends) AS dep
|
|
301
301
|
WHERE dep.value NOT IN (
|
|
302
|
-
SELECT id FROM slices WHERE milestone_id = :mid AND status IN (
|
|
302
|
+
SELECT id FROM slices WHERE milestone_id = :mid AND status IN (${TERMINAL_STATUS_SQL})
|
|
303
303
|
)
|
|
304
304
|
)
|
|
305
305
|
ORDER BY s.sequence, s.id
|
|
@@ -312,7 +312,7 @@ export function getActiveSliceFromDb(milestoneId: string): SliceRow | null {
|
|
|
312
312
|
export function getActiveTaskFromDb(milestoneId: string, sliceId: string): TaskRow | null {
|
|
313
313
|
if (!getDbOrNull()!) return null;
|
|
314
314
|
const row = getDbOrNull()!.prepare(
|
|
315
|
-
|
|
315
|
+
`SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN (${TERMINAL_STATUS_SQL}) ORDER BY sequence, id LIMIT 1`,
|
|
316
316
|
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
317
317
|
if (!row) return null;
|
|
318
318
|
return rowToTask(row);
|
|
@@ -339,7 +339,7 @@ export function getParallelMonitorSliceProgress(milestoneId: string): ParallelMo
|
|
|
339
339
|
s.id AS id,
|
|
340
340
|
s.status AS status,
|
|
341
341
|
COUNT(t.id) AS total,
|
|
342
|
-
COALESCE(SUM(CASE WHEN t.status
|
|
342
|
+
COALESCE(SUM(CASE WHEN t.status IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END), 0) AS done
|
|
343
343
|
FROM slices s
|
|
344
344
|
LEFT JOIN tasks t ON s.milestone_id=t.milestone_id AND s.id=t.slice_id
|
|
345
345
|
WHERE s.milestone_id=:mid
|
|
@@ -518,7 +518,7 @@ export function getSliceScopedArtifacts(milestoneId: string, sliceId: string): A
|
|
|
518
518
|
export function getActiveMilestoneIdFromDb(): IdStatusSummary | null {
|
|
519
519
|
if (!getDbOrNull()!) return null;
|
|
520
520
|
const row = getDbOrNull()!.prepare(
|
|
521
|
-
|
|
521
|
+
`SELECT id, status FROM milestones WHERE status NOT IN (${TERMINAL_STATUS_SQL}, 'parked') ORDER BY id LIMIT 1`,
|
|
522
522
|
).get();
|
|
523
523
|
if (!row) return null;
|
|
524
524
|
return rowToIdStatusSummary(row);
|
|
@@ -536,7 +536,7 @@ export function getSliceStatusSummary(milestoneId: string): IdStatusSummary[] {
|
|
|
536
536
|
export function getActiveTaskIdFromDb(milestoneId: string, sliceId: string): ActiveTaskSummary | null {
|
|
537
537
|
if (!getDbOrNull()!) return null;
|
|
538
538
|
const row = getDbOrNull()!.prepare(
|
|
539
|
-
|
|
539
|
+
`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`,
|
|
540
540
|
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
541
541
|
if (!row) return null;
|
|
542
542
|
return rowToActiveTaskSummary(row);
|
|
@@ -548,8 +548,8 @@ export function getSliceTaskCounts(milestoneId: string, sliceId: string): TaskSt
|
|
|
548
548
|
const row = getDbOrNull()!.prepare(
|
|
549
549
|
`SELECT
|
|
550
550
|
COUNT(*) as total,
|
|
551
|
-
SUM(CASE WHEN status IN (
|
|
552
|
-
SUM(CASE WHEN status NOT IN (
|
|
551
|
+
SUM(CASE WHEN status IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END) as done,
|
|
552
|
+
SUM(CASE WHEN status NOT IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END) as pending
|
|
553
553
|
FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`,
|
|
554
554
|
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
555
555
|
return rowToTaskStatusCounts(row);
|
|
@@ -14,7 +14,15 @@ import {
|
|
|
14
14
|
} from "./gsd-db.js";
|
|
15
15
|
import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
|
|
16
16
|
import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
buildTaskFileName,
|
|
19
|
+
gsdProjectionRoot,
|
|
20
|
+
gsdRoot,
|
|
21
|
+
resolveGsdPathContract,
|
|
22
|
+
resolveMilestoneFile,
|
|
23
|
+
resolveMilestonePath,
|
|
24
|
+
resolveSliceFile,
|
|
25
|
+
} from "./paths.js";
|
|
18
26
|
import { deriveState } from "./state.js";
|
|
19
27
|
import { isClosedStatus } from "./status-guards.js";
|
|
20
28
|
import { workflowEventLogPath } from "./workflow-event-ledger.js";
|
|
@@ -23,6 +31,7 @@ import { flushWorkflowProjections } from "./projection-flush.js";
|
|
|
23
31
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
24
32
|
import { parsePlan } from "./parsers-legacy.js";
|
|
25
33
|
import { parseSummary } from "./files.js";
|
|
34
|
+
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
26
35
|
|
|
27
36
|
const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
|
|
28
37
|
|
|
@@ -160,11 +169,11 @@ function isClearedByMilestoneShellProjectionFlush(
|
|
|
160
169
|
return issue.file === relativeFile(basePath, roadmapPath);
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
function artifactExistsOnDisk(basePath: string, artifactPath: string): boolean {
|
|
164
|
-
return resolveArtifactDiskPath(basePath, artifactPath) !== null;
|
|
172
|
+
function artifactExistsOnDisk(basePath: string, artifactPath: string, row?: ArtifactRow): boolean {
|
|
173
|
+
return resolveArtifactDiskPath(basePath, artifactPath, row) !== null;
|
|
165
174
|
}
|
|
166
175
|
|
|
167
|
-
function
|
|
176
|
+
function resolveLiteralArtifactDiskPath(basePath: string, artifactPath: string): string | null {
|
|
168
177
|
const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
|
|
169
178
|
if (isAbsolute(relativeArtifactPath)) {
|
|
170
179
|
return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
|
|
@@ -176,6 +185,50 @@ function resolveArtifactDiskPath(basePath: string, artifactPath: string): string
|
|
|
176
185
|
return null;
|
|
177
186
|
}
|
|
178
187
|
|
|
188
|
+
function relativeToGsdRoot(basePath: string, diskPath: string): string | null {
|
|
189
|
+
for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
|
|
190
|
+
if (!isPathInside(root, diskPath)) continue;
|
|
191
|
+
return relative(root, diskPath).split("\\").join("/");
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isFlatPhaseDiskPath(basePath: string, diskPath: string): boolean {
|
|
197
|
+
return relativeToGsdRoot(basePath, diskPath)?.startsWith(`${LAYOUT_SEGMENTS.level1}/`) ?? false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function hasFlatPhaseLayout(basePath: string): boolean {
|
|
201
|
+
return existsSync(join(gsdProjectionRoot(basePath), LAYOUT_SEGMENTS.level1));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolveFlatPhaseArtifactDiskPath(basePath: string, row: ArtifactRow): string | null {
|
|
205
|
+
if (!hasFlatPhaseLayout(basePath)) return null;
|
|
206
|
+
if (!row.milestone_id) return null;
|
|
207
|
+
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
208
|
+
if (!artifactType) return null;
|
|
209
|
+
|
|
210
|
+
if (row.slice_id && row.task_id) {
|
|
211
|
+
const phaseDir = resolveMilestonePath(basePath, row.milestone_id);
|
|
212
|
+
if (!phaseDir || !isFlatPhaseDiskPath(basePath, phaseDir)) return null;
|
|
213
|
+
const candidate = join(phaseDir, buildTaskFileName(row.task_id, artifactType));
|
|
214
|
+
return existsSync(candidate) ? candidate : null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const candidate = row.slice_id
|
|
218
|
+
? resolveSliceFile(basePath, row.milestone_id, row.slice_id, artifactType)
|
|
219
|
+
: resolveMilestoneFile(basePath, row.milestone_id, artifactType);
|
|
220
|
+
|
|
221
|
+
return candidate && isFlatPhaseDiskPath(basePath, candidate) ? candidate : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function resolveArtifactDiskPath(basePath: string, artifactPath: string, row?: ArtifactRow): string | null {
|
|
225
|
+
if (row && isMilestonesArtifactPath(row.path)) {
|
|
226
|
+
const flatPath = resolveFlatPhaseArtifactDiskPath(basePath, row);
|
|
227
|
+
if (flatPath) return flatPath;
|
|
228
|
+
}
|
|
229
|
+
return resolveLiteralArtifactDiskPath(basePath, artifactPath);
|
|
230
|
+
}
|
|
231
|
+
|
|
179
232
|
function taskExistsInPlan(basePath: string, milestoneId: string, sliceId: string, taskId: string): boolean {
|
|
180
233
|
const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
|
|
181
234
|
if (!planPath || !existsSync(planPath)) return false;
|
|
@@ -228,7 +281,7 @@ function readTaskCompletionEvidenceFromSummary(
|
|
|
228
281
|
if (!row.task_status && Number(row.task_count) > 0 && !taskExistsInPlan(basePath, row.milestone_id, row.slice_id, row.task_id)) {
|
|
229
282
|
return null;
|
|
230
283
|
}
|
|
231
|
-
const diskPath = resolveArtifactDiskPath(basePath, row.path);
|
|
284
|
+
const diskPath = resolveArtifactDiskPath(basePath, row.path, { ...row, artifact_type: "SUMMARY" });
|
|
232
285
|
if (!diskPath) return null;
|
|
233
286
|
try {
|
|
234
287
|
const fullSummaryMd = readFileSync(diskPath, "utf-8");
|
|
@@ -262,6 +315,7 @@ function repairTaskArtifactDbStatusDivergence(
|
|
|
262
315
|
basePath: string,
|
|
263
316
|
row: {
|
|
264
317
|
path: string;
|
|
318
|
+
artifact_type?: string;
|
|
265
319
|
milestone_id: string;
|
|
266
320
|
slice_id: string | null;
|
|
267
321
|
task_id: string | null;
|
|
@@ -353,6 +407,49 @@ function hasPresentMilestonesReplacement(basePath: string, row: ArtifactRow, art
|
|
|
353
407
|
);
|
|
354
408
|
}
|
|
355
409
|
|
|
410
|
+
function hasPresentFlatPhaseReplacement(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
|
|
411
|
+
if (resolveFlatPhaseArtifactDiskPath(basePath, row)) return true;
|
|
412
|
+
|
|
413
|
+
return artifactRows.some(
|
|
414
|
+
(other) =>
|
|
415
|
+
other.path !== row.path &&
|
|
416
|
+
!isMilestonesArtifactPath(other.path) &&
|
|
417
|
+
sameArtifactIdentity(row, other) &&
|
|
418
|
+
artifactExistsOnDisk(basePath, other.path, other),
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function hasNoFlatPhaseFileEquivalent(row: ArtifactRow): boolean {
|
|
423
|
+
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
424
|
+
if (row.slice_id && row.task_id) {
|
|
425
|
+
return artifactType === "PLAN" || artifactType === "SUMMARY";
|
|
426
|
+
}
|
|
427
|
+
return Boolean(row.slice_id && !row.task_id && artifactType === "SUMMARY");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function staleMilestonesArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
|
|
431
|
+
if (!isMilestonesArtifactPath(row.path)) return false;
|
|
432
|
+
if (!hasFlatPhaseLayout(basePath)) return false;
|
|
433
|
+
if (hasPresentFlatPhaseReplacement(basePath, row, artifactRows)) return true;
|
|
434
|
+
return hasNoFlatPhaseFileEquivalent(row) && !resolveLiteralArtifactDiskPath(basePath, row.path);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function stalePhasesArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
|
|
438
|
+
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
439
|
+
return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function staleArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
|
|
443
|
+
return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
|
|
444
|
+
stalePhasesArtifactRowFixable(basePath, row, artifactRows);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function staleArtifactPruneMessage(row: ArtifactRow): string {
|
|
448
|
+
return isMilestonesArtifactPath(row.path)
|
|
449
|
+
? `pruned stale legacy artifact row ${row.path}`
|
|
450
|
+
: `pruned stale flat-phase artifact row ${row.path}`;
|
|
451
|
+
}
|
|
452
|
+
|
|
356
453
|
export async function checkEngineHealth(
|
|
357
454
|
basePath: string,
|
|
358
455
|
issues: DoctorIssue[],
|
|
@@ -579,17 +676,18 @@ export async function checkEngineHealth(
|
|
|
579
676
|
.all() as ArtifactRow[];
|
|
580
677
|
|
|
581
678
|
for (const row of artifactRows) {
|
|
582
|
-
if (artifactExistsOnDisk(basePath, row.path)) continue;
|
|
583
679
|
const unitId = artifactUnitId(row);
|
|
584
680
|
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
585
|
-
if (
|
|
681
|
+
if (artifactExistsOnDisk(basePath, row.path)) continue;
|
|
682
|
+
if (options?.repair && staleArtifactRowFixable(basePath, row, artifactRows)) {
|
|
586
683
|
// Route the write through the Single Writer owner (gsd-db.ts) instead
|
|
587
684
|
// of issuing raw DELETE SQL here — doctor is a read-only consumer and
|
|
588
685
|
// the single-writer invariant forbids write SQL outside the allowlist.
|
|
589
686
|
deleteArtifactByPath(row.path);
|
|
590
|
-
fixesApplied.push(
|
|
687
|
+
fixesApplied.push(staleArtifactPruneMessage(row));
|
|
591
688
|
continue;
|
|
592
689
|
}
|
|
690
|
+
if (artifactExistsOnDisk(basePath, row.path, row)) continue;
|
|
593
691
|
if (isUserAuthoredArtifactType(row.artifact_type)) {
|
|
594
692
|
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
595
693
|
missingUserContentArtifacts.push({ path: issuePath, artifactType });
|
|
@@ -611,7 +709,7 @@ export async function checkEngineHealth(
|
|
|
611
709
|
unitId,
|
|
612
710
|
message: `Artifact ${issuePath} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
|
|
613
711
|
file: issuePath,
|
|
614
|
-
fixable:
|
|
712
|
+
fixable: staleArtifactRowFixable(basePath, row, artifactRows),
|
|
615
713
|
});
|
|
616
714
|
}
|
|
617
715
|
} catch {
|
|
@@ -643,6 +741,7 @@ export async function checkEngineHealth(
|
|
|
643
741
|
)
|
|
644
742
|
.all() as Array<{
|
|
645
743
|
path: string;
|
|
744
|
+
artifact_type: string;
|
|
646
745
|
milestone_id: string;
|
|
647
746
|
slice_id: string | null;
|
|
648
747
|
task_id: string | null;
|
|
@@ -654,7 +753,7 @@ export async function checkEngineHealth(
|
|
|
654
753
|
|
|
655
754
|
const seen = new Set<string>();
|
|
656
755
|
for (const row of rows) {
|
|
657
|
-
if (!artifactExistsOnDisk(basePath, row.path)) continue;
|
|
756
|
+
if (!artifactExistsOnDisk(basePath, row.path, row)) continue;
|
|
658
757
|
const reopenAt = latestExplicitReopenAt(basePath, row.milestone_id);
|
|
659
758
|
if (!isAfter(row.imported_at, reopenAt)) continue;
|
|
660
759
|
const isSliceSummary = row.slice_id && !row.task_id && row.slice_status && !["complete", "done", "skipped", "closed"].includes(row.slice_status);
|