@opengsd/gsd-pi 1.1.1-dev.616a1a1 → 1.1.1-dev.74e8dd1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
- package/dist/resources/extensions/gsd/auto/phases.js +4 -3
- package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
- package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
- package/dist/resources/extensions/gsd/auto-start.js +94 -15
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
- package/dist/resources/extensions/gsd/auto.js +22 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
- package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
- package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
- package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +2 -1
- package/dist/resources/extensions/gsd/error-classifier.js +2 -1
- package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
- package/dist/resources/extensions/gsd/gsd-db.js +37 -4
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
- package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
- package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/dist/resources/extensions/gsd/prompts/system.md +3 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
- package/dist/resources/extensions/gsd/skill-activation.js +20 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
- package/dist/resources/extensions/gsd/state.js +15 -12
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
- package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
- package/dist/resources/extensions/mcp-client/manager.js +31 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
- 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 +4 -4
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- 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/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- 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 +72 -31
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +82 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- 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 +15 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +15 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +338 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +412 -112
- 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.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/terminal.d.ts +1 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +8 -4
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +196 -16
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
- package/src/resources/extensions/gsd/auto/phases.ts +5 -3
- package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
- package/src/resources/extensions/gsd/auto-start.ts +112 -17
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
- package/src/resources/extensions/gsd/auto.ts +35 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
- package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
- package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
- package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +2 -1
- package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
- package/src/resources/extensions/gsd/gsd-db.ts +41 -6
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
- package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/src/resources/extensions/gsd/prompts/system.md +3 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
- package/src/resources/extensions/gsd/skill-activation.ts +20 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
- package/src/resources/extensions/gsd/state.ts +16 -12
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
- package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
- package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
- package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
- package/src/resources/extensions/mcp-client/manager.ts +33 -1
- package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
|
@@ -25,6 +25,57 @@ async function loadContextModePreferences(baseDir: string) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function registerExecTools(pi: ExtensionAPI): void {
|
|
28
|
+
pi.registerTool({
|
|
29
|
+
name: "gsd_uat_exec",
|
|
30
|
+
label: "UAT Exec",
|
|
31
|
+
description:
|
|
32
|
+
"Run a UAT-scoped bash/node/python check with milestone/slice/check metadata. " +
|
|
33
|
+
"Uses the same capped .gsd/exec evidence store as gsd_exec, but rejects commands that mutate dependencies, git state, credentials, or destructive files.",
|
|
34
|
+
promptSnippet: "Run one UAT check and save typed evidence under .gsd/exec",
|
|
35
|
+
promptGuidelines: [
|
|
36
|
+
"Use gsd_uat_exec for each automated UAT check.",
|
|
37
|
+
"Every PASS/FAIL check saved by gsd_uat_result_save must reference objective evidence from this tool or another approved GSD evidence path.",
|
|
38
|
+
"Do not install packages, mutate git state, edit source files, or dump credentials during UAT.",
|
|
39
|
+
],
|
|
40
|
+
parameters: Type.Object({
|
|
41
|
+
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
42
|
+
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
43
|
+
checkId: Type.String({ description: "Stable check ID from the UAT spec (e.g. UAT-01)" }),
|
|
44
|
+
intent: Type.String({
|
|
45
|
+
description:
|
|
46
|
+
"UAT command intent. Use one canonical value: uat-artifact-check, uat-runtime-check, " +
|
|
47
|
+
"uat-browser-check, uat-service-start, or uat-log-inspection. Short aliases such as artifact, " +
|
|
48
|
+
"runtime, browser, service-start, and log-inspection are accepted.",
|
|
49
|
+
}),
|
|
50
|
+
runtime: Type.Optional(
|
|
51
|
+
Type.String({
|
|
52
|
+
description:
|
|
53
|
+
"Optional interpreter. Defaults to bash. Supported: bash, node, python; sh/shell, js/nodejs, and py/python3 aliases are accepted.",
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
script: Type.Optional(Type.String({ description: "Script body. Keep output small (log the finding, not the data)." })),
|
|
57
|
+
command: Type.Optional(Type.String({ description: "Alias for script; defaults to bash when runtime is omitted." })),
|
|
58
|
+
cmd: Type.Optional(Type.String({ description: "Short alias for script." })),
|
|
59
|
+
code: Type.Optional(Type.String({ description: "Alias for script, useful for node/python snippets." })),
|
|
60
|
+
expected: Type.Optional(Type.String({ description: "Expected outcome for this UAT check." })),
|
|
61
|
+
timeout_ms: Type.Optional(
|
|
62
|
+
Type.Number({
|
|
63
|
+
description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
|
|
64
|
+
minimum: 1_000,
|
|
65
|
+
maximum: 600_000,
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
}),
|
|
69
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
70
|
+
const { executeUatExec } = await import("../tools/exec-tool.js");
|
|
71
|
+
const baseDir = resolveCtxCwd(_ctx);
|
|
72
|
+
return executeUatExec(params as Parameters<typeof executeUatExec>[0], {
|
|
73
|
+
baseDir,
|
|
74
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
28
79
|
pi.registerTool({
|
|
29
80
|
name: "gsd_exec",
|
|
30
81
|
label: "Exec (Sandboxed)",
|
|
@@ -36,7 +36,9 @@ import { resolveSkillManifest } from "../skill-manifest.js";
|
|
|
36
36
|
import { applyUnitSkillVisibility, unitHasSkillManifest } from "../skill-scope.js";
|
|
37
37
|
import { getGuidedUnitContext } from "../guided-unit-context.js";
|
|
38
38
|
import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-recovery.js";
|
|
39
|
-
import { AUTO_UNIT_SCOPED_TOOLS, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
|
|
39
|
+
import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
|
|
40
|
+
import { filterToolsForProvider } from "../model-router.js";
|
|
41
|
+
import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
|
|
40
42
|
|
|
41
43
|
let approvalQuestionAbortInFlight = false;
|
|
42
44
|
|
|
@@ -123,6 +125,7 @@ export const MINIMAL_GSD_TOOL_NAMES = [
|
|
|
123
125
|
"gsd_resume",
|
|
124
126
|
"gsd_milestone_status",
|
|
125
127
|
"gsd_checkpoint_db",
|
|
128
|
+
"gsd_plan_milestone",
|
|
126
129
|
"memory_query",
|
|
127
130
|
"capture_thought",
|
|
128
131
|
] as const;
|
|
@@ -226,6 +229,9 @@ export function buildMinimalAutoGsdToolSet(
|
|
|
226
229
|
unitType: string | undefined,
|
|
227
230
|
registeredToolNames: readonly string[] = activeToolNames,
|
|
228
231
|
): string[] {
|
|
232
|
+
if (unitType === "run-uat") {
|
|
233
|
+
return buildRunUatGsdToolSet(activeToolNames, registeredToolNames);
|
|
234
|
+
}
|
|
229
235
|
const unitTools = unitType ? AUTO_UNIT_SCOPED_TOOLS[unitType] ?? [] : [];
|
|
230
236
|
const autoBaseTools = new Set<string>(MINIMAL_AUTO_BASE_TOOL_NAMES);
|
|
231
237
|
const availableBaseTools = registeredToolNames.filter((name) => autoBaseTools.has(name));
|
|
@@ -240,6 +246,17 @@ export function buildMinimalAutoGsdToolSet(
|
|
|
240
246
|
return withPreservedShimTools([...new Set([...preserved, ...scoped])]);
|
|
241
247
|
}
|
|
242
248
|
|
|
249
|
+
export function buildRunUatGsdToolSet(
|
|
250
|
+
activeToolNames: readonly string[],
|
|
251
|
+
registeredToolNames: readonly string[] = activeToolNames,
|
|
252
|
+
): string[] {
|
|
253
|
+
const scoped = resolveScopedToolNames(
|
|
254
|
+
[...activeToolNames, ...registeredToolNames],
|
|
255
|
+
[...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES],
|
|
256
|
+
);
|
|
257
|
+
return [...new Set(scoped)];
|
|
258
|
+
}
|
|
259
|
+
|
|
243
260
|
export function buildMinimalGsdWorkflowToolSet(
|
|
244
261
|
activeToolNames: readonly string[],
|
|
245
262
|
registeredToolNames: readonly string[] = activeToolNames,
|
|
@@ -1022,9 +1039,8 @@ export function registerHooks(
|
|
|
1022
1039
|
if (result.block) return result;
|
|
1023
1040
|
});
|
|
1024
1041
|
|
|
1025
|
-
// ── Safety harness: evidence collection + destructive command
|
|
1042
|
+
// ── Safety harness: evidence collection + destructive command blocking ──
|
|
1026
1043
|
pi.on("tool_call", async (event, ctx) => {
|
|
1027
|
-
if (!isAutoActive()) return;
|
|
1028
1044
|
markToolStart(event.toolCallId, event.toolName);
|
|
1029
1045
|
safetyRecordToolCall(event.toolCallId, event.toolName, event.input as Record<string, unknown>);
|
|
1030
1046
|
|
|
@@ -1041,17 +1057,28 @@ export function registerHooks(
|
|
|
1041
1057
|
}
|
|
1042
1058
|
}
|
|
1043
1059
|
|
|
1044
|
-
// Destructive command classification
|
|
1060
|
+
// Destructive command classification + hard gate in all modes.
|
|
1045
1061
|
if (isToolCallEventType("bash", event)) {
|
|
1046
1062
|
const classification = classifyCommand(event.input.command);
|
|
1047
1063
|
if (classification.destructive) {
|
|
1064
|
+
const reason = [
|
|
1065
|
+
"HARD BLOCK: destructive Bash command requires explicit human confirmation.",
|
|
1066
|
+
`Detected: ${classification.labels.join(", ")}`,
|
|
1067
|
+
"Run this via ask_user_questions, wait for the user's response,",
|
|
1068
|
+
"then issue the command only when confirmed in the current turn.",
|
|
1069
|
+
].join(" ");
|
|
1048
1070
|
safetyLogWarning("safety", `destructive command: ${classification.labels.join(", ")}`, {
|
|
1049
1071
|
command: String(event.input.command).slice(0, 200),
|
|
1050
1072
|
});
|
|
1051
|
-
ctx
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1073
|
+
if (ctx) {
|
|
1074
|
+
await maybePauseAutoForApprovalGate(
|
|
1075
|
+
ctx,
|
|
1076
|
+
pi,
|
|
1077
|
+
isAutoActive(),
|
|
1078
|
+
"Depth confirmation is waiting for your answer — pausing auto-mode.",
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
return { block: true, reason };
|
|
1055
1082
|
}
|
|
1056
1083
|
}
|
|
1057
1084
|
});
|
|
@@ -1320,19 +1347,27 @@ export function registerHooks(
|
|
|
1320
1347
|
const fullToolsRequested = isFullGsdToolSurfaceRequested();
|
|
1321
1348
|
const dropAliases = !fullToolsRequested;
|
|
1322
1349
|
const dropBrowser = !fullToolsRequested && !isBrowserToolSurfaceRequested();
|
|
1323
|
-
const
|
|
1324
|
-
(name) => !(dropAliases && isWorkflowAliasTool(name))
|
|
1350
|
+
const aliasFilteredCompatible = compatible.filter(
|
|
1351
|
+
(name) => !(dropAliases && isWorkflowAliasTool(name)),
|
|
1352
|
+
);
|
|
1353
|
+
const providerCompatible = aliasFilteredCompatible.filter(
|
|
1354
|
+
(name) => !(dropBrowser && isBrowserTool(name)),
|
|
1325
1355
|
);
|
|
1326
1356
|
const surfaceReduced = providerCompatible.length !== compatible.length;
|
|
1327
1357
|
if (fullToolsRequested) {
|
|
1328
1358
|
return surfaceReduced ? { toolNames: providerCompatible } : undefined;
|
|
1329
1359
|
}
|
|
1330
1360
|
const registeredToolNames = resolveRegisteredToolNames(pi, event.activeToolNames);
|
|
1361
|
+
const compatibleRegisteredToolNames = filterToolsForProvider(
|
|
1362
|
+
registeredToolNames,
|
|
1363
|
+
event.selectedModelApi,
|
|
1364
|
+
event.selectedModelProvider,
|
|
1365
|
+
).compatible.filter((name) => !(dropAliases && isWorkflowAliasTool(name)));
|
|
1331
1366
|
const guidedUnit = getGuidedUnitContext();
|
|
1332
1367
|
const requestScoped = buildRequestScopedGsdToolSet(
|
|
1333
|
-
providerCompatible,
|
|
1368
|
+
guidedUnit?.unitType === "run-uat" ? aliasFilteredCompatible : providerCompatible,
|
|
1334
1369
|
event.requestCustomMessages,
|
|
1335
|
-
registeredToolNames,
|
|
1370
|
+
guidedUnit?.unitType === "run-uat" ? compatibleRegisteredToolNames : registeredToolNames,
|
|
1336
1371
|
guidedUnit?.unitType,
|
|
1337
1372
|
);
|
|
1338
1373
|
if (requestScoped) {
|
|
@@ -1342,9 +1377,11 @@ export function registerHooks(
|
|
|
1342
1377
|
if (dash.active && dash.currentUnit) {
|
|
1343
1378
|
return {
|
|
1344
1379
|
toolNames: buildMinimalAutoGsdToolSet(
|
|
1345
|
-
providerCompatible,
|
|
1380
|
+
dash.currentUnit.type === "run-uat" ? aliasFilteredCompatible : providerCompatible,
|
|
1346
1381
|
dash.currentUnit.type,
|
|
1347
|
-
|
|
1382
|
+
dash.currentUnit.type === "run-uat"
|
|
1383
|
+
? compatibleRegisteredToolNames
|
|
1384
|
+
: resolveRegisteredToolNames(pi, event.activeToolNames),
|
|
1348
1385
|
),
|
|
1349
1386
|
};
|
|
1350
1387
|
}
|
|
@@ -679,6 +679,7 @@ const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
|
|
|
679
679
|
* manifests still declare per-unit subsets via ToolsPolicy.allowedSubagents.
|
|
680
680
|
*/
|
|
681
681
|
const PLANNING_DISPATCH_AGENT_REGISTRY = {
|
|
682
|
+
mnemo: { readOnlySpecialist: true },
|
|
682
683
|
scout: { readOnlySpecialist: true },
|
|
683
684
|
planner: { readOnlySpecialist: true },
|
|
684
685
|
reviewer: { readOnlySpecialist: true },
|
|
@@ -692,7 +693,7 @@ export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set<string>(
|
|
|
692
693
|
.map(([agentId]) => agentId),
|
|
693
694
|
);
|
|
694
695
|
|
|
695
|
-
let
|
|
696
|
+
let warnedMissingControlledDispatchAgentClasses = false;
|
|
696
697
|
|
|
697
698
|
function isReadOnlySpecialist(agentId: string): boolean {
|
|
698
699
|
const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId as keyof typeof PLANNING_DISPATCH_AGENT_REGISTRY];
|
|
@@ -703,11 +704,20 @@ function allowedPlanningDispatchAgentsList(): string {
|
|
|
703
704
|
return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
|
|
704
705
|
}
|
|
705
706
|
|
|
706
|
-
function
|
|
707
|
-
|
|
708
|
-
|
|
707
|
+
function allowsControlledSubagentDispatch(
|
|
708
|
+
policy: ToolsPolicy,
|
|
709
|
+
): policy is ToolsPolicy & { readonly allowedSubagents: readonly string[] } {
|
|
710
|
+
return (
|
|
711
|
+
(policy.mode === "planning-dispatch" || policy.mode === "verification") &&
|
|
712
|
+
Array.isArray((policy as { readonly allowedSubagents?: unknown }).allowedSubagents)
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function warnMissingControlledDispatchAgentClasses(unitType: string, mode: string, toolName: string): void {
|
|
717
|
+
if (warnedMissingControlledDispatchAgentClasses) return;
|
|
718
|
+
warnedMissingControlledDispatchAgentClasses = true;
|
|
709
719
|
// TODO(#5060): Remove this migration shim once all subagent/task callers are verified to forward agent identities.
|
|
710
|
-
const message = `[write-gate]
|
|
720
|
+
const message = `[write-gate] controlled-dispatch: shouldBlockPlanningUnit called for tool "${toolName}" ` +
|
|
711
721
|
`on unit "${unitType}" without agentClasses - stale caller; blocking dispatch.`;
|
|
712
722
|
console.warn(message);
|
|
713
723
|
logWarning("intercept", message, {
|
|
@@ -777,8 +787,9 @@ function blockReason(unitType: string, mode: string, what: string): string {
|
|
|
777
787
|
* - "docs" → like "planning" but also allows writes to paths
|
|
778
788
|
* matching `allowedPathGlobs` relative to basePath.
|
|
779
789
|
* - "verification"
|
|
780
|
-
* → allows Bash for project verification commands,
|
|
781
|
-
* writes restricted to .gsd
|
|
790
|
+
* → allows Bash for project verification commands, keeps
|
|
791
|
+
* writes restricted to .gsd/, and permits subagent dispatch
|
|
792
|
+
* only when the manifest declares allowedSubagents.
|
|
782
793
|
*
|
|
783
794
|
* `pathOrCommand` is the file path for write/edit-shaped tools and the
|
|
784
795
|
* shell command for bash. Other tools ignore this argument.
|
|
@@ -825,7 +836,7 @@ export function shouldBlockPlanningUnit(
|
|
|
825
836
|
if (tool.startsWith("gsd_")) return { block: false };
|
|
826
837
|
|
|
827
838
|
if (PLANNING_SUBAGENT_TOOLS.has(tool)) {
|
|
828
|
-
if (policy
|
|
839
|
+
if (allowsControlledSubagentDispatch(policy)) {
|
|
829
840
|
const requested = (agentClasses ?? []).map(a => a.trim()).filter(Boolean);
|
|
830
841
|
const dispatchContract = compileSubagentPermissionContract(policy);
|
|
831
842
|
const allowedSubagents = dispatchContract.allowedSubagents;
|
|
@@ -834,7 +845,7 @@ export function shouldBlockPlanningUnit(
|
|
|
834
845
|
// agent identities yet. Block and warn so stale callers surface in telemetry
|
|
835
846
|
// instead of silently bypassing the gate.
|
|
836
847
|
if (agentClasses === undefined) {
|
|
837
|
-
|
|
848
|
+
warnMissingControlledDispatchAgentClasses(unitType, policy.mode, tool);
|
|
838
849
|
return {
|
|
839
850
|
block: true,
|
|
840
851
|
reason: blockReason(
|
|
@@ -857,7 +868,7 @@ export function shouldBlockPlanningUnit(
|
|
|
857
868
|
reason: blockReason(
|
|
858
869
|
unitType,
|
|
859
870
|
policy.mode,
|
|
860
|
-
`subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from
|
|
871
|
+
`subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
|
|
861
872
|
),
|
|
862
873
|
};
|
|
863
874
|
}
|
|
@@ -14,7 +14,7 @@ export interface GsdCommandDefinition {
|
|
|
14
14
|
type CompletionMap = Record<string, readonly GsdCommandDefinition[]>;
|
|
15
15
|
|
|
16
16
|
export const GSD_COMMAND_DESCRIPTION =
|
|
17
|
-
"GSD — Git Ship Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|brief|report|queue|quick|discuss|capture|triage|dispatch|verdict|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|closeout|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|memory|new-milestone|new-project|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|upgrade|fast|mcp|rethink|workflow|codebase|notifications|ship|do|usage|context|session-report|backlog|pr-branch|add-tests|scan|language|worktree|eval-review";
|
|
17
|
+
"GSD — Git Ship Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|brief|report|queue|quick|discuss|capture|triage|dispatch|verdict|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|closeout|rebuild|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|memory|new-milestone|new-project|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|upgrade|fast|mcp|rethink|workflow|codebase|notifications|ship|do|usage|context|session-report|backlog|pr-branch|add-tests|scan|language|worktree|eval-review";
|
|
18
18
|
|
|
19
19
|
export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
20
20
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -44,6 +44,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
|
44
44
|
{ cmd: "export", desc: "Alias for /gsd report" },
|
|
45
45
|
{ cmd: "cleanup", desc: "Remove merged branches or snapshots" },
|
|
46
46
|
{ cmd: "closeout", desc: "Recover failed git closeout actions (status, retry, resolve)" },
|
|
47
|
+
{ cmd: "rebuild", desc: "Rebuild markdown projections from the canonical DB" },
|
|
47
48
|
{ cmd: "model", desc: "Switch the active session model or open a picker" },
|
|
48
49
|
{ cmd: "mode", desc: "Switch workflow mode (solo/team)" },
|
|
49
50
|
{ cmd: "prefs", desc: "Manage preferences (model selection, timeouts, etc.)" },
|
|
@@ -214,6 +215,10 @@ const NESTED_COMPLETIONS: CompletionMap = {
|
|
|
214
215
|
{ cmd: "retry", desc: "Retry the latest failed closeout git action" },
|
|
215
216
|
{ cmd: "resolve", desc: "Mark closeout resolved after the worktree is clean" },
|
|
216
217
|
],
|
|
218
|
+
rebuild: [
|
|
219
|
+
{ cmd: "markdown", desc: "Rebuild markdown projections from the canonical DB" },
|
|
220
|
+
{ cmd: "database", desc: "Reserved for DB-native rebuilds; does not import markdown" },
|
|
221
|
+
],
|
|
217
222
|
knowledge: [
|
|
218
223
|
{ cmd: "rule", desc: "Add a project rule (always/never do X)" },
|
|
219
224
|
{ cmd: "pattern", desc: "Add a code pattern to follow" },
|
|
@@ -56,6 +56,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
|
|
|
56
56
|
" /gsd keys API key manager (LLM + tool keys)",
|
|
57
57
|
" /gsd doctor Diagnose and repair .gsd/ state",
|
|
58
58
|
" /gsd closeout Recover failed git closeout actions",
|
|
59
|
+
" /gsd rebuild Rebuild markdown projections from the DB [markdown]",
|
|
59
60
|
"",
|
|
60
61
|
"Use /gsd help full for the complete command reference.",
|
|
61
62
|
];
|
|
@@ -74,7 +75,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
|
|
|
74
75
|
" /gsd new-milestone Create milestone from headless context (used by gsd headless)",
|
|
75
76
|
" /gsd new-project Bootstrap a new project (use --deep for staged project-level discovery)",
|
|
76
77
|
" /gsd quick Execute a quick task without full planning overhead",
|
|
77
|
-
" /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|uat|replan]",
|
|
78
|
+
" /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|validate|reassess|uat|replan]",
|
|
78
79
|
" /gsd verdict <v> Override milestone validation verdict [pass|needs-attention|needs-remediation] [--milestone Mxxx] [--rationale \"...\"]",
|
|
79
80
|
" /gsd parallel Parallel milestone orchestration [start|status|stop|pause|resume|merge|watch]",
|
|
80
81
|
" /gsd workflow Custom workflow lifecycle [new|run|list|validate|pause|resume]",
|
|
@@ -138,7 +139,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
|
|
|
138
139
|
" /gsd skill-health Skill lifecycle dashboard",
|
|
139
140
|
" /gsd extensions Manage extensions [list|enable|disable|info]",
|
|
140
141
|
" /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
|
|
141
|
-
" /gsd mcp MCP server management [status|check|test|enable|disable|import|delete|init]",
|
|
142
|
+
" /gsd mcp MCP server management [status|check|discover|test|enable|disable|import|delete|init]",
|
|
142
143
|
"",
|
|
143
144
|
"MAINTENANCE",
|
|
144
145
|
" /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
|
|
@@ -147,6 +148,9 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
|
|
|
147
148
|
" /gsd export Alias for /gsd report",
|
|
148
149
|
" /gsd cleanup Remove merged branches or snapshots [branches|snapshots]",
|
|
149
150
|
" /gsd closeout Recover failed git closeout actions [status|retry|resolve] [unit-id]",
|
|
151
|
+
" /gsd rebuild markdown Rebuild markdown projections from the canonical DB",
|
|
152
|
+
" /gsd rebuild database Reserved for DB-native rebuilds; does not import markdown",
|
|
153
|
+
" /gsd recover --confirm Import markdown into the DB after DB loss/corruption",
|
|
150
154
|
" /gsd worktree Manage worktrees from the TUI [list|merge|clean|remove]",
|
|
151
155
|
" /gsd migrate Migrate .planning/ (v1) to DB-backed .gsd/ with backup + audit",
|
|
152
156
|
" /gsd remote Control remote auto-mode [slack|discord|status|disconnect]",
|
|
@@ -9,7 +9,7 @@ import { handleDoctor, handleCapture, handleKnowledge, handleRunHook, handleSkil
|
|
|
9
9
|
import { handleInspect } from "../../commands-inspect.js";
|
|
10
10
|
import { handleLogs } from "../../commands-logs.js";
|
|
11
11
|
import { handleDebug } from "../../commands-debug.js";
|
|
12
|
-
import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleCleanupProjects, handleCleanupWorktrees, handleRecover } from "../../commands-maintenance.js";
|
|
12
|
+
import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleCleanupProjects, handleCleanupWorktrees, handleRecover, handleRebuild } from "../../commands-maintenance.js";
|
|
13
13
|
import { handleExport } from "../../export.js";
|
|
14
14
|
import { handleHistory } from "../../history.js";
|
|
15
15
|
import { handleUndo } from "../../undo.js";
|
|
@@ -146,8 +146,12 @@ export async function handleOpsCommand(trimmed: string, ctx: ExtensionCommandCon
|
|
|
146
146
|
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
|
|
147
147
|
return true;
|
|
148
148
|
}
|
|
149
|
-
if (trimmed === "recover") {
|
|
150
|
-
await handleRecover(ctx, projectRoot());
|
|
149
|
+
if (trimmed === "recover" || trimmed.startsWith("recover ")) {
|
|
150
|
+
await handleRecover(ctx, projectRoot(), trimmed.replace(/^recover\s*/, "").trim());
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (trimmed === "rebuild" || trimmed.startsWith("rebuild ")) {
|
|
154
|
+
await handleRebuild(ctx, projectRoot(), trimmed.replace(/^rebuild\s*/, "").trim());
|
|
151
155
|
return true;
|
|
152
156
|
}
|
|
153
157
|
if (trimmed === "closeout" || trimmed.startsWith("closeout ")) {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GSD Maintenance — cleanup, skip, dry-run, and recover handlers.
|
|
3
3
|
*
|
|
4
|
-
* Contains: handleCleanupBranches, handleCleanupSnapshots, handleCleanupWorktrees, handleSkip, handleDryRun, handleRecover
|
|
4
|
+
* Contains: handleCleanupBranches, handleCleanupSnapshots, handleCleanupWorktrees, handleSkip, handleDryRun, handleRecover, handleRebuild
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
8
|
+
import { existsSync, mkdirSync, renameSync } from "node:fs";
|
|
9
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
8
10
|
import { deriveState } from "./state.js";
|
|
11
|
+
import { gsdProjectionRoot, gsdRoot } from "./paths.js";
|
|
9
12
|
import { nativeBranchList, nativeDetectMainBranch, nativeBranchListMerged, nativeBranchDelete, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
|
|
10
13
|
import { logWarning } from "./workflow-logger.js";
|
|
11
14
|
|
|
@@ -478,6 +481,39 @@ export async function handleCleanupProjects(args: string, ctx: ExtensionCommandC
|
|
|
478
481
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
479
482
|
}
|
|
480
483
|
|
|
484
|
+
function recoverConfirmed(args: string): boolean {
|
|
485
|
+
return args
|
|
486
|
+
.split(/\s+/)
|
|
487
|
+
.map((part) => part.trim().toLowerCase())
|
|
488
|
+
.some((part) => part === "--confirm" || part === "--yes" || part === "confirm");
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function confirmRecover(ctx: ExtensionCommandContext, args: string): Promise<boolean> {
|
|
492
|
+
if (recoverConfirmed(args)) return true;
|
|
493
|
+
|
|
494
|
+
const warning = [
|
|
495
|
+
"gsd recover imports markdown into the database.",
|
|
496
|
+
"It clears and reconstructs milestone, slice, and task hierarchy rows from rendered markdown.",
|
|
497
|
+
"Use /gsd rebuild markdown for normal DB-to-markdown realignment.",
|
|
498
|
+
].join("\n");
|
|
499
|
+
|
|
500
|
+
if (typeof ctx.ui.confirm === "function") {
|
|
501
|
+
const confirmed = await ctx.ui.confirm(
|
|
502
|
+
"Import markdown into the DB?",
|
|
503
|
+
`${warning}\n\nContinue only if the DB is lost or corrupt and markdown is the source you intend to import.`,
|
|
504
|
+
);
|
|
505
|
+
if (confirmed) return true;
|
|
506
|
+
ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
ctx.ui.notify(
|
|
511
|
+
`${warning}\n\nNo database changes made. Re-run /gsd recover --confirm to proceed.`,
|
|
512
|
+
"warning",
|
|
513
|
+
);
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
481
517
|
/**
|
|
482
518
|
* `gsd recover` — Reconstruct DB hierarchy state from rendered markdown on disk.
|
|
483
519
|
*
|
|
@@ -487,7 +523,7 @@ export async function handleCleanupProjects(args: string, ctx: ExtensionCommandC
|
|
|
487
523
|
*
|
|
488
524
|
* Prints counts of recovered items and the resulting project phase.
|
|
489
525
|
*/
|
|
490
|
-
export async function handleRecover(ctx: ExtensionCommandContext, basePath: string): Promise<void> {
|
|
526
|
+
export async function handleRecover(ctx: ExtensionCommandContext, basePath: string, args = ""): Promise<void> {
|
|
491
527
|
const { isDbAvailable: dbAvailable, clearEngineHierarchy, transaction: dbTransaction } = await import("./gsd-db.js");
|
|
492
528
|
const { migrateHierarchyToDb } = await import("./md-importer.js");
|
|
493
529
|
const { invalidateStateCache } = await import("./state.js");
|
|
@@ -497,6 +533,8 @@ export async function handleRecover(ctx: ExtensionCommandContext, basePath: stri
|
|
|
497
533
|
return;
|
|
498
534
|
}
|
|
499
535
|
|
|
536
|
+
if (!(await confirmRecover(ctx, args))) return;
|
|
537
|
+
|
|
500
538
|
try {
|
|
501
539
|
// 1. Delete + re-populate inside a single transaction for atomicity.
|
|
502
540
|
// clearEngineHierarchy() uses transaction() internally but transaction()
|
|
@@ -542,3 +580,160 @@ export async function handleRecover(ctx: ExtensionCommandContext, basePath: stri
|
|
|
542
580
|
ctx.ui.notify(`gsd recover failed: ${msg}`, "error");
|
|
543
581
|
}
|
|
544
582
|
}
|
|
583
|
+
|
|
584
|
+
function normalizeArtifactPath(value: string): string {
|
|
585
|
+
return value.replace(/\\/g, "/");
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function pathWithin(root: string, candidate: string): boolean {
|
|
589
|
+
const rel = relative(root, candidate);
|
|
590
|
+
return rel.length === 0 || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function artifactPathForDb(basePath: string, absPath: string): string {
|
|
594
|
+
const projectionRoot = gsdProjectionRoot(basePath);
|
|
595
|
+
const root = pathWithin(projectionRoot, absPath) ? projectionRoot : gsdRoot(basePath);
|
|
596
|
+
return normalizeArtifactPath(relative(root, absPath));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function quarantineRelativePath(basePath: string, absPath: string): string {
|
|
600
|
+
for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
|
|
601
|
+
if (pathWithin(root, absPath)) {
|
|
602
|
+
return normalizeArtifactPath(relative(root, absPath));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return normalizeArtifactPath(absPath.replace(/^[/\\]+/, ""));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function uniquePath(path: string): string {
|
|
609
|
+
if (!existsSync(path)) return path;
|
|
610
|
+
let idx = 2;
|
|
611
|
+
while (existsSync(`${path}.${idx}`)) idx++;
|
|
612
|
+
return `${path}.${idx}`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function resolveDiskArtifactPath(basePath: string, artifactPath: string): string {
|
|
616
|
+
if (isAbsolute(artifactPath)) return artifactPath;
|
|
617
|
+
const candidates = [
|
|
618
|
+
join(gsdProjectionRoot(basePath), artifactPath),
|
|
619
|
+
join(gsdRoot(basePath), artifactPath),
|
|
620
|
+
];
|
|
621
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0]!;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function quarantineProjectionFile(basePath: string, absPath: string, stamp: string): string {
|
|
625
|
+
const rel = quarantineRelativePath(basePath, absPath);
|
|
626
|
+
const target = uniquePath(join(gsdProjectionRoot(basePath), "quarantine", "projections", stamp, rel));
|
|
627
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
628
|
+
renameSync(absPath, target);
|
|
629
|
+
return target;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
type RebuildTarget = "markdown" | "database" | "usage";
|
|
633
|
+
|
|
634
|
+
function parseRebuildTarget(args: string): RebuildTarget {
|
|
635
|
+
const trimmed = args.trim().toLowerCase();
|
|
636
|
+
if (!trimmed || trimmed === "markdown") return "markdown";
|
|
637
|
+
if (trimmed === "database" || trimmed === "db") return "database";
|
|
638
|
+
return "usage";
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* `gsd rebuild markdown` — Re-render markdown projections from the authoritative DB.
|
|
643
|
+
*
|
|
644
|
+
* This is the DB-first realignment command. It does not import markdown into
|
|
645
|
+
* the DB. Completion SUMMARY files that contradict open DB rows are preserved
|
|
646
|
+
* under `.gsd/quarantine/projections/` before DB projections are rendered.
|
|
647
|
+
*/
|
|
648
|
+
export async function handleRebuild(ctx: ExtensionCommandContext, basePath: string, args = ""): Promise<void> {
|
|
649
|
+
const { isDbAvailable: dbAvailable, deleteArtifactByPath } = await import("./gsd-db.js");
|
|
650
|
+
const { detectArtifactDbDrift } = await import("./state-reconciliation/drift/artifact-db.js");
|
|
651
|
+
const { renderAllFromDb } = await import("./markdown-renderer.js");
|
|
652
|
+
const { invalidateStateCache } = await import("./state.js");
|
|
653
|
+
|
|
654
|
+
const target = parseRebuildTarget(args);
|
|
655
|
+
if (target === "usage") {
|
|
656
|
+
ctx.ui.notify(
|
|
657
|
+
[
|
|
658
|
+
"Usage:",
|
|
659
|
+
" /gsd rebuild markdown Rebuild markdown projections from the canonical DB",
|
|
660
|
+
" /gsd rebuild database Reserved for DB-native rebuilds; does not import markdown",
|
|
661
|
+
].join("\n"),
|
|
662
|
+
"warning",
|
|
663
|
+
);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (target === "database") {
|
|
668
|
+
ctx.ui.notify(
|
|
669
|
+
[
|
|
670
|
+
"gsd rebuild database is reserved for DB-native rebuilds.",
|
|
671
|
+
"It will not import markdown projections into the DB.",
|
|
672
|
+
"For normal realignment, run /gsd rebuild markdown.",
|
|
673
|
+
"If the DB is lost or corrupt and markdown is the source to import, run /gsd recover --confirm.",
|
|
674
|
+
].join("\n"),
|
|
675
|
+
"warning",
|
|
676
|
+
);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (!dbAvailable()) {
|
|
681
|
+
ctx.ui.notify("gsd rebuild markdown: No database open. Run a GSD command first to initialize the DB.", "error");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
try {
|
|
686
|
+
invalidateStateCache();
|
|
687
|
+
const state = await deriveState(basePath);
|
|
688
|
+
const drifts = detectArtifactDbDrift(state, { basePath, state });
|
|
689
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
690
|
+
const quarantined: string[] = [];
|
|
691
|
+
const seen = new Set<string>();
|
|
692
|
+
|
|
693
|
+
for (const drift of drifts) {
|
|
694
|
+
if (drift.kind !== "artifact-db-status-divergence") continue;
|
|
695
|
+
if (drift.artifactType !== "SUMMARY" || !drift.artifactPath) continue;
|
|
696
|
+
const absPath = resolveDiskArtifactPath(basePath, drift.artifactPath);
|
|
697
|
+
if (seen.has(absPath) || !existsSync(absPath)) continue;
|
|
698
|
+
seen.add(absPath);
|
|
699
|
+
const artifactDbPath = artifactPathForDb(basePath, absPath);
|
|
700
|
+
const target = quarantineProjectionFile(basePath, absPath, stamp);
|
|
701
|
+
deleteArtifactByPath(artifactDbPath);
|
|
702
|
+
quarantined.push(target);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const rendered = await renderAllFromDb(basePath);
|
|
706
|
+
invalidateStateCache();
|
|
707
|
+
|
|
708
|
+
const lines = [
|
|
709
|
+
"gsd rebuild markdown: rebuilt markdown projections from the canonical DB",
|
|
710
|
+
` Rendered: ${rendered.rendered}`,
|
|
711
|
+
` Skipped: ${rendered.skipped}`,
|
|
712
|
+
` Quarantined: ${quarantined.length}`,
|
|
713
|
+
];
|
|
714
|
+
if (rendered.errors.length > 0) {
|
|
715
|
+
lines.push(` Errors: ${rendered.errors.length}`);
|
|
716
|
+
for (const err of rendered.errors.slice(0, 5)) {
|
|
717
|
+
lines.push(` - ${err}`);
|
|
718
|
+
}
|
|
719
|
+
if (rendered.errors.length > 5) {
|
|
720
|
+
lines.push(` - ${rendered.errors.length - 5} more`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (quarantined.length > 0) {
|
|
724
|
+
lines.push("", " Quarantine:");
|
|
725
|
+
for (const target of quarantined.slice(0, 5)) {
|
|
726
|
+
lines.push(` - ${target}`);
|
|
727
|
+
}
|
|
728
|
+
if (quarantined.length > 5) {
|
|
729
|
+
lines.push(` - ${quarantined.length - 5} more`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
ctx.ui.notify(lines.join("\n"), rendered.errors.length > 0 ? "warning" : "success");
|
|
734
|
+
} catch (err) {
|
|
735
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
736
|
+
logWarning("command", `rebuild failed: ${msg}`);
|
|
737
|
+
ctx.ui.notify(`gsd rebuild failed: ${msg}`, "error");
|
|
738
|
+
}
|
|
739
|
+
}
|