diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 26dad411a..a2f8ce964 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "gsd-core", "displayName": "GSD Core", - "version": "1.6.0", + "version": "1.6.1", "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.", "author": { "name": "open-gsd", diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dbc738ca..0a64f2189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [1.6.1] - 2026-07-01 + +### Added + +- **Claude Sonnet 5 is now the `standard` (sonnet) tier model.** The model catalog and provider presets resolve the sonnet/standard tier to `claude-sonnet-5` (GA 2026-06-30) across the Anthropic-backed runtimes (`claude`, `copilot`, and the `anthropic`/`anthropic-fable` presets), plus the OpenRouter-style `anthropic/claude-sonnet-5` for `opencode`/`hermes`, replacing the superseded `claude-sonnet-4-6`. Opus and Haiku tiers are unchanged. (#1847) (#1848) + +### Fixed + +- `milestone complete` and `roadmap analyze` now exclude the Phase 0 / Phase 999 backlog sentinels. A milestone whose only directory-less ROADMAP heading is a backlog sentinel can be completed without `--force`, and `roadmap analyze` no longer counts the sentinel in `phase_count` or routes `next_phase` into it. Completes the `^999` exclusion #1445 added to the progress denominators. (#1691) +- **`phase.complete` no longer reports a false `is_last_phase` on a `
`-wrapped checkbox checklist (#1591, #1752)** — when the active milestone's phase checklist was written as `- [ ] Phase N:` checkbox items inside a `
` block and the next phase had no directory on disk yet (still in planning), `phase.complete`'s `isLastPhase` roadmap-enumeration fallback used a heading-only pattern (`/#{2,4}\s*Phase…/`) that never matched checkbox items. It returned `is_last_phase: true, next_phase: null` on a mid-milestone phase and — via the milestone-complete cascade — wrongly flipped STATE.md to `Milestone complete` and decremented `progress.total_phases` (e.g. 8 → 7). The pattern now matches both heading-style (`### Phase N:`) and checkbox-list phases (`- [ ] Phase N:` / `- [x] Phase N:`); `extractCurrentMilestone` already surfaces the `
`-wrapped checklist correctly, so no parser change was needed. Only the reproduced `phase.complete` fallback is changed; the heading-only sibling patterns elsewhere in `phase.cts` are untouched. + (#1819) +- Windows: stop double-quoting $CLAUDE_PROJECT_DIR-anchored managed node hook paths during the #2979 legacy rewrite, which produced "\"$CLAUDE_PROJECT_DIR\"/..." and broke every node managed hook with MODULE_NOT_FOUND (PreToolUse-guard deadlock). (#1746) + ## [1.6.0] - 2026-06-24 ### Added diff --git a/capabilities/ai-integration/capability.json b/capabilities/ai-integration/capability.json index 4e7405ba3..4a46bd661 100644 --- a/capabilities/ai-integration/capability.json +++ b/capabilities/ai-integration/capability.json @@ -1,7 +1,7 @@ { "id": "ai-integration", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "AI design contract", "description": "AI-SPEC design contract workflow for phases that build AI systems; owns the AI integration command, agents, and workflow.ai_integration_phase activation key.", "tier": "full", diff --git a/capabilities/antigravity/capability.json b/capabilities/antigravity/capability.json index 3ad49e14d..4cee91d71 100644 --- a/capabilities/antigravity/capability.json +++ b/capabilities/antigravity/capability.json @@ -1,7 +1,7 @@ { "id": "antigravity", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Antigravity", "description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; flat skill layout; tier-1 support.", "tier": "core", diff --git a/capabilities/audit/capability.json b/capabilities/audit/capability.json index 293381326..b9b410820 100644 --- a/capabilities/audit/capability.json +++ b/capabilities/audit/capability.json @@ -1,7 +1,7 @@ { "id": "audit", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Audit", "description": "Open-artifact audit and UAT-gap audit for milestone close gates; exposes `gsd-tools audit-uat` (cross-phase UAT outstanding items) and `gsd-tools audit-open` (structured open-artifact scan across debug, tasks, threads, todos, seeds, UAT, verification, context-questions).", "tier": "full", diff --git a/capabilities/augment/capability.json b/capabilities/augment/capability.json index 5fa6e875f..379a1ecc7 100644 --- a/capabilities/augment/capability.json +++ b/capabilities/augment/capability.json @@ -1,7 +1,7 @@ { "id": "augment", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Augment Code", "description": "Augment Code CLI — commands + nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", diff --git a/capabilities/claude/capability.json b/capabilities/claude/capability.json index f2f12751c..e0b286220 100644 --- a/capabilities/claude/capability.json +++ b/capabilities/claude/capability.json @@ -1,7 +1,7 @@ { "id": "claude", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Claude Code", "description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.", "tier": "core", diff --git a/capabilities/cline/capability.json b/capabilities/cline/capability.json index 1c0d85403..ca1ac84db 100644 --- a/capabilities/cline/capability.json +++ b/capabilities/cline/capability.json @@ -1,7 +1,7 @@ { "id": "cline", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Cline", "description": "Cline (VS Code extension) — global-only nested-skill layout; cline-rules hook surface (.clinerules); no hook events emitted; tier-2 support.", "tier": "core", diff --git a/capabilities/code-review/capability.json b/capabilities/code-review/capability.json index 153f433ff..b63792b3f 100644 --- a/capabilities/code-review/capability.json +++ b/capabilities/code-review/capability.json @@ -1,7 +1,7 @@ { "id": "code-review", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Code review", "description": "Source-file code review and review-fix workflow support for completed execution work.", "tier": "full", diff --git a/capabilities/codebuddy/capability.json b/capabilities/codebuddy/capability.json index 76538c71f..f2e398443 100644 --- a/capabilities/codebuddy/capability.json +++ b/capabilities/codebuddy/capability.json @@ -1,7 +1,7 @@ { "id": "codebuddy", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "CodeBuddy", "description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", diff --git a/capabilities/codex/capability.json b/capabilities/codex/capability.json index 893b9afeb..8116403e7 100644 --- a/capabilities/codex/capability.json +++ b/capabilities/codex/capability.json @@ -1,7 +1,7 @@ { "id": "codex", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "OpenAI Codex CLI", "description": "OpenAI Codex CLI — shell-var command style; per-agent sandbox tiers; config.toml + hooks.json hook surface; tier-1 support.", "tier": "core", diff --git a/capabilities/copilot/capability.json b/capabilities/copilot/capability.json index 73af7b196..9db1ce110 100644 --- a/capabilities/copilot/capability.json +++ b/capabilities/copilot/capability.json @@ -1,7 +1,7 @@ { "id": "copilot", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "GitHub Copilot", "description": "GitHub Copilot (VS Code) — markdown config format; copilot-inline hook surface; no hook events emitted; flat skill nesting (unconfirmed recursive loader); tier-2 support.", "tier": "core", diff --git a/capabilities/cursor/capability.json b/capabilities/cursor/capability.json index cea99308c..a2b22e1c9 100644 --- a/capabilities/cursor/capability.json +++ b/capabilities/cursor/capability.json @@ -1,7 +1,7 @@ { "id": "cursor", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Cursor", "description": "Cursor IDE — skills + converted commands artifact layout; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.", "tier": "core", diff --git a/capabilities/drift/capability.json b/capabilities/drift/capability.json index c69ad521a..5ad1731f0 100644 --- a/capabilities/drift/capability.json +++ b/capabilities/drift/capability.json @@ -1,7 +1,7 @@ { "id": "drift", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Drift detection gates", "description": "Drift detection gates for the planning loop. At execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md). At plan:pre: a non-blocking, warn-only codebase drift gate (gated on workflow.plan_drift_precheck) that flags a stale codebase map before planning, so plans are authored against a fresh STRUCTURE.md instead of discovering drift mid-execution.", "tier": "full", diff --git a/capabilities/gap-analysis/capability.json b/capabilities/gap-analysis/capability.json index e4926ef85..46fcc01b3 100644 --- a/capabilities/gap-analysis/capability.json +++ b/capabilities/gap-analysis/capability.json @@ -1,7 +1,7 @@ { "id": "gap-analysis", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Post-planning gap analysis", "description": "Proactive, non-blocking post-planning coverage report. After all PLAN.md files are generated, cross-references every REQ-ID and D-ID from REQUIREMENTS.md and CONTEXT.md against plan bodies. Emits a Source | Item | Status table. Does not block phase advancement.", "tier": "standard", diff --git a/capabilities/gemini/capability.json b/capabilities/gemini/capability.json index 65cd8aaff..180917b8b 100644 --- a/capabilities/gemini/capability.json +++ b/capabilities/gemini/capability.json @@ -1,7 +1,7 @@ { "id": "gemini", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Gemini CLI", "description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.", "tier": "core", diff --git a/capabilities/graphify/capability.json b/capabilities/graphify/capability.json index cff231e46..f69c2ef9b 100644 --- a/capabilities/graphify/capability.json +++ b/capabilities/graphify/capability.json @@ -1,7 +1,7 @@ { "id": "graphify", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Knowledge graph", "description": "Build, query, and inspect the project knowledge graph in `.planning/graphs/`; exposes graphify CLI subcommands (build, query, status, diff) and the /gsd-graphify skill.", "tier": "full", diff --git a/capabilities/hermes/capability.json b/capabilities/hermes/capability.json index c2f861741..d66847898 100644 --- a/capabilities/hermes/capability.json +++ b/capabilities/hermes/capability.json @@ -1,7 +1,7 @@ { "id": "hermes", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Hermes Agent", "description": "Hermes Agent (NousResearch) — skills nest under skills/gsd/ category bucket; nested skill layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", diff --git a/capabilities/intel/capability.json b/capabilities/intel/capability.json index 69c14bcc7..f12315289 100644 --- a/capabilities/intel/capability.json +++ b/capabilities/intel/capability.json @@ -1,7 +1,7 @@ { "id": "intel", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Codebase intelligence", "description": "Code-intelligence store for codebase querying, diff, snapshot, and API-surface extraction; exposes `gsd-tools intel` subcommands (query, status, update, diff, snapshot, patch-meta, validate, extract-exports, api-surface) and backs `/gsd-map-codebase` and `gsd-intel-updater`.", "tier": "full", diff --git a/capabilities/kilo/capability.json b/capabilities/kilo/capability.json index 59a1d6899..0c6858d14 100644 --- a/capabilities/kilo/capability.json +++ b/capabilities/kilo/capability.json @@ -1,7 +1,7 @@ { "id": "kilo", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Kilo Code", "description": "Kilo Code — XDG-based config dir; global skills at ~/.kilo/skills (separate from XDG config); flat command/ + skills artifact layout; no lifecycle hook registration; tier-2 support.", "tier": "core", diff --git a/capabilities/kimi/capability.json b/capabilities/kimi/capability.json index a4e74e4e3..fa66b7d2c 100644 --- a/capabilities/kimi/capability.json +++ b/capabilities/kimi/capability.json @@ -1,7 +1,7 @@ { "id": "kimi", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Kimi CLI", "description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; no hook surface; no hook events; tier-2 support.", "tier": "core", diff --git a/capabilities/mempalace/capability.json b/capabilities/mempalace/capability.json index a2e80c04a..d5ed38d7b 100644 --- a/capabilities/mempalace/capability.json +++ b/capabilities/mempalace/capability.json @@ -1,7 +1,7 @@ { "id": "mempalace", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "MemPalace memory", "description": "Cross-session, cross-project memory: deliberate recall before discuss/plan and verbatim capture + temporal-KG sync at phase boundaries, via the MemPalace MCP server and CLI.", "tier": "full", diff --git a/capabilities/nyquist/capability.json b/capabilities/nyquist/capability.json index 589402fa3..0f517f692 100644 --- a/capabilities/nyquist/capability.json +++ b/capabilities/nyquist/capability.json @@ -1,7 +1,7 @@ { "id": "nyquist", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Nyquist validation", "description": "Validation coverage audit that maps executed work back to tests and manual-only evidence.", "tier": "full", diff --git a/capabilities/opencode/capability.json b/capabilities/opencode/capability.json index 1ad177037..560783f67 100644 --- a/capabilities/opencode/capability.json +++ b/capabilities/opencode/capability.json @@ -1,7 +1,7 @@ { "id": "opencode", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "OpenCode", "description": "OpenCode — XDG-based config dir; flat command/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.", "tier": "core", diff --git a/capabilities/pattern-mapper/capability.json b/capabilities/pattern-mapper/capability.json index 259f48d84..740d48c94 100644 --- a/capabilities/pattern-mapper/capability.json +++ b/capabilities/pattern-mapper/capability.json @@ -1,7 +1,7 @@ { "id": "pattern-mapper", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Pattern mapping", "description": "Optional codebase-pattern mapping before planning; owns the pattern mapper agent and workflow.pattern_mapper activation key.", "tier": "full", diff --git a/capabilities/profile-pipeline/capability.json b/capabilities/profile-pipeline/capability.json index cc0b58c34..88062612e 100644 --- a/capabilities/profile-pipeline/capability.json +++ b/capabilities/profile-pipeline/capability.json @@ -1,7 +1,7 @@ { "id": "profile-pipeline", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Developer profiling pipeline", "description": "Developer behavioral profiling from Claude Code session history; scans session JSONL files, extracts and samples user messages, and generates profile artifacts (USER-PROFILE.md, dev-preferences.md, CLAUDE.md sections). Exposes eight `gsd-tools` commands: scan-sessions, extract-messages, profile-sample (pipeline phase) and write-profile, profile-questionnaire, generate-dev-preferences, generate-claude-profile, generate-claude-md (output phase). Backs the /gsd-profile-user skill and gsd-user-profiler agent.", "tier": "full", diff --git a/capabilities/qwen/capability.json b/capabilities/qwen/capability.json index 9da38ac16..0b17c1041 100644 --- a/capabilities/qwen/capability.json +++ b/capabilities/qwen/capability.json @@ -1,7 +1,7 @@ { "id": "qwen", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Qwen Code", "description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", diff --git a/capabilities/research/capability.json b/capabilities/research/capability.json index 63ecab84d..b6800db9f 100644 --- a/capabilities/research/capability.json +++ b/capabilities/research/capability.json @@ -1,7 +1,7 @@ { "id": "research", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Phase research", "description": "Optional phase research before planning; owns the phase researcher agent and workflow.research activation key.", "tier": "standard", diff --git a/capabilities/schema-gate/capability.json b/capabilities/schema-gate/capability.json index 254a160cf..0d9e88cae 100644 --- a/capabilities/schema-gate/capability.json +++ b/capabilities/schema-gate/capability.json @@ -1,7 +1,7 @@ { "id": "schema-gate", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Schema push detection gate", "description": "Detects ORM schema-relevant files in the phase scope during planning and injects a mandatory [BLOCKING] schema push task into the plan. Prevents false-positive verification where build/types pass because TypeScript types come from config, not the live database.", "tier": "full", diff --git a/capabilities/security/capability.json b/capabilities/security/capability.json index e9599ed0e..1521a7775 100644 --- a/capabilities/security/capability.json +++ b/capabilities/security/capability.json @@ -1,7 +1,7 @@ { "id": "security", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Security enforcement", "description": "Threat mitigation verification and ship-time security blocking for phases with security enforcement enabled.", "tier": "full", diff --git a/capabilities/tdd/capability.json b/capabilities/tdd/capability.json index c2f99c57b..b9e1782b3 100644 --- a/capabilities/tdd/capability.json +++ b/capabilities/tdd/capability.json @@ -1,7 +1,7 @@ { "id": "tdd", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Test-driven development", "description": "Injects TDD heuristics into the planner and enforces RED/GREEN gate compliance on type:tdd plans after execution. Owns workflow.tdd_mode; the --tdd CLI flag is the ephemeral override.", "tier": "full", diff --git a/capabilities/trae/capability.json b/capabilities/trae/capability.json index 3713c8300..dab1a679a 100644 --- a/capabilities/trae/capability.json +++ b/capabilities/trae/capability.json @@ -1,7 +1,7 @@ { "id": "trae", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Trae IDE", "description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.", "tier": "core", diff --git a/capabilities/ui/capability.json b/capabilities/ui/capability.json index 903477c89..899d9ba7b 100644 --- a/capabilities/ui/capability.json +++ b/capabilities/ui/capability.json @@ -1,7 +1,7 @@ { "id": "ui", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "UI design contracts", "description": "UI-SPEC design contract + retrospective UI audit for frontend phases.", "tier": "full", diff --git a/capabilities/windsurf/capability.json b/capabilities/windsurf/capability.json index 293e18b5f..50605bc9d 100644 --- a/capabilities/windsurf/capability.json +++ b/capabilities/windsurf/capability.json @@ -1,7 +1,7 @@ { "id": "windsurf", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Windsurf", "description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; no hook surface; no hook events; tier-2 support.", "tier": "core", diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 544d3f988..ce4c15fcd 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1355,13 +1355,13 @@ When `runtime` is set, profile tiers (`opus`/`sonnet`/`haiku`) resolve to runtim | Runtime | `opus` | `sonnet` | `haiku` | reasoning_effort | |---------|--------|----------|---------|------------------| -| `claude` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | (not used) | +| `claude` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) | | `codex` | `gpt-5.5` | `gpt-5.4` | `gpt-5.4-mini` | `xhigh` / `medium` / `medium` | | `gemini` | `gemini-3.1-pro-preview` | `gemini-3-flash` | `gemini-2.5-flash-lite` | (not used) | | `qwen` | `qwen3-max-2026-01-23` | `qwen3-coder-plus` | `qwen3-coder-next` | (not used) | -| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | (not used) | -| `copilot` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | (not used) | -| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | (not used) | +| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) | +| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) | +| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) | | Group B (`kilo`, `cline`, `cursor`, `windsurf` (alias: `devin-desktop`), `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | | | > **How these model IDs are sourced.** The catalog (`bin/shared/model-catalog.json`) pins each runtime's tier defaults to that provider's current frontier IDs, and may intentionally carry forward-dated IDs ahead of a provider's public docs. To verify an ID is live before changing it, check the provider's own source/API — e.g. Gemini: gemini-cli `packages/core/src/config/models.ts` or `gemini --model --prompt ping`; Codex: `codex debug models` or the OpenAI Codex models page; Qwen: Alibaba Model Studio model list. Only change an ID that the provider actually rejects — absence from documentation alone is not proof of invalidity. diff --git a/docs/pt-BR/CONFIGURATION.md b/docs/pt-BR/CONFIGURATION.md index c64c815e2..9510927f6 100644 --- a/docs/pt-BR/CONFIGURATION.md +++ b/docs/pt-BR/CONFIGURATION.md @@ -1139,13 +1139,13 @@ A saída JSON de `resolve-model` inclui `reasoning_effort` quando o nível de ru | Runtime | `opus` | `sonnet` | `haiku` | reasoning_effort | |---------|--------|----------|---------|------------------| -| `claude` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | (não usado) | +| `claude` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (não usado) | | `codex` | `gpt-5.5` | `gpt-5.3-codex` | `gpt-5.4-mini` | `xhigh` / `medium` / `medium` | | `gemini` | `gemini-3-pro` | `gemini-3-flash` | `gemini-2.5-flash-lite` | (não usado) | | `qwen` | `qwen3-max-2026-01-23` | `qwen3-coder-plus` | `qwen3-coder-next` | (não usado) | -| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | (não usado) | -| `copilot` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | (não usado) | -| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | (não usado) | +| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (não usado) | +| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (não usado) | +| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (não usado) | | Grupo B (`kilo`, `cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (sem padrão integrado — seu runtime trata da seleção de modelo) | | | | **Exemplo Codex** — uma configuração, modelos em nível, sem bloco grande de `model_overrides`: diff --git a/docs/zh-CN/CONFIGURATION.md b/docs/zh-CN/CONFIGURATION.md index 07aff134a..2b49cf333 100644 --- a/docs/zh-CN/CONFIGURATION.md +++ b/docs/zh-CN/CONFIGURATION.md @@ -1108,13 +1108,13 @@ minimal < low < medium < high < xhigh < max | 运行时 | `opus` | `sonnet` | `haiku` | reasoning_effort | |---------|--------|----------|---------|------------------| -| `claude` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | (不使用) | +| `claude` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (不使用) | | `codex` | `gpt-5.5` | `gpt-5.3-codex` | `gpt-5.4-mini` | `xhigh` / `medium` / `medium` | | `gemini` | `gemini-3-pro` | `gemini-3-flash` | `gemini-2.5-flash-lite` | (不使用) | | `qwen` | `qwen3-max-2026-01-23` | `qwen3-coder-plus` | `qwen3-coder-next` | (不使用) | -| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | (不使用) | -| `copilot` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | (不使用) | -| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | (不使用) | +| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (不使用) | +| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (不使用) | +| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (不使用) | | B 组(`kilo`、`cline`、`cursor`、`windsurf`、`augment`、`trae`、`codebuddy`、`antigravity`) | (无内置默认——您的运行时处理模型选择) | | | | **Codex 示例** — 单个配置,分层模型,无大型 `model_overrides` 块: diff --git a/gemini-extension.json b/gemini-extension.json index a93e82557..d08c5e4a9 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "gsd-core", - "version": "1.6.0", + "version": "1.6.1", "description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.", "contextFileName": "GEMINI.md" } diff --git a/gsd-core/bin/lib/capability-registry.cjs b/gsd-core/bin/lib/capability-registry.cjs index 79de051b7..ad28a6c46 100644 --- a/gsd-core/bin/lib/capability-registry.cjs +++ b/gsd-core/bin/lib/capability-registry.cjs @@ -10,7 +10,7 @@ const capabilities = { "ai-integration": { "id": "ai-integration", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "AI design contract", "description": "AI-SPEC design contract workflow for phases that build AI systems; owns the AI integration command, agents, and workflow.ai_integration_phase activation key.", "tier": "full", @@ -63,7 +63,7 @@ const capabilities = { "antigravity": { "id": "antigravity", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Antigravity", "description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; flat skill layout; tier-1 support.", "tier": "core", @@ -123,7 +123,7 @@ const capabilities = { "audit": { "id": "audit", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Audit", "description": "Open-artifact audit and UAT-gap audit for milestone close gates; exposes `gsd-tools audit-uat` (cross-phase UAT outstanding items) and `gsd-tools audit-open` (structured open-artifact scan across debug, tasks, threads, todos, seeds, UAT, verification, context-questions).", "tier": "full", @@ -160,7 +160,7 @@ const capabilities = { "augment": { "id": "augment", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Augment Code", "description": "Augment Code CLI — commands + nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", @@ -229,7 +229,7 @@ const capabilities = { "claude": { "id": "claude", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Claude Code", "description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.", "tier": "core", @@ -295,7 +295,7 @@ const capabilities = { "cline": { "id": "cline", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Cline", "description": "Cline (VS Code extension) — global-only nested-skill layout; cline-rules hook surface (.clinerules); no hook events emitted; tier-2 support.", "tier": "core", @@ -338,7 +338,7 @@ const capabilities = { "code-review": { "id": "code-review", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Code review", "description": "Source-file code review and review-fix workflow support for completed execution work.", "tier": "full", @@ -399,7 +399,7 @@ const capabilities = { "codebuddy": { "id": "codebuddy", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "CodeBuddy", "description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", @@ -468,7 +468,7 @@ const capabilities = { "codex": { "id": "codex", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "OpenAI Codex CLI", "description": "OpenAI Codex CLI — shell-var command style; per-agent sandbox tiers; config.toml + hooks.json hook surface; tier-1 support.", "tier": "core", @@ -521,7 +521,7 @@ const capabilities = { "copilot": { "id": "copilot", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "GitHub Copilot", "description": "GitHub Copilot (VS Code) — markdown config format; copilot-inline hook surface; no hook events emitted; flat skill nesting (unconfirmed recursive loader); tier-2 support.", "tier": "core", @@ -574,7 +574,7 @@ const capabilities = { "cursor": { "id": "cursor", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Cursor", "description": "Cursor IDE — skills + converted commands artifact layout; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.", "tier": "core", @@ -643,7 +643,7 @@ const capabilities = { "drift": { "id": "drift", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Drift detection gates", "description": "Drift detection gates for the planning loop. At execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md). At plan:pre: a non-blocking, warn-only codebase drift gate (gated on workflow.plan_drift_precheck) that flags a stale codebase map before planning, so plans are authored against a fresh STRUCTURE.md instead of discovering drift mid-execution.", "tier": "full", @@ -721,7 +721,7 @@ const capabilities = { "gap-analysis": { "id": "gap-analysis", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Post-planning gap analysis", "description": "Proactive, non-blocking post-planning coverage report. After all PLAN.md files are generated, cross-references every REQ-ID and D-ID from REQUIREMENTS.md and CONTEXT.md against plan bodies. Emits a Source | Item | Status table. Does not block phase advancement.", "tier": "standard", @@ -762,7 +762,7 @@ const capabilities = { "gemini": { "id": "gemini", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Gemini CLI", "description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.", "tier": "core", @@ -819,7 +819,7 @@ const capabilities = { "graphify": { "id": "graphify", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Knowledge graph", "description": "Build, query, and inspect the project knowledge graph in `.planning/graphs/`; exposes graphify CLI subcommands (build, query, status, diff) and the /gsd-graphify skill.", "tier": "full", @@ -860,7 +860,7 @@ const capabilities = { "hermes": { "id": "hermes", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Hermes Agent", "description": "Hermes Agent (NousResearch) — skills nest under skills/gsd/ category bucket; nested skill layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", @@ -913,7 +913,7 @@ const capabilities = { "intel": { "id": "intel", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Codebase intelligence", "description": "Code-intelligence store for codebase querying, diff, snapshot, and API-surface extraction; exposes `gsd-tools intel` subcommands (query, status, update, diff, snapshot, patch-meta, validate, extract-exports, api-surface) and backs `/gsd-map-codebase` and `gsd-intel-updater`.", "tier": "full", @@ -965,7 +965,7 @@ const capabilities = { "kilo": { "id": "kilo", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Kilo Code", "description": "Kilo Code — XDG-based config dir; global skills at ~/.kilo/skills (separate from XDG config); flat command/ + skills artifact layout; no lifecycle hook registration; tier-2 support.", "tier": "core", @@ -1040,7 +1040,7 @@ const capabilities = { "kimi": { "id": "kimi", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Kimi CLI", "description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; no hook surface; no hook events; tier-2 support.", "tier": "core", @@ -1096,7 +1096,7 @@ const capabilities = { "mempalace": { "id": "mempalace", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "MemPalace memory", "description": "Cross-session, cross-project memory: deliberate recall before discuss/plan and verbatim capture + temporal-KG sync at phase boundaries, via the MemPalace MCP server and CLI.", "tier": "full", @@ -1270,7 +1270,7 @@ const capabilities = { "nyquist": { "id": "nyquist", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Nyquist validation", "description": "Validation coverage audit that maps executed work back to tests and manual-only evidence.", "tier": "full", @@ -1320,7 +1320,7 @@ const capabilities = { "opencode": { "id": "opencode", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "OpenCode", "description": "OpenCode — XDG-based config dir; flat command/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.", "tier": "core", @@ -1390,7 +1390,7 @@ const capabilities = { "pattern-mapper": { "id": "pattern-mapper", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Pattern mapping", "description": "Optional codebase-pattern mapping before planning; owns the pattern mapper agent and workflow.pattern_mapper activation key.", "tier": "full", @@ -1444,7 +1444,7 @@ const capabilities = { "profile-pipeline": { "id": "profile-pipeline", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Developer profiling pipeline", "description": "Developer behavioral profiling from Claude Code session history; scans session JSONL files, extracts and samples user messages, and generates profile artifacts (USER-PROFILE.md, dev-preferences.md, CLAUDE.md sections). Exposes eight `gsd-tools` commands: scan-sessions, extract-messages, profile-sample (pipeline phase) and write-profile, profile-questionnaire, generate-dev-preferences, generate-claude-profile, generate-claude-md (output phase). Backs the /gsd-profile-user skill and gsd-user-profiler agent.", "tier": "full", @@ -1521,7 +1521,7 @@ const capabilities = { "qwen": { "id": "qwen", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Qwen Code", "description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", @@ -1578,7 +1578,7 @@ const capabilities = { "research": { "id": "research", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Phase research", "description": "Optional phase research before planning; owns the phase researcher agent and workflow.research activation key.", "tier": "standard", @@ -1630,7 +1630,7 @@ const capabilities = { "schema-gate": { "id": "schema-gate", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Schema push detection gate", "description": "Detects ORM schema-relevant files in the phase scope during planning and injects a mandatory [BLOCKING] schema push task into the plan. Prevents false-positive verification where build/types pass because TypeScript types come from config, not the live database.", "tier": "full", @@ -1676,7 +1676,7 @@ const capabilities = { "security": { "id": "security", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Security enforcement", "description": "Threat mitigation verification and ship-time security blocking for phases with security enforcement enabled.", "tier": "full", @@ -1775,7 +1775,7 @@ const capabilities = { "tdd": { "id": "tdd", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "Test-driven development", "description": "Injects TDD heuristics into the planner and enforces RED/GREEN gate compliance on type:tdd plans after execution. Owns workflow.tdd_mode; the --tdd CLI flag is the ephemeral override.", "tier": "full", @@ -1828,7 +1828,7 @@ const capabilities = { "trae": { "id": "trae", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Trae IDE", "description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.", "tier": "core", @@ -1880,7 +1880,7 @@ const capabilities = { "ui": { "id": "ui", "role": "feature", - "version": "1.6.0", + "version": "1.6.1", "title": "UI design contracts", "description": "UI-SPEC design contract + retrospective UI audit for frontend phases.", "tier": "full", @@ -1975,7 +1975,7 @@ const capabilities = { "windsurf": { "id": "windsurf", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Windsurf", "description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; no hook surface; no hook events; tier-2 support.", "tier": "core", @@ -2743,7 +2743,7 @@ const runtimes = { "antigravity": { "id": "antigravity", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Antigravity", "description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; flat skill layout; tier-1 support.", "tier": "core", @@ -2803,7 +2803,7 @@ const runtimes = { "augment": { "id": "augment", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Augment Code", "description": "Augment Code CLI — commands + nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", @@ -2872,7 +2872,7 @@ const runtimes = { "claude": { "id": "claude", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Claude Code", "description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.", "tier": "core", @@ -2938,7 +2938,7 @@ const runtimes = { "cline": { "id": "cline", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Cline", "description": "Cline (VS Code extension) — global-only nested-skill layout; cline-rules hook surface (.clinerules); no hook events emitted; tier-2 support.", "tier": "core", @@ -2981,7 +2981,7 @@ const runtimes = { "codebuddy": { "id": "codebuddy", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "CodeBuddy", "description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", @@ -3050,7 +3050,7 @@ const runtimes = { "codex": { "id": "codex", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "OpenAI Codex CLI", "description": "OpenAI Codex CLI — shell-var command style; per-agent sandbox tiers; config.toml + hooks.json hook surface; tier-1 support.", "tier": "core", @@ -3103,7 +3103,7 @@ const runtimes = { "copilot": { "id": "copilot", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "GitHub Copilot", "description": "GitHub Copilot (VS Code) — markdown config format; copilot-inline hook surface; no hook events emitted; flat skill nesting (unconfirmed recursive loader); tier-2 support.", "tier": "core", @@ -3156,7 +3156,7 @@ const runtimes = { "cursor": { "id": "cursor", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Cursor", "description": "Cursor IDE — skills + converted commands artifact layout; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.", "tier": "core", @@ -3225,7 +3225,7 @@ const runtimes = { "gemini": { "id": "gemini", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Gemini CLI", "description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.", "tier": "core", @@ -3282,7 +3282,7 @@ const runtimes = { "hermes": { "id": "hermes", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Hermes Agent", "description": "Hermes Agent (NousResearch) — skills nest under skills/gsd/ category bucket; nested skill layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", @@ -3335,7 +3335,7 @@ const runtimes = { "kilo": { "id": "kilo", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Kilo Code", "description": "Kilo Code — XDG-based config dir; global skills at ~/.kilo/skills (separate from XDG config); flat command/ + skills artifact layout; no lifecycle hook registration; tier-2 support.", "tier": "core", @@ -3410,7 +3410,7 @@ const runtimes = { "kimi": { "id": "kimi", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Kimi CLI", "description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; no hook surface; no hook events; tier-2 support.", "tier": "core", @@ -3466,7 +3466,7 @@ const runtimes = { "opencode": { "id": "opencode", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "OpenCode", "description": "OpenCode — XDG-based config dir; flat command/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.", "tier": "core", @@ -3536,7 +3536,7 @@ const runtimes = { "qwen": { "id": "qwen", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Qwen Code", "description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.", "tier": "core", @@ -3593,7 +3593,7 @@ const runtimes = { "trae": { "id": "trae", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Trae IDE", "description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.", "tier": "core", @@ -3645,7 +3645,7 @@ const runtimes = { "windsurf": { "id": "windsurf", "role": "runtime", - "version": "1.6.0", + "version": "1.6.1", "title": "Windsurf", "description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; no hook surface; no hook events; tier-2 support.", "tier": "core", diff --git a/gsd-core/bin/shared/model-catalog.json b/gsd-core/bin/shared/model-catalog.json index 308f1bcc8..6f2a28011 100644 --- a/gsd-core/bin/shared/model-catalog.json +++ b/gsd-core/bin/shared/model-catalog.json @@ -9,7 +9,7 @@ "runtimeTierDefaults": { "claude": { "opus": { "model": "claude-opus-4-8" }, - "sonnet": { "model": "claude-sonnet-4-6" }, + "sonnet": { "model": "claude-sonnet-5" }, "haiku": { "model": "claude-haiku-4-5" } }, "codex": { @@ -29,17 +29,17 @@ }, "opencode": { "opus": { "model": "anthropic/claude-opus-4-8" }, - "sonnet": { "model": "anthropic/claude-sonnet-4-6" }, + "sonnet": { "model": "anthropic/claude-sonnet-5" }, "haiku": { "model": "anthropic/claude-haiku-4-5" } }, "copilot": { "opus": { "model": "claude-opus-4-8" }, - "sonnet": { "model": "claude-sonnet-4-6" }, + "sonnet": { "model": "claude-sonnet-5" }, "haiku": { "model": "claude-haiku-4-5" } }, "hermes": { "opus": { "model": "anthropic/claude-opus-4-8" }, - "sonnet": { "model": "anthropic/claude-sonnet-4-6" }, + "sonnet": { "model": "anthropic/claude-sonnet-5" }, "haiku": { "model": "anthropic/claude-haiku-4-5" } }, "kilo": { @@ -91,13 +91,13 @@ "providerPresets": { "anthropic": { "opus": { "low": { "model": "claude-opus-4-5" }, "medium": { "model": "claude-opus-4-8" }, "high": { "model": "claude-opus-4-8" } }, - "sonnet": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-sonnet-4-6" }, "high": { "model": "claude-opus-4-8" } }, - "haiku": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-haiku-4-5" }, "high": { "model": "claude-sonnet-4-6" } } + "sonnet": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-sonnet-5" }, "high": { "model": "claude-opus-4-8" } }, + "haiku": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-haiku-4-5" }, "high": { "model": "claude-sonnet-5" } } }, "anthropic-fable": { "opus": { "low": { "model": "claude-opus-4-5" }, "medium": { "model": "claude-opus-4-8" }, "high": { "model": "claude-fable-5" } }, - "sonnet": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-sonnet-4-6" }, "high": { "model": "claude-fable-5" } }, - "haiku": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-haiku-4-5" }, "high": { "model": "claude-sonnet-4-6" } } + "sonnet": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-sonnet-5" }, "high": { "model": "claude-fable-5" } }, + "haiku": { "low": { "model": "claude-haiku-4-5" }, "medium": { "model": "claude-haiku-4-5" }, "high": { "model": "claude-sonnet-5" } } }, "openai": { "opus": { "low": { "model": "gpt-5.4", "reasoning_effort": "medium" }, "medium": { "model": "gpt-5.5", "reasoning_effort": "high" }, "high": { "model": "gpt-5.5", "reasoning_effort": "xhigh" } }, diff --git a/gsd-core/workflows/settings-advanced.md b/gsd-core/workflows/settings-advanced.md index 76f452a7b..3aad8c1d5 100644 --- a/gsd-core/workflows/settings-advanced.md +++ b/gsd-core/workflows/settings-advanced.md @@ -353,13 +353,13 @@ Built-in tier defaults by runtime: | Runtime | `opus` | `sonnet` | `haiku` | |------------|-------------------------------|---------------------------------|-------------------------------| -| `claude` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | +| `claude` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | | `codex` | `gpt-5.5` | `gpt-5.4` | `gpt-5.4-mini` | | `gemini` | `gemini-3.1-pro-preview` | `gemini-3-flash` | `gemini-2.5-flash-lite` | | `qwen` | `qwen3-max-2026-01-23` | `qwen3-coder-plus` | `qwen3-coder-next` | -| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | -| `copilot` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | -| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | +| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | +| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | +| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | | Group B (`kilo`, `cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | | Display a table to the user showing the effective configuration: @@ -623,8 +623,8 @@ AskUserQuestion([ header: "Provider", multiSelect: false, options: [ - { label: "anthropic", description: "claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5 (Anthropic / Claude)" }, - { label: "anthropic-fable", description: "claude-fable-5 / claude-sonnet-4-6 / claude-haiku-4-5 (Anthropic / Claude Fable opt-in)" }, + { label: "anthropic", description: "claude-opus-4-8 / claude-sonnet-5 / claude-haiku-4-5 (Anthropic / Claude)" }, + { label: "anthropic-fable", description: "claude-fable-5 / claude-sonnet-5 / claude-haiku-4-5 (Anthropic / Claude Fable opt-in)" }, { label: "openai", description: "gpt-5.5 / gpt-5.4 / gpt-5.4-mini (OpenAI / Codex)" }, { label: "Other known provider", description: "Type google or qwen; both still use the canonical tier mapping." } ] @@ -654,11 +654,11 @@ Canonical tier mappings by provider and budget: | Provider | Budget | high | medium | low | |-----------|--------|----------------------------|----------------------------|----------------------------| -| anthropic | high | claude-opus-4-8 | claude-opus-4-8 | claude-sonnet-4-6 | -| anthropic | medium | claude-opus-4-8 | claude-sonnet-4-6 | claude-haiku-4-5 | +| anthropic | high | claude-opus-4-8 | claude-opus-4-8 | claude-sonnet-5 | +| anthropic | medium | claude-opus-4-8 | claude-sonnet-5 | claude-haiku-4-5 | | anthropic | low | claude-haiku-4-5 | claude-haiku-4-5 | claude-haiku-4-5 | -| anthropic-fable | high | claude-fable-5 | claude-fable-5 | claude-sonnet-4-6 | -| anthropic-fable | medium | claude-opus-4-8 | claude-sonnet-4-6 | claude-haiku-4-5 | +| anthropic-fable | high | claude-fable-5 | claude-fable-5 | claude-sonnet-5 | +| anthropic-fable | medium | claude-opus-4-8 | claude-sonnet-5 | claude-haiku-4-5 | | anthropic-fable | low | claude-haiku-4-5 | claude-haiku-4-5 | claude-haiku-4-5 | | openai | high | gpt-5.5 | gpt-5.5 | gpt-5.5 | | openai | medium | gpt-5.5 | gpt-5.4 | gpt-5.4-mini | diff --git a/package-lock.json b/package-lock.json index 8e8f15205..fe39eb0c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@opengsd/gsd-core", - "version": "1.6.0", + "version": "1.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@opengsd/gsd-core", - "version": "1.6.0", + "version": "1.6.1", "license": "MIT", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.84", diff --git a/package.json b/package.json index d99ca54ac..cb0781744 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@opengsd/gsd-core", - "version": "1.6.0", + "version": "1.6.1", "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.", "bin": { "gsd-core": "bin/install.js", diff --git a/src/milestone.cts b/src/milestone.cts index f15c9baad..9bf81cc05 100644 --- a/src/milestone.cts +++ b/src/milestone.cts @@ -184,6 +184,13 @@ function cmdMilestoneComplete(cwd: string, version: string, options: MilestoneCo })(); while ((pm = phasePattern.exec(scopedContent)) !== null) { const phaseNum = pm[1]; + // Phase 0 (pre-milestone) and Phase 999 (backlog) are sentinels, not + // real phases — they legitimately have no directory and must not block + // milestone completion. Mirrors the engine-wide sentinel convention + // (phase-id getMilestoneFromPhaseId, roadmap-command-router SENTINELS, + // the #1445 /^999/ progress filters). (#1580) + const major = parseInt(phaseNum, 10); + if (major === 0 || major === 999) continue; const normalized = normalizePhaseName(phaseNum); // A phase has disk_status: 'no_directory' when no phase directory // with a matching token exists on disk. Use the same phaseTokenMatches diff --git a/src/phase.cts b/src/phase.cts index 4e0a3ae4a..879719098 100644 --- a/src/phase.cts +++ b/src/phase.cts @@ -1649,7 +1649,19 @@ function cmdPhaseComplete(cwd: string, phaseNum: string, raw: boolean): void { if (isLastPhase && roadmapContent !== null) { try { const roadmapForPhases = extractCurrentMilestone(roadmapContent, cwd); - const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi; + // #1591: match BOTH heading-style phases (`### Phase N:`) AND + // checkbox-list items, INCLUDING the canonical bold form the roadmap + // template emits (`- [ ] **Phase N: Name**`). When the active + // milestone's checklist is `- [ ]` items inside a
block + // (and the next phase has no directory yet, so the disk-based + // resolver finds nothing), this roadmap-enumeration fallback is the + // only path that can find the next phase. The prior heading-only + // pattern missed checkbox items, and a checkbox-only broadening still + // missed the bold template rows → is_last_phase=true on a mid-milestone + // phase. Allow optional `**`/`__` emphasis after the marker and stop + // the name capture at emphasis so bold names slug cleanly; the number + // capture is unchanged. + const phasePattern = /(?:#{2,4}|-\s*\[[ xX]\])\s*(?:\*\*|__)?\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n*]+)/gi; let pm: RegExpExecArray | null; while ((pm = phasePattern.exec(roadmapForPhases)) !== null) { if (comparePhaseNum(pm[1], phaseNum) > 0) { diff --git a/src/roadmap.cts b/src/roadmap.cts index 1c442c502..510edf647 100644 --- a/src/roadmap.cts +++ b/src/roadmap.cts @@ -318,6 +318,16 @@ function cmdRoadmapAnalyze(cwd: string, raw: boolean): void { }> = []; let match: RegExpExecArray | null; + // Phase 0 (pre-milestone) and Phase 999 (backlog) are sentinels, not real + // phases. They legitimately have no directory and must never be surfaced as + // current/next phase or counted in phase_count. Mirrors the engine-wide + // sentinel convention (phase-id getMilestoneFromPhaseId, roadmap-command-router + // SENTINELS, the #1445 /^999/ progress filters). (#1580) + const isSentinelPhase = (num: string): boolean => { + const major = parseInt(num, 10); + return major === 0 || major === 999; + }; + // Build phase directory lookup once (O(1) readdir instead of O(N) per phase) const _phaseDirNames = (() => { try { @@ -329,6 +339,7 @@ function cmdRoadmapAnalyze(cwd: string, raw: boolean): void { while ((match = phasePattern.exec(content)) !== null) { const phaseNum = match[1]; + if (isSentinelPhase(phaseNum)) continue; const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim(); // Extract goal from the section @@ -437,7 +448,7 @@ function cmdRoadmapAnalyze(cwd: string, raw: boolean): void { checklistPhases.add(checklistMatch[1]); } const detailPhases = new Set(phases.map(p => p.number)); - const missingDetails = [...checklistPhases].filter(p => !detailPhases.has(p)); + const missingDetails = [...checklistPhases].filter(p => !detailPhases.has(p) && !isSentinelPhase(p)); const result = { milestones, diff --git a/src/shell-command-projection.cts b/src/shell-command-projection.cts index ca762b3ed..570574e1c 100644 --- a/src/shell-command-projection.cts +++ b/src/shell-command-projection.cts @@ -253,6 +253,15 @@ export function isManagedHookCommand(commandText: unknown, opts: { surface?: str return false; } +/** + * Detect a `"$VAR"/rest` anchored hook-script token — a path whose leading + * shell variable is already double-quoted with the remainder left bare (the + * shape `projectLocalHookPrefix` emits for local installs, e.g. + * `"$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-x.js`). Such a token is ALREADY a + * valid, correctly-quoted shell argument and must never be re-quoted. + */ +const ANCHORED_HOOK_SCRIPT_TOKEN = /^"\$[A-Za-z_][A-Za-z0-9_]*"\//; + /** * Projection helper for legacy settings.json hook rewrites. * @@ -275,8 +284,20 @@ export function projectLegacySettingsHookCommand({ }): string | null { if (!absoluteRunner || !scriptPath) return null; const normalizedScriptPath = platform === 'win32' ? scriptPath.replace(/\\/g, '/') : scriptPath; + // #1693: a script path already carrying a `"$CLAUDE_PROJECT_DIR"`-anchored + // quoted prefix (local installs) is already a valid shell token — only the + // variable is quoted, the rest is bare. JSON.stringify-ing it on Windows + // yields `"\"$CLAUDE_PROJECT_DIR\"/..."` (escaped quotes inside an outer + // quote); node then receives an argument that *starts* with a `"`, treats it + // as relative, and dies with MODULE_NOT_FOUND. Emit anchored tokens verbatim; + // only bare absolute paths (which may contain spaces, e.g. "Program Files") + // need the JSON.stringify quoting. Scoped to win32: the non-Windows branch + // already preserves the caller's `scriptToken` (which is the bare anchored + // token for these inputs), so it never had the double-quote bug. const commandScriptToken = platform === 'win32' - ? JSON.stringify(normalizedScriptPath) + ? (ANCHORED_HOOK_SCRIPT_TOKEN.test(normalizedScriptPath) + ? normalizedScriptPath + : JSON.stringify(normalizedScriptPath)) : (scriptToken || JSON.stringify(normalizedScriptPath)); return projectShellCommandText({ runnerToken: absoluteRunner, diff --git a/tests/bug-3413-shell-command-projection.test.cjs b/tests/bug-3413-shell-command-projection.test.cjs index bbc6a2541..8a793fb01 100644 --- a/tests/bug-3413-shell-command-projection.test.cjs +++ b/tests/bug-3413-shell-command-projection.test.cjs @@ -187,3 +187,92 @@ describe('bug #3439: shell projection module owns managed-hook policy and legacy ); }); }); + +describe('#1693 regression: Windows legacy-node rewrite must not double-quote a "$CLAUDE_PROJECT_DIR"-anchored local hook path', () => { + const winRunner = '"C:/Program Files/nodejs/node.exe"'; + + // WHY: a local-install hook path already carries a `"$CLAUDE_PROJECT_DIR"` + // anchored prefix (only the variable quoted, rest bare). On Windows the legacy + // rewrite previously JSON.stringify'd the whole token, yielding + // `"\"$CLAUDE_PROJECT_DIR\"/..."`. node then received an argument starting with + // a literal `"`, treated it as relative, and died with MODULE_NOT_FOUND — + // breaking every node managed hook at once (self-locking deadlock). + test('projectLegacySettingsHookCommand emits the anchored path verbatim, not re-quoted', () => { + const anchored = '"$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js'; + const command = projectLegacySettingsHookCommand({ + absoluteRunner: winRunner, + scriptPath: anchored, + scriptToken: anchored, + platform: 'win32', + runtime: 'claude', + }); + assert.equal( + command, + '"C:/Program Files/nodejs/node.exe" "$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js', + ); + assert.ok(!command.includes('\\"'), 'must not contain escaped double-quotes'); + }); + + // WHY: the fix must be surgical — a BARE absolute Windows path (no anchored + // prefix) can contain spaces ("Program Files") and still REQUIRES quoting. + test('projectLegacySettingsHookCommand still quotes a bare absolute Windows path', () => { + const abs = 'C:/Program Files App/.claude/hooks/gsd-context-monitor.js'; + const command = projectLegacySettingsHookCommand({ + absoluteRunner: winRunner, + scriptPath: abs, + scriptToken: JSON.stringify(abs), + platform: 'win32', + runtime: 'claude', + }); + assert.equal( + command, + '"C:/Program Files/nodejs/node.exe" "C:/Program Files App/.claude/hooks/gsd-context-monitor.js"', + ); + }); + + // WHY: the anchored short-circuit is scoped to win32. On POSIX the rewrite + // already preserved the caller's original `scriptToken` and never had the + // double-quote bug, so that behavior must be left byte-identical. scriptPath + // is anchored but scriptToken is a DISTINCT single-quoted value: if the + // win32 gate were removed, the anchored short-circuit would emit scriptPath + // and this assertion would fail — that is what pins the gate. + test('projectLegacySettingsHookCommand preserves the original scriptToken for anchored paths on POSIX', () => { + const command = projectLegacySettingsHookCommand({ + absoluteRunner: '"/usr/local/bin/node"', + scriptPath: '"$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-statusline.js', + scriptToken: "'/x/hooks/gsd-statusline.js'", + platform: 'linux', + runtime: 'claude', + }); + assert.equal(command, `"/usr/local/bin/node" '/x/hooks/gsd-statusline.js'`); + }); + + // WHY: end-to-end through the installer rewrite — the actual #2979 path that + // ran during the user's 1.5.0 -> 1.6.0 local update. Managed node hooks get + // the absolute runner + clean anchored path; a non-node-prefixed managed .sh + // hook (already correct) is left untouched. + test('rewriteLegacyManagedNodeHookCommands produces clean anchored node commands on Windows', () => { + const settings = { + hooks: { + PostToolUse: [ + { + hooks: [ + { command: 'node "$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js' }, + ], + }, + ], + }, + }; + const changed = rewriteLegacyManagedNodeHookCommands(settings, winRunner, { + platform: 'win32', + runtime: 'claude', + }); + assert.equal(changed, true); + const rewritten = settings.hooks.PostToolUse[0].hooks[0].command; + assert.equal( + rewritten, + '"C:/Program Files/nodejs/node.exe" "$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js', + ); + assert.ok(!rewritten.includes('\\"'), 'rewritten command must not contain escaped double-quotes'); + }); +}); diff --git a/tests/bug-978-milestone-complete-force.test.cjs b/tests/bug-978-milestone-complete-force.test.cjs index 8cd74c883..0ff7d5612 100644 --- a/tests/bug-978-milestone-complete-force.test.cjs +++ b/tests/bug-978-milestone-complete-force.test.cjs @@ -19,10 +19,13 @@ const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); * Build a fixture where the guard will fire: * - STATE.md has `milestone: ` so the guard's version-match check is * satisfied. - * - ROADMAP.md lists a `### Phase 999.1: Backlog Work` heading for that - * milestone, but there is NO on-disk phase directory for it. + * - ROADMAP.md lists a `### Phase 2: Real Work` heading for that milestone, but + * there is NO on-disk phase directory for it. * * This guarantees "unstarted phase" detection without touching any real phases. + * NOTE: the unstarted phase must be a REAL phase number — Phase 0 and Phase 999 + * are backlog/pre-milestone sentinels that are intentionally excluded from this + * guard (#1580), so they would not fire it. */ function makeGuardFixture(tmpDir, version) { // STATE.md with frontmatter milestone field matching the version @@ -32,10 +35,10 @@ function makeGuardFixture(tmpDir, version) { ); // ROADMAP.md — the heading must include the version so getMilestonePhaseFilter - // does not return missingExplicitVersion. Phase 999.1 has no on-disk dir. + // does not return missingExplicitVersion. Phase 2 has no on-disk dir. fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), - `# Roadmap ${version}\n\n### Phase 999.1: Backlog Work\n**Goal:** Not started\n`, + `# Roadmap ${version}\n\n### Phase 2: Real Work\n**Goal:** Not started\n`, ); } @@ -80,7 +83,7 @@ describe('bug-978: milestone complete --force overrides unstarted-phase guard', const output = JSON.parse(result.output); assert.strictEqual(output.version, 'v1.0'); - // Milestone entry should have been created even though phase 999.1 has no dir + // Milestone entry should have been created even though phase 2 has no dir assert.ok( fs.existsSync(path.join(tmpDir, '.planning', 'MILESTONES.md')), 'MILESTONES.md should have been created', diff --git a/tests/feat-49-model-policy-presets.test.cjs b/tests/feat-49-model-policy-presets.test.cjs index d660a61ab..b7e3920ac 100644 --- a/tests/feat-49-model-policy-presets.test.cjs +++ b/tests/feat-49-model-policy-presets.test.cjs @@ -142,8 +142,8 @@ describe('#49 resolveModelPolicy Sub-path B: provider presets', () => { test('known provider "anthropic-fable" + tier "haiku" + budget "high" keeps low tier on Sonnet', () => { const policy = { provider: 'anthropic-fable', budget: 'high' }; const result = resolveModelPolicy(policy, 'haiku'); - assert.strictEqual(result, 'claude-sonnet-4-6', - `expected anthropic-fable haiku/high to resolve to claude-sonnet-4-6, got: ${result}`); + assert.strictEqual(result, 'claude-sonnet-5', + `expected anthropic-fable haiku/high to resolve to claude-sonnet-5, got: ${result}`); }); test('known provider "openai" + tier "sonnet" + budget "low" returns model with reasoning_effort from preset', () => { diff --git a/tests/fix-1445-999x-backlog-excluded-from-total-phases.test.cjs b/tests/fix-1445-999x-backlog-excluded-from-total-phases.test.cjs index 1490db251..00b4d169a 100644 --- a/tests/fix-1445-999x-backlog-excluded-from-total-phases.test.cjs +++ b/tests/fix-1445-999x-backlog-excluded-from-total-phases.test.cjs @@ -16,6 +16,14 @@ * Scenarios: * A. deriveProgressFromRoadmap with a progress table containing a 999.x row. * B. state json total_phases via extractCurrentMilestone / roadmapPhaseCount. + * + * Follow-up #1580: the same `^999` (and Phase 0) sentinel exclusion was missing + * in two more code paths — `milestone complete`'s unstarted-phase guard + * (src/milestone.cts) and `roadmap analyze`'s next_phase routing + phase_count + * (src/roadmap.cts). Scenarios C and D below cover those. + * + * C. milestone complete is NOT blocked by a Phase 999 backlog heading. + * D. roadmap analyze never routes next_phase to 999 / never counts it. */ const { describe, test, beforeEach, afterEach } = require('node:test'); @@ -172,3 +180,116 @@ describe('bug #1445 — state json excludes 999.x phase headings from total_phas ); }); }); + +// ─── Scenario C: milestone complete not blocked by a 999 backlog heading ───── + +describe('fix #1580 — milestone complete ignores the 999 backlog sentinel', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject('fix-1580-mc-'); + const planning = path.join(tmpDir, '.planning'); + // One real, on-disk phase + a directory-less Phase 999 backlog heading. + fs.writeFileSync( + path.join(planning, 'ROADMAP.md'), + [ + '# Roadmap v1.0', + '## v1.0 Milestone', + '## Phases', + '- [x] **Phase 1: Foundation**', + '## Phase Details', + '### Phase 1: Foundation', + '**Goal:** build it', + '### Phase 999: Backlog / Someday', + '**Goal:** deferred, never executed', + ].join('\n'), + 'utf-8', + ); + fs.writeFileSync( + path.join(planning, 'STATE.md'), + `---\nmilestone: v1.0\n---\n# State\n\n**Status:** In progress\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n`, + 'utf-8', + ); + const dir = path.join(planning, 'phases', '01-foundation'); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, 'PLAN.md'), '# Plan\n', 'utf-8'); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('completes WITHOUT --force despite a Phase 999 backlog heading', () => { + const result = runGsdTools( + ['milestone', 'complete', 'v1.0', '--name', 'Regression'], + tmpDir, + ); + assert.ok( + result.success, + `milestone complete must not be blocked by the 999 sentinel; got error: ${result.error}`, + ); + assert.ok( + !/Cannot mark milestone complete/.test(result.error || ''), + `the unstarted-phase guard must not fire on Phase 999. Got: ${result.error}`, + ); + }); +}); + +// ─── Scenario D: roadmap analyze never routes/ counts the 999 sentinel ──────── + +describe('fix #1580 — roadmap analyze excludes the 999 backlog sentinel', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject('fix-1580-ra-'); + const planning = path.join(tmpDir, '.planning'); + fs.writeFileSync( + path.join(planning, 'ROADMAP.md'), + [ + '# Roadmap v1.0', + '## v1.0 Milestone', + '## Phases', + '- [x] **Phase 1: Foundation**', + '## Phase Details', + '### Phase 1: Foundation', + '**Goal:** build it', + '### Phase 999: Backlog / Someday', + '**Goal:** deferred, never executed', + ].join('\n'), + 'utf-8', + ); + fs.writeFileSync( + path.join(planning, 'STATE.md'), + `---\nmilestone: v1.0\n---\n# State\n`, + 'utf-8', + ); + const dir = path.join(planning, 'phases', '01-foundation'); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, 'PLAN.md'), '# Plan\n', 'utf-8'); + fs.writeFileSync(path.join(dir, 'SUMMARY.md'), '# Summary\n', 'utf-8'); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('next_phase is never 999 and phase_count excludes the sentinel', () => { + const result = runGsdTools(['roadmap', 'analyze', '--raw'], tmpDir); + assert.ok(result.success, `roadmap analyze failed: ${result.error}`); + const analysis = JSON.parse(result.output); + assert.notEqual( + String(analysis.next_phase), + '999', + `next_phase must never route to the 999 backlog sentinel. Got ${analysis.next_phase}`, + ); + assert.equal( + analysis.phase_count, + 1, + `phase_count must exclude the 999 sentinel (expected 1). Got ${analysis.phase_count}`, + ); + assert.ok( + !(analysis.phases || []).some(p => String(p.number) === '999'), + 'the phases array must not include the 999 backlog sentinel', + ); + }); +}); diff --git a/tests/issue-2517-runtime-aware-profiles.test.cjs b/tests/issue-2517-runtime-aware-profiles.test.cjs index 84b3d43d4..812c0329e 100644 --- a/tests/issue-2517-runtime-aware-profiles.test.cjs +++ b/tests/issue-2517-runtime-aware-profiles.test.cjs @@ -705,9 +705,9 @@ describe('issue #2612: runtime "opencode" — OpenCode tier resolution', () => { assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'anthropic/claude-opus-4-8'); }); - test('sonnet tier -> anthropic/claude-sonnet-4-6', () => { + test('sonnet tier -> anthropic/claude-sonnet-5', () => { writeConfig(tmpDir, { runtime: 'opencode', model_profile: 'balanced' }); - assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-roadmapper'), 'anthropic/claude-sonnet-4-6'); + assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-roadmapper'), 'anthropic/claude-sonnet-5'); }); test('haiku tier -> anthropic/claude-haiku-4-5', () => { @@ -733,9 +733,9 @@ describe('issue #2612: runtime "copilot" — Copilot tier resolution', () => { assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'claude-opus-4-8'); }); - test('sonnet tier -> claude-sonnet-4-6', () => { + test('sonnet tier -> claude-sonnet-5', () => { writeConfig(tmpDir, { runtime: 'copilot', model_profile: 'balanced' }); - assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-roadmapper'), 'claude-sonnet-4-6'); + assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-roadmapper'), 'claude-sonnet-5'); }); test('haiku tier -> claude-haiku-4-5', () => { @@ -862,6 +862,6 @@ describe('issue #2612: partial override merge for new Group A runtimes', () => { // gsd-codebase-mapper budget -> haiku -> overridden assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-codebase-mapper'), 'claude-haiku-4-6'); // gsd-planner budget -> sonnet -> built-in default - assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'claude-sonnet-4-6'); + assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'claude-sonnet-5'); }); }); diff --git a/tests/model-alias-map.test.cjs b/tests/model-alias-map.test.cjs index d12f07afa..c4ae2f951 100644 --- a/tests/model-alias-map.test.cjs +++ b/tests/model-alias-map.test.cjs @@ -17,8 +17,8 @@ describe('MODEL_ALIAS_MAP (#1690 regression)', () => { assert.equal(MODEL_ALIAS_MAP.opus, 'claude-opus-4-8'); }); - test('sonnet maps to claude-sonnet-4-6', () => { - assert.equal(MODEL_ALIAS_MAP.sonnet, 'claude-sonnet-4-6'); + test('sonnet maps to claude-sonnet-5', () => { + assert.equal(MODEL_ALIAS_MAP.sonnet, 'claude-sonnet-5'); }); test('haiku maps to claude-haiku-4-5', () => { diff --git a/tests/phase.test.cjs b/tests/phase.test.cjs index fc70420fc..c40284bcb 100644 --- a/tests/phase.test.cjs +++ b/tests/phase.test.cjs @@ -2362,6 +2362,257 @@ describe('phase complete command', () => { assert.ok(state.includes('Milestone complete'), 'status should be milestone complete'); }); + // #1591: when the active milestone's phase checklist is wrapped in a + //
block AND phases are written as `- [ ] Phase N:` checkbox list + // items (not `### Phase N:` headings), phase.complete's next-phase enumerator + // saw no further phases → is_last_phase=true, next_phase=null on a mid- + // milestone phase, and STATE.md was wrongly marked "Milestone complete" with + // total_phases decremented. extractCurrentMilestone correctly surfaces the + //
-wrapped checklist; the defect was the heading-only phasePattern + // at the isLastPhase enumerator not recognizing checkbox-list phase items. + test('#1591:
-wrapped checkbox checklist — mid-milestone phase is NOT last', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + [ + '# ROADMAP', + '', + '## Phases', + '', + '
', + '✅ v1.0 First (Phases 1–3) — SHIPPED', + '', + '- [x] Phase 1: a', + '- [x] Phase 2: b', + '- [x] Phase 3: c', + '', + '
', + '', + '
', + '🚀 v2.0 Second (Phases 36–38) — IN PLANNING', + '', + '- [x] Phase 36: first (completed)', + '- [ ] Phase 37: second', + '- [ ] Phase 38: third', + '', + '
', + '', + '## Backlog', + '', + '### Phase 999.1: future (BACKLOG)', + '', + ].join('\n') + ); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + [ + '---', + 'gsd_state_version: 1.0', + 'milestone: v2.0', + 'milestone_name: Second', + 'current_phase: "36"', + 'status: executing', + '---', + '', + '# GSD State', + '', + '**Current Phase:** 36', + '**Status:** Executing Phase 36', + '', + ].join('\n') + ); + // Only the COMPLETING phase (36) has a directory. Phases 37/38 exist only + // as `- [ ]` checklist items in the ROADMAP — they are not yet started, so + // they have no phase dirs. This is the @Azd325 scenario: the disk-based + // next-phase resolver finds nothing, and the roadmap-enumeration fallback + // (the heading-only phasePattern) is the only path that can find Phase 37. + const d36 = path.join(tmpDir, '.planning', 'phases', '36-first'); + fs.mkdirSync(d36, { recursive: true }); + fs.writeFileSync(path.join(d36, '36-PLAN.md'), '# Plan\n'); + fs.writeFileSync(path.join(d36, '36-SUMMARY.md'), '# Summary\n'); + + const result = runVerifiedPhaseComplete('phase complete 36', tmpDir); + assert.ok(result.success, `phase complete failed: ${result.error}`); + const output = JSON.parse(result.output); + + assert.strictEqual( + output.is_last_phase, + false, + 'Phase 36 of 36–38 must NOT be last — Phases 37/38 are still open `- [ ]` (#1591)', + ); + assert.strictEqual( + output.next_phase, + '37', + 'next_phase must resolve to 37 from the
-wrapped checkbox checklist (#1591)', + ); + + // Cascade check: a wrong is_last_phase=true previously wrote "Milestone + // complete" + decremented total_phases. With the fix, the milestone is + // still in progress. + const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + assert.ok( + !/Milestone complete/i.test(state), + 'a mid-milestone phase must not flip STATE.md to "Milestone complete" (#1591)', + ); + }); + + // #1591 (bold-checklist follow-up): the roadmap TEMPLATE emits checklist rows + // in the canonical BOLD form `- [ ] **Phase N: Name**`. The initial checkbox + // broadening only matched the un-bolded `- [ ] Phase N:` shape, so the exact + //
-wrapped bold template still fell through to is_last_phase=true. + // Guard the canonical bold form explicitly. + test('#1591:
-wrapped BOLD checkbox checklist — mid-milestone phase is NOT last', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + [ + '# ROADMAP', + '', + '## Phases', + '', + '
', + '🚀 v2.0 Second (Phases 36–38) — IN PLANNING', + '', + '- [x] **Phase 36: first (completed)** - done', + '- [ ] **Phase 37: second** - one-line description', + '- [ ] **Phase 38: third** - one-line description', + '', + '
', + '', + ].join('\n') + ); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + [ + '---', + 'gsd_state_version: 1.0', + 'milestone: v2.0', + 'milestone_name: Second', + 'current_phase: "36"', + 'status: executing', + '---', + '', + '# GSD State', + '', + '**Current Phase:** 36', + '**Status:** Executing Phase 36', + '', + ].join('\n') + ); + // Only Phase 36 has a directory; 37/38 are bold `- [ ]` checklist rows only. + const d36 = path.join(tmpDir, '.planning', 'phases', '36-first'); + fs.mkdirSync(d36, { recursive: true }); + fs.writeFileSync(path.join(d36, '36-PLAN.md'), '# Plan\n'); + fs.writeFileSync(path.join(d36, '36-SUMMARY.md'), '# Summary\n'); + + const result = runVerifiedPhaseComplete('phase complete 36', tmpDir); + assert.ok(result.success, `phase complete failed: ${result.error}`); + const output = JSON.parse(result.output); + + assert.strictEqual( + output.is_last_phase, + false, + 'Phase 36 of 36–38 must NOT be last with the canonical BOLD checklist (#1591)', + ); + assert.strictEqual( + output.next_phase, + '37', + 'next_phase must resolve to 37 from the BOLD `- [ ] **Phase N: ...**` checklist (#1591)', + ); + + const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + assert.ok( + !/Milestone complete/i.test(state), + 'a mid-milestone phase must not flip STATE.md to "Milestone complete" — bold checklist (#1591)', + ); + }); + + // #1752: the #1591 follow-up — when phase.complete wrongly returned + // is_last_phase=true on a
-wrapped mid-milestone checklist, the + // milestone-complete cascade also DECREMENTED progress.total_phases (e.g. + // 8 -> 7) and flipped status. Same root cause, distinct symptom. With the + // #1591 fix (is_last_phase=false), the decrement must not occur: with all 8 + // phase dirs on disk, total_phases stays 8 and status does not flip. + test('#1752:
-wrapped checklist — total_phases is NOT decremented on a mid-milestone phase', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + [ + '# ROADMAP', + '', + '## Phases', + '', + '
', + '✅ v1.0 First (Phases 1–3) — SHIPPED', + '', + '- [x] Phase 1: a', + '- [x] Phase 2: b', + '- [x] Phase 3: c', + '', + '
', + '', + '
', + '🚀 v2.0 Second (Phases 36–43) — IN PLANNING', + '', + '- [x] Phase 36: first (completed)', + '- [ ] Phase 37: second', + '- [ ] Phase 38: third', + '- [ ] Phase 39: fourth', + '', + '
', + '', + ].join('\n') + ); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + [ + '---', + 'gsd_state_version: 1.0', + 'milestone: v2.0', + 'milestone_name: Second', + 'current_phase: "36"', + 'status: executing', + 'progress:', + ' total_phases: 8', + ' completed_phases: 5', + ' percent: 62', + '---', + '', + '# GSD State', + '', + '**Current Phase:** 36', + '**Status:** Executing Phase 36', + '', + ].join('\n') + ); + // All 8 phase dirs on disk (Phases 36–43) so the disk count is 8 — the + // reporter's real state. Before the #1591 fix, phase.complete 36 returned + // is_last_phase=true (no Phase 37+ heading match) and the milestone-complete + // path DECREMENTED total_phases 8 -> 7. + const names = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth']; + for (let i = 0; i < 8; i++) { + const num = String(36 + i); + const d = path.join(tmpDir, '.planning', 'phases', `${num}-${names[i]}`); + fs.mkdirSync(d, { recursive: true }); + fs.writeFileSync(path.join(d, `${num}-PLAN.md`), '# Plan\n'); + } + + const result = runVerifiedPhaseComplete('phase complete 36', tmpDir); + assert.ok(result.success, `phase complete failed: ${result.error}`); + const output = JSON.parse(result.output); + assert.strictEqual(output.is_last_phase, false, 'is_last_phase must be false (#1752 cascade root)'); + + const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + assert.ok( + !/Milestone complete/i.test(state), + 'a mid-milestone phase must not flip STATE.md to "Milestone complete" (#1752)', + ); + const tpMatch = state.match(/total_phases:\s*(\d+)/); + assert.ok(tpMatch, 'STATE.md must carry a total_phases value after phase.complete'); + assert.notStrictEqual( + parseInt(tpMatch[1], 10), + 7, + 'total_phases must NOT be decremented to 7 — the #1752 cascade of the false is_last_phase', + ); + }); + test('updates REQUIREMENTS.md traceability when phase completes', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 6e785faa5..d3b24ea65 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -67,7 +67,7 @@ "scan.md": 7688, "secure-phase.md": 13476, "session-report.md": 4044, - "settings-advanced.md": 39666, + "settings-advanced.md": 39646, "settings-integrations.md": 15848, "settings.md": 33413, "ship.md": 24647,