From 45d311e1ed8f8f8503e62e50a76c8ff386cc1649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Sun, 5 Apr 2026 15:06:24 +0800 Subject: [PATCH 1/9] Add GUI CI tests and validate release native binaries --- .github/workflows/pull-request.yml | 22 ++++++++++++++++++++++ .github/workflows/release-cli.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 0577c556..57f00137 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -134,6 +134,28 @@ jobs: - name: Run all tests run: pnpm run test + test-gui: + if: github.event.pull_request.draft == false + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-node-pnpm + + - uses: ./.github/actions/setup-rust + with: + cache-key: pr + + - name: Build native modules + run: pnpm run build:native + + - name: Build + run: pnpm run build + + - name: Run GUI tests + run: pnpm turbo test --filter=@truenine/memory-sync-gui + test-sdk: if: github.event.pull_request.draft == false runs-on: ubuntu-24.04 diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 96d7a6db..36e2feef 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -248,6 +248,33 @@ jobs: fi done <<< "$CLI_NATIVE_BINDING_PREFIXES" done + - name: Validate NAPI binary format and size + shell: bash + run: | + shopt -s nullglob + min_size=$((1024 * 1024)) + for target_dir in cli/npm/*/; do + if [ ! -f "${target_dir}package.json" ]; then + continue + fi + for node_file in "${target_dir}"*.node; do + if [ ! -f "$node_file" ]; then + continue + fi + size=$(stat -c%s "$node_file" 2>/dev/null || stat -f%z "$node_file" 2>/dev/null || echo 0) + if [ "$size" -lt "$min_size" ]; then + echo "ERROR: ${node_file} is too small (${size} bytes), expected at least ${min_size} bytes" + exit 1 + fi + file_output=$(file "$node_file") + echo "File info for ${node_file}: ${file_output}" + if [[ "$file_output" == *"ELF"* ]] || [[ "$file_output" == *"Mach-O"* ]] || [[ "$file_output" == *"PE"* ]]; then + echo "OK: ${node_file} is a valid native binary" + else + echo "WARNING: ${node_file} format could not be verified as ELF/Mach-O/PE" + fi + done + done - name: Publish CLI platform sub-packages uses: ./.github/actions/npm-publish-package with: From 89aa63edb2647a70789e416a1bb891891fa74d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Sun, 5 Apr 2026 19:09:25 +0800 Subject: [PATCH 2/9] Refine config handling and plugin output defaults --- cli/package.json | 3 + cli/src/commands/PluginsCommand.ts | 13 +- cli/src/plugin.config.ts | 54 ++-- cli/tsconfig.json | 3 +- doc/app/layout.tsx | 3 +- doc/app/robots.ts | 2 +- doc/app/sitemap.ts | 2 +- doc/content/cli/cleanup-protection.mdx | 51 +--- doc/content/cli/cli-commands.mdx | 2 + doc/content/cli/dry-run-and-clean.mdx | 10 +- doc/content/cli/frontmatter.mdx | 5 +- doc/content/cli/index.mdx | 5 +- doc/content/cli/output-scopes.mdx | 56 ++--- doc/content/cli/plugin-config.mdx | 40 ++- doc/content/cli/schema.mdx | 155 ++++++------ doc/content/cli/supported-outputs.mdx | 26 +- doc/content/cli/troubleshooting.mdx | 8 +- doc/content/cli/workspace-setup.mdx | 3 +- doc/content/quick-guide/aindex-and-config.mdx | 144 +++++++---- doc/content/technical-details/commands.mdx | 14 +- doc/content/technical-details/pipeline.mdx | 6 +- gui/package.json | 4 +- gui/src/pages/ConfigPage.tsx | 2 +- .../utils/configValidation.property.test.ts | 6 +- gui/src/utils/configValidation.test.ts | 51 ++++ gui/src/utils/configValidation.ts | 97 ++++++-- libraries/md-compiler/src/compiler/index.ts | 1 + .../src/compiler/mdx-to-md.test.ts | 17 ++ .../md-compiler/src/compiler/mdx-to-md.ts | 7 +- libraries/md-compiler/src/globals/index.ts | 14 ++ libraries/md-compiler/src/markdown/index.ts | 13 +- libraries/md-compiler/src/mdx-to-md.ts | 4 +- libraries/md-compiler/src/mdx_to_md.rs | 27 +- libraries/md-compiler/tsconfig.json | 3 +- mcp/src/server.ts | 6 +- scripts/postinstall.ts | 14 +- sdk/src/ConfigLoader.test.ts | 111 ++++++++- sdk/src/ConfigLoader.ts | 110 +------- sdk/src/ProtectedDeletionGuard.ts | 16 +- sdk/src/config.outputScopes.test.ts | 45 ---- sdk/src/config.plugins-fast-path.test.ts | 49 ---- sdk/src/config.test.ts | 97 ++++---- sdk/src/config.ts | 215 +++++----------- sdk/src/core/config/mod.rs | 164 +++++++++++- sdk/src/index.ts | 5 +- sdk/src/inputs/effect-orphan-cleanup.ts | 16 +- sdk/src/inputs/input-agentskills.ts | 16 +- sdk/src/inputs/input-command.ts | 2 +- sdk/src/inputs/input-rule.ts | 2 +- sdk/src/inputs/input-subagent.ts | 2 +- sdk/src/inputs/runtime.ts | 3 +- sdk/src/plugins/AbstractOutputPlugin.test.ts | 2 +- sdk/src/plugins/AgentsOutputPlugin.test.ts | 2 +- .../plugins/ClaudeCodeCLIOutputPlugin.test.ts | 2 +- sdk/src/plugins/CodexCLIOutputPlugin.test.ts | 98 +------- sdk/src/plugins/CursorOutputPlugin.test.ts | 114 +-------- sdk/src/plugins/EditorConfigOutputPlugin.ts | 59 ----- .../plugins/GenericSkillsOutputPlugin.test.ts | 58 ----- .../JetBrainsAIAssistantCodexOutputPlugin.ts | 1 + sdk/src/plugins/KiroCLIOutputPlugin.test.ts | 2 +- .../plugins/OpencodeCLIOutputPlugin.test.ts | 59 ++++- sdk/src/plugins/OpencodeCLIOutputPlugin.ts | 2 +- .../QoderIDEPluginOutputPlugin.test.ts | 120 +-------- .../plugins/ReadmeMdConfigFileOutputPlugin.ts | 57 ++++- sdk/src/plugins/TraeIDEOutputPlugin.test.ts | 4 +- sdk/src/plugins/WarpIDEOutputPlugin.test.ts | 4 +- sdk/src/plugins/WindsurfOutputPlugin.test.ts | 40 ++- sdk/src/plugins/ide-config-output.test.ts | 28 ++- .../AbstractOutputPlugin.frontmatter.test.ts | 8 +- .../AbstractOutputPlugin.subagents.test.ts | 2 +- .../plugin-core/AbstractOutputPlugin.ts | 38 +-- .../plugins/plugin-core/ConfigTypes.schema.ts | 42 +++- .../plugin-core/GlobalScopeCollector.test.ts | 52 ++++ .../plugin-core/GlobalScopeCollector.ts | 18 +- sdk/src/plugins/plugin-core/constants.ts | 1 - .../plugin-core/plugin.enablement.test.ts | 68 +++++ .../plugin.outputScopes.validation.test.ts | 182 -------------- sdk/src/plugins/plugin-core/plugin.ts | 200 ++++++++------- sdk/src/plugins/plugin-editorconfig.ts | 3 - sdk/src/plugins/plugin-warp-ide.ts | 3 - sdk/src/prompts.test.ts | 23 +- sdk/src/prompts.ts | 5 +- .../runtime/cleanup.execution-scope.test.ts | 234 +++++++++++++++--- sdk/src/runtime/cleanup.ts | 36 +-- sdk/src/wsl-mirror-sync.test.ts | 49 +++- sdk/src/wsl-mirror-sync.ts | 2 + sdk/test/native-binding/cleanup.ts | 20 +- sdk/test/native-binding/desk-paths.ts | 59 +---- sdk/test/setup-native-binding.ts | 22 +- sdk/tsconfig.eslint.json | 3 +- sdk/tsconfig.json | 3 +- sdk/tsdown.config.ts | 3 +- sdk/vite.config.ts | 3 +- 93 files changed, 1712 insertions(+), 1743 deletions(-) delete mode 100644 sdk/src/config.outputScopes.test.ts delete mode 100644 sdk/src/config.plugins-fast-path.test.ts delete mode 100644 sdk/src/plugins/EditorConfigOutputPlugin.ts create mode 100644 sdk/src/plugins/plugin-core/GlobalScopeCollector.test.ts create mode 100644 sdk/src/plugins/plugin-core/plugin.enablement.test.ts delete mode 100644 sdk/src/plugins/plugin-core/plugin.outputScopes.validation.test.ts delete mode 100644 sdk/src/plugins/plugin-editorconfig.ts delete mode 100644 sdk/src/plugins/plugin-warp-ide.ts diff --git a/cli/package.json b/cli/package.json index c4794ff7..066edc0b 100644 --- a/cli/package.json +++ b/cli/package.json @@ -33,6 +33,9 @@ "dist", "dist/tnmsc.schema.json" ], + "engines": { + "node": ">= 22" + }, "napi": { "binaryName": "napi-memory-sync-cli", "targets": [ diff --git a/cli/src/commands/PluginsCommand.ts b/cli/src/commands/PluginsCommand.ts index 35fd6d0e..5d12cd75 100644 --- a/cli/src/commands/PluginsCommand.ts +++ b/cli/src/commands/PluginsCommand.ts @@ -5,21 +5,10 @@ export class PluginsCommand implements Command { readonly name = 'plugins' async execute(ctx: CommandContext): Promise { - const {logger, outputPlugins, userConfigOptions} = ctx + const {logger, outputPlugins} = ctx const pluginInfos: JsonPluginInfo[] = [] - for (const plugin of userConfigOptions.plugins) { - pluginInfos.push({ - name: plugin.name, - kind: 'Output', - description: plugin.name, - dependencies: [...plugin.dependsOn ?? []] - }) - } - - const registeredNames = new Set(pluginInfos.map(plugin => plugin.name)) for (const plugin of outputPlugins) { - if (registeredNames.has(plugin.name)) continue pluginInfos.push({ name: plugin.name, kind: 'Output', diff --git a/cli/src/plugin.config.ts b/cli/src/plugin.config.ts index 20a49526..47740236 100644 --- a/cli/src/plugin.config.ts +++ b/cli/src/plugin.config.ts @@ -7,7 +7,6 @@ import { CursorOutputPlugin, defineConfig, DroidCLIOutputPlugin, - EditorConfigOutputPlugin, GeminiCLIOutputPlugin, GitExcludeOutputPlugin, JetBrainsAIAssistantCodexOutputPlugin, @@ -24,6 +23,10 @@ import { ZedIDEConfigOutputPlugin } from '@truenine/memory-sync-sdk' +type DefineConfigWithOutputPlugins = Parameters[0] & { + readonly outputPlugins: PipelineConfig['outputPlugins'] +} + export function resolveRuntimeCommandFromArgv(argv: readonly string[] = process.argv): RuntimeCommand { const args = argv.filter((arg): arg is string => arg != null) const userArgs = args.slice(2) @@ -39,34 +42,33 @@ export async function createDefaultPluginConfig( runtimeCommand: RuntimeCommand = resolveRuntimeCommandFromArgv(argv), executionCwd: string = process.cwd() ): Promise { + const outputPlugins: PipelineConfig['outputPlugins'] = [ + new AgentsOutputPlugin(), + new ClaudeCodeCLIOutputPlugin(), + new CodexCLIOutputPlugin(), + new JetBrainsAIAssistantCodexOutputPlugin(), + new DroidCLIOutputPlugin(), + new GeminiCLIOutputPlugin(), + new KiroCLIOutputPlugin(), + new OpencodeCLIOutputPlugin(), + new QoderIDEPluginOutputPlugin(), + new TraeIDEOutputPlugin(), + new TraeCNIDEOutputPlugin(), + new WarpIDEOutputPlugin(), + new WindsurfOutputPlugin(), + new CursorOutputPlugin(), + new GitExcludeOutputPlugin(), + new JetBrainsIDECodeStyleConfigOutputPlugin(), + new VisualStudioCodeIDEConfigOutputPlugin(), + new ZedIDEConfigOutputPlugin(), + new ReadmeMdConfigFileOutputPlugin() + ] + return defineConfig({ executionCwd, runtimeCommand, - pluginOptions: { - plugins: [ - new AgentsOutputPlugin(), - new ClaudeCodeCLIOutputPlugin(), - new CodexCLIOutputPlugin(), - new JetBrainsAIAssistantCodexOutputPlugin(), - new DroidCLIOutputPlugin(), - new GeminiCLIOutputPlugin(), - new KiroCLIOutputPlugin(), - new OpencodeCLIOutputPlugin(), - new QoderIDEPluginOutputPlugin(), - new TraeIDEOutputPlugin(), - new TraeCNIDEOutputPlugin(), - new WarpIDEOutputPlugin(), - new WindsurfOutputPlugin(), - new CursorOutputPlugin(), - new GitExcludeOutputPlugin(), - new JetBrainsIDECodeStyleConfigOutputPlugin(), - new EditorConfigOutputPlugin(), - new VisualStudioCodeIDEConfigOutputPlugin(), - new ZedIDEConfigOutputPlugin(), - new ReadmeMdConfigFileOutputPlugin() - ] - } - }) + outputPlugins + } as DefineConfigWithOutputPlugins) } export default createDefaultPluginConfig diff --git a/cli/tsconfig.json b/cli/tsconfig.json index d0fc74f3..3a30817c 100644 --- a/cli/tsconfig.json +++ b/cli/tsconfig.json @@ -26,7 +26,6 @@ "@truenine/plugin-claude-code-cli": ["./src/plugins/plugin-claude-code-cli.ts"], "@truenine/plugin-cursor": ["./src/plugins/plugin-cursor.ts"], "@truenine/plugin-droid-cli": ["./src/plugins/plugin-droid-cli.ts"], - "@truenine/plugin-editorconfig": ["./src/plugins/plugin-editorconfig.ts"], "@truenine/plugin-gemini-cli": ["./src/plugins/plugin-gemini-cli.ts"], "@truenine/plugin-git-exclude": ["./src/plugins/plugin-git-exclude.ts"], "@truenine/plugin-jetbrains-ai-codex": ["./src/plugins/plugin-jetbrains-ai-codex.ts"], @@ -37,7 +36,7 @@ "@truenine/plugin-readme": ["./src/plugins/plugin-readme.ts"], "@truenine/plugin-trae-ide": ["./src/plugins/plugin-trae-ide.ts"], "@truenine/plugin-vscode": ["./src/plugins/plugin-vscode.ts"], - "@truenine/plugin-warp-ide": ["./src/plugins/plugin-warp-ide.ts"], + "@truenine/plugin-warp-ide": ["../sdk/src/plugins/WarpIDEOutputPlugin.ts"], "@truenine/plugin-windsurf": ["./src/plugins/plugin-windsurf.ts"], "@truenine/plugin-zed": ["./src/plugins/plugin-zed.ts"] }, diff --git a/doc/app/layout.tsx b/doc/app/layout.tsx index 48a62c3e..cd34f4d1 100644 --- a/doc/app/layout.tsx +++ b/doc/app/layout.tsx @@ -1,6 +1,7 @@ import type {Metadata} from 'next' import {Inter, JetBrains_Mono} from 'next/font/google' -import {getSiteUrl, siteConfig} from '../lib/site' +import React from 'react' +import {getSiteUrl, siteConfig} from '@/lib/site' import 'nextra-theme-docs/style.css' import './globals.scss' diff --git a/doc/app/robots.ts b/doc/app/robots.ts index 7f4353bf..5ad9d4cc 100644 --- a/doc/app/robots.ts +++ b/doc/app/robots.ts @@ -1,5 +1,5 @@ import type {MetadataRoute} from 'next' -import {getSiteUrl} from '../lib/site' +import {getSiteUrl} from '@/lib/site' export default function robots(): MetadataRoute.Robots { const siteUrl = getSiteUrl() diff --git a/doc/app/sitemap.ts b/doc/app/sitemap.ts index d885c307..9c782fd3 100644 --- a/doc/app/sitemap.ts +++ b/doc/app/sitemap.ts @@ -2,7 +2,7 @@ import type {MetadataRoute} from 'next' import {readdir} from 'node:fs/promises' import path from 'node:path' import process from 'node:process' -import {getSiteUrl} from '../lib/site' +import {getSiteUrl} from '@/lib/site' const MDX_EXTENSION = '.mdx' const TRAILING_SLASHES_PATTERN = /\/+$/u diff --git a/doc/content/cli/cleanup-protection.mdx b/doc/content/cli/cleanup-protection.mdx index 8f9b53b2..0786aa25 100644 --- a/doc/content/cli/cleanup-protection.mdx +++ b/doc/content/cli/cleanup-protection.mdx @@ -1,53 +1,28 @@ --- title: Cleanup Protection -description: Explains how cleanupProtection rules keep the clean command from deleting files that should not be removed. +description: Explains that `cleanupProtection` is now a legacy `.tnmsc.json` field and how cleanup safety works today. sidebarTitle: Cleanup Protection status: stable --- # Cleanup Protection -`clean` is not a brainless file-deletion command, so `cleanupProtection` is not an optional afterthought. +`cleanupProtection` should now be treated as a legacy config block in `~/.aindex/.tnmsc.json`. -## Rule Structure +The `clean` command still has protection behavior, but it now comes from fixed runtime guardrails and plugin-declared cleanup boundaries rather than user-authored JSON rules in the global config file. -Each protection rule supports these fields: +## What Still Protects Cleanup Today -| Field | Required | Description | -| --- | --- | --- | -| `path` | Yes | Target path or glob | -| `protectionMode` | Yes | `direct` or `recursive` | -| `matcher` | No | `path` or `glob` | -| `reason` | No | Why this protection rule exists | +- `tnmsc clean --dry-run` lets you inspect the exact cleanup plan first. +- Workspace-level reserved roots still stay protected. +- Output plugins still declare their own cleanup and protection boundaries internally. -## Semantics +## What You Should Do Instead -- `direct`: protect only this exact target. -- `recursive`: protect the entire subtree under this path. +- Keep hand-written files out of tnmsc-managed output directories. +- Use `dry-run` before a real clean. +- If a target needs different cleanup behavior, change the project/plugin assembly rather than adding a `cleanupProtection` block back into `~/.aindex/.tnmsc.json`. -## When You Must Configure It +## What To Remove From Old Configs -If your output directories are mixed with: - -- hand-written files -- files generated by other tools that are not owned by tnmsc -- temporary manual patches - -then add protection rules before enabling `clean`, or sooner or later you will delete things that should have stayed. - -## Recommended Shape - -```json -{ - "cleanupProtection": { - "rules": [ - { - "path": ".cursor/local-notes", - "protectionMode": "recursive", - "matcher": "path", - "reason": "Maintained manually and not owned by tnmsc outputs" - } - ] - } -} -``` +If an older config still contains `cleanupProtection`, remove it. It is no longer part of the supported user-facing config surface. diff --git a/doc/content/cli/cli-commands.mdx b/doc/content/cli/cli-commands.mdx index 3b731b1e..69d15bef 100644 --- a/doc/content/cli/cli-commands.mdx +++ b/doc/content/cli/cli-commands.mdx @@ -24,6 +24,8 @@ The commands currently exposed by `tnmsc --help` are: The global user config lives at `~/.aindex/.tnmsc.json`. Edit that file directly. The authoritative field list and fixed aindex layout now live in [aindex and `.tnmsc.json`](/docs/quick-guide/aindex-and-config). +That includes the newer `codeStyles` block for lightweight user code-style preferences such as `indent` and `tabSize`. + ### `logLevel` Is Also Strictly Enumerated It can only be: diff --git a/doc/content/cli/dry-run-and-clean.mdx b/doc/content/cli/dry-run-and-clean.mdx index 31b9a888..4c3c394d 100644 --- a/doc/content/cli/dry-run-and-clean.mdx +++ b/doc/content/cli/dry-run-and-clean.mdx @@ -1,6 +1,6 @@ --- title: dry-run and clean -description: Explains how tnmsc previews outputs, then performs cleanup, with cleanupProtection used to control risk. +description: Explains how tnmsc previews outputs, then performs cleanup, with dry-run and built-in guardrails used to control risk. sidebarTitle: dry-run and clean status: stable --- @@ -13,12 +13,12 @@ status: stable - When integrating a project for the first time - Right after changing `plugin.config.ts` -- Right after changing [Output Scopes](/docs/cli/output-scopes) +- Right after changing source prompt scope or global config - Before validating the impact of a large source-content change ## What `clean` Does -`tnmsc clean` removes generated output files. It is not a blind directory deletion command. It follows the current output model and cleanup-protection rules. +`tnmsc clean` removes generated output files. It is not a blind directory deletion command. It follows the current output model and the cleanup declarations built into the runtime. After normal cleanup finishes, `tnmsc clean` also scans the current project source tree and removes remaining empty directories. That empty-dir sweep explicitly skips Git internals as well as dependency, build-output, and cache directory trees. @@ -32,10 +32,10 @@ tnmsc clean --dry-run ## Risk Boundary -If your output directories also contain hand-written files or outputs from other tools, read [Cleanup Protection](/docs/cli/cleanup-protection) first. Without protection rules, the risk of `clean` rises sharply. +If your output directories also contain hand-written files or outputs from other tools, do not rely on a `cleanupProtection` block in `~/.aindex/.tnmsc.json`. Keep those files out of tnmsc-managed output paths, or adjust the project/plugin assembly before running a real clean. ## Recommended Habits -1. Run `dry-run` first when you change config or switch projects. +1. Run `dry-run` first when you change config, source scope, or plugin assembly. 2. Run `clean --dry-run` first when you really intend to clean. 3. If something looks wrong, continue with [Troubleshooting](/docs/cli/troubleshooting). diff --git a/doc/content/cli/frontmatter.mdx b/doc/content/cli/frontmatter.mdx index a7a07760..ff3e23fe 100644 --- a/doc/content/cli/frontmatter.mdx +++ b/doc/content/cli/frontmatter.mdx @@ -24,7 +24,7 @@ Optional fields include: ## 2. The Sync System's `frontMatter` Config -The only public config field in the schema and `config.ts` right now is: +The `frontMatter` block currently exposes only one field: ```json { @@ -36,6 +36,8 @@ The only public config field in the schema and `config.ts` right now is: Its job is not to describe the page itself. It controls whether a blank line is preserved after front matter during output. +If you are looking for lightweight personal indentation preferences such as `tabSize` or `indent`, that is now the separate `codeStyles` block in `~/.aindex/.tnmsc.json`, not `frontMatter`. + ## 3. Source-Content Frontmatter Different input types also store description, trigger conditions, tool constraints, and similar fields in their own source-file frontmatter. Multiple output plugins consume those fields to map target metadata. @@ -52,6 +54,7 @@ See [Technical Details](/docs/technical-details) for the boundary between those - Docs frontmatter is for the docs site - The `frontMatter` config is for output behavior +- `codeStyles` is the separate config block for lightweight code-style preferences such as indentation - Source-content frontmatter is for the sync system and output plugins, but `skills` and `subagents` now take their names from the path rather than `name` These are three different concerns. diff --git a/doc/content/cli/index.mdx b/doc/content/cli/index.mdx index 532823db..7a0f2053 100644 --- a/doc/content/cli/index.mdx +++ b/doc/content/cli/index.mdx @@ -17,8 +17,9 @@ This section is organized around the public `tnmsc` command surface. Questions s - [First Sync](/docs/cli/first-sync): run `help`, `dry-run`, and the real write flow in the recommended order. - [CLI Commands](/docs/cli/cli-commands): check the command surface currently exposed by `tnmsc --help`. - [dry-run and clean](/docs/cli/dry-run-and-clean): preview first, write second, clean last. -- [plugin.config.ts](/docs/cli/plugin-config) and [JSON Schema](/docs/cli/schema): verify runtime assembly and schema-specific behavior. -- [Output Scopes](/docs/cli/output-scopes), [Front Matter](/docs/cli/frontmatter), and [Cleanup Protection](/docs/cli/cleanup-protection): confirm boundary-control behavior. +- [plugin.config.ts](/docs/cli/plugin-config) and [JSON Schema](/docs/cli/schema): verify runtime assembly and the current `.tnmsc.json` field surface. +- [Front Matter](/docs/cli/frontmatter): understand what the `frontMatter` block does and what it does not do. +- [Output Scopes](/docs/cli/output-scopes) and [Cleanup Protection](/docs/cli/cleanup-protection): migrate away from removed legacy config blocks. - [Supported Outputs](/docs/cli/supported-outputs), [Troubleshooting](/docs/cli/troubleshooting), and [Upgrade Notes](/docs/cli/upgrade-notes): handle day-to-day usage and version migration. ## Recommended Order diff --git a/doc/content/cli/output-scopes.mdx b/doc/content/cli/output-scopes.mdx index b2a9329a..d32ae8ce 100644 --- a/doc/content/cli/output-scopes.mdx +++ b/doc/content/cli/output-scopes.mdx @@ -1,57 +1,33 @@ --- title: Output Scopes -description: Explains how outputScopes restrict project and global output scopes per plugin and per topic. +description: Explains that `outputScopes` is now a legacy `.tnmsc.json` field and where output placement is controlled instead. sidebarTitle: Output Scopes status: stable --- # Output Scopes -`outputScopes` is one of the most important safety valves in the current implementation, and also one of the easiest to overlook. +`outputScopes` should now be treated as a legacy config block in `~/.aindex/.tnmsc.json`. -## What Problem It Solves +The current runtime no longer documents or supports per-plugin output-scope overrides from the global user config file. -Not every piece of content should be emitted by every plugin to both global and project destinations. `outputScopes` lets you declare the source scope by plugin and by topic. +## What Controls Output Placement Now -## Supported Topics +Output placement is now determined by the combination of: -The current core constants and schema support: +- which output plugins are assembled in [plugin.config.ts](/docs/cli/plugin-config) +- whether the source asset itself is project-scoped or global-scoped +- plugin-specific routing and remap behavior -- `prompt` -- `rules` -- `commands` -- `subagents` -- `skills` -- `mcp` +That is the place to debug first when content lands in the wrong destination. -## Allowed Scopes +## What To Do With Older Config Files -- `project` -- `global` +If an older config still contains `outputScopes`, remove it from `~/.aindex/.tnmsc.json` instead of expanding it further. -Some topics allow multi-value arrays, while some plugins declare a topic as single-valued. If you pass multiple scopes to a single-valued topic, validation fails before execution. +## Migration Checklist -## Example - -```json -{ - "outputScopes": { - "plugins": { - "CursorOutputPlugin": { - "prompt": "project", - "rules": ["project", "global"] - } - } - } -} -``` - -## When to Check It First - -Look here first when you see any of these: - -- Rules are written into global config even though you only wanted project-level output -- A global prompt is incorrectly pulled into a local target -- Skill, command, or MCP-related outputs appear in targets where they do not belong - -Check `outputScopes` before you edit the source files themselves. +- Remove `outputScopes` from the global config. +- Check the assembled plugins in [plugin.config.ts](/docs/cli/plugin-config). +- Check the source item scope you authored. +- Use `tnmsc dry-run` to verify the real write targets before a full sync. diff --git a/doc/content/cli/plugin-config.mdx b/doc/content/cli/plugin-config.mdx index 545ea95a..da299843 100644 --- a/doc/content/cli/plugin-config.mdx +++ b/doc/content/cli/plugin-config.mdx @@ -17,9 +17,11 @@ status: stable into `defineConfig()`, which produces the `PipelineConfig` used for real execution. +This file defines the assembled integration surface. It does not, by itself, mean every assembled plugin will emit outputs. + ## Current Default Output Plugins -The default output plugins currently assembled in `sdk/src/plugin.config.ts` include: +The default output plugins currently assembled in `cli/src/plugin.config.ts` include: - `AgentsOutputPlugin` - `ClaudeCodeCLIOutputPlugin` @@ -36,18 +38,46 @@ The default output plugins currently assembled in `sdk/src/plugin.config.ts` inc - `CursorOutputPlugin` - `GitExcludeOutputPlugin` - `JetBrainsIDECodeStyleConfigOutputPlugin` -- `EditorConfigOutputPlugin` - `VisualStudioCodeIDEConfigOutputPlugin` -- `ReadmeMdConfigFileOutputPlugin` +- `ReadmeMdConfigFileOutputPlugin` (also emits `.editorconfig`) + +## Assembly vs Enablement + +`plugin.config.ts` answers: + +- which plugin implementations are assembled into the runtime +- which targets the project knows how to generate + +`~/.aindex/.tnmsc.json` answers: + +- which of those assembled plugins are currently allowed to emit outputs + +The current built-in default behavior is: + +- `plugins.git` is enabled by default +- `plugins.readme` is enabled by default and also covers `.editorconfig` output +- the other assembled output plugins stay off until the user explicitly enables them in `~/.aindex/.tnmsc.json` + +Example: + +```json +{ + "plugins": { + "git": true, + "claudeCode": true, + "trae": true + } +} +``` ## What That Means -This configuration is not illustrative documentation. It is the current default sync reality. If a plugin appears here, the default execution path considers that target. +This configuration is not illustrative documentation. It is the current assembled runtime surface. If a plugin appears here, the runtime knows how to target it. Whether it actually writes files depends on the user config enablement rules described above. ## Minimal Mental Model You can think of `plugin.config.ts` as the file that: -- chooses which targets receive outputs +- chooses which targets are assembled into the runtime - decides whether to append programmatic config at the project level - keeps project config merged with global user config rather than bypassing it entirely diff --git a/doc/content/cli/schema.mdx b/doc/content/cli/schema.mdx index 5f1ebd72..d0079bef 100644 --- a/doc/content/cli/schema.mdx +++ b/doc/content/cli/schema.mdx @@ -1,6 +1,6 @@ --- title: JSON Schema -description: Summarizes the configuration fields and constraints currently exposed by tnmsc.schema.json. +description: Summarizes the stable `.tnmsc.json` config surface and the compatibility notes you should keep in mind when reading tnmsc.schema.json. sidebarTitle: JSON Schema status: stable --- @@ -9,67 +9,24 @@ status: stable The `.aindex` and `.tnmsc.json` setup details are now centralized in [aindex and `.tnmsc.json`](/docs/quick-guide/aindex-and-config). -This page keeps the schema-level behavior that is not specific to the config file location or aindex path layout. +The generated `tnmsc.schema.json` is currently more permissive than the small stable runtime config surface. For authoring new `~/.aindex/.tnmsc.json` files, use the stable fields below and treat older compatibility keys as legacy. -## `commandSeriesOptions` - -```json -{ - "commandSeriesOptions": { - "includeSeriesPrefix": true, - "pluginOverrides": { - "some-plugin-name": { - "includeSeriesPrefix": false, - "seriesSeparator": "/" - } - } - } -} -``` +## Stable runtime fields | Field | Type | Meaning | | --- | --- | --- | -| `commandSeriesOptions.includeSeriesPrefix` | `boolean` | Whether command output names include the series prefix | -| `commandSeriesOptions.pluginOverrides` | `Record` | Per-plugin overrides | -| `commandSeriesOptions.pluginOverrides..includeSeriesPrefix` | `boolean` | Override the top-level prefix behavior for one plugin | -| `commandSeriesOptions.pluginOverrides..seriesSeparator` | `string` | Override the separator used by one plugin | - -## Topics Supported by `outputScopes` - -The schema currently allows these topics: - -- `prompt` -- `rules` -- `commands` -- `subagents` -- `skills` -- `mcp` - -Each topic accepts either a single scope or an array of scopes: - -```json -{ - "outputScopes": { - "plugins": { - "some-plugin-name": { - "prompt": "project", - "skills": ["global", "project"] - } - } - } -} -``` - -The only valid scope values are: - -- `project` -- `global` - -See [Output Scopes](/docs/cli/output-scopes) for detailed behavior. +| `version` | `string` | version marker passed through the runtime | +| `workspaceDir` | `string` | workspace root | +| `logLevel` | enum | `trace` / `debug` / `info` / `warn` / `error` | +| `frontMatter` | `object` | front matter output-formatting options | +| `codeStyles` | `object` | lightweight user code style preferences | +| `windows` | `object` | Windows and WSL integration options | +| `profile` | `object` | open user-profile object | +| `plugins` | `object` | explicit per-plugin output enablement overrides | ## `frontMatter` -The only public field right now is: +The `frontMatter` block currently exposes one field: ```json { @@ -81,31 +38,25 @@ The only public field right now is: See [Front Matter](/docs/cli/frontmatter) for the distinction. -## `cleanupProtection` +## `codeStyles` ```json { - "cleanupProtection": { - "rules": [ - { - "path": ".codex/skills/.system", - "protectionMode": "recursive", - "matcher": "path", - "reason": "Preserve built-in skills" - } - ] + "codeStyles": { + "indent": "space", + "tabSize": 2 } } ``` -Each rule supports: +| Field | Type | Meaning | +| --- | --- | --- | +| `codeStyles.indent` | `"tab" \| "space"` | preferred indentation style | +| `codeStyles.tabSize` | `integer` | preferred indentation width | -- `path` -- `protectionMode`: `direct` / `recursive` -- `matcher`: `path` / `glob` -- `reason` +Additional keys are currently accepted so the block can hold a few more simple personal style preferences without forcing a schema change for every tiny addition. -See [Cleanup Protection](/docs/cli/cleanup-protection) for the semantics. +The block itself is optional. When it is omitted, the merged runtime defaults still resolve to `indent = "space"` and `tabSize = 2`. ## `windows` @@ -133,3 +84,65 @@ See [Cleanup Protection](/docs/cli/cleanup-protection) for the semantics. - `birthday` Additional keys are also accepted. + +## `plugins` + +The current TypeScript schema also recognizes: + +```json +{ + "plugins": { + "git": true, + "codex": false, + "trae": true + } +} +``` + +Each plugin key accepts only: + +- `true` +- `false` + +Known keys are: + +- `agentsMd` +- `claudeCode` +- `codex` +- `cursor` +- `droid` +- `gemini` +- `git` +- `jetbrains` +- `jetbrainsCodeStyle` +- `kiro` +- `opencode` +- `qoder` +- `readme` +- `trae` +- `traeCn` +- `vscode` +- `warp` +- `windsurf` +- `zed` + +When `plugins` is omitted, the current built-in default keeps `git` and `readme` enabled. The other known plugin keys stay off until you explicitly set them to `true`. + +Use `plugins.readme` for both README-like outputs and `.editorconfig`. + +Use `plugins.git`, not `plugins.gitExclude`. + +This block is the direct output gate. A plugin may still appear in [plugin.config.ts](/docs/cli/plugin-config) as an assembled integration target, but it will not emit files unless its enablement resolves to `true`. + +## Legacy compatibility keys + +Do not treat these as part of the supported user-facing config surface anymore: + +- `agents` +- `commandSeriesOptions` +- `outputScopes` +- `cleanupProtection` +- `aindex`, `dir`, and the old `*.src` / `*.dist` path overrides +- Older migration-era fields such as `fastCommandSeriesOptions`, `externalProjects`, `excludePatterns`, and `shadowSourceProject` + +If you still see those keys in older examples or transitional schema output, treat them as compatibility leftovers and remove them from new configs. diff --git a/doc/content/cli/supported-outputs.mdx b/doc/content/cli/supported-outputs.mdx index 0351883a..90865156 100644 --- a/doc/content/cli/supported-outputs.mdx +++ b/doc/content/cli/supported-outputs.mdx @@ -1,13 +1,13 @@ --- title: Supported Outputs -description: Summarizes the output targets currently integrated by the default plugin.config.ts. +description: Summarizes the output targets currently integrated by the assembled plugin surface, and clarifies that support is not the same thing as default enablement. sidebarTitle: Supported Outputs status: stable --- # Supported Outputs -The current default plugin configuration shows that the project is integrated with at least these output targets: +The current assembled plugin surface shows that the project is integrated with at least these output targets: ## AI / IDE / CLI Targets @@ -29,11 +29,27 @@ The current default plugin configuration shows that the project is integrated wi - Generic Skills export - `.git/info/exclude` -- `.editorconfig` - VS Code config - JetBrains code style config -- README-like outputs +- README-like outputs (including `.editorconfig`) + +## Current Built-in Default + +Supported does not mean enabled by default. + +The current built-in default behavior is: + +- `plugins.git` is enabled +- `plugins.readme` is enabled +- the other supported outputs remain opt-in until `~/.aindex/.tnmsc.json` explicitly enables them + +So this page is a capability list, not a list of outputs that always appear on disk. ## One Important Thing to Remember -The "Supported Tools" table in the README is a high-level user-facing description. The thing that actually controls default sync behavior is which plugins are assembled in [plugin.config.ts](/docs/cli/plugin-config). If the two disagree, trust the latter. +The "Supported Tools" table in the README is a high-level user-facing description. The real behavior comes from two layers together: + +- which plugins are assembled in [plugin.config.ts](/docs/cli/plugin-config) +- which plugin keys are enabled in `~/.aindex/.tnmsc.json` + +If a target is assembled but disabled in user config, treat it as supported but currently inactive. diff --git a/doc/content/cli/troubleshooting.mdx b/doc/content/cli/troubleshooting.mdx index 6c824574..923638db 100644 --- a/doc/content/cli/troubleshooting.mdx +++ b/doc/content/cli/troubleshooting.mdx @@ -1,6 +1,6 @@ --- title: Troubleshooting -description: Collects the version, command, and output-scope problems you are most likely to run into right now. +description: Collects the version, command, and output-placement problems you are most likely to run into right now. sidebarTitle: Troubleshooting status: stable --- @@ -16,12 +16,12 @@ The docs have moved from the old mixed grouping into seven top-level sections. S Check these first: 1. Which output plugins are actually assembled in [plugin.config.ts](/docs/cli/plugin-config). -2. Whether [Output Scopes](/docs/cli/output-scopes) limits the topic correctly. -3. Whether you authored a global prompt or a workspace prompt. +2. Whether you authored a global prompt or a workspace prompt. +3. Whether the target plugin has its own routing behavior for that content type. ## Symptom: `clean` Feels Unsafe or Deleted Too Much -Review [Cleanup Protection](/docs/cli/cleanup-protection) first. If your directories mix manual files with generated files and you do not configure protection rules, the risk of `clean` rises quickly. +Review [dry-run and clean](/docs/cli/dry-run-and-clean) first. If your directories mix manual files with generated files, move the manual files out of tnmsc-managed output paths before a real clean. ## Symptom: A Docs Page Fails to Build diff --git a/doc/content/cli/workspace-setup.mdx b/doc/content/cli/workspace-setup.mdx index 9fce915b..fc278531 100644 --- a/doc/content/cli/workspace-setup.mdx +++ b/doc/content/cli/workspace-setup.mdx @@ -35,7 +35,8 @@ Their responsibility boundaries are explained in [Technical Details](/docs/techn ## Project Configuration Entrypoint -The project-level assembly entrypoint is [plugin.config.ts](/docs/cli/plugin-config). It decides which output plugins are assembled by default and how project-level runtime options are merged into the real execution config. +The project-level assembly entrypoint is [plugin.config.ts](/docs/cli/plugin-config). It decides which output plugins are assembled and how project-level runtime options are merged into the real execution config. Per-user output enablement then comes from `~/.aindex/.tnmsc.json`. +`plugins.readme` covers both the README-family outputs and `.editorconfig`. ## Next Step diff --git a/doc/content/quick-guide/aindex-and-config.mdx b/doc/content/quick-guide/aindex-and-config.mdx index 22dacfee..a555b626 100644 --- a/doc/content/quick-guide/aindex-and-config.mdx +++ b/doc/content/quick-guide/aindex-and-config.mdx @@ -1,6 +1,6 @@ --- title: aindex and .tnmsc.json -description: Explains in one place where `.aindex` lives, how `~/.aindex/.tnmsc.json` is loaded, and what every supported config field means. +description: Explains where `.aindex` lives, how `~/.aindex/.tnmsc.json` is loaded, which fields are active today, and which legacy fields should be removed. sidebarTitle: aindex and Config status: stable --- @@ -64,6 +64,7 @@ The separation is intentional: - `~/.aindex/.tnmsc.json` stores user or machine-level config data - `plugin.config.ts` stores project-side plugin assembly and programmatic overrides +- `~/.aindex/.tnmsc.json` also decides which assembled output plugins are actually allowed to write ## Minimal example @@ -81,19 +82,20 @@ With that config: - the default global prompt source resolves to `/repo/demo/aindex/global.src.mdx` - `logLevel` falls back to `info` unless you set it explicitly -## Root fields in `~/.aindex/.tnmsc.json` +## Stable top-level fields in `~/.aindex/.tnmsc.json` + +These are the user-facing fields the current runtime actually consumes from the global config file: | Field | Type | Default | Meaning | | --- | --- | --- | --- | | `version` | `string` | `"0.0.0"` in merged runtime options | Version or release marker passed through the runtime | -| `workspaceDir` | `string` | `"~/project"` in merged runtime options | Project root directory | +| `workspaceDir` | `string` | omitted in config; resolved from the runtime working directory when not supplied | Workspace root directory, typically under `~/workspace/` | | `logLevel` | enum | `info` | `trace` / `debug` / `info` / `warn` / `error` | -| `commandSeriesOptions` | `object` | `{}` | Command-series naming and per-plugin overrides | -| `outputScopes` | `object` | `{}` | Output-scope overrides per plugin | | `frontMatter` | `object` | `{ "blankLineAfter": true }` | Front matter formatting behavior | -| `cleanupProtection` | `object` | `{}` | Cleanup protection rules | +| `codeStyles` | `object` | `{ "indent": "space", "tabSize": 2 }` in merged runtime options | User code style preferences exposed to prompt evaluation | | `windows` | `object` | `{}` | Windows and WSL-specific runtime options | | `profile` | `object` | omitted | User profile metadata | +| `plugins` | `object` | `{}` in merged runtime options | Explicit per-plugin output enablement overrides | Everything under the workspace aindex tree keeps a fixed name and fixed relative location. The structure is: @@ -125,73 +127,39 @@ Everything under the workspace aindex tree keeps a fixed name and fixed relative None of the fixed paths inside that tree are user-configurable in `~/.aindex/.tnmsc.json`. -If old config files still contain `aindex`, `dir`, or `*.src` / `*.dist` override fields, treat them as removed legacy fields. The current system no longer documents or supports those user-facing overrides. - -## Other supported config objects - -### `commandSeriesOptions` +### `frontMatter` ```json { - "commandSeriesOptions": { - "includeSeriesPrefix": true, - "pluginOverrides": { - "some-plugin-name": { - "includeSeriesPrefix": false, - "seriesSeparator": "/" - } - } + "frontMatter": { + "blankLineAfter": true } } ``` -| Field | Type | Meaning | -| --- | --- | --- | -| `commandSeriesOptions.includeSeriesPrefix` | `boolean` | Whether command output names include the series prefix | -| `commandSeriesOptions.pluginOverrides` | `Record` | Per-plugin overrides | -| `commandSeriesOptions.pluginOverrides..includeSeriesPrefix` | `boolean` | Override the top-level prefix behavior for one plugin | -| `commandSeriesOptions.pluginOverrides..seriesSeparator` | `string` | Override the separator used by one plugin | - -### `outputScopes` - -The schema currently allows these topics: - -- `prompt` -- `rules` -- `commands` -- `subagents` -- `skills` -- `mcp` - -Each topic accepts either a single scope or an array of scopes. The only valid scope values are: - -- `project` -- `global` - -See [Output Scopes](/docs/cli/output-scopes) for behavior details. +See [Front Matter](/docs/cli/frontmatter) for the formatting distinction. -### `frontMatter` +### `codeStyles` ```json { - "frontMatter": { - "blankLineAfter": true + "codeStyles": { + "indent": "space", + "tabSize": 2 } } ``` -See [Front Matter](/docs/cli/frontmatter) for the formatting distinction. - -### `cleanupProtection` +| Field | Type | Meaning | +| --- | --- | --- | +| `codeStyles.indent` | `"tab" \| "space"` | Preferred indentation style | +| `codeStyles.tabSize` | `integer` | Preferred indentation width | -Each rule supports: +Additional keys are also accepted so you can keep a few lightweight personal style preferences together in one place. -- `path` -- `protectionMode`: `direct` / `recursive` -- `matcher`: `path` / `glob` -- `reason` +The block itself is optional. When you omit it, the runtime still injects `codeStyles.indent = "space"` and `codeStyles.tabSize = 2`. -See [Cleanup Protection](/docs/cli/cleanup-protection) for cleanup semantics. +When prompts are compiled, these values are available in MDX expressions such as `{codeStyles.indent}` and `{codeStyles.tabSize}`. ### `windows` @@ -220,6 +188,72 @@ See [Cleanup Protection](/docs/cli/cleanup-protection) for cleanup semantics. Additional keys are also accepted. +## `plugins` + +The current runtime also recognizes a `plugins` block: + +```json +{ + "plugins": { + "git": true, + "claudeCode": false, + "trae": true + } +} +``` + +Each key accepts only a boolean: + +- `true`: allow that assembled plugin to emit outputs +- `false`: suppress output generation for that plugin, while still keeping cleanup behavior + +Known keys are: + +- `agentsMd` +- `claudeCode` +- `codex` +- `cursor` +- `droid` +- `gemini` +- `git` +- `jetbrains` +- `jetbrainsCodeStyle` +- `kiro` +- `opencode` +- `qoder` +- `readme` +- `trae` +- `traeCn` +- `vscode` +- `warp` +- `windsurf` +- `zed` + +Current built-in default behavior is: + +- `git` is enabled by default +- `readme` is enabled by default +- the other known plugin keys are disabled by default until you explicitly turn them on + +Use `plugins.readme` for both README-like outputs and `.editorconfig`. + +Use `plugins.git`, not `plugins.gitExclude`. + +This block controls actual output emission. The project-level [plugin.config.ts](/docs/cli/plugin-config) still assembles the available plugin set, but assembly alone does not mean the plugin will write files. + +## Removed legacy fields + +Do not put these back into new `~/.aindex/.tnmsc.json` files: + +- `agents` +- `aindex`, `dir`, and the old `*.src` / `*.dist` path-pair overrides such as `skills`, `commands`, `subAgents`, `rules`, `globalPrompt`, `workspacePrompt`, `app`, `ext`, `arch`, and `softwares` +- `commandSeriesOptions` +- `outputScopes` +- `cleanupProtection` +- Older experimental or pre-consolidation fields such as `fastCommandSeriesOptions`, `externalProjects`, `excludePatterns`, and `shadowSourceProject` + +Some compatibility layers may still parse or warn on those keys during migration, but they are no longer part of the supported user-facing config surface. + ## What to read next - If you want the command workflow, continue with [First Sync](/docs/cli/first-sync) diff --git a/doc/content/technical-details/commands.mdx b/doc/content/technical-details/commands.mdx index 9a2ed250..257e36c1 100644 --- a/doc/content/technical-details/commands.mdx +++ b/doc/content/technical-details/commands.mdx @@ -1,6 +1,6 @@ --- title: Commands -description: Introduces the role of commands and explains how they relate to commandSeriesOptions. +description: Introduces the role of commands and explains the current path-derived naming model. sidebarTitle: Commands status: stable --- @@ -33,11 +33,13 @@ Default output path: dist/commands/ ``` -## What `commandSeriesOptions` Does +## How Naming Works Now -The schema lets `commandSeriesOptions`: +Command naming now comes primarily from the source path and file name rather than a global config block. -- set `includeSeriesPrefix` -- override `includeSeriesPrefix` or `seriesSeparator` per plugin +Examples: -That means commands are not just flat output files. They can also carry series-based naming rules. Think of it as grouping and naming control for a command family rather than a single-file naming rule. +- `commands/git/status.src.mdx` keeps a path-derived grouping +- `commands/release_notes.src.mdx` can still derive a prefix from the flat file name + +The old `commandSeriesOptions` block in `~/.aindex/.tnmsc.json` is now legacy. New command configuration should start from the source tree shape and the output plugin behavior instead of trying to restore that removed global override layer. diff --git a/doc/content/technical-details/pipeline.mdx b/doc/content/technical-details/pipeline.mdx index 1ede1636..7282d083 100644 --- a/doc/content/technical-details/pipeline.mdx +++ b/doc/content/technical-details/pipeline.mdx @@ -1,6 +1,6 @@ --- title: Sync Pipeline -description: Explains how plugins declare target files, generation flow, output scopes, and cleanup behavior. +description: Explains how plugins declare target files, generation flow, scope selection, and cleanup behavior. sidebarTitle: Sync Pipeline status: stable --- @@ -14,7 +14,7 @@ Sync is not "copy a few prompt files into a few directories." It is a pipeline w - Read source-of-truth input assets - Merge project and global config - Decide output targets per plugin -- Apply `outputScopes` +- Resolve project or global scope from source metadata and plugin behavior - Write target files - Run cleanup when needed @@ -29,5 +29,5 @@ Without explicit modeling: ## Related Pages - Config-field behavior: see [CLI / JSON Schema](/docs/cli/schema) -- Output-scope control: see [CLI / Output Scopes](/docs/cli/output-scopes) +- Global config and fixed path layout: see [Quick Guide / aindex and `.tnmsc.json`](/docs/quick-guide/aindex-and-config) - Source-versus-derived relationship: see [Single Source of Truth](/docs/technical-details/source-of-truth) diff --git a/gui/package.json b/gui/package.json index d6f965af..743787fa 100644 --- a/gui/package.json +++ b/gui/package.json @@ -3,9 +3,7 @@ "version": "2026.10404.101", "private": true, "engines": { - "node": ">=25.2.1", - "pnpm": ">=10.28.0", - "rust": ">=1.93.1" + "node": ">= 22" }, "type": "module", "scripts": { diff --git a/gui/src/pages/ConfigPage.tsx b/gui/src/pages/ConfigPage.tsx index a83a09e9..b6d5be5c 100644 --- a/gui/src/pages/ConfigPage.tsx +++ b/gui/src/pages/ConfigPage.tsx @@ -94,7 +94,7 @@ const ConfigForm: FC = ({ data, onChange, t }) => { description={t(`config.field.${field}.desc`)} value={(data[field] as string) ?? ''} onChange={(v) => updateField(field, v)} - placeholder="~/project" + placeholder="~/workspace/" /> ))} diff --git a/gui/src/utils/configValidation.property.test.ts b/gui/src/utils/configValidation.property.test.ts index 410ad7d8..0a3ca70f 100644 --- a/gui/src/utils/configValidation.property.test.ts +++ b/gui/src/utils/configValidation.property.test.ts @@ -24,7 +24,7 @@ const STRING_FIELDS = [ 'version', ] as const -const OBJECT_FIELDS = ['profile', 'commandSeriesOptions', 'outputScopes'] as const +const NESTED_OBJECT_FIELDS = ['profile', 'commandSeriesOptions', 'outputScopes', 'codeStyles', 'plugins'] as const // ── Arbitraries ──────────────────────────────────────────────────────── @@ -175,14 +175,14 @@ describe('Property 3: 无效配置产生错误', () => { /** * **Validates: Requirements 3.4** * - * For any object field (profile, commandSeriesOptions, outputScopes) + * For any object field (profile, commandSeriesOptions, outputScopes, codeStyles, plugins) * set to a non-object value, validateConfig should return at least * one error for that field. */ it('non-object values for object fields produce errors', () => { fc.assert( fc.property( - fc.constantFrom(...OBJECT_FIELDS), + fc.constantFrom(...NESTED_OBJECT_FIELDS), arbNonObject, (field, badValue) => { const config = { [field]: badValue } diff --git a/gui/src/utils/configValidation.test.ts b/gui/src/utils/configValidation.test.ts index f989169c..587d29f0 100644 --- a/gui/src/utils/configValidation.test.ts +++ b/gui/src/utils/configValidation.test.ts @@ -129,6 +129,32 @@ describe('validateConfig — profile', () => { }) }) +// ─── codeStyles ──────────────────────────────────────────────────────── +describe('validateConfig — codeStyles', () => { + it('accepts a valid codeStyles object', () => { + expect(validateConfig({ codeStyles: { indent: 'space', tabSize: 2 } })).toHaveLength(0) + }) + + it('accepts extra keys inside codeStyles', () => { + expect(validateConfig({ codeStyles: { indent: 'tab', quoteStyle: 'single' } })).toHaveLength(0) + }) + + it('rejects non-object', () => { + const errors = validateConfig({ codeStyles: 'bad' }) + expect(errorFields(errors)).toContain('codeStyles') + }) + + it('rejects invalid indent', () => { + const errors = validateConfig({ codeStyles: { indent: 'mixed' } }) + expect(errorFields(errors)).toContain('codeStyles.indent') + }) + + it('rejects non-positive tabSize', () => { + const errors = validateConfig({ codeStyles: { tabSize: 0 } }) + expect(errorFields(errors)).toContain('codeStyles.tabSize') + }) +}) + // ─── commandSeriesOptions ─────────────────────────────────────────────── describe('validateConfig — commandSeriesOptions', () => { it('accepts a plain object', () => { @@ -173,6 +199,23 @@ describe('validateConfig — outputScopes', () => { }) }) +// ─── plugins ──────────────────────────────────────────────────────────── +describe('validateConfig — plugins', () => { + it('accepts a plain object with boolean plugin flags', () => { + expect(validateConfig({ plugins: { trae: true, claudeCode: false } })).toHaveLength(0) + }) + + it('rejects non-object', () => { + const errors = validateConfig({ plugins: 42 }) + expect(errorFields(errors)).toContain('plugins') + }) + + it('rejects non-boolean plugin flags', () => { + const errors = validateConfig({ plugins: { trae: 'yes' } }) + expect(errorFields(errors)).toContain('plugins.trae') + }) +}) + // ─── unknown fields → warnings ───────────────────────────────────────── describe('validateConfig — unknown fields', () => { it('produces a warning for unknown top-level keys', () => { @@ -181,6 +224,12 @@ describe('validateConfig — unknown fields', () => { expect(errors[0].severity).toBe('warning') }) + it('treats removed agents config as an unknown-field warning', () => { + const errors = validateConfig({ agents: { codex: true } }) + expect(errorFields(errors)).toEqual([]) + expect(warningFields(errors)).toContain('agents') + }) + it('produces warnings for multiple unknown keys', () => { const errors = validateConfig({ foo: 1, bar: 2 }) expect(warningFields(errors)).toEqual(expect.arrayContaining(['foo', 'bar'])) @@ -225,8 +274,10 @@ describe('validateConfig — realistic configs', () => { workspaceDir: '/workspace', logLevel: 'debug', profile: { name: 'test' }, + codeStyles: { indent: 'space', tabSize: 2 }, commandSeriesOptions: { includeSeriesPrefix: true }, outputScopes: { plugins: {} }, + plugins: { trae: true }, } expect(validateConfig(config)).toHaveLength(0) }) diff --git a/gui/src/utils/configValidation.ts b/gui/src/utils/configValidation.ts index 38945f97..853597b0 100644 --- a/gui/src/utils/configValidation.ts +++ b/gui/src/utils/configValidation.ts @@ -29,9 +29,11 @@ const KNOWN_FIELDS: ReadonlySet = new Set([ 'commandSeriesOptions', 'outputScopes', 'frontMatter', + 'codeStyles', 'cleanupProtection', 'windows', 'profile', + 'plugins', ]) const VALID_LOG_LEVELS: ReadonlySet = new Set([ @@ -42,6 +44,31 @@ const VALID_LOG_LEVELS: ReadonlySet = new Set([ 'error', ]) +const VALID_INDENT_STYLES: ReadonlySet = new Set([ + 'tab', + 'space', +]) + +function isPlainObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +function validateObjectField( + obj: Record, + field: string, + errors: ValidationError[] +): Record | undefined { + if (!(field in obj)) return void 0 + + const value = obj[field] + if (!isPlainObject(value)) { + errors.push({ field, message: `${field} must be an object`, severity: 'error' }) + return void 0 + } + + return value +} + /** * Validate a raw config object and return all validation issues. * @@ -58,12 +85,12 @@ export function validateConfig(raw: unknown): readonly ValidationError[] { return errors } - if (typeof raw !== 'object' || Array.isArray(raw)) { + if (!isPlainObject(raw)) { errors.push({ field: '', message: 'Config must be a plain object', severity: 'error' }) return errors } - const obj = raw as Record + const obj = raw // ── Unknown / extra fields → warnings ──────────────────────────────── for (const key of Object.keys(obj)) { @@ -99,34 +126,54 @@ export function validateConfig(raw: unknown): readonly ValidationError[] { } // ── profile ────────────────────────────────────────────────────────── - if ('profile' in obj) { - const v = obj['profile'] - if (typeof v !== 'object' || v === null || Array.isArray(v)) { - errors.push({ field: 'profile', message: 'profile must be an object', severity: 'error' }) + validateObjectField(obj, 'profile', errors) + + // ── commandSeriesOptions / outputScopes / frontMatter / cleanupProtection / windows / plugins ── + validateObjectField(obj, 'commandSeriesOptions', errors) + validateObjectField(obj, 'outputScopes', errors) + validateObjectField(obj, 'frontMatter', errors) + validateObjectField(obj, 'cleanupProtection', errors) + validateObjectField(obj, 'windows', errors) + const plugins = validateObjectField(obj, 'plugins', errors) + if (plugins != null) { + for (const [pluginName, enabled] of Object.entries(plugins)) { + if (typeof enabled !== 'boolean') { + errors.push({ + field: `plugins.${pluginName}`, + message: `plugins.${pluginName} must be a boolean`, + severity: 'error', + }) + } } } - // ── commandSeriesOptions ───────────────────────────────────────────── - if ('commandSeriesOptions' in obj) { - const v = obj['commandSeriesOptions'] - if (typeof v !== 'object' || v === null || Array.isArray(v)) { - errors.push({ - field: 'commandSeriesOptions', - message: 'commandSeriesOptions must be an object', - severity: 'error', - }) + // ── codeStyles ─────────────────────────────────────────────────────── + const codeStyles = validateObjectField(obj, 'codeStyles', errors) + if (codeStyles != null) { + if ('indent' in codeStyles) { + const indent = codeStyles['indent'] + if (typeof indent !== 'string' || !VALID_INDENT_STYLES.has(indent)) { + errors.push({ + field: 'codeStyles.indent', + message: `codeStyles.indent must be one of: ${[...VALID_INDENT_STYLES].join(', ')}`, + severity: 'error', + }) + } } - } - // ── outputScopes ───────────────────────────────────────────────────── - if ('outputScopes' in obj) { - const v = obj['outputScopes'] - if (typeof v !== 'object' || v === null || Array.isArray(v)) { - errors.push({ - field: 'outputScopes', - message: 'outputScopes must be an object', - severity: 'error', - }) + if ('tabSize' in codeStyles) { + const tabSize = codeStyles['tabSize'] + if ( + typeof tabSize !== 'number' + || !Number.isInteger(tabSize) + || tabSize <= 0 + ) { + errors.push({ + field: 'codeStyles.tabSize', + message: 'codeStyles.tabSize must be a positive integer', + severity: 'error', + }) + } } } diff --git a/libraries/md-compiler/src/compiler/index.ts b/libraries/md-compiler/src/compiler/index.ts index a7c1c0df..939fdeb2 100644 --- a/libraries/md-compiler/src/compiler/index.ts +++ b/libraries/md-compiler/src/compiler/index.ts @@ -52,6 +52,7 @@ export type { } from './types' export type { + CodeStylePreferences, EnvironmentContext, MdxGlobalScope, ToolReferences, diff --git a/libraries/md-compiler/src/compiler/mdx-to-md.test.ts b/libraries/md-compiler/src/compiler/mdx-to-md.test.ts index 27417d45..1d877975 100644 --- a/libraries/md-compiler/src/compiler/mdx-to-md.test.ts +++ b/libraries/md-compiler/src/compiler/mdx-to-md.test.ts @@ -133,6 +133,7 @@ never leave placeholders or "{TODO}" markers` const result = await mdxToMd(input, { globalScope: { profile: {name: 'TestUser'}, + codeStyles: {}, tool: {}, env: {}, os: {} @@ -146,6 +147,7 @@ never leave placeholders or "{TODO}" markers` const result = await mdxToMd(input, { globalScope: { profile: {}, + codeStyles: {}, tool: {websearch: 'websearch'}, env: {}, os: {} @@ -159,6 +161,7 @@ never leave placeholders or "{TODO}" markers` const result = await mdxToMd(input, { globalScope: { profile: {name: 'GlobalName'}, + codeStyles: {}, tool: {}, env: {}, os: {} @@ -167,6 +170,20 @@ never leave placeholders or "{TODO}" markers` }) expect(result).toContain('Name: OverriddenName') }) + + it('should have access to globalScope.codeStyles', async () => { + const input = 'Indent: {codeStyles.indent}, Width: {codeStyles.tabSize}' + const result = await mdxToMd(input, { + globalScope: { + profile: {}, + codeStyles: {indent: 'space', tabSize: 2}, + tool: {}, + env: {}, + os: {} + } + }) + expect(result).toContain('Indent: space, Width: 2') + }) }) describe('built-in Md component (Requirement 3)', () => { diff --git a/libraries/md-compiler/src/compiler/mdx-to-md.ts b/libraries/md-compiler/src/compiler/mdx-to-md.ts index 863c586b..fe678e47 100644 --- a/libraries/md-compiler/src/compiler/mdx-to-md.ts +++ b/libraries/md-compiler/src/compiler/mdx-to-md.ts @@ -1,4 +1,4 @@ -import type {YAML} from 'mdast' // Main entry point for lossless MDX to Markdown conversion // mdx-to-md.ts +import type {Yaml} from 'mdast' // Main entry point for lossless MDX to Markdown conversion // mdx-to-md.ts import type {EvaluationScope, MdxjsEsm, MdxToMdOptions, MdxToMdResult, ProcessingContext} from './types' import remarkFrontmatter from 'remark-frontmatter' import remarkGfm from 'remark-gfm' @@ -18,7 +18,7 @@ registerBuiltInComponents() // Register built-in components on module load * Custom scope values take precedence over global scope values. * Objects are deeply merged, primitives are overwritten. * - * @param globalScope - Global scope containing os, env, profile, tool + * @param globalScope - Global scope containing os, env, profile, codeStyles, tool * @param customScope - Custom scope values to merge * @returns Merged evaluation scope */ @@ -32,6 +32,7 @@ function mergeScopes( result['os'] = {...globalScope.os} result['env'] = {...globalScope.env} result['profile'] = {...globalScope.profile} + result['codeStyles'] = {...globalScope.codeStyles} result['tool'] = {...globalScope.tool} } @@ -77,7 +78,7 @@ export async function mdxToMd( let metadata: MdxToMdResult['metadata'] | undefined // Extract metadata if requested (YAML frontmatter + ESM exports merged) if (options?.extractMetadata === true) { - const yamlNode = ast.children.find((n): n is YAML => n.type === 'yaml') // 1. Extract YAML frontmatter + const yamlNode = ast.children.find((n): n is Yaml => n.type === 'yaml') // 1. Extract YAML frontmatter let yamlFrontMatter: Record | undefined if (yamlNode != null) { try { diff --git a/libraries/md-compiler/src/globals/index.ts b/libraries/md-compiler/src/globals/index.ts index 95c19cbc..2c6b6d68 100644 --- a/libraries/md-compiler/src/globals/index.ts +++ b/libraries/md-compiler/src/globals/index.ts @@ -11,6 +11,16 @@ export interface UserProfile { birthday?: string } +/** + * User code style preferences + * @example {codeStyles.indent}, {codeStyles.tabSize} + */ +export interface CodeStylePreferences { + [key: string]: unknown + indent?: 'tab' | 'space' + tabSize?: number +} + /** * Tool references for AI assistants * @example {tool.websearch}, {tool.webfetch}, {tool.readFile} @@ -147,6 +157,8 @@ export interface MdComponent { export interface MdxGlobalScope { /** User profile information */ profile: UserProfile + /** User code style preferences */ + codeStyles: CodeStylePreferences /** Tool name references for AI assistants */ tool: ToolReferences /** Environment variables context */ @@ -160,6 +172,8 @@ export interface MdxGlobalScope { declare global { /** User profile information */ const profile: UserProfile, + /** User code style preferences */ + codeStyles: CodeStylePreferences, /** Tool name references for AI assistants */ tool: ToolReferences, /** Environment variables context */ diff --git a/libraries/md-compiler/src/markdown/index.ts b/libraries/md-compiler/src/markdown/index.ts index 8f407c0c..0e4b124c 100644 --- a/libraries/md-compiler/src/markdown/index.ts +++ b/libraries/md-compiler/src/markdown/index.ts @@ -5,7 +5,7 @@ import {dirname, join} from 'node:path' import process from 'node:process' import * as YAML from 'yaml' -import {parseMdx} from '../compiler/parser' // Napi binding types +import {parseMdx} from '@/compiler' // Napi binding types import {shouldSkipNativeBinding} from '../native-binding' interface NapiMdCompilerModule { @@ -27,7 +27,6 @@ const EXTERNAL_URL_PATTERN = /^(?:https?:)?\/\//u const URL_TRAILING_MDX_EXTENSION_PATTERN = /\.mdx$/u const URL_HASH_MDX_EXTENSION_PATTERN = /\.mdx#/u const URL_QUERY_MDX_EXTENSION_PATTERN = /\.mdx\?/u - let napiBinding: NapiMdCompilerModule | null = null function isNapiMdCompilerModule(value: unknown): value is NapiMdCompilerModule { @@ -179,9 +178,9 @@ export function buildMarkdownWithRawFrontMatter( } // doubleQuoted — TS only (YAML-specific helper) export function doubleQuoted(value: string): unknown { - const s = new YAML.Scalar(value) - s.type = YAML.Scalar.QUOTE_DOUBLE - return s + const scalar = new YAML.Scalar(value) + scalar.type = YAML.Scalar.QUOTE_DOUBLE + return scalar } // parseMarkdown export function parseMarkdown>(rawContent: string): ParsedMarkdown { @@ -213,17 +212,21 @@ export function parseMarkdown>(rawContent: string): export function transformMdxReferencesToMd(content: string): string { if (napiBinding != null) return napiBinding.transformMdxReferencesToMd(content) + return content.replaceAll( MDX_REFERENCE_PATTERN, (_match, prefix: string, text: string, middle: string, url: string, suffix: string) => { const transformedText = text .replace(TRAILING_MDX_EXTENSION_PATTERN, '.md') .replaceAll(LINK_TEXT_MDX_EXTENSION_PATTERN, '.md') + if (EXTERNAL_URL_PATTERN.test(url)) return `${prefix}${transformedText}${middle}${url}${suffix}` + const transformedUrl = url .replace(URL_TRAILING_MDX_EXTENSION_PATTERN, '.md') .replace(URL_HASH_MDX_EXTENSION_PATTERN, '.md#') .replace(URL_QUERY_MDX_EXTENSION_PATTERN, '.md?') + return `${prefix}${transformedText}${middle}${transformedUrl}${suffix}` } ) diff --git a/libraries/md-compiler/src/mdx-to-md.ts b/libraries/md-compiler/src/mdx-to-md.ts index 536f509d..b1f8f5fa 100644 --- a/libraries/md-compiler/src/mdx-to-md.ts +++ b/libraries/md-compiler/src/mdx-to-md.ts @@ -1,5 +1,4 @@ -import type {ExportMetadata, MetadataSource} from './compiler/export-parser' -import type {MdxToMdOptions, MdxToMdResult} from './compiler/types' +import type {ExportMetadata, MdxToMdOptions, MdxToMdResult, MetadataSource} from '@/compiler' import {readdirSync} from 'node:fs' import {createRequire} from 'node:module' import {dirname, join} from 'node:path' @@ -189,6 +188,7 @@ function serializeOptions(options?: MdxToMdOptions): string | null { os: options.globalScope.os, env: options.globalScope.env, profile: options.globalScope.profile, + codeStyles: options.globalScope.codeStyles, tool: options.globalScope.tool } } diff --git a/libraries/md-compiler/src/mdx_to_md.rs b/libraries/md-compiler/src/mdx_to_md.rs index 0275b9a6..93f3b1f7 100644 --- a/libraries/md-compiler/src/mdx_to_md.rs +++ b/libraries/md-compiler/src/mdx_to_md.rs @@ -12,13 +12,14 @@ use crate::parser::parse_mdx; use crate::serializer::serialize; use crate::transformer::{ProcessingContext, transform_ast}; -/// Global scope for MDX compilation (os, env, profile, tool info). +/// Global scope for MDX compilation (os, env, profile, code style, tool info). #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MdxGlobalScope { pub os: Option>, pub env: Option>, pub profile: Option>, + pub code_styles: Option>, pub tool: Option>, } @@ -90,6 +91,12 @@ fn merge_scopes( serde_json::to_value(profile).unwrap_or(Value::Null), ); } + if let Some(code_styles) = &gs.code_styles { + result.insert( + "codeStyles".into(), + serde_json::to_value(code_styles).unwrap_or(Value::Null), + ); + } if let Some(tool) = &gs.tool { result.insert( "tool".into(), @@ -394,6 +401,24 @@ mod tests { assert!(result.contains("OS: linux"), "Got: {}", result); } + #[test] + fn test_global_scope_code_styles() { + let opts = MdxToMdOptions { + global_scope: Some(MdxGlobalScope { + code_styles: Some({ + let mut m = HashMap::new(); + m.insert("indent".into(), json!("space")); + m.insert("tabSize".into(), json!(2)); + m + }), + ..Default::default() + }), + ..Default::default() + }; + let result = mdx_to_md("Indent: {codeStyles.indent}, width: {codeStyles.tabSize}\n", Some(opts)).unwrap(); + assert!(result.contains("Indent: space, width: 2"), "Got: {}", result); + } + #[test] fn test_scope_merge_priority() { let mut custom = EvaluationScope::new(); diff --git a/libraries/md-compiler/tsconfig.json b/libraries/md-compiler/tsconfig.json index abad44cd..8ea6064c 100644 --- a/libraries/md-compiler/tsconfig.json +++ b/libraries/md-compiler/tsconfig.json @@ -5,6 +5,7 @@ "incremental": true, "composite": false, // Projects "target": "ESNext", // Language and Environment + "jsx": "react", "lib": [ "ESNext" ], @@ -55,7 +56,7 @@ "forceConsistentCasingInFileNames": true, "isolatedModules": true, "verbatimModuleSyntax": true, - "skipLibCheck": true // Completeness + "skipLibCheck": true }, "include": [ "src/**/*", diff --git a/mcp/src/server.ts b/mcp/src/server.ts index 7e5d8cca..956894c9 100644 --- a/mcp/src/server.ts +++ b/mcp/src/server.ts @@ -33,12 +33,12 @@ const localeSchema = z.enum(['zh', 'en'] satisfies readonly PromptSourceLocale[] const workspaceInputSchema = { workspaceDir: z.string().min(1).optional() } -const MCP_PACKAGE_NAME = typeof __MCP_PACKAGE_NAME__ === 'string' +const MCP_PACKAGE_NAME = typeof __MCP_PACKAGE_NAME__ !== 'undefined' ? __MCP_PACKAGE_NAME__ : '@truenine/memory-sync-mcp' -const MCP_VERSION = typeof __MCP_VERSION__ === 'string' +const MCP_VERSION = typeof __MCP_VERSION__ !== 'undefined' ? __MCP_VERSION__ - : '0.0.0-dev' + : 'dev' function createPromptServiceOptions( workspaceDir?: string diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts index 8fb1fc69..773baf2b 100644 --- a/scripts/postinstall.ts +++ b/scripts/postinstall.ts @@ -21,7 +21,15 @@ const commands = [ ] as const for (const command of commands) { - execSync(command, { - stdio: 'inherit', - }) + try { + execSync(command, { + stdio: 'inherit', + }) + } catch (error) { + console.error(`[postinstall] Command failed: ${command}`) + if (error instanceof Error && 'status' in error) { + console.error(`[postinstall] Exit code: ${(error as {status: number}).status}`) + } + process.exit(1) + } } diff --git a/sdk/src/ConfigLoader.test.ts b/sdk/src/ConfigLoader.test.ts index 8541037f..c2a81ece 100644 --- a/sdk/src/ConfigLoader.test.ts +++ b/sdk/src/ConfigLoader.test.ts @@ -5,24 +5,24 @@ import {afterEach, describe, expect, it} from 'vitest' import {ConfigLoader, getGlobalConfigPath} from './ConfigLoader' describe('configLoader', () => { - const originalHome = process.env.HOME - const originalUserProfile = process.env.USERPROFILE - const originalHomeDrive = process.env.HOMEDRIVE - const originalHomePath = process.env.HOMEPATH + const originalHome = process.env['HOME'] + const originalUserProfile = process.env['USERPROFILE'] + const originalHomeDrive = process.env['HOMEDRIVE'] + const originalHomePath = process.env['HOMEPATH'] afterEach(() => { - process.env.HOME = originalHome - process.env.USERPROFILE = originalUserProfile - process.env.HOMEDRIVE = originalHomeDrive - process.env.HOMEPATH = originalHomePath + process.env['HOME'] = originalHome + process.env['USERPROFILE'] = originalUserProfile + process.env['HOMEDRIVE'] = originalHomeDrive + process.env['HOMEPATH'] = originalHomePath }) it('searches only the canonical global config path', () => { const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-home-')) - process.env.HOME = tempHome - process.env.USERPROFILE = tempHome - delete process.env.HOMEDRIVE - delete process.env.HOMEPATH + process.env['HOME'] = tempHome + process.env['USERPROFILE'] = tempHome + delete process.env['HOMEDRIVE'] + delete process.env['HOMEPATH'] try { const loader = new ConfigLoader() @@ -90,4 +90,91 @@ describe('configLoader', () => { fs.rmSync(tempDir, {recursive: true, force: true}) } }) + + it('loads codeStyles from the user config file', () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-config-loader-code-styles-')) + const configPath = path.join(tempDir, '.tnmsc.json') + + try { + fs.writeFileSync(configPath, JSON.stringify({ + workspaceDir: '/tmp/workspace', + codeStyles: { + indent: 'space', + tabSize: 2, + quoteStyle: 'single' + } + }), 'utf8') + + const loader = new ConfigLoader() + const result = loader.loadFromFile(configPath) + + expect(result.found).toBe(true) + expect(result.config).toEqual({ + workspaceDir: '/tmp/workspace', + codeStyles: { + indent: 'space', + tabSize: 2, + quoteStyle: 'single' + } + }) + } + finally { + fs.rmSync(tempDir, {recursive: true, force: true}) + } + }) + + it('fills missing codeStyles fields with defaults when the block exists', () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-config-loader-code-styles-defaults-')) + const configPath = path.join(tempDir, '.tnmsc.json') + + try { + fs.writeFileSync(configPath, JSON.stringify({ + codeStyles: { + tabSize: 4 + } + }), 'utf8') + + const loader = new ConfigLoader() + const result = loader.loadFromFile(configPath) + + expect(result.found).toBe(true) + expect(result.config).toEqual({ + codeStyles: { + indent: 'space', + tabSize: 4 + } + }) + } + finally { + fs.rmSync(tempDir, {recursive: true, force: true}) + } + }) + + it('loads plugin enablement flags from the user config file', () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-config-loader-plugins-')) + const configPath = path.join(tempDir, '.tnmsc.json') + + try { + fs.writeFileSync(configPath, JSON.stringify({ + plugins: { + trae: true, + claudeCode: false + } + }), 'utf8') + + const loader = new ConfigLoader() + const result = loader.loadFromFile(configPath) + + expect(result.found).toBe(true) + expect(result.config).toEqual({ + plugins: { + trae: true, + claudeCode: false + } + }) + } + finally { + fs.rmSync(tempDir, {recursive: true, force: true}) + } + }) }) diff --git a/sdk/src/ConfigLoader.ts b/sdk/src/ConfigLoader.ts index 55325f4e..2906177e 100644 --- a/sdk/src/ConfigLoader.ts +++ b/sdk/src/ConfigLoader.ts @@ -1,11 +1,9 @@ import type {ILogger} from '@truenine/logger' import type { - CleanupProtectionOptions, + CodeStylesOptions, ConfigLoaderOptions, ConfigLoadResult, FrontMatterOptions, - OutputScopeOptions, - PluginOutputScopeTopics, UserConfigFile, WindowsOptions } from './plugins/plugin-core' @@ -29,42 +27,21 @@ import { DEFAULT_GLOBAL_CONFIG_DIR as RUNTIME_DEFAULT_GLOBAL_CONFIG_DIR } from './runtime-environment' -/** - * Default config file name - */ export const DEFAULT_CONFIG_FILE_NAME = '.tnmsc.json' -/** - * Default global config directory (relative to home) - */ export const DEFAULT_GLOBAL_CONFIG_DIR = '.aindex' -/** - * Get global config file path - */ export function getGlobalConfigPath(): string { return getRequiredGlobalConfigPath() } -/** - * Validation result for global config - */ export interface GlobalConfigValidationResult { readonly valid: boolean - readonly exists: boolean - readonly errors: readonly string[] - readonly shouldExit: boolean } -/** - * ConfigLoader handles discovery and loading of user configuration files. - * - * The config source is fixed and unambiguous: - * 1. Global: ~/.aindex/.tnmsc.json - */ export class ConfigLoader { private readonly logger: ILogger @@ -127,7 +104,7 @@ export class ConfigLoader { if (result.found) loadedConfigs.push(result) } - const merged = this.mergeConfigs(loadedConfigs.map(r => r.config)) // Merge configs (first has highest priority) + const merged = this.mergeConfigs(loadedConfigs.map(r => r.config)) const sources = loadedConfigs.map(r => r.source).filter((s): s is string => s !== null) return { @@ -150,7 +127,7 @@ export class ConfigLoader { const result = ZUserConfigFile.safeParse(parsed) if (result.success) return result.data - const errors = result.error.issues.map((i: {path: (string | number)[], message: string}) => `${i.path.join('.')}: ${i.message}`) // Validation failed - throw error instead of returning empty config + const errors = result.error.issues.map((i: {path: (string | number)[], message: string}) => `${i.path.join('.')}: ${i.message}`) throw new Error(`Config validation failed in ${filePath}:\n${errors.join('\n')}`) } @@ -160,59 +137,23 @@ export class ConfigLoader { const firstConfig = configs[0] if (configs.length === 1 && firstConfig != null) return firstConfig - const reversed = [...configs].reverse() // Reverse to merge from lowest to highest priority + const reversed = [...configs].reverse() return reversed.reduce((acc, config) => { - const mergedOutputScopes = this.mergeOutputScopeOptions(acc.outputScopes, config.outputScopes) + const mergedCodeStyles = this.mergeCodeStylesOptions(acc.codeStyles, config.codeStyles) const mergedFrontMatter = this.mergeFrontMatterOptions(acc.frontMatter, config.frontMatter) - const mergedCleanupProtection = this.mergeCleanupProtectionOptions( - acc.cleanupProtection, - config.cleanupProtection - ) const mergedWindows = this.mergeWindowsOptions(acc.windows, config.windows) return { ...acc, ...config, - ...mergedOutputScopes != null ? {outputScopes: mergedOutputScopes} : {}, + ...mergedCodeStyles != null ? {codeStyles: mergedCodeStyles} : {}, ...mergedFrontMatter != null ? {frontMatter: mergedFrontMatter} : {}, - ...mergedCleanupProtection != null ? {cleanupProtection: mergedCleanupProtection} : {}, ...mergedWindows != null ? {windows: mergedWindows} : {} } }, {}) } - private mergeOutputScopeTopics( - a?: PluginOutputScopeTopics, - b?: PluginOutputScopeTopics - ): PluginOutputScopeTopics | undefined { - if (a == null && b == null) return void 0 - if (a == null) return b - if (b == null) return a - return {...a, ...b} - } - - private mergeOutputScopeOptions( - a?: OutputScopeOptions, - b?: OutputScopeOptions - ): OutputScopeOptions | undefined { - if (a == null && b == null) return void 0 - if (a == null) return b - if (b == null) return a - - const mergedPlugins: Record = {} - for (const [pluginName, topics] of Object.entries(a.plugins ?? {})) { - if (topics != null) mergedPlugins[pluginName] = {...topics} - } - for (const [pluginName, topics] of Object.entries(b.plugins ?? {})) { - const mergedTopics = this.mergeOutputScopeTopics(mergedPlugins[pluginName], topics) - if (mergedTopics != null) mergedPlugins[pluginName] = mergedTopics - } - - if (Object.keys(mergedPlugins).length === 0) return {} - return {plugins: mergedPlugins} - } - private mergeFrontMatterOptions( a?: FrontMatterOptions, b?: FrontMatterOptions @@ -223,20 +164,14 @@ export class ConfigLoader { return {...a, ...b} } - private mergeCleanupProtectionOptions( - a?: CleanupProtectionOptions, - b?: CleanupProtectionOptions - ): CleanupProtectionOptions | undefined { + private mergeCodeStylesOptions( + a?: CodeStylesOptions, + b?: CodeStylesOptions + ): CodeStylesOptions | undefined { if (a == null && b == null) return void 0 if (a == null) return b if (b == null) return a - - return { - rules: [ - ...a.rules ?? [], - ...b.rules ?? [] - ] - } + return {...a, ...b} } private mergeWindowsOptions( @@ -266,44 +201,23 @@ export class ConfigLoader { } } -/** - * Result of loading and merging all configurations - */ export interface MergedConfigResult { readonly config: UserConfigFile - readonly sources: readonly string[] - readonly found: boolean } -/** - * Singleton instance for convenience - */ let defaultLoader: ConfigLoader | null = null -/** - * Get or create the default ConfigLoader instance - */ export function getConfigLoader(options?: ConfigLoaderOptions): ConfigLoader { if (options || !defaultLoader) defaultLoader = new ConfigLoader(options) return defaultLoader } -/** - * Load user configuration using default loader - */ export function loadUserConfig(cwd?: string): MergedConfigResult { return getConfigLoader().load(cwd) } -/** - * Validate global config file strictly. - * - If config doesn't exist: return invalid result (do not auto-create) - * - If config is invalid (parse error or validation error): return invalid result (do not recreate) - * - * @returns Validation result indicating whether program should continue or exit - */ export function validateGlobalConfig(): GlobalConfigValidationResult { const logger = createLogger('ConfigLoader') let configPath: string @@ -330,7 +244,7 @@ export function validateGlobalConfig(): GlobalConfigValidationResult { } } - if (!fs.existsSync(configPath)) { // Check if config file exists - do not auto-create + if (!fs.existsSync(configPath)) { const error = `Global config not found at ${configPath}. Please create it manually.` logger.error(buildConfigDiagnostic({ code: 'GLOBAL_CONFIG_MISSING', diff --git a/sdk/src/ProtectedDeletionGuard.ts b/sdk/src/ProtectedDeletionGuard.ts index 952dd79b..13b1ee5f 100644 --- a/sdk/src/ProtectedDeletionGuard.ts +++ b/sdk/src/ProtectedDeletionGuard.ts @@ -71,26 +71,26 @@ export class ProtectedDeletionGuardError extends Error { } function resolveXdgConfigHome(homeDir: string): string { - const xdgConfigHome = process.env['XDG_CONFIG_HOME'] - if (typeof xdgConfigHome === 'string' && xdgConfigHome.trim().length > 0) return xdgConfigHome + const xdgConfigHome = process.env['XDG_CONFIG_HOME']?.trim() + if (xdgConfigHome != null && xdgConfigHome.length > 0) return xdgConfigHome return path.join(homeDir, '.config') } function resolveXdgDataHome(homeDir: string): string { - const xdgDataHome = process.env['XDG_DATA_HOME'] - if (typeof xdgDataHome === 'string' && xdgDataHome.trim().length > 0) return xdgDataHome + const xdgDataHome = process.env['XDG_DATA_HOME']?.trim() + if (xdgDataHome != null && xdgDataHome.length > 0) return xdgDataHome return path.join(homeDir, '.local', 'share') } function resolveXdgStateHome(homeDir: string): string { - const xdgStateHome = process.env['XDG_STATE_HOME'] - if (typeof xdgStateHome === 'string' && xdgStateHome.trim().length > 0) return xdgStateHome + const xdgStateHome = process.env['XDG_STATE_HOME']?.trim() + if (xdgStateHome != null && xdgStateHome.length > 0) return xdgStateHome return path.join(homeDir, '.local', 'state') } function resolveXdgCacheHome(homeDir: string): string { - const xdgCacheHome = process.env['XDG_CACHE_HOME'] - if (typeof xdgCacheHome === 'string' && xdgCacheHome.trim().length > 0) return xdgCacheHome + const xdgCacheHome = process.env['XDG_CACHE_HOME']?.trim() + if (xdgCacheHome != null && xdgCacheHome.length > 0) return xdgCacheHome return path.join(homeDir, '.cache') } diff --git a/sdk/src/config.outputScopes.test.ts b/sdk/src/config.outputScopes.test.ts deleted file mode 100644 index a5b9e7ae..00000000 --- a/sdk/src/config.outputScopes.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {describe, expect, it} from 'vitest' -import {mergeConfig} from './config' - -describe('mergeConfig outputScopes', () => { - it('merges plugin topic overrides deeply', () => { - const merged = mergeConfig( - { - outputScopes: { - plugins: { - CursorOutputPlugin: { - commands: 'global', - skills: ['workspace', 'global'] - } - } - } - }, - { - outputScopes: { - plugins: { - CursorOutputPlugin: { - rules: 'project', - skills: 'project' - }, - OpencodeCLIOutputPlugin: { - mcp: 'global' - } - } - } - } - ) - - expect(merged.outputScopes).toEqual({ - plugins: { - CursorOutputPlugin: { - commands: 'global', - skills: 'project', - rules: 'project' - }, - OpencodeCLIOutputPlugin: { - mcp: 'global' - } - } - }) - }) -}) diff --git a/sdk/src/config.plugins-fast-path.test.ts b/sdk/src/config.plugins-fast-path.test.ts deleted file mode 100644 index 5ca909e6..00000000 --- a/sdk/src/config.plugins-fast-path.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as fs from 'node:fs' -import * as os from 'node:os' -import * as path from 'node:path' -import {afterEach, describe, expect, it, vi} from 'vitest' - -import {defineConfig} from './config' - -const {collectInputContextMock} = vi.hoisted(() => ({ - collectInputContextMock: vi.fn(async () => { - throw new Error('collectInputContext should not run for plugins fast path') - }) -})) - -vi.mock('./inputs/runtime', async importOriginal => { - const actual = await importOriginal() - - return { - ...actual, - collectInputContext: collectInputContextMock - } -}) - -afterEach(() => { - vi.clearAllMocks() -}) - -describe('defineConfig plugins fast path', () => { - it('skips input collection for plugins runtime commands', async () => { - const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-plugins-fast-path-')) - - try { - const result = await defineConfig({ - loadUserConfig: false, - runtimeCommand: 'plugins', - pluginOptions: { - workspaceDir: tempWorkspace, - plugins: [] - } - }) - - expect(collectInputContextMock).not.toHaveBeenCalled() - expect(result.context.workspace.directory.path).toBe(tempWorkspace) - expect(result.context.aindexDir).toBe(path.join(tempWorkspace, 'aindex')) - expect(result.outputPlugins).toEqual([]) - } finally { - fs.rmSync(tempWorkspace, {recursive: true, force: true}) - } - }) -}) diff --git a/sdk/src/config.test.ts b/sdk/src/config.test.ts index e818ce0e..a4d609a8 100644 --- a/sdk/src/config.test.ts +++ b/sdk/src/config.test.ts @@ -1,21 +1,22 @@ +import type {OutputPlugin} from './plugins/plugin-core' import * as fs from 'node:fs' import * as os from 'node:os' import * as path from 'node:path' import {afterEach, describe, expect, it, vi} from 'vitest' import {defineConfig} from './config' -import {WorkspaceInputCapability} from './inputs/input-workspace' +import {createLogger, PluginKind} from './plugins/plugin-core' describe('defineConfig', () => { - const originalHome = process.env.HOME - const originalUserProfile = process.env.USERPROFILE - const originalHomeDrive = process.env.HOMEDRIVE - const originalHomePath = process.env.HOMEPATH + const originalHome = process.env['HOME'] + const originalUserProfile = process.env['USERPROFILE'] + const originalHomeDrive = process.env['HOMEDRIVE'] + const originalHomePath = process.env['HOMEPATH'] afterEach(() => { - process.env.HOME = originalHome - process.env.USERPROFILE = originalUserProfile - process.env.HOMEDRIVE = originalHomeDrive - process.env.HOMEPATH = originalHomePath + process.env['HOME'] = originalHome + process.env['USERPROFILE'] = originalUserProfile + process.env['HOMEDRIVE'] = originalHomeDrive + process.env['HOMEPATH'] = originalHomePath vi.restoreAllMocks() }) @@ -26,10 +27,10 @@ describe('defineConfig', () => { const globalConfigPath = path.join(globalConfigDir, '.tnmsc.json') const localConfigPath = path.join(tempWorkspace, '.tnmsc.json') - process.env.HOME = tempHome - process.env.USERPROFILE = tempHome - delete process.env.HOMEDRIVE - delete process.env.HOMEPATH + process.env['HOME'] = tempHome + process.env['USERPROFILE'] = tempHome + delete process.env['HOMEDRIVE'] + delete process.env['HOMEPATH'] fs.mkdirSync(globalConfigDir, {recursive: true}) fs.writeFileSync( @@ -94,8 +95,7 @@ describe('defineConfig', () => { loadUserConfig: false, executionCwd: externalCwd, pluginOptions: { - workspaceDir: tempWorkspace, - plugins: [new WorkspaceInputCapability()] + workspaceDir: tempWorkspace } }) @@ -110,72 +110,71 @@ describe('defineConfig', () => { } }) - it('does not run builtin mutating input effects when plugins is explicitly empty', async () => { - const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-explicit-empty-plugins-')) - const orphanSkillDir = path.join(tempWorkspace, 'aindex', 'dist', 'skills', 'orphan-skill') - const orphanSkillFile = path.join(orphanSkillDir, 'SKILL.md') - - fs.mkdirSync(orphanSkillDir, {recursive: true}) - fs.writeFileSync(orphanSkillFile, 'orphan\n', 'utf8') + it('applies default codeStyles when user config omits them', async () => { + const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-code-styles-default-workspace-')) try { const result = await defineConfig({ loadUserConfig: false, pluginOptions: { - workspaceDir: tempWorkspace, - plugins: [] + workspaceDir: tempWorkspace } }) - expect(result.context.workspace.directory.path).toBe(tempWorkspace) - expect(fs.existsSync(orphanSkillFile)).toBe(true) + expect(result.userConfigOptions.codeStyles).toEqual({ + indent: 'space', + tabSize: 2 + }) } finally { fs.rmSync(tempWorkspace, {recursive: true, force: true}) } }) - it('does not run builtin mutating input effects when shorthand plugins is explicitly empty', async () => { - const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-shorthand-empty-plugins-')) - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-shorthand-empty-home-')) - const orphanSkillDir = path.join(tempWorkspace, 'aindex', 'dist', 'skills', 'orphan-skill') - const orphanSkillFile = path.join(orphanSkillDir, 'SKILL.md') - - process.env.HOME = tempHome - process.env.USERPROFILE = tempHome - delete process.env.HOMEDRIVE - delete process.env.HOMEPATH - - fs.mkdirSync(orphanSkillDir, {recursive: true}) - fs.writeFileSync(orphanSkillFile, 'orphan\n', 'utf8') + it('uses executionCwd as the workspace root when workspaceDir is omitted', async () => { + const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-runtime-workspace-fallback-')) try { const result = await defineConfig({ - workspaceDir: tempWorkspace, - plugins: [] + loadUserConfig: false, + executionCwd: tempWorkspace }) + expect(result.userConfigOptions.workspaceDir).toBe(tempWorkspace) expect(result.context.workspace.directory.path).toBe(tempWorkspace) - expect(fs.existsSync(orphanSkillFile)).toBe(true) + expect(result.executionPlan.cwd).toBe(tempWorkspace) + expect(result.executionPlan.workspaceDir).toBe(tempWorkspace) } finally { fs.rmSync(tempWorkspace, {recursive: true, force: true}) - fs.rmSync(tempHome, {recursive: true, force: true}) } }) - it('accepts legacy input capabilities in pluginOptions.plugins without crashing', async () => { - const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-legacy-input-capabilities-')) + it('returns programmatically assembled output plugins separately from user config options', async () => { + const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-output-plugin-assembly-')) + const outputPlugin: OutputPlugin = { + name: 'TestOutputPlugin', + type: PluginKind.Output, + log: createLogger('TestOutputPlugin', 'error'), + declarativeOutput: true, + outputCapabilities: {}, + async declareOutputFiles() { + return [] + }, + async convertContent() { + return '' + } + } try { const result = await defineConfig({ loadUserConfig: false, + outputPlugins: [outputPlugin], pluginOptions: { - workspaceDir: tempWorkspace, - plugins: [new WorkspaceInputCapability()] + workspaceDir: tempWorkspace } }) - expect(result.context.workspace.directory.path).toBe(tempWorkspace) - expect(result.outputPlugins).toEqual([]) + expect(result.outputPlugins).toEqual([outputPlugin]) + expect(result.userConfigOptions.plugins).toEqual({}) } finally { fs.rmSync(tempWorkspace, {recursive: true, force: true}) } diff --git a/sdk/src/config.ts b/sdk/src/config.ts index 6e56fa80..a4486a4e 100644 --- a/sdk/src/config.ts +++ b/sdk/src/config.ts @@ -1,16 +1,12 @@ import type { AindexConfig, - CleanupProtectionOptions, - CommandSeriesOptions, - CommandSeriesPluginOverride, + CodeStylesOptions, ConfigLoaderOptions, - InputCapability, InputCollectedContext, OutputCollectedContext, OutputPlugin, - OutputScopeOptions, PluginOptions, - PluginOutputScopeTopics, + PluginsConfig, UserConfigFile, WindowsOptions } from './plugins/plugin-core' @@ -24,17 +20,15 @@ import {resolveExecutionPlan} from './execution-plan' import {collectInputContext} from './inputs/runtime' import { buildDefaultAindexConfig, + buildDefaultCodeStylesOptions, FilePathKind, mergeAindexConfig, + mergeCodeStylesOptions, PathPlaceholders, - toOutputCollectedContext, - validateOutputScopeOverridesForPlugins + toOutputCollectedContext } from './plugins/plugin-core' import {resolveUserPath} from './runtime-environment' -/** - * Pipeline configuration containing collected context and output plugins - */ export interface PipelineConfig { readonly context: OutputCollectedContext readonly outputPlugins: readonly OutputPlugin[] @@ -45,58 +39,52 @@ export interface PipelineConfig { interface ResolvedPluginSetup { readonly mergedOptions: Required readonly outputPlugins: readonly OutputPlugin[] - readonly inputCapabilities: readonly InputCapability[] readonly userConfigFile?: UserConfigFile } -function isOutputPlugin(plugin: InputCapability | OutputPlugin): plugin is OutputPlugin { - return 'declarativeOutput' in plugin -} +const DEFAULT_AINDEX: Required = buildDefaultAindexConfig() -function isInputCapability(plugin: InputCapability | OutputPlugin): plugin is InputCapability { - return 'collect' in plugin && !isOutputPlugin(plugin) +type MergeablePluginOptions = Omit, 'workspaceDir'> & { + readonly workspaceDir?: string } -const DEFAULT_AINDEX: Required = buildDefaultAindexConfig() - -const DEFAULT_OPTIONS: Required = { +const DEFAULT_OPTIONS: Omit, 'workspaceDir'> = { version: '0.0.0', - workspaceDir: '~/project', logLevel: 'info', aindex: DEFAULT_AINDEX, - commandSeriesOptions: {}, - outputScopes: {}, frontMatter: { blankLineAfter: true }, - cleanupProtection: {}, + codeStyles: buildDefaultCodeStylesOptions(), windows: {}, - plugins: [] + plugins: {} +} + +function resolveWorkspaceDirOption(workspaceDir: string | undefined, fallbackWorkspaceDir?: string): string { + if (typeof workspaceDir === 'string' && workspaceDir.trim().length > 0) { + return workspaceDir + } + + return path.resolve(fallbackWorkspaceDir ?? process.cwd()) } -/** - * Convert UserConfigFile to PluginOptions - * UserConfigFile is the JSON schema, PluginOptions includes plugins - */ export function userConfigToPluginOptions(userConfig: UserConfigFile): Partial { return { ...userConfig.version != null ? {version: userConfig.version} : {}, ...userConfig.workspaceDir != null ? {workspaceDir: userConfig.workspaceDir} : {}, - ...userConfig.commandSeriesOptions != null ? {commandSeriesOptions: userConfig.commandSeriesOptions} : {}, - ...userConfig.outputScopes != null ? {outputScopes: userConfig.outputScopes} : {}, ...userConfig.frontMatter != null ? {frontMatter: userConfig.frontMatter} : {}, - ...userConfig.cleanupProtection != null ? {cleanupProtection: userConfig.cleanupProtection} : {}, + ...userConfig.codeStyles != null ? {codeStyles: userConfig.codeStyles} : {}, ...userConfig.windows != null ? {windows: userConfig.windows} : {}, + ...userConfig.plugins != null ? {plugins: userConfig.plugins} : {}, ...userConfig.logLevel != null ? {logLevel: userConfig.logLevel} : {} } } -/** - * Options for defineConfig - */ export interface DefineConfigOptions { readonly pluginOptions?: PluginOptions + readonly outputPlugins?: readonly OutputPlugin[] + readonly configLoaderOptions?: ConfigLoaderOptions readonly loadUserConfig?: boolean @@ -108,97 +96,45 @@ export interface DefineConfigOptions { readonly runtimeCommand?: RuntimeCommand } -/** - * Merge multiple PluginOptions with default configuration. - * Later options override earlier ones. - * Similar to vite/vitest mergeConfig. - */ export function mergeConfig(...configs: Partial[]): Required { - const initialConfig: Required = {...DEFAULT_OPTIONS} - return configs.reduce((acc: Required, config) => mergeTwoConfigs(acc, config), initialConfig) + return mergeConfigForRuntime(process.cwd(), ...configs) } -function mergeTwoConfigs(base: Required, override: Partial): Required { - const overridePlugins = override.plugins - const overrideCommandSeries = override.commandSeriesOptions - const overrideOutputScopes = override.outputScopes +export function mergeConfigForRuntime( + fallbackWorkspaceDir: string | undefined, + ...configs: Partial[] +): Required { + const initialConfig: MergeablePluginOptions = {...DEFAULT_OPTIONS} + const mergedConfig = configs.reduce((acc: MergeablePluginOptions, config) => mergeTwoConfigs(acc, config), initialConfig) + + return { + ...mergedConfig, + workspaceDir: resolveWorkspaceDirOption(mergedConfig.workspaceDir, fallbackWorkspaceDir) + } +} + +function mergeTwoConfigs(base: MergeablePluginOptions, override: Partial): MergeablePluginOptions { + const overrideCodeStyles = override.codeStyles const overrideFrontMatter = override.frontMatter - const overrideCleanupProtection = override.cleanupProtection + const overridePlugins = override.plugins const overrideWindows = override.windows return { ...base, ...override, aindex: mergeAindexConfig(base.aindex, override.aindex), - plugins: [ - // Array concatenation for plugins - ...base.plugins, - ...overridePlugins ?? [] - ], - commandSeriesOptions: mergeCommandSeriesOptions(base.commandSeriesOptions, overrideCommandSeries), // Deep merge for commandSeriesOptions - outputScopes: mergeOutputScopeOptions(base.outputScopes, overrideOutputScopes), + codeStyles: mergeResolvedCodeStylesOptions(base.codeStyles, overrideCodeStyles), frontMatter: mergeFrontMatterOptions(base.frontMatter, overrideFrontMatter), - cleanupProtection: mergeCleanupProtectionOptions(base.cleanupProtection, overrideCleanupProtection), + plugins: mergePluginsOptions(base.plugins, overridePlugins), windows: mergeWindowsOptions(base.windows, overrideWindows) } } -function mergeCommandSeriesOptions(base?: CommandSeriesOptions, override?: CommandSeriesOptions): CommandSeriesOptions { - if (override == null) return base ?? {} - if (base == null) return override - - const mergedPluginOverrides: Record = {} // Merge pluginOverrides deeply - - if (base.pluginOverrides != null) { - // Copy base plugin overrides - for (const [key, value] of Object.entries(base.pluginOverrides)) mergedPluginOverrides[key] = {...value} - } - - if (override.pluginOverrides != null) { - // Merge override plugin overrides - for (const [key, value] of Object.entries(override.pluginOverrides)) { - mergedPluginOverrides[key] = { - ...mergedPluginOverrides[key], - ...value - } - } - } - - const includeSeriesPrefix = override.includeSeriesPrefix ?? base.includeSeriesPrefix // Build result with conditional properties to satisfy exactOptionalPropertyTypes - const hasPluginOverrides = Object.keys(mergedPluginOverrides).length > 0 - - if (includeSeriesPrefix != null && hasPluginOverrides) return {includeSeriesPrefix, pluginOverrides: mergedPluginOverrides} - if (includeSeriesPrefix != null) return {includeSeriesPrefix} - if (hasPluginOverrides) return {pluginOverrides: mergedPluginOverrides} - return {} -} - -function mergeOutputScopeTopics(base?: PluginOutputScopeTopics, override?: PluginOutputScopeTopics): PluginOutputScopeTopics | undefined { - if (base == null && override == null) return void 0 - if (base == null) return override - if (override == null) return base - return {...base, ...override} -} - -function mergeOutputScopeOptions(base?: OutputScopeOptions, override?: OutputScopeOptions): OutputScopeOptions { - if (override == null) return base ?? {} - if (base == null) return override - - const mergedPlugins: Record = {} - if (base.plugins != null) { - for (const [pluginName, topics] of Object.entries(base.plugins)) { - if (topics != null) mergedPlugins[pluginName] = {...topics} - } - } - if (override.plugins != null) { - for (const [pluginName, topics] of Object.entries(override.plugins)) { - const mergedTopics = mergeOutputScopeTopics(mergedPlugins[pluginName], topics) - if (mergedTopics != null) mergedPlugins[pluginName] = mergedTopics - } - } - - if (Object.keys(mergedPlugins).length === 0) return {} - return {plugins: mergedPlugins} +function mergeResolvedCodeStylesOptions( + base: Required['codeStyles'], + override?: CodeStylesOptions +): Required['codeStyles'] { + return mergeCodeStylesOptions(base, override) } function mergeFrontMatterOptions( @@ -212,12 +148,12 @@ function mergeFrontMatterOptions( } } -function mergeCleanupProtectionOptions(base?: CleanupProtectionOptions, override?: CleanupProtectionOptions): CleanupProtectionOptions { +function mergePluginsOptions(base?: PluginsConfig, override?: PluginsConfig): PluginsConfig { if (override == null) return base ?? {} if (base == null) return override - return { - rules: [...base.rules ?? [], ...override.rules ?? []] + ...base, + ...override } } @@ -242,9 +178,6 @@ function mergeWindowsOptions(base?: WindowsOptions, override?: WindowsOptions): } } -/** - * Check if options is DefineConfigOptions - */ function isDefineConfigOptions(options: PluginOptions | DefineConfigOptions): options is DefineConfigOptions { return 'pluginOptions' in options || 'configLoaderOptions' in options @@ -254,23 +187,6 @@ function isDefineConfigOptions(options: PluginOptions | DefineConfigOptions): op || 'runtimeCommand' in options } -function getProgrammaticPluginDeclaration(options: PluginOptions | DefineConfigOptions): { - readonly hasExplicitProgrammaticPlugins: boolean - readonly explicitProgrammaticPlugins?: PluginOptions['plugins'] -} { - if (isDefineConfigOptions(options)) { - return { - hasExplicitProgrammaticPlugins: Object.hasOwn(options.pluginOptions ?? {}, 'plugins'), - explicitProgrammaticPlugins: options.pluginOptions?.plugins - } - } - - return { - hasExplicitProgrammaticPlugins: Object.hasOwn(options, 'plugins'), - explicitProgrammaticPlugins: options.plugins - } -} - function resolvePathForMinimalContext(rawPath: string, workspaceDir: string): string { let resolvedPath = rawPath @@ -314,18 +230,21 @@ async function resolvePluginSetup(options: PluginOptions | DefineConfigOptions = cwd: string | undefined, executionCwd: string | undefined, pluginOptions: PluginOptions, + outputPlugins: readonly OutputPlugin[], configLoaderOptions: ConfigLoaderOptions | undefined, runtimeCommand: RuntimeCommand | undefined if (isDefineConfigOptions(options)) { ({ pluginOptions = {}, + outputPlugins = [], cwd, executionCwd, configLoaderOptions, runtimeCommand } = { pluginOptions: options.pluginOptions, + outputPlugins: options.outputPlugins, cwd: options.cwd, executionCwd: options.executionCwd, configLoaderOptions: options.configLoaderOptions, @@ -334,6 +253,7 @@ async function resolvePluginSetup(options: PluginOptions | DefineConfigOptions = shouldLoadUserConfig = options.loadUserConfig ?? true } else { pluginOptions = options + outputPlugins = [] shouldLoadUserConfig = true configLoaderOptions = void 0 runtimeCommand = void 0 @@ -359,10 +279,10 @@ async function resolvePluginSetup(options: PluginOptions | DefineConfigOptions = } } - const mergedOptions = mergeConfig(userConfigOptions, pluginOptions) - const {plugins = [], logLevel} = mergedOptions - const logger = createLogger('defineConfig', logLevel) const resolvedExecutionCwd = path.resolve(executionCwd ?? cwd ?? process.cwd()) + const mergedOptions = mergeConfigForRuntime(resolvedExecutionCwd, userConfigOptions, pluginOptions) + const {logLevel} = mergedOptions + const logger = createLogger('defineConfig', logLevel) if (userConfigFound) { logger.info('user config loaded', {sources: userConfigSources}) @@ -374,14 +294,9 @@ async function resolvePluginSetup(options: PluginOptions | DefineConfigOptions = }) } - const outputPlugins = plugins.filter(isOutputPlugin) - const inputCapabilities = plugins.filter(isInputCapability) - validateOutputScopeOverridesForPlugins(outputPlugins, mergedOptions) - return { mergedOptions, outputPlugins, - inputCapabilities, executionCwd: resolvedExecutionCwd, ...userConfigFile != null && {userConfigFile}, ...runtimeCommand != null && {runtimeCommand}, @@ -390,19 +305,8 @@ async function resolvePluginSetup(options: PluginOptions | DefineConfigOptions = } } -/** - * Define configuration with support for user config files. - * - * Configuration priority (highest to lowest): - * 1. Programmatic options passed to defineConfig - * 2. Global config file (~/.aindex/.tnmsc.json) - * 3. Default values - * - * @param options - Plugin options or DefineConfigOptions - */ export async function defineConfig(options: PluginOptions | DefineConfigOptions = {}): Promise { - const {hasExplicitProgrammaticPlugins, explicitProgrammaticPlugins} = getProgrammaticPluginDeclaration(options) - const {mergedOptions, outputPlugins, inputCapabilities, userConfigFile, runtimeCommand, executionCwd} = await resolvePluginSetup(options) + const {mergedOptions, outputPlugins, userConfigFile, runtimeCommand, executionCwd} = await resolvePluginSetup(options) const logger = createLogger('defineConfig', mergedOptions.logLevel) if (shouldUsePluginsFastPath(runtimeCommand)) { @@ -417,8 +321,7 @@ export async function defineConfig(options: PluginOptions | DefineConfigOptions const merged = await collectInputContext({ userConfigOptions: mergedOptions, - ...inputCapabilities.length > 0 ? {capabilities: inputCapabilities} : {}, - includeBuiltinEffects: !(inputCapabilities.length > 0 || (hasExplicitProgrammaticPlugins && (explicitProgrammaticPlugins?.length ?? 0) === 0)), + includeBuiltinEffects: true, ...runtimeCommand != null ? {runtimeCommand} : {}, ...userConfigFile != null ? {userConfig: userConfigFile} : {} }) diff --git a/sdk/src/core/config/mod.rs b/sdk/src/core/config/mod.rs index 2b0a9788..808a76f9 100644 --- a/sdk/src/core/config/mod.rs +++ b/sdk/src/core/config/mod.rs @@ -196,6 +196,52 @@ pub struct UserProfile { pub extra: HashMap, } +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum CodeStyleIndent { + Tab, + Space, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CodeStyles { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub indent: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tab_size: Option, + #[serde(flatten)] + pub extra: HashMap, +} + +pub const DEFAULT_CODE_STYLE_TAB_SIZE: u16 = 2; + +pub fn build_default_code_styles() -> CodeStyles { + CodeStyles { + indent: Some(CodeStyleIndent::Space), + tab_size: Some(DEFAULT_CODE_STYLE_TAB_SIZE), + extra: HashMap::new(), + } +} + +fn normalize_code_styles(code_styles: &Option) -> CodeStyles { + match code_styles { + Some(value) => { + let mut merged = build_default_code_styles(); + merged.indent = value.indent.or(merged.indent); + merged.tab_size = value.tab_size.or(merged.tab_size); + merged.extra.extend(value.extra.clone()); + merged + } + None => build_default_code_styles(), + } +} + +fn normalize_user_config(mut config: UserConfigFile) -> UserConfigFile { + config.code_styles = Some(normalize_code_styles(&config.code_styles)); + config +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(untagged)] pub enum StringOrStrings { @@ -239,6 +285,8 @@ pub struct UserConfigFile { #[serde(default, skip_serializing_if = "Option::is_none")] pub profile: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_styles: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub windows: Option, } @@ -251,6 +299,7 @@ impl Default for UserConfigFile { log_level: None, fast_command_series_options: None, profile: None, + code_styles: None, windows: None, } } @@ -708,9 +757,28 @@ fn merge_windows(a: &Option, b: &Option) -> Opti } } +fn merge_code_styles(a: &Option, b: &Option) -> Option { + match (a, b) { + (None, None) => None, + (Some(v), None) => Some(v.clone()), + (None, Some(v)) => Some(v.clone()), + (Some(base), Some(over)) => { + let mut merged_extra = base.extra.clone(); + merged_extra.extend(over.extra.clone()); + + Some(CodeStyles { + indent: over.indent.or(base.indent), + tab_size: over.tab_size.or(base.tab_size), + extra: merged_extra, + }) + } + } +} + /// Merge two configs. `over` fields take priority over `base`. pub fn merge_configs_pair(base: &UserConfigFile, over: &UserConfigFile) -> UserConfigFile { let merged_aindex = merge_aindex(&base.aindex, &over.aindex); + let merged_code_styles = merge_code_styles(&base.code_styles, &over.code_styles); let merged_windows = merge_windows(&base.windows, &over.windows); UserConfigFile { @@ -726,6 +794,7 @@ pub fn merge_configs_pair(base: &UserConfigFile, over: &UserConfigFile) -> UserC .clone() .or_else(|| base.fast_command_series_options.clone()), profile: over.profile.clone().or_else(|| base.profile.clone()), + code_styles: merged_code_styles, windows: merged_windows, } } @@ -873,7 +942,7 @@ impl ConfigLoader { } let configs: Vec = loaded.iter().map(|r| r.config.clone()).collect(); - let merged = merge_configs(&configs); + let merged = normalize_user_config(merge_configs(&configs)); let sources: Vec = loaded.iter().filter_map(|r| r.source.clone()).collect(); Ok(MergedConfigResult { @@ -898,7 +967,7 @@ impl ConfigLoader { )); MergedConfigResult { - config: UserConfigFile::default(), + config: normalize_user_config(UserConfigFile::default()), sources: vec![], found: false, } @@ -1156,6 +1225,7 @@ fn preserve_invalid_config_and_exit( #[cfg(test)] mod tests { use super::*; + use serde_json::json; use tempfile::TempDir; #[test] @@ -1179,6 +1249,7 @@ mod tests { assert!(config.workspace_dir.is_none()); assert_eq!(config.aindex, build_default_aindex_config()); assert!(config.log_level.is_none()); + assert!(config.code_styles.is_none()); } #[test] @@ -1256,6 +1327,26 @@ mod tests { ); } + #[test] + fn test_user_config_file_deserialize_with_code_styles() { + let json = r#"{ + "codeStyles": { + "indent": "space", + "tabSize": 2, + "lineEnding": "lf" + } + }"#; + let config: UserConfigFile = serde_json::from_str(json).unwrap(); + let code_styles = config.code_styles.unwrap(); + + assert_eq!(code_styles.indent, Some(CodeStyleIndent::Space)); + assert_eq!(code_styles.tab_size, Some(2)); + assert_eq!( + code_styles.extra.get("lineEnding").and_then(|v| v.as_str()), + Some("lf") + ); + } + #[test] fn test_user_config_file_deserialize_with_windows_wsl2_instances() { let json = r#"{ @@ -1284,6 +1375,11 @@ mod tests { let config = UserConfigFile { workspace_dir: Some("~/workspace".into()), log_level: Some("info".into()), + code_styles: Some(CodeStyles { + indent: Some(CodeStyleIndent::Space), + tab_size: Some(2), + extra: HashMap::new(), + }), ..Default::default() }; let json = serde_json::to_string(&config).unwrap(); @@ -1363,6 +1459,70 @@ mod tests { } } + #[test] + fn test_merge_configs_merges_code_styles() { + let mut base_extra = HashMap::new(); + base_extra.insert("quoteStyle".into(), json!("single")); + let base_config = UserConfigFile { + code_styles: Some(CodeStyles { + indent: Some(CodeStyleIndent::Tab), + tab_size: Some(4), + extra: base_extra, + }), + ..Default::default() + }; + + let mut override_extra = HashMap::new(); + override_extra.insert("lineEnding".into(), json!("lf")); + let override_config = UserConfigFile { + code_styles: Some(CodeStyles { + indent: Some(CodeStyleIndent::Space), + tab_size: None, + extra: override_extra, + }), + ..Default::default() + }; + + let merged = merge_configs_pair(&base_config, &override_config); + let code_styles = merged.code_styles.expect("expected merged code styles"); + + assert_eq!(code_styles.indent, Some(CodeStyleIndent::Space)); + assert_eq!(code_styles.tab_size, Some(4)); + assert_eq!( + code_styles.extra.get("quoteStyle").and_then(|value| value.as_str()), + Some("single") + ); + assert_eq!( + code_styles.extra.get("lineEnding").and_then(|value| value.as_str()), + Some("lf") + ); + } + + #[test] + fn test_normalize_user_config_applies_default_code_styles() { + let config = normalize_user_config(UserConfigFile::default()); + let code_styles = config.code_styles.expect("expected normalized code styles"); + + assert_eq!(code_styles.indent, Some(CodeStyleIndent::Space)); + assert_eq!(code_styles.tab_size, Some(DEFAULT_CODE_STYLE_TAB_SIZE)); + } + + #[test] + fn test_normalize_user_config_merges_partial_code_styles() { + let config = normalize_user_config(UserConfigFile { + code_styles: Some(CodeStyles { + indent: None, + tab_size: Some(4), + extra: HashMap::new(), + }), + ..Default::default() + }); + let code_styles = config.code_styles.expect("expected normalized code styles"); + + assert_eq!(code_styles.indent, Some(CodeStyleIndent::Space)); + assert_eq!(code_styles.tab_size, Some(4)); + } + #[test] fn test_merge_configs_keeps_fixed_aindex_defaults() { let higher_priority = UserConfigFile { diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 423e9cc5..e8245f2f 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -16,7 +16,6 @@ export { export * from './plugins/plugin-core' export * from './plugins/plugin-cursor' export * from './plugins/plugin-droid-cli' -export * from './plugins/plugin-editorconfig' export * from './plugins/plugin-gemini-cli' export * from './plugins/plugin-git-exclude' export * from './plugins/plugin-jetbrains-ai-codex' @@ -29,9 +28,11 @@ export * from './plugins/plugin-readme' export * from './plugins/plugin-trae-cn-ide' export * from './plugins/plugin-trae-ide' export * from './plugins/plugin-vscode' -export * from './plugins/plugin-warp-ide' export * from './plugins/plugin-windsurf' export * from './plugins/plugin-zed' +export { + WarpIDEOutputPlugin +} from './plugins/WarpIDEOutputPlugin' export * from './prompts' export { diff --git a/sdk/src/inputs/effect-orphan-cleanup.ts b/sdk/src/inputs/effect-orphan-cleanup.ts index e061aa65..68717e86 100644 --- a/sdk/src/inputs/effect-orphan-cleanup.ts +++ b/sdk/src/inputs/effect-orphan-cleanup.ts @@ -43,20 +43,12 @@ export class OrphanFileCleanupEffectInputCapability extends AbstractInputCapabil this.registerEffect('orphan-file-cleanup', this.cleanupOrphanFiles.bind(this), 20) } - protected buildProtectedDeletionGuard(ctx: InputEffectContext): ReturnType { + protected buildProtectedDeletionGuard(_ctx: InputEffectContext): ReturnType { return createProtectedDeletionGuard({ - workspaceDir: ctx.workspaceDir, - aindexDir: ctx.aindexDir, + workspaceDir: _ctx.workspaceDir, + aindexDir: _ctx.aindexDir, includeReservedWorkspaceContentRoots: false, - rules: [ - ...(ctx.userConfigOptions.cleanupProtection?.rules ?? []).map(rule => ({ - path: rule.path, - protectionMode: rule.protectionMode, - reason: rule.reason ?? 'configured cleanup protection rule', - source: 'configured-cleanup-protection', - matcher: rule.matcher ?? 'path' - })) - ] + rules: [] }) } diff --git a/sdk/src/inputs/input-agentskills.ts b/sdk/src/inputs/input-agentskills.ts index 129a430c..2a51d1ff 100644 --- a/sdk/src/inputs/input-agentskills.ts +++ b/sdk/src/inputs/input-agentskills.ts @@ -13,9 +13,9 @@ import type { SkillYAMLFrontMatter } from '../plugins/plugin-core' import type {ResourceScanResult} from './input-agentskills-types' - import {Buffer} from 'node:buffer' import * as nodePath from 'node:path' + import {transformMdxReferencesToMd} from '@truenine/md-compiler/markdown' import { buildConfigDiagnostic, @@ -344,7 +344,7 @@ class ResourceProcessor { const {content, encoding, length} = this.readFileContent(filePath, ext) const mimeType = getMimeType(ext) - const resource: SkillResource = { + return { type: PromptKind.SkillResource, extension: ext, fileName, @@ -355,8 +355,6 @@ class ResourceProcessor { length, ...mimeType != null && {mimeType} } - - return resource } catch (e) { this.ctx.logger.warn(buildFileOperationDiagnostic({ @@ -701,14 +699,6 @@ export class SkillInputCapability extends AbstractInputCapability { super('SkillInputCapability') } - readMcpConfig( - skillDir: string, - fs: typeof import('node:fs'), - logger: ILogger - ): SkillMcpConfig | undefined { - return readMcpConfig(skillDir, fs, logger) - } - async scanSkillDirectory( skillDir: string, fs: typeof import('node:fs'), @@ -822,7 +812,7 @@ export class SkillInputCapability extends AbstractInputCapability { })) } - if (errors.length > 0) throw new Error(errors.map(error => error.error instanceof Error ? error.error.message : String(error.error)).join('\n')) + if (errors.length > 0) throw new Error(errors.map(error => error.error.message).join('\n')) for (const localized of localizedSkills) { const prompt = localized.dist?.prompt diff --git a/sdk/src/inputs/input-command.ts b/sdk/src/inputs/input-command.ts index 69026663..99c1a135 100644 --- a/sdk/src/inputs/input-command.ts +++ b/sdk/src/inputs/input-command.ts @@ -127,7 +127,7 @@ export class CommandInputCapability extends AbstractInputCapability { })) } - if (errors.length > 0) throw new Error(errors.map(error => error.error instanceof Error ? error.error.message : String(error.error)).join('\n')) + if (errors.length > 0) throw new Error(errors.map(error => error.error.message).join('\n')) const flatCommands: CommandPrompt[] = [] for (const localized of localizedCommands) { diff --git a/sdk/src/inputs/input-rule.ts b/sdk/src/inputs/input-rule.ts index e0810657..909f36b7 100644 --- a/sdk/src/inputs/input-rule.ts +++ b/sdk/src/inputs/input-rule.ts @@ -92,7 +92,7 @@ export class RuleInputCapability extends AbstractInputCapability { })) } - if (errors.length > 0) throw new Error(errors.map(error => error.error instanceof Error ? error.error.message : String(error.error)).join('\n')) + if (errors.length > 0) throw new Error(errors.map(error => error.error.message).join('\n')) return { rules: localizedRulesFromSrc diff --git a/sdk/src/inputs/input-subagent.ts b/sdk/src/inputs/input-subagent.ts index ebbc0b06..3254c268 100644 --- a/sdk/src/inputs/input-subagent.ts +++ b/sdk/src/inputs/input-subagent.ts @@ -154,7 +154,7 @@ export class SubAgentInputCapability extends AbstractInputCapability { })) } - if (errors.length > 0) throw new Error(errors.map(error => error.error instanceof Error ? error.error.message : String(error.error)).join('\n')) + if (errors.length > 0) throw new Error(errors.map(error => error.error.message).join('\n')) const flatSubAgents: SubAgentPrompt[] = [] for (const localized of localizedSubAgents) { diff --git a/sdk/src/inputs/runtime.ts b/sdk/src/inputs/runtime.ts index 83377e1f..1b8a26bb 100644 --- a/sdk/src/inputs/runtime.ts +++ b/sdk/src/inputs/runtime.ts @@ -79,7 +79,7 @@ export async function collectInputContext(options: InputRuntimeOptions): Promise ...includeBuiltinEffects ? createBuiltinInputEffectCapabilities() : [], ...capabilities ?? createBuiltinInputReaderCapabilities() ]) - const globalScopeCollector = new GlobalScopeCollector({userConfig}) + const globalScopeCollector = new GlobalScopeCollector({userConfig, userConfigOptions}) const globalScope: MdxGlobalScope = globalScopeCollector.collect() const scopeRegistry = new ScopeRegistry() scopeRegistry.setGlobalScope(globalScope) @@ -91,6 +91,7 @@ export async function collectInputContext(options: InputRuntimeOptions): Promise shellKind: globalScope.os.shellKind }, hasProfile: Object.keys(globalScope.profile).length > 0, + hasCodeStyles: Object.keys(globalScope.codeStyles).length > 0, hasTool: Object.keys(globalScope.tool).length > 0 }) diff --git a/sdk/src/plugins/AbstractOutputPlugin.test.ts b/sdk/src/plugins/AbstractOutputPlugin.test.ts index 8c6174c8..b3e12131 100644 --- a/sdk/src/plugins/AbstractOutputPlugin.test.ts +++ b/sdk/src/plugins/AbstractOutputPlugin.test.ts @@ -108,7 +108,7 @@ describe('abstractOutputPlugin prompt-source project exclusion', () => { ] } } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) const paths = declarations.map(declaration => declaration.path) diff --git a/sdk/src/plugins/AgentsOutputPlugin.test.ts b/sdk/src/plugins/AgentsOutputPlugin.test.ts index b9d9ffab..87d6089c 100644 --- a/sdk/src/plugins/AgentsOutputPlugin.test.ts +++ b/sdk/src/plugins/AgentsOutputPlugin.test.ts @@ -100,7 +100,7 @@ describe('agentsOutputPlugin prompt-source project exclusion', () => { ] } } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) const paths = declarations.map(declaration => declaration.path) diff --git a/sdk/src/plugins/ClaudeCodeCLIOutputPlugin.test.ts b/sdk/src/plugins/ClaudeCodeCLIOutputPlugin.test.ts index 21fe5dd3..fc9da145 100644 --- a/sdk/src/plugins/ClaudeCodeCLIOutputPlugin.test.ts +++ b/sdk/src/plugins/ClaudeCodeCLIOutputPlugin.test.ts @@ -43,7 +43,7 @@ function createCleanContext(workspaceBase: string, projects: readonly Project[]) projects: [...projects] } } - } as OutputCleanContext + } as unknown as OutputCleanContext } describe('claudeCodeCLIOutputPlugin cleanup', () => { diff --git a/sdk/src/plugins/CodexCLIOutputPlugin.test.ts b/sdk/src/plugins/CodexCLIOutputPlugin.test.ts index 88559c7d..702d654c 100644 --- a/sdk/src/plugins/CodexCLIOutputPlugin.test.ts +++ b/sdk/src/plugins/CodexCLIOutputPlugin.test.ts @@ -106,7 +106,7 @@ function createCleanContext(tempWorkspace?: string): OutputCleanContext { projects } } - } as OutputCleanContext + } as unknown as OutputCleanContext } function createWriteContext( @@ -155,7 +155,7 @@ function createWriteContext( subAgents, skills } - } as OutputWriteContext + } as unknown as OutputWriteContext } function createProjectCommandPrompt(): CommandPrompt { @@ -178,7 +178,7 @@ function createProjectCommandPrompt(): CommandPrompt { scope: 'project' }, markdownContents: [] - } as CommandPrompt + } as unknown as CommandPrompt } function createCommandPromptWithToolFields(): CommandPrompt { @@ -190,7 +190,7 @@ function createCommandPromptWithToolFields(): CommandPrompt { allowTools: ['shell'], allowedTools: ['shell'] } as unknown as CommandPrompt['yamlFrontMatter'] - } as CommandPrompt + } as unknown as CommandPrompt } function createSubAgentPrompt(scope: 'project' | 'global'): SubAgentPrompt { @@ -260,7 +260,7 @@ function createSkillPrompt( rawContent: '{"mcpServers":{"inspector":{"command":"npx","args":["inspector"]}}}' }, markdownContents: [] - } as SkillPrompt + } as unknown as SkillPrompt } describe('codexCLIOutputPlugin command output', () => { @@ -432,94 +432,6 @@ describe('codexCLIOutputPlugin command output', () => { }) }) - it('keeps codex skill files global when only mcp is project-scoped', async () => { - await withTempCodexDirs('tnmsc-codex-split-scope-project-mcp', async ({workspace, homeDir}) => { - const plugin = new TestCodexCLIOutputPlugin(homeDir) - const writeCtx = createWriteContext( - workspace, - [], - [], - { - outputScopes: { - plugins: { - CodexCLIOutputPlugin: { - skills: 'global', - mcp: 'project' - } - } - } - }, - [ - createSkillPrompt('project', 'inspect-locally'), - createSkillPrompt('global', 'ship-it') - ] - ) - - const declarations = await plugin.declareOutputFiles(writeCtx) - const paths = declarations.map(declaration => declaration.path) - - expect(paths).toContain( - path.join(homeDir, '.codex', 'skills', 'ship-it', 'SKILL.md') - ) - expect(paths).toContain( - path.join(workspace, 'project-a', '.codex', 'skills', 'inspect-locally', 'mcp.json') - ) - expect(paths).toContain( - path.join(workspace, 'project-b', '.codex', 'skills', 'inspect-locally', 'mcp.json') - ) - expect(paths).not.toContain( - path.join(workspace, 'project-a', '.codex', 'skills', 'ship-it', 'SKILL.md') - ) - expect(paths).not.toContain( - path.join(homeDir, '.codex', 'skills', 'inspect-locally', 'mcp.json') - ) - }) - }) - - it('keeps codex skill files project-scoped when only mcp is global-scoped', async () => { - await withTempCodexDirs('tnmsc-codex-split-scope-global-mcp', async ({workspace, homeDir}) => { - const plugin = new TestCodexCLIOutputPlugin(homeDir) - const writeCtx = createWriteContext( - workspace, - [], - [], - { - outputScopes: { - plugins: { - CodexCLIOutputPlugin: { - skills: 'project', - mcp: 'global' - } - } - } - }, - [ - createSkillPrompt('project', 'ship-it'), - createSkillPrompt('global', 'inspect-globally') - ] - ) - - const declarations = await plugin.declareOutputFiles(writeCtx) - const paths = declarations.map(declaration => declaration.path) - - expect(paths).toContain( - path.join(workspace, 'project-a', '.codex', 'skills', 'ship-it', 'SKILL.md') - ) - expect(paths).toContain( - path.join(workspace, 'project-b', '.codex', 'skills', 'ship-it', 'SKILL.md') - ) - expect(paths).toContain( - path.join(homeDir, '.codex', 'skills', 'inspect-globally', 'mcp.json') - ) - expect(paths).not.toContain( - path.join(homeDir, '.codex', 'skills', 'ship-it', 'SKILL.md') - ) - expect(paths).not.toContain( - path.join(workspace, 'project-a', '.codex', 'skills', 'inspect-globally', 'mcp.json') - ) - }) - }) - it('cleans global codex skills while preserving the built-in .system directory', async () => { await withTempCodexDirs('tnmsc-codex-cleanup-skills', async ({homeDir}) => { const plugin = new TestCodexCLIOutputPlugin(homeDir) diff --git a/sdk/src/plugins/CursorOutputPlugin.test.ts b/sdk/src/plugins/CursorOutputPlugin.test.ts index f51677a3..2f17b50b 100644 --- a/sdk/src/plugins/CursorOutputPlugin.test.ts +++ b/sdk/src/plugins/CursorOutputPlugin.test.ts @@ -47,7 +47,7 @@ function createCleanContext(): OutputCleanContext { projects: [] } } - } as OutputCleanContext + } as unknown as OutputCleanContext } function createCommandPrompt(): CommandPrompt { @@ -70,7 +70,7 @@ function createCommandPrompt(): CommandPrompt { scope: 'project' }, markdownContents: [] - } as CommandPrompt + } as unknown as CommandPrompt } function createGlobalMemoryPrompt(): GlobalMemoryPrompt { @@ -87,7 +87,7 @@ function createGlobalMemoryPrompt(): GlobalMemoryPrompt { getAbsolutePath: () => path.resolve('aindex/dist/global.mdx') }, markdownContents: [] - } as GlobalMemoryPrompt + } as unknown as GlobalMemoryPrompt } function createSkillPrompt( @@ -122,7 +122,7 @@ function createSkillPrompt( rawContent: '{"mcpServers":{"inspector":{"command":"npx","args":["inspector"]}}}' }, markdownContents: [] - } as SkillPrompt + } as unknown as SkillPrompt } function createRulePrompt(): RulePrompt { @@ -143,7 +143,7 @@ function createRulePrompt(): RulePrompt { globs: ['src/**'], scope: 'project', markdownContents: [] - } as RulePrompt + } as unknown as RulePrompt } describe('cursorOutputPlugin cleanup', () => { @@ -210,7 +210,7 @@ describe('cursorOutputPlugin cleanup', () => { skills: [createSkillPrompt()], rules: [createRulePrompt()] } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) const paths = declarations.map(declaration => declaration.path) @@ -222,106 +222,6 @@ describe('cursorOutputPlugin cleanup', () => { expect(declarations.every(declaration => declaration.scope === 'project')).toBe(true) }) - it('keeps skill files global when only mcp is project-scoped', async () => { - const workspaceBase = path.resolve('tmp/cursor-split-scope-project-mcp') - const homeDir = path.join(workspaceBase, 'home') - const plugin = new TestCursorOutputPlugin(homeDir) - const ctx = { - logger: createLogger('CursorOutputPlugin', 'error'), - fs, - path, - glob, - dryRun: true, - pluginOptions: { - outputScopes: { - plugins: { - CursorOutputPlugin: { - skills: 'global', - mcp: 'project' - } - } - } - }, - collectedOutputContext: { - workspace: { - directory: { - pathKind: FilePathKind.Absolute, - path: workspaceBase, - getDirectoryName: () => path.basename(workspaceBase) - }, - projects: [{ - name: '__workspace__', - isWorkspaceRootProject: true - }] - }, - skills: [ - createSkillPrompt('project', 'inspect-locally'), - createSkillPrompt('global', 'ship-it') - ] - } - } as OutputWriteContext - - const declarations = await plugin.declareOutputFiles(ctx) - const paths = declarations.map(declaration => declaration.path) - - expect(paths).toContain(path.join(homeDir, '.cursor', 'skills-cursor', 'ship-it', 'SKILL.md')) - expect(paths).toContain(path.join(workspaceBase, '.cursor', 'skills', 'inspect-locally', 'mcp.json')) - expect(paths).toContain(path.join(workspaceBase, '.cursor', 'mcp.json')) - expect(paths).not.toContain(path.join(workspaceBase, '.cursor', 'skills', 'ship-it', 'SKILL.md')) - expect(paths).not.toContain(path.join(homeDir, '.cursor', 'skills-cursor', 'inspect-locally', 'SKILL.md')) - expect(paths).not.toContain(path.join(homeDir, '.cursor', 'mcp.json')) - }) - - it('keeps skill files project-scoped when only mcp is global-scoped', async () => { - const workspaceBase = path.resolve('tmp/cursor-split-scope-global-mcp') - const homeDir = path.join(workspaceBase, 'home') - const plugin = new TestCursorOutputPlugin(homeDir) - const ctx = { - logger: createLogger('CursorOutputPlugin', 'error'), - fs, - path, - glob, - dryRun: true, - pluginOptions: { - outputScopes: { - plugins: { - CursorOutputPlugin: { - skills: 'project', - mcp: 'global' - } - } - } - }, - collectedOutputContext: { - workspace: { - directory: { - pathKind: FilePathKind.Absolute, - path: workspaceBase, - getDirectoryName: () => path.basename(workspaceBase) - }, - projects: [{ - name: '__workspace__', - isWorkspaceRootProject: true - }] - }, - skills: [ - createSkillPrompt('project', 'ship-it'), - createSkillPrompt('global', 'inspect-globally') - ] - } - } as OutputWriteContext - - const declarations = await plugin.declareOutputFiles(ctx) - const paths = declarations.map(declaration => declaration.path) - - expect(paths).toContain(path.join(workspaceBase, '.cursor', 'skills', 'ship-it', 'SKILL.md')) - expect(paths).toContain(path.join(homeDir, '.cursor', 'skills-cursor', 'inspect-globally', 'mcp.json')) - expect(paths).toContain(path.join(homeDir, '.cursor', 'mcp.json')) - expect(paths).not.toContain(path.join(homeDir, '.cursor', 'skills-cursor', 'ship-it', 'SKILL.md')) - expect(paths).not.toContain(path.join(workspaceBase, '.cursor', 'skills', 'inspect-globally', 'SKILL.md')) - expect(paths).not.toContain(path.join(workspaceBase, '.cursor', 'mcp.json')) - }) - it('writes the global prompt to workspace root through the synthetic workspace project', async () => { const workspaceBase = path.resolve('tmp/cursor-workspace-global-prompt') const plugin = new TestCursorOutputPlugin(path.join(workspaceBase, 'home')) @@ -345,7 +245,7 @@ describe('cursorOutputPlugin cleanup', () => { }, globalMemory: createGlobalMemoryPrompt() } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) diff --git a/sdk/src/plugins/EditorConfigOutputPlugin.ts b/sdk/src/plugins/EditorConfigOutputPlugin.ts deleted file mode 100644 index 88038b60..00000000 --- a/sdk/src/plugins/EditorConfigOutputPlugin.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { - OutputFileDeclaration, - OutputWriteContext -} from './plugin-core' -import {AbstractOutputPlugin} from './plugin-core' - -const EDITOR_CONFIG_FILE = '.editorconfig' - -/** - * Output plugin for writing .editorconfig files to project directories. - * Reads EditorConfig files collected by EditorConfigInputCapability. - */ -export class EditorConfigOutputPlugin extends AbstractOutputPlugin { - constructor() { - super('EditorConfigOutputPlugin', { - cleanup: { - delete: { - project: { - files: [EDITOR_CONFIG_FILE] - } - } - }, - capabilities: {} - }) - } - - override async declareOutputFiles(ctx: OutputWriteContext): Promise { - const declarations: OutputFileDeclaration[] = [] - const {projects} = ctx.collectedOutputContext.workspace - const {editorConfigFiles} = ctx.collectedOutputContext - - if (editorConfigFiles == null || editorConfigFiles.length === 0) return declarations - - for (const project of projects) { - const projectDir = project.dirFromWorkspacePath - if (projectDir == null) continue - - for (const config of editorConfigFiles) { - declarations.push({ - path: this.resolvePath(projectDir.basePath, projectDir.path, EDITOR_CONFIG_FILE), - scope: 'project', - source: {content: config.content} - }) - } - } - - return declarations - } - - override async convertContent( - declaration: OutputFileDeclaration, - ctx: OutputWriteContext - ): Promise { - void ctx - const source = declaration.source as {content?: string} - if (source.content == null) throw new Error(`Unsupported declaration source for ${this.name}`) - return source.content - } -} diff --git a/sdk/src/plugins/GenericSkillsOutputPlugin.test.ts b/sdk/src/plugins/GenericSkillsOutputPlugin.test.ts index 092e54f5..f669a559 100644 --- a/sdk/src/plugins/GenericSkillsOutputPlugin.test.ts +++ b/sdk/src/plugins/GenericSkillsOutputPlugin.test.ts @@ -114,64 +114,6 @@ describe('genericSkillsOutputPlugin synthetic workspace project output', () => { ) expect(declarations.every(declaration => declaration.scope === 'project')).toBe(true) }) - - it('writes global mcp.json even when skill files stay project-scoped', async () => { - const workspaceBase = path.resolve('tmp/generic-skills-workspace') - const homeDir = path.resolve('tmp/generic-skills-home') - const plugin = new TestGenericSkillsOutputPlugin(homeDir) - const skills = [ - createSkillPrompt('project', 'ship-it'), - createSkillPrompt('global', 'inspect-globally') - ] - const ctx = createContext(workspaceBase, { - outputScopes: { - plugins: { - GenericSkillsOutputPlugin: { - skills: 'project', - mcp: 'global' - } - } - } - }, skills) - - const declarations = await plugin.declareOutputFiles(ctx) - - expect(declarations.map(declaration => declaration.path)).toContain( - path.join(workspaceBase, '.agents', 'skills', 'ship-it', 'SKILL.md') - ) - expect(declarations.map(declaration => declaration.path)).toContain( - path.join(homeDir, '.agents', 'skills', 'inspect-globally', 'mcp.json') - ) - }) - - it('writes project mcp.json even when skill files stay global-scoped', async () => { - const workspaceBase = path.resolve('tmp/generic-skills-workspace') - const homeDir = path.resolve('tmp/generic-skills-home') - const plugin = new TestGenericSkillsOutputPlugin(homeDir) - const skills = [ - createSkillPrompt('project', 'inspect-locally'), - createSkillPrompt('global', 'ship-it') - ] - const ctx = createContext(workspaceBase, { - outputScopes: { - plugins: { - GenericSkillsOutputPlugin: { - skills: 'global', - mcp: 'project' - } - } - } - }, skills) - - const declarations = await plugin.declareOutputFiles(ctx) - - expect(declarations.map(declaration => declaration.path)).toContain( - path.join(homeDir, '.agents', 'skills', 'ship-it', 'SKILL.md') - ) - expect(declarations.map(declaration => declaration.path)).toContain( - path.join(workspaceBase, '.agents', 'skills', 'inspect-locally', 'mcp.json') - ) - }) }) describe('genericSkillsOutputPlugin cleanup', () => { diff --git a/sdk/src/plugins/JetBrainsAIAssistantCodexOutputPlugin.ts b/sdk/src/plugins/JetBrainsAIAssistantCodexOutputPlugin.ts index 91c0b9c5..fcf73e25 100644 --- a/sdk/src/plugins/JetBrainsAIAssistantCodexOutputPlugin.ts +++ b/sdk/src/plugins/JetBrainsAIAssistantCodexOutputPlugin.ts @@ -8,6 +8,7 @@ import type { ProjectChildrenMemoryPrompt, SkillPrompt } from './plugin-core' + import {Buffer} from 'node:buffer' import * as path from 'node:path' import {AbstractOutputPlugin, filterByProjectConfig, PLUGIN_NAMES} from './plugin-core' diff --git a/sdk/src/plugins/KiroCLIOutputPlugin.test.ts b/sdk/src/plugins/KiroCLIOutputPlugin.test.ts index 7684a263..0bb5c8e3 100644 --- a/sdk/src/plugins/KiroCLIOutputPlugin.test.ts +++ b/sdk/src/plugins/KiroCLIOutputPlugin.test.ts @@ -59,7 +59,7 @@ function createCleanContext( projects: [...projects] } } - } as OutputCleanContext + } as unknown as OutputCleanContext } describe('kiroCLIOutputPlugin cleanup', () => { diff --git a/sdk/src/plugins/OpencodeCLIOutputPlugin.test.ts b/sdk/src/plugins/OpencodeCLIOutputPlugin.test.ts index b97bc408..67c75716 100644 --- a/sdk/src/plugins/OpencodeCLIOutputPlugin.test.ts +++ b/sdk/src/plugins/OpencodeCLIOutputPlugin.test.ts @@ -1,8 +1,9 @@ -import type {OutputCleanContext, OutputWriteContext, SubAgentPrompt} from './plugin-core' +import type {OutputCleanContext, OutputWriteContext, Project, SubAgentPrompt} from './plugin-core' import * as fs from 'node:fs' import * as os from 'node:os' import * as path from 'node:path' import {describe, expect, it} from 'vitest' +import {collectDeletionTargets} from '@/commands/CleanupUtils' import {OpencodeCLIOutputPlugin} from './OpencodeCLIOutputPlugin' import {createLogger, FilePathKind, PromptKind} from './plugin-core' @@ -16,24 +17,32 @@ class TestOpencodeCLIOutputPlugin extends OpencodeCLIOutputPlugin { } } -function createCleanContext(): OutputCleanContext { +function createWorkspaceRootProject(): Project { + return { + name: '__workspace__', + isWorkspaceRootProject: true + } as Project +} + +function createCleanContext( + workspaceBase = path.resolve('.'), + projects: readonly Project[] = [] +): OutputCleanContext { return { logger: createLogger('OpencodeCLIOutputPlugin', 'error'), fs, path, glob: {} as never, dryRun: true, - runtimeTargets: {}, + runtimeTargets: {jetbrainsCodexDirs: []}, collectedOutputContext: { workspace: { directory: { - pathKind: FilePathKind.Relative, - path: '.', - basePath: '.', - getDirectoryName: () => '.', - getAbsolutePath: () => path.resolve('.') + pathKind: FilePathKind.Absolute, + path: workspaceBase, + getDirectoryName: () => path.basename(workspaceBase) }, - projects: [] + projects: [...projects] } } } as unknown as OutputCleanContext @@ -115,4 +124,36 @@ describe('opencodeCLIOutputPlugin cleanup', () => { fs.rmSync(tempHomeDir, {recursive: true, force: true}) } }) + + it('plans cleanup for the project-level .opencode directory itself', async () => { + const tempHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-opencode-home-')) + const workspaceBase = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-opencode-workspace-')) + const projectConfigFile = path.join(workspaceBase, '.opencode', 'opencode.json') + const projectAgentFile = path.join(workspaceBase, '.opencode', 'agents', 'ops-reviewer.md') + const unmanagedProjectFile = path.join(workspaceBase, '.opencode', 'notes.txt') + + fs.mkdirSync(path.dirname(projectAgentFile), {recursive: true}) + fs.writeFileSync(projectConfigFile, '{}\n', 'utf8') + fs.writeFileSync(projectAgentFile, '# reviewer\n', 'utf8') + fs.writeFileSync(unmanagedProjectFile, 'keep nothing\n', 'utf8') + + try { + const plugin = new TestOpencodeCLIOutputPlugin(tempHomeDir) + const cleanCtx = createCleanContext(workspaceBase, [createWorkspaceRootProject()]) + const cleanup = await plugin.declareCleanupPaths(cleanCtx) + const deleteDirs = cleanup.delete + ?.filter(target => target.kind === 'directory') + .map(target => target.path.replaceAll('\\', '/')) + ?? [] + const plan = await collectDeletionTargets([plugin], cleanCtx) + const normalizedDirsToDelete = plan.dirsToDelete.map(target => target.replaceAll('\\', '/')) + + expect(deleteDirs).toContain(path.join(workspaceBase, '.opencode').replaceAll('\\', '/')) + expect(normalizedDirsToDelete).toContain(path.join(workspaceBase, '.opencode').replaceAll('\\', '/')) + expect(plan.violations).toEqual([]) + } finally { + fs.rmSync(tempHomeDir, {recursive: true, force: true}) + fs.rmSync(workspaceBase, {recursive: true, force: true}) + } + }) }) diff --git a/sdk/src/plugins/OpencodeCLIOutputPlugin.ts b/sdk/src/plugins/OpencodeCLIOutputPlugin.ts index 81ed6774..c34dabfd 100644 --- a/sdk/src/plugins/OpencodeCLIOutputPlugin.ts +++ b/sdk/src/plugins/OpencodeCLIOutputPlugin.ts @@ -91,7 +91,7 @@ export class OpencodeCLIOutputPlugin extends AbstractOutputPlugin { delete: { project: { files: [GLOBAL_MEMORY_FILE, '.opencode/opencode.json'], - dirs: ['.opencode/commands', '.opencode/agents', '.opencode/skills', '.opencode/rules'] + dirs: ['.opencode', '.opencode/commands', '.opencode/agents', '.opencode/skills', '.opencode/rules'] }, global: { files: ['.config/opencode/AGENTS.md', '.config/opencode/opencode.json'], diff --git a/sdk/src/plugins/QoderIDEPluginOutputPlugin.test.ts b/sdk/src/plugins/QoderIDEPluginOutputPlugin.test.ts index 9ab63746..3c1fce69 100644 --- a/sdk/src/plugins/QoderIDEPluginOutputPlugin.test.ts +++ b/sdk/src/plugins/QoderIDEPluginOutputPlugin.test.ts @@ -5,16 +5,6 @@ import {describe, expect, it} from 'vitest' import {createLogger, FilePathKind, PromptKind} from './plugin-core' import {QoderIDEPluginOutputPlugin} from './QoderIDEPluginOutputPlugin' -class TestQoderIDEPluginOutputPlugin extends QoderIDEPluginOutputPlugin { - constructor(private readonly testHomeDir: string) { - super() - } - - protected override getHomeDir(): string { - return this.testHomeDir - } -} - function createWorkspaceRootPrompt(): ProjectRootMemoryPrompt { return { type: PromptKind.ProjectRootMemory, @@ -83,7 +73,7 @@ function createGlobalMemoryPrompt(): GlobalMemoryPrompt { getAbsolutePath: () => path.resolve('aindex/dist/global.mdx') }, markdownContents: [] - } as GlobalMemoryPrompt + } as unknown as GlobalMemoryPrompt } function createCommandPrompt(): CommandPrompt { @@ -106,7 +96,7 @@ function createCommandPrompt(): CommandPrompt { scope: 'project' }, markdownContents: [] - } as CommandPrompt + } as unknown as CommandPrompt } function createSkillPrompt( @@ -141,7 +131,7 @@ function createSkillPrompt( rawContent: '{"mcpServers":{"inspector":{"command":"npx","args":["inspector"]}}}' }, markdownContents: [] - } as SkillPrompt + } as unknown as SkillPrompt } function createRulePrompt(scope: 'project' | 'global' = 'project'): RulePrompt { @@ -162,7 +152,7 @@ function createRulePrompt(scope: 'project' | 'global' = 'project'): RulePrompt { globs: ['src/**'], scope, markdownContents: [] - } as RulePrompt + } as unknown as RulePrompt } describe('qoderIDEPluginOutputPlugin synthetic workspace project output', () => { @@ -192,7 +182,7 @@ describe('qoderIDEPluginOutputPlugin synthetic workspace project output', () => skills: [createSkillPrompt()], rules: [createRulePrompt('project')] } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) const paths = declarations.map(declaration => declaration.path) @@ -251,7 +241,7 @@ describe('qoderIDEPluginOutputPlugin synthetic workspace project output', () => globalMemory: createGlobalMemoryPrompt(), rules: [createRulePrompt('project')] } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) const paths = declarations.map(declaration => declaration.path) @@ -266,102 +256,6 @@ describe('qoderIDEPluginOutputPlugin synthetic workspace project output', () => expect(paths).toContain(path.join(workspaceBase, 'project-a', '.qoder', 'rules', 'rule-ops-guard.md')) }) - it('keeps skill files global when only mcp is project-scoped', async () => { - const workspaceBase = path.resolve('tmp/qoder-split-scope-project-mcp') - const homeDir = path.join(workspaceBase, 'home') - const plugin = new TestQoderIDEPluginOutputPlugin(homeDir) - const ctx = { - logger: createLogger('QoderIDEPluginOutputPlugin', 'error'), - fs, - path, - glob: {} as never, - dryRun: true, - pluginOptions: { - outputScopes: { - plugins: { - QoderIDEPluginOutputPlugin: { - skills: 'global', - mcp: 'project' - } - } - } - }, - collectedOutputContext: { - workspace: { - directory: { - pathKind: FilePathKind.Absolute, - path: workspaceBase, - getDirectoryName: () => path.basename(workspaceBase) - }, - projects: [{ - name: '__workspace__', - isWorkspaceRootProject: true - }] - }, - skills: [ - createSkillPrompt('project', 'inspect-locally'), - createSkillPrompt('global', 'ship-it') - ] - } - } as OutputWriteContext - - const declarations = await plugin.declareOutputFiles(ctx) - const paths = declarations.map(declaration => declaration.path) - - expect(paths).toContain(path.join(homeDir, '.qoder', 'skills', 'ship-it', 'SKILL.md')) - expect(paths).toContain(path.join(workspaceBase, '.qoder', 'skills', 'inspect-locally', 'mcp.json')) - expect(paths).not.toContain(path.join(workspaceBase, '.qoder', 'skills', 'ship-it', 'SKILL.md')) - expect(paths).not.toContain(path.join(homeDir, '.qoder', 'skills', 'inspect-locally', 'SKILL.md')) - }) - - it('keeps skill files project-scoped when only mcp is global-scoped', async () => { - const workspaceBase = path.resolve('tmp/qoder-split-scope-global-mcp') - const homeDir = path.join(workspaceBase, 'home') - const plugin = new TestQoderIDEPluginOutputPlugin(homeDir) - const ctx = { - logger: createLogger('QoderIDEPluginOutputPlugin', 'error'), - fs, - path, - glob: {} as never, - dryRun: true, - pluginOptions: { - outputScopes: { - plugins: { - QoderIDEPluginOutputPlugin: { - skills: 'project', - mcp: 'global' - } - } - } - }, - collectedOutputContext: { - workspace: { - directory: { - pathKind: FilePathKind.Absolute, - path: workspaceBase, - getDirectoryName: () => path.basename(workspaceBase) - }, - projects: [{ - name: '__workspace__', - isWorkspaceRootProject: true - }] - }, - skills: [ - createSkillPrompt('project', 'ship-it'), - createSkillPrompt('global', 'inspect-globally') - ] - } - } as OutputWriteContext - - const declarations = await plugin.declareOutputFiles(ctx) - const paths = declarations.map(declaration => declaration.path) - - expect(paths).toContain(path.join(workspaceBase, '.qoder', 'skills', 'ship-it', 'SKILL.md')) - expect(paths).toContain(path.join(homeDir, '.qoder', 'skills', 'inspect-globally', 'mcp.json')) - expect(paths).not.toContain(path.join(homeDir, '.qoder', 'skills', 'ship-it', 'SKILL.md')) - expect(paths).not.toContain(path.join(workspaceBase, '.qoder', 'skills', 'inspect-globally', 'SKILL.md')) - }) - it('writes the global prompt to workspace root through the synthetic workspace project', async () => { const workspaceBase = path.resolve('tmp/qoder-workspace-global-prompt') const plugin = new QoderIDEPluginOutputPlugin() @@ -385,7 +279,7 @@ describe('qoderIDEPluginOutputPlugin synthetic workspace project output', () => }, globalMemory: createGlobalMemoryPrompt() } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) diff --git a/sdk/src/plugins/ReadmeMdConfigFileOutputPlugin.ts b/sdk/src/plugins/ReadmeMdConfigFileOutputPlugin.ts index 0ae873d9..19699998 100644 --- a/sdk/src/plugins/ReadmeMdConfigFileOutputPlugin.ts +++ b/sdk/src/plugins/ReadmeMdConfigFileOutputPlugin.ts @@ -7,19 +7,47 @@ import type { import * as path from 'node:path' import {AbstractOutputPlugin, README_FILE_KIND_MAP} from './plugin-core' +const EDITOR_CONFIG_FILE = '.editorconfig' + function resolveOutputFileName(fileKind?: ReadmeFileKind): string { return README_FILE_KIND_MAP[fileKind ?? 'Readme'].out } +function appendEditorConfigDeclarations( + declarations: OutputFileDeclaration[], + ctx: OutputWriteContext +): void { + const {projects} = ctx.collectedOutputContext.workspace + const {editorConfigFiles} = ctx.collectedOutputContext + + if (editorConfigFiles == null || editorConfigFiles.length === 0) return + + for (const project of projects) { + const projectDir = project.dirFromWorkspacePath + if (projectDir == null) continue + + for (const config of editorConfigFiles) { + declarations.push({ + path: path.join(projectDir.basePath, projectDir.path, EDITOR_CONFIG_FILE), + scope: 'project', + source: {content: config.content} + }) + } + } +} + /** - * Output plugin for writing readme-family files to project directories. - * Reads README prompts collected by ReadmeMdInputCapability and writes them - * to the corresponding project directories. + * Output plugin for writing readme-family files and .editorconfig files to + * project directories. + * Reads README prompts collected by ReadmeMdInputCapability and EditorConfig + * files collected by EditorConfigInputCapability, then writes them to the + * corresponding project directories. * * Output mapping: * - fileKind=Readme → README.md * - fileKind=CodeOfConduct → CODE_OF_CONDUCT.md * - fileKind=Security → SECURITY.md + * - editorConfigFiles → .editorconfig * * Supports: * - Root files (written to project root) @@ -34,7 +62,7 @@ export class ReadmeMdConfigFileOutputPlugin extends AbstractOutputPlugin { cleanup: { delete: { project: { - files: ['README.md', 'CODE_OF_CONDUCT.md', 'SECURITY.md'] + files: ['README.md', 'CODE_OF_CONDUCT.md', 'SECURITY.md', EDITOR_CONFIG_FILE] } } }, @@ -45,18 +73,21 @@ export class ReadmeMdConfigFileOutputPlugin extends AbstractOutputPlugin { override async declareOutputFiles(ctx: OutputWriteContext): Promise { const declarations: OutputFileDeclaration[] = [] const {readmePrompts} = ctx.collectedOutputContext - if (readmePrompts == null || readmePrompts.length === 0) return declarations - for (const readme of readmePrompts) { - const outputFileName = resolveOutputFileName(readme.fileKind) - const filePath = path.join(readme.targetDir.basePath, readme.targetDir.path, outputFileName) - declarations.push({ - path: filePath, - scope: 'project', - source: {content: readme.content as string} - }) + if (readmePrompts != null) { + for (const readme of readmePrompts) { + const outputFileName = resolveOutputFileName(readme.fileKind) + const filePath = path.join(readme.targetDir.basePath, readme.targetDir.path, outputFileName) + declarations.push({ + path: filePath, + scope: 'project', + source: {content: readme.content as string} + }) + } } + appendEditorConfigDeclarations(declarations, ctx) + return declarations } diff --git a/sdk/src/plugins/TraeIDEOutputPlugin.test.ts b/sdk/src/plugins/TraeIDEOutputPlugin.test.ts index 54835979..775f381d 100644 --- a/sdk/src/plugins/TraeIDEOutputPlugin.test.ts +++ b/sdk/src/plugins/TraeIDEOutputPlugin.test.ts @@ -61,7 +61,7 @@ describe('traeIDEOutputPlugin steering rule output', () => { ] } } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) const steering = declarations.find(d => d.source != null && (d.source as {kind?: string}).kind === 'steeringRule') @@ -111,7 +111,7 @@ describe('traeIDEOutputPlugin steering rule output', () => { ] } } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) const steering = declarations.find(d => d.source != null && (d.source as {kind?: string}).kind === 'steeringRule') diff --git a/sdk/src/plugins/WarpIDEOutputPlugin.test.ts b/sdk/src/plugins/WarpIDEOutputPlugin.test.ts index cfd2b31e..ddf1d92b 100644 --- a/sdk/src/plugins/WarpIDEOutputPlugin.test.ts +++ b/sdk/src/plugins/WarpIDEOutputPlugin.test.ts @@ -19,7 +19,7 @@ function createGlobalMemoryPrompt(): GlobalMemoryPrompt { getAbsolutePath: () => path.resolve('aindex/dist/global.mdx') }, markdownContents: [] - } as GlobalMemoryPrompt + } as unknown as GlobalMemoryPrompt } function createWorkspaceRootPrompt(): ProjectRootMemoryPrompt { @@ -62,7 +62,7 @@ describe('warpIDEOutputPlugin workspace prompt support', () => { }, globalMemory: createGlobalMemoryPrompt() } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(ctx) const workspaceDeclaration = declarations.find(declaration => declaration.path === path.join(workspaceBase, 'WARP.md')) diff --git a/sdk/src/plugins/WindsurfOutputPlugin.test.ts b/sdk/src/plugins/WindsurfOutputPlugin.test.ts index 2e58d54a..c35daf7e 100644 --- a/sdk/src/plugins/WindsurfOutputPlugin.test.ts +++ b/sdk/src/plugins/WindsurfOutputPlugin.test.ts @@ -1,4 +1,4 @@ -import type {CommandPrompt, OutputCleanContext, OutputScopeSelection, OutputWriteContext, Project, RulePrompt, SkillPrompt} from './plugin-core' +import type {CommandPrompt, OutputCleanContext, OutputWriteContext, Project, RulePrompt, SkillPrompt} from './plugin-core' import * as fs from 'node:fs' import * as os from 'node:os' import * as path from 'node:path' @@ -38,7 +38,7 @@ function createCommandPrompt(scope: 'project' | 'global', seriName: string): Com scope }, markdownContents: [] - } as CommandPrompt + } as unknown as CommandPrompt } function createSkillPrompt(scope: 'project' | 'global', seriName: string): SkillPrompt { @@ -62,7 +62,7 @@ function createSkillPrompt(scope: 'project' | 'global', seriName: string): Skill scope }, markdownContents: [] - } as SkillPrompt + } as unknown as SkillPrompt } function createRulePrompt(scope: 'project' | 'global'): RulePrompt { @@ -83,7 +83,7 @@ function createRulePrompt(scope: 'project' | 'global'): RulePrompt { globs: ['src/**'], scope, markdownContents: [] - } as RulePrompt + } as unknown as RulePrompt } function createProject(workspaceBase: string, name: string, includeSeries: readonly string[], promptSource = false): Project { @@ -114,11 +114,7 @@ function createWriteContext( workspaceBase: string, projects: readonly Project[], commands: readonly CommandPrompt[], - skills: readonly SkillPrompt[], - scopeOverrides: { - readonly commands: OutputScopeSelection - readonly skills: OutputScopeSelection - } + skills: readonly SkillPrompt[] ): OutputWriteContext { return { logger: createLogger('WindsurfOutputPlugin', 'error'), @@ -126,13 +122,7 @@ function createWriteContext( path, glob: {} as never, dryRun: true, - pluginOptions: { - outputScopes: { - plugins: { - WindsurfOutputPlugin: scopeOverrides - } - } - }, + pluginOptions: {}, collectedOutputContext: { workspace: { directory: { @@ -145,7 +135,7 @@ function createWriteContext( commands, skills } - } as OutputWriteContext + } as unknown as OutputWriteContext } function createCleanContext(workspaceBase = path.resolve('tmp/windsurf-clean')): OutputCleanContext { @@ -166,7 +156,7 @@ function createCleanContext(workspaceBase = path.resolve('tmp/windsurf-clean')): projects: [createWorkspaceRootProject()] } } - } as OutputCleanContext + } as unknown as OutputCleanContext } describe('windsurfOutputPlugin synthetic workspace project output', () => { @@ -180,8 +170,7 @@ describe('windsurfOutputPlugin synthetic workspace project output', () => { createProject(workspaceBase, 'beta-project', ['beta']) ], [createCommandPrompt('project', 'alpha')], - [createSkillPrompt('project', 'alpha')], - {commands: 'project', skills: 'project'} + [createSkillPrompt('project', 'alpha')] ) const declarations = await plugin.declareOutputFiles(context) @@ -200,8 +189,7 @@ describe('windsurfOutputPlugin synthetic workspace project output', () => { workspaceBase, [createWorkspaceRootProject()], [createCommandPrompt('project', 'alpha')], - [createSkillPrompt('project', 'alpha')], - {commands: 'project', skills: 'project'} + [createSkillPrompt('project', 'alpha')] ) const declarations = await plugin.declareOutputFiles(context) @@ -232,7 +220,7 @@ describe('windsurfOutputPlugin synthetic workspace project output', () => { }, rules: [createRulePrompt('project')] } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(context) @@ -276,7 +264,7 @@ describe('windsurfOutputPlugin synthetic workspace project output', () => { } ] } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(context) const codeIgnoreDeclaration = declarations.find( @@ -334,7 +322,7 @@ describe('windsurfOutputPlugin synthetic workspace project output', () => { } ] } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(context) @@ -382,7 +370,7 @@ describe('windsurfOutputPlugin synthetic workspace project output', () => { } ] } - } as OutputWriteContext + } as unknown as OutputWriteContext const declarations = await plugin.declareOutputFiles(context) diff --git a/sdk/src/plugins/ide-config-output.test.ts b/sdk/src/plugins/ide-config-output.test.ts index 1fc47b65..b3b7c767 100644 --- a/sdk/src/plugins/ide-config-output.test.ts +++ b/sdk/src/plugins/ide-config-output.test.ts @@ -6,9 +6,9 @@ import type { import * as fs from 'node:fs' import * as path from 'node:path' import {describe, expect, it} from 'vitest' -import {EditorConfigOutputPlugin} from './EditorConfigOutputPlugin' import {JetBrainsIDECodeStyleConfigOutputPlugin} from './JetBrainsIDECodeStyleConfigOutputPlugin' import {createLogger, FilePathKind, IDEKind} from './plugin-core' +import {ReadmeMdConfigFileOutputPlugin} from './ReadmeMdConfigFileOutputPlugin' import {VisualStudioCodeIDEConfigOutputPlugin} from './VisualStudioCodeIDEConfigOutputPlugin' import {ZedIDEConfigOutputPlugin} from './ZedIDEConfigOutputPlugin' @@ -137,22 +137,36 @@ function createWriteContext(workspaceBase: string): OutputWriteContext { ) ] } - } as OutputWriteContext + } as unknown as OutputWriteContext } describe('ide config output plugins', () => { - it('includes the prompt source project for editorconfig output', async () => { + it('includes the prompt source project for editorconfig output via the readme plugin', async () => { const workspaceBase = path.resolve('tmp/ide-output-editorconfig') - const plugin = new EditorConfigOutputPlugin() - const declarations = await plugin.declareOutputFiles( - createWriteContext(workspaceBase) - ) + const plugin = new ReadmeMdConfigFileOutputPlugin() + const ctx = createWriteContext(workspaceBase) + const declarations = await plugin.declareOutputFiles(ctx) + const cleanup = await plugin.declareCleanupPaths(ctx) const paths = declarations.map(declaration => declaration.path) expect(paths).toEqual([ path.join(workspaceBase, 'aindex', '.editorconfig'), path.join(workspaceBase, 'memory-sync', '.editorconfig') ]) + expect(cleanup.delete).toEqual(expect.arrayContaining([ + { + kind: 'file', + label: 'delete.project', + path: path.join(workspaceBase, 'aindex', '.editorconfig'), + scope: 'project' + }, + { + kind: 'file', + label: 'delete.project', + path: path.join(workspaceBase, 'memory-sync', '.editorconfig'), + scope: 'project' + } + ])) }) it('includes the prompt source project for vscode output', async () => { diff --git a/sdk/src/plugins/plugin-core/AbstractOutputPlugin.frontmatter.test.ts b/sdk/src/plugins/plugin-core/AbstractOutputPlugin.frontmatter.test.ts index e0393c18..293f931c 100644 --- a/sdk/src/plugins/plugin-core/AbstractOutputPlugin.frontmatter.test.ts +++ b/sdk/src/plugins/plugin-core/AbstractOutputPlugin.frontmatter.test.ts @@ -67,7 +67,7 @@ function createWriteContext(blankLineAfter?: boolean): OutputWriteContext { projects: [] } } - } as OutputWriteContext + } as unknown as OutputWriteContext } function createCommandPrompt(): CommandPrompt { @@ -90,7 +90,7 @@ function createCommandPrompt(): CommandPrompt { description: 'Build command' }, markdownContents: [] - } as CommandPrompt + } as unknown as CommandPrompt } function createSubAgentPrompt(): SubAgentPrompt { @@ -137,7 +137,7 @@ function createSkillPrompt(): SkillPrompt { description: 'Ship release' }, markdownContents: [] - } as SkillPrompt + } as unknown as SkillPrompt } function createRulePrompt(): RulePrompt { @@ -162,7 +162,7 @@ function createRulePrompt(): RulePrompt { description: 'Rule desc' }, markdownContents: [] - } as RulePrompt + } as unknown as RulePrompt } describe('abstract output plugin front matter formatting', () => { diff --git a/sdk/src/plugins/plugin-core/AbstractOutputPlugin.subagents.test.ts b/sdk/src/plugins/plugin-core/AbstractOutputPlugin.subagents.test.ts index 79fe1470..03c93a35 100644 --- a/sdk/src/plugins/plugin-core/AbstractOutputPlugin.subagents.test.ts +++ b/sdk/src/plugins/plugin-core/AbstractOutputPlugin.subagents.test.ts @@ -70,7 +70,7 @@ function createWriteContext(subAgents: readonly SubAgentPrompt[]): OutputWriteCo }, subAgents } - } as OutputWriteContext + } as unknown as OutputWriteContext } describe('abstract output plugin subagent naming', () => { diff --git a/sdk/src/plugins/plugin-core/AbstractOutputPlugin.ts b/sdk/src/plugins/plugin-core/AbstractOutputPlugin.ts index 37fcf17f..f27125c6 100644 --- a/sdk/src/plugins/plugin-core/AbstractOutputPlugin.ts +++ b/sdk/src/plugins/plugin-core/AbstractOutputPlugin.ts @@ -571,8 +571,8 @@ export abstract class AbstractOutputPlugin extends AbstractPlugin implements Out } protected getXdgConfigHomeDir(): string { - const xdgConfigHome = process.env['XDG_CONFIG_HOME'] - if (typeof xdgConfigHome === 'string' && xdgConfigHome.trim().length > 0) { + const xdgConfigHome = process.env['XDG_CONFIG_HOME']?.trim() + if (xdgConfigHome != null && xdgConfigHome.length > 0) { return xdgConfigHome } return path.join(this.getHomeDir(), '.config') @@ -960,32 +960,14 @@ export abstract class AbstractOutputPlugin extends AbstractPlugin implements Out }) } - protected getCommandSeriesOptions(ctx: OutputWriteContext): CommandSeriesPluginOverride { - const globalOptions = ctx.pluginOptions?.commandSeriesOptions - const pluginOverride = globalOptions?.pluginOverrides?.[this.name] - - const includeSeriesPrefix = pluginOverride?.includeSeriesPrefix ?? globalOptions?.includeSeriesPrefix // Only include properties that have defined values to satisfy exactOptionalPropertyTypes // Plugin-specific overrides take precedence over global settings - const seriesSeparator = pluginOverride?.seriesSeparator - - if (includeSeriesPrefix != null && seriesSeparator != null) { - return {includeSeriesPrefix, seriesSeparator} - } // Build result object conditionally to avoid assigning undefined to readonly properties - if (includeSeriesPrefix != null) return {includeSeriesPrefix} - if (seriesSeparator != null) return {seriesSeparator} + protected getCommandSeriesOptions(): CommandSeriesPluginOverride { return {} } - protected getTransformOptionsFromContext(ctx: OutputWriteContext, additionalOptions?: CommandNameTransformOptions): CommandNameTransformOptions { - const seriesOptions = this.getCommandSeriesOptions(ctx) - - const includeSeriesPrefix = seriesOptions.includeSeriesPrefix ?? additionalOptions?.includeSeriesPrefix // Only include properties that have defined values to satisfy exactOptionalPropertyTypes // Merge: additionalOptions (plugin defaults) <- seriesOptions (config overrides) - const seriesSeparator = seriesOptions.seriesSeparator ?? additionalOptions?.seriesSeparator - - if (includeSeriesPrefix != null && seriesSeparator != null) { - return {includeSeriesPrefix, seriesSeparator} - } // Build result object conditionally to avoid assigning undefined to readonly properties - if (includeSeriesPrefix != null) return {includeSeriesPrefix} - if (seriesSeparator != null) return {seriesSeparator} + protected getTransformOptionsFromContext(_ctx: OutputWriteContext, additionalOptions?: CommandNameTransformOptions): CommandNameTransformOptions { + if (additionalOptions?.includeSeriesPrefix != null) { + return {includeSeriesPrefix: additionalOptions.includeSeriesPrefix} + } return {} } @@ -1098,8 +1080,10 @@ export abstract class AbstractOutputPlugin extends AbstractPlugin implements Out }) } - protected getTopicScopeOverride(ctx: OutputPluginContext | OutputWriteContext, topic: OutputScopeTopic): OutputScopeSelection | undefined { - return ctx.pluginOptions?.outputScopes?.plugins?.[this.name]?.[topic] + protected getTopicScopeOverride(_ctx: OutputPluginContext | OutputWriteContext, _topic: OutputScopeTopic): OutputScopeSelection | undefined { + void _ctx + void _topic + return void 0 } protected buildSkillFrontMatter(skill: SkillPrompt, options?: SkillFrontMatterOptions): Record { diff --git a/sdk/src/plugins/plugin-core/ConfigTypes.schema.ts b/sdk/src/plugins/plugin-core/ConfigTypes.schema.ts index b6f629cd..d3b442a3 100644 --- a/sdk/src/plugins/plugin-core/ConfigTypes.schema.ts +++ b/sdk/src/plugins/plugin-core/ConfigTypes.schema.ts @@ -80,6 +80,19 @@ export const ZOutputScopeOptions = z.object({plugins: z.record(z.string(), ZPlug */ export const ZFrontMatterOptions = z.object({blankLineAfter: z.boolean().optional()}) +export const DEFAULT_CODE_STYLE_INDENT = 'space' as const +export const DEFAULT_CODE_STYLE_TAB_SIZE = 2 as const +export const DEFAULT_CODE_STYLES_OPTIONS = { + indent: DEFAULT_CODE_STYLE_INDENT, + tabSize: DEFAULT_CODE_STYLE_TAB_SIZE +} as const + +export const ZCodeStyleIndent = z.enum(['tab', 'space']) +export const ZCodeStylesOptions = z.object({ + indent: ZCodeStyleIndent.default(DEFAULT_CODE_STYLES_OPTIONS.indent), + tabSize: z.number().int().positive().default(DEFAULT_CODE_STYLES_OPTIONS.tabSize) +}).catchall(z.unknown()) + export const ZProtectionMode = z.enum(['direct', 'recursive']) export const ZProtectionRuleMatcher = z.enum(['path', 'glob']) @@ -99,6 +112,8 @@ export const ZWindowsOptions = z.object({ wsl2: ZWindowsWsl2Options.optional() }) +export const ZPluginsConfig = z.record(z.string(), z.boolean()) + /** * Zod schema for user profile information. */ @@ -119,9 +134,11 @@ export const ZUserConfigFile = z.object({ commandSeriesOptions: ZCommandSeriesOptions.optional(), outputScopes: ZOutputScopeOptions.optional(), frontMatter: ZFrontMatterOptions.optional(), + codeStyles: ZCodeStylesOptions.optional(), cleanupProtection: ZCleanupProtectionOptions.optional(), windows: ZWindowsOptions.optional(), - profile: ZUserProfile.optional() + profile: ZUserProfile.optional(), + plugins: ZPluginsConfig.optional() }) /** @@ -164,6 +181,8 @@ export type OutputScopeSelection = z.infer export type PluginOutputScopeTopics = z.infer export type OutputScopeOptions = z.infer export type FrontMatterOptions = z.infer +export type CodeStyleIndent = z.infer +export type CodeStylesOptions = z.infer export type ProtectionMode = z.infer export type ProtectionRuleMatcher = z.infer export type CleanupProtectionRule = z.infer @@ -171,12 +190,33 @@ export type CleanupProtectionOptions = z.infer export type StringOrStringArray = z.infer export type WindowsWsl2Options = z.infer export type WindowsOptions = z.infer +export type PluginsConfig = z.infer export type UserConfigFile = z.infer export type McpProjectConfig = z.infer export type TypeSeriesConfig = z.infer export type ProjectConfig = z.infer export type ConfigLoaderOptions = z.infer +export function buildDefaultCodeStylesOptions(): CodeStylesOptions { + return {...DEFAULT_CODE_STYLES_OPTIONS} +} + +export function mergeCodeStylesOptions( + base: CodeStylesOptions = buildDefaultCodeStylesOptions(), + override?: Partial +): CodeStylesOptions { + if (override == null) return {...base} + + const definedOverride = Object.fromEntries( + Object.entries(override).filter(([, value]) => value !== void 0) + ) as Partial + + return { + ...base, + ...definedOverride + } +} + /** * Result of loading a config file. */ diff --git a/sdk/src/plugins/plugin-core/GlobalScopeCollector.test.ts b/sdk/src/plugins/plugin-core/GlobalScopeCollector.test.ts new file mode 100644 index 00000000..7082c398 --- /dev/null +++ b/sdk/src/plugins/plugin-core/GlobalScopeCollector.test.ts @@ -0,0 +1,52 @@ +import {describe, expect, it} from 'vitest' +import {GlobalScopeCollector} from './GlobalScopeCollector' + +describe('global scope collector', () => { + it('injects default codeStyles when the user config omits them', () => { + const collector = new GlobalScopeCollector() + + const scope = collector.collect() + + expect(scope.codeStyles).toEqual({ + indent: 'space', + tabSize: 2 + }) + }) + + it('collects codeStyles from the user config file', () => { + const collector = new GlobalScopeCollector({ + userConfig: { + codeStyles: { + indent: 'space', + tabSize: 2, + quoteStyle: 'single' + } + } + }) + + const scope = collector.collect() + + expect(scope.codeStyles).toEqual({ + indent: 'space', + tabSize: 2, + quoteStyle: 'single' + }) + }) + + it('merges default codeStyles with partial user config values', () => { + const collector = new GlobalScopeCollector({ + userConfig: { + codeStyles: { + tabSize: 4 + } + } + }) + + const scope = collector.collect() + + expect(scope.codeStyles).toEqual({ + indent: 'space', + tabSize: 4 + }) + }) +}) diff --git a/sdk/src/plugins/plugin-core/GlobalScopeCollector.ts b/sdk/src/plugins/plugin-core/GlobalScopeCollector.ts index 2e6157b8..21f8d1e8 100644 --- a/sdk/src/plugins/plugin-core/GlobalScopeCollector.ts +++ b/sdk/src/plugins/plugin-core/GlobalScopeCollector.ts @@ -1,10 +1,11 @@ import type {EvaluationScope} from '@truenine/md-compiler' -import type {EnvironmentContext, MdComponent, MdxGlobalScope, OsInfo, ToolReferences, UserProfile} from '@truenine/md-compiler/globals' // Collects and manages global scope variables for MDX expression evaluation. // src/scope/GlobalScopeCollector.ts -import type {UserConfigFile} from './types' +import type {CodeStylePreferences, EnvironmentContext, MdComponent, MdxGlobalScope, OsInfo, ToolReferences, UserProfile} from '@truenine/md-compiler/globals' // Collects and manages global scope variables for MDX expression evaluation. // src/scope/GlobalScopeCollector.ts +import type {PluginOptions, UserConfigFile} from './types' import * as os from 'node:os' import process from 'node:process' import {OsKind, ShellKind, ToolPresets} from '@truenine/md-compiler/globals' import {getEffectiveHomeDir} from '@/runtime-environment' +import {buildDefaultCodeStylesOptions, mergeCodeStylesOptions} from './ConfigTypes.schema' /** * Tool preset names supported by GlobalScopeCollector @@ -17,6 +18,8 @@ export type ToolPresetName = keyof typeof ToolPresets export interface GlobalScopeCollectorOptions { /** User configuration file */ readonly userConfig?: UserConfigFile | undefined + /** Resolved user config options */ + readonly userConfigOptions?: Pick | undefined /** Tool preset to use (default: 'default') */ readonly toolPreset?: ToolPresetName | undefined } @@ -27,10 +30,12 @@ export interface GlobalScopeCollectorOptions { */ export class GlobalScopeCollector { private readonly userConfig: UserConfigFile | undefined + private readonly userConfigOptions: Pick | undefined private readonly toolPreset: ToolPresetName constructor(options: GlobalScopeCollectorOptions = {}) { this.userConfig = options.userConfig + this.userConfigOptions = options.userConfigOptions this.toolPreset = options.toolPreset ?? 'default' } @@ -39,6 +44,7 @@ export class GlobalScopeCollector { os: this.collectOsInfo(), env: this.collectEnvContext(), profile: this.collectProfile(), + codeStyles: this.collectCodeStyles(), tool: this.collectToolReferences(), Md: this.createMdComponent() } @@ -96,6 +102,11 @@ export class GlobalScopeCollector { return {} } + private collectCodeStyles(): CodeStylePreferences { + const resolvedCodeStyles = this.userConfigOptions?.codeStyles ?? this.userConfig?.codeStyles + return mergeCodeStylesOptions(buildDefaultCodeStylesOptions(), resolvedCodeStyles) as CodeStylePreferences + } + private collectToolReferences(): ToolReferences { const defaults: ToolReferences = {...ToolPresets.default} if (this.toolPreset === 'claudeCode') return {...defaults, ...ToolPresets.claudeCode} @@ -134,7 +145,7 @@ export interface ScopeRegistration { export enum ScopePriority { /** System default values (os, default tool) */ SystemDefault = 0, - /** Values from configuration file (profile, custom tool) */ + /** Values from configuration file (profile, codeStyles, custom tool) */ UserConfig = 10, /** Values registered by plugins */ PluginRegistered = 20, @@ -177,6 +188,7 @@ export class ScopeRegistry { result['os'] = {...this.globalScope.os} result['env'] = {...this.globalScope.env} result['profile'] = {...this.globalScope.profile} + result['codeStyles'] = {...this.globalScope.codeStyles} result['tool'] = {...this.globalScope.tool} } diff --git a/sdk/src/plugins/plugin-core/constants.ts b/sdk/src/plugins/plugin-core/constants.ts index a1e5c7c0..b4704940 100644 --- a/sdk/src/plugins/plugin-core/constants.ts +++ b/sdk/src/plugins/plugin-core/constants.ts @@ -29,7 +29,6 @@ export const PLUGIN_NAMES = { ReadmeOutput: 'ReadmeMdConfigFileOutputPlugin', VSCodeOutput: 'VisualStudioCodeIDEConfigOutputPlugin', ZedOutput: 'ZedIDEConfigOutputPlugin', - EditorConfigOutput: 'EditorConfigOutputPlugin', AntigravityOutput: 'AntigravityOutputPlugin' } as const diff --git a/sdk/src/plugins/plugin-core/plugin.enablement.test.ts b/sdk/src/plugins/plugin-core/plugin.enablement.test.ts new file mode 100644 index 00000000..3a8b05f8 --- /dev/null +++ b/sdk/src/plugins/plugin-core/plugin.enablement.test.ts @@ -0,0 +1,68 @@ +import type {OutputPlugin, PluginOptions} from './plugin' +import {describe, expect, it} from 'vitest' +import {PluginKind} from './enums' +import {isOutputPluginEnabled} from './plugin' + +function createOutputPlugin(name: string): OutputPlugin { + return { + name, + type: PluginKind.Output, + log: { + trace: () => {}, + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + fatal: () => {} + }, + declarativeOutput: true, + outputCapabilities: {}, + async declareOutputFiles() { + return [] + }, + async convertContent() { + return '' + } + } +} + +describe('isOutputPluginEnabled', () => { + it('keeps git and readme enabled by default', () => { + expect(isOutputPluginEnabled(createOutputPlugin('AgentsOutputPlugin'))).toBe(false) + expect(isOutputPluginEnabled(createOutputPlugin('CodexCLIOutputPlugin'))).toBe(false) + expect(isOutputPluginEnabled(createOutputPlugin('GitExcludeOutputPlugin'))).toBe(true) + expect(isOutputPluginEnabled(createOutputPlugin('ReadmeMdConfigFileOutputPlugin'))).toBe(true) + expect(isOutputPluginEnabled(createOutputPlugin('TraeIDEOutputPlugin'))).toBe(false) + expect(isOutputPluginEnabled(createOutputPlugin('ClaudeCodeCLIOutputPlugin'))).toBe(false) + }) + + it('enables a plugin when the plugins config explicitly sets it to true', () => { + const pluginOptions: PluginOptions = { + plugins: { + trae: true + } + } + + expect(isOutputPluginEnabled(createOutputPlugin('TraeIDEOutputPlugin'), pluginOptions)).toBe(true) + }) + + it('lets the new git key explicitly disable git output', () => { + const pluginOptions: PluginOptions = { + plugins: { + git: false + } + } + + expect(isOutputPluginEnabled(createOutputPlugin('GitExcludeOutputPlugin'), pluginOptions)).toBe(false) + }) + + it('keeps opt-in plugins disabled when explicitly set to false', () => { + const pluginOptions: PluginOptions = { + plugins: { + codex: false + } + } + + expect(isOutputPluginEnabled(createOutputPlugin('CodexCLIOutputPlugin'), pluginOptions)).toBe(false) + }) +}) diff --git a/sdk/src/plugins/plugin-core/plugin.outputScopes.validation.test.ts b/sdk/src/plugins/plugin-core/plugin.outputScopes.validation.test.ts deleted file mode 100644 index 5b002ab5..00000000 --- a/sdk/src/plugins/plugin-core/plugin.outputScopes.validation.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type {ILogger} from '@truenine/logger' -import type {OutputPlugin, OutputWriteContext} from './plugin' -import * as fs from 'node:fs' -import * as path from 'node:path' -import {describe, expect, it} from 'vitest' -import {FilePathKind, PluginKind} from './enums' -import { - collectAllPluginOutputs, - executeDeclarativeWriteOutputs, - validateOutputScopeOverridesForPlugins -} from './plugin' - -function createMockLogger(): ILogger { - return { - trace: () => {}, - debug: () => {}, - info: () => {}, - warn: () => {}, - error: () => {}, - fatal: () => {} - } as ILogger -} - -function createMockWriteContext(pluginName: string, topicOverride: Record): OutputWriteContext { - return { - logger: createMockLogger(), - fs, - path, - glob: {} as never, - dryRun: true, - pluginOptions: { - outputScopes: { - plugins: { - [pluginName]: topicOverride - } - } - }, - collectedOutputContext: { - workspace: { - directory: { - pathKind: FilePathKind.Relative, - path: '.', - basePath: '.', - getDirectoryName: () => '.', - getAbsolutePath: () => path.resolve('.') - }, - projects: [] - } - } - } as OutputWriteContext -} - -function createMockOutputPlugin(name: string): OutputPlugin { - return { - type: PluginKind.Output, - name, - log: createMockLogger(), - declarativeOutput: true, - outputCapabilities: { - commands: { - scopes: ['global'], - singleScope: true - } - }, - async declareOutputFiles() { - return [] - }, - async convertContent() { - return '' - } - } -} - -function createMultiScopeOutputPlugin(name: string): OutputPlugin { - return { - type: PluginKind.Output, - name, - log: createMockLogger(), - declarativeOutput: true, - outputCapabilities: { - commands: { - scopes: ['project', 'global'], - singleScope: false - } - }, - async declareOutputFiles() { - return [] - }, - async convertContent() { - return '' - } - } -} - -function createScopedDeclarationPlugin(name: string): OutputPlugin { - return { - type: PluginKind.Output, - name, - log: createMockLogger(), - declarativeOutput: true, - outputCapabilities: {}, - async declareOutputFiles() { - return [ - {path: path.resolve('tmp/project.txt'), scope: 'project', source: {}}, - {path: path.resolve('tmp/global.txt'), scope: 'global', source: {}} - ] - }, - async convertContent() { - return '' - } - } -} - -describe('outputScopes capability validation', () => { - it('accepts valid topic override', async () => { - const plugin = createMockOutputPlugin('MockOutputPlugin') - const ctx = createMockWriteContext(plugin.name, {commands: 'global'}) - - const result = await executeDeclarativeWriteOutputs([plugin], ctx) - expect(result.has(plugin.name)).toBe(true) - }) - - it('throws when override topic is unsupported by plugin capabilities', async () => { - const plugin = createMockOutputPlugin('MockOutputPlugin') - const ctx = createMockWriteContext(plugin.name, {rules: 'global'}) - - await expect(executeDeclarativeWriteOutputs([plugin], ctx)) - .rejects - .toThrow('does not support topic "rules"') - }) - - it('throws when override scope is not allowed by plugin capabilities', async () => { - const plugin = createMockOutputPlugin('MockOutputPlugin') - const ctx = createMockWriteContext(plugin.name, {commands: 'project'}) - - await expect(executeDeclarativeWriteOutputs([plugin], ctx)) - .rejects - .toThrow('requests unsupported scopes [project]') - }) - - it('applies the same validation in output collection path', async () => { - const plugin = createMockOutputPlugin('MockOutputPlugin') - const ctx = createMockWriteContext(plugin.name, {rules: 'global'}) - - await expect(collectAllPluginOutputs([plugin], ctx)) - .rejects - .toThrow('does not support topic "rules"') - }) - - it('throws for multi-scope selection on single-scope topic', () => { - const plugin = createMockOutputPlugin('MockOutputPlugin') - const ctx = createMockWriteContext(plugin.name, {commands: ['global', 'project']}) - - expect(() => validateOutputScopeOverridesForPlugins([plugin], ctx.pluginOptions)) - .toThrow('is single-scope and cannot request multiple scopes') - }) - - it('accepts multi-scope selection when the topic supports parallel scopes', () => { - const plugin = createMultiScopeOutputPlugin('MultiScopeOutputPlugin') - const ctx = createMockWriteContext(plugin.name, {commands: ['project', 'global']}) - - expect(() => validateOutputScopeOverridesForPlugins([plugin], ctx.pluginOptions)).not.toThrow() - }) - - it('rejects workspace as an unsupported override scope', () => { - const plugin = createMultiScopeOutputPlugin('MultiScopeOutputPlugin') - const ctx = createMockWriteContext(plugin.name, {commands: 'workspace'}) - - expect(() => validateOutputScopeOverridesForPlugins([plugin], ctx.pluginOptions)) - .toThrow('requests unsupported scopes [workspace]') - }) - - it('classifies project and global declarations during output collection', async () => { - const plugin = createScopedDeclarationPlugin('ScopedDeclarationPlugin') - const ctx = createMockWriteContext(plugin.name, {}) - - const outputs = await collectAllPluginOutputs([plugin], ctx) - - expect(outputs.projectFiles).toEqual([path.resolve('tmp/project.txt')]) - expect(outputs.globalFiles).toEqual([path.resolve('tmp/global.txt')]) - }) -}) diff --git a/sdk/src/plugins/plugin-core/plugin.ts b/sdk/src/plugins/plugin-core/plugin.ts index 9bd97ec6..cf1529df 100644 --- a/sdk/src/plugins/plugin-core/plugin.ts +++ b/sdk/src/plugins/plugin-core/plugin.ts @@ -2,17 +2,14 @@ import type {ILogger} from '@truenine/logger' import type {MdxGlobalScope} from '@truenine/md-compiler/globals' import type { AindexConfig, - CleanupProtectionOptions, - CommandSeriesOptions, + CodeStylesOptions, FrontMatterOptions, - OutputScopeOptions, - OutputScopeSelection, - PluginOutputScopeTopics, + PluginsConfig, ProtectionMode, WindowsOptions } from './ConfigTypes.schema' import type {PluginKind} from './enums' -import type {InputCollectedContext, OutputCollectedContext, Project} from './InputTypes' +import type {InputCollectedContext, OutputCollectedContext} from './InputTypes' import type {ExecutionPlan} from '@/execution-plan' import type {RuntimeCommand} from '@/runtime-command' import {Buffer} from 'node:buffer' @@ -61,15 +58,6 @@ export interface InputCapability extends DependencyNode { collect: (ctx: InputCapabilityContext) => Partial | Promise> } -/** - * Capability that can enhance projects after all projects are collected. - * This is useful for capabilities that need to add data to projects - * collected by earlier capabilities. - */ -export interface ProjectEnhancerCapability extends InputCapability { - enhanceProjects: (ctx: InputCapabilityContext, projects: readonly Project[]) => Project[] -} - export interface OutputRuntimeTargets { readonly jetbrainsCodexDirs: readonly string[] } @@ -326,78 +314,122 @@ function isNodeBufferLike(value: unknown): value is Buffer { return Buffer.isBuffer(value) } -function normalizeScopeSelection(selection: OutputScopeSelection): readonly OutputDeclarationScope[] { - if (typeof selection === 'string') return [selection] - - const unique: OutputDeclarationScope[] = [] - for (const scope of selection) { - if (!unique.includes(scope)) unique.push(scope) +interface OutputPluginEnablementRule { + readonly configKey: string + readonly defaultEnabled: boolean +} + +const OUTPUT_PLUGIN_ENABLEMENT_RULES: Readonly> = { + AgentsOutputPlugin: { + configKey: 'agentsMd', + defaultEnabled: false + }, + ClaudeCodeCLIOutputPlugin: { + configKey: 'claudeCode', + defaultEnabled: false + }, + CodexCLIOutputPlugin: { + configKey: 'codex', + defaultEnabled: false + }, + CursorOutputPlugin: { + configKey: 'cursor', + defaultEnabled: false + }, + DroidCLIOutputPlugin: { + configKey: 'droid', + defaultEnabled: false + }, + GeminiCLIOutputPlugin: { + configKey: 'gemini', + defaultEnabled: false + }, + GitExcludeOutputPlugin: { + configKey: 'git', + defaultEnabled: true + }, + JetBrainsAIAssistantCodexOutputPlugin: { + configKey: 'jetbrains', + defaultEnabled: false + }, + JetBrainsIDECodeStyleConfigOutputPlugin: { + configKey: 'jetbrainsCodeStyle', + defaultEnabled: false + }, + KiroCLIOutputPlugin: { + configKey: 'kiro', + defaultEnabled: false + }, + OpencodeCLIOutputPlugin: { + configKey: 'opencode', + defaultEnabled: false + }, + QoderIDEPluginOutputPlugin: { + configKey: 'qoder', + defaultEnabled: false + }, + ReadmeMdConfigFileOutputPlugin: { + configKey: 'readme', + defaultEnabled: true + }, + TraeIDEOutputPlugin: { + configKey: 'trae', + defaultEnabled: false + }, + TraeCNIDEOutputPlugin: { + configKey: 'traeCn', + defaultEnabled: false + }, + VisualStudioCodeIDEConfigOutputPlugin: { + configKey: 'vscode', + defaultEnabled: false + }, + WarpIDEOutputPlugin: { + configKey: 'warp', + defaultEnabled: false + }, + WindsurfOutputPlugin: { + configKey: 'windsurf', + defaultEnabled: false + }, + ZedIDEConfigOutputPlugin: { + configKey: 'zed', + defaultEnabled: false } - return unique -} - -function getPluginScopeOverrides(pluginName: string, pluginOptions?: PluginOptions): PluginOutputScopeTopics | undefined { - return pluginOptions?.outputScopes?.plugins?.[pluginName] } -export function validateOutputPluginCapabilities(plugin: OutputPlugin): void { - for (const topic of OUTPUT_SCOPE_TOPICS) { - const capability = plugin.outputCapabilities[topic] - if (capability == null) continue - if (capability.scopes.length === 0) throw new Error(`Plugin ${plugin.name} declares empty scopes for topic "${topic}"`) - } +function resolveConfiguredPluginEnabled( + plugins: PluginsConfig | undefined, + configKey: string +): boolean | undefined { + return plugins?.[configKey] } -export function validateOutputScopeOverridesForPlugin(plugin: OutputPlugin, pluginOptions?: PluginOptions): void { - const overrides = getPluginScopeOverrides(plugin.name, pluginOptions) - if (overrides == null) return - - for (const topic of OUTPUT_SCOPE_TOPICS) { - const requestedSelection = overrides[topic] - if (requestedSelection == null) continue +export function isOutputPluginEnabled( + plugin: OutputPlugin, + pluginOptions?: PluginOptions +): boolean { + const enablementRule = OUTPUT_PLUGIN_ENABLEMENT_RULES[plugin.name] + if (enablementRule == null) return true - const capability = plugin.outputCapabilities[topic] - if (capability == null) { - throw new Error( - `Invalid outputScopes configuration: outputScopes.plugins.${plugin.name}.${topic} is set, but plugin ${plugin.name} does not support topic "${topic}".` - ) - } + const configuredEnabled = resolveConfiguredPluginEnabled( + pluginOptions?.plugins, + enablementRule.configKey + ) + if (configuredEnabled != null) return configuredEnabled - const requestedScopes = normalizeScopeSelection(requestedSelection) - if (capability.singleScope && requestedScopes.length > 1) { - const requested = requestedScopes.join(', ') - throw new Error( - `Invalid outputScopes configuration: outputScopes.plugins.${plugin.name}.${topic} is single-scope and cannot request multiple scopes [${requested}].` - ) - } - - const allowedScopes = new Set(capability.scopes) - const unsupportedScopes = requestedScopes.filter(scope => !allowedScopes.has(scope)) - - if (unsupportedScopes.length > 0) { - const allowed = capability.scopes.join(', ') - const requested = unsupportedScopes.join(', ') - throw new Error( - `Invalid outputScopes configuration: outputScopes.plugins.${plugin.name}.${topic} requests unsupported scopes [${requested}]. Allowed scopes: [${allowed}].` - ) - } - } -} - -export function validateOutputScopeOverridesForPlugins(plugins: readonly OutputPlugin[], pluginOptions?: PluginOptions): void { - for (const plugin of plugins) { - validateOutputPluginCapabilities(plugin) - validateOutputScopeOverridesForPlugin(plugin, pluginOptions) - } + return enablementRule.defaultEnabled } export async function collectOutputDeclarations( plugins: readonly OutputPlugin[], ctx: OutputWriteContext ): Promise> { - validateOutputScopeOverridesForPlugins(plugins, ctx.pluginOptions) - const declarationEntries = await Promise.all(plugins.map(async plugin => { + if (!isOutputPluginEnabled(plugin, ctx.pluginOptions)) { + return [plugin, [] as OutputFileDeclaration[]] as const + } + const declarations = await plugin.declareOutputFiles(ctx) return [plugin, filterPathScopedEntriesForExecutionPlan(declarations, ctx.executionPlan, ctx.collectedOutputContext)] as const })) @@ -436,18 +468,21 @@ export async function executeDeclarativeWriteOutputs( continue } - if (declaration.ifExists === 'error' && fs.existsSync(declaration.path)) throw new Error(`Refusing to overwrite existing file: ${declaration.path}`) + if (declaration.ifExists === 'error' && fs.existsSync(declaration.path)) { + throw new Error(`Refusing to overwrite existing file: ${declaration.path}`) + } const content = await plugin.convertContent(declaration, ctx) - isNodeBufferLike(content) ? fs.writeFileSync(declaration.path, content) : fs.writeFileSync(declaration.path, content, 'utf8') + if (isNodeBufferLike(content)) fs.writeFileSync(declaration.path, content) + else fs.writeFileSync(declaration.path, content, 'utf8') fileResults.push({path: declaration.path, success: true}) - } catch (error) { + } + catch (error) { fileResults.push({path: declaration.path, success: false, error: error as Error}) } } - const pluginResult: WriteResults = {files: fileResults, dirs: []} - results.set(plugin.name, pluginResult) + results.set(plugin.name, {files: fileResults, dirs: []}) } return results @@ -513,16 +548,13 @@ export interface PluginOptions { readonly aindex?: AindexConfig - readonly commandSeriesOptions?: CommandSeriesOptions - - readonly outputScopes?: OutputScopeOptions - readonly frontMatter?: FrontMatterOptions - readonly cleanupProtection?: CleanupProtectionOptions + readonly codeStyles?: CodeStylesOptions readonly windows?: WindowsOptions - plugins?: readonly (InputCapability | OutputPlugin)[] + readonly plugins?: PluginsConfig + logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error' } diff --git a/sdk/src/plugins/plugin-editorconfig.ts b/sdk/src/plugins/plugin-editorconfig.ts deleted file mode 100644 index 189999e5..00000000 --- a/sdk/src/plugins/plugin-editorconfig.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { - EditorConfigOutputPlugin -} from './EditorConfigOutputPlugin' diff --git a/sdk/src/plugins/plugin-warp-ide.ts b/sdk/src/plugins/plugin-warp-ide.ts deleted file mode 100644 index b9e1bf10..00000000 --- a/sdk/src/plugins/plugin-warp-ide.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { - WarpIDEOutputPlugin -} from './WarpIDEOutputPlugin' diff --git a/sdk/src/prompts.test.ts b/sdk/src/prompts.test.ts index add5c693..57251345 100644 --- a/sdk/src/prompts.test.ts +++ b/sdk/src/prompts.test.ts @@ -24,7 +24,7 @@ function writeFile(filePath: string, content: string, modifiedAt: Date): void { fs.utimesSync(filePath, modifiedAt, modifiedAt) } -function serviceOptions(workspaceDir: string) { +function serviceOptions(workspaceDir: string | undefined) { return { loadUserConfig: false, pluginOptions: { @@ -38,6 +38,27 @@ afterEach(() => { }) describe('prompt catalog service', () => { + it('uses cwd as the workspace root when workspaceDir is omitted', async () => { + const workspaceDir = createTempWorkspace('tnmsc-prompts-cwd-fallback-') + const aindexDir = path.join(workspaceDir, 'aindex') + + writeFile( + path.join(aindexDir, 'global.src.mdx'), + '---\ndescription: global zh\n---\nGlobal zh', + new Date() + ) + + const prompts = await listPrompts({ + loadUserConfig: false, + cwd: workspaceDir + }) + + expect(prompts.some(prompt => prompt.promptId === 'global-memory')).toBe(true) + expect(prompts.find(prompt => prompt.promptId === 'global-memory')?.paths.zh).toBe( + path.join(aindexDir, 'global.src.mdx') + ) + }) + it('lists every managed prompt family with status metadata', async () => { const workspaceDir = createTempWorkspace('tnmsc-prompts-') const aindexDir = path.join(workspaceDir, 'aindex') diff --git a/sdk/src/prompts.ts b/sdk/src/prompts.ts index b04dd9b4..d7632910 100644 --- a/sdk/src/prompts.ts +++ b/sdk/src/prompts.ts @@ -1,6 +1,7 @@ import type {AindexProjectSeriesName, PluginOptions, YAMLFrontMatter} from '@/plugins/plugin-core' import * as fs from 'node:fs' import * as path from 'node:path' +import process from 'node:process' import {parseMarkdown} from '@truenine/md-compiler/markdown' import glob from 'fast-glob' import { @@ -8,7 +9,7 @@ import { resolveAindexProjectSeriesConfig, resolveAindexProjectSeriesConfigs } from '@/aindex-project-series' -import {mergeConfig, userConfigToPluginOptions} from './config' +import {mergeConfigForRuntime, userConfigToPluginOptions} from './config' import {getConfigLoader} from './ConfigLoader' import {PathPlaceholders} from './plugins/plugin-core' import {resolveUserPath} from './runtime-environment' @@ -164,7 +165,7 @@ function resolvePromptEnvironment(options: PromptServiceOptions = {}): ResolvedP if (userConfigResult.found) userConfigOptions = userConfigToPluginOptions(userConfigResult.config) } - const mergedOptions = mergeConfig(userConfigOptions, pluginOptions) + const mergedOptions = mergeConfigForRuntime(cwd ?? process.cwd(), userConfigOptions, pluginOptions) const workspaceDir = resolveConfiguredPath(mergedOptions.workspaceDir, '') const aindexDir = path.join(workspaceDir, mergedOptions.aindex.dir) diff --git a/sdk/src/runtime/cleanup.execution-scope.test.ts b/sdk/src/runtime/cleanup.execution-scope.test.ts index 6c27eb89..72199b1b 100644 --- a/sdk/src/runtime/cleanup.execution-scope.test.ts +++ b/sdk/src/runtime/cleanup.execution-scope.test.ts @@ -4,24 +4,53 @@ import {afterEach, describe, expect, it} from 'vitest' import { createEmptyExecutionPlanProjectsBySeries, createLogger, - FilePathKind + FilePathKind, + PluginKind } from '../plugins/plugin-core' import {collectDeletionTargets} from './cleanup' -function createProject(workspaceDir: string, name: string, series: Project['promptSeries']): Project { +function createProject(workspaceDir: string | undefined, name: string, series: Project['promptSeries']): Project { return { name, promptSeries: series, dirFromWorkspacePath: { pathKind: FilePathKind.Relative, path: name, - basePath: workspaceDir, + basePath: workspaceDir ?? '', getDirectoryName: () => name, - getAbsolutePath: () => path.join(workspaceDir, name) + getAbsolutePath: () => path.join(workspaceDir ?? '', name) } } } +function createPluginOptions(workspaceDir: string, plugins: Record = {}) { + return { + version: '0.0.0', + workspaceDir, + logLevel: 'error' as const, + aindex: { + dir: 'aindex', + skills: {src: 'skills', dist: 'dist/skills'}, + commands: {src: 'commands', dist: 'dist/commands'}, + subAgents: {src: 'subagents', dist: 'dist/subagents'}, + rules: {src: 'rules', dist: 'dist/rules'}, + globalPrompt: {src: 'global.src.mdx', dist: 'dist/global.mdx'}, + workspacePrompt: {src: 'workspace.src.mdx', dist: 'dist/workspace.mdx'}, + app: {src: 'app', dist: 'dist/app'}, + ext: {src: 'ext', dist: 'dist/ext'}, + arch: {src: 'arch', dist: 'dist/arch'}, + softwares: {src: 'softwares', dist: 'dist/softwares'} + }, + frontMatter: {blankLineAfter: true}, + codeStyles: { + indent: 'space' as const, + tabSize: 2 + }, + windows: {}, + plugins + } +} + afterEach(() => { const testGlobals = globalThis as typeof globalThis & {__TNMSC_TEST_NATIVE_BINDING__?: object} delete testGlobals.__TNMSC_TEST_NATIVE_BINDING__ @@ -53,7 +82,7 @@ describe('cleanup execution scope filtering', () => { const plugin: OutputPlugin = { name: 'ExecutionScopeCleanupPlugin', - type: 'output', + type: PluginKind.Output, log: createLogger('ExecutionScopeCleanupPlugin', 'error'), declarativeOutput: true, outputCapabilities: {}, @@ -93,18 +122,18 @@ describe('cleanup execution scope filtering', () => { ext: [{ name: 'plugin-one', rootDir: path.join(workspaceDir, 'plugin-one'), - series: 'ext' + series: 'ext' as const }], app: [{ name: 'app-one', rootDir: path.join(workspaceDir, 'app-one'), - series: 'app' + series: 'app' as const }] }, matchedProject: { name: 'plugin-one', rootDir: path.join(workspaceDir, 'plugin-one'), - series: 'ext' + series: 'ext' as const } } @@ -123,30 +152,7 @@ describe('cleanup execution scope filtering', () => { ] } }, - pluginOptions: { - version: '0.0.0', - workspaceDir, - logLevel: 'error', - aindex: { - dir: 'aindex', - skills: {src: 'skills', dist: 'dist/skills'}, - commands: {src: 'commands', dist: 'dist/commands'}, - subAgents: {src: 'subagents', dist: 'dist/subagents'}, - rules: {src: 'rules', dist: 'dist/rules'}, - globalPrompt: {src: 'global.src.mdx', dist: 'dist/global.mdx'}, - workspacePrompt: {src: 'workspace.src.mdx', dist: 'dist/workspace.mdx'}, - app: {src: 'app', dist: 'dist/app'}, - ext: {src: 'ext', dist: 'dist/ext'}, - arch: {src: 'arch', dist: 'dist/arch'}, - softwares: {src: 'softwares', dist: 'dist/softwares'} - }, - commandSeriesOptions: {}, - outputScopes: {}, - frontMatter: {blankLineAfter: true}, - cleanupProtection: {}, - windows: {}, - plugins: [] - }, + pluginOptions: createPluginOptions(workspaceDir), runtimeTargets: {jetbrainsCodexDirs: []}, executionPlan, dryRun: true @@ -169,4 +175,168 @@ describe('cleanup execution scope filtering', () => { ] }) }) + + it('keeps cleanup for opt-in plugins while suppressing their outputs by default', async () => { + const workspaceDir = path.resolve('/tmp/tnmsc-cleanup-opt-in-disabled') + let capturedSnapshot: Record | undefined + + const testGlobals = globalThis as typeof globalThis & {__TNMSC_TEST_NATIVE_BINDING__?: object} + testGlobals.__TNMSC_TEST_NATIVE_BINDING__ = { + planCleanup(snapshotJson: string) { + capturedSnapshot = JSON.parse(snapshotJson) as Record + return JSON.stringify({ + filesToDelete: [], + dirsToDelete: [], + emptyDirsToDelete: [], + violations: [], + conflicts: [], + excludedScanGlobs: [] + }) + }, + performCleanup() { + throw new Error('performCleanup should not be called in this test') + } + } + + const plugin: OutputPlugin = { + name: 'TraeIDEOutputPlugin', + type: PluginKind.Output, + log: createLogger('TraeIDEOutputPlugin', 'error'), + declarativeOutput: true, + outputCapabilities: {}, + async declareOutputFiles() { + return [{ + path: path.join(workspaceDir, '.trae', 'commands', 'review.md'), + scope: 'project', + source: {} + }] + }, + async convertContent() { + return '' + }, + async declareCleanupPaths() { + return { + delete: [{ + path: path.join(workspaceDir, '.trae', 'commands'), + kind: 'directory', + scope: 'project' + }] + } + } + } + + const cleanCtx: OutputCleanContext = { + logger: createLogger('cleanup.execution-scope.test', 'error'), + collectedOutputContext: { + workspace: { + directory: { + pathKind: FilePathKind.Absolute, + path: workspaceDir, + getDirectoryName: () => path.basename(workspaceDir) + }, + projects: [] + } + }, + pluginOptions: createPluginOptions(workspaceDir), + runtimeTargets: {jetbrainsCodexDirs: []}, + executionPlan: { + scope: 'workspace', + cwd: workspaceDir, + workspaceDir, + projectsBySeries: createEmptyExecutionPlanProjectsBySeries() + }, + dryRun: true + } + + await collectDeletionTargets([plugin], cleanCtx) + + const pluginSnapshot = (capturedSnapshot?.['pluginSnapshots'] as Record[] | undefined)?.[0] + expect(pluginSnapshot?.['outputs']).toEqual([]) + expect(pluginSnapshot?.['cleanup']).toEqual({ + delete: [{ + path: path.join(workspaceDir, '.trae', 'commands'), + kind: 'directory', + scope: 'project' + }] + }) + }) + + it('restores outputs for opt-in plugins after they are explicitly enabled', async () => { + const workspaceDir = path.resolve('/tmp/tnmsc-cleanup-opt-in-enabled') + let capturedSnapshot: Record | undefined + + const testGlobals = globalThis as typeof globalThis & {__TNMSC_TEST_NATIVE_BINDING__?: object} + testGlobals.__TNMSC_TEST_NATIVE_BINDING__ = { + planCleanup(snapshotJson: string) { + capturedSnapshot = JSON.parse(snapshotJson) as Record + return JSON.stringify({ + filesToDelete: [], + dirsToDelete: [], + emptyDirsToDelete: [], + violations: [], + conflicts: [], + excludedScanGlobs: [] + }) + }, + performCleanup() { + throw new Error('performCleanup should not be called in this test') + } + } + + const outputPath = path.join(workspaceDir, '.trae', 'commands', 'review.md') + const plugin: OutputPlugin = { + name: 'TraeIDEOutputPlugin', + type: PluginKind.Output, + log: createLogger('TraeIDEOutputPlugin', 'error'), + declarativeOutput: true, + outputCapabilities: {}, + async declareOutputFiles() { + return [{ + path: outputPath, + scope: 'project', + source: {} + }] + }, + async convertContent() { + return '' + }, + async declareCleanupPaths() { + return { + delete: [{ + path: path.join(workspaceDir, '.trae', 'commands'), + kind: 'directory', + scope: 'project' + }] + } + } + } + + const cleanCtx: OutputCleanContext = { + logger: createLogger('cleanup.execution-scope.test', 'error'), + collectedOutputContext: { + workspace: { + directory: { + pathKind: FilePathKind.Absolute, + path: workspaceDir, + getDirectoryName: () => path.basename(workspaceDir) + }, + projects: [] + } + }, + pluginOptions: createPluginOptions(workspaceDir, {trae: true}), + runtimeTargets: {jetbrainsCodexDirs: []}, + executionPlan: { + scope: 'workspace', + cwd: workspaceDir, + workspaceDir, + projectsBySeries: createEmptyExecutionPlanProjectsBySeries() + }, + dryRun: true + } + + await collectDeletionTargets([plugin], cleanCtx) + + const pluginSnapshot = (capturedSnapshot?.['pluginSnapshots'] as Record[] | undefined)?.[0] + expect(pluginSnapshot?.['outputs']).toEqual([outputPath]) + }) }) diff --git a/sdk/src/runtime/cleanup.ts b/sdk/src/runtime/cleanup.ts index 5b4f00bb..dc422c5f 100644 --- a/sdk/src/runtime/cleanup.ts +++ b/sdk/src/runtime/cleanup.ts @@ -11,15 +11,15 @@ import type { ProtectionRuleMatcher } from '../ProtectedDeletionGuard' import * as path from 'node:path' +import {loadAindexProjectConfig} from '@/aindex-config' import { buildDiagnostic, buildFileOperationDiagnostic, diagnosticLines } from '@/diagnostics' import {filterPathScopedEntriesForExecutionPlan} from '@/execution-plan' -import {loadAindexProjectConfig} from '../aindex-config/AindexProjectConfigLoader' import {getNativeBinding} from '../core/native-binding' -import {collectAllPluginOutputs} from '../plugins/plugin-core' +import {collectAllPluginOutputs, isOutputPluginEnabled} from '../plugins/plugin-core' import { collectProjectRoots, collectProtectedInputSourceRules, @@ -244,8 +244,12 @@ async function collectPluginCleanupSnapshot( > ): Promise { const existingOutputDeclarations = predeclaredOutputs?.get(plugin) - const declaredOutputs - = existingOutputDeclarations ?? await plugin.declareOutputFiles({...cleanCtx, dryRun: true}) + const declaredOutputs = existingOutputDeclarations + ?? ( + !isOutputPluginEnabled(plugin, cleanCtx.pluginOptions) + ? [] + : await plugin.declareOutputFiles({...cleanCtx, dryRun: true}) + ) const outputs = filterPathScopedEntriesForExecutionPlan( declaredOutputs, cleanCtx.executionPlan, @@ -271,18 +275,8 @@ async function collectPluginCleanupSnapshot( } } -function collectConfiguredCleanupProtectionRules( - cleanCtx: OutputCleanContext -): NativeProtectedRule[] { - return (cleanCtx.pluginOptions?.cleanupProtection?.rules ?? []).map( - rule => ({ - path: rule.path, - protectionMode: mapProtectionMode(rule.protectionMode), - reason: rule.reason ?? 'configured cleanup protection rule', - source: 'configured-cleanup-protection', - matcher: mapProtectionRuleMatcher(rule.matcher ?? 'path') - }) - ) +function collectConfiguredCleanupProtectionRules(): NativeProtectedRule[] { + return [] } function buildCleanupProtectionConflictMessage( @@ -503,7 +497,7 @@ async function buildCleanupSnapshot( }) } - protectedRules.push(...collectConfiguredCleanupProtectionRules(cleanCtx)) + protectedRules.push(...collectConfiguredCleanupProtectionRules()) let emptyDirExcludeGlobs: string[] | undefined if (cleanCtx.collectedOutputContext.aindexDir != null) { @@ -542,9 +536,7 @@ export async function planCleanupWithNative( const nativeBinding = requireNativeCleanupBinding() if (nativeBinding?.planCleanup == null) { throw new Error('Native cleanup planning is unavailable') } - const result = await Promise.resolve( - nativeBinding.planCleanup(JSON.stringify(snapshot)) - ) + const result = await nativeBinding.planCleanup(JSON.stringify(snapshot)) return parseNativeJson(result) } @@ -554,9 +546,7 @@ export async function performCleanupWithNative( const nativeBinding = requireNativeCleanupBinding() if (nativeBinding?.performCleanup == null) { throw new Error('Native cleanup execution is unavailable') } - const result = await Promise.resolve( - nativeBinding.performCleanup(JSON.stringify(snapshot)) - ) + const result = await nativeBinding.performCleanup(JSON.stringify(snapshot)) return parseNativeJson(result) } diff --git a/sdk/src/wsl-mirror-sync.test.ts b/sdk/src/wsl-mirror-sync.test.ts index d4af7962..fdb204cd 100644 --- a/sdk/src/wsl-mirror-sync.test.ts +++ b/sdk/src/wsl-mirror-sync.test.ts @@ -3,7 +3,7 @@ import {Buffer} from 'node:buffer' import * as path from 'node:path' import {describe, expect, it, vi} from 'vitest' import {PluginKind} from './plugins/plugin-core' -import {syncWindowsConfigIntoWsl} from './wsl-mirror-sync' +import {collectDeclaredWslMirrorFiles, syncWindowsConfigIntoWsl} from './wsl-mirror-sync' class MemoryMirrorFs { readonly files = new Map() @@ -100,12 +100,15 @@ function createLogger(): RecordedLogger { } as RecordedLogger } -function createMirrorPlugin(sourcePaths: string | readonly string[] = []): OutputPlugin { +function createMirrorPlugin( + sourcePaths: string | readonly string[] = [], + pluginName: string = 'MirrorPlugin' +): OutputPlugin { const normalizedPaths = Array.isArray(sourcePaths) ? sourcePaths : [sourcePaths] return { type: PluginKind.Output, - name: 'MirrorPlugin', + name: pluginName, log: createLogger(), declarativeOutput: true, outputCapabilities: {}, @@ -210,6 +213,46 @@ function wasWslListCalled( } describe('wsl mirror sync', () => { + it('skips declared WSL mirror files for opt-in plugins that are not enabled', async () => { + const declarations = await collectDeclaredWslMirrorFiles( + [createMirrorPlugin('~/.claude/settings.json', 'ClaudeCodeCLIOutputPlugin')], + { + ...createWriteContext('Ubuntu'), + pluginOptions: { + windows: { + wsl2: { + instances: 'Ubuntu' + } + }, + plugins: {} + } + } as OutputWriteContext + ) + + expect(declarations).toEqual([]) + }) + + it('collects declared WSL mirror files after an opt-in plugin is explicitly enabled', async () => { + const declarations = await collectDeclaredWslMirrorFiles( + [createMirrorPlugin('~/.claude/settings.json', 'ClaudeCodeCLIOutputPlugin')], + { + ...createWriteContext('Ubuntu'), + pluginOptions: { + windows: { + wsl2: { + instances: 'Ubuntu' + } + }, + plugins: { + claudeCode: true + } + } + } as OutputWriteContext + ) + + expect(declarations).toEqual([{sourcePath: '~/.claude/settings.json'}]) + }) + it('copies declared host config files into each resolved WSL home', async () => { const memoryFs = new MemoryMirrorFs() const hostHomeDir = 'C:\\Users\\alpha' diff --git a/sdk/src/wsl-mirror-sync.ts b/sdk/src/wsl-mirror-sync.ts index 04d4cffc..c9d43fce 100644 --- a/sdk/src/wsl-mirror-sync.ts +++ b/sdk/src/wsl-mirror-sync.ts @@ -12,6 +12,7 @@ import {spawnSync} from 'node:child_process' import * as fs from 'node:fs' import * as path from 'node:path' import process from 'node:process' +import {isOutputPluginEnabled} from './plugins/plugin-core' import {getEffectiveHomeDir, resolveRuntimeEnvironment, resolveUserPath} from './runtime-environment' type MirrorFs = Pick @@ -190,6 +191,7 @@ export async function collectDeclaredWslMirrorFiles( ctx: OutputWriteContext ): Promise { const declarations = await Promise.all(outputPlugins.map(async plugin => { + if (!isOutputPluginEnabled(plugin, ctx.pluginOptions)) return [] if (plugin.declareWslMirrorFiles == null) return [] return plugin.declareWslMirrorFiles(ctx) })) diff --git a/sdk/test/native-binding/cleanup.ts b/sdk/test/native-binding/cleanup.ts index 93926c58..ad4270a5 100644 --- a/sdk/test/native-binding/cleanup.ts +++ b/sdk/test/native-binding/cleanup.ts @@ -6,14 +6,14 @@ import type { OutputFileDeclaration, OutputPlugin } from '../../src/plugins/plugin-core' -import type {ProtectedPathRule, ProtectionMode, ProtectionRuleMatcher} from '../../src/ProtectedDeletionGuard' +import type {ProtectedPathRule, ProtectionMode, ProtectionRuleMatcher} from '../../src/ProtectedDeletionGuard.ts' import type {DeletionError} from './desk-paths' import * as fs from 'node:fs' import * as path from 'node:path' import glob from 'fast-glob' -import {compactDeletionTargets} from '../../src/cleanup/delete-targets' -import {planWorkspaceEmptyDirectoryCleanup} from '../../src/cleanup/empty-directories' -import {buildDiagnostic, buildFileOperationDiagnostic, diagnosticLines} from '../../src/diagnostics' +import {compactDeletionTargets} from '../../src/cleanup/delete-targets.ts' +import {planWorkspaceEmptyDirectoryCleanup} from '../../src/cleanup/empty-directories.ts' +import {buildDiagnostic, buildFileOperationDiagnostic, diagnosticLines} from '../../src/diagnostics.ts' import {collectAllPluginOutputs} from '../../src/plugins/plugin-core' import { buildComparisonKeys, @@ -23,7 +23,7 @@ import { logProtectedDeletionGuardError, partitionDeletionTargets, resolveAbsolutePath -} from '../../src/ProtectedDeletionGuard' +} from '../../src/ProtectedDeletionGuard.ts' import {deleteEmptyDirectories, deleteTargets as deskDeleteTargets} from './desk-paths' /** @@ -233,16 +233,6 @@ async function collectCleanupTargets( addProtectRule(rule.path, rule.protectionMode, rule.reason, rule.source) } - for (const rule of cleanCtx.pluginOptions?.cleanupProtection?.rules ?? []) { - addProtectRule( - rule.path, - rule.protectionMode, - rule.reason ?? 'configured cleanup protection rule', - 'configured-cleanup-protection', - rule.matcher ?? 'path' - ) - } - for (const snapshot of pluginSnapshots) { for (const declaration of snapshot.outputs) { const resolvedOutputPath = resolveAbsolutePath(declaration.path) diff --git a/sdk/test/native-binding/desk-paths.ts b/sdk/test/native-binding/desk-paths.ts index 243ac6bf..f110bc74 100644 --- a/sdk/test/native-binding/desk-paths.ts +++ b/sdk/test/native-binding/desk-paths.ts @@ -1,16 +1,14 @@ import type {Buffer} from 'node:buffer' -import type {LoggerDiagnosticInput} from '../../src/plugins/plugin-core' import * as fs from 'node:fs' import path from 'node:path' import process from 'node:process' -import {buildFileOperationDiagnostic} from '../../src/diagnostics' -import {resolveRuntimeEnvironment, resolveUserPath} from '../../src/runtime-environment' +import {resolveRuntimeEnvironment, resolveUserPath} from '../../src/runtime-environment.ts' type PlatformFixedDir = 'win32' | 'darwin' | 'linux' function getLinuxDataDir(homeDir: string): string { const xdgDataHome = process.env['XDG_DATA_HOME'] - if (typeof xdgDataHome === 'string' && xdgDataHome.trim().length > 0) return resolveUserPath(xdgDataHome) + if ((xdgDataHome?.trim()?.length ?? 0) > 0 && xdgDataHome != null) return resolveUserPath(xdgDataHome) return path.join(homeDir, '.local', 'share') } @@ -209,56 +207,3 @@ export async function deleteTargets(targets: {readonly files?: readonly string[] dirErrors: dirResult.errors } } - -export interface WriteLogger { - readonly trace: (data: object) => void - readonly error: (diagnostic: LoggerDiagnosticInput) => void -} - -export interface SafeWriteOptions { - readonly fullPath: string - readonly content: string | Buffer - readonly type: string - readonly relativePath: string - readonly dryRun: boolean - readonly logger: WriteLogger -} - -export interface SafeWriteResult { - readonly path: string - readonly success: boolean - readonly skipped?: boolean - readonly error?: Error -} - -export function writeFileSafe(options: SafeWriteOptions): SafeWriteResult { - const {fullPath, content, type, relativePath, dryRun, logger} = options - - if (dryRun) { - logger.trace({action: 'dryRun', type, path: fullPath}) - return {path: relativePath, success: true, skipped: false} - } - - try { - writeFileSync(fullPath, content) - logger.trace({action: 'write', type, path: fullPath}) - return {path: relativePath, success: true} - } catch (error) { - const errMsg = error instanceof Error ? error.message : String(error) - logger.error( - buildFileOperationDiagnostic({ - code: 'OUTPUT_FILE_WRITE_FAILED', - title: `Failed to write ${type} output`, - operation: 'write', - targetKind: `${type} output file`, - path: fullPath, - error: errMsg, - details: { - relativePath, - type - } - }) - ) - return {path: relativePath, success: false, error: error as Error} - } -} diff --git a/sdk/test/setup-native-binding.ts b/sdk/test/setup-native-binding.ts index 297cd3f2..82baec7d 100644 --- a/sdk/test/setup-native-binding.ts +++ b/sdk/test/setup-native-binding.ts @@ -7,7 +7,7 @@ import type { import * as fs from 'node:fs' import * as path from 'node:path' import glob from 'fast-glob' -import {FilePathKind, PluginKind} from '../src/plugins/plugin-core/enums' +import {FilePathKind, PluginKind} from '../src/plugins/plugin-core/enums.ts' import * as deskPaths from './native-binding/desk-paths' interface NativeCleanupTarget { @@ -86,21 +86,6 @@ function createSyntheticOutputPlugin(snapshot: NativePluginCleanupSnapshot): Out async function createSyntheticCleanContext(snapshot: NativeCleanupSnapshot): Promise { const {mergeConfig} = await import('../src/config') const workspaceDir = path.resolve(snapshot.workspaceDir) - const cleanupProtectionRules = snapshot.protectedRules.map(rule => ({ - path: rule.path, - protectionMode: rule.protectionMode, - reason: rule.reason, - matcher: rule.matcher ?? 'path' - })) - - if (snapshot.aindexDir != null) { - cleanupProtectionRules.push({ - path: snapshot.aindexDir, - protectionMode: 'direct', - reason: 'resolved aindex root', - matcher: 'path' - }) - } return { logger: createMockLogger(), @@ -109,10 +94,7 @@ async function createSyntheticCleanContext(snapshot: NativeCleanupSnapshot): Pro glob, dryRun: false, pluginOptions: mergeConfig({ - workspaceDir, - cleanupProtection: { - rules: cleanupProtectionRules - } + workspaceDir }), collectedOutputContext: { workspace: { diff --git a/sdk/tsconfig.eslint.json b/sdk/tsconfig.eslint.json index a1ff95ae..ae9eeee1 100644 --- a/sdk/tsconfig.eslint.json +++ b/sdk/tsconfig.eslint.json @@ -22,7 +22,6 @@ "@truenine/plugin-claude-code-cli": ["./src/plugins/plugin-claude-code-cli.ts"], "@truenine/plugin-cursor": ["./src/plugins/plugin-cursor.ts"], "@truenine/plugin-droid-cli": ["./src/plugins/plugin-droid-cli.ts"], - "@truenine/plugin-editorconfig": ["./src/plugins/plugin-editorconfig.ts"], "@truenine/plugin-gemini-cli": ["./src/plugins/plugin-gemini-cli.ts"], "@truenine/plugin-git-exclude": ["./src/plugins/plugin-git-exclude.ts"], "@truenine/plugin-jetbrains-ai-codex": ["./src/plugins/plugin-jetbrains-ai-codex.ts"], @@ -33,7 +32,7 @@ "@truenine/plugin-readme": ["./src/plugins/plugin-readme.ts"], "@truenine/plugin-trae-ide": ["./src/plugins/plugin-trae-ide.ts"], "@truenine/plugin-vscode": ["./src/plugins/plugin-vscode.ts"], - "@truenine/plugin-warp-ide": ["./src/plugins/plugin-warp-ide.ts"], + "@truenine/plugin-warp-ide": ["./src/plugins/WarpIDEOutputPlugin.ts"], "@truenine/plugin-windsurf": ["./src/plugins/plugin-windsurf.ts"], "@truenine/plugin-zed": ["./src/plugins/plugin-zed.ts"], "@truenine/script-runtime": ["../libraries/script-runtime/src/index.ts"] diff --git a/sdk/tsconfig.json b/sdk/tsconfig.json index 9006c87e..fa0f270e 100644 --- a/sdk/tsconfig.json +++ b/sdk/tsconfig.json @@ -24,7 +24,6 @@ "@truenine/plugin-claude-code-cli": ["./src/plugins/plugin-claude-code-cli.ts"], "@truenine/plugin-cursor": ["./src/plugins/plugin-cursor.ts"], "@truenine/plugin-droid-cli": ["./src/plugins/plugin-droid-cli.ts"], - "@truenine/plugin-editorconfig": ["./src/plugins/plugin-editorconfig.ts"], "@truenine/plugin-gemini-cli": ["./src/plugins/plugin-gemini-cli.ts"], "@truenine/plugin-git-exclude": ["./src/plugins/plugin-git-exclude.ts"], "@truenine/plugin-jetbrains-ai-codex": ["./src/plugins/plugin-jetbrains-ai-codex.ts"], @@ -35,7 +34,7 @@ "@truenine/plugin-readme": ["./src/plugins/plugin-readme.ts"], "@truenine/plugin-trae-ide": ["./src/plugins/plugin-trae-ide.ts"], "@truenine/plugin-vscode": ["./src/plugins/plugin-vscode.ts"], - "@truenine/plugin-warp-ide": ["./src/plugins/plugin-warp-ide.ts"], + "@truenine/plugin-warp-ide": ["./src/plugins/WarpIDEOutputPlugin.ts"], "@truenine/plugin-windsurf": ["./src/plugins/plugin-windsurf.ts"], "@truenine/plugin-zed": ["./src/plugins/plugin-zed.ts"] }, diff --git a/sdk/tsdown.config.ts b/sdk/tsdown.config.ts index fa041e92..565917e3 100644 --- a/sdk/tsdown.config.ts +++ b/sdk/tsdown.config.ts @@ -15,7 +15,6 @@ const pluginAliases: Record = { '@truenine/plugin-claude-code-cli': resolve('src/plugins/plugin-claude-code-cli.ts'), '@truenine/plugin-cursor': resolve('src/plugins/plugin-cursor.ts'), '@truenine/plugin-droid-cli': resolve('src/plugins/plugin-droid-cli.ts'), - '@truenine/plugin-editorconfig': resolve('src/plugins/plugin-editorconfig.ts'), '@truenine/plugin-gemini-cli': resolve('src/plugins/plugin-gemini-cli.ts'), '@truenine/plugin-git-exclude': resolve('src/plugins/plugin-git-exclude.ts'), '@truenine/plugin-input-agentskills': resolve('src/plugins/plugin-input-agentskills/index.ts'), @@ -44,7 +43,7 @@ const pluginAliases: Record = { '@truenine/plugin-readme': resolve('src/plugins/plugin-readme.ts'), '@truenine/plugin-trae-ide': resolve('src/plugins/plugin-trae-ide.ts'), '@truenine/plugin-vscode': resolve('src/plugins/plugin-vscode.ts'), - '@truenine/plugin-warp-ide': resolve('src/plugins/plugin-warp-ide.ts'), + '@truenine/plugin-warp-ide': resolve('src/plugins/WarpIDEOutputPlugin.ts'), '@truenine/plugin-windsurf': resolve('src/plugins/plugin-windsurf.ts'), '@truenine/plugin-zed': resolve('src/plugins/plugin-zed.ts') } diff --git a/sdk/vite.config.ts b/sdk/vite.config.ts index 1c390295..cd876eee 100644 --- a/sdk/vite.config.ts +++ b/sdk/vite.config.ts @@ -25,7 +25,6 @@ const pluginAliases: Record = { '@truenine/plugin-claude-code-cli': resolve('src/plugins/plugin-claude-code-cli.ts'), '@truenine/plugin-cursor': resolve('src/plugins/plugin-cursor.ts'), '@truenine/plugin-droid-cli': resolve('src/plugins/plugin-droid-cli.ts'), - '@truenine/plugin-editorconfig': resolve('src/plugins/plugin-editorconfig.ts'), '@truenine/plugin-gemini-cli': resolve('src/plugins/plugin-gemini-cli.ts'), '@truenine/plugin-git-exclude': resolve('src/plugins/plugin-git-exclude.ts'), '@truenine/plugin-input-agentskills': resolve('src/plugins/plugin-input-agentskills/index.ts'), @@ -54,7 +53,7 @@ const pluginAliases: Record = { '@truenine/plugin-readme': resolve('src/plugins/plugin-readme.ts'), '@truenine/plugin-trae-ide': resolve('src/plugins/plugin-trae-ide.ts'), '@truenine/plugin-vscode': resolve('src/plugins/plugin-vscode.ts'), - '@truenine/plugin-warp-ide': resolve('src/plugins/plugin-warp-ide.ts'), + '@truenine/plugin-warp-ide': resolve('src/plugins/WarpIDEOutputPlugin.ts'), '@truenine/plugin-windsurf': resolve('src/plugins/plugin-windsurf.ts'), '@truenine/plugin-zed': resolve('src/plugins/plugin-zed.ts') } From 846127e1a6efeb060bf385b11ab4260191fb1006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Sun, 5 Apr 2026 20:30:47 +0800 Subject: [PATCH 3/9] =?UTF-8?q?ci(setup-rust):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=20Rust=20=E5=B7=A5=E5=85=B7=E9=93=BE?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E8=87=B3=201.93.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/setup-rust/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index d1017840..67630fb6 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -5,7 +5,7 @@ inputs: rust-version: description: Rust toolchain version required: false - default: "1.88.0" + default: "1.93.1" targets: description: Additional Rust targets to install (comma-separated) required: false From 99294d34eaf8df0331892dc0b682c8dcba480c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Sun, 5 Apr 2026 21:12:21 +0800 Subject: [PATCH 4/9] Use cached Tauri setup for GUI PR tests --- .github/workflows/pull-request.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 57f00137..dfa26eea 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -143,9 +143,14 @@ jobs: - uses: ./.github/actions/setup-node-pnpm - - uses: ./.github/actions/setup-rust + - name: Read GUI version + id: gui-version + run: | + echo "version=$(node -p \"require('./gui/package.json').version\")" >> "$GITHUB_OUTPUT" + + - uses: ./.github/actions/setup-tauri with: - cache-key: pr + version: ${{ steps.gui-version.outputs.version }} - name: Build native modules run: pnpm run build:native From ebd39175069941b8a4dadcd163acfa59b7f37125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Sun, 5 Apr 2026 21:16:29 +0800 Subject: [PATCH 5/9] Fix GUI version output quoting in PR workflow --- .github/workflows/pull-request.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index dfa26eea..eec667c4 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -146,7 +146,8 @@ jobs: - name: Read GUI version id: gui-version run: | - echo "version=$(node -p \"require('./gui/package.json').version\")" >> "$GITHUB_OUTPUT" + version="$(node -p 'require("./gui/package.json").version')" + echo "version=$version" >> "$GITHUB_OUTPUT" - uses: ./.github/actions/setup-tauri with: From bcd223a9c64473584ac4bf767ce4d7ed8deff61e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:14:17 +0000 Subject: [PATCH 6/9] build(deps): bump tauri-plugin-updater in the cargo-workspace group Bumps the cargo-workspace group with 1 update: [tauri-plugin-updater](https://github.com/tauri-apps/plugins-workspace). Updates `tauri-plugin-updater` from 2.10.0 to 2.10.1 - [Release notes](https://github.com/tauri-apps/plugins-workspace/releases) - [Commits](https://github.com/tauri-apps/plugins-workspace/compare/updater-v2.10.0...updater-v2.10.1) --- updated-dependencies: - dependency-name: tauri-plugin-updater dependency-version: 2.10.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-workspace ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 968f01ab..c4a69145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4183,9 +4183,9 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61" +checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" dependencies = [ "base64 0.22.1", "dirs", From c84f34a4766423344ebec1707c5a633d4cb8c66e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:15:41 +0000 Subject: [PATCH 7/9] build(deps): bump sass in /doc in the npm-doc group Bumps the npm-doc group in /doc with 1 update: [sass](https://github.com/sass/dart-sass). Updates `sass` from 1.98.0 to 1.99.0 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.98.0...1.99.0) --- updated-dependencies: - dependency-name: sass dependency-version: 1.99.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: npm-doc ... Signed-off-by: dependabot[bot] --- doc/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/package.json b/doc/package.json index a9839850..7f6bb2a2 100644 --- a/doc/package.json +++ b/doc/package.json @@ -24,7 +24,7 @@ "nextra-theme-docs": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "sass": "^1.98.0" + "sass": "^1.99.0" }, "devDependencies": { "@truenine/eslint10-config": "catalog:", From 0b035f2dcc9b756d08dc1a9df88a527f19a033ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:18:18 +0000 Subject: [PATCH 8/9] build(deps): bump the npm-root group with 10 updates Bumps the npm-root group with 10 updates: | Package | From | To | | --- | --- | --- | | [sass](https://github.com/sass/dart-sass) | `1.98.0` | `1.99.0` | | [@antfu/eslint-config](https://github.com/antfu/eslint-config) | `7.7.3` | `8.0.0` | | [@tauri-apps/plugin-updater](https://github.com/tauri-apps/plugins-workspace) | `2.10.0` | `2.10.1` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.5.0` | `25.5.2` | | [eslint](https://github.com/eslint/eslint) | `10.1.0` | `10.2.0` | | [fast-glob](https://github.com/mrmlnc/fast-glob) | `3.3.1` | `3.3.3` | | [picomatch](https://github.com/micromatch/picomatch) | `2.3.2` | `4.0.4` | | [@types/picomatch](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/picomatch) | `4.0.2` | `4.0.3` | | [turbo](https://github.com/vercel/turborepo) | `2.9.3` | `2.9.4` | | [zod](https://github.com/colinhacks/zod) | `3.25.76` | `4.3.6` | Updates `sass` from 1.98.0 to 1.99.0 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.98.0...1.99.0) Updates `@antfu/eslint-config` from 7.7.3 to 8.0.0 - [Release notes](https://github.com/antfu/eslint-config/releases) - [Commits](https://github.com/antfu/eslint-config/compare/v7.7.3...v8.0.0) Updates `@tauri-apps/plugin-updater` from 2.10.0 to 2.10.1 - [Release notes](https://github.com/tauri-apps/plugins-workspace/releases) - [Commits](https://github.com/tauri-apps/plugins-workspace/compare/updater-v2.10.0...updater-v2.10.1) Updates `@types/node` from 25.5.0 to 25.5.2 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `eslint` from 10.1.0 to 10.2.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.1.0...v10.2.0) Updates `fast-glob` from 3.3.1 to 3.3.3 - [Release notes](https://github.com/mrmlnc/fast-glob/releases) - [Commits](https://github.com/mrmlnc/fast-glob/compare/3.3.1...3.3.3) Updates `picomatch` from 2.3.2 to 4.0.4 - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/picomatch/compare/2.3.2...4.0.4) Updates `@types/picomatch` from 4.0.2 to 4.0.3 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/picomatch) Updates `turbo` from 2.9.3 to 2.9.4 - [Release notes](https://github.com/vercel/turborepo/releases) - [Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md) - [Commits](https://github.com/vercel/turborepo/compare/v2.9.3...v2.9.4) Updates `zod` from 3.25.76 to 4.3.6 - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](https://github.com/colinhacks/zod/compare/v3.25.76...v4.3.6) --- updated-dependencies: - dependency-name: sass dependency-version: 1.99.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: npm-root - dependency-name: "@antfu/eslint-config" dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: npm-root - dependency-name: "@tauri-apps/plugin-updater" dependency-version: 2.10.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: npm-root - dependency-name: "@types/node" dependency-version: 25.5.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: npm-root - dependency-name: eslint dependency-version: 10.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: npm-root - dependency-name: fast-glob dependency-version: 3.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: npm-root - dependency-name: picomatch dependency-version: 4.0.4 dependency-type: direct:production update-type: version-update:semver-major dependency-group: npm-root - dependency-name: "@types/picomatch" dependency-version: 4.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: npm-root - dependency-name: turbo dependency-version: 2.9.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: npm-root - dependency-name: zod dependency-version: 4.3.6 dependency-type: direct:production update-type: version-update:semver-major dependency-group: npm-root ... Signed-off-by: dependabot[bot] --- doc/package.json | 2 +- pnpm-lock.yaml | 876 ++++++++++++++++++++++---------------------- pnpm-workspace.yaml | 12 +- 3 files changed, 442 insertions(+), 448 deletions(-) diff --git a/doc/package.json b/doc/package.json index a9839850..7f6bb2a2 100644 --- a/doc/package.json +++ b/doc/package.json @@ -24,7 +24,7 @@ "nextra-theme-docs": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "sass": "^1.98.0" + "sass": "^1.99.0" }, "devDependencies": { "@truenine/eslint10-config": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16325f37..045eaedb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,8 @@ settings: catalogs: default: '@antfu/eslint-config': - specifier: ^7.7.3 - version: 7.7.3 + specifier: ^8.0.0 + version: 8.0.0 '@clack/prompts': specifier: ^1.2.0 version: 1.2.0 @@ -49,8 +49,8 @@ catalogs: specifier: ^2.3.5 version: 2.3.5 '@tauri-apps/plugin-updater': - specifier: ^2.10.0 - version: 2.10.0 + specifier: ^2.10.1 + version: 2.10.1 '@theguild/remark-mermaid': specifier: ^0.3.0 version: 0.3.0 @@ -70,11 +70,11 @@ catalogs: specifier: ^4.0.4 version: 4.0.4 '@types/node': - specifier: ^25.5.0 - version: 25.5.0 + specifier: ^25.5.2 + version: 25.5.2 '@types/picomatch': - specifier: ^4.0.2 - version: 4.0.2 + specifier: ^4.0.3 + version: 4.0.3 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -103,8 +103,8 @@ catalogs: specifier: ^2.1.1 version: 2.1.1 eslint: - specifier: ^10.1.0 - version: 10.1.0 + specifier: ^10.2.0 + version: 10.2.0 eslint-plugin-format: specifier: ^2.0.1 version: 2.0.1 @@ -208,8 +208,8 @@ catalogs: specifier: ^4.21.0 version: 4.21.0 turbo: - specifier: ^2.9.3 - version: 2.9.3 + specifier: ^2.9.4 + version: 2.9.4 tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -244,43 +244,43 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 'catalog:' - version: 7.7.3(@next/eslint-plugin-next@16.2.2)(@typescript-eslint/rule-tester@8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@unocss/eslint-plugin@66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@vue/compiler-sfc@3.5.26)(eslint-plugin-format@2.0.1(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))) + version: 8.0.0(@next/eslint-plugin-next@16.2.2)(@typescript-eslint/rule-tester@8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@unocss/eslint-plugin@66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vue/compiler-sfc@3.5.26)(eslint-plugin-format@2.0.1(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))) '@eslint/js': specifier: 'catalog:' - version: 10.0.1(eslint@10.1.0(jiti@2.6.1)) + version: 10.0.1(eslint@10.2.0(jiti@2.6.1)) '@napi-rs/cli': specifier: 'catalog:' - version: 3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(node-addon-api@7.1.1) + version: 3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(node-addon-api@7.1.1) '@next/eslint-plugin-next': specifier: 'catalog:' version: 16.2.2 '@truenine/eslint10-config': specifier: 'catalog:' - version: 2026.10402.10314(b6f5f8bbc7b250991807fe8a46682c75) + version: 2026.10402.10314(b56a2921cd0832854c1ed6fed0241803) '@types/node': specifier: 'catalog:' - version: 25.5.0 + version: 25.5.2 '@unocss/eslint-config': specifier: 'catalog:' - version: 66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + version: 66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vue/eslint-config-prettier': specifier: 'catalog:' - version: 10.2.0(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1) + version: 10.2.0(eslint@10.2.0(jiti@2.6.1))(prettier@3.8.1) '@vue/eslint-config-typescript': specifier: 'catalog:' - version: 14.7.0(eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1))))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + version: 14.7.0(eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) eslint: specifier: 'catalog:' - version: 10.1.0(jiti@2.6.1) + version: 10.2.0(jiti@2.6.1) eslint-plugin-format: specifier: 'catalog:' - version: 2.0.1(eslint@10.1.0(jiti@2.6.1)) + version: 2.0.1(eslint@10.2.0(jiti@2.6.1)) eslint-plugin-prettier: specifier: 'catalog:' - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(prettier@3.8.1) eslint-plugin-vue: specifier: 'catalog:' - version: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1))) + version: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))) fast-check: specifier: 'catalog:' version: 4.6.0 @@ -301,37 +301,37 @@ importers: version: 4.21.0 turbo: specifier: 'catalog:' - version: 2.9.3 + version: 2.9.4 typescript: specifier: 'catalog:' version: 6.0.2 typescript-eslint: specifier: 'catalog:' - version: 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + version: 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) vite: specifier: 'catalog:' - version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) cli: devDependencies: '@truenine/eslint10-config': specifier: 'catalog:' - version: 2026.10402.10314(b6f5f8bbc7b250991807fe8a46682c75) + version: 2026.10402.10314(b56a2921cd0832854c1ed6fed0241803) '@truenine/memory-sync-sdk': specifier: workspace:* version: link:../sdk '@types/node': specifier: 'catalog:' - version: 25.5.0 + version: 25.5.2 '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.2(vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))) + version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))) eslint: specifier: 'catalog:' - version: 10.1.0(jiti@2.6.1) + version: 10.2.0(jiti@2.6.1) npm-run-all2: specifier: 'catalog:' version: 8.0.4 @@ -346,7 +346,7 @@ importers: version: 6.0.2 vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: '@truenine/memory-sync-cli-darwin-arm64': specifier: workspace:* @@ -396,13 +396,13 @@ importers: version: 11.14.0 next: specifier: 'catalog:' - version: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) + version: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0) nextra: specifier: 'catalog:' - version: 4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2) + version: 4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2) nextra-theme-docs: specifier: 'catalog:' - version: 4.6.1(@types/react@19.2.14)(immer@11.1.4)(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(nextra@4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + version: 4.6.1(@types/react@19.2.14)(immer@11.1.4)(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(nextra@4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) react: specifier: 'catalog:' version: 19.2.4 @@ -410,15 +410,15 @@ importers: specifier: 'catalog:' version: 19.2.4(react@19.2.4) sass: - specifier: ^1.98.0 - version: 1.98.0 + specifier: ^1.99.0 + version: 1.99.0 devDependencies: '@truenine/eslint10-config': specifier: 'catalog:' - version: 2026.10402.10314(b6f5f8bbc7b250991807fe8a46682c75) + version: 2026.10402.10314(b56a2921cd0832854c1ed6fed0241803) '@types/node': specifier: 'catalog:' - version: 25.5.0 + version: 25.5.2 '@types/react': specifier: 'catalog:' version: 19.2.14 @@ -427,7 +427,7 @@ importers: version: 19.2.3(@types/react@19.2.14) eslint: specifier: 'catalog:' - version: 10.1.0(jiti@2.6.1) + version: 10.2.0(jiti@2.6.1) fast-glob: specifier: 'catalog:' version: 3.3.3 @@ -454,7 +454,7 @@ importers: version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/react-router': specifier: 'catalog:' version: 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -463,7 +463,7 @@ importers: version: 1.166.24 '@tanstack/router-plugin': specifier: 'catalog:' - version: 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) '@tauri-apps/api': specifier: 'catalog:' version: 2.10.1 @@ -475,7 +475,7 @@ importers: version: 2.3.5 '@tauri-apps/plugin-updater': specifier: 'catalog:' - version: 2.10.0 + version: 2.10.1 '@types/react': specifier: 'catalog:' version: 19.2.14 @@ -484,7 +484,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) class-variance-authority: specifier: 'catalog:' version: 0.7.1 @@ -523,13 +523,13 @@ importers: version: 1.4.0 vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) libraries/logger: devDependencies: '@napi-rs/cli': specifier: 'catalog:' - version: 3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(node-addon-api@7.1.1) + version: 3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(node-addon-api@7.1.1) npm-run-all2: specifier: 'catalog:' version: 8.0.4 @@ -541,13 +541,13 @@ importers: version: 6.0.2 vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) libraries/md-compiler: devDependencies: '@napi-rs/cli': specifier: 'catalog:' - version: 3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(node-addon-api@7.1.1) + version: 3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(node-addon-api@7.1.1) '@types/estree': specifier: 'catalog:' version: 1.0.8 @@ -589,7 +589,7 @@ importers: version: 11.0.5 vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) yaml: specifier: 'catalog:' version: 2.8.3 @@ -598,16 +598,16 @@ importers: devDependencies: '@napi-rs/cli': specifier: 'catalog:' - version: 3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(node-addon-api@7.1.1) + version: 3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(node-addon-api@7.1.1) '@truenine/eslint10-config': specifier: 'catalog:' - version: 2026.10402.10314(b6f5f8bbc7b250991807fe8a46682c75) + version: 2026.10402.10314(b56a2921cd0832854c1ed6fed0241803) '@types/node': specifier: 'catalog:' - version: 25.5.0 + version: 25.5.2 eslint: specifier: 'catalog:' - version: 10.1.0(jiti@2.6.1) + version: 10.2.0(jiti@2.6.1) jiti: specifier: 'catalog:' version: 2.6.1 @@ -622,7 +622,7 @@ importers: version: 6.0.2 vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) mcp: dependencies: @@ -638,13 +638,13 @@ importers: devDependencies: '@truenine/eslint10-config': specifier: 'catalog:' - version: 2026.10402.10314(b6f5f8bbc7b250991807fe8a46682c75) + version: 2026.10402.10314(b56a2921cd0832854c1ed6fed0241803) '@types/node': specifier: 'catalog:' - version: 25.5.0 + version: 25.5.2 eslint: specifier: 'catalog:' - version: 10.1.0(jiti@2.6.1) + version: 10.2.0(jiti@2.6.1) npm-run-all2: specifier: 'catalog:' version: 8.0.4 @@ -659,7 +659,7 @@ importers: version: 6.0.2 vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) sdk: dependencies: @@ -690,10 +690,10 @@ importers: version: 11.0.4 '@types/picomatch': specifier: 'catalog:' - version: 4.0.2 + version: 4.0.3 '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.2(vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))) + version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))) fast-glob: specifier: 'catalog:' version: 3.3.3 @@ -717,21 +717,21 @@ importers: version: 4.21.0 vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) zod-to-json-schema: specifier: 'catalog:' version: 3.25.2(zod@4.3.6) packages: - '@antfu/eslint-config@7.7.3': - resolution: {integrity: sha512-BtroDxTvmWtvr3yJkdWVCvwsKlnEdkreoeOyrdNezc/W5qaiQNf2xjcsQ3N5Yy0x27h+0WFfW8rG8YlVioG6dw==} + '@antfu/eslint-config@8.0.0': + resolution: {integrity: sha512-IKiCfsa1vRgj8srB2azqiN3nOAcVyP/TZ5Ibiz0TDW9NoQPizTvkmRTSi1vo4ax0SL9TH/8uJLK6uCfd6bQzLA==} hasBin: true peerDependencies: '@angular-eslint/eslint-plugin': ^21.1.0 '@angular-eslint/eslint-plugin-template': ^21.1.0 '@angular-eslint/template-parser': ^21.1.0 - '@eslint-react/eslint-plugin': ^2.11.0 + '@eslint-react/eslint-plugin': ^3.0.0 '@next/eslint-plugin-next': '>=15.0.0' '@prettier/plugin-xml': ^3.4.1 '@unocss/eslint-plugin': '>=0.50.0' @@ -740,7 +740,6 @@ packages: eslint-plugin-astro: ^1.2.0 eslint-plugin-format: '>=0.1.0' eslint-plugin-jsx-a11y: '>=6.10.2' - eslint-plugin-react-hooks: ^7.0.0 eslint-plugin-react-refresh: ^0.5.0 eslint-plugin-solid: ^0.14.3 eslint-plugin-svelte: '>=2.35.1' @@ -771,8 +770,6 @@ packages: optional: true eslint-plugin-jsx-a11y: optional: true - eslint-plugin-react-hooks: - optional: true eslint-plugin-react-refresh: optional: true eslint-plugin-solid: @@ -932,11 +929,11 @@ packages: '@dprint/toml@0.7.0': resolution: {integrity: sha512-eFaQTcfxKHB+YyTh83x7GEv+gDPuj9q5NFOTaoj5rZmQTbj6OgjjMxUicmS1R8zYcx8YAq5oA9J3YFa5U6x2gA==} - '@e18e/eslint-plugin@0.2.0': - resolution: {integrity: sha512-mXgODVwhuDjTJ+UT+XSvmMmCidtGKfrV5nMIv1UtpWex2pYLsIM3RSpT8HWIMAebS9qANbXPKlSX4BE7ZvuCgA==} + '@e18e/eslint-plugin@0.3.0': + resolution: {integrity: sha512-hHgfpxsrZ2UYHcicA+tGZnmk19uJTaye9VH79O+XS8R4ona2Hx3xjhXghclNW58uXMk3xXlbYEOMr8thsoBmWg==} peerDependencies: eslint: ^9.0.0 || ^10.0.0 - oxlint: ^1.41.0 + oxlint: ^1.55.0 peerDependenciesMeta: eslint: optional: true @@ -1136,8 +1133,8 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@2.0.3': - resolution: {integrity: sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==} + '@eslint/compat@2.0.4': + resolution: {integrity: sha512-o598tCGstJv9Kk4XapwP+oDij9HD9Qr3V37ABzTfdzVvbFciV+sfg9zSW6olj6G/IXj7p89SwSzPnZ+JUEPIPg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: eslint: ^8.40 || 9 || 10 @@ -1145,20 +1142,16 @@ packages: eslint: optional: true - '@eslint/config-array@0.23.3': - resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} + '@eslint/config-array@0.23.4': + resolution: {integrity: sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.5.3': - resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} + '@eslint/config-helpers@0.5.4': + resolution: {integrity: sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@1.1.1': - resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} + '@eslint/core@1.2.0': + resolution: {integrity: sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/js@10.0.1': @@ -1170,22 +1163,22 @@ packages: eslint: optional: true - '@eslint/markdown@7.5.1': - resolution: {integrity: sha512-R8uZemG9dKTbru/DQRPblbJyXpObwKzo8rv1KYGGuPUPtjM4LXBYM9q5CIZAComzZupws3tWbDwam5AFpPLyJQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@3.0.3': - resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} + '@eslint/markdown@8.0.1': + resolution: {integrity: sha512-WWKmld/EyNdEB8GMq7JMPX1SDWgyJAM1uhtCi5ySrqYQM4HQjmg11EX/q3ZpnpRXHfdccFtli3NBvvGaYjWyQw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.4': + resolution: {integrity: sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/plugin-kit@0.6.1': resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/plugin-kit@0.7.0': + resolution: {integrity: sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -2845,8 +2838,8 @@ packages: '@tauri-apps/plugin-shell@2.3.5': resolution: {integrity: sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==} - '@tauri-apps/plugin-updater@2.10.0': - resolution: {integrity: sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ==} + '@tauri-apps/plugin-updater@2.10.1': + resolution: {integrity: sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==} '@theguild/remark-mermaid@0.3.0': resolution: {integrity: sha512-Fy1J4FSj8totuHsHFpaeWyWRaRSIvpzGTRoEfnNJc1JmLV9uV70sYE3zcT+Jj5Yw20Xq4iCsiT+3Ho49BBZcBQ==} @@ -2875,33 +2868,33 @@ packages: '@ts-morph/common@0.28.1': resolution: {integrity: sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==} - '@turbo/darwin-64@2.9.3': - resolution: {integrity: sha512-P8foouaP+y/p+hhEGBoZpzMbpVvUMwPjDpcy6wN7EYfvvyISD1USuV27qWkczecihwuPJzQ1lDBuL8ERcavTyg==} + '@turbo/darwin-64@2.9.4': + resolution: {integrity: sha512-ZSlPqJ5Vqg/wgVw8P3AOVCIosnbBilOxLq7TMz3MN/9U46DUYfdG2jtfevNDufyxyrg98pcPs/GBgDRaaids6g==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.9.3': - resolution: {integrity: sha512-SIzEkvtNdzdI50FJDaIQ6kQGqgSSdFPcdn0wqmmONN6iGKjy6hsT+EH99GP65FsfV7DLZTh2NmtTIRl2kdoz5Q==} + '@turbo/darwin-arm64@2.9.4': + resolution: {integrity: sha512-9cjTWe4OiNlFMSRggPNh+TJlRs7MS5FWrHc96MOzft5vESWjjpvaadYPv5ykDW7b45mVHOF2U/W+48LoX9USWw==} cpu: [arm64] os: [darwin] - '@turbo/linux-64@2.9.3': - resolution: {integrity: sha512-pLRwFmcHHNBvsCySLS6OFabr/07kDT2pxEt/k6eBf/3asiVQZKJ7Rk88AafQx2aYA641qek4RsXvYO3JYpiBug==} + '@turbo/linux-64@2.9.4': + resolution: {integrity: sha512-Cl1GjxqBXQ+r9KKowmXG+lhD1gclLp48/SE7NxL//66iaMytRw0uiphWGOkccD92iPiRjHLRUaA9lOTtgr5OCA==} cpu: [x64] os: [linux] - '@turbo/linux-arm64@2.9.3': - resolution: {integrity: sha512-gy6ApUroC2Nzv+qjGtE/uPNkhHAFU4c8God+zd5Aiv9L9uBgHlxVJpHT3XWl5xwlJZ2KWuMrlHTaS5kmNB+q1Q==} + '@turbo/linux-arm64@2.9.4': + resolution: {integrity: sha512-j2hPAKVmGNN2EsKigEWD+43y9m7zaPhNAs6ptsyfq0u7evHHBAXAwOfv86OEMg/gvC+pwGip0i1CIm1bR1vYug==} cpu: [arm64] os: [linux] - '@turbo/windows-64@2.9.3': - resolution: {integrity: sha512-d0YelTX6hAsB7kIEtGB3PzIzSfAg3yDoUlHwuwJc3adBXUsyUIs0YLG+1NNtuhcDOUGnWQeKUoJ2pGWvbpRj7w==} + '@turbo/windows-64@2.9.4': + resolution: {integrity: sha512-1jWPjCe9ZRmsDTXE7uzqfySNQspnUx0g6caqvwps+k/sc+fm9hC/4zRQKlXZLbVmP3Xxp601Ju71boegHdnYGw==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.9.3': - resolution: {integrity: sha512-/08CwpKJl3oRY8nOlh2YgilZVJDHsr60XTNxRhuDeuFXONpUZ5X+Nv65izbG/xBew9qxcJFbDX9/sAmAX+ITcQ==} + '@turbo/windows-arm64@2.9.4': + resolution: {integrity: sha512-dlko15TQVu/BFYmIY018Y3covWMRQlUgAkD+OOk+Rokcfj6VY02Vv4mCfT/Zns6B4q8jGbOd6IZhnCFYsE8Viw==} cpu: [arm64] os: [win32] @@ -3052,11 +3045,11 @@ packages: '@types/nlcst@2.0.3': resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} - '@types/node@25.5.0': - resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/node@25.5.2': + resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} - '@types/picomatch@4.0.2': - resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==} + '@types/picomatch@4.0.3': + resolution: {integrity: sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ==} '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} @@ -3319,6 +3312,7 @@ packages: '@xmldom/xmldom@0.9.8': resolution: {integrity: sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==} engines: {node: '>=14.6'} + deprecated: this version has critical issues, please update to the latest version accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} @@ -4020,11 +4014,11 @@ packages: peerDependencies: eslint: ^8.40.0 || ^9.0.0 || ^10.0.0 - eslint-plugin-import-lite@0.5.2: - resolution: {integrity: sha512-XvfdWOC5dSLEI9krIPRlNmKSI2ViIE9pVylzfV9fCq0ZpDaNeUk6o0wZv0OzN83QdadgXp1NsY0qjLINxwYCsw==} + eslint-plugin-import-lite@0.6.0: + resolution: {integrity: sha512-80vevx2A7i3H7n1/6pqDO8cc5wRz6OwLDvIyVl9UflBV1N1f46e9Ihzi65IOLYoSxM6YykK2fTw1xm0Ixx6aTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: '>=9.0.0' + eslint: ^9.0.0 || ^10.0.0 eslint-plugin-jsdoc@62.9.0: resolution: {integrity: sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==} @@ -4048,8 +4042,8 @@ packages: resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} engines: {node: '>=5.0.0'} - eslint-plugin-perfectionist@5.7.0: - resolution: {integrity: sha512-WRHj7OZS/INutQ/gKN5C1ZGnMhkQ3oKZQAA2I7rl5yM8keBtSd9oj/qlJaHuwh5873FhMPqYlttcadF0YsTN7g==} + eslint-plugin-perfectionist@5.8.0: + resolution: {integrity: sha512-k8uIptWIxkUclonCFGyDzgYs9NI+Qh0a7cUXS3L7IYZDEsjXuimFBVbxXPQQngWqMiaxJRwbtYB4smMGMqF+cw==} engines: {node: ^20.0.0 || >=22.0.0} peerDependencies: eslint: ^8.45.0 || ^9.0.0 || ^10.0.0 @@ -4085,8 +4079,8 @@ packages: peerDependencies: eslint: '>=9.38.0' - eslint-plugin-unicorn@63.0.0: - resolution: {integrity: sha512-Iqecl9118uQEXYh7adylgEmGfkn5es3/mlQTLLkd4pXkIk9CTGrAbeUux+YljSa2ohXCBmQQ0+Ej1kZaFgcfkA==} + eslint-plugin-unicorn@64.0.0: + resolution: {integrity: sha512-rNZwalHh8i0UfPlhNwg5BTUO1CMdKNmjqe+TgzOTZnpKoi8VBgsW7u9qCHIdpxEzZ1uwrJrPF0uRb7l//K38gA==} engines: {node: ^20.10.0 || >=21.0.0} peerDependencies: eslint: '>=9.38.0' @@ -4142,8 +4136,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.1.0: - resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==} + eslint@10.2.0: + resolution: {integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -4406,10 +4400,6 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} - engines: {node: '>=18'} - globals@17.4.0: resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} engines: {node: '>=18'} @@ -4721,6 +4711,10 @@ packages: resolution: {integrity: sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ==} hasBin: true + katex@0.16.45: + resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -5516,8 +5510,8 @@ packages: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true - regjsparser@0.13.0: - resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true rehype-katex@7.0.1: @@ -5643,8 +5637,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.98.0: - resolution: {integrity: sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==} + sass@1.99.0: + resolution: {integrity: sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==} engines: {node: '>=14.0.0'} hasBin: true @@ -5952,8 +5946,8 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo@2.9.3: - resolution: {integrity: sha512-J/VUvsGRykPb9R8Kh8dHVBOqioDexLk9BhLCU/ZybRR+HN9UR3cURdazFvNgMDt9zPP8TF6K73Z+tplfmi0PqQ==} + turbo@2.9.4: + resolution: {integrity: sha512-wZ/kMcZCuK5oEp7sXSSo/5fzKjP9I2EhoiarZjyCm2Ixk0WxFrC/h0gF3686eHHINoFQOOSWgB/pGfvkR8rkgQ==} hasBin: true tw-animate-css@1.4.0: @@ -6295,49 +6289,49 @@ packages: snapshots: - '@antfu/eslint-config@7.7.3(@next/eslint-plugin-next@16.2.2)(@typescript-eslint/rule-tester@8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@unocss/eslint-plugin@66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@vue/compiler-sfc@3.5.26)(eslint-plugin-format@2.0.1(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)))': + '@antfu/eslint-config@8.0.0(@next/eslint-plugin-next@16.2.2)(@typescript-eslint/rule-tester@8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@unocss/eslint-plugin@66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vue/compiler-sfc@3.5.26)(eslint-plugin-format@2.0.1(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 1.2.0 - '@e18e/eslint-plugin': 0.2.0(eslint@10.1.0(jiti@2.6.1)) - '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.1.0(jiti@2.6.1)) - '@eslint/markdown': 7.5.1 - '@stylistic/eslint-plugin': 5.10.0(eslint@10.1.0(jiti@2.6.1)) - '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/parser': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - '@vitest/eslint-plugin': 1.6.14(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))) + '@e18e/eslint-plugin': 0.3.0(eslint@10.2.0(jiti@2.6.1)) + '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.2.0(jiti@2.6.1)) + '@eslint/markdown': 8.0.1 + '@stylistic/eslint-plugin': 5.10.0(eslint@10.2.0(jiti@2.6.1)) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@vitest/eslint-plugin': 1.6.14(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))) ansis: 4.2.0 cac: 7.0.0 - eslint: 10.1.0(jiti@2.6.1) - eslint-config-flat-gitignore: 2.3.0(eslint@10.1.0(jiti@2.6.1)) + eslint: 10.2.0(jiti@2.6.1) + eslint-config-flat-gitignore: 2.3.0(eslint@10.2.0(jiti@2.6.1)) eslint-flat-config-utils: 3.1.0 - eslint-merge-processors: 2.0.0(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-antfu: 3.2.2(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-command: 3.5.2(@typescript-eslint/rule-tester@8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-import-lite: 0.5.2(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-jsdoc: 62.9.0(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-jsonc: 3.1.2(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-n: 17.24.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + eslint-merge-processors: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-antfu: 3.2.2(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-command: 3.5.2(@typescript-eslint/rule-tester@8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-import-lite: 0.6.0(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-jsdoc: 62.9.0(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-jsonc: 3.1.2(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-n: 17.24.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-perfectionist: 5.7.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-pnpm: 1.6.0(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-regexp: 3.1.0(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-toml: 1.3.1(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-unicorn: 63.0.0(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1))) - eslint-plugin-yml: 3.3.1(eslint@10.1.0(jiti@2.6.1)) - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.26)(eslint@10.1.0(jiti@2.6.1)) + eslint-plugin-perfectionist: 5.8.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint-plugin-pnpm: 1.6.0(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-regexp: 3.1.0(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-toml: 1.3.1(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-unicorn: 64.0.0(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))) + eslint-plugin-yml: 3.3.1(eslint@10.2.0(jiti@2.6.1)) + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.26)(eslint@10.2.0(jiti@2.6.1)) globals: 17.4.0 local-pkg: 1.1.2 parse-gitignore: 2.0.0 toml-eslint-parser: 1.0.3 - vue-eslint-parser: 10.4.0(eslint@10.1.0(jiti@2.6.1)) + vue-eslint-parser: 10.4.0(eslint@10.2.0(jiti@2.6.1)) yaml-eslint-parser: 2.0.0 optionalDependencies: '@next/eslint-plugin-next': 16.2.2 - '@unocss/eslint-plugin': 66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-format: 2.0.1(eslint@10.1.0(jiti@2.6.1)) + '@unocss/eslint-plugin': 66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint-plugin-format: 2.0.1(eslint@10.2.0(jiti@2.6.1)) transitivePeerDependencies: - '@eslint/json' - '@typescript-eslint/rule-tester' @@ -6527,11 +6521,11 @@ snapshots: '@dprint/toml@0.7.0': {} - '@e18e/eslint-plugin@0.2.0(eslint@10.1.0(jiti@2.6.1))': + '@e18e/eslint-plugin@0.3.0(eslint@10.2.0(jiti@2.6.1))': dependencies: - eslint-plugin-depend: 1.5.0(eslint@10.1.0(jiti@2.6.1)) + eslint-plugin-depend: 1.5.0(eslint@10.2.0(jiti@2.6.1)) optionalDependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) '@emnapi/core@1.9.1': dependencies: @@ -6645,73 +6639,71 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.1.0(jiti@2.6.1))': + '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.2.0(jiti@2.6.1))': dependencies: escape-string-regexp: 4.0.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) ignore: 7.0.5 - '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.0(jiti@2.6.1))': dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@2.0.3(eslint@10.1.0(jiti@2.6.1))': + '@eslint/compat@2.0.4(eslint@10.2.0(jiti@2.6.1))': dependencies: - '@eslint/core': 1.1.1 + '@eslint/core': 1.2.0 optionalDependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) - '@eslint/config-array@0.23.3': + '@eslint/config-array@0.23.4': dependencies: - '@eslint/object-schema': 3.0.3 + '@eslint/object-schema': 3.0.4 debug: 4.4.3 minimatch: 10.2.5 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.3': + '@eslint/config-helpers@0.5.4': dependencies: - '@eslint/core': 1.1.1 - - '@eslint/core@0.17.0': - dependencies: - '@types/json-schema': 7.0.15 + '@eslint/core': 1.2.0 - '@eslint/core@1.1.1': + '@eslint/core@1.2.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.1.0(jiti@2.6.1))': + '@eslint/js@10.0.1(eslint@10.2.0(jiti@2.6.1))': optionalDependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) - '@eslint/markdown@7.5.1': + '@eslint/markdown@8.0.1': dependencies: - '@eslint/core': 0.17.0 - '@eslint/plugin-kit': 0.4.1 + '@eslint/core': 1.2.0 + '@eslint/plugin-kit': 0.6.1 github-slugger: 2.0.0 mdast-util-from-markdown: 2.0.3 mdast-util-frontmatter: 2.0.1 mdast-util-gfm: 3.1.0 + mdast-util-math: 3.0.0 micromark-extension-frontmatter: 2.0.0 micromark-extension-gfm: 3.0.0 + micromark-extension-math: 3.1.0 micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: - supports-color - '@eslint/object-schema@3.0.3': {} + '@eslint/object-schema@3.0.4': {} - '@eslint/plugin-kit@0.4.1': + '@eslint/plugin-kit@0.6.1': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.0 levn: 0.4.1 - '@eslint/plugin-kit@0.6.1': + '@eslint/plugin-kit@0.7.0': dependencies: - '@eslint/core': 1.1.1 + '@eslint/core': 1.2.0 levn: 0.4.1 '@floating-ui/core@1.7.5': @@ -6875,122 +6867,122 @@ snapshots: '@inquirer/ansi@2.0.4': {} - '@inquirer/checkbox@5.1.2(@types/node@25.5.0)': + '@inquirer/checkbox@5.1.2(@types/node@25.5.2)': dependencies: '@inquirer/ansi': 2.0.4 - '@inquirer/core': 11.1.7(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) '@inquirer/figures': 2.0.4 - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/confirm@6.0.10(@types/node@25.5.0)': + '@inquirer/confirm@6.0.10(@types/node@25.5.2)': dependencies: - '@inquirer/core': 11.1.7(@types/node@25.5.0) - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/core@11.1.7(@types/node@25.5.0)': + '@inquirer/core@11.1.7(@types/node@25.5.2)': dependencies: '@inquirer/ansi': 2.0.4 '@inquirer/figures': 2.0.4 - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/type': 4.0.4(@types/node@25.5.2) cli-width: 4.1.0 fast-wrap-ansi: 0.2.0 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/editor@5.0.10(@types/node@25.5.0)': + '@inquirer/editor@5.0.10(@types/node@25.5.2)': dependencies: - '@inquirer/core': 11.1.7(@types/node@25.5.0) - '@inquirer/external-editor': 2.0.4(@types/node@25.5.0) - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) + '@inquirer/external-editor': 2.0.4(@types/node@25.5.2) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/expand@5.0.10(@types/node@25.5.0)': + '@inquirer/expand@5.0.10(@types/node@25.5.2)': dependencies: - '@inquirer/core': 11.1.7(@types/node@25.5.0) - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/external-editor@2.0.4(@types/node@25.5.0)': + '@inquirer/external-editor@2.0.4(@types/node@25.5.2)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@inquirer/figures@2.0.4': {} - '@inquirer/input@5.0.10(@types/node@25.5.0)': + '@inquirer/input@5.0.10(@types/node@25.5.2)': dependencies: - '@inquirer/core': 11.1.7(@types/node@25.5.0) - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/number@4.0.10(@types/node@25.5.0)': + '@inquirer/number@4.0.10(@types/node@25.5.2)': dependencies: - '@inquirer/core': 11.1.7(@types/node@25.5.0) - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/password@5.0.10(@types/node@25.5.0)': + '@inquirer/password@5.0.10(@types/node@25.5.2)': dependencies: '@inquirer/ansi': 2.0.4 - '@inquirer/core': 11.1.7(@types/node@25.5.0) - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 - - '@inquirer/prompts@8.3.2(@types/node@25.5.0)': - dependencies: - '@inquirer/checkbox': 5.1.2(@types/node@25.5.0) - '@inquirer/confirm': 6.0.10(@types/node@25.5.0) - '@inquirer/editor': 5.0.10(@types/node@25.5.0) - '@inquirer/expand': 5.0.10(@types/node@25.5.0) - '@inquirer/input': 5.0.10(@types/node@25.5.0) - '@inquirer/number': 4.0.10(@types/node@25.5.0) - '@inquirer/password': 5.0.10(@types/node@25.5.0) - '@inquirer/rawlist': 5.2.6(@types/node@25.5.0) - '@inquirer/search': 4.1.6(@types/node@25.5.0) - '@inquirer/select': 5.1.2(@types/node@25.5.0) + '@types/node': 25.5.2 + + '@inquirer/prompts@8.3.2(@types/node@25.5.2)': + dependencies: + '@inquirer/checkbox': 5.1.2(@types/node@25.5.2) + '@inquirer/confirm': 6.0.10(@types/node@25.5.2) + '@inquirer/editor': 5.0.10(@types/node@25.5.2) + '@inquirer/expand': 5.0.10(@types/node@25.5.2) + '@inquirer/input': 5.0.10(@types/node@25.5.2) + '@inquirer/number': 4.0.10(@types/node@25.5.2) + '@inquirer/password': 5.0.10(@types/node@25.5.2) + '@inquirer/rawlist': 5.2.6(@types/node@25.5.2) + '@inquirer/search': 4.1.6(@types/node@25.5.2) + '@inquirer/select': 5.1.2(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/rawlist@5.2.6(@types/node@25.5.0)': + '@inquirer/rawlist@5.2.6(@types/node@25.5.2)': dependencies: - '@inquirer/core': 11.1.7(@types/node@25.5.0) - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/search@4.1.6(@types/node@25.5.0)': + '@inquirer/search@4.1.6(@types/node@25.5.2)': dependencies: - '@inquirer/core': 11.1.7(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) '@inquirer/figures': 2.0.4 - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/select@5.1.2(@types/node@25.5.0)': + '@inquirer/select@5.1.2(@types/node@25.5.2)': dependencies: '@inquirer/ansi': 2.0.4 - '@inquirer/core': 11.1.7(@types/node@25.5.0) + '@inquirer/core': 11.1.7(@types/node@25.5.2) '@inquirer/figures': 2.0.4 - '@inquirer/type': 4.0.4(@types/node@25.5.0) + '@inquirer/type': 4.0.4(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 - '@inquirer/type@4.0.4(@types/node@25.5.0)': + '@inquirer/type@4.0.4(@types/node@25.5.2)': optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -7078,9 +7070,9 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@napi-rs/cli@3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(node-addon-api@7.1.1)': + '@napi-rs/cli@3.6.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(node-addon-api@7.1.1)': dependencies: - '@inquirer/prompts': 8.3.2(@types/node@25.5.0) + '@inquirer/prompts': 8.3.2(@types/node@25.5.2) '@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) '@napi-rs/wasm-tools': 1.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) '@octokit/rest': 22.0.1 @@ -7821,11 +7813,11 @@ snapshots: '@standard-schema/utils@0.3.0': {} - '@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1))': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) '@typescript-eslint/types': 8.58.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -7900,12 +7892,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - '@tailwindcss/vite@4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@tailwindcss/vite@4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@tailwindcss/node': 4.2.2 '@tailwindcss/oxide': 4.2.2 tailwindcss: 4.2.2 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3) '@tanstack/history@1.161.6': {} @@ -7951,7 +7943,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@tanstack/router-plugin@1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -7968,7 +7960,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -8045,7 +8037,7 @@ snapshots: dependencies: '@tauri-apps/api': 2.10.1 - '@tauri-apps/plugin-updater@2.10.0': + '@tauri-apps/plugin-updater@2.10.1': dependencies: '@tauri-apps/api': 2.10.1 @@ -8060,21 +8052,21 @@ snapshots: npm-to-yarn: 3.0.1 unist-util-visit: 5.1.0 - '@truenine/eslint10-config@2026.10402.10314(b6f5f8bbc7b250991807fe8a46682c75)': + '@truenine/eslint10-config@2026.10402.10314(b56a2921cd0832854c1ed6fed0241803)': dependencies: - '@antfu/eslint-config': 7.7.3(@next/eslint-plugin-next@16.2.2)(@typescript-eslint/rule-tester@8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@unocss/eslint-plugin@66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@vue/compiler-sfc@3.5.26)(eslint-plugin-format@2.0.1(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))) - '@eslint/js': 10.0.1(eslint@10.1.0(jiti@2.6.1)) + '@antfu/eslint-config': 8.0.0(@next/eslint-plugin-next@16.2.2)(@typescript-eslint/rule-tester@8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@unocss/eslint-plugin@66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vue/compiler-sfc@3.5.26)(eslint-plugin-format@2.0.1(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))) + '@eslint/js': 10.0.1(eslint@10.2.0(jiti@2.6.1)) '@next/eslint-plugin-next': 16.2.2 - '@unocss/eslint-config': 66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - '@vue/eslint-config-prettier': 10.2.0(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1) - '@vue/eslint-config-typescript': 14.7.0(eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1))))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.1.0(jiti@2.6.1) - eslint-plugin-format: 2.0.1(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1) - eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1))) + '@unocss/eslint-config': 66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@vue/eslint-config-prettier': 10.2.0(eslint@10.2.0(jiti@2.6.1))(prettier@3.8.1) + '@vue/eslint-config-typescript': 14.7.0(eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint: 10.2.0(jiti@2.6.1) + eslint-plugin-format: 2.0.1(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(prettier@3.8.1) + eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))) prettier: 3.8.1 typescript: 6.0.2 - typescript-eslint: 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + typescript-eslint: 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@ts-morph/common@0.28.1': dependencies: @@ -8082,22 +8074,22 @@ snapshots: path-browserify: 1.0.1 tinyglobby: 0.2.15 - '@turbo/darwin-64@2.9.3': + '@turbo/darwin-64@2.9.4': optional: true - '@turbo/darwin-arm64@2.9.3': + '@turbo/darwin-arm64@2.9.4': optional: true - '@turbo/linux-64@2.9.3': + '@turbo/linux-64@2.9.4': optional: true - '@turbo/linux-arm64@2.9.3': + '@turbo/linux-arm64@2.9.4': optional: true - '@turbo/windows-64@2.9.3': + '@turbo/windows-64@2.9.4': optional: true - '@turbo/windows-arm64@2.9.3': + '@turbo/windows-arm64@2.9.4': optional: true '@tybys/wasm-util@0.10.1': @@ -8244,7 +8236,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@types/geojson@7946.0.16': {} @@ -8258,7 +8250,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@types/katex@0.16.8': {} @@ -8274,11 +8266,11 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/node@25.5.0': + '@types/node@25.5.2': dependencies: undici-types: 7.18.2 - '@types/picomatch@4.0.2': {} + '@types/picomatch@4.0.3': {} '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: @@ -8297,15 +8289,15 @@ snapshots: '@types/use-sync-external-store@0.0.6': {} - '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@typescript-eslint/scope-manager': 8.58.0 - '@typescript-eslint/type-utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/type-utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@typescript-eslint/visitor-keys': 8.58.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.2) @@ -8313,26 +8305,26 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/parser@8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@typescript-eslint/scope-manager': 8.56.1 '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@6.0.2) '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@typescript-eslint/scope-manager': 8.58.0 '@typescript-eslint/types': 8.58.0 '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -8355,13 +8347,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/rule-tester@8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/rule-tester@8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: - '@typescript-eslint/parser': 8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/parser': 8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@typescript-eslint/typescript-estree': 8.56.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) ajv: 6.14.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 semver: 7.7.4 @@ -8387,13 +8379,13 @@ snapshots: dependencies: typescript: 6.0.2 - '@typescript-eslint/type-utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/type-utils@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@typescript-eslint/types': 8.58.0 '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) debug: 4.4.3 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@6.0.2) typescript: 6.0.2 transitivePeerDependencies: @@ -8433,24 +8425,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/utils@8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.56.1 '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@6.0.2) - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/utils@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.58.0 '@typescript-eslint/types': 8.58.0 '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -8483,17 +8475,17 @@ snapshots: '@unocss/core@66.6.7': {} - '@unocss/eslint-config@66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@unocss/eslint-config@66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: - '@unocss/eslint-plugin': 66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@unocss/eslint-plugin': 66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) transitivePeerDependencies: - eslint - supports-color - typescript - '@unocss/eslint-plugin@66.6.7(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@unocss/eslint-plugin@66.6.7(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: - '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@unocss/config': 66.6.7 '@unocss/core': 66.6.7 '@unocss/rule-utils': 66.6.7 @@ -8514,12 +8506,12 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-react@6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-react@6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)))': + '@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.2 @@ -8531,17 +8523,17 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/eslint-plugin@1.6.14(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)))': + '@vitest/eslint-plugin@1.6.14(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: '@typescript-eslint/scope-manager': 8.58.0 - '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.1.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint: 10.2.0(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) typescript: 6.0.2 - vitest: 4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -8554,13 +8546,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3) '@vitest/pretty-format@4.1.2': dependencies: @@ -8616,23 +8608,23 @@ snapshots: '@vue/compiler-dom': 3.5.26 '@vue/shared': 3.5.26 - '@vue/eslint-config-prettier@10.2.0(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1)': + '@vue/eslint-config-prettier@10.2.0(eslint@10.2.0(jiti@2.6.1))(prettier@3.8.1)': dependencies: - eslint: 10.1.0(jiti@2.6.1) - eslint-config-prettier: 10.1.8(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1) + eslint: 10.2.0(jiti@2.6.1) + eslint-config-prettier: 10.1.8(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(prettier@3.8.1) prettier: 3.8.1 transitivePeerDependencies: - '@types/eslint' - '@vue/eslint-config-typescript@14.7.0(eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1))))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2)': + '@vue/eslint-config-typescript@14.7.0(eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: - '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.1.0(jiti@2.6.1) - eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1))) + '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint: 10.2.0(jiti@2.6.1) + eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))) fast-glob: 3.3.3 - typescript-eslint: 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - vue-eslint-parser: 10.4.0(eslint@10.1.0(jiti@2.6.1)) + typescript-eslint: 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + vue-eslint-parser: 10.4.0(eslint@10.2.0(jiti@2.6.1)) optionalDependencies: typescript: 6.0.2 transitivePeerDependencies: @@ -9250,86 +9242,86 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.1(eslint@10.1.0(jiti@2.6.1)): + eslint-compat-utils@0.5.1(eslint@10.2.0(jiti@2.6.1)): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) semver: 7.7.4 - eslint-config-flat-gitignore@2.3.0(eslint@10.1.0(jiti@2.6.1)): + eslint-config-flat-gitignore@2.3.0(eslint@10.2.0(jiti@2.6.1)): dependencies: - '@eslint/compat': 2.0.3(eslint@10.1.0(jiti@2.6.1)) - eslint: 10.1.0(jiti@2.6.1) + '@eslint/compat': 2.0.4(eslint@10.2.0(jiti@2.6.1)) + eslint: 10.2.0(jiti@2.6.1) - eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@10.2.0(jiti@2.6.1)): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) eslint-flat-config-utils@3.1.0: dependencies: - '@eslint/config-helpers': 0.5.3 + '@eslint/config-helpers': 0.5.4 pathe: 2.0.3 - eslint-formatting-reporter@0.0.0(eslint@10.1.0(jiti@2.6.1)): + eslint-formatting-reporter@0.0.0(eslint@10.2.0(jiti@2.6.1)): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) prettier-linter-helpers: 1.0.1 - eslint-json-compat-utils@0.2.3(eslint@10.1.0(jiti@2.6.1))(jsonc-eslint-parser@3.1.0): + eslint-json-compat-utils@0.2.3(eslint@10.2.0(jiti@2.6.1))(jsonc-eslint-parser@3.1.0): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) esquery: 1.7.0 jsonc-eslint-parser: 3.1.0 - eslint-merge-processors@2.0.0(eslint@10.1.0(jiti@2.6.1)): + eslint-merge-processors@2.0.0(eslint@10.2.0(jiti@2.6.1)): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) eslint-parser-plain@0.1.1: {} - eslint-plugin-antfu@3.2.2(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-antfu@3.2.2(eslint@10.2.0(jiti@2.6.1)): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) - eslint-plugin-command@3.5.2(@typescript-eslint/rule-tester@8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-command@3.5.2(@typescript-eslint/rule-tester@8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2))(@typescript-eslint/utils@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1)): dependencies: '@es-joy/jsdoccomment': 0.84.0 - '@typescript-eslint/rule-tester': 8.56.1(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/rule-tester': 8.56.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.1.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint: 10.2.0(jiti@2.6.1) - eslint-plugin-depend@1.5.0(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-depend@1.5.0(eslint@10.2.0(jiti@2.6.1)): dependencies: empathic: 2.0.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) module-replacements: 2.11.0 semver: 7.7.4 - eslint-plugin-es-x@7.8.0(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-es-x@7.8.0(eslint@10.2.0(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - eslint: 10.1.0(jiti@2.6.1) - eslint-compat-utils: 0.5.1(eslint@10.1.0(jiti@2.6.1)) + eslint: 10.2.0(jiti@2.6.1) + eslint-compat-utils: 0.5.1(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-format@2.0.1(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-format@2.0.1(eslint@10.2.0(jiti@2.6.1)): dependencies: '@dprint/formatter': 0.5.1 '@dprint/markdown': 0.21.1 '@dprint/toml': 0.7.0 - eslint: 10.1.0(jiti@2.6.1) - eslint-formatting-reporter: 0.0.0(eslint@10.1.0(jiti@2.6.1)) + eslint: 10.2.0(jiti@2.6.1) + eslint-formatting-reporter: 0.0.0(eslint@10.2.0(jiti@2.6.1)) eslint-parser-plain: 0.1.1 ohash: 2.0.11 oxfmt: 0.35.0 prettier: 3.8.1 synckit: 0.11.12 - eslint-plugin-import-lite@0.5.2(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-import-lite@0.6.0(eslint@10.2.0(jiti@2.6.1)): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) - eslint-plugin-jsdoc@62.9.0(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-jsdoc@62.9.0(eslint@10.2.0(jiti@2.6.1)): dependencies: '@es-joy/jsdoccomment': 0.86.0 '@es-joy/resolve.exports': 1.2.0 @@ -9337,7 +9329,7 @@ snapshots: comment-parser: 1.4.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) espree: 11.2.0 esquery: 1.7.0 html-entities: 2.6.0 @@ -9349,27 +9341,27 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-jsonc@3.1.2(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-jsonc@3.1.2(eslint@10.2.0(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) - '@eslint/core': 1.1.1 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + '@eslint/core': 1.2.0 '@eslint/plugin-kit': 0.6.1 '@ota-meshi/ast-token-store': 0.3.0 diff-sequences: 29.6.3 - eslint: 10.1.0(jiti@2.6.1) - eslint-json-compat-utils: 0.2.3(eslint@10.1.0(jiti@2.6.1))(jsonc-eslint-parser@3.1.0) + eslint: 10.2.0(jiti@2.6.1) + eslint-json-compat-utils: 0.2.3(eslint@10.2.0(jiti@2.6.1))(jsonc-eslint-parser@3.1.0) jsonc-eslint-parser: 3.1.0 natural-compare: 1.4.0 synckit: 0.11.12 transitivePeerDependencies: - '@eslint/json' - eslint-plugin-n@17.24.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-n@17.24.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) enhanced-resolve: 5.20.1 - eslint: 10.1.0(jiti@2.6.1) - eslint-plugin-es-x: 7.8.0(eslint@10.1.0(jiti@2.6.1)) + eslint: 10.2.0(jiti@2.6.1) + eslint-plugin-es-x: 7.8.0(eslint@10.2.0(jiti@2.6.1)) get-tsconfig: 4.13.7 globals: 15.15.0 globrex: 0.1.2 @@ -9381,19 +9373,19 @@ snapshots: eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-perfectionist@5.7.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-perfectionist@5.8.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): dependencies: - '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.1.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint: 10.2.0(jiti@2.6.1) natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-pnpm@1.6.0(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-pnpm@1.6.0(eslint@10.2.0(jiti@2.6.1)): dependencies: empathic: 2.0.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) jsonc-eslint-parser: 3.1.0 pathe: 2.0.3 pnpm-workspace-yaml: 1.6.0 @@ -9401,95 +9393,95 @@ snapshots: yaml: 2.8.3 yaml-eslint-parser: 2.0.0 - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(prettier@3.8.1): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) prettier: 3.8.1 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@10.1.0(jiti@2.6.1)) + eslint-config-prettier: 10.1.8(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-regexp@3.1.0(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-regexp@3.1.0(eslint@10.2.0(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 comment-parser: 1.4.6 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) jsdoc-type-pratt-parser: 7.2.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-toml@1.3.1(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-toml@1.3.1(eslint@10.2.0(jiti@2.6.1)): dependencies: - '@eslint/core': 1.1.1 + '@eslint/core': 1.2.0 '@eslint/plugin-kit': 0.6.1 '@ota-meshi/ast-token-store': 0.3.0 debug: 4.4.3 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) toml-eslint-parser: 1.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@63.0.0(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-unicorn@64.0.0(eslint@10.2.0(jiti@2.6.1)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 core-js-compat: 3.49.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) find-up-simple: 1.0.1 - globals: 16.5.0 + globals: 17.4.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 pluralize: 8.0.0 regexp-tree: 0.1.27 - regjsparser: 0.13.0 + regjsparser: 0.13.1 semver: 7.7.4 strip-indent: 4.1.1 - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1)): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1))): + eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) - eslint: 10.1.0(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + eslint: 10.2.0(jiti@2.6.1) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 7.1.1 semver: 7.7.4 - vue-eslint-parser: 10.4.0(eslint@10.1.0(jiti@2.6.1)) + vue-eslint-parser: 10.4.0(eslint@10.2.0(jiti@2.6.1)) xml-name-validator: 4.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.10.0(eslint@10.1.0(jiti@2.6.1)) - '@typescript-eslint/parser': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.2.0(jiti@2.6.1)) + '@typescript-eslint/parser': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-yml@3.3.1(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-yml@3.3.1(eslint@10.2.0(jiti@2.6.1)): dependencies: - '@eslint/core': 1.1.1 + '@eslint/core': 1.2.0 '@eslint/plugin-kit': 0.6.1 '@ota-meshi/ast-token-store': 0.3.0 debug: 4.4.3 diff-sequences: 29.6.3 escape-string-regexp: 5.0.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) natural-compare: 1.4.0 yaml-eslint-parser: 2.0.0 transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.26)(eslint@10.1.0(jiti@2.6.1)): + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.26)(eslint@10.2.0(jiti@2.6.1)): dependencies: '@vue/compiler-sfc': 3.5.26 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) eslint-scope@9.1.2: dependencies: @@ -9504,14 +9496,14 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.1.0(jiti@2.6.1): + eslint@10.2.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.3 - '@eslint/config-helpers': 0.5.3 - '@eslint/core': 1.1.1 - '@eslint/plugin-kit': 0.6.1 + '@eslint/config-array': 0.23.4 + '@eslint/config-helpers': 0.5.4 + '@eslint/core': 1.2.0 + '@eslint/plugin-kit': 0.7.0 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 @@ -9830,8 +9822,6 @@ snapshots: globals@15.15.0: {} - globals@16.5.0: {} - globals@17.4.0: {} globrex@0.1.2: {} @@ -10169,6 +10159,10 @@ snapshots: dependencies: commander: 8.3.0 + katex@0.16.45: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -10617,7 +10611,7 @@ snapshots: dependencies: '@types/katex': 0.16.8 devlop: 1.1.0 - katex: 0.16.44 + katex: 0.16.45 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 @@ -10860,7 +10854,7 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0): + next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0): dependencies: '@next/env': 16.2.2 '@swc/helpers': 0.5.15 @@ -10879,19 +10873,19 @@ snapshots: '@next/swc-linux-x64-musl': 16.2.2 '@next/swc-win32-arm64-msvc': 16.2.2 '@next/swc-win32-x64-msvc': 16.2.2 - sass: 1.98.0 + sass: 1.99.0 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@4.6.1(@types/react@19.2.14)(immer@11.1.4)(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(nextra@4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): + nextra-theme-docs@4.6.1(@types/react@19.2.14)(immer@11.1.4)(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(nextra@4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): dependencies: '@headlessui/react': 2.2.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4) clsx: 2.1.1 - next: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) + next: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0) next-themes: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - nextra: 4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2) + nextra: 4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2) react: 19.2.4 react-compiler-runtime: 19.1.0-rc.3(react@19.2.4) react-dom: 19.2.4(react@19.2.4) @@ -10903,7 +10897,7 @@ snapshots: - immer - use-sync-external-store - nextra@4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2): + nextra@4.6.1(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2): dependencies: '@formatjs/intl-localematcher': 0.6.2 '@headlessui/react': 2.2.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -10924,7 +10918,7 @@ snapshots: mdast-util-gfm: 3.1.0 mdast-util-to-hast: 13.2.1 negotiator: 1.0.0 - next: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) + next: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0) react: 19.2.4 react-compiler-runtime: 19.1.0-rc.3(react@19.2.4) react-dom: 19.2.4(react@19.2.4) @@ -11330,7 +11324,7 @@ snapshots: regexp-tree@0.1.27: {} - regjsparser@0.13.0: + regjsparser@0.13.1: dependencies: jsesc: 3.1.0 @@ -11553,7 +11547,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.98.0: + sass@1.99.0: dependencies: chokidar: 4.0.3 immutable: 5.1.5 @@ -11875,14 +11869,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - turbo@2.9.3: + turbo@2.9.4: optionalDependencies: - '@turbo/darwin-64': 2.9.3 - '@turbo/darwin-arm64': 2.9.3 - '@turbo/linux-64': 2.9.3 - '@turbo/linux-arm64': 2.9.3 - '@turbo/windows-64': 2.9.3 - '@turbo/windows-arm64': 2.9.3 + '@turbo/darwin-64': 2.9.4 + '@turbo/darwin-arm64': 2.9.4 + '@turbo/linux-64': 2.9.4 + '@turbo/linux-arm64': 2.9.4 + '@turbo/windows-64': 2.9.4 + '@turbo/windows-arm64': 2.9.4 tw-animate-css@1.4.0: {} @@ -11908,13 +11902,13 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript-eslint@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2): + typescript-eslint@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/parser': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.1.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint: 10.2.0(jiti@2.6.1) typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -12074,7 +12068,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -12082,21 +12076,21 @@ snapshots: rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 esbuild: 0.27.4 fsevents: 2.3.3 jiti: 2.6.1 - sass: 1.98.0 + sass: 1.99.0 tsx: 4.21.0 yaml: 2.8.3 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' - vitest@4.1.2(@types/node@25.5.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.2(@types/node@25.5.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.2 '@vitest/runner': 4.1.2 '@vitest/snapshot': 4.1.2 @@ -12113,10 +12107,10 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.99.0)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 transitivePeerDependencies: - msw @@ -12137,10 +12131,10 @@ snapshots: vscode-uri@3.1.0: {} - vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@2.6.1)): + vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1)): dependencies: debug: 4.4.3 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.2.0(jiti@2.6.1) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 espree: 11.2.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c4d25221..a40a8269 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -10,7 +10,7 @@ packages: - doc catalog: - '@antfu/eslint-config': ^7.7.3 + '@antfu/eslint-config': ^8.0.0 '@clack/prompts': ^1.2.0 '@eslint/js': ^10.0.1 '@mdx-js/react': ^3.1.1 @@ -26,15 +26,15 @@ catalog: '@tauri-apps/api': ^2.10.1 '@tauri-apps/cli': ^2.10.1 '@tauri-apps/plugin-shell': ^2.3.5 - '@tauri-apps/plugin-updater': ^2.10.0 + '@tauri-apps/plugin-updater': ^2.10.1 '@theguild/remark-mermaid': ^0.3.0 '@truenine/eslint10-config': ^2026.10402.10314 '@types/estree': ^1.0.8 '@types/estree-jsx': ^1.0.5 '@types/fs-extra': ^11.0.4 '@types/mdast': ^4.0.4 - '@types/node': ^25.5.0 - '@types/picomatch': ^4.0.2 + '@types/node': ^25.5.2 + '@types/picomatch': ^4.0.3 '@types/react': ^19.2.14 '@types/react-dom': ^19.2.3 '@unocss/eslint-config': ^66.6.7 @@ -44,7 +44,7 @@ catalog: '@vue/eslint-config-typescript': ^14.7.0 class-variance-authority: ^0.7.1 clsx: ^2.1.1 - eslint: ^10.1.0 + eslint: ^10.2.0 eslint-plugin-format: ^2.0.1 eslint-plugin-prettier: ^5.5.5 eslint-plugin-vue: ^10.8.0 @@ -79,7 +79,7 @@ catalog: tailwindcss: ^4.2.2 tsdown: ^0.21.7 tsx: ^4.21.0 - turbo: ^2.9.3 + turbo: ^2.9.4 tw-animate-css: ^1.4.0 typescript: 6.0.2 typescript-eslint: ^8.58.0 From dcbbeccbb66996be03bbbeaa0c6a92e398782540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Mon, 6 Apr 2026 19:18:52 +0800 Subject: [PATCH 9/9] Refactor plugin output generation and fix GUI CI setup --- .github/workflows/pull-request.yml | 10 +- package.json | 2 +- sdk/src/plugins/CodexCLIOutputPlugin.ts | 70 +++------- sdk/src/plugins/CursorOutputPlugin.ts | 120 ++++++------------ sdk/src/plugins/GenericSkillsOutputPlugin.ts | 82 ++++-------- .../JetBrainsAIAssistantCodexOutputPlugin.ts | 43 ++----- sdk/src/plugins/OpencodeCLIOutputPlugin.ts | 68 ++-------- sdk/src/plugins/QoderIDEPluginOutputPlugin.ts | 104 ++++++--------- sdk/src/plugins/TraeIDEOutputPlugin.ts | 58 ++------- sdk/src/plugins/WindsurfOutputPlugin.ts | 102 +++++++-------- .../plugin-core/AbstractOutputPlugin.ts | 106 +++++++++++++--- sdk/src/plugins/plugin-core/enums.ts | 5 + 12 files changed, 297 insertions(+), 473 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index eec667c4..a845e063 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -121,9 +121,15 @@ jobs: - uses: ./.github/actions/setup-node-pnpm - - uses: ./.github/actions/setup-rust + - name: Read GUI version + id: gui-version + run: | + version="$(node -p 'require("./gui/package.json").version')" + echo "version=$version" >> "$GITHUB_OUTPUT" + + - uses: ./.github/actions/setup-tauri with: - cache-key: pr + version: ${{ steps.gui-version.outputs.version }} - name: Build native modules run: pnpm run build:native diff --git a/package.json b/package.json index 300ca7eb..c012871b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ ], "scripts": { "build": "turbo build", - "test": "turbo test --filter=!@truenine/memory-sync-gui", + "test": "turbo test", "lint": "turbo lint", "lint:fix": "turbo lint:fix", "check:type": "turbo check:type", diff --git a/sdk/src/plugins/CodexCLIOutputPlugin.ts b/sdk/src/plugins/CodexCLIOutputPlugin.ts index 6e50bebb..cd07c822 100644 --- a/sdk/src/plugins/CodexCLIOutputPlugin.ts +++ b/sdk/src/plugins/CodexCLIOutputPlugin.ts @@ -7,7 +7,6 @@ import type { SkillPrompt } from './plugin-core' import {Buffer} from 'node:buffer' -import * as path from 'node:path' import { AbstractOutputPlugin, filterByProjectConfig, @@ -21,7 +20,6 @@ const PROMPTS_SUBDIR = 'prompts' const AGENTS_SUBDIR = 'agents' const SKILLS_SUBDIR = 'skills' const PRESERVED_SYSTEM_SKILL_DIR = '.system' -const SKILL_FILE_NAME = 'SKILL.md' const MCP_CONFIG_FILE = 'mcp.json' const CODEX_SUBAGENT_FIELD_ORDER = ['name', 'description', 'developer_instructions'] as const const CODEX_EXCLUDED_SUBAGENT_FIELDS = ['scope', 'seriName', 'argumentHint', 'color', 'namingCase', 'model'] as const @@ -183,46 +181,13 @@ export class CodexCLIOutputPlugin extends AbstractOutputPlugin { scope: 'project' | 'global', filteredSkills: readonly SkillPrompt[] ): void => { - for (const skill of filteredSkills) { - const skillName = this.getSkillName(skill) - const skillDir = path.join(baseDir, SKILLS_SUBDIR, skillName) - - declarations.push({ - path: path.join(skillDir, SKILL_FILE_NAME), - scope, - source: {kind: 'skillMain', skill} satisfies CodexOutputSource + this.appendSkillDeclarations(declarations, baseDir, scope, filteredSkills, { + skillSubDir: SKILLS_SUBDIR, + buildSkillReferenceSource: childDoc => ({ + kind: 'skillChildDoc', + content: childDoc.content as string }) - - if (skill.childDocs != null) { - for (const childDoc of skill.childDocs) { - declarations.push({ - path: path.join( - skillDir, - childDoc.relativePath.replace(/\.mdx$/, '.md') - ), - scope, - source: { - kind: 'skillChildDoc', - content: childDoc.content as string - } satisfies CodexOutputSource - }) - } - } - - if (skill.resources != null) { - for (const resource of skill.resources) { - declarations.push({ - path: path.join(skillDir, resource.relativePath), - scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } satisfies CodexOutputSource - }) - } - } - } + }) } const pushSkillMcpDeclarations = ( @@ -230,19 +195,16 @@ export class CodexCLIOutputPlugin extends AbstractOutputPlugin { scope: 'project' | 'global', filteredMcpSkills: readonly SkillPrompt[] ): void => { - for (const skill of filteredMcpSkills) { - if (skill.mcpConfig == null) continue - - const skillDir = path.join(baseDir, SKILLS_SUBDIR, this.getSkillName(skill)) - declarations.push({ - path: path.join(skillDir, MCP_CONFIG_FILE), - scope, - source: { - kind: 'skillMcpConfig', - rawContent: skill.mcpConfig.rawContent - } satisfies CodexOutputSource - }) - } + this.appendSkillMcpDeclarations( + declarations, + baseDir, + scope, + filteredMcpSkills, + { + skillSubDir: SKILLS_SUBDIR, + fileName: MCP_CONFIG_FILE + } + ) } if ( diff --git a/sdk/src/plugins/CursorOutputPlugin.ts b/sdk/src/plugins/CursorOutputPlugin.ts index 4fc08530..e22c1c65 100644 --- a/sdk/src/plugins/CursorOutputPlugin.ts +++ b/sdk/src/plugins/CursorOutputPlugin.ts @@ -31,7 +31,6 @@ const RULES_SUBDIR = OutputSubdirectories.RULES const GLOBAL_RULE_FILE = OutputFileNames.CURSOR_GLOBAL_RULE const SKILLS_CURSOR_SUBDIR = OutputSubdirectories.CURSOR_SKILLS const SKILLS_PROJECT_SUBDIR = 'skills' -const SKILL_FILE_NAME = OutputFileNames.SKILL const PRESERVED_SKILLS = PreservedSkills.CURSOR type CursorOutputSource @@ -201,47 +200,23 @@ export class CursorOutputPlugin extends AbstractOutputPlugin { ): void => { const skillsSubDir = scope === 'global' ? SKILLS_CURSOR_SUBDIR : SKILLS_PROJECT_SUBDIR - for (const skill of filteredSkills) { - const skillName = this.getSkillName(skill) - if (this.isPreservedSkill(skillName)) continue - - const skillDir = path.join(baseDir, skillsSubDir, skillName) - declarations.push({ - path: path.join(skillDir, SKILL_FILE_NAME), - scope, - source: {kind: 'skill', skill} satisfies CursorOutputSource - }) - - if (skill.childDocs != null) { - for (const childDoc of skill.childDocs) { - declarations.push({ - path: path.join( - skillDir, - childDoc.relativePath.replace(/\.mdx$/, '.md') - ), - scope, - source: { - kind: 'skillChildDoc', - content: childDoc.content as string - } satisfies CursorOutputSource - }) - } - } - - if (skill.resources != null) { - for (const resource of skill.resources) { - declarations.push({ - path: path.join(skillDir, resource.relativePath), - scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } satisfies CursorOutputSource - }) - } + const writableSkills = filteredSkills.filter( + skill => !this.isPreservedSkill(this.getSkillName(skill)) + ) + this.appendSkillDeclarations( + declarations, + baseDir, + scope, + writableSkills, + { + skillSubDir: skillsSubDir, + buildSkillMainSource: skill => ({kind: 'skill', skill}), + buildSkillReferenceSource: childDoc => ({ + kind: 'skillChildDoc', + content: childDoc.content as string + }) } - } + ) } const pushSkillMcpDeclarations = ( @@ -251,23 +226,16 @@ export class CursorOutputPlugin extends AbstractOutputPlugin { ): void => { const skillsSubDir = scope === 'global' ? SKILLS_CURSOR_SUBDIR : SKILLS_PROJECT_SUBDIR - for (const skill of filteredMcpSkills) { - if (skill.mcpConfig == null) continue - - const skillDir = path.join( - baseDir, - skillsSubDir, - this.getSkillName(skill) - ) - declarations.push({ - path: path.join(skillDir, MCP_CONFIG_FILE), - scope, - source: { - kind: 'skillMcpConfig', - rawContent: skill.mcpConfig.rawContent - } satisfies CursorOutputSource - }) - } + this.appendSkillMcpDeclarations( + declarations, + baseDir, + scope, + filteredMcpSkills, + { + skillSubDir: skillsSubDir, + fileName: MCP_CONFIG_FILE + } + ) } const pushMcpDeclaration = ( @@ -356,17 +324,13 @@ export class CursorOutputPlugin extends AbstractOutputPlugin { project.projectConfig, 'commands' ) - for (const command of filteredCommands) { - declarations.push({ - path: path.join( - baseDir, - COMMANDS_SUBDIR, - this.transformCommandName(command, transformOptions) - ), - scope: 'project', - source: {kind: 'command', command} satisfies CursorOutputSource - }) - } + this.appendCommandDeclarations( + declarations, + baseDir, + 'project', + filteredCommands, + transformOptions + ) } } @@ -376,17 +340,13 @@ export class CursorOutputPlugin extends AbstractOutputPlugin { promptSourceProjectConfig, 'commands' ) - for (const command of filteredCommands) { - declarations.push({ - path: path.join( - globalDir, - COMMANDS_SUBDIR, - this.transformCommandName(command, transformOptions) - ), - scope: 'global', - source: {kind: 'command', command} satisfies CursorOutputSource - }) - } + this.appendCommandDeclarations( + declarations, + globalDir, + 'global', + filteredCommands, + transformOptions + ) } if (rules != null && rules.length > 0) { diff --git a/sdk/src/plugins/GenericSkillsOutputPlugin.ts b/sdk/src/plugins/GenericSkillsOutputPlugin.ts index c7698ab3..3ee0cddd 100644 --- a/sdk/src/plugins/GenericSkillsOutputPlugin.ts +++ b/sdk/src/plugins/GenericSkillsOutputPlugin.ts @@ -87,49 +87,19 @@ export class GenericSkillsOutputPlugin extends AbstractOutputPlugin { scope: 'project' | 'global', filteredSkills: readonly SkillPrompt[] ): void => { - for (const skill of filteredSkills) { - const skillName = this.getSkillName(skill) - const skillDir = this.joinPath(baseSkillsDir, skillName) - - declarations.push({ - path: this.joinPath(skillDir, SKILL_FILE_NAME), - scope, - source: { - kind: 'skillMain', - skill - } satisfies GenericSkillOutputSource - }) - - if (skill.childDocs != null) { - for (const childDoc of skill.childDocs) { - declarations.push({ - path: this.joinPath( - skillDir, - childDoc.relativePath.replace(/\.mdx$/, '.md') - ), - scope, - source: { - kind: 'skillChildDoc', - content: childDoc.content as string - } satisfies GenericSkillOutputSource - }) - } + this.appendSkillDeclarations( + declarations, + baseSkillsDir, + scope, + filteredSkills, + { + skillSubDir: '', + buildSkillReferenceSource: childDoc => ({ + kind: 'skillChildDoc', + content: childDoc.content as string + }) } - - if (skill.resources != null) { - for (const resource of skill.resources) { - declarations.push({ - path: this.joinPath(skillDir, resource.relativePath), - scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } satisfies GenericSkillOutputSource - }) - } - } - } + ) } const pushMcpDeclarations = ( @@ -137,22 +107,20 @@ export class GenericSkillsOutputPlugin extends AbstractOutputPlugin { scope: 'project' | 'global', filteredMcpSkills: readonly SkillPrompt[] ): void => { - for (const skill of filteredMcpSkills) { - if (skill.mcpConfig == null) continue - - declarations.push({ - path: this.joinPath( - baseSkillsDir, - this.getSkillName(skill), - MCP_CONFIG_FILE - ), - scope, - source: { + this.appendSkillMcpDeclarations( + declarations, + baseSkillsDir, + scope, + filteredMcpSkills, + { + skillSubDir: '', + fileName: MCP_CONFIG_FILE, + buildSkillMcpSource: skill => ({ kind: 'skillMcp', - rawContent: skill.mcpConfig.rawContent - } satisfies GenericSkillOutputSource - }) - } + rawContent: skill.mcpConfig?.rawContent ?? '' + }) + } + ) } if ( diff --git a/sdk/src/plugins/JetBrainsAIAssistantCodexOutputPlugin.ts b/sdk/src/plugins/JetBrainsAIAssistantCodexOutputPlugin.ts index fcf73e25..77a33ce7 100644 --- a/sdk/src/plugins/JetBrainsAIAssistantCodexOutputPlugin.ts +++ b/sdk/src/plugins/JetBrainsAIAssistantCodexOutputPlugin.ts @@ -125,42 +125,15 @@ export class JetBrainsAIAssistantCodexOutputPlugin extends AbstractOutputPlugin scope: 'project' | 'global', filteredSkills: readonly SkillPrompt[] ): void => { - for (const skill of filteredSkills) { - const skillName = this.getSkillName(skill) - const skillDir = path.join(basePath, SKILLS_SUBDIR, skillName) - declarations.push({ - path: path.join(skillDir, SKILL_FILE_NAME), - scope, - source: {kind: 'skill', skill} satisfies JetBrainsCodexOutputSource + this.appendSkillDeclarations(declarations, basePath, scope, filteredSkills, { + skillSubDir: SKILLS_SUBDIR, + skillFileName: SKILL_FILE_NAME, + buildSkillMainSource: skill => ({kind: 'skill', skill}), + buildSkillReferenceSource: childDoc => ({ + kind: 'skillReference', + content: childDoc.content as string }) - - if (skill.childDocs != null) { - for (const refDoc of skill.childDocs) { - declarations.push({ - path: path.join(skillDir, refDoc.dir.path.replace(/\.mdx$/, '.md')), - scope, - source: { - kind: 'skillReference', - content: refDoc.content as string - } satisfies JetBrainsCodexOutputSource - }) - } - } - - if (skill.resources != null) { - for (const resource of skill.resources) { - declarations.push({ - path: path.join(skillDir, resource.relativePath), - scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } satisfies JetBrainsCodexOutputSource - }) - } - } - } + }) } if (selectedCommands.selectedScope === 'project' || selectedSkills.selectedScope === 'project') { diff --git a/sdk/src/plugins/OpencodeCLIOutputPlugin.ts b/sdk/src/plugins/OpencodeCLIOutputPlugin.ts index c34dabfd..2537cde9 100644 --- a/sdk/src/plugins/OpencodeCLIOutputPlugin.ts +++ b/sdk/src/plugins/OpencodeCLIOutputPlugin.ts @@ -189,47 +189,16 @@ export class OpencodeCLIOutputPlugin extends AbstractOutputPlugin { } const pushSkillDeclarations = (basePath: string, scope: 'project' | 'global', filteredSkills: readonly SkillPrompt[]): void => { - for (const skill of filteredSkills) { - const normalizedSkillName = this.validateAndNormalizeSkillName(this.getSkillName(skill)) - const skillDir = path.join(basePath, SKILLS_SUBDIR, normalizedSkillName) - - declarations.push({ - path: path.join(skillDir, 'SKILL.md'), - scope, - source: { - kind: 'skillMain', - skill, - normalizedSkillName - } satisfies OpencodeOutputSource + this.appendSkillDeclarations(declarations, basePath, scope, filteredSkills, { + skillSubDir: SKILLS_SUBDIR, + resolveSkillDirName: skill => + this.validateAndNormalizeSkillName(this.getSkillName(skill)), + buildSkillMainSource: (skill, normalizedSkillName) => ({ + kind: 'skillMain', + skill, + normalizedSkillName }) - - if (skill.childDocs != null) { - for (const refDoc of skill.childDocs) { - declarations.push({ - path: path.join(skillDir, refDoc.dir.path.replace(/\.mdx$/, '.md')), - scope, - source: { - kind: 'skillReference', - content: refDoc.content as string - } satisfies OpencodeOutputSource - }) - } - } - - if (skill.resources != null) { - for (const resource of skill.resources) { - declarations.push({ - path: path.join(skillDir, resource.relativePath), - scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } satisfies OpencodeOutputSource - }) - } - } - } + }) } const pushMcpDeclaration = (basePath: string, scope: 'project' | 'global', _filteredSkills: readonly SkillPrompt[]): void => { @@ -289,16 +258,7 @@ export class OpencodeCLIOutputPlugin extends AbstractOutputPlugin { const filteredCommands = filterByProjectConfig(selectedCommands.items, project.projectConfig, 'commands') if (selectedCommands.selectedScope === 'project') { - for (const command of filteredCommands) { - declarations.push({ - path: path.join(basePath, COMMANDS_SUBDIR, this.transformCommandName(command, transformOptions)), - scope: 'project', - source: { - kind: 'command', - command - } satisfies OpencodeOutputSource - }) - } + this.appendCommandDeclarations(declarations, basePath, 'project', filteredCommands, transformOptions) } const filteredSubAgents = filterByProjectConfig(selectedSubAgents.items, project.projectConfig, 'subAgents') @@ -329,13 +289,7 @@ export class OpencodeCLIOutputPlugin extends AbstractOutputPlugin { if (selectedCommands.selectedScope === 'global') { const filteredCommands = filterByProjectConfig(selectedCommands.items, promptSourceProjectConfig, 'commands') - for (const command of filteredCommands) { - declarations.push({ - path: path.join(globalDir, COMMANDS_SUBDIR, this.transformCommandName(command, transformOptions)), - scope: 'global', - source: {kind: 'command', command} satisfies OpencodeOutputSource - }) - } + this.appendCommandDeclarations(declarations, globalDir, 'global', filteredCommands, transformOptions) } if (selectedSubAgents.selectedScope === 'global') { diff --git a/sdk/src/plugins/QoderIDEPluginOutputPlugin.ts b/sdk/src/plugins/QoderIDEPluginOutputPlugin.ts index 0ae4d6ad..2f720d83 100644 --- a/sdk/src/plugins/QoderIDEPluginOutputPlugin.ts +++ b/sdk/src/plugins/QoderIDEPluginOutputPlugin.ts @@ -9,7 +9,12 @@ import type { } from './plugin-core' import {Buffer} from 'node:buffer' import * as path from 'node:path' -import {AbstractOutputPlugin, applySubSeriesGlobPrefix, filterByProjectConfig} from './plugin-core' +import { + AbstractOutputPlugin, + applySubSeriesGlobPrefix, + filterByProjectConfig, + OutputDeclarationScopeKind +} from './plugin-core' const QODER_CONFIG_DIR = '.qoder' const RULES_SUBDIR = 'rules' @@ -18,7 +23,6 @@ const SKILLS_SUBDIR = 'skills' const GLOBAL_RULE_FILE = 'global.md' const PROJECT_RULE_FILE = 'always.md' const CHILD_RULE_FILE_PREFIX = 'glob-' -const SKILL_FILE_NAME = 'SKILL.md' const MCP_CONFIG_FILE = 'mcp.json' const TRIGGER_ALWAYS = 'always_on' const TRIGGER_GLOB = 'glob' @@ -136,42 +140,13 @@ export class QoderIDEPluginOutputPlugin extends AbstractOutputPlugin { scope: 'project' | 'global', filteredSkills: readonly SkillPrompt[] ): void => { - for (const skill of filteredSkills) { - const skillName = this.getSkillName(skill) - const skillDir = path.join(baseDir, SKILLS_SUBDIR, skillName) - declarations.push({ - path: path.join(skillDir, SKILL_FILE_NAME), - scope, - source: {kind: 'skillMain', skill} satisfies QoderOutputSource + this.appendSkillDeclarations(declarations, baseDir, scope, filteredSkills, { + skillSubDir: SKILLS_SUBDIR, + buildSkillReferenceSource: childDoc => ({ + kind: 'skillChildDoc', + content: childDoc.content as string }) - - if (skill.childDocs != null) { - for (const childDoc of skill.childDocs) { - declarations.push({ - path: path.join(skillDir, childDoc.relativePath.replace(/\.mdx$/, '.md')), - scope, - source: { - kind: 'skillChildDoc', - content: childDoc.content as string - } satisfies QoderOutputSource - }) - } - } - - if (skill.resources != null) { - for (const resource of skill.resources) { - declarations.push({ - path: path.join(skillDir, resource.relativePath), - scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } satisfies QoderOutputSource - }) - } - } - } + }) } const pushSkillMcpDeclarations = ( @@ -179,46 +154,43 @@ export class QoderIDEPluginOutputPlugin extends AbstractOutputPlugin { scope: 'project' | 'global', filteredMcpSkills: readonly SkillPrompt[] ): void => { - for (const skill of filteredMcpSkills) { - if (skill.mcpConfig == null) continue - - const skillDir = path.join(baseDir, SKILLS_SUBDIR, this.getSkillName(skill)) - declarations.push({ - path: path.join(skillDir, MCP_CONFIG_FILE), - scope, - source: { - kind: 'skillMcpConfig', - rawContent: skill.mcpConfig.rawContent - } satisfies QoderOutputSource - }) - } + this.appendSkillMcpDeclarations( + declarations, + baseDir, + scope, + filteredMcpSkills, + { + skillSubDir: SKILLS_SUBDIR, + fileName: MCP_CONFIG_FILE + } + ) } - if (selectedCommands.selectedScope === 'project') { + if (selectedCommands.selectedScope === OutputDeclarationScopeKind.Project) { for (const project of this.getProjectOutputProjects(ctx)) { const projectBase = this.resolveProjectConfigDir(ctx, project) if (projectBase == null) continue const filteredCommands = filterByProjectConfig(selectedCommands.items, project.projectConfig, 'commands') - for (const command of filteredCommands) { - declarations.push({ - path: path.join(projectBase, COMMANDS_SUBDIR, this.transformCommandName(command, transformOptions)), - scope: 'project', - source: {kind: 'command', command} satisfies QoderOutputSource - }) - } + this.appendCommandDeclarations( + declarations, + projectBase, + OutputDeclarationScopeKind.Project, + filteredCommands, + transformOptions + ) } } - if (selectedCommands.selectedScope === 'global') { + if (selectedCommands.selectedScope === OutputDeclarationScopeKind.Global) { const filteredCommands = filterByProjectConfig(selectedCommands.items, promptSourceProjectConfig, 'commands') - for (const command of filteredCommands) { - declarations.push({ - path: path.join(globalDir, COMMANDS_SUBDIR, this.transformCommandName(command, transformOptions)), - scope: 'global', - source: {kind: 'command', command} satisfies QoderOutputSource - }) - } + this.appendCommandDeclarations( + declarations, + globalDir, + OutputDeclarationScopeKind.Global, + filteredCommands, + transformOptions + ) } if (selectedSkills.selectedScope === 'project' || selectedMcpSkills.selectedScope === 'project') { diff --git a/sdk/src/plugins/TraeIDEOutputPlugin.ts b/sdk/src/plugins/TraeIDEOutputPlugin.ts index 691cea41..fefd4c45 100644 --- a/sdk/src/plugins/TraeIDEOutputPlugin.ts +++ b/sdk/src/plugins/TraeIDEOutputPlugin.ts @@ -15,7 +15,6 @@ const STEERING_SUBDIR = 'steering' const RULES_SUBDIR = 'rules' const COMMANDS_SUBDIR = 'commands' const SKILLS_SUBDIR = 'skills' -const SKILL_FILE_NAME = 'SKILL.md' type TraeOutputSource = | {readonly kind: 'globalMemory', readonly content: string} @@ -139,26 +138,14 @@ export class TraeIDEOutputPlugin extends AbstractOutputPlugin { if (projectBase == null) continue const filteredCommands = filterByProjectConfig(selectedCommands.items, project.projectConfig, 'commands') - for (const command of filteredCommands) { - declarations.push({ - path: path.join(projectBase, COMMANDS_SUBDIR, this.transformCommandName(command, transformOptions)), - scope: 'project', - source: {kind: 'command', command} satisfies TraeOutputSource - }) - } + this.appendCommandDeclarations(declarations, projectBase, 'project', filteredCommands, transformOptions) } } if (selectedCommands.selectedScope === 'global') { const baseDir = this.getGlobalConfigDir() const filteredCommands = filterByProjectConfig(selectedCommands.items, promptSourceProjectConfig, 'commands') - for (const command of filteredCommands) { - declarations.push({ - path: path.join(baseDir, COMMANDS_SUBDIR, this.transformCommandName(command, transformOptions)), - scope: 'global', - source: {kind: 'command', command} satisfies TraeOutputSource - }) - } + this.appendCommandDeclarations(declarations, baseDir, 'global', filteredCommands, transformOptions) } const pushSkillDeclarations = ( @@ -166,42 +153,13 @@ export class TraeIDEOutputPlugin extends AbstractOutputPlugin { scope: 'project' | 'global', filteredSkills: readonly SkillPrompt[] ): void => { - for (const skill of filteredSkills) { - const skillName = this.getSkillName(skill) - const skillDir = path.join(baseDir, SKILLS_SUBDIR, skillName) - declarations.push({ - path: path.join(skillDir, SKILL_FILE_NAME), - scope, - source: {kind: 'skillMain', skill} satisfies TraeOutputSource + this.appendSkillDeclarations(declarations, baseDir, scope, filteredSkills, { + skillSubDir: SKILLS_SUBDIR, + buildSkillReferenceSource: childDoc => ({ + kind: 'skillChildDoc', + content: childDoc.content as string }) - - if (skill.childDocs != null) { - for (const childDoc of skill.childDocs) { - declarations.push({ - path: path.join(skillDir, childDoc.relativePath.replace(/\.mdx$/, '.md')), - scope, - source: { - kind: 'skillChildDoc', - content: childDoc.content as string - } satisfies TraeOutputSource - }) - } - } - - if (skill.resources != null) { - for (const resource of skill.resources) { - declarations.push({ - path: path.join(skillDir, resource.relativePath), - scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } satisfies TraeOutputSource - }) - } - } - } + }) } if (selectedSkills.selectedScope === 'project') { diff --git a/sdk/src/plugins/WindsurfOutputPlugin.ts b/sdk/src/plugins/WindsurfOutputPlugin.ts index 1e35e46f..6026b3b9 100644 --- a/sdk/src/plugins/WindsurfOutputPlugin.ts +++ b/sdk/src/plugins/WindsurfOutputPlugin.ts @@ -1,7 +1,15 @@ import type {CommandPrompt, OutputFileDeclaration, OutputWriteContext, RulePrompt, SkillPrompt} from './plugin-core' import {Buffer} from 'node:buffer' import * as path from 'node:path' -import {AbstractOutputPlugin, applySubSeriesGlobPrefix, filterByProjectConfig, IgnoreFiles, PLUGIN_NAMES} from './plugin-core' +import { + AbstractOutputPlugin, + applySubSeriesGlobPrefix, + filterByProjectConfig, + IgnoreFiles, + OutputDeclarationScopeKind, + PLUGIN_NAMES, + PromptKind +} from './plugin-core' const CODEIUM_WINDSURF_DIR = '.codeium/windsurf' const WORKFLOWS_SUBDIR = 'global_workflows' @@ -9,7 +17,6 @@ const PROJECT_WORKFLOWS_SUBDIR = 'workflows' const MEMORIES_SUBDIR = 'memories' const GLOBAL_MEMORY_FILE = 'global_rules.md' const SKILLS_SUBDIR = 'skills' -const SKILL_FILE_NAME = 'SKILL.md' const WINDSURF_RULES_DIR = '.windsurf' const WINDSURF_RULES_SUBDIR = 'rules' const RULE_FILE_PREFIX = 'rule-' @@ -100,64 +107,47 @@ export class WindsurfOutputPlugin extends AbstractOutputPlugin { }) } - const pushSkillDeclarations = ( - basePath: string, - scope: 'project' | 'global', - skill: SkillPrompt - ): void => { - const skillName = this.getSkillName(skill) - const skillDir = path.join(basePath, SKILLS_SUBDIR, skillName) - declarations.push({ - path: path.join(skillDir, SKILL_FILE_NAME), - scope, - source: {kind: 'skillMain', skill} satisfies WindsurfOutputSource - }) - - if (skill.childDocs != null) { - for (const childDoc of skill.childDocs) { - declarations.push({ - path: path.join(skillDir, childDoc.relativePath.replace(/\.mdx$/, '.md')), - scope, - source: { - kind: 'skillChildDoc', - content: childDoc.content as string - } satisfies WindsurfOutputSource - }) - } - } - - if (skill.resources != null) { - for (const resource of skill.resources) { - declarations.push({ - path: path.join(skillDir, resource.relativePath), - scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } satisfies WindsurfOutputSource - }) - } - } - } - - if (selectedSkills.selectedScope === 'project') { + if (selectedSkills.selectedScope === OutputDeclarationScopeKind.Project) { for (const project of this.getProjectOutputProjects(ctx)) { const projectRootDir = this.resolveProjectRootDir(ctx, project) const projectBase = projectRootDir == null ? void 0 : path.join(projectRootDir, WINDSURF_RULES_DIR) if (projectBase == null) continue const filteredSkills = filterByProjectConfig(selectedSkills.items, project.projectConfig, 'skills') - for (const skill of filteredSkills) pushSkillDeclarations(projectBase, 'project', skill) + this.appendSkillDeclarations( + declarations, + projectBase, + OutputDeclarationScopeKind.Project, + filteredSkills, + { + skillSubDir: SKILLS_SUBDIR, + buildSkillReferenceSource: childDoc => ({ + kind: PromptKind.SkillChildDoc, + content: childDoc.content as string + }) + } + ) } } - if (selectedSkills.selectedScope === 'global') { + if (selectedSkills.selectedScope === OutputDeclarationScopeKind.Global) { const filteredSkills = filterByProjectConfig(selectedSkills.items, promptSourceProjectConfig, 'skills') - for (const skill of filteredSkills) pushSkillDeclarations(globalBase, 'global', skill) + this.appendSkillDeclarations( + declarations, + globalBase, + OutputDeclarationScopeKind.Global, + filteredSkills, + { + skillSubDir: SKILLS_SUBDIR, + buildSkillReferenceSource: childDoc => ({ + kind: PromptKind.SkillChildDoc, + content: childDoc.content as string + }) + } + ) } const transformOptions = this.getTransformOptionsFromContext(ctx, {includeSeriesPrefix: true}) - if (selectedCommands.selectedScope === 'project') { + if (selectedCommands.selectedScope === OutputDeclarationScopeKind.Project) { for (const project of this.getProjectOutputProjects(ctx)) { const projectRootDir = this.resolveProjectRootDir(ctx, project) const projectBase = projectRootDir == null ? void 0 : path.join(projectRootDir, WINDSURF_RULES_DIR) @@ -173,15 +163,15 @@ export class WindsurfOutputPlugin extends AbstractOutputPlugin { } } - if (selectedCommands.selectedScope === 'global') { + if (selectedCommands.selectedScope === OutputDeclarationScopeKind.Global) { const filteredCommands = filterByProjectConfig(selectedCommands.items, promptSourceProjectConfig, 'commands') - for (const command of filteredCommands) { - declarations.push({ - path: path.join(globalBase, WORKFLOWS_SUBDIR, this.transformCommandName(command, transformOptions)), - scope: 'global', - source: {kind: 'command', command} satisfies WindsurfOutputSource - }) - } + this.appendCommandDeclarations( + declarations, + globalBase, + OutputDeclarationScopeKind.Global, + filteredCommands, + transformOptions + ) } if (rules != null && rules.length > 0) { diff --git a/sdk/src/plugins/plugin-core/AbstractOutputPlugin.ts b/sdk/src/plugins/plugin-core/AbstractOutputPlugin.ts index f27125c6..1d28584c 100644 --- a/sdk/src/plugins/plugin-core/AbstractOutputPlugin.ts +++ b/sdk/src/plugins/plugin-core/AbstractOutputPlugin.ts @@ -25,7 +25,9 @@ import type { RegistryOperationResult, RulePrompt, RuleScope, + SkillChildDoc, SkillPrompt, + SkillResource, SubAgentPrompt, WslMirrorFileDeclaration } from './types' @@ -249,6 +251,32 @@ export interface CombineOptions { position?: 'before' | 'after' } +export interface SkillDeclarationOptions { + readonly skillSubDir?: string + readonly skillFileName?: string + readonly resolveSkillDirName?: (skill: SkillPrompt) => string + readonly resolveChildDocPath?: ( + skillDir: string, + childDoc: SkillChildDoc + ) => string + readonly buildSkillMainSource?: ( + skill: SkillPrompt, + skillDirName: string + ) => unknown + readonly buildSkillReferenceSource?: (childDoc: SkillChildDoc) => unknown + readonly buildSkillResourceSource?: (resource: SkillResource) => unknown +} + +export interface SkillMcpDeclarationOptions { + readonly skillSubDir?: string + readonly fileName?: string + readonly resolveSkillDirName?: (skill: SkillPrompt) => string + readonly buildSkillMcpSource?: ( + skill: SkillPrompt, + skillDirName: string + ) => unknown +} + type DeclarativeOutputSource = | {readonly kind: 'projectRootMemory', readonly content: string} | {readonly kind: 'projectChildMemory', readonly content: string} @@ -882,27 +910,49 @@ export abstract class AbstractOutputPlugin extends AbstractPlugin implements Out declarations: OutputFileDeclaration[], basePath: string, scope: OutputDeclarationScope, - scopedSkills: readonly SkillPrompt[] + scopedSkills: readonly SkillPrompt[], + options: SkillDeclarationOptions = {} ): void { + const skillSubDir = options.skillSubDir ?? this.skillsConfig.subDir + const skillFileName = options.skillFileName ?? 'SKILL.md' + const resolveSkillDirName + = options.resolveSkillDirName ?? (skill => this.getSkillName(skill)) + const resolveChildDocPath + = options.resolveChildDocPath + ?? ((skillDir, childDoc) => + path.join(skillDir, childDoc.relativePath.replace(/\.mdx$/, '.md'))) + const buildSkillMainSource + = options.buildSkillMainSource ?? (skill => ({kind: 'skillMain', skill})) + const buildSkillReferenceSource + = options.buildSkillReferenceSource + ?? (childDoc => ({ + kind: 'skillReference', + content: childDoc.content as string + })) + const buildSkillResourceSource + = options.buildSkillResourceSource + ?? (resource => ({ + kind: 'skillResource', + content: resource.content, + encoding: resource.encoding + })) + for (const skill of scopedSkills) { - const skillName = this.getSkillName(skill) - const skillDir = path.join(basePath, this.skillsConfig.subDir, skillName) + const skillDirName = resolveSkillDirName(skill) + const skillDir = path.join(basePath, skillSubDir, skillDirName) declarations.push({ - path: path.join(skillDir, 'SKILL.md'), + path: path.join(skillDir, skillFileName), scope, - source: {kind: 'skillMain', skill} + source: buildSkillMainSource(skill, skillDirName) }) if (skill.childDocs != null) { for (const childDoc of skill.childDocs) { declarations.push({ - path: path.join(skillDir, childDoc.dir.path.replace(/\.mdx$/, '.md')), + path: resolveChildDocPath(skillDir, childDoc), scope, - source: { - kind: 'skillReference', - content: childDoc.content as string - } + source: buildSkillReferenceSource(childDoc) }) } } @@ -912,17 +962,43 @@ export abstract class AbstractOutputPlugin extends AbstractPlugin implements Out declarations.push({ path: path.join(skillDir, resource.relativePath), scope, - source: { - kind: 'skillResource', - content: resource.content, - encoding: resource.encoding - } + source: buildSkillResourceSource(resource) }) } } } } + protected appendSkillMcpDeclarations( + declarations: OutputFileDeclaration[], + basePath: string, + scope: OutputDeclarationScope, + scopedSkills: readonly SkillPrompt[], + options: SkillMcpDeclarationOptions = {} + ): void { + const skillSubDir = options.skillSubDir ?? this.skillsConfig.subDir + const fileName = options.fileName ?? 'mcp.json' + const resolveSkillDirName + = options.resolveSkillDirName ?? (skill => this.getSkillName(skill)) + const buildSkillMcpSource + = options.buildSkillMcpSource + ?? (skill => ({ + kind: 'skillMcpConfig', + rawContent: skill.mcpConfig?.rawContent ?? '' + })) + + for (const skill of scopedSkills) { + if (skill.mcpConfig == null) continue + + const skillDirName = resolveSkillDirName(skill) + declarations.push({ + path: path.join(basePath, skillSubDir, skillDirName, fileName), + scope, + source: buildSkillMcpSource(skill, skillDirName) + }) + } + } + protected appendRuleDeclarations(declarations: OutputFileDeclaration[], basePath: string, scope: OutputDeclarationScope, rules: readonly RulePrompt[]): void { const rulesDir = path.join(basePath, this.rulesConfig.subDir ?? 'rules') diff --git a/sdk/src/plugins/plugin-core/enums.ts b/sdk/src/plugins/plugin-core/enums.ts index d146d369..e27bd552 100644 --- a/sdk/src/plugins/plugin-core/enums.ts +++ b/sdk/src/plugins/plugin-core/enums.ts @@ -18,6 +18,11 @@ export enum PromptKind { export type RuleScope = 'project' | 'global' +export enum OutputDeclarationScopeKind { + Project = 'project', + Global = 'global' +} + export enum FilePathKind { Relative = 'relative', Absolute = 'absolute',