Compare commits

..
Author SHA1 Message Date
Clay GoodandClaude Opus 5.5 239ddaf21f test: give the deprecation notice tests their own anchor in update.test.ts
Each of the four #2022 PRs added its tests before the same describe, so
merging them in sequence conflicted. No test changes.

Part of #2022

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:43:25 -05:00
Clay GoodandClaude Opus 5.5 29a3df1bcb fix(update): only advise switching where it changes something
The notice fired for any tool that gets command files, and told its users
to switch to skills-only delivery. For a tool that keeps its commands
under every delivery, because nothing it runs loads skills, that advice
would point at a switch that cannot help and suggests they lose their
only working surface.

The notice now counts only tools that switching would change: ones that
get command files now and would not under `skills`. On today's tools
every adapter-backed tool qualifies, so behavior is unchanged; it becomes
the right condition as soon as a tool keeps commands regardless.

Part of #2022

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:22:26 -05:00
Clay GoodandClaude Opus 5.5 2563f0f48b docs(update): describe when the deprecation notice shows accurately
The notice shows whenever delivery gives a configured tool command files,
including up-to-date runs that write nothing. The docs callout, changeset
and comments said it showed on runs that write command files.

Part of #2022

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:10:27 -05:00
Clay GoodandClaude Opus 5.5 019acb0011 feat(update): announce that generated command files are deprecated
Generated command files are moving to deprecation in favor of skills, and
users need to hear it from the tool before the next major changes the
default. `openspec update` now ends with a four-line notice whenever it
still writes command files for the project: what is changing, how to
switch now, how to keep commands, and where the plan lives.

The notice is shown on the up-to-date path too, which returns early and
is what most routine runs hit. It stays silent once delivery is `skills`,
and for projects whose tools get no command files at all. It is worded
"by default" so it is true both for users on the implicit default and for
users who set `delivery` explicitly, whose setting the next major keeps.

The delivery guide and the `delivery` reference entry carry the same
message, and a docs-claims test pins them to the CLI notice so the two
cannot drift.

