Commit Graph
21 Commits
Author SHA1 Message Date
dependabot[bot]andClay Good cb5ae2cd16 ci: bump changesets/action from 1.9.0 to 2.1.1 in the github-actions group (#1746)
* ci: bump changesets/action in the github-actions group

Bumps the github-actions group with 1 update: [changesets/action](https://github.com/changesets/action).


Updates `changesets/action` from 1.9.0 to 2.1.1
- [Release notes](https://github.com/changesets/action/releases)
- [Changelog](https://github.com/changesets/action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/changesets/action/compare/a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d...8488615a623b1b9c987934bb89eae8af6a946ac1)

---
updated-dependencies:
- dependency-name: changesets/action
  dependency-version: 2.1.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(ci): complete changesets action v2 migration

* fix(nix): invalidate pnpm dependency hash

* fix(nix): use calculated dependency hash

* fix(nix): refresh pnpm dependency hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Clay Good <hi@claygood.com>
2026-09-02 21:55:12 +00:00
dependabot[bot]andClay Good c5c38a7aca chore(deps-dev): bump eslint from 10.8.1 to 10.9.0 in the development-dependencies group (#1745)
* chore(deps-dev): bump eslint in the development-dependencies group

Bumps the development-dependencies group with 1 update: [eslint](https://github.com/eslint/eslint).


Updates `eslint` from 10.8.1 to 10.9.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.8.1...v10.9.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(nix): refresh dependency hash

* fix(nix): use calculated dependency hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Clay Good <hi@claygood.com>
2026-09-02 19:15:51 +00:00
dd7cea3ffe feat(show): diff delta requirements against the main specs (#980)
* Proposed feature: openspec show --diff to see changed requirements more clearly

* Implementation of the --diff feature, which led to some spec changes during implementation.

* Update the proposal to be clearer. Thanks coderabbit

* Fix a bug identified by coderabbit with excessive trimming, and add a testcase for it.

* fix(show): harden --diff for review feedback

Keeps `openspec show <change>` without `--diff` a raw proposal
passthrough, reports when a change has no delta specs instead of
returning silently, preserves the Reason/Migration body of a REMOVED
requirement, and resolves main specs through the command's root so
`--store <id>` diffs against that store.

Text mode and JSON mode now render from one shared collection pass, the
CLI tests drive argv arrays from a mkdtemp project instead of
interpolated shell strings, `--diff` is registered for shell
completions, and the stray package-lock.json is gone.

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

* build(deps): declare the diff dependency and refresh the flake hash

Adds the `diff` runtime dependency that requirement-diff.ts imports,
updates pnpm-lock.yaml, and regenerates the flake's pnpmDeps hash so
`nix build` matches the new lockfile.

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

* fix(show): diff nested capabilities too

collectSpecDiffs enumerated only top-level directories under the
change's specs/, so a nested capability (specs/<area>/<id>/spec.md) was
skipped: text mode printed nothing for it and its MODIFIED deltas came
back from --json with no diff. It now uses the same discoverSpecFiles()
helper ChangeParser uses, so the capability ids match the `spec` field
of the JSON deltas.

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

* fix(show): report header mismatches instead of hiding them

Two cases where --diff quietly showed something misleading:

A MODIFIED requirement whose capability has no main spec was rendered as
all-additions, which reads like a new capability. It is an authoring
error archive will reject, so it now prints the raw text with a warning
naming the missing spec.

A header that differs from the main spec only in case or interior
spacing found no match at all under exact lookup, or matched under a
lowercase-only comparison that let a real mismatch through silently.
Lookup is now exact first, then the shared foldRequirementName fallback,
and a folded match prints the diff the author meant alongside a warning
that archive matches names exactly.

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

* refactor(show): name the main spec as such in collectSpecDiffs

Comment and locals still called the main spec the "base" spec, and the
no-main-spec comment described the old all-additions behavior.

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

* chore(deps-dev): bump the development-dependencies group with 2 updates

Bumps the development-dependencies group with 2 updates: [smol-toml](https://github.com/squirrelchat/smol-toml) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `smol-toml` from 1.7.1 to 1.8.0
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.7.1...v1.8.0)

Updates `typescript-eslint` from 8.66.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.67.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(nix): invalidate pnpm dependency hash

* fix(nix): update pnpm dependency hash

* fix(show): harden requirement diff output

* build(nix): pin combined dependency hash

* test(show): assert proposal precedes diffs

* docs(show): clarify JSON diff diagnostics

* fix(show): retain unified diff hunk headers

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Clay Good <hi@claygood.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-26 20:18:03 +00:00
dependabot[bot]andClay Good c0c50f9a4c chore(deps-dev): bump the development-dependencies group with 2 updates (#1718)
* chore(deps-dev): bump the development-dependencies group with 2 updates

Bumps the development-dependencies group with 2 updates: [smol-toml](https://github.com/squirrelchat/smol-toml) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `smol-toml` from 1.7.1 to 1.8.0
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.7.1...v1.8.0)

Updates `typescript-eslint` from 8.66.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.67.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(nix): invalidate pnpm dependency hash

* fix(nix): update pnpm dependency hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Clay Good <hi@claygood.com>
2026-08-26 19:25:07 +00:00
91813641cf chore(deps): migrate to @inquirer/prompts v8 + @inquirer/core v11 (#1667)
* chore(deps): migrate to @inquirer/prompts v8 + @inquirer/core v11

Bumps both packages together. The two Dependabot attempts each moved one
half (#1450 prompts->8, #1422 core->11) and failed: prompts@8 pulls
checkbox@5 -> core@^11, while package.json depends on core@^10 directly
for two custom prompts, so a one-sided bump leaves two copies of
@inquirer/core in the tree — custom prompts on v10 internals alongside
bundled prompts on v11.

Resolves the `instructions` removal in checkbox v5 by dropping the
option: the built-in keys help tip now renders a superset of the hint
that was being passed, so no theme override is needed.

Closes #1458

* fix(nix): regenerate pnpmDeps hash for the inquirer v8 lockfile

The pnpmDeps fixed-output hash is pinned to the contents of pnpm-lock.yaml,
so the inquirer v8/core v11 migration invalidated it and Nix Flake Validation
failed with 'pnpm failed to install dependencies'.

Regenerated against this branch's lockfile and verified: nix build .#default
completes (exit 0) through openspec-1.9.0.drv, not just past the fetch.

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

---------

Co-authored-by: Clay Good <hi@claygood.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:39:33 +00:00
Clay GoodandClaude Opus 4.8 3281f1f068 fix(deps): patch js-yaml and nanoid advisories via pnpm overrides (#1635)
Resolve all three open Dependabot alerts (all high severity):

- GHSA-5p4m-2wfm-xmqj — js-yaml quadratic-CPU !!omap DoS (#96, #97).
  Root tree carried js-yaml 3.15.0 (via read-yaml-file) and 4.3.0 (via
  @changesets/parse). Dev-only; never in the published CLI, which uses
  `yaml`, not `js-yaml`. Pinned to >=3.15.1 / >=4.3.1.
- GHSA-2v37-7h3g-55p8 / CVE-2026-67213 — nanoid size=0 infinite loop (#99).
  Present in both root (dev, via postcss<-vitest) and website (build-time,
  via postcss<-next) trees. Pinned to >=3.3.17 (resolves to 3.3.18).

Overrides added to all four override surfaces (pnpm-workspace.yaml +
package.json, root and website) to keep them in sync, each YAML entry
annotated with its advisory id and removal condition.

flake.nix pnpmDeps FOD hash regenerated for the root lockfile change
(verified via nix build; hash-mismatch-count 0). dependabot.yml gains a
note documenting the two surfaces Dependabot cannot manage (pnpm
overrides + the Nix flake).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 23:12:15 +00:00
Clay GoodandClaude Opus 4.8 4b114aade9 chore(deps-dev): bump development-dependencies group + refresh flake hash (#1633)
* chore(deps-dev): bump development-dependencies group + refresh flake pnpmDeps hash

Supersedes #1630. Dependabot's PR bumps two dev dependencies within their
existing package.json semver ranges (eslint 10.8.0 -> 10.8.1, typescript-eslint
8.65.0 -> 8.66.0), touching only pnpm-lock.yaml. That lockfile change
invalidates the flake's fixed-output pnpmDeps.hash, so #1630 fails Nix Flake
Validation ("pnpm failed to install dependencies"). Dependabot cannot update the
Nix FOD hash, so this PR carries the same bump together with the refreshed hash.

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

* fix(nix): set pnpmDeps hash for updated lockfile

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 22:24:01 +00:00
Ismet TogayandClay Good 3e50944fb0 fix(build): allow esbuild install scripts (#1196)
* Add pnpm-workspace.yaml to allow esbuild build scripts

pnpm 10+ blocks all dependency build scripts by default unless explicitly
approved via allowBuilds or onlyBuiltDependencies in pnpm-workspace.yaml.

esbuild (transitive dependency of vitest -> vite) has a postinstall script
that downloads a platform-specific native binary. Without this config,
pnpm install exits non-zero with [ERR_PNPM_IGNORED_BUILDS], breaking any
downstream packaging (AUR, Nix, Docker) or local setup using pnpm >=10.

Refs: #1195

* fix(build): declare pnpm workspace root

* fix(build): harden pnpm workspace policies

---------

Co-authored-by: Clay Good <hi@claygood.com>
2026-08-05 00:18:37 +00:00
Clay Good 02b124e6b6 fix(security): patch fast-uri, postcss, and brace-expansion advisories (#1510)
* fix(security): patch fast-uri, postcss, and brace-expansion advisories

Resolve the two open Dependabot alerts plus a third high-severity advisory
the repo's own audit surfaces but Dependabot had not filed, all via
version-ranged pnpm overrides (they lapse once the upstream tree moves past
them):

- fast-uri 3.1.4 -> 3.1.5 (website): GHSA-7p8r-x3mc-p8w7, high. Host
  confusion via backslash authority introducer. Pulled in transitively by
  ajv@8.18.0; bounded to ^3.1.5 so it stays on the 3.x line ajv expects.
- postcss 8.5.22 -> 8.5.25 (root): GHSA-fxqj-rqcc-2cmp, moderate. Arbitrary
  .map file read via attacker-controlled sourceMappingURL. Pulled in by
  vite (dev/test tooling).
- brace-expansion 5.0.8 -> 5.0.9 (website): GHSA-rgw5-rvv9-x895, high. DoS
  via unbounded recursion. The existing override capped at >=5.0.8, and
  5.0.8 is itself vulnerable under this newer advisory; the root already
  resolved to 5.0.9.

Root and website audits are clean at --audit-level high (and any-severity
for the website). Full test suite: 3662 passing.

* harden(security): bound overrides, scope release perms, add website lockfile drift check, document archive TOCTOU intent

Hardening pass over the security fixes, from a parallel review of the
dependency, CI, archive, and adjacent-code surfaces. Each item is low-risk
and verified; resolved dependency versions are unchanged.

- deps: bound the three security overrides to their current major
  (brace-expansion ">=5.0.9 <6", postcss ">=8.5.23 <9"). A bare ">=X" pin
  would take a future major on the next lockfile regen without review; the
  website already models the caret-bounded idiom.
- ci: scope release-prepare.yml permissions per job. The top-level block
  dropped "pull-requests: write"; only the "prepare" job (which opens the
  Version Packages PR) now holds it. The "beta" job only tags/releases and
  publishes via OIDC, so it inherits the narrower default (least privilege).
- ci: add a "Website Lockfile Drift" job to security.yml. The website keeps
  its own lockfile and is never installed in CI, so a website override that
  stops resolving would go unnoticed and `pnpm audit` would scan a stale
  graph. A `pnpm install --frozen-lockfile --ignore-scripts --dir website`
  fails fast on that drift (root drift is already caught in ci.yml).
- archive: add intent comments at the 7 js/file-system-race sites in
  src/core/archive.ts. The stat->read->re-stat pattern is a deliberate
  concurrent-change detector; the comments record why, so no future refactor
  (human or scanner-driven) collapses it to fd I/O and blinds the guard.

Verified: 3662 tests pass, build clean, website build clean, root+website
audits clean at --audit-level high, and the new frozen-lockfile check passes
locally.

* chore(nix): refresh pnpmDeps hash for the lockfile change

The root pnpm-lock.yaml changed (postcss + brace-expansion overrides), which
stales the fixed-output pnpmDeps hash and fails Nix Flake Validation. Repin to
the value CI computed from the new lockfile.
2026-08-04 22:52:40 +00:00
dependabot[bot]andClay Good 23c2787789 chore(deps-dev): bump eslint from 10.7.0 to 10.8.0 in the development-dependencies group (#1494)
* chore(deps-dev): bump eslint in the development-dependencies group

Bumps the development-dependencies group with 1 update: [eslint](https://github.com/eslint/eslint).


Updates `eslint` from 10.7.0 to 10.8.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.7.0...v10.8.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(nix): refresh pnpm dependency hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Clay Good <hi@claygood.com>
2026-08-04 17:39:36 +00:00
Clay GoodandClaude Opus 5 87312900f5 fix(telemetry): send the usage event directly instead of via posthog-node (#1476)
* fix(telemetry): send the usage event directly instead of via posthog-node

Installing OpenSpec shipped posthog-node's transitive tree
(@posthog/core, @posthog/types) to every consumer. Those packages
release several times a day, so any freshly resolved install tripped
supply-chain age policies — pnpm's minimumReleaseAge failed with
ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION on entries younger than the
policy window (#1390). No pinning fixes this: exact-pinning posthog-node
leaves its own ranges floating, npm overrides only apply at a consumer's
root, pnpm ignores a dependency's npm-shrinkwrap, and bundledDependencies
under a pnpm-managed node_modules packs the virtual-store layout and
breaks module resolution (verified: the bundled CLI crashes on import).

The SDK's only remaining job here was the wire format: the client was
already configured to send one event immediately, time-bounded, with no
retries, through an injected fetch that never throws. Post the same
capture payload to the same /batch/ endpoint with that fetch directly.
Same event name, properties, distinct id, and opt-out guards; shutdown
still flushes in-flight events, each bounded by the request timeout.

Verified end to end: the packed tarball contains zero posthog files, a
pnpm consumer with minimumReleaseAge: 1440 installs cleanly with zero
posthog lockfile entries, and the live endpoint answers 200 OK to the
new payload. Regression tests pin the manifest and src free of posthog.

Fixes #1390

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

* build(nix): update the pnpm deps hash for the posthog-node removal

Value taken from the CI mismatch report.

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

* fix(telemetry): dispose the response body so no socket outlives shutdown

undici keeps the connection occupied until the response body is consumed
or canceled, and telemetry never reads it — on both the success and
non-2xx paths the socket could linger after shutdown() returned. Cancel
the body before the tracked promise resolves, with coverage for both
paths (bodyUsed asserted after shutdown), and the live endpoint
re-verified with disposal in place.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 01:06:32 +00:00
Clay GoodandClaude Opus 5 17af60c66e fix(archive): make the scenario-drift check fence-aware, plus release-audit follow-ups (#1475)
* fix(archive): make the scenario-drift check fence-aware

parseScenarioBlocks matched #### Scenario: headers on raw lines while the
validator's countScenarios masks fenced code blocks (#1151). The drift
check (#1391) inherited the raw scan, so a fenced scenario example in the
current spec aborted an archive that validate had passed, and a fenced
name in the MODIFIED block counted as keeping a scenario the block had
actually dropped. Build the shared code-fence mask and skip masked lines
in both the header scan and the block-end scan.

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

* fix(update): tear down the redirected request when the budget expires

The overall request budget was armed inside the first send() and its
callback closed over that hop's request. After a redirect the timer
destroyed the already-dead first request, so a redirect target that
trickled bytes kept resetting its idle timeout and held the socket open
until the body-size cap. Track the in-flight request and have the budget
timer destroy whichever one is open.

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

* chore(release): add changesets for user-facing changes missing from the 1.7.0 notes

18 feat/fix commits merged since v1.6.0 without a changeset, so the
pending Version Packages PR would have released them silently: five tool
integrations (ZCode, Hermes, CodeArts, Kimi Code rename, Codex
skills-only), skills.sh distribution, symlinked schema dirs, nested spec
discovery, drift multiplicity, checkbox markers, Windows welcome input,
npx avoidance, doctor store drift, local dates, missing-core-workflows
warning, store-aware main specs, open-questions guidance, and spec
content guidance. Plus changesets for this branch's two fixes.

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

* fix(adapters): escape TOML-active characters in Gemini command files

The gemini adapter interpolated the description into a TOML basic string
and the body into a multiline basic string with no escaping. Every
current template value happens to be safe; the first description with a
double quote or backslash would silently produce invalid TOML for all
Gemini command files. Escape both contexts (#1447 fixed the same class
for the YAML adapters but scoped itself to YAML).

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

* fix(update): harden install detection and redirect handling

Three follow-ups from the release audit:
- A path segment literally named volta (a user or project directory)
  classified the install as volta-managed and swallowed the upgrade
  offer. The undotted spelling now requires volta's own tools/image
  layout, matching how pnpm and yarn already demand corroboration.
- The Windows npm-ownership fallback checked that the npm prefix exists,
  which is true of any X\node_modules\pkg tree, hand-copied ones
  included. Corroborate with the openspec.cmd shim npm actually writes.
- A https registry redirecting to plain http was followed; a MITM on
  that reply controls the newer-version answer. Refuse the downgrade.

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

* chore(cli): export zcodeAdapter from the barrel and sync a completion description

zcode was registered but missing from the adapters barrel (its test
imported the module directly), and the completion registry still carried
the pre-#1062 description for the instructions command.

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

* fix(parser): strip a UTF-8 BOM before parsing specs and deltas

A BOM-prefixed delta spec (Windows editors, PowerShell Out-File) failed
validate and archive with 'No delta sections found' because the first
line never matched '## ADDED Requirements'. Strip the BOM in both
normalizers, the same way tool detection already does.

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

* fix(cli): reject over-long change names with a validation message

A 300-character change name surfaced two raw ENAMETOOLONG errno dumps
from stat and mkdir. Bound the name at 200 characters in
validateChangeName so the failure is a normal validation error.

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

* fix(archive): finish the early-sync no-op rules for MODIFIED and RENAMED

Two asymmetries left over from the #1376/#1386/#1437 no-op work:

- MODIFIED counted every delta as applied even when the block was
  byte-equal to the main spec, so a fully early-synced change rewrote
  the file (normalization churn), printed '~ N modified', and reported
  specsUpdated: true where its ADDED/REMOVED/RENAMED twins print 'Specs
  already in sync; no files changed.' Count only real replacements.
- RENAMED's already-synced skip (source gone, target present) had no
  near-miss guard: a case/whitespace variant of the source still in the
  spec means a typo'd header, and REMOVED already hard-aborts on that
  signal. Apply the same guard, excluding the target itself so a
  case-only rename still no-ops.

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

* fix(validate): stop reporting an unreadable specs dir as 'no deltas'

The delta-validation loop swallowed every error as 'if no specs dir,
treat as no deltas', so an EACCES capability folder produced the
misleading 'Change must have at least one delta' while archive let the
same error propagate. Tolerate only ENOENT and ENOTDIR (a stray specs
file); anything else stays loud, matching discoverSpecFiles' documented
fail-loud contract.

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

* fix(update): say when commands-only delivery leaves a tool with nothing

Under delivery: commands, update removed the skills of adapterless
skills-only tools (Hermes, Kimi Code, Vibe, CodeArts, ForgeCode) without
a word — leaving zero OpenSpec artifacts while the tool's detection dir
kept re-suggesting an init that would also generate nothing. Print the
same per-tool configuration correction init already prints, pointing at
'openspec config set delivery both'.

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

* fix(completion): honor $ZSH and $ZSH_CUSTOM for Oh My Zsh installs

The installer used a set $ZSH only as an is-installed signal and then
wrote to ~/.oh-my-zsh regardless, so a custom OMZ location got a
freshly created ~/.oh-my-zsh tree that no shell ever loads — and
isInstalled/uninstall looked in the same wrong place. Route every path
through the $ZSH/$ZSH_CUSTOM-aware helpers.

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

* fix(init): make the static welcome screen wait for the Enter it asks for

The static branch printed 'Press Enter to select tools...' and returned
immediately, so the Enter landed in the tool picker and submitted the
pre-selected set sight-unseen. #1462 routed reduced-motion,
OPENSPEC_NO_ANIMATION, --no-animation, NO_COLOR, and narrow-terminal
users onto this path. Wait in a TTY; drop the prompt line when there is
no TTY to wait on.

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

* fix(feedback): keep the manual fallback on every gh failure

Only missing-gh and unauthenticated flows showed the formatted feedback
and pre-filled submission URL; issues-disabled, network, or rate-limit
failures printed gh's stderr and discarded the path to submit what the
user had already typed. Route those through the same manual fallback,
preserving gh's exit code.

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

* test(update): model the npm shim in the Windows prefix fixture

The ownership corroboration now checks for the openspec.cmd shim npm
writes beside node_modules; the Homebrew-prefix fixture built the layout
without it, so the test failed on windows-pwsh. Write the shim in the
fixture and pin the inverse: the same shape with nothing npm wrote (a
hand-copied portable tree) is not an npm install.

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

* fix(update): require volta's full tools/image layout for the undotted spelling

The corroboration used has('tools', 'image'), which is some() — volta
AND (tools OR image) — so /srv/volta/tools/apps/... still classified as
a Volta install and swallowed the upgrade offer. Require both segments,
matching the real %LOCALAPPDATA%\Volta\tools\image layout.

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

* fix(adapters): escape control characters in Gemini multiline prompts

escapeTomlMultilineBasicString handled backslashes and quote-triples but
not the C0 controls that are as invalid in a multiline basic string as
in a single-line one. Reuse TOML_CONTROL_CHARS, applied last so the
escapes it introduces are not re-doubled.

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

* fix(completion): finish the $ZSH_CUSTOM support and isolate it in tests

The fpath verification advice still grepped the literal
custom/completions, which a relocated $ZSH_CUSTOM need never contain —
grep the actual directory instead. The installer tests cleared only
$ZSH, so on a machine exporting $ZSH_CUSTOM they would have written
into (and deleted from) the developer's real OMZ custom dir — the same
leakage class #1400 fixed for $ZSH. Clear/restore both, and pin the
custom-location paths with two new tests.

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

* chore(release): correct the hermes and zcode changeset wording

Hermes is skills-only (no command adapter), and zcode's namespaced
commands register /opsx:<id>, not /opsx-* — the release notes must not
reintroduce the invocation-spelling confusion #1471 removed.

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

* test(feedback): pin the manual fallback on a non-label gh failure

The new reportGhFailure output (formatted feedback + pre-filled URL) had
no coverage; the network-failure test now asserts it.

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

* fix(completion): match fpath entries as literal strings in the OMZ guidance

The verification advice interpolated the completions dir into
grep "<dir>" where regex metacharacters make the check unreliable and
quotes could break the displayed command. Print one fpath entry per
line and match with grep -F on a shell-quoted literal.

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

* fix(adapters): never emit a bare carriage return in Gemini TOML prompts

A lone CR is illegal in a multiline basic string — Python 3.13 tomllib
rejects the file — and the control-char pass deliberately skipped it on
the assumption it only appears as CRLF. Normalize CRLF to LF and escape
any remaining CR as \r. The escaping guarantee is now parser-backed:
smol-toml (new devDependency) round-trips every hostile body in the
regression matrix (lone CR, CRLF, CR before a quote run, NUL/VT/FF,
trailing backslash, four- and five-quote runs), and the same outputs
were verified against Python tomllib.

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

* build(nix): update the pnpm deps hash for the smol-toml devDependency

The lockfile changed, so the fixed-output derivation hash moved; value
taken from the CI mismatch report.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 23:57:41 +00:00
Clay GoodandClaude Fable 5 05c701970a chore(security): override brace-expansion to fix the failing audit (#1461)
* chore(security): override brace-expansion to fix the failing audit

A new advisory (GHSA-mh99-v99m-4gvg, high) flags brace-expansion <= 5.0.7
with the only patched release being 5.0.8. The scheduled Security workflow
has failed on every run since 2026-07-27.

pnpm audit --fix adds a scoped override in both the root and website
packages; the lockfile diffs touch only brace-expansion and its subtree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(nix): blank pnpmDeps hash to surface the new lockfile hash

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(nix): pin pnpmDeps hash for the updated lockfile

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 21:15:19 +00:00
Clay GoodandClaude Opus 5 abb422a04b chore(deps): consolidate dependabot bumps with flake hash update (#1457)
* chore(deps): consolidate dependabot bumps (typescript 6, @types/node 26, ora 9, commander, posthog-node)

Replaces #1448, #1451, #1452 and #1453 with a single lockfile resolution.
Each of those PRs changed pnpm-lock.yaml, so merging them serially would
invalidate the flake.nix pnpmDeps hash four times over.

- typescript 5.9.3 -> 6.0.3 (#1452)
- @types/node 24.2.0 -> 26.x (#1451)
- ora 8.2.0 -> 9.4.1 (#1453)
- commander 14.0.0 -> 14.0.3, posthog-node 5.46.0 -> 5.46.1 (#1448, lockfile only)

#1450 (@inquirer/prompts 8) is deliberately excluded: it needs a code
migration, not a version bump.

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

* chore(nix): update pnpmDeps hash for bumped lockfile

Hash taken from this PR's first Nix Flake Validation run. Note it differs
from the hash any individual dependabot PR would have produced -- the
combined lockfile resolves to its own content hash.

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

* chore(deps): align @types/node with the Node 20.19 runtime floor

Addresses review feedback: compiling against Node 26 declarations lets the
type checker admit APIs that are unavailable on the runtimes OpenSpec
actually supports (engines: node >=20.19.0).

Pins @types/node to ^20.19.43, the latest release in the line matching the
declared floor. This also corrects a pre-existing drift -- main was on
@types/node 24 against the same 20.19 floor, so the types were already
ahead of the supported runtime before this PR.

Verified: build clean, tsc --noEmit clean, eslint clean, 112 files /
2253 tests passing, dist/ emit byte-identical to origin/main, and no peer
dependency warnings.

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

* chore(nix): update pnpmDeps hash for the realigned lockfile

The @types/node downgrade to the 20.19 line changed the dependency set
again (it pulls undici-types 6.21.0), so the previous hash no longer
matches. Value taken from a forced-mismatch Nix run on this branch.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 20:37:39 +00:00
Clay GoodandClaude Fable 5 5406c8b3ff chore(deps): consolidate dependabot bumps with flake hash update (#1427)
* chore(deps): consolidate dependabot bumps (chalk, posthog-node, zod, changesets, typescript-eslint, eslint 10)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(nix): update pnpmDeps hash for bumped lockfile

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:54:37 +00:00
Clay GoodandClaude Opus 4.8 e2f748c64f chore(security): add security policy, dependabot config, and config key guards (#1415)
* chore(security): add security policy, dependabot config, and config key guards

Adds a SECURITY.md with a private disclosure path and an explicit threat
model, a Dependabot configuration covering the CLI package, the docs site,
and CI actions, and closes a prototype-pollution path in `config set`.

`--allow-unknown` was meant to relax the known-key check but skipped every
key check, so `openspec config set --allow-unknown __proto__.polluted x`
reported success and assigned onto Object.prototype for the process
lifetime. Unsafe segments are now rejected at the command layer regardless
of `--allow-unknown`, and setNestedValue/deleteNestedValue refuse them for
any caller.

Also bumps the bundled yaml dependency from 2.8.2 to 2.9.0, the only
advisory in this repo that affects code shipped in the npm package.

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

* chore(nix): update pnpmDeps hash for the yaml bump

fetchPnpmDeps pins a fixed-output hash over the whole dependency set, so
changing pnpm-lock.yaml invalidates it. Recovered the new value from a
hash-mismatch build.

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

* chore(security): clear dependency advisories and automate future checks

Refreshes both lockfiles so every open advisory in the CLI package is
resolved, replaces a quadratically-backtracking heading parser, and adds
the automation to catch the next one.

Dependency refresh (in-range, lockfile only): brace-expansion, flatted,
js-yaml, minimatch, postcss, rollup, and vite all move to patched versions
in the root lockfile; fast-uri and brace-expansion move in the website
lockfile. Only @changesets/cli needed a declared floor bump, to reach a
patched js-yaml. Production dependencies now report zero advisories.

extractFirstPurposeLine parsed ATX headings with /\s+#+\s*$/, which
backtracks quadratically on a whitespace-padded title. Replaced with a
linear hand-rolled scan, verified identical to the old implementation
across 303,000 generated inputs.

Automation: a Security workflow runs dependency review on pull requests,
blocks on advisories in published dependencies, and re-audits weekly; every
GitHub Action is pinned to a commit SHA so a moved tag cannot change what
CI executes.

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

* fix(ci): drop the pnpm cache from the audit job

Nothing is installed there, so setup-node's cache-save post step failed on
the missing store path even though both audit steps passed.

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

* chore(nix): repin pnpmDeps hash after the dependency refresh

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

* chore(security): apply opengrep findings and fix a dependency-review permission gap

Ran opengrep against the repository to check the claims in #1414. Of 208
findings, 201 are one path-traversal rule firing on joins built from module
constants, argv, or readdir entry names; 1 non-literal-regexp is fed only by
LEGACY_SLASH_COMMAND_PATHS. Neither is reachable from untrusted input. The
actionable results are applied here.

- dependabot: add a cooldown so a freshly published version is not adopted
  immediately. Security updates ignore the cooldown, so this delays only
  routine bumps, long enough for a compromised release to be yanked.
- getNestedValue now refuses prototype-reaching segments, matching the
  guards already on setNestedValue and deleteNestedValue.
- dependency-review no longer asks to comment on the pull request. That
  needs `pull-requests: write`, which the workflow does not grant and a
  fork's token never gets, so a real finding would have failed on the
  comment instead of reporting the vulnerable dependency.
- SECURITY.md: show the command that proves build tooling is absent from an
  installed copy, rather than asking readers to take it on trust.

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

* fix(dependabot): drop semver cooldown keys unsupported by github-actions

Dependabot rejected the whole config file, which would have silently
disabled every version update.

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

* chore(security): make the audit advisory and document runtime behavior

The published-dependency audit no longer fails the job. A newly published
advisory should not block an unrelated pull request, and the step depends on
registry availability; Dependabot alerts and dependency review remain the
gates. SECURITY.md is corrected to match — it claimed the audit was blocking.

Also documents what the CLI does on your machine, all verified rather than
asserted: the install script prints one line and makes no network request or
file write; every shell-invoking call uses a fixed literal while anything
carrying user input uses an argument array with shell:false; telemetry sends
a command name, a version, and a local random UUID, with IP capture disabled.

Secret scanning is now listed, confirmed enabled by a repository admin.

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

* fix(ci): keep the production audit blocking off pull requests

Making the step advisory on every event meant a newly published
high-severity advisory in a shipped dependency could not fail any run
unless a dependency changed. It stays advisory on pull requests, so an
unrelated change is never blocked by an advisory published that morning,
and blocks on the weekly schedule and on pushes to main, where a failure
is the signal rather than a tax on someone else's work.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 18:55:51 +00:00
Clay GoodandClaude Fable 5 9d40ae98f0 fix(nix): build with Node.js 22 now that nixpkgs marks Node 20 insecure (#1406)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 13:32:00 +00:00
Tabish Bidiwale 737518b36f [codex] Refresh security dependency locks (#1249)
* fix: refresh security dependency locks

* fix: refresh nix pnpm dependency hash
2026-06-24 08:54:03 +00:00
Jérôme BenoitandTabish Bidiwale 86d2e04cae chore(nix): improve flake with dynamic version and build optimization (#550)
* chore(nix): improve flake with dynamic version and source filtering

- Read version dynamically from package.json instead of hardcoding
- Add lib.fileset source filtering to exclude node_modules and build artifacts
- Update update-flake.sh to support dynamic version pattern
- Add hash change detection to skip unnecessary rebuilds
- Improve error handling with automatic rollback on failure
- Update specs to reflect dynamic version behavior

* chore(ci): bump Nix actions to latest versions

- nix-installer-action: v13 → v21
- magic-nix-cache-action: v8 → v13
- Update validation message for unchanged flake.nix

* chore: add changeset for Nix improvements

* fix(nix): make update-flake.sh portable to macOS

- Fix grep pattern on line 37 to include opening parenthesis
- Replace GNU grep -oP with portable sed alternatives (lines 53, 68, 70)
- Ensures script works on both Linux and macOS (BSD sed/grep)

* fix(nix): properly check build verification exit status

Fix logic bug where build failures were incorrectly reported as success.
The script now:
- Captures build exit code and output separately
- Fails fast if build returns non-zero exit code
- Only checks for 'dirty tree' warning if build succeeded

This addresses CodeRabbit review feedback on line 101-107.

---------

Co-authored-by: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com>
2026-01-27 14:23:11 -08:00
Tabish Bidiwale ae83b4e16d feat(cli): add interactive UI for artifact experimental setup (#560)
* feat(cli): add interactive UI for artifact experimental setup

Add animated welcome screen and searchable multi-select prompt when
running `openspec artifact-experimental-setup` without the --tool flag
in interactive mode. Users can now browse and select multiple tools
for setup instead of requiring the --tool flag.

- Add welcome screen with ASCII art animation
- Add searchable multi-select prompt component
- Support multi-tool setup in single command invocation

* fix(nix): update flake version and reset hash for rebuild

- Update version from 0.20.0 to 0.23.0 to match package.json
- Set pnpmDeps hash to empty string to trigger rebuild
- Fix update-flake.sh to work on macOS (use portable grep/sed)

CI will fail with correct hash which we'll then apply.

* fix(nix): set correct pnpmDeps hash

* feat(cli): improve error handling for multi-tool setup

- Continue setup for remaining tools when one fails
- Collect and report all failures at the end
- Only throw if all tools fail
- Show partial success summary (configured vs failed)
2026-01-22 18:39:38 -08:00
Pim SnelandTabish Bidiwale ed4d965208 feat: add nix flake support (sorry for this duplicate) (#459)
* add nix flake support

* feat: add Nix flake maintenance automation

* Add Nix Flake CI Validation

* fix updatescript, update flake

* make update-script compatible with macos

---------

Co-authored-by: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com>
2026-01-16 13:00:11 -08:00