@opengsd/gsd-pi 1.2.0-dev.9ad8ae33 → 1.2.0-dev.a6376d75
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/cli-model-override.d.ts +15 -0
- package/dist/cli-model-override.js +21 -0
- package/dist/cli.js +1 -18
- package/dist/loader.js +6 -4
- package/dist/register-agent-bundles.d.ts +11 -2
- package/dist/register-agent-bundles.js +18 -4
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/ask-user-questions.js +3 -2
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +447 -215
- package/dist/resources/extensions/claude-code-cli/turn-assembler.js +33 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +215 -0
- package/dist/resources/extensions/gsd/auto/dispatch-history.js +21 -6
- package/dist/resources/extensions/gsd/auto/dispatch.js +365 -0
- package/dist/resources/extensions/gsd/auto/finalize.js +347 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -1
- package/dist/resources/extensions/gsd/auto/milestone-lease-reclaim.js +56 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +85 -15
- package/dist/resources/extensions/gsd/auto/phase-helpers.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +17 -2372
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +534 -0
- package/dist/resources/extensions/gsd/auto/unit-phase.js +694 -0
- package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto/worktree-safety-phase.js +125 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +15 -1
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +37 -7
- package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +9 -2
- package/dist/resources/extensions/gsd/db/queries.js +30 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +256 -125
- package/dist/resources/extensions/gsd/guided-flow.js +88 -2
- package/dist/resources/extensions/gsd/health-widget.js +87 -28
- package/dist/resources/extensions/gsd/mcp-bridge.js +10 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +2 -2
- package/dist/resources/extensions/gsd/notifications.js +12 -7
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -1
- package/dist/resources/extensions/gsd/skill-activation.js +3 -6
- package/dist/resources/extensions/gsd/state.js +6 -2
- package/dist/resources/extensions/gsd/tool-surface-readiness.js +83 -31
- package/dist/resources/extensions/gsd/tools/complete-task.js +62 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
- package/dist/resources/extensions/gsd/unit-registry.js +34 -4
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-readiness-cache.js +105 -0
- package/dist/resources/extensions/gsd/worktree-safety.js +28 -26
- package/dist/resources/extensions/mcp-client/manager.js +6 -1
- package/dist/runtime-checks.d.ts +10 -0
- package/dist/runtime-checks.js +27 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +6 -4
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +8 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +50 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +34 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +12 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +4 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +12 -3
- package/packages/mcp-server/dist/cli-runner.d.ts +40 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -0
- package/packages/mcp-server/dist/cli-runner.js +137 -0
- package/packages/mcp-server/dist/cli-runner.js.map +1 -0
- package/packages/mcp-server/dist/cli.js +2 -58
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/pid-registry.d.ts +46 -0
- package/packages/mcp-server/dist/pid-registry.d.ts.map +1 -0
- package/packages/mcp-server/dist/pid-registry.js +452 -0
- package/packages/mcp-server/dist/pid-registry.js.map +1 -0
- package/packages/mcp-server/dist/probe-mode.d.ts +4 -0
- package/packages/mcp-server/dist/probe-mode.d.ts.map +1 -0
- package/packages/mcp-server/dist/probe-mode.js +10 -0
- package/packages/mcp-server/dist/probe-mode.js.map +1 -0
- package/packages/mcp-server/dist/stdio-watchdog.d.ts +8 -0
- package/packages/mcp-server/dist/stdio-watchdog.d.ts.map +1 -0
- package/packages/mcp-server/dist/stdio-watchdog.js +40 -0
- package/packages/mcp-server/dist/stdio-watchdog.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +62 -43
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -5
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +43 -2
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +45 -17
- package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/index.d.ts +1 -1
- package/packages/pi-tui/dist/index.d.ts.map +1 -1
- package/packages/pi-tui/dist/index.js +1 -1
- package/packages/pi-tui/dist/index.js.map +1 -1
- package/packages/pi-tui/dist/terminal-image.d.ts +33 -0
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +54 -2
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts +8 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +63 -18
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +110 -36
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +45 -17
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +7 -2
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +531 -226
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +672 -7
- package/src/resources/extensions/claude-code-cli/turn-assembler.ts +38 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +309 -0
- package/src/resources/extensions/gsd/auto/dispatch-history.ts +22 -6
- package/src/resources/extensions/gsd/auto/dispatch.ts +449 -0
- package/src/resources/extensions/gsd/auto/finalize.ts +445 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -1
- package/src/resources/extensions/gsd/auto/milestone-lease-reclaim.ts +74 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +95 -15
- package/src/resources/extensions/gsd/auto/phase-helpers.ts +199 -0
- package/src/resources/extensions/gsd/auto/phases.ts +58 -3061
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +704 -0
- package/src/resources/extensions/gsd/auto/unit-phase.ts +910 -0
- package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto/worktree-safety-phase.ts +149 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +20 -1
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +56 -6
- package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +11 -4
- package/src/resources/extensions/gsd/db/queries.ts +29 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +267 -142
- package/src/resources/extensions/gsd/guided-flow.ts +128 -2
- package/src/resources/extensions/gsd/health-widget.ts +91 -27
- package/src/resources/extensions/gsd/mcp-bridge.ts +39 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +2 -2
- package/src/resources/extensions/gsd/notifications.ts +13 -6
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -1
- package/src/resources/extensions/gsd/skill-activation.ts +3 -6
- package/src/resources/extensions/gsd/state.ts +7 -1
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-blocked-remediation-message.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +206 -22
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +76 -12
- package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +77 -1
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +169 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +141 -5
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +8 -0
- package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +117 -91
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-environment-async.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +47 -16
- package/src/resources/extensions/gsd/tests/mcp-readiness-preflight.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-settlement.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/notifications.test.ts +64 -9
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/remote-notification-from-desktop.test.ts +31 -81
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -17
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +184 -10
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-readiness-cache.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +65 -2
- package/src/resources/extensions/gsd/tests/workflow-phase-contract-matrix.test.ts +332 -0
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety-phase.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +72 -0
- package/src/resources/extensions/gsd/tool-surface-readiness.ts +126 -19
- package/src/resources/extensions/gsd/tools/complete-task.ts +87 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
- package/src/resources/extensions/gsd/unit-registry.ts +34 -4
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-readiness-cache.ts +150 -0
- package/src/resources/extensions/gsd/worktree-safety.ts +41 -39
- package/src/resources/extensions/mcp-client/manager.ts +7 -1
- /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_ssgManifest.js +0 -0
|
@@ -18,12 +18,12 @@ function readSrc(file: string): string {
|
|
|
18
18
|
return readFileSync(join(gsdDir, file), "utf-8");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
test("#4620: auto/
|
|
22
|
-
const src = readSrc("auto/
|
|
21
|
+
test("#4620: auto/pre-dispatch plan gate failed message includes doctor heal hint", () => {
|
|
22
|
+
const src = readSrc("auto/pre-dispatch.ts");
|
|
23
23
|
assert.match(
|
|
24
24
|
src,
|
|
25
25
|
/Plan gate failed-closed:[\s\S]*\/gsd doctor heal/,
|
|
26
|
-
"auto/
|
|
26
|
+
"auto/pre-dispatch.ts should include /gsd doctor heal in plan gate failed notification",
|
|
27
27
|
);
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -55,14 +55,20 @@ const PHASE_PROMPT_TOOL_CALLS: Record<string, readonly string[]> = {
|
|
|
55
55
|
"plan-slice": ["gsd_reassess_roadmap", "gsd_plan_slice", "gsd_decision_save"],
|
|
56
56
|
"refine-slice": ["gsd_plan_slice", "gsd_decision_save"],
|
|
57
57
|
"replan-slice": ["gsd_replan_slice"],
|
|
58
|
-
"execute-task": [
|
|
58
|
+
"execute-task": [
|
|
59
|
+
"gsd_task_complete",
|
|
60
|
+
"gsd_exec",
|
|
61
|
+
"gsd_exec_search",
|
|
62
|
+
"gsd_resume",
|
|
63
|
+
"gsd_capture_thought",
|
|
64
|
+
],
|
|
59
65
|
"reactive-execute": ["gsd_summary_save"],
|
|
60
66
|
"complete-slice": [
|
|
61
67
|
"gsd_exec",
|
|
62
68
|
"gsd_task_reopen",
|
|
63
69
|
"gsd_replan_slice",
|
|
64
70
|
"gsd_requirement_update",
|
|
65
|
-
"
|
|
71
|
+
"gsd_capture_thought",
|
|
66
72
|
"gsd_slice_complete",
|
|
67
73
|
"gsd_summary_save",
|
|
68
74
|
],
|
|
@@ -240,6 +246,8 @@ test("workflow-start prompt defaults to autonomy instead of per-phase confirmati
|
|
|
240
246
|
const prompt = readPrompt("workflow-start");
|
|
241
247
|
assert.match(prompt, /Keep moving by default/i);
|
|
242
248
|
assert.match(prompt, /Decision gates, not ceremony/i);
|
|
249
|
+
assert.match(prompt, /Persist workflow state/i);
|
|
250
|
+
assert.match(prompt, /completedAt/i);
|
|
243
251
|
assert.doesNotMatch(prompt, /confirm with the user before proceeding/i);
|
|
244
252
|
assert.doesNotMatch(prompt, /Gate between phases/i);
|
|
245
253
|
});
|
|
@@ -17,10 +17,8 @@ import {
|
|
|
17
17
|
shouldDeferTransientErrorToCoreRetry,
|
|
18
18
|
suppressTerminalDeletedWorktreeMessageEnd,
|
|
19
19
|
} from "../bootstrap/agent-end-recovery.ts";
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
_classifyZeroToolProviderMessageForTest,
|
|
23
|
-
} from "../auto/phases.ts";
|
|
20
|
+
import { _buildCancelledUnitStopReason } from "../auto/phase-helpers.ts";
|
|
21
|
+
import { _classifyZeroToolProviderMessageForTest } from "../auto/unit-phase.ts";
|
|
24
22
|
import { autoSession } from "../auto-runtime-state.ts";
|
|
25
23
|
import { getNextFallbackModel } from "../preferences.ts";
|
|
26
24
|
// Zero-import module — imported by path rather than through the package
|
|
@@ -5,103 +5,53 @@
|
|
|
5
5
|
* as a fire-and-forget side-effect so that Telegram/Slack/Discord channels
|
|
6
6
|
* receive the same events as native desktop notifications.
|
|
7
7
|
*
|
|
8
|
-
* Testing strategy (
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 2. The sendDesktopNotification function body calls sendRemoteNotification
|
|
14
|
-
* with title and message as arguments.
|
|
15
|
-
* 3. The call uses the void fire-and-forget pattern with a .catch(() => {})
|
|
16
|
-
* suppressor so that async failures never break the synchronous caller.
|
|
8
|
+
* Testing strategy (behavioral):
|
|
9
|
+
* Import sendDesktopNotification and the swappable remoteNotificationDispatcher.
|
|
10
|
+
* Mock the dispatcher's send method, call sendDesktopNotification, and assert
|
|
11
|
+
* that the dispatcher received the same title/message and that the call is
|
|
12
|
+
* fire-and-forget (the function returns without awaiting).
|
|
17
13
|
*
|
|
18
14
|
* Relates to #4341.
|
|
19
15
|
*/
|
|
20
16
|
|
|
21
17
|
import test from "node:test";
|
|
22
18
|
import assert from "node:assert/strict";
|
|
23
|
-
import { readFileSync } from "node:fs";
|
|
24
|
-
import { join, dirname } from "node:path";
|
|
25
|
-
import { fileURLToPath } from "node:url";
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
import {
|
|
21
|
+
sendDesktopNotification,
|
|
22
|
+
remoteNotificationDispatcher,
|
|
23
|
+
} from "../notifications.js";
|
|
29
24
|
|
|
30
|
-
test("
|
|
31
|
-
const
|
|
32
|
-
SOURCE.includes('from "../remote-questions/notify.js"') ||
|
|
33
|
-
SOURCE.includes("from '../remote-questions/notify.js'");
|
|
34
|
-
assert.ok(
|
|
35
|
-
hasImport,
|
|
36
|
-
"notifications.ts must import from '../remote-questions/notify.js'",
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const importLine = SOURCE.split("\n").find(
|
|
40
|
-
(line) =>
|
|
41
|
-
line.includes("sendRemoteNotification") &&
|
|
42
|
-
(line.includes("remote-questions/notify") || line.includes("remote-questions/notify.js")),
|
|
43
|
-
);
|
|
44
|
-
assert.ok(
|
|
45
|
-
importLine,
|
|
46
|
-
"The import statement must include sendRemoteNotification from the remote-questions/notify module",
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("sendDesktopNotification calls sendRemoteNotification(title, message)", () => {
|
|
51
|
-
// Extract the body of sendDesktopNotification — from its opening brace to the
|
|
52
|
-
// closing brace at the same indentation level.
|
|
53
|
-
const fnStart = SOURCE.indexOf("export function sendDesktopNotification(");
|
|
54
|
-
assert.ok(fnStart > -1, "sendDesktopNotification must be present in notifications.ts");
|
|
25
|
+
test("sendDesktopNotification calls sendRemoteNotification with title and message", async (t) => {
|
|
26
|
+
const sendMock = t.mock.method(remoteNotificationDispatcher, "send", async () => {});
|
|
55
27
|
|
|
56
|
-
|
|
57
|
-
const nextExportIdx = SOURCE.indexOf("\nexport ", fnStart + 1);
|
|
58
|
-
const fnBody = nextExportIdx > -1 ? SOURCE.slice(fnStart, nextExportIdx) : SOURCE.slice(fnStart);
|
|
59
|
-
|
|
60
|
-
assert.ok(
|
|
61
|
-
fnBody.includes("sendRemoteNotification"),
|
|
62
|
-
"sendDesktopNotification must call sendRemoteNotification",
|
|
63
|
-
);
|
|
28
|
+
sendDesktopNotification("Test Title", "Test Message");
|
|
64
29
|
|
|
65
|
-
assert.
|
|
66
|
-
|
|
67
|
-
"sendRemoteNotification must be called with title as first argument",
|
|
68
|
-
);
|
|
30
|
+
assert.equal(sendMock.mock.callCount(), 1);
|
|
31
|
+
assert.deepEqual(sendMock.mock.calls[0].arguments, ["Test Title", "Test Message"]);
|
|
69
32
|
});
|
|
70
33
|
|
|
71
|
-
test("
|
|
72
|
-
const
|
|
73
|
-
const nextExportIdx = SOURCE.indexOf("\nexport ", fnStart + 1);
|
|
74
|
-
const fnBody = nextExportIdx > -1 ? SOURCE.slice(fnStart, nextExportIdx) : SOURCE.slice(fnStart);
|
|
34
|
+
test("sendDesktopNotification does not await the remote notification", async (t) => {
|
|
35
|
+
const sendMock = t.mock.method(remoteNotificationDispatcher, "send", async () => {});
|
|
75
36
|
|
|
76
|
-
|
|
77
|
-
fnBody.includes("void sendRemoteNotification("),
|
|
78
|
-
"sendRemoteNotification must be called with void (fire-and-forget)",
|
|
79
|
-
);
|
|
37
|
+
const result = sendDesktopNotification("Async Title", "Async Message");
|
|
80
38
|
|
|
81
|
-
assert.
|
|
82
|
-
|
|
83
|
-
"sendRemoteNotification call must be followed by .catch() to suppress unhandled-rejection warnings",
|
|
84
|
-
);
|
|
39
|
+
assert.equal(result, undefined, "sendDesktopNotification must return void");
|
|
40
|
+
assert.equal(sendMock.mock.callCount(), 1);
|
|
85
41
|
});
|
|
86
42
|
|
|
87
|
-
test("
|
|
88
|
-
|
|
89
|
-
// gated behind the desktop-notification preference check. Users who disable
|
|
90
|
-
// desktop notifications must still receive Telegram/Slack/Discord messages.
|
|
91
|
-
const fnStart = SOURCE.indexOf("export function sendDesktopNotification(");
|
|
92
|
-
assert.ok(fnStart > -1, "sendDesktopNotification must be present in notifications.ts");
|
|
93
|
-
|
|
94
|
-
const nextExportIdx = SOURCE.indexOf("\nexport ", fnStart + 1);
|
|
95
|
-
const fnBody = nextExportIdx > -1 ? SOURCE.slice(fnStart, nextExportIdx) : SOURCE.slice(fnStart);
|
|
43
|
+
test("sendDesktopNotification fires remote notification even when desktop notifications are disabled", async (t) => {
|
|
44
|
+
const sendMock = t.mock.method(remoteNotificationDispatcher, "send", async () => {});
|
|
96
45
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
remoteCallIdx < guardIdx,
|
|
105
|
-
`sendRemoteNotification (pos ${remoteCallIdx}) must appear BEFORE the shouldSendDesktopNotification guard (pos ${guardIdx}) so that remote channels fire even when desktop notifications are disabled`,
|
|
46
|
+
sendDesktopNotification(
|
|
47
|
+
"Remote Title",
|
|
48
|
+
"Remote Message",
|
|
49
|
+
"info",
|
|
50
|
+
"complete",
|
|
51
|
+
undefined,
|
|
52
|
+
{ notifications: { enabled: false } },
|
|
106
53
|
);
|
|
54
|
+
|
|
55
|
+
assert.equal(sendMock.mock.callCount(), 1);
|
|
56
|
+
assert.deepEqual(sendMock.mock.calls[0].arguments, ["Remote Title", "Remote Message"]);
|
|
107
57
|
});
|
|
@@ -67,7 +67,13 @@ test("Tool Contract compiles known Unit prompt and tool policy", () => {
|
|
|
67
67
|
|
|
68
68
|
assert.equal(result.ok, true);
|
|
69
69
|
assert.equal(result.ok && result.contract.unitType, "execute-task");
|
|
70
|
-
assert.deepEqual(result.ok && result.contract.requiredWorkflowTools, [
|
|
70
|
+
assert.deepEqual(result.ok && result.contract.requiredWorkflowTools, [
|
|
71
|
+
"gsd_task_complete",
|
|
72
|
+
"gsd_exec",
|
|
73
|
+
"gsd_exec_search",
|
|
74
|
+
"gsd_resume",
|
|
75
|
+
"gsd_capture_thought",
|
|
76
|
+
]);
|
|
71
77
|
assert.deepEqual(result.ok && result.contract.forbiddenWorkflowTools, []);
|
|
72
78
|
assert.equal(result.ok && result.contract.toolsPolicy.mode, "all");
|
|
73
79
|
assert.ok(result.ok && result.contract.validationRules.includes("closeout-tool-present"));
|
|
@@ -92,14 +92,14 @@ test("buildSkillActivationBlock activates skills via prefer_skills when context
|
|
|
92
92
|
prefer_skills: ["react"],
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
assert.match(result, /
|
|
95
|
+
assert.match(result, /Read the installed 'react' skill file from <available_skills>/);
|
|
96
96
|
assert.doesNotMatch(result, /swiftui/);
|
|
97
97
|
} finally {
|
|
98
98
|
cleanup(base);
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
test("buildSkillActivationBlock includes always_use_skills
|
|
102
|
+
test("buildSkillActivationBlock includes always_use_skills using read-based skill loading", () => {
|
|
103
103
|
const base = makeTempBase();
|
|
104
104
|
try {
|
|
105
105
|
writeSkill(base, "swift-testing", "Use for Swift Testing assertions and verification patterns.");
|
|
@@ -109,7 +109,10 @@ test("buildSkillActivationBlock includes always_use_skills from preferences usin
|
|
|
109
109
|
always_use_skills: ["swift-testing"],
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
assert.equal(
|
|
112
|
+
assert.equal(
|
|
113
|
+
result,
|
|
114
|
+
"<skill_activation>Read the installed 'swift-testing' skill file from <available_skills>.</skill_activation>",
|
|
115
|
+
);
|
|
113
116
|
} finally {
|
|
114
117
|
cleanup(base);
|
|
115
118
|
}
|
|
@@ -137,8 +140,8 @@ test("buildSkillActivationBlock includes skill_rules matches and task-plan skill
|
|
|
137
140
|
skill_rules: [{ when: "prisma database schema", use: ["prisma"] }],
|
|
138
141
|
});
|
|
139
142
|
|
|
140
|
-
assert.match(result, /
|
|
141
|
-
assert.match(result, /
|
|
143
|
+
assert.match(result, /Read the installed 'accessibility' skill file from <available_skills>/);
|
|
144
|
+
assert.match(result, /Read the installed 'prisma' skill file from <available_skills>/);
|
|
142
145
|
} finally {
|
|
143
146
|
cleanup(base);
|
|
144
147
|
}
|
|
@@ -160,7 +163,7 @@ test("buildSkillActivationBlock matches skill_rules against exact unit type cont
|
|
|
160
163
|
],
|
|
161
164
|
});
|
|
162
165
|
|
|
163
|
-
assert.match(result, /
|
|
166
|
+
assert.match(result, /Read the installed 'complete-slice-policies' skill file from <available_skills>/);
|
|
164
167
|
assert.doesNotMatch(result, /slice-broad/);
|
|
165
168
|
} finally {
|
|
166
169
|
cleanup(base);
|
|
@@ -264,8 +267,8 @@ test("buildSkillActivationBlock allows valid skill names and rejects invalid one
|
|
|
264
267
|
});
|
|
265
268
|
|
|
266
269
|
assert.match(result, /skill_activation/);
|
|
267
|
-
assert.match(result, /
|
|
268
|
-
assert.match(result, /
|
|
270
|
+
assert.match(result, /Read the installed 'react' skill file from <available_skills>/);
|
|
271
|
+
assert.match(result, /Read the installed 'good-skill-2' skill file from <available_skills>/);
|
|
269
272
|
assert.doesNotMatch(result, /bad'name/);
|
|
270
273
|
} finally {
|
|
271
274
|
cleanup(base);
|
|
@@ -289,8 +292,8 @@ test("buildSkillActivationBlock: explicit always_use_skills bypass the unit-type
|
|
|
289
292
|
always_use_skills: ["write-docs", "swiftui"],
|
|
290
293
|
});
|
|
291
294
|
|
|
292
|
-
assert.match(result, /
|
|
293
|
-
assert.match(result, /
|
|
295
|
+
assert.match(result, /Read the installed 'write-docs' skill file from <available_skills>/);
|
|
296
|
+
assert.match(result, /Read the installed 'swiftui' skill file from <available_skills>/);
|
|
294
297
|
} finally {
|
|
295
298
|
cleanup(base);
|
|
296
299
|
}
|
|
@@ -307,7 +310,7 @@ test("buildSkillActivationBlock falls through to all skills for unknown unit typ
|
|
|
307
310
|
});
|
|
308
311
|
|
|
309
312
|
// Unknown unit type = wildcard fallback (pre-manifest behavior).
|
|
310
|
-
assert.match(result, /
|
|
313
|
+
assert.match(result, /Read the installed 'swiftui' skill file from <available_skills>/);
|
|
311
314
|
} finally {
|
|
312
315
|
cleanup(base);
|
|
313
316
|
}
|
|
@@ -324,7 +327,7 @@ test("buildSkillActivationBlock without unitType preserves pre-manifest behavior
|
|
|
324
327
|
always_use_skills: ["swiftui"],
|
|
325
328
|
});
|
|
326
329
|
|
|
327
|
-
assert.match(result, /
|
|
330
|
+
assert.match(result, /Read the installed 'swiftui' skill file from <available_skills>/);
|
|
328
331
|
} finally {
|
|
329
332
|
cleanup(base);
|
|
330
333
|
}
|
|
@@ -341,12 +344,12 @@ test("milestone prompt builders propagate always_use_skills through buildSkillAc
|
|
|
341
344
|
loadOnlyTestSkills(base);
|
|
342
345
|
|
|
343
346
|
const researchPrompt = await buildResearchMilestonePrompt("M001", "Test", base);
|
|
344
|
-
assert.match(researchPrompt, /
|
|
345
|
-
assert.match(researchPrompt, /
|
|
347
|
+
assert.match(researchPrompt, /Read the installed 'write-docs' skill file from <available_skills>/);
|
|
348
|
+
assert.match(researchPrompt, /Read the installed 'swiftui' skill file from <available_skills>/);
|
|
346
349
|
|
|
347
350
|
const planPrompt = await buildPlanMilestonePrompt("M001", "Test", base);
|
|
348
|
-
assert.match(planPrompt, /
|
|
349
|
-
assert.match(planPrompt, /
|
|
351
|
+
assert.match(planPrompt, /Read the installed 'write-docs' skill file from <available_skills>/);
|
|
352
|
+
assert.match(planPrompt, /Read the installed 'swiftui' skill file from <available_skills>/);
|
|
350
353
|
} finally {
|
|
351
354
|
cleanup(base);
|
|
352
355
|
}
|
|
@@ -377,7 +380,7 @@ test("complete-slice prompt propagates always_use_skills through buildSkillActiv
|
|
|
377
380
|
|
|
378
381
|
const prompt = await buildCompleteSlicePrompt("M001", "Test", "S01", "Slice", base);
|
|
379
382
|
|
|
380
|
-
assert.match(prompt, /
|
|
383
|
+
assert.match(prompt, /Read the installed 'write-docs' skill file from <available_skills>/);
|
|
381
384
|
} finally {
|
|
382
385
|
cleanup(base);
|
|
383
386
|
}
|
|
@@ -339,7 +339,7 @@ test("resume path only hard-exits on blocked stop, not blocked pause (#6154)", (
|
|
|
339
339
|
);
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
-
test("prepareForUnit
|
|
342
|
+
test("prepareForUnit enforces worktree safety for all isolation modes (#6154)", () => {
|
|
343
343
|
const orchSrc = readGsdFile("auto/orchestrator.ts");
|
|
344
344
|
const prepareForUnitIdx = orchSrc.indexOf("private async prepareWorktreeForUnit(");
|
|
345
345
|
const prepareForUnitBody = orchSrc.slice(prepareForUnitIdx, orchSrc.indexOf("private classifyAndRecover(", prepareForUnitIdx));
|
|
@@ -350,8 +350,12 @@ test("prepareForUnit skips worktree safety when isolation is not worktree (#6154
|
|
|
350
350
|
"prepareForUnit should resolve the effective isolation mode once",
|
|
351
351
|
);
|
|
352
352
|
assert.ok(
|
|
353
|
-
prepareForUnitBody.includes('
|
|
354
|
-
"prepareForUnit should
|
|
353
|
+
prepareForUnitBody.includes('const writeScope ='),
|
|
354
|
+
"prepareForUnit should classify the unit's write scope before validating",
|
|
355
|
+
);
|
|
356
|
+
assert.ok(
|
|
357
|
+
!prepareForUnitBody.includes('if (isolationMode !== "worktree")'),
|
|
358
|
+
"prepareForUnit must not bypass worktree safety outside worktree isolation mode",
|
|
355
359
|
);
|
|
356
360
|
});
|
|
357
361
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
5
|
|
|
6
|
-
import { _resolveCurrentUnitStartedAtForTest } from "../auto/
|
|
6
|
+
import { _resolveCurrentUnitStartedAtForTest } from "../auto/phase-helpers.ts";
|
|
7
7
|
|
|
8
8
|
test("unit started-at resolver tolerates stopAuto clearing currentUnit", () => {
|
|
9
9
|
assert.equal(_resolveCurrentUnitStartedAtForTest(null), undefined);
|
|
@@ -365,13 +365,14 @@ test("buildMinimalAutoGsdToolSet includes closeout tool for complete-slice", ()
|
|
|
365
365
|
"gsd_complete_slice",
|
|
366
366
|
"memory_query",
|
|
367
367
|
"capture_thought",
|
|
368
|
+
"gsd_capture_thought",
|
|
368
369
|
], "complete-slice");
|
|
369
370
|
|
|
370
371
|
assert.ok(result.includes("gsd_slice_complete"));
|
|
371
372
|
assert.ok(result.includes("gsd_task_reopen"));
|
|
372
373
|
assert.ok(result.includes("gsd_replan_slice"));
|
|
373
374
|
assert.ok(result.includes("subagent"));
|
|
374
|
-
assert.ok(result.includes("
|
|
375
|
+
assert.ok(result.includes("gsd_capture_thought"));
|
|
375
376
|
assert.ok(!result.includes("gsd_task_complete"));
|
|
376
377
|
assert.ok(!result.includes("gsd_complete_slice"));
|
|
377
378
|
});
|
|
@@ -387,6 +388,7 @@ test("buildMinimalAutoGsdToolSet preserves workflow MCP-namespaced closeout tool
|
|
|
387
388
|
"mcp__gsd-workflow__gsd_exec",
|
|
388
389
|
"mcp__gsd-workflow__memory_query",
|
|
389
390
|
"mcp__gsd-workflow__capture_thought",
|
|
391
|
+
"mcp__gsd-workflow__gsd_capture_thought",
|
|
390
392
|
], "complete-slice");
|
|
391
393
|
|
|
392
394
|
assert.ok(result.includes("mcp__gsd-workflow__gsd_task_reopen"));
|
|
@@ -395,7 +397,7 @@ test("buildMinimalAutoGsdToolSet preserves workflow MCP-namespaced closeout tool
|
|
|
395
397
|
assert.ok(!result.includes("mcp__gsd-workflow__gsd_complete_slice"));
|
|
396
398
|
assert.ok(result.includes("mcp__gsd-workflow__gsd_exec"));
|
|
397
399
|
assert.ok(result.includes("mcp__gsd-workflow__memory_query"));
|
|
398
|
-
assert.ok(result.includes("mcp__gsd-
|
|
400
|
+
assert.ok(result.includes("mcp__gsd-workflow__gsd_capture_thought"));
|
|
399
401
|
});
|
|
400
402
|
|
|
401
403
|
test("buildMinimalAutoGsdToolSet covers execute-task-simple", () => {
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import { describe, test } from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
|
|
7
|
-
import { getToolSurfaceReadinessError } from "../tool-surface-readiness.ts";
|
|
7
|
+
import { getToolSurfaceReadinessError, awaitWorkflowMcpToolRegistration } from "../tool-surface-readiness.ts";
|
|
8
|
+
import { clearWorkflowMcpProbeCache, recordWorkflowMcpProbe } from "../workflow-mcp-readiness-cache.ts";
|
|
8
9
|
import { isToolUnavailableError } from "../auto-tool-tracking.ts";
|
|
9
10
|
import { classifyError, isTransient } from "../error-classifier.ts";
|
|
10
11
|
import { toMcpToolName } from "../mcp-tool-name.ts";
|
|
@@ -58,18 +59,82 @@ describe("getToolSurfaceReadinessError", () => {
|
|
|
58
59
|
assert.match(error, /gsd_uat_exec/);
|
|
59
60
|
});
|
|
60
61
|
|
|
61
|
-
test("
|
|
62
|
-
// The SDK reports still-connecting servers as "pending" at init — the
|
|
63
|
-
// common healthy session. A genuine miss after pass-through is caught
|
|
64
|
-
// in-session ("No such tool available" → tool-unavailable → retry).
|
|
62
|
+
test("still blocks pending init when required tools are absent from the live surface", () => {
|
|
65
63
|
const error = getToolSurfaceReadinessError({
|
|
66
|
-
unitType: "
|
|
64
|
+
unitType: "run-uat",
|
|
67
65
|
workflowServerName: SERVER,
|
|
68
66
|
observation: { tools: ["read", "bash"], mcpServers: [{ name: SERVER, status: "pending" }] },
|
|
69
67
|
});
|
|
68
|
+
assert.ok(error);
|
|
69
|
+
assert.match(error!, /status is "pending"/);
|
|
70
|
+
assert.match(error!, /gsd_uat_exec/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("accepts pending server status when the live init surface already contains every required tool", () => {
|
|
74
|
+
const error = getToolSurfaceReadinessError({
|
|
75
|
+
unitType: "run-uat",
|
|
76
|
+
workflowServerName: SERVER,
|
|
77
|
+
observation: {
|
|
78
|
+
tools: [
|
|
79
|
+
prefixed("gsd_uat_exec"),
|
|
80
|
+
prefixed("gsd_uat_result_save"),
|
|
81
|
+
prefixed("gsd_resume"),
|
|
82
|
+
prefixed("gsd_milestone_status"),
|
|
83
|
+
prefixed("gsd_journal_query"),
|
|
84
|
+
],
|
|
85
|
+
mcpServers: [{ name: SERVER, status: "pending" }],
|
|
86
|
+
},
|
|
87
|
+
});
|
|
70
88
|
assert.equal(error, null);
|
|
71
89
|
});
|
|
72
90
|
|
|
91
|
+
test("does not accept a pending live init surface just because a probe cache covers required tools", () => {
|
|
92
|
+
clearWorkflowMcpProbeCache();
|
|
93
|
+
const projectRoot = "/tmp/project-discuss-probe";
|
|
94
|
+
recordWorkflowMcpProbe(projectRoot, SERVER, [
|
|
95
|
+
"ask_user_questions",
|
|
96
|
+
"gsd_summary_save",
|
|
97
|
+
"gsd_requirement_save",
|
|
98
|
+
"gsd_requirement_update",
|
|
99
|
+
"gsd_plan_milestone",
|
|
100
|
+
"gsd_milestone_generate_id",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const error = getToolSurfaceReadinessError({
|
|
104
|
+
unitType: "discuss-milestone",
|
|
105
|
+
workflowServerName: SERVER,
|
|
106
|
+
projectRoot,
|
|
107
|
+
observation: {
|
|
108
|
+
tools: [],
|
|
109
|
+
mcpServers: [{ name: SERVER, status: "pending" }],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
assert.ok(error, "expected live init tools to be authoritative over direct probe cache");
|
|
114
|
+
assert.match(error, /status is "pending"/);
|
|
115
|
+
assert.match(error, /ask_user_questions/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("pending server status reports only required tools missing from the live init surface", () => {
|
|
119
|
+
const error = getToolSurfaceReadinessError({
|
|
120
|
+
unitType: "run-uat",
|
|
121
|
+
workflowServerName: SERVER,
|
|
122
|
+
observation: {
|
|
123
|
+
tools: [
|
|
124
|
+
prefixed("gsd_uat_result_save"),
|
|
125
|
+
prefixed("gsd_resume"),
|
|
126
|
+
prefixed("gsd_milestone_status"),
|
|
127
|
+
prefixed("gsd_journal_query"),
|
|
128
|
+
],
|
|
129
|
+
mcpServers: [{ name: SERVER, status: "pending" }],
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
assert.ok(error, "expected a readiness error while gsd_uat_exec is still absent");
|
|
133
|
+
assert.match(error, /status is "pending"/);
|
|
134
|
+
assert.match(error, /gsd_uat_exec/);
|
|
135
|
+
assert.doesNotMatch(error, /gsd_uat_result_save/);
|
|
136
|
+
});
|
|
137
|
+
|
|
73
138
|
test("returns null when all required tools are registered under the MCP prefix", () => {
|
|
74
139
|
const error = getToolSurfaceReadinessError({
|
|
75
140
|
unitType: "run-uat",
|
|
@@ -82,7 +147,7 @@ describe("getToolSurfaceReadinessError", () => {
|
|
|
82
147
|
assert.equal(error, null);
|
|
83
148
|
});
|
|
84
149
|
|
|
85
|
-
test("reports the failed server
|
|
150
|
+
test("reports the failed server as terminal", () => {
|
|
86
151
|
const error = getToolSurfaceReadinessError({
|
|
87
152
|
unitType: "run-uat",
|
|
88
153
|
workflowServerName: SERVER,
|
|
@@ -90,7 +155,20 @@ describe("getToolSurfaceReadinessError", () => {
|
|
|
90
155
|
});
|
|
91
156
|
assert.ok(error, "expected a readiness error");
|
|
92
157
|
assert.match(error, /workflow tool surface not ready for run-uat/);
|
|
93
|
-
assert.match(error, /
|
|
158
|
+
assert.match(error, /terminal/);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("reports terminal status even when required tools are already on the init surface", () => {
|
|
162
|
+
const error = getToolSurfaceReadinessError({
|
|
163
|
+
unitType: "run-uat",
|
|
164
|
+
workflowServerName: SERVER,
|
|
165
|
+
observation: {
|
|
166
|
+
tools: RUN_UAT_TOOLS.map(prefixed),
|
|
167
|
+
mcpServers: [{ name: SERVER, status: "failed" }],
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
assert.ok(error, "expected a readiness error despite tools on the surface");
|
|
171
|
+
assert.match(error, /terminal/);
|
|
94
172
|
assert.match(error, /gsd_uat_exec/);
|
|
95
173
|
});
|
|
96
174
|
|
|
@@ -101,7 +179,7 @@ describe("getToolSurfaceReadinessError", () => {
|
|
|
101
179
|
observation: { tools: ["read", "bash"], mcpServers: [{ name: SERVER, status: "needs-auth" }] },
|
|
102
180
|
});
|
|
103
181
|
assert.ok(error, "expected a readiness error for needs-auth");
|
|
104
|
-
assert.match(error, /
|
|
182
|
+
assert.match(error, /terminal/);
|
|
105
183
|
});
|
|
106
184
|
|
|
107
185
|
test("aborts on disabled (terminal — cannot self-heal)", () => {
|
|
@@ -111,7 +189,7 @@ describe("getToolSurfaceReadinessError", () => {
|
|
|
111
189
|
observation: { tools: ["read", "bash"], mcpServers: [{ name: SERVER, status: "disabled" }] },
|
|
112
190
|
});
|
|
113
191
|
assert.ok(error, "expected a readiness error for disabled");
|
|
114
|
-
assert.match(error, /
|
|
192
|
+
assert.match(error, /terminal/);
|
|
115
193
|
});
|
|
116
194
|
|
|
117
195
|
test("reports partially-registered surfaces even when the server says connected", () => {
|
|
@@ -128,6 +206,102 @@ describe("getToolSurfaceReadinessError", () => {
|
|
|
128
206
|
assert.match(error, /gsd_uat_result_save/);
|
|
129
207
|
assert.doesNotMatch(error, /gsd_uat_exec,/);
|
|
130
208
|
});
|
|
209
|
+
|
|
210
|
+
test("reports the screenshot case: result save registered but UAT exec missing", () => {
|
|
211
|
+
const error = getToolSurfaceReadinessError({
|
|
212
|
+
unitType: "run-uat",
|
|
213
|
+
workflowServerName: SERVER,
|
|
214
|
+
observation: {
|
|
215
|
+
tools: [
|
|
216
|
+
prefixed("gsd_uat_result_save"),
|
|
217
|
+
prefixed("gsd_resume"),
|
|
218
|
+
prefixed("gsd_milestone_status"),
|
|
219
|
+
prefixed("gsd_journal_query"),
|
|
220
|
+
],
|
|
221
|
+
mcpServers: [{ name: SERVER, status: "connected" }],
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
assert.ok(error, "expected a readiness error when gsd_uat_exec is absent");
|
|
225
|
+
assert.match(error, /connected but has not registered/);
|
|
226
|
+
assert.match(error, /gsd_uat_exec/);
|
|
227
|
+
assert.doesNotMatch(error, /gsd_uat_result_save/);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("awaitWorkflowMcpToolRegistration", () => {
|
|
232
|
+
test("does not skip live probe when cache already covers required tools", async () => {
|
|
233
|
+
clearWorkflowMcpProbeCache();
|
|
234
|
+
const { recordWorkflowMcpProbe } = await import("../workflow-mcp-readiness-cache.ts");
|
|
235
|
+
recordWorkflowMcpProbe("/tmp/project-cache-hit", SERVER, RUN_UAT_TOOLS);
|
|
236
|
+
|
|
237
|
+
let probeCalls = 0;
|
|
238
|
+
const error = await awaitWorkflowMcpToolRegistration({
|
|
239
|
+
unitType: "run-uat",
|
|
240
|
+
workflowServerName: SERVER,
|
|
241
|
+
projectRoot: "/tmp/project-cache-hit",
|
|
242
|
+
timeoutMs: 1,
|
|
243
|
+
pollMs: 1,
|
|
244
|
+
probe: async () => {
|
|
245
|
+
probeCalls += 1;
|
|
246
|
+
return { ok: true, tools: RUN_UAT_TOOLS };
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
assert.equal(error, null);
|
|
250
|
+
assert.ok(probeCalls > 0, "preflight must probe the live MCP server even when cache is warm");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("resolves when probe reports required tools", async () => {
|
|
254
|
+
clearWorkflowMcpProbeCache();
|
|
255
|
+
const error = await awaitWorkflowMcpToolRegistration({
|
|
256
|
+
unitType: "run-uat",
|
|
257
|
+
workflowServerName: SERVER,
|
|
258
|
+
projectRoot: "/tmp/project",
|
|
259
|
+
timeoutMs: 1_000,
|
|
260
|
+
pollMs: 1,
|
|
261
|
+
probe: async () => ({
|
|
262
|
+
ok: true,
|
|
263
|
+
tools: RUN_UAT_TOOLS,
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
assert.equal(error, null);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("times out when required tools never register", async () => {
|
|
270
|
+
clearWorkflowMcpProbeCache();
|
|
271
|
+
const error = await awaitWorkflowMcpToolRegistration({
|
|
272
|
+
unitType: "run-uat",
|
|
273
|
+
workflowServerName: SERVER,
|
|
274
|
+
projectRoot: "/tmp/project",
|
|
275
|
+
timeoutMs: 5,
|
|
276
|
+
pollMs: 1,
|
|
277
|
+
probe: async () => ({ ok: true, tools: ["gsd_uat_result_save"] }),
|
|
278
|
+
});
|
|
279
|
+
assert.ok(error);
|
|
280
|
+
assert.match(error!, /did not register required tools before session start/);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("aborts while waiting for workflow MCP tools", async () => {
|
|
284
|
+
clearWorkflowMcpProbeCache();
|
|
285
|
+
const controller = new AbortController();
|
|
286
|
+
let probeCount = 0;
|
|
287
|
+
const wait = awaitWorkflowMcpToolRegistration({
|
|
288
|
+
unitType: "run-uat",
|
|
289
|
+
workflowServerName: SERVER,
|
|
290
|
+
projectRoot: "/tmp/project-abort",
|
|
291
|
+
timeoutMs: 10_000,
|
|
292
|
+
pollMs: 10_000,
|
|
293
|
+
signal: controller.signal,
|
|
294
|
+
probe: async () => {
|
|
295
|
+
probeCount += 1;
|
|
296
|
+
return { ok: true, tools: ["gsd_uat_result_save"] };
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
controller.abort();
|
|
301
|
+
|
|
302
|
+
await assert.rejects(wait, /AbortError/);
|
|
303
|
+
assert.equal(probeCount, 1);
|
|
304
|
+
});
|
|
131
305
|
});
|
|
132
306
|
|
|
133
307
|
describe("readiness error classification contract", () => {
|