gsd-pi 2.66.1-dev.3c26b49 → 2.66.1-dev.3cea7ac
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/extensions/ask-user-questions.js +79 -11
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
- package/dist/resources/extensions/gsd/auto/loop.js +13 -1
- package/dist/resources/extensions/gsd/auto/phases.js +10 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto/session.js +1 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
- package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
- package/dist/resources/extensions/gsd/auto-prompts.js +6 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
- package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
- package/dist/resources/extensions/gsd/commands/handlers/core.js +3 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -4
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
- package/dist/resources/extensions/gsd/detection.js +6 -0
- package/dist/resources/extensions/gsd/files.js +19 -2
- package/dist/resources/extensions/gsd/guided-flow.js +12 -8
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
- package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
- package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
- package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
- package/dist/resources/extensions/remote-questions/manager.js +8 -0
- package/dist/resources/extensions/shared/interview-ui.js +10 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- 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/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- 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 +20 -20
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.js +11 -1
- package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
- package/packages/pi-ai/src/utils/json-parse.ts +11 -1
- package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
- package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
- package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +9 -7
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts +3 -1
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +14 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +6 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +8 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
- package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
- package/packages/pi-tui/src/autocomplete.ts +9 -7
- package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
- package/packages/pi-tui/src/components/editor.ts +14 -3
- package/packages/pi-tui/src/stdin-buffer.ts +7 -0
- package/packages/pi-tui/src/tui.ts +9 -0
- package/src/resources/extensions/ask-user-questions.ts +103 -11
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +10 -5
- package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
- package/src/resources/extensions/gsd/auto/session.ts +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
- package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
- package/src/resources/extensions/gsd/auto-prompts.ts +5 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
- package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -6
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +11 -4
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
- package/src/resources/extensions/gsd/detection.ts +6 -0
- package/src/resources/extensions/gsd/files.ts +21 -2
- package/src/resources/extensions/gsd/guided-flow.ts +15 -8
- package/src/resources/extensions/gsd/index.ts +6 -0
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
- package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
- package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
- package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
- package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
- package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -0
- package/src/resources/extensions/shared/interview-ui.ts +13 -0
- /package/dist/web/standalone/.next/static/{ZzNRjwBFLOhqEu4BYCQi9 → HxFcJ8GrYNPsg9ARz7GPz}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ZzNRjwBFLOhqEu4BYCQi9 → HxFcJ8GrYNPsg9ARz7GPz}/_ssgManifest.js +0 -0
|
@@ -3,7 +3,7 @@ import { isToolCallEventType } from "@gsd/pi-coding-agent";
|
|
|
3
3
|
import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
|
|
4
4
|
import { buildBeforeAgentStartResult } from "./system-context.js";
|
|
5
5
|
import { handleAgentEnd } from "./agent-end-recovery.js";
|
|
6
|
-
import { clearDiscussionFlowState,
|
|
6
|
+
import { clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
|
|
7
7
|
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
8
8
|
import { cleanupQuickBranch } from "../quick.js";
|
|
9
9
|
import { getDiscussionMilestoneId } from "../guided-flow.js";
|
|
@@ -21,6 +21,7 @@ import { logWarning as safetyLogWarning } from "../workflow-logger.js";
|
|
|
21
21
|
import { installNotifyInterceptor } from "./notify-interceptor.js";
|
|
22
22
|
import { initNotificationStore } from "../notification-store.js";
|
|
23
23
|
import { initNotificationWidget } from "../notification-widget.js";
|
|
24
|
+
import { initHealthWidget } from "../health-widget.js";
|
|
24
25
|
// Skip the welcome screen on the very first session_start — cli.ts already
|
|
25
26
|
// printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
|
|
26
27
|
let isFirstSession = true;
|
|
@@ -33,6 +34,7 @@ export function registerHooks(pi) {
|
|
|
33
34
|
initNotificationStore(process.cwd());
|
|
34
35
|
installNotifyInterceptor(ctx);
|
|
35
36
|
initNotificationWidget(ctx);
|
|
37
|
+
initHealthWidget(ctx);
|
|
36
38
|
resetWriteGateState();
|
|
37
39
|
resetToolCallLoopGuard();
|
|
38
40
|
resetAskUserQuestionsCache();
|
|
@@ -154,11 +156,42 @@ export function registerHooks(pi) {
|
|
|
154
156
|
}
|
|
155
157
|
});
|
|
156
158
|
pi.on("tool_call", async (event) => {
|
|
159
|
+
const discussionBasePath = process.cwd();
|
|
157
160
|
// ── Loop guard: block repeated identical tool calls ──
|
|
158
161
|
const loopCheck = checkToolCallLoop(event.toolName, event.input);
|
|
159
162
|
if (loopCheck.block) {
|
|
160
163
|
return { block: true, reason: loopCheck.reason };
|
|
161
164
|
}
|
|
165
|
+
// ── Discussion gate enforcement: track pending gate questions ─────────
|
|
166
|
+
// Only gate-shaped ask_user_questions calls should block execution.
|
|
167
|
+
// The gate stays pending until the user selects the approval option.
|
|
168
|
+
if (event.toolName === "ask_user_questions") {
|
|
169
|
+
const milestoneId = getDiscussionMilestoneId(discussionBasePath);
|
|
170
|
+
const inDiscussion = milestoneId !== null || isQueuePhaseActive();
|
|
171
|
+
if (inDiscussion) {
|
|
172
|
+
const questions = event.input?.questions ?? [];
|
|
173
|
+
const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
|
|
174
|
+
if (typeof questionId === "string") {
|
|
175
|
+
setPendingGate(questionId);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ── Discussion gate enforcement: block tool calls while gate is pending ──
|
|
180
|
+
// If ask_user_questions was called with a gate ID but hasn't been confirmed,
|
|
181
|
+
// block all non-read-only tool calls to prevent the model from skipping gates.
|
|
182
|
+
if (getPendingGate()) {
|
|
183
|
+
const milestoneId = getDiscussionMilestoneId(discussionBasePath);
|
|
184
|
+
if (isToolCallEventType("bash", event)) {
|
|
185
|
+
const bashGuard = shouldBlockPendingGateBash(event.input.command, milestoneId, isQueuePhaseActive());
|
|
186
|
+
if (bashGuard.block)
|
|
187
|
+
return bashGuard;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const gateGuard = shouldBlockPendingGate(event.toolName, milestoneId, isQueuePhaseActive());
|
|
191
|
+
if (gateGuard.block)
|
|
192
|
+
return gateGuard;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
162
195
|
// ── Queue-mode execution guard (#2545): block source-code mutations ──
|
|
163
196
|
// When /gsd queue is active, the agent should only create milestones,
|
|
164
197
|
// not execute work. Block write/edit to non-.gsd/ paths and bash commands
|
|
@@ -197,7 +230,7 @@ export function registerHooks(pi) {
|
|
|
197
230
|
}
|
|
198
231
|
if (!isToolCallEventType("write", event))
|
|
199
232
|
return;
|
|
200
|
-
const result = shouldBlockContextWrite(event.toolName, event.input.path, getDiscussionMilestoneId(),
|
|
233
|
+
const result = shouldBlockContextWrite(event.toolName, event.input.path, getDiscussionMilestoneId(discussionBasePath), isQueuePhaseActive());
|
|
201
234
|
if (result.block)
|
|
202
235
|
return result;
|
|
203
236
|
});
|
|
@@ -220,21 +253,42 @@ export function registerHooks(pi) {
|
|
|
220
253
|
pi.on("tool_result", async (event) => {
|
|
221
254
|
if (event.toolName !== "ask_user_questions")
|
|
222
255
|
return;
|
|
223
|
-
const milestoneId = getDiscussionMilestoneId();
|
|
256
|
+
const milestoneId = getDiscussionMilestoneId(process.cwd());
|
|
224
257
|
const queueActive = isQueuePhaseActive();
|
|
225
258
|
if (!milestoneId && !queueActive)
|
|
226
259
|
return;
|
|
227
260
|
const details = event.details;
|
|
261
|
+
// ── Discussion gate enforcement: handle gate question responses ──
|
|
262
|
+
// If the result is cancelled or has no response, the pending gate stays active
|
|
263
|
+
// so the model is blocked from non-read-only tools until it re-asks.
|
|
264
|
+
// If the user responded at all (even "needs adjustment"), clear the pending gate
|
|
265
|
+
// because the user engaged — the prompt handles the re-ask-after-adjustment flow.
|
|
266
|
+
const questions = event.input?.questions ?? [];
|
|
267
|
+
const currentPendingGate = getPendingGate();
|
|
268
|
+
if (currentPendingGate) {
|
|
269
|
+
if (details?.cancelled || !details?.response) {
|
|
270
|
+
// Gate stays pending — model will be blocked from non-read-only tools
|
|
271
|
+
// until it re-asks and gets a valid response
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
const pendingQuestion = questions.find((question) => question?.id === currentPendingGate);
|
|
275
|
+
if (pendingQuestion) {
|
|
276
|
+
const answer = details.response?.answers?.[currentPendingGate];
|
|
277
|
+
if (isDepthConfirmationAnswer(answer?.selected, pendingQuestion.options)) {
|
|
278
|
+
clearPendingGate();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
228
283
|
if (details?.cancelled || !details?.response)
|
|
229
284
|
return;
|
|
230
|
-
const questions = event.input?.questions ?? [];
|
|
231
285
|
for (const question of questions) {
|
|
232
286
|
if (typeof question.id === "string" && question.id.includes("depth_verification")) {
|
|
233
287
|
// Only unlock the gate if the user selected the first option (confirmation).
|
|
234
288
|
// Cross-references against the question's defined options to reject free-form "Other" text.
|
|
235
289
|
const answer = details.response?.answers?.[question.id];
|
|
236
290
|
if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
|
|
237
|
-
markDepthVerified();
|
|
291
|
+
markDepthVerified(extractDepthVerificationMilestoneId(question.id) ?? milestoneId);
|
|
238
292
|
}
|
|
239
293
|
break;
|
|
240
294
|
}
|
|
@@ -4,16 +4,18 @@ import { Key } from "@gsd/pi-tui";
|
|
|
4
4
|
import { GSDDashboardOverlay } from "../dashboard-overlay.js";
|
|
5
5
|
import { GSDNotificationOverlay } from "../notification-overlay.js";
|
|
6
6
|
import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
|
|
7
|
+
import { projectRoot } from "../commands/context.js";
|
|
7
8
|
import { shortcutDesc } from "../../shared/mod.js";
|
|
8
9
|
export function registerShortcuts(pi) {
|
|
9
10
|
pi.registerShortcut(Key.ctrlAlt("g"), {
|
|
10
11
|
description: shortcutDesc("Open GSD dashboard", "/gsd status"),
|
|
11
12
|
handler: async (ctx) => {
|
|
12
|
-
|
|
13
|
+
const basePath = projectRoot();
|
|
14
|
+
if (!existsSync(join(basePath, ".gsd"))) {
|
|
13
15
|
ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
|
|
14
16
|
return;
|
|
15
17
|
}
|
|
16
|
-
await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done()), {
|
|
18
|
+
await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)), {
|
|
17
19
|
overlay: true,
|
|
18
20
|
overlayOptions: {
|
|
19
21
|
width: "90%",
|
|
@@ -27,7 +29,7 @@ export function registerShortcuts(pi) {
|
|
|
27
29
|
pi.registerShortcut(Key.ctrlAlt("n"), {
|
|
28
30
|
description: shortcutDesc("Open notification history", "/gsd notifications"),
|
|
29
31
|
handler: async (ctx) => {
|
|
30
|
-
await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()), {
|
|
32
|
+
await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)), {
|
|
31
33
|
overlay: true,
|
|
32
34
|
overlayOptions: {
|
|
33
35
|
width: "80%",
|
|
@@ -42,12 +44,13 @@ export function registerShortcuts(pi) {
|
|
|
42
44
|
pi.registerShortcut(Key.ctrlAlt("p"), {
|
|
43
45
|
description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
|
|
44
46
|
handler: async (ctx) => {
|
|
45
|
-
const
|
|
47
|
+
const basePath = projectRoot();
|
|
48
|
+
const parallelDir = join(basePath, ".gsd", "parallel");
|
|
46
49
|
if (!existsSync(parallelDir)) {
|
|
47
50
|
ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
50
|
-
await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done()), {
|
|
53
|
+
await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true)), {
|
|
51
54
|
overlay: true,
|
|
52
55
|
overlayOptions: {
|
|
53
56
|
width: "90%",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
|
|
2
|
+
const CONTEXT_MILESTONE_RE = /(?:^|[/\\])(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/i;
|
|
3
|
+
const DEPTH_VERIFICATION_MILESTONE_RE = /depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i;
|
|
2
4
|
/**
|
|
3
5
|
* Path segment that identifies .gsd/ planning artifacts.
|
|
4
6
|
* Writes to these paths are allowed during queue mode.
|
|
@@ -22,10 +24,49 @@ const QUEUE_SAFE_TOOLS = new Set([
|
|
|
22
24
|
* Matches the leading command in a bash invocation.
|
|
23
25
|
*/
|
|
24
26
|
const BASH_READ_ONLY_RE = /^\s*(cat|head|tail|less|more|wc|file|stat|du|df|which|type|echo|printf|ls|find|grep|rg|awk|sed\b(?!.*-i)|sort|uniq|diff|comm|tr|cut|tee\s+-a\s+\/dev\/null|git\s+(log|show|diff|status|branch|tag|remote|rev-parse|ls-files|blame|shortlog|describe|stash\s+list|config\s+--get|cat-file)|gh\s+(issue|pr|api|repo|release)\s+(view|list|diff|status|checks)|mkdir\s+-p\s+\.gsd|rtk\s)/;
|
|
25
|
-
|
|
27
|
+
const verifiedDepthMilestones = new Set();
|
|
26
28
|
let activeQueuePhase = false;
|
|
29
|
+
/**
|
|
30
|
+
* Discussion gate enforcement state.
|
|
31
|
+
*
|
|
32
|
+
* When ask_user_questions is called with a recognized gate question ID,
|
|
33
|
+
* we track the pending gate. Until the gate is confirmed (user selects the
|
|
34
|
+
* first/recommended option), all non-read-only tool calls are blocked.
|
|
35
|
+
* This mechanically prevents the model from rationalizing past failed or
|
|
36
|
+
* cancelled gate questions.
|
|
37
|
+
*/
|
|
38
|
+
let pendingGateId = null;
|
|
39
|
+
/**
|
|
40
|
+
* Recognized gate question ID patterns.
|
|
41
|
+
* These appear in both discuss-prepared.md (4-layer) and discuss.md (depth/requirements/roadmap).
|
|
42
|
+
*/
|
|
43
|
+
const GATE_QUESTION_PATTERNS = [
|
|
44
|
+
"layer1_scope_gate",
|
|
45
|
+
"layer2_architecture_gate",
|
|
46
|
+
"layer3_error_gate",
|
|
47
|
+
"layer4_quality_gate",
|
|
48
|
+
"depth_verification",
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Tools that are safe to call while a gate is pending.
|
|
52
|
+
* Includes read-only tools and ask_user_questions itself (so the model can re-ask).
|
|
53
|
+
*/
|
|
54
|
+
const GATE_SAFE_TOOLS = new Set([
|
|
55
|
+
"ask_user_questions",
|
|
56
|
+
"read", "grep", "find", "ls", "glob",
|
|
57
|
+
"search-the-web", "resolve_library", "get_library_docs", "fetch_page",
|
|
58
|
+
"search_and_read",
|
|
59
|
+
]);
|
|
27
60
|
export function isDepthVerified() {
|
|
28
|
-
return
|
|
61
|
+
return verifiedDepthMilestones.size > 0;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check whether a specific milestone has passed depth verification.
|
|
65
|
+
*/
|
|
66
|
+
export function isMilestoneDepthVerified(milestoneId) {
|
|
67
|
+
if (!milestoneId)
|
|
68
|
+
return false;
|
|
69
|
+
return verifiedDepthMilestones.has(milestoneId);
|
|
29
70
|
}
|
|
30
71
|
export function isQueuePhaseActive() {
|
|
31
72
|
return activeQueuePhase;
|
|
@@ -34,14 +75,103 @@ export function setQueuePhaseActive(active) {
|
|
|
34
75
|
activeQueuePhase = active;
|
|
35
76
|
}
|
|
36
77
|
export function resetWriteGateState() {
|
|
37
|
-
|
|
78
|
+
verifiedDepthMilestones.clear();
|
|
79
|
+
pendingGateId = null;
|
|
38
80
|
}
|
|
39
81
|
export function clearDiscussionFlowState() {
|
|
40
|
-
|
|
82
|
+
verifiedDepthMilestones.clear();
|
|
41
83
|
activeQueuePhase = false;
|
|
84
|
+
pendingGateId = null;
|
|
85
|
+
}
|
|
86
|
+
export function markDepthVerified(milestoneId) {
|
|
87
|
+
if (!milestoneId)
|
|
88
|
+
return;
|
|
89
|
+
verifiedDepthMilestones.add(milestoneId);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check whether a question ID matches a recognized gate pattern.
|
|
93
|
+
*/
|
|
94
|
+
export function isGateQuestionId(questionId) {
|
|
95
|
+
return GATE_QUESTION_PATTERNS.some(pattern => questionId.includes(pattern));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Extract the milestone ID embedded in a depth-verification question id.
|
|
99
|
+
* Prompts are expected to use ids like `depth_verification_M001_confirm`.
|
|
100
|
+
*/
|
|
101
|
+
export function extractDepthVerificationMilestoneId(questionId) {
|
|
102
|
+
const match = questionId.match(DEPTH_VERIFICATION_MILESTONE_RE);
|
|
103
|
+
return match?.[1] ?? null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Extract the milestone ID from a milestone CONTEXT file path.
|
|
107
|
+
*/
|
|
108
|
+
function extractContextMilestoneId(inputPath) {
|
|
109
|
+
const match = inputPath.match(CONTEXT_MILESTONE_RE);
|
|
110
|
+
return match?.[1] ?? null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Mark a gate as pending (called when ask_user_questions is invoked with a gate ID).
|
|
114
|
+
*/
|
|
115
|
+
export function setPendingGate(gateId) {
|
|
116
|
+
pendingGateId = gateId;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Clear the pending gate (called when the user confirms).
|
|
120
|
+
*/
|
|
121
|
+
export function clearPendingGate() {
|
|
122
|
+
pendingGateId = null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the currently pending gate, if any.
|
|
126
|
+
*/
|
|
127
|
+
export function getPendingGate() {
|
|
128
|
+
return pendingGateId;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check whether a tool call should be blocked because a discussion gate
|
|
132
|
+
* is pending (ask_user_questions was called but not confirmed).
|
|
133
|
+
*
|
|
134
|
+
* Returns { block: true, reason } if the tool should be blocked.
|
|
135
|
+
* Read-only tools and ask_user_questions itself are always allowed.
|
|
136
|
+
*/
|
|
137
|
+
export function shouldBlockPendingGate(toolName, _milestoneId, _queuePhaseActive) {
|
|
138
|
+
if (!pendingGateId)
|
|
139
|
+
return { block: false };
|
|
140
|
+
if (GATE_SAFE_TOOLS.has(toolName))
|
|
141
|
+
return { block: false };
|
|
142
|
+
// Bash read-only commands are also safe
|
|
143
|
+
if (toolName === "bash")
|
|
144
|
+
return { block: false }; // bash is checked separately below
|
|
145
|
+
return {
|
|
146
|
+
block: true,
|
|
147
|
+
reason: [
|
|
148
|
+
`HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
|
|
149
|
+
`You MUST re-call ask_user_questions with the gate question before making any other tool calls.`,
|
|
150
|
+
`If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
|
|
151
|
+
`did not match a provided option, you MUST re-ask — never rationalize past the block.`,
|
|
152
|
+
`Do NOT proceed, do NOT use alternative approaches, do NOT skip the gate.`,
|
|
153
|
+
].join(" "),
|
|
154
|
+
};
|
|
42
155
|
}
|
|
43
|
-
|
|
44
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Check whether a bash command should be blocked because a discussion gate is pending.
|
|
158
|
+
* Read-only bash commands are allowed; mutating commands are blocked.
|
|
159
|
+
*/
|
|
160
|
+
export function shouldBlockPendingGateBash(command, _milestoneId, _queuePhaseActive) {
|
|
161
|
+
if (!pendingGateId)
|
|
162
|
+
return { block: false };
|
|
163
|
+
// Allow read-only bash commands
|
|
164
|
+
if (BASH_READ_ONLY_RE.test(command))
|
|
165
|
+
return { block: false };
|
|
166
|
+
return {
|
|
167
|
+
block: true,
|
|
168
|
+
reason: [
|
|
169
|
+
`HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
|
|
170
|
+
`You MUST re-call ask_user_questions with the gate question before running mutating commands.`,
|
|
171
|
+
`If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
|
|
172
|
+
`did not match a provided option, you MUST re-ask — never rationalize past the block.`,
|
|
173
|
+
].join(" "),
|
|
174
|
+
};
|
|
45
175
|
}
|
|
46
176
|
/**
|
|
47
177
|
* Check whether a depth_verification answer confirms the discussion is complete.
|
|
@@ -67,16 +197,23 @@ export function isDepthConfirmationAnswer(selected, options) {
|
|
|
67
197
|
// accept only if it contains "(Recommended)" — the prompt convention suffix.
|
|
68
198
|
return value.includes("(Recommended)");
|
|
69
199
|
}
|
|
70
|
-
export function shouldBlockContextWrite(toolName, inputPath, milestoneId,
|
|
200
|
+
export function shouldBlockContextWrite(toolName, inputPath, milestoneId, _queuePhaseActive) {
|
|
71
201
|
if (toolName !== "write")
|
|
72
202
|
return { block: false };
|
|
73
|
-
const inDiscussion = milestoneId !== null;
|
|
74
|
-
const inQueue = queuePhaseActive ?? false;
|
|
75
|
-
if (!inDiscussion && !inQueue)
|
|
76
|
-
return { block: false };
|
|
77
203
|
if (!MILESTONE_CONTEXT_RE.test(inputPath))
|
|
78
204
|
return { block: false };
|
|
79
|
-
|
|
205
|
+
const targetMilestoneId = extractContextMilestoneId(inputPath) ?? milestoneId;
|
|
206
|
+
if (!targetMilestoneId) {
|
|
207
|
+
return {
|
|
208
|
+
block: true,
|
|
209
|
+
reason: [
|
|
210
|
+
`HARD BLOCK: Cannot write milestone CONTEXT.md without knowing which milestone it belongs to.`,
|
|
211
|
+
`This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
|
|
212
|
+
`Required action: call ask_user_questions with question id containing "depth_verification" and the milestone id.`,
|
|
213
|
+
].join(" "),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (isMilestoneDepthVerified(targetMilestoneId))
|
|
80
217
|
return { block: false };
|
|
81
218
|
return {
|
|
82
219
|
block: true,
|
|
@@ -89,6 +226,37 @@ export function shouldBlockContextWrite(toolName, inputPath, milestoneId, depthV
|
|
|
89
226
|
].join(" "),
|
|
90
227
|
};
|
|
91
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Check whether a gsd_summary_save CONTEXT artifact should be blocked.
|
|
231
|
+
* Slice-level CONTEXT artifacts are allowed; milestone-level CONTEXT writes
|
|
232
|
+
* require the milestone to be depth-verified first.
|
|
233
|
+
*/
|
|
234
|
+
export function shouldBlockContextArtifactSave(artifactType, milestoneId, sliceId) {
|
|
235
|
+
if (artifactType !== "CONTEXT")
|
|
236
|
+
return { block: false };
|
|
237
|
+
if (sliceId)
|
|
238
|
+
return { block: false };
|
|
239
|
+
if (!milestoneId) {
|
|
240
|
+
return {
|
|
241
|
+
block: true,
|
|
242
|
+
reason: [
|
|
243
|
+
`HARD BLOCK: Cannot save milestone CONTEXT without a milestone_id.`,
|
|
244
|
+
`This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
|
|
245
|
+
].join(" "),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (isMilestoneDepthVerified(milestoneId))
|
|
249
|
+
return { block: false };
|
|
250
|
+
return {
|
|
251
|
+
block: true,
|
|
252
|
+
reason: [
|
|
253
|
+
`HARD BLOCK: Cannot save milestone CONTEXT without depth verification for ${milestoneId}.`,
|
|
254
|
+
`This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
|
|
255
|
+
`Required action: call ask_user_questions with question id containing "depth_verification_${milestoneId}".`,
|
|
256
|
+
`The user MUST select the "(Recommended)" confirmation option to unlock this gate.`,
|
|
257
|
+
].join(" "),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
92
260
|
/**
|
|
93
261
|
* Queue-mode execution guard (#2545).
|
|
94
262
|
*
|
|
@@ -130,6 +298,10 @@ export function shouldBlockQueueExecution(toolName, input, queuePhaseActive) {
|
|
|
130
298
|
`Use read-only commands (cat, grep, git log, etc.) to investigate, then write planning artifacts.`,
|
|
131
299
|
};
|
|
132
300
|
}
|
|
133
|
-
// Unknown tools —
|
|
134
|
-
|
|
301
|
+
// Unknown tools — block by default in queue mode so custom tools cannot
|
|
302
|
+
// bypass execution restrictions.
|
|
303
|
+
return {
|
|
304
|
+
block: true,
|
|
305
|
+
reason: `Blocked: /gsd queue is a planning tool — it creates milestones, not executes work. Unknown tools are not permitted during queue mode.`,
|
|
306
|
+
};
|
|
135
307
|
}
|
|
@@ -77,7 +77,7 @@ export async function handleStatus(ctx) {
|
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
const { GSDDashboardOverlay } = await import("../../dashboard-overlay.js");
|
|
80
|
-
const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done()), {
|
|
80
|
+
const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)), {
|
|
81
81
|
overlay: true,
|
|
82
82
|
overlayOptions: {
|
|
83
83
|
width: "70%",
|
|
@@ -99,7 +99,7 @@ export async function handleVisualize(ctx) {
|
|
|
99
99
|
return;
|
|
100
100
|
}
|
|
101
101
|
const { GSDVisualizerOverlay } = await import("../../visualizer-overlay.js");
|
|
102
|
-
const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDVisualizerOverlay(tui, theme, () => done()), {
|
|
102
|
+
const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDVisualizerOverlay(tui, theme, () => done(true)), {
|
|
103
103
|
overlay: true,
|
|
104
104
|
overlayOptions: {
|
|
105
105
|
width: "80%",
|
|
@@ -195,7 +195,7 @@ export async function handleCoreCommand(trimmed, ctx) {
|
|
|
195
195
|
}
|
|
196
196
|
if (trimmed === "show-config") {
|
|
197
197
|
const { GSDConfigOverlay, formatConfigText } = await import("../../config-overlay.js");
|
|
198
|
-
const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDConfigOverlay(tui, theme, () => done()), {
|
|
198
|
+
const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDConfigOverlay(tui, theme, () => done(true)), {
|
|
199
199
|
overlay: true,
|
|
200
200
|
overlayOptions: {
|
|
201
201
|
width: "65%",
|
|
@@ -240,10 +240,16 @@ async function configureModels(ctx, prefs) {
|
|
|
240
240
|
for (const group of byProvider.values()) {
|
|
241
241
|
group.sort((a, b) => a.id.localeCompare(b.id));
|
|
242
242
|
}
|
|
243
|
-
//
|
|
243
|
+
// Display names for providers in the preferences wizard UI.
|
|
244
|
+
const PROVIDER_DISPLAY_NAMES = { anthropic: "anthropic-api" };
|
|
245
|
+
const displayName = (p) => PROVIDER_DISPLAY_NAMES[p] ?? p;
|
|
246
|
+
// Build provider menu with model counts (display name → real name lookup)
|
|
247
|
+
const displayToReal = new Map();
|
|
244
248
|
const providerOptions = providers.map(p => {
|
|
245
249
|
const count = byProvider.get(p).length;
|
|
246
|
-
|
|
250
|
+
const label = `${displayName(p)} (${count} models)`;
|
|
251
|
+
displayToReal.set(label, p);
|
|
252
|
+
return label;
|
|
247
253
|
});
|
|
248
254
|
providerOptions.push("(keep current)", "(clear)", "(type manually)");
|
|
249
255
|
for (const phase of modelPhases) {
|
|
@@ -267,13 +273,13 @@ async function configureModels(ctx, prefs) {
|
|
|
267
273
|
continue;
|
|
268
274
|
}
|
|
269
275
|
// Step 2: pick model within provider
|
|
270
|
-
const providerName = providerChoice.replace(/ \(\d+ models?\)$/, "");
|
|
276
|
+
const providerName = displayToReal.get(providerChoice) ?? providerChoice.replace(/ \(\d+ models?\)$/, "");
|
|
271
277
|
const group = byProvider.get(providerName);
|
|
272
278
|
if (!group)
|
|
273
279
|
continue;
|
|
274
280
|
const modelOptions = group.map(m => m.id);
|
|
275
281
|
modelOptions.push("(keep current)", "(clear)");
|
|
276
|
-
const modelChoice = await ctx.ui.select(`${phaseLabel} — ${providerName}:`, modelOptions);
|
|
282
|
+
const modelChoice = await ctx.ui.select(`${phaseLabel} — ${displayName(providerName)}:`, modelOptions);
|
|
277
283
|
if (modelChoice && typeof modelChoice === "string" && modelChoice !== "(keep current)") {
|
|
278
284
|
if (modelChoice === "(clear)") {
|
|
279
285
|
delete models[phase];
|
|
@@ -135,7 +135,9 @@ export class CustomWorkflowEngine {
|
|
|
135
135
|
* Returns "milestone-complete" when all steps are now done, "continue" otherwise.
|
|
136
136
|
*/
|
|
137
137
|
async reconcile(state, completedStep) {
|
|
138
|
-
|
|
138
|
+
// Re-read the graph from disk so we do not overwrite concurrent
|
|
139
|
+
// workflow edits with a stale in-memory snapshot from deriveState().
|
|
140
|
+
const graph = readGraph(this.runDir);
|
|
139
141
|
// Extract stepId from "<workflowName>/<stepId>"
|
|
140
142
|
const { milestone, slice, task } = parseUnitId(completedStep.unitId);
|
|
141
143
|
const stepId = task ?? slice ?? milestone;
|
|
@@ -170,6 +170,12 @@ const TEST_MARKERS = [
|
|
|
170
170
|
/** Directories skipped during bounded recursive project scans. */
|
|
171
171
|
const RECURSIVE_SCAN_IGNORED_DIRS = new Set([
|
|
172
172
|
".git",
|
|
173
|
+
".gsd",
|
|
174
|
+
".planning",
|
|
175
|
+
".plans",
|
|
176
|
+
".claude",
|
|
177
|
+
".cursor",
|
|
178
|
+
".vscode",
|
|
173
179
|
"node_modules",
|
|
174
180
|
".venv",
|
|
175
181
|
"venv",
|
|
@@ -104,6 +104,23 @@ export function extractAllSections(body, level = 2) {
|
|
|
104
104
|
function escapeRegex(s) {
|
|
105
105
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Normalize a task-plan file reference that may include inline description text
|
|
109
|
+
* after the path, for example:
|
|
110
|
+
* "docs/file.md — explanation"
|
|
111
|
+
* "docs/file.md - explanation"
|
|
112
|
+
*/
|
|
113
|
+
export function normalizePlannedFileReference(value) {
|
|
114
|
+
const trimmed = value.trim().replace(/`/g, "");
|
|
115
|
+
const match = /^(.*?)(?:\s+(?:—|-)\s+)(.+)$/.exec(trimmed);
|
|
116
|
+
if (!match)
|
|
117
|
+
return trimmed;
|
|
118
|
+
const pathCandidate = match[1].trim();
|
|
119
|
+
if (pathCandidate.includes("/") || pathCandidate.includes("\\") || pathCandidate.includes(".")) {
|
|
120
|
+
return pathCandidate;
|
|
121
|
+
}
|
|
122
|
+
return trimmed;
|
|
123
|
+
}
|
|
107
124
|
/** Parse bullet list items from a text block. */
|
|
108
125
|
export function parseBullets(text) {
|
|
109
126
|
return text.split('\n')
|
|
@@ -540,11 +557,11 @@ export function parseTaskPlanIO(content) {
|
|
|
540
557
|
let match;
|
|
541
558
|
backtickPathRegex.lastIndex = 0;
|
|
542
559
|
while ((match = backtickPathRegex.exec(trimmed)) !== null) {
|
|
543
|
-
const candidate = match[1];
|
|
560
|
+
const candidate = normalizePlannedFileReference(match[1]);
|
|
544
561
|
// Filter out things that look like code tokens rather than file paths
|
|
545
562
|
// (e.g. `true`, `false`, `npm run test`). A file path has at least one
|
|
546
563
|
// dot or slash.
|
|
547
|
-
if (candidate.includes("/") || candidate.includes(".")) {
|
|
564
|
+
if (candidate.includes("/") || candidate.includes("\\") || candidate.includes(".")) {
|
|
548
565
|
paths.push(candidate);
|
|
549
566
|
}
|
|
550
567
|
}
|
|
@@ -142,12 +142,13 @@ export function checkAutoStartAfterDiscuss() {
|
|
|
142
142
|
// Parse PROJECT.md for milestone sequence, warn if any are missing context.
|
|
143
143
|
// Don't block — milestones can be intentionally queued without context.
|
|
144
144
|
const projectFile = resolveGsdRootFile(basePath, "PROJECT");
|
|
145
|
+
let projectIds = [];
|
|
145
146
|
if (projectFile) {
|
|
146
147
|
try {
|
|
147
148
|
const projectContent = readFileSync(projectFile, "utf-8");
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
const missing =
|
|
149
|
+
projectIds = parseMilestoneSequenceFromProject(projectContent);
|
|
150
|
+
if (projectIds.length > 1) {
|
|
151
|
+
const missing = projectIds.filter(id => {
|
|
151
152
|
const hasContext = !!resolveMilestoneFile(basePath, id, "CONTEXT");
|
|
152
153
|
const hasDraft = !!resolveMilestoneFile(basePath, id, "CONTEXT-DRAFT");
|
|
153
154
|
const hasDir = existsSync(join(gsdRoot(basePath), "milestones", id));
|
|
@@ -165,9 +166,14 @@ export function checkAutoStartAfterDiscuss() {
|
|
|
165
166
|
}
|
|
166
167
|
// Gate 4: Discussion manifest process verification (multi-milestone only)
|
|
167
168
|
// The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
|
|
168
|
-
// If the
|
|
169
|
-
//
|
|
169
|
+
// If the project is multi-milestone, the manifest is required. When it is
|
|
170
|
+
// missing, fail closed instead of assuming the discussion finished.
|
|
170
171
|
const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
|
|
172
|
+
const requiresManifest = projectIds.length > 1 || findMilestoneIds(basePath).length > 1;
|
|
173
|
+
if (requiresManifest && !existsSync(manifestPath)) {
|
|
174
|
+
ctx.ui.notify("Multi-milestone discussion manifest is missing. Auto-start will remain paused until the manifest is written.", "warning");
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
171
177
|
if (existsSync(manifestPath)) {
|
|
172
178
|
try {
|
|
173
179
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -178,9 +184,7 @@ export function checkAutoStartAfterDiscuss() {
|
|
|
178
184
|
return false;
|
|
179
185
|
}
|
|
180
186
|
// Cross-check manifest milestones against PROJECT.md if available
|
|
181
|
-
if (
|
|
182
|
-
const projectContent = readFileSync(projectFile, "utf-8");
|
|
183
|
-
const projectIds = parseMilestoneSequenceFromProject(projectContent);
|
|
187
|
+
if (projectIds.length > 0) {
|
|
184
188
|
const manifestIds = Object.keys(manifest.milestones ?? {});
|
|
185
189
|
const untracked = projectIds.filter(id => !manifestIds.includes(id));
|
|
186
190
|
if (untracked.length > 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { isDepthConfirmationAnswer, isDepthVerified, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockQueueExecution, } from "./bootstrap/write-gate.js";
|
|
1
|
+
export { isDepthConfirmationAnswer, isDepthVerified, isGateQuestionId, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockPendingGate, shouldBlockPendingGateBash, shouldBlockQueueExecution, setPendingGate, clearPendingGate, getPendingGate, } from "./bootstrap/write-gate.js";
|
|
2
2
|
export default async function registerExtension(pi) {
|
|
3
3
|
const { registerGsdExtension } = await import("./bootstrap/register-extension.js");
|
|
4
4
|
registerGsdExtension(pi);
|
|
@@ -406,6 +406,8 @@ export class ParallelMonitorOverlay {
|
|
|
406
406
|
lines.push(t.fg("muted", " ESC/q to close │ ↑↓ scroll"));
|
|
407
407
|
// Apply scroll — use terminal rows as height estimate
|
|
408
408
|
const termHeight = process.stdout.rows || 40;
|
|
409
|
+
const maxScroll = Math.max(0, lines.length - termHeight);
|
|
410
|
+
this.scrollOffset = Math.min(Math.max(this.scrollOffset, 0), maxScroll);
|
|
409
411
|
const visible = lines.slice(this.scrollOffset, this.scrollOffset + termHeight);
|
|
410
412
|
this.cachedLines = visible;
|
|
411
413
|
return visible;
|
|
@@ -177,7 +177,9 @@ function _parsePlanImpl(content) {
|
|
|
177
177
|
for (const line of lines) {
|
|
178
178
|
const cbMatch = line.match(/^-\s+\[([ xX])\]\s+\*\*([\w.]+):\s+(.+?)\*\*\s*(.*)/);
|
|
179
179
|
// Heading-style: ### T01 -- Title, ### T01: Title, ### T01 — Title
|
|
180
|
-
const hdMatch = !cbMatch
|
|
180
|
+
const hdMatch = !cbMatch
|
|
181
|
+
? line.match(/^#{2,4}\s+([A-Z]+\d+(?:\.[A-Z]+\d+)*)\s*(?:--|—|:)\s*(.+)/)
|
|
182
|
+
: null;
|
|
181
183
|
if (cbMatch || hdMatch) {
|
|
182
184
|
const taskId = cbMatch ? cbMatch[2] : hdMatch[1];
|
|
183
185
|
// Skip tasks already found in the Tasks section
|
|
@@ -63,6 +63,6 @@ Then:
|
|
|
63
63
|
13. Do not commit manually — the system auto-commits your changes after this unit completes.
|
|
64
64
|
- Say: "Milestone {{milestoneId}} complete."
|
|
65
65
|
|
|
66
|
-
**Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success. Verification failures BLOCK completion — there is no override. The milestone stays in its current state until issues are resolved and verification is re-run.
|
|
66
|
+
**Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success. Verification failures BLOCK completion — there is no override. The milestone stays in its current state until issues are resolved and verification is re-run. **If a verification tool itself fails, errors, or returns unexpected output, treat it as a verification failure** — never rationalize past a tool error ("tool didn't respond, assuming success" is forbidden). A tool that cannot verify is a tool that did not verify.
|
|
67
67
|
|
|
68
68
|
**File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|