9faacc0c153a88f939ef07ced74d56dc468f0708
55 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3fac6e629f |
test(#3145): bound the installer/runtime cluster onto the process seam (#3176)
* test(#3145): bound the installer/runtime cluster onto the process seam Migrates 156 unbounded sync spawn sites across 47 files. Allowlist 120 to 73. Timeouts are sized from evidence already in the tree rather than a house default, because this wave spawns installers rather than git plumbing and an undersized bound does not catch a hang -- it manufactures CI flake, which is worse, since a flake gets re-run instead of investigated. install.test.cjs records a real spawnSync ETIMEDOUT at a 60000ms cap on a loaded bench while another lane passed the same commit in 12.7s, so full installs are bound at 120000ms against that recorded incident. Also adds an auditable escape to the guard's timeout ceiling. The 600000ms cap was set in #3143 from partial evidence, but fragment-single-edit- propagation carries a documented, load-tested 900000ms bound on a run that chains a full build plus eight generators -- the guard would have rejected a correct timeout the moment that file left the allowlist. A value above the ceiling is now permitted only with an inline allow-spawn-timeout-ceiling marker carrying a non-empty reason. It raises the ceiling; it never waives the requirement for a bound, which is asserted directly. install-shared.cjs keeps its hand-rolled assert rather than routing through throwIfFailed: its message embeds both streams, and throwIfFailed carries only a trimmed stderr. The message now also names the outcome, so a bounded timeout reads as such across its 38 importers instead of as expected null to equal 0. * test(#3145): extract class-norm timeouts and correct the build-hooks sizing A pre-PR review found 52 copies of four class-norm timeout constants across this wave. These are not per-suite fixture bindings -- they are shared facts about how long a class of subprocess takes, derived from a recorded bench incident. That norm already moved once (60000 to 120000 after a real ETIMEDOUT), and 52 copies would have drifted the next time it moved. Extracts tests/helpers/timeouts.cjs, where each norm is justified once, and converts the copies. A site that genuinely differs -- a real tsc compile, or regen:derived -- keeps its own local constant with its own justification. Also corrects a misclassification: scripts/build-hooks.js was sized as a build at 120000 in twelve places and 60000 in another, but it compiles and bundles nothing. Its own header says no bundling needed; it copies pre-built files and syntax-checks them with vm. Three different values bounded one script; now there is one. * test(#3145): fix red CI — lint self-match and a Windows chunk overrun Two failures on PR 3176. lint-allow-test-rule-refs read a RuleTester fixture as a real exemption. The fixture exists to prove an unrelated marker does NOT suppress the rule, so it carries that marker's literal text as test data. Split via concatenation, the same idiom no-unbounded-spawn-allowlist.test.cjs already uses for its own self-match problem. The explanatory comment needed the same treatment. The Windows shard 3/3 chunk was killed at its 600000ms budget. Output stopped seven minutes before the kill, so this was an overrun rather than a slow chunk: regenDerivedPropagatesSingleFragmentEditWithNoSecondSourceSurface runs regen:derived bounded at 900000ms, which is larger than the whole chunk budget, so the chunk killer always fires first and it can never complete there. Both the test and that bound predate this change; modifying the file pulled it into the Windows targeted set and exposed it. Skipped on Windows with the reason recorded; the Linux lanes cover it. The 900000 bound and its ceiling marker are unchanged -- they are correct. * test(#3145): refresh the stale test-timings cost table The Windows shard was killed at its 600000ms per-chunk budget. run-tests.cjs packs chunks by measured duration from tests/test-timings.json, and an unknown file falls back to the table's median weight -- advisory by design, but it silently underweights exactly the files that matter. Four of the failing chunk's 22 files were absent from the table, including the two heaviest: fragment-single-edit-propagation.install.test.cjs at 230s (it runs regen:derived) and agent-fragments-emission.install.test.cjs at 79s. Both were weighted as average, so the chunk's total weight read 53.68 against a budget of 60 and the packer produced a single chunk. Regenerated from a passing full-suite run, per the remedy the script itself documents. 700 to 770 entries, 70 added, 0 dropped -- verified, since gen-test-timings.cjs replaces the table wholesale rather than merging. Proven against the real packer: the same 22 files now weigh 103.91 and split into two chunks. No logic, budget, or timeout was changed; raising a budget to make a red gate pass is not a fix. --------- Co-authored-by: sim <sim@local> |
||
|
|
4926c2e904 |
fix(#3004): update Codex adapter collaboration-tool vocabulary (#3104)
* fix(#3004): update Codex adapter collaboration-tool vocabulary The generated Codex skill adapter documented stale tool vocabulary: - wait(ids) → collaboration.wait_agent(timeout_ms=...) (the real tool), with explicit disambiguation from the unrelated exec-cell functions.wait - close_agent(id) unconditional → gated on tool visibility (same schema- detection pattern already used for spawn_agent's agent_type field) - Missing required task_name field and fork_turns parameter → added alongside the existing fork_context guidance (coexist, not replace) Updated the regression test to assert the new vocabulary (wait_agent not wait(ids), functions.wait disambiguation, task_name, fork_turns, tool_search gate on close_agent). * chore(#3004): backfill changeset PR number 3104 --------- Co-authored-by: sim <sim@local> |
||
|
|
6ad30f74b6 |
feat(#2584): Phase 3 — scheduler consumer + isolation adapters (#2635)
Final phase of #2584 (ADR-1239 Codex-binding amendment). execute-phase now negotiates dispatch.isolation and dispatches through the matching adapter, so a wave's independent plans run concurrently on six runtimes instead of one — with no runtime=== branch in the scheduler. harness-worktree passes the host's declared isolation flag (claude, cursor); orchestrator-worktree creates the worktree via the Phase-2 verb and spawns the executor into it with the resolved argv/cwd (codex, opencode, kimi, kimi-code); none stays sequential. Undeclared/unknown/unresolvable isolation degrades to none — never an unisolated parallel run. Fixes two shipped Phase-2 descriptors that per-host research found would fail at spawn: kimi lacked its headless flag (would launch the interactive TUI and hang the orchestrator), and kimi-code named a non-existent binary (Kimi Code installs as 'kimi'). Adds the worktree-path root confinement Phase 2 deferred here, and leading-dash guards on the resolver's prompt/cwd matching the existing git-argument guard. Closes #2627 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cd6665d73b |
fix(#2406): stop Codex config.toml from double-registering agent roles (#2432)
* fix(#2406): stop Codex config.toml from double-registering agent roles generateCodexConfigBlock emitted an [agents.<name>] role table per agent pointing config_file back at the standalone agents/<name>.toml Codex already auto-discovers, so every install declared each role twice in one config layer and Codex logged a duplicate-role warning per agent. Remove the redundant role-table loop; the standalone per-agent TOML is now the sole canonical registration source. The existing marker-truncate and leaked-section stripping in mergeCodexConfig already clean up legacy [agents.gsd-*] tables from prior installs, so updates converge to zero duplicates without any new migration path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#2406): regenerate fixtures + lint gate-prep * fix(#2406): repair failing tests after gate verification * docs(#2406): add changeset for Codex duplicate agent-role fix Adds the missing .changeset/*.md fragment for the Codex config.toml double-registration fix, closing the PR-gate finding from review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#2406): backfill changeset pr (#2432) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a22333034b |
fix(#2310): guard Codex agent model_overrides so Anthropic aliases never leak into .toml (#2312)
* fix(#2310): guard Codex agent model_overrides so Anthropic aliases never leak into .toml generateCodexAgentToml embedded a per-agent `model_overrides` value verbatim as the Codex `.toml` `model`, leaking GSD/Claude tier aliases (opus/sonnet/haiku/fable) and `claude-*` ids. Codex/ChatGPT rejects those (400 "The 'sonnet' model is not supported when using Codex with a ChatGPT account"), and since spawn_agent has no inline model param, the model is baked into the .toml at install time — so the orchestrator could not recover and fell back to the non-equivalent generic-agent workaround. Translate a GSD tier alias through the Codex tier map (sonnet -> gpt-5.6-terra); drop with a deduped warning any Anthropic-flavored value with no Codex mapping (fable) or a `claude-*` id, so emission falls through to the runtime-aware resolver or Codex's default. A final safety gate blocks an Anthropic-flavored model from the runtime- resolver path too (runtime/target mismatch). Mirrors the Claude-side override guard (#2041). Real Codex/OpenAI model ids in model_overrides still pass through verbatim (#2256 preserved); runtime:"codex" tier resolution unchanged (#2517). Adds regression + fast-check property tests in tests/codex-config.test.cjs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#2310): backfill changeset PR number to #2312 * fix(#2310): Codex passive-model posture — omit Anthropic-flavored model (all namespacings) Adopt ADR-1239's passive/session-only posture for Codex model handling: a Codex agent .toml `model` is embedded ONLY for an explicit real-Codex model_overrides pin; any Anthropic-flavored value is omitted so the agent inherits the always- available session model (never a 400). - model_overrides tier alias (opus/sonnet/haiku/fable) or a Claude model id → omit (was: translate to gpt-*); an explicit real-Codex model id → embed verbatim (#2256 preserved). - Detect ALL Anthropic namespacings, not just `claude-*`: single-source the canonical CLAUDE_AGENT_ALIASES from model-resolver.cts and treat any id whose value contains "claude" (case-insensitive) as Anthropic-flavored — catching `anthropic/claude-*` and `us.anthropic.claude-*` (the forms the catalog assigns to opencode/hermes/kilo), which reach a Codex .toml via the runtime-resolver path on a mixed-runtime + Codex install. - The final safety gate applies to the runtime-resolver path too. The full passive posture (removing #2517's runtime-resolver per-tier embedding + a correctness health-check + a Codex TOML sync path) is tracked as the ADR-2310 epic #2313. Regression + fast-check property tests in tests/codex-config.test.cjs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
185abe2d66 |
feat(codex): advance Codex/OpenAI model defaults to GPT-5.6 (Sol/Terra/Luna)
Update runtimeTierDefaults.codex and providerPresets.openai in model-catalog.json to the GPT-5.6 family (gpt-5.6-sol/terra/luna), advancing from the superseded GPT-5.4/5.5 generation. Model IDs verified against OpenAI developer API docs: - gpt-5.6-sol: flagship, /, reasoning xhigh - gpt-5.6-terra: balanced, .50/, reasoning medium - gpt-5.6-luna: fast/cheap, /, reasoning medium Tier mapping is 1:1 (Sol↔flagship, Terra↔balanced, Luna↔fast), so profile semantics are unchanged — only the underlying IDs advance. Updates: catalog JSON, test assertions (catalog defaults), docs (CONFIGURATION.md + zh-CN/pt-BR translations, workflow settings), and changeset. Closes #2122 |
||
|
|
6e773d97df |
feat(#2088): migrate Codex onto the Embeddable Orchestration System (ADR-1239)
Drive Codex install/uninstall through the descriptor-driven Host-Integration Interface (declarative embedding adapter → engine surface dispatch) and fold every positive `runtime === 'codex'` / `isCodex` projection into descriptor-driven `runtime.hostBehaviors`. Install/uninstall output stays byte-parity-gated (tests/fixtures/golden-install-parity/codex.json); no other runtime changes. Three Context7-verified upgrades, each with a test on the user-reachable surface: - Skill root → canonical $HOME/.agents/skills via a skills-kind `home` override, with pre-move migration cleanup (stale ~/.codex/skills/gsd-* removed on install and uninstall; user content preserved). Fixes getGlobalSkillsBase, writeManifest, and the skill-manifest inventory to honor the override so --skills-root / sync-skills / the manifest report the real location. - Six new hooks.json lifecycle events (PreToolUse, PermissionRequest, PreCompact, PostCompact, SubagentStop, UserPromptSubmit) shared by install + uninstall; extendedHookEvents reconciled [] -> the schema-valid wired subset. - Explicit `[agents] max_depth = 1` in the managed config.toml block, pinning the negotiated dispatch.maxDepth:1 axis. validateCodexConfigSchema now permits a known-scalar-only bare `[agents]` AgentsToml table (still rejects [[agents]] and unknown-key break-forms, #2760); mergeCodexConfig preserves the user's own AgentsToml scalars (max_threads etc.) instead of dropping them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8f2ebbe9bf |
feat(#1928): remove sunset Gemini CLI runtime, redirect to Antigravity (#1996)
* feat(#1928): remove sunset gemini cli runtime, redirect to antigravity Google sunset Gemini CLI on 2026-06-18; Antigravity CLI is its official successor (already a first-class GSD runtime). Remove the gemini runtime from the enum (16->15), aliases, labels, config-home fragment, install path, converters (convertClaudeToGemini{Markdown,Toml,Agent}, convertSlashCommandsToGeminiMentions), capability descriptor, gemini-extension.json, RULESET.GEMINI.*, and the interactive menu (renumbered, no gap). --gemini now prints an explicit deprecation notice citing the 2026-06-18 sunset and redirects to --antigravity (no silent alias, per the issue's Hyrum's-Law rejection). Antigravity is preserved throughout: its GEMINI.md contextFileName, .gemini/antigravity config home, the shared convertGeminiToolName/claudeToGeminiTools tool vocabulary, and the 'gemini' hookEvents dialect it declares. GEMINI.md retargeted as Antigravity's context file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#1928): backfill changeset PR number (#1996) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#1928): drop Gemini CLI from issue templates (review nit) Removes the sunset Gemini CLI runtime from the two GitHub issue-template runtime lists that the removal PR missed, per @davesienkowski's review nit: - feature_request.yml: 'Applicable runtimes' checkbox (a user could otherwise request a feature for a runtime GSD no longer supports) - bug_report.yml: 'Runtime' dropdown + the stale ~/.gemini/settings.json retrieval-help line Leaves the post-removal templates fully consistent with the Antigravity redirect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
697cbb1f05 |
test(#1977): consolidate 22 misc + repo-invariant regression tests
Final epic-#1969 batch. Fold 22 issue-named files: the 4 genuine repo-wide invariant scans (551-eslint-bin-lib-coverage, bug-3054 stale /gsd-next, bug-3810 no-gsd-sdk-runtime-refs, feat-3593 cli-negative-universal) into a NEW shared repo-invariants.test.cjs; the other 18 as singletons into their nearest module suite (model-resolver, codex-config, runtime-converters, security, state-transition, worktree-safety, roadmap-parser, etc.). Verbatim block-scoped describe wrappers; 334 subtests conserved 1:1. Host-env pre-check (B2+B6): the 6 CLI folds into GSD_TEST_MODE-setting hosts (model-resolver/ codex-config/runtime-converters) are benign — each origin independently sets GSD_TEST_MODE=1 itself (idempotent), unlike the B6 real-install case. Regenerates regression-name allowlist (222->213), ratchets file-count allowlist (state 17->16), makes 7 relocated allow-test-rule exemptions issue-ref-compliant (ADR-456; prunes stale ids). Repoints 2 tests/ refs in docs/TESTING-SUITES.md. lint:ci green. Part of epic #1969. Closes #1977. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
4f779eda43 |
test(#1970): consolidate 61 install-suite regression tests into function suites
Fold 61 issue-named install/codex/runtime regression files into the canonical test file that owns each subject-under-test, preserving every assertion and its origin issue number as provenance (block-scoped describe wrappers, zero assertion loss — 652 subtests conserved 1:1). Routes: - codex-config.test.cjs +19 (codex config/toml/hooks/adapter/skill surface) - install.test.cjs +18 (node-runner norm, manifest, arg parse, finishInstall) - install-runtime-artifacts +13 (per-runtime conversion + emission) - install-minimal-hooks +6 (hook-event dialects + guards) - path-replacement +2 (opencode absolute pathPrefix) - install-write-confinement +2 (pristine dir writes) - install-regressions +1 (user-artifact preservation) Removes 61 tests/ files → 61 fewer CI processes. Regenerates the regression-name allowlist (271→222), ratchets the file-count allowlist (config 10→9, install 12→9), and makes 13 relocated allow-test-rule exemptions issue-ref-compliant (ADR-456; prunes 13 stale allowlist ids). Repoints ADR-0009's moved-test list. lint:ci green. Part of epic #1969. Closes #1970. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f08b177215 |
feat(#1726): G1-G6 portability AST rules; fix all offenders; delete the ratchet (Phase 4) (#1731)
Phase 4 of epic #1702. Closes #1726. |
||
|
|
fc5ca178a2 |
test(#1178): consolidate duplicated agent-roster helper into tests/helpers (#1420)
* test(#1178): consolidate duplicated agent-roster helper into tests/helpers The "list gsd-*.md agent files, strip .md, sort" derivation was hand-duplicated across the suite (two listAgentFiles(), an identical agentFilesOnDisk(), and inline readdir blocks). Add tests/helpers/agent-roster.cjs exporting listAgentFiles(agentsDir?) and route the genuinely-identical source-roster sites through it. Semantically-different sites (installed-dest dirs, absolute-path returns, .toml-inclusive Codex rosters, full-.md-filename readers, the uniform multi-family inventory table) are left intact, each with a one-line comment. Test-only; no production code touched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(#1178): note AGENTS_DIR export is for future call sites Review nit: clarify that the currently-unused AGENTS_DIR export is intentional — available for future tests needing the canonical source agents path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Tom Boucher <trekkie@nomorestars.com> |
||
|
|
f3c06f59df |
fix(#1326): stop emitting Codex agents/openai.yaml sidecars; clean up stale ones (#1360)
* fix(#1326): stop emitting Codex agents/openai.yaml sidecars; clean up stale ones Codex installs wrote an agents/openai.yaml sidecar under every managed gsd-* skill dir. Recent Codex builds index both SKILL.md and the sidecar, so each GSD skill appeared twice in autocomplete (canonical gsd-* name + humanized display_name). - Replace writeCodexSkillMetadataFiles / generateCodexSkillMetadataYaml with cleanupCodexSkillMetadataSidecars: Codex-only (if isCodex), removes stale managed gsd-*/agents/openai.yaml and prunes the now-empty agents/ dir. - Preserve user-owned dirs (gsd-dev-preferences), non-empty agents/ dirs, and non-gsd dirs; lstat-guard against symlinked agents/ so a delete can never escape the skills tree; fail-open per directory. - Codex relies on SKILL.md alone for /skills discovery. - Update USER-GUIDE/FEATURES docs and rewrite the #774 emission tests into cleanup tests. Scope: the sidecar duplicate only. The separate multi-root (~/.agents/skills shared-skills) duplicate facet is a distinct concern, not addressed here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#1326): add changeset for Codex sidecar cleanup Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0c0a8966ba |
fix(#1151): drive codex sandbox_mode emission from runtime descriptor sandboxTier axis (#1152)
* fix(#1151): drive codex sandbox_mode emission from runtime descriptor sandboxTier axis The sandboxTier runtime-capability axis was cosmetic: declared and validated on all 16 descriptors but read by nothing. The codex per-agent sandbox_mode line was emitted unconditionally from the hardcoded CODEX_AGENT_SANDBOX map, so the descriptor field drove no behaviour (ADR-857 audit finding F10; the "rides along in 5e/5g" promise in ADR-1016 §8 never landed). Make the axis load-bearing: - resolveInstallPlan projects sandboxTier as a 7th InstallPlan axis and fails loud (throws) on a missing/invalid value rather than coercing to 'none'. - installCodexConfig / generateCodexAgentToml gate sandbox_mode emission on sandboxTier !== 'none'. - The per-agent CODEX_AGENT_SANDBOX map is kept: it is GSD agent policy, not a runtime-descriptor property (different layer). Full removal of that map is tracked under #1138 (phase-6 descriptor-residue removal). For codex (sandboxTier === 'codex-agent-sandbox') the emitted TOML is byte-identical to before; for 'none' runtimes sandbox_mode is omitted. Adds leaf, projection, and installCodexConfig threading-seam regression tests; updates the enh-1082 InstallPlan golden master with sandboxTier for all 16 runtimes. Confirmed hypothesis: schema-first vocabulary closure outran consumer wiring, with no conformance gate to catch the orphaned axis. Closes #1151 Refs #857, #1138 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#1151): stamp changeset with PR number 1152 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
fb37fa7dd5 |
fix(#725): route Codex gsd-tools calls through shim (#731)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d617735bed |
fix(codex): avoid partial model effort pinning (#842)
Co-authored-by: Andrew Kriz <akriz@vt.edu> Co-authored-by: Tom Boucher <trekkie@nomorestars.com> |
||
|
|
19280510fe |
feat(#774): emit service_tier/model_verbosity in Codex agent TOML + agents/openai.yaml skill chip (#828)
* feat(#774): emit service_tier/model_verbosity in Codex agent TOML + agents/openai.yaml skill chip - Add service_tier = "flex" and model_verbosity = "low" to the Codex ConfigProfile TOML for light-tier agents (gsd-research-synthesizer, gsd-codebase-mapper, gsd-plan-checker, and 8 others identified via AGENT_DEFAULT_TIERS). Field names/values verified against Codex schema (profile_toml.rs / config_types.rs Verbosity enum). Non-light agents are unaffected. - Add generateCodexSkillMetadataYaml() and writeCodexSkillMetadataFiles(): after installRuntimeArtifacts, iterate every gsd-* skill directory, read the short-description already emitted in the SKILL.md frontmatter by convertClaudeCommandToCodexSkill, and write agents/openai.yaml with interface.display_name and interface.short_description for the Codex TUI skill picker chip. - yamlQuote (JSON.stringify) handles all YAML-unsafe chars. - User-owned gsd-dev-preferences dir is never overwritten. - Errors per-skill are swallowed so a bad SKILL.md can't abort install. - agents/openai.yaml is covered by the snapshot/rollback system and manifest hash (writeManifest hashes skill dirs recursively). - Uninstall symmetry: _removeGsdEntries removes whole gsd-* dirs. - 21 new tests in codex-config.test.cjs covering service_tier/verbosity TOML emission, YAML generation (round-trip via js-yaml), and writeCodexSkillMetadataFiles including an e2e integration test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#774): correct docs-lint coverage — proper changeset format + USER-GUIDE entry Rewrite the changeset fragment from old @opengsd/gsd-core:patch format to the required type:/pr: schema so the docs-lint parser can consume it. Add a new "Codex skill picker and agent scheduling (#774)" section to docs/USER-GUIDE.md describing the flex-tier scheduling and /skills TUI chip enrichments — both are user-visible and belong in docs rather than behind a docs-exempt marker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
463cffd894 |
chore(#604): rename get-shit-done/ runtime directory to gsd-core/ (#615)
* chore(#604): rename get-shit-done/ runtime directory to gsd-core/ Renames the installed runtime directory `get-shit-done/` to `gsd-core/` so the on-disk name matches the package (`@opengsd/gsd-core`), repo, and binary (`gsd-tools`). The npm package name and binary are unchanged; npx/npm consumers are unaffected. Mechanical (bulk, ~90% of the diff): - `git mv get-shit-done gsd-core` - Swept path/identifier references across the repo via `perl -pe 's/get-shit-done(?!-\w)/gsd-core/g'`. The negative lookahead preserves the five legitimate slug variants that are NOT the directory: get-shit-done-{OLD,cc,classic,cli,redux} (old package/repo names). - Build/manifest wiring: package.json (bin, files, coverage globs), tsconfig.build.json (outDir), ~86 .gitignore build-output entries, stryker.config.mjs, scan-ignore files, install.js path strings. - Frozen (not rewritten): CHANGELOG.md history; translated docs (README.<locale>.md and docs/{ja-JP,ko-KR,pt-BR,zh-CN}/). New logic (review here): - src/installer-migrations/003-rename-get-shit-done-to-gsd-core.cts: a proper ADR-0008 installer migration. On upgrade it walks the legacy `~/.claude/get-shit-done/` tree, classifies each file via the prior install manifest, and emits remove-managed / backup-and-remove for managed files while PRESERVING unknown user-added files. Symlink-safe (skips a symlinked root and symlinked entries; bounds-checks every path under configDir). The framework rolls back on install failure. Emptied dirs may remain (framework has no recursive dir-removal primitive) — documented. - scripts/lint-legacy-dir-name.cjs: CI regression guard forbidding the bare `get-shit-done` directory token (split token to avoid self-match; case- insensitive; `(?!-\w)` lookahead allows the slug variants; allowlists CHANGELOG, translated docs, and `gsd-allow-legacy-name` marker lines). Wired into the lint-tests CI job. - Restored scripts/lint-package-identity-drift.cjs detection regexes (the mechanical sweep had wrongly rewritten the old-name patterns it exists to detect) and marked them as intentional legacy references. - TDD tests for the migration and the guard; do.md slash-command guard regex tightened so a `/gsd-core/bin` path segment is not mistaken for a command; changeset + docs/installer-migrations.md row added. Breaking: the installed runtime path moves `~/.claude/get-shit-done/` -> `~/.claude/gsd-core/`. Migration 003 removes the stale legacy dir's managed files (preserving user files) on upgrade. Users with custom hooks/configs hardcoding the old path must update them. Closes #604 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): unsweep pending changesets + allowlist injection-example docs CI fixes for the rename PR: - Do not sweep pending .changeset/*.md (ephemeral release-note fragments, like CHANGELOG); reverted those body edits so 5 pre-existing malformed fragments (missing type/pr) no longer enter the PR diff and trip docs-lint. Allowlisted .changeset/ in the legacy-name guard accordingly. - Allowlisted TEST-EXAMPLES.md and docs/explanation/security-model.md in prompt-injection-scan.sh: they contain intentional injection examples / security-model prose; the path-reference rewrites are kept. CodeQL alerts on this PR are pre-existing (alert lines unchanged by this PR; none in the new migration/guard) and are out of scope for the rename. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): resolve CodeQL alerts surfaced on this PR The rename diff touched files carrying pre-existing CodeQL findings; per the no-pre-existing-dismissal rule, fixing every surfaced alert rather than waving them off. All behavior-preserving: - scripts/ci-test-scope.cjs: build the config-path match from string .includes() instead of a RegExp over an arg-derived value (js/regex-injection). - src/profile-output.cts: escape backslashes before pipe-escaping desc/safeName so the table-cell escape is complete (js/incomplete-sanitization). - tests/{bug-2643,bug-2808,docs-parity-live-registry}: two-pass HTML-comment strip so a bare/unclosed `<!--` cannot survive (js/incomplete-multi-character-sanitization). - tests/inline-plan-threshold: drop the no-op `\s`->`\s` identity replace, keep the meaningful POSIX-class conversion (js/identity-replacement). Verified: build:lib green; the touched test files + ci-test-scope + profile-output suites pass; lint:legacy-name clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): correctly resolve remaining CodeQL alerts (regex-injection + sanitization) The prior commit's fixes for two alerts were ineffective: - ci-test-scope.cjs js/regex-injection: the alert is the CLI-arg-derived `file` reaching static regex `.test(file)` calls (not the config rule). Removed ALL regex over file/t — startsWith/includes/=== string checks + an isWindowsHint helper — so there is no regex sink for the tainted value. - js/incomplete-multi-character-sanitization (3 test files): a single `.replace(/<!--...-->/g,'')` can let `<!--` re-form. Replaced with a fixpoint loop (replace until stable) plus a final bare-opener strip. Verified: no regex over file/t remains; ci-test-scope + the 3 test suites pass; lint:legacy-name clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): make ci-test-scope + comment-strippers regex-free to clear CodeQL CodeQL flags the regex PATTERNS syntactically (regex-injection on the --files arg split; incomplete-multi-character-sanitization on the <!--...--> replace), so loop fixes do not satisfy it. Made these paths regex-free: - ci-test-scope.cjs splitFiles: char-by-char separator tokenizer (no /[,\\s]+/). - 3 test files: indexOf/slice HTML-comment stripper (no .replace(/<!--/)). Behavior preserved; ci-test-scope + the 3 suites pass; guard clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): unblock security base64 scan on the large rename diff The security job hit its 10m timeout: base64-scan.sh choked on the binary test fixture tests/feat-3594-parser-property-style.test.cjs (embedded NUL/ non-UTF8 bytes -> thousands of bogus blobs + "ignored null byte" warnings), and the ~800-file rename diff is slow to scan regardless. - scripts/base64-scan.sh: skip binary-by-content files (grep -Iq .) — they can't carry base64-obfuscated *text* and feeding NUL bytes through the per-line scanner is pathologically slow. collect_files already filtered binary *extensions*; this catches binary *content* in text extensions. - .github/workflows/security-scan.yml: raise the security job timeout 10m->30m to accommodate very large diffs (the scan itself is unchanged). Verified locally: scan skips the fixture, 0 "ignored null byte" warnings, 0 findings, exit 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): sweep get-shit-done refs introduced by merging next The branch was updated with next (#614/#384/#618 etc.), which reference the get-shit-done/ dir (still named that on next). Swept the stale references in the merged files to gsd-core so the rename stays consistent and lint:legacy-name passes: - commands/gsd/discuss-phase.md (runtime-launcher shim paths) - src/core.cts (getAgentsDir layout comments) - tests/bug-384-agents-runtime-aware.test.cjs (require path to runtime lib) Verified: guard 0 violations; build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): exclude gsd-core/ path segments from bug-3683 command cross-ref invariant The #614 runtime-launcher shim added to discuss-phase.md references `${_GSD_RUNTIME_ROOT}/gsd-core/bin/...`. bug-3683's REF_PATTERN excluded path-y refs only via lookbehind, but `}` precedes `/gsd-core/` in the shim, so it mis-read the directory path as a dangling `/gsd-core` command ref (same class as the #604 bug-2954 fix). Added a trailing `(?![\w-]*\/)` so `/gsd-<x>/...` path segments are not treated as slash-command references. Verified locally on BOTH platforms before pushing: - mac (node 26) full suite: 0 failures - gsd-test-runner (linux, node22 image) full suite: 0 failures - bug-3683 + bug-2954 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): lazily resolve findProjectRoot in gsd-tools (harden flaky CI) CI intermittently failed state.test's gsd-tools subprocess with "findProjectRoot is not a function" (flip-flopping across legs; not reproducible on mac full suite, gsd-test linux full suite, test:unit, or state.test x8). findProjectRoot is a re-export from core.cjs (sourced from project-root.cjs); binding it via destructure at module-load can be undefined under a load-ordering edge. Resolve it lazily at call time via a small wrapper so the lookup happens after core.cjs is fully initialized. Verified green on BOTH platforms before pushing: - mac (node 26) full suite: 0 failures - gsd-test-runner (linux, node22) full suite: 0 failures - state.test.cjs: 106/106; gsd-tools loads cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): allowlist verification-patterns.md placeholder examples in secret scan The rename git-mv'd references/verification-patterns.md into gsd-core/, pulling it into the secret-scan diff. It documents stub/placeholder RED-FLAG env-var examples (illustrative Stripe test-key / database-URL / API-key placeholders) — not real credentials. Added it to .secretscanignore with the strict annotation, mirroring the existing gsd-core/workflows/plan-phase.md exception. Verified locally: secret-scan-lint --strict OK; secret-scan --diff origin/next exits 0 with 0 findings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a28dcec981 |
chore(#597): replace count-based ratchet guards with AST lint + named-set allowlists (#603)
The windows-test-parity ratchet greps test source for fs.rmSync-without-
maxRetries (and six other Windows-portability anti-patterns), failing when an
integer offender COUNT exceeds a frozen baseline (rmSync: 95). A count ratchet
is a Goodhart metric: fixing one offender and adding another keeps the count
constant, so a new defect slips through green. Replace it — and every other
count ratchet in the repo — with a layered, masking-proof design.
Behavioral seam test
- tests/helpers-cleanup.test.cjs proves helpers.cleanup() carries the Windows
EBUSY retry budget. cleanup() delegates retries to Node's fs.rmSync via
maxRetries (it owns no loop), so the test asserts the option contract
(recursive/force/maxRetries>0/retryDelay>0) + real-FS removal + the cwd-guard,
rather than a loop that does not exist. The EBUSY risk is now tested ONCE at
the helper, not approximated textually at every call site.
Write-time ESLint rule (AST-accurate, replaces the grep)
- eslint-rules/no-raw-rmsync-in-tests.cjs (error in tests/**/*.test.cjs) bans
raw fs.rmSync, steering to cleanup(). Catches member, computed (fs['rmSync']),
destructured and aliased forms; escape hatch is inline
`// eslint-disable-next-line local/no-raw-rmsync-in-tests -- <reason>` only.
- Migrated 336 raw fs.rmSync teardown calls across ~116 test files to cleanup().
~18 genuinely load-bearing sites (mid-test SUT/fault-injection removals,
error-swallowing or name-colliding local teardown helpers) keep the raw call
with an inline eslint-disable + reason.
Shared anti-ratchet primitive
- scripts/lib/allowlist-ratchet.cjs:
- assertWithinAllowlist: fails on NOVEL ids (new offender introduced) AND on
STALE ids (a known offender was fixed but not pruned) — identity, not count,
and a ratchet DOWN toward zero.
- assertTightCeiling: a size/length budget whose ceiling must stay within a
grace band of the high-water mark, so budgets may only tighten, never creep.
Ratchets converted onto the primitive
- windows-test-parity-guard.test.cjs: rmSync rule deleted (now ESLint-enforced);
the remaining six patterns moved from integer baselines to named-set
allowlists with ratchet-down.
- scripts/lint-test-file-count.{cjs,allowlist.json}: per-module integer counts →
named filename sets (closes the swap-a-file-keep-the-count blind spot); a
module dropping under cap now FAILS to force pruning its allowlist entry.
- enh-2790 skill-count `<= 63` → named skill allowlist (ratchets toward ~58).
Size budgets hardened (tighten-only)
- agent-size / workflow-size / feat-3039 help-tiered: ceilings lowered to the
current high-water mark and an assertTightCeiling anti-creep check added per
tier. Fixed external-contract limits (description ≤100 chars, agent ≤100 KB)
are intentionally left as-is — they are not grandfathered creeping budgets.
No user-facing behavior change (tests + tooling only); no USER_FACING_PREFIXES
touched, so no changeset fragment is required.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
b735f270c5 |
chore(#469): clear residual warn-level lint in effort test files (#470)
Remove unused `os` import left by #463's effort-API conversion and fix two no-useless-escape chars in codex-config test description string. Part of ESLint harness cleanup effort (#452). Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b8c33647d8 |
refactor(tests): retire output-grep & source-grep via typed surfaces (finish #2974) (#462)
* refactor(#455): implement typed surfaces to retire grep tests Production surfaces added: - hooks/managed-hooks-registry.cjs: new CJS module exporting MANAGED_HOOKS as a typed array; gsd-check-update-worker.js now requires it instead of declaring an inline array - bin/install.js: elevate inline gsdHooks to module-level GSD_UNINSTALL_HOOKS, export it alongside runtimeMap/allRuntimes (already exported) - scripts/build-hooks.js: export HOOKS_TO_COPY; guard build() behind require.main===module so tests can require the file without triggering a build - get-shit-done/bin/lib/init.cjs: add --json mode to agent-skills command, emitting typed IR { agent_type, block, skills_count } for test assertions - get-shit-done/bin/gsd-tools.cjs: wire --json flag for agent-skills dispatch Category-B source-grep migrations: - tests/managed-hooks.test.cjs: require MANAGED_HOOKS from registry, drop fs.readFileSync+regex - tests/orphaned-hooks.test.cjs: require MANAGED_HOOKS+HOOKS_TO_COPY as typed exports - tests/hooks-opt-in.test.cjs: replace gsdHooks regex-parse with GSD_UNINSTALL_HOOKS import - tests/install-minimal-hooks.test.cjs: replace gsdHooks regex-parse with GSD_UNINSTALL_HOOKS - tests/copilot-install.test.cjs: replace src.includes() checks with typed assertions on runtimeMap, allRuntimes, parseRuntimeInput, buildRuntimePromptText - tests/agent-skills.test.cjs: migrate to --json typed IR assertions pending-migration-to-typed-ir token cleared (87 of 87 files): - 78 files already had source-text-is-the-product; removed duplicate token - 5 files already used typed assertions; reclassified or annotated - 4 files required individual reclassification to source-text-is-the-product or architectural-invariant Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#455): update workflow-guard test to typed GSD_UNINSTALL_HOOKS import; isolate HOME in runtime-launcher (D) test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#455): guard install.js main() behind require.main===module so the typed export is require-safe Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(#455): document --json typed surfaces for agent-skills, progress, validate context Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(#455): add changeset fragment for new --json surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#455): complete grep migration for files flagged by lint-tests The branch commit 4e630d99 stripped `allow-test-rule: pending-migration-to-typed-ir` from ~80 test files without replacing their assertions or adding the correct exemption annotation. The files were NOT source-grep tests — they read .md workflow/agent/command/reference files (source-text-is-the-product) or hook source files for structural invariants (structural-regression-guard). No assertion logic was changed; only the correct allow-test-rule annotation was added to each file per CONTRIBUTING.md exception matrix. 73 files: `source-text-is-the-product` — workflow/agent/command/reference .md 7 files: `structural-regression-guard` — hook .js / bin/install.js structural checks Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2d3027767b |
fix(3426): Codex Windows hooks use .cmd shim to avoid POSIX exec fail (#3768)
* test(#3426): add RED test for Codex Windows hooks .cmd shim requirement Drive buildCodexHookWindowsShimIR (typed IR) + ensureCodexHooksJsonSessionStart integration against mocked win32 platform. Counter-tests confirm darwin/linux paths remain unchanged. NOTE: Windows wall-clock verification depends on Docker matrix Windows runners. Local test exercises the generator IR shape only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#3426): Codex Windows hooks use .cmd shim to avoid bash.exe POSIX-exec failure Root cause: Codex on Windows runs hook commands from PowerShell/cmd. The previous hooks.json command format was `"node.exe" "script.js"`. Codex's hook-dispatch shell (Git Bash / MSYS) tried to POSIX-exec node.exe (a Windows PE binary) via execvp(), which fails with ENOEXEC — reported as `bash.exe: cannot execute binary file`. Fix: `ensureCodexHooksJsonSessionStart` now calls `buildCodexHookWindowsShimIR` on win32 to write a .cmd shim alongside the .js hook file. cmd.exe executes .cmd files natively via CreateProcess, bypassing the POSIX exec layer entirely. Non-Windows paths (darwin, linux) are unchanged: they continue to use the node-runner command. Also adds `gsd-check-update.cmd` to the codex-hooks-json managed-basename set so reconcileCodexHooksJsonSessionStart correctly replaces stale node-runner entries on reinstall. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(3426): update changeset to reference PR #3768 * fix(3426): fail-loud on Codex Windows shim-write failure instead of silently restoring broken command Replace the silent fallback to `projectManagedHookCommand` (the old `node.exe script.js` form) with an explicit warn-and-skip path. When `atomicWriteFileSync` fails to write the `.cmd` shim, the previous code silently called `reconcileCodexHooksJsonSessionStart` with the legacy node-runner command. That command triggers the exact `bash.exe: cannot execute binary file` POSIX-exec failure that #3426 exists to fix — so a successful-looking install was secretly restoring the original bug. New behaviour: - Emit `console.warn` with the failure reason and a remediation hint, matching the `${yellow}⚠${reset} Skipped …` idiom used at line 9098. - Return `{ changed: false, wrote: false }` to skip registration for this runtime entirely, so the outer caller can surface "NOT installed" instead of "installed (but broken)". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(3426): typed-IR assertions on .cmd shim eol/quoting/passthrough + IR extension Extend `buildCodexHookWindowsShimIR` to expose two new typed fields on the returned IR object (CONTRIBUTING.md L558-L565 IR-first discipline): eol: { cmd: '\r\n' } — CRLF is canonical for cmd.exe .cmd files passthroughArgs: true — shim forwards all args via %* Add a new describe block (Step 2b) with three IR-level assertions: 1. `eol.cmd === '\r\n'` — prevents silent EOL regression that could break parsing on Windows versions that require CRLF. 2. `invocation.target` is the raw unquoted path (no shell-metachar leakage) — quoting happens only at render time. 3. `passthroughArgs === true` — the %* forwarding contract is explicitly typed so regressions fail before the text is rendered. All assertions operate on the typed IR returned by the generator, NOT on the rendered `.cmd` file content — text-matching is the anti-pattern CONTRIBUTING.md L522-582 prohibits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(3426): fix Windows CI failures — update hook-command filter patterns Four test files filtered for managed hooks in hooks.json using the literal string `gsd-check-update.js`. On Windows the PR-introduced .cmd shim changes the hooks.json command to `"path/gsd-check-update.cmd"` (no node prefix, .cmd extension), so those filters matched 0 entries and 24 Windows subtests failed. Fixes: - bug-2760-codex-install-defensive.test.cjs (7 filters): change `/gsd-check-update\.js/` → `/gsd-check-update/` to match both .js (POSIX) and .cmd (Windows) commands. - bug-3357-codex-legacy-hooks-json-migration.test.cjs (3 filters): same `.js` → no-extension change. - bug-3427-3433-codex-install-shape.test.cjs (2 filters): same fix; add explanatory comment to uninstall assertion. - codex-config.test.cjs (9 filters + 1 exact-command assertion): bulk-replace all `hooksJsonCommands.filter(cmd => cmd.includes('gsd-check-update.js'))` with `gsd-check-update`; make the `fresh CODEX_HOME` test platform- aware — on win32 assert `.cmd` shim path, on POSIX assert the existing `"runner" "script.js"` form (#3017). All four suites pass locally (macOS / darwin). Windows subtests verified against the Windows CI failure log patterns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3426): address pr-review-toolkit + codex review findings - fix(uninstall): add gsd-check-update.cmd to gsdHooks cleanup list so the .cmd shim is removed from disk on Windows uninstall (was left as orphan artifact — silent failure post-uninstall) - test(3426): add uninstall test asserting gsd-check-update.cmd is deleted from hooks dir after `uninstall(true, 'codex')` (no coverage existed) - fix(comment): correct JSDoc on buildCodexHookWindowsShimIR — shim content is three-line @ECHO OFF/@SETLOCAL/@runner snippet, not bare `@node "script.js" %*` as the old comment claimed - fix(comment): update stale assertion message in codex-config.test.cjs L1457 — said "config.toml references it" but the hook is in hooks.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1746f89ac7 |
fix(3566): narrow scope; update codex-config tests for canonical hooks key
Two follow-up adjustments after running the full suite: 1. Reverted the rewriteTomlKeyLines() change. The original `match.keyRaw || key` fallback respects user ownership of pre-existing legacy lines (#2760 defensive principle). My fix now applies the canonical-vs-legacy split at the INSERTION points only: fresh installs write `hooks = true`, but a pre-existing user-authored `codex_hooks = true` is preserved verbatim. Codex's own runtime legacy_key alias handles backward-compat at the Codex layer. 2. Updated 12 test cases in tests/codex-config.test.cjs that pinned the old fresh-write key. These assertions now expect canonical `hooks` for fresh-write scenarios; tests covering legacy-line preservation already pass against the narrowed fix without further edits. Also updated bug-3566 regression-test cases for the legacy-preservation path — they now verify that user-owned `codex_hooks` survives an install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d4d4178603 |
Merge pull request #3483 from radioflyer28/feat/agent-launch-reasoning-transport-3474
feat: transport resolved reasoning effort to agent launches |
||
|
|
44e65428e0 | test(codex): harden hooks.json sessionStart test helper | ||
|
|
9dc9e37445 | test(codex): align hook assertions with hooks.json | ||
|
|
810778e137 | fix(sdk): gate reasoning effort by runtime allowlist | ||
|
|
f70829d067 | feat: transport resolved reasoning effort | ||
|
|
26dcdb1ad0 |
Refactor SDK-first architecture seams (#3316)
* refactor: tighten sdk-first architecture seams Refs #3312 * refactor: finish state document seam cleanup Refs #3312 * test: harden minimal install cleanup assertion * ci: support sdk-scoped package lock * fix(3316): restore root package-lock.json and align changeset pr ref Reverts dec57a83 ("ci: support sdk-scoped package lock") and restores the root package-lock.json that c249d34d deleted. The deletion was the wrong direction: - The root package.json declares its own runtime and dev deps (@anthropic-ai/claude-agent-sdk, ws, c8). Without a root lockfile, `npm install --no-package-lock` resolves whatever satisfies semver at install time — CI today and CI in six months can install different transitive trees, defeating reproducibility. - The lockfile has been part of every release on this repo (long history on main); removing it loses the npm audit / Dependabot target without compensating benefit. - The CI workaround pattern (cache-dependency-path: sdk/package-lock.json + `npm install --no-package-lock`) papered over the symptom rather than fix the cause. Also fix the changeset pr: from 3312 (issue) to 3316 (PR). CONTEXT.md flags this exact failure mode as a recurring CodeRabbit finding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address coderabbit review findings * fix: close remaining coderabbit threads --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8e25eb6546 |
fix(#3017): codex SessionStart hook uses absolute node, not bare 'node' (#3022)
* fix(#3017): codex SessionStart hook uses absolute node, not bare 'node' PR #3002 fixed #2979 for settings.json-based managed JS hooks (Claude Code, Gemini, Antigravity) by routing through buildHookCommand() → resolveNodeRunner(), emitting the absolute Node binary path so hooks resolve under GUI/minimal-PATH runtimes (/usr/bin:/bin:/usr/sbin:/sbin) where nvm/Homebrew/Volta-installed node is not on PATH. The Codex install path bypassed both helpers — line 7935 of bin/install.js wrote `command = "node ${path}"` directly into config.toml. So Codex SessionStart hook still failed with exit 127 ("node: command not found") under the same minimal-PATH conditions PR #3002 was meant to close. Fix: - Add buildCodexHookBlock(targetDir, { absoluteRunner, eol }) — a pure helper that emits the toml hook block with the absolute runner. Returns null when absoluteRunner is null so the caller skips registration with a warning instead of writing a broken bare-node hook. - Add rewriteLegacyCodexHookBlock(content, absoluteRunner) — mirror of rewriteLegacyManagedNodeHookCommands for the toml surface, so reinstall migrates a 1.39.x bare-node config.toml to the absolute form. Uses basename equality (CODEX_MANAGED_HOOK_BASENAMES set) so user- authored bare-node hooks are left alone. - Replace the inline string-concat at line 7935 with a call to the new helper, threaded with the detected line ending so CRLF files stay CRLF. - On the codex reinstall path, call rewriteLegacyCodexHookBlock first so existing bare-node entries get migrated before the new entry is added. Tests: - bug-3017-codex-hook-absolute-node.test.cjs (9 tests, all typed-IR): - buildCodexHookBlock emits absolute runner, parses to expected fields - returns null on missing runner (caller skips) - integrates with resolveNodeRunner() in the live process - rewriteLegacyCodexHookBlock migrates managed bare-node entries - leaves user-authored bare-node hooks alone (basename allowlist) - leaves entries with absolute runner unchanged (idempotent) - returns content unchanged when absoluteRunner is null - codex-config.test.cjs e2e expectation updated to match new shape: parsed.hooks.SessionStart[0].hooks[0].command now equals '"<process.execPath>" "<hookPath>"' instead of 'node <hookPath>'. Verification: - 9/9 pass on the new regression test - 179/179 pass across all codex-touching test files - 6767/6767 pass on full suite, lint-no-source-grep clean - Adheres to typed-IR / CONTRIBUTING.md "Prohibited: Raw Text Matching": parseCodexHookBlock returns a typed record; assertions are on structured fields (runner, hookPath, type, hasMarker), not stdout regex. Closes #3017 * test(#3017): tighten runner assertions to exact process.execPath (CR) CodeRabbit on PR #3022 (3 findings, 2 actionable + 1 nitpick): 1. .changeset/codex-bare-node-fix.md:3 — replace `pr: TBD` with `pr: 3022` so changeset metadata is traceable. 2. tests/bug-3017-codex-hook-absolute-node.test.cjs:81-146 — the test asserted `parsed.runner !== 'node'` and `parsed.runner.includes('/node')`, which would false-positive on any absolute path containing '/node' (e.g. /Users/x/notnode/foo). Tightened to compare against the EXACT absolute path supplied by the caller (after stripping toml + JSON escape layers via a new unescapeRunner() helper). The live-process integration test now compares against process.execPath exactly. The rewriteLegacyCodexHookBlock test also uses exact-equality. 3. Nitpick (skipped): use repository's TOML parser for parsing instead of bespoke regex. The hand-rolled parser is small, scoped, and fully tested by these structural assertions; pulling in a TOML lib for tests would create a circular dependency on the SUT (the installer's own parser). Leaving as-is. Verification: 9/9 pass on regression test, 6767/6767 full suite, lint clean. |
||
|
|
918f987a19 |
feat(#2982): extend no-source-grep lint to catch var-binding readFileSync.includes() (#2985)
* feat(#2982): extend no-source-grep lint to catch var-binding readFileSync.includes() The base lint (scripts/lint-no-source-grep.cjs) only catches readFileSync(...).<text-method>() chained directly. The much more common var-binding form escapes it: const src = fs.readFileSync(p, 'utf8'); // 50 lines later if (src.includes('foo')) {} // ← still grep, lint missed it Scan of the test suite found ~141 files using this pattern. Implementation built TDD per #2982 with structured-IR assertions: scripts/lint-no-source-grep-extras.cjs - detectVarBindingViolations(src) — pure detector, two passes: pass 1 collects vars bound from readFileSync, pass 2 finds any <var>.<includes|startsWith|endsWith|match|search>( on those vars. - detectWrappedAssertOkMatch(src) — flags assert.ok(<expr>.match(...)) which escapes the assert.match rule. - VIOLATION enum exposes stable codes for tests to assert on. scripts/lint-no-source-grep.cjs - Wires the new detectors into the existing per-file check; one additional violation row per file with the first 3 sample tokens. tests/bug-2982-lint-var-binding.test.cjs - 13 tests, all assertions on typed VIOLATION enum / structured records. Covers all 5 text-match methods, multi-var, no-bind, string literal (must NOT trigger), wrapped assert.ok(.match), and assert.match (must NOT double-flag). Migration backlog (#2974 expanded scope): - 42 files annotated `// allow-test-rule: source-text-is-the-product` (legitimate — they read .md/.json/.yml files whose deployed text IS the product) - 3 files annotated `// allow-test-rule: pending-migration-to-typed-ir [#2974]` (read .cjs/.js source — clear migration debt) - 95 files annotated `pending-migration-to-typed-ir [#2974]` with `Per-file review may reclassify as source-text-is-the-product during migration` (mixed — manual review under #2974) After this lands the lint reports 0 violations on main; new violations in PRs surface immediately. Closes #2982 Refs #2974 * test(#2982): fix truncated test name per CR The label ended with a bare '(' from a copy-paste mishap. Now reads 'does NOT flag .matchAll(...) — matchAll is not match, so assert.ok(.matchAll(...)) is not flagged'. * chore(#2982): add changeset fragment for PR #2985 * chore(#2982): add changeset fragment for PR #2985 |
||
|
|
3c03a153a5 |
fix(#2773): emit correct Codex 0.124.0+ two-level nested hooks schema (#2809)
* fix(#2773): emit correct Codex 0.124.0+ two-level nested hooks schema Codex 0.124.0's stable spec requires: [[hooks.SessionStart]] ← event entry (optional matcher) [[hooks.SessionStart.hooks]] ← handler sub-table type = "command" command = "node ..." Previous GSD versions wrote the flat [[hooks]] + event = "SessionStart" form (#2637) or a single-block [[hooks.SessionStart]] without the nested .hooks sub-table (#2760). Both are rejected by Codex 0.124.0+ at launch. Changes: bin/install.js - Hook block emission now always writes the two-level nested AoT form. - migrateCodexHooksMapFormat extended to also migrate flat [[hooks]] array-of-tables entries (event = "..." key → [[hooks.<EVENT>]] form). Flat [[hooks]] and [[hooks.<EVENT>]] are mutually exclusive TOML types; any pre-existing flat entries must be promoted before GSD appends its own namespaced hooks. - Migrated flat AoT blocks are inserted BEFORE the GSD marker so they stay in the "user" portion of the file and survive stripGsdFromCodexConfig. - stripCodexGsd* regexes cover all four historical block shapes. - validateCodexConfigSchema no longer rejects flat [[hooks]] at the root level (removing the false-positive that blocked install when users had their own AfterCommand hooks). The validator still enforces the nested [[hooks.<EVENT>.hooks]] shape for entries that have a .hooks sub-table. tests/ - bug-2760-codex-install-defensive.test.cjs: 29/29 passing. Added 5 new regression cases for fresh install, upgrade from each legacy shape, idempotent reinstall, and user hook preservation. - codex-config.test.cjs: 106/106 passing. All migration tests updated to assert [[hooks.<TYPE>.hooks]] sub-table (command now in handler level, not event-entry level). New tests: flat [[hooks]] migration (SessionStart, AfterCommand), install+uninstall preserves non-GSD AfterCommand hook. Closes #2773 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address CodeRabbit review + CI regression in bug-2698-crlf-install CI regression (#2698 tests): Strip GSD-managed hook blocks BEFORE running migrateCodexHooksMapFormat. The previous order let migration convert the stale [[hooks]] + event = "SessionStart" + gsd-update-check.js block to [[hooks.SessionStart]] form before Shape 1 strip regex could match it; Shape 1 only matches the flat [[hooks]] form, so the stale block survived reinstall. Swapping to strip-then-migrate ensures only user-authored hooks reach the migration step. Shape 3/4 regexes also extended to match both gsd-check-update.js and the legacy gsd-update-check.js filename so no variant slips through. CodeRabbit actionable (major): migrateCodexHooksMapFormat now accepts single-quoted TOML event values (event = 'SessionStart') in the flat [[hooks]] filter and event-name extractor. TOML spec allows single-quoted literal strings; double-quote-only regexes silently skipped them, leaving the block unmigrated and triggering the hard-fail validator. CodeRabbit nitpicks: tests/codex-config.test.cjs: replace indexOf('[[hooks.AfterCommand]]') ordering check with parseTomlToObject structural assertions (no-source-grep rule). tests/bug-2760-codex-install-defensive.test.cjs: replace three content.match(/…/g).length raw-text counts with parseTomlToObject structural assertions for single-handler and single-event-entry invariants. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address CodeRabbit review #2 — extractFlatHookEventName helper + type assertions - bin/install.js: consolidate TOML_QUOTED_STRING + TOML_EVENT_CAPTURE into a single extractFlatHookEventName() helper that rejects empty-string event values (event = "" or event = ''); previously two independent regexes had to be kept in sync and neither guarded against a blank event name producing a [[hooks.]] header - tests/bug-2760-codex-install-defensive.test.cjs: add comments explaining why the e.command fallback is retained in both allSessionStartCommands and afterToolCommands collectors — migration only upgrades [hooks.TYPE] map-format sections, not existing [[hooks.TYPE]] namespaced AoT entries authored with command at event-entry level; removing the fallback causes false failures for preserved user entries - tests/codex-config.test.cjs: add type = "command" assertions to all migration tests that verify .command but were missing .type checks; buildNestedBlock injects type = "command" when the source body has no explicit type key, so every migrated handler must carry it per the Codex 0.124.0+ schema 138 tests pass, 0 fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: CR round 3 + proactive audit — TOML quoting, stale AoT migration, strict validator Three real issues from CodeRabbit round 3, plus the collateral improvements they enable: bin/install.js — tomlBareKey() helper (#2773 CR6a) buildNestedBlock interpolated the raw event name into [[hooks.${type}]] and [[hooks.${type}.hooks]] headers without TOML escaping. An event name containing spaces or punctuation (e.g. "Before Tool") would produce invalid TOML that parseTomlToObject would subsequently reject. Added tomlBareKey() — wraps the key in double-quoted TOML strings when it contains non-bare-key characters ([A-Za-z0-9_-]). bin/install.js — staleNamespacedAotSections migration path (#2773 CR6b) migrateCodexHooksMapFormat handled [hooks.TYPE] (map-format) and flat [[hooks]] with event = "..." but ignored [[hooks.TYPE]] AoT entries that carried handler fields (command, type, timeout, statusMessage) at event-entry level without a nested [[hooks.TYPE.hooks]] sub-table. This is the pre-#2773 single-block shape that Codex 0.124.0+ rejects. Added staleNamespacedAotSections as the third migration category: detected by STALE_HANDLER_FIELD_PATTERN + absence of a [[hooks.TYPE.hooks]] sub-table in the same file; promoted to the two-level nested form by buildNestedBlock. Matcher-only entries (no handler fields) are intentionally skipped. bin/install.js — validator now rejects event-level handler fields (#2773 CR6c) With migration covering the stale AoT shape, validateCodexConfigSchema can be strict: entries that have handler fields at event-entry level but no .hooks sub-array return ok: false instead of silently passing. Matcher-only entries (no handler fields and no .hooks) remain valid as event filters. tests/codex-config.test.cjs — four new migration tests + missing type assertion Four tests cover the new stale AoT migration path: single-entry promotion, already-nested entry is left untouched (no double-wrap), multiple event types, and matcher-only entry is skipped. Added the missing type = "command" assertion to the CRLF migration test (the one miss from CR round 2). tests/bug-2760-codex-install-defensive.test.cjs — strict .hooks-only collectors With stale AoT entries now migrated, the entry.command fallbacks in allSessionStartCommands and afterToolCommands are dead code. Replaced with strict entry.hooks-only collection guarded by an every(Array.isArray(e.hooks)) pre-assertion, so any future regression that leaves handler fields at event level produces an explicit test failure rather than silently collecting them. 142 tests pass, 0 fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: CR round 4 — segment-safe quoted-key detection + structural test assertions bin/install.js — getTomlTableSections now exposes segments (#2773 CR7a) The staleNamespacedAotSections filter used section.path.split('.').length > 2 to skip [[hooks.TYPE.hooks]] sub-table entries. That check misclassifies quoted event names containing dots: [[hooks."before.tool"]] has path hooks.before.tool (3 dot-parts) but only 2 true parsed segments, so it was incorrectly excluded from migration. Fixed by adding segments to the getTomlTableSections return shape (already available on record.tableHeader.segments) and replacing the split-based check with section.segments.length !== 2, which uses the true parsed key count regardless of dots inside quoted names. tests/codex-config.test.cjs — replace raw-equality assertions (#2773 CR7b) The two new no-op migration tests (already-nested and matcher-only) used assert.strictEqual(result, content) — raw string equality that conflicts with the repo no-source-grep testing standard. Replaced with structural assertions using parseTomlToObject: the already-nested test verifies the handler stays under .hooks[0] and no double-wrap occurs; the matcher-only test verifies the matcher key is preserved and no .hooks sub-array is added. 142 tests pass, 0 fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: CR round 5 — parseHooksBody key parser, empty-handler guard, segment-safe legacyMap filter, stronger test assertions - parseHooksBody: replace /^([\w.]+)\s*=/ regex with parseTomlKey() so hyphenated keys (status-message) and quoted keys are not silently dropped - buildNestedBlock: guard against handlerEntries.length === 0 — do not synthesise [[hooks.TYPE.hooks]] with type="command" but no command for matcher-only or otherwise handler-empty stale sections - legacyMapSections filter: use section.segments.length === 2 (same fix applied to staleNamespacedAotSections in round 4) to prevent [hooks.X.Y] 3-segment tables from being misclassified as event entries - tests: add regression test for [[hooks."before.tool"]] quoted-dot event names; strengthen command path assertion to exact absolute path comparison Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
b44482cf03 |
fix(#2760): defensive Codex install — strip legacy agents blocks, default hooks to AoT, validate post-write schema (#2785)
* fix(#2760): defensive Codex install — strip legacy agents blocks, default hooks to AoT, validate post-write schema Three defects, three defensive fixes shipped together. Issue reporter never returned with the requested diagnostic backup, but four additional users have since confirmed the same Codex breakage and ZakAnun confirmed manual cleanup is the only working workaround — defensive triple ships without the original backup grep, justified by the corroborating reports. Fix 1 (defect 3 — confirmed real). The Codex hooks emit path always appended a top-level `[[hooks]]` AoT block, which collides with users who already use the namespaced AoT form `[[hooks.SessionStart]]`. New helper `hasUserNamespacedAotHooks()` detects the user's preferred shape on parse and the install emits the GSD-managed hook in that same shape when present. Default for fresh configs stays at top-level `[[hooks]]` so status-quo behavior is preserved. Fix 2 (defects 1+2 — defensive). `stripLeakedGsdCodexSections()` (the install-time stripper) now always purges bare `[agents]` single-bracket tables and `[[agents]]` sequence tables regardless of GSD marker presence — both forms are invalid in current Codex schema and produce "invalid type: ..., expected struct AgentsToml". Previously gated on GSD-name lookup which missed marker-stripped configs and third-party authored entries. The uninstall-time stripper (`stripCodexGsdAgentSections`) keeps its old conservative behavior so user-authored entries survive uninstall. Fix 3 (defensive). Post-write schema validation parses the bytes about to be committed and asserts no bare `[agents]`, no `[[agents]]`, and no bare `[hooks.<Event>]` tables remain. On failure the install restores the pre-install backup of config.toml and aborts loudly so the user is never left with a Codex CLI that refuses to load. Pre-install snapshot is captured before installCodexConfig runs (not after) so restore returns the file to its true pre-GSD state. Tests added (10 new, 1 updated): - bug-2760-codex-install-defensive.test.cjs (10 new tests across 4 describes: hooks AoT preservation, strip robustness for both [agents] and [[agents]] without marker, schema validator behavior, abort+restore via test seam) - codex-config.test.cjs "case 2 ..." updated to reflect new defensive bare-[agents] purge Full suite: 5747 pass / 0 fail. Closes #2760 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#2760): normalize Codex hooks emit field name across migration and managed paths The migrateCodexHooksMapFormat path emitted `type = "<TYPE>"` for legacy [hooks.TYPE] sections, while the GSD-managed Codex install emitted `event = "SessionStart"` — same target [[hooks]] schema, two different field names. Codex currently tolerates both via permissive parsing, but the moment one path tightens this becomes a silent #2760-class regression. Normalize both call sites on `event` (the existing GSD-managed convention). Update migration emit, docstring, and existing migration assertions to match. Add a parity regression test that drives both code paths and asserts the [[hooks]] field key is identical. * test(#2153): fix test isolation by building hooks/dist on demand The "Codex install copies hook file (#2153)" regression depends on hooks/dist/ being populated, but that directory is gitignored and only built by `npm run build:hooks`. The npm pretest chain runs `build:sdk` but not `build:hooks`, so when this file is run in isolation (`node --test tests/codex-config.test.cjs`) the hook copy step skips silently and the regression test fails on a stale-environment artifact rather than a real bug. Add a top-level before() hook that runs scripts/build-hooks.js when hooks/dist/ is missing or empty. Matches the pattern already used by bug-1834-sh-hooks-installed and other install integration tests, so the suite passes regardless of runner ordering or which tests are targeted. * fix(#2760): structural TOML validation, atomic writes, and behavioral test rewrites Addresses CodeRabbit review on PR #2785 plus source-grep violations the maintainer flagged in the regression test. Fix 1 (CR 3149606220) — validateCodexConfigSchema now parses the TOML into a structured object first via the new parseTomlToObject helper, then runs schema-shape checks against both the parsed structure and the table section headers. Malformed TOML with valid-looking headers no longer slips past validation. Fix 2 (CR 3149606224) — Replaced the four source-grep assertions in tests/bug-2760-codex-install-defensive.test.cjs (lines 109, 125, 169, 201) with structural assertions against the parsed TOML object via the exported parseTomlToObject helper. Tests now verify behavior (the file parses and contains the expected structure) instead of literal byte patterns. Robust to formatting changes — exactly what the regex-loosening suggestion was reaching for, done correctly. Confirmed clean by `npm run lint:tests` (0 violations). Fix 3 (CR 3149606234) — The describe block that mutates installModule.__codexSchemaValidator now runs with concurrency: false so the test seam mutation cannot leak into sibling suites that also call runCodexInstall. Fix 4 (CR outside-diff) — Approach (b): atomic temp-file + renameSync. Added atomicWriteFileSync helper used by mergeCodexConfig and the final hooks-write. A mid-write failure leaves the .tmp-<pid>-<n> sibling behind (cleaned up immediately) and never truncates the original config.toml. Paired with try/catch wrapping around the entire post-snapshot mutation sequence so any unexpected throw also triggers restoreCodexSnapshot. Two layers of defense: atomic write prevents the corruption window, snapshot restore handles non-atomic write paths. Added behavioral test for fix 4: stubs fs.renameSync to throw on the configPath rename, asserts the on-disk bytes match the pre-install snapshot byte-for-byte, asserts the parsed structure is still the user's [model] section (no half-written GSD agents block), and asserts no stray .tmp-* files remain. Marked concurrency: false because it monkey-patches a global. Test results: 5749/5749 pass, 0 fail. lint:tests clean. * test(#2760): TOML-parse based assertions for bare-agents purge and hook-field parity (CodeRabbit follow-up) * fix(#2760): treat write failures as fatal, strip legacy hooks before guard, tighten TOML parser (CR4) CR4 finding 1 (MAJOR) — Write failures silently succeeded. The inner catch around atomicWriteFileSync restored the snapshot then re-threw, but the outer catch only matched 'post-write Codex schema validation failed' and downgraded everything else to a warn-and-continue. Install finished with "Done!" while Codex had no GSD agents configured. Fix: wrap writeErr with a `post-write Codex install failed:` prefix and broaden the outer guard to `.startsWith( 'post-write')` so both schema-validation and write failures abort install. CR4 finding 2 (MAJOR) — Legacy flat [[hooks]] block prevented namespaced AoT upgrade. The `!configContent.includes('gsd-check-update')` guard short- circuited the new namespaced emit when an existing install had the legacy flat [[hooks]] block, leaving users stuck in the mixed layout this fix is designed to eliminate. Fix: strip ALL existing managed gsd-check-update hook blocks (top-level [[hooks]] AND namespaced [[hooks.SessionStart]]) BEFORE evaluating the includes guard, so every install converges on the right shape regardless of prior state. CR4 finding 3 (MAJOR) — Homegrown TOML parser silently accepted malformed input. parseTomlValue happily consumed the `0` prefix of `timeout = 0.5` and parseTomlToObject did not verify the full RHS was consumed, so `key = "x" junk` and date/time literals slipped through. Per CONTRIBUTING ("No external dependencies in core"), option (b) was chosen over adding @iarna/toml: (a) parseTomlValue rejects any integer immediately followed by `.`, `e`, `E`, `:`, `-`, `T`, or `Z` (floats / dates / times); (b) parseTomlToObject scans from parsed.end to the next newline and throws `trailing bytes after value` if anything other than whitespace + optional `# comment` is present. * test(#2760): add CR4 regression tests + scope GSD_TEST_MODE + rename rename-fault test CR4 finding 5 (NIT) — GSD_TEST_MODE leak. Saved previous value, set '1' for the require, then restored (delete if undefined). No more test-only env var leaking to siblings in the same node process. CR4 finding 4 (NIT) — Renamed the existing fix-4 test from 'fs.writeFileSync' to 'fs.renameSync' (the only call actually faulted) and added a sibling test that stubs fs.writeFileSync to throw on the .tmp- target — exercising the pre-rename branch of atomicWriteFileSync that was previously untested. Both serialize via concurrency: false on the existing describe block. CR4 finding 1 (MAJOR test) — New behavioral test asserts install throws with a `post-write Codex install failed` message AND never prints "Done!" when the hook-block atomic rename fails. Captures stdout via console.log stub, asserts byte equality of restored snapshot. Faults only the rename whose temp source contains gsd-check-update so earlier mergeCodexConfig writes are not collateral damage. CR4 finding 2 (MAJOR test) — New TOML-parsed behavioral test for the legacy-hook upgrade path: pre-install has [[hooks.SessionStart]] (user) + legacy flat [[hooks]] managed gsd-check-update entry; post-install must have hooks.SessionStart as Array-of-tables with both user hook and GSD entry, and no top-level [[hooks]] AoT remaining. Also asserts exactly one gsd-check-update entry (no duplicates). CR4 finding 3 (MAJOR test) — parseTomlToObject regression suite: rejects floats (timeout = 0.5), dates (created = 1979-05-27), trailing garbage (key = "x" junk), and accepts trailing whitespace + # comment. * fix(#2760): CR5 — pre-write fatal, TOML duplicate-key/header rejection, namespaced AoT migration Address all five CodeRabbit round-5 findings on PR #2785: Finding 1 (MAJOR) — Pre-write failures in the Codex hook configuration catch (around bin/install.js:7002) used to fall through to console.warn even though restoreCodexSnapshot() had already run. This produced "Done!" output with no Codex hooks configured. Now wraps the original error with a "(pre-write)" prefix and rethrows so install aborts loudly. Same defect class as CR4 finding 1, different layer. Finding 2 (MAJOR) — parseTomlToObject silently reused existing tables and overwrote duplicate keys. Real TOML 1.0 rejects: - duplicate scalar key in same table ([a]\nx=1\nx=2) - re-declared [a] header (two [a] sections) - [[arr]] then [arr] for same path (shape mismatch) Tracks pathShape, declaredHeaders, and per-table-instance key sets; throws "duplicate or shape-mismatched table header at <path>" or "duplicate key <name> in <path>". Finding 3 (MAJOR) — migrateCodexHooksMapFormat used to emit flat [[hooks]]\nevent="<TYPE>", which produced mixed flat+namespaced layouts when the user already had [[hooks.<OTHER>]] entries. Now emits [[hooks.<TYPE>]] directly (the namespace IS the event); managed-emit detector hasUserNamespacedAotHooks fires correctly so the install converges on a single namespaced layout regardless of pre-existing state. Finding 4 (NIT) — tests/bug-2760-codex-install-defensive.test.cjs rename-failure test tightened from "throw OR warn acceptable" to assert.equal(threw, true), locking the contract Finding 1 establishes. Finding 5 (NIT) — bug-2760 test suite snapshots and restores fs.renameSync defensively in beforeEach/afterEach (symmetric with fs.writeFileSync), removing the fragile per-test try/finally. Second test in the same suite cleaned up to drop its try/finally. Updates tests/codex-config.test.cjs to assert the new namespaced AoT migration shape via parseTomlToObject (no source-grep). Existing field- parity test reframed as shape-parity since both paths now emit namespaced. Tests: 5764 pass (+8 new). lint:tests: 0 violations. * docs(#2760): add CHANGELOG entry for Codex install defensive triple Adds the [Unreleased] Fixed entry for the Codex install fix landed in this PR — defensive strip of legacy [agents]/[[agents]] blocks, namespaced AoT hook detection across all events, atomic write + rollback, strict TOML validation rejecting duplicate keys/repeated headers/trailing bytes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d5cd64dde5 |
fix(#2637): migrate legacy Codex [hooks] map format to [[hooks]] array on install (#2747)
Codex 0.124.0 changed the required config.toml hooks format from the old map-style ([hooks.shell]) to array-of-tables ([[hooks]]). Old GSD installs that wrote the legacy format now cause a startup parse error on upgrade. Add migrateCodexHooksMapFormat() which detects non-array [hooks] and [hooks.TYPE] sections and rewrites them to [[hooks]] entries with an injected type = "TYPE" key. The migration runs at the start of every Codex install so affected configs self-heal on the next `gsd install --codex`. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
8f7f43abaa |
test(installer): add failing test for [agents.<name>] struct format (Codex 0.124.0+)
Adds test asserting generateCodexConfigBlock emits [agents.<name>] struct format, not [[agents]] sequence format. Confirms RED phase for #2727 fix. |
||
|
|
7ed05c8811 |
fix(#2645): emit [[agents]] array-of-tables in Codex config.toml (#2664)
* fix(#2645): emit [[agents]] array-of-tables in Codex config.toml Codex ≥0.116 rejects `[agents.<name>]` map tables with `invalid type: map, expected a sequence`. Switch generateCodexConfigBlock to emit `[[agents]]` array-of-tables with an explicit `name` field per entry. Strip + merge paths now self-heal on reinstall — both the legacy `[agents.gsd-*]` map shape (pre-#2645 configs) and the new `[[agents]]` with `name = "gsd-*"` shape are recognized and replaced, while user-authored `[[agents]]` entries are preserved. Fixes #2645 * fix(#2645): use TOML-aware parser to strip managed [[agents]] sections CodeRabbit flagged that the prior regex-based stripper for [[agents]] array-of-tables only matched headers at column 0 and stopped at any line beginning with `[`. An indented [[agents]] header would not terminate the preceding match, so a managed `gsd-*` block could absorb a following user-authored agent and silently delete it. Replace the ad-hoc regex with the existing TOML-aware section parser (getTomlTableSections + removeContentRanges) so section boundaries are authoritative regardless of indentation. Same logic applies to legacy [agents.gsd-*] map sections. Add a comprehensive mixed-shape test covering multiple GSD entries (both legacy map and new array-of-tables, double- and single-quoted names) interleaved with multiple user-authored agents in both shapes — verifies all GSD entries are stripped and every user entry is preserved. |
||
|
|
0da696eb6c |
fix(install): replace all ~/.claude/ paths in Codex .toml files (#2320) (#2325)
* fix(install): replace all ~/.claude/ paths in generated Codex .toml files (#2320) installCodexConfig() only rewrote get-shit-done/-scoped paths; all other ~/.claude/ references (hooks, skills, configDir) leaked into generated .toml files unchanged. Add three additional regex replacements to catch $HOME/.claude/, ~/.claude/, and ./.claude/ patterns and rewrite them to .codex equivalents. Adds regression test PATHS-01. Closes #2320 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(install): handle bare .claude end-of-string and scan all .toml files (CR feedback) - Use capture group (\/|$) so replacements handle both ~/.claude/ and bare ~/.claude at end of string, not just the trailing-slash form - Expand PATHS-01 test to scan agents/*.toml + top-level config.toml - Broaden leak pattern to match ./.claude, ~, and $HOME variants with or without trailing slash Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
262b395879 |
fix: embed model_overrides in Codex TOML and OpenCode agent files (#2279)
* docs: sync ARCHITECTURE.md command count to 74 commands/gsd/ has 74 .md files; the two count references in ARCHITECTURE.md still said 73. Fixes the command-count-sync regression test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: embed model_overrides in Codex TOML and OpenCode agent files (#2256) Codex and OpenCode use static agent files (TOML / markdown frontmatter) rather than inline Task(model=...) parameters, so model_overrides set in ~/.gsd/defaults.json was silently ignored — all subagents fell through to the runtime's default model. Fix: at install time, read model_overrides from ~/.gsd/defaults.json and embed the matching model ID into each agent file: - Codex: model = "..." field in the agent TOML (generateCodexAgentToml) - OpenCode: model: ... field in agent frontmatter (convertClaudeToOpencodeFrontmatter) Also adds readGsdGlobalModelOverrides() helper and passes the result through installCodexConfig() and the OpenCode agent install loop. Closes #2256 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(commands): add gsd:inbox command for GitHub issue/PR triage inbox.md was created but not committed, causing the command count to read 73 in git while ARCHITECTURE.md correctly stated 74. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
b896db6f91 |
fix: copy hook files to Codex install target (#2153) (#2166)
Codex install registered gsd-check-update.js in config.toml but never copied the hook file to ~/.codex/hooks/. The hook-copy block in install() was gated by !isCodex, leaving a broken reference on every fresh Codex global install. Adds a dedicated hook-copy step inside the isCodex branch that mirrors the existing copy logic (template substitution, chmod). Adds a regression test that verifies the hook file physically exists after install. Closes #2153 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
d0ab1d8aaa |
fix(codex): convert /gsd- workflow commands to $gsd- during installation (#1994)
The convertSlashCommandsToCodexSkillMentions function only converted colon-style skill invocations (/gsd:command) but not hyphen-style command references (/gsd-command) used in workflow output templates (Next Up blocks, phase completion messages, etc.). This caused Codex users to see /gsd- prefixed commands instead of $gsd- in chat output. - Add regex to convert /gsd-command → $gsd-command with negative lookbehind to exclude file paths (e.g. bin/gsd-tools.cjs) - Strip /clear references in Codex output (no Codex equivalent) - Add 5 regression tests covering command conversion, path preservation, and /clear removal Co-authored-by: Lakshman <lakshman@lakshman-GG9LQ90J61.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
84de0cc760 |
fix(install): comprehensive audit cleanup of hook copy, uninstall, and manifest (#1755) (#1768)
- Add chmod +x for .sh hooks during install (fixes #1755 permission denied) - Fix Codex hook: wrong path (get-shit-done/hooks/) and inverted filename (gsd-update-check.js → gsd-check-update.js) - Fix cache invalidation path from ~/cache/ to ~/.cache/gsd/ - Track .sh hooks in writeManifest so saveLocalPatches detects modifications - Add gsd-workflow-guard.js to uninstall file cleanup list - Add community hooks (session-state, validate-commit, phase-boundary) to uninstall settings.json cleanup - Remove phantom gsd-check-update.sh from uninstall list - Remove dead isCursor/isWindsurf branches in uninstall (already handled by combined branch) - Warn when expected .sh hooks are missing after verifyInstalled - Add 15 regression tests in install-hooks-copy.test.cjs - Update codex-config.test.cjs assertions for corrected hook filename Fixes #1755 |
||
|
|
2703422be8 |
refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md (#1675)
* refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md
- Replace require('node:assert') with require('node:assert/strict') across
all 73 test files to enforce strict equality (no type coercion)
- Replace try/finally cleanup blocks with t.after() hooks in core.test.cjs
and hooks-opt-in.test.cjs per the test lifecycle standards
- Utility functions in codex-config and security-scan retain try/finally
as that is appropriate for per-function resource guards, not lifecycle hooks
Closes #1674
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* perf(tests): add --test-concurrency=4 to test runner for parallel file execution
Node.js --test-concurrency controls how many test files run as parallel child
processes. Set to 4 by default, configurable via TEST_CONCURRENCY env var.
Fixes tests at a known level rather than inheriting os.availableParallelism()
which varies across CI environments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(security): allowlist verify.test.cjs in prompt-injection scanner
tests/verify.test.cjs uses <human>...</human> as GSD phase task-type
XML (meaning "a human should verify this step"), which matches the
scanner's fake-message-boundary pattern for LLM APIs. This is a
false positive — add it to the allowlist alongside the other test files
that legitimately contain injection-adjacent patterns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
2b8c95a05c |
fix(install): add .claude path replacement for Codex runtime
convertClaudeToCodexMarkdown() was missing path replacement — unlike Copilot/Gemini/Antigravity converters which all replace $HOME/.claude/ paths. This left hardcoded .claude references in Codex agent files, causing ENOENT when gsd-tools.cjs tried to load from ~/.claude/ on Codex installations. Closes #1430 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
f4d8858188 |
fix: repair trapped non-boolean keys under [features] on Codex re-install
Pre-#1346 GSD installs prepended [features] before bare top-level keys in ~/.codex/config.toml, trapping keys like model="gpt-5.3-codex" under [features] where Codex expects only booleans. The #1346 fix prevented NEW corruption but did not repair EXISTING corrupted configs. Re-installing GSD left the trapped keys in place, causing "invalid type: string, expected a boolean" on every Codex launch. repairTrappedFeaturesKeys() now detects non-boolean key-value lines inside [features] and relocates them before the [features] header during ensureCodexHooksFeature(), so re-installs heal previously corrupted configs. Fixes #1379 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
9f8d11d603 |
fix: preserve top-level config keys and use absolute agent paths for Codex ≥0.116
Two fixes for Codex config.toml compatibility: 1. ensureCodexHooksFeature: insert [features] before the first table header instead of prepending it before all content. Prepending traps bare top-level keys (model, model_reasoning_effort) under [features], where Codex rejects them with "invalid type: string, expected a boolean". 2. generateCodexConfigBlock: use absolute config_file paths when targetDir is provided. Codex ≥0.116 requires AbsolutePathBuf and cannot resolve relative "agents/..." paths, failing with "AbsolutePathBuf deserialized without a base path". Fixes #1202 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
b6a49163ea | fix(codex-config): preserve EOL when enabling codex hooks | ||
|
|
52f6c71bdf |
Merge pull request #1144 from ChaptersOfFloatingLife/fix/codex-agent-toml-metadata
fix(codex): include required agent metadata in TOML |
||
|
|
1f2e17923a |
Merge pull request #1142 from medhatgalal/AI/fix-codex-markerless-gsd-merge
Fix duplicate Codex GSD agent blocks without marker |
||
|
|
862f6b91ba |
fix: prevent Codex config.toml corruption from non-boolean [features] keys (#1202)
When GSD installs codex_hooks = true under [features], any non-boolean keys already in that section (e.g. model = "gpt-5.4") cause Codex's TOML parser to fail with 'invalid type: string, expected a boolean'. Root cause: TOML sections extend until the next [section] header. If the user placed model/model_reasoning_effort under [features] (common since Codex's own config format encourages this), GSD's installer didn't detect or correct the structural issue. Fix: After injecting codex_hooks, scan the [features] section for non-boolean values and move them above [features] to the top level. This preserves the user's keys while keeping [features] clean for Codex's strict boolean parser. Includes 2 regression tests: - Detects non-boolean keys under [features] (model, model_reasoning_effort) - Confirms boolean keys (codex_hooks, multi_agent) are not flagged Closes #1202 |
||
|
|
5a7d56e6c5 | fix(codex): include required agent metadata in TOML |