fix(update): refresh command files for tools configured without skills (#1442)

* fix(update): mark command-configured tools as needing update when skill version is missing

* fix(update): compare command content fingerprint for commands-only tools when skill version is missing

* test(update): isolate config homes in regressions

* fix(update): keep skill drift detectable behind the command fingerprint

Review follow-ups on the commands-only update fix:

- Only fall back to the command-content fingerprint when a tool has no skill
  files at all. Gating on `generatedByVersion === null` also swallowed the case
  where a SKILL.md exists but its version is unreadable, so a truncated or
  hand-edited skill file could never be repaired by `openspec update` again.
- Drop the command `generatedBy` scan: command adapters emit no version stamp,
  so the loop was unreachable and made the fingerprint fallback read as a
  secondary path rather than the only one.
- Compute version status with the same workflow set the generation loop writes
  (`legacyWorkflowOverrides[toolId] ?? desiredWorkflows`), so a legacy-upgraded
  tool is not fingerprinted against commands it was never given.
- Remove the unread `delivery` option from the three tool-detection signatures,
  the leftover `getCommandConfiguredTools` / `COMMAND_IDS` imports, and the
  unused `toolHasAnyConfiguredCommand` re-export.
- runCLI: never let temp-dir cleanup replace the CLI result or a real failure,
  and treat an explicitly-empty XDG_CONFIG_HOME as an override.

Adds regressions for the unreadable-skill case and for a deselected workflow
leaving a command file behind, and documents how "up to date" is decided.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(update): ignore CRLF and BOM when fingerprinting command files

Round-two review follow-ups:

- Command files are committed project files. A Windows clone with
  `core.autocrlf` re-materializes them with CRLF endings, which the byte-exact
  comparison read as drift: every fresh checkout spent one `openspec update`
  rewriting identical content and announcing a bogus "unknown → <version>".
  Normalize CRLF and a leading BOM on both sides before comparing.
- Collapse `getCommandConfiguredTools`, which the widened `getConfiguredTools`
  made a strict subset of itself, into the single remaining caller.
- Correct the new `openspec update` doc paragraph: content drift is only
  detected for commands-only installs, so it must not promise that hand edits
  are always overwritten.
- Add the changeset this repo requires per fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(update): drop dead exports and correct stale status doc comments

`ToolVersionStatus.configured` and `.generatedByVersion` are now fed by command
files too, so their comments no longer say "skills". Removes the barrel exports
and the `options` parameter this change added but nothing consumes, and the
import left dangling by the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(update): make the CRLF fixture idempotent on a CRLF checkout

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(update): cover non-claude adapters and a custom profile

The fingerprint regressions all ran against claude and the core profile, so two
things were covered by reasoning rather than by an executed test:

- Command paths differ in shape per adapter. Added a parametrized case over
  gemini (nested dir, TOML), cursor (flat opsx-* file), and cline, whose
  commands live in .clinerules/workflows — not in its skillsDir (.cline) at
  all, so a commands-only install leaves that directory absent. Each asserts
  detection, a clean fingerprint, and drift. Reverting the getConfiguredTools
  widening fails all three.
- A custom profile must be fingerprinted against its own workflow subset.
  The new case inits with ['explore', 'apply'] and asserts the same tree reads
  as drifted when compared against the wider core set. Making the fingerprint
  ignore the caller's workflows and fall back to global config fails it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Clay Good <hi@claygood.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henry Su
2026-07-28 17:09:32 +00:00
committed by GitHub
co-authored by Claude Opus 5 Clay Good
parent 6295515d4d
commit 10fa39b1c3
8 changed files with 389 additions and 61 deletions
+10
View File
@@ -203,6 +203,16 @@ Whenever anything is printed, it names the directory the running CLI was loaded
It asks the registry in `npm_config_registry` when npm exports it, and `https://registry.npmjs.org` otherwise. No `.npmrc` is read: letting file contents choose where an outbound request goes is a flow worth avoiding, and a project's `.npmrc` travels with the repository. On a private mirror, export `npm_config_registry` — or set `OPENSPEC_NO_UPDATE_CHECK` to skip the check entirely. The check is skipped when `CI` is set to anything but an explicit off-value (`false`, `0`, `no`, `off`, or empty), under `NODE_ENV=test`, and whenever `OPENSPEC_NO_UPDATE_CHECK` (any value), `DO_NOT_TRACK=1`, or `OPENSPEC_TELEMETRY=0` is set. It runs before the update and can delay it by at most 1.5 seconds — it gives up after that even when the network drops packets silently, and stays quiet when the registry is unreachable.
**How "up to date" is decided:** skill files record the version that generated
them, so OpenSpec compares that against the installed CLI. Command files carry no
version stamp, so for a tool that has commands but no skills (delivery
`commands`), OpenSpec compares the file contents against what it would generate
now — edits to those files count as drift and are overwritten. With delivery
`skills` or `both`, only the recorded version is checked, so a hand-edited file
whose version still matches is left alone; use `--force` to rewrite it. Either
way, generated files are OpenSpec's to own — keep your own instructions
elsewhere.
---
## Stores (standalone OpenSpec repos)