Merge remote-tracking branch 'upstream/next' into kimi-runtime-support
This commit is contained in:
@@ -198,6 +198,8 @@ const learnings = require('./lib/learnings.cjs');
|
||||
const gapChecker = require('./lib/gap-checker.cjs');
|
||||
const { routeStateCommand } = require('./lib/state-command-router.cjs');
|
||||
const { routeVerifyCommand } = require('./lib/verify-command-router.cjs');
|
||||
const { routeVerificationCommand } = require('./lib/verification-command-router.cjs');
|
||||
const verification = require('./lib/verification.cjs');
|
||||
const { routeInitCommand } = require('./lib/init-command-router.cjs');
|
||||
const { routePhaseCommand } = require('./lib/phase-command-router.cjs');
|
||||
const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
|
||||
@@ -547,7 +549,22 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
||||
}
|
||||
|
||||
case 'resolve-granularity': {
|
||||
commands.cmdResolveGranularity(cwd, args[1], raw);
|
||||
// Parse optional --granularity <val> flag (space form only); positional is phase-type.
|
||||
// The =form (--granularity=<val>) is intentionally not supported: parseNamedArgs and
|
||||
// the /gsd:plan-phase + init plan-phase paths accept only the space form, so supporting
|
||||
// = here alone would create an inconsistency (#703).
|
||||
const granArgs = args.slice(1);
|
||||
let granOverride;
|
||||
const granPositionals = [];
|
||||
for (let i = 0; i < granArgs.length; i++) {
|
||||
const a = granArgs[i];
|
||||
if (a === '--granularity' && granArgs[i + 1] !== undefined && !granArgs[i + 1].startsWith('--')) {
|
||||
if (granOverride === undefined) { granOverride = granArgs[++i]; } else { ++i; }
|
||||
} else {
|
||||
granPositionals.push(a);
|
||||
}
|
||||
}
|
||||
commands.cmdResolveGranularity(cwd, granPositionals[0], raw, granOverride);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -761,6 +778,26 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
||||
break;
|
||||
}
|
||||
|
||||
// ─── Verification Status ───────────────────────────────────────────────
|
||||
//
|
||||
// verification status <phaseDir>
|
||||
// Read the first *-VERIFICATION.md in phaseDir and return
|
||||
// { status, next_action, next_command } routing result.
|
||||
//
|
||||
// Note: `verification` (reads verifier-emitted status) is distinct from
|
||||
// `verify` (runs verification checks like plan-structure/artifacts).
|
||||
|
||||
case 'verification': {
|
||||
routeVerificationCommand({
|
||||
verification,
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'generate-slug': {
|
||||
// Phase 6 (#3575): dispatch via SDK executeForCjs when available.
|
||||
// SDK handler: generateSlug in sdk/src/query/utils.ts.
|
||||
@@ -1232,8 +1269,12 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
||||
worktreeSafety.cmdWorktreeCleanupWave(cwd, args.slice(2));
|
||||
} else if (subcommand === 'reap-orphans') {
|
||||
worktreeSafety.cmdWorktreeReapOrphans(cwd);
|
||||
} else if (subcommand === 'base-check') {
|
||||
require('./lib/worktree-base-ref.cjs').cmdWorktreeBaseCheck(cwd, args.slice(2));
|
||||
} else if (subcommand === 'set-baseref') {
|
||||
require('./lib/worktree-base-ref.cjs').cmdWorktreeSetBaseRef(cwd, args.slice(2));
|
||||
} else {
|
||||
error('Unknown worktree subcommand. Available: cleanup-wave, reap-orphans', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
||||
error('Unknown worktree subcommand. Available: cleanup-wave, reap-orphans, base-check, set-baseref', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@
|
||||
"model_policy.budget",
|
||||
"model_policy.high",
|
||||
"model_policy.medium",
|
||||
"model_policy.low"
|
||||
"model_policy.low",
|
||||
"agent_skills_security.trusted_global_roots"
|
||||
],
|
||||
"runtimeStateKeys": [
|
||||
"workflow._auto_chain_active"
|
||||
|
||||
@@ -34,7 +34,7 @@ Configuration options for `.planning/` directory behavior.
|
||||
| `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy |
|
||||
| `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy |
|
||||
| `git.quick_branch_template` | `null` | Optional branch template for quick-task runs |
|
||||
| `workflow.use_worktrees` | `true` | Whether executor agents run in isolated git worktrees. Set to `false` to disable worktrees — agents execute sequentially on the main working tree instead. Recommended for solo developers or when worktree merges cause issues. |
|
||||
| `workflow.use_worktrees` | `true` | Whether executor agents run in isolated git worktrees. Set to `false` to disable worktrees — agents execute sequentially on the main working tree instead. Recommended for solo developers or when worktree merges cause issues. Note: if your branch is ahead of `origin/HEAD` (a diverged milestone or feature branch), GSD auto-degrades to sequential and prints a warning; set `worktree.baseRef:"head"` in `.claude/settings.local.json` to restore parallel execution. See the branch-divergence note below. |
|
||||
| `workflow.subagent_timeout` | `300000` | Timeout in milliseconds for parallel subagent tasks (e.g. codebase mapping). Increase for large codebases or slower models. Default: 300000 (5 minutes). |
|
||||
| `workflow.inline_plan_threshold` | `2` | Plans with this many tasks or fewer execute inline (Pattern C) instead of spawning a subagent. Avoids ~14K token spawn overhead for small plans. Set to `0` to always spawn subagents. |
|
||||
| `manager.flags.discuss` | `""` | Flags passed to `/gsd:discuss-phase` when dispatched from manager (e.g. `"--auto --analyze"`) |
|
||||
@@ -385,6 +385,8 @@ Several config fields affect each other or trigger special behavior:
|
||||
|
||||
8. **`sub_repos` auto-sync** -- On every config load, GSD scans for child directories with `.git` and updates the `sub_repos` array if the filesystem has changed. Legacy `multiRepo: true` is automatically migrated to a detected `sub_repos` array.
|
||||
|
||||
9. **`workflow.use_worktrees` and branch divergence** -- When `use_worktrees` is `true` (default), executor worktrees are forked from `origin/HEAD` by the Claude Code harness. If your current branch has commits that `origin/HEAD` does not (for example an unmerged milestone or feature branch), GSD automatically degrades to sequential execution for that run and prints a one-line `⚠ Worktree base mismatch` warning. To restore parallel execution permanently, set `worktree.baseRef:"head"` in `.claude/settings.local.json` (run `node gsd-tools.cjs worktree set-baseref`). This makes the harness fork worktrees from the live HEAD instead of `origin/HEAD`. Both fresh installs and upgrades of GSD Core set this automatically (no-clobber) when `use_worktrees` is enabled; you can also run the command manually at any time. Setting `workflow.use_worktrees: false` is the alternative if worktrees are not needed at all.
|
||||
|
||||
---
|
||||
|
||||
## Example Configurations
|
||||
|
||||
@@ -92,6 +92,16 @@ if [ "$RUNTIME" = "codex" ] && [ "$USE_WORKTREES" != "false" ]; then
|
||||
fi
|
||||
# Sweep orphaned locked worktrees from prior crashed sessions before spawning executors (#3707).
|
||||
[ "$USE_WORKTREES" != "false" ] && gsd_run query worktree.reap-orphans 2>/dev/null || true
|
||||
# Auto-degrade to sequential if HEAD has diverged from the worktree fork base (#683).
|
||||
# Only applies to Claude Code (isolation="worktree" is Claude-Code-specific).
|
||||
if [ "$RUNTIME" = "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
|
||||
_SHOULD_DEGRADE=$(gsd_run query worktree.base-check --pick shouldDegrade 2>/dev/null || true)
|
||||
if [ "$_SHOULD_DEGRADE" = "true" ]; then
|
||||
_DEGRADE_MSG=$(gsd_run query worktree.base-check --pick message 2>/dev/null || true)
|
||||
[ -n "$_DEGRADE_MSG" ] && printf '%s\n' "$_DEGRADE_MSG" >&2
|
||||
USE_WORKTREES=false
|
||||
fi
|
||||
fi
|
||||
```
|
||||
Codex maps subagents to `spawn_agent`, which has no direct Codex mapping for Claude Code's `isolation="worktree"` parameter. Failing closed prevents main-checkout edits while the workflow believes agents are isolated.
|
||||
|
||||
@@ -111,6 +121,8 @@ fi
|
||||
|
||||
When `USE_WORKTREES` (project-level) is `false`, all executor agents run without `isolation="worktree"` — they execute sequentially on the main working tree instead of in parallel worktrees. The per-plan decision below has no effect when worktrees are project-disabled.
|
||||
|
||||
`USE_WORKTREES` is also automatically set to `false` for the duration of a run when `worktree base-check` detects that the orchestrator HEAD has diverged from the worktree fork base (the #683 condition — e.g. an unmerged milestone or feature branch). This check runs only when `RUNTIME=claude` because `isolation="worktree"` is a Claude Code-specific feature; other runtimes do not use it. The auto-degrade prints a one-line warning to stderr and falls through to the sequential path so executors do not hit the exit-42 worktree-branch-check halt. To restore parallel worktree execution, set `worktree.baseRef:"head"` in `.claude/settings.local.json` (or run `gsd-tools worktree set-baseref`) — this makes the fork base track the live HEAD instead of a fixed remote ref. The `worktree-branch-check` exit-42 guard inside each executor remains in place as a backstop.
|
||||
|
||||
Read context window size for adaptive prompt enrichment:
|
||||
|
||||
```bash
|
||||
@@ -1413,16 +1425,15 @@ ${VERIFIER_SKILLS}",
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
Read status:
|
||||
Read status via the canonical query (scoped to frontmatter, covers missing/unknown cases):
|
||||
```bash
|
||||
grep "^status:" "$PHASE_DIR"/*-VERIFICATION.md | cut -d: -f2 | tr -d ' '
|
||||
VERIFICATION=$(gsd_run query verification.status "$PHASE_DIR" 2>/dev/null)
|
||||
STATUS=$(printf '%s' "$VERIFICATION" | jq -r '.status' 2>/dev/null || echo "")
|
||||
NEXT_ACTION=$(printf '%s' "$VERIFICATION" | jq -r '.next_action' 2>/dev/null || echo "")
|
||||
NEXT_COMMAND=$(printf '%s' "$VERIFICATION" | jq -r '.next_command' 2>/dev/null || echo "")
|
||||
```
|
||||
|
||||
| Status | Action |
|
||||
|--------|--------|
|
||||
| `passed` | → update_roadmap |
|
||||
| `human_needed` | Persist and present human testing items; keep phase pending until verification reruns as `passed` |
|
||||
| `gaps_found` | Present gap summary, offer `/gsd:plan-phase {phase} --gaps ${GSD_WS}` |
|
||||
Route on `$STATUS`: if `passed`, proceed to update_roadmap. Otherwise keep the phase pending — present `$NEXT_ACTION` to the user and, when `$NEXT_COMMAND` is non-empty, show it as the next command to run. The query covers all cases including missing files (`missing`) and unexpected values (`unknown`), so no per-status arm needs to be listed here.
|
||||
|
||||
**If human_needed:**
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ Load all context in one call (paths only to minimize orchestrator context):
|
||||
|
||||
```bash
|
||||
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi
|
||||
INIT=$(gsd_run query init.plan-phase "$PHASE")
|
||||
GRAN_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--granularity[[:space:]]+([^[:space:]-][^[:space:]]*) ]]; then GRAN_PARAM="--granularity ${BASH_REMATCH[2]}"; fi
|
||||
INIT=$(gsd_run query init.plan-phase "$PHASE" $GRAN_PARAM)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-phase-researcher)
|
||||
AGENT_SKILLS_PLANNER=$(gsd_run query agent-skills gsd-planner)
|
||||
@@ -46,7 +47,7 @@ When `TDD_MODE` is `true`, the planner agent is instructed to apply `type: tdd`
|
||||
|
||||
When `CONTEXT_WINDOW >= 500000`, the planner prompt includes the 3 most recent prior phase CONTEXT.md and SUMMARY.md files PLUS any phases explicitly listed in the current phase's `Depends on:` field in ROADMAP.md. Explicit dependencies always load regardless of recency (e.g., Phase 7 declaring `Depends on: Phase 2` always sees Phase 2's context). Bounded recency keeps the planner's context budget focused on recent work.
|
||||
|
||||
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `phase_status` (#3569), `planning_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`.
|
||||
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `phase_status` (#3569), `planning_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`, `granularity`.
|
||||
|
||||
**If `response_language` is set:** Include `response_language: {value}` in all spawned subagent prompts so any user-facing output stays in the configured language.
|
||||
|
||||
@@ -99,7 +100,7 @@ The gate fires only on `Complete`. `Executed` and `Needs Review` are not gated
|
||||
|
||||
## 2. Parse and Normalize Arguments
|
||||
|
||||
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--research-phase <N>`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--ingest <path-or-glob>`, `--ingest-format <auto|nygard|madr|narrative>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`, `--mvp`, `--tdd`, `--force` (override closed-phase gate, see §1.5)).
|
||||
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--research-phase <N>`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--ingest <path-or-glob>`, `--ingest-format <auto|nygard|madr|narrative>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`, `--mvp`, `--tdd`, `--granularity <coarse|standard|fine>`, `--force` (override closed-phase gate, see §1.5)).
|
||||
|
||||
**`--research-phase <N>` — research-only mode (#3042 + #3044).** When this flag is present, parse `<N>` as the phase number (overrides any positional phase argument), set `RESEARCH_ONLY=true`, and treat the rest of this workflow as a research-dispatch only — the planner spawn (step 8), plan-checker, verification, gaps, bounce, and post-planning-gaps blocks all skip on `RESEARCH_ONLY`. Use this for cross-phase research, doc review before committing to a planning approach, and correction-without-replanning loops. Replaces the deleted `/gsd-research-phase` command.
|
||||
|
||||
@@ -120,6 +121,8 @@ if $RESEARCH_ONLY && [[ "$ARGUMENTS" =~ (^|[[:space:]])--view([[:space:]]|$) ]];
|
||||
fi
|
||||
```
|
||||
|
||||
**`--granularity <coarse|standard|fine>` — CLI override (#703).** When present, this value is the resolved granularity passed to the planner — it wins over any per-phase `granularities.<type>` config, top-level `granularity` config, or project defaults. The init JSON always includes a `granularity` field reflecting the resolved value; read it from there. Invalid values (anything other than `coarse`, `standard`, `fine`) cause an error at the CLI boundary.
|
||||
|
||||
Set `TEXT_MODE=true` if `--text` is present in $ARGUMENTS OR `text_mode` from init JSON is `true`. When `TEXT_MODE` is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for Claude Code remote sessions (`/rc` mode) where TUI menus don't work through the Claude App.
|
||||
|
||||
**MVP_MODE resolution.** Resolve `MVP_MODE` once via the centralized `phase.mvp-mode` query verb. Precedence (first hit wins): CLI flag → ROADMAP.md `**Mode:** mvp` → `workflow.mvp_mode` config → false. The verb is the single source of truth — do not re-implement the chain.
|
||||
@@ -935,6 +938,7 @@ Each TDD plan gets one feature with RED/GREEN/REFACTOR gate sequence.
|
||||
|
||||
**MVP_MODE:** ${MVP_MODE} (when true, follow vertical-slice rules from `~/.claude/gsd-core/references/planner-mvp-mode.md`; when false, ignore MVP guidance entirely.)
|
||||
**WALKING_SKELETON:** ${WALKING_SKELETON} (when true, the first deliverable must be a Walking Skeleton — Read the template at `~/.claude/gsd-core/references/skeleton-template.md` and produce SKELETON.md alongside PLAN.md.)
|
||||
**Granularity:** {granularity}
|
||||
|
||||
${MVP_MODE === 'true' ? `
|
||||
<mvp_mode_active>
|
||||
|
||||
@@ -41,15 +41,12 @@ Verify the work is ready to ship:
|
||||
|
||||
1. **Verification passed?**
|
||||
```bash
|
||||
VERIFICATION_FILE=$(ls ${PHASE_DIR}/*-VERIFICATION.md 2>/dev/null | head -1)
|
||||
STATUS=$(sed -n '/^---$/,/^---$/p' "${VERIFICATION_FILE}" 2>/dev/null | grep -m1 "^status:" | cut -d: -f2 | tr -d ' ')
|
||||
VERIFICATION=$(gsd_run query verification.status "${PHASE_DIR}" 2>/dev/null)
|
||||
STATUS=$(printf '%s' "$VERIFICATION" | jq -r '.status' 2>/dev/null || echo "")
|
||||
NEXT_ACTION=$(printf '%s' "$VERIFICATION" | jq -r '.next_action' 2>/dev/null || echo "")
|
||||
NEXT_COMMAND=$(printf '%s' "$VERIFICATION" | jq -r '.next_command' 2>/dev/null || echo "")
|
||||
```
|
||||
The verifier emits exactly `passed`, `gaps_found`, or `human_needed` (see the status table in `execute-phase.md`); only `passed` may ship. Route on `${STATUS}` — on any non-`passed` value, block with `PHASE_VERIFICATION_INCOMPLETE` and state the matching next action:
|
||||
- `passed` → verification complete; continue to the next preflight check.
|
||||
- `gaps_found` → run `/gsd:plan-phase ${PHASE_NUMBER} --gaps` to plan the fixes, then re-run `/gsd:execute-phase` before shipping.
|
||||
- `human_needed` → complete the manual tests in `${PHASE_DIR}/*-UAT.md`, then re-run the verify step until status is `passed`.
|
||||
- empty (no `*-VERIFICATION.md`) → the verify step never completed; re-run `/gsd:execute-phase`.
|
||||
- any other value → unexpected status `${STATUS}`; re-run `/gsd:execute-phase` verification.
|
||||
Only `passed` may ship. If `$STATUS` is `passed`, verification is complete — continue to the next preflight check. Any other value (including `gaps_found`, `human_needed`, `missing`, and `unknown`) blocks with `PHASE_VERIFICATION_INCOMPLETE`: present `$NEXT_ACTION` to the user and, when `$NEXT_COMMAND` is non-empty, show it as the command to run next. The query already handles missing files and unexpected values, so no per-status arm is needed.
|
||||
|
||||
2. **Clean working tree?**
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user