@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
|
@@ -1118,6 +1118,7 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
|
|
|
1118
1118
|
unitLifetimeDispatches: new Map<string, number>(),
|
|
1119
1119
|
unitRecoveryCount: new Map<string, number>(),
|
|
1120
1120
|
verificationRetryCount: new Map<string, number>(),
|
|
1121
|
+
zeroToolRetryCount: new Map<string, number>(),
|
|
1121
1122
|
gitService: null,
|
|
1122
1123
|
lastRequestTimestamp: 0,
|
|
1123
1124
|
autoStartTime: Date.now(),
|
|
@@ -4693,6 +4694,104 @@ test("runUnitPhase retries 0-tool units with ordinary network-related assistant
|
|
|
4693
4694
|
assert.equal(deps.callLog.includes("pauseAuto"), false);
|
|
4694
4695
|
});
|
|
4695
4696
|
|
|
4697
|
+
test("runUnitPhase pauses auto-mode when zero-tool-call retry is exhausted", async (t) => {
|
|
4698
|
+
_resetPendingResolve();
|
|
4699
|
+
|
|
4700
|
+
const basePath = mkdtempSync(join(tmpdir(), "gsd-zero-tool-exhausted-"));
|
|
4701
|
+
t.after(() => {
|
|
4702
|
+
rmSync(basePath, { recursive: true, force: true });
|
|
4703
|
+
});
|
|
4704
|
+
|
|
4705
|
+
const ctx = {
|
|
4706
|
+
...makeMockCtx(),
|
|
4707
|
+
ui: {
|
|
4708
|
+
notify: () => {},
|
|
4709
|
+
setStatus: () => {},
|
|
4710
|
+
setWorkingMessage: () => {},
|
|
4711
|
+
},
|
|
4712
|
+
sessionManager: {
|
|
4713
|
+
getEntries: () => [],
|
|
4714
|
+
},
|
|
4715
|
+
modelRegistry: {
|
|
4716
|
+
getProviderAuthMode: () => undefined,
|
|
4717
|
+
isProviderRequestReady: () => true,
|
|
4718
|
+
},
|
|
4719
|
+
} as any;
|
|
4720
|
+
const pi = {
|
|
4721
|
+
...makeMockPi(),
|
|
4722
|
+
sendMessage: () => {
|
|
4723
|
+
queueMicrotask(() => resolveAgentEnd(makeEvent([
|
|
4724
|
+
{
|
|
4725
|
+
role: "assistant",
|
|
4726
|
+
content: [
|
|
4727
|
+
{ type: "text", text: "Error: I'll investigate the network error handling next." },
|
|
4728
|
+
],
|
|
4729
|
+
},
|
|
4730
|
+
])));
|
|
4731
|
+
},
|
|
4732
|
+
} as any;
|
|
4733
|
+
const s = makeLoopSession({
|
|
4734
|
+
basePath,
|
|
4735
|
+
canonicalProjectRoot: basePath,
|
|
4736
|
+
originalBasePath: basePath,
|
|
4737
|
+
});
|
|
4738
|
+
// Pre-seed counter at MAX_ZERO_TOOL_RETRIES so the next zero-tool turn exhausts the cap
|
|
4739
|
+
s.zeroToolRetryCount.set("execute-task/M001/S01/T01", 1);
|
|
4740
|
+
|
|
4741
|
+
const mockLedger = {
|
|
4742
|
+
version: 1,
|
|
4743
|
+
projectStartedAt: Date.now(),
|
|
4744
|
+
units: [] as any[],
|
|
4745
|
+
};
|
|
4746
|
+
const deps = makeMockDeps({
|
|
4747
|
+
closeoutUnit: async () => {
|
|
4748
|
+
mockLedger.units.push({
|
|
4749
|
+
type: "execute-task",
|
|
4750
|
+
id: "M001/S01/T01",
|
|
4751
|
+
startedAt: s.currentUnit?.startedAt ?? Date.now(),
|
|
4752
|
+
toolCalls: 0,
|
|
4753
|
+
assistantMessages: 1,
|
|
4754
|
+
tokens: { input: 100, output: 20, total: 120, cacheRead: 0, cacheWrite: 0 },
|
|
4755
|
+
cost: 0.01,
|
|
4756
|
+
});
|
|
4757
|
+
},
|
|
4758
|
+
getLedger: () => mockLedger,
|
|
4759
|
+
});
|
|
4760
|
+
let seq = 0;
|
|
4761
|
+
|
|
4762
|
+
const result = await runUnitPhase(
|
|
4763
|
+
{ ctx, pi, s, deps, prefs: undefined, iteration: 1, flowId: "flow-zero-tool-exhausted", nextSeq: () => ++seq },
|
|
4764
|
+
{
|
|
4765
|
+
unitType: "execute-task",
|
|
4766
|
+
unitId: "M001/S01/T01",
|
|
4767
|
+
prompt: "do work",
|
|
4768
|
+
finalPrompt: "do work",
|
|
4769
|
+
pauseAfterUatDispatch: false,
|
|
4770
|
+
state: {
|
|
4771
|
+
phase: "executing",
|
|
4772
|
+
activeMilestone: { id: "M001", title: "Milestone" },
|
|
4773
|
+
activeSlice: { id: "S01", title: "Slice" },
|
|
4774
|
+
activeTask: { id: "T01", title: "Task" },
|
|
4775
|
+
registry: [{ id: "M001", title: "Milestone", status: "active" }],
|
|
4776
|
+
recentDecisions: [],
|
|
4777
|
+
blockers: [],
|
|
4778
|
+
nextAction: "",
|
|
4779
|
+
progress: { milestones: { done: 0, total: 1 } },
|
|
4780
|
+
requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
|
|
4781
|
+
} as any,
|
|
4782
|
+
mid: "M001",
|
|
4783
|
+
midTitle: "Milestone",
|
|
4784
|
+
isRetry: false,
|
|
4785
|
+
previousTier: undefined,
|
|
4786
|
+
},
|
|
4787
|
+
{ recentUnits: [{ key: "execute-task/M001/S01/T01" }], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 },
|
|
4788
|
+
);
|
|
4789
|
+
|
|
4790
|
+
assert.equal(result.action, "break");
|
|
4791
|
+
assert.equal((result as any).reason, "zero-tool-calls-exhausted");
|
|
4792
|
+
assert.equal(deps.callLog.includes("pauseAuto"), true);
|
|
4793
|
+
});
|
|
4794
|
+
|
|
4696
4795
|
test("autoLoop pauses user-driven deep question instead of flagging 0 tool calls", async () => {
|
|
4697
4796
|
_resetPendingResolve();
|
|
4698
4797
|
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
selectAndApplyModel,
|
|
34
34
|
ModelPolicyDispatchBlockedError,
|
|
35
35
|
clearToolBaseline,
|
|
36
|
+
getToolBaselineSnapshot,
|
|
36
37
|
} from "../auto-model-selection.js";
|
|
37
38
|
import { applyModelPolicyFilter } from "../uok/model-policy.js";
|
|
38
39
|
import {
|
|
@@ -139,7 +140,7 @@ function makeCtx(
|
|
|
139
140
|
test("vacuous-truth (a): unit type with empty workflow-required tools → dispatch succeeds", async () => {
|
|
140
141
|
const env = makeTempProject();
|
|
141
142
|
try {
|
|
142
|
-
// `
|
|
143
|
+
// `rewrite-docs` has no required workflow tools
|
|
143
144
|
// → returns []. Exercises the empty-requiredTools branch in
|
|
144
145
|
// applyModelPolicyFilter (existing test used
|
|
145
146
|
// gate-evaluate which has non-empty required tools and never hit this path).
|
|
@@ -161,7 +162,7 @@ test("vacuous-truth (a): unit type with empty workflow-required tools → dispat
|
|
|
161
162
|
const result = await selectAndApplyModel(
|
|
162
163
|
makeCtx(availableModels),
|
|
163
164
|
pi as any,
|
|
164
|
-
"
|
|
165
|
+
"rewrite-docs",
|
|
165
166
|
"x1",
|
|
166
167
|
env.dir,
|
|
167
168
|
undefined,
|
|
@@ -308,8 +309,8 @@ test("genuinely-impossible (a): pi-native required tool incompatible with candid
|
|
|
308
309
|
test("genuinely-impossible (b): cross-provider routing disabled + provider mismatch → typed error", async () => {
|
|
309
310
|
const env = makeTempProject();
|
|
310
311
|
try {
|
|
311
|
-
// Use plan-slice
|
|
312
|
-
//
|
|
312
|
+
// Use plan-slice but pretend no candidate model can carry its required
|
|
313
|
+
// workflow tools. The simplest way: provide a model whose
|
|
313
314
|
// api is a fictitious "no-tools" string — `filterToolsForProvider` returns
|
|
314
315
|
// every tool as filtered for an unknown api with toolCalling=false, OR we
|
|
315
316
|
// can pick a real api that also denies the tool. We use an api that
|
|
@@ -711,3 +712,64 @@ test("cross-mode (#4965): auto → guided → auto preserves the original auto-e
|
|
|
711
712
|
env.cleanup();
|
|
712
713
|
}
|
|
713
714
|
});
|
|
715
|
+
|
|
716
|
+
// ─── 8. Baseline union: MCP tools connected after baseline capture (#477) ─────
|
|
717
|
+
//
|
|
718
|
+
// `getToolBaselineSnapshot` must return the UNION of the frozen WeakMap baseline
|
|
719
|
+
// and the current live tool set. This ensures:
|
|
720
|
+
// (a) Provider-narrowed tools (in baseline, dropped from live) are still seen
|
|
721
|
+
// by transport preflight — the bug-5 fix from #477.
|
|
722
|
+
// (b) Tools connected after the baseline was captured (e.g. MCP server attached
|
|
723
|
+
// mid-session) are also visible — so a paused run that resumes after MCP
|
|
724
|
+
// reconnects clears the transport warning on the first iteration instead of
|
|
725
|
+
// repeating it indefinitely.
|
|
726
|
+
|
|
727
|
+
test("baseline union (#477): getToolBaselineSnapshot includes live tools not present in frozen baseline", async () => {
|
|
728
|
+
const env = makeTempProject();
|
|
729
|
+
try {
|
|
730
|
+
const availableModels = [
|
|
731
|
+
{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
|
|
732
|
+
];
|
|
733
|
+
|
|
734
|
+
const initialTools = ["bash", "read", "write"];
|
|
735
|
+
const pi = makeRecordingPi(initialTools);
|
|
736
|
+
clearToolBaseline(pi as unknown as object);
|
|
737
|
+
|
|
738
|
+
// Capture baseline with only native tools (no MCP connected yet).
|
|
739
|
+
await selectAndApplyModel(
|
|
740
|
+
makeCtx(availableModels),
|
|
741
|
+
pi as any,
|
|
742
|
+
"execute-task",
|
|
743
|
+
"u1",
|
|
744
|
+
env.dir,
|
|
745
|
+
undefined,
|
|
746
|
+
false,
|
|
747
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
748
|
+
undefined,
|
|
749
|
+
/* isAutoMode */ true,
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
// Simulate: provider narrows tools (Groq cap, hook override, etc.).
|
|
753
|
+
// The baseline in the WeakMap still has the full initial set.
|
|
754
|
+
pi.setActiveTools(["bash"]);
|
|
755
|
+
|
|
756
|
+
// Simulate: user connects MCP mid-session (after the baseline was captured).
|
|
757
|
+
const liveTools = pi.getActiveTools().concat(["mcp__gsd-workflow__gsd_uat_exec"]);
|
|
758
|
+
pi.setActiveTools(liveTools);
|
|
759
|
+
|
|
760
|
+
const snapshot = getToolBaselineSnapshot(pi as any);
|
|
761
|
+
|
|
762
|
+
// All baseline tools must be present (even the provider-narrowed ones).
|
|
763
|
+
for (const t of initialTools) {
|
|
764
|
+
assert.ok(snapshot.includes(t), `snapshot must include baseline tool: ${t}`);
|
|
765
|
+
}
|
|
766
|
+
// Newly connected MCP tool must also be present.
|
|
767
|
+
assert.ok(
|
|
768
|
+
snapshot.includes("mcp__gsd-workflow__gsd_uat_exec"),
|
|
769
|
+
"snapshot must include MCP tool connected after baseline capture",
|
|
770
|
+
);
|
|
771
|
+
} finally {
|
|
772
|
+
env.restoreEnv();
|
|
773
|
+
env.cleanup();
|
|
774
|
+
}
|
|
775
|
+
});
|
|
@@ -1558,6 +1558,14 @@ test("verifyExpectedArtifact complete-milestone passes when DB milestone is comp
|
|
|
1558
1558
|
|
|
1559
1559
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
1560
1560
|
insertMilestone({ id: "M001", title: "Milestone One", status: "complete" });
|
|
1561
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Done Slice", status: "complete" });
|
|
1562
|
+
insertAssessment({
|
|
1563
|
+
path: "milestones/M001/M001-VALIDATION.md",
|
|
1564
|
+
milestoneId: "M001",
|
|
1565
|
+
status: "pass",
|
|
1566
|
+
scope: "milestone-validation",
|
|
1567
|
+
fullContent: "verdict: pass",
|
|
1568
|
+
});
|
|
1561
1569
|
|
|
1562
1570
|
const result = verifyExpectedArtifact("complete-milestone", "M001", base);
|
|
1563
1571
|
assert.equal(result, true, "complete-milestone should pass when DB status is complete");
|
|
@@ -1566,7 +1574,7 @@ test("verifyExpectedArtifact complete-milestone passes when DB milestone is comp
|
|
|
1566
1574
|
}
|
|
1567
1575
|
});
|
|
1568
1576
|
|
|
1569
|
-
test("verifyExpectedArtifact complete-milestone
|
|
1577
|
+
test("verifyExpectedArtifact complete-milestone rejects success SUMMARY when DB milestone is still open (#4658)", () => {
|
|
1570
1578
|
const base = makeGitBase();
|
|
1571
1579
|
try {
|
|
1572
1580
|
execFileSync("git", ["checkout", "-b", "feat/ms-db-lag-success"], { cwd: base, stdio: "ignore" });
|
|
@@ -1591,7 +1599,7 @@ test("verifyExpectedArtifact complete-milestone tolerates transient DB lag when
|
|
|
1591
1599
|
insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
|
|
1592
1600
|
|
|
1593
1601
|
const result = verifyExpectedArtifact("complete-milestone", "M001", base);
|
|
1594
|
-
assert.equal(result,
|
|
1602
|
+
assert.equal(result, false, "success SUMMARY must not overrule an open DB milestone");
|
|
1595
1603
|
} finally {
|
|
1596
1604
|
cleanup(base);
|
|
1597
1605
|
}
|
|
@@ -30,6 +30,7 @@ test("checkAutoStartAfterDiscuss waits until discussion artifacts exist before r
|
|
|
30
30
|
setPendingAutoStart(base, {
|
|
31
31
|
basePath: base,
|
|
32
32
|
milestoneId: "M001",
|
|
33
|
+
startAuto: false,
|
|
33
34
|
ctx: { ui: { notify: (message: string) => notifications.push(message) } } as any,
|
|
34
35
|
pi: { sendMessage: () => {} } as any,
|
|
35
36
|
});
|
|
@@ -41,5 +42,7 @@ test("checkAutoStartAfterDiscuss waits until discussion artifacts exist before r
|
|
|
41
42
|
writeFileSync(join(base, ".gsd", "STATE.md"), "# State\n", "utf-8");
|
|
42
43
|
|
|
43
44
|
assert.equal(checkAutoStartAfterDiscuss(), true);
|
|
44
|
-
assert.deepEqual(notifications, [
|
|
45
|
+
assert.deepEqual(notifications, [
|
|
46
|
+
"Milestone M001 context captured. Continuing the planning pipeline.",
|
|
47
|
+
]);
|
|
45
48
|
});
|
|
@@ -20,6 +20,10 @@ test('resolveAutoSupervisorConfig provides safe timeout defaults', () => {
|
|
|
20
20
|
assert.equal(supervisor.soft_timeout_minutes, 20);
|
|
21
21
|
assert.equal(supervisor.idle_timeout_minutes, 10);
|
|
22
22
|
assert.equal(supervisor.hard_timeout_minutes, 30);
|
|
23
|
+
// A single hung tool gets its own short budget, well below the idle window,
|
|
24
|
+
// so a genuinely stuck tool is recovered in minutes instead of waiting out
|
|
25
|
+
// the full idle timeout.
|
|
26
|
+
assert.equal(supervisor.stalled_tool_timeout_minutes, 5);
|
|
23
27
|
} finally {
|
|
24
28
|
if (previousGsdHome === undefined) {
|
|
25
29
|
delete process.env.GSD_HOME;
|
|
@@ -92,13 +92,23 @@ test("checkAutoStartAfterDiscuss completes when discussion manifest is absent",
|
|
|
92
92
|
setPendingAutoStart(base, {
|
|
93
93
|
basePath: base,
|
|
94
94
|
milestoneId: "M001",
|
|
95
|
-
|
|
95
|
+
startAuto: false,
|
|
96
|
+
ctx: {
|
|
97
|
+
ui: {
|
|
98
|
+
notify: (message: string, level: string) => notifications.push({ message, level }),
|
|
99
|
+
},
|
|
100
|
+
} as any,
|
|
96
101
|
pi: { sendMessage: () => { scheduled = true; } } as any,
|
|
97
102
|
});
|
|
98
103
|
|
|
99
104
|
assert.equal(checkAutoStartAfterDiscuss(), true);
|
|
100
105
|
assert.equal(scheduled, false);
|
|
101
|
-
assert.deepEqual(notifications, [
|
|
106
|
+
assert.deepEqual(notifications, [
|
|
107
|
+
{
|
|
108
|
+
message: "Milestone M001 context captured. Continuing the planning pipeline.",
|
|
109
|
+
level: "success",
|
|
110
|
+
},
|
|
111
|
+
]);
|
|
102
112
|
} finally {
|
|
103
113
|
clearPendingAutoStart(base);
|
|
104
114
|
rmSync(base, { recursive: true, force: true });
|
|
@@ -76,3 +76,12 @@ test('BUNDLED_SKILL_TRIGGERS: skill ids are unique', () => {
|
|
|
76
76
|
seen.add(skill);
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
|
+
|
|
80
|
+
test('BUNDLED_SKILL_TRIGGERS: gsd-browser and agent-browser stay distinct', () => {
|
|
81
|
+
const gsdBrowser = BUNDLED_SKILL_TRIGGERS.find(entry => entry.skill === 'gsd-browser');
|
|
82
|
+
const agentBrowser = BUNDLED_SKILL_TRIGGERS.find(entry => entry.skill === 'agent-browser');
|
|
83
|
+
|
|
84
|
+
assert.ok(gsdBrowser, 'gsd-browser trigger should be registered');
|
|
85
|
+
assert.ok(agentBrowser, 'agent-browser trigger should be registered');
|
|
86
|
+
assert.notStrictEqual(gsdBrowser.trigger, agentBrowser.trigger);
|
|
87
|
+
});
|
|
@@ -164,17 +164,13 @@ describe("checkAutoStartAfterDiscuss Gate 1a (pending depth-verification gate)",
|
|
|
164
164
|
test("Gate 1a does NOT trip when the pending gate is for a DIFFERENT milestone", () => {
|
|
165
165
|
base = mkBase();
|
|
166
166
|
openDatabase(":memory:");
|
|
167
|
-
// status: "queued" so that Gate 1b downstream of Gate 1a fires its
|
|
168
|
-
// recovery notify ("context file exists but milestone is still queued") —
|
|
169
|
-
// observing that notify proves we advanced past Gate 1a. If Gate 1a
|
|
170
|
-
// wrongly tripped on the M999 gate it would `return false` immediately
|
|
171
|
-
// and Gate 1b would never run, so the notify would be absent.
|
|
172
167
|
insertMilestone({ id: "M001", title: "Pending Gate Test", status: "queued" });
|
|
173
168
|
|
|
174
169
|
cap = mkCapture();
|
|
175
170
|
setPendingAutoStart(base, {
|
|
176
171
|
basePath: base,
|
|
177
172
|
milestoneId: "M001",
|
|
173
|
+
startAuto: false,
|
|
178
174
|
ctx: mkCtx(cap),
|
|
179
175
|
pi: mkPi(cap),
|
|
180
176
|
});
|
|
@@ -182,21 +178,19 @@ describe("checkAutoStartAfterDiscuss Gate 1a (pending depth-verification gate)",
|
|
|
182
178
|
setPendingGate("depth_verification_M999_confirm", base);
|
|
183
179
|
|
|
184
180
|
const result = checkAutoStartAfterDiscuss();
|
|
185
|
-
assert.equal(result,
|
|
181
|
+
assert.equal(result, true, "different milestone gate must not block this handoff");
|
|
186
182
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const gate1bNotify = cap.notifies.find(n =>
|
|
190
|
-
n.level === "warning" && /M001.*context file exists but milestone is still queued/i.test(n.msg)
|
|
183
|
+
const successNotify = cap.notifies.find(n =>
|
|
184
|
+
n.level === "success" && /M001 context captured/i.test(n.msg)
|
|
191
185
|
);
|
|
192
186
|
assert.ok(
|
|
193
|
-
|
|
194
|
-
`expected
|
|
187
|
+
successNotify,
|
|
188
|
+
`expected context-captured success notify about M001; got: ${JSON.stringify(cap.notifies)}`,
|
|
195
189
|
);
|
|
196
190
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
191
|
+
const retryNotify = cap.notifies.find(n => /queued|gsd_plan_milestone/i.test(n.msg));
|
|
192
|
+
assert.equal(retryNotify, undefined, "handoff must not mention queued state or internal plan retry");
|
|
193
|
+
|
|
200
194
|
const m999Notify = cap.notifies.find(n => /M999/i.test(n.msg));
|
|
201
195
|
assert.equal(m999Notify, undefined, "no notify should reference M999 (the pending-gate milestone)");
|
|
202
196
|
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
// gsd-pi + Regression tests for checkAutoStartAfterDiscuss
|
|
1
|
+
// gsd-pi + Regression tests for checkAutoStartAfterDiscuss handoff copy (R3b)
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// is absent. Otherwise the user sees "ready" and then /gsd reports
|
|
6
|
-
// "No Active Milestone" because the milestone was never registered.
|
|
3
|
+
// Missing-row repair may accept a context handoff, but "Milestone X ready."
|
|
4
|
+
// is reserved for executable plans with persisted slices in DB mode.
|
|
7
5
|
|
|
8
6
|
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
9
7
|
import assert from "node:assert/strict";
|
|
@@ -21,6 +19,7 @@ import {
|
|
|
21
19
|
openDatabase,
|
|
22
20
|
closeDatabase,
|
|
23
21
|
insertMilestone,
|
|
22
|
+
insertSlice,
|
|
24
23
|
getMilestone,
|
|
25
24
|
} from "../gsd-db.ts";
|
|
26
25
|
import {
|
|
@@ -92,49 +91,60 @@ describe("checkAutoStartAfterDiscuss ready-notify DB guard (R3b)", () => {
|
|
|
92
91
|
}
|
|
93
92
|
});
|
|
94
93
|
|
|
95
|
-
test("
|
|
94
|
+
test("repairs a missing milestone DB row and accepts context-captured handoff", () => {
|
|
96
95
|
base = mkBase();
|
|
97
|
-
// Open a fresh in-memory DB but DO NOT insertMilestone for M001.
|
|
98
96
|
openDatabase(":memory:");
|
|
99
97
|
|
|
100
98
|
cap = mkCapture();
|
|
101
99
|
setPendingAutoStart(base, {
|
|
102
100
|
basePath: base,
|
|
103
101
|
milestoneId: "M001",
|
|
102
|
+
startAuto: false,
|
|
104
103
|
ctx: mkCtx(cap),
|
|
105
104
|
pi: mkPi(cap),
|
|
106
105
|
});
|
|
107
106
|
|
|
108
107
|
const result = checkAutoStartAfterDiscuss();
|
|
109
|
-
assert.equal(result,
|
|
108
|
+
assert.equal(result, true, "missing row with pinned context should repair and accept handoff");
|
|
110
109
|
|
|
111
|
-
// No success "ready" notify
|
|
112
110
|
const successReady = cap.notifies.find(
|
|
113
111
|
(n) => n.level === "success" && /ready\.?$/i.test(n.msg),
|
|
114
112
|
);
|
|
115
113
|
assert.equal(successReady, undefined, "must not announce 'ready' when DB row missing");
|
|
116
114
|
|
|
117
|
-
// When CONTEXT.md is on disk the R3b path recovers: it inserts a placeholder
|
|
118
|
-
// "queued" row (so Gate 1b can retry gsd_plan_milestone) and emits a warning.
|
|
119
115
|
const recovered = getMilestone("M001");
|
|
120
116
|
assert.ok(recovered, "R3b recovery must insert a placeholder 'queued' DB row");
|
|
121
117
|
assert.equal(recovered!.status, "queued", "placeholder row must have status 'queued'");
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
assert.equal(
|
|
120
|
+
cap.notifies.some(n => n.level === "warning"),
|
|
121
|
+
false,
|
|
122
|
+
"successful missing-row repair must not warn the user",
|
|
123
|
+
);
|
|
124
|
+
assert.deepEqual(cap.notifies, [
|
|
125
|
+
{
|
|
126
|
+
msg: "Milestone M001 context captured. Continuing the planning pipeline.",
|
|
127
|
+
level: "success",
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
127
130
|
});
|
|
128
131
|
|
|
129
|
-
test("announces 'ready' when DB row
|
|
132
|
+
test("announces 'ready' when DB row has executable slices", () => {
|
|
130
133
|
base = mkBase();
|
|
131
134
|
openDatabase(":memory:");
|
|
132
135
|
insertMilestone({ id: "M001", title: "Ready Guard Test", status: "active" });
|
|
136
|
+
insertSlice({
|
|
137
|
+
id: "S01",
|
|
138
|
+
milestoneId: "M001",
|
|
139
|
+
title: "Executable Slice",
|
|
140
|
+
status: "pending",
|
|
141
|
+
});
|
|
133
142
|
|
|
134
143
|
cap = mkCapture();
|
|
135
144
|
setPendingAutoStart(base, {
|
|
136
145
|
basePath: base,
|
|
137
146
|
milestoneId: "M001",
|
|
147
|
+
startAuto: false,
|
|
138
148
|
ctx: mkCtx(cap),
|
|
139
149
|
pi: mkPi(cap),
|
|
140
150
|
});
|
|
@@ -9,6 +9,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
9
9
|
import { handleGSDCommand } from "../commands/dispatcher.ts";
|
|
10
10
|
import {
|
|
11
11
|
closeDatabase,
|
|
12
|
+
insertAssessment,
|
|
12
13
|
insertMilestone,
|
|
13
14
|
insertSlice,
|
|
14
15
|
openDatabase,
|
|
@@ -106,6 +107,13 @@ function seedRegisteredCompletedWorktreeWithoutRoadmap(base: string): void {
|
|
|
106
107
|
title: "Live Text Search",
|
|
107
108
|
status: "complete",
|
|
108
109
|
});
|
|
110
|
+
insertAssessment({
|
|
111
|
+
path: "milestones/M008/M008-VALIDATION.md",
|
|
112
|
+
milestoneId: "M008",
|
|
113
|
+
status: "pass",
|
|
114
|
+
scope: "milestone-validation",
|
|
115
|
+
fullContent: "verdict: pass",
|
|
116
|
+
});
|
|
109
117
|
writeFileSync(
|
|
110
118
|
join(base, ".gsd", "PREFERENCES.md"),
|
|
111
119
|
"---\ngit:\n isolation: worktree\n---\n",
|
|
@@ -124,6 +132,19 @@ function seedRegisteredCompletedWorktree(base: string): void {
|
|
|
124
132
|
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
125
133
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
126
134
|
insertMilestone({ id: "M008", title: "Live Text Search", status: "complete" });
|
|
135
|
+
insertSlice({
|
|
136
|
+
id: "S01",
|
|
137
|
+
milestoneId: "M008",
|
|
138
|
+
title: "Live Text Search",
|
|
139
|
+
status: "complete",
|
|
140
|
+
});
|
|
141
|
+
insertAssessment({
|
|
142
|
+
path: "milestones/M008/M008-VALIDATION.md",
|
|
143
|
+
milestoneId: "M008",
|
|
144
|
+
status: "pass",
|
|
145
|
+
scope: "milestone-validation",
|
|
146
|
+
fullContent: "verdict: pass",
|
|
147
|
+
});
|
|
127
148
|
writeWorktreePreferencesAndRoadmap(base);
|
|
128
149
|
|
|
129
150
|
const worktreePath = join(base, ".gsd", "worktrees", "M008");
|
|
@@ -155,6 +155,124 @@ describe('complete-slice verification gate (#3580)', () => {
|
|
|
155
155
|
}
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
// ── Browser/web UAT classification gate (M001/S03 regression) ──────────
|
|
159
|
+
const BROWSER_UAT_BODY = [
|
|
160
|
+
'## UAT Type',
|
|
161
|
+
'- UAT mode: artifact-driven',
|
|
162
|
+
'',
|
|
163
|
+
'## Smoke Test',
|
|
164
|
+
'1. Open the page in a browser and perform add/edit/complete/delete once.',
|
|
165
|
+
].join('\n');
|
|
166
|
+
|
|
167
|
+
test('rejects an artifact-driven UAT that drives a browser (open the page in a browser)', async () => {
|
|
168
|
+
const result = await handleCompleteSlice(
|
|
169
|
+
makeParams({ uatContent: BROWSER_UAT_BODY }),
|
|
170
|
+
basePath,
|
|
171
|
+
);
|
|
172
|
+
assert.ok('error' in result, 'expected handler to reject a browser UAT mislabeled artifact-driven');
|
|
173
|
+
assert.match((result as { error: string }).error, /requires browser verification/i);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('allows a runtime-executable UAT that runs a browser test command (playwright)', async () => {
|
|
177
|
+
// Bugbot regression: runtime-executable legitimately drives a browser via a
|
|
178
|
+
// command captured by gsd_uat_exec — it must not be pushed to gsd-browser.
|
|
179
|
+
const body = [
|
|
180
|
+
'## UAT Type',
|
|
181
|
+
'- UAT mode: runtime-executable',
|
|
182
|
+
'',
|
|
183
|
+
'## Test Cases',
|
|
184
|
+
'1. Run `npx playwright test` and confirm a passing exit code; capture a screenshot artifact.',
|
|
185
|
+
'2. Hit http://localhost:3000/health and assert a 200 response.',
|
|
186
|
+
].join('\n');
|
|
187
|
+
const result = await handleCompleteSlice(
|
|
188
|
+
makeParams({ uatContent: body }),
|
|
189
|
+
basePath,
|
|
190
|
+
);
|
|
191
|
+
if ('error' in result) {
|
|
192
|
+
assert.doesNotMatch(
|
|
193
|
+
result.error,
|
|
194
|
+
/artifact-driven|browser-capable|browser verification/i,
|
|
195
|
+
`runtime-executable command UATs must not be gated, got: ${result.error}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('allows an artifact-driven UAT that only disclaims browser coverage (no false positive)', async () => {
|
|
201
|
+
// S01-style: genuinely artifact-driven persistence scaffolding that merely
|
|
202
|
+
// mentions "cross-browser" / "browser-level" in a Not-Proven disclaimer.
|
|
203
|
+
const body = [
|
|
204
|
+
'## UAT Type',
|
|
205
|
+
'- UAT mode: artifact-driven',
|
|
206
|
+
'',
|
|
207
|
+
'## Not Proven By This UAT',
|
|
208
|
+
'- Interactive browser-level CRUD and real cross-browser localStorage behavior.',
|
|
209
|
+
].join('\n');
|
|
210
|
+
const result = await handleCompleteSlice(
|
|
211
|
+
makeParams({ uatContent: body }),
|
|
212
|
+
basePath,
|
|
213
|
+
);
|
|
214
|
+
if ('error' in result) {
|
|
215
|
+
assert.doesNotMatch(
|
|
216
|
+
result.error,
|
|
217
|
+
/requires browser verification/i,
|
|
218
|
+
`disclaimer-only mention must not trip the browser gate, got: ${result.error}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('allows an artifact-driven UAT whose "navigate" step targets a file, not a browser', async () => {
|
|
224
|
+
// Bugbot regression: a bare "navigate to <file/API>" must not trip the gate
|
|
225
|
+
// just because it contains the word "navigate".
|
|
226
|
+
const body = [
|
|
227
|
+
'## UAT Type',
|
|
228
|
+
'- UAT mode: artifact-driven',
|
|
229
|
+
'',
|
|
230
|
+
'## Test Cases',
|
|
231
|
+
'1. Navigate to the generated report file and confirm the schema section exists.',
|
|
232
|
+
].join('\n');
|
|
233
|
+
const result = await handleCompleteSlice(
|
|
234
|
+
makeParams({ uatContent: body }),
|
|
235
|
+
basePath,
|
|
236
|
+
);
|
|
237
|
+
if ('error' in result) {
|
|
238
|
+
assert.doesNotMatch(
|
|
239
|
+
result.error,
|
|
240
|
+
/requires browser verification/i,
|
|
241
|
+
`non-web "navigate" must not trip the browser gate, got: ${result.error}`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('allows a browser UAT when it is declared browser-executable', async () => {
|
|
247
|
+
const body = BROWSER_UAT_BODY.replace('artifact-driven', 'browser-executable');
|
|
248
|
+
const result = await handleCompleteSlice(
|
|
249
|
+
makeParams({ uatContent: body }),
|
|
250
|
+
basePath,
|
|
251
|
+
);
|
|
252
|
+
if ('error' in result) {
|
|
253
|
+
assert.doesNotMatch(
|
|
254
|
+
result.error,
|
|
255
|
+
/requires browser verification/i,
|
|
256
|
+
`browser-executable UAT must pass the browser gate, got: ${result.error}`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('allows a browser UAT when it is declared mixed (mixed receives browser tools)', async () => {
|
|
262
|
+
const body = BROWSER_UAT_BODY.replace('artifact-driven', 'mixed (artifact-driven + browser)');
|
|
263
|
+
const result = await handleCompleteSlice(
|
|
264
|
+
makeParams({ uatContent: body }),
|
|
265
|
+
basePath,
|
|
266
|
+
);
|
|
267
|
+
if ('error' in result) {
|
|
268
|
+
assert.doesNotMatch(
|
|
269
|
+
result.error,
|
|
270
|
+
/requires browser verification/i,
|
|
271
|
+
`mixed UAT must pass the browser gate, got: ${result.error}`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
158
276
|
test('backfills prior verification narrative when verification is omitted on re-completion', async () => {
|
|
159
277
|
// Seed full_summary_md with a prior verification narrative (simulates a
|
|
160
278
|
// previous completion where the verification text was recorded).
|