Files
OpenSpec/test
Clay GoodandClaude Opus 4.8 07dea6ed2f fix(update): don't hijack the agents target on legacy Codex upgrade (#1522)
* fix(update): don't hijack the agents target on legacy Codex upgrade

Codex and the vendor-neutral `agents` target share `.agents/skills`. In
upgradeLegacyTools, a Codex install inferred only from global ~/.codex/prompts
wrote Codex skills into `.agents` and flipped the ownership marker
agents -> codex, silently rewriting an existing agents-owned tree. The main
generation path reconciles shared-target ownership first; this legacy-upgrade
path did not. Add sharedSkillRootOwnedByOther() and skip generation when a
different tool already owns the shared root (marker or existing tree), while
still allowing a genuine first-time Codex upgrade with no `.agents` yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(update): cover the hijack guard end-to-end; name the owner on skip

Harden the agents-target ownership fix after a multi-agent review:
- Add an integration test that runs the real update flow for the bug
  scenario (agents-owned .agents + a legacy global Codex prompt) and asserts
  the marker stays `agents` and skills keep generic `/openspec-` syntax. A
  unit test of the predicate can't catch a future refactor that stops calling
  it; this can.
- Name the owning tool in the skip message ("...managed by another tool
  (Shared .agents skills)") via a new sharedSkillRootOwner() helper that
  sharedSkillRootOwnedByOther now delegates to.
- Add a unit case for the ambiguous-tree branch (existing skills, no marker,
  no inferable syntax) and one asserting sharedSkillRootOwner names agents.
- Document the known, harmless re-offer tradeoff (a skipped tool isn't
  recorded as configured, so a persistent legacy prompt re-offers it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(update): preserve skipped tool's legacy files; add upgrade-path tests

Adversarial review of the shared-root ownership guard surfaced one real
integration defect and the review asks from alfred/CodeRabbit.

Defect: when the guard skips a legacy Codex upgrade because the `.agents`
root is owned by another tool, the caller's immediate legacy cleanup still
deleted Codex's repo-local `.codex/prompts/openspec-*.md`. That violates the
cleanup contract (remove X only because replacement Y was written): no
replacement is written for a skipped tool, so its legacy files must stay.

`upgradeLegacyTools` now reports `skippedSharedSkillTools`, and
`performImmediateLegacyCleanup` exempts those tools' repo-local artifacts via
a new `omitToolLegacyArtifacts` helper. Refactored the per-artifact tool
matching out of `getToolsFromLegacyArtifacts` so both share one matcher.

Tests (addressing the review + the defect):
- update.test.ts: hijack test now asserts Codex is absent from the persisted
  configured-tool set and that the skip names the established owner.
- update.test.ts: inverse no-root case proves a first-time Codex upgrade still
  writes the `codex` marker via the real UpdateCommand path.
- update.test.ts: a skipped tool's repo-local `.codex/prompts` is preserved.
- legacy-cleanup.test.ts: unit coverage for omitToolLegacyArtifacts.
- shared-skill-target.test.ts: assert sharedSkillRootOwner resolves 'agents'.

Docs + changeset updated to describe the preserve-on-skip behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(update): lock in legacy-prompt preservation on skipped Codex upgrade

Address the outstanding CodeRabbit review notes on #1522. The fix itself
is confirmed correct by three independent adversarial reviews — these are
test-only hardening that locks in the guarantees the fix promises:

- Assert the global ~/.codex/prompts survives (byte-for-byte) in the
  hijack scenario. Previously the test set the prompt up but never
  checked it was preserved; on unfixed code Codex would be generated,
  its 'explore' workflow would read as installed, and the deferred
  global cleanup would delete the prompt — so this assertion fails
  without the fix.
- Assert the repo-local .codex/prompts is preserved by content, not
  mere existence (distinguishes 'left untouched' from 'deleted+rewritten').
- Restore the stdout/stderr spies in a finally so a throw can't swallow
  output for the rest of the suite.
- Cover backslash-delimited (Windows) paths in omitToolLegacyArtifacts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 21:22:32 +00:00
..