When GSD is installed with `bun add -g`, running `gsd update` or
`/gsd update` previously shelled out to `npm install -g`, which fails
with EACCES on systems where npm has no write access to the global
node_modules directory.
Adds `resolveInstallCommand(pkg)` to `update-check.ts` that returns
`bun add -g <pkg>` when `process.versions.bun` is defined (i.e. the
current runtime is Bun), and `npm install -g <pkg>` otherwise. All
three update paths — `update-cmd.ts`, `commands-handlers.ts`, and the
interactive startup prompt in `update-check.ts` — now use this helper,
including the fallback error message shown to the user.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CI on #4141 failed because threading an explicit flatRateCtx parameter
through resolvePreferredModelConfig broke two contracts the test suite
locks in:
1. interactive-routing-bypass (#3962) asserts that
resolvePreferredModelConfig is invoked with exactly three positional
arguments and that its `if (!isAutoMode) return undefined` guard
lives within the first 600 chars of the function body. The new
flatRateCtx param + JSDoc pushed the guard past that window and
lengthened the call site.
2. silent-catch-diagnostics (#3348) requires migrated files to route
through workflow-logger instead of leaving empty catch blocks. The
new buildFlatRateContext() swallowed registry lookup errors with a
comment-only catch.
Fix both without regressing flat-rate detection:
- Hang the flat-rate context off autoModeStartModel itself via an
optional `flatRateCtx` field. selectAndApplyModel now enriches
autoModeStartModel up front (preserving the variable name) and
resolvePreferredModelConfig reads autoModeStartModel.flatRateCtx —
signature shrinks back to three params, call site returns to the
3-arg form the test anchors on.
- Replace the empty catch in buildFlatRateContext() with a
logWarning("dispatch", ...) that surfaces the lookup failure while
still falling through with authMode undefined, matching the
fail-closed policy everywhere else in the file.
The 3-entry hard-coded FLAT_RATE_PROVIDERS set in auto-model-selection.ts
treated only github-copilot/copilot/claude-code as flat-rate, so dynamic
routing would happily downgrade units on user-registered subscription
proxies and any externalCli CLI wrapper — quality loss with no cost
benefit for users whose provider charges a flat rate per request.
Make isFlatRateProvider extensible by composing three signals:
1. Built-in list (unchanged, wins first for regression safety).
2. externalCli auto-detection via ctx.modelRegistry.getProviderAuthMode()
— any CLI wrapper around the user's subscription is inherently
flat-rate.
3. User-declared `flat_rate_providers` preference for private
subscription-backed proxies, enterprise-gated deployments, and custom
CLI wrappers the built-in list doesn't know about.
Add a buildFlatRateContext() helper so every call site constructs the
context the same way and degrades gracefully when ctx/prefs/registry are
unavailable (never breaks flat-rate detection).
Thread the context through:
- resolvePreferredModelConfig (routing synthesis guard)
- selectAndApplyModel primary-model and fallback provider checks
- auto-start.ts dynamic-routing banner so the startup message matches
dispatch-time reality
Preferences:
- Add `flat_rate_providers?: string[]` to GSDPreferences and
KNOWN_PREFERENCE_KEYS in preferences-types.ts.
- Add a string-array validator in preferences-validation.ts that trims
whitespace and drops empty entries.
Tests:
- Extend flat-rate-routing-guard.test.ts with 13 new cases covering
externalCli auto-detection, userFlatRate preference matching
(case-insensitive), combined signals, buildFlatRateContext() behavior
(including registry-lookup-throws and non-canonical auth-mode
responses), plus regression cases for the built-in list.
- Add 5 validator cases in preferences.test.ts for the new
flat_rate_providers field (string-array accepted, whitespace trimmed,
non-array rejected, non-string elements rejected, known-key warning
check).
When a user picks a custom-provider model via /gsd model (Ollama, vLLM,
LM Studio, OpenAI-compatible proxies — anything defined in
~/.gsd/agent/models.json) and then runs /gsd auto, the bootstrap silently
swaps it out for whichever model PREFERENCES.md happens to list. That
model is invariably a built-in provider (claude-code, anthropic) the user
isn't logged into, so auto-mode immediately fails with
"Not logged in · Please run /login", pauses, and resets the session to
claude-code/claude-sonnet-4-6.
Root cause: #3517 made resolveDefaultSessionModel() (PREFERENCES.md) take
priority over ctx.model (settings.json) in auto-start.ts. That fix was
correct for the scenario where settings.json had a stale built-in default
but PREFERENCES.md was freshly configured, but it has no awareness of
custom providers — PREFERENCES.md cannot reference them, so honoring it
when the session provider is custom always discards the user's explicit
choice.
Add isCustomProvider() to preferences-models.ts which checks whether a
provider is declared in ~/.gsd/agent/models.json (with ~/.pi/agent
fallback). Read the file directly with JSON.parse to avoid pulling in
the model-registry at this call site, and treat any read or parse error
as not-custom so a malformed models.json never breaks bootstrap.
In bootstrapAutoSession(), when the session provider is custom, use
ctx.model directly. Otherwise fall through to the existing #3517
behavior (preferredModel ?? ctx.model).
Tests:
- New behavioral regression in model-isolation.test.ts that mirrors
the auto-start.ts logic and verifies the four interesting cases:
custom session beats PREFERENCES.md, built-in session still defers
to PREFERENCES.md (#3517 preserved), custom session with no
PREFERENCES.md uses ctx.model, and null ctx.model falls through.
- New string-grep guard in auto-start-model-capture.test.ts that the
isCustomProvider() call is wired into the snapshot path.
- Updated #3517 grep to allow the new branching shape while still
asserting preferredModel remains a snapshot source for built-ins.
https://claude.ai/code/session_01QLYCeiXWjSFPEXFxjkSLni
* fix(ci): address 5 pipeline integrity issues from release audit
- version-stamp.mjs: regenerate package-lock.json after dev version stamp
(mirrors the same fix applied to bump-version.mjs in #4116)
- bump-version.mjs: regenerate root and web/package-lock.json after version
bump so both lockfiles are always in sync at release time
- pipeline.yml: add post-bump validation step that verifies all package.json
files parse as valid JSON before the release commit is made
- pipeline.yml: split "Commit, tag, and push" — commit+tag+rebase happen
before build, but git push is deferred until after build and npm publish
both succeed, preventing a broken tag from landing on main
- pipeline.yml: emit a :⚠️: annotation when live LLM tests fail so
failures are visible in the Actions UI instead of silently swallowed
Closes#4118
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(gsd): address 3 silent-crash secondary issues from #3348 post-#3696
Three gaps that remained after the double-fault fix in #3696:
1. unhandledRejection not wired — installEpipeGuard only registered
uncaughtException; promise rejections that escaped without a catch
were not handled by the GSD error path. Added _gsdRejectionGuard
alongside _gsdEpipeGuard.
2. Non-fatal overcorrection — the #3696 fix replaced re-throwing with
log-and-continue, leaving the process running in an indeterminate
state after any non-EPIPE/non-ENOENT exception. Replaced with
writeCrashLog + process.exit(1). writeCrashLog is extracted into
bootstrap/crash-log.ts (zero deps) so tests can import it without
pulling in the full extension graph.
3. unit-end not emitted after crash-with-side-effects — hameltomor
observed that complete-milestone M001 wrote SUMMARY.md and updated
the DB but never emitted unit-end (#3348 comment-4237533440). Added
emitCrashRecoveredUnitEnd() in crash-recovery.ts: on the next
auto-mode startup, if a stale lock references a unit whose
unit-start has no matching unit-end in the journal, a synthetic
unit-end with status "crash-recovered" is emitted before the lock
is cleared. This closes the causal chain for downstream tooling
and forensics without requiring changes to the lock file schema.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
reactive_execution.subagent_model was validated and stored but never
passed to the prompt builders that generate subagent dispatch instructions.
The executing agent therefore autonomously chose its default model instead
of the configured preference.
- buildReactiveExecutePrompt: add subagentModel? param, inject into
instruction string; auto-dispatch passes reactiveConfig.subagent_model
with fallback to resolveModelWithFallbacksForUnit("subagent")
- buildParallelResearchSlicesPrompt: same pattern, resolves from
models.subagent preference
- buildGateEvaluatePrompt: same pattern
- system-context: inject configured subagent model into system prompt
so the executing agent always knows which model to use for subagents
Closes#4078
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 0 of #3631 — remove dead code before screaming architecture reorg.
- auto-observability.ts (72 LOC): zero imports anywhere in codebase
- rtk-status.ts (53 LOC): zero imports anywhere in codebase
- file-watcher.ts (100 LOC): zero imports anywhere in codebase
- file-watcher.test.ts: test for dead file-watcher.ts
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(gsd): reconcile stale slice rows and rebuild STATE.md before DB close
Two coupled defects caused auto-mode split-brain where dispatch falsely
reported "No slice eligible" while STATE.md showed executable work:
1. deriveStateFromDb() reconciled missing slice rows but not stale
existing ones. A slice with status "pending" in the DB but a SUMMARY
file on disk was never repaired, permanently blocking downstream
slices. Added slice-level stale reconciliation matching the existing
task-level pattern.
2. stopAuto() closed the DB before rebuilding STATE.md, forcing
deriveState() into filesystem fallback mode. Moved rebuildState()
before closeDatabase() so stop-time STATE.md uses the same
authoritative DB backend as dispatch.
Fixes#3599
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression test for stale slice row reconciliation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(gsd): block direct writes to gsd.db via hooks to prevent corruption
When gsd_complete_task tool was unavailable, agents fell back to shell-
based sqlite3/sql.js writes to .gsd/gsd.db, corrupting the WAL-backed
database.
Extend write-intercept to block:
- File writes to gsd.db, gsd.db-wal, gsd.db-shm
- Bash commands using sqlite3/sql.js/better-sqlite3 targeting gsd.db
- Shell redirects/cp/mv targeting gsd.db
Closes#3625
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression test for blocking direct gsd.db writes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 of #3631 — eliminate circular imports before screaming arch reorg.
Cycle 1 (auto.ts ↔ auto-direct-dispatch.ts):
Remove redundant re-export of dispatchDirectPhase from auto.ts.
No consumer imported it through auto.ts.
Cycle 2 (context-injector.ts ↔ custom-workflow-engine.ts):
Extract readFrozenDefinition to new definition-io.ts.
context-injector now imports from definition-io directly.
Cycle 3 (preferences.ts ↔ preferences-skills.ts):
Move formatSkillRef to preferences-types.ts (pure fn, depends only on
SkillResolution which is already there).
Move resolveSkillDiscoveryMode + resolveSkillStalenessDays into
preferences.ts (trivial wrappers over loadEffectiveGSDPreferences).
Tests: new definition-io.test.ts (3 tests), preferences-formatting.test.ts
(6 tests covering all formatSkillRef branches).
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The first pass at #4099 only pre-authorized `mcp__<server>__*` tools, but
in `acceptEdits` mode the SDK still gates Read, Write, Glob/Grep, and
basic shell inspection commands like `ls`. GSD subagents need the full
workflow toolset and were still hitting "This command requires approval"
prompts on every tool call.
Two changes:
1. `resolveClaudePermissionMode` now returns `bypassPermissions` for all
GSD subagent runs (auto + interactive), dropping the `acceptEdits`
branch and the `isAutoActive` dynamic import. The host Claude Code
session's permission model is the user-visible gate; the inner SDK
process re-prompting on every tool was approval fatigue with no net
safety benefit. `GSD_CLAUDE_CODE_PERMISSION_MODE` env override stays
so security-conscious users can opt back into a stricter mode.
2. Expanded the pre-authorized `allowedTools` list to include Read,
Write, Edit, Glob, Grep, `Bash(ls:*)`, and `Bash(pwd)` alongside the
MCP server globs. Acts as a belt-and-suspenders safety net for users
who set the env override to `acceptEdits`.
Tradeoff documented inline: bypass means a prompt-injection payload read
from an untrusted file could trigger tool calls without a second gate.
Accepted because the workflow is explicit user intent and the
alternative is continuous approval fatigue that blocks real work.
Tests updated for the new allowedTools shape; permission-mode tests
already accepted bypass as the default.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(gsd): add memory pressure watchdog and persist stuck detection state
Two architectural improvements to auto-mode resilience:
1. Memory pressure monitoring (#3331): checks heap usage every 5
iterations and triggers graceful shutdown at 85% of V8 heap limit,
preventing OOM SIGKILL after long-running sessions.
2. Stuck detection persistence (#3704): saves loopState (recentUnits,
stuckRecoveryAttempts) to .gsd/runtime/stuck-state.json so counters
survive session restarts. Previously, restarting auto-mode reset all
stuck detection, allowing the same blocked unit to burn a full retry
budget each session.
Closes#3331Closes#3704
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use valid LogComponent 'dispatch' instead of 'autoLoop'
* fix: replace empty catch blocks with debug logging in auto/loop.ts
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(state): prevent false degraded-mode warning when DB not yet initialized
deriveState() is called during before_agent_start context injection,
before any tool invocation has had a chance to open the DB. Previously,
isDbAvailable() returning false in this path triggered a misleading
"DB unavailable — using filesystem state derivation (degraded mode)"
warning, even though the DB was simply not yet initialized (not failed).
Add a _dbOpenAttempted flag in gsd-db.ts that tracks whether
openDatabase() has been called at least once. The degraded-mode warning
now only fires when the DB was actually attempted and failed to open,
not when it hasn't been initialized yet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(state): add regression test for false degraded-mode warning
Adds the test file that was missing from the previous commit,
fixing the CI require-tests gate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The queueMicrotask() deferral in deliverResult() only prevented duplicate
follow-ups when a job completed *while* await_job was blocked in Promise.race().
For jobs that completed before await_job was called (common in multi-turn
interactive sessions), the microtask had already fired and queued the follow-up
message before suppressFollowUp could run.
Fix: replace queueMicrotask with setTimeout(0), storing the timer handle on
the job object. suppressFollowUp() (new method on AsyncJobManager) cancels
that timer and marks awaited = true atomically, handling both the within-turn
and cross-turn cases.
await-tool.ts now calls manager.suppressFollowUp(id) instead of directly
setting j.awaited = true, which gives it the cancellable timer path.
Adds a regression test specifically for the cross-turn case.
* feat(pi-ai): add Alibaba DashScope as standalone provider
Adds `alibaba-dashscope` for users with a regular DashScope API key,
separate from the existing `alibaba-coding-plan` free-tier provider.
- types.ts: register `alibaba-dashscope` as KnownProvider
- env-api-keys.ts: map to DASHSCOPE_API_KEY
- models.custom.ts: add qwen3-max, qwen3.5-plus, qwen3.5-flash,
qwen3-coder-plus with international endpoint and real pricing
- model-resolver.ts: default model qwen3.5-plus
- key-manager.ts: add alibaba-coding-plan and alibaba-dashscope
to PROVIDER_REGISTRY so /gsd keys add works for both
Co-Authored-By: Claude Code <noreply@anthropic.com>
* feat(pi-ai): add qwen3.6-plus to alibaba-dashscope provider
qwen3.6-plus is available on DashScope international endpoint.
Pricing: $0.5/M input, $3/M output (base tier, 0-256K tokens).
Supports thinking mode (reasoning: true).
Source: https://www.alibabacloud.com/help/en/model-studio/model-pricing
Co-Authored-By: Claude Code <noreply@anthropic.com>
* test(pi-ai): add tests for alibaba-dashscope provider and key-manager regression
- packages/pi-ai/src/models.test.ts: add describe block covering all 5
alibaba-dashscope models (presence, base URL, API, provider field,
context window, paid pricing, per-model reasoning/cost assertions,
independence from alibaba-coding-plan, failure path for unknown model)
- src/resources/extensions/gsd/tests/key-manager.test.ts: add regression
tests for #3891 — alibaba-coding-plan was missing from PROVIDER_REGISTRY,
causing /gsd keys add alibaba-coding-plan to fail silently; also covers
alibaba-dashscope registration, env var separation, and getAllKeyStatuses
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
- Add OLLAMA_API_KEY Bearer token auth to all Ollama HTTP client requests
(fetchWithTimeout, pullModel, chat) via getAuthHeaders/withAuth helpers.
Local Ollama ignores the Authorization header; cloud endpoints require it.
- Fix isRunning() probe for cloud endpoints: use /api/tags instead of root /
since cloud hosts may not serve the root endpoint.
- Resolve real context window for unknown models via /api/show model_info
({arch}.context_length) instead of defaulting to 8192. Priority chain:
known table > /api/show > estimate from parameter_size > 8192.
- Use dependency injection for discoverModels() to allow test mocking
without ESM named export issues.
- Pick up OLLAMA_API_KEY in provider registration (apiKey field).
Closes#3544
Co-authored-by: luannevesb <luannevesb@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Since 2.72.0 the interactive permission default is acceptEdits, which
auto-approves built-in Edit/Write/Bash but leaves the SDK permission
gate up for MCP tools. Without a canUseTool handler, every
mcp__gsd-workflow__* call surfaces as "This command requires approval"
and blocks GSD actions (#4099).
Add allowedTools entries (mcp__<server>__*) for each registered workflow
MCP server in buildSdkOptions so they run unattended while the rest of
the acceptEdits safety gate stays intact. Env-overridden server names
are handled by deriving the glob list from the built mcpServers keys.
Fixes#4099
Organize discussion question rounds into four layers (Scope →
Architecture → Error States → Quality Bar) with user-confirmed
gates between each. Prevents silent advancement and ensures
systematic depth coverage.
Each gate pauses for user confirmation. Users can skip forward
at any gate. Adjustments are reflected back before advancing.
Work-type adaptation shapes question depth per layer.
Prompt-only change — no TypeScript modifications.
Builds on #3977 (multi-round question structure).
* fix: update GSD runtime ignore patterns for team mode
Add missing runtime files to gitignore patterns across codebase and docs:
- .gsd/completed-units*.json (wildcard for archived per-milestone files)
- .gsd/state-manifest.json (workflow state manifest)
- .gsd/gsd.db* (SQLite database and WAL sidecars)
- .gsd/journal/ (daily-rotated event journal)
- .gsd/doctor-history.jsonl (diagnostic check history)
- .gsd/event-log.jsonl (workflow event log)
Updated files:
- gitignore.ts: GSD_RUNTIME_PATTERNS
- git-service.ts: RUNTIME_EXCLUSION_PATHS
- worktree-manager.ts: SKIP_PATHS, SKIP_EXACT, SKIP_PREFIXES
- doctor-runtime-checks.ts: criticalPatterns
- tests/git-service.test.ts: test expectations
- docs: README.md, working-in-teams.mdx
* docs: add comments noting gitignore.ts as canonical source of truth
Address code review feedback about maintenance risk of having multiple
sources of truth for ignore patterns. Add clear comments in all files
that reference GSD_RUNTIME_PATTERNS to indicate gitignore.ts is the
canonical source that must stay synchronized.
renderSummaryContent() in workflow-projections.ts wraps full_summary_md
(already a complete markdown doc with frontmatter) inside a second generated
frontmatter/heading envelope. This produces double frontmatter, double H1
headings, and duplicate Deviations/Known Issues sections.
The fix checks whether full_summary_md exists and starts with frontmatter
delimiters. If so, it is used as the entire output. The fallback synthesis
from individual DB columns only runs when full_summary_md is absent or
lacks frontmatter.
Adds 3 regression tests to projection-regression.test.ts.
Closes#4102.
buildPromptFromContext previously serialized multi-turn history using
literal [User] / [Assistant] / [System] bracket labels. Those tokens
are the exact pattern the anti-fabrication rule in system.md and
discuss.md forbids — the model saw its own input framed as a bracket-
labeled transcript and mirrored the format in its output, inventing
both sides of the conversation during /gsd discuss turns.
Replace the bracket labels with XML-tag structure:
- <conversation_history> wraps the whole turn sequence
- <user_message> / <assistant_message> per turn
- <prior_system_context> for the system prompt (renamed from
<system_prompt> to avoid overlap with Claude Code's reserved
<system-reminder> convention)
Prepend a directive telling the model to respond only to the final
user message and not emit the XML tags in its own response. Keep
system.md and discuss.md in sync by documenting that prior context
is delivered in those tags.
Add regression tests asserting:
- no literal [User]/[Assistant]/[System] substrings in the prompt
- history wrapped in <conversation_history> with per-turn tags
- directive leads the prompt
- empty-history edge cases still render correctly
Strip the `mcp__<server>__` prefix from tool_use blocks emitted by the
Claude Agent SDK so registered GSD extension renderers (gsd_plan_milestone,
gsd_task_complete, etc.) match instead of falling through to the generic
JSON-dump fallback. The original server name is preserved on the toolCall
block under `mcpServer` for downstream rendering.
Tighten the generic ToolExecutionComponent fallback for any remaining
prefixed names (third-party MCP servers): show a muted `server·tool`
title, render primitive args as compact `key=value` pairs, and truncate
output to 10 lines when collapsed.
When GSD auto-mode is running a planning phase, the planner subagent
could bypass GSD's state machine and artifact system. This adds a
shared state module and conflict check to block agents that overlap
with the active GSD phase.
- Add shared/gsd-phase-state.ts for cross-extension phase coordination
- Add conflicts_with frontmatter field to agent definitions
- Block conflicting agents with clear error directing to GSD workflow
- Tag planner agent with conflicts_with for plan/research phases
- 10 new tests for phase state and conflict parsing
The gate-registry test intentionally passes an invalid gate id "Q999"
to verify error handling, but the strict GateId union type rejects it
at compile time. Cast to GateId to fix the typecheck:extensions CI step.
Every workflow turn that needed a quality gate either let it drop
silently or bulk-stamped it at closeout. Q8 was the worst case: seeded
as scope:"slice" by plan-slice, treated as a blocker for the
evaluating-gates phase by state.ts, then filtered out of the
gate-evaluate prompt via `if (!meta) continue;` and never closed by
complete-slice — a guaranteed auto-loop stall once slice gates were
enabled.
Introduce gate-registry.ts as the single source of truth for which
turn owns which gate (Q3/Q4 → gate-evaluate, Q5/Q6/Q7 → execute-task,
Q8 → complete-slice, MV01–MV04 → validate-milestone). Every layer of
the prompt system now consults it:
- state.ts derives pending counts by owner turn, not scope, so Q8
never stalls evaluating-gates again.
- auto-prompts.ts builders call assertGateCoverage() and render a
"Gates to Close" block from the registry instead of a hand-rolled
GATE_QUESTIONS table.
- complete-slice and complete-task handlers saveGateResult for every
gate they own, mapping gate id → params field so empty sections
become `omitted` and populated sections become `pass`.
- milestone-validation-gates sources its MV id list from the registry.
- prompt-validation.ts adds validateSliceSummaryOutput /
validateTaskSummaryOutput / validateMilestoneValidationOutput
schema checks.
- gsd_save_gate_result accepts MV01–MV04 (via the registry keys) in
the MCP server and bootstrap tool registration.
Tests: new gate-registry + prompt-system-gate-coverage +
complete-slice-gate-closure suites, plus a Q8 regression case in
gate-dispatch.test.ts. 161 related tests pass end-to-end.
https://claude.ai/code/session_019PT3EmrkMxr4TsgGGLSYK3
Rename intermediateToolCalls → intermediateToolBlocks to match upstream
rename, and pass onElicitation via extraOptions (4th arg) instead of
overrides (3rd arg) in buildSdkOptions test.
Audit-driven fixes across the two MCP server surfaces and the Claude Code
streaming adapter:
- src/mcp-server.ts: propagate `extra.signal` into `tool.execute` so MCP
clients can actually cancel long-running Bash/WebFetch/grep calls, and
route the remaining `/server` subpath import through `createRequire`
for #3603 consistency.
- src/tests/mcp-createRequire.test.ts: extend regression coverage to the
`/server` subpath.
- claude-code-cli/stream-adapter.ts: (a) classify aborts as `aborted`
instead of the retry-eligible `stream_exhausted_without_result`,
(b) merge final-turn toolCall blocks from the builder into the
AssistantMessage via the new `mergePendingToolCalls` helper so a turn
ending in `tool_use` stop_reason no longer drops its tool calls, and
(c) resolve the SDK permission mode via `resolveClaudePermissionMode`
(auto-mode → bypass, interactive → acceptEdits, env override).
- packages/mcp-server/src/server.ts: make `gsd_query` actually respect
its `query` argument with known categories + forward-compatible
fallback, and thread `extra.signal` into `gsd_execute` so an aborted
RPC request cancels the newly-created session instead of leaking a
background RpcClient process.
- stream-adapter test suite: add regression tests for abort
classification, final-turn tool-call merging, and permission mode
resolution.
Verified via: mcp-createRequire, stream-adapter (27), partial-builder,
mcp-server package (31), workflow-tools (13) — 83 tests green.
https://claude.ai/code/session_0174sYny3VvdwYTdCNTmY4Do