Part of #2022

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:57:30 -05:00
43 changed files with 1054 additions and 1496 deletions
@@ -1,7 +0,0 @@
---
"@fission-ai/openspec": patch
---
### Bug Fixes
- **Archive accepts `openspec/`, `openspec/changes` or `openspec/specs` as a symlink** — `openspec archive` no longer refuses a project-owned symlink that points to another directory, such as a shared docs repository. An `archive/` directory that resolves outside the changes directory is still refused.
+5
View File
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---
Generated command files are now marked deprecated. While your delivery setting gives a configured tool command files, `openspec update` ends with a short notice: the next major version installs skills only by default, how to switch now (`openspec config set delivery skills && openspec update`), how to keep commands (`openspec config set delivery both`), and a link to the plan. The notice also appears when every tool is already up to date, and it never appears once delivery is `skills` or for tools that get no command files, such as Codex. Nothing about what OpenSpec installs changes in this release.
@@ -1,7 +0,0 @@
---
"@fission-ai/openspec": patch
---
### Bug Fixes
- **Explore no longer offers to write default-schema files under a custom schema** — The explore workflow's "where to capture" table and example offers now say they describe the default spec-driven schema. With another schema, the agent captures each insight in an artifact that schema defines, names it in the offer, and never creates a file such as `design.md` that the schema does not have.
-5
View File
@@ -1,5 +0,0 @@
---
"@fission-ai/openspec": patch
---
Shell Tab completion is faster. `openspec __complete`, which the completion scripts run on every Tab press, now loads 48 modules instead of 344 when completing changes or specs, and no longer records usage, so a press never waits on the telemetry request and can't use up the first-run telemetry notice where nobody sees it. Completion output is unchanged.
-5
View File
@@ -1,5 +0,0 @@
---
"@fission-ai/openspec": patch
---
`openspec validate` and `openspec archive` now warn when a delta spec holds text archive does not carry into the main spec: a `## ` section other than Purpose and the four delta sections, text above the first `## ` header, or prose before the first requirement in ADDED or MODIFIED. That text was dropped without notice before. Like other validation warnings, these fail `openspec validate --strict`. What archive carries is unchanged.
+1 -1
View File
@@ -4,7 +4,7 @@ This directory contains the VS Code dev container configuration for OpenSpec dev
## What's Included
- **Node.js 22 LTS** - TypeScript/JavaScript runtime (OpenSpec itself supports Node 20.19.0 and newer)
- **Node.js 20 LTS** (>=20.19.0) - TypeScript/JavaScript runtime
- **pnpm** - Fast, disk space efficient package manager
- **Git + GitHub CLI** - Version control tools
- **VS Code Extensions**:
+2 -17
View File
@@ -55,34 +55,19 @@ jobs:
strategy:
fail-fast: false
matrix:
# Every OS runs the oldest supported Node (package.json engines).
# Linux also runs the current LTS lines users are likely to have.
include:
- os: ubuntu-latest
shell: bash
label: linux-bash
node: '20.19.0'
vitest_workers: 4
- os: macos-latest
shell: bash
label: macos-bash
node: '20.19.0'
vitest_workers: 4
- os: windows-latest
shell: pwsh
label: windows-pwsh
node: '20.19.0'
vitest_workers: 2
- os: ubuntu-latest
shell: bash
label: linux-bash-node22
node: '22'
vitest_workers: 4
- os: ubuntu-latest
shell: bash
label: linux-bash-node24
node: '24'
vitest_workers: 4
defaults:
run:
@@ -101,7 +86,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
node-version: '20.19.0'
cache: 'pnpm'
- name: Print environment diagnostics
@@ -120,7 +105,7 @@ jobs:
run: pnpm test
- name: Upload test coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == '20.19.0'
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-report-${{ github.event_name }}
+31 -15
View File
@@ -33,6 +33,11 @@ Our philosophy:
→ scalable from personal projects to enterprises
```
> [!TIP]
> **New workflow now available!** We've rebuilt OpenSpec with a new artifact-guided workflow.
>
> Run `/opsx:propose "your idea"` to get started. → [Learn more here](docs/opsx.md)
<p align="center">
Follow <a href="https://x.com/0xTab">@0xTab on X</a> for updates · Join the <a href="https://discord.gg/YctCnvvshC">OpenSpec Discord</a> for help and questions.
</p>
@@ -107,13 +112,13 @@ OpenSpec is built with OpenSpec — browse this repo's live [specs](openspec/spe
Solo, OpenSpec keeps you and your AI honest on a single repo. On a team, the hard part moves: a feature spans the API server, the web app, and a shared library; requirements are owned by one team and consumed by others; planning starts before any code exists.
**[Stores](https://openspec.dev/docs/stores)** are the answer — planning in a repo of its own. The same `openspec/` shape you already know (specs and changes), shared by `git push` like anything else. One source of truth your whole team and every coding agent can read, across every repo.
**[Stores](docs/stores-beta/user-guide.md)** are the answer — planning in a repo of its own. The same `openspec/` shape you already know (specs and changes), shared by `git push` like anything else. One source of truth your whole team and every coding agent can read, across every repo.
- **Cross-repo features** — one change, one plan, even when the code lands in three repos.
- **Shared requirements** — a platform team owns the specs; product teams reference them read-only, right where their coding agent can read them. No drifting wiki.
- **Plan before code** — capture the plan in the store now; the code repos catch up later.
> Stores are in **beta**. Start with the [Stores guide](https://openspec.dev/docs/stores).
> Stores are in **beta**. Start with the [Stores User Guide](docs/stores-beta/user-guide.md).
## Quick Start
@@ -138,33 +143,44 @@ cd your-project
openspec init
```
> **Want your AI to do it?** Paste the [setup prompt](https://openspec.dev/docs/installation#install-with-your-ai-assistant) into your coding assistant — it installs the CLI, runs `openspec init`, and verifies the result.
> **Want your AI to do it?** Paste the [setup prompt](docs-lab/start/installation.md#install-with-your-ai-assistant) into your coding assistant — it installs the CLI, runs `openspec init`, and verifies the result.
Now talk to your AI:
- **Not sure what to build yet?** Start with `/opsx:explore`, a no-stakes thinking partner that reads your code, weighs options, and shapes a plan before any code gets written. ([Quickstart](https://openspec.dev/docs/quickstart))
- **Not sure what to build yet?** Start with `/opsx:explore`, a no-stakes thinking partner that reads your code, weighs options, and shapes a plan before any code gets written. ([Explore guide](docs/explore.md))
- **Already know what you want?** Go straight to `/opsx:propose <what-you-want-to-build>`.
Both are in the default profile. If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:bulk-archive`, `/opsx:onboard`), select it with `openspec config profile` and apply with `openspec update`.
`/opsx:propose` is the canonical name; your tool may spell it `/opsx-propose` (Cursor, GitHub Copilot), `@opsx-propose` (Amazon Q) or `$openspec-propose` (Codex). `openspec init` prints the right form for the tools you picked — see [Supported Tools](https://openspec.dev/docs/supported-tools).
`/opsx:propose` is the canonical name; your tool may spell it `/opsx-propose` (Cursor, GitHub Copilot), `@opsx-propose` (Amazon Q) or `$openspec-propose` (Codex). `openspec init` prints the right form for the tools you picked — see [How To Invoke](docs/supported-tools.md#how-to-invoke).
> [!NOTE]
> Not sure if your tool is supported? [View the full list](https://openspec.dev/docs/supported-tools) – we support 30+ tools and growing.
> Not sure if your tool is supported? [View the full list](docs/supported-tools.md) – we support 30+ tools and growing.
>
> Also works with Homebrew, pnpm, yarn, bun, and Nix. [See installation options](https://openspec.dev/docs/installation).
> Also works with Homebrew, pnpm, yarn, bun, and Nix. [See installation options](docs-lab/start/installation.md).
## Docs
Full documentation lives at **[openspec.dev/docs](https://openspec.dev/docs)**.
**Start here:** the **[Documentation Home](docs/README.md)** maps everything. New to OpenSpec? Read [Getting Started](docs/getting-started.md), then [How Commands Work](docs/how-commands-work.md) (where you actually type `/opsx:propose`).
→ **[Getting Started](docs/getting-started.md)**: first steps<br>
→ **[Explore First](docs/explore.md)**: think it through with `/opsx:explore` before you commit<br>
→ **[How Commands Work](docs/how-commands-work.md)**: where slash commands run vs the CLI<br>
→ **[Core Concepts at a Glance](docs/overview.md)**: the whole mental model, one page<br>
→ **[Examples & Recipes](docs/examples.md)**: real changes, start to finish<br>
→ **[Workflows](docs/workflows.md)**: combos and patterns<br>
→ **[Existing Projects](docs/existing-projects.md)**: adopt OpenSpec on a brownfield codebase<br>
→ **[Editing a Change](docs/editing-changes.md)**: update artifacts, go back, reconcile manual edits<br>
→ **[Commands](docs/commands.md)**: slash commands & skills<br>
→ **[CLI](docs/cli.md)**: terminal reference<br>
→ **[Stores](docs/stores-beta/user-guide.md)**: plan in a separate repo, shared across your team (beta)<br>
→ **[Supported Tools](docs/supported-tools.md)**: tool integrations & install paths<br>
→ **[Concepts](docs/concepts.md)**: how it all fits<br>
→ **[Multi-Language](docs/multi-language.md)**: multi-language support<br>
→ **[Customization](docs/customization.md)**: make it yours<br>
→ **[Community Showcase](docs/community.md)**: projects and resources built with and for OpenSpec<br>
→ **[FAQ](docs/faq.md)** · **[Troubleshooting](docs/troubleshooting.md)** · **[Glossary](docs/glossary.md)**: quick help
→ **[Quickstart](https://openspec.dev/docs/quickstart)**: your first change, start to finish<br>
→ **[Set up your project](https://openspec.dev/docs/setup)**: what `openspec init` installs<br>
→ **[CLI](https://openspec.dev/docs/cli)**: terminal reference<br>
→ **[Supported Tools](https://openspec.dev/docs/supported-tools)**: tool integrations & install paths<br>
→ **[Customize](https://openspec.dev/docs/customize)**: profiles, project config, and schemas<br>
→ **[Stores](https://openspec.dev/docs/stores)**: plan in a separate repo, shared across your team (beta)<br>
→ **[Community Showcase](docs/community.md)**: projects and resources built with and for OpenSpec
## Community schemas
+3
View File
@@ -44,6 +44,9 @@ The picker asks what to configure ([delivery](#delivery-skills-commands-or-both)
Delivery is a profile setting that lets you choose to have only skills or only commands installed. The default is `both`. [Set up your project](../start/setup.md#the-workflow-files-skills-and-commands) explains the two forms and why both exist. The field's exact contract is in [CLI settings (config.json)](../reference/configuration/config-json.md#delivery).
> [!IMPORTANT]
> Command files are deprecated. The next major version of OpenSpec installs skills only by default, and `openspec update` says so on every run while your delivery setting gives a configured tool command files, including runs where everything is already up to date. To switch now, set `delivery` to `skills` and run `openspec update`: it removes the command files OpenSpec generated and lists any you edited instead of deleting them. To keep commands through the next major, set `delivery` to `both` explicitly. The plan is tracked in [#2022](https://github.com/Fission-AI/OpenSpec/issues/2022).
Two ways to change it:
**Interactively**: run `openspec config profile` and choose "Delivery only". Here's switching to skills only:
@@ -24,7 +24,7 @@ Which workflow set `openspec init` installs. Defaults to `core`: propose, explor
### delivery
Whether init installs workflows as skills, as slash commands, or both. Defaults to `both`.
Whether init installs workflows as skills, as slash commands, or both. Defaults to `both`. Commands are deprecated: the next major version changes the default to `skills`, and an explicit `both` or `commands` keeps its value ([#2022](https://github.com/Fission-AI/OpenSpec/issues/2022)).
### workflows
-3
View File
@@ -1,8 +1,5 @@
# OpenSpec Documentation
> [!WARNING]
> These pages are legacy and some are out of date. The current docs are at **[openspec.dev/docs](https://openspec.dev/docs)** (source: [`docs-lab/`](../docs-lab/)).
Welcome. This is the home for everything OpenSpec.
OpenSpec helps you and your AI coding assistant **agree on what to build before any code is written.** You describe the change, the AI drafts a short spec and a task list, you both look at the same plan, and then the work happens. No more discovering halfway through that the AI built the wrong thing.
+1 -1
View File
@@ -7,7 +7,7 @@ Listed projects are maintained independently. Inclusion does not imply official
## Projects and resources
- **[OpenSpec Workbench](https://github.com/VeryComplexAndLongName/OpenSpec-UI)**: Running and supervising agents on OpenSpec changes.
- **[MySpec](https://myspec.dev)**: Cloud beta (account required) that conducts guided spec interviews and exports four-file bundles or OpenSpec-compatible changes. Its MCP server (`@myspec/mcp-server`) and [Claude Code plugins](https://github.com/myspecs/claude-plugins) let coding agents read those specs, write OpenSpec changes, and implement them task by task. The service sends submitted content to third-party AI providers and is not designed for sensitive data.
- **[MySpec](https://myspec.dev)**: Cloud beta (account required) that conducts guided spec interviews and exports four-file bundles or OpenSpec-compatible changes. The service sends submitted content to third-party AI providers and is not designed for sensitive data.
- **[openspec-guard](https://github.com/guillaume-flambard/spec-guard)**: CLI and GitHub Action that reports which OpenSpec scenarios are covered by a Vitest or Jest test, without running the tests.
## Add your project
-1
View File
@@ -423,7 +423,6 @@ Community schemas are not vendored into OpenSpec core — they live in their own
| `nanopm` | @nmrtn | [nmrtn/nanopm](https://github.com/nmrtn/nanopm/tree/main/openspec-schema) | PM-first workflow. Runs [nanopm](https://github.com/nmrtn/nanopm)'s planning pipeline (audit → strategy → roadmap → PRD) upstream of implementation. Bridges product planning to OpenSpec's spec-driven engineering workflow. Artifacts read from `.nanopm/` if present — proposal sources the audit, design sources the strategy, and tasks source the PRD breakdown. |
| `e2e-runbooks` | @Lukk17 | [Lukk17/openspec-schemas](https://github.com/Lukk17/openspec-schemas/tree/master/openspec/schemas/e2e-runbooks) | Capability-level end-to-end test runbooks. Each capability gets an immutable spec, an immutable tasks-template, and one timestamped run record per execution. Assertions are observable behaviour only (HTTP status, response body, persisted state — never log substrings); each run records start/end UTC, duration, and best-estimate LLM token consumption. |
| `anvil` | @jikkujoyce | [jikkujoyce/openspec-schemas](https://github.com/jikkujoyce/openspec-schemas/tree/main/schemas/anvil) | Spec-driven workflow with TDD discipline and an adversarial review step. Flow: `proposal` → `specs` → `design` → `review` → `test-plan` → `tasks` → `apply` → `verify`. `review` is written by a fresh-context, read-only reviewer (a second model when one is available) and emits a `VERDICT:` line telling the agent to gate `test-plan`, `tasks`, and `apply`; OpenSpec only checks that artifacts exist, so enforce the gate with your own CI or hook. `test-plan` maps every spec scenario to a named test and doubles as a red/green ledger that `verify` audits. |
| `ml-research` | @tsenst | [idealo/openspec-schemas](https://github.com/idealo/openspec-schemas/tree/main/openspec/schemas/ml-research) | Machine-learning experiment workflow. Flow: `proposal` → `design` → `evaluation` → `tasks` → `apply` → `specs`. `evaluation` fixes metrics, success criteria, and the falsification condition before the run; `apply` writes `results.md`; `specs` records only evidence-backed findings (refuted hypotheses included) as `Finding:` requirements with linked `Evidence:` blocks. |
> Want to contribute a community schema? Open an issue with a link to your repository, or submit a PR adding a row to this table.
+1 -1
View File
@@ -53,7 +53,7 @@
inherit (finalAttrs) pname version src;
pnpm = final.pnpm_10;
fetcherVersion = 3;
hash = "sha256-hzhk/JEqix4m/k564GbcVuNtoi21E9TkJprPFnPV+Zk=";
hash = "sha256-NIMHf7t+FnBgsswDtB1k4E/zX1ks1r3708ggE2ps9E8=";
};
nativeBuildInputs = with final; [
+2 -2
View File
@@ -224,12 +224,12 @@ The spec update confirmation SHALL provide clear visibility into changes before
EXISTING specs to be updated:
- cli-init (from changes/update-init-command/specs/cli-init/spec.md)
Proceed with spec updates? (Y/n)
Update 2 specs and archive 'add-archive-command'? [y/N]:
```
#### Scenario: Handling confirmation response
- **WHEN** waiting for user confirmation
- **THEN** default to "Yes", so pressing Enter proceeds (declining requires an explicit "n" or "no")
- **THEN** default to "No" for safety (require explicit "y" or "yes")
- **AND** skip confirmation when `--yes` or `-y` flag is provided
#### Scenario: User declines confirmation
+8 -8
View File
@@ -92,14 +92,14 @@ The `openspec schemas` command SHALL display the source of each schema.
### Requirement: Use config schema as default for new changes
The system SHALL use the schema field from `openspec/config.yaml` as the default when creating new changes without an explicit `--schema` flag, and SHALL fall back to the planning-home default when the config supplies no schema.
The system SHALL use the schema field from `openspec/config.yaml` as the default when creating new changes without explicit `--schema` flag and no planning-home default applies.
#### Scenario: Create change without --schema flag and config exists
- **WHEN** user runs `openspec new change foo` and config contains `schema: "tdd"`
- **THEN** system creates change with schema "tdd", even when a planning-home default applies
- **WHEN** user runs `openspec new change foo`, no planning-home default applies, and config contains `schema: "tdd"`
- **THEN** system creates change with schema "tdd"
#### Scenario: Create change without --schema flag and no config
- **WHEN** user runs `openspec new change foo`, no config file exists, and no planning-home default applies
- **WHEN** user runs `openspec new change foo`, no planning-home default applies, and no config file exists
- **THEN** system creates change with default schema "spec-driven"
#### Scenario: Create change with explicit --schema flag
@@ -108,7 +108,7 @@ The system SHALL use the schema field from `openspec/config.yaml` as the default
### Requirement: Resolve schema with updated precedence order
The system SHALL resolve the schema for a change using the following precedence order: CLI flag, change metadata, project config, planning-home default, hardcoded default.
The system SHALL resolve the schema for a change using the following precedence order: CLI flag, change metadata, planning-home default, project config, hardcoded default.
#### Scenario: CLI flag is provided
- **WHEN** user runs command with `--schema custom`
@@ -119,11 +119,11 @@ The system SHALL resolve the schema for a change using the following precedence
- **THEN** system uses "bound" from change metadata
#### Scenario: Only project config specifies schema
- **WHEN** no CLI flag or change metadata exists, but config has `schema: tdd`
- **THEN** system uses "tdd" from project config, even when a planning-home default applies
- **WHEN** no CLI flag, change metadata, or planning-home default exists, but config has `schema: tdd`
- **THEN** system uses "tdd" from project config
#### Scenario: No schema specified anywhere
- **WHEN** no CLI flag, change metadata, project config, or planning-home default
- **WHEN** no CLI flag, change metadata, planning-home default, or project config
- **THEN** system uses hardcoded default "spec-driven"
### Requirement: Support project-local schema names in config
+1 -1
View File
@@ -62,7 +62,7 @@ The system SHALL display a one-line telemetry disclosure notice on the first com
#### Scenario: First command execution
- **WHEN** a user runs their first openspec command
- **AND** telemetry is enabled
- **THEN** the system displays: "Note: OpenSpec collects anonymous usage stats. Opt out: OPENSPEC_TELEMETRY=0 or openspec config set telemetry.enabled false"
- **THEN** the system displays: "Note: OpenSpec collects anonymous usage stats. Opt out: OPENSPEC_TELEMETRY=0"
#### Scenario: Subsequent command execution
- **WHEN** a user has already seen the notice (noticeSeen: true in config)
+252 -228
View File
@@ -11,7 +11,6 @@ overrides:
js-yaml@>=4.0.0 <4.3.1: '>=4.3.1 <5'
nanoid@<3.3.17: '>=3.3.17 <4'
fflate@<0.8.3: '>=0.8.3 <0.9'
shell-quote@>=1.8.4 <1.11.0: '>=1.11.0 <2'
importers:
@@ -19,7 +18,7 @@ importers:
dependencies:
'@inquirer/core':
specifier: ^12.0.0
version: 12.0.4(@types/node@20.19.43)
version: 12.0.3(@types/node@20.19.43)
'@inquirer/prompts':
specifier: ^8.5.2
version: 8.5.2(@types/node@20.19.43)
@@ -62,7 +61,7 @@ importers:
version: 4.1.11(vitest@4.1.11)
eslint:
specifier: ^10.5.0
version: 10.12.0
version: 10.11.0
smol-toml:
specifier: ^1.7.1
version: 1.9.0
@@ -71,7 +70,7 @@ importers:
version: 6.0.3
typescript-eslint:
specifier: ^8.65.0
version: 8.71.0(eslint@10.12.0)(typescript@6.0.3)
version: 8.70.1(eslint@10.11.0)(typescript@6.0.3)
vitest:
specifier: ^4.1.11
version: 4.1.11(@types/node@20.19.43)(@vitest/ui@4.1.11)(vite@7.3.6(@types/node@20.19.43)(yaml@2.9.1))
@@ -367,8 +366,12 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@inquirer/ansi@2.0.9':
resolution: {integrity: sha512-0Ywt3S8tqM6TWUcsRlBFQ1x+X3/o8kktX0ZiKAnByYb9GFDknnOhse1PVtfiD2BXPI8ar+BWx9PV9R0lQIcygw==}
'@inquirer/ansi@2.0.7':
resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
'@inquirer/ansi@2.0.8':
resolution: {integrity: sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
'@inquirer/checkbox@5.2.1':
@@ -398,8 +401,8 @@ packages:
'@types/node':
optional: true
'@inquirer/core@12.0.4':
resolution: {integrity: sha512-M5fB67y7jPObI45vhD82RskWwCSm0scOvenV/I6K8tIi2gfr9DXqQqJ+iw+Wi6SgapikdOiJibYzz1gW47fsDg==}
'@inquirer/core@12.0.3':
resolution: {integrity: sha512-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -434,6 +437,10 @@ packages:
'@types/node':
optional: true
'@inquirer/figures@2.0.7':
resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
'@inquirer/figures@2.0.9':
resolution: {integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
@@ -501,8 +508,17 @@ packages:
'@types/node':
optional: true
'@inquirer/type@4.2.0':
resolution: {integrity: sha512-2rnHX8RPo4glJYqnFc/v3nQeIhDcjyRRyaT7MGcfL/8xv/8K03SXYoA2tX06WVSKywWOE/ncbG3dtuzLjR5TCQ==}
'@inquirer/type@4.0.7':
resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/type@4.1.1':
resolution: {integrity: sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -560,141 +576,141 @@ packages:
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
'@rollup/rollup-android-arm-eabi@4.64.3':
resolution: {integrity: sha512-a31GFbL70GlGUGhn8goVjRLtdDG1urLCJFWLZ7JcxdHK9pPvCohLFtYLgJDdNbtrNZSrJl1u9bK05ISsSC2EpA==}
'@rollup/rollup-android-arm-eabi@4.63.5':
resolution: {integrity: sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.64.3':
resolution: {integrity: sha512-IRv757RLiPGlNxAX5JXU0eiUlUPvKiwFRbkqLd+gywptLK4uSEg1qkl6en6r0cXMazSnWUWOBABv+QfuLV3n5Q==}
'@rollup/rollup-android-arm64@4.63.5':
resolution: {integrity: sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.64.3':
resolution: {integrity: sha512-UkzI35Fnz6+MJNATdnjnZ3TpdG/rXab8h7oDOD9R0zDVIFmuuYrvgUj8V8mh+A//+LR4TlEQo4ZW4RhyRtWv3A==}
'@rollup/rollup-darwin-arm64@4.63.5':
resolution: {integrity: sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.64.3':
resolution: {integrity: sha512-C3AyMtAnKQ/jSkZ0chIoWbPXRbFz8JnPLhl1ULecEhcaOGkT19xt7KmO8BulQxt9S/XideHRXvsVQUbGj4JCOg==}
'@rollup/rollup-darwin-x64@4.63.5':
resolution: {integrity: sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.64.3':
resolution: {integrity: sha512-M8yAdBMyHh14JLeXDNGvEkljzuHdcoLBQ399O4Fp4nvX51lueuEY/5U2G9Ci0D6/bQlBp/3JLiBQH55STwwbwg==}
'@rollup/rollup-freebsd-arm64@4.63.5':
resolution: {integrity: sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.64.3':
resolution: {integrity: sha512-WQjuOKGh2FN98ZK6i0uM6M6YqnPEds85p3oRkrxiEeTQo/p7RpuU8c9MwQ88LfgwVAzohumlzbQQSikz4Zw9rw==}
'@rollup/rollup-freebsd-x64@4.63.5':
resolution: {integrity: sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.64.3':
resolution: {integrity: sha512-5tDLZG5s9WyVKoEHgwos6FpKrgpotl61EYeHSLSVkQzMzTsaClwEr8mok9rm3y734zjiGInG4dfw/56ebWVvEQ==}
'@rollup/rollup-linux-arm-gnueabihf@4.63.5':
resolution: {integrity: sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.64.3':
resolution: {integrity: sha512-Mh5KbLeplM2HX6lQI18jsxSsqvPW8d+Hyk8HtY6Zl93woMitRSCZ92rBEzq6v8bfZGlpyMgj4P/sKW4KCjhH3A==}
'@rollup/rollup-linux-arm-musleabihf@4.63.5':
resolution: {integrity: sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.64.3':
resolution: {integrity: sha512-QZrrwHnznTv6y4iiHtCqDN+ZsF4qNFQObMOLgPrbxhh9O5o4rgoUe5OnkqG/ceCObMrAjhQt+eQ6HSDzBFgVMw==}
'@rollup/rollup-linux-arm64-gnu@4.63.5':
resolution: {integrity: sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.64.3':
resolution: {integrity: sha512-4vLehpXSwlkltnvk5ETB+Hz+d3xayZS3NamZ5Vq4bSo+IUoIALovEJF1YgWPMjHwN7R5o/R4BpP9h3q8EK1xAA==}
'@rollup/rollup-linux-arm64-musl@4.63.5':
resolution: {integrity: sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.64.3':
resolution: {integrity: sha512-SEN7TTTOdXJKZmsWKOnQeVfgy3Dje/08ISXWwFhTggQ3SxjuIaChvmOB5ZDZqJNlBjaXrV6nAHraqEAvdLGqcw==}
'@rollup/rollup-linux-loong64-gnu@4.63.5':
resolution: {integrity: sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.64.3':
resolution: {integrity: sha512-1LDsQMRMCEdGF9Dih5qyh6fF1LK3NnA+pAtE2+9AgZwUdXyd19TOBVC9crSB63iZBHR4vOG7h8gN/LcUH3J7xw==}
'@rollup/rollup-linux-loong64-musl@4.63.5':
resolution: {integrity: sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.64.3':
resolution: {integrity: sha512-v3D2tAfRriqGOi+5Ac5CiYbBo2NXtLVxJlludTvUZRj502lVMFeVIuvvWbz4qCrotn3e3Uvpvh7FcMpG2QxxXQ==}
'@rollup/rollup-linux-ppc64-gnu@4.63.5':
resolution: {integrity: sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.64.3':
resolution: {integrity: sha512-db5q6dP189V2+JkMpsXBl8ym6bC0x8ydZ47fUJZ9YdM6T2OF5uqNzX9HaAZHRP/lqjA9bJeNwi0qw1/2/37dUA==}
'@rollup/rollup-linux-ppc64-musl@4.63.5':
resolution: {integrity: sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.64.3':
resolution: {integrity: sha512-nArAVDmrL5ix9UoipcKEsYJw4kMUQCIPYRWuYF2POOC7jd9rmN95d/48ewsYFC/OdtwEx6VYYWYxrp8FbMOU2Q==}
'@rollup/rollup-linux-riscv64-gnu@4.63.5':
resolution: {integrity: sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.64.3':
resolution: {integrity: sha512-2dc7LElu7EVSvpQT8epNnBLa02wlqilNYqHN0FA/RaKqCr9U9ZFYQ0Eh4sfgSzLneLDuJsJRjcezR/l2UyYGXA==}
'@rollup/rollup-linux-riscv64-musl@4.63.5':
resolution: {integrity: sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.64.3':
resolution: {integrity: sha512-9PYq2IEzSwXcvQLERcT9LKXRzw8J05GFTbJHA2anJR2WmlRjA1esgXVYhanp+bmjKsDVoZeU2L7Z2pRClLcZTw==}
'@rollup/rollup-linux-s390x-gnu@4.63.5':
resolution: {integrity: sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.64.3':
resolution: {integrity: sha512-vMh+mt0cw1BHldd8cqaU6A5s1W4EUKl6+8MRsv86mj954L4xyruKD7qPXMUI2icImadAZARK10h0onaGTLmCVg==}
'@rollup/rollup-linux-x64-gnu@4.63.5':
resolution: {integrity: sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.64.3':
resolution: {integrity: sha512-PpiKU+sWbqDGc0U0su8oXiyeyqm0UQjmaaPKHktX+DaLe6YKJwW2YVD0f2AXHdVvblZbRqpwRVClWP2F5HCqrQ==}
'@rollup/rollup-linux-x64-musl@4.63.5':
resolution: {integrity: sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.64.3':
resolution: {integrity: sha512-/3enY/AmaIt8IS0fWjX4ztYVPBqkj49cQTy/hCryhBFaEzDfJwwEolq9sxvyWAM2qmsmVuPdeRg5JXSrmeqvJg==}
'@rollup/rollup-openbsd-x64@4.63.5':
resolution: {integrity: sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig==}
cpu: [x64]
os: [openbsd]
'@rollup/rollup-openharmony-arm64@4.64.3':
resolution: {integrity: sha512-zsEP/QDuet52hhwvi5T1VPg0/Tkms0m3zkVv7CPsznvhdO4pL4oJTMz26EJsiCe18OeLKPgpTu3XVO3C1eq3ew==}
'@rollup/rollup-openharmony-arm64@4.63.5':
resolution: {integrity: sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g==}
cpu: [arm64]
os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.64.3':
resolution: {integrity: sha512-4ESknW4Db+kqvazVOfsReYJaHxcIsOqcgwjKV2J1tG5Bn4RVf5ocTQKSGuq2HWBJcOLuyim8IWX8DuH6nKmELA==}
'@rollup/rollup-win32-arm64-msvc@4.63.5':
resolution: {integrity: sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.64.3':
resolution: {integrity: sha512-3B8VCQyIyv7FQxYYhb3HpR3bIpronR20txjMSffSoz/sm/DDs3/49CFpEueOsvq9qW9sWoI/6pi6mtciD9NXFg==}
'@rollup/rollup-win32-ia32-msvc@4.63.5':
resolution: {integrity: sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-gnu@4.64.3':
resolution: {integrity: sha512-YOoDkKh2oQxOxRB5D9AR8sj3qpMShpmyTkjojiBjMGigz3caV7UvCnXWWUQC0TBmrZ5zuHe2pWl8S+cMfvXzRg==}
'@rollup/rollup-win32-x64-gnu@4.63.5':
resolution: {integrity: sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw==}
cpu: [x64]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.64.3':
resolution: {integrity: sha512-qTv4bG+eKJKifzxNJevMUh8L6CjJrz9/Kn1IbP2F5bo0tBzNIR7HMg4vni6XmDzcKUl00lHHY+J5ZgjbGiujOQ==}
'@rollup/rollup-win32-x64-msvc@4.63.5':
resolution: {integrity: sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw==}
cpu: [x64]
os: [win32]
@@ -719,63 +735,63 @@ packages:
'@types/node@20.19.43':
resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==}
'@typescript-eslint/eslint-plugin@8.71.0':
resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==}
'@typescript-eslint/eslint-plugin@8.70.1':
resolution: {integrity: sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.71.0
'@typescript-eslint/parser': ^8.70.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/parser@8.71.0':
resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==}
'@typescript-eslint/parser@8.70.1':
resolution: {integrity: sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/project-service@8.71.0':
resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==}
'@typescript-eslint/project-service@8.70.1':
resolution: {integrity: sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/scope-manager@8.71.0':
resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==}
'@typescript-eslint/scope-manager@8.70.1':
resolution: {integrity: sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.71.0':
resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==}
'@typescript-eslint/tsconfig-utils@8.70.1':
resolution: {integrity: sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/type-utils@8.71.0':
resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==}
'@typescript-eslint/type-utils@8.70.1':
resolution: {integrity: sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/types@8.71.0':
resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==}
'@typescript-eslint/types@8.70.1':
resolution: {integrity: sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.71.0':
resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==}
'@typescript-eslint/typescript-estree@8.70.1':
resolution: {integrity: sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/utils@8.71.0':
resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==}
'@typescript-eslint/utils@8.70.1':
resolution: {integrity: sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/visitor-keys@8.71.0':
resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==}
'@typescript-eslint/visitor-keys@8.70.1':
resolution: {integrity: sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@vitest/expect@4.1.11':
@@ -925,8 +941,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint@10.12.0:
resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==}
eslint@10.11.0:
resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -1149,8 +1165,8 @@ packages:
resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==}
engines: {node: ^20.17.0 || >=22.9.0}
nanoid@3.3.20:
resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==}
nanoid@3.3.19:
resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -1210,8 +1226,8 @@ packages:
resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
engines: {node: '>=12'}
postcss@8.5.29:
resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==}
postcss@8.5.28:
resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
@@ -1237,8 +1253,8 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rollup@4.64.3:
resolution: {integrity: sha512-iIMWbptLYsZdGTJMH8ngApXdsCatE6bCMv2eqGIfGE1++kwh7TCWBdWKK0FKC0yx++196vGK5VdagzRXSLMa8A==}
rollup@4.63.5:
resolution: {integrity: sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -1261,8 +1277,8 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
shell-quote@1.12.0:
resolution: {integrity: sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==}
shell-quote@1.10.0:
resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==}
engines: {node: '>= 0.4'}
siginfo@2.0.0:
@@ -1283,8 +1299,8 @@ packages:
resolution: {integrity: sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==}
engines: {node: '>= 18'}
source-map-js@1.2.2:
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
stackback@0.0.2:
@@ -1342,8 +1358,8 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
typescript-eslint@8.71.0:
resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==}
typescript-eslint@8.70.1:
resolution: {integrity: sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -1688,9 +1704,9 @@ snapshots:
'@esbuild/win32-x64@0.28.2':
optional: true
'@eslint-community/eslint-utils@4.10.1(eslint@10.12.0)':
'@eslint-community/eslint-utils@4.10.1(eslint@10.11.0)':
dependencies:
eslint: 10.12.0
eslint: 10.11.0
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -1734,29 +1750,31 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@inquirer/ansi@2.0.9': {}
'@inquirer/ansi@2.0.7': {}
'@inquirer/ansi@2.0.8': {}
'@inquirer/checkbox@5.2.1(@types/node@20.19.43)':
dependencies:
'@inquirer/ansi': 2.0.9
'@inquirer/ansi': 2.0.7
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/figures': 2.0.9
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/figures': 2.0.7
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/confirm@6.1.1(@types/node@20.19.43)':
dependencies:
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/core@11.2.1(@types/node@20.19.43)':
dependencies:
'@inquirer/ansi': 2.0.9
'@inquirer/figures': 2.0.9
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/ansi': 2.0.7
'@inquirer/figures': 2.0.7
'@inquirer/type': 4.0.7(@types/node@20.19.43)
cli-width: 4.1.0
fast-wrap-ansi: 0.2.2
mute-stream: 3.0.0
@@ -1764,11 +1782,11 @@ snapshots:
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/core@12.0.4(@types/node@20.19.43)':
'@inquirer/core@12.0.3(@types/node@20.19.43)':
dependencies:
'@inquirer/ansi': 2.0.9
'@inquirer/ansi': 2.0.8
'@inquirer/figures': 2.0.9
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/type': 4.1.1(@types/node@20.19.43)
cli-width: 4.1.0
fast-wrap-ansi: 0.2.2
mute-stream: 3.0.0
@@ -1780,14 +1798,14 @@ snapshots:
dependencies:
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/external-editor': 3.0.3(@types/node@20.19.43)
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/expand@5.1.1(@types/node@20.19.43)':
dependencies:
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
@@ -1798,27 +1816,29 @@ snapshots:
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/figures@2.0.7': {}
'@inquirer/figures@2.0.9': {}
'@inquirer/input@5.1.2(@types/node@20.19.43)':
dependencies:
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/number@4.1.1(@types/node@20.19.43)':
dependencies:
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/password@5.1.1(@types/node@20.19.43)':
dependencies:
'@inquirer/ansi': 2.0.9
'@inquirer/ansi': 2.0.7
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
@@ -1840,28 +1860,32 @@ snapshots:
'@inquirer/rawlist@5.3.1(@types/node@20.19.43)':
dependencies:
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/search@4.2.1(@types/node@20.19.43)':
dependencies:
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/figures': 2.0.9
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/figures': 2.0.7
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/select@5.2.1(@types/node@20.19.43)':
dependencies:
'@inquirer/ansi': 2.0.9
'@inquirer/ansi': 2.0.7
'@inquirer/core': 11.2.1(@types/node@20.19.43)
'@inquirer/figures': 2.0.9
'@inquirer/type': 4.2.0(@types/node@20.19.43)
'@inquirer/figures': 2.0.7
'@inquirer/type': 4.0.7(@types/node@20.19.43)
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/type@4.2.0(@types/node@20.19.43)':
'@inquirer/type@4.0.7(@types/node@20.19.43)':
optionalDependencies:
'@types/node': 20.19.43
'@inquirer/type@4.1.1(@types/node@20.19.43)':
optionalDependencies:
'@types/node': 20.19.43
@@ -1909,79 +1933,79 @@ snapshots:
'@polka/url@1.0.0-next.29': {}
'@rollup/rollup-android-arm-eabi@4.64.3':
'@rollup/rollup-android-arm-eabi@4.63.5':
optional: true
'@rollup/rollup-android-arm64@4.64.3':
'@rollup/rollup-android-arm64@4.63.5':
optional: true
'@rollup/rollup-darwin-arm64@4.64.3':
'@rollup/rollup-darwin-arm64@4.63.5':
optional: true
'@rollup/rollup-darwin-x64@4.64.3':
'@rollup/rollup-darwin-x64@4.63.5':
optional: true
'@rollup/rollup-freebsd-arm64@4.64.3':
'@rollup/rollup-freebsd-arm64@4.63.5':
optional: true
'@rollup/rollup-freebsd-x64@4.64.3':
'@rollup/rollup-freebsd-x64@4.63.5':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.64.3':
'@rollup/rollup-linux-arm-gnueabihf@4.63.5':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.64.3':
'@rollup/rollup-linux-arm-musleabihf@4.63.5':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.64.3':
'@rollup/rollup-linux-arm64-gnu@4.63.5':
optional: true
'@rollup/rollup-linux-arm64-musl@4.64.3':
'@rollup/rollup-linux-arm64-musl@4.63.5':
optional: true
'@rollup/rollup-linux-loong64-gnu@4.64.3':
'@rollup/rollup-linux-loong64-gnu@4.63.5':
optional: true
'@rollup/rollup-linux-loong64-musl@4.64.3':
'@rollup/rollup-linux-loong64-musl@4.63.5':
optional: true
'@rollup/rollup-linux-ppc64-gnu@4.64.3':
'@rollup/rollup-linux-ppc64-gnu@4.63.5':
optional: true
'@rollup/rollup-linux-ppc64-musl@4.64.3':
'@rollup/rollup-linux-ppc64-musl@4.63.5':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.64.3':
'@rollup/rollup-linux-riscv64-gnu@4.63.5':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.64.3':
'@rollup/rollup-linux-riscv64-musl@4.63.5':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.64.3':
'@rollup/rollup-linux-s390x-gnu@4.63.5':
optional: true
'@rollup/rollup-linux-x64-gnu@4.64.3':
'@rollup/rollup-linux-x64-gnu@4.63.5':
optional: true
'@rollup/rollup-linux-x64-musl@4.64.3':
'@rollup/rollup-linux-x64-musl@4.63.5':
optional: true
'@rollup/rollup-openbsd-x64@4.64.3':
'@rollup/rollup-openbsd-x64@4.63.5':
optional: true
'@rollup/rollup-openharmony-arm64@4.64.3':
'@rollup/rollup-openharmony-arm64@4.63.5':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.64.3':
'@rollup/rollup-win32-arm64-msvc@4.63.5':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.64.3':
'@rollup/rollup-win32-ia32-msvc@4.63.5':
optional: true
'@rollup/rollup-win32-x64-gnu@4.64.3':
'@rollup/rollup-win32-x64-gnu@4.63.5':
optional: true
'@rollup/rollup-win32-x64-msvc@4.64.3':
'@rollup/rollup-win32-x64-msvc@4.63.5':
optional: true
'@standard-schema/spec@1.1.0': {}
@@ -2002,15 +2026,15 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0)(typescript@6.0.3))(eslint@10.12.0)(typescript@6.0.3)':
'@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@10.11.0)(typescript@6.0.3))(eslint@10.11.0)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.71.0(eslint@10.12.0)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/type-utils': 8.71.0(eslint@10.12.0)(typescript@6.0.3)
'@typescript-eslint/utils': 8.71.0(eslint@10.12.0)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.71.0
eslint: 10.12.0
'@typescript-eslint/parser': 8.70.1(eslint@10.11.0)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.70.1
'@typescript-eslint/type-utils': 8.70.1(eslint@10.11.0)(typescript@6.0.3)
'@typescript-eslint/utils': 8.70.1(eslint@10.11.0)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.70.1
eslint: 10.11.0
ignore: 7.0.10
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -2018,56 +2042,56 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.71.0(eslint@10.12.0)(typescript@6.0.3)':
'@typescript-eslint/parser@8.70.1(eslint@10.11.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.71.0
'@typescript-eslint/scope-manager': 8.70.1
'@typescript-eslint/types': 8.70.1
'@typescript-eslint/typescript-estree': 8.70.1(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.70.1
debug: 4.4.3
eslint: 10.12.0
eslint: 10.11.0
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.71.0(typescript@6.0.3)':
'@typescript-eslint/project-service@8.70.1(typescript@6.0.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3)
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/tsconfig-utils': 8.70.1(typescript@6.0.3)
'@typescript-eslint/types': 8.70.1
debug: 4.4.3
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.71.0':
'@typescript-eslint/scope-manager@8.70.1':
dependencies:
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/visitor-keys': 8.71.0
'@typescript-eslint/types': 8.70.1
'@typescript-eslint/visitor-keys': 8.70.1
'@typescript-eslint/tsconfig-utils@8.71.0(typescript@6.0.3)':
'@typescript-eslint/tsconfig-utils@8.70.1(typescript@6.0.3)':
dependencies:
typescript: 6.0.3
'@typescript-eslint/type-utils@8.71.0(eslint@10.12.0)(typescript@6.0.3)':
'@typescript-eslint/type-utils@8.70.1(eslint@10.11.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3)
'@typescript-eslint/utils': 8.71.0(eslint@10.12.0)(typescript@6.0.3)
'@typescript-eslint/types': 8.70.1
'@typescript-eslint/typescript-estree': 8.70.1(typescript@6.0.3)
'@typescript-eslint/utils': 8.70.1(eslint@10.11.0)(typescript@6.0.3)
debug: 4.4.3
eslint: 10.12.0
eslint: 10.11.0
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.71.0': {}
'@typescript-eslint/types@8.70.1': {}
'@typescript-eslint/typescript-estree@8.71.0(typescript@6.0.3)':
'@typescript-eslint/typescript-estree@8.70.1(typescript@6.0.3)':
dependencies:
'@typescript-eslint/project-service': 8.71.0(typescript@6.0.3)
'@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3)
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/visitor-keys': 8.71.0
'@typescript-eslint/project-service': 8.70.1(typescript@6.0.3)
'@typescript-eslint/tsconfig-utils': 8.70.1(typescript@6.0.3)
'@typescript-eslint/types': 8.70.1
'@typescript-eslint/visitor-keys': 8.70.1
debug: 4.4.3
minimatch: 10.2.6
semver: 7.8.5
@@ -2077,20 +2101,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.71.0(eslint@10.12.0)(typescript@6.0.3)':
'@typescript-eslint/utils@8.70.1(eslint@10.11.0)(typescript@6.0.3)':
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0)
'@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3)
eslint: 10.12.0
'@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0)
'@typescript-eslint/scope-manager': 8.70.1
'@typescript-eslint/types': 8.70.1
'@typescript-eslint/typescript-estree': 8.70.1(typescript@6.0.3)
eslint: 10.11.0
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.71.0':
'@typescript-eslint/visitor-keys@8.70.1':
dependencies:
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/types': 8.70.1
eslint-visitor-keys: 5.0.1
'@vitest/expect@4.1.11':
@@ -2258,9 +2282,9 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
eslint@10.12.0:
eslint@10.11.0:
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0)
'@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0)
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5
'@eslint/config-helpers': 0.7.0
@@ -2440,7 +2464,7 @@ snapshots:
launch-editor@2.14.1:
dependencies:
picocolors: 1.1.1
shell-quote: 1.12.0
shell-quote: 1.10.0
levn@0.4.1:
dependencies:
@@ -2479,7 +2503,7 @@ snapshots:
mute-stream@3.0.0: {}
nanoid@3.3.20: {}
nanoid@3.3.19: {}
natural-compare@1.4.0: {}
@@ -2533,11 +2557,11 @@ snapshots:
picomatch@4.0.7: {}
postcss@8.5.29:
postcss@8.5.28:
dependencies:
nanoid: 3.3.20
nanoid: 3.3.19
picocolors: 1.1.1
source-map-js: 1.2.2
source-map-js: 1.2.1
prelude-ls@1.2.1: {}
@@ -2556,36 +2580,36 @@ snapshots:
reusify@1.1.0: {}
rollup@4.64.3:
rollup@4.63.5:
dependencies:
'@types/estree': 1.0.9
optionalDependencies:
'@napi-rs/lzma-linux-x64-gnu': 1.5.1
'@rollup/rollup-android-arm-eabi': 4.64.3
'@rollup/rollup-android-arm64': 4.64.3
'@rollup/rollup-darwin-arm64': 4.64.3
'@rollup/rollup-darwin-x64': 4.64.3
'@rollup/rollup-freebsd-arm64': 4.64.3
'@rollup/rollup-freebsd-x64': 4.64.3
'@rollup/rollup-linux-arm-gnueabihf': 4.64.3
'@rollup/rollup-linux-arm-musleabihf': 4.64.3
'@rollup/rollup-linux-arm64-gnu': 4.64.3
'@rollup/rollup-linux-arm64-musl': 4.64.3
'@rollup/rollup-linux-loong64-gnu': 4.64.3
'@rollup/rollup-linux-loong64-musl': 4.64.3
'@rollup/rollup-linux-ppc64-gnu': 4.64.3
'@rollup/rollup-linux-ppc64-musl': 4.64.3
'@rollup/rollup-linux-riscv64-gnu': 4.64.3
'@rollup/rollup-linux-riscv64-musl': 4.64.3
'@rollup/rollup-linux-s390x-gnu': 4.64.3
'@rollup/rollup-linux-x64-gnu': 4.64.3
'@rollup/rollup-linux-x64-musl': 4.64.3
'@rollup/rollup-openbsd-x64': 4.64.3
'@rollup/rollup-openharmony-arm64': 4.64.3
'@rollup/rollup-win32-arm64-msvc': 4.64.3
'@rollup/rollup-win32-ia32-msvc': 4.64.3
'@rollup/rollup-win32-x64-gnu': 4.64.3
'@rollup/rollup-win32-x64-msvc': 4.64.3
'@rollup/rollup-android-arm-eabi': 4.63.5
'@rollup/rollup-android-arm64': 4.63.5
'@rollup/rollup-darwin-arm64': 4.63.5
'@rollup/rollup-darwin-x64': 4.63.5
'@rollup/rollup-freebsd-arm64': 4.63.5
'@rollup/rollup-freebsd-x64': 4.63.5
'@rollup/rollup-linux-arm-gnueabihf': 4.63.5
'@rollup/rollup-linux-arm-musleabihf': 4.63.5
'@rollup/rollup-linux-arm64-gnu': 4.63.5
'@rollup/rollup-linux-arm64-musl': 4.63.5
'@rollup/rollup-linux-loong64-gnu': 4.63.5
'@rollup/rollup-linux-loong64-musl': 4.63.5
'@rollup/rollup-linux-ppc64-gnu': 4.63.5
'@rollup/rollup-linux-ppc64-musl': 4.63.5
'@rollup/rollup-linux-riscv64-gnu': 4.63.5
'@rollup/rollup-linux-riscv64-musl': 4.63.5
'@rollup/rollup-linux-s390x-gnu': 4.63.5
'@rollup/rollup-linux-x64-gnu': 4.63.5
'@rollup/rollup-linux-x64-musl': 4.63.5
'@rollup/rollup-openbsd-x64': 4.63.5
'@rollup/rollup-openharmony-arm64': 4.63.5
'@rollup/rollup-win32-arm64-msvc': 4.63.5
'@rollup/rollup-win32-ia32-msvc': 4.63.5
'@rollup/rollup-win32-x64-gnu': 4.63.5
'@rollup/rollup-win32-x64-msvc': 4.63.5
fsevents: 2.3.3
run-parallel@1.2.0:
@@ -2602,7 +2626,7 @@ snapshots:
shebang-regex@3.0.0: {}
shell-quote@1.12.0: {}
shell-quote@1.10.0: {}
siginfo@2.0.0: {}
@@ -2618,7 +2642,7 @@ snapshots:
smol-toml@1.9.0: {}
source-map-js@1.2.2: {}
source-map-js@1.2.1: {}
stackback@0.0.2: {}
@@ -2662,13 +2686,13 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
typescript-eslint@8.71.0(eslint@10.12.0)(typescript@6.0.3):
typescript-eslint@8.70.1(eslint@10.11.0)(typescript@6.0.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0)(typescript@6.0.3))(eslint@10.12.0)(typescript@6.0.3)
'@typescript-eslint/parser': 8.71.0(eslint@10.12.0)(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3)
'@typescript-eslint/utils': 8.71.0(eslint@10.12.0)(typescript@6.0.3)
eslint: 10.12.0
'@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@10.11.0)(typescript@6.0.3))(eslint@10.11.0)(typescript@6.0.3)
'@typescript-eslint/parser': 8.70.1(eslint@10.11.0)(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.70.1(typescript@6.0.3)
'@typescript-eslint/utils': 8.70.1(eslint@10.11.0)(typescript@6.0.3)
eslint: 10.11.0
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -2686,8 +2710,8 @@ snapshots:
esbuild: 0.28.2
fdir: 6.5.0(picomatch@4.0.7)
picomatch: 4.0.7
postcss: 8.5.29
rollup: 4.64.3
postcss: 8.5.28
rollup: 4.63.5
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 20.19.43
-5
View File
@@ -26,11 +26,6 @@ overrides:
# Dev-only (transitive via @vitest/ui); never in the published CLI. Remove once
# transitive fflate is >=0.8.3 (check: pnpm why fflate).
fflate@<0.8.3: '>=0.8.3 <0.9'
# GHSA-pqg4-j6r4-53mv — shell-quote `quote()` command injection via a line
# terminator after a `{ comment }` token. Dev-only (transitive via
# @changesets/cli > launch-editor); never in the published CLI. Remove once
# transitive shell-quote is >=1.11.0 (check: pnpm why shell-quote).
shell-quote@>=1.8.4 <1.11.0: '>=1.11.0 <2'
auditConfig:
ignoreGhsas:
+2 -4
View File
@@ -178,8 +178,6 @@ If the user mentions a change or you detect one is relevant:
`<capability-path>` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve an existing capability's full path and follow the project's established organization for new capabilities.
The files below are the default spec-driven schema's. With another schema, capture each insight in whichever artifact from `artifactPaths` fits it, and name that artifact in your offer; never create an artifact the schema does not define.
| Insight Type | Where to Capture |
|----------------------------|-------------------------------------|
| New requirement discovered | `specs/<capability-path>/spec.md` |
@@ -189,7 +187,7 @@ If the user mentions a change or you detect one is relevant:
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers (default schema):
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
@@ -318,7 +316,7 @@ You: That changes everything.
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? Run `/openspec-propose` and this becomes a change."
- **Result in artifact updates**: "Updated the design with these decisions"
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
-21
View File
@@ -123,14 +123,6 @@ export function isCompletionRun(commandPath: string): boolean {
return commandPath.split(':')[0] === 'completion' || commandPath === '__complete';
}
/**
* True for the hidden `__complete` resolver alone. It runs on every Tab press,
* so the hooks skip telemetry and the completion tip for it entirely.
*/
export function isTabCompletionRun(commandPath: string): boolean {
return commandPath === '__complete';
}
/**
* True when the first-run completions tip must be deferred rather than shown.
*
@@ -204,13 +196,6 @@ program.hook('preAction', async (thisCommand, actionCommand) => {
process.env.NO_COLOR = '1';
}
// `__complete` runs on every Tab press, with stderr sent to /dev/null by the
// generated scripts. Tracking it would hold each press open for the request,
// and the first-run notice would be marked seen without anyone reading it.
if (isTabCompletionRun(getCommandPath(actionCommand))) {
return;
}
// Show first-run telemetry notice (if not seen). It's written to stderr, so it
// never pollutes stdout — but --json runs still defer it (see isJsonRun) so the
// very first invocation stays free of any incidental output on either stream.
@@ -225,12 +210,6 @@ program.hook('preAction', async (thisCommand, actionCommand) => {
// Shutdown telemetry after command completes
program.hook('postAction', async (_thisCommand, actionCommand) => {
// Nothing to flush (preAction tracked nothing), and the tip is always
// deferred for completion runs, so skip loading either module.
if (isTabCompletionRun(getCommandPath(actionCommand))) {
return;
}
// Show the first-run shell-completions tip (on stderr, so piped stdout stays
// clean). postAction, not preAction: the tip trails the command's own output
// instead of pushing an error message or `init`'s setup summary down the
+1 -4
View File
@@ -1,3 +1,4 @@
import ora from 'ora';
import { CompletionFactory } from '../core/completions/factory.js';
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
import { detectShell, SupportedShell } from '../utils/shell-detection.js';
@@ -124,9 +125,6 @@ export class CompletionCommand {
const generator = CompletionFactory.createGenerator(shell);
const installer = CompletionFactory.createInstaller(shell);
// Loaded here, not at the top: `__complete` shares this module and runs on
// every Tab press, so it must not pay for the spinner.
const { default: ora } = await import('ora');
const spinner = ora(`Installing ${shell} completion script...`).start();
try {
@@ -236,7 +234,6 @@ export class CompletionCommand {
}
}
const { default: ora } = await import('ora');
const spinner = ora(`Uninstalling ${shell} completion script...`).start();
try {
+7 -19
View File
@@ -1358,22 +1358,13 @@ export class ArchiveCommand {
const archiveDir = root.archiveDir;
const mainSpecsDir = root.specsDir;
const openspecDir = path.dirname(changesDir);
// openspec/, changes/ and specs/ may be project-owned symlinks to another
// directory; archive/ may not leave the changes directory it lives in.
for (const [allowedDirectory, managedDir, allowLinkedLeaf] of [
[root.path, openspecDir, true],
[openspecDir, changesDir, true],
[changesDir, archiveDir, false],
[openspecDir, mainSpecsDir, true],
for (const [allowedDirectory, managedDir] of [
[root.path, changesDir],
[changesDir, archiveDir],
[root.path, mainSpecsDir],
] as const) {
try {
if (allowLinkedLeaf) {
FileSystemUtils.assertPathWithinAllowingLinkedLeaf(allowedDirectory, managedDir);
} else {
FileSystemUtils.assertPathWithin(allowedDirectory, managedDir);
}
FileSystemUtils.assertPathWithin(allowedDirectory, managedDir);
} catch {
throw new ArchiveBlockedError(
'archive_path_outside_root',
@@ -2199,9 +2190,6 @@ export class ArchiveCommand {
retirementAuthorizationFingerprint!
);
}
// update.source is canonical (symlinks resolved), so measure it from
// the canonical change directory; changes/ may itself be a symlink.
const canonicalChangeDir = FileSystemUtils.canonicalizeExistingPath(changeDir);
const verifyArchivedDeltas = async (
stagedSource?: string
): Promise<void> => {
@@ -2225,7 +2213,7 @@ export class ArchiveCommand {
for (const proposed of prepared) {
const archivedSource = path.join(
archivePath,
path.relative(canonicalChangeDir, proposed.update.source)
path.relative(changeDir, proposed.update.source)
);
if (
(await fingerprintPortableContent(archivedSource)) !==
@@ -2238,7 +2226,7 @@ export class ArchiveCommand {
if (stagedSource) {
const stagedDelta = path.join(
stagedSource,
path.relative(canonicalChangeDir, proposed.update.source)
path.relative(changeDir, proposed.update.source)
);
if (
(await fingerprintPortableContent(stagedDelta)) !==
+35
View File
@@ -41,3 +41,38 @@ export function shouldGenerateCommandsForTool(toolId: string, delivery: Delivery
export function shouldReconcileCommandFilesForTool(toolId: string, delivery: Delivery): boolean {
return delivery === 'skills' && resolveCommandSurfaceCapability(toolId) === 'adapter-backed';
}
export const COMMAND_DEPRECATION_URL = 'https://github.com/Fission-AI/OpenSpec/issues/2022';
/**
* The notice `openspec update` prints while delivery gives a tool command files,
* which are deprecated in favor of skills. It is shown only for a tool that
* switching to skills-only delivery would change: one that gets command files
* now and would not under `skills`. A skills-only project is never told about a
* change that does not affect it, and a tool that keeps its commands under
* every delivery is never advised to switch away from its only working surface.
*
* Worded "by default" so it holds both for users on the implicit default, whom
* the next major moves to skills, and for users who set `delivery` explicitly,
* whose setting the next major keeps.
*/
export function formatCommandDeprecationNotice(
toolIds: readonly string[],
delivery: Delivery
): string[] {
const switchingChangesSomething = toolIds.some(
(toolId) =>
shouldGenerateCommandsForTool(toolId, delivery) &&
!shouldGenerateCommandsForTool(toolId, 'skills')
);
if (!switchingChangesSomething) {
return [];
}
return [
'Command files are deprecated: the next major version of OpenSpec installs skills only by default.',
' Switch to skills now: openspec config set delivery skills && openspec update',
' Keep commands for now: openspec config set delivery both',
` Details: ${COMMAND_DEPRECATION_URL}`,
];
}
+2 -4
View File
@@ -1,4 +1,5 @@
import { getActiveChangeIds, getSpecIds } from '../../utils/item-discovery.js';
import { listSchemas } from '../artifact-graph/index.js';
/**
* Cache entry for completion data
@@ -95,10 +96,7 @@ export class CompletionProvider {
return this.schemaCache.data;
}
// Fetch fresh data. The resolver (and the yaml and zod it parses schemas
// with) loads only here: completing changes or specs never needs it, and
// the artifact-graph barrel would also pull in fast-glob.
const { listSchemas } = await import('../artifact-graph/resolver.js');
// Fetch fresh data
const schemaNames = listSchemas(this.projectRoot);
// Update cache
-63
View File
@@ -169,16 +169,6 @@ export interface OrphanedRequirement {
line: number; // 1-based line number in the delta file
}
/**
* Authored text archive does not carry into the main spec: a non-delta `## `
* section, text above the first `## `, or prose before the first requirement
* in ADDED/MODIFIED. `section` is null for text above the first `## `.
*/
export interface UnappliedContent {
section: string | null;
line: number; // 1-based: the section header, or the first line of leading prose
}
export interface DeltaPlan {
added: RequirementBlock[];
modified: RequirementBlock[];
@@ -192,7 +182,6 @@ export interface DeltaPlan {
unpairedRenames: UnpairedRename[];
/** Canonical requirement blocks written outside every delta section. */
orphanedRequirements: OrphanedRequirement[];
unappliedContent: UnappliedContent[];
skippedHeaders: SkippedHeader[]; // non-canonical ### headers the reader skipped
sectionPresence: {
added: boolean;
@@ -267,7 +256,6 @@ export function parseDeltaSpec(content: string): DeltaPlan {
renamed: renamedPairs,
unpairedRenames,
orphanedRequirements: findOrphanedRequirements(lines, fenceMask),
unappliedContent: findUnappliedContent(lines, fenceMask),
skippedHeaders,
sectionPresence: {
added: addedLookup.found,
@@ -327,57 +315,6 @@ function findOrphanedRequirements(
return orphans;
}
export function describeUnappliedContent(content: UnappliedContent, sentenceStart = false): string {
const text = content.section === null
? 'text above the first "## " section'
: DELTA_SECTION_TITLES.has(content.section.toLowerCase())
? `text before the first requirement in "## ${content.section}"`
: `section "## ${content.section}"`;
return sentenceStart ? text.charAt(0).toUpperCase() + text.slice(1) : text;
}
/**
* Where text the merge never reads begins. HTML comments, `# ` titles, Purpose
* (warned about separately), REMOVED/RENAMED bodies, and stray `###` headers
* before the first requirement (INFO since #498) are not reported.
*/
function findUnappliedContent(lines: string[], fenceMask: boolean[]): UnappliedContent[] {
// Blank out comments outside fences, keeping line count; an unterminated one
// runs to EOF. Comments only decide whether a line holds text: sections come
// from the same split the merge reads, where a `## ` line inside a comment is
// still a boundary. Fenced lines keep their text, so a `<!--` in a code sample
// never hides what follows the fence.
const comments = lines
.map((line, i) => (fenceMask[i] ? '' : line))
.join('\n')
.replace(/<!--[\s\S]*?(?:--!?>|$)/g, (m) => m.replace(/[^\n]/g, ' '))
.split('\n');
const masked = lines.map((line, i) => (fenceMask[i] ? line : comments[i]));
const sections = splitTopLevelSections(lines, fenceMask);
const firstText = (from: number, to: number, skip?: RegExp) => {
for (let i = from; i < to; i++) if (masked[i].trim() && !skip?.test(masked[i])) return i;
return -1;
};
const found: UnappliedContent[] = [];
const preamble = firstText(0, sections.length ? sections[0].body.bodyStartLine - 2 : lines.length, /^#\s/);
if (preamble !== -1) found.push({ section: null, line: preamble + 1 });
for (const { title, body } of sections) {
const folded = title.toLowerCase();
const start = body.bodyStartLine - 1;
if (folded === 'added requirements' || folded === 'modified requirements') {
const block = body.lines.findIndex((l, i) => !body.fenceMask[i] && REQUIREMENT_HEADER_REGEX.test(l));
const first = firstText(start, start + (block === -1 ? body.lines.length : block), /^###\s/);
if (first !== -1) found.push({ section: title, line: first + 1 });
} else if (!DELTA_SECTION_TITLES.has(folded)) {
// The Purpose carry-over reads past commented-out headers, so only a
// `## Purpose` outside a comment is the one archive handles.
if (folded === 'purpose' && masked[start - 1].trim()) continue;
if (firstText(start, start + body.lines.length) !== -1) found.push({ section: title, line: start });
}
}
return found;
}
/** One `## ` section of a delta file, in the order it was written. */
interface DeltaSection {
title: string;
-6
View File
@@ -13,7 +13,6 @@ import {
extractRequirementsSection,
diffScenarioNames,
describeScenarioBalance,
describeUnappliedContent,
foldRequirementName,
parseDeltaSpec,
normalizeRequirementName,
@@ -252,11 +251,6 @@ export async function buildUpdatedSpec(
);
}
// Archive carries requirement blocks only; name any other text it leaves behind.
for (const lost of plan.unappliedContent) {
warn(`${specName} - ${describeUnappliedContent(lost)} (line ${lost.line}) is not carried into the main spec.`);
}
// Pre-validate duplicates within sections
const addedNames = new Set<string>();
for (const add of plan.added) {
+4 -8
View File
@@ -217,8 +217,6 @@ If the user mentions a change or you detect one is relevant:
\`<capability-path>\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve an existing capability's full path and follow the project's established organization for new capabilities.
The files below are the default spec-driven schema's. With another schema, capture each insight in whichever artifact from \`artifactPaths\` fits it, and name that artifact in your offer; never create an artifact the schema does not define.
| Insight Type | Where to Capture |
|----------------------------|-------------------------------------|
| New requirement discovered | \`specs/<capability-path>/spec.md\` |
@@ -228,7 +226,7 @@ If the user mentions a change or you detect one is relevant:
| New work identified | \`tasks.md\` |
| Assumption invalidated | Relevant artifact |
Example offers (default schema):
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
@@ -357,7 +355,7 @@ You: That changes everything.
There's no required ending. Discovery might:
- **Flow into a proposal**: "${DISCOVERY_END_HANDOFF}"
- **Result in artifact updates**: "Updated the design with these decisions"
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
@@ -553,8 +551,6 @@ If the user mentions a change or you detect one is relevant:
\`<capability-path>\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve an existing capability's full path and follow the project's established organization for new capabilities.
The files below are the default spec-driven schema's. With another schema, capture each insight in whichever artifact from \`artifactPaths\` fits it, and name that artifact in your offer; never create an artifact the schema does not define.
| Insight Type | Where to Capture |
|----------------------------|-------------------------------------|
| New requirement discovered | \`specs/<capability-path>/spec.md\` |
@@ -564,7 +560,7 @@ If the user mentions a change or you detect one is relevant:
| New work identified | \`tasks.md\` |
| Assumption invalidated | Relevant artifact |
Example offers (default schema):
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
@@ -589,7 +585,7 @@ If the user mentions a change or you detect one is relevant:
There's no required ending. Discovery might:
- **Flow into a proposal**: "${DISCOVERY_END_HANDOFF}"
- **Result in artifact updates**: "Updated the design with these decisions"
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
+20
View File
@@ -69,6 +69,7 @@ import {
resolveCommandSurfaceCapability,
resolveCommandInvocation,
shouldGenerateCommandsForTool,
formatCommandDeprecationNotice,
shouldGenerateSkillsForTool,
shouldReconcileCommandFilesForTool,
shouldRemoveSkillsForTool,
@@ -280,6 +281,7 @@ export class UpdateCommand {
this.detectNewTools(resolvedProjectPath, configuredTools);
this.displayProfileNotes(resolvedProjectPath, configuredTools, desiredWorkflows, profile, delivery);
this.displaySetupNotes(configuredTools);
this.displayCommandDeprecation(configuredTools, delivery);
throwIfUpdateFailed(legacyUpgradeFailures);
return;
}
@@ -525,6 +527,7 @@ export class UpdateCommand {
// 14. Display the profile notes
this.displayProfileNotes(resolvedProjectPath, configuredAndNewTools, desiredWorkflows, profile, delivery);
this.displaySetupNotes(configuredAndNewTools);
this.displayCommandDeprecation(configuredAndNewTools, delivery);
// 15. List affected tools
if (updatedTools.length > 0) {
@@ -696,6 +699,23 @@ export class UpdateCommand {
}
}
/**
* Announces that generated command files are deprecated, on every run while
* delivery gives a configured tool command files, whether or not this run
* rewrote them. `update` is run on upgrade, so this reaches users
* before the next major changes the default, without nagging day to day.
*/
private displayCommandDeprecation(toolIds: string[], delivery: Delivery): void {
const [headline, ...detail] = formatCommandDeprecationNotice(toolIds, delivery);
if (!headline) return;
console.log();
console.log(chalk.yellow(headline));
for (const line of detail) {
console.log(chalk.dim(line));
}
}
/**
* Detects new tool directories that aren't currently configured and displays a hint.
*/
-12
View File
@@ -18,7 +18,6 @@ import {
extractRequirementsSection,
diffScenarioNames,
describeScenarioBalance,
describeUnappliedContent,
type RequirementBlock,
} from '../parsers/requirement-blocks.js';
import {
@@ -258,17 +257,6 @@ export class Validator {
});
}
// Archive carries requirement blocks only, by design. Warn so text
// written elsewhere is moved rather than lost without notice.
for (const lost of plan.unappliedContent) {
issues.push({
level: 'WARNING',
path: entryPath,
line: lost.line,
message: `${describeUnappliedContent(lost, true)} is not carried into the main spec by archive. Move it into a requirement, or into proposal.md or design.md.`,
});
}
const sectionNames: string[] = [];
if (plan.sectionPresence.added) sectionNames.push('## ADDED Requirements');
if (plan.sectionPresence.modified) sectionNames.push('## MODIFIED Requirements');
-27
View File
@@ -125,33 +125,6 @@ export class FileSystemUtils {
}
}
/**
* Like assertPathWithin, but a targetPath that is itself a symlink may
* resolve outside allowedDirectory, so a project can keep a managed
* directory (for example openspec/changes) in another repository. Its parent
* must still be confined and the link must resolve to an existing path.
*/
static assertPathWithinAllowingLinkedLeaf(allowedDirectory: string, targetPath: string): void {
const resolvedTarget = path.resolve(targetPath);
let isLink = false;
try {
isLink = nodeFs.lstatSync(resolvedTarget).isSymbolicLink();
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error;
}
}
if (!isLink) {
this.assertPathWithin(allowedDirectory, resolvedTarget);
return;
}
this.assertPathWithin(allowedDirectory, path.dirname(resolvedTarget));
// Throws for a dangling link, which cannot be proven to be a directory.
nodeFs.realpathSync.native(resolvedTarget);
}
static resolveProjectArtifactPath(projectPath: string, artifactPath: string): string {
if (path.isAbsolute(artifactPath)) {
throw new Error(`Refusing to manage an artifact outside the project: ${artifactPath}`);
+1 -96
View File
@@ -1,6 +1,6 @@
import { afterAll, describe, expect, it } from 'vitest';
import { spawnSync } from 'node:child_process';
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
import { mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -118,101 +118,6 @@ describe('CLI startup loads only what the command needs', () => {
}
);
// Shell completion scripts run `__complete` on every Tab press. It needs no
// package beyond commander, and never telemetry: tracking would hold each
// press open for the request.
it.each(['changes', 'specs'])(
'`openspec __complete %s` loads only commander and no telemetry',
async (type) => {
await ensureCliBuilt();
const { own, packages, status } = loadedModules(['__complete', type]);
expect(status).toBe(0);
expect([...packages]).toEqual(['commander']);
expect(implementationsLoaded(own)).toEqual(['completion']);
expect(own).not.toContain('telemetry/index.js');
}
);
it('`openspec __complete schemas` loads the schema reader but not fast-glob or ora', async () => {
await ensureCliBuilt();
const { own, packages, status } = loadedModules(['__complete', 'schemas']);
expect(status).toBe(0);
expect([...packages]).not.toContain('fast-glob');
expect([...packages]).not.toContain('ora');
expect(own).not.toContain('telemetry/index.js');
// The name promises a schema reader, so pin it: the resolver directly, not
// the artifact-graph barrel, which re-exports it but also pulls outputs.js
// and with it fast-glob.
expect(own).toContain('core/artifact-graph/resolver.js');
expect(own).not.toContain('core/artifact-graph/index.js');
});
// Negative control for the two cases above. `isTabCompletionRun` matches the
// command path exactly, unlike the adjacent `isCompletionRun`, which
// prefix-matches `completion:*`. Widening one into the other would silently
// stop tracking every user-facing command in that group, and every assertion
// above would still pass.
it.each([['list --json'], ['completion generate zsh']])(
'`openspec %s` still reaches the telemetry hooks',
async (invocation) => {
await ensureCliBuilt();
const { own } = loadedModules(invocation.split(' '));
expect(own).toContain('telemetry/index.js');
expect(own).toContain('core/completion-tip.js');
}
);
// The module-count assertions above pin that telemetry is not LOADED. They do
// not pin the user-visible consequence, which is the reason the skip exists:
// the first-run disclosure goes to stderr, every generated completion script
// sends `__complete` stderr to /dev/null, and `maybeShowTelemetryNotice` marks
// `noticeSeen` before `trackCommand` re-reads it. So before this change a
// user's first-ever interaction being a Tab press burned the disclosure where
// nobody could read it and began collecting on that same press.
//
// Telemetry is deliberately left ENABLED here: the point is that an enabled
// run still records nothing. HOME must be isolated as well as XDG_CONFIG_HOME,
// or `migrateLegacyTelemetryConfig` imports the developer's real config from
// ~/.config/openspec/config.json and the test passes vacuously.
it('`openspec __complete` leaves the first-run telemetry notice unconsumed', async () => {
await ensureCliBuilt();
const home = mkdtempSync(path.join(os.tmpdir(), 'openspec-notice-'));
workDirs.push(home);
const configHome = path.join(home, 'config');
// These have to be DELETED, not blanked. Both opt-outs read as "set to
// anything that is not an explicit on-value", so `OPENSPEC_TELEMETRY: ''`
// and `DO_NOT_TRACK: ''` would switch telemetry OFF and the assertions
// below would hold on any build, passing vacuously.
const env = {
...process.env,
HOME: home,
USERPROFILE: home,
XDG_CONFIG_HOME: configHome,
XDG_DATA_HOME: path.join(home, 'data'),
};
delete env.OPENSPEC_TELEMETRY;
delete env.DO_NOT_TRACK;
delete env.CI;
const result = spawnSync(process.execPath, [binPath, '__complete', 'changes'], {
cwd: home,
env,
encoding: 'utf-8',
timeout: 30_000,
windowsHide: true,
});
expect(result.status).toBe(0);
// No disclosure written to a stream the completion scripts discard.
expect(result.stderr).toBe('');
// And no config file, so neither noticeSeen nor an anonymousId was minted.
expect(existsSync(path.join(configHome, 'openspec', 'config.json'))).toBe(false);
});
it.each([
['config list --json', 'config'],
['config path', 'config'],
-174
View File
@@ -619,180 +619,6 @@ describe('ArchiveCommand', () => {
await expect(fs.readdir(outsideDir)).resolves.toEqual([]);
});
it('archives when openspec/changes is a project-owned symlink to another directory', async () => {
const changeName = 'linked-change';
const docsRepo = realpathSync.native(
await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-docs-repo-'))
);
onTestFinished(() => fs.rm(docsRepo, { recursive: true, force: true }));
const sharedChanges = path.join(docsRepo, 'changes');
const changesLink = path.join(tempDir, 'openspec', 'changes');
await fs.rm(changesLink, { recursive: true, force: true });
await fs.mkdir(path.join(sharedChanges, changeName), { recursive: true });
await fs.writeFile(path.join(sharedChanges, changeName, 'tasks.md'), '- [x] Task 1\n');
await fs.symlink(sharedChanges, changesLink, process.platform === 'win32' ? 'junction' : 'dir');
await archiveCommand.execute(changeName, {
yes: true,
noValidate: true,
skipSpecs: true,
});
await expect(fs.access(path.join(sharedChanges, changeName))).rejects.toThrow();
const archived = await fs.readdir(path.join(sharedChanges, 'archive'));
expect(archived.some((name) => name.endsWith(`-${changeName}`))).toBe(true);
});
it.each([
{ linkChanges: true, linkSpecs: false },
{ linkChanges: false, linkSpecs: true },
{ linkChanges: true, linkSpecs: true },
])(
'applies a spec delta and archives with symlinked changes=$linkChanges specs=$linkSpecs',
async ({ linkChanges, linkSpecs }) => {
const changeName = 'linked-delta';
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
const docsRepo = realpathSync.native(
await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-docs-repo-'))
);
onTestFinished(() => fs.rm(docsRepo, { recursive: true, force: true }));
const openspecDir = path.join(tempDir, 'openspec');
const changesDir = linkChanges ? path.join(docsRepo, 'changes') : path.join(openspecDir, 'changes');
const specsDir = linkSpecs ? path.join(docsRepo, 'specs') : path.join(openspecDir, 'specs');
if (linkChanges) {
await fs.rm(path.join(openspecDir, 'changes'), { recursive: true, force: true });
await fs.mkdir(changesDir, { recursive: true });
await fs.symlink(changesDir, path.join(openspecDir, 'changes'), linkType);
}
if (linkSpecs) {
await fs.rm(path.join(openspecDir, 'specs'), { recursive: true, force: true });
await fs.mkdir(specsDir, { recursive: true });
await fs.symlink(specsDir, path.join(openspecDir, 'specs'), linkType);
}
const deltaDir = path.join(changesDir, changeName, 'specs', 'linked-capability');
await fs.mkdir(deltaDir, { recursive: true });
await fs.writeFile(
path.join(deltaDir, 'spec.md'),
`# Linked Capability - Changes
## ADDED Requirements
### Requirement: The system SHALL archive through linked directories
#### Scenario: Linked directories
- **WHEN** the change is archived
- **THEN** the delta is applied and the change is archived
`
);
await archiveCommand.execute(changeName, { yes: true, noValidate: true });
const mainSpec = await fs.readFile(
path.join(specsDir, 'linked-capability', 'spec.md'),
'utf-8'
);
expect(mainSpec).toContain('### Requirement: The system SHALL archive through linked directories');
await expect(fs.access(path.join(changesDir, changeName))).rejects.toThrow();
const archived = await fs.readdir(path.join(changesDir, 'archive'));
const archivedName = archived.find((name) => name.endsWith(`-${changeName}`));
expect(archivedName).toBeDefined();
await expect(
fs.access(path.join(changesDir, 'archive', archivedName!, 'specs', 'linked-capability', 'spec.md'))
).resolves.not.toThrow();
}
);
it('archives when the whole openspec directory is a project-owned symlink', async () => {
const changeName = 'linked-openspec';
const docsRepo = realpathSync.native(
await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-docs-repo-'))
);
onTestFinished(() => fs.rm(docsRepo, { recursive: true, force: true }));
const sharedOpenspec = path.join(docsRepo, 'openspec');
await fs.rm(path.join(tempDir, 'openspec'), { recursive: true, force: true });
await fs.mkdir(path.join(sharedOpenspec, 'changes', changeName), { recursive: true });
await fs.mkdir(path.join(sharedOpenspec, 'specs'), { recursive: true });
await fs.writeFile(
path.join(sharedOpenspec, 'changes', changeName, 'tasks.md'),
'- [x] Task 1\n'
);
await fs.symlink(
sharedOpenspec,
path.join(tempDir, 'openspec'),
process.platform === 'win32' ? 'junction' : 'dir'
);
await archiveCommand.execute(changeName, { yes: true, noValidate: true, skipSpecs: true });
const archived = await fs.readdir(path.join(sharedOpenspec, 'changes', 'archive'));
expect(archived.some((name) => name.endsWith(`-${changeName}`))).toBe(true);
});
it('rejects an archive directory that escapes a symlinked changes directory', async () => {
const changeName = 'linked-escape';
const docsRepo = realpathSync.native(
await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-docs-repo-'))
);
onTestFinished(() => fs.rm(docsRepo, { recursive: true, force: true }));
const sharedChanges = path.join(docsRepo, 'changes');
const outsideDir = path.join(docsRepo, 'outside');
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
await fs.rm(path.join(tempDir, 'openspec', 'changes'), { recursive: true, force: true });
await fs.mkdir(path.join(sharedChanges, changeName), { recursive: true });
await fs.mkdir(outsideDir);
await fs.writeFile(path.join(sharedChanges, changeName, 'tasks.md'), '- [x] Task 1\n');
await fs.symlink(sharedChanges, path.join(tempDir, 'openspec', 'changes'), linkType);
await fs.symlink(outsideDir, path.join(sharedChanges, 'archive'), linkType);
await expect(
archiveCommand.execute(changeName, { yes: true, noValidate: true, skipSpecs: true })
).rejects.toThrow(/outside the OpenSpec root/u);
await expect(fs.access(path.join(sharedChanges, changeName))).resolves.not.toThrow();
await expect(fs.readdir(outsideDir)).resolves.toEqual([]);
});
it('verifies deltas through a symlinked changes directory on the cross-device fallback', async () => {
const changeName = 'linked-exdev';
const docsRepo = realpathSync.native(
await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-docs-repo-'))
);
onTestFinished(() => fs.rm(docsRepo, { recursive: true, force: true }));
const sharedChanges = path.join(docsRepo, 'changes');
const deltaDir = path.join(sharedChanges, changeName, 'specs', 'linked-capability');
await fs.rm(path.join(tempDir, 'openspec', 'changes'), { recursive: true, force: true });
await fs.mkdir(deltaDir, { recursive: true });
await fs.writeFile(
path.join(deltaDir, 'spec.md'),
`## ADDED Requirements
### Requirement: The system SHALL survive EXDEV
The system SHALL archive.
#### Scenario: Cross device
- **WHEN** rename fails with EXDEV
- **THEN** the copy fallback archives the change
`
);
await fs.symlink(
sharedChanges,
path.join(tempDir, 'openspec', 'changes'),
process.platform === 'win32' ? 'junction' : 'dir'
);
const rename = vi.spyOn(fs, 'rename').mockRejectedValueOnce(
Object.assign(new Error('cross-device move'), { code: 'EXDEV' })
);
try {
await archiveCommand.execute(changeName, { yes: true, noValidate: true });
} finally {
rename.mockRestore();
}
await expect(fs.access(path.join(sharedChanges, changeName))).rejects.toThrow();
const archived = await fs.readdir(path.join(sharedChanges, 'archive'));
expect(archived.some((name) => name.endsWith(`-${changeName}`))).toBe(true);
});
it('archives normally when the project root is reached through a symlink alias', async () => {
if (process.platform === 'win32') return;
+1 -23
View File
@@ -1,12 +1,7 @@
import { describe, it, expect } from 'vitest';
import { Command, Option } from 'commander';
import {
isJsonRun,
isCompletionRun,
isTabCompletionRun,
shouldDeferCompletionTip,
} from '../../src/cli/index.js';
import { isJsonRun, isCompletionRun, shouldDeferCompletionTip } from '../../src/cli/index.js';
/**
* Reproduce the three ways `--json` reaches a command in the real CLI, so a
@@ -111,23 +106,6 @@ describe('isCompletionRun', () => {
);
});
describe('isTabCompletionRun', () => {
/**
* Only the hidden resolver skips telemetry. The user-facing `completion`
* commands are run by a person and are tracked like any other command.
*/
it('matches the hidden resolver', () => {
expect(isTabCompletionRun('__complete')).toBe(true);
});
it.each(['completion', 'completion:install', 'completion:generate', 'list'])(
'does not match "%s"',
(commandPath) => {
expect(isTabCompletionRun(commandPath)).toBe(false);
}
);
});
describe('shouldDeferCompletionTip', () => {
/**
* The tip must survive every run that cannot display it. Deferring (rather
@@ -0,0 +1,34 @@
import { describe, it, expect } from 'vitest';
import {
COMMAND_DEPRECATION_URL,
formatCommandDeprecationNotice,
} from '../../src/core/command-surface.js';
describe('formatCommandDeprecationNotice', () => {
it.each(['both', 'commands'] as const)(
'announces the change when delivery %s still generates command files',
(delivery) => {
expect(formatCommandDeprecationNotice(['claude'], delivery)).toEqual([
'Command files are deprecated: the next major version of OpenSpec installs skills only by default.',
' Switch to skills now: openspec config set delivery skills && openspec update',
' Keep commands for now: openspec config set delivery both',
` Details: ${COMMAND_DEPRECATION_URL}`,
]);
}
);
it('says nothing to a project already on skills-only delivery', () => {
expect(formatCommandDeprecationNotice(['claude', 'cursor'], 'skills')).toEqual([]);
});
it('says nothing when no configured tool gets command files', () => {
// Codex is skills-invocable and has no command adapter, so a Codex-only
// project is unaffected by the deprecation even on the default delivery.
expect(formatCommandDeprecationNotice(['codex'], 'both')).toEqual([]);
expect(formatCommandDeprecationNotice([], 'both')).toEqual([]);
});
it('announces it once for a mixed project where one tool gets commands', () => {
expect(formatCommandDeprecationNotice(['codex', 'claude'], 'both')).toHaveLength(4);
});
});
@@ -421,35 +421,5 @@ describe('ZshGenerator', () => {
expect(script).toContain('_openspec_view() {');
expect(script).toContain('_arguments');
});
// The bash, fish and powershell generator tests each pin this redirect;
// zsh was the one that did not, and it has the most `__complete` call
// sites and is the default shell on macOS. The redirect is what keeps a
// Tab press silent, so anything `__complete` ever writes to stderr - a
// deprecation notice, a Node experimental warning - would otherwise print
// into the middle of the user's prompt with no test objecting.
it('sends every __complete call stderr to /dev/null', () => {
const commands: CommandDefinition[] = [
{ name: 'archive', description: 'Archive a change', flags: [] },
{ name: 'show', description: 'Show an item', flags: [] },
{ name: 'new', description: 'Create a change', flags: [] },
];
const script = generator.generate(commands);
expect(script).toContain('openspec __complete changes 2>/dev/null');
expect(script).toContain('openspec __complete specs 2>/dev/null');
expect(script).toContain('openspec __complete schemas 2>/dev/null');
// No invocation may survive a template edit that drops a redirect. Only
// real call sites count: the script also explains itself in comments
// that name the command without running it.
const invocations = script
.split('\n')
.filter((line) => line.includes('openspec __complete') && !line.trim().startsWith('#'));
expect(invocations.length).toBeGreaterThan(0);
for (const line of invocations) {
expect(line, `unredirected __complete call: ${line.trim()}`).toContain('2>/dev/null');
}
});
});
});
-149
View File
@@ -1,149 +0,0 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { parseDeltaSpec } from '../../../src/core/parsers/requirement-blocks.js';
import { buildUpdatedSpec, findSpecUpdates } from '../../../src/core/specs-apply.js';
import { Validator } from '../../../src/core/validation/validator.js';
// Archive carries requirement blocks only. Text written anywhere else in a delta
// used to vanish with no warning while `validate --strict` passed.
const DELTA = [
'# billing delta', // 1 - a title is not reported
'Context above the first section.', // 2
'## ADDED Requirements', // 3
'Intro prose.', // 4
'### Requirement: Invoices', // 5
'The system SHALL generate invoices.', // 6
'', // 7
'#### Scenario: Period closes', // 8
'- **WHEN** a', // 9
'- **THEN** b', // 10
'Trailing note, carried with the block.', // 11
'## Acceptance Criteria', // 12
'- Invoices reconcile.', // 13
].join('\n');
describe('parseDeltaSpec unappliedContent', () => {
it('reports text above the first section, leading prose, and non-delta sections', () => {
expect(parseDeltaSpec(DELTA).unappliedContent).toEqual([
{ section: null, line: 2 },
{ section: 'ADDED Requirements', line: 4 },
{ section: 'Acceptance Criteria', line: 12 },
]);
});
it('does not exempt a commented-out Purpose, and honours the --!> terminator', () => {
const plan = parseDeltaSpec(
['<!--', '## Purpose', '--!>', 'Real prose.', '## ADDED Requirements', '### Requirement: A', 'a', '## Notes', 'n'].join(
'\n'
)
);
expect(plan.unappliedContent).toEqual([
{ section: 'Purpose', line: 2 },
{ section: 'Notes', line: 8 },
]);
});
// The merge splits sections on every `## ` line outside a fence, comment or not.
// Reading boundaries from comment-masked lines disagreed with it both ways.
it('reads section boundaries exactly as the merge does', () => {
// `<!--` in inline code and a later `-->` are not a comment to the merge;
// masking across them hid `## ADDED Requirements` and blamed a carried requirement.
const inlineOpener = parseDeltaSpec(
['# Delta: strip `<!--` openers', '## ADDED Requirements', '### Requirement: A', 'Flow: ingest --> render', 'The system SHALL a.'].join('\n')
);
expect(inlineOpener.added.map((b) => b.name)).toEqual(['A']);
expect(inlineOpener.unappliedContent).toEqual([]);
// A header inside a comment is still a section to the merge, so the
// requirement after the comment is carried, not text above the first section.
const commentedHeader = parseDeltaSpec(
['<!-- add requirements below', '## ADDED Requirements', '-->', '### Requirement: A', 'The system SHALL a.'].join('\n')
);
expect(commentedHeader.added.map((b) => b.name)).toEqual(['A']);
expect(commentedHeader.unappliedContent).toEqual([]);
// ...and text after a commented-out `## Notes` is dropped by the merge.
const commentedNotes = parseDeltaSpec(
['## ADDED Requirements', '### Requirement: A', 'a', '<!--', '## Notes', '-->', 'Dropped.'].join('\n')
);
expect(commentedNotes.added[0].raw).not.toContain('Dropped.');
expect(commentedNotes.unappliedContent).toEqual([{ section: 'Notes', line: 5 }]);
});
it('does not let a `<!--` inside a code fence hide later sections', () => {
const plan = parseDeltaSpec(
['## ADDED Requirements', '### Requirement: A', 'a', '```html', '<!-- unterminated sample', '```', '## Notes', 'Dropped.'].join('\n')
);
expect(plan.unappliedContent).toEqual([{ section: 'Notes', line: 7 }]);
});
it('ignores Purpose, REMOVED/RENAMED bodies, HTML comments, fences, and stray ### headers', () => {
const plan = parseDeltaSpec(
[
'<!--',
'Multi-line instructions.',
'-->',
'## Purpose',
'A new capability.',
'## ADDED Requirements',
'<!-- add requirements below -->',
'### Documentation Requirements',
'### Requirement: A',
'a',
'```markdown',
'## Notes',
'```',
'## REMOVED Requirements',
'### Requirement: B',
'**Reason**: obsolete',
'## RENAMED Requirements',
'- FROM: `### Requirement: C`',
'- TO: `### Requirement: D`',
].join('\n')
);
expect(plan.unappliedContent).toEqual([]);
});
});
describe('archive and validate report unapplied content', () => {
let tempDir: string;
let changeDir: string;
let specsRoot: string;
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-unapplied-'));
specsRoot = path.join(tempDir, 'openspec', 'specs');
changeDir = path.join(tempDir, 'openspec', 'changes', 'c');
await fs.mkdir(path.join(changeDir, 'specs', 'billing'), { recursive: true });
await fs.mkdir(specsRoot, { recursive: true });
await fs.writeFile(path.join(changeDir, 'specs', 'billing', 'spec.md'), DELTA);
});
afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true });
});
it('archive warns and still carries only the requirement', async () => {
const [update] = await findSpecUpdates(changeDir, specsRoot);
const built = await buildUpdatedSpec(update, 'c', { silent: true });
expect(built.warnings).toContain(
'billing - section "## Acceptance Criteria" (line 12) is not carried into the main spec.'
);
expect(built.rebuilt).toContain('### Requirement: Invoices');
expect(built.rebuilt).not.toContain('Acceptance Criteria');
});
it('validate warns, which fails --strict', async () => {
const report = await new Validator(true).validateChangeDeltaSpecs(changeDir);
const lost = report.issues.filter((issue) => /not carried into the main spec/.test(issue.message));
expect(lost.map((issue) => [issue.level, issue.line])).toEqual([
['WARNING', 2],
['WARNING', 4],
['WARNING', 12],
]);
expect(report.valid).toBe(false);
});
});
@@ -76,14 +76,14 @@ function specDrivenTitles(): Record<string, string> {
}
const EXPECTED_FUNCTION_HASHES: Record<string, string> = {
getExploreSkillTemplate: 'efeb143c50472bf0db0f031f7fc9e32f3c2662b569101f6473b2cbbb99425367',
getExploreSkillTemplate: 'c1fddb294758004936add586f5826694cb06175cff935b75fd3a8d92332332e6',
getNewChangeSkillTemplate: '0e5035b7b42198afc430206a1dbc9579096650ef0813d85e837d5a6cd0b98a85',
getContinueChangeSkillTemplate: '32ec5965a2e90c033c9285f57a73cd6f39a31b58029d7546dba3cb6ba4c091f7',
getApplyChangeSkillTemplate: 'c4f02c29137e19b34bf1dc59146941b8c920214aa2cf3ce80ae7f1b982e976fc',
getFfChangeSkillTemplate: 'd091600476a815ba99f69b446bcd46af5bf73d1c2810215a0c6196937d019cf6',
getSyncSpecsSkillTemplate: 'bc80fe9b07eaa289e5eb8a3ce65eb7df722a16d864e37283c678220712e4f230',
getOnboardSkillTemplate: '84258a06c0ca88de708a23dd74e9a17efe11eff63a071b3864c781dcd5a0a4b7',
getOpsxExploreCommandTemplate: '477c1a97cf272778953d1ad032113e3f20afaefa332a5c0882e536d919c176e2',
getOpsxExploreCommandTemplate: '5d11f8ecb4c457140a3e874a8bf7aa72674e922e698c208832b1f34d3c617719',
getOpsxNewCommandTemplate: '6d504fef1e0d4ced7c423f4cc9d9d2cee11b1a6224edf685e06a3f0757e0ebff',
getOpsxContinueCommandTemplate: '659596cef4859162707d8acbd411afb863110f34a7b90ad037bae5eb84dd858d',
getOpsxApplyCommandTemplate: '9b733946aa7216b45d084bb5affd7071086db6f91282c3666731a17543825ff1',
@@ -104,7 +104,7 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = {
};
const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = {
'openspec-explore': '92055ecb0f60011b76369defb441883a985fb798f4cb90397f956f73383deda2',
'openspec-explore': '7d80caf9cd25a2565ba190b1297f1631c7f2c2db5e614597b4284abc0118ea70',
'openspec-new-change': '27e09d43785953827efc9a98bb9d6cf06db48fe6abe7e1c049409fe5b5061323',
'openspec-continue-change': '4edf6d8b220f09146029e7671f140b7ef94a666d8e29fe7ad78ce40bc7ec9017',
'openspec-apply-change': '991a1d4687f1c8c7147d8c3b68cf14e9c515c6c53bb026356dac3b80e909b827',
@@ -1253,20 +1253,6 @@ describe('skill templates split parity', () => {
expect(text, variant).not.toContain('Loose prose left under `## Requirements` does NOT block');
}
});
// The explore capture table names spec-driven files; without this caveat an
// agent on a custom schema offers to write artifacts it never defines (#2084).
it('scopes the explore capture table to the default schema', () => {
const variants = [
['explore skill', getExploreSkillTemplate().instructions],
['explore command', getOpsxExploreCommandTemplate().content],
] as const;
for (const [variant, text] of variants) {
expect(text, variant).toContain(
"The files below are the default spec-driven schema's. With another schema, capture each insight in whichever artifact from `artifactPaths` fits it"
);
}
});
});
describe('apply skill/command shared instruction core', () => {
+46
View File
@@ -1905,6 +1905,52 @@ metadata:
});
});
describe('command deprecation notice', () => {
const headline = 'Command files are deprecated';
async function runUpdate(command: UpdateCommand = new UpdateCommand()): Promise<string[]> {
const consoleSpy = vi.spyOn(console, 'log');
await command.execute(testDir);
const logs = consoleSpy.mock.calls.flat().map(String);
consoleSpy.mockRestore();
return logs;
}
it('announces the deprecation when update regenerates command files', async () => {
await new InitCommand({ tools: 'claude', force: true }).execute(testDir);
const logs = await runUpdate(new UpdateCommand({ force: true }));
expect(logs.some((entry) => entry.includes(headline))).toBe(true);
expect(
logs.some((entry) => entry.includes('openspec config set delivery skills && openspec update'))
).toBe(true);
expect(logs.some((entry) => entry.includes('issues/2022'))).toBe(true);
});
it('still announces it when every tool is already up to date', async () => {
// The up-to-date path returns early, and is what most users hit on a
// routine run. Missing it there would hide the notice from them.
await new InitCommand({ tools: 'claude', force: true }).execute(testDir);
const logs = await runUpdate();
expect(logs.some((entry) => entry.includes('up to date'))).toBe(true);
expect(logs.some((entry) => entry.includes(headline))).toBe(true);
});
it('stays quiet once delivery is skills-only', async () => {
setMockConfig({ featureFlags: {}, profile: 'core', delivery: 'skills' });
await new InitCommand({ tools: 'claude', force: true }).execute(testDir);
const forced = await runUpdate(new UpdateCommand({ force: true }));
const routine = await runUpdate();
expect(forced.some((entry) => entry.includes(headline))).toBe(false);
expect(routine.some((entry) => entry.includes(headline))).toBe(false);
});
});
describe('error handling', () => {
it('should report a failed legacy-only Codex bootstrap to automation', async () => {
setMockConfig({ featureFlags: {}, profile: 'core', delivery: 'commands' });
@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
COMMAND_DEPRECATION_URL,
formatCommandDeprecationNotice,
} from '../src/core/command-surface.js';
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const read = (...segments: string[]) =>
fs.readFileSync(path.join(REPO_ROOT, 'docs-lab', ...segments), 'utf-8');
const PROFILES = read('customize', 'profiles.md');
const DELIVERY_SECTION = PROFILES.split('## Delivery: skills, commands, or both')[1].split(
'## Switching profiles'
)[0];
const CONFIG_JSON = read('reference', 'configuration', 'config-json.md');
const DELIVERY_FIELD = CONFIG_JSON.split('### delivery')[1].split('### workflows')[0];
// The CLI notice and the docs announce the same deprecation. Pin them to each
// other so neither can change its advice without the other (#2022).
describe('command deprecation documentation', () => {
const notice = formatCommandDeprecationNotice(['claude'], 'both').join('\n');
it('says what the CLI notice says in the delivery guide', () => {
expect(notice).toContain('the next major version of OpenSpec installs skills only by default');
expect(DELIVERY_SECTION).toContain(
'The next major version of OpenSpec installs skills only by default'
);
});
it('gives the same two choices as the notice', () => {
expect(notice).toContain('openspec config set delivery skills && openspec update');
expect(DELIVERY_SECTION).toContain('set `delivery` to `skills` and run `openspec update`');
expect(notice).toContain('openspec config set delivery both');
expect(DELIVERY_SECTION).toContain('set `delivery` to `both` explicitly');
});
it('links the same tracking issue from the notice and both pages', () => {
const issue = COMMAND_DEPRECATION_URL;
expect(notice).toContain(issue);
expect(DELIVERY_SECTION).toContain(issue);
expect(DELIVERY_FIELD).toContain(issue);
});
});
+2 -2
View File
@@ -22,8 +22,8 @@ import { fileURLToPath } from 'node:url';
// unless you ask" is the wording this very test recommends.
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
// The pages that pitch explore to a user. Both trees ship: `docs/` is the
// legacy tree still readable on GitHub, `docs-lab/` is what the documentation site publishes
// The pages that pitch explore to a user. Both trees ship: `docs/` is what
// README links today, `docs-lab/` is what the documentation site publishes
// (`website/content/docs` is generated from it and is not committed).
const PAGES = [
'README.md',
+11 -11
View File
@@ -13,22 +13,22 @@
},
"dependencies": {
"beautiful-mermaid": "^1.1.3",
"fumadocs-core": "^16.16.1",
"fumadocs-mdx": "^15.4.6",
"fumadocs-ui": "^16.16.1",
"lucide-react": "^1.52.0",
"fumadocs-core": "^16.15.17",
"fumadocs-mdx": "^15.3.1",
"fumadocs-ui": "^16.15.17",
"lucide-react": "^1.34.0",
"next": "16.3.8",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"zod": "^4.6.5"
"react": "^19.2.7",
"react-dom": "^19.2.7",
"zod": "^4.4.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.3.1",
"@types/mdx": "^2.0.14",
"@types/node": "^26.6.4",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"postcss": "^8.5.29",
"@types/node": "^26.3.0",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.7",
"postcss": "^8.5.28",
"serve": "^14.2.6",
"tailwindcss": "^4.3.1",
"typescript": "^6.0.3"
+531 -518
View File
File diff suppressed because it is too large Load Diff