* 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.
Covers 10 real-world scenarios users face after milestones are underway:
- Quick fixes and captures during auto-mode
- Steering a running slice
- Inserting new milestones before planned ones
- Parking/unparking milestones to reorder execution
- Dedicated bugfix milestones
- Handling bugs in completed slices
- Course-correcting a milestone that went wrong
Adds navigation entry under Features in docs.json.
* chore(pi-ai): regenerate model registry from upstream APIs
Regenerated models.generated.ts by running generate-models.ts against
live provider APIs. Last generated: 2026-04-09.
+48 models added, 19 removed across all providers.
Notable additions: z-ai/glm-5.1 via OpenRouter (closes#4069,
supersedes custom entry in #4055), zai-org/GLM-5.1, z-ai/glm-5v-turbo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(pi-ai): add structural and regression tests for models.generated.ts
- Regression #3582: pins qwen/qwen3.6-plus in openrouter
- Regression #4069: pins z-ai/glm-5.1 in openrouter
- Structural invariants across all 23 providers / all models
- Registry shape: exact provider list, model count lower bound
- Removed models guard: decommissioned models must stay absent
- Spot-checks for notable models added in this regeneration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* 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>
Adds a prominent "Linked issue" section at the top of the PR template
with a required acknowledgement checkbox. PRs that leave the issue
number blank or uncheck the box signal they haven't read the contribution
guidelines and will be closed.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The Next.js auth middleware (proxy.ts) was never wired in — it exported
`proxy` from a file named proxy.ts, but Next.js requires a `middleware`
export from middleware.ts. The middleware-manifest.json was empty,
leaving all 42 API routes accessible without authentication.
Fixes:
- Rename web/proxy.ts → web/middleware.ts, export `middleware` not `proxy`
- Add defense-in-depth auth-guard to /api/shutdown and /api/update routes
- Remove shell: true from update-service spawn (command injection surface)
- Update contract tests to verify middleware file name and export
Closes#4014
Co-authored-by: Claude Opus 4.6 (1M context) <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
Asserts that getPiDefaultModelAndProvider and migratePiCredentials remain
callable top-level exports from src/pi-migration.ts. If either is ever
renamed or unexported, this test fails before the root `tsc` build breaks
every CI job on main — the same class of regression introduced by
110c01b8c.
Commit 110c01b8c added an inline `validateConfiguredModel` function in
`src/cli.ts` while leaving the prior import from
`./startup-model-validation.js` in place, producing TS2440 (import
declaration conflicts with local declaration). The same commit added a
call to `getPiDefaultModelAndProvider()` without importing it, producing
TS2304 (cannot find name). Both errors block `npm run build` and every
CI job on main.
Drop the stale import and add `getPiDefaultModelAndProvider` to the
existing `./pi-migration.js` import where the symbol is actually
exported. The local `validateConfiguredModel` function (lines 139-174)
becomes the sole definition in scope. `./startup-model-validation.js`
is still consumed by its dedicated test files so the module stays.
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.
Extension-based providers like pi-claude-cli register their models
during extension loading, but registrations were queued and not flushed
until after model resolution ran. This caused findInitialModel() and
the startup model validation to see extension models as nonexistent,
permanently overwriting the user's saved model selection on every launch.
- Flush pendingProviderRegistrations in createAgentSession() before
findInitialModel() so extension models are visible in the registry
- Move model validation to after createAgentSession() in both print
and interactive code paths
- Load extensions before --list-models so extension models appear
The claude-cli onboarding path stored the auth sentinel for claude-code
but did not update defaultProvider in settings.json. Users who had an
existing Anthropic API key were left on the "anthropic" provider because
the startup migration in cli.ts correctly skips direct-key holders.
Write defaultProvider = "claude-code" to settings.json in the claude-cli
branch so the provider switch takes effect immediately.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
PR #4093 split build and integration-tests into parallel CI jobs but the new
integration-tests job only ran `npm ci`, leaving tests without the compiled
artifacts they spawn at runtime. That caused 8 failures on main (run 24325713845):
- e2e-headless, e2e-smoke, pack-install — throw "dist/loader.js not found"
- 4 web-session-parity / web-live-state tests — "session manager module not
found; checked=packages/pi-coding-agent/dist/core/session-manager.js"
- web-mode-onboarding — "sh: 1: next: not found" when the test shells
`npm run build:web-host` at runtime (web/node_modules/.bin/next absent)
Add `npm --prefix web ci` and `npm run build` to the integration-tests job
before `test:integration`, matching what the build job already does. Using
`needs: build` + artifact sharing would serialize the two jobs and undo the
parallelism PR #4093 was buying, so the build is duplicated intentionally.
Split integration tests into a separate job that runs concurrently with
the build+unit test job. Integration tests run from TypeScript source
(no build artifact dependency), so they can start immediately after
detect-changes without waiting for compilation.
Add useblacksmith/cache@v5 to persist web/.next/cache across CI runs,
eliminating redundant Webpack recompilation of unchanged pages. Applied
to ci.yml build job and pipeline.yml dev-publish/prod-release jobs.