Merge pull request #1849 from open-gsd/hotfix/1.6.1
chore: merge release v1.6.1 to main
This commit is contained in:
@@ -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",
|
||||
|
||||
13
CHANGELOG.md
13
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 `<details>`-wrapped checkbox checklist (#1591, #1752)** — when the active milestone's phase checklist was written as `- [ ] Phase N:` checkbox items inside a `<details>` 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 `<details>`-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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 <id> --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.
|
||||
|
||||
@@ -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`:
|
||||
|
||||
@@ -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` 块:
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -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 |
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <details> 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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,10 +19,13 @@ const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
||||
* Build a fixture where the guard will fire:
|
||||
* - STATE.md has `milestone: <version>` 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',
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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
|
||||
// <details> 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
|
||||
// <details>-wrapped checklist; the defect was the heading-only phasePattern
|
||||
// at the isLastPhase enumerator not recognizing checkbox-list phase items.
|
||||
test('#1591: <details>-wrapped checkbox checklist — mid-milestone phase is NOT last', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
[
|
||||
'# ROADMAP',
|
||||
'',
|
||||
'## Phases',
|
||||
'',
|
||||
'<details>',
|
||||
'<summary>✅ v1.0 First (Phases 1–3) — SHIPPED</summary>',
|
||||
'',
|
||||
'- [x] Phase 1: a',
|
||||
'- [x] Phase 2: b',
|
||||
'- [x] Phase 3: c',
|
||||
'',
|
||||
'</details>',
|
||||
'',
|
||||
'<details>',
|
||||
'<summary>🚀 v2.0 Second (Phases 36–38) — IN PLANNING</summary>',
|
||||
'',
|
||||
'- [x] Phase 36: first (completed)',
|
||||
'- [ ] Phase 37: second',
|
||||
'- [ ] Phase 38: third',
|
||||
'',
|
||||
'</details>',
|
||||
'',
|
||||
'## 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 <details>-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
|
||||
// <details>-wrapped bold template still fell through to is_last_phase=true.
|
||||
// Guard the canonical bold form explicitly.
|
||||
test('#1591: <details>-wrapped BOLD checkbox checklist — mid-milestone phase is NOT last', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
[
|
||||
'# ROADMAP',
|
||||
'',
|
||||
'## Phases',
|
||||
'',
|
||||
'<details>',
|
||||
'<summary>🚀 v2.0 Second (Phases 36–38) — IN PLANNING</summary>',
|
||||
'',
|
||||
'- [x] **Phase 36: first (completed)** - done',
|
||||
'- [ ] **Phase 37: second** - one-line description',
|
||||
'- [ ] **Phase 38: third** - one-line description',
|
||||
'',
|
||||
'</details>',
|
||||
'',
|
||||
].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 <details>-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: <details>-wrapped checklist — total_phases is NOT decremented on a mid-milestone phase', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
[
|
||||
'# ROADMAP',
|
||||
'',
|
||||
'## Phases',
|
||||
'',
|
||||
'<details>',
|
||||
'<summary>✅ v1.0 First (Phases 1–3) — SHIPPED</summary>',
|
||||
'',
|
||||
'- [x] Phase 1: a',
|
||||
'- [x] Phase 2: b',
|
||||
'- [x] Phase 3: c',
|
||||
'',
|
||||
'</details>',
|
||||
'',
|
||||
'<details>',
|
||||
'<summary>🚀 v2.0 Second (Phases 36–43) — IN PLANNING</summary>',
|
||||
'',
|
||||
'- [x] Phase 36: first (completed)',
|
||||
'- [ ] Phase 37: second',
|
||||
'- [ ] Phase 38: third',
|
||||
'- [ ] Phase 39: fourth',
|
||||
'',
|
||||
'</details>',
|
||||
'',
|
||||
].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'),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user