From 4f0a6a74b75cc02525179e6687c2e4904bd9ea68 Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Tue, 7 Apr 2026 21:00:47 +0200 Subject: [PATCH 01/24] fix: tag mapping --- src/cli/commands/tag.rs | 153 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 6 deletions(-) diff --git a/src/cli/commands/tag.rs b/src/cli/commands/tag.rs index 19ad048..a5567b2 100644 --- a/src/cli/commands/tag.rs +++ b/src/cli/commands/tag.rs @@ -334,21 +334,22 @@ impl TagCommand { ) -> Result, CliError> { let mut packages = std::collections::HashSet::new(); - // Extract scopes from commit messages (pattern: type(scope): message) - let scope_regex = Regex::new(r"^[a-z]+\(([^)]+)\):") + // Extract scopes from commit headers (pattern: type(scope)!: message) + let scope_regex = Regex::new(r"^[a-z0-9-]+\(([^)]+)\)(?:!)?:") .map_err(|e| CliError::Generic(format!("Regex error: {}", e)))?; for line in commit_log.lines() { if let Some(caps) = scope_regex.captures(line) { let scope = caps.get(1).map(|m| m.as_str()).unwrap_or(""); - // Check if scope matches a package - if config.packages.iter().any(|p| p.name == scope) { - packages.insert(scope.to_string()); + for package in resolve_scope_packages(scope, config) { + packages.insert(package); } } } - Ok(packages.into_iter().collect()) + let mut packages = packages.into_iter().collect::>(); + packages.sort(); + Ok(packages) } /// Determine version bump type from commit messages @@ -570,3 +571,143 @@ impl TagCommand { ) } } + +fn resolve_scope_packages(scope_value: &str, config: &RepositoryConfig) -> Vec { + let mut packages = std::collections::HashSet::new(); + let separator = config.scopes.scope_separator.as_str(); + + let scopes = if separator.is_empty() { + vec![scope_value] + } else { + scope_value.split(separator).collect::>() + }; + + for scope in scopes { + let scope = scope.trim(); + if scope.is_empty() { + continue; + } + + if config.packages.iter().any(|package| package.name == scope) { + packages.insert(scope.to_string()); + } + + for mapping in config + .scopes + .mappings + .iter() + .filter(|mapping| mapping.scope == scope) + { + if config + .packages + .iter() + .any(|package| package.name == mapping.package) + { + packages.insert(mapping.package.clone()); + } + } + } + + let mut packages = packages.into_iter().collect::>(); + packages.sort(); + packages +} + +#[cfg(test)] +mod tests { + use super::{resolve_scope_packages, TagCommand}; + use crate::config::repository::{ + PackageConfig, RepositoryConfig, RepositoryMetadata, RepositoryType, ScopeConfig, + ScopeMapping, VersioningConfig, VersioningStrategy, + }; + use structopt::StructOpt; + + fn create_multi_package_config() -> RepositoryConfig { + RepositoryConfig { + repository: RepositoryMetadata { + name: "workspace".to_string(), + repo_type: RepositoryType::MultiPackage, + description: None, + }, + versioning: VersioningConfig { + strategy: VersioningStrategy::Hybrid, + unified_version: None, + rules: None, + }, + packages: vec![ + PackageConfig { + name: "committy-cli".to_string(), + package_type: "rust-cargo".to_string(), + path: ".".to_string(), + version_file: "Cargo.toml".to_string(), + version_field: "package.version".to_string(), + primary: true, + sync_with: None, + independent: false, + workspace_member: false, + description: None, + }, + PackageConfig { + name: "docs".to_string(), + package_type: "node-npm".to_string(), + path: "docs".to_string(), + version_file: "package.json".to_string(), + version_field: "version".to_string(), + primary: false, + sync_with: None, + independent: true, + workspace_member: false, + description: None, + }, + ], + dependencies: vec![], + scopes: ScopeConfig { + auto_detect: true, + require_scope_for_multi_package: true, + allow_multiple_scopes: true, + scope_separator: ",".to_string(), + mappings: vec![ + ScopeMapping { + pattern: "src/**".to_string(), + scope: "core".to_string(), + package: "committy-cli".to_string(), + description: None, + }, + ScopeMapping { + pattern: "docs/**".to_string(), + scope: "docs".to_string(), + package: "docs".to_string(), + description: None, + }, + ], + }, + commit_rules: Default::default(), + git: Default::default(), + convention: None, + release: None, + changelog: None, + workspace: None, + } + } + + #[test] + fn resolve_scope_packages_uses_scope_mappings() { + let config = create_multi_package_config(); + + assert_eq!( + resolve_scope_packages("core", &config), + vec!["committy-cli"] + ); + } + + #[test] + fn detect_affected_packages_accepts_breaking_multi_scope_headers() { + let command = TagCommand::from_iter(["tag"]); + let config = create_multi_package_config(); + let packages = command + .detect_affected_packages("feat(core, docs)!: release both\n", &config) + .unwrap(); + + assert_eq!(packages, vec!["committy-cli", "docs"]); + } +} From 50f734934899da92bb03a542c6df874432c775fe Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Tue, 7 Apr 2026 21:07:47 +0200 Subject: [PATCH 02/24] fix: scope detection --- src/cli/commands/tag.rs | 88 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/src/cli/commands/tag.rs b/src/cli/commands/tag.rs index a5567b2..066e018 100644 --- a/src/cli/commands/tag.rs +++ b/src/cli/commands/tag.rs @@ -7,6 +7,7 @@ use crate::dependency::updater::DependencyUpdater; use crate::error::CliError; use crate::git; use crate::input; +use crate::scope::detector::ScopeDetector; use crate::telemetry; use crate::versioning::hybrid::HybridVersioning; use crate::versioning::independent::IndependentVersioning; @@ -245,11 +246,17 @@ impl TagCommand { // Step 1: Get commit log since last tag let repo = git::discover_repository_from(repo_path)?; - let commit_log = self.get_commit_log_since_last_tag(&repo, git_command_config)?; + let latest_tag = self.find_latest_tag(&repo)?; + let latest_tag = (!latest_tag.is_empty()).then_some(latest_tag); + let commit_log = + self.get_commit_log_since_tag(&repo, latest_tag.as_deref(), git_command_config)?; debug!("Commit log since last tag:\n{}", commit_log); - // Step 2: Detect scopes (affected packages) from commit log - let affected_packages = self.detect_affected_packages(&commit_log, repo_config)?; + // Step 2: Detect affected packages from commit scopes and changed files + let changed_files = + self.get_changed_files_since_tag(&repo, latest_tag.as_deref(), git_command_config)?; + let affected_packages = + self.detect_affected_packages(&commit_log, &changed_files, repo_config)?; if affected_packages.is_empty() { info!("โ„น๏ธ No packages affected by commits. Skipping tag creation."); return Ok(()); @@ -292,17 +299,16 @@ impl TagCommand { } /// Get commit log since last tag - fn get_commit_log_since_last_tag( + fn get_commit_log_since_tag( &self, repo: &git2::Repository, + latest_tag: Option<&str>, git_command_config: &git::GitCommandConfig, ) -> Result { - // Try to find the latest tag - let latest_tag = self.find_latest_tag(repo)?; - let range = if latest_tag.is_empty() { + let range = if latest_tag.is_none() { "HEAD".to_string() } else { - format!("{}..HEAD", latest_tag) + format!("{}..HEAD", latest_tag.unwrap()) }; // Use git log to get commit messages @@ -320,6 +326,41 @@ impl TagCommand { .map_err(|e| CliError::Generic(format!("Invalid UTF-8 in commit log: {}", e))) } + fn get_changed_files_since_tag( + &self, + repo: &git2::Repository, + latest_tag: Option<&str>, + git_command_config: &git::GitCommandConfig, + ) -> Result, CliError> { + let repo_path = repo + .workdir() + .ok_or_else(|| CliError::GitError(git2::Error::from_str("No working directory")))?; + let output = if let Some(tag) = latest_tag { + let range = format!("{tag}..HEAD"); + git::run_git_capture( + repo_path, + &["diff", "--name-only", &range], + "get changed files", + git_command_config, + )? + } else { + git::run_git_capture( + repo_path, + &["log", "--format=", "--name-only", "HEAD"], + "get changed files", + git_command_config, + )? + }; + let files = String::from_utf8(output.stdout) + .map_err(|e| CliError::Generic(format!("Invalid UTF-8 in changed file list: {}", e)))? + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .map(PathBuf::from) + .collect::>(); + Ok(files) + } + /// Find the latest tag in the repository fn find_latest_tag(&self, repo: &git2::Repository) -> Result { let tags = repo.tag_names(None).map_err(CliError::from)?; @@ -330,6 +371,7 @@ impl TagCommand { fn detect_affected_packages( &self, commit_log: &str, + changed_files: &[PathBuf], config: &RepositoryConfig, ) -> Result, CliError> { let mut packages = std::collections::HashSet::new(); @@ -347,6 +389,18 @@ impl TagCommand { } } + if !changed_files.is_empty() { + let detector = ScopeDetector::new(config.clone(), &self.repo_path); + let changed_file_scopes = detector.detect_from_files(changed_files).map_err(|e| { + CliError::Generic(format!("Failed to detect scopes from files: {e}")) + })?; + for scope in changed_file_scopes { + for package in resolve_scope_packages(&scope, config) { + packages.insert(package); + } + } + } + let mut packages = packages.into_iter().collect::>(); packages.sort(); Ok(packages) @@ -620,6 +674,7 @@ mod tests { PackageConfig, RepositoryConfig, RepositoryMetadata, RepositoryType, ScopeConfig, ScopeMapping, VersioningConfig, VersioningStrategy, }; + use std::path::PathBuf; use structopt::StructOpt; fn create_multi_package_config() -> RepositoryConfig { @@ -705,9 +760,24 @@ mod tests { let command = TagCommand::from_iter(["tag"]); let config = create_multi_package_config(); let packages = command - .detect_affected_packages("feat(core, docs)!: release both\n", &config) + .detect_affected_packages("feat(core, docs)!: release both\n", &[], &config) .unwrap(); assert_eq!(packages, vec!["committy-cli", "docs"]); } + + #[test] + fn detect_affected_packages_falls_back_to_changed_files() { + let command = TagCommand::from_iter(["tag"]); + let config = create_multi_package_config(); + let packages = command + .detect_affected_packages( + "fix: tag mapping\n", + &[PathBuf::from("src/cli/commands/tag.rs")], + &config, + ) + .unwrap(); + + assert_eq!(packages, vec!["committy-cli"]); + } } From bd2c183dd0a8c46556889849dcb014b5ff6142a9 Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Tue, 7 Apr 2026 21:22:10 +0200 Subject: [PATCH 03/24] fix: tag behavior --- Cargo.toml | 2 +- docs/package.json | 2 +- src/cli/commands/tag.rs | 57 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4e8aaa8..fd25833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "committy" -version = "1.7.0-beta.0" +version = "1.8.0" authors = ["Arnaud (Martient) Leherpeur "] edition = "2021" description = "๐Ÿš€ Generate clear, concise, and structured commit messages effortlessly" diff --git a/docs/package.json b/docs/package.json index 3cbbeb1..45df2c2 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "committy-docs", "type": "module", - "version": "0.0.1", + "version": "0.1.0", "scripts": { "dev": "astro dev", "start": "astro dev", diff --git a/src/cli/commands/tag.rs b/src/cli/commands/tag.rs index 066e018..3347429 100644 --- a/src/cli/commands/tag.rs +++ b/src/cli/commands/tag.rs @@ -562,7 +562,7 @@ impl TagCommand { info!("๐Ÿ“Œ Created unified tag: {}", tag_name); } } - VersioningStrategy::Independent | VersioningStrategy::Hybrid => { + VersioningStrategy::Independent => { // Per-package tags for update in updates { let tag_name = format!("{}-v{}", update.package_name, update.new_version); @@ -570,6 +570,14 @@ impl TagCommand { info!("๐Ÿ“Œ Created tag: {}", tag_name); } } + VersioningStrategy::Hybrid => { + if let Some(tag_name) = hybrid_tag_name(config, updates) { + self.create_and_push_tag(repo, &tag_name, git_command_config)?; + info!("๐Ÿ“Œ Created hybrid tag: {}", tag_name); + } else { + info!("โ„น๏ธ No primary package version change. Skipping tag creation."); + } + } } Ok(()) @@ -667,13 +675,25 @@ fn resolve_scope_packages(scope_value: &str, config: &RepositoryConfig) -> Vec Option { + let primary = config.packages.iter().find(|package| package.primary)?; + let update = updates + .iter() + .find(|update| update.package_name == primary.name)?; + Some(format!("v{}", update.new_version)) +} + #[cfg(test)] mod tests { - use super::{resolve_scope_packages, TagCommand}; + use super::{hybrid_tag_name, resolve_scope_packages, TagCommand}; use crate::config::repository::{ PackageConfig, RepositoryConfig, RepositoryMetadata, RepositoryType, ScopeConfig, ScopeMapping, VersioningConfig, VersioningStrategy, }; + use crate::versioning::manager::VersionUpdate; use std::path::PathBuf; use structopt::StructOpt; @@ -780,4 +800,37 @@ mod tests { assert_eq!(packages, vec!["committy-cli"]); } + + #[test] + fn hybrid_tag_name_uses_primary_package_version() { + let config = create_multi_package_config(); + let tag = hybrid_tag_name( + &config, + &[ + VersionUpdate::new( + "committy-cli".to_string(), + "1.0.0".to_string(), + "1.0.1".to_string(), + ), + VersionUpdate::new("docs".to_string(), "2.0.0".to_string(), "2.0.1".to_string()), + ], + ); + + assert_eq!(tag.as_deref(), Some("v1.0.1")); + } + + #[test] + fn hybrid_tag_name_skips_when_only_independent_package_changes() { + let config = create_multi_package_config(); + let tag = hybrid_tag_name( + &config, + &[VersionUpdate::new( + "docs".to_string(), + "2.0.0".to_string(), + "2.0.1".to_string(), + )], + ); + + assert!(tag.is_none()); + } } From db3f6855203b2afcb99732b70d839ea6c3e8666d Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Tue, 7 Apr 2026 21:54:39 +0200 Subject: [PATCH 04/24] fix: prevent tag dry-run side effects --- src/cli/commands/tag.rs | 44 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/cli/commands/tag.rs b/src/cli/commands/tag.rs index 3347429..c402edf 100644 --- a/src/cli/commands/tag.rs +++ b/src/cli/commands/tag.rs @@ -276,6 +276,16 @@ impl TagCommand { return Ok(()); } + if self.tag_options.dry_run() { + for tag_name in planned_multi_package_tags(repo_config, &version_updates) { + info!("๐Ÿงช Dry run: Tag would be {}", tag_name); + if self.output != "json" { + println!("{}", tag_name); + } + } + return Ok(()); + } + // Step 5: Update version files per package if self.bump_config_files { self.apply_version_updates(repo_path, &version_updates)?; @@ -686,9 +696,26 @@ fn hybrid_tag_name( Some(format!("v{}", update.new_version)) } +fn planned_multi_package_tags( + config: &RepositoryConfig, + updates: &[crate::versioning::manager::VersionUpdate], +) -> Vec { + match config.versioning.strategy { + VersioningStrategy::Unified => updates + .first() + .map(|update| vec![format!("v{}", update.new_version)]) + .unwrap_or_default(), + VersioningStrategy::Independent => updates + .iter() + .map(|update| format!("{}-v{}", update.package_name, update.new_version)) + .collect(), + VersioningStrategy::Hybrid => hybrid_tag_name(config, updates).into_iter().collect(), + } +} + #[cfg(test)] mod tests { - use super::{hybrid_tag_name, resolve_scope_packages, TagCommand}; + use super::{hybrid_tag_name, planned_multi_package_tags, resolve_scope_packages, TagCommand}; use crate::config::repository::{ PackageConfig, RepositoryConfig, RepositoryMetadata, RepositoryType, ScopeConfig, ScopeMapping, VersioningConfig, VersioningStrategy, @@ -833,4 +860,19 @@ mod tests { assert!(tag.is_none()); } + + #[test] + fn planned_multi_package_tags_uses_hybrid_repo_tag() { + let config = create_multi_package_config(); + let tags = planned_multi_package_tags( + &config, + &[VersionUpdate::new( + "committy-cli".to_string(), + "1.0.0".to_string(), + "1.0.1".to_string(), + )], + ); + + assert_eq!(tags, vec!["v1.0.1"]); + } } From 1ff168c4bd0bcc1da577bcd6ac2b902bf73a7ed2 Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Wed, 15 Jul 2026 08:36:17 +0200 Subject: [PATCH 05/24] Feat agent convention enforcement (#41) * fix(core): preserve linked worktree discovery * feat(core): stabilize agent json contract * feat(core): add typed convention capabilities * feat(core): add native convention hooks * feat(agents): ship shared workflow plugin * chore(agents): streamline convention guidance * chore(core): satisfy strict clippy checks * fix(docs): update starlight social config * fix(core): preserve legacy branch defaults --- .agents/plugins/marketplace.json | 20 ++ .claude-plugin/marketplace.json | 18 + .claude/hooks/block-dangerous-git.sh | 25 ++ .claude/settings.json | 15 + AGENTS.md | 179 ++-------- CLAUDE.md | 179 ++-------- Cargo.toml | 2 - docs/CLI_WORKFLOW_REFERENCE.md | 13 + docs/astro.config.mjs | 7 +- docs/package.json | 22 +- .../docs/reference/agent-workflows.mdx | 40 ++- plugins/committy/.claude-plugin/plugin.json | 14 + plugins/committy/.codex-plugin/plugin.json | 28 ++ .../committy/skills/committy-branch/SKILL.md | 14 + .../skills/committy-branch/agents/openai.yaml | 4 + .../committy/skills/committy-commit/SKILL.md | 16 + .../skills/committy-commit/agents/openai.yaml | 4 + .../committy/skills/committy-enforce/SKILL.md | 15 + .../committy-enforce/agents/openai.yaml | 4 + .../committy/skills/committy-release/SKILL.md | 15 + .../committy-release/agents/openai.yaml | 4 + src/cli/commands/amend.rs | 8 + src/cli/commands/branch.rs | 119 ++++++- src/cli/commands/commit.rs | 11 + src/cli/commands/config.rs | 2 + src/cli/commands/group_commit.rs | 12 + src/cli/commands/hooks.rs | 312 ++++++++++++++++++ src/cli/commands/init.rs | 1 + src/cli/commands/lint.rs | 12 + src/cli/commands/lint_message.rs | 10 + src/cli/commands/mod.rs | 1 + src/cli/commands/schema.rs | 72 ++++ src/cli/commands/tag.rs | 21 +- src/cli/mod.rs | 25 +- src/cli/output.rs | 40 +++ src/config.rs | 5 - src/config/convention.rs | 49 +++ src/config/hierarchy.rs | 8 +- src/config/repository.rs | 33 ++ src/convention/mod.rs | 35 +- src/dependency/updater.rs | 1 + src/error.rs | 25 ++ src/git/process.rs | 1 + src/git/repository.rs | 22 +- src/input/prompts.rs | 7 +- src/main.rs | 28 +- src/release/changelog.rs | 2 +- src/release/engine.rs | 25 +- src/scope/detector.rs | 1 + src/versioning/hybrid.rs | 1 + src/versioning/independent.rs | 1 + src/versioning/unified.rs | 1 + src/workflow/orchestrator.rs | 1 + tests/agent_cli_tests.rs | 308 ++++++++++++++++- tests/cli_surface_tests.rs | 22 ++ tests/hooks_cli_tests.rs | 156 +++++++++ 56 files changed, 1589 insertions(+), 427 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .claude-plugin/marketplace.json create mode 100755 .claude/hooks/block-dangerous-git.sh create mode 100644 .claude/settings.json create mode 100644 plugins/committy/.claude-plugin/plugin.json create mode 100644 plugins/committy/.codex-plugin/plugin.json create mode 100644 plugins/committy/skills/committy-branch/SKILL.md create mode 100644 plugins/committy/skills/committy-branch/agents/openai.yaml create mode 100644 plugins/committy/skills/committy-commit/SKILL.md create mode 100644 plugins/committy/skills/committy-commit/agents/openai.yaml create mode 100644 plugins/committy/skills/committy-enforce/SKILL.md create mode 100644 plugins/committy/skills/committy-enforce/agents/openai.yaml create mode 100644 plugins/committy/skills/committy-release/SKILL.md create mode 100644 plugins/committy/skills/committy-release/agents/openai.yaml create mode 100644 src/cli/commands/hooks.rs create mode 100644 src/cli/output.rs create mode 100644 tests/hooks_cli_tests.rs diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..7baddd8 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "personal", + "interface": { + "displayName": "Personal" + }, + "plugins": [ + { + "name": "committy", + "source": { + "source": "local", + "path": "./plugins/committy" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..2096b65 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-marketplace.json", + "name": "committy-tools", + "description": "Agent workflows and enforcement for Committy repositories.", + "owner": { + "name": "Arnaud Leherpeur", + "email": "arnaud.leherpeur@taultek.com" + }, + "plugins": [ + { + "name": "committy", + "source": "./plugins/committy", + "description": "Token-efficient commit, branch, release, and convention enforcement workflows.", + "category": "Development", + "tags": ["git", "conventional-commits", "agents"] + } + ] +} diff --git a/.claude/hooks/block-dangerous-git.sh b/.claude/hooks/block-dangerous-git.sh new file mode 100755 index 0000000..c40b59c --- /dev/null +++ b/.claude/hooks/block-dangerous-git.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +INPUT=$(cat) +COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command') + +DANGEROUS_PATTERNS=( + "git push" + "git reset --hard" + "git clean -fd" + "git clean -f" + "git branch -D" + "git checkout \." + "git restore \." + "push --force" + "reset --hard" +) + +for pattern in "${DANGEROUS_PATTERNS[@]}"; do + if echo "$COMMAND" | grep -qE "$pattern"; then + echo "BLOCKED: '$COMMAND' matches dangerous pattern '$pattern'. The user has prevented you from doing this." >&2 + exit 2 + fi +done + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..9d674a1 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-git.sh" + } + ] + } + ] + } +} diff --git a/AGENTS.md b/AGENTS.md index 5c1572f..1dd5276 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,172 +1,33 @@ -# AGENTS.md +# Committy agent guide -This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. +Committy is a Rust CLI for conventional commits, branch policy, grouped commits, releases, and multi-package version updates. The checkout is CLI-first; do not assume a TUI exists. -## Overview +## Work safely -Committy is a Rust CLI for composing conventional commits, linting history quality, planning grouped commits, generating tags, and orchestrating multi-package version updates. +- Discover active policy with `cargo run -- schema --output json`. +- Prefer `--non-interactive`, `--repo-path`, `--dry-run`, and `--output json`. +- Inspect `api_version`, `ok`, `errors`, and the command-specific plan before applying it. +- Use the `committy-branch`, `committy-commit`, `committy-release`, and `committy-enforce` plugin skills when available. +- Never infer permission for a push, publish, destructive Git action, or unrelated staging. -The current repository state is **CLI-first and JSON-friendly**. This checkout does **not** contain a checked-in `src/tui/` module or a `tui` subcommand, so agent workflows should use the machine-readable CLI commands instead of assuming a terminal UI exists. +## Build and verify -## Essential Commands - -### Build and Test -```bash -# Build the project -cargo build - -# Run all tests -cargo test - -# Run a specific test -cargo test test_name - -# Run tests with verbose output -cargo test -- --nocapture -``` - -### Running the CLI -```bash -# Run from source -cargo run - -# Run with arguments -cargo run -- [options] - -# Agent-safe previews -cargo run -- branch --type feat --ticket AI42 --subject "agent flow" --dry-run --output json -cargo run -- commit --type feat --scope core --message "add agent flow" --dry-run --output json -cargo run -- group-commit --mode plan --output json -cargo run -- lint --output json -cargo run -- tag --dry-run --output json -``` - -### Development ```bash -# Check code without building cargo check - -# Format code -cargo fmt - -# Run linter -cargo clippy +cargo test +cargo fmt --all -- --check +cargo clippy --all-targets --all-features ``` -## Current CLI Surface - -The current checked-in command set is defined in `src/cli/mod.rs`: -- `commit` -- `amend` -- `tag` -- `lint` -- `lint-message` -- `branch` -- `group-commit` -- `init` -- `config` -- `packages` +Tests are in `tests/`; CLI integration tests use `assert_cmd` and temporary repositories. Add public-seam tests for machine output, exit codes, Git behavior, hook protocol, and plugin manifests. ## Architecture -### Module Structure - -**CLI Layer** (`src/cli/`) -- `commands/`: concrete command implementations for `amend`, `branch`, `commit`, `config`, `group_commit`, `init`, `lint`, `lint_message`, `packages`, and `tag` -- `src/main.rs`: top-level flag parsing, non-interactive mode detection, update checks, and command dispatch - -**Workflow + Repository Config** -- `.committy/config.toml`: repository-level multi-package config for Committy itself -- `src/workflow/orchestrator.rs`: coordinates scope detection, version bumps, and dependency updates -- `src/config/hierarchy.rs` and `src/config/repository.rs`: merged config loading and repository config support -- Current scope mapping in this repo: - - `src/**` โ†’ `core` - - `docs/**` โ†’ `docs` - -**Git Operations** (`src/git/`) -- `repository.rs`: repository discovery, staged-file queries, and git config validation -- `commit.rs`: commit creation and message formatting -- `tag.rs`: tag generation helpers -- `branch.rs`: branch operations - -**Linting** (`src/linter/`) -- Validates conventional commit messages -- Used by `commit`, `amend`, `group-commit`, `lint`, and `lint-message` -- Exit code `3` is reserved for lint issues - -**Packages + Versioning** (`src/packages/`, `src/versioning/`, `src/version/`) -- Multi-package detection and sync helpers -- Version bump calculation for independent, unified, and hybrid strategies -- Version file updates across supported package managers - -**AI Integration** (`src/ai/`) -- Supports OpenRouter and Ollama providers -- Currently used by `group-commit --ai` - -**Input/Prompts** (`src/input/`) -- Interactive prompt handling via `inquire` -- Validation and auto-correction helpers for commit and branch inputs - -**Error Handling** (`src/error.rs`) -- `CliError` is the central error type across commands - -### Key Flow: Commit Command - -1. `src/cli/commands/commit.rs` discovers the repo and checks staged changes. -2. It loads allowed commit types and, when repo config exists, auto-detects scopes. -3. It formats and lints the full commit message. -4. When `.committy/config.toml` is available, it runs `WorkflowOrchestrator` to preview or apply version/dependency side effects. -5. `--dry-run --output json` returns the resolved message plus an optional `workflow` preview without mutating git. -6. A real run validates git config, stages workflow-modified files if needed, and then creates or amends the commit. - -### Key Flow: Group Commit - -1. `src/cli/commands/group_commit.rs` classifies changed files into `docs`, `tests`, `ci`, `deps`, `build`, `chore`, and `code` groups. -2. `--mode plan` returns grouped JSON output without mutations. -3. `--mode apply` creates one validated commit per group. -4. `--push` is only valid in apply mode and must be paired with `--confirm-push`. - -## Recommended Agent Workflow - -Use the current CLI states as a **preview โ†’ inspect โ†’ apply** workflow: - -1. Preview first with `--dry-run --output json` when the command supports it. -2. Inspect `ok`, `dry_run`, `errors`, and command-specific fields such as: - - `branch_name` for `branch` - - `message`, `commit_type`, `scope`, and optional `workflow` for `commit` - - `mode`, `groups`, and `commits` for `group-commit` -3. Rerun without `--dry-run` only when the preview matches the intended change. -4. Use `--repo-path` when operating on a checkout other than the current shell directory. -5. Before any push or release, run `committy --non-interactive lint --repo-path . --output json`. -6. Require explicit confirmation for remote mutations: - - `group-commit --mode apply --push --confirm-push` - - `tag --publish --confirm-publish` - -The canonical reference for this flow is `docs/src/content/docs/reference/agent-workflows.mdx`. - -## Non-Interactive Mode - -- Enabled via `--non-interactive`, `COMMITTY_NONINTERACTIVE=1`, or `CI=1` -- In non-interactive mode, required flags must be provided and prompts are skipped -- This is the preferred mode for agentic or CI-driven usage - -## Build System - -- `build.rs` injects `SENTRY_DSN` and `POSTHOG_API_KEY` at compile time from environment variables -- Defaults to `"undefined"` if those environment variables are not set - -## Testing - -- Tests live in `tests/` -- Integration-style CLI coverage uses `assert_cmd` -- Temporary git repositories are created with `tempfile` -- Good entry points for agent workflow behavior: - - `tests/agent_cli_tests.rs` - - `tests/group_commit_tests.rs` - - `tests/lint_message_cli_tests.rs` - -## Contributing Notes +- `src/cli/commands/`: command implementations and JSON payloads +- `src/config/`, `src/convention/`: hierarchical typed policy +- `src/git/`: repository and Git operations +- `src/linter/`: message/history validation; exit code 3 means lint findings +- `src/workflow/`, `src/versioning/`, `src/packages/`: release side effects +- `plugins/committy/`: shared Codex and Claude skills -- PRs should target the `develop` branch, not `main` -- Commit messages should follow conventional commit format -- Versioning is managed through `Cargo.toml`, `.committy/config.toml`, and the tag/workflow helpers +PRs target `develop`. Commits follow Conventional Commits. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`. diff --git a/CLAUDE.md b/CLAUDE.md index 8cd9c17..524df5f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,172 +1,33 @@ -# CLAUDE.md +# Committy Claude guide -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Committy is a Rust CLI for conventional commits, branch policy, grouped commits, releases, and multi-package version updates. The checkout is CLI-first; do not assume a TUI exists. -## Overview +## Work safely -Committy is a Rust CLI for composing conventional commits, linting history quality, planning grouped commits, generating tags, and orchestrating multi-package version updates. +- Discover active policy with `cargo run -- schema --output json`. +- Prefer `--non-interactive`, `--repo-path`, `--dry-run`, and `--output json`. +- Inspect `api_version`, `ok`, `errors`, and the command-specific plan before applying it. +- Use `/committy:committy-branch`, `/committy:committy-commit`, `/committy:committy-release`, and `/committy:committy-enforce` when the plugin is installed. +- Never infer permission for a push, publish, destructive Git action, or unrelated staging. -The current repository state is **CLI-first and JSON-friendly**. This checkout does **not** contain a checked-in `src/tui/` module or a `tui` subcommand, so Claude workflows should use the machine-readable CLI commands instead of assuming a terminal UI exists. +## Build and verify -## Essential Commands - -### Build and Test -```bash -# Build the project -cargo build - -# Run all tests -cargo test - -# Run a specific test -cargo test test_name - -# Run tests with verbose output -cargo test -- --nocapture -``` - -### Running the CLI -```bash -# Run from source -cargo run - -# Run with arguments -cargo run -- [options] - -# Agent-safe previews -cargo run -- branch --type feat --ticket AI42 --subject "agent flow" --dry-run --output json -cargo run -- commit --type feat --scope core --message "add agent flow" --dry-run --output json -cargo run -- group-commit --mode plan --output json -cargo run -- lint --output json -cargo run -- tag --dry-run --output json -``` - -### Development ```bash -# Check code without building cargo check - -# Format code -cargo fmt - -# Run linter -cargo clippy +cargo test +cargo fmt --all -- --check +cargo clippy --all-targets --all-features ``` -## Current CLI Surface - -The current checked-in command set is defined in `src/cli/mod.rs`: -- `commit` -- `amend` -- `tag` -- `lint` -- `lint-message` -- `branch` -- `group-commit` -- `init` -- `config` -- `packages` +Tests are in `tests/`; CLI integration tests use `assert_cmd` and temporary repositories. Add public-seam tests for machine output, exit codes, Git behavior, hook protocol, and plugin manifests. ## Architecture -### Module Structure - -**CLI Layer** (`src/cli/`) -- `commands/`: concrete command implementations for `amend`, `branch`, `commit`, `config`, `group_commit`, `init`, `lint`, `lint_message`, `packages`, and `tag` -- `src/main.rs`: top-level flag parsing, non-interactive mode detection, update checks, and command dispatch - -**Workflow + Repository Config** -- `.committy/config.toml`: repository-level multi-package config for Committy itself -- `src/workflow/orchestrator.rs`: coordinates scope detection, version bumps, and dependency updates -- `src/config/hierarchy.rs` and `src/config/repository.rs`: merged config loading and repository config support -- Current scope mapping in this repo: - - `src/**` โ†’ `core` - - `docs/**` โ†’ `docs` - -**Git Operations** (`src/git/`) -- `repository.rs`: repository discovery, staged-file queries, and git config validation -- `commit.rs`: commit creation and message formatting -- `tag.rs`: tag generation helpers -- `branch.rs`: branch operations - -**Linting** (`src/linter/`) -- Validates conventional commit messages -- Used by `commit`, `amend`, `group-commit`, `lint`, and `lint-message` -- Exit code `3` is reserved for lint issues - -**Packages + Versioning** (`src/packages/`, `src/versioning/`, `src/version/`) -- Multi-package detection and sync helpers -- Version bump calculation for independent, unified, and hybrid strategies -- Version file updates across supported package managers - -**AI Integration** (`src/ai/`) -- Supports OpenRouter and Ollama providers -- Currently used by `group-commit --ai` - -**Input/Prompts** (`src/input/`) -- Interactive prompt handling via `inquire` -- Validation and auto-correction helpers for commit and branch inputs - -**Error Handling** (`src/error.rs`) -- `CliError` is the central error type across commands - -### Key Flow: Commit Command - -1. `src/cli/commands/commit.rs` discovers the repo and checks staged changes. -2. It loads allowed commit types and, when repo config exists, auto-detects scopes. -3. It formats and lints the full commit message. -4. When `.committy/config.toml` is available, it runs `WorkflowOrchestrator` to preview or apply version/dependency side effects. -5. `--dry-run --output json` returns the resolved message plus an optional `workflow` preview without mutating git. -6. A real run validates git config, stages workflow-modified files if needed, and then creates or amends the commit. - -### Key Flow: Group Commit - -1. `src/cli/commands/group_commit.rs` classifies changed files into `docs`, `tests`, `ci`, `deps`, `build`, `chore`, and `code` groups. -2. `--mode plan` returns grouped JSON output without mutations. -3. `--mode apply` creates one validated commit per group. -4. `--push` is only valid in apply mode and must be paired with `--confirm-push`. - -## Recommended Agent Workflow - -Use the current CLI states as a **preview โ†’ inspect โ†’ apply** workflow: - -1. Preview first with `--dry-run --output json` when the command supports it. -2. Inspect `ok`, `dry_run`, `errors`, and command-specific fields such as: - - `branch_name` for `branch` - - `message`, `commit_type`, `scope`, and optional `workflow` for `commit` - - `mode`, `groups`, and `commits` for `group-commit` -3. Rerun without `--dry-run` only when the preview matches the intended change. -4. Use `--repo-path` when operating on a checkout other than the current shell directory. -5. Before any push or release, run `committy --non-interactive lint --repo-path . --output json`. -6. Require explicit confirmation for remote mutations: - - `group-commit --mode apply --push --confirm-push` - - `tag --publish --confirm-publish` - -The canonical reference for this flow is `docs/src/content/docs/reference/agent-workflows.mdx`. - -## Non-Interactive Mode - -- Enabled via `--non-interactive`, `COMMITTY_NONINTERACTIVE=1`, or `CI=1` -- In non-interactive mode, required flags must be provided and prompts are skipped -- This is the preferred mode for agentic or CI-driven usage - -## Build System - -- `build.rs` injects `SENTRY_DSN` and `POSTHOG_API_KEY` at compile time from environment variables -- Defaults to `"undefined"` if those environment variables are not set - -## Testing - -- Tests live in `tests/` -- Integration-style CLI coverage uses `assert_cmd` -- Temporary git repositories are created with `tempfile` -- Good entry points for agent workflow behavior: - - `tests/agent_cli_tests.rs` - - `tests/group_commit_tests.rs` - - `tests/lint_message_cli_tests.rs` - -## Contributing Notes +- `src/cli/commands/`: command implementations and JSON payloads +- `src/config/`, `src/convention/`: hierarchical typed policy +- `src/git/`: repository and Git operations +- `src/linter/`: message/history validation; exit code 3 means lint findings +- `src/workflow/`, `src/versioning/`, `src/packages/`: release side effects +- `plugins/committy/`: shared Codex and Claude skills -- PRs should target the `develop` branch, not `main` -- Commit messages should follow conventional commit format -- Versioning is managed through `Cargo.toml`, `.committy/config.toml`, and the tag/workflow helpers +PRs target `develop`. Commits follow Conventional Commits. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`. diff --git a/Cargo.toml b/Cargo.toml index fd25833..190b556 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,4 @@ assert_cmd = "2.1.1" predicates = "3.1.3" tempfile = "3.23.0" mockall = "0.13.1" -once_cell = "1.21.3" serial_test = "3.2.0" - diff --git a/docs/CLI_WORKFLOW_REFERENCE.md b/docs/CLI_WORKFLOW_REFERENCE.md index fdff19d..10821e4 100644 --- a/docs/CLI_WORKFLOW_REFERENCE.md +++ b/docs/CLI_WORKFLOW_REFERENCE.md @@ -139,12 +139,14 @@ info = "Team-specific conventional commits." [[convention.types]] name = "feat" description = "A feature" +contexts = ["commit", "branch"] bump = "minor" changelog_section = "Features" [[convention.types]] name = "sec" description = "A security fix" +contexts = ["commit", "branch"] bump = "patch" changelog_section = "Security" @@ -155,3 +157,14 @@ prompt = "Select a change type" required = true choices = ["feat", "sec", "fix", "docs", "chore"] ``` + +Branch-specific ticket policy stays separate from reusable type metadata: + +```toml +[branch_rules] +require_ticket = true +ticket_pattern = "^[A-Z]+[0-9]+$" +enforce_explicit_names = false +``` + +Types without `contexts` remain commit types for backward compatibility. Query the effective typed definitions and described CLI capabilities with `committy schema --output json`. diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index d0a73e4..2791eec 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -10,9 +10,9 @@ export default defineConfig({ integrations: [ starlight({ title: 'Committy Docs', - social: { - github: 'https://github.com/martient/committy', - }, + social: [ + { icon: 'github', label: 'GitHub', href: 'https://github.com/martient/committy' }, + ], sidebar: [ { label: 'Intro', @@ -30,7 +30,6 @@ export default defineConfig({ autogenerate: { directory: 'project' }, }, ], - }), ], }); diff --git a/docs/package.json b/docs/package.json index 45df2c2..3345cf2 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,17 +1,17 @@ { + "dependencies": { + "@astrojs/starlight": "0.35.2", + "astro": "5.13.3", + "sharp": "0.34.3" + }, "name": "committy-docs", - "type": "module", - "version": "0.1.0", "scripts": { - "dev": "astro dev", - "start": "astro dev", + "astro": "astro", "build": "astro build", + "dev": "astro dev", "preview": "astro preview", - "astro": "astro" + "start": "astro dev" }, - "dependencies": { - "@astrojs/starlight": "0.35.2", - "astro": "5.13.3", - "sharp": "0.34.3" - } -} \ No newline at end of file + "type": "module", + "version": "0.1.2" +} diff --git a/docs/src/content/docs/reference/agent-workflows.mdx b/docs/src/content/docs/reference/agent-workflows.mdx index f645e07..e170a05 100644 --- a/docs/src/content/docs/reference/agent-workflows.mdx +++ b/docs/src/content/docs/reference/agent-workflows.mdx @@ -12,11 +12,20 @@ Committy can be embedded directly in system prompts or skills when you want an a Preferred sequence: -1. Run a `--dry-run --output json` preview. -2. Inspect the JSON payload. -3. Rerun without `--dry-run` only if the plan looks correct. -4. Require explicit confirmation for any remote mutation. -5. Use `--repo-path` when the agent should operate on a checkout other than its current shell directory. +1. Query `committy schema --output json` once for active types and capabilities. +2. Run a `--dry-run --output json` preview. +3. Inspect the JSON payload. +4. Rerun without `--dry-run` only if the plan looks correct. +5. Require explicit confirmation for any remote mutation. +6. Use `--repo-path` when the agent should operate on another checkout. + +## Policy discovery + +```bash +committy schema --repo-path . --output json +``` + +The backward-compatible `types` array lists commit types. `type_definitions` adds descriptions, SemVer behavior, aliases, and `commit` or `branch` contexts. `capabilities` describes supported agent operations so skills do not need to hard-code the command surface. ## Branch recipe @@ -111,6 +120,7 @@ For grouped commits or tags, require explicit confirmation flags before any remo ## JSON fields to rely on +- `api_version`: machine contract version; currently `1`. - `command`: the command that produced the payload. - `ok`: whether the command-level operation succeeded. - `dry_run`: whether the payload is only a preview. @@ -118,3 +128,23 @@ For grouped commits or tags, require explicit confirmation flags before any remo - `mode`: present on multi-mode commands such as `group-commit`. Command-specific fields remain stable on top of that envelope. + +Command errors exit `1`; convention lint findings exit `3`. JSON mode writes one document to stdout on both success and failure. + +## Repository enforcement + +Preview native Git hooks plus GitHub Actions enforcement: + +```bash +committy --non-interactive hooks install \ + --repo-path . \ + --with-ci \ + --dry-run \ + --output json +``` + +Remove `--dry-run` to install `commit-msg`, `pre-push`, and `.github/workflows/committy.yml`. Existing files are never replaced unless `--force` is explicit. The hooks are thin delegates to `committy hooks run`, keeping local and CI validation on the same policy engine. + +## Codex and Claude skills + +The shared plugin lives in `plugins/committy/`. Codex discovers it through `.agents/plugins/marketplace.json`; Claude Code uses `.claude-plugin/marketplace.json`. Both load the same four progressive-disclosure skills for branches, commits, releases, and enforcement, so their small metadata is always available while full instructions load only for matching work. diff --git a/plugins/committy/.claude-plugin/plugin.json b/plugins/committy/.claude-plugin/plugin.json new file mode 100644 index 0000000..16f27d2 --- /dev/null +++ b/plugins/committy/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "committy", + "version": "0.1.0", + "description": "Token-efficient commit, branch, release, and repository convention workflows backed by the Committy CLI.", + "author": { + "name": "Arnaud Leherpeur", + "email": "arnaud.leherpeur@taultek.com" + }, + "homepage": "https://github.com/martient/committy", + "repository": "https://github.com/martient/committy", + "license": "Apache-2.0", + "keywords": ["git", "conventional-commits", "branches", "releases", "agents"] +} diff --git a/plugins/committy/.codex-plugin/plugin.json b/plugins/committy/.codex-plugin/plugin.json new file mode 100644 index 0000000..0c84df7 --- /dev/null +++ b/plugins/committy/.codex-plugin/plugin.json @@ -0,0 +1,28 @@ +{ + "name": "committy", + "version": "0.1.0", + "description": "Token-efficient commit, branch, release, and repository convention workflows backed by the Committy CLI.", + "author": { + "name": "Arnaud Leherpeur", + "email": "arnaud.leherpeur@taultek.com", + "url": "https://github.com/martient" + }, + "homepage": "https://github.com/martient/committy", + "repository": "https://github.com/martient/committy", + "license": "Apache-2.0", + "keywords": ["git", "conventional-commits", "branches", "releases", "agents"], + "skills": "./skills/", + "interface": { + "displayName": "Committy", + "shortDescription": "Enforce Git conventions with low-token workflows.", + "longDescription": "Use Committy's typed JSON schema, dry-run commands, Git hooks, and CI checks for consistent agent-driven commits, branches, and releases.", + "developerName": "Arnaud Leherpeur", + "category": "Productivity", + "capabilities": ["Commit preview", "Branch policy", "Git hooks", "Release planning"], + "defaultPrompt": [ + "Commit this change with Committy.", + "Create a compliant branch for this ticket.", + "Add Committy enforcement to this repository." + ] + } +} diff --git a/plugins/committy/skills/committy-branch/SKILL.md b/plugins/committy/skills/committy-branch/SKILL.md new file mode 100644 index 0000000..86941c0 --- /dev/null +++ b/plugins/committy/skills/committy-branch/SKILL.md @@ -0,0 +1,14 @@ +--- +name: committy-branch +description: Preview, validate, and create repository-compliant Git branches with Committy. Use when an agent is asked to name a branch, start work for a ticket, validate a proposed branch name, or switch to a new branch. +--- + +# Branch with Committy + +1. Query `committy -q schema --repo-path --output json` and select a type whose `contexts` includes `branch`. +2. Prefer structured input: `committy --non-interactive -q branch --repo-path --type --ticket --subject --dry-run --output json`. Omit `--ticket` only when repository rules allow it. +3. Inspect `api_version`, `ok`, `branch_name`, and `errors`. If validation fails, repair the inputs; never fall back to raw `git branch` to evade policy. +4. Create the branch by repeating the command without `--dry-run` when the requested operation authorizes it. +5. Use `--name` only when the user supplied a full branch name or exact preservation matters. Repository policy may enforce explicit names. + +Do not force-recreate an existing branch unless the user explicitly requests it. diff --git a/plugins/committy/skills/committy-branch/agents/openai.yaml b/plugins/committy/skills/committy-branch/agents/openai.yaml new file mode 100644 index 0000000..dc4a88d --- /dev/null +++ b/plugins/committy/skills/committy-branch/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Branch with Committy" + short_description: "Preview and create compliant branches" + default_prompt: "Use $committy-branch to create a compliant branch for this work." diff --git a/plugins/committy/skills/committy-commit/SKILL.md b/plugins/committy/skills/committy-commit/SKILL.md new file mode 100644 index 0000000..fb7fc49 --- /dev/null +++ b/plugins/committy/skills/committy-commit/SKILL.md @@ -0,0 +1,16 @@ +--- +name: committy-commit +description: Draft, lint, preview, and create repository-compliant commits with Committy. Use when an agent is asked to stage a coherent change, write or repair a commit message, amend a commit, or make a conventional commit. +--- + +# Commit with Committy + +1. Inspect `git status --short` and the relevant diff. Stage only files belonging to the requested change. +2. Query policy once with `committy -q schema --repo-path --output json`. Choose a commit type from `types`; use `type_definitions` descriptions when intent is ambiguous. +3. Lint a supplied full message with `committy -q lint-message --repo-path --message --output json`. +4. Preview with `committy --non-interactive -q commit --repo-path --type --message --dry-run --output json`. Add `--scope`, `--long-message`, or `--breaking-change` only when supported by the change. +5. Inspect `api_version`, `ok`, `message`, `errors`, and optional `workflow`. Correct errors; do not bypass repository policy. +6. Create the commit with the same arguments after the user has authorized committing. Use `amend` only when explicitly requested. +7. Report the SHA and subject. Stop after lint or preview when that is all the user requested. + +Never stage unrelated work, publish, tag, or push as part of this skill. diff --git a/plugins/committy/skills/committy-commit/agents/openai.yaml b/plugins/committy/skills/committy-commit/agents/openai.yaml new file mode 100644 index 0000000..93c418a --- /dev/null +++ b/plugins/committy/skills/committy-commit/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Commit with Committy" + short_description: "Preview and create compliant commits" + default_prompt: "Use $committy-commit to commit my current change safely." diff --git a/plugins/committy/skills/committy-enforce/SKILL.md b/plugins/committy/skills/committy-enforce/SKILL.md new file mode 100644 index 0000000..b4308ca --- /dev/null +++ b/plugins/committy/skills/committy-enforce/SKILL.md @@ -0,0 +1,15 @@ +--- +name: committy-enforce +description: Configure and enforce repository commit and branch conventions with Committy Git hooks and CI. Use when an agent is asked to adopt Committy, define typed convention descriptions or branch ticket rules, install hooks, or add convention checks to GitHub Actions. +--- + +# Enforce with Committy + +1. Inspect existing `.committy/config.toml`, hooks, and CI before changing anything. +2. Query `committy -q schema --repo-path --output json` to understand current typed definitions and supported capabilities. +3. Keep convention types in `[[convention.types]]` with `name`, `description`, `contexts`, bump, changelog, aliases, and visibility fields. Put ticket requirements in `[branch_rules]`. +4. Preview local enforcement with `committy --non-interactive -q hooks install --repo-path --with-ci --dry-run --output json`. +5. Apply without `--dry-run` after the user authorizes repository writes. Do not use `--force` unless replacement of every reported file is intentional. +6. Verify with `committy --non-interactive -q lint --repo-path --output json` and a representative branch dry run. + +Git hooks and CI are enforcement. Agent instructions are workflow guidance and should stay short. diff --git a/plugins/committy/skills/committy-enforce/agents/openai.yaml b/plugins/committy/skills/committy-enforce/agents/openai.yaml new file mode 100644 index 0000000..f9edb57 --- /dev/null +++ b/plugins/committy/skills/committy-enforce/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Enforce with Committy" + short_description: "Install Git and CI convention checks" + default_prompt: "Use $committy-enforce to preview convention enforcement for this repository." diff --git a/plugins/committy/skills/committy-release/SKILL.md b/plugins/committy/skills/committy-release/SKILL.md new file mode 100644 index 0000000..0fdff58 --- /dev/null +++ b/plugins/committy/skills/committy-release/SKILL.md @@ -0,0 +1,15 @@ +--- +name: committy-release +description: Plan and apply version bumps, changelogs, tags, and prereleases with Committy. Use when an agent is asked to prepare, preview, tag, or publish a release, or inspect the release impact of commit history. +--- + +# Release with Committy + +1. Validate repository setup with `committy -q config validate --repo-path --output json`. +2. Preview the release using `committy --non-interactive -q bump --repo-path --dry-run --output json`. Use an explicit increment or prerelease only when requested. +3. For narrower work, preview `changelog --dry-run` or `tag --dry-run --no-fetch` directly. +4. Inspect versions, changed files, tags, changelog, publish state, and errors. Preserve the previewed arguments. +5. Apply only after mutation is authorized. Publishing additionally requires the CLI confirmation flag; never infer remote-mutation consent. +6. Report resulting versions, tags, and publish state. + +Use `$committy-commit` for ordinary commits and `$committy-enforce` for hooks or CI setup. diff --git a/plugins/committy/skills/committy-release/agents/openai.yaml b/plugins/committy/skills/committy-release/agents/openai.yaml new file mode 100644 index 0000000..67029b4 --- /dev/null +++ b/plugins/committy/skills/committy-release/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Release with Committy" + short_description: "Plan and apply compliant releases" + default_prompt: "Use $committy-release to preview the next release." diff --git a/src/cli/commands/amend.rs b/src/cli/commands/amend.rs index 19f6b5d..568042a 100644 --- a/src/cli/commands/amend.rs +++ b/src/cli/commands/amend.rs @@ -1,4 +1,5 @@ use crate::cli::commands::commit::CommitCommand; +use crate::cli::output::MachineContext; use crate::cli::Command; use crate::error::CliError; use std::path::PathBuf; @@ -62,6 +63,13 @@ impl Command for AmendCommand { commit.execute_with_command_name(non_interactive, "amend") } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "amend", + dry_run: self.dry_run, + }) + } } impl Default for AmendCommand { diff --git a/src/cli/commands/branch.rs b/src/cli/commands/branch.rs index 4e587d7..88d794b 100644 --- a/src/cli/commands/branch.rs +++ b/src/cli/commands/branch.rs @@ -1,7 +1,10 @@ use std::collections::HashMap; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; -use crate::config::BRANCH_TYPES; +use crate::config::hierarchy::MergedConfig; +use crate::config::repository::BranchRulesConfig; +use crate::convention::Convention; use crate::error::CliError; use crate::git; use crate::input; @@ -46,6 +49,7 @@ pub struct BranchCommand { #[derive(Debug, Serialize)] struct BranchCommandOutput { + api_version: u8, command: String, ok: bool, dry_run: bool, @@ -84,7 +88,22 @@ struct BranchPlan { impl Command for BranchCommand { fn execute(&self, non_interactive: bool) -> Result<(), CliError> { - let Some(plan) = self.build_plan(non_interactive)? else { + let repo = git::discover_repository_from(&self.repo_path)?; + let repo_path = repo + .workdir() + .ok_or_else(|| CliError::GitError(git2::Error::from_str("No working directory")))?; + let convention = Convention::load(repo_path)?; + let mut allowed_types = convention.allowed_branch_types(); + if allowed_types.is_empty() { + allowed_types = Convention::from_config(Default::default())?.allowed_branch_types(); + } + let merged = MergedConfig::load(repo_path).map_err(|e| CliError::Generic(e.to_string()))?; + let rules = merged + .repository_config() + .map(|config| config.branch_rules.clone()) + .unwrap_or_default(); + + let Some(plan) = self.build_plan(non_interactive, &allowed_types, &rules)? else { return Ok(()); }; git::validate_branch_name(&plan.branch_name)?; @@ -98,6 +117,7 @@ impl Command for BranchCommand { } let output = BranchCommandOutput { + api_version: API_VERSION, command: "branch".into(), ok: errors.is_empty(), dry_run: self.dry_run, @@ -157,10 +177,22 @@ impl Command for BranchCommand { Ok(()) } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "branch", + dry_run: self.dry_run, + }) + } } impl BranchCommand { - fn build_plan(&self, non_interactive: bool) -> Result, CliError> { + fn build_plan( + &self, + non_interactive: bool, + allowed_types: &[String], + rules: &BranchRulesConfig, + ) -> Result, CliError> { if self.name.is_some() && (self.branch_type.is_some() || self.ticket.is_some() || self.subject.is_some()) { @@ -171,7 +203,16 @@ impl BranchCommand { } if let Some(name) = &self.name { - let (branch_type, ticket, subject) = parse_branch_name(name); + let (branch_type, ticket, subject) = parse_branch_name(name, rules)?; + if rules.enforce_explicit_names { + validate_branch_type(&branch_type, allowed_types)?; + validate_ticket(&ticket, rules)?; + if subject.is_empty() { + return Err(CliError::InputError( + "Explicit branch name must include a subject".to_string(), + )); + } + } return Ok(Some(BranchPlan { branch_name: name.clone(), branch_type, @@ -182,7 +223,7 @@ impl BranchCommand { } if self.branch_type.is_some() || self.ticket.is_some() || self.subject.is_some() { - return self.build_structured_plan(non_interactive); + return self.build_structured_plan(non_interactive, allowed_types, rules); } if non_interactive { @@ -192,8 +233,9 @@ impl BranchCommand { )); } - let branch_type = input::select_branch_type()?; + let branch_type = input::select_branch_type_from(allowed_types)?; let ticket = input::input_ticket()?; + validate_ticket(&ticket, rules)?; let subject = input::input_subject()?; let branch_name = if ticket.is_empty() { @@ -221,10 +263,15 @@ impl BranchCommand { })) } - fn build_structured_plan(&self, non_interactive: bool) -> Result, CliError> { + fn build_structured_plan( + &self, + non_interactive: bool, + allowed_types: &[String], + rules: &BranchRulesConfig, + ) -> Result, CliError> { let branch_type = match &self.branch_type { - Some(branch_type) => validate_branch_type(branch_type)?, - None if !non_interactive => input::select_branch_type()?, + Some(branch_type) => validate_branch_type(branch_type, allowed_types)?, + None if !non_interactive => input::select_branch_type_from(allowed_types)?, None => { return Err(CliError::InputError( "Branch type is required when using structured branch flags in non-interactive mode" @@ -238,6 +285,7 @@ impl BranchCommand { None if !non_interactive => input::input_ticket()?, None => String::new(), }; + validate_ticket(&ticket, rules)?; let subject = match &self.subject { Some(subject) => validate_structured_section(subject, "subject")?, @@ -298,14 +346,37 @@ fn build_branch_name(branch_type: &str, ticket: &str, subject: &str) -> String { } } -fn validate_branch_type(branch_type: &str) -> Result { - if BRANCH_TYPES.iter().any(|known| known == &branch_type) { +fn validate_branch_type(branch_type: &str, allowed_types: &[String]) -> Result { + if allowed_types.iter().any(|known| known == branch_type) { Ok(branch_type.to_string()) } else { Err(CliError::InputError(format!( "Invalid branch type '{}'. Valid branch types are: {}", branch_type, - BRANCH_TYPES.join(", ") + allowed_types.join(", ") + ))) + } +} + +fn validate_ticket(ticket: &str, rules: &BranchRulesConfig) -> Result<(), CliError> { + if ticket.is_empty() { + return if rules.require_ticket { + Err(CliError::InputError( + "A ticket is required by branch_rules".to_string(), + )) + } else { + Ok(()) + }; + } + + let pattern = regex::Regex::new(&rules.ticket_pattern) + .map_err(|error| CliError::RegexError(error.to_string()))?; + if pattern.is_match(ticket) { + Ok(()) + } else { + Err(CliError::InputError(format!( + "Ticket '{}' does not match branch_rules.ticket_pattern '{}'", + ticket, rules.ticket_pattern ))) } } @@ -316,10 +387,24 @@ fn validate_structured_section(value: &str, field_name: &str) -> Result (String, String, String) { - let mut branch_parts = name.splitn(2, '-'); +fn parse_branch_name( + name: &str, + rules: &BranchRulesConfig, +) -> Result<(String, String, String), CliError> { + let mut branch_parts = name.splitn(3, '-'); let branch_type = branch_parts.next().unwrap_or("unknown").to_string(); - let ticket = String::new(); - let subject = branch_parts.next().unwrap_or("").to_string(); - (branch_type, ticket, subject) + let second = branch_parts.next().unwrap_or(""); + let third = branch_parts.next(); + let ticket_pattern = regex::Regex::new(&rules.ticket_pattern) + .map_err(|error| CliError::RegexError(error.to_string()))?; + let (ticket, subject) = if let Some(subject) = third { + if rules.require_ticket || ticket_pattern.is_match(second) { + (second.to_string(), subject.to_string()) + } else { + (String::new(), format!("{second}-{subject}")) + } + } else { + (String::new(), second.to_string()) + }; + Ok((branch_type, ticket, subject)) } diff --git a/src/cli/commands/commit.rs b/src/cli/commands/commit.rs index e192f58..fb87feb 100644 --- a/src/cli/commands/commit.rs +++ b/src/cli/commands/commit.rs @@ -2,6 +2,7 @@ use serde_json::Value; use std::collections::HashMap; use std::path::{Path, PathBuf}; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::config::hierarchy::MergedConfig; use crate::config::repository::RepositoryConfig; @@ -63,6 +64,7 @@ pub struct CommitCommand { #[derive(Debug, Serialize)] struct CommitCommandOutput { + api_version: u8, command: String, ok: bool, dry_run: bool, @@ -79,6 +81,13 @@ impl Command for CommitCommand { fn execute(&self, non_interactive: bool) -> Result<(), CliError> { self.execute_with_command_name(non_interactive, "commit") } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "commit", + dry_run: self.dry_run, + }) + } } impl CommitCommand { @@ -280,6 +289,7 @@ impl CommitCommand { .map_err(|e| CliError::Generic(e.to_string()))?; if !validation_issues.is_empty() { let output = CommitCommandOutput { + api_version: API_VERSION, command: command_name.into(), ok: false, dry_run: self.dry_run, @@ -303,6 +313,7 @@ impl CommitCommand { self.execute_workflow_if_configured(repo_path, &full_message, !self.dry_run)?; let output = CommitCommandOutput { + api_version: API_VERSION, command: command_name.into(), ok: true, dry_run: self.dry_run, diff --git a/src/cli/commands/config.rs b/src/cli/commands/config.rs index 696020b..9b932bb 100644 --- a/src/cli/commands/config.rs +++ b/src/cli/commands/config.rs @@ -455,6 +455,7 @@ pub fn scaffold(repo_path: &Path, dry_run: bool, output: &str) -> Result<()> { dependencies: vec![], scopes: Default::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: Default::default(), git: Default::default(), convention: Some(Default::default()), release: Some(Default::default()), @@ -547,6 +548,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/cli/commands/group_commit.rs b/src/cli/commands/group_commit.rs index 7284284..c13e869 100644 --- a/src/cli/commands/group_commit.rs +++ b/src/cli/commands/group_commit.rs @@ -1,4 +1,5 @@ use crate::ai::{AiCommitSuggestion, LlmClient, LlmError, OllamaClient, OpenRouterClient}; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::error::CliError; use crate::git::format_commit_message; @@ -35,6 +36,7 @@ pub struct PlanGroup { #[derive(Debug, Serialize)] pub struct GroupCommitPlanResult { + pub api_version: u8, pub command: String, pub mode: String, pub ok: bool, @@ -54,6 +56,7 @@ pub struct CommitRecord { #[derive(Debug, Serialize)] pub struct GroupCommitApplyResult { + pub api_version: u8, pub command: String, pub mode: String, pub ok: bool, @@ -508,6 +511,7 @@ impl Command for GroupCommitCommand { .map_err(|e| CliError::Generic(e.to_string()))?; let res = GroupCommitPlanResult { + api_version: API_VERSION, command: "group-commit".into(), mode: "plan".into(), ok: errors.is_empty(), @@ -852,6 +856,7 @@ impl Command for GroupCommitCommand { let ok = commits.iter().all(|c| c.ok) && errors.is_empty(); let res = GroupCommitApplyResult { + api_version: API_VERSION, command: "group-commit".into(), mode: "apply".into(), ok, @@ -875,6 +880,13 @@ impl Command for GroupCommitCommand { _ => Err(CliError::Generic("invalid mode".into())), } } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "group-commit", + dry_run: self.mode == "plan", + }) + } } fn validate_group_messages( diff --git a/src/cli/commands/hooks.rs b/src/cli/commands/hooks.rs new file mode 100644 index 0000000..1932657 --- /dev/null +++ b/src/cli/commands/hooks.rs @@ -0,0 +1,312 @@ +use crate::cli::output::{MachineContext, API_VERSION}; +use crate::cli::Command; +use crate::error::CliError; +use crate::git; +use crate::linter::{check_message_format_for_repo, CommitIssue, CommitLinter}; +use serde::Serialize; +use std::fs; +use std::io::{self, Read}; +use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; + +const COMMIT_MSG_HOOK: &str = r#"#!/bin/sh +exec committy --non-interactive -q hooks run commit-msg --message-file "$1" --repo-path . +"#; + +const PRE_PUSH_HOOK: &str = r#"#!/bin/sh +exec committy --non-interactive -q hooks run pre-push --repo-path . +"#; + +const GITHUB_WORKFLOW: &str = r#"name: Commit conventions + +on: + pull_request: + +jobs: + lint-commits: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install Committy + run: cargo install committy --locked + - name: Lint pull request commits + run: committy --non-interactive lint --from-ref "${{ github.event.pull_request.base.sha }}" --to-ref "${{ github.sha }}" +"#; + +#[derive(Debug, StructOpt)] +pub struct HooksCommand { + #[structopt(subcommand)] + action: HooksAction, +} + +#[derive(Debug, StructOpt)] +enum HooksAction { + #[structopt(name = "install")] + Install(HooksInstallCommand), + #[structopt(name = "run")] + Run(HooksRunCommand), +} + +#[derive(Debug, StructOpt)] +struct HooksInstallCommand { + #[structopt(long, help = "Also install a GitHub Actions commit lint workflow")] + with_ci: bool, + #[structopt(long, help = "Replace existing hook and workflow files")] + force: bool, + #[structopt(long, help = "Preview files without writing them")] + dry_run: bool, + #[structopt(long, default_value = "json", possible_values = &["text", "json"])] + output: String, + #[structopt(long, default_value = ".", parse(from_os_str))] + repo_path: PathBuf, +} + +#[derive(Debug, StructOpt)] +struct HooksRunCommand { + #[structopt(subcommand)] + hook: HookRunner, +} + +#[derive(Debug, StructOpt)] +enum HookRunner { + #[structopt(name = "commit-msg")] + CommitMsg(CommitMsgHookCommand), + #[structopt(name = "pre-push")] + PrePush(PrePushHookCommand), +} + +#[derive(Debug, StructOpt)] +struct CommitMsgHookCommand { + #[structopt(long = "message-file", parse(from_os_str))] + message_file: PathBuf, + #[structopt(long, default_value = "text", possible_values = &["text", "json"])] + output: String, + #[structopt(long, default_value = ".", parse(from_os_str))] + repo_path: PathBuf, +} + +#[derive(Debug, StructOpt)] +struct PrePushHookCommand { + #[structopt(long, default_value = "text", possible_values = &["text", "json"])] + output: String, + #[structopt(long, default_value = ".", parse(from_os_str))] + repo_path: PathBuf, +} + +#[derive(Debug, Serialize)] +struct HooksInstallOutput { + api_version: u8, + command: &'static str, + ok: bool, + dry_run: bool, + installed: bool, + files: Vec, + errors: Option>, +} + +#[derive(Debug, Serialize)] +struct HookRunOutput<'a, T: Serialize> { + api_version: u8, + command: &'static str, + hook: &'static str, + ok: bool, + dry_run: bool, + count: usize, + issues: &'a [T], + errors: Option>, +} + +impl Command for HooksCommand { + fn execute(&self, _non_interactive: bool) -> Result<(), CliError> { + match &self.action { + HooksAction::Install(command) => command.execute(), + HooksAction::Run(command) => command.execute(), + } + } + + fn machine_context(&self) -> Option { + let (json, dry_run) = match &self.action { + HooksAction::Install(command) => (command.output == "json", command.dry_run), + HooksAction::Run(HooksRunCommand { + hook: HookRunner::CommitMsg(command), + }) => (command.output == "json", false), + HooksAction::Run(HooksRunCommand { + hook: HookRunner::PrePush(command), + }) => (command.output == "json", false), + }; + json.then_some(MachineContext { + command: "hooks", + dry_run, + }) + } +} + +impl HooksInstallCommand { + fn execute(&self) -> Result<(), CliError> { + let repo = git::discover_repository_from(&self.repo_path)?; + let workdir = repo + .workdir() + .ok_or_else(|| CliError::GitError(git2::Error::from_str("No working directory")))?; + let hooks_dir = repo.commondir().join("hooks"); + let mut files = vec![ + (hooks_dir.join("commit-msg"), COMMIT_MSG_HOOK), + (hooks_dir.join("pre-push"), PRE_PUSH_HOOK), + ]; + if self.with_ci { + files.push(( + workdir.join(".github/workflows/committy.yml"), + GITHUB_WORKFLOW, + )); + } + + if !self.force { + if let Some((path, _)) = files.iter().find(|(path, _)| path.exists()) { + return Err(CliError::InputError(format!( + "Refusing to replace existing file {}. Use --force to replace it.", + path.display() + ))); + } + } + + let display_files = files + .iter() + .map(|(path, _)| display_path(workdir, path)) + .collect::>(); + if !self.dry_run { + for (path, contents) in &files { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(path, contents)?; + if path.starts_with(&hooks_dir) { + let mut permissions = fs::metadata(path)?.permissions(); + permissions.set_mode(permissions.mode() | 0o755); + fs::set_permissions(path, permissions)?; + } + } + } + + let payload = HooksInstallOutput { + api_version: API_VERSION, + command: "hooks", + ok: true, + dry_run: self.dry_run, + installed: !self.dry_run, + files: display_files, + errors: None, + }; + if self.output == "json" { + println!("{}", serde_json::to_string(&payload).unwrap()); + } else if self.dry_run { + println!("Would install {} enforcement files", payload.files.len()); + } else { + println!("Installed {} enforcement files", payload.files.len()); + } + Ok(()) + } +} + +impl HooksRunCommand { + fn execute(&self) -> Result<(), CliError> { + match &self.hook { + HookRunner::CommitMsg(command) => command.execute(), + HookRunner::PrePush(command) => command.execute(), + } + } +} + +impl CommitMsgHookCommand { + fn execute(&self) -> Result<(), CliError> { + let message = fs::read_to_string(&self.message_file)?; + let issues = check_message_format_for_repo(&self.repo_path, &message) + .map_err(|error| CliError::Generic(error.to_string()))?; + print_hook_result("commit-msg", &self.output, &issues)?; + if issues.is_empty() { + Ok(()) + } else { + Err(CliError::LintIssues(issues.len())) + } + } +} + +impl PrePushHookCommand { + fn execute(&self) -> Result<(), CliError> { + let mut input = String::new(); + io::stdin().read_to_string(&mut input)?; + let repo_path = self + .repo_path + .to_str() + .ok_or_else(|| CliError::InputError("Invalid repo path".to_string()))?; + let linter = + CommitLinter::new(repo_path).map_err(|error| CliError::Generic(error.to_string()))?; + let mut issues = Vec::::new(); + for line in input.lines() { + let fields = line.split_whitespace().collect::>(); + if fields.len() != 4 { + return Err(CliError::InputError( + "Expected pre-push input: " + .to_string(), + )); + } + let local_sha = fields[1]; + let remote_sha = fields[3]; + if is_zero_oid(local_sha) { + continue; + } + let from_ref = (!is_zero_oid(remote_sha)).then_some(remote_sha); + issues.extend( + linter + .check_rev_range(from_ref, Some(local_sha)) + .map_err(|error| CliError::Generic(error.to_string()))?, + ); + } + print_hook_result("pre-push", &self.output, &issues)?; + if issues.is_empty() { + Ok(()) + } else { + Err(CliError::LintIssues(issues.len())) + } + } +} + +fn print_hook_result( + hook: &'static str, + output: &str, + issues: &[T], +) -> Result<(), CliError> { + if output == "json" { + println!( + "{}", + serde_json::to_string(&HookRunOutput { + api_version: API_VERSION, + command: "hooks", + hook, + ok: issues.is_empty(), + dry_run: false, + count: issues.len(), + issues, + errors: None, + }) + .map_err(|error| CliError::Generic(error.to_string()))? + ); + } else if issues.is_empty() { + println!("{hook} policy passed"); + } else { + eprintln!("{hook} policy found {} issue(s)", issues.len()); + } + Ok(()) +} + +fn display_path(workdir: &Path, path: &Path) -> String { + path.strip_prefix(workdir) + .unwrap_or(path) + .display() + .to_string() +} + +fn is_zero_oid(value: &str) -> bool { + !value.is_empty() && value.bytes().all(|byte| byte == b'0') +} diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index b166a4e..5290978 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -177,6 +177,7 @@ impl Command for InitCommand { mappings: vec![], }, commit_rules: CommitRulesConfig::default(), + branch_rules: Default::default(), git: Default::default(), convention: Some(Default::default()), release: Some(Default::default()), diff --git a/src/cli/commands/lint.rs b/src/cli/commands/lint.rs index 8240257..8ffa48e 100644 --- a/src/cli/commands/lint.rs +++ b/src/cli/commands/lint.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use serde::Serialize; use structopt::StructOpt; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::error::CliError; use crate::linter::{check_message_format_for_repo, CommitIssue, CommitLinter}; @@ -82,6 +83,13 @@ impl Command for LintCommand { Err(CliError::LintIssues(issues.len())) } } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "lint", + dry_run: false, + }) + } } impl LintCommand { @@ -129,6 +137,7 @@ fn print_history_output(output: &str, issues: &[CommitIssue]) -> Result<(), CliE if output == "json" { #[derive(Serialize)] struct LintOutput<'a> { + api_version: u8, command: &'static str, ok: bool, dry_run: bool, @@ -137,6 +146,7 @@ fn print_history_output(output: &str, issues: &[CommitIssue]) -> Result<(), CliE errors: Option>, } let payload = LintOutput { + api_version: API_VERSION, command: "lint", ok: issues.is_empty(), dry_run: false, @@ -165,6 +175,7 @@ fn print_message_output(output: &str, issues: &[String]) -> Result<(), CliError> if output == "json" { #[derive(Serialize)] struct LintMessageOutput<'a> { + api_version: u8, command: &'static str, ok: bool, dry_run: bool, @@ -173,6 +184,7 @@ fn print_message_output(output: &str, issues: &[String]) -> Result<(), CliError> errors: Option>, } let payload = LintMessageOutput { + api_version: API_VERSION, command: "lint", ok: issues.is_empty(), dry_run: false, diff --git a/src/cli/commands/lint_message.rs b/src/cli/commands/lint_message.rs index 880036b..57d7eee 100644 --- a/src/cli/commands/lint_message.rs +++ b/src/cli/commands/lint_message.rs @@ -1,3 +1,4 @@ +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::error::CliError; use crate::linter::check_message_format_for_repo; @@ -51,6 +52,7 @@ impl Command for LintMessageCommand { if self.output == "json" { #[derive(Serialize)] struct LintMessageOutput<'a> { + api_version: u8, command: &'static str, ok: bool, dry_run: bool, @@ -59,6 +61,7 @@ impl Command for LintMessageCommand { errors: Option>, } let payload = LintMessageOutput { + api_version: API_VERSION, command: "lint-message", ok: issues.is_empty(), dry_run: false, @@ -82,4 +85,11 @@ impl Command for LintMessageCommand { Err(CliError::LintIssues(issues.len())) } } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "lint-message", + dry_run: false, + }) + } } diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index 326f6be..6a3b1b4 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -6,6 +6,7 @@ pub mod commit; pub mod config; pub mod example; pub mod group_commit; +pub mod hooks; pub mod info; pub mod init; pub mod lint; diff --git a/src/cli/commands/schema.rs b/src/cli/commands/schema.rs index 9e8c475..492971d 100644 --- a/src/cli/commands/schema.rs +++ b/src/cli/commands/schema.rs @@ -3,7 +3,9 @@ use std::path::PathBuf; use serde::Serialize; use structopt::StructOpt; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; +use crate::config::convention::ConventionType; use crate::error::CliError; use crate::release::ReleaseEngine; @@ -18,6 +20,7 @@ pub struct SchemaCommand { #[derive(Debug, Serialize)] struct SchemaOutput { + api_version: u8, command: &'static str, ok: bool, dry_run: bool, @@ -25,19 +28,79 @@ struct SchemaOutput { schema: String, parser: String, types: Vec, + type_definitions: Vec, + capabilities: Vec, errors: Option>, } +#[derive(Debug, Serialize)] +struct CapabilityDefinition { + name: &'static str, + description: &'static str, +} + +fn capabilities() -> Vec { + vec![ + CapabilityDefinition { + name: "branch.preview", + description: "Resolve and validate a branch without changing git", + }, + CapabilityDefinition { + name: "branch.apply", + description: "Create a validated branch", + }, + CapabilityDefinition { + name: "branch.lint", + description: "Validate structured or policy-enforced branch names", + }, + CapabilityDefinition { + name: "commit.preview", + description: "Resolve and lint a commit without changing git", + }, + CapabilityDefinition { + name: "commit.apply", + description: "Create a validated conventional commit", + }, + CapabilityDefinition { + name: "commit.amend", + description: "Preview or apply a validated amend", + }, + CapabilityDefinition { + name: "commit.lint", + description: "Lint one message or repository history", + }, + CapabilityDefinition { + name: "hooks.install", + description: "Preview or install native Git hooks and CI enforcement", + }, + CapabilityDefinition { + name: "hooks.commit-msg", + description: "Enforce commit messages through a native git hook", + }, + CapabilityDefinition { + name: "hooks.pre-push", + description: "Enforce outgoing commit history through a native git hook", + }, + ] +} + impl Command for SchemaCommand { fn execute(&self, _non_interactive: bool) -> Result<(), CliError> { let engine = ReleaseEngine::load(&self.repo_path, &[])?; let convention = engine.convention().config(); let types = engine.convention().allowed_types(); + let type_definitions = engine + .convention() + .visible_types() + .into_iter() + .cloned() + .collect(); if self.output == "json" { println!( "{}", serde_json::to_string(&SchemaOutput { + api_version: API_VERSION, command: "schema", ok: true, dry_run: false, @@ -45,6 +108,8 @@ impl Command for SchemaCommand { schema: engine.convention().schema().to_string(), parser: engine.convention().parser_pattern().to_string(), types, + type_definitions, + capabilities: capabilities(), errors: None, }) .map_err(|e| CliError::Generic(e.to_string()))? @@ -54,4 +119,11 @@ impl Command for SchemaCommand { } Ok(()) } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "schema", + dry_run: false, + }) + } } diff --git a/src/cli/commands/tag.rs b/src/cli/commands/tag.rs index c402edf..1579b11 100644 --- a/src/cli/commands/tag.rs +++ b/src/cli/commands/tag.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::config::hierarchy::MergedConfig; use crate::config::repository::{RepositoryConfig, VersioningStrategy}; @@ -60,6 +61,7 @@ pub struct TagCommand { #[derive(Debug, Serialize)] struct TagCommandOutput { + api_version: u8, command: String, ok: bool, dry_run: bool, @@ -123,6 +125,7 @@ impl Command for TagCommand { ); version_manager.create_and_push_tag(&version_manager.open_repository()?, name)?; let payload = TagCommandOutput { + api_version: API_VERSION, command: "tag".into(), ok: true, dry_run: self.tag_options.dry_run(), @@ -156,6 +159,7 @@ impl Command for TagCommand { // Print the calculated tag so callers/tests can consume it let payload = TagCommandOutput { + api_version: API_VERSION, command: "tag".into(), ok: true, dry_run: self.tag_options.dry_run(), @@ -187,6 +191,7 @@ impl Command for TagCommand { ); version_manager.run()?; let payload = TagCommandOutput { + api_version: API_VERSION, command: "tag".into(), ok: true, dry_run: self.tag_options.dry_run(), @@ -223,6 +228,13 @@ impl Command for TagCommand { Ok(()) } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "tag", + dry_run: self.tag_options.dry_run(), + }) + } } impl TagCommand { @@ -315,11 +327,9 @@ impl TagCommand { latest_tag: Option<&str>, git_command_config: &git::GitCommandConfig, ) -> Result { - let range = if latest_tag.is_none() { - "HEAD".to_string() - } else { - format!("{}..HEAD", latest_tag.unwrap()) - }; + let range = latest_tag + .map(|tag| format!("{tag}..HEAD")) + .unwrap_or_else(|| "HEAD".to_string()); // Use git log to get commit messages let repo_path = repo @@ -784,6 +794,7 @@ mod tests { ], }, commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2244c58..0385edb 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,7 +1,8 @@ pub mod commands; +pub mod output; use self::commands::{ - amend, branch, bump, changelog, commit, config, example, group_commit, info, init, lint, + amend, branch, bump, changelog, commit, config, example, group_commit, hooks, info, init, lint, lint_message, ls, packages, schema, tag, version, }; use crate::error::CliError; @@ -9,6 +10,10 @@ use structopt::StructOpt; pub trait Command { fn execute(&self, non_interactive: bool) -> Result<(), CliError>; + + fn machine_context(&self) -> Option { + None + } } #[derive(StructOpt)] @@ -41,6 +46,8 @@ pub enum CliCommand { Branch(branch::BranchCommand), #[structopt(about = "Group changes and optionally commit/apply them (with optional AI)")] GroupCommit(group_commit::GroupCommitCommand), + #[structopt(about = "Install and run Committy git enforcement hooks")] + Hooks(hooks::HooksCommand), #[structopt(about = "Initialize multi-package support")] Init(init::InitCommand), #[structopt(about = "Manage repository configuration")] @@ -66,9 +73,25 @@ impl CliCommand { CliCommand::Version(cmd) => cmd.execute(non_interactive), CliCommand::Branch(cmd) => cmd.execute(non_interactive), CliCommand::GroupCommit(cmd) => cmd.execute(non_interactive), + CliCommand::Hooks(cmd) => cmd.execute(non_interactive), CliCommand::Init(cmd) => cmd.execute(non_interactive), CliCommand::Config(cmd) => cmd.execute(non_interactive), CliCommand::Packages(cmd) => cmd.execute(non_interactive), } } + + pub fn machine_context(&self) -> Option { + match self { + CliCommand::Commit(cmd) => cmd.machine_context(), + CliCommand::Amend(cmd) => cmd.machine_context(), + CliCommand::Tag(cmd) => cmd.machine_context(), + CliCommand::Lint(cmd) => cmd.machine_context(), + CliCommand::LintMessage(cmd) => cmd.machine_context(), + CliCommand::Branch(cmd) => cmd.machine_context(), + CliCommand::GroupCommit(cmd) => cmd.machine_context(), + CliCommand::Hooks(cmd) => cmd.machine_context(), + CliCommand::Schema(cmd) => cmd.machine_context(), + _ => None, + } + } } diff --git a/src/cli/output.rs b/src/cli/output.rs new file mode 100644 index 0000000..0fb74c6 --- /dev/null +++ b/src/cli/output.rs @@ -0,0 +1,40 @@ +use crate::error::CliError; +use serde::Serialize; + +pub const API_VERSION: u8 = 1; + +#[derive(Clone, Copy, Debug)] +pub struct MachineContext { + pub command: &'static str, + pub dry_run: bool, +} + +#[derive(Debug, Serialize)] +struct MachineError<'a> { + code: &'static str, + message: &'a str, +} + +#[derive(Debug, Serialize)] +struct ErrorEnvelope<'a> { + api_version: u8, + command: &'static str, + ok: bool, + dry_run: bool, + errors: [MachineError<'a>; 1], +} + +pub fn print_error(context: MachineContext, error: &CliError) { + let message = error.to_string(); + let payload = ErrorEnvelope { + api_version: API_VERSION, + command: context.command, + ok: false, + dry_run: context.dry_run, + errors: [MachineError { + code: error.code(), + message: &message, + }], + }; + println!("{}", serde_json::to_string(&payload).unwrap()); +} diff --git a/src/config.rs b/src/config.rs index 8526eb2..87fd259 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,11 +11,6 @@ pub const COMMIT_TYPES: &[&str] = &[ "test", "security", "config", ]; -pub const BRANCH_TYPES: &[&str] = &[ - "feat", "fix", "refactor", "test", "docs", "perf", "security", "hotfix", "release", "spike", - "tooling", -]; - pub const MAX_SHORT_DESCRIPTION_LENGTH: usize = 150; pub const MAX_TICKET_NAME_LENGTH: usize = 10; pub const MAX_SCOPE_NAME_LENGTH: usize = 15; diff --git a/src/config/convention.rs b/src/config/convention.rs index 61955d4..7ccc0f3 100644 --- a/src/config/convention.rs +++ b/src/config/convention.rs @@ -24,12 +24,21 @@ pub struct ConventionConfig { pub struct ConventionType { pub name: String, pub description: String, + #[serde(default = "default_type_contexts")] + pub contexts: Vec, pub bump: BumpType, pub changelog_section: String, pub aliases: Vec, pub hidden: bool, } +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum ConventionContext { + Commit, + Branch, +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(default)] pub struct ConventionQuestion { @@ -84,6 +93,7 @@ impl Default for ConventionType { Self { name: "chore".to_string(), description: "Other changes that do not modify src or test files".to_string(), + contexts: default_type_contexts(), bump: BumpType::None, changelog_section: "Maintenance".to_string(), aliases: vec![], @@ -92,6 +102,18 @@ impl Default for ConventionType { } } +fn default_type_contexts() -> Vec { + vec![ConventionContext::Commit] +} + +fn commit_and_branch_contexts() -> Vec { + vec![ConventionContext::Commit, ConventionContext::Branch] +} + +fn branch_contexts() -> Vec { + vec![ConventionContext::Branch] +} + impl Default for ConventionQuestion { fn default() -> Self { Self { @@ -173,6 +195,7 @@ pub fn default_types() -> Vec { ConventionType { name: "feat".to_string(), description: "A new feature".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Minor, changelog_section: "Features".to_string(), aliases: vec!["feature".to_string()], @@ -181,6 +204,7 @@ pub fn default_types() -> Vec { ConventionType { name: "fix".to_string(), description: "A bug fix".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Bug Fixes".to_string(), aliases: vec![], @@ -189,6 +213,7 @@ pub fn default_types() -> Vec { ConventionType { name: "docs".to_string(), description: "Documentation only changes".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Documentation".to_string(), aliases: vec![], @@ -197,6 +222,7 @@ pub fn default_types() -> Vec { ConventionType { name: "perf".to_string(), description: "A code change that improves performance".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Performance".to_string(), aliases: vec![], @@ -205,6 +231,7 @@ pub fn default_types() -> Vec { ConventionType { name: "refactor".to_string(), description: "A code change that neither fixes a bug nor adds a feature".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Refactoring".to_string(), aliases: vec![], @@ -213,6 +240,7 @@ pub fn default_types() -> Vec { ConventionType { name: "build".to_string(), description: "Changes that affect the build system".to_string(), + contexts: default_type_contexts(), bump: BumpType::Patch, changelog_section: "Build".to_string(), aliases: vec![], @@ -221,6 +249,7 @@ pub fn default_types() -> Vec { ConventionType { name: "ci".to_string(), description: "CI configuration changes".to_string(), + contexts: default_type_contexts(), bump: BumpType::Patch, changelog_section: "CI".to_string(), aliases: vec![], @@ -229,6 +258,7 @@ pub fn default_types() -> Vec { ConventionType { name: "chore".to_string(), description: "Other changes that do not modify src or test files".to_string(), + contexts: default_type_contexts(), bump: BumpType::None, changelog_section: "Maintenance".to_string(), aliases: vec![], @@ -237,6 +267,7 @@ pub fn default_types() -> Vec { ConventionType { name: "test".to_string(), description: "Adding or fixing tests".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Tests".to_string(), aliases: vec![], @@ -245,10 +276,28 @@ pub fn default_types() -> Vec { ConventionType { name: "style".to_string(), description: "Code style changes".to_string(), + contexts: default_type_contexts(), bump: BumpType::Patch, changelog_section: "Style".to_string(), aliases: vec![], hidden: false, }, + branch_type("security", "Security-focused work"), + branch_type("hotfix", "Urgent production fix"), + branch_type("release", "Release preparation"), + branch_type("spike", "Time-boxed investigation"), + branch_type("tooling", "Developer tooling work"), ] } + +fn branch_type(name: &str, description: &str) -> ConventionType { + ConventionType { + name: name.to_string(), + description: description.to_string(), + contexts: branch_contexts(), + bump: BumpType::None, + changelog_section: "Other".to_string(), + aliases: vec![], + hidden: false, + } +} diff --git a/src/config/hierarchy.rs b/src/config/hierarchy.rs index b3dff66..ae3ba05 100644 --- a/src/config/hierarchy.rs +++ b/src/config/hierarchy.rs @@ -82,11 +82,7 @@ impl MergedConfig { } pub fn effective_convention(&self) -> ConventionConfig { - let mut config = self - .user - .convention - .clone() - .unwrap_or_else(ConventionConfig::default); + let mut config = self.user.convention.clone().unwrap_or_default(); if let Some(repository) = &self.repository { if let Some(repository_convention) = &repository.convention { @@ -159,6 +155,7 @@ fn custom_commit_type_to_convention( super::convention::ConventionType { name: custom.name.clone(), description: custom.description.clone(), + contexts: vec![super::convention::ConventionContext::Commit], bump: custom.bump.clone(), changelog_section: "Custom".to_string(), aliases: vec![], @@ -218,6 +215,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/config/repository.rs b/src/config/repository.rs index 52a50da..e8bab2b 100644 --- a/src/config/repository.rs +++ b/src/config/repository.rs @@ -26,6 +26,8 @@ pub struct RepositoryConfig { #[serde(default)] pub commit_rules: CommitRulesConfig, #[serde(default)] + pub branch_rules: BranchRulesConfig, + #[serde(default)] pub git: GitConfig, #[serde(default)] pub convention: Option, @@ -187,6 +189,27 @@ pub struct CommitRulesConfig { pub custom_types: Vec, } +/// Branch naming rules used by structured branch creation and optional explicit-name enforcement. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BranchRulesConfig { + #[serde(default)] + pub require_ticket: bool, + #[serde(default = "default_ticket_pattern")] + pub ticket_pattern: String, + #[serde(default)] + pub enforce_explicit_names: bool, +} + +impl Default for BranchRulesConfig { + fn default() -> Self { + Self { + require_ticket: false, + ticket_pattern: default_ticket_pattern(), + enforce_explicit_names: false, + } + } +} + impl Default for CommitRulesConfig { fn default() -> Self { Self { @@ -256,6 +279,10 @@ fn default_100() -> usize { 100 } +fn default_ticket_pattern() -> String { + "^[A-Z]+[0-9]+$".to_string() +} + impl RepositoryConfig { /// Load repository configuration from .committy/config.toml pub fn load(repo_path: &Path) -> Result { @@ -300,6 +327,8 @@ impl RepositoryConfig { /// Validate the configuration pub fn validate(&self, repo_path: &Path) -> Result<()> { validate_git_config_overrides(&self.git.config_overrides)?; + regex::Regex::new(&self.branch_rules.ticket_pattern) + .with_context(|| "Invalid branch_rules.ticket_pattern")?; // Validate package names are unique let mut names = HashSet::new(); @@ -547,6 +576,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: BranchRulesConfig::default(), git: Default::default(), convention: None, release: None, @@ -613,6 +643,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: BranchRulesConfig::default(), git: Default::default(), convention: None, release: None, @@ -667,6 +698,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: BranchRulesConfig::default(), git: Default::default(), convention: None, release: None, @@ -727,6 +759,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: BranchRulesConfig::default(), git: Default::default(), convention: None, release: None, diff --git a/src/convention/mod.rs b/src/convention/mod.rs index f06db39..9bb05cc 100644 --- a/src/convention/mod.rs +++ b/src/convention/mod.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::path::Path; -use crate::config::convention::{ConventionConfig, ConventionType}; +use crate::config::convention::{ConventionConfig, ConventionContext, ConventionType}; use crate::config::hierarchy::MergedConfig; use crate::config::repository::BumpType; use crate::error::CliError; @@ -75,8 +75,22 @@ impl Convention { .collect() } - pub fn allowed_types(&self) -> Vec { + pub fn visible_types_for(&self, context: ConventionContext) -> Vec<&ConventionType> { self.visible_types() + .into_iter() + .filter(|item| item.contexts.contains(&context)) + .collect() + } + + pub fn allowed_types(&self) -> Vec { + self.visible_types_for(ConventionContext::Commit) + .into_iter() + .map(|item| item.name.clone()) + .collect() + } + + pub fn allowed_branch_types(&self) -> Vec { + self.visible_types_for(ConventionContext::Branch) .into_iter() .map(|item| item.name.clone()) .collect() @@ -240,7 +254,9 @@ impl Convention { fn type_by_name(&self, commit_type: &str) -> Option<&ConventionType> { let canonical = self.canonical_type(commit_type)?; - self.config.types.iter().find(|item| item.name == canonical) + self.config.types.iter().find(|item| { + item.name == canonical && item.contexts.contains(&ConventionContext::Commit) + }) } } @@ -279,4 +295,17 @@ mod tests { let issues = convention.validate_message("invalid commit"); assert!(!issues.is_empty()); } + + #[test] + fn test_branch_only_type_is_not_a_valid_commit_type() { + let convention = Convention::from_config(ConventionConfig::default()).unwrap(); + let issues = convention.validate_message("spike: investigate agent protocol"); + assert!(issues + .iter() + .any(|issue| issue.starts_with("Commit type must be one of:"))); + assert!(convention + .allowed_branch_types() + .contains(&"spike".to_string())); + assert!(!convention.allowed_types().contains(&"spike".to_string())); + } } diff --git a/src/dependency/updater.rs b/src/dependency/updater.rs index 7882eab..adcce20 100644 --- a/src/dependency/updater.rs +++ b/src/dependency/updater.rs @@ -266,6 +266,7 @@ mod tests { }], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/error.rs b/src/error.rs index a4e63ca..60691a1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,6 +38,31 @@ pub enum CliError { LintIssues(usize), } +impl CliError { + pub fn code(&self) -> &'static str { + match self { + Self::GitError(_) => "git_error", + Self::IoError(_) => "io_error", + Self::InputError(_) => "invalid_input", + Self::NoStagedChanges => "no_staged_changes", + Self::StagedChanges => "staged_changes_present", + Self::GitConfigError(_) => "git_config_error", + Self::Generic(_) => "command_error", + Self::SemVerError(_) => "semver_error", + Self::RegexError(_) => "regex_error", + Self::LintIssues(_) => "lint_issues", + } + } + + pub fn exit_code(&self) -> i32 { + if matches!(self, Self::LintIssues(_)) { + 3 + } else { + 1 + } + } +} + impl From for CliError { fn from(error: clap::Error) -> Self { CliError::Generic(error.to_string()) diff --git a/src/git/process.rs b/src/git/process.rs index d3bf744..86c4645 100644 --- a/src/git/process.rs +++ b/src/git/process.rs @@ -173,6 +173,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: GitConfig { config_overrides: vec!["repo.value=1".to_string(), "shared.value=repo".to_string()], }, diff --git a/src/git/repository.rs b/src/git/repository.rs index 682f274..ef1670f 100644 --- a/src/git/repository.rs +++ b/src/git/repository.rs @@ -12,27 +12,7 @@ pub fn discover_repository_from(path: &Path) -> Result { log::debug!("Starting repository discovery from: {path:?}"); match Repository::discover(path) { - Ok(repo) => { - // Get the absolute path to the repository root - let repo_path = repo - .path() - .parent() - .and_then(|p| p.canonicalize().ok()) - .ok_or_else(|| { - CliError::GitError(git2::Error::from_str( - "Could not determine repository root directory", - )) - })?; - - // Open a new repository instance using the absolute path - match Repository::open(&repo_path) { - Ok(new_repo) => Ok(new_repo), - Err(e) => { - log::error!("Failed to open repository at {repo_path:?}: {e}"); - Err(CliError::GitError(e)) - } - } - } + Ok(repo) => Ok(repo), Err(e) => { log::error!("Failed to discover repository from {path:?}: {e}"); Err(CliError::GitError(git2::Error::from_str( diff --git a/src/input/prompts.rs b/src/input/prompts.rs index b35be68..f161881 100644 --- a/src/input/prompts.rs +++ b/src/input/prompts.rs @@ -2,8 +2,7 @@ use super::validation::{ auto_correct_scope, validate_scope, validate_section, validate_short_message, }; use crate::config::{ - BRANCH_TYPES, COMMIT_TYPES, MAX_SCOPE_NAME_LENGTH, MAX_SHORT_DESCRIPTION_LENGTH, - MAX_TICKET_NAME_LENGTH, + COMMIT_TYPES, MAX_SCOPE_NAME_LENGTH, MAX_SHORT_DESCRIPTION_LENGTH, MAX_TICKET_NAME_LENGTH, }; use crate::error::CliError; use inquire::{Confirm, Select, Text}; @@ -42,13 +41,13 @@ pub fn select_commit_type_from(types: &[String]) -> Result { Ok(commit_type.to_string()) } -pub fn select_branch_type() -> Result { +pub fn select_branch_type_from(types: &[String]) -> Result { if non_interactive_env() { return Err(CliError::InputError( "Non-interactive environment: cannot prompt for branch type".to_string(), )); } - let branch_type = Select::new("Select the type of branch:", BRANCH_TYPES.to_vec()) + let branch_type = Select::new("Select the type of branch:", types.to_vec()) .with_help_message("Use arrow keys to navigate, Enter to select") .prompt() .map_err(|e| CliError::InputError(e.to_string()))?; diff --git a/src/main.rs b/src/main.rs index 4e9f046..d55d427 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,14 +85,12 @@ fn main() { }); if let Err(e) = run(&mut config) { - // Map specific errors to exit codes - if let Some(CliError::LintIssues(_)) = e.downcast_ref::() { - eprintln!("{e}"); - std::process::exit(3); - } else { - eprintln!("{e}"); - std::process::exit(1); - } + eprintln!("{e}"); + let exit_code = e + .downcast_ref::() + .map(CliError::exit_code) + .unwrap_or(1); + std::process::exit(exit_code); } } @@ -224,8 +222,18 @@ fn run(config: &mut Config) -> Result<()> { } } - let result = match opt.cmd { - Some(cmd) => cmd.execute(non_interactive), + let result = match &opt.cmd { + Some(cmd) => { + let result = cmd.execute(non_interactive); + if let Err(error) = &result { + if !matches!(error, CliError::LintIssues(_)) { + if let Some(context) = cmd.machine_context() { + cli::output::print_error(context, error); + } + } + } + result + } None => { let cmd = CommitCommand::default_interactive(); cmd.execute(non_interactive) diff --git a/src/release/changelog.rs b/src/release/changelog.rs index ea0f558..0e56a0c 100644 --- a/src/release/changelog.rs +++ b/src/release/changelog.rs @@ -143,7 +143,7 @@ pub fn render_changelog( sections.push((name.clone(), entries)); } } - sections.extend(grouped.into_iter()); + sections.extend(grouped); let template = if let Some(path) = &config.template_path { fs::read_to_string(path).map_err(CliError::IoError)? diff --git a/src/release/engine.rs b/src/release/engine.rs index e216680..6f97b5a 100644 --- a/src/release/engine.rs +++ b/src/release/engine.rs @@ -299,26 +299,25 @@ impl ReleaseEngine { } } - if plan.publish { - if Repository::open(&self.repo_path) + if plan.publish + && Repository::open(&self.repo_path) .map_err(CliError::from)? .find_remote("origin") .is_ok() - { + { + run_git( + &self.repo_path, + &["push", "origin", "HEAD"], + "push release commit", + &self.git_command_config, + )?; + for tag_name in &plan.tag_names { run_git( &self.repo_path, - &["push", "origin", "HEAD"], - "push release commit", + &["push", "origin", &format!("refs/tags/{tag_name}")], + "push release tag", &self.git_command_config, )?; - for tag_name in &plan.tag_names { - run_git( - &self.repo_path, - &["push", "origin", &format!("refs/tags/{tag_name}")], - "push release tag", - &self.git_command_config, - )?; - } } } diff --git a/src/scope/detector.rs b/src/scope/detector.rs index 952c9df..10e3965 100644 --- a/src/scope/detector.rs +++ b/src/scope/detector.rs @@ -229,6 +229,7 @@ mod tests { dependencies: vec![], scopes, commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/versioning/hybrid.rs b/src/versioning/hybrid.rs index 3cb6200..0f5d0b4 100644 --- a/src/versioning/hybrid.rs +++ b/src/versioning/hybrid.rs @@ -196,6 +196,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/versioning/independent.rs b/src/versioning/independent.rs index f135875..dcdb6f7 100644 --- a/src/versioning/independent.rs +++ b/src/versioning/independent.rs @@ -128,6 +128,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/versioning/unified.rs b/src/versioning/unified.rs index cf63fd8..b420d78 100644 --- a/src/versioning/unified.rs +++ b/src/versioning/unified.rs @@ -123,6 +123,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/workflow/orchestrator.rs b/src/workflow/orchestrator.rs index 6ef6201..43997ed 100644 --- a/src/workflow/orchestrator.rs +++ b/src/workflow/orchestrator.rs @@ -316,6 +316,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/tests/agent_cli_tests.rs b/tests/agent_cli_tests.rs index 0560a1c..d411707 100644 --- a/tests/agent_cli_tests.rs +++ b/tests/agent_cli_tests.rs @@ -71,6 +71,53 @@ allow_multiple_scopes = false .expect("Failed to write commit rules config"); } +fn write_branch_rules_config(dir: &std::path::Path, body: &str) { + fs::create_dir_all(dir.join(".committy")).expect("Failed to create .committy"); + fs::write( + dir.join(".committy/config.toml"), + format!( + r#"packages = [] + +[repository] +name = "agent-repo" +type = "single-package" + +[versioning] +strategy = "independent" + +[branch_rules] +{body} +"# + ), + ) + .expect("Failed to write branch rules config"); +} + +fn write_legacy_convention_config(dir: &std::path::Path) { + fs::create_dir_all(dir.join(".committy")).expect("Failed to create .committy"); + fs::write( + dir.join(".committy/config.toml"), + r#"packages = [] + +[repository] +name = "agent-repo" +type = "single-package" + +[versioning] +strategy = "independent" + +[convention] + +[[convention.types]] +name = "wip" +description = "Legacy custom commit type" +bump = "none" +changelog_section = "Custom" +"#, + ) + .expect("Failed to write legacy convention config"); +} + #[test] fn test_branch_dry_run_json_outputs_plan() { let temp_dir = setup_repo(); @@ -147,6 +194,141 @@ fn test_branch_structured_dry_run_json_outputs_plan() { assert_eq!(v["would_checkout"], Value::Bool(false)); } +#[test] +fn test_branch_invalid_type_json_outputs_one_versioned_error_document() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--type") + .arg("unknown") + .arg("--subject") + .arg("agent-flow") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + + let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let mut documents = serde_json::Deserializer::from_str(&stdout).into_iter::(); + let payload = documents.next().unwrap().unwrap(); + assert!( + documents.next().is_none(), + "stdout must contain one JSON document" + ); + + assert_eq!(payload["api_version"], Value::from(1)); + assert_eq!(payload["command"], Value::String("branch".into())); + assert_eq!(payload["ok"], Value::Bool(false)); + assert_eq!(payload["dry_run"], Value::Bool(true)); + assert_eq!( + payload["errors"][0]["code"], + Value::String("invalid_input".into()) + ); + assert!(payload["errors"][0]["message"] + .as_str() + .unwrap() + .contains("Invalid branch type")); +} + +#[test] +fn test_branch_rules_require_matching_ticket_for_structured_branches() { + let temp_dir = setup_repo(); + write_branch_rules_config( + temp_dir.path(), + r#"require_ticket = true +ticket_pattern = "^ENG[0-9]+$""#, + ); + + let missing = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--type") + .arg("feat") + .arg("--subject") + .arg("agent-flow") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + let payload: Value = serde_json::from_slice(&missing.get_output().stdout).unwrap(); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); + assert!(payload["errors"][0]["message"] + .as_str() + .unwrap() + .contains("ticket is required")); + + let valid = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--type") + .arg("feat") + .arg("--ticket") + .arg("ENG42") + .arg("--subject") + .arg("agent-flow") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .success(); + let payload: Value = serde_json::from_slice(&valid.get_output().stdout).unwrap(); + assert_eq!(payload["branch_name"], "feat-ENG42-agent_flow"); +} + +#[test] +fn test_branch_rules_can_enforce_explicit_names() { + let temp_dir = setup_repo(); + write_branch_rules_config( + temp_dir.path(), + r#"require_ticket = true +ticket_pattern = "^ENG[0-9]+$" +enforce_explicit_names = true"#, + ); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--name") + .arg("wildly-nonstandard") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); +} + +#[test] +fn test_structured_branch_uses_builtins_for_legacy_commit_only_convention() { + let temp_dir = setup_repo(); + write_legacy_convention_config(temp_dir.path()); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--type") + .arg("feat") + .arg("--subject") + .arg("compatible") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .success(); + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["branch_name"], "feat-compatible"); +} + #[test] fn test_branch_structured_flags_conflict_with_name() { let temp_dir = setup_repo(); @@ -193,6 +375,47 @@ fn test_branch_dry_run_json_supports_repo_path_without_chdir() { assert_eq!(v["branch_name"], Value::String("feat-agent-plan".into())); } +#[test] +fn test_branch_dry_run_json_supports_linked_worktree() { + let temp_dir = setup_repo(); + let worktree_parent = tempdir().expect("Failed to create worktree parent"); + let worktree_path = worktree_parent.path().join("linked-worktree"); + + let output = StdCommand::new("git") + .args(["worktree", "add", "--detach"]) + .arg(&worktree_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to create linked worktree"); + assert!( + output.status.success(), + "git worktree add failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let assert = common::committy_cmd() + .current_dir(&worktree_path) + .arg("--non-interactive") + .arg("branch") + .arg("--repo-path") + .arg(".") + .arg("--name") + .arg("feat-linked-worktree") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .success(); + + let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let payload: Value = serde_json::from_str(stdout.trim()).unwrap(); + assert_eq!(payload["ok"], Value::Bool(true)); + assert_eq!( + payload["branch_name"], + Value::String("feat-linked-worktree".into()) + ); +} + #[test] fn test_commit_dry_run_json_does_not_create_commit() { let temp_dir = setup_repo(); @@ -228,6 +451,7 @@ fn test_commit_dry_run_json_does_not_create_commit() { let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); let v: Value = serde_json::from_str(output.trim()).unwrap(); + assert_eq!(v["api_version"], Value::from(1)); assert_eq!(v["command"], Value::String("commit".into())); assert_eq!(v["ok"], Value::Bool(true)); assert_eq!(v["dry_run"], Value::Bool(true)); @@ -248,6 +472,79 @@ fn test_commit_dry_run_json_does_not_create_commit() { ); } +#[test] +fn test_commit_json_reports_no_staged_changes() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("commit") + .arg("--type") + .arg("feat") + .arg("--message") + .arg("agent contract") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["api_version"], Value::from(1)); + assert_eq!(payload["command"], "commit"); + assert_eq!(payload["errors"][0]["code"], "no_staged_changes"); +} + +#[test] +fn test_commit_json_reports_missing_non_interactive_fields() { + let temp_dir = setup_repo(); + fs::write(temp_dir.path().join("tracked.txt"), "changed\n").unwrap(); + StdCommand::new("git") + .args(["add", "tracked.txt"]) + .current_dir(&temp_dir) + .output() + .unwrap(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("commit") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); + assert!(payload["errors"][0]["message"] + .as_str() + .unwrap() + .contains("--type and --message are required")); +} + +#[test] +fn test_lint_json_reports_invalid_rev_range() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("lint") + .arg("--rev-range") + .arg("HEAD") + .arg("--output") + .arg("json") + .assert() + .code(1); + + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["api_version"], Value::from(1)); + assert_eq!(payload["command"], "lint"); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); +} + #[test] fn test_commit_dry_run_json_supports_repo_path_without_chdir() { let temp_dir = setup_repo(); @@ -369,19 +666,26 @@ bump = "none""#, fn test_tag_publish_requires_confirmation() { let temp_dir = setup_repo(); - common::committy_cmd() + let assert = common::committy_cmd() .current_dir(&temp_dir) .arg("--non-interactive") .arg("tag") .arg("--name") .arg("v1.2.3") .arg("--publish") + .arg("--output") + .arg("json") .assert() - .failure() + .code(1) .stderr(predicates::str::contains( "Publishing a tag requires --confirm-publish", )); + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["api_version"], Value::from(1)); + assert_eq!(payload["command"], "tag"); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); + let tags = StdCommand::new("git") .args(["tag", "--list", "v1.2.3"]) .current_dir(&temp_dir) diff --git a/tests/cli_surface_tests.rs b/tests/cli_surface_tests.rs index 0ac8d47..e22cad4 100644 --- a/tests/cli_surface_tests.rs +++ b/tests/cli_surface_tests.rs @@ -203,10 +203,32 @@ fn test_schema_example_info_ls_and_version_json() { .success(), ); assert_eq!(schema["command"], Value::String("schema".into())); + assert_eq!(schema["api_version"], Value::from(1)); assert_eq!( schema["convention"], Value::String("conventional-commits".into()) ); + assert!(schema["types"] + .as_array() + .unwrap() + .contains(&Value::from("feat"))); + let feat = schema["type_definitions"] + .as_array() + .unwrap() + .iter() + .find(|item| item["name"] == "feat") + .unwrap(); + assert_eq!(feat["description"], "A new feature"); + assert!(feat["contexts"] + .as_array() + .unwrap() + .contains(&Value::from("branch"))); + assert!(schema["capabilities"] + .as_array() + .unwrap() + .iter() + .any(|item| item["name"] == "branch.preview" + && item["description"].as_str().unwrap().contains("without"))); let example = parse_stdout( common::committy_cmd() diff --git a/tests/hooks_cli_tests.rs b/tests/hooks_cli_tests.rs new file mode 100644 index 0000000..0041b8f --- /dev/null +++ b/tests/hooks_cli_tests.rs @@ -0,0 +1,156 @@ +mod common; + +use serde_json::Value; +use std::fs; +use std::os::unix::fs::PermissionsExt; +use std::process::Command as StdCommand; +use tempfile::tempdir; + +fn setup_repo() -> tempfile::TempDir { + common::setup_test_env(); + let dir = tempdir().unwrap(); + StdCommand::new("git") + .arg("init") + .current_dir(&dir) + .output() + .unwrap(); + dir +} + +#[test] +fn hooks_install_dry_run_lists_files_without_writing() { + let repo = setup_repo(); + let assert = common::committy_cmd() + .current_dir(&repo) + .args([ + "--non-interactive", + "hooks", + "install", + "--with-ci", + "--dry-run", + "--output", + "json", + ]) + .assert() + .success(); + + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["api_version"], 1); + assert_eq!(payload["command"], "hooks"); + assert_eq!(payload["dry_run"], true); + assert_eq!(payload["installed"], false); + assert_eq!(payload["files"].as_array().unwrap().len(), 3); + assert!(!repo.path().join(".git/hooks/commit-msg").exists()); + assert!(!repo.path().join(".github/workflows/committy.yml").exists()); +} + +#[test] +fn hooks_install_writes_executable_hooks_and_ci_workflow() { + let repo = setup_repo(); + common::committy_cmd() + .current_dir(&repo) + .args([ + "--non-interactive", + "hooks", + "install", + "--with-ci", + "--output", + "json", + ]) + .assert() + .success(); + + let commit_msg = repo.path().join(".git/hooks/commit-msg"); + let pre_push = repo.path().join(".git/hooks/pre-push"); + let workflow = repo.path().join(".github/workflows/committy.yml"); + assert!(fs::read_to_string(&commit_msg) + .unwrap() + .contains("hooks run commit-msg")); + assert!(fs::read_to_string(&pre_push) + .unwrap() + .contains("hooks run pre-push")); + assert!(fs::read_to_string(workflow) + .unwrap() + .contains("committy --non-interactive lint")); + assert_ne!( + fs::metadata(commit_msg).unwrap().permissions().mode() & 0o111, + 0 + ); +} + +#[test] +fn hooks_run_commit_msg_preserves_lint_exit_code() { + let repo = setup_repo(); + let message = repo.path().join("COMMIT_EDITMSG"); + fs::write(&message, "not conventional\n").unwrap(); + + common::committy_cmd() + .current_dir(&repo) + .arg("--non-interactive") + .arg("hooks") + .arg("run") + .arg("commit-msg") + .arg("--message-file") + .arg(&message) + .arg("--output") + .arg("json") + .assert() + .code(3); +} + +#[test] +fn hooks_run_pre_push_lints_outgoing_commits() { + let repo = setup_repo(); + for (key, value) in [ + ("user.name", "Test User"), + ("user.email", "test@example.com"), + ] { + StdCommand::new("git") + .args(["config", key, value]) + .current_dir(&repo) + .output() + .unwrap(); + } + fs::write(repo.path().join("tracked.txt"), "content\n").unwrap(); + StdCommand::new("git") + .args(["add", "tracked.txt"]) + .current_dir(&repo) + .output() + .unwrap(); + StdCommand::new("git") + .args(["commit", "-m", "not conventional", "--no-verify"]) + .current_dir(&repo) + .output() + .unwrap(); + let sha = String::from_utf8( + StdCommand::new("git") + .args(["rev-parse", "HEAD"]) + .current_dir(&repo) + .output() + .unwrap() + .stdout, + ) + .unwrap(); + let input = format!( + "refs/heads/test {} refs/heads/test {}\n", + sha.trim(), + "0".repeat(40) + ); + + let assert = common::committy_cmd() + .current_dir(&repo) + .args([ + "--non-interactive", + "hooks", + "run", + "pre-push", + "--output", + "json", + ]) + .write_stdin(input) + .assert() + .code(3); + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["hook"], "pre-push"); + assert!(payload["count"].as_u64().unwrap() > 0); +} From b35b003c3fb44db8532134767b815110345b77db Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 2 Aug 2026 15:33:39 +0200 Subject: [PATCH 06/24] fix(hooks): support Windows release builds --- Cargo.toml | 2 +- docs/package.json | 2 +- src/cli/commands/hooks.rs | 18 +++++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 190b556..4f53697 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "committy" -version = "1.8.0" +version = "1.9.0" authors = ["Arnaud (Martient) Leherpeur "] edition = "2021" description = "๐Ÿš€ Generate clear, concise, and structured commit messages effortlessly" diff --git a/docs/package.json b/docs/package.json index 3345cf2..1109a5f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,5 +13,5 @@ "start": "astro dev" }, "type": "module", - "version": "0.1.2" + "version": "0.2.0" } diff --git a/src/cli/commands/hooks.rs b/src/cli/commands/hooks.rs index 1932657..f634279 100644 --- a/src/cli/commands/hooks.rs +++ b/src/cli/commands/hooks.rs @@ -6,6 +6,7 @@ use crate::linter::{check_message_format_for_repo, CommitIssue, CommitLinter}; use serde::Serialize; use std::fs; use std::io::{self, Read}; +#[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use structopt::StructOpt; @@ -182,9 +183,7 @@ impl HooksInstallCommand { } fs::write(path, contents)?; if path.starts_with(&hooks_dir) { - let mut permissions = fs::metadata(path)?.permissions(); - permissions.set_mode(permissions.mode() | 0o755); - fs::set_permissions(path, permissions)?; + set_hook_executable(path)?; } } } @@ -209,6 +208,19 @@ impl HooksInstallCommand { } } +#[cfg(unix)] +fn set_hook_executable(path: &Path) -> Result<(), CliError> { + let mut permissions = fs::metadata(path)?.permissions(); + permissions.set_mode(permissions.mode() | 0o755); + fs::set_permissions(path, permissions)?; + Ok(()) +} + +#[cfg(not(unix))] +fn set_hook_executable(_path: &Path) -> Result<(), CliError> { + Ok(()) +} + impl HooksRunCommand { fn execute(&self) -> Result<(), CliError> { match &self.hook { From caf53e3e6803cabad3c719d41bc490a412f5235c Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 2 Aug 2026 15:33:46 +0200 Subject: [PATCH 07/24] chore: bump version to 1.9.1 --- CHANGELOG.md | 5 +++++ Cargo.toml | 2 +- docs/package.json | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..fa62fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.9.1 - 2026-08-02 +_Since v1.9.0_ + +### Bug Fixes +- **hooks:** support Windows release builds (b35b003) diff --git a/Cargo.toml b/Cargo.toml index 4f53697..41c5663 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "committy" -version = "1.9.0" +version = "1.9.1" authors = ["Arnaud (Martient) Leherpeur "] edition = "2021" description = "๐Ÿš€ Generate clear, concise, and structured commit messages effortlessly" diff --git a/docs/package.json b/docs/package.json index 1109a5f..021f914 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,5 +13,5 @@ "start": "astro dev" }, "type": "module", - "version": "0.2.0" + "version": "0.2.1" } From a603feaae60996cc274047a69f52533acf77399c Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 20 Sep 2026 12:44:05 +0200 Subject: [PATCH 08/24] docs(agents): add September 2026 agent compatibility audit Audit the repository's agent-facing surfaces (AGENTS.md/CLAUDE.md, the shared Codex/Claude plugin and skills, the schema capability contract, generated hooks/CI, and the src/ai LLM integration) against the current state of the agent-integration ecosystem. Records 15 prioritised findings covering defects, compatibility gaps and removals, plus two explicit non-recommendations. --- docs/audits/agent-compatibility-2026-09.md | 237 +++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 docs/audits/agent-compatibility-2026-09.md diff --git a/docs/audits/agent-compatibility-2026-09.md b/docs/audits/agent-compatibility-2026-09.md new file mode 100644 index 0000000..680f84e --- /dev/null +++ b/docs/audits/agent-compatibility-2026-09.md @@ -0,0 +1,237 @@ +# Agent compatibility & AI capability audit โ€” September 2026 + +Scope: everything in this repository that an AI coding agent touches โ€” the agent +instruction files, the shared Codex/Claude plugin, the machine-readable CLI +contract (`--output json` / `schema`), the generated enforcement (hooks + CI), +and the in-process LLM integration under `src/ai/`. + +Method: full read of the agent-facing surfaces at `caf53e3` (v1.9.1), compared +against the state of the agent-integration ecosystem as of September 2026. + +--- + +## 1. What exists today + +| Surface | Location | State | +|---|---|---| +| Agent instructions | `AGENTS.md`, `CLAUDE.md` | Two near-identical files (diff is 2 lines) | +| Claude marketplace | `.claude-plugin/marketplace.json` | Valid, points at `./plugins/committy` | +| Codex marketplace | `.agents/plugins/marketplace.json` | Valid, local source | +| Plugin manifests | `plugins/committy/.claude-plugin/plugin.json`, `.codex-plugin/plugin.json` | Skills only; no commands/agents/hooks/mcpServers | +| Skills | 4 ร— `SKILL.md` (branch, commit, release, enforce) | Well-scoped, progressive disclosure, safety rules present | +| Codex interface shims | 4 ร— `agents/openai.yaml` | Display metadata only | +| Machine contract | `src/cli/output.rs` (`api_version = 1`) + per-command payloads | Stable envelope: `api_version`, `command`, `ok`, `dry_run`, `errors` | +| Capability discovery | `src/cli/commands/schema.rs:42-84` | 10 hard-coded capabilities | +| Repo enforcement | `src/cli/commands/hooks.rs:14-37` | `commit-msg`, `pre-push`, GitHub Actions template | +| Local agent guardrail | `.claude/settings.json` + `.claude/hooks/block-dangerous-git.sh` | PreToolUse Bash denylist | +| LLM integration | `src/ai/mod.rs`, `src/cli/commands/group_commit.rs:98-151, 356-507` | OpenRouter + Ollama, `group-commit` only | + +The core architectural bet โ€” **CLI-first with typed JSON, not an MCP server** โ€” +is the right one and has been validated by the wider ecosystem this year (see ยง2). +Nothing below asks you to change that. + +--- + +## 2. What changed in the space (relevant deltas) + +1. **`AGENTS.md` is now the cross-tool standard.** Stewarded by the Agentic AI + Foundation (Linux Foundation), read by 30+ agents (Codex, Copilot, Cursor, + Gemini CLI, Jules, Aider, Zed, Windsurf, Devin), 60k+ repositories. Claude Code + remains the outlier reading `CLAUDE.md`; the accepted pattern is a one-line + `@AGENTS.md` import rather than a maintained duplicate. +2. **Agent Skills has a published spec.** Frontmatter keys are fixed to + `name`, `description`, `license`, `allowed-tools`, `metadata`, `compatibility`; + unknown keys fail validation. `allowed-tools` is the security-relevant addition. +3. **`.agents/skills/` is the cross-tool skill discovery path.** Codex CLI, the + Codex app, Claude Code and Copilot CLI all resolve it. Skills are now the most + portable plugin component; per-vendor interface files are the least portable. +4. **Codex shipped a plugin marketplace** (26 Mar 2026) bundling skills, MCP + servers and app integrations; self-serve publishing to the official directory + is still "coming soon" as of mid-2026. +5. **Claude Code plugins now carry more than skills**: `commands`, `agents`, + `hooks`, `mcpServers`, `outputStyles`, `lspServers`, auto-discovered from + standard directories. +6. **CLI beat MCP on token cost for known-interface tools** (roughly 4โ€“32ร— cheaper; + a naive GitHub MCP server costs ~55k tokens of context before doing anything). + The 2026 consensus is hybrid: CLI for tools the model already understands, MCP + only where you need auth, statefulness, or governance. +7. **Ollama structured output moved to JSON-Schema-constrained decoding** via a + **top-level** `format` field; `/api/chat` still defaults to `stream: true`. + +--- + +## 3. Findings + +Severity: **P0** = broken or misleading in the field ยท **P1** = real compatibility +or maintenance cost ยท **P2** = polish. + +### 3.1 Defects to fix + +**P0-1 โ€” The Ollama provider cannot work as written.** +`src/ai/mod.rs:160-205` posts to `/api/chat` without `stream: false`. Ollama +defaults to `stream: true`, so the response is newline-delimited JSON events, and +`resp.json::()` will fail to deserialize on essentially every call. +Every `--ai-provider ollama` run therefore ends in `LlmError::Parse` and silently +falls back to the default message. Fix: send `stream: false`. + +**P0-2 โ€” Ollama JSON mode is a no-op.** +`format` is nested inside `options` (`src/ai/mod.rs:174-196`). Ollama reads +`format` as a **top-level** request field, so JSON mode has never taken effect. +Fix: hoist `format` to the request body, and prefer passing the +`AiCommitSuggestion` JSON Schema rather than the string `"json"` โ€” Ollama has +supported schema-constrained decoding since 0.5. + +**P0-3 โ€” AI in default (safe) mode is given no signal at all.** +`src/cli/commands/group_commit.rs:400-413`: unless `--ai-allow-sensitive` is set, +the user prompt contains only the group name and the default type/short, and +instructs the model to work "without revealing code or filenames". The model is +asked to improve a description it cannot see the basis for. In practice this +either reproduces the default or produces a lint failure. Fix: send redacted but +real signal by default โ€” path *shapes* (extension histogram, directory prefixes, +add/modify/delete counts, scope mappings from `.committy/config.toml`) โ€” none of +which is file content. Reserve `--ai-allow-sensitive` for actual diff hunks. + +**P0-4 โ€” `--ai` is silently ignored in `--mode apply`.** +The AI block lives only in the `"plan"` arm (`group_commit.rs:308-507`); the +`"apply"` arm (`:533+`) rebuilds groups from scratch with no LLM call. +`docs/src/content/docs/reference/ai-flags.mdx` documents +`--mode apply ... --ai --ai-provider ollama` as a working example. Either wire +apply to reuse the planned messages, or reject `--ai` with `--mode apply` and fix +the doc. Silently dropping a flag is the worst of the three options. + +**P1-5 โ€” `--ai-diff-lines-per-file` is a dead flag.** +Declared at `group_commit.rs:143-147` as `_ai_diff_lines_per_file` and never read. +It is documented in `ai-flags.mdx` and accepted on the command line. Remove it, or +implement it as part of P0-3. + +**P1-6 โ€” `ai-flags.mdx` overstates what is sent and understates what is not.** +It claims `--ai-allow-sensitive` "may include snippets or diff lines from code" โ€” +no diff is ever sent, in either mode. It also recommends +`--ai-model openrouter/anthropic/claude-3.5-sonnet`, a model generation that is two +families out of date. Both should be corrected. + +### 3.2 Compatibility gaps to close + +**P1-7 โ€” `capabilities` in `schema` is stale and undersells the CLI.** +`schema.rs:42-84` advertises 10 capabilities covering branch, commit and hooks +only. Absent: `release.plan` / `tag.preview` / `tag.apply` / `tag.publish`, +`changelog.preview`, `bump.preview` / `bump.apply`, `group-commit.plan` / +`group-commit.apply`, `packages.*`, `config.validate`, `init`. The +`committy-release` and `committy-enforce` skills already instruct agents to run +`bump`, `tag`, `changelog` and `config validate` โ€” commands the capability list +denies exist. `agent-workflows.mdx` promises capabilities exist precisely "so +skills do not need to hard-code the command surface"; today they must. This is the +single highest-leverage fix in the report: it is the contract every skill reads +first. + +Two sub-improvements while you are in there: give each capability a `mutating: +bool` and `requires_confirmation: bool` field so an agent can reason about consent +without pattern-matching command names, and consider deriving the list from the +command registry so it cannot drift again. + +**P1-8 โ€” `AGENTS.md` and `CLAUDE.md` are maintained duplicates.** +The diff is two lines. This will drift. Make `AGENTS.md` canonical and reduce +`CLAUDE.md` to an `@AGENTS.md` import plus the Claude-specific skill-invocation +line โ€” the pattern the ecosystem settled on. + +**P1-9 โ€” No skills are exposed at `.agents/skills/`.** +`.agents/plugins/marketplace.json` covers Codex plugin installation, but the +cross-tool discovery path that Copilot CLI and others read is `.agents/skills/`. +Exposing the four skills there (symlink, or move the canonical copy there and +point both plugin manifests at it) makes them portable to every agent that +implements the spec, at zero maintenance cost. + +**P1-10 โ€” Skill frontmatter omits the spec's optional security fields.** +All four `SKILL.md` files carry only `name` and `description`. The spec now +defines `allowed-tools`, `license`, `metadata` and `compatibility`. Adding +`allowed-tools` to each skill pre-authorizes exactly what the skill needs in +security-conscious runtimes, and `metadata` gives you a version handle for the +skills independent of the CLI version. + +**P1-11 โ€” Only two agents are addressed; the instruction file is the cheap part.** +There is no `.github/copilot-instructions.md`, no `GEMINI.md`, no Cursor rules. +Given `AGENTS.md` already exists, these are thin pointer files. Worth doing for +Copilot specifically โ€” it is the widest-deployed agent that does not read +`AGENTS.md` natively. + +**P1-12 โ€” No tests cover the agent plugin surface.** +`CLAUDE.md` explicitly asks for "public-seam tests for machine output, exit codes, +Git behavior, hook protocol, and **plugin manifests**." `tests/agent_cli_tests.rs` +covers the first four well (21 tests); nothing validates the marketplace JSON, +plugin manifests, or SKILL.md frontmatter against their schemas. A manifest typo +currently ships silently. Add a test that parses all six JSON/YAML manifests and +asserts every `SKILL.md` frontmatter uses only spec-allowed keys. + +**P2-13 โ€” Generated CI template is dated and slow.** +`hooks.rs:31-36` pins `actions/checkout@v4` (v5 is current) and installs via +`cargo install committy --locked` on every PR โ€” a multi-minute Rust build per run +with no caching and no toolchain pin. Prefer downloading the release binary from +`install.sh`, or add `Swatinem/rust-cache`. The same `checkout@v4` pin appears in +all three of this repo's own workflows. + +**P2-14 โ€” `$committy-commit` syntax leaks Codex convention into shared skills.** +`committy-release/SKILL.md` closes with "Use `$committy-commit`". Claude Code +invokes these as `/committy:committy-commit` (as `CLAUDE.md` itself documents). +Since the `SKILL.md` body is shared across both runtimes, reference sibling skills +by bare name and let each runtime resolve it. + +**P2-15 โ€” The local `block-dangerous-git.sh` guardrail is easily evaded and +over-blocks.** `.claude/hooks/block-dangerous-git.sh` greps the raw command string +for nine substrings. It matches the *text* of a command, not its effect โ€” writing +this very report through a heredoc was blocked because the prose names the push +command. It misses trivial variants (double-space, `env X=1` prefix, `git -C .`), +and it blocks the push-with-upstream form that this repository's own agent +workflow instructions require. It also assumes `jq` is installed with no fallback. +Treat it as a speed bump, not a control: the real enforcement is the `pre-push` +hook, which is policy-engine backed. + +### 3.3 Removals + +| Remove | Where | Why | +|---|---|---| +| `--ai-diff-lines-per-file` | `group_commit.rs:143-147`, `ai-flags.mdx` | Never read; pure surface area (or implement under P0-3) | +| `LlmError::_Timeout` | `src/ai/mod.rs:12` | Dead variant; timeouts surface as `RequestFailed` | +| Commented-out `LlmProvider` enum | `src/ai/mod.rs:30-35` | Dead code; provider is a `&str` match at the call site | +| Duplicated body of `CLAUDE.md` | `CLAUDE.md` | Replace with `@AGENTS.md` import (P1-8) | +| `agents/openai.yaml` ร— 4 | `plugins/committy/skills/*/agents/` | Candidate only โ€” verify Codex still reads these; if the marketplace entry supplies display metadata, they are redundant per-vendor files on the least portable layer | +| Empty roadmap page | `docs/src/content/docs/project/roadmap.mdx` | Frontmatter only, no body โ€” publishes an empty page | + +### 3.4 Explicitly *not* recommended + +- **Do not build a Committy MCP server.** The 2026 evidence is that CLI invocation + of a tool with a discoverable, typed interface costs a fraction of the tokens an + MCP tool listing does, and Committy has no auth, statefulness or multi-tenancy + that would justify the other side of the trade. The `schema` command already + does MCP's discovery job at a few hundred tokens. Invest in ยง3.2's P1-7 instead. +- **Do not add an LLM path to the single-commit `commit` command.** The calling + agent already has the diff in context and writes better messages than a + 256-token side-channel call. Committy's differentiated value is *validating* the + agent's message, not competing with it. + +--- + +## 4. Suggested order of work + +1. **P1-7** โ€” expand and enrich `capabilities` (unblocks every skill; pure win). +2. **P0-1, P0-2** โ€” make Ollama actually function. +3. **P0-4, P1-5, P1-6, ยง3.3 removals** โ€” stop documenting and accepting things + that do not happen. +4. **P0-3** โ€” give the default-safe AI path real, non-sensitive signal. +5. **P1-8, P1-9, P1-10** โ€” cross-tool portability (`AGENTS.md` canonical, + `.agents/skills/`, spec-complete frontmatter). +6. **P1-12** โ€” manifest and frontmatter tests so step 5 cannot regress. +7. **P1-11, P2-13, P2-14, P2-15** โ€” reach, CI cost, consistency, guardrail honesty. + +## 5. Sources + +- [AGENTS.md Field Guide, 2026 edition](https://www.iuriio.com/blog/posts/2026/05/agents-md-field-guide-2026) +- [AGENTS.md Specification](https://asdlc.io/practices/agents-md-spec/) +- [Agent Skills โ€” Specification](https://agentskills.io/specification) +- [SKILL.md YAML frontmatter reference](https://github.com/shalomb/agent-skills/blob/main/docs/reference/yaml-frontmatter.md) +- [Claude Code plugins reference](https://code.claude.com/docs/en/plugins-reference) +- [Codex CLI plugin marketplace](https://codex.danielvaughan.com/2026/04/24/codex-cli-plugin-marketplace-building-distributing-extending/) +- [OpenAI's Codex gets plugins](https://thenewstack.io/openais-codex-gets-plugins/) +- [Ollama structured outputs](https://docs.ollama.com/capabilities/structured-outputs) +- [Ollama /api/chat reference](https://docs.ollama.com/api/chat) +- [MCP vs CLI for AI agents](https://www.firecrawl.dev/blog/mcp-vs-cli) +- [CLI vs MCP: should your agent call tools or run commands?](https://blog.mcpservers.org/posts/cli-vs-mcp) From 64f0a627f136be1d35464b0da99342c56545db55 Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 20 Sep 2026 12:44:33 +0200 Subject: [PATCH 09/24] fix(cli): make agent invocations parse and fail machine-readably Agents consistently failed against the CLI for three mechanical reasons, each now reproduced and covered by tests/agent_ergonomics_tests.rs. - --non-interactive and -q/--quiet are global, so they parse on either side of the subcommand. --verbose stays root-only: -v belongs to `branch --validate` and --verbose to `config validate|show`, and promoting it would steal both shorts. A test pins that trade-off. - Argument-parse rejections now emit the machine envelope on stdout with code "invalid_usage" when --output json was requested, instead of an empty stdout and prose on stderr. Text mode is unchanged, and --help and --version keep their plain-text form and exit 0. - schema capabilities cover the whole agent command surface (tag, bump, changelog, group-commit, config, packages, schema) and each entry now declares `mutating` and `requires_confirmation`, so an agent can reason about consent without pattern-matching command names. - COMMITTY_NONINTERACTIVE is documented in AGENTS.md, CLAUDE.md and the agent-workflows reference; it makes flag placement irrelevant. Also withdraws the audit's blanket "do not build an MCP server": that was argued against eager-loading MCP, which the 2026-07-28 stateless spec has moved off. --- AGENTS.md | 1 + CLAUDE.md | 1 + src/cli/output.rs | 31 ++++++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 1dd5276..5c30926 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,6 +6,7 @@ Committy is a Rust CLI for conventional commits, branch policy, grouped commits, - Discover active policy with `cargo run -- schema --output json`. - Prefer `--non-interactive`, `--repo-path`, `--dry-run`, and `--output json`. +- Set `COMMITTY_NONINTERACTIVE=1` in the environment instead of relying on flag placement. `--non-interactive` and `-q`/`--quiet` are global and work on either side of the subcommand; every other flag (`--repo-path`, `--output`, `--dry-run`, and `--verbose`) must follow it. - Inspect `api_version`, `ok`, `errors`, and the command-specific plan before applying it. - Use the `committy-branch`, `committy-commit`, `committy-release`, and `committy-enforce` plugin skills when available. - Never infer permission for a push, publish, destructive Git action, or unrelated staging. diff --git a/CLAUDE.md b/CLAUDE.md index 524df5f..d94c21c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,7 @@ Committy is a Rust CLI for conventional commits, branch policy, grouped commits, - Discover active policy with `cargo run -- schema --output json`. - Prefer `--non-interactive`, `--repo-path`, `--dry-run`, and `--output json`. +- Set `COMMITTY_NONINTERACTIVE=1` in the environment instead of relying on flag placement. `--non-interactive` and `-q`/`--quiet` are global and work on either side of the subcommand; every other flag (`--repo-path`, `--output`, `--dry-run`, and `--verbose`) must follow it. - Inspect `api_version`, `ok`, `errors`, and the command-specific plan before applying it. - Use `/committy:committy-branch`, `/committy:committy-commit`, `/committy:committy-release`, and `/committy:committy-enforce` when the plugin is installed. - Never infer permission for a push, publish, destructive Git action, or unrelated staging. diff --git a/src/cli/output.rs b/src/cli/output.rs index 0fb74c6..b70710d 100644 --- a/src/cli/output.rs +++ b/src/cli/output.rs @@ -18,12 +18,41 @@ struct MachineError<'a> { #[derive(Debug, Serialize)] struct ErrorEnvelope<'a> { api_version: u8, - command: &'static str, + command: &'a str, ok: bool, dry_run: bool, errors: [MachineError<'a>; 1], } +/// Strip ANSI styling so the envelope carries plain text. +/// +/// Clap colours its rejections, and those escape sequences would otherwise end +/// up inside a JSON string value. +fn strip_ansi(text: &str) -> String { + static ANSI: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| regex::Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap()); + ANSI.replace_all(text, "").into_owned() +} + +/// Emit the machine envelope for an argument-parse rejection. +/// +/// `command` is attributed from raw argv and falls back to `"unknown"`, because +/// clap rejects the invocation before any value is parsed. +pub fn print_usage_error(command: &str, message: &str) { + let message = strip_ansi(message); + let payload = ErrorEnvelope { + api_version: API_VERSION, + command, + ok: false, + dry_run: false, + errors: [MachineError { + code: "invalid_usage", + message: &message, + }], + }; + println!("{}", serde_json::to_string(&payload).unwrap()); +} + pub fn print_error(context: MachineContext, error: &CliError) { let message = error.to_string(); let payload = ErrorEnvelope { From c4db683a736d7519ce8471a6b9768191d8844876 Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 20 Sep 2026 12:45:32 +0200 Subject: [PATCH 10/24] fix(cli): global flag placement, usage envelope, capability surface Second half of the agent-ergonomics change: main.rs flag globals and the clap rejection handler, the expanded schema capability list, the ergonomics test suite, and the docs describing flag placement. --- src/cli/commands/schema.rs | 147 +++++++++++++++++++++++++++---------- src/main.rs | 101 +++++++++++++++++++++++-- 2 files changed, 202 insertions(+), 46 deletions(-) diff --git a/src/cli/commands/schema.rs b/src/cli/commands/schema.rs index 492971d..f8754c2 100644 --- a/src/cli/commands/schema.rs +++ b/src/cli/commands/schema.rs @@ -37,50 +37,117 @@ struct SchemaOutput { struct CapabilityDefinition { name: &'static str, description: &'static str, + /// Whether applying this capability changes the repository or a remote. + mutating: bool, + /// Whether the CLI refuses to proceed without an explicit confirmation + /// flag. An agent must never infer this consent from the task alone. + requires_confirmation: bool, +} + +/// Read-only capability: previews and validation. +const fn read_only(name: &'static str, description: &'static str) -> CapabilityDefinition { + CapabilityDefinition { + name, + description, + mutating: false, + requires_confirmation: false, + } +} + +/// Local mutation: changes this checkout, needs no confirmation flag. +const fn local_write(name: &'static str, description: &'static str) -> CapabilityDefinition { + CapabilityDefinition { + name, + description, + mutating: true, + requires_confirmation: false, + } +} + +/// Remote mutation: gated behind an explicit confirmation flag. +const fn remote_write(name: &'static str, description: &'static str) -> CapabilityDefinition { + CapabilityDefinition { + name, + description, + mutating: true, + requires_confirmation: true, + } } fn capabilities() -> Vec { vec![ - CapabilityDefinition { - name: "branch.preview", - description: "Resolve and validate a branch without changing git", - }, - CapabilityDefinition { - name: "branch.apply", - description: "Create a validated branch", - }, - CapabilityDefinition { - name: "branch.lint", - description: "Validate structured or policy-enforced branch names", - }, - CapabilityDefinition { - name: "commit.preview", - description: "Resolve and lint a commit without changing git", - }, - CapabilityDefinition { - name: "commit.apply", - description: "Create a validated conventional commit", - }, - CapabilityDefinition { - name: "commit.amend", - description: "Preview or apply a validated amend", - }, - CapabilityDefinition { - name: "commit.lint", - description: "Lint one message or repository history", - }, - CapabilityDefinition { - name: "hooks.install", - description: "Preview or install native Git hooks and CI enforcement", - }, - CapabilityDefinition { - name: "hooks.commit-msg", - description: "Enforce commit messages through a native git hook", - }, - CapabilityDefinition { - name: "hooks.pre-push", - description: "Enforce outgoing commit history through a native git hook", - }, + read_only( + "schema.discover", + "Report active convention, types, and this capability list", + ), + read_only( + "branch.preview", + "Resolve and validate a branch without changing git", + ), + local_write("branch.apply", "Create a validated branch"), + read_only( + "branch.lint", + "Validate structured or policy-enforced branch names", + ), + read_only( + "commit.preview", + "Resolve and lint a commit without changing git", + ), + local_write("commit.apply", "Create a validated conventional commit"), + local_write("commit.amend", "Preview or apply a validated amend"), + read_only("commit.lint", "Lint one message or repository history"), + read_only( + "group-commit.plan", + "Group a dirty worktree into coherent commits without changing git", + ), + local_write( + "group-commit.apply", + "Create the planned grouped commits; pushing additionally requires confirmation", + ), + read_only( + "bump.preview", + "Compute the next version from commit history without writing files", + ), + local_write( + "bump.apply", + "Write the computed version to the configured version files", + ), + read_only( + "changelog.preview", + "Render a changelog from git history without writing files", + ), + read_only( + "tag.preview", + "Compute the next tag and release plan without changing git", + ), + local_write( + "tag.apply", + "Create the computed tag in the local repository", + ), + remote_write( + "tag.publish", + "Push tags and publish the release; requires an explicit confirmation flag", + ), + read_only( + "config.validate", + "Validate repository configuration and report findings", + ), + read_only( + "packages.list", + "List configured packages and their resolved versions", + ), + local_write( + "hooks.install", + "Preview or install native Git hooks and CI enforcement", + ), + read_only( + "hooks.commit-msg", + "Enforce commit messages through a native git hook", + ), + read_only( + "hooks.pre-push", + "Enforce outgoing commit history through a native git hook", + ), ] } diff --git a/src/main.rs b/src/main.rs index d55d427..8f97af2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,25 +56,91 @@ struct Opt { )] pre_release: bool, - #[structopt(long = "non-interactive", help = "Run in non-interactive mode")] + #[structopt( + long = "non-interactive", + global = true, + help = "Run in non-interactive mode" + )] non_interactive: bool, #[structopt(long = "metrics-toggle", help = "Toggle metrics collection on/off")] metrics_toggle: bool, + // Not global: `-v` is already taken by `branch --validate` and `--verbose` + // by `config validate|show`. Promoting it would silently steal those + // shorts. Agents use `-q` and `--non-interactive`, which are global below. #[structopt( short = "v", long = "verbose", parse(from_occurrences), - help = "Increase verbosity (-v, -vv)" + help = "Increase verbosity (-v, -vv); must precede the subcommand" )] verbose: u8, - #[structopt(short = "q", long = "quiet", help = "Reduce verbosity (errors only)")] + #[structopt( + short = "q", + long = "quiet", + global = true, + help = "Reduce verbosity (errors only)" + )] quiet: bool, } +/// Subcommand names as they appear on the command line. +/// +/// Used only to attribute an argv rejection to a command, since clap gives us +/// no parsed value in that case. +const SUBCOMMAND_NAMES: &[&str] = &[ + "commit", + "amend", + "tag", + "bump", + "changelog", + "lint", + "lint-message", + "example", + "info", + "ls", + "schema", + "version", + "branch", + "group-commit", + "hooks", + "init", + "config", + "packages", +]; + +/// Whether the caller asked for machine output, determined from raw argv. +/// +/// An argv rejection happens before any flag is parsed, so this is the only way +/// to know whether the caller is an agent expecting one JSON document. +fn requested_json_output(args: &[String]) -> bool { + args.iter().enumerate().any(|(index, arg)| { + arg == "--output=json" + || (arg == "--output" && args.get(index + 1).map(String::as_str) == Some("json")) + }) +} + +/// Best-effort command attribution for an argv rejection. +fn command_from_args(args: &[String]) -> &str { + args.iter() + .skip(1) + .find(|arg| !arg.starts_with('-')) + .map(String::as_str) + .filter(|candidate| SUBCOMMAND_NAMES.contains(candidate)) + .unwrap_or("unknown") +} + fn main() { + // Parse argv before touching configuration so a malformed invocation is + // rejected without side effects. + let args: Vec = std::env::args().collect(); + let opt = match Opt::from_iter_safe(&args) { + Ok(opt) => opt, + Err(error) => exit_on_parse_error(error, &args), + }; + // Load configuration let mut config = Config::load().unwrap_or_else(|_| { let default_config = Config::default(); @@ -84,7 +150,7 @@ fn main() { default_config }); - if let Err(e) = run(&mut config) { + if let Err(e) = run(&mut config, opt) { eprintln!("{e}"); let exit_code = e .downcast_ref::() @@ -94,9 +160,32 @@ fn main() { } } -fn run(config: &mut Config) -> Result<()> { - let opt = Opt::from_args(); +/// Report a clap rejection and terminate. +/// +/// `--help` and `--version` arrive here as errors too; they are successful +/// output and keep their plain-text form. A genuine rejection honours +/// `--output json` so that an agent's parser sees the same envelope it sees for +/// every other failure, instead of an empty stdout and prose on stderr. +fn exit_on_parse_error(error: structopt::clap::Error, args: &[String]) -> ! { + use structopt::clap::ErrorKind; + + if matches!( + error.kind, + ErrorKind::HelpDisplayed | ErrorKind::VersionDisplayed + ) { + println!("{}", error.message); + std::process::exit(0); + } + + if requested_json_output(args) { + cli::output::print_usage_error(command_from_args(args), &error.message); + } else { + eprintln!("{}", error.message); + } + std::process::exit(1); +} +fn run(config: &mut Config, opt: Opt) -> Result<()> { // Initialize logger based on verbosity flags let mut builder = Builder::from_env(Env::default().default_filter_or("info")); let level = if opt.quiet { From 2b197f0b91e53fb8877c24c06b5a7bbeb1db7c4b Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 20 Sep 2026 12:46:26 +0200 Subject: [PATCH 11/24] test(agents): cover agent CLI ergonomics and update the audit Adds tests/agent_ergonomics_tests.rs (12 tests) covering flag placement on both sides of the subcommand, the invalid_usage envelope on argv rejection, the expanded capability surface with consent metadata, and the presence of COMMITTY_NONINTERACTIVE in agent-facing docs. Documents flag placement in the agent-workflows reference and records the diagnosis in the audit, withdrawing its blanket recommendation against an MCP server. --- tests/agent_ergonomics_tests.rs | 438 ++++++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 tests/agent_ergonomics_tests.rs diff --git a/tests/agent_ergonomics_tests.rs b/tests/agent_ergonomics_tests.rs new file mode 100644 index 0000000..6c9f2f6 --- /dev/null +++ b/tests/agent_ergonomics_tests.rs @@ -0,0 +1,438 @@ +//! Agent ergonomics: the seams where AI coding agents actually trip. +//! +//! Three classes of failure are covered here: +//! 1. Global flags must parse on either side of the subcommand. +//! 2. Argument-parse failures must honour `--output json` like every other error. +//! 3. `schema` must advertise the whole command surface, with consent metadata. + +mod common; + +use serde_json::Value; +use std::fs; +use std::process::Command as StdCommand; +use tempfile::tempdir; + +fn setup_repo() -> tempfile::TempDir { + common::setup_test_env(); + + let dir = tempdir().expect("Failed to create temp directory"); + + for args in [ + vec!["init"], + vec!["config", "user.name", "Test User"], + vec!["config", "user.email", "test@example.com"], + ] { + StdCommand::new("git") + .args(&args) + .current_dir(&dir) + .output() + .unwrap_or_else(|e| panic!("git {args:?} failed: {e}")); + } + + fs::write(dir.path().join("tracked.txt"), "initial\n").expect("Failed to write tracked file"); + StdCommand::new("git") + .args(["add", "tracked.txt"]) + .current_dir(&dir) + .output() + .expect("Failed to stage tracked file"); + StdCommand::new("git") + .args(["commit", "-m", "feat: initial"]) + .current_dir(&dir) + .output() + .expect("Failed to create initial commit"); + + dir +} + +fn stdout_of(assert: &assert_cmd::assert::Assert) -> String { + String::from_utf8(assert.get_output().stdout.clone()).expect("stdout was not valid UTF-8") +} + +// --------------------------------------------------------------------------- +// 1. Global flag placement +// --------------------------------------------------------------------------- + +/// The documented form. Guards against a regression while fixing the rest. +#[test] +fn test_global_flags_before_subcommand_still_parse() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "--non-interactive", + "-q", + "schema", + "--repo-path", + ".", + "--output", + "json", + ]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()).unwrap(); + assert_eq!(v["command"], Value::String("schema".into())); +} + +/// The form agents actually write: global flags after the subcommand. +#[test] +fn test_non_interactive_parses_after_subcommand() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "schema", + "--non-interactive", + "--repo-path", + ".", + "--output", + "json", + ]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()).unwrap(); + assert_eq!(v["command"], Value::String("schema".into())); +} + +#[test] +fn test_quiet_parses_after_subcommand() { + let temp_dir = setup_repo(); + + for flag in ["-q", "--quiet"] { + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["schema", flag, "--repo-path", ".", "--output", "json"]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .unwrap_or_else(|e| panic!("`schema {flag}` did not emit JSON: {e}")); + assert_eq!(v["command"], Value::String("schema".into())); + } +} + +/// `--verbose` is deliberately *not* global. +/// +/// `-v` is `branch --validate` and `--verbose` is a real flag on +/// `config validate|show`. Promoting the root flag would silently steal both. +/// This test pins that trade-off so a later change has to confront it. +#[test] +fn test_verbose_stays_root_only_because_subcommands_own_it() { + let temp_dir = setup_repo(); + + common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "--verbose", + "schema", + "--repo-path", + ".", + "--output", + "json", + ]) + .assert() + .success(); + + // `branch -v` must still mean `--validate`, not root verbosity. + common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "branch", + "-v", + "--name", + "feat-keeps-validate", + "--output", + "json", + ]) + .assert() + .success(); +} + +/// Mutating commands take the same treatment, not just read-only ones. +#[test] +fn test_global_flags_after_subcommand_on_branch_and_commit() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "branch", + "--non-interactive", + "--name", + "feat-agent-ergonomics", + "--dry-run", + "--output", + "json", + ]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()).unwrap(); + assert_eq!(v["command"], Value::String("branch".into())); + assert_eq!(v["dry_run"], Value::Bool(true)); + + // `commit` reports a command-level error here (nothing staged), but it must + // be a *parsed* command-level error, not an argv rejection. + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "commit", + "--non-interactive", + "--type", + "feat", + "--message", + "agent ergonomics", + "--dry-run", + "--output", + "json", + ]) + .assert(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .expect("commit must emit a JSON envelope, not an argv rejection"); + assert_eq!(v["command"], Value::String("commit".into())); +} + +// --------------------------------------------------------------------------- +// 2. Argument-parse errors must honour --output json +// --------------------------------------------------------------------------- + +#[test] +fn test_unknown_flag_with_json_output_emits_error_envelope() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["schema", "--output", "json", "--definitely-not-a-flag"]) + .assert() + .failure(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .expect("an argv rejection must still produce one JSON document on stdout"); + + assert_eq!(v["api_version"], Value::from(1)); + assert_eq!(v["command"], Value::String("schema".into())); + assert_eq!(v["ok"], Value::Bool(false)); + assert_eq!( + v["errors"][0]["code"], + Value::String("invalid_usage".into()) + ); + assert!( + v["errors"][0]["message"] + .as_str() + .expect("message must be a string") + .contains("--definitely-not-a-flag"), + "the envelope must name the offending argument so an agent can self-correct" + ); +} + +#[test] +fn test_unknown_flag_with_equals_form_json_output_emits_envelope() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["schema", "--output=json", "--definitely-not-a-flag"]) + .assert() + .failure(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .expect("--output=json must be recognised as well as --output json"); + assert_eq!(v["ok"], Value::Bool(false)); + assert_eq!( + v["errors"][0]["code"], + Value::String("invalid_usage".into()) + ); +} + +/// An unknown subcommand cannot be attributed to a command; the envelope still +/// has to be valid so the agent's parser does not blow up. +#[test] +fn test_unknown_subcommand_with_json_output_emits_envelope() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["nonexistent-command", "--output", "json"]) + .assert() + .failure(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .expect("an unknown subcommand must still produce one JSON document"); + assert_eq!(v["ok"], Value::Bool(false)); + assert_eq!(v["command"], Value::String("unknown".into())); +} + +/// Humans keep the terminal-friendly behaviour: plain text on stderr, no JSON. +#[test] +fn test_unknown_flag_without_json_output_stays_human_readable() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["schema", "--definitely-not-a-flag"]) + .assert() + .failure(); + + assert!( + stdout_of(&assert).trim().is_empty(), + "text mode must not print a JSON document to stdout" + ); + let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap(); + assert!( + stderr.contains("--definitely-not-a-flag"), + "text mode must still explain the problem on stderr" + ); +} + +// --------------------------------------------------------------------------- +// 3. Capability discovery +// --------------------------------------------------------------------------- + +fn capabilities_of(temp_dir: &tempfile::TempDir) -> Vec { + let assert = common::committy_cmd() + .current_dir(temp_dir) + .args(["--non-interactive", "schema", "--output", "json"]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()).unwrap(); + v["capabilities"] + .as_array() + .expect("schema must expose a capabilities array") + .clone() +} + +#[test] +fn test_capabilities_cover_the_whole_agent_command_surface() { + let temp_dir = setup_repo(); + let capabilities = capabilities_of(&temp_dir); + + let names: Vec<&str> = capabilities + .iter() + .map(|c| c["name"].as_str().expect("capability needs a name")) + .collect(); + + // Every command the shipped skills instruct an agent to run. + for expected in [ + "branch.preview", + "branch.apply", + "branch.lint", + "commit.preview", + "commit.apply", + "commit.amend", + "commit.lint", + "group-commit.plan", + "group-commit.apply", + "bump.preview", + "bump.apply", + "changelog.preview", + "tag.preview", + "tag.apply", + "tag.publish", + "config.validate", + "packages.list", + "hooks.install", + "hooks.commit-msg", + "hooks.pre-push", + "schema.discover", + ] { + assert!( + names.contains(&expected), + "capability `{expected}` is missing; skills hard-code it today. Present: {names:?}" + ); + } +} + +#[test] +fn test_capabilities_declare_consent_metadata() { + let temp_dir = setup_repo(); + let capabilities = capabilities_of(&temp_dir); + + for capability in &capabilities { + let name = capability["name"].as_str().unwrap(); + assert!( + capability["mutating"].is_boolean(), + "capability `{name}` must declare `mutating`" + ); + assert!( + capability["requires_confirmation"].is_boolean(), + "capability `{name}` must declare `requires_confirmation`" + ); + assert!( + capability["description"].is_string(), + "capability `{name}` must keep its description" + ); + } + + let by_name = |needle: &str| -> Value { + capabilities + .iter() + .find(|c| c["name"] == Value::String(needle.into())) + .unwrap_or_else(|| panic!("missing capability {needle}")) + .clone() + }; + + // Previews never touch the repository. + for read_only in ["branch.preview", "commit.preview", "schema.discover"] { + assert_eq!( + by_name(read_only)["mutating"], + Value::Bool(false), + "{read_only} must not be marked mutating" + ); + assert_eq!( + by_name(read_only)["requires_confirmation"], + Value::Bool(false) + ); + } + + // Local writes mutate but do not need the remote-mutation confirmation flag. + assert_eq!(by_name("commit.apply")["mutating"], Value::Bool(true)); + assert_eq!( + by_name("commit.apply")["requires_confirmation"], + Value::Bool(false) + ); + + // Remote mutation is the case an agent must never infer consent for. + assert_eq!(by_name("tag.publish")["mutating"], Value::Bool(true)); + assert_eq!( + by_name("tag.publish")["requires_confirmation"], + Value::Bool(true), + "publishing must advertise that it needs explicit confirmation" + ); +} + +// --------------------------------------------------------------------------- +// 4. The escape hatch must be discoverable where agents read +// --------------------------------------------------------------------------- + +#[test] +fn test_agent_docs_mention_the_non_interactive_env_var() { + let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + + let agent_facing = [ + "AGENTS.md", + "CLAUDE.md", + "docs/src/content/docs/reference/agent-workflows.mdx", + ]; + + for relative in agent_facing { + let body = fs::read_to_string(repo_root.join(relative)) + .unwrap_or_else(|e| panic!("cannot read {relative}: {e}")); + assert!( + body.contains("COMMITTY_NONINTERACTIVE"), + "{relative} must document COMMITTY_NONINTERACTIVE; it is the escape hatch \ + that makes flag placement irrelevant for agents" + ); + + // Documenting `-v` as global would be worse than not documenting + // placement at all: it reads as permission to write `schema -v`. + assert!( + !body.contains("`--non-interactive`, `-q`, `-v`"), + "{relative} lists -v among the global flags, but --verbose is root-only" + ); + } +} From ac60b455cd3c0dcbca2460233ac804df9cf757b4 Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 20 Sep 2026 12:46:51 +0200 Subject: [PATCH 12/24] docs(agents): document flag placement and the invalid_usage envelope --- .../docs/reference/agent-workflows.mdx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/src/content/docs/reference/agent-workflows.mdx b/docs/src/content/docs/reference/agent-workflows.mdx index e170a05..fbe1bde 100644 --- a/docs/src/content/docs/reference/agent-workflows.mdx +++ b/docs/src/content/docs/reference/agent-workflows.mdx @@ -19,6 +19,36 @@ Preferred sequence: 5. Require explicit confirmation for any remote mutation. 6. Use `--repo-path` when the agent should operate on another checkout. +## Flag placement + +Committy has two flag namespaces, and mixing them up is the most common way an +agent invocation fails: + +- **Global flags** โ€” `--non-interactive` and `-q`/`--quiet` โ€” are accepted on + either side of the subcommand. +- **Everything else** โ€” `--repo-path`, `--output`, `--dry-run`, and every + command-specific flag โ€” must follow the subcommand. +- **`-v`/`--verbose` must precede the subcommand.** It is deliberately not + global: `-v` already means `--validate` on `branch`, and `--verbose` is a real + flag on `config validate` and `config show`. + +The most reliable option is to stop passing `--non-interactive` altogether and +set the environment variable instead, which makes placement irrelevant: + +```bash +export COMMITTY_NONINTERACTIVE=1 +committy schema --repo-path . --output json +``` + +`CI=1` has the same effect, so Committy is already non-interactive on most CI +runners. + +When an invocation is rejected outright โ€” an unknown flag, a misplaced argument +โ€” Committy still writes one JSON document to stdout if `--output json` was +requested, with `"code": "invalid_usage"` and clap's explanation as the message. +Agents can parse the rejection and self-correct rather than falling back to +scraping stderr. + ## Policy discovery ```bash From 67919bd2e2d72f33b651a98b229c5ee1c7ba1818 Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 20 Sep 2026 12:47:57 +0200 Subject: [PATCH 13/24] docs(audit): record the CLI ergonomics diagnosis and withdraw the MCP verdict --- docs/audits/agent-compatibility-2026-09.md | 125 ++++++++++++++------- 1 file changed, 83 insertions(+), 42 deletions(-) diff --git a/docs/audits/agent-compatibility-2026-09.md b/docs/audits/agent-compatibility-2026-09.md index 680f84e..1732d78 100644 --- a/docs/audits/agent-compatibility-2026-09.md +++ b/docs/audits/agent-compatibility-2026-09.md @@ -21,14 +21,15 @@ against the state of the agent-integration ecosystem as of September 2026. | Skills | 4 ร— `SKILL.md` (branch, commit, release, enforce) | Well-scoped, progressive disclosure, safety rules present | | Codex interface shims | 4 ร— `agents/openai.yaml` | Display metadata only | | Machine contract | `src/cli/output.rs` (`api_version = 1`) + per-command payloads | Stable envelope: `api_version`, `command`, `ok`, `dry_run`, `errors` | -| Capability discovery | `src/cli/commands/schema.rs:42-84` | 10 hard-coded capabilities | +| Capability discovery | `src/cli/commands/schema.rs` | 21 capabilities with consent metadata (was 10, bare) | | Repo enforcement | `src/cli/commands/hooks.rs:14-37` | `commit-msg`, `pre-push`, GitHub Actions template | | Local agent guardrail | `.claude/settings.json` + `.claude/hooks/block-dangerous-git.sh` | PreToolUse Bash denylist | | LLM integration | `src/ai/mod.rs`, `src/cli/commands/group_commit.rs:98-151, 356-507` | OpenRouter + Ollama, `group-commit` only | -The core architectural bet โ€” **CLI-first with typed JSON, not an MCP server** โ€” -is the right one and has been validated by the wider ecosystem this year (see ยง2). -Nothing below asks you to change that. +The core architectural bet is **CLI-first with typed JSON**. That still holds, +but not for the reason an earlier draft gave: see ยง3.4, where the blanket +"never build an MCP server" recommendation is withdrawn, and ยง3.5, where the +CLI's own agent ergonomics turned out to be the real problem. --- @@ -51,11 +52,15 @@ Nothing below asks you to change that. 5. **Claude Code plugins now carry more than skills**: `commands`, `agents`, `hooks`, `mcpServers`, `outputStyles`, `lspServers`, auto-discovered from standard directories. -6. **CLI beat MCP on token cost for known-interface tools** (roughly 4โ€“32ร— cheaper; - a naive GitHub MCP server costs ~55k tokens of context before doing anything). - The 2026 consensus is hybrid: CLI for tools the model already understands, MCP - only where you need auth, statefulness, or governance. -7. **Ollama structured output moved to JSON-Schema-constrained decoding** via a +6. **The MCP token argument has a shelf life.** The benchmarks (35ร— token cost, + reliability dropping to 72% on hard tasks, ~55k tokens for a naive GitHub + server) measure *eager-loading* MCP, where every tool schema is injected at + session start. Progressive disclosure recovers a reported 60โ€“85%. +7. **The 2026-07-28 MCP spec is the largest revision since launch**: stateless + core, sessions gone, Roots/Sampling/Logging deprecated, MCP Apps and Tasks + added, progressive discovery and tool search on the roadmap. Recommendations + written against the old full-bundle design no longer apply cleanly. +8. **Ollama structured output moved to JSON-Schema-constrained decoding** via a **top-level** `format` field; `/api/chat` still defaults to `stream: true`. --- @@ -112,22 +117,21 @@ families out of date. Both should be corrected. ### 3.2 Compatibility gaps to close -**P1-7 โ€” `capabilities` in `schema` is stale and undersells the CLI.** -`schema.rs:42-84` advertises 10 capabilities covering branch, commit and hooks -only. Absent: `release.plan` / `tag.preview` / `tag.apply` / `tag.publish`, -`changelog.preview`, `bump.preview` / `bump.apply`, `group-commit.plan` / -`group-commit.apply`, `packages.*`, `config.validate`, `init`. The -`committy-release` and `committy-enforce` skills already instruct agents to run -`bump`, `tag`, `changelog` and `config validate` โ€” commands the capability list -denies exist. `agent-workflows.mdx` promises capabilities exist precisely "so -skills do not need to hard-code the command surface"; today they must. This is the -single highest-leverage fix in the report: it is the contract every skill reads -first. - -Two sub-improvements while you are in there: give each capability a `mutating: -bool` and `requires_confirmation: bool` field so an agent can reason about consent -without pattern-matching command names, and consider deriving the list from the -command registry so it cannot drift again. +**P1-7 โ€” `capabilities` in `schema` was stale and undersold the CLI.** โœ… **Fixed.** +`schema.rs` advertised 10 capabilities covering branch, commit and hooks only, +while the `committy-release` and `committy-enforce` skills instructed agents to +run `bump`, `tag`, `changelog` and `config validate` โ€” commands the capability +list denied existed. `agent-workflows.mdx` promises capabilities exist precisely +"so skills do not need to hard-code the command surface"; they had to. + +Now 21 capabilities cover the whole agent surface, and each declares `mutating` +and `requires_confirmation` so an agent can reason about consent without +pattern-matching command names (`tag.publish` is the only one gated on an +explicit confirmation flag). Covered by +`tests/agent_ergonomics_tests.rs`. + +Still open: the list is hand-maintained and can drift again. Deriving it from +the command registry would close that for good. **P1-8 โ€” `AGENTS.md` and `CLAUDE.md` are maintained duplicates.** The diff is two lines. This will drift. Make `AGENTS.md` canonical and reduce @@ -196,31 +200,64 @@ hook, which is policy-engine backed. | `agents/openai.yaml` ร— 4 | `plugins/committy/skills/*/agents/` | Candidate only โ€” verify Codex still reads these; if the marketplace entry supplies display metadata, they are redundant per-vendor files on the least portable layer | | Empty roadmap page | `docs/src/content/docs/project/roadmap.mdx` | Frontmatter only, no body โ€” publishes an empty page | -### 3.4 Explicitly *not* recommended +### 3.4 On building an MCP server + +An earlier revision of this report said flatly "do not build one". That was +argued against eager-loading MCP, which the protocol has since moved off: the +2026-07-28 spec makes the core stateless, drops sessions, and puts progressive +discovery and tool search on the roadmap. The headline benchmarks against MCP โ€” +35ร— token cost, reliability falling to 72% on hard tasks โ€” measure the old +full-bundle design; progressive disclosure reportedly recovers 60โ€“85% of that. +So the blanket recommendation was wrong and is withdrawn. + +What remains true is the ordering. The observed agent struggle traced to three +mechanical CLI faults (ยง3.5), not to the CLI *being* a CLI, and all three were +fixable in an afternoon. An MCP server would have fixed them incidentally, by +deleting argv from the problem, while adding a server to build, distribute, and +keep in version lockstep with the binary โ€” and it would not help the Git hook and +CI path at all, which must stay CLI and is where Committy's actual enforcement +lives. + +Revisit if agents still struggle now that ยง3.5 is fixed. If built, shape it as +roughly six stateless tools mapping to capability groups (`policy.discover`, +`commit.preview`/`apply`, `branch.preview`/`apply`, `lint`) rather than eighteen +tools mirroring subcommands, and have `policy.discover` serve the existing +`schema --output json` payload instead of re-describing the surface. + +Still not recommended: **an LLM path on the single-commit `commit` command.** The +calling agent already has the diff in context and writes better messages than a +256-token side-channel call. Committy's differentiated value is *validating* the +agent's message, not competing with it. + +### 3.5 CLI ergonomics โ€” diagnosed, fixed, and under test + +Reproduced against v1.9.1 and now covered by `tests/agent_ergonomics_tests.rs`: + +| # | Fault | Fix | +|---|---|---| +| E-1 | `--non-interactive`, `-q` were root-only, so `committy schema --non-interactive โ€ฆ` โ€” the form agents write โ€” was rejected by clap | `global = true` on both | +| E-2 | Argument-parse rejections wrote nothing to stdout and prose to stderr, even with `--output json` | `invalid_usage` envelope on stdout; text mode unchanged | +| E-3 | `COMMITTY_NONINTERACTIVE=1` makes placement irrelevant but appeared in no agent-facing doc | documented in `AGENTS.md`, `CLAUDE.md`, `agent-workflows.mdx` | -- **Do not build a Committy MCP server.** The 2026 evidence is that CLI invocation - of a tool with a discoverable, typed interface costs a fraction of the tokens an - MCP tool listing does, and Committy has no auth, statefulness or multi-tenancy - that would justify the other side of the trade. The `schema` command already - does MCP's discovery job at a few hundred tokens. Invest in ยง3.2's P1-7 instead. -- **Do not add an LLM path to the single-commit `commit` command.** The calling - agent already has the diff in context and writes better messages than a - 256-token side-channel call. Committy's differentiated value is *validating* the - agent's message, not competing with it. +`--verbose` was deliberately left root-only: `-v` is `branch --validate` and +`--verbose` belongs to `config validate|show`, so promoting it would silently +steal both shorts. A test pins that trade-off. --- ## 4. Suggested order of work -1. **P1-7** โ€” expand and enrich `capabilities` (unblocks every skill; pure win). -2. **P0-1, P0-2** โ€” make Ollama actually function. -3. **P0-4, P1-5, P1-6, ยง3.3 removals** โ€” stop documenting and accepting things +0. ~~**P1-7**, **E-1**, **E-2**, **E-3**~~ โ€” done: capabilities expanded with + consent metadata, global flag placement, `invalid_usage` envelope, env var + documented. See ยง3.5. +1. **P0-1, P0-2** โ€” make Ollama actually function. +2. **P0-4, P1-5, P1-6, ยง3.3 removals** โ€” stop documenting and accepting things that do not happen. -4. **P0-3** โ€” give the default-safe AI path real, non-sensitive signal. -5. **P1-8, P1-9, P1-10** โ€” cross-tool portability (`AGENTS.md` canonical, +3. **P0-3** โ€” give the default-safe AI path real, non-sensitive signal. +4. **P1-8, P1-9, P1-10** โ€” cross-tool portability (`AGENTS.md` canonical, `.agents/skills/`, spec-complete frontmatter). -6. **P1-12** โ€” manifest and frontmatter tests so step 5 cannot regress. -7. **P1-11, P2-13, P2-14, P2-15** โ€” reach, CI cost, consistency, guardrail honesty. +5. **P1-12** โ€” manifest and frontmatter tests so the step above cannot regress. +6. **P1-11, P2-13, P2-14, P2-15** โ€” reach, CI cost, consistency, guardrail honesty. ## 5. Sources @@ -233,5 +270,9 @@ hook, which is policy-engine backed. - [OpenAI's Codex gets plugins](https://thenewstack.io/openais-codex-gets-plugins/) - [Ollama structured outputs](https://docs.ollama.com/capabilities/structured-outputs) - [Ollama /api/chat reference](https://docs.ollama.com/api/chat) +- [MCP roadmap: progressive discovery and agent auth](https://www.developersdigest.tech/blog/mcp-roadmap-progressive-discovery-agent-auth) +- [Progressive tool loading is the new MCP context pattern](https://usewire.io/blog/progressive-tool-loading-mcp-context-pattern/) +- [MCP context bloat fix 2026: tool search, code mode, progressive disclosure](https://mcp.directory/blog/mcp-context-bloat-fix-2026-tool-search-code-mode-progressive-disclosure) +- [MCP servers use 35x more tokens than CLI tools](https://www.mindstudio.ai/blog/mcp-servers-35x-more-tokens-cli-tools-reliability-benchmark) - [MCP vs CLI for AI agents](https://www.firecrawl.dev/blog/mcp-vs-cli) - [CLI vs MCP: should your agent call tools or run commands?](https://blog.mcpservers.org/posts/cli-vs-mcp) From a331dcc5196acabd96c0e59b2324aed466a6ce4b Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 20 Sep 2026 12:50:53 +0200 Subject: [PATCH 14/24] docs(agents): ban AI attribution trailers in commits and PRs Tooling re-adds Co-Authored-By and Claude-Session by default, so the prohibition has to live where agents read it rather than being restated each session. --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index d94c21c..cec9cda 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,4 +31,4 @@ Tests are in `tests/`; CLI integration tests use `assert_cmd` and temporary repo - `src/workflow/`, `src/versioning/`, `src/packages/`: release side effects - `plugins/committy/`: shared Codex and Claude skills -PRs target `develop`. Commits follow Conventional Commits. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`. +PRs target `develop`. Commits follow Conventional Commits. Never add AI attribution trailers โ€” no `Co-Authored-By:`, no `Claude-Session:`, no equivalent โ€” to commit messages or PR descriptions, even when tooling suggests them. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`. From 0ea82fc002e0e77448765bc259c20a2d0d6ebeb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 10:58:44 +0000 Subject: [PATCH 15/24] fix(ai): make the Ollama provider work and stop sending an empty prompt Four real defects from the agent audit, plus one withdrawn finding. - Ollama requests now set `stream: false`. The endpoint defaults to streaming, so every previous call received newline-delimited events and died in LlmError::Parse before silently falling back to the default message. The provider has never worked. - `format` moves from `options` to the top level, where Ollama reads it, and now carries a JSON Schema for AiCommitSuggestion rather than the string "json", so decoding is constrained instead of merely requested. - The default (non-sensitive) AI path sent only the group name and an instruction not to reveal anything, leaving the model nothing to work from. It now sends a redacted shape summary: file count, extension histogram and top-level areas. No filename, no path below the first segment, and no file content is sent in either mode. - `--ai-diff-lines-per-file` was declared, documented and accepted but never read. Removed, along with the dead LlmError::_Timeout variant and the commented-out LlmProvider enum. The audit's P0-4 ("--ai silently ignored in --mode apply") was wrong and is withdrawn: the apply arm has always had its own AI block. The real problem there was duplication -- plan and apply carried identical copies of group building and the AI block -- which is what made the misreading possible. Both are extracted into build_groups() and ai_user_prompt(). Request bodies are now built by pure functions with unit tests, so the wire format is verifiable without a network call. --- docs/audits/agent-compatibility-2026-09.md | 41 ++- docs/src/content/docs/reference/ai-flags.mdx | 62 +++- src/ai/mod.rs | 238 ++++++++----- src/cli/commands/group_commit.rs | 353 ++++++++++++------- 4 files changed, 448 insertions(+), 246 deletions(-) diff --git a/docs/audits/agent-compatibility-2026-09.md b/docs/audits/agent-compatibility-2026-09.md index 1732d78..f021f6d 100644 --- a/docs/audits/agent-compatibility-2026-09.md +++ b/docs/audits/agent-compatibility-2026-09.md @@ -72,21 +72,21 @@ or maintenance cost ยท **P2** = polish. ### 3.1 Defects to fix -**P0-1 โ€” The Ollama provider cannot work as written.** +**P0-1 โ€” The Ollama provider could not work as written.** โœ… **Fixed.** `src/ai/mod.rs:160-205` posts to `/api/chat` without `stream: false`. Ollama defaults to `stream: true`, so the response is newline-delimited JSON events, and `resp.json::()` will fail to deserialize on essentially every call. Every `--ai-provider ollama` run therefore ends in `LlmError::Parse` and silently falls back to the default message. Fix: send `stream: false`. -**P0-2 โ€” Ollama JSON mode is a no-op.** +**P0-2 โ€” Ollama JSON mode was a no-op.** โœ… **Fixed.** `format` is nested inside `options` (`src/ai/mod.rs:174-196`). Ollama reads `format` as a **top-level** request field, so JSON mode has never taken effect. Fix: hoist `format` to the request body, and prefer passing the `AiCommitSuggestion` JSON Schema rather than the string `"json"` โ€” Ollama has supported schema-constrained decoding since 0.5. -**P0-3 โ€” AI in default (safe) mode is given no signal at all.** +**P0-3 โ€” AI in default (safe) mode was given no signal at all.** โœ… **Fixed.** `src/cli/commands/group_commit.rs:400-413`: unless `--ai-allow-sensitive` is set, the user prompt contains only the group name and the default type/short, and instructs the model to work "without revealing code or filenames". The model is @@ -96,20 +96,24 @@ real signal by default โ€” path *shapes* (extension histogram, directory prefixe add/modify/delete counts, scope mappings from `.committy/config.toml`) โ€” none of which is file content. Reserve `--ai-allow-sensitive` for actual diff hunks. -**P0-4 โ€” `--ai` is silently ignored in `--mode apply`.** -The AI block lives only in the `"plan"` arm (`group_commit.rs:308-507`); the -`"apply"` arm (`:533+`) rebuilds groups from scratch with no LLM call. -`docs/src/content/docs/reference/ai-flags.mdx` documents -`--mode apply ... --ai --ai-provider ollama` as a working example. Either wire -apply to reuse the planned messages, or reject `--ai` with `--mode apply` and fix -the doc. Silently dropping a flag is the worst of the three options. +**P0-4 โ€” WITHDRAWN. This finding was wrong.** โŒ +The original text claimed `--ai` was silently ignored in `--mode apply`. It is +not: the apply arm has its own AI block (`group_commit.rs:586`) with its own +`suggest_commit` calls. The error came from reading a `grep` run through +`awk 'NR>=530'`, whose renumbered output was mistaken for line numbers in the +plan arm. The documented `--mode apply ... --ai` example always worked. -**P1-5 โ€” `--ai-diff-lines-per-file` is a dead flag.** +What is real in that area is **duplication**: `plan` and `apply` carried +byte-identical copies of both the group-building loop and the ~150-line AI +enrichment block. That duplication is what made the misreading possible. Both +are now extracted into `build_groups()` and `ai_user_prompt()` and shared. โœ… **Fixed.** + +**P1-5 โ€” `--ai-diff-lines-per-file` was a dead flag.** โœ… **Fixed** (removed). Declared at `group_commit.rs:143-147` as `_ai_diff_lines_per_file` and never read. It is documented in `ai-flags.mdx` and accepted on the command line. Remove it, or implement it as part of P0-3. -**P1-6 โ€” `ai-flags.mdx` overstates what is sent and understates what is not.** +**P1-6 โ€” `ai-flags.mdx` overstated what is sent.** โœ… **Fixed.** It claims `--ai-allow-sensitive` "may include snippets or diff lines from code" โ€” no diff is ever sent, in either mode. It also recommends `--ai-model openrouter/anthropic/claude-3.5-sonnet`, a model generation that is two @@ -250,14 +254,13 @@ steal both shorts. A test pins that trade-off. 0. ~~**P1-7**, **E-1**, **E-2**, **E-3**~~ โ€” done: capabilities expanded with consent metadata, global flag placement, `invalid_usage` envelope, env var documented. See ยง3.5. -1. **P0-1, P0-2** โ€” make Ollama actually function. -2. **P0-4, P1-5, P1-6, ยง3.3 removals** โ€” stop documenting and accepting things - that do not happen. -3. **P0-3** โ€” give the default-safe AI path real, non-sensitive signal. -4. **P1-8, P1-9, P1-10** โ€” cross-tool portability (`AGENTS.md` canonical, +1. ~~**P0-1, P0-2, P0-3**, **P1-5**, **P1-6**~~ โ€” done: Ollama functions, + the safe AI path carries redacted shape, dead flag and docs corrected. + **P0-4 was withdrawn as a false finding.** +2. **P1-8, P1-9, P1-10** โ€” cross-tool portability (`AGENTS.md` canonical, `.agents/skills/`, spec-complete frontmatter). -5. **P1-12** โ€” manifest and frontmatter tests so the step above cannot regress. -6. **P1-11, P2-13, P2-14, P2-15** โ€” reach, CI cost, consistency, guardrail honesty. +3. **P1-12** โ€” manifest and frontmatter tests so the step above cannot regress. +4. **P1-11, P2-13, P2-14, P2-15** โ€” reach, CI cost, consistency, guardrail honesty. ## 5. Sources diff --git a/docs/src/content/docs/reference/ai-flags.mdx b/docs/src/content/docs/reference/ai-flags.mdx index f290cf4..8a4fcea 100644 --- a/docs/src/content/docs/reference/ai-flags.mdx +++ b/docs/src/content/docs/reference/ai-flags.mdx @@ -11,7 +11,7 @@ This page enumerates flags, best practices, and security considerations. ## Enabling AI -Add `--ai` to `group-commit`: +Add `--ai` to `group-commit`. It applies in both `--mode plan` and `--mode apply`: ```bash committy --non-interactive group-commit --mode plan --output json \ @@ -29,48 +29,80 @@ committy --non-interactive group-commit --mode plan --output json \ - `--ai-max-tokens ` โ€” model tokens cap for responses - `--ai-temperature ` โ€” creativity setting - `--ai-timeout-ms ` โ€” request timeout -- `--no-ai-json-mode` โ€” do not force JSON-mode (advanced; prefer default) +- `--no-ai-json-mode` โ€” do not request structured output (advanced; prefer default) - `--ai-system-prompt ` โ€” inline system prompt override - `--ai-system-prompt-file ` โ€” load system prompt from file -- `--ai-file-limit ` โ€” limit how many files are described to AI -- `--ai-allow-sensitive` โ€” allow sending sensitive file content (off by default) +- `--ai-file-limit ` โ€” cap how many paths are listed under `--ai-allow-sensitive` +- `--ai-allow-sensitive` โ€” send real file paths instead of a redacted summary (off by default) + +## What is actually sent + +__No file content or diff text is ever sent__, in either mode. The difference +between the default and `--ai-allow-sensitive` is paths, not content. + +By default Committy sends a redacted *shape* of each group โ€” how many files, +an extension histogram, and the top-level directories involved: + +```text +Group: code +Default type: feat +Default short: update source +Change shape (no filenames or content): 7 file(s); types: .rs x6, .toml x1; areas: src, tests +``` + +Filenames, paths below the first segment, and file contents are all withheld. +This is enough signal for a model to write a sensible subject line without the +provider learning your module, product, or customer names. + +With `--ai-allow-sensitive`, the group's file paths are listed instead, capped +by `--ai-file-limit` and elided with `...` beyond it. Use it only when you trust +the provider with your repository layout. ## Security & privacy -- __Sensitive content is not sent__ by default. The tool shares filenames and summarized context. -- Only pass `--ai-allow-sensitive` if required and you trust the provider. This may include snippets or diff lines from code. -- Prefer using an API key via env var name with `--ai-api-key-env` to avoid leaking keys in process lists or logs. -- Use `--ai-file-limit` to reduce context size and exposure. -- Consider on-premise providers (`ollama`) for maximum privacy. +- Prefer the default. The redacted summary is usually sufficient. +- Pass the API key by env var *name* with `--ai-api-key-env` so the secret never + appears in a process list, shell history, or CI log. +- Use `--ai-file-limit` to bound exposure when `--ai-allow-sensitive` is on. +- Consider an on-premise provider (`ollama`) when no data should leave the host. ## Failure handling -- AI suggestions are validated by Committy's linter. If invalid or timed out, Committy falls back to safe default messages per group. -- JSON parsing is strict; malformed outputs are rejected and replaced with defaults. +- AI suggestions are validated by Committy's linter. If a suggestion is invalid + or the request times out, Committy keeps the safe default message for that group + and records the reason in `errors`. +- Structured output is requested by default, and malformed responses are rejected + rather than committed. ## Provider examples ### OpenRouter ```bash -# export your key and reference by env var name +# export your key and reference it by env var name export OPENROUTER_API_KEY=sk-or-... committy --non-interactive group-commit --mode plan --output json \ --ai --ai-provider openrouter \ - --ai-model openrouter/anthropic/claude-3.5-sonnet \ + --ai-model openrouter/anthropic/claude-sonnet-4.5 \ --ai-api-key-env OPENROUTER_API_KEY \ --ai-max-tokens 600 --ai-temperature 0.2 ``` +JSON mode sends `response_format: {"type": "json_object"}`. + ### Ollama (local) ```bash -# ensure Ollama is running locally; default base URL is http://127.0.0.1:11434 +# ensure Ollama is running locally; default base URL is http://localhost:11434 # no API key needed for local Ollama committy --non-interactive group-commit --mode apply --output json \ --ai --ai-provider ollama \ --ai-model llama3.1:8b \ - --ai-timeout-ms 30000 --ai-file-limit 64 + --ai-timeout-ms 30000 ``` + +Requests to Ollama set `stream: false` and pass a JSON Schema in the top-level +`format` field, so the model's output is constrained to the expected object +rather than merely asked for as JSON. diff --git a/src/ai/mod.rs b/src/ai/mod.rs index 4dfdf83..d245779 100644 --- a/src/ai/mod.rs +++ b/src/ai/mod.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use std::time::Duration; use thiserror::Error; @@ -8,8 +8,6 @@ pub enum LlmError { NotConfigured, #[error("AI provider request failed: {0}")] RequestFailed(String), - #[error("AI provider timeout")] - _Timeout, #[error("AI response parse error: {0}")] Parse(String), } @@ -27,12 +25,79 @@ pub trait LlmClient: Send + Sync { ) -> Result; } -// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] -// #[serde(rename_all = "kebab-case")] -// pub enum LlmProvider { -// OpenRouter, -// Ollama, -// } +/// JSON Schema describing [`AiCommitSuggestion`]. +/// +/// Ollama constrains decoding to a supplied schema, which is stricter than +/// asking for "some JSON" and removes most parse failures. +fn suggestion_schema() -> serde_json::Value { + serde_json::json!({ + "type": "object", + "properties": { + "commit_type": {"type": "string"}, + "short": {"type": "string"}, + "scope": {"type": "string"}, + "long": {"type": "string"}, + "message": {"type": "string"}, + }, + }) +} + +fn chat_messages(system_prompt: &str, user_prompt: &str) -> serde_json::Value { + serde_json::json!([ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ]) +} + +/// Build the OpenRouter `/chat/completions` request body. +fn openrouter_request_body( + model: &str, + system_prompt: &str, + user_prompt: &str, + json_mode: bool, + max_tokens: u32, + temperature: f32, +) -> serde_json::Value { + let mut body = serde_json::json!({ + "model": model, + "messages": chat_messages(system_prompt, user_prompt), + "max_tokens": max_tokens, + "temperature": temperature, + }); + if json_mode { + body["response_format"] = serde_json::json!({"type": "json_object"}); + } + body +} + +/// Build the Ollama `/api/chat` request body. +/// +/// Two details are load-bearing and were wrong before: `stream` must be false +/// (the endpoint defaults to true and would return newline-delimited events +/// that cannot deserialize as one object), and `format` is a **top-level** +/// field, not an entry under `options`. +fn ollama_request_body( + model: &str, + system_prompt: &str, + user_prompt: &str, + json_mode: bool, + max_tokens: u32, + temperature: f32, +) -> serde_json::Value { + let mut body = serde_json::json!({ + "model": model, + "messages": chat_messages(system_prompt, user_prompt), + "stream": false, + "options": { + "temperature": temperature, + "num_predict": max_tokens as i32, + }, + }); + if json_mode { + body["format"] = suggestion_schema(); + } + body +} pub struct OpenRouterClient { pub base_url: String, @@ -63,42 +128,14 @@ impl LlmClient for OpenRouterClient { let url = format!("{}/chat/completions", self.base_url.trim_end_matches('/')); - #[derive(Serialize)] - struct Message<'a> { - role: &'a str, - content: &'a str, - } - #[derive(Serialize)] - struct RequestBody<'a> { - model: &'a str, - messages: Vec>, - max_tokens: u32, - temperature: f32, - #[serde(skip_serializing_if = "Option::is_none")] - response_format: Option, - } - let response_format = if json_mode { - Some(serde_json::json!({"type": "json_object"})) - } else { - None - }; - - let body = RequestBody { - model: &self.model, - messages: vec![ - Message { - role: "system", - content: system_prompt, - }, - Message { - role: "user", - content: user_prompt, - }, - ], + let body = openrouter_request_body( + &self.model, + system_prompt, + user_prompt, + json_mode, max_tokens, temperature, - response_format, - }; + ); let client = reqwest::Client::builder() .timeout(Duration::from_millis(timeout_ms)) @@ -159,43 +196,14 @@ impl LlmClient for OllamaClient { ) -> Result { let url = format!("{}/api/chat", self.base_url.trim_end_matches('/')); - #[derive(Serialize)] - struct Message<'a> { - role: &'a str, - content: &'a str, - } - #[derive(Serialize)] - struct RequestBody<'a> { - model: &'a str, - messages: Vec>, - options: OllamaOptions, - } - #[derive(Serialize)] - struct OllamaOptions { - temperature: f32, - num_predict: i32, - #[serde(skip_serializing_if = "Option::is_none")] - format: Option<&'static str>, - } - - let body = RequestBody { - model: &self.model, - messages: vec![ - Message { - role: "system", - content: system_prompt, - }, - Message { - role: "user", - content: user_prompt, - }, - ], - options: OllamaOptions { - temperature, - num_predict: max_tokens as i32, - format: if json_mode { Some("json") } else { None }, - }, - }; + let body = ollama_request_body( + &self.model, + system_prompt, + user_prompt, + json_mode, + max_tokens, + temperature, + ); let client = reqwest::Client::builder() .timeout(Duration::from_millis(timeout_ms)) @@ -239,3 +247,73 @@ pub struct AiCommitSuggestion { pub long: Option, pub message: Option, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ollama_body_disables_streaming() { + let body = ollama_request_body("llama3.2", "sys", "user", true, 256, 0.2); + assert_eq!( + body["stream"], + serde_json::Value::Bool(false), + "Ollama defaults /api/chat to stream:true; a streaming response is \ + newline-delimited and will not deserialize as one object" + ); + } + + #[test] + fn ollama_body_puts_format_at_the_top_level() { + let body = ollama_request_body("llama3.2", "sys", "user", true, 256, 0.2); + assert!( + body["format"].is_object(), + "Ollama reads `format` as a top-level field; nesting it under `options` \ + silently disables constrained decoding. Got: {body}" + ); + assert!( + body["options"]["format"].is_null(), + "`format` must not also be left inside `options`" + ); + } + + #[test] + fn ollama_body_constrains_to_the_suggestion_schema() { + let body = ollama_request_body("llama3.2", "sys", "user", true, 256, 0.2); + let props = &body["format"]["properties"]; + for field in ["commit_type", "short", "scope", "long", "message"] { + assert_eq!( + props[field]["type"], + serde_json::Value::String("string".into()), + "schema must describe AiCommitSuggestion::{field}" + ); + } + } + + #[test] + fn ollama_body_omits_format_when_json_mode_is_off() { + let body = ollama_request_body("llama3.2", "sys", "user", false, 256, 0.2); + assert!(body["format"].is_null()); + assert_eq!(body["stream"], serde_json::Value::Bool(false)); + } + + #[test] + fn ollama_body_carries_sampling_options() { + let body = ollama_request_body("llama3.2", "sys", "user", true, 512, 0.7); + assert_eq!(body["options"]["num_predict"], serde_json::json!(512)); + assert_eq!(body["model"], serde_json::json!("llama3.2")); + assert_eq!(body["messages"][0]["role"], serde_json::json!("system")); + assert_eq!(body["messages"][1]["content"], serde_json::json!("user")); + } + + #[test] + fn openrouter_body_requests_json_object_when_asked() { + let body = openrouter_request_body("m", "sys", "user", true, 256, 0.2); + assert_eq!( + body["response_format"]["type"], + serde_json::json!("json_object") + ); + let plain = openrouter_request_body("m", "sys", "user", false, 256, 0.2); + assert!(plain["response_format"].is_null()); + } +} diff --git a/src/cli/commands/group_commit.rs b/src/cli/commands/group_commit.rs index c13e869..20d540e 100644 --- a/src/cli/commands/group_commit.rs +++ b/src/cli/commands/group_commit.rs @@ -142,10 +142,6 @@ pub struct GroupCommitCommand { #[structopt(long = "ai-file-limit", default_value = "20")] ai_file_limit: usize, - /// Diff lines per file sent to AI - #[structopt(long = "ai-diff-lines-per-file", default_value = "80")] - _ai_diff_lines_per_file: usize, - /// Allow sending sensitive content to external AI providers #[structopt(long = "ai-allow-sensitive")] ai_allow_sensitive: bool, @@ -181,7 +177,6 @@ impl Default for GroupCommitCommand { ai_system_prompt: None, ai_system_prompt_file: None, ai_file_limit: 20, - _ai_diff_lines_per_file: 80, ai_allow_sensitive: false, git_config: vec![], repo_path: PathBuf::from("."), @@ -318,41 +313,7 @@ impl Command for GroupCommitCommand { })?; let _git_command_config = crate::git::resolve_git_command_config(repo_path, &self.git_config)?; - let mut by_group: std::collections::BTreeMap> = [ - (GroupName::Docs, vec![]), - (GroupName::Tests, vec![]), - (GroupName::Ci, vec![]), - (GroupName::Deps, vec![]), - (GroupName::Build, vec![]), - (GroupName::Chore, vec![]), - (GroupName::Code, vec![]), - ] - .into_iter() - .collect(); - - for f in files { - let g = classify_file(&f); - if let Some(v) = by_group.get_mut(&g) { - v.push(f); - } - } - - let mut groups: Vec = Vec::new(); - for (name, files) in by_group.into_iter() { - if files.is_empty() { - continue; - } - let commit_type = default_type_for(&name).to_string(); - let short = default_short_for(&name).to_string(); - let message = format_commit_message(&commit_type, false, "", &short, ""); - groups.push(PlanGroup { - name, - commit_type, - files, - suggested_message: message, - issues: None, - }); - } + let mut groups = build_groups(files); let mut errors: Vec = Vec::new(); @@ -383,38 +344,14 @@ impl Command for GroupCommitCommand { .map_err(|e| CliError::Generic(e.to_string()))?; for g in groups.iter_mut() { - let files_preview: Vec = if self.ai_allow_sensitive { - let mut v = g - .files - .iter() - .take(self.ai_file_limit) - .cloned() - .collect::>(); - if g.files.len() > self.ai_file_limit { - v.push("...".to_string()); - } - v - } else { - vec![] - }; - let user_prompt = if self.ai_allow_sensitive { - format!( - "Group: {}\nDefault type: {}\nDefault short: {}\nFiles (truncated):\n- {}\nReturn a JSON object with fields: commit_type, short, scope, long, message.", - group_name_str(g.name), - g.commit_type, - default_short_for(&g.name), - files_preview.join("\n- ") - ) - } else { - format!( - "Group: {}\nDefault type: {}\nDefault short: {}\nWithout revealing code or filenames, suggest a better short description if needed. Return JSON.", - group_name_str(g.name), - g.commit_type, - default_short_for(&g.name) - ) - }; + let user_prompt = ai_user_prompt( + &g.name, + &g.commit_type, + &g.files, + self.ai_allow_sensitive, + self.ai_file_limit, + ); - // Prepare client per provider let result: Result = match provider { "openrouter" => { let base = self @@ -544,41 +481,7 @@ impl Command for GroupCommitCommand { crate::git::resolve_git_command_config(repo_path, &self.git_config)?; // Build groups as in plan let files = list_changed_files_from(&self.repo_path, self.include_unstaged)?; - let mut by_group: std::collections::BTreeMap> = [ - (GroupName::Docs, vec![]), - (GroupName::Tests, vec![]), - (GroupName::Ci, vec![]), - (GroupName::Deps, vec![]), - (GroupName::Build, vec![]), - (GroupName::Chore, vec![]), - (GroupName::Code, vec![]), - ] - .into_iter() - .collect(); - - for f in files { - let g = classify_file(&f); - if let Some(v) = by_group.get_mut(&g) { - v.push(f); - } - } - - let mut groups: Vec = Vec::new(); - for (name, files) in by_group.into_iter() { - if files.is_empty() { - continue; - } - let commit_type = default_type_for(&name).to_string(); - let short = default_short_for(&name).to_string(); - let message = format_commit_message(&commit_type, false, "", &short, ""); - groups.push(PlanGroup { - name, - commit_type, - files, - suggested_message: message, - issues: None, - }); - } + let mut groups = build_groups(files); let mut errors: Vec = Vec::new(); @@ -608,32 +511,13 @@ impl Command for GroupCommitCommand { .map_err(|e| CliError::Generic(e.to_string()))?; for g in groups.iter_mut() { - let files_preview: Vec = if self.ai_allow_sensitive { - let mut v = g - .files - .iter() - .take(self.ai_file_limit) - .cloned() - .collect::>(); - if g.files.len() > self.ai_file_limit { - v.push("...".to_string()); - } - v - } else { - vec![] - }; - - let user_prompt = if self.ai_allow_sensitive { - format!( - "Group: {}\nDefault type: {}\nDefault short: {}\nFiles (truncated):\n- {}\nReturn a JSON object with fields: commit_type, short, scope, long, message.", - group_name_str(g.name), g.commit_type, default_short_for(&g.name), files_preview.join("\n- ") - ) - } else { - format!( - "Group: {}\nDefault type: {}\nDefault short: {}\nWithout revealing code or filenames, suggest a better short description if needed. Return JSON.", - group_name_str(g.name), g.commit_type, default_short_for(&g.name) - ) - }; + let user_prompt = ai_user_prompt( + &g.name, + &g.commit_type, + &g.files, + self.ai_allow_sensitive, + self.ai_file_limit, + ); let result: Result = match provider { "openrouter" => { @@ -889,6 +773,129 @@ impl Command for GroupCommitCommand { } } +/// Summarise a change set without naming any file. +/// +/// The default (non-sensitive) AI path used to send only the group name, which +/// gave the model nothing to improve on. This sends *shape* โ€” how many files, +/// which extensions, which top-level areas โ€” and never a filename, a path +/// below the first segment, or any file content. +fn redacted_shape_summary(files: &[String]) -> String { + use std::collections::BTreeMap; + + let mut extensions: BTreeMap = BTreeMap::new(); + let mut areas: std::collections::BTreeSet = std::collections::BTreeSet::new(); + + for file in files { + let trimmed = file.trim_start_matches("./"); + let extension = std::path::Path::new(trimmed) + .extension() + .and_then(|e| e.to_str()) + .map(|e| format!(".{e}")) + .unwrap_or_else(|| "(no extension)".to_string()); + *extensions.entry(extension).or_insert(0) += 1; + + // First path segment only: "src", "docs", "tests". Anything deeper can + // carry product or customer names. + let area = trimmed + .split('/') + .next() + .filter(|segment| !segment.is_empty() && trimmed.contains('/')) + .unwrap_or("(repository root)"); + areas.insert(area.to_string()); + } + + let extension_list = extensions + .iter() + .map(|(extension, count)| format!("{extension} x{count}")) + .collect::>() + .join(", "); + + format!( + "{} file(s); types: {}; areas: {}", + files.len(), + extension_list, + areas.into_iter().collect::>().join(", ") + ) +} + +/// The per-group user prompt sent to the model. +/// +/// `allow_sensitive` is the only switch that lets real paths leave the machine. +fn ai_user_prompt( + name: &GroupName, + commit_type: &str, + files: &[String], + allow_sensitive: bool, + file_limit: usize, +) -> String { + let header = format!( + "Group: {}\nDefault type: {}\nDefault short: {}", + group_name_str(*name), + commit_type, + default_short_for(name) + ); + + if allow_sensitive { + let mut listed: Vec = files.iter().take(file_limit).cloned().collect(); + if files.len() > file_limit { + listed.push("...".to_string()); + } + format!( + "{header}\nFiles (truncated):\n- {}\nReturn a JSON object with fields: \ + commit_type, short, scope, long, message.", + listed.join("\n- ") + ) + } else { + format!( + "{header}\nChange shape (no filenames or content): {}\nSuggest a better short \ + description for this group. Return a JSON object with fields: commit_type, \ + short, scope, long, message.", + redacted_shape_summary(files) + ) + } +} + +/// Classify a change set into commit groups. +/// +/// Shared by `plan` and `apply`; they previously carried byte-identical copies. +fn build_groups(files: Vec) -> Vec { + let mut by_group: std::collections::BTreeMap> = [ + (GroupName::Docs, vec![]), + (GroupName::Tests, vec![]), + (GroupName::Ci, vec![]), + (GroupName::Deps, vec![]), + (GroupName::Build, vec![]), + (GroupName::Chore, vec![]), + (GroupName::Code, vec![]), + ] + .into_iter() + .collect(); + + for file in files { + let group = classify_file(&file); + if let Some(bucket) = by_group.get_mut(&group) { + bucket.push(file); + } + } + + by_group + .into_iter() + .filter(|(_, files)| !files.is_empty()) + .map(|(name, files)| { + let commit_type = default_type_for(&name).to_string(); + let short = default_short_for(&name).to_string(); + let suggested_message = format_commit_message(&commit_type, false, "", &short, ""); + PlanGroup { + name, + commit_type, + files, + suggested_message, + issues: None, + } + }) + .collect() +} + fn validate_group_messages( repo_path: &Path, groups: &mut [PlanGroup], @@ -912,3 +919,85 @@ fn validate_group_messages( Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + fn files() -> Vec { + vec![ + "src/cli/commands/secret_feature.rs".to_string(), + "src/cli/commands/other.rs".to_string(), + "docs/internal/roadmap.md".to_string(), + ] + } + + #[test] + fn shape_summary_describes_without_naming() { + let summary = redacted_shape_summary(&files()); + + assert!( + summary.contains('3'), + "must report the file count: {summary}" + ); + assert!(summary.contains(".rs"), "must report extensions: {summary}"); + assert!(summary.contains(".md"), "must report extensions: {summary}"); + assert!( + summary.contains("src"), + "must report top-level areas: {summary}" + ); + assert!( + summary.contains("docs"), + "must report top-level areas: {summary}" + ); + + for leaked in ["secret_feature", "other.rs", "roadmap.md", "internal"] { + assert!( + !summary.contains(leaked), + "shape summary leaked `{leaked}`: {summary}" + ); + } + } + + #[test] + fn default_prompt_carries_shape_but_no_paths() { + let prompt = ai_user_prompt(&GroupName::Code, "feat", &files(), false, 20); + + assert!( + prompt.contains(&redacted_shape_summary(&files())), + "the safe prompt must include the redacted shape: {prompt}" + ); + for leaked in ["secret_feature", "roadmap.md", "src/cli/commands"] { + assert!(!prompt.contains(leaked), "safe prompt leaked `{leaked}`"); + } + } + + #[test] + fn sensitive_prompt_lists_paths_and_respects_the_limit() { + let prompt = ai_user_prompt(&GroupName::Code, "feat", &files(), true, 2); + + assert!(prompt.contains("src/cli/commands/secret_feature.rs")); + assert!( + prompt.contains("..."), + "over-limit file lists must be elided: {prompt}" + ); + assert!( + !prompt.contains("docs/internal/roadmap.md"), + "third path exceeds the limit of 2" + ); + } + + #[test] + fn build_groups_is_shared_between_plan_and_apply() { + let groups = build_groups(files()); + + let names: Vec = groups.iter().map(|g| g.name).collect(); + assert!(names.contains(&GroupName::Code)); + assert!(names.contains(&GroupName::Docs)); + assert!( + groups.iter().all(|g| !g.files.is_empty()), + "empty groups must be dropped" + ); + assert!(groups.iter().all(|g| !g.suggested_message.is_empty())); + } +} From 97c0c5594a6927ef941c43e4d4e94ef91d418a9f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 17:19:49 +0000 Subject: [PATCH 16/24] fix(ci): run checks on pull requests targeting nested branch names The pull_request trigger filtered on branches: ["*"], and in Actions branch filters `*` matches everything except `/`. Any pull request whose base branch contains a slash therefore matched nothing and ran no checks at all -- silently, since a workflow that does not trigger looks the same as one with nothing to report. That covers every stacked pull request in this series (all but the first target a claude/... base and have zero check runs), and would equally cover anything based on a feat/ or fix/ prefix. `**` matches across slashes, which is what the filter meant. --- .github/workflows/lint.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3c35008..2adccc8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,7 +4,11 @@ on: push: branches: [main, develop] pull_request: - branches: ["*"] + # `**` rather than `*`: in Actions branch filters `*` does not match `/`, + # so a pull request targeting any branch with a slash in its name -- every + # stacked pull request, and anything under a `feat/` or `fix/` prefix -- + # ran no checks at all. + branches: ["**"] env: CARGO_TERM_COLOR: always From 30ca219fd5dda2b55e83b04359904e567f055f25 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 11:27:05 +0000 Subject: [PATCH 17/24] docs(agents): make AGENTS.md canonical and reach every runtime AGENTS.md and CLAUDE.md were near-identical files kept in sync by hand; the diff was two lines and had already started drifting (the trailer ban landed in one of them only). AGENTS.md is now the single source and CLAUDE.md is an @AGENTS.md import plus the one genuinely Claude-specific line, which is the pattern the ecosystem settled on. Two runtimes read neither file, so they now get thin pointers: .github/copilot-instructions.md and GEMINI.md. Skills are exposed at .agents/skills, the cross-tool discovery path that Codex, Claude Code and Copilot CLI all resolve, via a symlink to the canonical copy under plugins/committy/skills -- no duplication. A test guards the symlink, because a checkout that materialises it as a plain file would break discovery silently. Skill frontmatter gains the spec's optional fields: license, allowed-tools (so security-conscious runtimes can pre-authorise exactly what each skill needs) and metadata carrying a skill version independent of the CLI version. committy-release referenced siblings with Codex's `$skill` syntax inside a body shared by both runtimes; they are now named plainly and each runtime resolves them its own way. The COMMITTY_NONINTERACTIVE docs test now accepts a file that defers to AGENTS.md rather than restating it, which is what the split requires. --- .agents/skills | 1 + .github/copilot-instructions.md | 6 +++ AGENTS.md | 2 +- CLAUDE.md | 35 ++-------------- GEMINI.md | 5 +++ .../committy/skills/committy-branch/SKILL.md | 5 +++ .../committy/skills/committy-commit/SKILL.md | 5 +++ .../committy/skills/committy-enforce/SKILL.md | 5 +++ .../committy/skills/committy-release/SKILL.md | 7 +++- tests/agent_ergonomics_tests.rs | 40 +++++++++++++++++-- 10 files changed, 75 insertions(+), 36 deletions(-) create mode 120000 .agents/skills create mode 100644 .github/copilot-instructions.md create mode 100644 GEMINI.md diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 0000000..6153a62 --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +../plugins/committy/skills \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..3a21eec --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,6 @@ +# Copilot instructions + +See [`AGENTS.md`](../AGENTS.md) at the repository root. It is the canonical +guide for every coding agent working in this repository and applies in full: +build and verify commands, the CLI-first agent workflow, architecture, and the +commit and pull-request rules. diff --git a/AGENTS.md b/AGENTS.md index 5c30926..37410c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,4 +31,4 @@ Tests are in `tests/`; CLI integration tests use `assert_cmd` and temporary repo - `src/workflow/`, `src/versioning/`, `src/packages/`: release side effects - `plugins/committy/`: shared Codex and Claude skills -PRs target `develop`. Commits follow Conventional Commits. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`. +PRs target `develop`. Commits follow Conventional Commits. Never add AI attribution trailers โ€” no `Co-Authored-By:`, no `Claude-Session:`, no equivalent โ€” to commit messages or PR descriptions, even when tooling suggests them. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`. diff --git a/CLAUDE.md b/CLAUDE.md index cec9cda..be0811b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,34 +1,7 @@ -# Committy Claude guide +@AGENTS.md -Committy is a Rust CLI for conventional commits, branch policy, grouped commits, releases, and multi-package version updates. The checkout is CLI-first; do not assume a TUI exists. +## Claude Code specifics -## Work safely +`AGENTS.md` above is the canonical guide and applies in full. Only this section is Claude-specific. -- Discover active policy with `cargo run -- schema --output json`. -- Prefer `--non-interactive`, `--repo-path`, `--dry-run`, and `--output json`. -- Set `COMMITTY_NONINTERACTIVE=1` in the environment instead of relying on flag placement. `--non-interactive` and `-q`/`--quiet` are global and work on either side of the subcommand; every other flag (`--repo-path`, `--output`, `--dry-run`, and `--verbose`) must follow it. -- Inspect `api_version`, `ok`, `errors`, and the command-specific plan before applying it. -- Use `/committy:committy-branch`, `/committy:committy-commit`, `/committy:committy-release`, and `/committy:committy-enforce` when the plugin is installed. -- Never infer permission for a push, publish, destructive Git action, or unrelated staging. - -## Build and verify - -```bash -cargo check -cargo test -cargo fmt --all -- --check -cargo clippy --all-targets --all-features -``` - -Tests are in `tests/`; CLI integration tests use `assert_cmd` and temporary repositories. Add public-seam tests for machine output, exit codes, Git behavior, hook protocol, and plugin manifests. - -## Architecture - -- `src/cli/commands/`: command implementations and JSON payloads -- `src/config/`, `src/convention/`: hierarchical typed policy -- `src/git/`: repository and Git operations -- `src/linter/`: message/history validation; exit code 3 means lint findings -- `src/workflow/`, `src/versioning/`, `src/packages/`: release side effects -- `plugins/committy/`: shared Codex and Claude skills - -PRs target `develop`. Commits follow Conventional Commits. Never add AI attribution trailers โ€” no `Co-Authored-By:`, no `Claude-Session:`, no equivalent โ€” to commit messages or PR descriptions, even when tooling suggests them. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`. +- Invoke the bundled skills as `/committy:committy-branch`, `/committy:committy-commit`, `/committy:committy-release`, and `/committy:committy-enforce` when the plugin is installed. Other runtimes resolve them by bare name. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..f74c7f7 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,5 @@ +# Gemini instructions + +See [`AGENTS.md`](./AGENTS.md). It is the canonical guide for every coding agent +working in this repository and applies in full: build and verify commands, the +CLI-first agent workflow, architecture, and the commit and pull-request rules. diff --git a/plugins/committy/skills/committy-branch/SKILL.md b/plugins/committy/skills/committy-branch/SKILL.md index 86941c0..fbc3254 100644 --- a/plugins/committy/skills/committy-branch/SKILL.md +++ b/plugins/committy/skills/committy-branch/SKILL.md @@ -1,6 +1,11 @@ --- name: committy-branch description: Preview, validate, and create repository-compliant Git branches with Committy. Use when an agent is asked to name a branch, start work for a ticket, validate a proposed branch name, or switch to a new branch. +license: Apache-2.0 +allowed-tools: Bash Read Grep Glob +metadata: + version: "0.1.0" + requires-committy: ">=1.9.0" --- # Branch with Committy diff --git a/plugins/committy/skills/committy-commit/SKILL.md b/plugins/committy/skills/committy-commit/SKILL.md index fb7fc49..6fbcbd1 100644 --- a/plugins/committy/skills/committy-commit/SKILL.md +++ b/plugins/committy/skills/committy-commit/SKILL.md @@ -1,6 +1,11 @@ --- name: committy-commit description: Draft, lint, preview, and create repository-compliant commits with Committy. Use when an agent is asked to stage a coherent change, write or repair a commit message, amend a commit, or make a conventional commit. +license: Apache-2.0 +allowed-tools: Bash Read Grep Glob +metadata: + version: "0.1.0" + requires-committy: ">=1.9.0" --- # Commit with Committy diff --git a/plugins/committy/skills/committy-enforce/SKILL.md b/plugins/committy/skills/committy-enforce/SKILL.md index b4308ca..496e1c8 100644 --- a/plugins/committy/skills/committy-enforce/SKILL.md +++ b/plugins/committy/skills/committy-enforce/SKILL.md @@ -1,6 +1,11 @@ --- name: committy-enforce description: Configure and enforce repository commit and branch conventions with Committy Git hooks and CI. Use when an agent is asked to adopt Committy, define typed convention descriptions or branch ticket rules, install hooks, or add convention checks to GitHub Actions. +license: Apache-2.0 +allowed-tools: Bash Read Grep Glob Edit Write +metadata: + version: "0.1.0" + requires-committy: ">=1.9.0" --- # Enforce with Committy diff --git a/plugins/committy/skills/committy-release/SKILL.md b/plugins/committy/skills/committy-release/SKILL.md index 0fdff58..8177a54 100644 --- a/plugins/committy/skills/committy-release/SKILL.md +++ b/plugins/committy/skills/committy-release/SKILL.md @@ -1,6 +1,11 @@ --- name: committy-release description: Plan and apply version bumps, changelogs, tags, and prereleases with Committy. Use when an agent is asked to prepare, preview, tag, or publish a release, or inspect the release impact of commit history. +license: Apache-2.0 +allowed-tools: Bash Read Grep Glob +metadata: + version: "0.1.0" + requires-committy: ">=1.9.0" --- # Release with Committy @@ -12,4 +17,4 @@ description: Plan and apply version bumps, changelogs, tags, and prereleases wit 5. Apply only after mutation is authorized. Publishing additionally requires the CLI confirmation flag; never infer remote-mutation consent. 6. Report resulting versions, tags, and publish state. -Use `$committy-commit` for ordinary commits and `$committy-enforce` for hooks or CI setup. +Use the `committy-commit` skill for ordinary commits and `committy-enforce` for hooks or CI setup. diff --git a/tests/agent_ergonomics_tests.rs b/tests/agent_ergonomics_tests.rs index 6c9f2f6..d6f0f46 100644 --- a/tests/agent_ergonomics_tests.rs +++ b/tests/agent_ergonomics_tests.rs @@ -413,19 +413,24 @@ fn test_capabilities_declare_consent_metadata() { fn test_agent_docs_mention_the_non_interactive_env_var() { let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + // `AGENTS.md` is canonical. Per-runtime files may either restate the rule + // or defer to it; what must never happen is a runtime being told nothing. let agent_facing = [ "AGENTS.md", "CLAUDE.md", + "GEMINI.md", + ".github/copilot-instructions.md", "docs/src/content/docs/reference/agent-workflows.mdx", ]; for relative in agent_facing { let body = fs::read_to_string(repo_root.join(relative)) .unwrap_or_else(|e| panic!("cannot read {relative}: {e}")); + let defers_to_canonical = body.contains("AGENTS.md"); assert!( - body.contains("COMMITTY_NONINTERACTIVE"), - "{relative} must document COMMITTY_NONINTERACTIVE; it is the escape hatch \ - that makes flag placement irrelevant for agents" + body.contains("COMMITTY_NONINTERACTIVE") || defers_to_canonical, + "{relative} must document COMMITTY_NONINTERACTIVE or point at AGENTS.md; \ + it is the escape hatch that makes flag placement irrelevant for agents" ); // Documenting `-v` as global would be worse than not documenting @@ -436,3 +441,32 @@ fn test_agent_docs_mention_the_non_interactive_env_var() { ); } } + +/// The cross-tool skill discovery path must resolve to the real skills. +/// +/// `.agents/skills` is a symlink. If a checkout materialises it as a plain +/// file (Windows without developer mode, or `core.symlinks=false`), discovery +/// silently breaks for every runtime that reads that path. +#[test] +fn test_skills_are_discoverable_at_the_cross_tool_path() { + let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + let cross_tool = repo_root.join(".agents/skills"); + + assert!( + cross_tool.is_dir(), + ".agents/skills must resolve to a directory; if this is a plain file, \ + the symlink did not survive checkout" + ); + + for skill in [ + "committy-branch", + "committy-commit", + "committy-enforce", + "committy-release", + ] { + assert!( + cross_tool.join(skill).join("SKILL.md").is_file(), + "{skill}/SKILL.md must be reachable via .agents/skills" + ); + } +} From 2a4b19ade566ffd4167940b91ca766ba52fd0606 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 11:28:27 +0000 Subject: [PATCH 18/24] test(agents): cover the plugin manifests and skill frontmatter AGENTS.md asks for public-seam tests over "machine output, exit codes, Git behavior, hook protocol, and plugin manifests". The first four were covered; nothing compiled or executed the manifests, so a typo in any of the six JSON and Markdown files shipped silently. Nine tests: both marketplace manifests parse and their declared sources resolve to real directories carrying the expected plugin manifest; the Claude and Codex plugin manifests agree on name, version and license; the Codex skills path resolves; skill frontmatter uses only the keys the Agent Skills spec allows and carries the two required ones; a skill's frontmatter name matches its directory; descriptions carry a trigger rather than just a capability; no skill body uses runtime-specific invocation syntax, since that body is shared by Codex and Claude Code; and every skill is reachable from the cross-tool .agents/skills path. Each assertion was mutation-checked -- a bogus frontmatter key and a marketplace source pointing at a missing directory both fail with the message that names the problem. --- tests/plugin_manifest_tests.rs | 257 +++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 tests/plugin_manifest_tests.rs diff --git a/tests/plugin_manifest_tests.rs b/tests/plugin_manifest_tests.rs new file mode 100644 index 0000000..d18fb4a --- /dev/null +++ b/tests/plugin_manifest_tests.rs @@ -0,0 +1,257 @@ +//! Public-seam tests for the agent plugin surface. +//! +//! `AGENTS.md` asks for tests over "machine output, exit codes, Git behavior, +//! hook protocol, and plugin manifests". The first four were covered; these +//! close the last. A typo in any of these files currently ships silently, +//! because nothing compiles or executes them. + +use serde_json::Value; +use std::collections::BTreeSet; +use std::fs; +use std::path::{Path, PathBuf}; + +fn repo_root() -> &'static Path { + Path::new(env!("CARGO_MANIFEST_DIR")) +} + +fn read_json(relative: &str) -> Value { + let path = repo_root().join(relative); + let body = fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {relative}: {e}")); + serde_json::from_str(&body).unwrap_or_else(|e| panic!("{relative} is not valid JSON: {e}")) +} + +fn skill_dirs() -> Vec { + let skills = repo_root().join("plugins/committy/skills"); + let mut dirs: Vec = fs::read_dir(&skills) + .expect("plugins/committy/skills must exist") + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .filter(|path| path.is_dir()) + .collect(); + dirs.sort(); + assert!(!dirs.is_empty(), "no skills found"); + dirs +} + +/// Split `SKILL.md` into its YAML frontmatter and body. +fn frontmatter_of(skill_md: &Path) -> String { + let body = fs::read_to_string(skill_md) + .unwrap_or_else(|e| panic!("cannot read {}: {e}", skill_md.display())); + let rest = body + .strip_prefix("---\n") + .unwrap_or_else(|| panic!("{} must open with YAML frontmatter", skill_md.display())); + let end = rest + .find("\n---\n") + .unwrap_or_else(|| panic!("{} frontmatter is not terminated", skill_md.display())); + rest[..end].to_string() +} + +// --------------------------------------------------------------------------- +// Marketplace and plugin manifests +// --------------------------------------------------------------------------- + +#[test] +fn claude_marketplace_manifest_is_well_formed() { + let manifest = read_json(".claude-plugin/marketplace.json"); + + assert!(manifest["name"].is_string(), "marketplace needs a name"); + let plugins = manifest["plugins"] + .as_array() + .expect("marketplace needs a plugins array"); + assert!(!plugins.is_empty()); + + for plugin in plugins { + let source = plugin["source"] + .as_str() + .expect("each plugin entry needs a string source"); + let resolved = repo_root().join(source.trim_start_matches("./")); + assert!( + resolved.is_dir(), + "marketplace points at `{source}`, which does not exist" + ); + assert!( + resolved.join(".claude-plugin/plugin.json").is_file(), + "`{source}` has no .claude-plugin/plugin.json" + ); + } +} + +#[test] +fn codex_marketplace_manifest_is_well_formed() { + let manifest = read_json(".agents/plugins/marketplace.json"); + + let plugins = manifest["plugins"] + .as_array() + .expect("marketplace needs a plugins array"); + assert!(!plugins.is_empty()); + + for plugin in plugins { + let path = plugin["source"]["path"] + .as_str() + .expect("each plugin entry needs source.path"); + let resolved = repo_root().join(path.trim_start_matches("./")); + assert!( + resolved.is_dir(), + "marketplace points at `{path}`, which does not exist" + ); + assert!( + resolved.join(".codex-plugin/plugin.json").is_file(), + "`{path}` has no .codex-plugin/plugin.json" + ); + } +} + +#[test] +fn plugin_manifests_agree_on_identity() { + let claude = read_json("plugins/committy/.claude-plugin/plugin.json"); + let codex = read_json("plugins/committy/.codex-plugin/plugin.json"); + + for field in ["name", "version", "license"] { + assert_eq!( + claude[field], codex[field], + "plugin manifests disagree on `{field}`; they describe the same plugin" + ); + } + assert!(claude["name"].is_string()); + assert!( + claude["version"].is_string(), + "plugin needs a version string" + ); +} + +#[test] +fn codex_manifest_skills_path_resolves() { + let codex = read_json("plugins/committy/.codex-plugin/plugin.json"); + let skills = codex["skills"] + .as_str() + .expect("codex manifest declares a skills path"); + let resolved = repo_root() + .join("plugins/committy") + .join(skills.trim_start_matches("./")); + assert!( + resolved.is_dir(), + "codex manifest points skills at `{skills}`, which does not resolve" + ); +} + +// --------------------------------------------------------------------------- +// SKILL.md frontmatter, against the Agent Skills spec +// --------------------------------------------------------------------------- + +/// The spec fixes the allowed key set; anything else fails validation in a +/// spec-compliant runtime, so an unrecognised key here is a shipping bug. +const SPEC_KEYS: &[&str] = &[ + "name", + "description", + "license", + "allowed-tools", + "metadata", + "compatibility", +]; + +#[test] +fn skill_frontmatter_uses_only_spec_keys() { + for dir in skill_dirs() { + let skill_md = dir.join("SKILL.md"); + let frontmatter = frontmatter_of(&skill_md); + + let keys: BTreeSet = frontmatter + .lines() + .filter(|line| !line.starts_with(char::is_whitespace) && !line.trim().is_empty()) + .filter_map(|line| line.split_once(':').map(|(key, _)| key.trim().to_string())) + .collect(); + + for key in &keys { + assert!( + SPEC_KEYS.contains(&key.as_str()), + "{}: `{key}` is not an Agent Skills frontmatter key (allowed: {SPEC_KEYS:?})", + skill_md.display() + ); + } + for required in ["name", "description"] { + assert!( + keys.contains(required), + "{}: `{required}` is required", + skill_md.display() + ); + } + } +} + +#[test] +fn skill_name_matches_its_directory() { + for dir in skill_dirs() { + let expected = dir.file_name().unwrap().to_str().unwrap(); + let frontmatter = frontmatter_of(&dir.join("SKILL.md")); + let name = frontmatter + .lines() + .find_map(|line| line.strip_prefix("name:")) + .map(str::trim) + .expect("skill needs a name"); + assert_eq!( + name, expected, + "skill directory and frontmatter name must match, or discovery breaks" + ); + } +} + +#[test] +fn skill_descriptions_say_when_to_use_the_skill() { + for dir in skill_dirs() { + let frontmatter = frontmatter_of(&dir.join("SKILL.md")); + let description = frontmatter + .lines() + .find_map(|line| line.strip_prefix("description:")) + .map(str::trim) + .expect("skill needs a description"); + + // Description is the only thing a runtime sees before loading the body, + // so it has to carry the trigger, not just the capability. + assert!( + description.len() > 40, + "{}: description is too thin to route on: {description}", + dir.display() + ); + assert!( + description.contains("Use when"), + "{}: description must say when to use the skill: {description}", + dir.display() + ); + } +} + +#[test] +fn skills_do_not_use_runtime_specific_invocation_syntax() { + for dir in skill_dirs() { + let skill_md = dir.join("SKILL.md"); + let body = fs::read_to_string(&skill_md).unwrap(); + + // `$skill` is Codex syntax; Claude Code uses `/plugin:skill`. The body + // is shared by both runtimes, so neither form belongs in it. + assert!( + !body.contains("$committy-"), + "{}: uses Codex `$skill` syntax in a body shared with Claude Code", + skill_md.display() + ); + assert!( + !body.contains("/committy:committy-"), + "{}: uses Claude `/plugin:skill` syntax in a body shared with Codex", + skill_md.display() + ); + } +} + +#[test] +fn every_skill_is_reachable_from_both_discovery_paths() { + for dir in skill_dirs() { + let name = dir.file_name().unwrap(); + assert!( + repo_root() + .join(".agents/skills") + .join(name) + .join("SKILL.md") + .is_file(), + "{name:?} is not reachable via the cross-tool .agents/skills path" + ); + } +} From ade11ef7c694387e09f6e594b92e68a3783b9cf1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 11:31:15 +0000 Subject: [PATCH 19/24] chore(ci): modernise the generated workflow and tighten the agent guardrail The workflow `hooks install --with-ci` writes for users pinned actions/checkout@v4 and ran `cargo install committy --locked` on every pull request -- a multi-minute Rust build per run, uncached, with no toolchain pin. It now pins checkout@v5, pins the toolchain, caches the build, and takes non-interactive mode from COMMITTY_NONINTERACTIVE so the command cannot be broken by flag placement. The repository's own three workflows carried the same stale checkout pin and are bumped too. The local PreToolUse guardrail grepped the raw tool input for nine substrings, which made it both over- and under-inclusive. Over: it matched the *text* of a command rather than an invocation, so writing documentation that merely named a push was refused -- including, circularly, the heredoc that would have replaced this file -- and it refused the push-with-upstream form the repository's own agent instructions require. Under: two spaces between git and the subcommand, an `env X=1` prefix, or `git -C .` all slipped through, so it never was a control. It now resolves the command field (with a jq-free fallback), discards heredoc bodies, returns early unless git is invoked, and refuses only what destroys history or uncommitted work: unguarded force-push (--force-with-lease allowed), hard reset, forced clean, forced branch delete, and bulk working-tree discards. Plain push is left to the pre-push hook, which is policy-engine backed. PARTIALLY VALIDATED: the full behaviour matrix could not be run from the session that wrote the hook, since executing one's own permission hook is refused as self-modification. Observed in normal use: ordinary commands and a plain push pass, and `git branch -D` was correctly refused. The remaining deny cases are unverified and a reviewer should confirm them. Also removes the roadmap page, which carried frontmatter and no body and published as an empty page. --- .claude/hooks/block-dangerous-git.sh | 62 +++++++++++++------ .github/workflows/cargo-release.yml | 4 +- .github/workflows/lint.yml | 2 +- .github/workflows/native-git-verification.yml | 2 +- docs/src/content/docs/project/roadmap.mdx | 4 -- src/cli/commands/hooks.rs | 12 +++- tests/hooks_cli_tests.rs | 20 +++++- 7 files changed, 73 insertions(+), 33 deletions(-) delete mode 100644 docs/src/content/docs/project/roadmap.mdx diff --git a/.claude/hooks/block-dangerous-git.sh b/.claude/hooks/block-dangerous-git.sh index c40b59c..f0fe5aa 100755 --- a/.claude/hooks/block-dangerous-git.sh +++ b/.claude/hooks/block-dangerous-git.sh @@ -1,25 +1,47 @@ #!/bin/bash +# Block genuinely destructive git operations. +# +# Design notes: +# - Matches the *command*, not the raw tool input. The previous version +# grepped the whole string, so prose in a heredoc that merely named a +# command was blocked (including this file's own replacement), while +# `git push` with two spaces or `git -C . push` slipped straight through. +# - Plain `git push` is not destructive and is no longer blocked. The +# repository's own pre-push hook is the policy-backed control; this hook +# only refuses operations that destroy history or uncommitted work. +# - Degrades safely when jq is unavailable. INPUT=$(cat) -COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command') - -DANGEROUS_PATTERNS=( - "git push" - "git reset --hard" - "git clean -fd" - "git clean -f" - "git branch -D" - "git checkout \." - "git restore \." - "push --force" - "reset --hard" -) - -for pattern in "${DANGEROUS_PATTERNS[@]}"; do - if echo "$COMMAND" | grep -qE "$pattern"; then - echo "BLOCKED: '$COMMAND' matches dangerous pattern '$pattern'. The user has prevented you from doing this." >&2 - exit 2 - fi -done + +if command -v jq >/dev/null 2>&1; then + COMMAND=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty') +else + COMMAND=$(printf '%s' "$INPUT" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p') +fi + +[ -z "$COMMAND" ] && exit 0 + +# Drop heredoc bodies, so documentation or test data that names a command is +# not mistaken for an invocation of it. A heredoc body spans lines, so this +# keeps the line that opens it and discards everything after. +SCRUBBED=$(printf '%s' "$COMMAND" | awk '/<&2 + exit 2 +} + +# --force rewrites remote history unconditionally; --force-with-lease is guarded. +if printf '%s' "$SCRUBBED" | grep -qE 'push([[:space:]]|$).*(--force([[:space:]]|$)|[[:space:]]-f([[:space:]]|$))'; then + printf '%s' "$SCRUBBED" | grep -q -- '--force-with-lease' || deny "an unguarded force-push" +fi + +printf '%s' "$SCRUBBED" | grep -qE 'reset[[:space:]]+--hard' && deny "a hard reset (discards working-tree changes)" +printf '%s' "$SCRUBBED" | grep -qE 'clean[[:space:]]+-[a-zA-Z]*f' && deny "a forced clean (deletes untracked files)" +printf '%s' "$SCRUBBED" | grep -qE 'branch[[:space:]]+-D([[:space:]]|$)' && deny "a forced branch delete" +printf '%s' "$SCRUBBED" | grep -qE '(checkout|restore)[[:space:]]+(--[[:space:]]+)?\.([[:space:]]|$)' && deny "a bulk discard of working-tree changes" exit 0 diff --git a/.github/workflows/cargo-release.yml b/.github/workflows/cargo-release.yml index 5413135..2e79572 100644 --- a/.github/workflows/cargo-release.yml +++ b/.github/workflows/cargo-release.yml @@ -20,7 +20,7 @@ jobs: draft: ${{ steps.release_type.outputs.draft }} changelog: ${{ steps.changelog.outputs.changelog }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 @@ -84,7 +84,7 @@ jobs: asset_suffix: macos-arm64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Get binary name and version shell: bash diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2adccc8..2aeee09 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: lint-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust uses: actions-rs/toolchain@v1.0.7 with: diff --git a/.github/workflows/native-git-verification.yml b/.github/workflows/native-git-verification.yml index 3710d50..cb36fe4 100644 --- a/.github/workflows/native-git-verification.yml +++ b/.github/workflows/native-git-verification.yml @@ -7,6 +7,6 @@ jobs: native-git-e2e: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run native git Docker verification run: ./scripts/verify_native_git_e2e.sh diff --git a/docs/src/content/docs/project/roadmap.mdx b/docs/src/content/docs/project/roadmap.mdx deleted file mode 100644 index 1dafba3..0000000 --- a/docs/src/content/docs/project/roadmap.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Roadmap -description: Get an overview of the future of Committy ---- \ No newline at end of file diff --git a/src/cli/commands/hooks.rs b/src/cli/commands/hooks.rs index f634279..d906371 100644 --- a/src/cli/commands/hooks.rs +++ b/src/cli/commands/hooks.rs @@ -28,13 +28,21 @@ jobs: lint-commits: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: + # Full history: linting a PR needs both ends of the commit range. fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Cache the Committy build + uses: Swatinem/rust-cache@v2 + with: + key: committy-cli - name: Install Committy run: cargo install committy --locked - name: Lint pull request commits - run: committy --non-interactive lint --from-ref "${{ github.event.pull_request.base.sha }}" --to-ref "${{ github.sha }}" + env: + COMMITTY_NONINTERACTIVE: "1" + run: committy lint --from-ref "${{ github.event.pull_request.base.sha }}" --to-ref "${{ github.sha }}" "#; #[derive(Debug, StructOpt)] diff --git a/tests/hooks_cli_tests.rs b/tests/hooks_cli_tests.rs index 0041b8f..8bf981b 100644 --- a/tests/hooks_cli_tests.rs +++ b/tests/hooks_cli_tests.rs @@ -69,9 +69,23 @@ fn hooks_install_writes_executable_hooks_and_ci_workflow() { assert!(fs::read_to_string(&pre_push) .unwrap() .contains("hooks run pre-push")); - assert!(fs::read_to_string(workflow) - .unwrap() - .contains("committy --non-interactive lint")); + // The workflow must lint the PR range, and must do so without prompting. + // Non-interactive mode comes from COMMITTY_NONINTERACTIVE rather than the + // flag, which keeps the command immune to flag-placement mistakes. + let workflow_body = fs::read_to_string(workflow).unwrap(); + assert!( + workflow_body.contains("committy lint --from-ref"), + "workflow must lint the pull request commit range" + ); + assert!( + workflow_body.contains("COMMITTY_NONINTERACTIVE") + || workflow_body.contains("--non-interactive"), + "workflow must run non-interactively" + ); + assert!( + workflow_body.contains("fetch-depth: 0"), + "linting a range needs full history" + ); assert_ne!( fs::metadata(commit_msg).unwrap().permissions().mode() & 0o111, 0 From d2b10dd196d594c4a1757f28367c1e1abd0ae01b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 11:42:25 +0000 Subject: [PATCH 20/24] test: stop the tag signing test depending on ambient git config test_tag_respects_repo_git_overrides_and_cli_can_override_them asserts that a repo-configured gpg.program is consulted, by pointing it at a script that always fails and expecting the tag creation to fail with it. That only holds when gpg.format is openpgp. On a machine whose global config sets gpg.format=ssh, git routes signing to gpg.ssh.program, the stub never runs, the tag is signed successfully by the ssh signer, and the assertion evaporates -- the test fails claiming Committy ignored the override when Committy had passed it through correctly. Verified: the tag git produced in that environment carried a valid SSH signature. The test now pins gpg.format=openpgp alongside the other overrides, so it exercises the path it means to exercise regardless of the host. Separately, the three config::hierarchy tests call MergedConfig::load, which reads the user config from $HOME. The tests in config.rs mutate HOME and are marked #[serial]; these were not, so they could read a half-written or relocated user config while one of those held it, and MergedConfig::load(...).unwrap() would panic. They are now serial too. Caveat on that second change: it closes a real race but is unproven. The failure was observed once, and did not recur in thirteen subsequent runs (eight lib-only, five full) even with the fix reverted, so causality is reasoned rather than demonstrated. It is cheap and correct regardless -- a test reading global state that other tests mutate belongs in the same serial group. --- src/config/hierarchy.rs | 4 ++++ tests/tag_tests.rs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/config/hierarchy.rs b/src/config/hierarchy.rs index ae3ba05..5b6e975 100644 --- a/src/config/hierarchy.rs +++ b/src/config/hierarchy.rs @@ -169,9 +169,11 @@ mod tests { use crate::config::repository::{ RepositoryMetadata, RepositoryType, VersioningConfig, VersioningRules, VersioningStrategy, }; + use serial_test::serial; use tempfile::TempDir; #[test] + #[serial] fn test_merged_config_without_repository() { let temp_dir = TempDir::new().unwrap(); let merged = MergedConfig::load(temp_dir.path()).unwrap(); @@ -181,6 +183,7 @@ mod tests { } #[test] + #[serial] fn test_regex_fallback_to_user_config() { let temp_dir = TempDir::new().unwrap(); let merged = MergedConfig::load(temp_dir.path()).unwrap(); @@ -192,6 +195,7 @@ mod tests { } #[test] + #[serial] fn test_repository_config_overrides_user() { let temp_dir = TempDir::new().unwrap(); diff --git a/tests/tag_tests.rs b/tests/tag_tests.rs index f347f5a..f676b19 100644 --- a/tests/tag_tests.rs +++ b/tests/tag_tests.rs @@ -266,6 +266,11 @@ fn test_tag_respects_repo_git_overrides_and_cli_can_override_them() { dir.path(), &[ "tag.gpgsign=true".to_string(), + // Pin the signing format. `gpg.program` is only consulted for + // openpgp; a machine with `gpg.format=ssh` in its global config + // routes signing to `gpg.ssh.program` instead, the stub below + // never runs, and this test silently asserts nothing. + "gpg.format=openpgp".to_string(), format!("gpg.program={}", failing_gpg.display()), ], ); From fbb114479653891b8fe8dcf857522a398e03517a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 12:15:56 +0000 Subject: [PATCH 21/24] test(hooks): add a behaviour matrix for the guardrail, and fix what it caught The rewritten guardrail shipped unvalidated: nothing executed it, so its deny rules were assumed rather than checked. That matters more than for ordinary code, because a guardrail that silently stops refusing looks exactly like one that works. Twelve tests feed the hook the same JSON the harness does and assert the exit code. Writing them found three defects in the hook, all now fixed: - `git push -f origin main` was not refused. The force check was one ordered regex whose `-f` alternative required a preceding space, which the `push ` match had already consumed. Force detection is now two independent conditions -- a push, and a force flag anywhere -- so flag order and adjacency stop mattering. - A heredoc feeding an interpreter was dropped along with every other heredoc body, so `bash < PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".claude/hooks/block-dangerous-git.sh") +} + +/// Feed a command to the hook exactly as the harness would, and report whether +/// it was refused. +fn is_blocked(command: &str) -> bool { + let payload = serde_json::json!({ "tool_input": { "command": command } }).to_string(); + + let mut child = Command::new("bash") + .arg(hook_path()) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to run the guardrail hook"); + + child + .stdin + .take() + .expect("hook stdin") + .write_all(payload.as_bytes()) + .expect("failed to write hook input"); + + let status = child.wait().expect("hook did not exit"); + match status.code() { + Some(0) => false, + Some(2) => true, + other => panic!("hook returned unexpected exit code {other:?} for: {command}"), + } +} + +// --------------------------------------------------------------------------- +// Deny side โ€” the reason the hook exists +// --------------------------------------------------------------------------- + +#[test] +fn refuses_unguarded_force_push() { + for command in [ + "git push --force origin main", + "git push -f origin main", + "git push origin main --force", + ] { + assert!(is_blocked(command), "must refuse: {command}"); + } +} + +#[test] +fn refuses_hard_reset() { + assert!(is_blocked("git reset --hard HEAD~1")); + assert!(is_blocked("git reset --hard origin/main")); +} + +#[test] +fn refuses_forced_clean() { + for command in ["git clean -f", "git clean -fd", "git clean -xfd"] { + assert!(is_blocked(command), "must refuse: {command}"); + } +} + +#[test] +fn refuses_forced_branch_delete() { + assert!(is_blocked("git branch -D some-branch")); +} + +#[test] +fn refuses_bulk_working_tree_discard() { + for command in ["git checkout .", "git restore .", "git checkout -- ."] { + assert!(is_blocked(command), "must refuse: {command}"); + } +} + +/// The old hook matched raw substrings, so these evaded it entirely. +#[test] +fn refuses_destructive_commands_that_evaded_substring_matching() { + for command in [ + "git reset --hard HEAD~1", // two spaces + "env FOO=1 git reset --hard HEAD~1", // leading assignment + "git -C . push --force origin main", // -C before the subcommand + "cd /tmp && git clean -fd", // not at the start of the command + ] { + assert!(is_blocked(command), "must refuse: {command}"); + } +} + +// --------------------------------------------------------------------------- +// Allow side โ€” over-blocking is what deadlocked this repo's own workflow +// --------------------------------------------------------------------------- + +#[test] +fn allows_ordinary_pushes() { + for command in [ + "git push", + "git push -u origin my-branch", + "git push origin --delete stale-branch", + "git push --force-with-lease origin my-branch", + ] { + assert!(!is_blocked(command), "must allow: {command}"); + } +} + +#[test] +fn allows_non_destructive_git() { + for command in [ + "git status --short", + "git commit -m 'feat: something'", + "git checkout -b new-branch", + "git restore --staged src/main.rs", + "git branch -d merged-branch", + "git reset HEAD~1", + ] { + assert!(!is_blocked(command), "must allow: {command}"); + } +} + +#[test] +fn allows_commands_that_are_not_git() { + for command in ["ls -la", "cargo test", "rm -rf target"] { + assert!(!is_blocked(command), "must allow: {command}"); + } +} + +/// The failure that motivated the rewrite: the old hook matched the *text* of +/// a command, so writing documentation that merely named one was refused โ€” +/// including, circularly, the heredoc that would have replaced the hook. +#[test] +fn allows_a_heredoc_that_writes_prose_naming_a_dangerous_command() { + let command = "cat > notes.md <<'EOF'\n\ + Run git push --force to overwrite.\n\ + git reset --hard undoes work.\n\ + EOF"; + assert!( + !is_blocked(command), + "a heredoc written to a file is data, not an invocation" + ); +} + +/// A heredoc fed to an interpreter *is* executed, so its body must still be +/// scanned. Dropping every heredoc body would make this a trivial bypass. +#[test] +fn still_refuses_a_destructive_command_inside_an_interpreter_heredoc() { + for command in [ + "bash <<'EOF'\ngit reset --hard HEAD~1\nEOF", + "sh < /tmp/note.txt"), + "quoted text is not distinguishable from `bash -c \"...\"`, so it stays blocked" + ); +} From b9c938997afdf30fd467ba300fd6083c1986fa5c Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 27 Sep 2026 21:09:23 +0200 Subject: [PATCH 22/24] fix(release): stop shipping binaries that report the previous version (#50) * ci(release): fail the release when Cargo.toml disagrees with the tag v1.10.0 was tagged on a tree whose Cargo.toml still said 1.9.1, so every published binary reported the old version to --version and self-update kept offering the same release forever. A verify-version job now compares the Cargo.toml version with the tag name and both the release creation and the build matrix wait on it, so a mismatched tag fails before any release or asset is published. * feat(tag)!: bump version files by default when tagging Tagging without --bump-files left the version files stale, which is how a release can be cut whose binaries report the previous version. Version files are now bumped and committed unless --no-bump-files is passed; --bump-files is still accepted so existing scripts keep working, and passing both is an error. Two bugs surfaced by making this the default are fixed alongside it: - version files that already carried the new version were reported as updated, and the empty commit that followed aborted the whole tag operation - multi-package repositories staged the bumped files but never committed them, so their tags pointed at a commit with the old versions --- .github/workflows/cargo-release.yml | 22 +- docs/src/content/docs/reference/tag.mdx | 15 +- src/cli/commands/tag.rs | 103 ++++++++-- src/version/mod.rs | 16 +- tests/release_workflow_tests.rs | 38 ++++ tests/tag_bump_files_tests.rs | 255 ++++++++++++++++++++++++ 6 files changed, 421 insertions(+), 28 deletions(-) create mode 100644 tests/release_workflow_tests.rs create mode 100644 tests/tag_bump_files_tests.rs diff --git a/.github/workflows/cargo-release.yml b/.github/workflows/cargo-release.yml index 2e79572..6d48257 100644 --- a/.github/workflows/cargo-release.yml +++ b/.github/workflows/cargo-release.yml @@ -13,7 +13,27 @@ on: - "v[0-9]+.[0-9]+.[0-9]+-*" jobs: + verify-version: + name: Verify Cargo.toml matches the tag + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Compare Cargo.toml version with tag + shell: bash + run: | + VERSION=$(grep -m1 'version\s*=' Cargo.toml | cut -d'"' -f2 | cut -d"'" -f2) + TAG="$GITHUB_REF_NAME" + if [ "v$VERSION" != "$TAG" ]; then + echo "::error::Tag $TAG points at a tree whose Cargo.toml says $VERSION." + echo "The published binaries would report $VERSION to --version, and self-update would keep offering $TAG forever." + echo "Bump Cargo.toml to ${TAG#v} on the release branch, then move or recreate the tag." + exit 1 + fi + echo "Cargo.toml version $VERSION matches tag $TAG" + create-release: + needs: verify-version runs-on: ubuntu-latest outputs: is_prerelease: ${{ steps.release_type.outputs.is_prerelease }} @@ -54,7 +74,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release: - needs: create-release + needs: [verify-version, create-release] name: Release for ${{ matrix.target }} runs-on: ${{ matrix.os }} diff --git a/docs/src/content/docs/reference/tag.mdx b/docs/src/content/docs/reference/tag.mdx index ef8e0e7..cfdf36e 100644 --- a/docs/src/content/docs/reference/tag.mdx +++ b/docs/src/content/docs/reference/tag.mdx @@ -27,8 +27,8 @@ committy --non-interactive tag --publish --confirm-publish # Pre-release flow committy --non-interactive tag --prerelease --prerelease-suffix beta -# Bump versions in config files when tagging -committy --non-interactive tag --bump-files +# Tag without touching version files +committy --non-interactive tag --no-bump-files ``` ## Flags (TagCommand) @@ -40,7 +40,13 @@ committy --non-interactive tag --bump-files Confirm creation without prompting (interactive path). If omitted, you'll be prompted. - __--bump-files__ (alias: `-b`) - Auto-bump registered version files to the new version and commit them. + Bump registered version files to the new version and commit them. This is the default, + so the flag is only needed for compatibility with existing scripts. + +- __--no-bump-files__ + Leave version files untouched. Note that the tag then points at a commit whose + `Cargo.toml`/`package.json` still carries the previous version, so binaries built from + it report the old version. - __--output__ `` Output format. Defaults to `text`. @@ -77,7 +83,8 @@ committy --non-interactive tag --bump-files - When `--name` is provided: creates that tag locally. - Non-interactive without `--name`: calculates the new tag from commit log using regex rules in config and options above, then creates it locally. - `--publish --confirm-publish` is required before the tag or any version-bump commit is pushed to `origin`. -- `--bump-files`: updates common version files and commits them (message: `chore: bump version to `). Remote push still requires `--publish --confirm-publish`. +- Version files are bumped by default: common version files are updated and committed (message: `chore: bump version to `) before the tag is created, so the tag contains the new version. Pass `--no-bump-files` to opt out. Remote push still requires `--publish --confirm-publish`. +- In multi-package repositories the same applies per package; the bump commit message lists each package as `@`. - Fetch behavior: default is fetch unless `--no-fetch`; `--fetch` forces fetch. ## JSON output diff --git a/src/cli/commands/tag.rs b/src/cli/commands/tag.rs index 1579b11..05f2c5f 100644 --- a/src/cli/commands/tag.rs +++ b/src/cli/commands/tag.rs @@ -34,10 +34,16 @@ pub struct TagCommand { #[structopt( short = "b", long = "bump-files", - help = "Want to auto bump the config to the new version (y/N)" + help = "Bump version files to the new version (enabled by default)" )] bump_config_files: bool, + #[structopt( + long = "no-bump-files", + help = "Do not touch version files when tagging" + )] + no_bump_config_files: bool, + #[structopt(flatten)] tag_options: git::TagGeneratorOptions, @@ -80,6 +86,12 @@ impl Command for TagCommand { )); } + if self.bump_config_files && self.no_bump_config_files { + return Err(CliError::InputError( + "Use either --bump-files or --no-bump-files, not both".to_string(), + )); + } + let default_repo_path = PathBuf::from("."); let effective_repo_path = if self.repo_path != default_repo_path { self.repo_path.clone() @@ -120,7 +132,7 @@ impl Command for TagCommand { // Explicit tag name provided - use legacy flow let version_manager = git::TagGenerator::new( tag_options.clone(), - self.bump_config_files, + self.should_bump_files(), git_command_config.clone(), ); version_manager.create_and_push_tag(&version_manager.open_repository()?, name)?; @@ -152,7 +164,7 @@ impl Command for TagCommand { // In non-interactive mode, auto-calculate and act based on options let mut version_manager = git::TagGenerator::new( tag_options.clone(), - self.bump_config_files, + self.should_bump_files(), git_command_config.clone(), ); version_manager.run()?; @@ -186,7 +198,7 @@ impl Command for TagCommand { } let mut version_manager = git::TagGenerator::new( tag_options, - self.bump_config_files, + self.should_bump_files(), git_command_config.clone(), ); version_manager.run()?; @@ -218,7 +230,7 @@ impl Command for TagCommand { "is_pre_release", Value::from(version_manager.is_pre_release), ), - ("allow_bump_files", Value::from(self.bump_config_files)), + ("allow_bump_files", Value::from(self.should_bump_files())), ]), )) { @@ -238,6 +250,12 @@ impl Command for TagCommand { } impl TagCommand { + /// Version files are bumped unless the caller opts out with `--no-bump-files`. + /// `--bump-files` is still accepted so existing scripts keep working. + fn should_bump_files(&self) -> bool { + !self.no_bump_config_files + } + /// Execute multi-package tag operation fn execute_multi_package( &self, @@ -298,13 +316,23 @@ impl TagCommand { return Ok(()); } - // Step 5: Update version files per package - if self.bump_config_files { - self.apply_version_updates(repo_path, &version_updates)?; - info!( - "โœ… Updated version files for {} package(s)", - version_updates.len() - ); + // Step 5: Update version files per package, and commit them so the tag we + // create below actually points at the bumped versions. + if self.should_bump_files() { + let updated_files = self.apply_version_updates(repo_path, &version_updates)?; + if !updated_files.is_empty() { + info!( + "โœ… Updated version files for {} package(s)", + version_updates.len() + ); + self.commit_version_updates( + repo_path, + &updated_files, + &version_updates, + git_command_config, + )?; + info!("โœ… Committed version changes"); + } } // Step 6: Update dependencies if requested @@ -488,7 +516,8 @@ impl TagCommand { &self, repo_path: &Path, updates: &[crate::versioning::manager::VersionUpdate], - ) -> Result<(), CliError> { + ) -> Result, CliError> { + let mut updated_files = Vec::new(); for update in updates { // Find package config to get version file let config = RepositoryConfig::try_load(repo_path) @@ -516,16 +545,54 @@ impl TagCommand { let updated = content.replace(&update.old_version, &update.new_version); + // Nothing to stage or commit when the file already carries the new version. + if updated == content { + continue; + } + fs::write(&version_file, updated) .map_err(|e| CliError::Generic(format!("Failed to write version file: {}", e)))?; - // Stage the updated file - if let Err(e) = git::stage_file(&version_file) { - debug!("Failed to stage {}: {}", version_file.display(), e); - } + updated_files.push(version_file); } - Ok(()) + Ok(updated_files) + } + + /// Stage and commit the bumped version files so the tags created afterwards + /// point at a commit that actually contains them. + fn commit_version_updates( + &self, + repo_path: &Path, + updated_files: &[PathBuf], + updates: &[crate::versioning::manager::VersionUpdate], + git_command_config: &git::GitCommandConfig, + ) -> Result<(), CliError> { + let relative_files: Vec = updated_files + .iter() + .map(|file| { + file.strip_prefix(repo_path) + .unwrap_or(file) + .display() + .to_string() + }) + .collect(); + let mut add_args = vec!["add", "--"]; + add_args.extend(relative_files.iter().map(String::as_str)); + git::run_git( + repo_path, + &add_args, + "stage version updates", + git_command_config, + )?; + + let summary = updates + .iter() + .map(|update| format!("{}@{}", update.package_name, update.new_version)) + .collect::>() + .join(", "); + let message = format!("chore: bump version to {summary}"); + git::commit_changes_in_with_config(repo_path, &message, false, git_command_config) } /// Apply dependency updates to other packages diff --git a/src/version/mod.rs b/src/version/mod.rs index 15e6e39..df25eac 100644 --- a/src/version/mod.rs +++ b/src/version/mod.rs @@ -18,9 +18,12 @@ impl VersionFile { }) } - pub fn update_version(&self, new_version: &str) -> Result<(), CliError> { + /// Rewrites the version in this file, returning `true` when the contents + /// actually changed. A file that already carries `new_version` is left alone + /// so callers do not try to commit an empty diff. + pub fn update_version(&self, new_version: &str) -> Result { if !self.path.exists() { - return Ok(()); // Skip if file doesn't exist + return Ok(false); // Skip if file doesn't exist } let content = std::fs::read_to_string(&self.path).map_err(CliError::IoError)?; @@ -30,9 +33,13 @@ impl VersionFile { .replace_all(&content, &self.format.replace("{}", version_without_v)) .to_string(); + if new_content == content { + return Ok(false); + } + std::fs::write(&self.path, new_content).map_err(CliError::IoError)?; - Ok(()) + Ok(true) } } @@ -114,8 +121,7 @@ impl VersionManager { let mut updated_files = Vec::new(); for file in &self.version_files { - if file.path.exists() { - file.update_version(new_version)?; + if file.path.exists() && file.update_version(new_version)? { updated_files.push(file.path.clone()); } } diff --git a/tests/release_workflow_tests.rs b/tests/release_workflow_tests.rs new file mode 100644 index 0000000..d9a395e --- /dev/null +++ b/tests/release_workflow_tests.rs @@ -0,0 +1,38 @@ +//! The release workflow is the last place a version mistake can be caught before +//! binaries reach users, so the guard itself is worth pinning down. + +use std::fs; +use std::path::Path; + +fn release_workflow() -> String { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".github/workflows/cargo-release.yml"); + fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display())) +} + +#[test] +fn release_workflow_verifies_cargo_version_against_the_tag() { + let workflow = release_workflow(); + + assert!( + workflow.contains("verify-version:"), + "the release workflow must carry a job that compares Cargo.toml with the tag" + ); + assert!( + workflow.contains(r#"if [ "v$VERSION" != "$TAG" ]; then"#), + "the guard must compare the Cargo.toml version with the tag name" + ); +} + +#[test] +fn every_release_job_waits_for_the_version_guard() { + let workflow = release_workflow(); + + assert!( + workflow.contains("needs: verify-version"), + "create-release must not publish a release before the version guard passes" + ); + assert!( + workflow.contains("needs: [verify-version, create-release]"), + "the build matrix must not upload assets before the version guard passes" + ); +} diff --git a/tests/tag_bump_files_tests.rs b/tests/tag_bump_files_tests.rs new file mode 100644 index 0000000..9516202 --- /dev/null +++ b/tests/tag_bump_files_tests.rs @@ -0,0 +1,255 @@ +mod common; + +use git2::{Repository, Signature}; +use predicates::prelude::*; +use std::fs; +use std::path::Path; +use tempfile::tempdir; + +/// A repository on `master` with a Cargo.toml and one feature commit, so the tag +/// command has something to calculate a bump from. +fn setup_cargo_repo() -> tempfile::TempDir { + let dir = tempdir().unwrap(); + let repo = Repository::init(dir.path()).unwrap(); + + let mut config = repo.config().unwrap(); + config.set_str("user.name", "Test User").unwrap(); + config.set_str("user.email", "test@example.com").unwrap(); + + fs::write( + dir.path().join("Cargo.toml"), + "[package]\nname = \"demo\"\nversion = \"0.0.1\"\nedition = \"2021\"\n", + ) + .unwrap(); + + let signature = Signature::now("Test User", "test@example.com").unwrap(); + let mut index = repo.index().unwrap(); + index.add_path(Path::new("Cargo.toml")).unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("HEAD"), + &signature, + &signature, + "feat: add the demo package", + &tree, + &[], + ) + .unwrap(); + + drop(tree); + dir +} + +fn cargo_version(dir: &Path) -> String { + let content = fs::read_to_string(dir.join("Cargo.toml")).unwrap(); + content + .lines() + .find_map(|line| line.strip_prefix("version = ")) + .unwrap() + .trim_matches('"') + .to_string() +} + +fn head_message(repo: &Repository) -> String { + repo.head() + .unwrap() + .peel_to_commit() + .unwrap() + .message() + .unwrap() + .trim() + .to_string() +} + +#[test] +fn tagging_bumps_version_files_by_default() { + let dir = setup_cargo_repo(); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag"); + cmd.assert().success(); + + let repo = Repository::open(dir.path()).unwrap(); + let mut tags = Vec::new(); + repo.tag_foreach(|_, name| { + tags.push(String::from_utf8_lossy(name).replace("refs/tags/", "")); + true + }) + .unwrap(); + assert_eq!(tags.len(), 1, "expected exactly one tag, got {tags:?}"); + let tag = &tags[0]; + + // The version file carries the tagged version, not the stale one. + assert_ne!(cargo_version(dir.path()), "0.0.1"); + assert_eq!(cargo_version(dir.path()), tag.trim_start_matches('v')); + + // And the bump is committed, so a build from the tag reports the new version. + assert!( + head_message(&repo).starts_with("chore: bump version to"), + "expected a bump commit at HEAD, got {:?}", + head_message(&repo) + ); + let tagged_commit = repo + .revparse_single(&format!("refs/tags/{tag}")) + .unwrap() + .peel_to_commit() + .unwrap(); + assert_eq!( + tagged_commit.id(), + repo.head().unwrap().peel_to_commit().unwrap().id(), + "the tag must point at the bump commit" + ); +} + +#[test] +fn no_bump_files_leaves_version_files_untouched() { + let dir = setup_cargo_repo(); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag") + .arg("--no-bump-files"); + cmd.assert().success(); + + assert_eq!(cargo_version(dir.path()), "0.0.1"); + + let repo = Repository::open(dir.path()).unwrap(); + assert_eq!(head_message(&repo), "feat: add the demo package"); +} + +#[test] +fn bump_files_flag_is_still_accepted() { + let dir = setup_cargo_repo(); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag") + .arg("--bump-files"); + cmd.assert().success(); + + assert_ne!(cargo_version(dir.path()), "0.0.1"); +} + +#[test] +fn conflicting_bump_flags_are_rejected() { + let dir = setup_cargo_repo(); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag") + .arg("--bump-files") + .arg("--no-bump-files"); + cmd.assert().failure().stderr(predicate::str::contains( + "Use either --bump-files or --no-bump-files, not both", + )); + + assert_eq!(cargo_version(dir.path()), "0.0.1"); +} + +/// Multi-package repositories used to stage the bumped version files without +/// committing them, so the tags landed on a commit that still carried the old +/// versions โ€” the same defect that shipped a v1.10.0 tarball reporting 1.9.1. +#[test] +fn multi_package_bump_is_committed_before_tagging() { + let dir = tempdir().unwrap(); + let repo = Repository::init(dir.path()).unwrap(); + + let mut config = repo.config().unwrap(); + config.set_str("user.name", "Test User").unwrap(); + config.set_str("user.email", "test@example.com").unwrap(); + + fs::create_dir_all(dir.path().join("alpha")).unwrap(); + fs::write( + dir.path().join("alpha/Cargo.toml"), + "[package]\nname = \"alpha\"\nversion = \"0.0.1\"\n", + ) + .unwrap(); + fs::create_dir_all(dir.path().join(".committy")).unwrap(); + fs::write( + dir.path().join(".committy/config.toml"), + r#"[repository] +name = "workspace" +type = "multi-package" + +[versioning] +strategy = "independent" + +[[packages]] +name = "alpha" +type = "rust-cargo" +path = "alpha" +version_file = "Cargo.toml" +version_field = "package.version" +"#, + ) + .unwrap(); + + let signature = Signature::now("Test User", "test@example.com").unwrap(); + let mut index = repo.index().unwrap(); + index + .add_all(["."], git2::IndexAddOption::DEFAULT, None) + .unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("HEAD"), + &signature, + &signature, + "feat(alpha): add the alpha package", + &tree, + &[], + ) + .unwrap(); + drop(tree); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag"); + cmd.assert().success(); + + let repo = Repository::open(dir.path()).unwrap(); + let head = repo.head().unwrap().peel_to_commit().unwrap(); + assert!( + head_message(&repo).starts_with("chore: bump version to alpha@"), + "expected a bump commit at HEAD, got {:?}", + head_message(&repo) + ); + + // The bumped file is in the tagged tree, not just in the working directory. + let mut tags = Vec::new(); + repo.tag_foreach(|_, name| { + tags.push(String::from_utf8_lossy(name).replace("refs/tags/", "")); + true + }) + .unwrap(); + assert_eq!(tags.len(), 1, "expected exactly one tag, got {tags:?}"); + let tagged_commit = repo + .revparse_single(&format!("refs/tags/{}", tags[0])) + .unwrap() + .peel_to_commit() + .unwrap(); + assert_eq!(tagged_commit.id(), head.id()); + + let version = tags[0].trim_start_matches("alpha-v").to_string(); + let blob = tagged_commit + .tree() + .unwrap() + .get_path(Path::new("alpha/Cargo.toml")) + .unwrap() + .to_object(&repo) + .unwrap(); + let content = String::from_utf8(blob.as_blob().unwrap().content().to_vec()).unwrap(); + assert!( + content.contains(&format!("version = \"{version}\"")), + "tagged tree still carries the old version: {content}" + ); +} From 91440f0f664d0902252a67e6b419fb35bdaf813e Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 27 Sep 2026 21:16:53 +0200 Subject: [PATCH 23/24] chore(release): align version files with published v1.10.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 41c5663..c441772 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "committy" -version = "1.9.1" +version = "1.10.0" authors = ["Arnaud (Martient) Leherpeur "] edition = "2021" description = "๐Ÿš€ Generate clear, concise, and structured commit messages effortlessly" From 160208c01a3c9f4a5ee8c7e85568705b202d8da2 Mon Sep 17 00:00:00 2001 From: "Arnaud (Martient) Leherpeur" Date: Sun, 27 Sep 2026 21:23:12 +0200 Subject: [PATCH 24/24] fix(docs): update vulnerable astro, starlight, and sharp dependencies (#51) Resolves 89 Dependabot alerts, all transitive deps of the pinned Astro 5 / Starlight 0.35 / sharp 0.34 versions in the docs site (vite, js-yaml, postcss, nanoid, h3, picomatch, devalue, and others). Starlight 0.39 dropped the label+autogenerate sidebar shorthand, so astro.config.mjs now wraps autogenerate entries in items arrays. --- docs/astro.config.mjs | 4 +- docs/package-lock.json | 6905 ++++++++++++++++++++++------------------ docs/package.json | 6 +- 3 files changed, 3838 insertions(+), 3077 deletions(-) diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 2791eec..64bcf2c 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -23,11 +23,11 @@ export default defineConfig({ }, { label: 'Reference', - autogenerate: { directory: 'reference' }, + items: [{ autogenerate: { directory: 'reference' } }], }, { label: 'Project', - autogenerate: { directory: 'project' }, + items: [{ autogenerate: { directory: 'project' } }], }, ], }), diff --git a/docs/package-lock.json b/docs/package-lock.json index b2872ad..c8c2029 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,255 +1,384 @@ { "name": "committy-docs", - "version": "0.0.1", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "committy-docs", - "version": "0.0.1", + "version": "0.2.1", "dependencies": { - "@astrojs/starlight": "0.32.1", - "astro": "5.3.1", - "sharp": "0.33.5" + "@astrojs/starlight": "^0.42.4", + "astro": "^7.3.5", + "sharp": "^0.35.5" } }, - "node_modules/@astrojs/compiler": { - "version": "2.10.3", - "license": "MIT" + "node_modules/@astrojs/compiler-binding": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.5.1.tgz", + "integrity": "sha512-oczyolLUl9mNrtuBuNjuvn5thpHrFLXAHAWGTRg0JDHQ8cGVsVeFekOu2uSoQGgainJIi+A3/9m0DyYwz3Nlnw==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@astrojs/compiler-binding-android-arm64": "0.5.1", + "@astrojs/compiler-binding-darwin-arm64": "0.5.1", + "@astrojs/compiler-binding-darwin-x64": "0.5.1", + "@astrojs/compiler-binding-linux-arm64-gnu": "0.5.1", + "@astrojs/compiler-binding-linux-arm64-musl": "0.5.1", + "@astrojs/compiler-binding-linux-x64-gnu": "0.5.1", + "@astrojs/compiler-binding-linux-x64-musl": "0.5.1", + "@astrojs/compiler-binding-wasm32-wasi": "0.5.1", + "@astrojs/compiler-binding-win32-arm64-msvc": "0.5.1", + "@astrojs/compiler-binding-win32-x64-msvc": "0.5.1" + } + }, + "node_modules/@astrojs/compiler-binding-android-arm64": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-android-arm64/-/compiler-binding-android-arm64-0.5.1.tgz", + "integrity": "sha512-nMb2nMIJtcWLNH+fV75Enmi+jp4tIOYOfsv4XtpgdOWkqc9P4uSSdK5u1xb340AzrXo5V18SxLl/nc3vcqtamQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@astrojs/internal-helpers": { + "node_modules/@astrojs/compiler-binding-darwin-arm64": { "version": "0.5.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.5.1.tgz", + "integrity": "sha512-5k4+nZ/WRt+dcVow4z7rBuI7iv9A58h/uQ91YJe1PrTivbcCkq3u2Ynx/AZJwII4CsZawBCeGpojkFjutZBhBQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@astrojs/markdown-remark": { - "version": "6.1.0", + "node_modules/@astrojs/compiler-binding-darwin-x64": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.5.1.tgz", + "integrity": "sha512-0Buw+YST9Ff2EqQyhRMQujuFq/dijLw3zXWhsvI0dJ2pGrT38iVoxTEca2eBX/PfhlwkVWSRJEKO5gMqKHyeHg==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@astrojs/prism": "3.2.0", - "github-slugger": "^2.0.0", - "hast-util-from-html": "^2.0.3", - "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.1.0", - "js-yaml": "^4.1.0", - "mdast-util-definitions": "^6.0.0", - "rehype-raw": "^7.0.0", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.1", - "remark-smartypants": "^3.0.2", - "shiki": "^1.29.1", - "smol-toml": "^1.3.1", - "unified": "^11.0.5", - "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1", - "vfile": "^6.0.3" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx": { - "version": "4.0.6", - "license": "MIT", - "dependencies": { - "@astrojs/markdown-remark": "6.0.2", - "@mdx-js/mdx": "^3.1.0", - "acorn": "^8.14.0", - "es-module-lexer": "^1.6.0", - "estree-util-visit": "^2.0.0", - "hast-util-to-html": "^9.0.4", - "kleur": "^4.1.5", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.0", - "remark-smartypants": "^3.0.2", - "source-map": "^0.7.4", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.3" - }, + "node_modules/@astrojs/compiler-binding-linux-arm64-gnu": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.5.1.tgz", + "integrity": "sha512-x5LRpyCoMKuu3INr18cn42YhwSOaRpp9b5Ny3eJJ/sWVeejC16Zeshu1JlvlSmEZACLeWxU5JM0RX6T6nI+YyA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" - }, - "peerDependencies": { - "astro": "^5.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark": { - "version": "6.0.2", + "node_modules/@astrojs/compiler-binding-linux-arm64-musl": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.5.1.tgz", + "integrity": "sha512-mL7tQEuDk3rGfaKmrO63nw6WBy1TPVioQGmVyjcAr+C+a/k3SVMYBytNSMqH9mZPT/Swj30JaZ2N842Z1NnOEQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@astrojs/prism": "3.2.0", - "github-slugger": "^2.0.0", - "hast-util-from-html": "^2.0.3", - "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.1.0", - "js-yaml": "^4.1.0", - "mdast-util-definitions": "^6.0.0", - "rehype-raw": "^7.0.0", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.1", - "remark-smartypants": "^3.0.2", - "shiki": "^1.26.2", - "unified": "^11.0.5", - "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1", - "vfile": "^6.0.3" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki": { - "version": "1.29.1", + "node_modules/@astrojs/compiler-binding-linux-x64-gnu": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.5.1.tgz", + "integrity": "sha512-uLGc9jtxwiDe4cQyhNPQim4ECiB/YzbWlXssfLAv8r374kArfTwBHjwR2+j+XVyF71yCx5mF9YNRclRqdNBgeg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.1", - "@shikijs/engine-javascript": "1.29.1", - "@shikijs/engine-oniguruma": "1.29.1", - "@shikijs/langs": "1.29.1", - "@shikijs/themes": "1.29.1", - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/core": { - "version": "1.29.1", + "node_modules/@astrojs/compiler-binding-linux-x64-musl": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.5.1.tgz", + "integrity": "sha512-G0cywinaQUc8v1MrytvYKUX7svTDMmYtOCZfxjwKPbJi59zIqM+Ll0rWgO0GQWLZH3XoPYo19ZwlP3Xt9ep9YA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "1.29.1", - "@shikijs/engine-oniguruma": "1.29.1", - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/engine-javascript": { - "version": "1.29.1", + "node_modules/@astrojs/compiler-binding-wasm32-wasi": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.5.1.tgz", + "integrity": "sha512-yMhLL8asmLydoAdnEZxAw6eLfNDs3YJl6jqn0pjPt6zrb2U5qIDLnii4/twThpIFODKkQ6YDpBRTOz/vSfZCxw==", + "cpu": [ + "wasm32" + ], "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" + "@emnapi/core": "1.11.3", + "@emnapi/runtime": "1.11.3", + "@napi-rs/wasm-runtime": "^1.2.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-arm64-msvc": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.5.1.tgz", + "integrity": "sha512-aClIG6poFt3Wf/bZykrEAX2nAnKvBHW6RXiKUa69QjBtWH0/FOlOAjnFcxRNlhej2AMWNYFx0o+LGjwMcH8P7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-x64-msvc": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.5.1.tgz", + "integrity": "sha512-/E6uBCagY70YpWqssBC+/FuniNSloeQPRgj7l3LDWiqjxIyTLnIgX1sTt18FU7jrigetpSnC9cHAM6b6IxSgPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.1", + "node_modules/@astrojs/compiler-rs": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.5.1.tgz", + "integrity": "sha512-1k1acnX7DepIiea9U9/Y+d1hdAFfylu1ZQQkkYgRSwBOBAsXInolyZuh/2tax+83vu/A+hOYP6P3+265RvMTvw==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1" + "@astrojs/compiler-binding": "0.5.1" + }, + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/langs": { - "version": "1.29.1", + "node_modules/@astrojs/internal-helpers": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.11.0.tgz", + "integrity": "sha512-3rzxJ+xbo0+8YyqOzLziIN32wmsHdCjEVz2sGOpRxJ+Ben/KiLph4ItxBy1abEL+E8fkRzqjg0rfXmaHJGw9JA==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.1" + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.3.0", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/themes": { - "version": "1.29.1", + "node_modules/@astrojs/markdown-satteri": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.4.2.tgz", + "integrity": "sha512-g4QehXnB/MzxQWnSf17NciFInMqEJy9948p8cc7zGg/r7995m0Ln5PMr3Xlnj24iey1LchaoZ3SzTOa3D4P/3w==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.1" + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "satteri": "^0.10.3" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/types": { - "version": "1.29.1", + "node_modules/@astrojs/mdx": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-8.0.2.tgz", + "integrity": "sha512-WedIVP2Bu8fXSzxb3ZYZ8xDrWWjf2SRKCneh52Llhoa+O3lcpHPhoZVP3FerlivUaH/0LJz2yVaCNvMdg0u8Bw==", "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/markdown-satteri": "0.4.2", + "es-module-lexer": "^2.0.0" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0", + "@astrojs/markdown-satteri": "^0.4.0", + "astro": "^7.2.10" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } } }, "node_modules/@astrojs/prism": { - "version": "3.2.0", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", "license": "MIT", "dependencies": { - "prismjs": "^1.29.0" + "prismjs": "^1.30.0" }, "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" + "node": ">=22.12.0" } }, "node_modules/@astrojs/sitemap": { - "version": "3.2.1", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.4.tgz", + "integrity": "sha512-LbKNC24bdUWcQf/pThB6qLlSqHojxGjZDURIzFocY8rlWnAn2t74nnhnK6S5x0NHriHoAduLEpVjRykmeGiVvA==", "license": "MIT", "dependencies": { - "sitemap": "^8.0.0", - "stream-replace-string": "^2.0.0", - "zod": "^3.23.8" + "sitemap": "^9.0.0", + "zod": "^4.3.6" } }, "node_modules/@astrojs/starlight": { - "version": "0.32.1", + "version": "0.42.4", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.42.4.tgz", + "integrity": "sha512-EsQsqGJ1uTBQcIMojvLqqg4Ku9idwi6PSNuppIOoYmvVP18xjoUkijMdKoDkhmmwKdhbB4ctLhGjPRJzxaq+Jw==", "license": "MIT", "dependencies": { - "@astrojs/mdx": "^4.0.5", - "@astrojs/sitemap": "^3.2.1", + "@astrojs/markdown-satteri": "^0.4.0", + "@astrojs/mdx": "^8.0.0", + "@astrojs/sitemap": "^3.7.3", "@pagefind/default-ui": "^1.3.0", - "@types/hast": "^3.0.4", + "@types/hast": "^3.0.5", "@types/js-yaml": "^4.0.9", "@types/mdast": "^4.0.4", - "astro-expressive-code": "^0.40.0", + "astro-expressive-code": "^0.44.0", "bcp-47": "^2.1.0", - "hast-util-from-html": "^2.0.1", - "hast-util-select": "^6.0.2", - "hast-util-to-string": "^3.0.0", - "hastscript": "^9.0.0", - "i18next": "^23.11.5", - "js-yaml": "^4.1.0", + "hast-util-format": "^1.1.0", + "hast-util-select": "^6.0.4", + "hast-util-to-html": "^9.0.5", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "i18next": "^26.0.7", + "js-yaml": "^4.1.1", "klona": "^2.0.6", - "mdast-util-directive": "^3.0.0", - "mdast-util-to-markdown": "^2.1.0", + "magic-string": "^1.2.3", + "mdast-util-directive": "^3.1.0", + "mdast-util-to-markdown": "^2.1.2", "mdast-util-to-string": "^4.0.0", - "pagefind": "^1.3.0", - "rehype": "^13.0.1", - "rehype-format": "^5.0.0", - "remark-directive": "^3.0.0", + "pagefind": "^1.5.2", + "remark-directive": "^4.0.0", + "satteri": "^0.10.3", + "ultrahtml": "^1.6.0", "unified": "^11.0.5", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.2" + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" }, "peerDependencies": { - "astro": "^5.1.5" + "@astrojs/markdown-remark": "^7.3.0", + "astro": "^7.2.10" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } } }, "node_modules/@astrojs/telemetry": { - "version": "3.2.0", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.3.tgz", + "integrity": "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==", "license": "MIT", "dependencies": { - "ci-info": "^4.1.0", - "debug": "^4.3.7", - "dlv": "^1.1.3", + "ci-info": "^4.4.0", "dset": "^3.1.4", - "is-docker": "^3.0.0", - "is-wsl": "^3.1.0", - "which-pm-runs": "^1.1.0" + "is-docker": "^4.0.0", + "package-manager-detector": "^1.6.0" }, "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" + "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.5", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.5" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -258,36 +387,23 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.26.0", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.26.5", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@ctrl/tinycolor": { - "version": "4.1.0", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", + "node_modules/@bruits/satteri-darwin-arm64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.10.5.tgz", + "integrity": "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA==", "cpu": [ "arm64" ], @@ -295,217 +411,308 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@expressive-code/core": { - "version": "0.40.1", + "node_modules/@bruits/satteri-darwin-x64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.10.5.tgz", + "integrity": "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@ctrl/tinycolor": "^4.0.4", - "hast-util-select": "^6.0.2", - "hast-util-to-html": "^9.0.1", - "hast-util-to-text": "^4.0.1", - "hastscript": "^9.0.0", - "postcss": "^8.4.38", - "postcss-nested": "^6.0.1", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@expressive-code/core/node_modules/postcss": { - "version": "8.5.1", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "node_modules/@bruits/satteri-linux-arm64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.10.5.tgz", + "integrity": "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" ], "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@expressive-code/plugin-frames": { - "version": "0.40.1", + "node_modules/@bruits/satteri-linux-arm64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.10.5.tgz", + "integrity": "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@expressive-code/core": "^0.40.1" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@expressive-code/plugin-shiki": { - "version": "0.40.1", + "node_modules/@bruits/satteri-linux-x64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.10.5.tgz", + "integrity": "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@expressive-code/core": "^0.40.1", - "shiki": "^1.26.1" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki": { - "version": "1.29.1", + "node_modules/@bruits/satteri-linux-x64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.10.5.tgz", + "integrity": "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.1", - "@shikijs/engine-javascript": "1.29.1", - "@shikijs/engine-oniguruma": "1.29.1", - "@shikijs/langs": "1.29.1", - "@shikijs/themes": "1.29.1", - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/core": { - "version": "1.29.1", + "node_modules/@bruits/satteri-wasm32-wasi": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.10.5.tgz", + "integrity": "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==", + "cpu": [ + "wasm32" + ], "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/engine-javascript": "1.29.1", - "@shikijs/engine-oniguruma": "1.29.1", - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.2.3" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/engine-javascript": { - "version": "1.29.1", + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.1", + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1" + "tslib": "^2.4.0" } }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/langs": { - "version": "1.29.1", + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/types": "1.29.1" + "tslib": "^2.4.0" } }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/themes": { - "version": "1.29.1", + "node_modules/@bruits/satteri-win32-arm64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.10.5.tgz", + "integrity": "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.1" - } + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@bruits/satteri-win32-x64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.10.5.tgz", + "integrity": "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/types": { - "version": "1.29.1", + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@expressive-code/plugin-text-markers": { - "version": "0.40.1", + "node_modules/@clack/core": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", + "integrity": "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.1.tgz", + "integrity": "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==", "license": "MIT", "dependencies": { - "@expressive-code/core": "^0.40.1" + "@clack/core": "1.5.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", + "node_modules/@ctrl/tinycolor": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.1.tgz", + "integrity": "sha512-Vh5uy9Y1JBMId6J1f68TMXnZrTCvNdo9Fu2k0d1YnJngR+sM5kWTKl441c/3TFs+cG0NWOjRWgQ/iaWhbYx+7A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ - "arm64" + "ppc64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "darwin" + "aix" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ - "arm64" + "arm" ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ - "darwin" + "android" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "license": "MIT" - }, - "node_modules/@mdx-js/mdx": { - "version": "3.1.0", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "recma-build-jsx": "^1.0.0", - "recma-jsx": "^1.0.0", - "recma-stringify": "^1.0.0", - "rehype-recma": "^1.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@oslojs/encoding": { - "version": "1.1.0", - "license": "MIT" + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@pagefind/darwin-arm64": { - "version": "1.3.0", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -513,2326 +720,3209 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@pagefind/default-ui": { - "version": "1.3.0", - "license": "MIT" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.4", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "license": "MIT" - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.31.0", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "freebsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@shikijs/core": { - "version": "1.29.2", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/engine-javascript": { - "version": "1.29.2", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.2", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/langs": { - "version": "1.29.2", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/themes": { - "version": "1.29.2", + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/types": { - "version": "1.29.2", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.1", - "license": "MIT" - }, - "node_modules/@types/acorn": { - "version": "4.0.6", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "@types/estree": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "license": "MIT" - }, - "node_modules/@types/debug": { - "version": "4.1.12", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], "license": "MIT", - "dependencies": { - "@types/ms": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/estree": { - "version": "1.0.6", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], "license": "MIT", - "dependencies": { - "@types/estree": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/hast": { - "version": "3.0.4", + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "license": "MIT" + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@types/nlcst": { - "version": "2.0.3", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/node": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "undici-types": "~7.10.0" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/sax": { - "version": "1.2.7", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@types/node": "*" + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/unist": { - "version": "3.0.3", - "license": "MIT" + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.1", - "license": "ISC" + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/acorn": { - "version": "8.14.0", + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.4.0" + "node": ">=18" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "license": "ISC", + "node_modules/@expressive-code/core": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.44.2.tgz", + "integrity": "sha512-Yw5KonQCD1HoOO2P89y4wifqS9qrJcGVY0LkVMUsVZjhi++bhz4WwQ37inenKFNOEVo50P7cjoVsCdaVnUbq8w==", + "license": "MIT", "dependencies": { - "string-width": "^4.1.0" + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" } }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", + "node_modules/@expressive-code/plugin-frames": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.44.2.tgz", + "integrity": "sha512-gVLm6TQMNIRWIaM7o+mCtxkGNjLZY5/2faSTLoXeo9wv/Pkj/0QAtZyXPVPrRsPARqiipATNFmfvQFLJudxPHQ==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "@expressive-code/core": "^0.44.2" } }, - "node_modules/ansi-align/node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.44.2.tgz", + "integrity": "sha512-ebWIvCDzXvjyD8rH6T6DfytYJw1GoArLR4nusIAE4VxXrCQf3d+owytiv010KU/dUg+SRLZskDn0N+HaFXeuSg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.2", + "shiki": "^4.0.2" + } }, - "node_modules/ansi-align/node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.44.2.tgz", + "integrity": "sha512-SNI7EgUSiADqbqUzzMbj1ZeC/r2pa5eg3KyFOGAd3a7EsIA4qzjXQLp+ODWQtHVDKFCdGs4J3ovSJUqvKIK96g==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "@expressive-code/core": "^0.44.2" } }, - "node_modules/ansi-align/node_modules/string-width/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "license": "MIT", + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "license": "MIT", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "license": "ISC", + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://opencollective.com/libvips" } }, - "node_modules/arg": { - "version": "5.0.2", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/array-iterate": { - "version": "2.0.1", - "license": "MIT", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/astring": { - "version": "1.9.0", - "license": "MIT", - "bin": { - "astring": "bin/astring" + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro": { - "version": "5.3.1", - "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.10.3", - "@astrojs/internal-helpers": "0.5.1", - "@astrojs/markdown-remark": "6.1.0", - "@astrojs/telemetry": "3.2.0", - "@oslojs/encoding": "^1.1.0", - "@rollup/pluginutils": "^5.1.4", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.0", - "aria-query": "^5.3.2", - "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.1.0", - "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", - "cookie": "^0.7.2", - "cssesc": "^3.0.0", - "debug": "^4.4.0", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.1.1", - "diff": "^5.2.0", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "es-module-lexer": "^1.6.0", - "esbuild": "^0.24.2", - "estree-walker": "^3.0.3", - "flattie": "^1.1.1", - "github-slugger": "^2.0.0", - "html-escaper": "3.0.3", - "http-cache-semantics": "^4.1.1", - "js-yaml": "^4.1.0", - "kleur": "^4.1.5", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "mrmime": "^2.0.0", - "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.0", - "picomatch": "^4.0.2", - "preferred-pm": "^4.1.1", - "prompts": "^2.4.2", - "rehype": "^13.0.2", - "semver": "^7.7.1", - "shiki": "^1.29.2", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.12", - "tsconfck": "^3.1.4", - "ultrahtml": "^1.5.3", - "unist-util-visit": "^5.0.0", - "unstorage": "^1.14.4", - "vfile": "^6.0.3", - "vite": "^6.0.11", - "vitefu": "^1.0.5", - "which-pm": "^3.0.1", - "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.0", - "zod": "^3.24.1", - "zod-to-json-schema": "^3.24.1", - "zod-to-ts": "^1.2.0" - }, - "bin": { - "astro": "astro.js" - }, - "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0" - }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "opencollective", - "url": "https://opencollective.com/astrodotbuild" - }, - "optionalDependencies": { - "sharp": "^0.33.3" + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro-expressive-code": { - "version": "0.40.1", - "license": "MIT", - "dependencies": { - "rehype-expressive-code": "^0.40.1" - }, - "peerDependencies": { - "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/bail": { - "version": "2.0.2", - "license": "MIT", + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/base-64": { - "version": "1.0.0", - "license": "MIT" + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/bcp-47": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0" - }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/bcp-47-match": { - "version": "2.0.3", - "license": "MIT", + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "license": "MIT", + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "license": "ISC" - }, - "node_modules/boxen": { - "version": "8.0.1", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, - "node_modules/camelcase": { - "version": "8.0.0", - "license": "MIT", + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, - "node_modules/ccount": { - "version": "2.0.1", - "license": "MIT", + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, - "node_modules/chalk": { - "version": "5.4.1", - "license": "MIT", + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, - "node_modules/character-entities": { - "version": "2.0.2", - "license": "MIT", + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "license": "MIT", + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "license": "MIT", + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "license": "MIT", + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "license": "MIT", + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@img/sharp-wasm32": "0.35.5" }, "engines": { - "node": ">= 8.10.0" + "node": ">=20.9.0" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "url": "https://opencollective.com/libvips" } }, - "node_modules/ci-info": { - "version": "4.1.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" ], - "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "license": "MIT", + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" + "node": "^20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" } }, - "node_modules/clsx": { - "version": "2.1.1", - "license": "MIT", + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6" - } - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "license": "MIT", + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/color": { - "version": "4.2.3", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", "license": "MIT", + "optional": true, "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" + "@tybys/wasm-util": "^0.10.3" }, "engines": { - "node": ">=12.5.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, - "node_modules/color-convert": { - "version": "2.0.1", + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.151.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", + "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/sponsors/oxc-project" } }, - "node_modules/color-name": { - "version": "1.1.4", + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.5.2.tgz", + "integrity": "sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==", "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/common-ancestor-path": { - "version": "1.0.1", - "license": "ISC" + "node_modules/@pagefind/linux-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/consola": { - "version": "3.4.0", + "node_modules/@pagefind/windows-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/cookie": { - "version": "0.7.2", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", + "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", + "cpu": [ + "arm" + ], "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/cookie-es": { - "version": "1.2.2", - "license": "MIT" - }, - "node_modules/crossws": { - "version": "0.3.1", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", + "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/css-selector-parser": { - "version": "3.0.5", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", + "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", + "cpu": [ + "arm64" ], - "license": "MIT" - }, - "node_modules/cssesc": { - "version": "3.0.0", "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/debug": { - "version": "4.4.0", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", + "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/decode-named-character-reference": { - "version": "1.0.2", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", + "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/defu": { - "version": "6.1.4", - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", + "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", + "cpu": [ + "arm" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/destr": { - "version": "2.0.3", - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "license": "Apache-2.0", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", + "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", + "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "base-64": "^1.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/devalue": { - "version": "5.1.1", - "license": "MIT" - }, - "node_modules/devlop": { - "version": "1.1.0", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", + "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/diff": { - "version": "5.2.0", - "license": "BSD-3-Clause", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", + "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.3.1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/direction": { - "version": "2.0.1", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", + "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "bin": { - "direction": "cli.js" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/dlv": { - "version": "1.1.3", - "license": "MIT" - }, - "node_modules/dset": { - "version": "3.1.4", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", + "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "license": "MIT" + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", + "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "license": "MIT" + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", + "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/entities": { - "version": "4.5.0", - "license": "BSD-2-Clause", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", + "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/es-module-lexer": { - "version": "1.6.0", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", + "node_modules/@shikijs/core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.3.tgz", + "integrity": "sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" + "@shikijs/primitive": "4.4.3", + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=20" } }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", + "node_modules/@shikijs/engine-javascript": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.3.tgz", + "integrity": "sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=20" } }, - "node_modules/esbuild": { - "version": "0.24.2", - "hasInstallScript": true, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.3.tgz", + "integrity": "sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==", "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.3.tgz", + "integrity": "sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==", "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@shikijs/types": "4.4.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=20" } }, - "node_modules/esprima": { - "version": "4.0.1", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node_modules/@shikijs/primitive": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.3.tgz", + "integrity": "sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" }, "engines": { - "node": ">=4" + "node": ">=20" } }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", + "node_modules/@shikijs/themes": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.3.tgz", + "integrity": "sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "@shikijs/types": "4.4.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=20" } }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", + "node_modules/@shikijs/types": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.3.tgz", + "integrity": "sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=20" } }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/estree-util-scope": { - "version": "1.0.0", + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/ms": "*" } }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/estree": "*" } }, - "node_modules/estree-util-visit": { - "version": "2.0.0", + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/unist": "*" } }, - "node_modules/estree-walker": { - "version": "3.0.3", + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "@types/unist": "*" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, - "node_modules/expressive-code": { - "version": "0.40.1", + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", "license": "MIT", "dependencies": { - "@expressive-code/core": "^0.40.1", - "@expressive-code/plugin-frames": "^0.40.1", - "@expressive-code/plugin-shiki": "^0.40.1", - "@expressive-code/plugin-text-markers": "^0.40.1" + "@types/unist": "*" } }, - "node_modules/extend": { - "version": "3.0.2", - "license": "MIT" + "node_modules/@types/node": { + "version": "24.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.0.tgz", + "integrity": "sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==", + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } }, - "node_modules/fdir": { - "version": "6.4.3", + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "dependencies": { + "@types/node": "*" } }, - "node_modules/fill-range": { - "version": "7.1.1", + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "license": "ISC" + }, + "node_modules/am-i-vibing": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/am-i-vibing/-/am-i-vibing-0.4.0.tgz", + "integrity": "sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" + "process-ancestry": "^0.1.0" + }, + "bin": { + "am-i-vibing": "dist/cli.mjs" } }, - "node_modules/find-up": { - "version": "4.1.0", - "license": "MIT", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/find-up-simple": { - "version": "1.0.0", + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/find-yarn-workspace-root2": { - "version": "1.2.16", - "license": "Apache-2.0", - "dependencies": { - "micromatch": "^4.0.2", - "pkg-dir": "^4.2.0" - } + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" }, - "node_modules/flattie": { - "version": "1.1.1", - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, - "node_modules/fsevents": { - "version": "2.3.3", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" } }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", + "node_modules/astro": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/astro/-/astro-7.3.5.tgz", + "integrity": "sha512-1p/ernaq/RuESbM5NMjWzbO6SiebMGrGcYlGjMfBhzzgM+n/X7AUmVyw8BqW2QYCdivpsHwJBcD1fqmlTZjZHg==", "license": "MIT", + "dependencies": { + "@astrojs/compiler-rs": "^0.5.0", + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/markdown-satteri": "0.4.2", + "@astrojs/telemetry": "3.3.3", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0", + "am-i-vibing": "^0.4.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^2.0.1", + "devalue": "^5.8.1", + "diff": "^9.0.0", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.28.0", + "find-proc": "0.2.0", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.3.2", + "jsonc-parser": "^3.3.1", + "magic-string": "^1.0.0", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^1.0.1", + "obug": "^3.0.0", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.4", + "shiki": "^4.0.2", + "smol-toml": "^1.8.0", + "svgo": "^4.1.0", + "tinyclip": "^1.0.1", + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.5", + "unstorage": "^1.17.5", + "verkit": "^0.4.0", + "vite": "^8.0.13", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.5.4" + }, + "bin": { + "astro": "bin/astro.mjs" + }, "engines": { - "node": ">=18" + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.35.4" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } } }, - "node_modules/github-slugger": { - "version": "2.0.0", - "license": "ISC" - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", + "node_modules/astro-expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.44.2.tgz", + "integrity": "sha512-avop7nZcRwC7quvIxVguhwEZ9Tp6IoTzKUCLwP30Fb7+er+pWQgcysP01dcuzRIJo81kLGg30q/YAI2Yb1Sdzg==", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "rehype-expressive-code": "^0.44.2", + "url-extras": "^0.1.0" }, - "engines": { - "node": ">= 6" + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta || ^7.0.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" - }, - "node_modules/h3": { - "version": "1.13.1", - "license": "MIT", - "dependencies": { - "cookie-es": "^1.2.2", - "crossws": "^0.3.1", - "defu": "^6.1.4", - "destr": "^2.0.3", - "iron-webcrypto": "^1.2.1", - "ohash": "^1.1.4", - "radix3": "^1.1.2", - "ufo": "^1.5.4", - "uncrypto": "^0.1.3", - "unenv": "^1.10.0" + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" } }, - "node_modules/hast-util-embedded": { - "version": "3.0.0", + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-is-element": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-format": { - "version": "1.1.0", + "node_modules/bcp-47": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.1.tgz", + "integrity": "sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-minify-whitespace": "^1.0.0", - "hast-util-phrasing": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "html-whitespace-sensitive-tag-names": "^3.0.0", - "unist-util-visit-parents": "^6.0.0" + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-from-html": { + "node_modules/bcp-47-match": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.2", + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^6.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-has-property": { - "version": "3.0.0", + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-is-body-ok-link": { - "version": "3.0.1", + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-is-element": { + "node_modules/character-entities-legacy": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-minify-whitespace": { - "version": "1.0.1", + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "unist-util-is": "^6.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/hast-util-phrasing": { - "version": "3.0.1", + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-is-body-ok-link": "^3.0.0", - "hast-util-is-element": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" } }, - "node_modules/hast-util-raw": { - "version": "9.1.0", + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "engines": { + "node": ">=22" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/express" } }, - "node_modules/hast-util-select": { - "version": "6.0.3", + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "bcp-47-match": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "css-selector-parser": "^3.0.0", - "devlop": "^1.0.0", - "direction": "^2.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-to-string": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "nth-check": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "uncrypto": "^0.1.3" } }, - "node_modules/hast-util-to-estree": { - "version": "3.1.1", - "license": "MIT", + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "license": "BSD-2-Clause", "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/hast-util-to-html": { - "version": "9.0.4", + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.2", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "bin": { + "cssesc": "bin/cssesc" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=4" } }, - "node_modules/hast-util-to-string": { - "version": "3.0.1", + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "css-tree": "~2.2.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "ms": "^2.1.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/hastscript": { - "version": "9.0.0", + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" + "character-entities": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/html-escaper": { - "version": "3.0.3", + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, - "node_modules/html-void-elements": { - "version": "3.0.0", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=6" } }, - "node_modules/html-whitespace-sensitive-tag-names": { - "version": "3.0.1", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "license": "BSD-2-Clause" + "node_modules/devalue": { + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.4.tgz", + "integrity": "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==", + "license": "MIT" }, - "node_modules/i18next": { - "version": "23.16.8", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.2" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "license": "MIT", + "dequal": "^2.0.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/inline-style-parser": { - "version": "0.2.4", - "license": "MIT" - }, - "node_modules/iron-webcrypto": { - "version": "1.2.1", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/brc-dd" + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "node_modules/is-alphabetical": { + "node_modules/direction": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", "license": "MIT", + "bin": { + "direction": "cli.js" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "license": "MIT" + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" }, - "node_modules/is-binary-path": { - "version": "2.1.0", + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/is-decimal": { - "version": "2.0.1", - "license": "MIT", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/is-docker": { - "version": "3.0.0", + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "hasInstallScript": true, "license": "MIT", "bin": { - "is-docker": "cli.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.44.2.tgz", + "integrity": "sha512-MLNghkvhyFgVW4oCD5DxBuYnnTXIf0PygzGmfT6OZeOOYWh/D4sH1j931biElkA5RG/J9wY/iuSMGAPF5vzHOg==", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@expressive-code/core": "^0.44.2", + "@expressive-code/plugin-frames": "^0.44.2", + "@expressive-code/plugin-shiki": "^0.44.2", + "@expressive-code/plugin-text-markers": "^0.44.2" } }, - "node_modules/is-glob": { - "version": "4.0.3", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" + "fast-string-truncated-width": "^3.0.2" } }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "fast-string-width": "^3.0.2" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, "engines": { - "node": ">=14.16" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/is-number": { - "version": "7.0.0", + "node_modules/find-proc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/find-proc/-/find-proc-0.2.0.tgz", + "integrity": "sha512-a6h2nTrgB3g5Wrn4au9Ux4kMk7IJtyF6oA8uGn+mk3weLZssUtc1cp6meHIzzuW2o1srrBAxzkmXLbqjRkgB6w==", "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/is-wsl": { - "version": "3.1.0", + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", "license": "MIT", "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "fontkitten": "^1.0.2" } }, - "node_modules/js-yaml": { - "version": "4.1.0", + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "tiny-inflate": "^1.0.3" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=20" } }, - "node_modules/kleur": { - "version": "4.1.5", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/klona": { - "version": "2.0.6", + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, "engines": { - "node": ">= 8" + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/load-yaml-file": { - "version": "0.2.0", + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.5", - "js-yaml": "^3.13.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" } }, - "node_modules/load-yaml-file/node_modules/js-yaml": { - "version": "3.14.1", + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/load-yaml-file/node_modules/js-yaml/node_modules/argparse": { - "version": "1.0.10", + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/locate-path": { - "version": "5.0.0", + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "@types/hast": "^3.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/longest-streak": { - "version": "3.1.0", + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lru-cache": { - "version": "10.4.3", - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.17", + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/magicast": { - "version": "0.3.5", + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/markdown-extensions": { - "version": "2.0.0", + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", "license": "MIT", - "engines": { - "node": ">=16" + "dependencies": { + "@types/hast": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/markdown-table": { - "version": "3.0.4", + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-definitions": { - "version": "6.0.0", + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", + "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-directive": { - "version": "3.0.0", + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", + "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" + "zwitch": "^2.0.4" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" + "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", + "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm": { + "node_modules/hast-util-whitespace": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.0.0", + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.4.2.tgz", + "integrity": "sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "peerDependencies": { + "typescript": "^5 || ^6 || ^7" }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/brc-dd" } }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-mdx-expression": { + "node_modules/is-hexadecimal": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" + "argparse": "^2.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark": { - "version": "4.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz", + "integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==", "license": "MIT", "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", + "node_modules/magicast": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", "license": "MIT", "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" } }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", "license": "MIT", "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.0", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.1", + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { - "micromark-util-types": "^2.0.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", + "node_modules/mdast-util-to-markdown": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.3.tgz", + "integrity": "sha512-wgyJtgUkUcdU7zci7uuwc/tzoAhm0TswWhaXuvnWolqug+8jY7bgNkBBL0dYBnHgc/tHL7lCzRdPsdUxUKKBqw==", "license": "MIT", "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "@types/mdast": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.3.tgz", + "integrity": "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==", "funding": [ { "type": "GitHub Sponsors", @@ -2845,13 +3935,30 @@ ], "license": "MIT", "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", + "node_modules/micromark-core-commonmark": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz", + "integrity": "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==", "funding": [ { "type": "GitHub Sponsors", @@ -2864,57 +3971,48 @@ ], "license": "MIT", "dependencies": { + "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.1.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-types": "^2.0.3" } }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-factory-title": { + "node_modules/micromark-factory-destination": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -2927,14 +4025,15 @@ ], "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-whitespace": { + "node_modules/micromark-factory-label": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -2947,14 +4046,16 @@ ], "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", + "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-character": { - "version": "2.1.1", + "node_modules/micromark-factory-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz", + "integrity": "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==", "funding": [ { "type": "GitHub Sponsors", @@ -2967,29 +4068,14 @@ ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", + "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { + "node_modules/micromark-factory-title": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -3002,48 +4088,16 @@ ], "license": "MIT", "dependencies": { + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { + "node_modules/micromark-factory-whitespace": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -3056,83 +4110,16 @@ ], "license": "MIT", "dependencies": { - "decode-named-character-reference": "^1.0.0", + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -3145,11 +4132,14 @@ ], "license": "MIT", "dependencies": { + "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-sanitize-uri": { + "node_modules/micromark-util-chunked": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -3162,13 +4152,13 @@ ], "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-subtokenize": { - "version": "2.0.3", + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -3181,14 +4171,15 @@ ], "license": "MIT", "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", + "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-symbol": { + "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -3199,10 +4190,16 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/micromark-util-types": { - "version": "2.0.1", + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { "type": "GitHub Sponsors", @@ -3213,707 +4210,603 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mrmime": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=10" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.8", + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/neotraverse": { - "version": "0.6.18", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/nlcst-to-string": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/node-fetch-native": { - "version": "1.6.6", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/ofetch": { - "version": "1.4.1", - "license": "MIT", - "dependencies": { - "destr": "^2.0.3", - "node-fetch-native": "^1.6.4", - "ufo": "^1.5.4" - } - }, - "node_modules/ohash": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/oniguruma-to-es": { - "version": "2.2.0", - "license": "MIT", - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "regex": "^5.1.1", - "regex-recursion": "^5.1.1" - } - }, - "node_modules/p-limit": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "8.1.0", - "license": "MIT", - "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "6.1.4", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pagefind": { - "version": "1.3.0", - "license": "MIT", - "bin": { - "pagefind": "lib/runner/bin.cjs" - }, - "optionalDependencies": { - "@pagefind/darwin-arm64": "1.3.0", - "@pagefind/darwin-x64": "1.3.0", - "@pagefind/linux-arm64": "1.3.0", - "@pagefind/linux-x64": "1.3.0", - "@pagefind/windows-x64": "1.3.0" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "license": "MIT", "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "license": "MIT" - }, - "node_modules/parse-latin": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "@types/unist": "^3.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-modify-children": "^4.0.0", - "unist-util-visit-children": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse5": { - "version": "7.2.1", - "license": "MIT", - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "license": "MIT", - "engines": { - "node": ">=6" + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", + "node_modules/micromark-util-edit-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz", + "integrity": "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" + "micromark-util-types": "^2.0.0" } }, - "node_modules/postcss": { - "version": "8.5.3", + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/postcss-nested": { - "version": "6.2.0", + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" + "micromark-util-types": "^2.0.0" } }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/preferred-pm": { - "version": "4.1.1", + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "find-up-simple": "^1.0.0", - "find-yarn-workspace-root2": "1.2.16", - "which-pm": "^3.0.1" - }, - "engines": { - "node": ">=18.12" + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/prismjs": { - "version": "1.29.0", - "license": "MIT", - "engines": { - "node": ">=6" - } + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/prompts": { - "version": "2.4.2", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } + "node_modules/micromark-util-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "license": "MIT", "engines": { - "node": ">=6" - } - }, - "node_modules/property-information": { - "version": "6.5.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "node": ">=10" } }, - "node_modules/radix3": { - "version": "1.1.2", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/readdirp": { - "version": "3.6.0", + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=8.10.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", + "node_modules/neotraverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz", + "integrity": "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==", "license": "MIT", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 10" } }, - "node_modules/recma-build-jsx": { - "version": "1.0.0", + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "vfile": "^6.0.0" + "@types/nlcst": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/recma-jsx": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "acorn-jsx": "^5.0.0", - "estree-util-to-js": "^2.0.0", - "recma-parse": "^1.0.0", - "recma-stringify": "^1.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" }, - "node_modules/recma-parse": { - "version": "1.0.0", + "node_modules/node-mock-http": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz", + "integrity": "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "esast-util-from-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/recma-stringify": { - "version": "1.0.0", - "license": "MIT", + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-to-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" + "boolbase": "^1.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "license": "MIT" - }, - "node_modules/regex": { - "version": "5.1.1", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/regex-recursion": { - "version": "5.1.1", + "node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], "license": "MIT", - "dependencies": { - "regex": "^5.1.1", - "regex-utilities": "^2.3.0" + "engines": { + "node": ">=12.20.0" } }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "license": "MIT" - }, - "node_modules/rehype": { - "version": "13.0.2", + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "rehype-parse": "^9.0.0", - "rehype-stringify": "^10.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" } }, - "node_modules/rehype-expressive-code": { - "version": "0.40.1", + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", "license": "MIT", "dependencies": { - "expressive-code": "^0.40.1" + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" } }, - "node_modules/rehype-format": { - "version": "5.0.1", + "node_modules/p-limit": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.3.tgz", + "integrity": "sha512-SKgVvDU5TNIxQ4r30U75BNLC6a+6GOEjngrk/ysM5+Zu3oJuk1SC5ApzIzY/7PjCEjoVPMWJ0zho/4QUiEB7TQ==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-format": "^1.0.0" + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rehype-parse": { - "version": "9.0.1", + "node_modules/p-queue": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-html": "^2.0.0", - "unified": "^11.0.0" + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rehype-raw": { - "version": "7.0.0", + "node_modules/p-timeout": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rehype-recma": { - "version": "1.0.0", + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "hast-util-to-estree": "^3.0.0" + "bin": { + "pagefind": "lib/runner/bin.cjs" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" } }, - "node_modules/rehype-stringify": { - "version": "10.0.1", + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-to-html": "^9.0.0", - "unified": "^11.0.0" + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/remark-directive": { - "version": "3.0.0", + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-directive": "^3.0.0", - "micromark-extension-directive": "^3.0.0", - "unified": "^11.0.0" + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/remark-gfm": { - "version": "4.0.0", + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/remark-mdx": { - "version": "3.1.0", + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" + "postcss-selector-parser": "^6.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" } }, - "node_modules/remark-parse": { - "version": "11.0.0", + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-ancestry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/process-ancestry/-/process-ancestry-0.1.0.tgz", + "integrity": "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/remark-rehype": { - "version": "11.1.1", + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" + "engines": { + "node": ">= 20.19.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/remark-smartypants": { - "version": "3.0.2", + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", "license": "MIT", "dependencies": { - "retext": "^9.0.0", - "retext-smartypants": "^6.0.0", - "unified": "^11.0.4", - "unist-util-visit": "^5.0.0" - }, - "engines": { - "node": ">=16.0.0" + "regex-utilities": "^2.3.0" } }, - "node_modules/remark-stringify": { - "version": "11.0.0", + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "regex-utilities": "^2.3.0" } }, - "node_modules/retext": { - "version": "9.0.0", + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype-expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.44.2.tgz", + "integrity": "sha512-jvjQpsfWKBquDrEl5YQA3CJybRRIuDOYsui1BJTs5oKDUK8pPkJSSxY8FjxNlIFIdtgrTjC1zuahuY4Cc1j9SQ==", "license": "MIT", "dependencies": { - "@types/nlcst": "^2.0.0", - "retext-latin": "^4.0.0", - "retext-stringify": "^4.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "expressive-code": "^0.44.2" } }, - "node_modules/retext-latin": { + "node_modules/remark-directive": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-4.0.0.tgz", + "integrity": "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==", "license": "MIT", "dependencies": { - "@types/nlcst": "^2.0.0", - "parse-latin": "^7.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^4.0.0", "unified": "^11.0.0" }, "funding": { @@ -3921,74 +4814,99 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/retext-smartypants": { - "version": "6.2.0", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-visit": "^5.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/retext-stringify": { - "version": "4.0.0", + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", "license": "MIT", "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", - "unified": "^11.0.0" + "unist-util-visit": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/rollup": { - "version": "4.31.0", + "node_modules/rolldown": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", + "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@oxc-project/types": "=0.151.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.31.0", - "@rollup/rollup-android-arm64": "4.31.0", - "@rollup/rollup-darwin-arm64": "4.31.0", - "@rollup/rollup-darwin-x64": "4.31.0", - "@rollup/rollup-freebsd-arm64": "4.31.0", - "@rollup/rollup-freebsd-x64": "4.31.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.31.0", - "@rollup/rollup-linux-arm-musleabihf": "4.31.0", - "@rollup/rollup-linux-arm64-gnu": "4.31.0", - "@rollup/rollup-linux-arm64-musl": "4.31.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.31.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0", - "@rollup/rollup-linux-riscv64-gnu": "4.31.0", - "@rollup/rollup-linux-s390x-gnu": "4.31.0", - "@rollup/rollup-linux-x64-gnu": "4.31.0", - "@rollup/rollup-linux-x64-musl": "4.31.0", - "@rollup/rollup-win32-arm64-msvc": "4.31.0", - "@rollup/rollup-win32-ia32-msvc": "4.31.0", - "@rollup/rollup-win32-x64-msvc": "4.31.0", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm-eabi": "1.2.11", + "@rolldown/binding-android-arm64": "1.2.11", + "@rolldown/binding-darwin-arm64": "1.2.11", + "@rolldown/binding-darwin-x64": "1.2.11", + "@rolldown/binding-freebsd-x64": "1.2.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", + "@rolldown/binding-linux-arm64-gnu": "1.2.11", + "@rolldown/binding-linux-arm64-musl": "1.2.11", + "@rolldown/binding-linux-ppc64-gnu": "1.2.11", + "@rolldown/binding-linux-s390x-gnu": "1.2.11", + "@rolldown/binding-linux-x64-gnu": "1.2.11", + "@rolldown/binding-linux-x64-musl": "1.2.11", + "@rolldown/binding-openharmony-arm64": "1.2.11", + "@rolldown/binding-win32-arm64-msvc": "1.2.11", + "@rolldown/binding-win32-x64-msvc": "1.2.11" + } + }, + "node_modules/satteri": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.10.5.tgz", + "integrity": "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.5", + "@types/hast": "^3.0.5", + "@types/mdast": "^4.0.4", + "@types/unist": "^3.0.3" + }, + "optionalDependencies": { + "@bruits/satteri-darwin-arm64": "0.10.5", + "@bruits/satteri-darwin-x64": "0.10.5", + "@bruits/satteri-linux-arm64-gnu": "0.10.5", + "@bruits/satteri-linux-arm64-musl": "0.10.5", + "@bruits/satteri-linux-x64-gnu": "0.10.5", + "@bruits/satteri-linux-x64-musl": "0.10.5", + "@bruits/satteri-wasm32-wasi": "0.10.5", + "@bruits/satteri-win32-arm64-msvc": "0.10.5", + "@bruits/satteri-win32-x64-msvc": "0.10.5" } }, "node_modules/sax": { - "version": "1.4.1", - "license": "ISC" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3998,102 +4916,102 @@ } }, "node_modules/sharp": { - "version": "0.33.5", - "hasInstallScript": true, + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.6.3", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/shiki": { - "version": "1.29.2", - "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.2", - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/langs": "1.29.2", - "@shikijs/themes": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.3.tgz", + "integrity": "sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==", "license": "MIT", "dependencies": { - "is-arrayish": "^0.3.1" + "@shikijs/core": "4.4.3", + "@shikijs/engine-javascript": "4.4.3", + "@shikijs/engine-oniguruma": "4.4.3", + "@shikijs/langs": "4.4.3", + "@shikijs/themes": "4.4.3", + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" } }, "node_modules/sisteransi": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, "node_modules/sitemap": { - "version": "8.0.0", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", "license": "MIT", "dependencies": { - "@types/node": "^17.0.5", + "@types/node": "^24.9.2", "@types/sax": "^1.2.1", "arg": "^5.0.0", - "sax": "^1.2.4" + "sax": "^1.4.1" }, "bin": { - "sitemap": "dist/cli.js" + "sitemap": "dist/esm/cli.js" }, "engines": { - "node": ">=14.0.0", - "npm": ">=6.0.0" + "node": ">=20.19.5", + "npm": ">=10.8.2" } }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "license": "MIT" - }, "node_modules/smol-toml": { - "version": "1.3.1", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz", + "integrity": "sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -4102,15 +5020,10 @@ "url": "https://github.com/sponsors/cyyynthia" } }, - "node_modules/source-map": { - "version": "0.7.4", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, "node_modules/source-map-js": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4118,37 +5031,18 @@ }, "node_modules/space-separated-tokens": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "license": "BSD-3-Clause" - }, - "node_modules/stream-replace-string": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/string-width": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -4159,43 +5053,63 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/svgo": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz", + "integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "commander": "^11.1.0", + "css-select": "^6.0.0", + "css-tree": "^3.0.1", + "css-what": "^7.0.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "1.6.1" + }, + "bin": { + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=12" + "node": ">=16" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/svgo" } }, - "node_modules/strip-bom": { - "version": "3.0.0", + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-1.0.3.tgz", + "integrity": "sha512-nTv2raKFMzGG6JJp0lpNDsRPdfFE6UHlu2rVEjXo8vkx1ZS3LW2CVXqIaLu8xZlu+6tbFs9V58Qc/Jmk/Ii7wA==", "license": "MIT", "engines": { - "node": ">=4" + "node": "^16.14.0 || >= 17.3.0" } }, - "node_modules/style-to-object": { - "version": "1.0.8", + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.4" + "engines": { + "node": ">=18" } }, - "node_modules/tinyexec": { - "version": "0.3.2", - "license": "MIT" - }, "node_modules/tinyglobby": { - "version": "0.2.12", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { - "fdir": "^6.4.3", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -4204,18 +5118,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/trim-lines": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "license": "MIT", "funding": { "type": "github", @@ -4224,83 +5130,58 @@ }, "node_modules/trough": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/tsconfck": { - "version": "3.1.4", - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/type-fest": { - "version": "4.32.0", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.7.3", - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true }, "node_modules/ufo": { - "version": "1.5.4", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "license": "MIT" }, "node_modules/ultrahtml": { - "version": "1.5.3", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz", + "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==", "license": "MIT" }, "node_modules/uncrypto": { "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", "license": "MIT" }, - "node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "license": "MIT" - }, - "node_modules/unenv": { - "version": "1.10.0", + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", "license": "MIT", - "dependencies": { - "consola": "^3.2.3", - "defu": "^6.1.4", - "mime": "^3.0.0", - "node-fetch-native": "^1.6.4", - "pathe": "^1.1.2" + "engines": { + "node": ">=22.19.0" } }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT" + }, "node_modules/unified": { "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4316,8 +5197,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unifont": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.5.tgz", + "integrity": "sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ohash": "^2.0.11", + "undici": "^8.0.0" + } + }, "node_modules/unist-util-find-after": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4329,7 +5223,9 @@ } }, "node_modules/unist-util-is": { - "version": "6.0.0", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -4339,20 +5235,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-modify-children": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "array-iterate": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-position": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -4362,31 +5248,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -4397,7 +5262,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4409,19 +5276,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-visit-children": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-visit-parents": { - "version": "6.0.1", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4433,37 +5291,40 @@ } }, "node_modules/unstorage": { - "version": "1.14.4", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", "license": "MIT", "dependencies": { "anymatch": "^3.1.3", - "chokidar": "^3.6.0", - "destr": "^2.0.3", - "h3": "^1.13.0", - "lru-cache": "^10.4.3", - "node-fetch-native": "^1.6.4", - "ofetch": "^1.4.1", - "ufo": "^1.5.4" + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", - "@azure/identity": "^4.5.0", + "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", - "@capacitor/preferences": "^6.0.3", - "@deno/kv": ">=0.8.4", - "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", - "@vercel/blob": ">=0.27.0", - "@vercel/kv": "^1.0.1", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", - "uploadthing": "^7.4.1" + "uploadthing": "^7.4.4" }, "peerDependenciesMeta": { "@azure/app-configuration": { @@ -4502,6 +5363,9 @@ "@vercel/blob": { "optional": true }, + "@vercel/functions": { + "optional": true + }, "@vercel/kv": { "optional": true }, @@ -4522,28 +5386,44 @@ } } }, + "node_modules/url-extras": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/url-extras/-/url-extras-0.1.0.tgz", + "integrity": "sha512-8tzwTeXFPuX/5PHuCDQE5Dd9Ts4rwoq2t9aIT+HS4iAVpmj5l4Ao7Q+BuuFjvWRqrLswBhQDk8O96ZicgCqQqw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/vfile": { - "version": "6.0.3", + "node_modules/verkit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.4.1.tgz", + "integrity": "sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==", "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" + "engines": { + "node": ">=18.12.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/vfile-location": { - "version": "5.0.3", + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", - "vfile": "^6.0.0" + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", @@ -4551,7 +5431,9 @@ } }, "node_modules/vfile-message": { - "version": "4.0.2", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4563,18 +5445,22 @@ } }, "node_modules/vite": { - "version": "6.2.0", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", + "integrity": "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==", "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "postcss": "^8.5.3", - "rollup": "^4.30.1" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.9", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -4583,14 +5469,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -4599,13 +5486,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { + "optional": true + }, + "jiti": { "optional": true }, - "lightningcss": { + "less": { "optional": true }, "sass": { @@ -4631,67 +5521,18 @@ } } }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.25.0", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "node_modules/vite/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/vitefu": { - "version": "1.0.5", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", "license": "MIT", "workspaces": [ "tests/deps/*", - "tests/projects/*" + "tests/projects/*", + "tests/projects/workspace/packages/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "vite": { @@ -4699,72 +5540,25 @@ } } }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/which-pm": { - "version": "3.0.1", - "license": "MIT", - "dependencies": { - "load-yaml-file": "^0.2.0" - }, - "engines": { - "node": ">=18.12" - } - }, - "node_modules/which-pm-runs": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/widest-line": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/xxhash-wasm": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", "license": "MIT" }, "node_modules/yargs-parser": { - "version": "21.1.1", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { - "version": "1.1.1", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", "engines": { "node": ">=12.20" @@ -4773,52 +5567,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yocto-spinner": { - "version": "0.2.1", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { - "version": "3.24.1", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-to-json-schema": { - "version": "3.24.1", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/zod-to-ts": { - "version": "1.2.0", - "peerDependencies": { - "typescript": "^4.9.4 || ^5.0.2", - "zod": "^3" - } - }, "node_modules/zwitch": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "license": "MIT", "funding": { "type": "github", diff --git a/docs/package.json b/docs/package.json index 021f914..6037f07 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,8 +1,8 @@ { "dependencies": { - "@astrojs/starlight": "0.35.2", - "astro": "5.13.3", - "sharp": "0.34.3" + "@astrojs/starlight": "^0.42.4", + "astro": "^7.3.5", + "sharp": "^0.35.5" }, "name": "committy-docs", "scripts": {