From db1b6f36e43e8848f9f6543f5d200c3743d78403 Mon Sep 17 00:00:00 2001 From: metyatech Date: Fri, 6 Feb 2026 11:21:31 +0900 Subject: [PATCH 1/6] chore: implement linting and formatting compliance with AGENTS.md --- .github/ISSUE_TEMPLATE/bug_report.md | 14 +- .github/ISSUE_TEMPLATE/feature_request.md | 7 +- .github/pull_request_template.md | 8 +- .github/workflows/ci.yml | 10 +- .prettierignore | 4 + .prettierrc | 7 + AGENTS.md | 48 +- CHANGELOG.md | 14 + CODE_OF_CONDUCT.md | 7 +- CONTRIBUTING.md | 5 +- README.md | 19 +- SECURITY.md | 5 +- agent-rules-local/release.md | 2 +- agent-ruleset.json | 12 +- eslint.config.js | 28 + package-lock.json | 1632 ++++++++++++++++++++- package.json | 14 +- src/compose-agents.ts | 381 ++--- test/compose-agents.test.js | 575 ++++---- tools/compose-agents.js | 2 +- tools/tool-rules.md | 1 + 21 files changed, 2272 insertions(+), 523 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 eslint.config.js diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 28d8f12..d3bf3c5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,23 +5,29 @@ labels: bug --- ## Summary + A clear and concise description of the problem. ## Steps to reproduce -1. -2. -3. + +1. +2. +3. ## Expected behavior + What you expected to happen. ## Actual behavior + What actually happened. ## Environment + - OS: - Node.js: - Version: ## Additional context -Add any other context, logs, or screenshots here. + +Add any other context, logs, or screenshots here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 4c5097c..d4e89a3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,16 +5,21 @@ labels: enhancement --- ## Summary + A clear and concise description of the feature. ## Problem / motivation + What problem does this solve? ## Proposed solution + Describe the solution you'd like. ## Alternatives considered + Describe any alternative solutions or features you've considered. ## Additional context -Add any other context or screenshots about the feature request here. + +Add any other context or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6376c63..913891c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,15 +1,19 @@ ## Summary + Describe the change and why it is needed. ## Changes -- + +- ## Testing + - [ ] npm run lint - [ ] npm test - [ ] npm pack --dry-run (if publishing-related) ## Checklist + - [ ] README.md updated or not needed (explain why) - [ ] No generated artifacts committed -- [ ] Changes are scoped to the affected repository only +- [ ] Changes are scoped to the affected repository only diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f6a0b4..3d5bd9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,14 +18,14 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" - cache: "npm" + node-version: '20' + cache: 'npm' - name: Install dependencies run: npm ci - - name: Lint - run: npm run lint + - name: Check + run: npm run check - name: Test - run: npm test + run: npm test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..409cc41 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +dist/ +node_modules/ +AGENTS.md +package-lock.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4cbc711 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/AGENTS.md b/AGENTS.md index 92d67b1..72481f0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,6 @@ # Tool Rules (compose-agentsmd) + - Before starting any work, run `compose-agentsmd` from the project root. - If `compose-agentsmd` is not available, install it via npm: `npm install -g compose-agentsmd`. - To update shared rules, run `compose-agentsmd edit-rules`, edit the workspace rules, then run `compose-agentsmd apply-rules`. @@ -65,7 +66,14 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/command-execution.md - If elevated privileges are required, use sudo where available; otherwise run as Administrator. - Keep changes scoped to affected repositories; when shared modules change, update consumers and verify at least one. - If no branch is specified, work on the current branch; direct commits to main/master are allowed. -- After addressing PR comments, resolve related conversations; after completing a PR, merge it, sync the target branch, and delete the PR branch locally and remotely. +- After addressing PR review feedback, resolve the corresponding review thread(s) before concluding; if you lack permission, state it explicitly. +- After pushing fixes for PR review feedback, always re-request review from the same reviewer(s) when possible; if there are no current reviewers, ask who should review. +- When Codex and/or Copilot review bots are configured for the repo, always trigger a re-review after pushing fixes. +- For Codex re-review: comment `@codex review` on the PR. +- For Copilot re-review: use `gh api` to remove+re-request the bot reviewer `copilot-pull-request-reviewer[bot]` (do not rely on `gh pr edit --add-reviewer Copilot`). + - Remove: `gh api --method DELETE /repos/{owner}/{repo}/pulls/{pr}/requested_reviewers -f "reviewers[]=copilot-pull-request-reviewer[bot]"` + - Add: `gh api --method POST /repos/{owner}/{repo}/pulls/{pr}/requested_reviewers -f "reviewers[]=copilot-pull-request-reviewer[bot]"` +- After completing a PR, merge it, sync the target branch, and delete the PR branch locally and remotely. Source: github:metyatech/agent-rules@HEAD/rules/global/implementation-and-coding-standards.md @@ -113,6 +121,7 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/linting-formatting-and-st ### JavaScript / TypeScript (incl. React/Next) - Format+lint: ESLint + Prettier. +- When configuring Prettier, always add and maintain `.prettierignore` so generated/build outputs and composed files are not formatted/linted as source (e.g., `dist/`, build artifacts, and `AGENTS.md` when generated by compose-agentsmd). - Typecheck: `tsc` with strict settings for TS projects. - Dependency scan: `osv-scanner`. If unsupported, use the package manager's audit tooling. @@ -239,14 +248,21 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/planning-and-approval-gat # Planning and approval gate -- Default to a two-phase workflow: plan first, execute after explicit user approval. -- Before any state-changing execution (writing or modifying files, running formatters/linters/tests/builds, installing dependencies, or running git commands beyond status/diff/log), do all of the following: - - Restate the request as concrete acceptance criteria. - - Ask blocking questions and list key assumptions/risks. - - Produce a written plan (use your planning tool, e.g., `update_plan`) including the intended file changes and the commands you plan to run. - - Ask for approval explicitly and wait for a clear “yes” before executing. -- Allowed before approval: clarifying questions and read-only inspection (reading files, searching, and `git status` / `git diff` / `git log`). -- Exception: if the user explicitly requests immediate execution (e.g., “skip planning”, “just do it”), proceed without this gate. +- Default to a two-phase workflow: clarify goal + plan first, execute after explicit requester approval. +- If a request may require any state-changing work, you MUST first dialogue with the requester to clarify details and make the goal explicit. Do not proceed while the goal is ambiguous. +- Allowed before approval: + - Clarifying questions and read-only inspection (reading files, searching, and `git status` / `git diff` / `git log`). + - Any unavoidable automated work triggered as a side-effect of those read-only commands. + - Any command execution that must not adversely affect program behavior or external systems (including changes made by tooling), such as: + - Installing/restoring dependencies using repo-standard tooling (lockfile changes are allowed). + - Running formatters/linters/typecheck/tests/builds (including auto-fix/formatting that modifies files). + - Running code generation/build steps that are deterministic and repo-scoped. + - Running these from clean → dirty → clean is acceptable; publishing/deploying/migrating is not. +- Before any other state-changing execution (e.g., writing or modifying files by hand, changing runtime behavior, or running git commands beyond status/diff/log), do all of the following: + - Restate the request as concrete acceptance criteria (explicit goal, success/failure conditions). + - Produce a written plan (use your planning tool when available) focused on the goal, approach, and verification checkpoints (do not enumerate per-file implementation details or exact commands unless the requester asks). + - Confirm the plan with the requester, ask for approval explicitly, and wait for a clear “yes” before executing. +- No other exceptions: even if the user requests immediate execution (e.g., “skip planning”, “just do it”), treat that as a request to move quickly through this gate, not to bypass it. Source: github:metyatech/agent-rules@HEAD/rules/global/quality-testing-and-errors.md @@ -335,10 +351,10 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/writing-and-documentation ## README and docs -- Every repository must include README.md covering overview/purpose, setup, dev commands (build/test/lint), required env/config, and release/deploy steps if applicable. -- For any code change, assess README impact and update it in the same change set when needed. -- If a README update is not needed, explain why in the final response. -- CLI examples in docs must include required parameters. +- Every repository must include README.md covering overview/purpose, supported environments/compatibility, install/setup, usage examples, dev commands (build/test/lint/format), required env/config, release/deploy steps if applicable, and links to SECURITY.md / CONTRIBUTING.md / LICENSE / CHANGELOG.md when they exist. +- For any change, assess documentation impact and update all affected docs in the same change set so docs match behavior (README, docs/, examples, comments, templates, ADRs/specs, diagrams). +- If no documentation updates are needed, explain why in the final response. +- For CLIs, document every parameter (required and optional) with a description and at least one example; also include at least one end-to-end example command. - Do not include user-specific local paths, fixed workspace directories, drive letters, or personal data in doc examples. Prefer repo-relative paths and placeholders so instructions work in arbitrary environments. ## Markdown linking @@ -419,3 +435,9 @@ Source: agent-rules-local/compose-agentsmd-local.md # Compose-Agentsmd Local Overrides - For this repository only, generate AGENTS.md using `npm run compose` (do not run `compose-agentsmd` directly). + +Source: agent-rules-local/release.md + +# Distribution and release + +- After publishing this repository, update the globally installed CLI to the latest version. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e0d257..d4aa2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,16 @@ All notable changes to this project will be documented in this file. ## 3.2.4 - 2026-02-02 + - Refactored CLI argument value parsing to reduce duplication. ## 3.2.3 - 2026-02-01 + - Restored shared tool rules to use `compose-agentsmd` and moved the repo-specific compose instruction into a local rule. - Added a local rules file and wired it into the ruleset, then regenerated `AGENTS.md`. ## 3.2.2 - 2026-02-01 + - Normalized AGENTS.md Source paths to GitHub-style refs for remote rules and project-relative paths for local rules, with matching test updates. - Added a pre-commit hook to run lint/test/build. - Updated the ruleset to use `github:metyatech/agent-rules` with `cli`/`release` domains, removing the local release extra and regenerating AGENTS.md with the shared rules. @@ -17,9 +20,11 @@ All notable changes to this project will be documented in this file. - Bumped @types/node to ^25.1.0. ## 3.2.1 - 2026-01-27 + - Regenerated `AGENTS.md` to exclude the `AGENTS.md` file from rule diff output. ## 1.0.1 - 2026-01-25 + - Switched the package to ESM (`"type": "module"`, NodeNext compiler options). - Hardened npm publish settings (`publishConfig.access`, `files`, `prepare`). - Expanded test coverage for rules root precedence and ruleset validation. @@ -27,35 +32,44 @@ All notable changes to this project will be documented in this file. - Regenerated `AGENTS.md` from the updated rule modules. ## 1.1.0 - 2026-01-26 + - Added JSON Schema validation for rulesets with an explicit schema file. - Prepended tool guidance rules to generated `AGENTS.md` files. - Updated shared rule modules for JSON schema validation and English rule writing guidance. ## 1.1.1 - 2026-01-26 + - Clarified that README updates must be edited at the same time as code changes in shared rules. ## 1.1.2 - 2026-01-26 + - Clarified that version bumps must include both release creation and package publishing. ## 2.0.0 - 2026-01-26 + - Switched ruleset format to `source/global/domains/extra` with remote GitHub sources and cache support in `~/.agentsmd`. - Added `--refresh` and `--clear-cache` cache management commands. - Updated ruleset schema, tests, and README to match the new format. ## 2.0.1 - 2026-01-26 + - Moved the publish/global-update rule into local rules for this repository. ## 2.0.2 - 2026-01-26 + - Externalized tool-inserted rules and usage text into `tools/`. - Added rule guidance to externalize long embedded strings/templates when possible. ## 3.0.0 - 2026-01-26 + - Removed recursive ruleset discovery; only the project root ruleset is composed unless `--ruleset` is provided. ## 3.1.0 - 2026-01-26 + - Added `--version`/`-V` and `--verbose`/`-v` flags with verbose diagnostics output. ## 3.2.0 - 2026-01-26 + - Added `init` to bootstrap rulesets with dry-run, confirmation, and compose options. - Allowed ruleset files to include line/block comments (JSON with comments). - Updated init defaults to use generic GitHub sources and omit global/domains/extra unless specified. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0beef1a..4648545 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,20 +1,25 @@ # Code of Conduct ## Our pledge + We are committed to providing a friendly, safe, and welcoming environment for everyone. ## Expected behavior + - Be respectful and considerate. - Assume good intent and collaborate constructively. - Provide and accept feedback gracefully. ## Unacceptable behavior + - Harassment, discrimination, or hateful conduct. - Personal attacks, trolling, or intimidation. - Publishing private information without permission. ## Enforcement + Maintainers may remove, edit, or reject contributions that violate this Code of Conduct. ## Reporting -Report issues to the maintainers via GitHub Security Advisories or a private channel when possible. + +Report issues to the maintainers via GitHub Security Advisories or a private channel when possible. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f3b1f77..8c4503b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,7 @@ # Contributing ## Development setup + 1. Install dependencies. 2. Run lint and tests before submitting changes. @@ -11,6 +12,7 @@ npm test ``` ## Rules composition workflow + This repository treats `AGENTS.md` as a generated file. - Do not edit `AGENTS.md` directly. @@ -22,6 +24,7 @@ npm run compose ``` ## Pull requests + - Keep changes scoped to the affected repository. - Include tests for behavioral changes. -- Update `README.md` when usage or behavior changes. +- Update `README.md` when usage or behavior changes. diff --git a/README.md b/README.md index dda1e0c..a6fd247 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Ruleset files accept JSON with `//` or `/* */` comments. // Additional local rule files to append. "extra": ["agent-rules-local/custom.md"], // Output file name. - "output": "AGENTS.md" + "output": "AGENTS.md", } ``` @@ -134,11 +134,12 @@ npm install npm run lint npm run build npm test -``` - -## Overview -This repository contains the compose-agentsmd project. - -## Requirements and Configuration -- No required environment variables are documented. - +``` + +## Overview + +This repository contains the compose-agentsmd project. + +## Requirements and Configuration + +- No required environment variables are documented. diff --git a/SECURITY.md b/SECURITY.md index b18cc35..aca7955 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,15 +1,18 @@ # Security Policy ## Supported versions + The latest published version is supported. ## Reporting a vulnerability + Please report vulnerabilities privately via GitHub Security Advisories or by opening a private report to the maintainers. Do not file public issues for security-sensitive reports. When reporting, include: + - A clear description of the vulnerability - Steps to reproduce or a proof of concept - Impact assessment - Suggested mitigations (if known) -We will acknowledge receipt and work on a fix as quickly as possible. +We will acknowledge receipt and work on a fix as quickly as possible. diff --git a/agent-rules-local/release.md b/agent-rules-local/release.md index 44ca777..cd4a91b 100644 --- a/agent-rules-local/release.md +++ b/agent-rules-local/release.md @@ -1,3 +1,3 @@ # Distribution and release -- After publishing this repository, update the globally installed CLI to the latest version. \ No newline at end of file +- After publishing this repository, update the globally installed CLI to the latest version. diff --git a/agent-ruleset.json b/agent-ruleset.json index 913d996..bbc4b87 100644 --- a/agent-ruleset.json +++ b/agent-ruleset.json @@ -1,12 +1,6 @@ -{ +{ "source": "github:metyatech/agent-rules", - "domains": [ - "cli", - "node", - "release" - ], - "extra": [ - "agent-rules-local/compose-agentsmd-local.md" - ], + "domains": ["cli", "node", "release"], + "extra": ["agent-rules-local/compose-agentsmd-local.md", "agent-rules-local/release.md"], "output": "AGENTS.md" } diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..e3dae94 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,28 @@ +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import prettier from 'eslint-config-prettier'; +import globals from 'globals'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + prettier, + { + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + { + files: ['src/**/*.ts'], + languageOptions: { + parserOptions: { + project: './tsconfig.json', + }, + }, + }, + { + ignores: ['dist/**', 'node_modules/**', 'AGENTS.md'], + }, +); diff --git a/package-lock.json b/package-lock.json index b8a547c..9dddb77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,311 @@ }, "devDependencies": { "@types/node": "^25.1.0", - "typescript": "^5.7.3" + "@typescript-eslint/eslint-plugin": "^8.54.0", + "@typescript-eslint/parser": "^8.54.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.3.0", + "prettier": "^3.8.1", + "typescript": "^5.7.3", + "typescript-eslint": "^8.54.0" }, "engines": { "node": ">=20" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.1.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", @@ -29,53 +328,1132 @@ "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.16.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.54.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "argparse": "^2.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -85,6 +1463,121 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -99,12 +1592,85 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 7d1fa52..80fa1d8 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,10 @@ }, "scripts": { "build": "tsc -p tsconfig.json", - "lint": "tsc -p tsconfig.json --noEmit", + "lint": "eslint .", + "format": "prettier --write .", + "typecheck": "tsc -p tsconfig.json --noEmit", + "check": "npm run lint && npm run typecheck && prettier --check . && npm audit", "prepare": "npm run build", "prepack": "npm run build", "test": "npm run build && node --test", @@ -45,7 +48,14 @@ }, "devDependencies": { "@types/node": "^25.1.0", - "typescript": "^5.7.3" + "@typescript-eslint/eslint-plugin": "^8.54.0", + "@typescript-eslint/parser": "^8.54.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.3.0", + "prettier": "^3.8.1", + "typescript": "^5.7.3", + "typescript-eslint": "^8.54.0" }, "dependencies": { "ajv": "^8.17.1" diff --git a/src/compose-agents.ts b/src/compose-agents.ts index ca7d9c4..d843a8f 100644 --- a/src/compose-agents.ts +++ b/src/compose-agents.ts @@ -1,21 +1,21 @@ #!/usr/bin/env node -import fs from "node:fs"; -import path from "node:path"; -import os from "node:os"; -import { execFileSync } from "node:child_process"; -import readline from "node:readline"; -import { Ajv, type ErrorObject } from "ajv"; - -const DEFAULT_RULESET_NAME = "agent-ruleset.json"; -const DEFAULT_OUTPUT = "AGENTS.md"; -const DEFAULT_CACHE_ROOT = path.join(os.homedir(), ".agentsmd", "cache"); -const DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), ".agentsmd", "workspace"); -const DEFAULT_INIT_SOURCE = "github:owner/repo@latest"; +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; +import { execFileSync } from 'node:child_process'; +import readline from 'node:readline'; +import { Ajv, type ErrorObject } from 'ajv'; + +const DEFAULT_RULESET_NAME = 'agent-ruleset.json'; +const DEFAULT_OUTPUT = 'AGENTS.md'; +const DEFAULT_CACHE_ROOT = path.join(os.homedir(), '.agentsmd', 'cache'); +const DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), '.agentsmd', 'workspace'); +const DEFAULT_INIT_SOURCE = 'github:owner/repo@latest'; const DEFAULT_INIT_DOMAINS: string[] = []; const DEFAULT_INIT_EXTRA: string[] = []; -const RULESET_SCHEMA_PATH = new URL("../agent-ruleset.schema.json", import.meta.url); -const PACKAGE_JSON_PATH = new URL("../package.json", import.meta.url); +const RULESET_SCHEMA_PATH = new URL('../agent-ruleset.schema.json', import.meta.url); +const PACKAGE_JSON_PATH = new URL('../package.json', import.meta.url); type CliArgs = { help?: boolean; @@ -37,11 +37,11 @@ type CliArgs = { dryRun?: boolean; yes?: boolean; force?: boolean; - command?: "compose" | "edit-rules" | "apply-rules" | "init"; + command?: 'compose' | 'edit-rules' | 'apply-rules' | 'init'; }; -const TOOL_RULES_PATH = new URL("../tools/tool-rules.md", import.meta.url); -const USAGE_PATH = new URL("../tools/usage.txt", import.meta.url); +const TOOL_RULES_PATH = new URL('../tools/tool-rules.md', import.meta.url); +const USAGE_PATH = new URL('../tools/usage.txt', import.meta.url); const readValueArg = (remaining: string[], index: number, flag: string): string => { const value = remaining[index + 1]; @@ -53,131 +53,131 @@ const readValueArg = (remaining: string[], index: number, flag: string): string const parseArgs = (argv: string[]): CliArgs => { const args: CliArgs = {}; - const knownCommands = new Set(["edit-rules", "apply-rules", "init"]); + const knownCommands = new Set(['edit-rules', 'apply-rules', 'init']); const remaining = [...argv]; if (remaining.length > 0 && knownCommands.has(remaining[0])) { - args.command = remaining.shift() as "edit-rules" | "apply-rules" | "init"; + args.command = remaining.shift() as 'edit-rules' | 'apply-rules' | 'init'; } for (let i = 0; i < remaining.length; i += 1) { const arg = remaining[i]; - if (arg === "--help" || arg === "-h") { + if (arg === '--help' || arg === '-h') { args.help = true; continue; } - if (arg === "--version" || arg === "-V") { + if (arg === '--version' || arg === '-V') { args.version = true; continue; } - if (arg === "--verbose" || arg === "-v") { + if (arg === '--verbose' || arg === '-v') { args.verbose = true; continue; } - if (arg === "--quiet" || arg === "-q") { + if (arg === '--quiet' || arg === '-q') { args.quiet = true; continue; } - if (arg === "--json") { + if (arg === '--json') { args.json = true; continue; } - if (arg === "--root") { - const value = readValueArg(remaining, i, "--root"); + if (arg === '--root') { + const value = readValueArg(remaining, i, '--root'); args.root = value; i += 1; continue; } - if (arg === "--ruleset") { - const value = readValueArg(remaining, i, "--ruleset"); + if (arg === '--ruleset') { + const value = readValueArg(remaining, i, '--ruleset'); args.ruleset = value; i += 1; continue; } - if (arg === "--ruleset-name") { - const value = readValueArg(remaining, i, "--ruleset-name"); + if (arg === '--ruleset-name') { + const value = readValueArg(remaining, i, '--ruleset-name'); args.rulesetName = value; i += 1; continue; } - if (arg === "--refresh") { + if (arg === '--refresh') { args.refresh = true; continue; } - if (arg === "--clear-cache") { + if (arg === '--clear-cache') { args.clearCache = true; continue; } - if (arg === "--source") { - const value = readValueArg(remaining, i, "--source"); + if (arg === '--source') { + const value = readValueArg(remaining, i, '--source'); args.source = value; i += 1; continue; } - if (arg === "--domains") { - const value = readValueArg(remaining, i, "--domains"); - args.domains = [...(args.domains ?? []), ...value.split(",").map((entry) => entry.trim())]; + if (arg === '--domains') { + const value = readValueArg(remaining, i, '--domains'); + args.domains = [...(args.domains ?? []), ...value.split(',').map((entry) => entry.trim())]; i += 1; continue; } - if (arg === "--no-domains") { + if (arg === '--no-domains') { args.domains = []; continue; } - if (arg === "--extra") { - const value = readValueArg(remaining, i, "--extra"); - args.extra = [...(args.extra ?? []), ...value.split(",").map((entry) => entry.trim())]; + if (arg === '--extra') { + const value = readValueArg(remaining, i, '--extra'); + args.extra = [...(args.extra ?? []), ...value.split(',').map((entry) => entry.trim())]; i += 1; continue; } - if (arg === "--no-extra") { + if (arg === '--no-extra') { args.extra = []; continue; } - if (arg === "--output") { - const value = readValueArg(remaining, i, "--output"); + if (arg === '--output') { + const value = readValueArg(remaining, i, '--output'); args.output = value; i += 1; continue; } - if (arg === "--no-global") { + if (arg === '--no-global') { args.global = false; continue; } - if (arg === "--compose") { + if (arg === '--compose') { args.compose = true; continue; } - if (arg === "--dry-run") { + if (arg === '--dry-run') { args.dryRun = true; continue; } - if (arg === "--yes") { + if (arg === '--yes') { args.yes = true; continue; } - if (arg === "--force") { + if (arg === '--force') { args.force = true; continue; } @@ -188,10 +188,10 @@ const parseArgs = (argv: string[]): CliArgs => { return args; }; -const normalizeTrailingWhitespace = (content: string): string => content.replace(/\s+$/u, ""); -const normalizePath = (filePath: string): string => filePath.replace(/\\/g, "/"); +const normalizeTrailingWhitespace = (content: string): string => content.replace(/\s+$/u, ''); +const normalizePath = (filePath: string): string => filePath.replace(/\\/g, '/'); const isNonEmptyString = (value: unknown): value is string => - typeof value === "string" && value.trim() !== ""; + typeof value === 'string' && value.trim() !== ''; const normalizeListOption = (values: string[] | undefined, label: string): string[] | undefined => { if (!values) { return undefined; @@ -214,26 +214,26 @@ const askQuestion = (prompt: string): Promise => }); }); -const usage = normalizeTrailingWhitespace(fs.readFileSync(USAGE_PATH, "utf8")); -const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, "utf8")) as { version?: string }; -const getVersion = (): string => packageJson.version ?? "unknown"; +const usage = normalizeTrailingWhitespace(fs.readFileSync(USAGE_PATH, 'utf8')); +const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf8')) as { version?: string }; +const getVersion = (): string => packageJson.version ?? 'unknown'; -const rulesetSchema = JSON.parse(fs.readFileSync(RULESET_SCHEMA_PATH, "utf8")); -const TOOL_RULES = normalizeTrailingWhitespace(fs.readFileSync(TOOL_RULES_PATH, "utf8")); +const rulesetSchema = JSON.parse(fs.readFileSync(RULESET_SCHEMA_PATH, 'utf8')); +const TOOL_RULES = normalizeTrailingWhitespace(fs.readFileSync(TOOL_RULES_PATH, 'utf8')); const ajv = new Ajv({ allErrors: true, strict: false }); const validateRulesetSchema = ajv.compile(rulesetSchema); const formatSchemaErrors = (errors: ErrorObject[] | null | undefined): string => { if (!errors || errors.length === 0) { - return "Unknown schema validation error"; + return 'Unknown schema validation error'; } return errors .map((error) => { - const pathLabel = error.instancePath ? error.instancePath : "(root)"; - return `${pathLabel} ${error.message ?? "is invalid"}`; + const pathLabel = error.instancePath ? error.instancePath : '(root)'; + return `${pathLabel} ${error.message ?? 'is invalid'}`; }) - .join("; "); + .join('; '); }; const resolveFrom = (baseDir: string, targetPath: string): string => { @@ -272,9 +272,9 @@ const ensureDirectoryExists = (dirPath: string): void => { }; const stripJsonComments = (input: string): string => { - let output = ""; + let output = ''; let inString = false; - let stringChar = ""; + let stringChar = ''; let escaping = false; let inLineComment = false; let inBlockComment = false; @@ -284,7 +284,7 @@ const stripJsonComments = (input: string): string => { const next = input[i + 1]; if (inLineComment) { - if (char === "\n") { + if (char === '\n') { inLineComment = false; output += char; } @@ -292,7 +292,7 @@ const stripJsonComments = (input: string): string => { } if (inBlockComment) { - if (char === "*" && next === "/") { + if (char === '*' && next === '/') { inBlockComment = false; i += 1; } @@ -305,30 +305,30 @@ const stripJsonComments = (input: string): string => { escaping = false; continue; } - if (char === "\\") { + if (char === '\\') { escaping = true; continue; } if (char === stringChar) { inString = false; - stringChar = ""; + stringChar = ''; } continue; } - if (char === "/" && next === "/") { + if (char === '/' && next === '/') { inLineComment = true; i += 1; continue; } - if (char === "/" && next === "*") { + if (char === '/' && next === '*') { inBlockComment = true; i += 1; continue; } - if (char === "\"" || char === "'") { + if (char === '"' || char === "'") { inString = true; stringChar = char; output += char; @@ -342,7 +342,7 @@ const stripJsonComments = (input: string): string => { }; const readJsonFile = (filePath: string): unknown => { - const raw = fs.readFileSync(filePath, "utf8"); + const raw = fs.readFileSync(filePath, 'utf8'); return JSON.parse(stripJsonComments(raw)); }; @@ -402,7 +402,7 @@ const collectMarkdownFiles = (rootDir: string): string[] => { continue; } - if (entry.isFile() && path.extname(entry.name).toLowerCase() === ".md") { + if (entry.isFile() && path.extname(entry.name).toLowerCase() === '.md') { results.push(entryPath); } } @@ -415,7 +415,11 @@ const collectMarkdownFiles = (rootDir: string): string[] => { }); }; -const addRulePaths = (rulePaths: string[], resolvedRules: string[], seenRules: Set): void => { +const addRulePaths = ( + rulePaths: string[], + resolvedRules: string[], + seenRules: Set, +): void => { for (const rulePath of rulePaths) { const resolvedRulePath = path.resolve(rulePath); if (seenRules.has(resolvedRulePath)) { @@ -432,33 +436,33 @@ type ComposeOptions = { dryRun?: boolean; }; -const sanitizeCacheSegment = (value: string): string => value.replace(/[\\/]/gu, "__"); +const sanitizeCacheSegment = (value: string): string => value.replace(/[\\/]/gu, '__'); const looksLikeCommitHash = (value: string): boolean => /^[a-f0-9]{7,40}$/iu.test(value); const execGit = (args: string[], cwd?: string): string => - execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim(); + execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); const parseGithubSource = (source: string): GithubSource => { const trimmed = source.trim(); - if (!trimmed.startsWith("github:")) { + if (!trimmed.startsWith('github:')) { throw new Error(`Unsupported source: ${source}`); } - const withoutPrefix = trimmed.slice("github:".length); - const [repoPart, refPart] = withoutPrefix.split("@"); - const [owner, repo] = repoPart.split("/"); + const withoutPrefix = trimmed.slice('github:'.length); + const [repoPart, refPart] = withoutPrefix.split('@'); + const [owner, repo] = repoPart.split('/'); if (!isNonEmptyString(owner) || !isNonEmptyString(repo)) { throw new Error(`Invalid GitHub source (expected github:owner/repo@ref): ${source}`); } - const ref = isNonEmptyString(refPart) ? refPart : "latest"; + const ref = isNonEmptyString(refPart) ? refPart : 'latest'; return { owner, repo, ref, url: `https://github.com/${owner}/${repo}.git` }; }; const parseSemver = (tag: string): number[] | null => { - const cleaned = tag.startsWith("v") ? tag.slice(1) : tag; - const parts = cleaned.split("."); + const cleaned = tag.startsWith('v') ? tag.slice(1) : tag; + const parts = cleaned.split('.'); if (parts.length < 2 || parts.length > 3) { return null; } @@ -484,18 +488,18 @@ const compareSemver = (a: number[], b: number[]): number => { }; const resolveLatestTag = (repoUrl: string): { tag?: string; hash?: string } => { - const raw = execGit(["ls-remote", "--tags", "--refs", repoUrl]); + const raw = execGit(['ls-remote', '--tags', '--refs', repoUrl]); if (!raw) { return {}; } const candidates = raw - .split("\n") + .split('\n') .map((line) => line.trim()) .filter(Boolean) .map((line) => { const [hash, ref] = line.split(/\s+/u); - const tag = ref?.replace("refs/tags/", ""); + const tag = ref?.replace('refs/tags/', ''); if (!hash || !tag) { return null; } @@ -517,7 +521,7 @@ const resolveLatestTag = (repoUrl: string): { tag?: string; hash?: string } => { }; const resolveHeadHash = (repoUrl: string): string => { - const raw = execGit(["ls-remote", repoUrl, "HEAD"]); + const raw = execGit(['ls-remote', repoUrl, 'HEAD']); const [hash] = raw.split(/\s+/u); if (!hash) { throw new Error(`Unable to resolve HEAD for ${repoUrl}`); @@ -526,7 +530,7 @@ const resolveHeadHash = (repoUrl: string): string => { }; const resolveRefHash = (repoUrl: string, ref: string): string | null => { - const raw = execGit(["ls-remote", repoUrl, ref, `refs/tags/${ref}`, `refs/heads/${ref}`]); + const raw = execGit(['ls-remote', repoUrl, ref, `refs/tags/${ref}`, `refs/heads/${ref}`]); if (!raw) { return null; } @@ -535,27 +539,27 @@ const resolveRefHash = (repoUrl: string, ref: string): string | null => { }; const cloneAtRef = (repoUrl: string, ref: string, destination: string): void => { - execGit(["clone", "--depth", "1", "--branch", ref, repoUrl, destination]); + execGit(['clone', '--depth', '1', '--branch', ref, repoUrl, destination]); }; const fetchCommit = (repoUrl: string, commitHash: string, destination: string): void => { ensureDir(destination); - execGit(["init"], destination); - execGit(["remote", "add", "origin", repoUrl], destination); - execGit(["fetch", "--depth", "1", "origin", commitHash], destination); - execGit(["checkout", "FETCH_HEAD"], destination); + execGit(['init'], destination); + execGit(['remote', 'add', 'origin', repoUrl], destination); + execGit(['fetch', '--depth', '1', 'origin', commitHash], destination); + execGit(['checkout', 'FETCH_HEAD'], destination); }; const resolveGithubRulesRoot = ( source: string, - refresh: boolean + refresh: boolean, ): { rulesRoot: string; resolvedRef: string } => { const parsed = parseGithubSource(source); - const resolved = parsed.ref === "latest" ? resolveLatestTag(parsed.url) : null; - const resolvedRef = resolved?.tag ?? (parsed.ref === "latest" ? "HEAD" : parsed.ref); + const resolved = parsed.ref === 'latest' ? resolveLatestTag(parsed.url) : null; + const resolvedRef = resolved?.tag ?? (parsed.ref === 'latest' ? 'HEAD' : parsed.ref); const resolvedHash = resolved?.hash ?? - (resolvedRef === "HEAD" + (resolvedRef === 'HEAD' ? resolveHeadHash(parsed.url) : resolveRefHash(parsed.url, resolvedRef)); @@ -564,7 +568,9 @@ const resolveGithubRulesRoot = ( } const cacheSegment = - resolvedRef === "HEAD" ? sanitizeCacheSegment(resolvedHash ?? resolvedRef) : sanitizeCacheSegment(resolvedRef); + resolvedRef === 'HEAD' + ? sanitizeCacheSegment(resolvedHash ?? resolvedRef) + : sanitizeCacheSegment(resolvedRef); const cacheDir = path.join(DEFAULT_CACHE_ROOT, parsed.owner, parsed.repo, cacheSegment); if (refresh && fs.existsSync(cacheDir)) { @@ -586,7 +592,7 @@ const resolveGithubRulesRoot = ( } } - const rulesRoot = path.join(cacheDir, "rules"); + const rulesRoot = path.join(cacheDir, 'rules'); ensureDirectoryExists(rulesRoot); return { rulesRoot, resolvedRef }; @@ -598,13 +604,14 @@ const resolveLocalRulesRoot = (rulesetDir: string, source: string): string => { throw new Error(`Missing source path: ${resolvedSource}`); } - const candidate = path.basename(resolvedSource) === "rules" ? resolvedSource : path.join(resolvedSource, "rules"); + const candidate = + path.basename(resolvedSource) === 'rules' ? resolvedSource : path.join(resolvedSource, 'rules'); ensureDirectoryExists(candidate); return candidate; }; const resolveWorkspaceRoot = (rulesetDir: string, source: string): string => { - if (source.startsWith("github:")) { + if (source.startsWith('github:')) { const parsed = parseGithubSource(source); return path.join(DEFAULT_WORKSPACE_ROOT, parsed.owner, parsed.repo); } @@ -618,42 +625,42 @@ const ensureWorkspaceForGithubSource = (source: string): string => { if (!fs.existsSync(workspaceRoot)) { ensureDir(path.dirname(workspaceRoot)); - execGit(["clone", parsed.url, workspaceRoot]); + execGit(['clone', parsed.url, workspaceRoot]); } - if (parsed.ref !== "latest") { - execGit(["fetch", "--all"], workspaceRoot); - execGit(["checkout", parsed.ref], workspaceRoot); + if (parsed.ref !== 'latest') { + execGit(['fetch', '--all'], workspaceRoot); + execGit(['checkout', parsed.ref], workspaceRoot); } return workspaceRoot; }; const applyRulesFromWorkspace = (rulesetDir: string, source: string): void => { - if (!source.startsWith("github:")) { + if (!source.startsWith('github:')) { return; } const workspaceRoot = ensureWorkspaceForGithubSource(source); - const status = execGit(["status", "--porcelain"], workspaceRoot); + const status = execGit(['status', '--porcelain'], workspaceRoot); if (status) { throw new Error(`Workspace has uncommitted changes: ${workspaceRoot}`); } - const branch = execGit(["rev-parse", "--abbrev-ref", "HEAD"], workspaceRoot); - if (branch === "HEAD") { + const branch = execGit(['rev-parse', '--abbrev-ref', 'HEAD'], workspaceRoot); + if (branch === 'HEAD') { throw new Error(`Workspace is in detached HEAD state: ${workspaceRoot}`); } - execGit(["push"], workspaceRoot); + execGit(['push'], workspaceRoot); }; const resolveRulesRoot = ( rulesetDir: string, source: string, - refresh: boolean + refresh: boolean, ): { rulesRoot: string; resolvedRef?: string } => { - if (source.startsWith("github:")) { + if (source.startsWith('github:')) { return resolveGithubRulesRoot(source, refresh); } @@ -665,12 +672,12 @@ const formatRuleSourcePath = ( rulesRoot: string, rulesetDir: string, source: string, - resolvedRef?: string + resolvedRef?: string, ): string => { // Check if this rule is from the resolved rulesRoot (GitHub or local source) const isFromSource = rulePath.startsWith(rulesRoot); - - if (isFromSource && source.startsWith("github:")) { + + if (isFromSource && source.startsWith('github:')) { // GitHub source rule const parsed = parseGithubSource(source); const cacheRepoRoot = path.dirname(rulesRoot); @@ -690,9 +697,13 @@ const composeRuleset = (rulesetPath: string, rootDir: string, options: ComposeOp const outputFileName = projectRuleset.output ?? DEFAULT_OUTPUT; const outputPath = resolveFrom(rulesetDir, outputFileName); - const { rulesRoot, resolvedRef } = resolveRulesRoot(rulesetDir, projectRuleset.source, options.refresh ?? false); - const globalRoot = path.join(rulesRoot, "global"); - const domainsRoot = path.join(rulesRoot, "domains"); + const { rulesRoot, resolvedRef } = resolveRulesRoot( + rulesetDir, + projectRuleset.source, + options.refresh ?? false, + ); + const globalRoot = path.join(rulesRoot, 'global'); + const domainsRoot = path.join(rulesRoot, 'domains'); const resolvedRules: string[] = []; const seenRules = new Set(); @@ -712,37 +723,43 @@ const composeRuleset = (rulesetPath: string, rootDir: string, options: ComposeOp addRulePaths(directRulePaths, resolvedRules, seenRules); const parts = resolvedRules.map((rulePath) => { - const body = normalizeTrailingWhitespace(fs.readFileSync(rulePath, "utf8")); - const sourcePath = formatRuleSourcePath(rulePath, rulesRoot, rulesetDir, projectRuleset.source, resolvedRef); + const body = normalizeTrailingWhitespace(fs.readFileSync(rulePath, 'utf8')); + const sourcePath = formatRuleSourcePath( + rulePath, + rulesRoot, + rulesetDir, + projectRuleset.source, + resolvedRef, + ); return `Source: ${sourcePath}\n\n${body}`; }); - const lintHeader = ""; + const lintHeader = ''; const toolRules = normalizeTrailingWhitespace(TOOL_RULES); - const output = `${lintHeader}\n${[toolRules, ...parts].join("\n\n")}\n`; + const output = `${lintHeader}\n${[toolRules, ...parts].join('\n\n')}\n`; if (!options.dryRun) { fs.mkdirSync(path.dirname(outputPath), { recursive: true }); - fs.writeFileSync(outputPath, output, "utf8"); + fs.writeFileSync(outputPath, output, 'utf8'); } return normalizePath(path.relative(rootDir, outputPath)); }; type InitPlanItem = { - action: "create" | "overwrite"; + action: 'create' | 'overwrite'; path: string; }; -const LOCAL_RULES_TEMPLATE = "# Local Rules\n\n- Add project-specific instructions here.\n"; +const LOCAL_RULES_TEMPLATE = '# Local Rules\n\n- Add project-specific instructions here.\n'; const buildInitRuleset = (args: CliArgs): ProjectRuleset => { - const domains = normalizeListOption(args.domains, "--domains"); - const extra = normalizeListOption(args.extra, "--extra"); + const domains = normalizeListOption(args.domains, '--domains'); + const extra = normalizeListOption(args.extra, '--extra'); const ruleset: ProjectRuleset = { source: args.source ?? DEFAULT_INIT_SOURCE, - output: args.output ?? DEFAULT_OUTPUT + output: args.output ?? DEFAULT_OUTPUT, }; if (args.global === false) { @@ -766,13 +783,13 @@ const formatInitRuleset = (ruleset: ProjectRuleset): string => { const domainsValue = JSON.stringify(ruleset.domains ?? []); const extraValue = JSON.stringify(ruleset.extra ?? []); const lines = [ - "{", + '{', ' // Rules source. Use github:owner/repo@ref or a local path.', ` "source": "${ruleset.source}",`, ' // Domain folders under rules/domains.', ` "domains": ${domainsValue},`, ' // Additional local rule files to append.', - ` "extra": ${extraValue},` + ` "extra": ${extraValue},`, ]; if (ruleset.global === false) { @@ -782,19 +799,19 @@ const formatInitRuleset = (ruleset: ProjectRuleset): string => { lines.push(' // Output file name.'); lines.push(` "output": "${ruleset.output ?? DEFAULT_OUTPUT}"`); - lines.push("}"); + lines.push('}'); - return `${lines.join("\n")}\n`; + return `${lines.join('\n')}\n`; }; const formatPlan = (items: InitPlanItem[], rootDir: string): string => { const lines = items.map((item) => { - const verb = item.action === "overwrite" ? "Overwrite" : "Create"; + const verb = item.action === 'overwrite' ? 'Overwrite' : 'Create'; const relative = normalizePath(path.relative(rootDir, item.path)); return `- ${verb}: ${relative}`; }); - return `Init plan:\n${lines.join("\n")}\n`; + return `Init plan:\n${lines.join('\n')}\n`; }; const confirmInit = async (args: CliArgs): Promise => { @@ -803,17 +820,19 @@ const confirmInit = async (args: CliArgs): Promise => { } if (!process.stdin.isTTY) { - throw new Error("Confirmation required. Re-run with --yes to continue."); + throw new Error('Confirmation required. Re-run with --yes to continue.'); } - const answer = await askQuestion("Proceed with init? [y/N] "); + const answer = await askQuestion('Proceed with init? [y/N] '); if (!/^y(es)?$/iu.test(answer.trim())) { - throw new Error("Init aborted."); + throw new Error('Init aborted.'); } }; const initProject = async (args: CliArgs, rootDir: string, rulesetName: string): Promise => { - const rulesetPath = args.ruleset ? resolveFrom(rootDir, args.ruleset) : path.join(rootDir, rulesetName); + const rulesetPath = args.ruleset + ? resolveFrom(rootDir, args.ruleset) + : path.join(rootDir, rulesetName); const rulesetDir = path.dirname(rulesetPath); const ruleset = buildInitRuleset(args); const outputPath = resolveFrom(rulesetDir, ruleset.output ?? DEFAULT_OUTPUT); @@ -824,9 +843,9 @@ const initProject = async (args: CliArgs, rootDir: string, rulesetName: string): if (!args.force) { throw new Error(`Ruleset already exists: ${normalizePath(rulesetPath)}`); } - plan.push({ action: "overwrite", path: rulesetPath }); + plan.push({ action: 'overwrite', path: rulesetPath }); } else { - plan.push({ action: "create", path: rulesetPath }); + plan.push({ action: 'create', path: rulesetPath }); } const extraFiles = (ruleset.extra ?? []).map((rulePath) => resolveFrom(rulesetDir, rulePath)); @@ -834,24 +853,26 @@ const initProject = async (args: CliArgs, rootDir: string, rulesetName: string): for (const extraPath of extraFiles) { if (fs.existsSync(extraPath)) { if (args.force) { - plan.push({ action: "overwrite", path: extraPath }); + plan.push({ action: 'overwrite', path: extraPath }); extraToWrite.push(extraPath); } continue; } - plan.push({ action: "create", path: extraPath }); + plan.push({ action: 'create', path: extraPath }); extraToWrite.push(extraPath); } if (args.compose) { if (fs.existsSync(outputPath)) { if (!args.force) { - throw new Error(`Output already exists: ${normalizePath(outputPath)} (use --force to overwrite)`); + throw new Error( + `Output already exists: ${normalizePath(outputPath)} (use --force to overwrite)`, + ); } - plan.push({ action: "overwrite", path: outputPath }); + plan.push({ action: 'overwrite', path: outputPath }); } else { - plan.push({ action: "create", path: outputPath }); + plan.push({ action: 'create', path: outputPath }); } } @@ -867,15 +888,15 @@ const initProject = async (args: CliArgs, rootDir: string, rulesetName: string): dryRun: true, plan: plan.map((item) => ({ action: item.action, - path: normalizePath(path.relative(rootDir, item.path)) - })) + path: normalizePath(path.relative(rootDir, item.path)), + })), }, null, - 2 - ) + "\n" + 2, + ) + '\n', ); } else if (!args.quiet) { - process.stdout.write("Dry run: no changes made.\n"); + process.stdout.write('Dry run: no changes made.\n'); } return; } @@ -883,11 +904,11 @@ const initProject = async (args: CliArgs, rootDir: string, rulesetName: string): await confirmInit(args); fs.mkdirSync(path.dirname(rulesetPath), { recursive: true }); - fs.writeFileSync(`${rulesetPath}`, formatInitRuleset(ruleset), "utf8"); + fs.writeFileSync(`${rulesetPath}`, formatInitRuleset(ruleset), 'utf8'); for (const extraPath of extraToWrite) { fs.mkdirSync(path.dirname(extraPath), { recursive: true }); - fs.writeFileSync(extraPath, LOCAL_RULES_TEMPLATE, "utf8"); + fs.writeFileSync(extraPath, LOCAL_RULES_TEMPLATE, 'utf8'); } let composedOutput: string | undefined; @@ -900,21 +921,25 @@ const initProject = async (args: CliArgs, rootDir: string, rulesetName: string): JSON.stringify( { initialized: [normalizePath(path.relative(rootDir, rulesetPath))], - localRules: extraToWrite.map((filePath) => normalizePath(path.relative(rootDir, filePath))), + localRules: extraToWrite.map((filePath) => + normalizePath(path.relative(rootDir, filePath)), + ), composed: composedOutput ? [composedOutput] : [], - dryRun: false + dryRun: false, }, null, - 2 - ) + "\n" + 2, + ) + '\n', ); } else if (!args.quiet) { - process.stdout.write(`Initialized ruleset:\n- ${normalizePath(path.relative(rootDir, rulesetPath))}\n`); + process.stdout.write( + `Initialized ruleset:\n- ${normalizePath(path.relative(rootDir, rulesetPath))}\n`, + ); if (extraToWrite.length > 0) { process.stdout.write( `Initialized local rules:\n${extraToWrite .map((filePath) => `- ${normalizePath(path.relative(rootDir, filePath))}`) - .join("\n")}\n` + .join('\n')}\n`, ); } if (composedOutput) { @@ -923,7 +948,11 @@ const initProject = async (args: CliArgs, rootDir: string, rulesetName: string): } }; -const getRulesetFiles = (rootDir: string, specificRuleset: string | undefined, rulesetName: string): string[] => { +const getRulesetFiles = ( + rootDir: string, + specificRuleset: string | undefined, + rulesetName: string, +): string[] => { if (specificRuleset) { const resolved = resolveFrom(rootDir, specificRuleset); ensureFileExists(resolved); @@ -937,7 +966,11 @@ const getRulesetFiles = (rootDir: string, specificRuleset: string | undefined, r return [defaultRuleset]; }; -const ensureSingleRuleset = (rulesetFiles: string[], rootDir: string, rulesetName: string): string => { +const ensureSingleRuleset = ( + rulesetFiles: string[], + rootDir: string, + rulesetName: string, +): string => { if (rulesetFiles.length === 0) { const expectedPath = normalizePath(path.join(rootDir, rulesetName)); throw new Error(`Missing ruleset file: ${expectedPath}`); @@ -961,34 +994,34 @@ const main = async (): Promise => { if (args.clearCache) { clearCache(); - process.stdout.write("Cache cleared.\n"); + process.stdout.write('Cache cleared.\n'); return; } const rootDir = args.root ? path.resolve(args.root) : process.cwd(); const rulesetName = args.rulesetName || DEFAULT_RULESET_NAME; const rulesetFiles = getRulesetFiles(rootDir, args.ruleset, rulesetName); - const command = args.command ?? "compose"; + const command = args.command ?? 'compose'; const logVerbose = (message: string): void => { if (args.verbose) { process.stdout.write(`${message}\n`); } }; - logVerbose("Verbose:"); + logVerbose('Verbose:'); logVerbose(`- Root: ${rootDir}`); logVerbose(`- Ruleset name: ${rulesetName}`); logVerbose( - `- Ruleset files:\n${rulesetFiles.map((file) => ` - ${normalizePath(path.relative(rootDir, file))}`).join("\n")}` + `- Ruleset files:\n${rulesetFiles.map((file) => ` - ${normalizePath(path.relative(rootDir, file))}`).join('\n')}`, ); - if (command === "edit-rules") { + if (command === 'edit-rules') { const rulesetPath = ensureSingleRuleset(rulesetFiles, rootDir, rulesetName); const rulesetDir = path.dirname(rulesetPath); const ruleset = readProjectRuleset(rulesetPath); let workspaceRoot = resolveWorkspaceRoot(rulesetDir, ruleset.source); - if (ruleset.source.startsWith("github:")) { + if (ruleset.source.startsWith('github:')) { workspaceRoot = ensureWorkspaceForGithubSource(ruleset.source); } @@ -996,12 +1029,12 @@ const main = async (): Promise => { return; } - if (command === "init") { + if (command === 'init') { await initProject(args, rootDir, rulesetName); return; } - if (command === "apply-rules") { + if (command === 'apply-rules') { const rulesetPath = ensureSingleRuleset(rulesetFiles, rootDir, rulesetName); const rulesetDir = path.dirname(rulesetPath); const ruleset = readProjectRuleset(rulesetPath); @@ -1009,7 +1042,9 @@ const main = async (): Promise => { applyRulesFromWorkspace(rulesetDir, ruleset.source); const output = composeRuleset(rulesetPath, rootDir, { refresh: true, dryRun: args.dryRun }); if (args.json) { - process.stdout.write(JSON.stringify({ composed: [output], dryRun: !!args.dryRun }, null, 2) + "\n"); + process.stdout.write( + JSON.stringify({ composed: [output], dryRun: !!args.dryRun }, null, 2) + '\n', + ); } else if (!args.quiet) { process.stdout.write(`Composed AGENTS.md:\n- ${output}\n`); } @@ -1023,12 +1058,16 @@ const main = async (): Promise => { const outputs = rulesetFiles .sort() - .map((rulesetPath) => composeRuleset(rulesetPath, rootDir, { refresh: args.refresh, dryRun: args.dryRun })); + .map((rulesetPath) => + composeRuleset(rulesetPath, rootDir, { refresh: args.refresh, dryRun: args.dryRun }), + ); if (args.json) { - process.stdout.write(JSON.stringify({ composed: outputs, dryRun: !!args.dryRun }, null, 2) + "\n"); + process.stdout.write( + JSON.stringify({ composed: outputs, dryRun: !!args.dryRun }, null, 2) + '\n', + ); } else if (!args.quiet) { - process.stdout.write(`Composed AGENTS.md:\n${outputs.map((file) => `- ${file}`).join("\n")}\n`); + process.stdout.write(`Composed AGENTS.md:\n${outputs.map((file) => `- ${file}`).join('\n')}\n`); } }; diff --git a/test/compose-agents.test.js b/test/compose-agents.test.js index 6e5e256..c79e9ba 100644 --- a/test/compose-agents.test.js +++ b/test/compose-agents.test.js @@ -1,29 +1,29 @@ -import test from "node:test"; -import assert from "node:assert/strict"; -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { execFileSync } from "node:child_process"; -import { fileURLToPath } from "node:url"; +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const repoRoot = path.resolve(__dirname, ".."); -const cliPath = path.join(repoRoot, "dist", "compose-agents.js"); -const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8")); +const repoRoot = path.resolve(__dirname, '..'); +const cliPath = path.join(repoRoot, 'dist', 'compose-agents.js'); +const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); const writeFile = (filePath, content) => { fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, content, "utf8"); + fs.writeFileSync(filePath, content, 'utf8'); }; -const normalizeTrailingWhitespace = (content) => content.replace(/\s+$/u, ""); -const normalizePath = (value) => value.replace(/\\/g, "/"); +const normalizeTrailingWhitespace = (content) => content.replace(/\s+$/u, ''); +const normalizePath = (value) => value.replace(/\\/g, '/'); const stripJsonComments = (input) => { - let output = ""; + let output = ''; let inString = false; - let stringChar = ""; + let stringChar = ''; let escaping = false; let inLineComment = false; let inBlockComment = false; @@ -33,7 +33,7 @@ const stripJsonComments = (input) => { const next = input[i + 1]; if (inLineComment) { - if (char === "\n") { + if (char === '\n') { inLineComment = false; output += char; } @@ -41,7 +41,7 @@ const stripJsonComments = (input) => { } if (inBlockComment) { - if (char === "*" && next === "/") { + if (char === '*' && next === '/') { inBlockComment = false; i += 1; } @@ -54,30 +54,30 @@ const stripJsonComments = (input) => { escaping = false; continue; } - if (char === "\\") { + if (char === '\\') { escaping = true; continue; } if (char === stringChar) { inString = false; - stringChar = ""; + stringChar = ''; } continue; } - if (char === "/" && next === "/") { + if (char === '/' && next === '/') { inLineComment = true; i += 1; continue; } - if (char === "/" && next === "*") { + if (char === '/' && next === '*') { inBlockComment = true; i += 1; continue; } - if (char === "\"" || char === "'") { + if (char === '"' || char === "'") { inString = true; stringChar = char; output += char; @@ -90,15 +90,15 @@ const stripJsonComments = (input) => { return output; }; const TOOL_RULES = normalizeTrailingWhitespace( - fs.readFileSync(path.join(repoRoot, "tools", "tool-rules.md"), "utf8") + fs.readFileSync(path.join(repoRoot, 'tools', 'tool-rules.md'), 'utf8'), ); const runCli = (args, options) => execFileSync(process.execPath, [cliPath, ...args], { cwd: options.cwd, env: { ...process.env, ...options.env }, - encoding: "utf8", - stdio: "pipe" + encoding: 'utf8', + stdio: 'pipe', }); const formatRuleBlock = (rulePath, body, projectRoot) => { @@ -108,38 +108,38 @@ const formatRuleBlock = (rulePath, body, projectRoot) => { const withToolRules = (body) => `\n${TOOL_RULES}\n\n${body}`; -test("prints version with --version and -V", () => { +test('prints version with --version and -V', () => { const expected = `${packageJson.version}\n`; - const stdoutLong = runCli(["--version"], { cwd: repoRoot }); - const stdoutShort = runCli(["-V"], { cwd: repoRoot }); + const stdoutLong = runCli(['--version'], { cwd: repoRoot }); + const stdoutShort = runCli(['-V'], { cwd: repoRoot }); assert.equal(stdoutLong, expected); assert.equal(stdoutShort, expected); }); -test("prints verbose diagnostics with -v", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('prints verbose diagnostics with -v', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { source: path.relative(projectRoot, sourceRoot), global: true, - output: "AGENTS.md" + output: 'AGENTS.md', }, null, - 2 - ) + 2, + ), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only\n1"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1'); - const stdout = runCli(["-v", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['-v', '--root', projectRoot], { cwd: repoRoot }); assert.match(stdout, /Verbose:/u); assert.match(stdout, /Ruleset files:/u); assert.match(stdout, /Composed AGENTS\.md:/u); @@ -148,50 +148,56 @@ test("prints verbose diagnostics with -v", () => { } }); - - -test("composes AGENTS.md using local source and extra rules", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('composes AGENTS.md using local source and extra rules', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { source: path.relative(projectRoot, sourceRoot), global: true, - output: "AGENTS.md", - domains: ["node"], - extra: ["agent-rules-local/custom.md"] + output: 'AGENTS.md', + domains: ['node'], + extra: ['agent-rules-local/custom.md'], }, null, - 2 - ) + 2, + ), ); - writeFile(path.join(projectRoot, "agent-rules-local", "custom.md"), "# Custom\nlocal"); + writeFile(path.join(projectRoot, 'agent-rules-local', 'custom.md'), '# Custom\nlocal'); - writeFile(path.join(rulesRoot, "global", "a.md"), "# Global A\nA"); - writeFile(path.join(rulesRoot, "global", "b.md"), "# Global B\nB"); - writeFile(path.join(rulesRoot, "domains", "node", "c.md"), "# Domain C\nC"); + writeFile(path.join(rulesRoot, 'global', 'a.md'), '# Global A\nA'); + writeFile(path.join(rulesRoot, 'global', 'b.md'), '# Global B\nB'); + writeFile(path.join(rulesRoot, 'domains', 'node', 'c.md'), '# Domain C\nC'); - const stdout = runCli(["--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['--root', projectRoot], { cwd: repoRoot }); assert.match(stdout, /Composed AGENTS\.md:/u); - const outputPath = path.join(projectRoot, "AGENTS.md"); - const output = fs.readFileSync(outputPath, "utf8"); + const outputPath = path.join(projectRoot, 'AGENTS.md'); + const output = fs.readFileSync(outputPath, 'utf8'); const expected = withToolRules( [ - formatRuleBlock(path.join(rulesRoot, "global", "a.md"), "# Global A\nA", projectRoot), - formatRuleBlock(path.join(rulesRoot, "global", "b.md"), "# Global B\nB", projectRoot), - formatRuleBlock(path.join(rulesRoot, "domains", "node", "c.md"), "# Domain C\nC", projectRoot), - formatRuleBlock(path.join(projectRoot, "agent-rules-local", "custom.md"), "# Custom\nlocal", projectRoot) - ].join("\n\n") + "\n" + formatRuleBlock(path.join(rulesRoot, 'global', 'a.md'), '# Global A\nA', projectRoot), + formatRuleBlock(path.join(rulesRoot, 'global', 'b.md'), '# Global B\nB', projectRoot), + formatRuleBlock( + path.join(rulesRoot, 'domains', 'node', 'c.md'), + '# Domain C\nC', + projectRoot, + ), + formatRuleBlock( + path.join(projectRoot, 'agent-rules-local', 'custom.md'), + '# Custom\nlocal', + projectRoot, + ), + ].join('\n\n') + '\n', ); assert.equal(output, expected); @@ -200,481 +206,508 @@ test("composes AGENTS.md using local source and extra rules", () => { } }); -test("fails fast when ruleset is missing", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('fails fast when ruleset is missing', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { assert.throws( - () => runCli(["--root", tempRoot], { cwd: repoRoot }), - /Missing ruleset file: .*agent-ruleset\.json/u + () => runCli(['--root', tempRoot], { cwd: repoRoot }), + /Missing ruleset file: .*agent-ruleset\.json/u, ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("does not search for rulesets in subdirectories", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('does not search for rulesets in subdirectories', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const nestedRoot = path.join(tempRoot, "nested"); + const nestedRoot = path.join(tempRoot, 'nested'); writeFile( - path.join(nestedRoot, "agent-ruleset.json"), + path.join(nestedRoot, 'agent-ruleset.json'), JSON.stringify( { - source: path.relative(nestedRoot, path.join(tempRoot, "rules-source")), - output: "AGENTS.md" + source: path.relative(nestedRoot, path.join(tempRoot, 'rules-source')), + output: 'AGENTS.md', }, null, - 2 - ) + 2, + ), ); assert.throws( - () => runCli(["--root", tempRoot], { cwd: repoRoot }), - /Missing ruleset file: .*agent-ruleset\.json/u + () => runCli(['--root', tempRoot], { cwd: repoRoot }), + /Missing ruleset file: .*agent-ruleset\.json/u, ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("supports global=false to skip global rules", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('supports global=false to skip global rules', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { source: path.relative(projectRoot, sourceRoot), global: false, - domains: ["node"], - output: "AGENTS.md" + domains: ['node'], + output: 'AGENTS.md', }, null, - 2 - ) + 2, + ), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only Global\n1"); - writeFile(path.join(rulesRoot, "domains", "node", "domain.md"), "# Domain\nD"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only Global\n1'); + writeFile(path.join(rulesRoot, 'domains', 'node', 'domain.md'), '# Domain\nD'); - runCli(["--root", projectRoot], { cwd: repoRoot }); + runCli(['--root', projectRoot], { cwd: repoRoot }); - const output = fs.readFileSync(path.join(projectRoot, "AGENTS.md"), "utf8"); + const output = fs.readFileSync(path.join(projectRoot, 'AGENTS.md'), 'utf8'); assert.equal( output, - withToolRules(formatRuleBlock(path.join(rulesRoot, "domains", "node", "domain.md"), "# Domain\nD", projectRoot) + "\n") + withToolRules( + formatRuleBlock( + path.join(rulesRoot, 'domains', 'node', 'domain.md'), + '# Domain\nD', + projectRoot, + ) + '\n', + ), ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("supports source path pointing to a rules directory", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('supports source path pointing to a rules directory', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const rulesRoot = path.join(tempRoot, "rules-root", "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const rulesRoot = path.join(tempRoot, 'rules-root', 'rules'); const rulesRootRelative = path.relative(projectRoot, rulesRoot); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { - output: "AGENTS.md", + output: 'AGENTS.md', source: rulesRootRelative, - global: true + global: true, }, null, - 2 - ) + 2, + ), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Ruleset Root\nruleset"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Ruleset Root\nruleset'); - runCli(["--root", projectRoot], { cwd: repoRoot }); + runCli(['--root', projectRoot], { cwd: repoRoot }); - const output = fs.readFileSync(path.join(projectRoot, "AGENTS.md"), "utf8"); + const output = fs.readFileSync(path.join(projectRoot, 'AGENTS.md'), 'utf8'); assert.equal( output, - withToolRules(formatRuleBlock(path.join(rulesRoot, "global", "only.md"), "# Ruleset Root\nruleset", projectRoot) + "\n") + withToolRules( + formatRuleBlock( + path.join(rulesRoot, 'global', 'only.md'), + '# Ruleset Root\nruleset', + projectRoot, + ) + '\n', + ), ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("accepts rulesets with comments", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('accepts rulesets with comments', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); - const sourceRelative = path.relative(projectRoot, sourceRoot).replace(/\\/g, "/"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); + const sourceRelative = path.relative(projectRoot, sourceRoot).replace(/\\/g, '/'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), `{ // rules source "source": "${sourceRelative}", "output": "AGENTS.md" } -` +`, ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only\n1"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1'); - runCli(["--root", projectRoot], { cwd: repoRoot }); + runCli(['--root', projectRoot], { cwd: repoRoot }); - const output = fs.readFileSync(path.join(projectRoot, "AGENTS.md"), "utf8"); + const output = fs.readFileSync(path.join(projectRoot, 'AGENTS.md'), 'utf8'); assert.equal( output, - withToolRules(formatRuleBlock(path.join(rulesRoot, "global", "only.md"), "# Only\n1", projectRoot) + "\n") + withToolRules( + formatRuleBlock(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1', projectRoot) + '\n', + ), ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("rejects invalid ruleset shapes with a clear error", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('rejects invalid ruleset shapes with a clear error', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); + const projectRoot = path.join(tempRoot, 'project'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { - source: "", - output: "", - domains: ["node", ""], - extra: ["valid.md", ""] + source: '', + output: '', + domains: ['node', ''], + extra: ['valid.md', ''], }, null, - 2 - ) + 2, + ), ); assert.throws( - () => runCli(["--root", projectRoot], { cwd: repoRoot }), - /Invalid ruleset schema .*source|Invalid ruleset schema .*\/output/u + () => runCli(['--root', projectRoot], { cwd: repoRoot }), + /Invalid ruleset schema .*source|Invalid ruleset schema .*\/output/u, ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("clears cached rules with --clear-cache", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('clears cached rules with --clear-cache', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const fakeHome = path.join(tempRoot, "home"); - const cacheRoot = path.join(fakeHome, ".agentsmd", "cache", "owner", "repo", "ref"); + const fakeHome = path.join(tempRoot, 'home'); + const cacheRoot = path.join(fakeHome, '.agentsmd', 'cache', 'owner', 'repo', 'ref'); fs.mkdirSync(cacheRoot, { recursive: true }); - fs.writeFileSync(path.join(cacheRoot, "marker.txt"), "cache", "utf8"); + fs.writeFileSync(path.join(cacheRoot, 'marker.txt'), 'cache', 'utf8'); - const stdout = runCli(["--clear-cache"], { + const stdout = runCli(['--clear-cache'], { cwd: repoRoot, - env: { USERPROFILE: fakeHome, HOME: fakeHome } + env: { USERPROFILE: fakeHome, HOME: fakeHome }, }); assert.match(stdout, /Cache cleared\./u); - assert.equal(fs.existsSync(path.join(fakeHome, ".agentsmd", "cache")), false); + assert.equal(fs.existsSync(path.join(fakeHome, '.agentsmd', 'cache')), false); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("edit-rules uses local source path as workspace", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('edit-rules uses local source path as workspace', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { source: path.relative(projectRoot, sourceRoot), - output: "AGENTS.md" + output: 'AGENTS.md', }, null, - 2 - ) + 2, + ), ); - fs.mkdirSync(path.join(sourceRoot, "rules", "global"), { recursive: true }); + fs.mkdirSync(path.join(sourceRoot, 'rules', 'global'), { recursive: true }); - const stdout = runCli(["edit-rules", "--root", projectRoot], { cwd: repoRoot }); - assert.match(stdout, new RegExp(`Rules workspace: ${sourceRoot.replace(/\\/g, "\\\\")}`, "u")); + const stdout = runCli(['edit-rules', '--root', projectRoot], { cwd: repoRoot }); + assert.match(stdout, new RegExp(`Rules workspace: ${sourceRoot.replace(/\\/g, '\\\\')}`, 'u')); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("apply-rules composes with refresh for local source", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('apply-rules composes with refresh for local source', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { source: path.relative(projectRoot, sourceRoot), - output: "AGENTS.md" + output: 'AGENTS.md', }, null, - 2 - ) + 2, + ), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only\n1"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1'); - runCli(["apply-rules", "--root", projectRoot], { cwd: repoRoot }); + runCli(['apply-rules', '--root', projectRoot], { cwd: repoRoot }); - const output = fs.readFileSync(path.join(projectRoot, "AGENTS.md"), "utf8"); + const output = fs.readFileSync(path.join(projectRoot, 'AGENTS.md'), 'utf8'); assert.equal( output, - withToolRules(formatRuleBlock(path.join(rulesRoot, "global", "only.md"), "# Only\n1", projectRoot) + "\n") + withToolRules( + formatRuleBlock(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1', projectRoot) + '\n', + ), ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("apply-rules supports --json output", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('apply-rules supports --json output', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { source: path.relative(projectRoot, sourceRoot), - output: "AGENTS.md" + output: 'AGENTS.md', }, null, - 2 - ) + 2, + ), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only\n1"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1'); - const stdout = runCli(["apply-rules", "--json", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['apply-rules', '--json', '--root', projectRoot], { cwd: repoRoot }); const result = JSON.parse(stdout); - assert.deepEqual(result, { composed: ["AGENTS.md"], dryRun: false }); + assert.deepEqual(result, { composed: ['AGENTS.md'], dryRun: false }); - const output = fs.readFileSync(path.join(projectRoot, "AGENTS.md"), "utf8"); + const output = fs.readFileSync(path.join(projectRoot, 'AGENTS.md'), 'utf8'); assert.equal( output, - withToolRules(formatRuleBlock(path.join(rulesRoot, "global", "only.md"), "# Only\n1", projectRoot) + "\n") + withToolRules( + formatRuleBlock(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1', projectRoot) + '\n', + ), ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("apply-rules respects --dry-run with --json", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('apply-rules respects --dry-run with --json', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), + path.join(projectRoot, 'agent-ruleset.json'), JSON.stringify( { source: path.relative(projectRoot, sourceRoot), - output: "AGENTS.md" + output: 'AGENTS.md', }, null, - 2 - ) + 2, + ), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only\n1"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1'); - const stdout = runCli(["apply-rules", "--dry-run", "--json", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['apply-rules', '--dry-run', '--json', '--root', projectRoot], { + cwd: repoRoot, + }); assert.doesNotMatch(stdout, /Composed AGENTS\.md:/u); const result = JSON.parse(stdout); - assert.deepEqual(result, { composed: ["AGENTS.md"], dryRun: true }); - assert.equal(fs.existsSync(path.join(projectRoot, "AGENTS.md")), false); + assert.deepEqual(result, { composed: ['AGENTS.md'], dryRun: true }); + assert.equal(fs.existsSync(path.join(projectRoot, 'AGENTS.md')), false); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("init creates a default ruleset with comments", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('init creates a default ruleset with comments', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); + const projectRoot = path.join(tempRoot, 'project'); - const stdout = runCli(["init", "--yes", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['init', '--yes', '--root', projectRoot], { cwd: repoRoot }); assert.match(stdout, /Initialized ruleset:/u); - const rulesetPath = path.join(projectRoot, "agent-ruleset.json"); - const rulesetRaw = fs.readFileSync(rulesetPath, "utf8"); + const rulesetPath = path.join(projectRoot, 'agent-ruleset.json'); + const rulesetRaw = fs.readFileSync(rulesetPath, 'utf8'); assert.match(rulesetRaw, /\/\/ Rules source/u); assert.equal(/"global"\s*:/u.test(rulesetRaw), false); const ruleset = JSON.parse(stripJsonComments(rulesetRaw)); assert.deepEqual(ruleset, { - source: "github:owner/repo@latest", + source: 'github:owner/repo@latest', domains: [], extra: [], - output: "AGENTS.md" + output: 'AGENTS.md', }); - const localRulesPath = path.join(projectRoot, "agent-rules-local", "custom.md"); + const localRulesPath = path.join(projectRoot, 'agent-rules-local', 'custom.md'); assert.equal(fs.existsSync(localRulesPath), false); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("supports --quiet and -q to suppress output", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('supports --quiet and -q to suppress output', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), - JSON.stringify({ source: path.relative(projectRoot, sourceRoot) }, null, 2) + path.join(projectRoot, 'agent-ruleset.json'), + JSON.stringify({ source: path.relative(projectRoot, sourceRoot) }, null, 2), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only\n1"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1'); - const stdoutLong = runCli(["--quiet", "--root", projectRoot], { cwd: repoRoot }); - assert.equal(stdoutLong, ""); + const stdoutLong = runCli(['--quiet', '--root', projectRoot], { cwd: repoRoot }); + assert.equal(stdoutLong, ''); - const stdoutShort = runCli(["-q", "--root", projectRoot], { cwd: repoRoot }); - assert.equal(stdoutShort, ""); + const stdoutShort = runCli(['-q', '--root', projectRoot], { cwd: repoRoot }); + assert.equal(stdoutShort, ''); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("supports --json for machine-readable output", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('supports --json for machine-readable output', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), - JSON.stringify({ source: path.relative(projectRoot, sourceRoot) }, null, 2) + path.join(projectRoot, 'agent-ruleset.json'), + JSON.stringify({ source: path.relative(projectRoot, sourceRoot) }, null, 2), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only\n1"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1'); - const stdout = runCli(["--json", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['--json', '--root', projectRoot], { cwd: repoRoot }); const result = JSON.parse(stdout); - assert.deepEqual(result, { composed: ["AGENTS.md"], dryRun: false }); + assert.deepEqual(result, { composed: ['AGENTS.md'], dryRun: false }); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("supports --dry-run for compose", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('supports --dry-run for compose', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); - const rulesRoot = path.join(sourceRoot, "rules"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); + const rulesRoot = path.join(sourceRoot, 'rules'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), - JSON.stringify({ source: path.relative(projectRoot, sourceRoot) }, null, 2) + path.join(projectRoot, 'agent-ruleset.json'), + JSON.stringify({ source: path.relative(projectRoot, sourceRoot) }, null, 2), ); - writeFile(path.join(rulesRoot, "global", "only.md"), "# Only\n1"); + writeFile(path.join(rulesRoot, 'global', 'only.md'), '# Only\n1'); - const stdout = runCli(["--dry-run", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['--dry-run', '--root', projectRoot], { cwd: repoRoot }); assert.match(stdout, /Composed AGENTS\.md:/u); - assert.equal(fs.existsSync(path.join(projectRoot, "AGENTS.md")), false); + assert.equal(fs.existsSync(path.join(projectRoot, 'AGENTS.md')), false); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("init --dry-run does not write files", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('init --dry-run does not write files', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); + const projectRoot = path.join(tempRoot, 'project'); - const stdout = runCli(["init", "--dry-run", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['init', '--dry-run', '--root', projectRoot], { cwd: repoRoot }); assert.match(stdout, /Dry run/u); - assert.equal(fs.existsSync(path.join(projectRoot, "agent-ruleset.json")), false); + assert.equal(fs.existsSync(path.join(projectRoot, 'agent-ruleset.json')), false); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("init refuses to overwrite an existing ruleset without --force", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('init refuses to overwrite an existing ruleset without --force', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - writeFile(path.join(projectRoot, "agent-ruleset.json"), JSON.stringify({ source: "local" }, null, 2)); + const projectRoot = path.join(tempRoot, 'project'); + writeFile( + path.join(projectRoot, 'agent-ruleset.json'), + JSON.stringify({ source: 'local' }, null, 2), + ); assert.throws( - () => runCli(["init", "--yes", "--root", projectRoot], { cwd: repoRoot }), - /Ruleset already exists/u + () => runCli(['init', '--yes', '--root', projectRoot], { cwd: repoRoot }), + /Ruleset already exists/u, ); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("init respects --quiet and --json", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('init respects --quiet and --json', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); + const projectRoot = path.join(tempRoot, 'project'); // Case 1: --quiet suppresses standard output - const stdoutQuiet = runCli(["init", "--yes", "--quiet", "--root", projectRoot], { cwd: repoRoot }); - assert.equal(stdoutQuiet, ""); - assert.equal(fs.existsSync(path.join(projectRoot, "agent-ruleset.json")), true); + const stdoutQuiet = runCli(['init', '--yes', '--quiet', '--root', projectRoot], { + cwd: repoRoot, + }); + assert.equal(stdoutQuiet, ''); + assert.equal(fs.existsSync(path.join(projectRoot, 'agent-ruleset.json')), true); // Clean up for next case fs.rmSync(projectRoot, { recursive: true, force: true }); // Case 2: --json outputs JSON and suppresses standard output - const stdoutJson = runCli(["init", "--yes", "--json", "--root", projectRoot], { cwd: repoRoot }); + const stdoutJson = runCli(['init', '--yes', '--json', '--root', projectRoot], { + cwd: repoRoot, + }); const result = JSON.parse(stdoutJson); assert.equal(result.dryRun, false); - assert.deepEqual(result.initialized, ["agent-ruleset.json"]); + assert.deepEqual(result.initialized, ['agent-ruleset.json']); assert.deepEqual(result.localRules, []); assert.deepEqual(result.composed, []); - assert.equal(fs.existsSync(path.join(projectRoot, "agent-ruleset.json")), true); + assert.equal(fs.existsSync(path.join(projectRoot, 'agent-ruleset.json')), true); // Check that there is no other output mixed with JSON assert.doesNotMatch(stdoutJson, /Initialized ruleset:/u); @@ -683,57 +716,61 @@ test("init respects --quiet and --json", () => { fs.rmSync(projectRoot, { recursive: true, force: true }); // Case 3: --json takes precedence over --quiet - const stdoutJsonQuiet = runCli(["init", "--yes", "--quiet", "--json", "--root", projectRoot], { cwd: repoRoot }); + const stdoutJsonQuiet = runCli(['init', '--yes', '--quiet', '--json', '--root', projectRoot], { + cwd: repoRoot, + }); const resultJsonQuiet = JSON.parse(stdoutJsonQuiet); assert.equal(resultJsonQuiet.dryRun, false); - assert.deepEqual(resultJsonQuiet.initialized, ["agent-ruleset.json"]); + assert.deepEqual(resultJsonQuiet.initialized, ['agent-ruleset.json']); assert.deepEqual(resultJsonQuiet.localRules, []); assert.deepEqual(resultJsonQuiet.composed, []); - assert.equal(fs.existsSync(path.join(projectRoot, "agent-ruleset.json")), true); + assert.equal(fs.existsSync(path.join(projectRoot, 'agent-ruleset.json')), true); assert.doesNotMatch(stdoutJsonQuiet, /Initialized ruleset:/u); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("compose respects --dry-run with --json", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('compose respects --dry-run with --json', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); - const sourceRoot = path.join(tempRoot, "rules-source"); + const projectRoot = path.join(tempRoot, 'project'); + const sourceRoot = path.join(tempRoot, 'rules-source'); writeFile( - path.join(projectRoot, "agent-ruleset.json"), - JSON.stringify({ source: path.relative(projectRoot, sourceRoot) }, null, 2) + path.join(projectRoot, 'agent-ruleset.json'), + JSON.stringify({ source: path.relative(projectRoot, sourceRoot) }, null, 2), ); - fs.mkdirSync(path.join(sourceRoot, "rules", "global"), { recursive: true }); + fs.mkdirSync(path.join(sourceRoot, 'rules', 'global'), { recursive: true }); - const stdout = runCli(["--dry-run", "--json", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['--dry-run', '--json', '--root', projectRoot], { cwd: repoRoot }); const result = JSON.parse(stdout); assert.equal(result.dryRun, true); - assert.deepEqual(result.composed, ["AGENTS.md"]); - assert.equal(fs.existsSync(path.join(projectRoot, "AGENTS.md")), false); + assert.deepEqual(result.composed, ['AGENTS.md']); + assert.equal(fs.existsSync(path.join(projectRoot, 'AGENTS.md')), false); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } }); -test("init --dry-run with --json outputs plan", () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "compose-agentsmd-")); +test('init --dry-run with --json outputs plan', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'compose-agentsmd-')); try { - const projectRoot = path.join(tempRoot, "project"); + const projectRoot = path.join(tempRoot, 'project'); - const stdout = runCli(["init", "--dry-run", "--json", "--root", projectRoot], { cwd: repoRoot }); + const stdout = runCli(['init', '--dry-run', '--json', '--root', projectRoot], { + cwd: repoRoot, + }); const result = JSON.parse(stdout); assert.equal(result.dryRun, true); assert.equal(Array.isArray(result.plan), true); - assert.deepEqual(result.plan, [{ action: "create", path: "agent-ruleset.json" }]); - assert.equal(fs.existsSync(path.join(projectRoot, "agent-ruleset.json")), false); + assert.deepEqual(result.plan, [{ action: 'create', path: 'agent-ruleset.json' }]); + assert.equal(fs.existsSync(path.join(projectRoot, 'agent-ruleset.json')), false); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } diff --git a/tools/compose-agents.js b/tools/compose-agents.js index 35b1bfd..a8073f6 100644 --- a/tools/compose-agents.js +++ b/tools/compose-agents.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -import "../dist/compose-agents.js"; +import '../dist/compose-agents.js'; diff --git a/tools/tool-rules.md b/tools/tool-rules.md index 46fde4e..8f624fd 100644 --- a/tools/tool-rules.md +++ b/tools/tool-rules.md @@ -1,4 +1,5 @@ # Tool Rules (compose-agentsmd) + - Before starting any work, run `compose-agentsmd` from the project root. - If `compose-agentsmd` is not available, install it via npm: `npm install -g compose-agentsmd`. - To update shared rules, run `compose-agentsmd edit-rules`, edit the workspace rules, then run `compose-agentsmd apply-rules`. From e8634772f69d51aade781ca38adec9328e7bc2d4 Mon Sep 17 00:00:00 2001 From: metyatech Date: Fri, 13 Feb 2026 20:17:46 +0900 Subject: [PATCH 2/6] fix: address PR review comments Refs #3904490781 --- .github/ISSUE_TEMPLATE/bug_report.md | 6 +++--- .github/pull_request_template.md | 4 ++-- CONTRIBUTING.md | 4 ++-- README.md | 2 +- package-lock.json | 2 -- package.json | 4 +--- 6 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d3bf3c5..fe9954e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,9 +10,9 @@ A clear and concise description of the problem. ## Steps to reproduce -1. -2. -3. +1. +2. +3. ## Expected behavior diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 913891c..29d4122 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,11 +4,11 @@ Describe the change and why it is needed. ## Changes -- +- ## Testing -- [ ] npm run lint +- [ ] npm run check - [ ] npm test - [ ] npm pack --dry-run (if publishing-related) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c4503b..88c7b94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,11 +3,11 @@ ## Development setup 1. Install dependencies. -2. Run lint and tests before submitting changes. +2. Run checks and tests before submitting changes. ```sh npm ci -npm run lint +npm run check npm test ``` diff --git a/README.md b/README.md index a6fd247..1119dc2 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Remote sources are cached under `~/.agentsmd/cache////`. Use ` ```sh npm install -npm run lint +npm run check npm run build npm test ``` diff --git a/package-lock.json b/package-lock.json index 9dddb77..f075237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,6 @@ }, "devDependencies": { "@types/node": "^25.1.0", - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "globals": "^17.3.0", diff --git a/package.json b/package.json index 80fa1d8..f4f7851 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "lint": "eslint .", "format": "prettier --write .", "typecheck": "tsc -p tsconfig.json --noEmit", - "check": "npm run lint && npm run typecheck && prettier --check . && npm audit", + "check": "npm run lint && npm run typecheck && prettier --check . && npm audit --audit-level=high --omit=dev", "prepare": "npm run build", "prepack": "npm run build", "test": "npm run build && node --test", @@ -48,8 +48,6 @@ }, "devDependencies": { "@types/node": "^25.1.0", - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "globals": "^17.3.0", From 4ee1196e5e79bf56441e84c27856d5261879672f Mon Sep 17 00:00:00 2001 From: metyatech Date: Sat, 14 Feb 2026 17:35:03 +0900 Subject: [PATCH 3/6] chore: fix remaining PR review comments. Refs #3904490781 --- .github/pull_request_template.md | 2 ++ CONTRIBUTING.md | 2 ++ README.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 29d4122..09ae1cc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,6 +9,8 @@ Describe the change and why it is needed. ## Testing - [ ] npm run check +- [ ] npm run format +- [ ] npm run typecheck - [ ] npm test - [ ] npm pack --dry-run (if publishing-related) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88c7b94..300203e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,8 @@ ```sh npm ci npm run check +npm run format +npm run typecheck npm test ``` diff --git a/README.md b/README.md index 1119dc2..c48b988 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,8 @@ Remote sources are cached under `~/.agentsmd/cache////`. Use ` ```sh npm install npm run check +npm run format +npm run typecheck npm run build npm test ``` From f950d289158b0d5ac4a346daa8fc346aed206b10 Mon Sep 17 00:00:00 2001 From: metyatech Date: Tue, 17 Feb 2026 09:27:44 +0900 Subject: [PATCH 4/6] chore: address PR review comments and synchronize AGENTS.md. Refs #5 --- AGENTS.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++------- README.md | 3 +- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 72481f0..d0c58ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,35 @@ - Also provide a short, copy-pasteable command the user can run to view the diff in the same format. Use absolute paths so it works regardless of the current working directory, and scope it to the changed rule files. - If a diff is provided, a separate detailed summary is not required. If a diff is not possible, include a detailed summary of what changed (added/removed/modified items). +Source: github:metyatech/agent-rules@HEAD/rules/global/00-delivery-hard-gates.md + +# Delivery hard gates + +These are non-negotiable completion gates for any state-changing work and for any response that claims "done", "fixed", "working", or "passing". + +## Acceptance criteria (AC) + +- Before state-changing work, list Acceptance Criteria (AC) as binary, testable statements. +- For read-only tasks, AC may be deliverables/questions answered; keep them checkable. +- If AC are ambiguous or not testable, ask blocking questions before proceeding. + +## Evidence and verification + +- For each AC, define verification evidence (automated test preferred; otherwise a deterministic manual procedure). +- Maintain an explicit mapping: `AC -> evidence (tests/commands/manual steps)`. +- For code or runtime-behavior changes, automated tests are required unless the requester explicitly approves skipping. +- Bugfixes MUST include a regression test that fails before the fix and passes after. +- Run the repo's full verification suite (lint/format/typecheck/test/build) using a single repo-standard `verify` command when available; if missing, add it. +- Enforce verification locally via commit-time hooks (pre-commit or repo-native) and in CI; skipping requires explicit requester approval. +- For non-code changes, run the relevant subset and justify. +- If required checks cannot be run, stop and ask for explicit approval to proceed with partial verification, and provide an exact manual verification plan. + +## Final response (MUST include) + +- AC list. +- `AC -> evidence` mapping with outcomes (PASS/FAIL/NOT RUN/N/A) and brief notes where needed. +- The exact verification commands executed and their outcomes. + Source: github:metyatech/agent-rules@HEAD/rules/global/agent-rules-composition.md # Rule composition and maintenance @@ -47,6 +76,8 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/autonomous-operations.md - Do not preserve backward compatibility unless explicitly requested; avoid legacy aliases and compatibility shims by default. - When work reveals rule gaps, redundancy, or misplacement, proactively update rule modules/rulesets (including moves/renames) and regenerate AGENTS.md without waiting for explicit user requests. - After each task, run a brief retrospective; if you notice avoidable mistakes, missing checks, or recurring back-and-forth, encode the fix as a rule update and regenerate AGENTS.md. +- If you state a persistent workflow change (e.g., "from now on", "I'll always"), immediately propose the corresponding rule update and request approval in the same task; do not leave it as an unrecorded promise. +- Because session memory resets between tasks, treat rule files as persistent memory; when any issue or avoidable mistake occurs, update rules in the same task to prevent recurrence. - Treat these rules as the source of truth; do not override them with repository conventions. If a repo conflicts, update the repo to comply or update the rules to encode the exception; do not make undocumented exceptions. - When something is unclear, investigate to resolve it; do not proceed with unresolved material uncertainty. If still unclear, ask and include what you checked. - Do not proceed based on assumptions or guesses without explicit user approval; hypotheses may be discussed but must not drive action. @@ -67,10 +98,12 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/command-execution.md - Keep changes scoped to affected repositories; when shared modules change, update consumers and verify at least one. - If no branch is specified, work on the current branch; direct commits to main/master are allowed. - After addressing PR review feedback, resolve the corresponding review thread(s) before concluding; if you lack permission, state it explicitly. -- After pushing fixes for PR review feedback, always re-request review from the same reviewer(s) when possible; if there are no current reviewers, ask who should review. -- When Codex and/or Copilot review bots are configured for the repo, always trigger a re-review after pushing fixes. -- For Codex re-review: comment `@codex review` on the PR. -- For Copilot re-review: use `gh api` to remove+re-request the bot reviewer `copilot-pull-request-reviewer[bot]` (do not rely on `gh pr edit --add-reviewer Copilot`). +- After pushing fixes for PR review feedback, re-request review only from reviewer(s) who posted the addressed feedback in the current round. +- Do not re-request review from reviewers (including AI reviewers) who did not post addressed feedback, or who already indicated no actionable issues. +- If no applicable reviewer remains, ask who should review next. +- When Codex and/or Copilot review bots are configured for the repo, trigger re-review only for the bot(s) that posted addressed feedback. +- For Codex re-review (only when applicable): comment `@codex review` on the PR. +- For Copilot re-review (only when applicable): use `gh api` to remove+re-request the bot reviewer `copilot-pull-request-reviewer[bot]` (do not rely on `gh pr edit --add-reviewer Copilot`). - Remove: `gh api --method DELETE /repos/{owner}/{repo}/pulls/{pr}/requested_reviewers -f "reviewers[]=copilot-pull-request-reviewer[bot]"` - Add: `gh api --method POST /repos/{owner}/{repo}/pulls/{pr}/requested_reviewers -f "reviewers[]=copilot-pull-request-reviewer[bot]"` - After completing a PR, merge it, sync the target branch, and delete the PR branch locally and remotely. @@ -81,6 +114,10 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/implementation-and-coding - Prefer official/standard approaches recommended by the framework or tooling. - Prefer well-maintained external dependencies; build in-house only when no suitable option exists. +- Prefer third-party tools/services over custom implementations when they can meet the requirements; prefer free options (OSS/free-tier) when feasible and call out limitations/tradeoffs. +- PowerShell: `\` is a literal character (not an escape). Do not cargo-cult `\\` escaping patterns from other languages; validate APIs that require names like `Local\...` (e.g., named mutexes). +- PowerShell: avoid assigning to or shadowing automatic/read-only variables (e.g., `$args`, `$PID`); use different names for locals. +- PowerShell: when invoking PowerShell from PowerShell, avoid double-quoted `-Command` strings that allow the outer shell to expand `$...`; prefer `-File`, single quotes, or here-strings to control expansion. - If functionality appears reusable, assess reuse first and propose a shared module/repo; prefer remote dependencies (never local filesystem paths). - Maintainability > testability > extensibility > readability. - Single responsibility; keep modules narrowly scoped and prefer composition over inheritance. @@ -88,7 +125,11 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/implementation-and-coding - Avoid deep nesting; use guard clauses and small functions. - Use clear, intention-revealing naming; avoid "Utils" dumping grounds. - Prefer configuration/constants over hardcoding; consolidate change points. +- For GUI changes, prioritize ergonomics and discoverability so first-time users can complete core flows without external documents. +- Every user-facing GUI component (inputs, actions, status indicators, lists, and dialog controls) must include an in-app explanation (for example tooltip, context help panel, or equivalent). +- Do not rely on README-only guidance for GUI operation; critical usage guidance must be available inside the GUI itself. - Keep everything DRY across code, specs, docs, tests, configs, and scripts; proactively refactor repeated procedures into shared configs/scripts with small, local overrides. +- Persist durable runtime/domain data in a database with a fully normalized schema (3NF/BCNF target): store each fact once with keys/constraints, and compute derived statuses/views at read time instead of duplicating them. - Fix root causes; remove obsolete/unused code, branches, comments, and helpers. - Externalize large embedded strings/templates/rules when possible. - Do not commit build artifacts (follow the repo's .gitignore). @@ -110,6 +151,17 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/linting-formatting-and-st - Do not disable rules globally; keep suppressions narrow, justified, and time-bounded. - Pin tool versions (lockfiles/manifests) for reproducible CI. +## Design and visual accessibility automation + +- For any design/UI styling change in any project, enforce automated visual accessibility checks as part of the repo-standard `verify` command and CI. +- Do not rely on per-page/manual test maintenance; use route discovery (for example sitemap, generated route lists, or framework route manifests) so newly added pages are automatically included. +- Validate both light and dark themes when theme switching is supported. +- Validate at least default, hover, and focus states for interactive elements. +- Enforce non-text boundary contrast checks across all visible UI elements that present boundaries (including interactive controls and container-like elements), not only predefined component classes. +- Do not hardcode a narrow selector allowlist for boundary checks; use broad DOM discovery with only minimal technical exclusions (for example hidden/zero-size/non-rendered nodes). +- Fail CI on violations; do not silently ignore design regressions. +- If temporary exclusions are unavoidable, keep them narrowly scoped, documented with rationale, and remove them promptly. + ## Security baseline - Require dependency vulnerability scanning appropriate to the ecosystem (SCA) for merges. If you cannot enable it, report the limitation and get explicit user approval before proceeding without it. @@ -172,6 +224,7 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/linting-formatting-and-st - Format+lint: PSScriptAnalyzer (Invoke-Formatter + Invoke-ScriptAnalyzer). - Runtime: Set-StrictMode -Version Latest; fail fast on errors. - Tests: Pester when tests exist. +- Enforce PSScriptAnalyzer via the repo’s standard `verify` command/script when PowerShell is used; treat findings as errors. ### Shell (sh/bash) @@ -259,9 +312,12 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/planning-and-approval-gat - Running code generation/build steps that are deterministic and repo-scoped. - Running these from clean → dirty → clean is acceptable; publishing/deploying/migrating is not. - Before any other state-changing execution (e.g., writing or modifying files by hand, changing runtime behavior, or running git commands beyond status/diff/log), do all of the following: - - Restate the request as concrete acceptance criteria (explicit goal, success/failure conditions). + - Restate the request as Acceptance Criteria (AC) and verification methods, following "Delivery hard gates". - Produce a written plan (use your planning tool when available) focused on the goal, approach, and verification checkpoints (do not enumerate per-file implementation details or exact commands unless the requester asks). - Confirm the plan with the requester, ask for approval explicitly, and wait for a clear “yes” before executing. + - Once the requester has approved a plan, proceed within that plan without re-requesting approval; re-request approval only when you change or expand the plan. + - Do not treat the original task request as plan approval; approval must be an explicit response to the presented plan. +- If state-changing execution starts without the required post-plan “yes”, stop immediately, report the gate miss, add/update a prevention rule, regenerate AGENTS.md, and then restart from the approval gate. - No other exceptions: even if the user requests immediate execution (e.g., “skip planning”, “just do it”), treat that as a request to move quickly through this gate, not to bypass it. Source: github:metyatech/agent-rules@HEAD/rules/global/quality-testing-and-errors.md @@ -278,24 +334,23 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/quality-testing-and-error - For code changes, treat "relevant checks" as the repo's full lint/typecheck/test/build suite (prefer CI results). - Prefer a green baseline: if relevant checks fail before you change anything, report it and get explicit user approval before proceeding. - If you cannot reproduce/verify, do not guess a fix; request missing info or create a failing regression test. -- Always report verification: list the exact commands/steps run and their outcome; if anything is unverified, state why and how to verify. +- Follow "Delivery hard gates" for Acceptance Criteria, verification evidence, and final reporting; if anything is unverified, state why and how to verify. ## Verification -- Run the repo's full lint/typecheck/test/build checks using repo-standard commands. +- Follow "Delivery hard gates" for running and reporting verification. - If you are unsure what constitutes the full suite, run the repo's default verify/CI commands rather than guessing. - Before committing code changes, run the full suite; if a relevant check is missing and feasible to add, add it in the same change set. -- Enforce via CI: run the full suite on pull requests and on pushes to the default branch; if no CI harness exists, add one using repo-standard commands. +- Enforce via CI: run the full suite on pull requests and on pushes to the default branch, and make it a required status check for merges; if no CI harness exists, add one using repo-standard commands. - Configure required status checks on the default branch when you have permission; otherwise report the limitation. - Do not rely on smoke-only gating or scheduled-only full runs for correctness; merges must require the full suite. -- Ensure commit-time automation (pre-commit or repo-native) runs the full suite when feasible. -- If required checks cannot be run, treat it as an exception: explain why, provide exact commands/steps, and get explicit user approval before proceeding. +- Ensure commit-time automation (pre-commit or repo-native) runs the full suite and blocks commits. - Never disable checks, weaken assertions, loosen types, or add retries solely to make checks pass. ## Tests (behavior changes) - Follow test-first: add/update tests, observe failure, implement the fix, then observe pass. -- For bug fixes, add a regression test that fails before the fix at the level where the bug occurs (unit/integration/E2E). +- For bugfixes, follow "Delivery hard gates" (regression test: fail-before/pass-after). - Add/update automated tests for behavior changes and regression coverage. - Cover success, failure, boundary, invalid input, and key state transitions (including first-run/cold-start vs subsequent-run behavior when relevant); include representative concurrency/retry/recovery when relevant. - Keep tests deterministic; minimize time/random/external I/O; inject when needed. @@ -324,6 +379,18 @@ Source: github:metyatech/agent-rules@HEAD/rules/global/quality-testing-and-error - Log minimally but with diagnostic context; never log secrets or personal data. - Remove temporary debugging/instrumentation before the final patch. +Source: github:metyatech/agent-rules@HEAD/rules/global/superpowers-integration.md + +# Superpowers integration + +- If Superpowers skills are available in the current agent environment, use them to drive *how* you work (design, planning, debugging, TDD, review) instead of inventing an ad-hoc process. +- Do not duplicate Superpowers installation/usage instructions in this ruleset; follow Superpowers’ own guidance for loading/invoking skills. +- The hard gates in this ruleset still apply when using Superpowers workflows: + - Before any state-changing work: present AC + AC->evidence + a plan, then wait for an explicit “yes”. + - After changes: report AC -> evidence outcomes and the exact verification commands executed. +- When a Superpowers workflow asks for writing docs / commits / pushes, treat those as state-changing steps: include them in the plan and require explicit requester approval before doing them. +- If Superpowers skills are unavailable, proceed with these rules as the fallback. + Source: github:metyatech/agent-rules@HEAD/rules/global/user-identity-and-accounts.md # User identity and accounts diff --git a/README.md b/README.md index c48b988..3244437 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ compose-agentsmd apply-rules Ruleset files accept JSON with `//` or `/* */` comments. + ```jsonc { // Rules source. Use github:owner/repo@ref or a local path. @@ -83,7 +84,7 @@ Ruleset files accept JSON with `//` or `/* */` comments. // Additional local rule files to append. "extra": ["agent-rules-local/custom.md"], // Output file name. - "output": "AGENTS.md", + "output": "AGENTS.md" } ``` From 300e4bd1b037f72dfb96225a4fa590784c683e78 Mon Sep 17 00:00:00 2001 From: metyatech Date: Tue, 17 Feb 2026 10:19:43 +0900 Subject: [PATCH 5/6] docs: simplify development instructions. Refs #5 --- CONTRIBUTING.md | 2 -- README.md | 3 --- 2 files changed, 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 300203e..88c7b94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,8 +8,6 @@ ```sh npm ci npm run check -npm run format -npm run typecheck npm test ``` diff --git a/README.md b/README.md index 3244437..6279643 100644 --- a/README.md +++ b/README.md @@ -133,9 +133,6 @@ Remote sources are cached under `~/.agentsmd/cache////`. Use ` ```sh npm install npm run check -npm run format -npm run typecheck -npm run build npm test ``` From 488b2aaf1586e7524af4327e0af19cfbecfa304a Mon Sep 17 00:00:00 2001 From: metyatech Date: Tue, 17 Feb 2026 11:21:52 +0900 Subject: [PATCH 6/6] chore: address PR review comments and add missing local rules. Refs #5 --- .github/pull_request_template.md | 2 -- AGENTS.md | 9 +++++++++ README.md | 2 ++ agent-rules-local/operations.md | 6 ++++++ agent-ruleset.json | 6 +++++- eslint.config.js | 8 ++++++++ 6 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 agent-rules-local/operations.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 09ae1cc..29d4122 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,8 +9,6 @@ Describe the change and why it is needed. ## Testing - [ ] npm run check -- [ ] npm run format -- [ ] npm run typecheck - [ ] npm test - [ ] npm pack --dry-run (if publishing-related) diff --git a/AGENTS.md b/AGENTS.md index d0c58ff..abced54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -508,3 +508,12 @@ Source: agent-rules-local/release.md # Distribution and release - After publishing this repository, update the globally installed CLI to the latest version. + +Source: agent-rules-local/operations.md + +# Local operations rules + +- After any change that requires a process or task restart to take effect, restart the affected local components before concluding. +- Verify post-restart state and report which components were restarted and their final status. +- For agent-runner behavior changes, identify the responsible runtime component (Scheduled Task `AgentRunner` / `dist/cli.js run`, webhook, UI) and restart the correct one(s) before concluding. +- Do not claim a restart occurred unless verified by deterministic evidence (new PID, port check, and/or the latest task-run log showing the expected new behavior). diff --git a/README.md b/README.md index 6279643..3b0f44b 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,8 @@ Remote sources are cached under `~/.agentsmd/cache////`. Use ` ```sh npm install npm run check +npm run format +npm run typecheck npm test ``` diff --git a/agent-rules-local/operations.md b/agent-rules-local/operations.md new file mode 100644 index 0000000..600d742 --- /dev/null +++ b/agent-rules-local/operations.md @@ -0,0 +1,6 @@ +# Local operations rules + +- After any change that requires a process or task restart to take effect, restart the affected local components before concluding. +- Verify post-restart state and report which components were restarted and their final status. +- For agent-runner behavior changes, identify the responsible runtime component (Scheduled Task `AgentRunner` / `dist/cli.js run`, webhook, UI) and restart the correct one(s) before concluding. +- Do not claim a restart occurred unless verified by deterministic evidence (new PID, port check, and/or the latest task-run log showing the expected new behavior). diff --git a/agent-ruleset.json b/agent-ruleset.json index bbc4b87..71a6b79 100644 --- a/agent-ruleset.json +++ b/agent-ruleset.json @@ -1,6 +1,10 @@ { "source": "github:metyatech/agent-rules", "domains": ["cli", "node", "release"], - "extra": ["agent-rules-local/compose-agentsmd-local.md", "agent-rules-local/release.md"], + "extra": [ + "agent-rules-local/compose-agentsmd-local.md", + "agent-rules-local/release.md", + "agent-rules-local/operations.md" + ], "output": "AGENTS.md" } diff --git a/eslint.config.js b/eslint.config.js index e3dae94..0acd531 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -22,6 +22,14 @@ export default tseslint.config( }, }, }, + { + files: ['test/**/*.js'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, { ignores: ['dist/**', 'node_modules/**', 'AGENTS.md'], },