@opengsd/gsd-pi 1.1.1-dev.9bb7453 → 1.1.1-dev.9f86580
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/browser-tools/engine/managed-gsd-browser.js +18 -2
- package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
- package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/dist/resources/extensions/browser-tools/index.js +29 -2
- package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
- package/dist/resources/extensions/gsd/auto/phases.js +45 -3
- package/dist/resources/extensions/gsd/auto/session.js +2 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +21 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +26 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +4 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
- package/dist/resources/extensions/gsd/auto-timers.js +24 -10
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
- package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
- package/dist/resources/extensions/gsd/auto.js +26 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +16 -10
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +48 -29
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
- package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +1 -0
- package/dist/resources/extensions/gsd/context-masker.js +129 -5
- package/dist/resources/extensions/gsd/guided-flow.js +93 -108
- package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
- package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
- package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
- package/dist/resources/extensions/gsd/preferences-models.js +1 -0
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +5 -19
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
- package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
- package/dist/resources/extensions/gsd/tool-contract.js +6 -1
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +47 -7
- package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +113 -8
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +193 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -78
- package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
- package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- 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 +5 -5
- 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 +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -3
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +3 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +157 -18
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +159 -36
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/handoff.js +16 -3
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
- package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
- package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +36 -5
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
- package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
- package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
- package/src/resources/extensions/gsd/auto/phases.ts +48 -6
- package/src/resources/extensions/gsd/auto/session.ts +2 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +26 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +4 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
- package/src/resources/extensions/gsd/auto-timers.ts +25 -9
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
- package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
- package/src/resources/extensions/gsd/auto.ts +28 -4
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +16 -10
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +63 -29
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
- package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +1 -0
- package/src/resources/extensions/gsd/context-masker.ts +152 -5
- package/src/resources/extensions/gsd/guided-flow.ts +128 -135
- package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
- package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
- package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
- package/src/resources/extensions/gsd/preferences-models.ts +1 -0
- package/src/resources/extensions/gsd/preferences-types.ts +8 -0
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +5 -19
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
- package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +147 -5
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +409 -0
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
- package/src/resources/extensions/gsd/tool-contract.ts +7 -1
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +82 -7
- package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +146 -9
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +210 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -78
- package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
- package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
- /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_ssgManifest.js +0 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createObservationMask,
|
|
6
|
+
createResponsesInputObservationMask,
|
|
7
|
+
truncateContextResultMessages,
|
|
8
|
+
truncateResponsesInputResultItems,
|
|
9
|
+
} from "../context-masker.js";
|
|
5
10
|
|
|
6
11
|
// These helpers produce messages in the pi-ai LLM payload format
|
|
7
12
|
// (post-convertToLlm, pre-provider), which is what before_provider_request sees.
|
|
@@ -120,3 +125,53 @@ test("masks toolResult by role, not by type field", () => {
|
|
|
120
125
|
const result = mask(messages as any);
|
|
121
126
|
assert.equal((result[1].content as any)[0].text, MASK_TEXT);
|
|
122
127
|
});
|
|
128
|
+
|
|
129
|
+
test("truncates recent bash result user messages", () => {
|
|
130
|
+
const messages = [
|
|
131
|
+
userMsg("turn 1"),
|
|
132
|
+
bashResult("a".repeat(50)),
|
|
133
|
+
assistantMsg("response 1"),
|
|
134
|
+
];
|
|
135
|
+
const result = truncateContextResultMessages(messages as any, 10);
|
|
136
|
+
const text = (result[1].content as any)[0].text;
|
|
137
|
+
assert.ok(text.length < (messages[1].content as any)[0].text.length);
|
|
138
|
+
assert.match(text, /…\[truncated\]/);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("masks Responses API function outputs older than keepRecentTurns", () => {
|
|
142
|
+
const mask = createResponsesInputObservationMask(1);
|
|
143
|
+
const items = [
|
|
144
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 1" }] },
|
|
145
|
+
{ type: "function_call_output", call_id: "call_1", output: "old output" },
|
|
146
|
+
{ type: "message", role: "assistant", content: [{ type: "output_text", text: "response 1" }] },
|
|
147
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 2" }] },
|
|
148
|
+
];
|
|
149
|
+
const result = mask(items as any);
|
|
150
|
+
assert.equal(result[1].output, MASK_TEXT);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("masks Responses API bash result user items older than keepRecentTurns", () => {
|
|
154
|
+
const mask = createResponsesInputObservationMask(1);
|
|
155
|
+
const items = [
|
|
156
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 1" }] },
|
|
157
|
+
{ role: "user", content: [{ type: "input_text", text: "Ran `npm test`\n```\nold output\n```" }] },
|
|
158
|
+
{ type: "message", role: "assistant", content: [{ type: "output_text", text: "response 1" }] },
|
|
159
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 2" }] },
|
|
160
|
+
];
|
|
161
|
+
const result = mask(items as any);
|
|
162
|
+
assert.equal((result[1].content as any)[0].text, MASK_TEXT);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("truncates Responses API function outputs and recent bash result items", () => {
|
|
166
|
+
const items = [
|
|
167
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 1" }] },
|
|
168
|
+
{ type: "function_call_output", call_id: "call_1", output: "b".repeat(50) },
|
|
169
|
+
{ role: "user", content: [{ type: "input_text", text: "Ran `npm test`\n```\n" + "c".repeat(50) + "\n```" }] },
|
|
170
|
+
];
|
|
171
|
+
const result = truncateResponsesInputResultItems(items as any, 12);
|
|
172
|
+
|
|
173
|
+
assert.match(result[1].output as string, /…\[truncated\]/);
|
|
174
|
+
assert.match((result[2].content as any)[0].text, /…\[truncated\]/);
|
|
175
|
+
assert.ok((result[1].output as string).length < (items[1].output as string).length);
|
|
176
|
+
assert.ok((result[2].content as any)[0].text.length < (items[2].content as any)[0].text.length);
|
|
177
|
+
});
|
|
@@ -129,6 +129,7 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
|
|
|
129
129
|
unitLifetimeDispatches: new Map<string, number>(),
|
|
130
130
|
unitRecoveryCount: new Map<string, number>(),
|
|
131
131
|
verificationRetryCount: new Map<string, number>(),
|
|
132
|
+
zeroToolRetryCount: new Map<string, number>(),
|
|
132
133
|
gitService: null,
|
|
133
134
|
autoStartTime: Date.now(),
|
|
134
135
|
activeEngineId: null,
|
|
@@ -14,7 +14,7 @@ import { execFileSync } from "node:child_process";
|
|
|
14
14
|
|
|
15
15
|
import { DISPATCH_RULES, resolveDispatch, type DispatchContext } from "../auto-dispatch.ts";
|
|
16
16
|
import { AutoSession } from "../auto/session.ts";
|
|
17
|
-
import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
|
|
17
|
+
import { closeDatabase, insertAssessment, insertGateRow, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
|
|
18
18
|
|
|
19
19
|
function makeBase(): string {
|
|
20
20
|
const base = mkdtempSync(join(tmpdir(), "gsd-complete-dispatch-"));
|
|
@@ -225,6 +225,14 @@ describe("complete phase dispatch guard (#5683)", () => {
|
|
|
225
225
|
base = makeBase();
|
|
226
226
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
227
227
|
insertMilestone({ id: "M001", title: "Milestone One", status: "complete" });
|
|
228
|
+
insertSlice({ milestoneId: "M001", id: "S01", title: "Done", status: "complete" });
|
|
229
|
+
insertAssessment({
|
|
230
|
+
path: "milestones/M001/M001-VALIDATION.md",
|
|
231
|
+
milestoneId: "M001",
|
|
232
|
+
status: "pass",
|
|
233
|
+
scope: "milestone-validation",
|
|
234
|
+
fullContent: "verdict: pass",
|
|
235
|
+
});
|
|
228
236
|
|
|
229
237
|
const ctx = buildDispatchCtx(base);
|
|
230
238
|
ctx.state.phase = "complete";
|
|
@@ -234,6 +242,37 @@ describe("complete phase dispatch guard (#5683)", () => {
|
|
|
234
242
|
assert.equal(result?.action, "stop");
|
|
235
243
|
assert.equal(result?.reason, "All milestones complete.");
|
|
236
244
|
});
|
|
245
|
+
|
|
246
|
+
test("blocks terminal stop when closed milestone still has pending gates", async () => {
|
|
247
|
+
base = makeBase();
|
|
248
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
249
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "complete" });
|
|
250
|
+
insertSlice({ milestoneId: "M001", id: "S01", title: "Done", status: "complete" });
|
|
251
|
+
insertAssessment({
|
|
252
|
+
path: "milestones/M001/M001-VALIDATION.md",
|
|
253
|
+
milestoneId: "M001",
|
|
254
|
+
status: "pass",
|
|
255
|
+
scope: "milestone-validation",
|
|
256
|
+
fullContent: "verdict: pass",
|
|
257
|
+
});
|
|
258
|
+
insertGateRow({
|
|
259
|
+
milestoneId: "M001",
|
|
260
|
+
sliceId: "S01",
|
|
261
|
+
gateId: "Q3",
|
|
262
|
+
scope: "slice",
|
|
263
|
+
status: "pending",
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const ctx = buildDispatchCtx(base);
|
|
267
|
+
ctx.state.phase = "complete";
|
|
268
|
+
|
|
269
|
+
const result = await rule.match(ctx);
|
|
270
|
+
|
|
271
|
+
assert.equal(result?.action, "stop");
|
|
272
|
+
assert.equal(result?.level, "warning");
|
|
273
|
+
assert.match(result?.reason ?? "", /closeout-consistency-blocked/);
|
|
274
|
+
assert.match(result?.reason ?? "", /quality gate Q3 is still pending/);
|
|
275
|
+
});
|
|
237
276
|
});
|
|
238
277
|
|
|
239
278
|
describe("complete milestone context recovery guard (#5831)", () => {
|
|
@@ -216,6 +216,30 @@ test("dispatch-rule-coverage: planning with active slice and skip_research → p
|
|
|
216
216
|
);
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
+
test("dispatch-rule-coverage: planning boundary without planner handoff → research-slice", async (t) => {
|
|
220
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-planning-"));
|
|
221
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
222
|
+
|
|
223
|
+
writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
|
|
224
|
+
writeMilestoneFile(tmp, "M001", "ROADMAP", "# Roadmap\n");
|
|
225
|
+
|
|
226
|
+
const state = makeState({
|
|
227
|
+
phase: "planning",
|
|
228
|
+
activeSlice: { id: "S01", title: "First Slice" },
|
|
229
|
+
nextAction: "Plan slice S01 (First Slice).",
|
|
230
|
+
});
|
|
231
|
+
const match = await findFirstMatch(makeCtx(tmp, state));
|
|
232
|
+
assertMatch(
|
|
233
|
+
match,
|
|
234
|
+
{
|
|
235
|
+
ruleName: "planning (no research, not S01) → research-slice",
|
|
236
|
+
action: "dispatch",
|
|
237
|
+
unitType: "research-slice",
|
|
238
|
+
},
|
|
239
|
+
"planning boundary without planner handoff",
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
219
243
|
test("dispatch-rule-coverage: executing with task plan present → execute-task", async (t) => {
|
|
220
244
|
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-exec-"));
|
|
221
245
|
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* gsd-pi / guided-flow — regression tests for Gate 1b
|
|
2
|
+
* gsd-pi / guided-flow — regression tests for Gate 1b discussion handoff
|
|
3
3
|
*
|
|
4
|
-
* Gate 1b
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* → emit recovery hint directing the LLM to retry gsd_plan_milestone.
|
|
8
|
-
* (b) discuss-incomplete: discuss did not finish, no CONTEXT.md, DB row "queued".
|
|
9
|
-
* → silent block (no recovery hint).
|
|
4
|
+
* Gate 1b treats queued + pinned CONTEXT.md as Discussion Complete, Planning
|
|
5
|
+
* Pending. It must accept the handoff without warning the user or injecting a
|
|
6
|
+
* hidden gsd_plan_milestone retry.
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
9
|
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
@@ -27,8 +24,6 @@ import {
|
|
|
27
24
|
insertMilestone,
|
|
28
25
|
} from "../gsd-db.ts";
|
|
29
26
|
|
|
30
|
-
// ─── Harness ───────────────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
27
|
interface MockCapture {
|
|
33
28
|
notifies: Array<{ msg: string; level: string }>;
|
|
34
29
|
messages: Array<{ payload: any; options: any }>;
|
|
@@ -58,24 +53,26 @@ function mkPi(cap: MockCapture): any {
|
|
|
58
53
|
};
|
|
59
54
|
}
|
|
60
55
|
|
|
61
|
-
/**
|
|
62
|
-
* Create a minimal temp tree with a .gsd/milestones/M001 directory.
|
|
63
|
-
*/
|
|
64
56
|
function mkBase(): string {
|
|
65
57
|
const base = mkdtempSync(join(tmpdir(), "gsd-gate1b-"));
|
|
66
58
|
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
67
59
|
return base;
|
|
68
60
|
}
|
|
69
61
|
|
|
70
|
-
|
|
62
|
+
function writeContext(base: string): void {
|
|
63
|
+
writeFileSync(
|
|
64
|
+
join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
|
|
65
|
+
"# M001: Test Milestone\n\nContext written by discuss phase.\n",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
71
68
|
|
|
72
|
-
describe("Gate 1b
|
|
69
|
+
describe("Gate 1b discussion handoff in checkAutoStartAfterDiscuss", () => {
|
|
73
70
|
let base: string;
|
|
74
71
|
let cap: MockCapture;
|
|
75
72
|
|
|
76
73
|
beforeEach(() => {
|
|
77
74
|
clearPendingAutoStart();
|
|
78
|
-
drainLogs();
|
|
75
|
+
drainLogs();
|
|
79
76
|
});
|
|
80
77
|
|
|
81
78
|
afterEach(() => {
|
|
@@ -86,104 +83,59 @@ describe("Gate 1b orphan discrimination in checkAutoStartAfterDiscuss", () => {
|
|
|
86
83
|
}
|
|
87
84
|
});
|
|
88
85
|
|
|
89
|
-
test("
|
|
86
|
+
test("queued row + CONTEXT.md accepts context-captured handoff without hidden retry", () => {
|
|
90
87
|
base = mkBase();
|
|
91
88
|
openDatabase(":memory:");
|
|
92
|
-
|
|
93
|
-
// DB row exists with status "queued" (plan_milestone was blocked)
|
|
94
89
|
insertMilestone({ id: "M001", title: "Test Milestone", status: "queued" });
|
|
95
|
-
|
|
96
|
-
// CONTEXT.md on disk (discuss phase completed)
|
|
97
|
-
writeFileSync(
|
|
98
|
-
join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
|
|
99
|
-
"# M001: Test Milestone\n\nContext written by discuss phase.\n",
|
|
100
|
-
);
|
|
90
|
+
writeContext(base);
|
|
101
91
|
|
|
102
92
|
cap = mkCapture();
|
|
103
93
|
setPendingAutoStart(base, {
|
|
104
94
|
basePath: base,
|
|
105
95
|
milestoneId: "M001",
|
|
96
|
+
startAuto: false,
|
|
106
97
|
ctx: mkCtx(cap),
|
|
107
98
|
pi: mkPi(cap),
|
|
108
99
|
});
|
|
109
100
|
|
|
110
101
|
const result = checkAutoStartAfterDiscuss();
|
|
111
102
|
|
|
112
|
-
|
|
113
|
-
assert.equal(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"exactly one sendMessage call expected for the recovery hint",
|
|
120
|
-
);
|
|
121
|
-
assert.equal(
|
|
122
|
-
cap.messages[0].payload.customType,
|
|
123
|
-
"gsd-plan-milestone-blocked-recovery",
|
|
124
|
-
"recovery message must have customType gsd-plan-milestone-blocked-recovery",
|
|
125
|
-
);
|
|
103
|
+
assert.equal(result, true, "queued + context is a valid planning-pending handoff");
|
|
104
|
+
assert.equal(cap.messages.length, 0, "must not inject a hidden recovery turn");
|
|
105
|
+
assert.equal(cap.notifies.length, 1, "must emit one success notification");
|
|
106
|
+
assert.deepEqual(cap.notifies[0], {
|
|
107
|
+
msg: "Milestone M001 context captured. Continuing the planning pipeline.",
|
|
108
|
+
level: "success",
|
|
109
|
+
});
|
|
126
110
|
assert.equal(
|
|
127
|
-
cap.
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
);
|
|
131
|
-
assert.match(
|
|
132
|
-
cap.messages[0].payload.content,
|
|
133
|
-
/gsd_plan_milestone/,
|
|
134
|
-
"recovery message content must mention gsd_plan_milestone",
|
|
111
|
+
cap.notifies.some(n => /queued|gsd_plan_milestone/i.test(n.msg)),
|
|
112
|
+
false,
|
|
113
|
+
"user-visible copy must not mention queued state or internal plan tool retry",
|
|
135
114
|
);
|
|
136
|
-
|
|
137
|
-
// User must be notified via ctx.ui.notify
|
|
138
|
-
assert.ok(
|
|
139
|
-
cap.notifies.some((n) => n.level === "warning" && /queued/.test(n.msg)),
|
|
140
|
-
"user must be notified with a warning about the queued state",
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// logWarning must have recorded the Gate 1b event
|
|
144
|
-
const logs = drainLogs();
|
|
145
|
-
const gate1bLog = logs.find(
|
|
146
|
-
(e) => e.component === "guided" && /Gate 1b/.test(e.message),
|
|
147
|
-
);
|
|
148
|
-
assert.ok(gate1bLog, "Gate 1b warning must be logged via logWarning");
|
|
149
115
|
});
|
|
150
116
|
|
|
151
|
-
test("
|
|
117
|
+
test("queued row without CONTEXT.md still waits silently for discussion output", () => {
|
|
152
118
|
base = mkBase();
|
|
153
119
|
openDatabase(":memory:");
|
|
154
|
-
|
|
155
|
-
// DB row exists with status "queued", but NO CONTEXT.md on disk
|
|
156
120
|
insertMilestone({ id: "M001", title: "Test Milestone", status: "queued" });
|
|
157
121
|
|
|
158
|
-
// No CONTEXT.md written — discuss phase is incomplete
|
|
159
122
|
cap = mkCapture();
|
|
160
123
|
setPendingAutoStart(base, {
|
|
161
124
|
basePath: base,
|
|
162
125
|
milestoneId: "M001",
|
|
126
|
+
startAuto: false,
|
|
163
127
|
ctx: mkCtx(cap),
|
|
164
128
|
pi: mkPi(cap),
|
|
165
129
|
});
|
|
166
130
|
|
|
167
|
-
drainLogs();
|
|
131
|
+
drainLogs();
|
|
168
132
|
|
|
169
133
|
const result = checkAutoStartAfterDiscuss();
|
|
170
134
|
|
|
171
|
-
|
|
172
|
-
assert.equal(
|
|
173
|
-
|
|
174
|
-
// No recovery hint — Gate 1 blocks before Gate 1b is reached
|
|
175
|
-
assert.equal(
|
|
176
|
-
cap.messages.length,
|
|
177
|
-
0,
|
|
178
|
-
"no sendMessage calls expected when CONTEXT.md is absent",
|
|
179
|
-
);
|
|
180
|
-
assert.equal(
|
|
181
|
-
cap.notifies.length,
|
|
182
|
-
0,
|
|
183
|
-
"no user notifications expected for discuss-incomplete case",
|
|
184
|
-
);
|
|
135
|
+
assert.equal(result, false, "must keep waiting while discuss has not written context");
|
|
136
|
+
assert.equal(cap.messages.length, 0, "no hidden recovery turn expected");
|
|
137
|
+
assert.equal(cap.notifies.length, 0, "no user notifications expected");
|
|
185
138
|
|
|
186
|
-
// No Gate 1b log entry
|
|
187
139
|
const logs = drainLogs();
|
|
188
140
|
const gate1bLog = logs.find(
|
|
189
141
|
(e) => e.component === "guided" && /Gate 1b/.test(e.message),
|
|
@@ -178,7 +178,7 @@ test("checkAutoStartAfterDiscuss(basePath) selects the matching pending entry wh
|
|
|
178
178
|
}
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
test("checkAutoStartAfterDiscuss can
|
|
181
|
+
test("checkAutoStartAfterDiscuss can accept context handoff without scheduling auto-start", () => {
|
|
182
182
|
const base = mkdtempSync(join(tmpdir(), "gsd-auto-start-headless-owned-"));
|
|
183
183
|
let waitForIdleCalls = 0;
|
|
184
184
|
const notifications: string[] = [];
|
|
@@ -209,9 +209,11 @@ test("checkAutoStartAfterDiscuss can emit ready without scheduling auto-start",
|
|
|
209
209
|
});
|
|
210
210
|
|
|
211
211
|
assert.equal(checkAutoStartAfterDiscuss(base), true);
|
|
212
|
-
assert.deepEqual(notifications, [
|
|
212
|
+
assert.deepEqual(notifications, [
|
|
213
|
+
"Milestone M001 context captured. Continuing the planning pipeline.",
|
|
214
|
+
]);
|
|
213
215
|
assert.equal(waitForIdleCalls, 0, "headless-owned auto start must not schedule guided-flow auto");
|
|
214
|
-
assert.equal(getDiscussionMilestoneId(base), null, "
|
|
216
|
+
assert.equal(getDiscussionMilestoneId(base), null, "accepted handoff should still be cleared");
|
|
215
217
|
} finally {
|
|
216
218
|
clearPendingAutoStart();
|
|
217
219
|
rmSync(base, { recursive: true, force: true });
|
|
@@ -135,7 +135,13 @@ describe("guided-flow STATE.md rebuild (#3475)", () => {
|
|
|
135
135
|
|
|
136
136
|
assert.equal(accepted, true);
|
|
137
137
|
assert.equal(notifications.some(n => n.level === "error" && n.message.includes("no DB row exists")), false);
|
|
138
|
-
assert.ok(
|
|
138
|
+
assert.ok(
|
|
139
|
+
notifications.some(
|
|
140
|
+
n =>
|
|
141
|
+
n.level === "success" &&
|
|
142
|
+
n.message.includes("Milestone M001 context captured. Continuing the planning pipeline."),
|
|
143
|
+
),
|
|
144
|
+
);
|
|
139
145
|
clearPendingAutoStart(base);
|
|
140
146
|
});
|
|
141
147
|
|
|
@@ -157,13 +163,13 @@ describe("guided-flow STATE.md rebuild (#3475)", () => {
|
|
|
157
163
|
};
|
|
158
164
|
setPendingAutoStart(base, { basePath: base, milestoneId: "M001", ctx: ctx as any, pi: {} as any });
|
|
159
165
|
|
|
160
|
-
for (let i = 0; i <
|
|
166
|
+
for (let i = 0; i < 3; i += 1) {
|
|
161
167
|
assert.equal(checkAutoStartAfterDiscuss(), false);
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
assert.equal(
|
|
165
|
-
notifications.filter(n => n.level === "warning"
|
|
166
|
-
|
|
171
|
+
notifications.filter(n => n.level === "warning").length,
|
|
172
|
+
0,
|
|
167
173
|
);
|
|
168
174
|
assert.equal(
|
|
169
175
|
notifications.filter(n => n.level === "error" && n.message.includes("DB row recovery failed")).length,
|
|
@@ -171,4 +177,34 @@ describe("guided-flow STATE.md rebuild (#3475)", () => {
|
|
|
171
177
|
);
|
|
172
178
|
clearPendingAutoStart(base);
|
|
173
179
|
});
|
|
180
|
+
|
|
181
|
+
test("checkAutoStartAfterDiscuss does not double-notify on 4th+ call after recovery limit", () => {
|
|
182
|
+
base = createFixtureBase();
|
|
183
|
+
openDatabase(":memory:");
|
|
184
|
+
const db = _getAdapter();
|
|
185
|
+
assert.ok(db, "database should be open");
|
|
186
|
+
db.exec("DROP TABLE milestones");
|
|
187
|
+
db.exec("CREATE TABLE milestones (id TEXT PRIMARY KEY)");
|
|
188
|
+
|
|
189
|
+
writeFile(base, "milestones/M001/M001-CONTEXT.md", "# M001: Planned\n");
|
|
190
|
+
writeFile(base, "STATE.md", "# GSD State\n\n**Active Milestone:** M001: Planned\n");
|
|
191
|
+
|
|
192
|
+
const notifications: Array<{ message: string; level: string }> = [];
|
|
193
|
+
const ctx = {
|
|
194
|
+
ui: { notify: (message: string, level: string) => notifications.push({ message, level }) },
|
|
195
|
+
waitForIdle: () => new Promise<void>(() => {}),
|
|
196
|
+
};
|
|
197
|
+
setPendingAutoStart(base, { basePath: base, milestoneId: "M001", ctx: ctx as any, pi: {} as any });
|
|
198
|
+
|
|
199
|
+
for (let i = 0; i < 5; i += 1) {
|
|
200
|
+
assert.equal(checkAutoStartAfterDiscuss(), false);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
assert.equal(
|
|
204
|
+
notifications.filter(n => n.level === "error" && n.message.includes("DB row recovery failed")).length,
|
|
205
|
+
1,
|
|
206
|
+
"user must see exactly one error notification even after repeated calls past the recovery limit",
|
|
207
|
+
);
|
|
208
|
+
clearPendingAutoStart(base);
|
|
209
|
+
});
|
|
174
210
|
});
|
package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { nativeMergeSquash } from "../../native-git-bridge.ts";
|
|
|
29
29
|
import { drainLogs, setStderrLoggingEnabled } from "../../workflow-logger.ts";
|
|
30
30
|
import {
|
|
31
31
|
closeDatabase,
|
|
32
|
+
insertAssessment,
|
|
32
33
|
insertMilestone,
|
|
33
34
|
insertSlice,
|
|
34
35
|
insertTask,
|
|
@@ -207,6 +208,13 @@ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => {
|
|
|
207
208
|
});
|
|
208
209
|
}
|
|
209
210
|
}
|
|
211
|
+
insertAssessment({
|
|
212
|
+
path: "milestones/M020/M020-VALIDATION.md",
|
|
213
|
+
milestoneId: "M020",
|
|
214
|
+
status: "pass",
|
|
215
|
+
scope: "milestone-validation",
|
|
216
|
+
fullContent: "verdict: pass",
|
|
217
|
+
});
|
|
210
218
|
|
|
211
219
|
const roadmap = makeRoadmap("M020", "Backend foundation", [
|
|
212
220
|
{ id: "S01", title: "Core API" },
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import {
|
|
42
42
|
openDatabase,
|
|
43
43
|
closeDatabase,
|
|
44
|
+
insertAssessment,
|
|
44
45
|
insertMilestone,
|
|
45
46
|
insertSlice,
|
|
46
47
|
updateMilestoneStatus,
|
|
@@ -347,6 +348,13 @@ test("mergeCompletedMilestone — synthesizes roadmap from DB when projection is
|
|
|
347
348
|
title: "Search Bar",
|
|
348
349
|
status: "complete",
|
|
349
350
|
});
|
|
351
|
+
insertAssessment({
|
|
352
|
+
path: "milestones/M010/M010-VALIDATION.md",
|
|
353
|
+
milestoneId: "M010",
|
|
354
|
+
status: "pass",
|
|
355
|
+
scope: "milestone-validation",
|
|
356
|
+
fullContent: "verdict: pass",
|
|
357
|
+
});
|
|
350
358
|
|
|
351
359
|
createMilestoneBranch(repo, "M010", [
|
|
352
360
|
{ name: "app.js", content: "export const app = true;\n" },
|
|
@@ -671,6 +679,14 @@ function setupCanonicalDbWithWorktree(basePath: string, mid: string): void {
|
|
|
671
679
|
const dbPath = join(basePath, ".gsd", "gsd.db");
|
|
672
680
|
openDatabase(dbPath);
|
|
673
681
|
insertMilestone({ id: mid, title: `Milestone ${mid}`, status: "complete" });
|
|
682
|
+
insertSlice({ id: "S01", milestoneId: mid, title: "Done Slice", status: "complete" });
|
|
683
|
+
insertAssessment({
|
|
684
|
+
path: `milestones/${mid}/${mid}-VALIDATION.md`,
|
|
685
|
+
milestoneId: mid,
|
|
686
|
+
status: "pass",
|
|
687
|
+
scope: "milestone-validation",
|
|
688
|
+
fullContent: "verdict: pass",
|
|
689
|
+
});
|
|
674
690
|
updateMilestoneStatus(mid, "complete", new Date().toISOString());
|
|
675
691
|
closeDatabase();
|
|
676
692
|
}
|
|
@@ -11,6 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { extractUatType } from '../../files.ts';
|
|
12
12
|
import { resolveSliceFile } from '../../paths.ts';
|
|
13
13
|
import { buildRunUatPrompt, checkNeedsRunUat } from '../../auto-prompts.ts';
|
|
14
|
+
import { buildRunUatResultPresentation, RUN_UAT_TOOL_PRESENTATION_PLAN_ID } from '../../tool-presentation-plan.ts';
|
|
14
15
|
|
|
15
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
const worktreePromptsDir = join(__dirname, '../..', 'prompts');
|
|
@@ -20,6 +21,8 @@ function loadPromptFromWorktree(name: string, vars: Record<string, string> = {})
|
|
|
20
21
|
let content = readFileSync(path, 'utf-8');
|
|
21
22
|
const effectiveVars = {
|
|
22
23
|
skillActivation: 'If no installed skill clearly matches this unit, skip explicit skill activation and continue with the required workflow.',
|
|
24
|
+
canonicalPresentation: JSON.stringify(buildRunUatResultPresentation(), null, 2),
|
|
25
|
+
toolPresentationPlanId: RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
|
|
23
26
|
...vars,
|
|
24
27
|
};
|
|
25
28
|
for (const [key, value] of Object.entries(effectiveVars)) {
|
|
@@ -720,7 +723,9 @@ test('(u) run-uat prompt promotes artifact-driven browser specs to browser-execu
|
|
|
720
723
|
|
|
721
724
|
assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`browser-executable`/);
|
|
722
725
|
assert.match(prompt, /uatType: "browser-executable"/);
|
|
723
|
-
assert.match(prompt, /use
|
|
726
|
+
assert.match(prompt, /use browser tools/i);
|
|
727
|
+
assert.match(prompt, /"browser_navigate"/);
|
|
728
|
+
assert.match(prompt, /"browser_assert"/);
|
|
724
729
|
} finally {
|
|
725
730
|
cleanup(base);
|
|
726
731
|
}
|
|
@@ -738,6 +743,7 @@ test('(v) run-uat prompt keeps deferred browser work artifact-driven', async ()
|
|
|
738
743
|
assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`artifact-driven`/);
|
|
739
744
|
assert.match(prompt, /uatType: "artifact-driven"/);
|
|
740
745
|
assert.doesNotMatch(prompt, /uatType: "browser-executable"/);
|
|
746
|
+
assert.doesNotMatch(prompt, /"browser_navigate"/);
|
|
741
747
|
} finally {
|
|
742
748
|
cleanup(base);
|
|
743
749
|
}
|
|
@@ -226,6 +226,33 @@ test("direct /gsd auto skips paused-session replay when recovered unit already c
|
|
|
226
226
|
}
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
test("paused-session resume skips replay when unit identity was never recorded", () => {
|
|
230
|
+
const base = makeTmpBase();
|
|
231
|
+
try {
|
|
232
|
+
// No currentUnit and no persisted unit type/id — identity is unknown. The
|
|
233
|
+
// old code fell back to the literal "unknown" unit, which can neither be
|
|
234
|
+
// verified nor correctly targeted, and synthesized a full tool-call replay
|
|
235
|
+
// (the thrash that turns one stuck unit into several). The fix skips the
|
|
236
|
+
// replay and resumes from rebuilt disk state instead.
|
|
237
|
+
const state = {
|
|
238
|
+
pausedSessionFile: join(base, ".gsd", "activity", "paused-session.jsonl"),
|
|
239
|
+
currentUnit: null,
|
|
240
|
+
pausedUnitType: null,
|
|
241
|
+
pausedUnitId: null,
|
|
242
|
+
pendingCrashRecovery: "stale-recovery-prompt",
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const result = _handlePausedSessionResumeRecoveryForTest(base, state);
|
|
246
|
+
assert.equal(result.skippedReplay, true);
|
|
247
|
+
assert.equal(state.pausedSessionFile, null);
|
|
248
|
+
assert.equal(state.pendingCrashRecovery, null, "must not synthesize a replay for an unknown unit");
|
|
249
|
+
assert.equal(state.pausedUnitType, null);
|
|
250
|
+
assert.equal(state.pausedUnitId, null);
|
|
251
|
+
} finally {
|
|
252
|
+
cleanup(base);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
229
256
|
test("interrupted-session source preserves raw lock and excludes same-pid from running classification", async () => {
|
|
230
257
|
const source = await import(`node:fs/promises`).then((fs) =>
|
|
231
258
|
fs.readFile(new URL("../interrupted-session.ts", import.meta.url), "utf-8")
|
|
@@ -200,6 +200,7 @@ function makeSession() {
|
|
|
200
200
|
unitLifetimeDispatches: new Map<string, number>(),
|
|
201
201
|
unitRecoveryCount: new Map<string, number>(),
|
|
202
202
|
verificationRetryCount: new Map<string, number>(),
|
|
203
|
+
zeroToolRetryCount: new Map<string, number>(),
|
|
203
204
|
gitService: null,
|
|
204
205
|
autoStartTime: Date.now(),
|
|
205
206
|
cmdCtx: {
|
|
@@ -54,7 +54,13 @@ test("ensureProjectWorkflowMcpConfig creates .mcp.json with workflow and browser
|
|
|
54
54
|
"--identity-scope",
|
|
55
55
|
"project",
|
|
56
56
|
]);
|
|
57
|
-
|
|
57
|
+
// --identity-scope requires a non-empty --identity-key or gsd-browser exits
|
|
58
|
+
// immediately ("Connection closed"); the key must be stable per project.
|
|
59
|
+
assert.equal(browserArgs[mcpArgIndex + 5], "--identity-key");
|
|
60
|
+
assert.equal(typeof browserArgs[mcpArgIndex + 6], "string");
|
|
61
|
+
assert.ok((browserArgs[mcpArgIndex + 6] ?? "").length > 0, "identity-key must be non-empty");
|
|
62
|
+
assert.equal(browserArgs[mcpArgIndex + 7], "--identity-project");
|
|
63
|
+
assert.equal(browserArgs[mcpArgIndex + 8], projectRoot);
|
|
58
64
|
assert.equal((browserServer as { cwd?: string })?.cwd, projectRoot);
|
|
59
65
|
|
|
60
66
|
const settings = JSON.parse(readFileSync(join(projectRoot, ".claude", "settings.local.json"), "utf-8")) as {
|
|
@@ -342,7 +342,7 @@ describe("formatMcpInitResult", () => {
|
|
|
342
342
|
assert.match(result, /\/tmp\/project\/\.mcp\.json/);
|
|
343
343
|
assert.match(result, /mcp-capable clients/i);
|
|
344
344
|
assert.match(result, /workflow and gsd-browser MCP servers/i);
|
|
345
|
-
assert.match(result, /Pi Providers use
|
|
345
|
+
assert.match(result, /Pi Providers use built-in browser tools/i);
|
|
346
346
|
assert.doesNotMatch(result, /claude code/i);
|
|
347
347
|
});
|
|
348
348
|
|