Commit Graph

92 Commits

Author SHA1 Message Date
openspec-release-bot[bot] 9d4e5974e5 Version Packages (#1822)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-09 20:59:22 +00:00
Clay Good e4e112d94f chore(deps): declare pnpm overrides only in pnpm-workspace.yaml (#1816)
The security overrides were declared twice: in pnpm-workspace.yaml, with
the advisory comments explaining each pin, and again under
package.json's pnpm.overrides. The copies are not additive — pnpm 10
uses package.json's block instead of the workspace list when both are
present — and Dependabot rewrites plain-name entries in package.json
whenever it bumps the same package. So a routine bump silently
displaces the pins that patch advisories, and fails the equality test
that guards them (#1812).

Keeps one declaration, in the file that carries the reasoning, and
asserts the mirror stays gone.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 17:47:22 +00:00
openspec-release-bot[bot] e062b9572b Version Packages (#1766)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-03 00:00:56 +00:00
dependabot[bot] 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
Pablo Formoso 1bcdf1b032 fix(build): prepare npm git installs without pnpm (#792)
* fix: handle npm git dep installation for GitHub installs

npm v11's git dep preparation runs `prepare` before node_modules exist
in the temp clone directory, causing TypeScript compilation to fail.

Changes:
- build.js: skip build gracefully when node_modules absent
- package.json: use `node build.js` directly in prepare/prepack for
  npm compatibility (avoids pnpm dependency during git dep install)

Note: postinstall.js already handles all errors internally via
main().catch(() => process.exit(0)), so no `|| true` wrapper needed.

Install from GitHub with:
  npm pack github:user/repo#branch
  npm install -g ./fission-ai-openspec-x.y.z.tgz

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

* fix(build): prepare npm git installs without pnpm

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Clay Good <hi@claygood.com>
2026-09-02 19:49:03 +00:00
openspec-release-bot[bot] a0ddb60d04 Version Packages (#1728)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-26 21:19:57 +00:00
bsmedberg-xometry 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
openspec-release-bot[bot] 1ebddd17f4 Version Packages (#1705)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-19 22:23:30 +00:00
Clay Good 7276c6c268 fix(packaging): print the completions tip from the CLI, not a postinstall script (#1704)
* fix(packaging): print the completions tip from the CLI, not a postinstall script

The package's only install script existed to print one line suggesting
`openspec completion install`. Shipping it made every `npm install -g`
emit an npm allow-scripts warning, and `npm approve-scripts` then failed
with ENOMATCH because it looks in the local project, not a global install
— so the warning looked like a packaging fault with no way to clear it.

The tip now prints once on the CLI's first run, recorded via a
`completionTipSeen` flag in the existing global config alongside the
telemetry notice's `noticeSeen`. It writes to stderr so it can never
contaminate piped stdout, and is suppressed under CI,
OPENSPEC_NO_COMPLETIONS=1, `--json` runs, and `openspec completion`
itself. The published package now ships no lifecycle scripts at all.

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

* fix(completions): stop the first-run tip from corrupting global config

Adversarial review of the previous commit found it wrote a defaults-merged
config: `saveGlobalConfig({ ...getGlobalConfig(), completionTipSeen: true })`
stamped `profile: "core"` into every user's config.json on first run.
`migrateIfNeeded` treats a raw `profile` as "already migrated", so the
one-time profile migration would never run again — and `openspec update`
then deleted the user's installed workflow skills. Reproduced: 2 skill
directories removed where main reports "Migrated: custom profile with 8
workflows". The same write also overwrote an unparsable config with
defaults and made `openspec config list` report defaults as explicit.

The tip now reads and writes the raw config file and touches only its own
key, leaving an unreadable config strictly alone.

Other hardening from the same review:

- Suppress the tip for the hidden `__complete` resolver. Generated
  completion scripts call it on every Tab press with stderr discarded, so
  the one-shot tip was consumed where nobody could see it.
- Defer, never consume, when stderr is not a terminal. Agents and pipes
  drive this CLI far more often than humans do and would otherwise spend
  the tip into a log nobody opens.
- Skip the tip when completions are already installed. Previously the CLI
  advertised `completion install` to users who had run it — including on
  the very next command after installing. Adds `isInstalled()` to the
  bash/fish/powershell installers, mirroring the zsh one.
- Use the repo's `isCiEnvironment()` instead of a `CI === 'true'` string
  check, so `CI=yes`/`True`/`on` are as quiet as telemetry is.
- Move the call to `postAction` so the tip trails the command's output
  instead of pushing errors and `init`'s setup summary down the screen.
- Record before printing, so an unwritable config dir means silence rather
  than nagging on every run.

Tests: assert the message literal (mutation testing showed the message text
was the one unguarded behavior), the raw-write shape, corrupt-config
safety, the already-installed path, the defer policy, and an e2e case
pinning the non-TTY contract.

Docs: SECURITY.md no longer claims zero lifecycle scripts — `prepare` is
still declared and runs for git/directory installs; the registry-install
claim is the accurate one. `OPENSPEC_NO_COMPLETIONS` is now documented.

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

* test(completions): make the unwritable-config case portable to Windows

fs.chmodSync(dir, 0o555) does not stop a write on Windows, so this test's
unwritable condition never existed there: markTipSeen succeeded, the tip
printed, and windows-pwsh was the only failing job.

Occupy the config directory's path with a file instead. mkdirSync with
recursive: true tolerates an existing directory but throws on an existing
file on every platform, so the persist fails where a real permission error
would - before anything is printed. Also asserts the path is still a file,
so a partial write through the failure would be caught.

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

* fix(completions): retire the first-run tip instead of advising a dead end

Second adversarial pass over the tip, covering the hardening commit itself.

- An undetected or unsupported shell now retires the tip quietly. It used
  to print, but `openspec completion install` exits 1 for exactly those
  users ("Shell 'tcsh' is not supported yet" / "Could not auto-detect
  shell"), so the one message they would ever get about completions sent
  them to a command that fails.
- `markTipSeen` re-reads the config immediately before writing and swaps
  the file in by rename. Deciding whether to show the tip costs a `ps`
  spawn plus a stat, and a sibling process writing config in that window
  got clobbered — on a first run that is exactly when telemetry mints
  `anonymousId`. Concurrent-process loss drops from 15/40 to ~2/40, and
  what now usually loses is the tip's own flag (it simply shows once
  more) rather than telemetry identity. The residual is the non-atomic
  read-modify-write shape shared with telemetry's own writer.
- `isInstalled()` uses stat().isFile(), so a directory at the install
  path no longer counts as an installed completion script.
- Documented what `isInstalled()` actually promises: the script file, not
  the profile sourcing line that bash and PowerShell also need. Callers
  deciding whether to *advertise* completions want the loose reading — a
  user whose profile config failed has already met the installer.
- Corrected a comment claiming the probe costs "one stat": detectShell()
  forks `ps` to read the parent process on every non-Windows run.

Tests: mutation testing found four surviving mutants — dropping
isCompletionRun from the defer policy, reverting isCiEnvironment to a
CI==='true' string check, failing closed on an undetected shell, and
neutering the non-object config guard (which lets a JSON array config be
rewritten as {"0":...}). All four now fail a test. Adds direct coverage
for the three new isInstalled() implementations, which had none.

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

* fix(validate): stop `change validate` exiting past commander's postAction

`change validate` on a failing change called process.exit(exitCode). That
tears down before commander's postAction hook, which is the same trap the
`update` command documents 165 lines earlier: "exiting here would skip
commander's postAction hook, killing the telemetry flush mid-request".

A change that fails validation is a routine outcome, not an error, so this
silently dropped the telemetry flush and — since the completions tip moved
to postAction — the first-run tip for anyone whose first command was a
failing validate. Verified under a pty: before, the tip never printed and
completionTipSeen was never recorded; after, both happen and the exit code
is still 1 (validate() already sets process.exitCode, which Node honours at
natural exit — top-level `validate --all` has always relied on exactly
that). The existing e2e in validate-scenario-loss.test.ts pins the exit
code.

Also wraps the postAction tip in try/finally so the telemetry flush runs
even if the hint throws: program.parse() is synchronous, so a rejection
there has no catch above it.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 20:19:51 +00:00
Ryan de Melo 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
openspec-release-bot[bot] 2826b8889e Version Packages (#1629)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-13 15:22:19 +00:00
Clay Good 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
openspec-release-bot[bot] d57889664c Version Packages (#1488)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-05 20:55:58 +00:00
Ismet Togay 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
openspec-release-bot[bot] 4e16790d90 Version Packages (#1380)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-29 01:25:23 +00:00
Clay Good 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 Good 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 Good 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 Good 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 Good 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 Good 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 Good ffe27de18d chore(scripts): add a parity-hash regeneration helper (#1416)
skill-templates-parity.test.ts pins a SHA-256 per workflow template so an
unintended template edit fails loudly. The cost lands on every intended
edit: the pinned hashes go stale, and because all 37 live in two maps in
one file, two branches editing different templates collide there on rebase.
Resolving that means hand-editing 64-character hashes, which is where
transcription mistakes come from - and the test proves a hash matches its
source, never that the source is right, so a bad value regenerated over a
bad merge passes CI in silence.

Recompute every pinned hash from the built dist/ and rewrite the map in
place, reporting which entries moved. The skill-directory mapping comes
from getSkillTemplates(), the same helper the skills.sh generator uses, so
adding a workflow needs no second list here; function labels resolve
dynamically against the module exports, so there is no hard-coded list at
all.

"Nothing to update" has to mean it, so four things abort the run without
writing:
  - dist/ missing or older than src/, which would pin hashes from a stale
    build that the parity test - which reads src/ - then rejects
  - a pinned label with no matching export, from a renamed or deleted
    template
  - a pinned hash whose line the patterns do not recognise, counted by
    comparing 64-hex literals found against literals rewritten; the count
    uses a deliberately broader pattern so it is a real cross-check rather
    than a restatement of the same patterns
  - a skill the registry deploys that nothing pins, compared in the other
    direction: pins-to-registry only sees pins that already exist

That last direction closes a hole that predates this script. A workflow
added to getSkillTemplates() but never pinned was invisible to the parity
test too, which compares only the entries it already lists - so it shipped
with no golden hash while everything reported success. skill-templates-
parity.test.ts now pins the registry itself, so CI catches it whether or
not anyone runs this script.

The rewriting lives in parity-hash-shared.mjs, following the split between
generate-skillssh.mjs and skillssh-shared.mjs, so those guards can be
exercised against fabricated input. Running the script for real from a test
would rewrite the repository's own parity test file mid-suite. Each case in
parity-hash-shared.test.ts was mutation-checked: removing the guard it
covers makes it fail.

The script cannot silently emit a wrong hash: the parity test recomputes
the same values independently and compares, so a drift between the two
copies of stableStringify fails the test. The test stays the authority.

Dev tooling only. scripts/ is not published (package.json files ships just
scripts/postinstall.js), no src/ is touched, and no runtime behaviour
changes - hence no changeset.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 18:54:13 +00:00
Clay Good 46a4d78222 feat(skills): publish workflow skills to skills.sh (#1357)
* feat(skills): publish workflow skills to skills.sh

Commit the 12 OpenSpec workflow skills as static skills/<name>/SKILL.md so
`npx skills add Fission-AI/OpenSpec` can install them (skills.sh reads static
files from the repo; OpenSpec otherwise only generates skills at init time).

Files are generated from the existing templates via `pnpm generate:skills`,
not hand-copied, and skillssh-parity.test.ts fails CI if a template changes
without regenerating. The volatile generatedBy frontmatter line is stripped so
the committed copies stay byte-stable across releases.

Closes #1258

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

* fix(skills): force LF on committed skills/ so Windows CI parity holds

The skills.sh distribution files are generated LF-only and compared
byte-for-byte by skillssh-parity.test.ts. Windows autocrlf checked them
out as CRLF, failing the parity assertion. A scoped .gitattributes pins
them to LF on checkout.

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

* fix(skills): reject symlinks and assert the exact committed skill set

Review feedback (alfred): the parity test only visited expected templates,
so an extra or renamed skills/ directory shipped with green CI, and the
generator would write through a pre-existing symlinked skill directory to
anywhere on disk.

- generator: refuse to run if skills/ contains any symlink (checked before
  any deletion, so a bad tree is left intact), validate dirNames against a
  path-segment allowlist, and lstat the target before writing.
- parity test: assert skills/ holds exactly README.md plus one real
  directory per template, each containing a single real SKILL.md.
- focused tests cover symlink refusal (no partial deletion), traversal
  names, and stale-directory cleanup.

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

* fix(templates): abort archive on Cancel, honest summary, fence languages

CodeRabbit review on #1357, fixed at the template source and regenerated:

- archive-change: choosing "Cancel" at the sync prompt now stops the flow
  instead of archiving anyway (skill + command templates).
- archive-change skill: the success output no longer hardcodes "All
  artifacts complete. All tasks complete." when archiving incomplete work.
- archive/bulk-archive/sync-specs/verify-change: language identifiers on
  previously plain code fences (MD040), skill and command twins alike.

Golden hashes in skill-templates-parity.test.ts recomputed from dist/;
skills/ regenerated via pnpm generate:skills.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 21:41:11 +00:00
openspec-release-bot[bot] e1b51d111a Version Packages (#1295)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-10 13:05:54 +00:00
Tabish Bidiwale 8ac624b279 chore: remove stale npm lockfile (#1319)
* chore: remove stale npm lockfile

* ci: use package manager metadata for pnpm setup

* chore: scope npm lockfile ignore to root
2026-07-07 17:08:25 +00:00
openspec-release-bot[bot] 546224e00d Version Packages (#1248)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-28 13:02:43 +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
openspec-release-bot[bot] 1b06fddd59 Version Packages (#1166)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-03 09:31:40 +00:00
openspec-release-bot[bot] bc7ab26650 Version Packages (#1023)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-01 21:23:33 +00:00
Tabish Bidiwale 1cdf0410df [codex] Propose workspace open agent context (#1054)
* Propose workspace open agent context

* Implement workspace open surface

* Address workspace open review feedback

* Archive workspace open agent context

* Fix workspace open Windows launcher args
2026-05-06 03:53:55 +00:00
openspec-release-bot[bot] 3c7a05c5dc Version Packages (#996)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-21 16:14:12 +00:00
openspec-release-bot[bot] 9b5007dbc3 Version Packages (#953)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-11 15:43:55 +00:00
openspec-release-bot[bot] 6a3a1263fe Version Packages (#751)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-23 15:43:08 -08:00
openspec-release-bot[bot] 36fbc898da Version Packages (#628)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-30 14:55:43 -08:00
openspec-release-bot[bot] 37686944a9 Version Packages (#606)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-30 02:27:45 -08:00
openspec-release-bot[bot] f6b415cb9b Version Packages (#597)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-26 19:33:25 -08:00
openspec-release-bot[bot] d155126235 Version Packages (#588)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-26 02:51:24 -08:00
openspec-release-bot[bot] 0e6f42c81c Version Packages (#579)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-25 16:09:16 -08:00
openspec-release-bot[bot] 3b16b13613 Version Packages (#541)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-20 17:21:29 -08:00
openspec-release-bot[bot] 7d1237f00d Version Packages (#531)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-20 00:01:09 -08:00
openspec-release-bot[bot] 473093f885 Version Packages (#517)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-19 00:59:49 -08:00
openspec-release-bot[bot] ed2e832066 Version Packages (#503)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-13 22:50:11 -08:00
Tabish Bidiwale 36078b1947 chore: improve release notes pipeline (#481)
* chore: improve changelog generation with GitHub integration

- Switch to @changesets/changelog-github for PR/author links in CHANGELOG.md
- Add comprehensive changeset README with template and contributor guidance
- Remove release:local script (CI-only releases)

* ci: add AI-powered release notes polishing

Transforms raw changelog into user-friendly release notes when a
GitHub Release is published. Uses Claude Code Action to:
- Generate concise release title (e.g., "v0.18.0 - OPSX Workflow")
- Rewrite changelog as developer-friendly release notes
- Remove noise (commit hashes, PR numbers, internal changes)

Requires CLAUDE_CODE_OAUTH_TOKEN secret (from claude setup-token).
2026-01-10 22:33:43 -08:00
github-actions[bot] 5633556b6d Version Packages (#475)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-10 18:04:55 -08:00
Tabish Bidiwale 940898c1c5 Add optional anonymous usage statistics (#468)
* chore: add proposal for PostHog analytics integration

Introduces the proposal artifact for adding opt-in telemetry to OpenSpec
using PostHog. Covers command tracking, feature adoption metrics, and
privacy-respecting consent management.

* feat: add optional anonymous usage statistics

Introduces privacy-first usage analytics to help understand how OpenSpec
is being used. Key privacy protections:

- Only tracks command names and version (no arguments, paths, or content)
- Opt-out via OPENSPEC_TELEMETRY=0 or DO_NOT_TRACK=1
- Auto-disabled in CI environments
- No IP address collection (explicitly disabled)
- Anonymous ID is a random UUID with no PII

Uses PostHog with a reverse proxy to avoid ad blockers. First-run shows
a one-line notice informing users about the collection.

* docs: make telemetry section collapsible and concise
2026-01-09 23:10:31 -08:00
github-actions[bot] 533cb0fa87 chore(release): version packages (#458)
* Version Packages

* chore: trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <tabishbidiwale@gmail.com>
2026-01-07 00:38:00 -08:00
Tabish Bidiwale ab47cc6b00 feat: restructure schemas as directories with templates (#411)
* feat: restructure schemas as directories with templates

Move built-in schemas from embedded TypeScript objects to a file-based
directory structure. This enables co-located templates alongside schemas.

Changes:
- Remove builtin-schemas.ts (replaced by file-based schemas)
- Add schemas/ directory at package root with spec-driven and tdd schemas
- Update resolveSchema() to load from directory structure
- Resolution checks user dir → package dir

* chore: archive restructure-schema-directories change

* docs: update artifact_poc.md for directory-based schema structure

Update documentation to reflect the new schema structure where schemas
are directories containing schema.yaml and co-located templates/ rather
than single .yaml files with separate template directories.
2025-12-28 17:00:15 +11:00
Tabish Bidiwale af273b8e0b proposal: add artifact graph core query system (#400)
* proposal: add artifact graph core query system

Add OpenSpec change proposal for Slice 1 of the artifact POC - the core
"What's Ready?" query system. This implements:

- ArtifactGraph class for DAG-based dependency modeling
- Filesystem-based state detection (file existence = completion)
- Topological sort for build order calculation
- Ready/blocked artifact queries

This is a parallel module that will coexist with the current system.

* docs: specify Zod for schema validation in artifact graph proposal

- Add decision section for Zod schema validation in design.md
- Update data structures to show Zod schemas with z.infer<> types
- Update tasks to specify Zod usage for type definitions and parsing

* docs: add 2-level schema resolution and built-in schemas

- Add decision for global → built-in schema resolution pattern
- Add resolver.ts for schema lookup logic
- Add built-in schemas directory (spec-driven.yaml, tdd.yaml)
- Add schema resolution tests
- Follows ESLint/Prettier/Git patterns (defaults baked in package)

* experiment: add vertical slice version of artifact graph change

Creates add-artifact-graph-core-v2 with requirements organized as
vertical slices - each requirement file contains its spec, design
decisions, and tasks bundled together for comparison.

* feat(core): add getGlobalDataDir for XDG-compliant data directory

Add getGlobalDataDir() function following XDG Base Directory Specification
for storing user data like schema overrides:
- XDG_DATA_HOME takes precedence on all platforms
- Unix/macOS fallback: ~/.local/share/openspec/
- Windows fallback: %LOCALAPPDATA%/openspec/

* feat(artifact-graph): add core dependency graph module

Implement Slice 1 ("What's Ready?") of the artifact graph system:

- types.ts: Zod schemas for artifact definitions with derived TypeScript types
- schema.ts: YAML parsing with validation for duplicates, invalid refs, cycles
- graph.ts: ArtifactGraph class with Kahn's algorithm for topological sort
- state.ts: Filesystem-based completion detection with glob pattern support
- resolver.ts: Two-level schema resolution (global override → built-in)
- builtin-schemas.ts: spec-driven and tdd workflow definitions

Key design decisions:
- Filesystem as database (stateless, git-friendly)
- Cycle errors show full path (e.g., "A → B → C → A")
- Deterministic ordering via sorted queues

* test(artifact-graph): add comprehensive test suite

52 tests covering all artifact-graph functionality:

- schema.test.ts: Parsing, validation errors, cycle detection
- graph.test.ts: Build order, ready artifacts, blocked queries
- state.test.ts: File existence, glob patterns, missing directories
- resolver.test.ts: Schema resolution with global overrides

* docs(openspec): archive add-artifact-graph-core change

Archive completed change proposal and create artifact-graph spec with
6 requirements covering schema loading, build order, state detection,
ready queries, completion checks, and blocked queries.

* chore: remove experimental artifact-graph-core-v2 folder

Clean up experimental vertical slice proposal that is no longer needed.

* feat(artifact-graph): validate global schema overrides

Global schema overrides are now validated through the same pipeline as
built-in schemas, catching invalid schemas, cyclic dependencies, and
invalid requires references at load time. Added SchemaLoadError for
better error context with file paths.

* test(artifact-graph): add workflow integration tests

Add end-to-end integration tests that exercise the full artifact-graph
pipeline: resolveSchema → ArtifactGraph → detectCompleted → queries.

Tests cover:
- Complete spec-driven and tdd workflow progressions
- Out-of-order file creation handling
- Glob pattern matching with multiple files
- Build order consistency
- Edge cases (empty/missing directories, non-matching files)

* refactor(artifact-graph): adopt zod v4 error message format

Update custom error messages from string format to zod v4 object format
using `{ error: 'message' }` convention.

* fix(test): prevent hanging vitest threads after test runs

- Add teardownTimeout (3s) to vitest config for forced cleanup
- Add global teardown function to vitest.setup.ts
- Call child.unref() to prevent child processes from blocking event loop
- Explicitly destroy stdio streams on process close/error
2025-12-25 22:09:42 +11:00
github-actions[bot] c08a53cb21 chore(release): version packages (#397)
* Version Packages

* chore: trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <tabishbidiwale@gmail.com>
2025-12-23 12:59:10 +11:00