@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
|
@@ -15,12 +15,27 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
|
|
18
|
+
/** Create a temp project root with a git repo for phase-function tests. */
|
|
19
|
+
function makeTestBase(prefix: string): string {
|
|
20
|
+
const base = mkdtempSync(join(tmpdir(), prefix));
|
|
21
|
+
execFileSync("git", ["init", "--initial-branch=main"], { cwd: base, stdio: "ignore" });
|
|
22
|
+
execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
|
|
23
|
+
execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
|
|
24
|
+
writeFileSync(join(base, "README.md"), "# test\n");
|
|
25
|
+
execFileSync("git", ["add", "README.md"], { cwd: base, stdio: "ignore" });
|
|
26
|
+
execFileSync("git", ["commit", "-m", "chore: seed"], { cwd: base, stdio: "ignore" });
|
|
27
|
+
return base;
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
import type { JournalEntry } from "../journal.js";
|
|
19
31
|
import type { LoopDeps } from "../auto/loop-deps.js";
|
|
20
32
|
import { WorktreeStateProjection } from "../worktree-state-projection.js";
|
|
21
33
|
import type { IterationContext, LoopState, PreDispatchData, IterationData } from "../auto/types.js";
|
|
22
34
|
import type { SessionLockStatus } from "../session-lock.js";
|
|
23
|
-
import { runDispatch
|
|
35
|
+
import { runDispatch } from "../auto/dispatch.js";
|
|
36
|
+
import { runUnitPhase } from "../auto/unit-phase.js";
|
|
37
|
+
import { runPreDispatch } from "../auto/pre-dispatch.js";
|
|
38
|
+
import { runFinalize } from "../auto/finalize.js";
|
|
24
39
|
import { readUnitRuntimeRecord } from "../unit-runtime.js";
|
|
25
40
|
import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
|
|
26
41
|
import {
|
|
@@ -182,7 +197,7 @@ function makeSession() {
|
|
|
182
197
|
verbose: false,
|
|
183
198
|
stepMode: false,
|
|
184
199
|
paused: false,
|
|
185
|
-
basePath: "
|
|
200
|
+
basePath: makeTestBase("gsd-journal-session-"),
|
|
186
201
|
originalBasePath: "",
|
|
187
202
|
currentMilestoneId: "M001",
|
|
188
203
|
currentUnit: null,
|
|
@@ -296,10 +311,18 @@ test("runDispatch emits dispatch-stop when dispatch returns stop action", async
|
|
|
296
311
|
assert.equal(stopEvents[0].flowId, ic.flowId);
|
|
297
312
|
});
|
|
298
313
|
|
|
299
|
-
test("runDispatch checks prior-slice completion against the project root in worktree mode", async () => {
|
|
314
|
+
test("runDispatch checks prior-slice completion against the project root in worktree mode", async (t) => {
|
|
300
315
|
const capture = createEventCapture();
|
|
301
316
|
const guardCalls: Array<{ fn: string; args: unknown[] }> = [];
|
|
317
|
+
const projectRoot = makeTestBase("gsd-wt-prior-slice-");
|
|
318
|
+
const milestoneId = "M029-xoklo9";
|
|
319
|
+
const worktreeRoot = join(projectRoot, ".gsd", "worktrees", milestoneId);
|
|
320
|
+
execFileSync("git", ["worktree", "add", "-b", `auto/${milestoneId}`, worktreeRoot], { cwd: projectRoot, stdio: "ignore" });
|
|
321
|
+
t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
|
|
322
|
+
|
|
302
323
|
const deps = makeMockDeps(capture, {
|
|
324
|
+
getIsolationMode: () => "worktree",
|
|
325
|
+
autoWorktreeBranch: (mid: string) => `auto/${mid}`,
|
|
303
326
|
getMainBranch: (basePath: string) => {
|
|
304
327
|
guardCalls.push({ fn: "getMainBranch", args: [basePath] });
|
|
305
328
|
return "main";
|
|
@@ -320,19 +343,21 @@ test("runDispatch checks prior-slice completion against the project root in work
|
|
|
320
343
|
const ic = makeIC(deps, {
|
|
321
344
|
s: {
|
|
322
345
|
...makeSession(),
|
|
323
|
-
basePath:
|
|
324
|
-
originalBasePath:
|
|
346
|
+
basePath: worktreeRoot,
|
|
347
|
+
originalBasePath: projectRoot,
|
|
348
|
+
canonicalProjectRoot: projectRoot,
|
|
349
|
+
currentMilestoneId: milestoneId,
|
|
325
350
|
} as any,
|
|
326
351
|
});
|
|
327
352
|
const preData: PreDispatchData = {
|
|
328
353
|
state: {
|
|
329
354
|
phase: "executing",
|
|
330
|
-
activeMilestone: { id:
|
|
355
|
+
activeMilestone: { id: milestoneId, title: "Test", status: "active" },
|
|
331
356
|
activeSlice: { id: "S01", title: "Slice 1" },
|
|
332
|
-
registry: [{ id:
|
|
357
|
+
registry: [{ id: milestoneId, status: "active" }],
|
|
333
358
|
blockers: [],
|
|
334
359
|
} as any,
|
|
335
|
-
mid:
|
|
360
|
+
mid: milestoneId,
|
|
336
361
|
midTitle: "Test Milestone",
|
|
337
362
|
};
|
|
338
363
|
|
|
@@ -342,12 +367,12 @@ test("runDispatch checks prior-slice completion against the project root in work
|
|
|
342
367
|
consecutiveFinalizeTimeouts: 0,
|
|
343
368
|
});
|
|
344
369
|
|
|
345
|
-
assert.equal(result.action, "next");
|
|
370
|
+
assert.equal(result.action, "next", "dispatch must proceed under worktree isolation");
|
|
346
371
|
assert.deepEqual(guardCalls, [
|
|
347
|
-
{ fn: "getMainBranch", args: [
|
|
372
|
+
{ fn: "getMainBranch", args: [projectRoot] },
|
|
348
373
|
{
|
|
349
374
|
fn: "getPriorSliceCompletionBlocker",
|
|
350
|
-
args: [
|
|
375
|
+
args: [projectRoot, "main", "execute-task", "M001/S01/T01"],
|
|
351
376
|
},
|
|
352
377
|
]);
|
|
353
378
|
});
|
|
@@ -434,6 +459,9 @@ test("runDispatch pauses when execute-task artifacts exist but DB status is stil
|
|
|
434
459
|
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
435
460
|
const tasksDir = join(sliceDir, "tasks");
|
|
436
461
|
mkdirSync(tasksDir, { recursive: true });
|
|
462
|
+
execFileSync("git", ["init", "--initial-branch=main"], { cwd: base, stdio: "ignore" });
|
|
463
|
+
execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
|
|
464
|
+
execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
|
|
437
465
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
438
466
|
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
439
467
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
|
|
@@ -517,6 +545,9 @@ test("runDispatch pauses at Level 2 when execute-task artifacts exist but DB sta
|
|
|
517
545
|
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
518
546
|
const tasksDir = join(sliceDir, "tasks");
|
|
519
547
|
mkdirSync(tasksDir, { recursive: true });
|
|
548
|
+
execFileSync("git", ["init", "--initial-branch=main"], { cwd: base, stdio: "ignore" });
|
|
549
|
+
execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
|
|
550
|
+
execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
|
|
520
551
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
521
552
|
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
522
553
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
|
|
@@ -888,7 +919,7 @@ test("runUnitPhase increments unitDispatchCount for repeated artifact-missing re
|
|
|
888
919
|
|
|
889
920
|
test("runUnitPhase pre-dispatch model validation failures do not emit unit-start or dispatch runtime state", async (t) => {
|
|
890
921
|
const capture = createEventCapture();
|
|
891
|
-
const base =
|
|
922
|
+
const base = makeTestBase(`gsd-pre-dispatch-block-${randomUUID()}`);
|
|
892
923
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
893
924
|
|
|
894
925
|
const deps = makeMockDeps(capture, {
|
|
@@ -1084,7 +1115,7 @@ test("terminal event is emitted on blocked state", async () => {
|
|
|
1084
1115
|
});
|
|
1085
1116
|
|
|
1086
1117
|
test("#4671: plan-v2 missing CONTEXT.md reaches dispatch recovery instead of pausing", async () => {
|
|
1087
|
-
const basePath =
|
|
1118
|
+
const basePath = makeTestBase("gsd-4671-predispatch-");
|
|
1088
1119
|
mkdirSync(join(basePath, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
|
|
1089
1120
|
openDatabase(join(basePath, ".gsd", "gsd.db"));
|
|
1090
1121
|
try {
|
|
@@ -1141,7 +1172,7 @@ test("#4671: plan-v2 missing CONTEXT.md reaches dispatch recovery instead of pau
|
|
|
1141
1172
|
});
|
|
1142
1173
|
|
|
1143
1174
|
test("plan-v2 empty graph rederives state before pausing", async () => {
|
|
1144
|
-
const basePath =
|
|
1175
|
+
const basePath = makeTestBase("gsd-plan-v2-empty-graph-");
|
|
1145
1176
|
mkdirSync(join(basePath, ".gsd", "milestones", "M001"), { recursive: true });
|
|
1146
1177
|
writeFileSync(
|
|
1147
1178
|
join(basePath, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
|
|
@@ -1204,7 +1235,7 @@ test("plan-v2 empty graph rederives state before pausing", async () => {
|
|
|
1204
1235
|
});
|
|
1205
1236
|
|
|
1206
1237
|
test("plan-v2 empty graph pauses after one failed rederive", async () => {
|
|
1207
|
-
const basePath =
|
|
1238
|
+
const basePath = makeTestBase("gsd-plan-v2-empty-graph-pause-");
|
|
1208
1239
|
mkdirSync(join(basePath, ".gsd", "milestones", "M001"), { recursive: true });
|
|
1209
1240
|
writeFileSync(
|
|
1210
1241
|
join(basePath, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
|
|
@@ -1394,7 +1425,7 @@ test("session-failed cancellations close out and emit unit-end before hard stop"
|
|
|
1394
1425
|
test("runFinalize pauses and emits unit-end when pre-verification times out", async () => {
|
|
1395
1426
|
const capture = createEventCapture();
|
|
1396
1427
|
let pauseCalls = 0;
|
|
1397
|
-
const basePath =
|
|
1428
|
+
const basePath = makeTestBase("gsd-finalize-timeout-");
|
|
1398
1429
|
|
|
1399
1430
|
const deps = makeMockDeps(capture, {
|
|
1400
1431
|
pauseAuto: async () => { pauseCalls++; },
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Tests for the MCP readiness preflight gate in dispatchWorkflow.
|
|
3
|
+
|
|
4
|
+
import { describe, test, mock } from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
11
|
+
|
|
12
|
+
import { _awaitWorkflowMcpReadinessForTest as awaitWorkflowMcpReadiness } from "../guided-flow.ts";
|
|
13
|
+
import { clearMcpConfigCache } from "../../mcp-client/manager.ts";
|
|
14
|
+
import { clearWorkflowMcpProbeCache } from "../workflow-mcp-readiness-cache.ts";
|
|
15
|
+
|
|
16
|
+
function makeCtx(opts: { authMode?: string; baseUrl?: string; systemPrompt?: string } = {}) {
|
|
17
|
+
const { authMode = "externalCli", baseUrl = "local://pipe", systemPrompt = "" } = opts;
|
|
18
|
+
return {
|
|
19
|
+
model: { provider: "anthropic", baseUrl },
|
|
20
|
+
modelRegistry: {
|
|
21
|
+
getProviderAuthMode: mock.fn(() => authMode),
|
|
22
|
+
},
|
|
23
|
+
getSystemPrompt: mock.fn(() => systemPrompt),
|
|
24
|
+
ui: {
|
|
25
|
+
setStatus: mock.fn(),
|
|
26
|
+
notify: mock.fn(),
|
|
27
|
+
},
|
|
28
|
+
} as any;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makePi(tools: string[] | (() => string[])) {
|
|
32
|
+
const getTools = typeof tools === "function" ? tools : () => tools;
|
|
33
|
+
return {
|
|
34
|
+
getActiveTools: getTools,
|
|
35
|
+
getAllTools: () => getTools().map((name: string) => ({ name })),
|
|
36
|
+
} as any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("awaitWorkflowMcpReadiness", () => {
|
|
40
|
+
test("returns null (ready) when not using MCP transport", async () => {
|
|
41
|
+
clearWorkflowMcpProbeCache();
|
|
42
|
+
const ctx = makeCtx({ authMode: "native", baseUrl: "https://api.anthropic.com" });
|
|
43
|
+
const pi = makePi([]);
|
|
44
|
+
assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("returns null (ready) when provider has no auth mode", async () => {
|
|
48
|
+
const ctx = {
|
|
49
|
+
model: undefined,
|
|
50
|
+
modelRegistry: { getProviderAuthMode: mock.fn(() => undefined) },
|
|
51
|
+
ui: { setStatus: mock.fn(), notify: mock.fn() },
|
|
52
|
+
} as any;
|
|
53
|
+
const pi = makePi([]);
|
|
54
|
+
assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("returns null (ready) when MCP tools are already registered", async () => {
|
|
58
|
+
const ctx = makeCtx();
|
|
59
|
+
const pi = makePi(["read", "bash", "mcp__gsd-workflow__ask_user_questions", "mcp__gsd-workflow__gsd_plan_slice"]);
|
|
60
|
+
assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("returns null (ready) when host system prompt advertises workflow MCP tools", async () => {
|
|
64
|
+
const originalCommand = process.env.GSD_WORKFLOW_MCP_COMMAND;
|
|
65
|
+
const ctx = makeCtx({
|
|
66
|
+
systemPrompt: "Available tools: read, bash, mcp__gsd-workflow__ask_user_questions",
|
|
67
|
+
});
|
|
68
|
+
const pi = makePi(["read", "bash"]);
|
|
69
|
+
try {
|
|
70
|
+
process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
|
|
71
|
+
assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
|
|
72
|
+
} finally {
|
|
73
|
+
if (originalCommand === undefined) {
|
|
74
|
+
delete process.env.GSD_WORKFLOW_MCP_COMMAND;
|
|
75
|
+
} else {
|
|
76
|
+
process.env.GSD_WORKFLOW_MCP_COMMAND = originalCommand;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("treats a successful direct probe as readiness when session tools are absent", async () => {
|
|
82
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
83
|
+
const projectDir = mkdtempSync(join(tmpdir(), "gsd-guided-mcp-probe-project-"));
|
|
84
|
+
const gsdHomeDir = mkdtempSync(join(tmpdir(), "gsd-guided-mcp-probe-home-"));
|
|
85
|
+
try {
|
|
86
|
+
process.env.GSD_HOME = gsdHomeDir;
|
|
87
|
+
mkdirSync(join(projectDir, ".gsd"), { recursive: true });
|
|
88
|
+
|
|
89
|
+
const require = createRequire(import.meta.url);
|
|
90
|
+
const mcpModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/mcp.js")).href;
|
|
91
|
+
const stdioModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/stdio.js")).href;
|
|
92
|
+
const serverPath = join(projectDir, "fake-workflow-mcp-server.mjs");
|
|
93
|
+
writeFileSync(
|
|
94
|
+
serverPath,
|
|
95
|
+
[
|
|
96
|
+
`const { McpServer } = await import(${JSON.stringify(mcpModuleUrl)});`,
|
|
97
|
+
`const { StdioServerTransport } = await import(${JSON.stringify(stdioModuleUrl)});`,
|
|
98
|
+
'const server = new McpServer({ name: "fake", version: "1.0.0" }, { capabilities: { tools: {} } });',
|
|
99
|
+
'server.tool("gsd_status", "Probe-visible workflow tool", {}, async () => ({ content: [{ type: "text", text: "ok" }] }));',
|
|
100
|
+
'await server.connect(new StdioServerTransport());',
|
|
101
|
+
].join("\n"),
|
|
102
|
+
"utf-8",
|
|
103
|
+
);
|
|
104
|
+
writeFileSync(
|
|
105
|
+
join(projectDir, ".mcp.json"),
|
|
106
|
+
JSON.stringify({ mcpServers: { "gsd-workflow": { command: process.execPath, args: [serverPath] } } }),
|
|
107
|
+
"utf-8",
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const ctx = makeCtx();
|
|
111
|
+
const pi = makePi(["read", "bash"]);
|
|
112
|
+
|
|
113
|
+
assert.equal(
|
|
114
|
+
await awaitWorkflowMcpReadiness(pi, ctx, projectDir, { timeoutMs: 15_000, pollMs: 100 }),
|
|
115
|
+
null,
|
|
116
|
+
);
|
|
117
|
+
} finally {
|
|
118
|
+
if (previousGsdHome === undefined) {
|
|
119
|
+
delete process.env.GSD_HOME;
|
|
120
|
+
} else {
|
|
121
|
+
process.env.GSD_HOME = previousGsdHome;
|
|
122
|
+
}
|
|
123
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
124
|
+
rmSync(gsdHomeDir, { recursive: true, force: true });
|
|
125
|
+
clearMcpConfigCache();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("returns server and probe error when workflow MCP never becomes ready", async () => {
|
|
130
|
+
const originalCommand = process.env.GSD_WORKFLOW_MCP_COMMAND;
|
|
131
|
+
const ctx = makeCtx();
|
|
132
|
+
const pi = makePi(["read", "bash"]);
|
|
133
|
+
try {
|
|
134
|
+
process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
|
|
135
|
+
const failure = await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path", {
|
|
136
|
+
timeoutMs: 5,
|
|
137
|
+
pollMs: 1,
|
|
138
|
+
probe: async () => ({
|
|
139
|
+
ok: false,
|
|
140
|
+
tools: [],
|
|
141
|
+
serverName: "gsd-workflow",
|
|
142
|
+
error: "connection refused",
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
assert.deepEqual(failure, { server: "gsd-workflow", error: "connection refused" });
|
|
146
|
+
assert.ok(ctx.ui.setStatus.mock.calls.length >= 2);
|
|
147
|
+
} finally {
|
|
148
|
+
if (originalCommand === undefined) {
|
|
149
|
+
delete process.env.GSD_WORKFLOW_MCP_COMMAND;
|
|
150
|
+
} else {
|
|
151
|
+
process.env.GSD_WORKFLOW_MCP_COMMAND = originalCommand;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("returns null (ready) when MCP tools appear during polling", async () => {
|
|
157
|
+
const ctx = makeCtx();
|
|
158
|
+
let callCount = 0;
|
|
159
|
+
const pi = makePi(() => {
|
|
160
|
+
callCount++;
|
|
161
|
+
if (callCount >= 3) return ["mcp__gsd-workflow__gsd_plan_slice"];
|
|
162
|
+
return ["read", "bash"];
|
|
163
|
+
});
|
|
164
|
+
assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
|
|
165
|
+
assert.ok(callCount >= 3, `expected at least 3 tool-check calls, got ${callCount}`);
|
|
166
|
+
assert.ok(ctx.ui.setStatus.mock.calls.length >= 2);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("requires the active unit's workflow tools, not just any MCP workflow tool", async () => {
|
|
170
|
+
const originalCommand = process.env.GSD_WORKFLOW_MCP_COMMAND;
|
|
171
|
+
const ctx = makeCtx();
|
|
172
|
+
const pi = makePi(["read", "bash", "mcp__gsd-workflow__gsd_slice_complete"]);
|
|
173
|
+
try {
|
|
174
|
+
process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
|
|
175
|
+
const failure = await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path", {
|
|
176
|
+
unitType: "complete-slice",
|
|
177
|
+
timeoutMs: 5,
|
|
178
|
+
pollMs: 1,
|
|
179
|
+
probe: async () => ({
|
|
180
|
+
ok: true,
|
|
181
|
+
tools: ["gsd_slice_complete"],
|
|
182
|
+
serverName: "gsd-workflow",
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
assert.deepEqual(failure, { server: "gsd-workflow" });
|
|
187
|
+
} finally {
|
|
188
|
+
if (originalCommand === undefined) {
|
|
189
|
+
delete process.env.GSD_WORKFLOW_MCP_COMMAND;
|
|
190
|
+
} else {
|
|
191
|
+
process.env.GSD_WORKFLOW_MCP_COMMAND = originalCommand;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("returns null when no launch config is discoverable", async () => {
|
|
197
|
+
const ctx = makeCtx();
|
|
198
|
+
const pi = makePi(["read", "bash"]);
|
|
199
|
+
const launch = (await import("../workflow-mcp.js")).detectWorkflowMcpLaunchConfig(process.cwd());
|
|
200
|
+
if (!launch) {
|
|
201
|
+
assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, process.cwd()), null);
|
|
202
|
+
}
|
|
203
|
+
// When launch config IS found, the function would timeout (15s) — skip that path in tests.
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -58,13 +58,14 @@ describe("formatMcpStatusReport", () => {
|
|
|
58
58
|
assert.match(result, /disabled/i);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
test("
|
|
61
|
+
test("labels probe-only availability separately from live connections", () => {
|
|
62
62
|
const servers: McpServerStatus[] = [
|
|
63
63
|
{ name: "gsd-workflow", transport: "stdio", connected: false, available: true, toolCount: 62, error: undefined },
|
|
64
64
|
];
|
|
65
65
|
const result = formatMcpStatusReport(servers);
|
|
66
66
|
assert.match(result, /gsd-workflow/);
|
|
67
|
-
assert.match(result, /available — 62 tools/);
|
|
67
|
+
assert.match(result, /probe available — 62 tools/);
|
|
68
|
+
assert.doesNotMatch(result, /connected — 62 tools/);
|
|
68
69
|
assert.doesNotMatch(result, /disconnected/);
|
|
69
70
|
});
|
|
70
71
|
|
|
@@ -132,7 +133,7 @@ describe("formatMcpServerDetail", () => {
|
|
|
132
133
|
assert.match(result, /disconnected/i);
|
|
133
134
|
});
|
|
134
135
|
|
|
135
|
-
test("shows available status with tool names", () => {
|
|
136
|
+
test("shows probe-available status with tool names", () => {
|
|
136
137
|
const result = formatMcpServerDetail({
|
|
137
138
|
name: "gsd-workflow",
|
|
138
139
|
transport: "stdio",
|
|
@@ -142,7 +143,7 @@ describe("formatMcpServerDetail", () => {
|
|
|
142
143
|
tools: ["gsd_milestone_status"],
|
|
143
144
|
error: undefined,
|
|
144
145
|
});
|
|
145
|
-
assert.match(result, /available/i);
|
|
146
|
+
assert.match(result, /probe available/i);
|
|
146
147
|
assert.match(result, /gsd_milestone_status/);
|
|
147
148
|
});
|
|
148
149
|
|
|
@@ -206,7 +207,7 @@ describe("handleMcpStatus", () => {
|
|
|
206
207
|
await handleMcpStatus("status", ctx as unknown as ExtensionCommandContext);
|
|
207
208
|
|
|
208
209
|
assert.match(message, /gsd-workflow/);
|
|
209
|
-
assert.match(message, /available — 1 tools/);
|
|
210
|
+
assert.match(message, /probe available — 1 tools/);
|
|
210
211
|
assert.doesNotMatch(message, /disconnected/);
|
|
211
212
|
} finally {
|
|
212
213
|
process.chdir(originalCwd);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
|
|
7
|
-
import { _runMilestoneMergeWithStashRestore } from "../auto/
|
|
7
|
+
import { _runMilestoneMergeWithStashRestore } from "../auto/closeout.js";
|
|
8
8
|
import type { IterationContext } from "../auto/types.js";
|
|
9
9
|
import { MergeConflictError } from "../git-service.js";
|
|
10
10
|
import type {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { describe, test } from "node:test";
|
|
11
11
|
import assert from "node:assert/strict";
|
|
12
12
|
|
|
13
|
-
import { _resolveDispatchGuardBasePath, _resolveReportBasePath } from "../auto/
|
|
13
|
+
import { _resolveDispatchGuardBasePath, _resolveReportBasePath } from "../auto/phase-helpers.ts";
|
|
14
14
|
|
|
15
15
|
describe("_resolveReportBasePath", () => {
|
|
16
16
|
test("uses originalBasePath when set (worktree scenario)", () => {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Regression tests for milestone closeout settlement guidance.
|
|
3
|
+
|
|
4
|
+
import test, { afterEach } from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { evaluateAllCompleteSettlement } from "../milestone-settlement.ts";
|
|
12
|
+
import { resolveExpectedArtifactPath } from "../auto-artifact-paths.ts";
|
|
13
|
+
import {
|
|
14
|
+
closeDatabase,
|
|
15
|
+
insertAssessment,
|
|
16
|
+
insertMilestone,
|
|
17
|
+
insertSlice,
|
|
18
|
+
insertTask,
|
|
19
|
+
openDatabase,
|
|
20
|
+
} from "../gsd-db.ts";
|
|
21
|
+
|
|
22
|
+
let base = "";
|
|
23
|
+
|
|
24
|
+
function runGit(cwd: string, args: string[]): void {
|
|
25
|
+
execFileSync("git", args, { cwd, stdio: "ignore" });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function initRepo(root: string): void {
|
|
29
|
+
runGit(root, ["init", "-b", "main"]);
|
|
30
|
+
runGit(root, ["config", "user.email", "test@example.com"]);
|
|
31
|
+
runGit(root, ["config", "user.name", "Test User"]);
|
|
32
|
+
writeFileSync(join(root, "README.md"), "# fixture\n");
|
|
33
|
+
runGit(root, ["add", "."]);
|
|
34
|
+
runGit(root, ["commit", "-m", "chore: init"]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function seedClosedMilestone(root: string, worktree: string): void {
|
|
38
|
+
mkdirSync(join(root, ".gsd"), { recursive: true });
|
|
39
|
+
openDatabase(join(root, ".gsd", "gsd.db"));
|
|
40
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "complete" });
|
|
41
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice One", status: "complete" });
|
|
42
|
+
insertTask({
|
|
43
|
+
id: "T01",
|
|
44
|
+
sliceId: "S01",
|
|
45
|
+
milestoneId: "M001",
|
|
46
|
+
title: "Task One",
|
|
47
|
+
status: "complete",
|
|
48
|
+
verificationResult: "passed",
|
|
49
|
+
});
|
|
50
|
+
insertAssessment({
|
|
51
|
+
path: ".gsd/milestones/M001/M001-VALIDATION.md",
|
|
52
|
+
milestoneId: "M001",
|
|
53
|
+
status: "pass",
|
|
54
|
+
scope: "milestone-validation",
|
|
55
|
+
fullContent: "verdict: pass\n",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
mkdirSync(join(worktree, ".gsd", "milestones", "M001"), { recursive: true });
|
|
59
|
+
const summaryPath = resolveExpectedArtifactPath("complete-milestone", "M001", worktree);
|
|
60
|
+
assert.ok(summaryPath, "complete-milestone summary path should resolve");
|
|
61
|
+
mkdirSync(dirname(summaryPath), { recursive: true });
|
|
62
|
+
writeFileSync(summaryPath, "# Milestone One\n\nComplete.\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
try { closeDatabase(); } catch { /* ignore */ }
|
|
67
|
+
if (base) rmSync(base, { recursive: true, force: true });
|
|
68
|
+
base = "";
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("merge-pending settlement routes back to complete-milestone dispatch without manual merge guidance", () => {
|
|
72
|
+
base = mkdtempSync(join(tmpdir(), "gsd-milestone-settlement-"));
|
|
73
|
+
initRepo(base);
|
|
74
|
+
const worktree = join(base, ".gsd", "worktrees", "M001");
|
|
75
|
+
mkdirSync(dirname(worktree), { recursive: true });
|
|
76
|
+
runGit(base, ["worktree", "add", "-b", "milestone/M001", worktree, "HEAD"]);
|
|
77
|
+
seedClosedMilestone(base, worktree);
|
|
78
|
+
|
|
79
|
+
const result = evaluateAllCompleteSettlement({
|
|
80
|
+
milestoneId: "M001",
|
|
81
|
+
statePhase: "complete",
|
|
82
|
+
basePath: worktree,
|
|
83
|
+
originalBasePath: base,
|
|
84
|
+
milestoneMerged: false,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
assert.equal(result.ok, false);
|
|
88
|
+
assert.equal(result.reason, "merge-pending");
|
|
89
|
+
assert.equal(result.nextAction, "Retry `/gsd dispatch complete-milestone M001`.");
|
|
90
|
+
assert.doesNotMatch(result.message, /merge manually/i);
|
|
91
|
+
assert.doesNotMatch(result.nextAction, /merge manually/i);
|
|
92
|
+
});
|
|
@@ -14,7 +14,7 @@ import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { AutoSession } from "../auto/session.ts";
|
|
17
|
-
import { runPreDispatch } from "../auto/
|
|
17
|
+
import { runPreDispatch } from "../auto/pre-dispatch.ts";
|
|
18
18
|
|
|
19
19
|
test("milestone transition archives completed units and rebuilds state", async () => {
|
|
20
20
|
const tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-cu-reset-")));
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
4
6
|
|
|
7
|
+
import { playQuestionBell } from "../../ask-user-questions.js";
|
|
8
|
+
import { stopAuto } from "../auto.js";
|
|
9
|
+
import { autoSession } from "../auto-runtime-state.js";
|
|
5
10
|
import {
|
|
6
11
|
buildDesktopNotificationCommand,
|
|
7
12
|
shouldSendDesktopNotification,
|
|
@@ -58,18 +63,68 @@ test("playNotificationBell is silent when disabled", () => {
|
|
|
58
63
|
assert.equal(output, "");
|
|
59
64
|
});
|
|
60
65
|
|
|
61
|
-
test("
|
|
62
|
-
|
|
66
|
+
test("playQuestionBell writes a terminal bell when local bell is enabled", async () => {
|
|
67
|
+
let output = "";
|
|
68
|
+
const stream = { write: (chunk: string) => { output += chunk; } };
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
await playQuestionBell({ enabled: true, local_bell: true }, stream);
|
|
71
|
+
|
|
72
|
+
assert.equal(output, "\u0007");
|
|
66
73
|
});
|
|
67
74
|
|
|
68
|
-
test("
|
|
69
|
-
|
|
75
|
+
test("playQuestionBell is silent when local bell is disabled", async () => {
|
|
76
|
+
let output = "";
|
|
77
|
+
const stream = { write: (chunk: string) => { output += chunk; } };
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
await playQuestionBell({ enabled: true, local_bell: false }, stream);
|
|
80
|
+
|
|
81
|
+
assert.equal(output, "");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("stopAuto plays local bell for auto-mode stop notifications", async () => {
|
|
85
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-stop-bell-"));
|
|
86
|
+
const previousCwd = process.cwd();
|
|
87
|
+
const previousStderrWrite = process.stderr.write;
|
|
88
|
+
const previousStderrIsTTY = process.stderr.isTTY;
|
|
89
|
+
let bellOutput = "";
|
|
90
|
+
|
|
91
|
+
autoSession.reset();
|
|
92
|
+
autoSession.active = true;
|
|
93
|
+
autoSession.basePath = base;
|
|
94
|
+
|
|
95
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
96
|
+
writeFileSync(
|
|
97
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
98
|
+
"---\nnotifications:\n enabled: true\n local_bell: true\n---\n",
|
|
99
|
+
"utf-8",
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
process.stderr.isTTY = true;
|
|
103
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
104
|
+
if (typeof chunk === "string") {
|
|
105
|
+
bellOutput += chunk;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}) as typeof process.stderr.write;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await stopAuto(
|
|
112
|
+
{ hasUI: false, ui: { notify: () => {}, setStatus: () => {}, setWidget: () => {}, setHeader: () => {} } } as any,
|
|
113
|
+
undefined,
|
|
114
|
+
"test stop",
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
assert.ok(
|
|
118
|
+
bellOutput.includes("\u0007"),
|
|
119
|
+
"stopAuto must write a terminal bell to stderr when the local bell preference is enabled",
|
|
120
|
+
);
|
|
121
|
+
} finally {
|
|
122
|
+
process.stderr.write = previousStderrWrite;
|
|
123
|
+
process.stderr.isTTY = previousStderrIsTTY;
|
|
124
|
+
autoSession.reset();
|
|
125
|
+
process.chdir(previousCwd);
|
|
126
|
+
rmSync(base, { recursive: true, force: true });
|
|
127
|
+
}
|
|
73
128
|
});
|
|
74
129
|
|
|
75
130
|
test("buildDesktopNotificationCommand falls back to osascript on macOS when terminal-notifier is absent", () => {
|
|
@@ -29,8 +29,8 @@ import {
|
|
|
29
29
|
|
|
30
30
|
const SKILL_NAME = "testskill";
|
|
31
31
|
const COMPLETE_SLICE_SKILL_NAME = "complete-slice-policies";
|
|
32
|
-
const SKILL_ACTIVATION_SUBSTRING = `
|
|
33
|
-
const COMPLETE_SLICE_SKILL_ACTIVATION_SUBSTRING = `
|
|
32
|
+
const SKILL_ACTIVATION_SUBSTRING = `Read the installed '${SKILL_NAME}' skill file from <available_skills>`;
|
|
33
|
+
const COMPLETE_SLICE_SKILL_ACTIVATION_SUBSTRING = `Read the installed '${COMPLETE_SLICE_SKILL_NAME}' skill file from <available_skills>`;
|
|
34
34
|
|
|
35
35
|
const tmpDirs: string[] = [];
|
|
36
36
|
let savedCwd: string | undefined;
|
|
@@ -28,6 +28,11 @@ const BANNED_DECISION_PATHS = new Set([
|
|
|
28
28
|
"gsd/auto-post-unit.ts",
|
|
29
29
|
"gsd/milestone-closeout.ts",
|
|
30
30
|
"gsd/auto/phases.ts",
|
|
31
|
+
"gsd/auto/pre-dispatch.ts",
|
|
32
|
+
"gsd/auto/dispatch.ts",
|
|
33
|
+
"gsd/auto/unit-phase.ts",
|
|
34
|
+
"gsd/auto/finalize.ts",
|
|
35
|
+
"gsd/auto/closeout.ts",
|
|
31
36
|
"gsd/auto/orchestrator.ts",
|
|
32
37
|
"gsd/auto/loop.ts",
|
|
33
38
|
"gsd/tools/complete-slice.ts",
|