@opengsd/gsd-pi 1.1.1-dev.9f86580 → 1.1.1-dev.b2556262
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless-recover.js +56 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/index.js +39 -22
- package/dist/resources/extensions/browser-tools/state.js +12 -0
- package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
- package/dist/resources/extensions/browser-tools/utils.js +3 -3
- package/dist/resources/extensions/gsd/auto/loop.js +4 -2
- package/dist/resources/extensions/gsd/auto/phases.js +43 -10
- package/dist/resources/extensions/gsd/auto/session.js +20 -1
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +72 -12
- package/dist/resources/extensions/gsd/auto-model-selection.js +128 -9
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -19
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +14 -11
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +172 -65
- package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
- package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
- package/dist/resources/extensions/gsd/db-writer.js +35 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/dist/resources/extensions/gsd/gsd-db.js +480 -172
- package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
- package/dist/resources/extensions/gsd/md-importer.js +38 -3
- package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
- package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
- package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
- package/dist/resources/extensions/gsd/preferences-models.js +110 -43
- package/dist/resources/extensions/gsd/preferences-types.js +13 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
- package/dist/resources/extensions/gsd/preferences.js +4 -1
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
- package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
- package/dist/resources/extensions/gsd/source-observations.js +306 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
- package/dist/resources/extensions/gsd/state.js +7 -3
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +1 -9
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -6
- package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
- package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +57 -429
- package/dist/resources/extensions/gsd/uat-policy.js +130 -0
- package/dist/resources/extensions/gsd/uat-run.js +414 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
- package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
- package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
- package/dist/resources/extensions/subagent/agents.js +1 -0
- package/dist/resources/extensions/subagent/index.js +27 -12
- package/dist/resources/extensions/subagent/launch.js +7 -2
- 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/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +4 -4
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- 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 +18 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/dist/native.js +22 -0
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +30 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +23 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +25 -24
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/utils.d.ts +11 -0
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +119 -6
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/themes.js +1 -1
- package/pkg/dist/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +39 -22
- package/src/resources/extensions/browser-tools/state.ts +13 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
- package/src/resources/extensions/browser-tools/utils.ts +3 -3
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -2
- package/src/resources/extensions/gsd/auto/phases.ts +42 -10
- package/src/resources/extensions/gsd/auto/session.ts +22 -1
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +85 -12
- package/src/resources/extensions/gsd/auto-model-selection.ts +164 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +23 -20
- package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +13 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +225 -72
- package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
- package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
- package/src/resources/extensions/gsd/db-writer.ts +38 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/src/resources/extensions/gsd/gsd-db.ts +564 -186
- package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
- package/src/resources/extensions/gsd/md-importer.ts +49 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
- package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
- package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
- package/src/resources/extensions/gsd/preferences-models.ts +112 -43
- package/src/resources/extensions/gsd/preferences-types.ts +39 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
- package/src/resources/extensions/gsd/preferences.ts +5 -0
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
- package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
- package/src/resources/extensions/gsd/source-observations.ts +402 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
- package/src/resources/extensions/gsd/state.ts +7 -4
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +16 -2
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +73 -6
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
- package/src/resources/extensions/gsd/tool-contract.ts +28 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +1 -11
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -6
- package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
- package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +66 -526
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/uat-policy.ts +191 -0
- package/src/resources/extensions/gsd/uat-run.ts +550 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
- package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
- package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
- package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
- package/src/resources/extensions/subagent/agents.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +28 -3
- package/src/resources/extensions/subagent/launch.ts +8 -0
- package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
- /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Registers GSD extension runtime hooks and token-saving tool policies.
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { isToolCallEventType } from "@gsd/pi-coding-agent";
|
|
7
7
|
import { ALWAYS_PRESERVED_SHIM_TOOL_NAMES } from "@gsd/pi-ai";
|
|
8
8
|
import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
|
|
9
|
-
import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
|
|
9
|
+
import { buildMilestoneFileName, clearPathCache, milestonesDir, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
|
|
10
10
|
import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isMilestoneDepthVerified, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeWrite, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
|
|
11
11
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
12
12
|
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
13
13
|
import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
14
|
-
import { clearToolInvocationError, getAutoRuntimeSnapshot, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError } from "../auto-runtime-state.js";
|
|
14
|
+
import { clearToolInvocationError, getAutoRuntimeSnapshot, getSourceObservationStore, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError, } from "../auto-runtime-state.js";
|
|
15
15
|
import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
16
16
|
import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
|
|
17
17
|
import { saveActivityLog } from "../activity-log.js";
|
|
@@ -31,6 +31,7 @@ import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-rec
|
|
|
31
31
|
import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
|
|
32
32
|
import { filterToolsForProvider } from "../model-router.js";
|
|
33
33
|
import { RUN_UAT_READ_ONLY_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
|
|
34
|
+
import { injectSourceContextBlockIntoPayload, supportsSourceObservationsForUnit } from "../source-observations.js";
|
|
34
35
|
let approvalQuestionAbortInFlight = false;
|
|
35
36
|
async function loadWelcomeScreenModule() {
|
|
36
37
|
const candidates = [];
|
|
@@ -370,6 +371,50 @@ function deferApprovalGate(gateId, basePath) {
|
|
|
370
371
|
function contextBasePath(ctx) {
|
|
371
372
|
return typeof ctx?.cwd === "string" ? ctx.cwd : process.cwd();
|
|
372
373
|
}
|
|
374
|
+
function beginSourceObservationStoreForCurrentUnit(ctx) {
|
|
375
|
+
if (!isAutoActive())
|
|
376
|
+
return null;
|
|
377
|
+
const dash = getAutoRuntimeSnapshot();
|
|
378
|
+
if (!dash.currentUnit)
|
|
379
|
+
return null;
|
|
380
|
+
if (!supportsSourceObservationsForUnit(dash.currentUnit.type))
|
|
381
|
+
return null;
|
|
382
|
+
const store = getSourceObservationStore();
|
|
383
|
+
store.beginUnit({
|
|
384
|
+
unitType: dash.currentUnit.type,
|
|
385
|
+
unitId: dash.currentUnit.id,
|
|
386
|
+
startedAt: dash.currentUnit.startedAt,
|
|
387
|
+
basePath: dash.currentUnit.workspaceRoot ?? (dash.basePath || contextBasePath(ctx)),
|
|
388
|
+
});
|
|
389
|
+
return store;
|
|
390
|
+
}
|
|
391
|
+
function refreshSourceObservationAfterMutation(canonicalName, input, ctx) {
|
|
392
|
+
if (canonicalName !== "edit" && canonicalName !== "write")
|
|
393
|
+
return;
|
|
394
|
+
if (!input || typeof input !== "object")
|
|
395
|
+
return;
|
|
396
|
+
const store = beginSourceObservationStoreForCurrentUnit(ctx);
|
|
397
|
+
if (!store)
|
|
398
|
+
return;
|
|
399
|
+
store.observeMutation(input);
|
|
400
|
+
}
|
|
401
|
+
function clearSourceObservationsAfterShell(canonicalName) {
|
|
402
|
+
if (!isAutoActive())
|
|
403
|
+
return;
|
|
404
|
+
if (!isShellExecutionTool(canonicalName))
|
|
405
|
+
return;
|
|
406
|
+
const dash = getAutoRuntimeSnapshot();
|
|
407
|
+
if (!dash.currentUnit || !supportsSourceObservationsForUnit(dash.currentUnit.type))
|
|
408
|
+
return;
|
|
409
|
+
getSourceObservationStore().clear();
|
|
410
|
+
}
|
|
411
|
+
function isShellExecutionTool(canonicalName) {
|
|
412
|
+
return canonicalName === "bash" ||
|
|
413
|
+
canonicalName === "bg_shell" ||
|
|
414
|
+
canonicalName === "async_bash" ||
|
|
415
|
+
canonicalName === "shell" ||
|
|
416
|
+
canonicalName === "powershell";
|
|
417
|
+
}
|
|
373
418
|
function activateDeferredApprovalGate(basePath) {
|
|
374
419
|
if (deferredApprovalGate?.basePath !== basePath)
|
|
375
420
|
return;
|
|
@@ -392,6 +437,79 @@ function isContextDraftSummarySave(toolName, input) {
|
|
|
392
437
|
return false;
|
|
393
438
|
return input.artifact_type === "CONTEXT-DRAFT";
|
|
394
439
|
}
|
|
440
|
+
function selectedAnswerLabel(selected) {
|
|
441
|
+
if (Array.isArray(selected))
|
|
442
|
+
return selected.map(String).join(", ");
|
|
443
|
+
if (selected == null)
|
|
444
|
+
return "";
|
|
445
|
+
return String(selected);
|
|
446
|
+
}
|
|
447
|
+
function formatQuestionExchange(questions, answers) {
|
|
448
|
+
const lines = [];
|
|
449
|
+
for (const question of questions) {
|
|
450
|
+
lines.push(`### ${question.header ?? "Question"}`, "", question.question ?? "");
|
|
451
|
+
if (Array.isArray(question.options)) {
|
|
452
|
+
lines.push("");
|
|
453
|
+
for (const opt of question.options) {
|
|
454
|
+
lines.push(`- **${opt.label ?? ""}** — ${opt.description ?? ""}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const answer = question.id ? answers?.[question.id] : undefined;
|
|
458
|
+
if (answer) {
|
|
459
|
+
lines.push("");
|
|
460
|
+
const selected = selectedAnswerLabel(answer.selected);
|
|
461
|
+
if (selected)
|
|
462
|
+
lines.push(`**Selected:** ${selected}`);
|
|
463
|
+
if (answer.notes)
|
|
464
|
+
lines.push(`**Notes:** ${String(answer.notes)}`);
|
|
465
|
+
}
|
|
466
|
+
lines.push("");
|
|
467
|
+
}
|
|
468
|
+
return lines.join("\n");
|
|
469
|
+
}
|
|
470
|
+
async function ensureMilestoneShell(basePath, milestoneId) {
|
|
471
|
+
const milestoneDir = resolveMilestonePath(basePath, milestoneId)
|
|
472
|
+
?? join(milestonesDir(basePath), milestoneId);
|
|
473
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
474
|
+
clearPathCache();
|
|
475
|
+
try {
|
|
476
|
+
const { ensureDbOpen } = await import("./dynamic-tools.js");
|
|
477
|
+
if (await ensureDbOpen(basePath)) {
|
|
478
|
+
const { getMilestone, insertMilestone } = await import("../gsd-db.js");
|
|
479
|
+
if (!getMilestone(milestoneId)) {
|
|
480
|
+
insertMilestone({
|
|
481
|
+
id: milestoneId,
|
|
482
|
+
title: `New milestone ${milestoneId}`,
|
|
483
|
+
status: "queued",
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
safetyLogWarning("guided", `failed to persist milestone shell for ${milestoneId}: ${err.message}`);
|
|
490
|
+
}
|
|
491
|
+
return milestoneDir;
|
|
492
|
+
}
|
|
493
|
+
async function saveDiscussionQuestionRound(basePath, milestoneId, questions, details) {
|
|
494
|
+
const milestoneDir = await ensureMilestoneShell(basePath, milestoneId);
|
|
495
|
+
const answers = details?.response?.answers;
|
|
496
|
+
const timestamp = new Date().toISOString();
|
|
497
|
+
const exchange = formatQuestionExchange(questions, answers);
|
|
498
|
+
const discussionPath = join(milestoneDir, buildMilestoneFileName(milestoneId, "DISCUSSION"));
|
|
499
|
+
const existingDiscussion = await loadFile(discussionPath) ?? `# ${milestoneId} Discussion Log\n\n`;
|
|
500
|
+
await saveFile(discussionPath, `${existingDiscussion}## Exchange — ${timestamp}\n\n${exchange}---\n\n`);
|
|
501
|
+
const draftPath = join(milestoneDir, buildMilestoneFileName(milestoneId, "CONTEXT-DRAFT"));
|
|
502
|
+
const existingDraft = await loadFile(draftPath);
|
|
503
|
+
const draftHeader = existingDraft
|
|
504
|
+
?? [
|
|
505
|
+
`# ${milestoneId}: New milestone ${milestoneId}`,
|
|
506
|
+
"",
|
|
507
|
+
"This draft was captured automatically from structured question responses.",
|
|
508
|
+
"Use it so `/gsd` can resume the in-flight milestone discussion.",
|
|
509
|
+
"",
|
|
510
|
+
].join("\n");
|
|
511
|
+
await saveFile(draftPath, `${draftHeader.trimEnd()}\n\n## Captured Question Round — ${timestamp}\n\n${exchange}`);
|
|
512
|
+
}
|
|
395
513
|
function withDepthGateDisplayReason(result, displayReason = "Depth confirmation is waiting for your answer.") {
|
|
396
514
|
if (!result.block)
|
|
397
515
|
return result;
|
|
@@ -918,6 +1036,17 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
918
1036
|
if (isAutoActive() && typeof event.toolCallId === "string") {
|
|
919
1037
|
markToolEnd(event.toolCallId);
|
|
920
1038
|
}
|
|
1039
|
+
const toolName = canonicalToolName(event.toolName);
|
|
1040
|
+
if (isAutoActive() && toolName === "read" && !event.isError) {
|
|
1041
|
+
const store = beginSourceObservationStoreForCurrentUnit(ctx);
|
|
1042
|
+
if (store) {
|
|
1043
|
+
store.observeRead(event.input);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (!event.isError) {
|
|
1047
|
+
refreshSourceObservationAfterMutation(toolName, event.input, ctx);
|
|
1048
|
+
clearSourceObservationsAfterShell(toolName);
|
|
1049
|
+
}
|
|
921
1050
|
if (isAutoActive() && event.isError) {
|
|
922
1051
|
const resultPayload = ("result" in event ? event.result : undefined);
|
|
923
1052
|
const errorText = typeof resultPayload === "string"
|
|
@@ -934,12 +1063,10 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
934
1063
|
else if (isAutoActive()) {
|
|
935
1064
|
clearToolInvocationError();
|
|
936
1065
|
}
|
|
937
|
-
const toolName = canonicalToolName(event.toolName);
|
|
938
1066
|
if (toolName !== "ask_user_questions")
|
|
939
1067
|
return;
|
|
940
1068
|
const basePath = contextBasePath(ctx);
|
|
941
1069
|
const milestoneId = await getDiscussionMilestoneIdFor(basePath);
|
|
942
|
-
const queueActive = isQueuePhaseActive(basePath);
|
|
943
1070
|
const details = event.details;
|
|
944
1071
|
// ── Discussion gate enforcement: handle gate question responses ──
|
|
945
1072
|
// If the result is cancelled or has no response, the pending gate stays active
|
|
@@ -1012,38 +1139,9 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1012
1139
|
break;
|
|
1013
1140
|
}
|
|
1014
1141
|
}
|
|
1015
|
-
if (!milestoneId && !queueActive)
|
|
1016
|
-
return;
|
|
1017
1142
|
if (!milestoneId)
|
|
1018
1143
|
return;
|
|
1019
|
-
|
|
1020
|
-
if (!milestoneDir)
|
|
1021
|
-
return;
|
|
1022
|
-
const discussionPath = join(milestoneDir, buildMilestoneFileName(milestoneId, "DISCUSSION"));
|
|
1023
|
-
const timestamp = new Date().toISOString();
|
|
1024
|
-
const lines = [`## Exchange — ${timestamp}`, ""];
|
|
1025
|
-
for (const question of questions) {
|
|
1026
|
-
lines.push(`### ${question.header ?? "Question"}`, "", question.question ?? "");
|
|
1027
|
-
if (Array.isArray(question.options)) {
|
|
1028
|
-
lines.push("");
|
|
1029
|
-
for (const opt of question.options) {
|
|
1030
|
-
lines.push(`- **${opt.label}** — ${opt.description ?? ""}`);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
const answer = details.response?.answers?.[question.id];
|
|
1034
|
-
if (answer) {
|
|
1035
|
-
lines.push("");
|
|
1036
|
-
const selected = Array.isArray(answer.selected) ? answer.selected.join(", ") : answer.selected;
|
|
1037
|
-
lines.push(`**Selected:** ${selected}`);
|
|
1038
|
-
if (answer.notes) {
|
|
1039
|
-
lines.push(`**Notes:** ${answer.notes}`);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
lines.push("");
|
|
1043
|
-
}
|
|
1044
|
-
lines.push("---", "");
|
|
1045
|
-
const existing = await loadFile(discussionPath) ?? `# ${milestoneId} Discussion Log\n\n`;
|
|
1046
|
-
await saveFile(discussionPath, existing + lines.join("\n"));
|
|
1144
|
+
await saveDiscussionQuestionRound(basePath, milestoneId, questions, details);
|
|
1047
1145
|
});
|
|
1048
1146
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
1049
1147
|
const basePath = contextBasePath(ctx);
|
|
@@ -1095,42 +1193,51 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1095
1193
|
const payload = event.payload;
|
|
1096
1194
|
if (!payload || typeof payload !== "object")
|
|
1097
1195
|
return;
|
|
1098
|
-
// ──
|
|
1099
|
-
//
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
payload.messages = createObservationMask(keepTurns)(messages);
|
|
1113
|
-
}
|
|
1114
|
-
const input = payload.input;
|
|
1115
|
-
if (Array.isArray(input)) {
|
|
1116
|
-
payload.input = createResponsesInputObservationMask(keepTurns)(input);
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
// Tool result truncation: cap individual tool result content length.
|
|
1120
|
-
// In pi-ai format, toolResult messages have role: "toolResult" and content: TextContent[].
|
|
1121
|
-
// Creates new objects to avoid mutating shared conversation state.
|
|
1122
|
-
const maxChars = cmConfig?.tool_result_max_chars ?? 800;
|
|
1123
|
-
const msgs = payload.messages;
|
|
1124
|
-
if (Array.isArray(msgs)) {
|
|
1125
|
-
payload.messages = truncateContextResultMessages(msgs, maxChars);
|
|
1196
|
+
// ── Context Management ──────────────────────────────────────────────
|
|
1197
|
+
// Load preferences once for both masking and truncation.
|
|
1198
|
+
try {
|
|
1199
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
1200
|
+
const { createObservationMask, createResponsesInputObservationMask, truncateContextResultMessages, truncateResponsesInputResultItems, } = await import("../context-masker.js");
|
|
1201
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
1202
|
+
const cmConfig = prefs?.preferences.context_management;
|
|
1203
|
+
// Observation masking: replace old tool results with placeholders.
|
|
1204
|
+
// Only active during auto-mode when context_management.observation_masking is enabled.
|
|
1205
|
+
if (isAutoActive() && cmConfig?.observation_masking !== false) {
|
|
1206
|
+
const keepTurns = cmConfig?.observation_mask_turns ?? 8;
|
|
1207
|
+
const messages = payload.messages;
|
|
1208
|
+
if (Array.isArray(messages)) {
|
|
1209
|
+
payload.messages = createObservationMask(keepTurns)(messages);
|
|
1126
1210
|
}
|
|
1127
1211
|
const input = payload.input;
|
|
1128
1212
|
if (Array.isArray(input)) {
|
|
1129
|
-
payload.input =
|
|
1213
|
+
payload.input = createResponsesInputObservationMask(keepTurns)(input);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
// Tool result truncation: cap individual tool result content length.
|
|
1217
|
+
// Applies in ALL modes (auto + interactive) to prevent context bloat.
|
|
1218
|
+
// In pi-ai format, toolResult messages have role: "toolResult" and content: TextContent[].
|
|
1219
|
+
// Creates new objects to avoid mutating shared conversation state.
|
|
1220
|
+
const maxChars = cmConfig?.tool_result_max_chars ?? 800;
|
|
1221
|
+
const msgs = payload.messages;
|
|
1222
|
+
if (Array.isArray(msgs)) {
|
|
1223
|
+
payload.messages = truncateContextResultMessages(msgs, maxChars);
|
|
1224
|
+
}
|
|
1225
|
+
const input = payload.input;
|
|
1226
|
+
if (Array.isArray(input)) {
|
|
1227
|
+
payload.input = truncateResponsesInputResultItems(input, maxChars);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
catch { /* non-fatal */ }
|
|
1231
|
+
try {
|
|
1232
|
+
if (isAutoActive()) {
|
|
1233
|
+
const sourceContextBlock = getSourceObservationStore().renderActiveBlock();
|
|
1234
|
+
if (sourceContextBlock) {
|
|
1235
|
+
const nextPayload = injectSourceContextBlockIntoPayload(payload, sourceContextBlock);
|
|
1236
|
+
Object.assign(payload, nextPayload);
|
|
1130
1237
|
}
|
|
1131
1238
|
}
|
|
1132
|
-
catch { /* non-fatal */ }
|
|
1133
1239
|
}
|
|
1240
|
+
catch { /* non-fatal */ }
|
|
1134
1241
|
// ── Service Tier ────────────────────────────────────────────────────
|
|
1135
1242
|
const modelId = event.model?.id;
|
|
1136
1243
|
if (!modelId)
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Shared closeout detection and merge actions for /gsd home and smart entry.
|
|
3
|
+
import { setAutoOutcomeWidget } from "./auto-dashboard.js";
|
|
3
4
|
import { invalidateAllCaches } from "./cache.js";
|
|
4
5
|
import { mergeCompletedMilestone } from "./parallel-merge.js";
|
|
5
6
|
import { cleanupQuickBranch, detectStrandedQuickBranch } from "./quick.js";
|
|
6
7
|
import { findUnmergedCompletedMilestones, } from "./unmerged-milestone-guard.js";
|
|
7
8
|
import { appendRequirementsBacklogToSummary } from "./requirements-backlog.js";
|
|
9
|
+
const MILESTONE_MERGE_CLOSEOUT_COMMANDS = [
|
|
10
|
+
"/gsd status for overview",
|
|
11
|
+
"/gsd visualize to inspect",
|
|
12
|
+
"/gsd notifications for history",
|
|
13
|
+
"/gsd start for new work",
|
|
14
|
+
];
|
|
8
15
|
export async function loadCloseoutContext(basePath) {
|
|
9
16
|
const unmergedMilestones = await findUnmergedCompletedMilestones(basePath);
|
|
10
17
|
return {
|
|
@@ -65,6 +72,18 @@ export function buildIdleMenuSummary(state, closeout) {
|
|
|
65
72
|
}
|
|
66
73
|
return [state.nextAction || "No active milestone."];
|
|
67
74
|
}
|
|
75
|
+
export function showMilestoneMergeCloseout(ctx, blocker) {
|
|
76
|
+
ctx.ui.setStatus?.("gsd-auto", undefined);
|
|
77
|
+
ctx.ui.setStatus?.("gsd-step", undefined);
|
|
78
|
+
ctx.ui.setWidget?.("gsd-progress", undefined);
|
|
79
|
+
setAutoOutcomeWidget(ctx, {
|
|
80
|
+
status: "complete",
|
|
81
|
+
title: `Milestone ${blocker.milestoneId} merged`,
|
|
82
|
+
detail: `Merged ${blocker.branch} into ${blocker.integrationBranch}. Product changes are now on ${blocker.integrationBranch}.`,
|
|
83
|
+
nextAction: "Review the closeout, then start the next milestone when ready.",
|
|
84
|
+
commands: MILESTONE_MERGE_CLOSEOUT_COMMANDS,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
68
87
|
export async function runMergeQuickTask(ctx, basePath, strandedQuick) {
|
|
69
88
|
const merged = cleanupQuickBranch(basePath);
|
|
70
89
|
if (merged) {
|
|
@@ -75,6 +94,18 @@ export async function runMergeQuickTask(ctx, basePath, strandedQuick) {
|
|
|
75
94
|
ctx.ui.notify("Could not merge the quick-task branch automatically. Run `git status`, resolve any conflicts, then retry /gsd.", "error");
|
|
76
95
|
return false;
|
|
77
96
|
}
|
|
97
|
+
export async function runMergeMilestoneBlocker(ctx, basePath, blocker) {
|
|
98
|
+
ctx.ui.notify(`Completing preserved milestone merge for ${blocker.milestoneId} from ${blocker.branch} into ${blocker.integrationBranch}.`, "info");
|
|
99
|
+
const result = await mergeCompletedMilestone(basePath, blocker.milestoneId);
|
|
100
|
+
if (result.success) {
|
|
101
|
+
invalidateAllCaches();
|
|
102
|
+
showMilestoneMergeCloseout(ctx, blocker);
|
|
103
|
+
ctx.ui.notify(`Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Closeout is complete.`, "info");
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
ctx.ui.notify(`Milestone ${blocker.milestoneId} merge failed: ${result.error ?? "unknown error"}`, "error");
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
78
109
|
export async function runMergeMilestone(ctx, basePath, milestoneId) {
|
|
79
110
|
const blockers = await findUnmergedCompletedMilestones(basePath);
|
|
80
111
|
const blocker = milestoneId
|
|
@@ -84,15 +115,7 @@ export async function runMergeMilestone(ctx, basePath, milestoneId) {
|
|
|
84
115
|
ctx.ui.notify("No unmerged completed milestone found.", "warning");
|
|
85
116
|
return false;
|
|
86
117
|
}
|
|
87
|
-
ctx
|
|
88
|
-
const result = await mergeCompletedMilestone(basePath, blocker.milestoneId);
|
|
89
|
-
if (result.success) {
|
|
90
|
-
ctx.ui.notify(`Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Run /gsd again when ready.`, "info");
|
|
91
|
-
invalidateAllCaches();
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
ctx.ui.notify(`Milestone ${blocker.milestoneId} merge failed: ${result.error ?? "unknown error"}`, "error");
|
|
95
|
-
return false;
|
|
118
|
+
return runMergeMilestoneBlocker(ctx, basePath, blocker);
|
|
96
119
|
}
|
|
97
120
|
export async function handleCloseoutChoice(ctx, basePath, choice, closeout) {
|
|
98
121
|
if (choice === "finish_quick") {
|
|
@@ -14,7 +14,7 @@ import { handleSessionReport } from "../../commands-session-report.js";
|
|
|
14
14
|
import { handlePrBranch } from "../../commands-pr-branch.js";
|
|
15
15
|
import { currentDirectoryRoot, projectRoot } from "../context.js";
|
|
16
16
|
import { findUnmergedCompletedMilestones } from "../../unmerged-milestone-guard.js";
|
|
17
|
-
import {
|
|
17
|
+
import { runMergeMilestoneBlocker } from "../../closeout-wizard.js";
|
|
18
18
|
async function handleCompletedMilestoneRecovery(phase, ctx, basePath) {
|
|
19
19
|
const tokens = phase.split(/\s+/).filter(Boolean);
|
|
20
20
|
const dispatchPhase = tokens[0] ?? "";
|
|
@@ -27,14 +27,7 @@ async function handleCompletedMilestoneRecovery(phase, ctx, basePath) {
|
|
|
27
27
|
: blockers[0];
|
|
28
28
|
if (!blocker)
|
|
29
29
|
return false;
|
|
30
|
-
ctx
|
|
31
|
-
const result = await mergeCompletedMilestone(basePath, blocker.milestoneId);
|
|
32
|
-
if (result.success) {
|
|
33
|
-
ctx.ui.notify(`Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Run /gsd again when ready.`, "info");
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
ctx.ui.notify(`Milestone ${blocker.milestoneId} merge recovery failed: ${result.error}`, "error");
|
|
37
|
-
}
|
|
30
|
+
await runMergeMilestoneBlocker(ctx, basePath, blocker);
|
|
38
31
|
return true;
|
|
39
32
|
}
|
|
40
33
|
export function normalizeReportExportArgs(trimmed) {
|
|
@@ -448,22 +448,57 @@ function recoverConfirmed(args) {
|
|
|
448
448
|
.map((part) => part.trim().toLowerCase())
|
|
449
449
|
.some((part) => part === "--confirm" || part === "--yes" || part === "confirm");
|
|
450
450
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
451
|
+
function recoverAllowsDataLoss(args) {
|
|
452
|
+
return args
|
|
453
|
+
.split(/\s+/)
|
|
454
|
+
.map((part) => part.trim().toLowerCase())
|
|
455
|
+
.some((part) => part === "--allow-data-loss" || part === "--force");
|
|
456
|
+
}
|
|
457
|
+
async function confirmRecover(ctx, args, markdown, beforeDb, dataLoss) {
|
|
454
458
|
const warning = [
|
|
455
459
|
"gsd recover imports markdown into the database.",
|
|
456
460
|
"It clears and reconstructs milestone, slice, and task hierarchy rows from rendered markdown.",
|
|
457
461
|
"Use /gsd rebuild markdown for normal DB-to-markdown realignment.",
|
|
458
|
-
|
|
462
|
+
"",
|
|
463
|
+
` Markdown on disk: ${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T`,
|
|
464
|
+
` Current DB: ${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T`,
|
|
465
|
+
];
|
|
466
|
+
if (dataLoss) {
|
|
467
|
+
warning.push("", "⚠ The DB holds rows the markdown lacks. Recover will permanently DELETE", " those rows. A snapshot is written to .gsd/backups/ first, but if the DB", " is the source of truth you almost certainly want /gsd rebuild markdown.");
|
|
468
|
+
}
|
|
469
|
+
const warningText = warning.join("\n");
|
|
470
|
+
if (recoverConfirmed(args)) {
|
|
471
|
+
// Non-interactive --confirm still refuses a data-loss recover unless the
|
|
472
|
+
// caller explicitly opts in with --allow-data-loss / --force.
|
|
473
|
+
if (dataLoss && !recoverAllowsDataLoss(args)) {
|
|
474
|
+
ctx.ui.notify(`${warningText}\n\nRefusing: this would delete authoritative DB rows. Re-run with ` +
|
|
475
|
+
`/gsd recover --confirm --allow-data-loss to proceed, or use /gsd rebuild markdown ` +
|
|
476
|
+
`to re-project markdown from the DB instead.`, "error");
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
459
481
|
if (typeof ctx.ui.confirm === "function") {
|
|
460
|
-
const confirmed = await ctx.ui.confirm("Import markdown into the DB?", `${
|
|
461
|
-
if (confirmed)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
482
|
+
const confirmed = await ctx.ui.confirm("Import markdown into the DB?", `${warningText}\n\nContinue only if the DB is lost or corrupt and markdown is the source you intend to import.`);
|
|
483
|
+
if (!confirmed) {
|
|
484
|
+
ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
// Data loss requires a second, explicit acknowledgement — the interactive
|
|
488
|
+
// equivalent of the --allow-data-loss opt-in the non-interactive paths
|
|
489
|
+
// demand. A single generic "yes" must not silently delete DB rows.
|
|
490
|
+
if (dataLoss) {
|
|
491
|
+
const acknowledged = await ctx.ui.confirm("Permanently delete DB rows the markdown lacks?", "This recover will DELETE authoritative DB rows the markdown does not contain. " +
|
|
492
|
+
"A snapshot is saved to .gsd/backups/ first, but /gsd rebuild markdown is usually " +
|
|
493
|
+
"what you want. Proceed with the deletion?");
|
|
494
|
+
if (!acknowledged) {
|
|
495
|
+
ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return true;
|
|
465
500
|
}
|
|
466
|
-
ctx.ui.notify(`${
|
|
501
|
+
ctx.ui.notify(`${warningText}\n\nNo database changes made. Re-run /gsd recover --confirm to proceed.`, "warning");
|
|
467
502
|
return false;
|
|
468
503
|
}
|
|
469
504
|
/**
|
|
@@ -476,16 +511,27 @@ async function confirmRecover(ctx, args) {
|
|
|
476
511
|
* Prints counts of recovered items and the resulting project phase.
|
|
477
512
|
*/
|
|
478
513
|
export async function handleRecover(ctx, basePath, args = "") {
|
|
479
|
-
const { isDbAvailable: dbAvailable, clearEngineHierarchy, transaction: dbTransaction } = await import("./gsd-db.js");
|
|
514
|
+
const { isDbAvailable: dbAvailable, clearEngineHierarchy, transaction: dbTransaction, backupDatabaseSnapshot } = await import("./gsd-db.js");
|
|
480
515
|
const { migrateHierarchyToDb } = await import("./md-importer.js");
|
|
481
516
|
const { invalidateStateCache } = await import("./state.js");
|
|
517
|
+
const { countDbHierarchy, countMarkdownHierarchy, recoverWouldDeleteDbRows } = await import("./migration-auto-check.js");
|
|
518
|
+
const { renderAllFromDb } = await import("./markdown-renderer.js");
|
|
482
519
|
if (!dbAvailable()) {
|
|
483
520
|
ctx.ui.notify("gsd recover: No database open. Run a GSD command first to initialize the DB.", "error");
|
|
484
521
|
return;
|
|
485
522
|
}
|
|
486
|
-
|
|
523
|
+
// Compare markdown-on-disk against the live DB so the confirmation prompt can
|
|
524
|
+
// surface exactly what recover will overwrite (and refuse silent data loss).
|
|
525
|
+
// The data-loss check is identity-based, not count-based: it flags any DB row
|
|
526
|
+
// markdown lacks, including equal-count divergence (DB S99 vs markdown S01).
|
|
527
|
+
const markdown = countMarkdownHierarchy(basePath);
|
|
528
|
+
const beforeDb = countDbHierarchy();
|
|
529
|
+
const dataLoss = recoverWouldDeleteDbRows(basePath);
|
|
530
|
+
if (!(await confirmRecover(ctx, args, markdown, beforeDb, dataLoss)))
|
|
487
531
|
return;
|
|
488
532
|
try {
|
|
533
|
+
// 0. Snapshot the DB before the destructive clear so recover is reversible.
|
|
534
|
+
const backupPath = backupDatabaseSnapshot("pre-recover");
|
|
489
535
|
// 1. Delete + re-populate inside a single transaction for atomicity.
|
|
490
536
|
// clearEngineHierarchy() uses transaction() internally but transaction()
|
|
491
537
|
// is re-entrant, so wrapping in dbTransaction() keeps the whole
|
|
@@ -494,8 +540,14 @@ export async function handleRecover(ctx, basePath, args = "") {
|
|
|
494
540
|
clearEngineHierarchy();
|
|
495
541
|
return migrateHierarchyToDb(basePath);
|
|
496
542
|
});
|
|
497
|
-
//
|
|
543
|
+
// 2. Invalidate state cache so deriveState() picks up fresh DB data
|
|
498
544
|
invalidateStateCache();
|
|
545
|
+
// 3. Re-project markdown from the freshly imported DB so disk and DB agree
|
|
546
|
+
// immediately (otherwise the markdown still reflects the pre-import state
|
|
547
|
+
// and the next startup check would flag fresh drift). renderAllFromDb
|
|
548
|
+
// swallows per-artifact failures into its result, so inspect them — a
|
|
549
|
+
// silent projection failure must not be reported as a clean success.
|
|
550
|
+
const renderResult = await renderAllFromDb(basePath);
|
|
499
551
|
// 4. Derive state to verify sanity
|
|
500
552
|
const state = await deriveState(basePath);
|
|
501
553
|
// 5. Report
|
|
@@ -507,6 +559,18 @@ export async function handleRecover(ctx, basePath, args = "") {
|
|
|
507
559
|
``,
|
|
508
560
|
` Phase: ${state.phase}`,
|
|
509
561
|
];
|
|
562
|
+
// Post-import verification: markdown that failed to parse imports as fewer
|
|
563
|
+
// rows than countMarkdownHierarchy saw on disk. Surface the shortfall.
|
|
564
|
+
if (counts.milestones < markdown.milestones ||
|
|
565
|
+
counts.slices < markdown.slices ||
|
|
566
|
+
counts.tasks < markdown.tasks) {
|
|
567
|
+
lines.push(``, ` ⚠ Imported fewer rows than markdown contained ` +
|
|
568
|
+
`(${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T on disk). ` +
|
|
569
|
+
`Some markdown may have failed to parse — review before continuing.`);
|
|
570
|
+
}
|
|
571
|
+
if (backupPath) {
|
|
572
|
+
lines.push(``, ` Backup: ${backupPath}`);
|
|
573
|
+
}
|
|
510
574
|
if (state.activeMilestone) {
|
|
511
575
|
lines.push(` Active: ${state.activeMilestone.id}: ${state.activeMilestone.title}`);
|
|
512
576
|
}
|
|
@@ -516,8 +580,22 @@ export async function handleRecover(ctx, basePath, args = "") {
|
|
|
516
580
|
if (state.activeTask) {
|
|
517
581
|
lines.push(` Task: ${state.activeTask.id}: ${state.activeTask.title}`);
|
|
518
582
|
}
|
|
519
|
-
|
|
520
|
-
|
|
583
|
+
// Surface markdown projection failures: renderAllFromDb resolves even when
|
|
584
|
+
// individual artifacts fail to render, so a clean exit here would otherwise
|
|
585
|
+
// hide a stale/partial projection.
|
|
586
|
+
const renderFailed = renderResult.errors.length > 0;
|
|
587
|
+
if (renderFailed) {
|
|
588
|
+
lines.push(``, ` ⚠ ${renderResult.errors.length} markdown projection(s) failed to render — ` +
|
|
589
|
+
`markdown may be stale. Re-run /gsd rebuild markdown.`);
|
|
590
|
+
for (const e of renderResult.errors.slice(0, 5))
|
|
591
|
+
lines.push(` - ${e}`);
|
|
592
|
+
if (renderResult.errors.length > 5) {
|
|
593
|
+
lines.push(` …and ${renderResult.errors.length - 5} more`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
process.stderr.write(`gsd-recover: recovered ${counts.milestones}M/${counts.slices}S/${counts.tasks}T hierarchy` +
|
|
597
|
+
`${renderFailed ? ` (${renderResult.errors.length} projection errors)` : ""}\n`);
|
|
598
|
+
ctx.ui.notify(lines.join("\n"), renderFailed ? "warning" : "success");
|
|
521
599
|
}
|
|
522
600
|
catch (err) {
|
|
523
601
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1236,7 +1236,7 @@ async function configureHooks(ctx, prefs) {
|
|
|
1236
1236
|
if (geEnabled !== undefined)
|
|
1237
1237
|
ge.enabled = geEnabled;
|
|
1238
1238
|
const currentSliceGates = Array.isArray(ge.slice_gates) ? ge.slice_gates : [];
|
|
1239
|
-
const sgInput = await ctx.ui.input(`
|
|
1239
|
+
const sgInput = await ctx.ui.input(`Gate-evaluate slice gates (Q3,Q4; comma-separated, blank keeps)${currentSliceGates.length ? ` (current: ${currentSliceGates.join(", ")})` : " (default: Q3,Q4)"}:`, currentSliceGates.join(", "));
|
|
1240
1240
|
if (sgInput !== null && sgInput !== undefined) {
|
|
1241
1241
|
const parsed = parseStringList(sgInput);
|
|
1242
1242
|
if (parsed.length > 0)
|
|
@@ -1697,7 +1697,7 @@ export function serializePreferencesToFrontmatter(prefs) {
|
|
|
1697
1697
|
// Ordered keys for consistent output
|
|
1698
1698
|
const orderedKeys = [
|
|
1699
1699
|
"version", "mode", "always_use_skills", "prefer_skills", "avoid_skills",
|
|
1700
|
-
"skill_rules", "custom_instructions", "models", "skill_discovery",
|
|
1700
|
+
"skill_rules", "custom_instructions", "models", "thinking", "skill_discovery",
|
|
1701
1701
|
"skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
|
|
1702
1702
|
"budget_ceiling", "budget_enforcement", "context_pause_threshold",
|
|
1703
1703
|
"notifications", "cmux", "remote_questions", "git",
|
|
@@ -397,6 +397,41 @@ let _decisionSaveLock = Promise.resolve();
|
|
|
397
397
|
export function _resetDecisionSaveLock() {
|
|
398
398
|
_decisionSaveLock = Promise.resolve();
|
|
399
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Re-project root DECISIONS.md from the authoritative decision records, with no
|
|
402
|
+
* new decision being added. Mirrors the projection saveDecisionToDb performs
|
|
403
|
+
* after a save, but over the full set — so a DB → markdown re-projection
|
|
404
|
+
* (recover, rebuild, reconcile) re-derives DECISIONS.md and never leaves it
|
|
405
|
+
* showing a stale subset (e.g. after a worktree merge that accepted one
|
|
406
|
+
* branch's DECISIONS.md while the DB holds the union of both branches'
|
|
407
|
+
* decisions). DB stays the single source of truth; this only writes markdown.
|
|
408
|
+
*/
|
|
409
|
+
export async function regenerateDecisionsMarkdown(basePath) {
|
|
410
|
+
const { getAllDecisionsFromMemories } = await import('./context-store.js');
|
|
411
|
+
const allDecisions = getAllDecisionsFromMemories();
|
|
412
|
+
const filePath = resolveGsdRootFile(basePath, 'DECISIONS');
|
|
413
|
+
let existingContent = null;
|
|
414
|
+
if (existsSync(filePath)) {
|
|
415
|
+
existingContent = readFileSync(filePath, 'utf-8');
|
|
416
|
+
}
|
|
417
|
+
// Nothing to project: no decisions in the DB and no file to normalize.
|
|
418
|
+
if (allDecisions.length === 0 && existingContent === null)
|
|
419
|
+
return;
|
|
420
|
+
let md;
|
|
421
|
+
if (existingContent && !isDecisionsTableFormat(existingContent)) {
|
|
422
|
+
// Preserve freeform content; refresh only the appended decisions table.
|
|
423
|
+
const marker = '---\n\n## Decisions Table';
|
|
424
|
+
const markerIdx = existingContent.indexOf(marker);
|
|
425
|
+
const freeformPart = markerIdx >= 0
|
|
426
|
+
? existingContent.substring(0, markerIdx).trimEnd()
|
|
427
|
+
: existingContent.trimEnd();
|
|
428
|
+
md = freeformPart + '\n' + generateDecisionsAppendBlock(allDecisions);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
md = generateDecisionsMd(allDecisions);
|
|
432
|
+
}
|
|
433
|
+
await saveFile(filePath, md);
|
|
434
|
+
}
|
|
400
435
|
/**
|
|
401
436
|
* Save a new decision to DB and regenerate DECISIONS.md.
|
|
402
437
|
* Auto-assigns the next ID via nextDecisionId().
|