* fix(#1515): make Codex installs resolve their own runtime and fail closed on worktrees A Codex install with a runtime-neutral .planning/config.json resolved RUNTIME=claude and enabled git worktree isolation, which Codex's spawn_agent cannot honor. Two root causes: 1. Workflows read `config-get runtime` / `config-get workflow.use_worktrees` without `--raw`, so config-get's JSON-quoted output ("codex") was captured verbatim into the bash var and broke every `[ "$RUNTIME" = ... ]` check — the Codex fail-closed guard was dead even when runtime:codex was explicit, and Claude's own worktree degrade-check was dead too. Add `--raw` to those reads across execute-phase, autonomous, manager, diagnose-issues, quick. 2. The conversion engine emitted `--default claude` for every runtime. Stamp the codex-emitted workflows to `--default codex` (runtime) and `--default false` (use_worktrees) in _applyRuntimeRewrites case 'codex', so a neutral config on a Codex install resolves runtime=codex / worktrees off. Also extend the Codex fail-closed worktree guard to quick.md and diagnose-issues.md (they spawned isolation="worktree" with no runtime guard). Regression test asserts source<->engine parity across all five workflows (DEFECT.GENERATIVE-FIX) plus fast-check property coverage of the stamping. Closes #1515 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013vX5eUtWa2wsZEyeMf5i3r * chore(#1515): backfill changeset PR number (#1519) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013vX5eUtWa2wsZEyeMf5i3r --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
5
.changeset/humble-wasps-click.md
Normal file
5
.changeset/humble-wasps-click.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 1519
|
||||
---
|
||||
**Codex installs no longer run with unsafe Claude-style worktree isolation** — a Codex install with a runtime-neutral `.planning/config.json` was resolving its runtime as Claude and enabling git worktree isolation, which Codex's `spawn_agent` cannot honor; the Codex fail-closed guard was also silently dead because runtime/worktree config was read JSON-quoted and broke shell equality checks. Codex-emitted workflows now resolve `runtime=codex`, default `workflow.use_worktrees` to `false`, and fail closed when worktrees are forced on. (#1515)
|
||||
@@ -248,7 +248,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
|
||||
| `workflow.max_discuss_passes` | number | `3` | Maximum number of question rounds in discuss-phase before the workflow stops asking. Useful in headless/auto mode to prevent infinite discussion loops. |
|
||||
| `workflow.skip_discuss` | boolean | `false` | When `true`, `/gsd-autonomous` bypasses the discuss-phase entirely, writing minimal CONTEXT.md from the ROADMAP phase goal. Useful for projects where developer preferences are fully captured in PROJECT.md/REQUIREMENTS.md. Added in v1.28 |
|
||||
| `workflow.text_mode` | boolean | `false` | Replaces AskUserQuestion TUI menus with plain-text numbered lists. Required for Claude Code remote sessions (`/rc` mode) where TUI menus don't render. Can also be set per-session with `--text` flag on discuss-phase. Added in v1.28 |
|
||||
| `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31. **Branch-divergence note:** when your branch has diverged from `origin/HEAD`, GSD auto-degrades to sequential and prints a warning. See [`worktree.baseRef`](#worktree-settings) to restore parallel execution on a diverged branch. |
|
||||
| `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31. **Branch-divergence note:** when your branch has diverged from `origin/HEAD`, GSD auto-degrades to sequential and prints a warning. See [`worktree.baseRef`](#worktree-settings) to restore parallel execution on a diverged branch. **Codex note:** Codex maps subagents to `spawn_agent` and cannot honor Claude Code's `isolation="worktree"`, so a Codex-installed workflow resolves its runtime as `codex` and defaults this key to `false` even when `.planning/config.json` is runtime-neutral; forcing `use_worktrees: true` on a Codex install fails closed before any executor dispatch (#1515). |
|
||||
| `workflow.worktree_skip_hooks` | boolean | `false` | When `true`, executor agents in worktree mode pass `--no-verify` (skipping pre-commit hooks) and post-wave hook validation runs against the merged result instead. Opt-in escape hatch for projects whose hooks cannot run in agent worktrees. Default `false` runs hooks on every commit (#2924). |
|
||||
| `workflow.code_review` | boolean | `true` | Enable `/gsd-code-review` and `/gsd-code-review --fix` commands. When `false`, the commands exit with a configuration gate message. Added in v1.34 |
|
||||
| `workflow.code_review_depth` | string | `standard` | Default review depth for `/gsd-code-review`: `quick` (pattern-matching only), `standard` (per-file analysis), or `deep` (cross-file with import graphs). Can be overridden per-run with `--depth=`. Added in v1.34 |
|
||||
|
||||
@@ -360,7 +360,7 @@ UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
||||
**If `INTERACTIVE` is set:** Background dispatch is only safe where a backgrounded agent can still spawn subagents. On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so the plan-checker never runs and `workflow.plan_check` silently degrades to a self-check. Resolve the runtime first:
|
||||
|
||||
```bash
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
||||
```
|
||||
|
||||
- **On Claude Code (`RUNTIME` is `claude`):** Run plan **inline** (do NOT background) so the plan-checker runs. The next phase's discuss does not overlap planning here — correctness over overlap.
|
||||
@@ -422,7 +422,7 @@ Verify plan produced output — re-run `init phase-op` and check `has_plans`. If
|
||||
**If `INTERACTIVE` is set:** Wait for the plan agent to complete (if not already) and verify plans exist. Background dispatch is only safe where a backgrounded agent can still spawn subagents. On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so the per-plan worktree-isolated executors and the verifier never run (`workflow.use_worktrees` and `workflow.verifier` silently degrade). Resolve the runtime first:
|
||||
|
||||
```bash
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
||||
```
|
||||
|
||||
- **On Claude Code (`RUNTIME` is `claude`):** Run execute **inline** (do NOT background) so worktree isolation and verification run:
|
||||
|
||||
@@ -59,7 +59,12 @@ gaps = [
|
||||
|
||||
```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 [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/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" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/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; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
||||
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
|
||||
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
||||
if [ "$RUNTIME" = "codex" ] && [ "$USE_WORKTREES" != "false" ]; then
|
||||
echo "FATAL: Codex worktree isolation is unsupported. Set workflow.use_worktrees=false or use a runtime with Agent isolation=\"worktree\" support." >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Report diagnosis plan to user:**
|
||||
|
||||
@@ -91,8 +91,8 @@ Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelizat
|
||||
Read runtime/worktree config and fail closed before any executor dispatch:
|
||||
|
||||
```bash
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
||||
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
||||
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
|
||||
EXECUTOR_STALL_INTERVAL_MINUTES=$(gsd_run query config-get executor.stall_detect_interval_minutes 2>/dev/null || echo "5")
|
||||
EXECUTOR_STALL_THRESHOLD_MINUTES=$(gsd_run query config-get executor.stall_threshold_minutes 2>/dev/null || echo "10")
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ After discuss completes, loop back to dashboard step.
|
||||
Planning runs autonomously. **First resolve the runtime.** On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so it cannot spawn the plan-checker the pipeline relies on — backgrounding it there silently turns `workflow.plan_check` into a self-check. So run plan **inline** on Claude Code, and **background** it only on runtimes where a backgrounded agent can still nest subagents.
|
||||
|
||||
```bash
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
||||
```
|
||||
|
||||
**If `RUNTIME` is `claude` (Claude Code):** Run plan inline so the plan-checker and quality gates actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
|
||||
@@ -301,7 +301,7 @@ Loop back to dashboard step.
|
||||
Execution runs autonomously. **First resolve the runtime.** On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so it cannot spawn the per-plan worktree-isolated executors or the verifier — backgrounding it there silently disables `workflow.use_worktrees` isolation and `workflow.verifier`. So run execute **inline** on Claude Code, and **background** it only on runtimes where a backgrounded agent can still nest subagents.
|
||||
|
||||
```bash
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
||||
```
|
||||
|
||||
**If `RUNTIME` is `claude` (Claude Code):** Run execute inline so worktree isolation and the verifier actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
|
||||
|
||||
@@ -137,7 +137,12 @@ AGENT_SKILLS_VERIFIER=$(gsd_run query agent-skills gsd-verifier)
|
||||
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
|
||||
|
||||
```bash
|
||||
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
|
||||
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
|
||||
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
||||
if [ "$RUNTIME" = "codex" ] && [ "$USE_WORKTREES" != "false" ]; then
|
||||
echo "FATAL: Codex worktree isolation is unsupported. Set workflow.use_worktrees=false or use a runtime with Agent isolation=\"worktree\" support." >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
If `USE_WORKTREES` is not `"false"`, run a startup orphan sweep before spawning any executors. This reaps locked worktrees whose lock-owner process is dead, whose branch is merged into the default branch, and whose lock file mtime is older than 5 minutes. Running it at startup prevents accumulation of orphaned worktrees from prior sessions that exited without cleanup (#3707).
|
||||
|
||||
@@ -2139,6 +2139,20 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
||||
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
||||
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
||||
content = content.replace(/~\/\.codex\//g, pathPrefix);
|
||||
// #1515: stamp Codex's own runtime identity + safe worktree default into
|
||||
// emitted workflow runtime-resolution blocks. A Codex install with a
|
||||
// runtime-neutral .planning/config.json must resolve RUNTIME=codex (Codex
|
||||
// cannot honor Claude's isolation="worktree"), and default
|
||||
// workflow.use_worktrees to false so the fail-closed guard lets execution
|
||||
// proceed without worktrees instead of falling back to Claude semantics.
|
||||
content = content.replace(
|
||||
/config-get runtime --default claude --raw 2>\/dev\/null \|\| echo "claude"/g,
|
||||
'config-get runtime --default codex --raw 2>/dev/null || echo "codex"',
|
||||
);
|
||||
content = content.replace(
|
||||
/config-get workflow\.use_worktrees --raw 2>\/dev\/null \|\| echo "true"/g,
|
||||
'config-get workflow.use_worktrees --default false --raw 2>/dev/null || echo "false"',
|
||||
);
|
||||
content = processAttribution(content, attribution);
|
||||
break;
|
||||
|
||||
|
||||
130
tests/fix-1515-codex-runtime-default.test.cjs
Normal file
130
tests/fix-1515-codex-runtime-default.test.cjs
Normal file
@@ -0,0 +1,130 @@
|
||||
'use strict';
|
||||
/**
|
||||
* Regression tests for bug #1515: Codex install with runtime-neutral
|
||||
* .planning/config.json resolves runtime as 'claude' and enables worktree
|
||||
* isolation (unsafe for Codex).
|
||||
*
|
||||
* Root causes:
|
||||
* A) config-get reads in workflows lacked --raw → output JSON-quoted →
|
||||
* every comparison like [ "$RUNTIME" = "codex" ] failed silently.
|
||||
* B) The conversion engine emitted --default claude for every runtime →
|
||||
* neutral Codex config fell back to claude default.
|
||||
*
|
||||
* All tests assert on the SUT's RETURN VALUE (engine output), not raw file reads,
|
||||
* except the integration test (test 4) which is explicitly the source↔engine
|
||||
* parity guard and carries the allow-test-rule exemption.
|
||||
*/
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const fc = require('fast-check');
|
||||
const conversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Unit tests: engine stamps codex-specific defaults into emitted workflows
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('codex emit stamps its own runtime default into the runtime-resolution line', () => {
|
||||
const line =
|
||||
'RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")\n';
|
||||
const out = conversion._applyRuntimeRewrites(line, 'codex', '$HOME/.codex/', true, undefined);
|
||||
assert.ok(
|
||||
out.includes('config-get runtime --default codex --raw'),
|
||||
`Expected 'config-get runtime --default codex --raw' in output; got:\n${out}`,
|
||||
);
|
||||
assert.ok(
|
||||
out.includes('|| echo "codex")'),
|
||||
`Expected '|| echo "codex")' in output; got:\n${out}`,
|
||||
);
|
||||
assert.ok(
|
||||
!out.includes('--default claude'),
|
||||
`Expected '--default claude' to be fully rewritten; got:\n${out}`,
|
||||
);
|
||||
assert.ok(
|
||||
!out.includes('echo "claude"'),
|
||||
`Expected 'echo "claude"' to be fully rewritten; got:\n${out}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('codex emit defaults workflow.use_worktrees to false', () => {
|
||||
const line =
|
||||
'USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")\n';
|
||||
const out = conversion._applyRuntimeRewrites(line, 'codex', '$HOME/.codex/', true, undefined);
|
||||
assert.ok(
|
||||
out.includes('config-get workflow.use_worktrees --default false --raw'),
|
||||
`Expected 'config-get workflow.use_worktrees --default false --raw' in output; got:\n${out}`,
|
||||
);
|
||||
assert.ok(
|
||||
out.includes('|| echo "false")'),
|
||||
`Expected '|| echo "false")' in output; got:\n${out}`,
|
||||
);
|
||||
assert.ok(
|
||||
!out.includes('|| echo "true")'),
|
||||
`Expected '|| echo "true")' to be fully rewritten; got:\n${out}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('non-codex runtime (cursor) does NOT rewrite the runtime default — stamping is codex-scoped', () => {
|
||||
const line =
|
||||
'RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")\n';
|
||||
const out = conversion._applyRuntimeRewrites(line, 'cursor', '/home/u/.cursor/', true, undefined);
|
||||
assert.ok(
|
||||
out.includes('--default claude --raw'),
|
||||
`Expected cursor output to preserve '--default claude --raw'; got:\n${out}`,
|
||||
);
|
||||
assert.ok(
|
||||
!out.includes('--default codex'),
|
||||
`Expected cursor output NOT to contain '--default codex'; got:\n${out}`,
|
||||
);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Integration / parity guard: real source ↔ engine output for codex (all surfaces)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('regression: every edited workflow gets codex-stamped (source↔engine parity, all surfaces) (#1515)', () => {
|
||||
// allow-test-rule: emitted workflow runtime-resolution shell block is the runtime contract surface (#1515) — asserts on engine-transformed output of the real source
|
||||
const WORKFLOWS = ['execute-phase.md', 'autonomous.md', 'manager.md', 'diagnose-issues.md', 'quick.md'];
|
||||
const CLAUDE_RUNTIME = 'config-get runtime --default claude --raw 2>/dev/null || echo "claude"';
|
||||
const CODEX_RUNTIME = 'config-get runtime --default codex --raw 2>/dev/null || echo "codex"';
|
||||
const TRUE_WT = 'config-get workflow.use_worktrees --raw 2>/dev/null || echo "true"';
|
||||
const FALSE_WT = 'config-get workflow.use_worktrees --default false --raw 2>/dev/null || echo "false"';
|
||||
for (const wf of WORKFLOWS) {
|
||||
const src = fs.readFileSync(path.join(__dirname, '..', 'gsd-core', 'workflows', wf), 'utf8');
|
||||
const out = conversion._applyRuntimeRewrites(src, 'codex', '$HOME/.codex/', true, undefined);
|
||||
// No un-stamped claude/true resolution line may survive codex emit on ANY surface.
|
||||
assert.ok(!out.includes(CLAUDE_RUNTIME), `${wf}: residual un-stamped runtime read — engine regex no longer matches source line (parity drift)`);
|
||||
assert.ok(!out.includes(TRUE_WT), `${wf}: residual un-stamped use_worktrees read — parity drift`);
|
||||
// If the source HAS such a read, the codex form must be present.
|
||||
if (src.includes(CLAUDE_RUNTIME)) assert.ok(out.includes(CODEX_RUNTIME), `${wf}: runtime read not stamped to codex`);
|
||||
if (src.includes(TRUE_WT)) assert.ok(out.includes(FALSE_WT), `${wf}: use_worktrees read not defaulted to false`);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Property tests (RULESET.TESTS.property-based-testing)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('property: runtime stamping applies iff runtime is codex (#1515)', () => {
|
||||
const RUNTIMES = ['claude','codex','cursor','cline','windsurf','augment','trae','qwen','hermes','gemini','opencode','kilo','copilot','antigravity','codebuddy'];
|
||||
const line = 'RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")\n';
|
||||
fc.assert(fc.property(fc.constantFrom(...RUNTIMES), (rt) => {
|
||||
const out = conversion._applyRuntimeRewrites(line, rt, `$HOME/.${rt}/`, true, undefined);
|
||||
return rt === 'codex'
|
||||
? out.includes('--default codex --raw') && !out.includes('--default claude')
|
||||
: out.includes('--default claude --raw') && !out.includes('--default codex');
|
||||
}));
|
||||
});
|
||||
|
||||
test('property: codex stamping is idempotent on resolution lines (#1515)', () => {
|
||||
fc.assert(fc.property(fc.constantFrom('runtime', 'use_worktrees'), (which) => {
|
||||
const line = which === 'runtime'
|
||||
? 'RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")\n'
|
||||
: 'USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")\n';
|
||||
const once = conversion._applyRuntimeRewrites(line, 'codex', '$HOME/.codex/', true, undefined);
|
||||
const twice = conversion._applyRuntimeRewrites(once, 'codex', '$HOME/.codex/', true, undefined);
|
||||
return once === twice;
|
||||
}));
|
||||
});
|
||||
@@ -8,14 +8,14 @@
|
||||
"audit-fix.md": 10988,
|
||||
"audit-milestone.md": 17637,
|
||||
"audit-uat.md": 7425,
|
||||
"autonomous.md": 42263,
|
||||
"autonomous.md": 42275,
|
||||
"check-todos.md": 9431,
|
||||
"cleanup.md": 9897,
|
||||
"code-review-fix.md": 23890,
|
||||
"code-review.md": 31602,
|
||||
"complete-milestone.md": 29987,
|
||||
"debug.md": 13505,
|
||||
"diagnose-issues.md": 12425,
|
||||
"diagnose-issues.md": 12762,
|
||||
"discovery-phase.md": 8651,
|
||||
"discuss-phase-assumptions.md": 26984,
|
||||
"discuss-phase-power.md": 11273,
|
||||
@@ -24,7 +24,7 @@
|
||||
"docs-update.md": 55662,
|
||||
"edit-phase.md": 12883,
|
||||
"eval-review.md": 9923,
|
||||
"execute-phase.md": 92914,
|
||||
"execute-phase.md": 92926,
|
||||
"execute-plan.md": 31365,
|
||||
"explore.md": 10497,
|
||||
"extract-learnings.md": 12849,
|
||||
@@ -39,7 +39,7 @@
|
||||
"insert-phase.md": 8943,
|
||||
"list-phase-assumptions.md": 4305,
|
||||
"list-workspaces.md": 5655,
|
||||
"manager.md": 25937,
|
||||
"manager.md": 25949,
|
||||
"map-codebase.md": 20789,
|
||||
"milestone-summary.md": 11774,
|
||||
"mvp-phase.md": 13582,
|
||||
@@ -57,7 +57,7 @@
|
||||
"pr-branch.md": 9561,
|
||||
"profile-user.md": 20650,
|
||||
"progress.md": 29387,
|
||||
"quick.md": 48435,
|
||||
"quick.md": 48772,
|
||||
"reapply-patches.md": 20393,
|
||||
"remove-phase.md": 8469,
|
||||
"remove-workspace.md": 7507,
|
||||
|
||||
Reference in New Issue
Block a user