From 34d878591d60683ed10a3d47720b761db14f407d Mon Sep 17 00:00:00 2001 From: Mison Date: Thu, 12 Mar 2026 00:31:29 +0800 Subject: [PATCH 1/7] feat(core): unify .agents templates and global sync --- .../.shared/ui-ux-pro-max/data/charts.csv | 0 .../.shared/ui-ux-pro-max/data/colors.csv | 0 .../.shared/ui-ux-pro-max/data/icons.csv | 0 .../.shared/ui-ux-pro-max/data/landing.csv | 0 .../.shared/ui-ux-pro-max/data/products.csv | 0 .../.shared/ui-ux-pro-max/data/prompts.csv | 0 .../ui-ux-pro-max/data/react-performance.csv | 0 .../ui-ux-pro-max/data/stacks/flutter.csv | 0 .../data/stacks/html-tailwind.csv | 0 .../data/stacks/jetpack-compose.csv | 0 .../ui-ux-pro-max/data/stacks/nextjs.csv | 0 .../ui-ux-pro-max/data/stacks/nuxt-ui.csv | 0 .../ui-ux-pro-max/data/stacks/nuxtjs.csv | 0 .../data/stacks/react-native.csv | 0 .../ui-ux-pro-max/data/stacks/react.csv | 0 .../ui-ux-pro-max/data/stacks/shadcn.csv | 0 .../ui-ux-pro-max/data/stacks/svelte.csv | 0 .../ui-ux-pro-max/data/stacks/swiftui.csv | 0 .../.shared/ui-ux-pro-max/data/stacks/vue.csv | 0 .../.shared/ui-ux-pro-max/data/styles.csv | 0 .../.shared/ui-ux-pro-max/data/typography.csv | 0 .../ui-ux-pro-max/data/ui-reasoning.csv | 0 .../ui-ux-pro-max/data/ux-guidelines.csv | 0 .../ui-ux-pro-max/data/web-interface.csv | 0 .../.shared/ui-ux-pro-max/scripts/core.py | 0 .../ui-ux-pro-max/scripts/design_system.py | 0 .../.shared/ui-ux-pro-max/scripts/search.py | 0 {.agent => .agents}/ARCHITECTURE.md | 0 .../agents/backend-specialist.md | 0 .../agents/code-archaeologist.md | 0 .../agents/database-architect.md | 0 {.agent => .agents}/agents/debugger.md | 0 {.agent => .agents}/agents/devops-engineer.md | 0 .../agents/documentation-writer.md | 0 {.agent => .agents}/agents/explorer-agent.md | 0 .../agents/frontend-specialist.md | 0 {.agent => .agents}/agents/game-developer.md | 0 .../agents/mobile-developer.md | 0 {.agent => .agents}/agents/orchestrator.md | 0 .../agents/penetration-tester.md | 0 .../agents/performance-optimizer.md | 0 {.agent => .agents}/agents/product-manager.md | 0 {.agent => .agents}/agents/product-owner.md | 0 {.agent => .agents}/agents/project-planner.md | 0 .../agents/qa-automation-engineer.md | 0 .../agents/security-auditor.md | 0 {.agent => .agents}/agents/seo-specialist.md | 0 {.agent => .agents}/agents/test-engineer.md | 0 {.agent => .agents}/mcp_config.json | 0 {.agent => .agents}/rules/GEMINI.md | 0 {.agent => .agents}/scripts/auto_preview.py | 0 {.agent => .agents}/scripts/checklist.py | 0 .../scripts/session_manager.py | 0 {.agent => .agents}/scripts/verify_all.py | 0 .../skills/api-patterns/SKILL.md | 0 .../skills/api-patterns/api-style.md | 0 .../skills/api-patterns/auth.md | 0 .../skills/api-patterns/documentation.md | 0 .../skills/api-patterns/graphql.md | 0 .../skills/api-patterns/rate-limiting.md | 0 .../skills/api-patterns/response.md | 0 .../skills/api-patterns/rest.md | 0 .../api-patterns/scripts/api_validator.py | 0 .../skills/api-patterns/security-testing.md | 0 .../skills/api-patterns/trpc.md | 0 .../skills/api-patterns/versioning.md | 0 .../skills/app-builder/SKILL.md | 0 .../skills/app-builder/agent-coordination.md | 0 .../skills/app-builder/feature-building.md | 0 .../skills/app-builder/project-detection.md | 0 .../skills/app-builder/scaffolding.md | 0 .../skills/app-builder/tech-stack.md | 0 .../skills/app-builder/templates/SKILL.md | 0 .../templates/astro-static/TEMPLATE.md | 0 .../templates/chrome-extension/TEMPLATE.md | 0 .../templates/cli-tool/TEMPLATE.md | 0 .../templates/electron-desktop/TEMPLATE.md | 0 .../templates/express-api/TEMPLATE.md | 0 .../templates/flutter-app/TEMPLATE.md | 0 .../templates/monorepo-turborepo/TEMPLATE.md | 0 .../templates/nextjs-fullstack/TEMPLATE.md | 0 .../templates/nextjs-saas/TEMPLATE.md | 0 .../templates/nextjs-static/TEMPLATE.md | 0 .../templates/nuxt-app/TEMPLATE.md | 0 .../templates/python-fastapi/TEMPLATE.md | 0 .../templates/react-native-app/TEMPLATE.md | 0 .../skills/architecture/SKILL.md | 0 .../skills/architecture/context-discovery.md | 0 .../skills/architecture/examples.md | 0 .../skills/architecture/pattern-selection.md | 0 .../skills/architecture/patterns-reference.md | 0 .../skills/architecture/trade-off-analysis.md | 0 .../skills/bash-linux/SKILL.md | 0 .../skills/behavioral-modes/SKILL.md | 0 .../skills/brainstorming/SKILL.md | 0 .../brainstorming/dynamic-questioning.md | 0 .../skills/clean-code/SKILL.md | 0 .../skills/code-review-checklist/SKILL.md | 0 .../skills/database-design/SKILL.md | 0 .../database-design/database-selection.md | 0 .../skills/database-design/indexing.md | 0 .../skills/database-design/migrations.md | 0 .../skills/database-design/optimization.md | 0 .../skills/database-design/orm-selection.md | 0 .../skills/database-design/schema-design.md | 0 .../scripts/schema_validator.py | 0 .../skills/deployment-procedures/SKILL.md | 0 {.agent => .agents}/skills/doc.md | 0 .../skills/documentation-templates/SKILL.md | 0 .../skills/frontend-design/SKILL.md | 0 .../skills/frontend-design/animation-guide.md | 0 .../skills/frontend-design/color-system.md | 0 .../skills/frontend-design/decision-trees.md | 0 .../skills/frontend-design/motion-graphics.md | 0 .../scripts/accessibility_checker.py | 0 .../frontend-design/scripts/ux_audit.py | 0 .../frontend-design/typography-system.md | 0 .../skills/frontend-design/ux-psychology.md | 0 .../skills/frontend-design/visual-effects.md | 0 .../skills/game-development/2d-games/SKILL.md | 0 .../skills/game-development/3d-games/SKILL.md | 0 .../skills/game-development/SKILL.md | 0 .../skills/game-development/game-art/SKILL.md | 0 .../game-development/game-audio/SKILL.md | 0 .../game-development/game-design/SKILL.md | 0 .../game-development/mobile-games/SKILL.md | 0 .../game-development/multiplayer/SKILL.md | 0 .../skills/game-development/pc-games/SKILL.md | 0 .../skills/game-development/vr-ar/SKILL.md | 0 .../game-development/web-games/SKILL.md | 0 .../skills/geo-fundamentals/SKILL.md | 0 .../geo-fundamentals/scripts/geo_checker.py | 0 .../skills/i18n-localization/SKILL.md | 0 .../i18n-localization/scripts/i18n_checker.py | 0 .../skills/intelligent-routing/SKILL.md | 0 .../skills/lint-and-validate/SKILL.md | 0 .../lint-and-validate/scripts/lint_runner.py | 0 .../scripts/type_coverage.py | 0 .../skills/mcp-builder/SKILL.md | 0 .../skills/mobile-design/SKILL.md | 0 .../skills/mobile-design/decision-trees.md | 0 .../skills/mobile-design/mobile-backend.md | 0 .../mobile-design/mobile-color-system.md | 0 .../skills/mobile-design/mobile-debugging.md | 0 .../mobile-design/mobile-design-thinking.md | 0 .../skills/mobile-design/mobile-navigation.md | 0 .../mobile-design/mobile-performance.md | 0 .../skills/mobile-design/mobile-testing.md | 0 .../skills/mobile-design/mobile-typography.md | 0 .../skills/mobile-design/platform-android.md | 0 .../skills/mobile-design/platform-ios.md | 0 .../mobile-design/scripts/mobile_audit.py | 0 .../skills/mobile-design/touch-psychology.md | 0 .../1-async-eliminating-waterfalls.md | 0 .../2-bundle-bundle-size-optimization.md | 0 .../3-server-server-side-performance.md | 0 .../4-client-client-side-data-fetching.md | 0 .../5-rerender-re-render-optimization.md | 0 .../6-rendering-rendering-performance.md | 0 .../7-js-javascript-performance.md | 0 .../8-advanced-advanced-patterns.md | 0 .../skills/nextjs-react-expert/SKILL.md | 0 .../scripts/convert_rules.py | 0 .../scripts/react_performance_checker.py | 0 .../skills/nodejs-best-practices/SKILL.md | 0 .../skills/parallel-agents/SKILL.md | 0 .../skills/performance-profiling/SKILL.md | 0 .../scripts/lighthouse_audit.py | 0 .../skills/plan-writing/SKILL.md | 0 .../skills/powershell-windows/SKILL.md | 0 .../skills/python-patterns/SKILL.md | 0 .../skills/red-team-tactics/SKILL.md | 0 .../skills/refactoring-patterns/SKILL.md | 0 {.agent => .agents}/skills/rust-pro/SKILL.md | 0 .../skills/seo-fundamentals/SKILL.md | 0 .../seo-fundamentals/scripts/seo_checker.py | 0 .../skills/server-management/SKILL.md | 0 .../skills/systematic-debugging/SKILL.md | 0 .../skills/tailwind-patterns/SKILL.md | 0 .../skills/tdd-workflow/SKILL.md | 0 .../skills/testing-patterns/SKILL.md | 0 .../testing-patterns/scripts/test_runner.py | 0 .../skills/vulnerability-scanner/SKILL.md | 0 .../vulnerability-scanner/checklists.md | 0 .../scripts/security_scan.py | 0 .../skills/web-design-guidelines/SKILL.md | 0 .../skills/webapp-testing/SKILL.md | 0 .../scripts/playwright_runner.py | 0 {.agent => .agents}/workflows/brainstorm.md | 0 {.agent => .agents}/workflows/create.md | 0 {.agent => .agents}/workflows/debug.md | 0 {.agent => .agents}/workflows/deploy.md | 0 {.agent => .agents}/workflows/enhance.md | 0 {.agent => .agents}/workflows/orchestrate.md | 0 {.agent => .agents}/workflows/plan.md | 0 {.agent => .agents}/workflows/preview.md | 0 .../workflows/restore-localize-compat.md | 0 {.agent => .agents}/workflows/status.md | 0 {.agent => .agents}/workflows/test.md | 0 .../workflows/ui-ux-pro-max.md | 0 .github/workflows/ci.yml | 36 +++ AGENTS.md | 2 +- README.md | 19 ++ bin/adapters/codex.js | 30 +- bin/ag-kit.js | 297 +++++++++++++++++- bin/core/builder.js | 2 +- bin/core/resource-loader.js | 4 +- bin/utils.js | 13 +- docs/mapping-spec.md | 6 +- docs/multi-target-adapter.md | 11 +- docs/official/antigravity/rules-workflows.md | 2 + docs/official/antigravity/skills.md | 2 + docs/operations.md | 18 +- docs/plan-global-install.md | 79 +++++ docs/terminology-style-guide.md | 2 +- package.json | 5 +- scripts/ci-verify.js | 91 ++++++ tests/global-sync.test.js | 88 ++++++ tests/standards-compliance.test.js | 22 +- web/src/app/docs/rules-workflows/page.tsx | 7 + web/src/app/docs/skills/page.tsx | 7 + 221 files changed, 709 insertions(+), 34 deletions(-) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/charts.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/colors.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/icons.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/landing.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/products.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/prompts.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/react-performance.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/flutter.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/nextjs.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/react-native.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/react.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/shadcn.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/svelte.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/swiftui.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/stacks/vue.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/styles.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/typography.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/ui-reasoning.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/ux-guidelines.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/data/web-interface.csv (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/scripts/core.py (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/scripts/design_system.py (100%) rename {.agent => .agents}/.shared/ui-ux-pro-max/scripts/search.py (100%) rename {.agent => .agents}/ARCHITECTURE.md (100%) rename {.agent => .agents}/agents/backend-specialist.md (100%) rename {.agent => .agents}/agents/code-archaeologist.md (100%) rename {.agent => .agents}/agents/database-architect.md (100%) rename {.agent => .agents}/agents/debugger.md (100%) rename {.agent => .agents}/agents/devops-engineer.md (100%) rename {.agent => .agents}/agents/documentation-writer.md (100%) rename {.agent => .agents}/agents/explorer-agent.md (100%) rename {.agent => .agents}/agents/frontend-specialist.md (100%) rename {.agent => .agents}/agents/game-developer.md (100%) rename {.agent => .agents}/agents/mobile-developer.md (100%) rename {.agent => .agents}/agents/orchestrator.md (100%) rename {.agent => .agents}/agents/penetration-tester.md (100%) rename {.agent => .agents}/agents/performance-optimizer.md (100%) rename {.agent => .agents}/agents/product-manager.md (100%) rename {.agent => .agents}/agents/product-owner.md (100%) rename {.agent => .agents}/agents/project-planner.md (100%) rename {.agent => .agents}/agents/qa-automation-engineer.md (100%) rename {.agent => .agents}/agents/security-auditor.md (100%) rename {.agent => .agents}/agents/seo-specialist.md (100%) rename {.agent => .agents}/agents/test-engineer.md (100%) rename {.agent => .agents}/mcp_config.json (100%) rename {.agent => .agents}/rules/GEMINI.md (100%) rename {.agent => .agents}/scripts/auto_preview.py (100%) rename {.agent => .agents}/scripts/checklist.py (100%) rename {.agent => .agents}/scripts/session_manager.py (100%) rename {.agent => .agents}/scripts/verify_all.py (100%) rename {.agent => .agents}/skills/api-patterns/SKILL.md (100%) rename {.agent => .agents}/skills/api-patterns/api-style.md (100%) rename {.agent => .agents}/skills/api-patterns/auth.md (100%) rename {.agent => .agents}/skills/api-patterns/documentation.md (100%) rename {.agent => .agents}/skills/api-patterns/graphql.md (100%) rename {.agent => .agents}/skills/api-patterns/rate-limiting.md (100%) rename {.agent => .agents}/skills/api-patterns/response.md (100%) rename {.agent => .agents}/skills/api-patterns/rest.md (100%) rename {.agent => .agents}/skills/api-patterns/scripts/api_validator.py (100%) rename {.agent => .agents}/skills/api-patterns/security-testing.md (100%) rename {.agent => .agents}/skills/api-patterns/trpc.md (100%) rename {.agent => .agents}/skills/api-patterns/versioning.md (100%) rename {.agent => .agents}/skills/app-builder/SKILL.md (100%) rename {.agent => .agents}/skills/app-builder/agent-coordination.md (100%) rename {.agent => .agents}/skills/app-builder/feature-building.md (100%) rename {.agent => .agents}/skills/app-builder/project-detection.md (100%) rename {.agent => .agents}/skills/app-builder/scaffolding.md (100%) rename {.agent => .agents}/skills/app-builder/tech-stack.md (100%) rename {.agent => .agents}/skills/app-builder/templates/SKILL.md (100%) rename {.agent => .agents}/skills/app-builder/templates/astro-static/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/chrome-extension/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/cli-tool/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/electron-desktop/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/express-api/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/flutter-app/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/nextjs-saas/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/nextjs-static/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/nuxt-app/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/python-fastapi/TEMPLATE.md (100%) rename {.agent => .agents}/skills/app-builder/templates/react-native-app/TEMPLATE.md (100%) rename {.agent => .agents}/skills/architecture/SKILL.md (100%) rename {.agent => .agents}/skills/architecture/context-discovery.md (100%) rename {.agent => .agents}/skills/architecture/examples.md (100%) rename {.agent => .agents}/skills/architecture/pattern-selection.md (100%) rename {.agent => .agents}/skills/architecture/patterns-reference.md (100%) rename {.agent => .agents}/skills/architecture/trade-off-analysis.md (100%) rename {.agent => .agents}/skills/bash-linux/SKILL.md (100%) rename {.agent => .agents}/skills/behavioral-modes/SKILL.md (100%) rename {.agent => .agents}/skills/brainstorming/SKILL.md (100%) rename {.agent => .agents}/skills/brainstorming/dynamic-questioning.md (100%) rename {.agent => .agents}/skills/clean-code/SKILL.md (100%) rename {.agent => .agents}/skills/code-review-checklist/SKILL.md (100%) rename {.agent => .agents}/skills/database-design/SKILL.md (100%) rename {.agent => .agents}/skills/database-design/database-selection.md (100%) rename {.agent => .agents}/skills/database-design/indexing.md (100%) rename {.agent => .agents}/skills/database-design/migrations.md (100%) rename {.agent => .agents}/skills/database-design/optimization.md (100%) rename {.agent => .agents}/skills/database-design/orm-selection.md (100%) rename {.agent => .agents}/skills/database-design/schema-design.md (100%) rename {.agent => .agents}/skills/database-design/scripts/schema_validator.py (100%) rename {.agent => .agents}/skills/deployment-procedures/SKILL.md (100%) rename {.agent => .agents}/skills/doc.md (100%) rename {.agent => .agents}/skills/documentation-templates/SKILL.md (100%) rename {.agent => .agents}/skills/frontend-design/SKILL.md (100%) rename {.agent => .agents}/skills/frontend-design/animation-guide.md (100%) rename {.agent => .agents}/skills/frontend-design/color-system.md (100%) rename {.agent => .agents}/skills/frontend-design/decision-trees.md (100%) rename {.agent => .agents}/skills/frontend-design/motion-graphics.md (100%) rename {.agent => .agents}/skills/frontend-design/scripts/accessibility_checker.py (100%) rename {.agent => .agents}/skills/frontend-design/scripts/ux_audit.py (100%) rename {.agent => .agents}/skills/frontend-design/typography-system.md (100%) rename {.agent => .agents}/skills/frontend-design/ux-psychology.md (100%) rename {.agent => .agents}/skills/frontend-design/visual-effects.md (100%) rename {.agent => .agents}/skills/game-development/2d-games/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/3d-games/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/game-art/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/game-audio/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/game-design/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/mobile-games/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/multiplayer/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/pc-games/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/vr-ar/SKILL.md (100%) rename {.agent => .agents}/skills/game-development/web-games/SKILL.md (100%) rename {.agent => .agents}/skills/geo-fundamentals/SKILL.md (100%) rename {.agent => .agents}/skills/geo-fundamentals/scripts/geo_checker.py (100%) rename {.agent => .agents}/skills/i18n-localization/SKILL.md (100%) rename {.agent => .agents}/skills/i18n-localization/scripts/i18n_checker.py (100%) rename {.agent => .agents}/skills/intelligent-routing/SKILL.md (100%) rename {.agent => .agents}/skills/lint-and-validate/SKILL.md (100%) rename {.agent => .agents}/skills/lint-and-validate/scripts/lint_runner.py (100%) rename {.agent => .agents}/skills/lint-and-validate/scripts/type_coverage.py (100%) rename {.agent => .agents}/skills/mcp-builder/SKILL.md (100%) rename {.agent => .agents}/skills/mobile-design/SKILL.md (100%) rename {.agent => .agents}/skills/mobile-design/decision-trees.md (100%) rename {.agent => .agents}/skills/mobile-design/mobile-backend.md (100%) rename {.agent => .agents}/skills/mobile-design/mobile-color-system.md (100%) rename {.agent => .agents}/skills/mobile-design/mobile-debugging.md (100%) rename {.agent => .agents}/skills/mobile-design/mobile-design-thinking.md (100%) rename {.agent => .agents}/skills/mobile-design/mobile-navigation.md (100%) rename {.agent => .agents}/skills/mobile-design/mobile-performance.md (100%) rename {.agent => .agents}/skills/mobile-design/mobile-testing.md (100%) rename {.agent => .agents}/skills/mobile-design/mobile-typography.md (100%) rename {.agent => .agents}/skills/mobile-design/platform-android.md (100%) rename {.agent => .agents}/skills/mobile-design/platform-ios.md (100%) rename {.agent => .agents}/skills/mobile-design/scripts/mobile_audit.py (100%) rename {.agent => .agents}/skills/mobile-design/touch-psychology.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/3-server-server-side-performance.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/4-client-client-side-data-fetching.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/5-rerender-re-render-optimization.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/6-rendering-rendering-performance.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/7-js-javascript-performance.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/8-advanced-advanced-patterns.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/SKILL.md (100%) rename {.agent => .agents}/skills/nextjs-react-expert/scripts/convert_rules.py (100%) rename {.agent => .agents}/skills/nextjs-react-expert/scripts/react_performance_checker.py (100%) rename {.agent => .agents}/skills/nodejs-best-practices/SKILL.md (100%) rename {.agent => .agents}/skills/parallel-agents/SKILL.md (100%) rename {.agent => .agents}/skills/performance-profiling/SKILL.md (100%) rename {.agent => .agents}/skills/performance-profiling/scripts/lighthouse_audit.py (100%) rename {.agent => .agents}/skills/plan-writing/SKILL.md (100%) rename {.agent => .agents}/skills/powershell-windows/SKILL.md (100%) rename {.agent => .agents}/skills/python-patterns/SKILL.md (100%) rename {.agent => .agents}/skills/red-team-tactics/SKILL.md (100%) rename {.agent => .agents}/skills/refactoring-patterns/SKILL.md (100%) rename {.agent => .agents}/skills/rust-pro/SKILL.md (100%) rename {.agent => .agents}/skills/seo-fundamentals/SKILL.md (100%) rename {.agent => .agents}/skills/seo-fundamentals/scripts/seo_checker.py (100%) rename {.agent => .agents}/skills/server-management/SKILL.md (100%) rename {.agent => .agents}/skills/systematic-debugging/SKILL.md (100%) rename {.agent => .agents}/skills/tailwind-patterns/SKILL.md (100%) rename {.agent => .agents}/skills/tdd-workflow/SKILL.md (100%) rename {.agent => .agents}/skills/testing-patterns/SKILL.md (100%) rename {.agent => .agents}/skills/testing-patterns/scripts/test_runner.py (100%) rename {.agent => .agents}/skills/vulnerability-scanner/SKILL.md (100%) rename {.agent => .agents}/skills/vulnerability-scanner/checklists.md (100%) rename {.agent => .agents}/skills/vulnerability-scanner/scripts/security_scan.py (100%) rename {.agent => .agents}/skills/web-design-guidelines/SKILL.md (100%) rename {.agent => .agents}/skills/webapp-testing/SKILL.md (100%) rename {.agent => .agents}/skills/webapp-testing/scripts/playwright_runner.py (100%) rename {.agent => .agents}/workflows/brainstorm.md (100%) rename {.agent => .agents}/workflows/create.md (100%) rename {.agent => .agents}/workflows/debug.md (100%) rename {.agent => .agents}/workflows/deploy.md (100%) rename {.agent => .agents}/workflows/enhance.md (100%) rename {.agent => .agents}/workflows/orchestrate.md (100%) rename {.agent => .agents}/workflows/plan.md (100%) rename {.agent => .agents}/workflows/preview.md (100%) rename {.agent => .agents}/workflows/restore-localize-compat.md (100%) rename {.agent => .agents}/workflows/status.md (100%) rename {.agent => .agents}/workflows/test.md (100%) rename {.agent => .agents}/workflows/ui-ux-pro-max.md (100%) create mode 100644 .github/workflows/ci.yml create mode 100644 docs/plan-global-install.md create mode 100644 scripts/ci-verify.js create mode 100644 tests/global-sync.test.js diff --git a/.agent/.shared/ui-ux-pro-max/data/charts.csv b/.agents/.shared/ui-ux-pro-max/data/charts.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/charts.csv rename to .agents/.shared/ui-ux-pro-max/data/charts.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/colors.csv b/.agents/.shared/ui-ux-pro-max/data/colors.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/colors.csv rename to .agents/.shared/ui-ux-pro-max/data/colors.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/icons.csv b/.agents/.shared/ui-ux-pro-max/data/icons.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/icons.csv rename to .agents/.shared/ui-ux-pro-max/data/icons.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/landing.csv b/.agents/.shared/ui-ux-pro-max/data/landing.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/landing.csv rename to .agents/.shared/ui-ux-pro-max/data/landing.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/products.csv b/.agents/.shared/ui-ux-pro-max/data/products.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/products.csv rename to .agents/.shared/ui-ux-pro-max/data/products.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/prompts.csv b/.agents/.shared/ui-ux-pro-max/data/prompts.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/prompts.csv rename to .agents/.shared/ui-ux-pro-max/data/prompts.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/react-performance.csv b/.agents/.shared/ui-ux-pro-max/data/react-performance.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/react-performance.csv rename to .agents/.shared/ui-ux-pro-max/data/react-performance.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/flutter.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/flutter.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/nextjs.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/nextjs.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/react-native.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/react-native.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/react.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/react.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/react.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/shadcn.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/shadcn.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/svelte.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/svelte.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/swiftui.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/swiftui.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv b/.agents/.shared/ui-ux-pro-max/data/stacks/vue.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/stacks/vue.csv rename to .agents/.shared/ui-ux-pro-max/data/stacks/vue.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/styles.csv b/.agents/.shared/ui-ux-pro-max/data/styles.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/styles.csv rename to .agents/.shared/ui-ux-pro-max/data/styles.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/typography.csv b/.agents/.shared/ui-ux-pro-max/data/typography.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/typography.csv rename to .agents/.shared/ui-ux-pro-max/data/typography.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv b/.agents/.shared/ui-ux-pro-max/data/ui-reasoning.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv rename to .agents/.shared/ui-ux-pro-max/data/ui-reasoning.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv b/.agents/.shared/ui-ux-pro-max/data/ux-guidelines.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv rename to .agents/.shared/ui-ux-pro-max/data/ux-guidelines.csv diff --git a/.agent/.shared/ui-ux-pro-max/data/web-interface.csv b/.agents/.shared/ui-ux-pro-max/data/web-interface.csv similarity index 100% rename from .agent/.shared/ui-ux-pro-max/data/web-interface.csv rename to .agents/.shared/ui-ux-pro-max/data/web-interface.csv diff --git a/.agent/.shared/ui-ux-pro-max/scripts/core.py b/.agents/.shared/ui-ux-pro-max/scripts/core.py similarity index 100% rename from .agent/.shared/ui-ux-pro-max/scripts/core.py rename to .agents/.shared/ui-ux-pro-max/scripts/core.py diff --git a/.agent/.shared/ui-ux-pro-max/scripts/design_system.py b/.agents/.shared/ui-ux-pro-max/scripts/design_system.py similarity index 100% rename from .agent/.shared/ui-ux-pro-max/scripts/design_system.py rename to .agents/.shared/ui-ux-pro-max/scripts/design_system.py diff --git a/.agent/.shared/ui-ux-pro-max/scripts/search.py b/.agents/.shared/ui-ux-pro-max/scripts/search.py similarity index 100% rename from .agent/.shared/ui-ux-pro-max/scripts/search.py rename to .agents/.shared/ui-ux-pro-max/scripts/search.py diff --git a/.agent/ARCHITECTURE.md b/.agents/ARCHITECTURE.md similarity index 100% rename from .agent/ARCHITECTURE.md rename to .agents/ARCHITECTURE.md diff --git a/.agent/agents/backend-specialist.md b/.agents/agents/backend-specialist.md similarity index 100% rename from .agent/agents/backend-specialist.md rename to .agents/agents/backend-specialist.md diff --git a/.agent/agents/code-archaeologist.md b/.agents/agents/code-archaeologist.md similarity index 100% rename from .agent/agents/code-archaeologist.md rename to .agents/agents/code-archaeologist.md diff --git a/.agent/agents/database-architect.md b/.agents/agents/database-architect.md similarity index 100% rename from .agent/agents/database-architect.md rename to .agents/agents/database-architect.md diff --git a/.agent/agents/debugger.md b/.agents/agents/debugger.md similarity index 100% rename from .agent/agents/debugger.md rename to .agents/agents/debugger.md diff --git a/.agent/agents/devops-engineer.md b/.agents/agents/devops-engineer.md similarity index 100% rename from .agent/agents/devops-engineer.md rename to .agents/agents/devops-engineer.md diff --git a/.agent/agents/documentation-writer.md b/.agents/agents/documentation-writer.md similarity index 100% rename from .agent/agents/documentation-writer.md rename to .agents/agents/documentation-writer.md diff --git a/.agent/agents/explorer-agent.md b/.agents/agents/explorer-agent.md similarity index 100% rename from .agent/agents/explorer-agent.md rename to .agents/agents/explorer-agent.md diff --git a/.agent/agents/frontend-specialist.md b/.agents/agents/frontend-specialist.md similarity index 100% rename from .agent/agents/frontend-specialist.md rename to .agents/agents/frontend-specialist.md diff --git a/.agent/agents/game-developer.md b/.agents/agents/game-developer.md similarity index 100% rename from .agent/agents/game-developer.md rename to .agents/agents/game-developer.md diff --git a/.agent/agents/mobile-developer.md b/.agents/agents/mobile-developer.md similarity index 100% rename from .agent/agents/mobile-developer.md rename to .agents/agents/mobile-developer.md diff --git a/.agent/agents/orchestrator.md b/.agents/agents/orchestrator.md similarity index 100% rename from .agent/agents/orchestrator.md rename to .agents/agents/orchestrator.md diff --git a/.agent/agents/penetration-tester.md b/.agents/agents/penetration-tester.md similarity index 100% rename from .agent/agents/penetration-tester.md rename to .agents/agents/penetration-tester.md diff --git a/.agent/agents/performance-optimizer.md b/.agents/agents/performance-optimizer.md similarity index 100% rename from .agent/agents/performance-optimizer.md rename to .agents/agents/performance-optimizer.md diff --git a/.agent/agents/product-manager.md b/.agents/agents/product-manager.md similarity index 100% rename from .agent/agents/product-manager.md rename to .agents/agents/product-manager.md diff --git a/.agent/agents/product-owner.md b/.agents/agents/product-owner.md similarity index 100% rename from .agent/agents/product-owner.md rename to .agents/agents/product-owner.md diff --git a/.agent/agents/project-planner.md b/.agents/agents/project-planner.md similarity index 100% rename from .agent/agents/project-planner.md rename to .agents/agents/project-planner.md diff --git a/.agent/agents/qa-automation-engineer.md b/.agents/agents/qa-automation-engineer.md similarity index 100% rename from .agent/agents/qa-automation-engineer.md rename to .agents/agents/qa-automation-engineer.md diff --git a/.agent/agents/security-auditor.md b/.agents/agents/security-auditor.md similarity index 100% rename from .agent/agents/security-auditor.md rename to .agents/agents/security-auditor.md diff --git a/.agent/agents/seo-specialist.md b/.agents/agents/seo-specialist.md similarity index 100% rename from .agent/agents/seo-specialist.md rename to .agents/agents/seo-specialist.md diff --git a/.agent/agents/test-engineer.md b/.agents/agents/test-engineer.md similarity index 100% rename from .agent/agents/test-engineer.md rename to .agents/agents/test-engineer.md diff --git a/.agent/mcp_config.json b/.agents/mcp_config.json similarity index 100% rename from .agent/mcp_config.json rename to .agents/mcp_config.json diff --git a/.agent/rules/GEMINI.md b/.agents/rules/GEMINI.md similarity index 100% rename from .agent/rules/GEMINI.md rename to .agents/rules/GEMINI.md diff --git a/.agent/scripts/auto_preview.py b/.agents/scripts/auto_preview.py similarity index 100% rename from .agent/scripts/auto_preview.py rename to .agents/scripts/auto_preview.py diff --git a/.agent/scripts/checklist.py b/.agents/scripts/checklist.py similarity index 100% rename from .agent/scripts/checklist.py rename to .agents/scripts/checklist.py diff --git a/.agent/scripts/session_manager.py b/.agents/scripts/session_manager.py similarity index 100% rename from .agent/scripts/session_manager.py rename to .agents/scripts/session_manager.py diff --git a/.agent/scripts/verify_all.py b/.agents/scripts/verify_all.py similarity index 100% rename from .agent/scripts/verify_all.py rename to .agents/scripts/verify_all.py diff --git a/.agent/skills/api-patterns/SKILL.md b/.agents/skills/api-patterns/SKILL.md similarity index 100% rename from .agent/skills/api-patterns/SKILL.md rename to .agents/skills/api-patterns/SKILL.md diff --git a/.agent/skills/api-patterns/api-style.md b/.agents/skills/api-patterns/api-style.md similarity index 100% rename from .agent/skills/api-patterns/api-style.md rename to .agents/skills/api-patterns/api-style.md diff --git a/.agent/skills/api-patterns/auth.md b/.agents/skills/api-patterns/auth.md similarity index 100% rename from .agent/skills/api-patterns/auth.md rename to .agents/skills/api-patterns/auth.md diff --git a/.agent/skills/api-patterns/documentation.md b/.agents/skills/api-patterns/documentation.md similarity index 100% rename from .agent/skills/api-patterns/documentation.md rename to .agents/skills/api-patterns/documentation.md diff --git a/.agent/skills/api-patterns/graphql.md b/.agents/skills/api-patterns/graphql.md similarity index 100% rename from .agent/skills/api-patterns/graphql.md rename to .agents/skills/api-patterns/graphql.md diff --git a/.agent/skills/api-patterns/rate-limiting.md b/.agents/skills/api-patterns/rate-limiting.md similarity index 100% rename from .agent/skills/api-patterns/rate-limiting.md rename to .agents/skills/api-patterns/rate-limiting.md diff --git a/.agent/skills/api-patterns/response.md b/.agents/skills/api-patterns/response.md similarity index 100% rename from .agent/skills/api-patterns/response.md rename to .agents/skills/api-patterns/response.md diff --git a/.agent/skills/api-patterns/rest.md b/.agents/skills/api-patterns/rest.md similarity index 100% rename from .agent/skills/api-patterns/rest.md rename to .agents/skills/api-patterns/rest.md diff --git a/.agent/skills/api-patterns/scripts/api_validator.py b/.agents/skills/api-patterns/scripts/api_validator.py similarity index 100% rename from .agent/skills/api-patterns/scripts/api_validator.py rename to .agents/skills/api-patterns/scripts/api_validator.py diff --git a/.agent/skills/api-patterns/security-testing.md b/.agents/skills/api-patterns/security-testing.md similarity index 100% rename from .agent/skills/api-patterns/security-testing.md rename to .agents/skills/api-patterns/security-testing.md diff --git a/.agent/skills/api-patterns/trpc.md b/.agents/skills/api-patterns/trpc.md similarity index 100% rename from .agent/skills/api-patterns/trpc.md rename to .agents/skills/api-patterns/trpc.md diff --git a/.agent/skills/api-patterns/versioning.md b/.agents/skills/api-patterns/versioning.md similarity index 100% rename from .agent/skills/api-patterns/versioning.md rename to .agents/skills/api-patterns/versioning.md diff --git a/.agent/skills/app-builder/SKILL.md b/.agents/skills/app-builder/SKILL.md similarity index 100% rename from .agent/skills/app-builder/SKILL.md rename to .agents/skills/app-builder/SKILL.md diff --git a/.agent/skills/app-builder/agent-coordination.md b/.agents/skills/app-builder/agent-coordination.md similarity index 100% rename from .agent/skills/app-builder/agent-coordination.md rename to .agents/skills/app-builder/agent-coordination.md diff --git a/.agent/skills/app-builder/feature-building.md b/.agents/skills/app-builder/feature-building.md similarity index 100% rename from .agent/skills/app-builder/feature-building.md rename to .agents/skills/app-builder/feature-building.md diff --git a/.agent/skills/app-builder/project-detection.md b/.agents/skills/app-builder/project-detection.md similarity index 100% rename from .agent/skills/app-builder/project-detection.md rename to .agents/skills/app-builder/project-detection.md diff --git a/.agent/skills/app-builder/scaffolding.md b/.agents/skills/app-builder/scaffolding.md similarity index 100% rename from .agent/skills/app-builder/scaffolding.md rename to .agents/skills/app-builder/scaffolding.md diff --git a/.agent/skills/app-builder/tech-stack.md b/.agents/skills/app-builder/tech-stack.md similarity index 100% rename from .agent/skills/app-builder/tech-stack.md rename to .agents/skills/app-builder/tech-stack.md diff --git a/.agent/skills/app-builder/templates/SKILL.md b/.agents/skills/app-builder/templates/SKILL.md similarity index 100% rename from .agent/skills/app-builder/templates/SKILL.md rename to .agents/skills/app-builder/templates/SKILL.md diff --git a/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md b/.agents/skills/app-builder/templates/astro-static/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/astro-static/TEMPLATE.md rename to .agents/skills/app-builder/templates/astro-static/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md b/.agents/skills/app-builder/templates/chrome-extension/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md rename to .agents/skills/app-builder/templates/chrome-extension/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md b/.agents/skills/app-builder/templates/cli-tool/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/cli-tool/TEMPLATE.md rename to .agents/skills/app-builder/templates/cli-tool/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md b/.agents/skills/app-builder/templates/electron-desktop/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md rename to .agents/skills/app-builder/templates/electron-desktop/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/express-api/TEMPLATE.md b/.agents/skills/app-builder/templates/express-api/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/express-api/TEMPLATE.md rename to .agents/skills/app-builder/templates/express-api/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md b/.agents/skills/app-builder/templates/flutter-app/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/flutter-app/TEMPLATE.md rename to .agents/skills/app-builder/templates/flutter-app/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md b/.agents/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md rename to .agents/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md b/.agents/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md rename to .agents/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md b/.agents/skills/app-builder/templates/nextjs-saas/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md rename to .agents/skills/app-builder/templates/nextjs-saas/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md b/.agents/skills/app-builder/templates/nextjs-static/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md rename to .agents/skills/app-builder/templates/nextjs-static/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md b/.agents/skills/app-builder/templates/nuxt-app/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md rename to .agents/skills/app-builder/templates/nuxt-app/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md b/.agents/skills/app-builder/templates/python-fastapi/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md rename to .agents/skills/app-builder/templates/python-fastapi/TEMPLATE.md diff --git a/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md b/.agents/skills/app-builder/templates/react-native-app/TEMPLATE.md similarity index 100% rename from .agent/skills/app-builder/templates/react-native-app/TEMPLATE.md rename to .agents/skills/app-builder/templates/react-native-app/TEMPLATE.md diff --git a/.agent/skills/architecture/SKILL.md b/.agents/skills/architecture/SKILL.md similarity index 100% rename from .agent/skills/architecture/SKILL.md rename to .agents/skills/architecture/SKILL.md diff --git a/.agent/skills/architecture/context-discovery.md b/.agents/skills/architecture/context-discovery.md similarity index 100% rename from .agent/skills/architecture/context-discovery.md rename to .agents/skills/architecture/context-discovery.md diff --git a/.agent/skills/architecture/examples.md b/.agents/skills/architecture/examples.md similarity index 100% rename from .agent/skills/architecture/examples.md rename to .agents/skills/architecture/examples.md diff --git a/.agent/skills/architecture/pattern-selection.md b/.agents/skills/architecture/pattern-selection.md similarity index 100% rename from .agent/skills/architecture/pattern-selection.md rename to .agents/skills/architecture/pattern-selection.md diff --git a/.agent/skills/architecture/patterns-reference.md b/.agents/skills/architecture/patterns-reference.md similarity index 100% rename from .agent/skills/architecture/patterns-reference.md rename to .agents/skills/architecture/patterns-reference.md diff --git a/.agent/skills/architecture/trade-off-analysis.md b/.agents/skills/architecture/trade-off-analysis.md similarity index 100% rename from .agent/skills/architecture/trade-off-analysis.md rename to .agents/skills/architecture/trade-off-analysis.md diff --git a/.agent/skills/bash-linux/SKILL.md b/.agents/skills/bash-linux/SKILL.md similarity index 100% rename from .agent/skills/bash-linux/SKILL.md rename to .agents/skills/bash-linux/SKILL.md diff --git a/.agent/skills/behavioral-modes/SKILL.md b/.agents/skills/behavioral-modes/SKILL.md similarity index 100% rename from .agent/skills/behavioral-modes/SKILL.md rename to .agents/skills/behavioral-modes/SKILL.md diff --git a/.agent/skills/brainstorming/SKILL.md b/.agents/skills/brainstorming/SKILL.md similarity index 100% rename from .agent/skills/brainstorming/SKILL.md rename to .agents/skills/brainstorming/SKILL.md diff --git a/.agent/skills/brainstorming/dynamic-questioning.md b/.agents/skills/brainstorming/dynamic-questioning.md similarity index 100% rename from .agent/skills/brainstorming/dynamic-questioning.md rename to .agents/skills/brainstorming/dynamic-questioning.md diff --git a/.agent/skills/clean-code/SKILL.md b/.agents/skills/clean-code/SKILL.md similarity index 100% rename from .agent/skills/clean-code/SKILL.md rename to .agents/skills/clean-code/SKILL.md diff --git a/.agent/skills/code-review-checklist/SKILL.md b/.agents/skills/code-review-checklist/SKILL.md similarity index 100% rename from .agent/skills/code-review-checklist/SKILL.md rename to .agents/skills/code-review-checklist/SKILL.md diff --git a/.agent/skills/database-design/SKILL.md b/.agents/skills/database-design/SKILL.md similarity index 100% rename from .agent/skills/database-design/SKILL.md rename to .agents/skills/database-design/SKILL.md diff --git a/.agent/skills/database-design/database-selection.md b/.agents/skills/database-design/database-selection.md similarity index 100% rename from .agent/skills/database-design/database-selection.md rename to .agents/skills/database-design/database-selection.md diff --git a/.agent/skills/database-design/indexing.md b/.agents/skills/database-design/indexing.md similarity index 100% rename from .agent/skills/database-design/indexing.md rename to .agents/skills/database-design/indexing.md diff --git a/.agent/skills/database-design/migrations.md b/.agents/skills/database-design/migrations.md similarity index 100% rename from .agent/skills/database-design/migrations.md rename to .agents/skills/database-design/migrations.md diff --git a/.agent/skills/database-design/optimization.md b/.agents/skills/database-design/optimization.md similarity index 100% rename from .agent/skills/database-design/optimization.md rename to .agents/skills/database-design/optimization.md diff --git a/.agent/skills/database-design/orm-selection.md b/.agents/skills/database-design/orm-selection.md similarity index 100% rename from .agent/skills/database-design/orm-selection.md rename to .agents/skills/database-design/orm-selection.md diff --git a/.agent/skills/database-design/schema-design.md b/.agents/skills/database-design/schema-design.md similarity index 100% rename from .agent/skills/database-design/schema-design.md rename to .agents/skills/database-design/schema-design.md diff --git a/.agent/skills/database-design/scripts/schema_validator.py b/.agents/skills/database-design/scripts/schema_validator.py similarity index 100% rename from .agent/skills/database-design/scripts/schema_validator.py rename to .agents/skills/database-design/scripts/schema_validator.py diff --git a/.agent/skills/deployment-procedures/SKILL.md b/.agents/skills/deployment-procedures/SKILL.md similarity index 100% rename from .agent/skills/deployment-procedures/SKILL.md rename to .agents/skills/deployment-procedures/SKILL.md diff --git a/.agent/skills/doc.md b/.agents/skills/doc.md similarity index 100% rename from .agent/skills/doc.md rename to .agents/skills/doc.md diff --git a/.agent/skills/documentation-templates/SKILL.md b/.agents/skills/documentation-templates/SKILL.md similarity index 100% rename from .agent/skills/documentation-templates/SKILL.md rename to .agents/skills/documentation-templates/SKILL.md diff --git a/.agent/skills/frontend-design/SKILL.md b/.agents/skills/frontend-design/SKILL.md similarity index 100% rename from .agent/skills/frontend-design/SKILL.md rename to .agents/skills/frontend-design/SKILL.md diff --git a/.agent/skills/frontend-design/animation-guide.md b/.agents/skills/frontend-design/animation-guide.md similarity index 100% rename from .agent/skills/frontend-design/animation-guide.md rename to .agents/skills/frontend-design/animation-guide.md diff --git a/.agent/skills/frontend-design/color-system.md b/.agents/skills/frontend-design/color-system.md similarity index 100% rename from .agent/skills/frontend-design/color-system.md rename to .agents/skills/frontend-design/color-system.md diff --git a/.agent/skills/frontend-design/decision-trees.md b/.agents/skills/frontend-design/decision-trees.md similarity index 100% rename from .agent/skills/frontend-design/decision-trees.md rename to .agents/skills/frontend-design/decision-trees.md diff --git a/.agent/skills/frontend-design/motion-graphics.md b/.agents/skills/frontend-design/motion-graphics.md similarity index 100% rename from .agent/skills/frontend-design/motion-graphics.md rename to .agents/skills/frontend-design/motion-graphics.md diff --git a/.agent/skills/frontend-design/scripts/accessibility_checker.py b/.agents/skills/frontend-design/scripts/accessibility_checker.py similarity index 100% rename from .agent/skills/frontend-design/scripts/accessibility_checker.py rename to .agents/skills/frontend-design/scripts/accessibility_checker.py diff --git a/.agent/skills/frontend-design/scripts/ux_audit.py b/.agents/skills/frontend-design/scripts/ux_audit.py similarity index 100% rename from .agent/skills/frontend-design/scripts/ux_audit.py rename to .agents/skills/frontend-design/scripts/ux_audit.py diff --git a/.agent/skills/frontend-design/typography-system.md b/.agents/skills/frontend-design/typography-system.md similarity index 100% rename from .agent/skills/frontend-design/typography-system.md rename to .agents/skills/frontend-design/typography-system.md diff --git a/.agent/skills/frontend-design/ux-psychology.md b/.agents/skills/frontend-design/ux-psychology.md similarity index 100% rename from .agent/skills/frontend-design/ux-psychology.md rename to .agents/skills/frontend-design/ux-psychology.md diff --git a/.agent/skills/frontend-design/visual-effects.md b/.agents/skills/frontend-design/visual-effects.md similarity index 100% rename from .agent/skills/frontend-design/visual-effects.md rename to .agents/skills/frontend-design/visual-effects.md diff --git a/.agent/skills/game-development/2d-games/SKILL.md b/.agents/skills/game-development/2d-games/SKILL.md similarity index 100% rename from .agent/skills/game-development/2d-games/SKILL.md rename to .agents/skills/game-development/2d-games/SKILL.md diff --git a/.agent/skills/game-development/3d-games/SKILL.md b/.agents/skills/game-development/3d-games/SKILL.md similarity index 100% rename from .agent/skills/game-development/3d-games/SKILL.md rename to .agents/skills/game-development/3d-games/SKILL.md diff --git a/.agent/skills/game-development/SKILL.md b/.agents/skills/game-development/SKILL.md similarity index 100% rename from .agent/skills/game-development/SKILL.md rename to .agents/skills/game-development/SKILL.md diff --git a/.agent/skills/game-development/game-art/SKILL.md b/.agents/skills/game-development/game-art/SKILL.md similarity index 100% rename from .agent/skills/game-development/game-art/SKILL.md rename to .agents/skills/game-development/game-art/SKILL.md diff --git a/.agent/skills/game-development/game-audio/SKILL.md b/.agents/skills/game-development/game-audio/SKILL.md similarity index 100% rename from .agent/skills/game-development/game-audio/SKILL.md rename to .agents/skills/game-development/game-audio/SKILL.md diff --git a/.agent/skills/game-development/game-design/SKILL.md b/.agents/skills/game-development/game-design/SKILL.md similarity index 100% rename from .agent/skills/game-development/game-design/SKILL.md rename to .agents/skills/game-development/game-design/SKILL.md diff --git a/.agent/skills/game-development/mobile-games/SKILL.md b/.agents/skills/game-development/mobile-games/SKILL.md similarity index 100% rename from .agent/skills/game-development/mobile-games/SKILL.md rename to .agents/skills/game-development/mobile-games/SKILL.md diff --git a/.agent/skills/game-development/multiplayer/SKILL.md b/.agents/skills/game-development/multiplayer/SKILL.md similarity index 100% rename from .agent/skills/game-development/multiplayer/SKILL.md rename to .agents/skills/game-development/multiplayer/SKILL.md diff --git a/.agent/skills/game-development/pc-games/SKILL.md b/.agents/skills/game-development/pc-games/SKILL.md similarity index 100% rename from .agent/skills/game-development/pc-games/SKILL.md rename to .agents/skills/game-development/pc-games/SKILL.md diff --git a/.agent/skills/game-development/vr-ar/SKILL.md b/.agents/skills/game-development/vr-ar/SKILL.md similarity index 100% rename from .agent/skills/game-development/vr-ar/SKILL.md rename to .agents/skills/game-development/vr-ar/SKILL.md diff --git a/.agent/skills/game-development/web-games/SKILL.md b/.agents/skills/game-development/web-games/SKILL.md similarity index 100% rename from .agent/skills/game-development/web-games/SKILL.md rename to .agents/skills/game-development/web-games/SKILL.md diff --git a/.agent/skills/geo-fundamentals/SKILL.md b/.agents/skills/geo-fundamentals/SKILL.md similarity index 100% rename from .agent/skills/geo-fundamentals/SKILL.md rename to .agents/skills/geo-fundamentals/SKILL.md diff --git a/.agent/skills/geo-fundamentals/scripts/geo_checker.py b/.agents/skills/geo-fundamentals/scripts/geo_checker.py similarity index 100% rename from .agent/skills/geo-fundamentals/scripts/geo_checker.py rename to .agents/skills/geo-fundamentals/scripts/geo_checker.py diff --git a/.agent/skills/i18n-localization/SKILL.md b/.agents/skills/i18n-localization/SKILL.md similarity index 100% rename from .agent/skills/i18n-localization/SKILL.md rename to .agents/skills/i18n-localization/SKILL.md diff --git a/.agent/skills/i18n-localization/scripts/i18n_checker.py b/.agents/skills/i18n-localization/scripts/i18n_checker.py similarity index 100% rename from .agent/skills/i18n-localization/scripts/i18n_checker.py rename to .agents/skills/i18n-localization/scripts/i18n_checker.py diff --git a/.agent/skills/intelligent-routing/SKILL.md b/.agents/skills/intelligent-routing/SKILL.md similarity index 100% rename from .agent/skills/intelligent-routing/SKILL.md rename to .agents/skills/intelligent-routing/SKILL.md diff --git a/.agent/skills/lint-and-validate/SKILL.md b/.agents/skills/lint-and-validate/SKILL.md similarity index 100% rename from .agent/skills/lint-and-validate/SKILL.md rename to .agents/skills/lint-and-validate/SKILL.md diff --git a/.agent/skills/lint-and-validate/scripts/lint_runner.py b/.agents/skills/lint-and-validate/scripts/lint_runner.py similarity index 100% rename from .agent/skills/lint-and-validate/scripts/lint_runner.py rename to .agents/skills/lint-and-validate/scripts/lint_runner.py diff --git a/.agent/skills/lint-and-validate/scripts/type_coverage.py b/.agents/skills/lint-and-validate/scripts/type_coverage.py similarity index 100% rename from .agent/skills/lint-and-validate/scripts/type_coverage.py rename to .agents/skills/lint-and-validate/scripts/type_coverage.py diff --git a/.agent/skills/mcp-builder/SKILL.md b/.agents/skills/mcp-builder/SKILL.md similarity index 100% rename from .agent/skills/mcp-builder/SKILL.md rename to .agents/skills/mcp-builder/SKILL.md diff --git a/.agent/skills/mobile-design/SKILL.md b/.agents/skills/mobile-design/SKILL.md similarity index 100% rename from .agent/skills/mobile-design/SKILL.md rename to .agents/skills/mobile-design/SKILL.md diff --git a/.agent/skills/mobile-design/decision-trees.md b/.agents/skills/mobile-design/decision-trees.md similarity index 100% rename from .agent/skills/mobile-design/decision-trees.md rename to .agents/skills/mobile-design/decision-trees.md diff --git a/.agent/skills/mobile-design/mobile-backend.md b/.agents/skills/mobile-design/mobile-backend.md similarity index 100% rename from .agent/skills/mobile-design/mobile-backend.md rename to .agents/skills/mobile-design/mobile-backend.md diff --git a/.agent/skills/mobile-design/mobile-color-system.md b/.agents/skills/mobile-design/mobile-color-system.md similarity index 100% rename from .agent/skills/mobile-design/mobile-color-system.md rename to .agents/skills/mobile-design/mobile-color-system.md diff --git a/.agent/skills/mobile-design/mobile-debugging.md b/.agents/skills/mobile-design/mobile-debugging.md similarity index 100% rename from .agent/skills/mobile-design/mobile-debugging.md rename to .agents/skills/mobile-design/mobile-debugging.md diff --git a/.agent/skills/mobile-design/mobile-design-thinking.md b/.agents/skills/mobile-design/mobile-design-thinking.md similarity index 100% rename from .agent/skills/mobile-design/mobile-design-thinking.md rename to .agents/skills/mobile-design/mobile-design-thinking.md diff --git a/.agent/skills/mobile-design/mobile-navigation.md b/.agents/skills/mobile-design/mobile-navigation.md similarity index 100% rename from .agent/skills/mobile-design/mobile-navigation.md rename to .agents/skills/mobile-design/mobile-navigation.md diff --git a/.agent/skills/mobile-design/mobile-performance.md b/.agents/skills/mobile-design/mobile-performance.md similarity index 100% rename from .agent/skills/mobile-design/mobile-performance.md rename to .agents/skills/mobile-design/mobile-performance.md diff --git a/.agent/skills/mobile-design/mobile-testing.md b/.agents/skills/mobile-design/mobile-testing.md similarity index 100% rename from .agent/skills/mobile-design/mobile-testing.md rename to .agents/skills/mobile-design/mobile-testing.md diff --git a/.agent/skills/mobile-design/mobile-typography.md b/.agents/skills/mobile-design/mobile-typography.md similarity index 100% rename from .agent/skills/mobile-design/mobile-typography.md rename to .agents/skills/mobile-design/mobile-typography.md diff --git a/.agent/skills/mobile-design/platform-android.md b/.agents/skills/mobile-design/platform-android.md similarity index 100% rename from .agent/skills/mobile-design/platform-android.md rename to .agents/skills/mobile-design/platform-android.md diff --git a/.agent/skills/mobile-design/platform-ios.md b/.agents/skills/mobile-design/platform-ios.md similarity index 100% rename from .agent/skills/mobile-design/platform-ios.md rename to .agents/skills/mobile-design/platform-ios.md diff --git a/.agent/skills/mobile-design/scripts/mobile_audit.py b/.agents/skills/mobile-design/scripts/mobile_audit.py similarity index 100% rename from .agent/skills/mobile-design/scripts/mobile_audit.py rename to .agents/skills/mobile-design/scripts/mobile_audit.py diff --git a/.agent/skills/mobile-design/touch-psychology.md b/.agents/skills/mobile-design/touch-psychology.md similarity index 100% rename from .agent/skills/mobile-design/touch-psychology.md rename to .agents/skills/mobile-design/touch-psychology.md diff --git a/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md b/.agents/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md similarity index 100% rename from .agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md rename to .agents/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md diff --git a/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md b/.agents/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md similarity index 100% rename from .agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md rename to .agents/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md diff --git a/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md b/.agents/skills/nextjs-react-expert/3-server-server-side-performance.md similarity index 100% rename from .agent/skills/nextjs-react-expert/3-server-server-side-performance.md rename to .agents/skills/nextjs-react-expert/3-server-server-side-performance.md diff --git a/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md b/.agents/skills/nextjs-react-expert/4-client-client-side-data-fetching.md similarity index 100% rename from .agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md rename to .agents/skills/nextjs-react-expert/4-client-client-side-data-fetching.md diff --git a/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md b/.agents/skills/nextjs-react-expert/5-rerender-re-render-optimization.md similarity index 100% rename from .agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md rename to .agents/skills/nextjs-react-expert/5-rerender-re-render-optimization.md diff --git a/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md b/.agents/skills/nextjs-react-expert/6-rendering-rendering-performance.md similarity index 100% rename from .agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md rename to .agents/skills/nextjs-react-expert/6-rendering-rendering-performance.md diff --git a/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md b/.agents/skills/nextjs-react-expert/7-js-javascript-performance.md similarity index 100% rename from .agent/skills/nextjs-react-expert/7-js-javascript-performance.md rename to .agents/skills/nextjs-react-expert/7-js-javascript-performance.md diff --git a/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md b/.agents/skills/nextjs-react-expert/8-advanced-advanced-patterns.md similarity index 100% rename from .agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md rename to .agents/skills/nextjs-react-expert/8-advanced-advanced-patterns.md diff --git a/.agent/skills/nextjs-react-expert/SKILL.md b/.agents/skills/nextjs-react-expert/SKILL.md similarity index 100% rename from .agent/skills/nextjs-react-expert/SKILL.md rename to .agents/skills/nextjs-react-expert/SKILL.md diff --git a/.agent/skills/nextjs-react-expert/scripts/convert_rules.py b/.agents/skills/nextjs-react-expert/scripts/convert_rules.py similarity index 100% rename from .agent/skills/nextjs-react-expert/scripts/convert_rules.py rename to .agents/skills/nextjs-react-expert/scripts/convert_rules.py diff --git a/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py b/.agents/skills/nextjs-react-expert/scripts/react_performance_checker.py similarity index 100% rename from .agent/skills/nextjs-react-expert/scripts/react_performance_checker.py rename to .agents/skills/nextjs-react-expert/scripts/react_performance_checker.py diff --git a/.agent/skills/nodejs-best-practices/SKILL.md b/.agents/skills/nodejs-best-practices/SKILL.md similarity index 100% rename from .agent/skills/nodejs-best-practices/SKILL.md rename to .agents/skills/nodejs-best-practices/SKILL.md diff --git a/.agent/skills/parallel-agents/SKILL.md b/.agents/skills/parallel-agents/SKILL.md similarity index 100% rename from .agent/skills/parallel-agents/SKILL.md rename to .agents/skills/parallel-agents/SKILL.md diff --git a/.agent/skills/performance-profiling/SKILL.md b/.agents/skills/performance-profiling/SKILL.md similarity index 100% rename from .agent/skills/performance-profiling/SKILL.md rename to .agents/skills/performance-profiling/SKILL.md diff --git a/.agent/skills/performance-profiling/scripts/lighthouse_audit.py b/.agents/skills/performance-profiling/scripts/lighthouse_audit.py similarity index 100% rename from .agent/skills/performance-profiling/scripts/lighthouse_audit.py rename to .agents/skills/performance-profiling/scripts/lighthouse_audit.py diff --git a/.agent/skills/plan-writing/SKILL.md b/.agents/skills/plan-writing/SKILL.md similarity index 100% rename from .agent/skills/plan-writing/SKILL.md rename to .agents/skills/plan-writing/SKILL.md diff --git a/.agent/skills/powershell-windows/SKILL.md b/.agents/skills/powershell-windows/SKILL.md similarity index 100% rename from .agent/skills/powershell-windows/SKILL.md rename to .agents/skills/powershell-windows/SKILL.md diff --git a/.agent/skills/python-patterns/SKILL.md b/.agents/skills/python-patterns/SKILL.md similarity index 100% rename from .agent/skills/python-patterns/SKILL.md rename to .agents/skills/python-patterns/SKILL.md diff --git a/.agent/skills/red-team-tactics/SKILL.md b/.agents/skills/red-team-tactics/SKILL.md similarity index 100% rename from .agent/skills/red-team-tactics/SKILL.md rename to .agents/skills/red-team-tactics/SKILL.md diff --git a/.agent/skills/refactoring-patterns/SKILL.md b/.agents/skills/refactoring-patterns/SKILL.md similarity index 100% rename from .agent/skills/refactoring-patterns/SKILL.md rename to .agents/skills/refactoring-patterns/SKILL.md diff --git a/.agent/skills/rust-pro/SKILL.md b/.agents/skills/rust-pro/SKILL.md similarity index 100% rename from .agent/skills/rust-pro/SKILL.md rename to .agents/skills/rust-pro/SKILL.md diff --git a/.agent/skills/seo-fundamentals/SKILL.md b/.agents/skills/seo-fundamentals/SKILL.md similarity index 100% rename from .agent/skills/seo-fundamentals/SKILL.md rename to .agents/skills/seo-fundamentals/SKILL.md diff --git a/.agent/skills/seo-fundamentals/scripts/seo_checker.py b/.agents/skills/seo-fundamentals/scripts/seo_checker.py similarity index 100% rename from .agent/skills/seo-fundamentals/scripts/seo_checker.py rename to .agents/skills/seo-fundamentals/scripts/seo_checker.py diff --git a/.agent/skills/server-management/SKILL.md b/.agents/skills/server-management/SKILL.md similarity index 100% rename from .agent/skills/server-management/SKILL.md rename to .agents/skills/server-management/SKILL.md diff --git a/.agent/skills/systematic-debugging/SKILL.md b/.agents/skills/systematic-debugging/SKILL.md similarity index 100% rename from .agent/skills/systematic-debugging/SKILL.md rename to .agents/skills/systematic-debugging/SKILL.md diff --git a/.agent/skills/tailwind-patterns/SKILL.md b/.agents/skills/tailwind-patterns/SKILL.md similarity index 100% rename from .agent/skills/tailwind-patterns/SKILL.md rename to .agents/skills/tailwind-patterns/SKILL.md diff --git a/.agent/skills/tdd-workflow/SKILL.md b/.agents/skills/tdd-workflow/SKILL.md similarity index 100% rename from .agent/skills/tdd-workflow/SKILL.md rename to .agents/skills/tdd-workflow/SKILL.md diff --git a/.agent/skills/testing-patterns/SKILL.md b/.agents/skills/testing-patterns/SKILL.md similarity index 100% rename from .agent/skills/testing-patterns/SKILL.md rename to .agents/skills/testing-patterns/SKILL.md diff --git a/.agent/skills/testing-patterns/scripts/test_runner.py b/.agents/skills/testing-patterns/scripts/test_runner.py similarity index 100% rename from .agent/skills/testing-patterns/scripts/test_runner.py rename to .agents/skills/testing-patterns/scripts/test_runner.py diff --git a/.agent/skills/vulnerability-scanner/SKILL.md b/.agents/skills/vulnerability-scanner/SKILL.md similarity index 100% rename from .agent/skills/vulnerability-scanner/SKILL.md rename to .agents/skills/vulnerability-scanner/SKILL.md diff --git a/.agent/skills/vulnerability-scanner/checklists.md b/.agents/skills/vulnerability-scanner/checklists.md similarity index 100% rename from .agent/skills/vulnerability-scanner/checklists.md rename to .agents/skills/vulnerability-scanner/checklists.md diff --git a/.agent/skills/vulnerability-scanner/scripts/security_scan.py b/.agents/skills/vulnerability-scanner/scripts/security_scan.py similarity index 100% rename from .agent/skills/vulnerability-scanner/scripts/security_scan.py rename to .agents/skills/vulnerability-scanner/scripts/security_scan.py diff --git a/.agent/skills/web-design-guidelines/SKILL.md b/.agents/skills/web-design-guidelines/SKILL.md similarity index 100% rename from .agent/skills/web-design-guidelines/SKILL.md rename to .agents/skills/web-design-guidelines/SKILL.md diff --git a/.agent/skills/webapp-testing/SKILL.md b/.agents/skills/webapp-testing/SKILL.md similarity index 100% rename from .agent/skills/webapp-testing/SKILL.md rename to .agents/skills/webapp-testing/SKILL.md diff --git a/.agent/skills/webapp-testing/scripts/playwright_runner.py b/.agents/skills/webapp-testing/scripts/playwright_runner.py similarity index 100% rename from .agent/skills/webapp-testing/scripts/playwright_runner.py rename to .agents/skills/webapp-testing/scripts/playwright_runner.py diff --git a/.agent/workflows/brainstorm.md b/.agents/workflows/brainstorm.md similarity index 100% rename from .agent/workflows/brainstorm.md rename to .agents/workflows/brainstorm.md diff --git a/.agent/workflows/create.md b/.agents/workflows/create.md similarity index 100% rename from .agent/workflows/create.md rename to .agents/workflows/create.md diff --git a/.agent/workflows/debug.md b/.agents/workflows/debug.md similarity index 100% rename from .agent/workflows/debug.md rename to .agents/workflows/debug.md diff --git a/.agent/workflows/deploy.md b/.agents/workflows/deploy.md similarity index 100% rename from .agent/workflows/deploy.md rename to .agents/workflows/deploy.md diff --git a/.agent/workflows/enhance.md b/.agents/workflows/enhance.md similarity index 100% rename from .agent/workflows/enhance.md rename to .agents/workflows/enhance.md diff --git a/.agent/workflows/orchestrate.md b/.agents/workflows/orchestrate.md similarity index 100% rename from .agent/workflows/orchestrate.md rename to .agents/workflows/orchestrate.md diff --git a/.agent/workflows/plan.md b/.agents/workflows/plan.md similarity index 100% rename from .agent/workflows/plan.md rename to .agents/workflows/plan.md diff --git a/.agent/workflows/preview.md b/.agents/workflows/preview.md similarity index 100% rename from .agent/workflows/preview.md rename to .agents/workflows/preview.md diff --git a/.agent/workflows/restore-localize-compat.md b/.agents/workflows/restore-localize-compat.md similarity index 100% rename from .agent/workflows/restore-localize-compat.md rename to .agents/workflows/restore-localize-compat.md diff --git a/.agent/workflows/status.md b/.agents/workflows/status.md similarity index 100% rename from .agent/workflows/status.md rename to .agents/workflows/status.md diff --git a/.agent/workflows/test.md b/.agents/workflows/test.md similarity index 100% rename from .agent/workflows/test.md rename to .agents/workflows/test.md diff --git a/.agent/workflows/ui-ux-pro-max.md b/.agents/workflows/ui-ux-pro-max.md similarity index 100% rename from .agent/workflows/ui-ux-pro-max.md rename to .agents/workflows/ui-ux-pro-max.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..030ad6d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + node: [20, 22] + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.9" + + - name: Install dependencies + run: bun install + + - name: Run tests + run: bun run test + + - name: CI end-to-end verify + run: bun run ci:verify diff --git a/AGENTS.md b/AGENTS.md index 988a8cb..f5c3a11 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ - `tests/`:Node 内置测试(`*.test.js`),覆盖 CLI、适配器、生成器、清理与健康检查。 - `docs/` 与 `reference/`:规范文档与参考资料。 - `web/`:Next.js 文档站(`web/src` 源码,`web/public` 静态资源)。 -- `.agent/`:模板资源源文件,供 CLI 安装到目标项目。 +- `.agents/`:模板资源源文件(Canonical),供 CLI 投影生成目标项目结构(Gemini -> `.agent/`,Codex -> `.agents/`)。 ## 构建、测试与开发命令 - 根项目依赖安装:`bun install`(如需兼容可用 `npm install`)。 diff --git a/README.md b/README.md index 769c942..a0041ca 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,25 @@ npm install -g . 这会把所选目标结构安装到你的项目中(`gemini -> .agent`,`codex -> .agents`),并把 Codex 托管内容注入工作区 `AGENTS.md` 与 `antigravity.rules`(说明性托管区块,不是 Codex 官方 `.rules` 审批策略文件)。 +### 全局安装(跨项目复用 Skills) + +为区分“项目安装”和“全局安装”,提供专用命令面: + +- 项目安装:`ag-kit init` / `ag-kit update`(功能最完整) +- 全局安装:`ag-kit global sync`(仅同步 Skills,跨项目复用) + +示例: + +```bash +ag-kit global sync --target codex +ag-kit global sync --target gemini +ag-kit global status +``` + +全局安装只同步 Skills,不写入 Rules/Agents/Workflows,避免全局副作用。 + +规划与边界细节见:`docs/plan-global-install.md` + ### Codex 规则边界说明 - `antigravity.rules`:本项目生成并注入的托管说明文件,用于记录受管资源与运维约束。 diff --git a/bin/adapters/codex.js b/bin/adapters/codex.js index 866c9d9..fe2d151 100644 --- a/bin/adapters/codex.js +++ b/bin/adapters/codex.js @@ -132,16 +132,17 @@ class CodexAdapter extends BaseAdapter { const CodexBuilder = require("../core/builder"); let buildTemp = ""; - const hasAgentDir = fs.existsSync(path.join(installSource, ".agent")); + const hasAgentsRoot = fs.existsSync(path.join(installSource, ".agents")); + const hasLegacyAgentRoot = fs.existsSync(path.join(installSource, ".agent")); const hasSkillsDir = fs.existsSync(path.join(installSource, "skills")); const isCodexPrebuilt = fs.existsSync(path.join(installSource, "manifest.json")); if (!isCodexPrebuilt) { if (hasSkillsDir) { - this.log("🛠️ 检测到 .agent 内容格式,正在构建 Codex 结构..."); + this.log("🛠️ 检测到模板目录格式,正在构建 Codex 结构..."); const mockRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-root-")); - const mockAgent = path.join(mockRoot, ".agent"); - this._copyDir(installSource, mockAgent); + const mockAgents = path.join(mockRoot, ".agents"); + this._copyDir(installSource, mockAgents); buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-out-")); CodexBuilder.build(mockRoot, buildTemp); @@ -154,8 +155,8 @@ class CodexAdapter extends BaseAdapter { fs.rmSync(mockRoot, { recursive: true, force: true }); fs.rmSync(buildTemp, { recursive: true, force: true }); }; - } else if (hasAgentDir) { - this.log("🛠️ 检测到仓库根目录格式,正在构建 Codex 结构..."); + } else if (hasAgentsRoot) { + this.log("🛠️ 检测到仓库根目录格式(.agents),正在构建 Codex 结构..."); buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-out-")); CodexBuilder.build(installSource, buildTemp); installSource = buildTemp; @@ -166,6 +167,23 @@ class CodexAdapter extends BaseAdapter { if (previousCleanup) previousCleanup(); fs.rmSync(buildTemp, { recursive: true, force: true }); }; + } else if (hasLegacyAgentRoot) { + this.log("🛠️ 检测到旧版仓库根目录格式(.agent),正在构建 Codex 结构..."); + const mockRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-root-")); + const mockAgents = path.join(mockRoot, ".agents"); + this._copyDir(path.join(installSource, ".agent"), mockAgents); + + buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-out-")); + CodexBuilder.build(mockRoot, buildTemp); + installSource = buildTemp; + sourceLabel = `${sourceLabel}:compiled`; + + const previousCleanup = cleanup; + cleanup = () => { + if (previousCleanup) previousCleanup(); + fs.rmSync(mockRoot, { recursive: true, force: true }); + fs.rmSync(buildTemp, { recursive: true, force: true }); + }; } } diff --git a/bin/ag-kit.js b/bin/ag-kit.js index 2fee242..9a3a4e4 100755 --- a/bin/ag-kit.js +++ b/bin/ag-kit.js @@ -5,12 +5,15 @@ const os = require("os"); const path = require("path"); const pkg = require("../package.json"); -const { readGlobalNpmDependencies } = require("./utils"); +const { readGlobalNpmDependencies, cloneBranchAgentDir } = require("./utils"); +const ManifestManager = require("./utils/manifest"); +const AtomicWriter = require("./utils/atomic-writer"); +const CodexBuilder = require("./core/builder"); const GeminiAdapter = require("./adapters/gemini"); const CodexAdapter = require("./adapters/codex"); const { selectTargets } = require("./interactive"); -const BUNDLED_AGENT_DIR = path.resolve(__dirname, "../.agent"); +const BUNDLED_AGENT_DIR = path.resolve(__dirname, "../.agents"); const WORKSPACE_INDEX_VERSION = 2; const UPSTREAM_GLOBAL_PACKAGE = "@vudovn/ag-kit"; const TOOLKIT_PACKAGE_NAMES = new Set(["@mison/ag-kit-cn", "antigravity-kit-cn", "antigravity-kit"]); @@ -40,12 +43,68 @@ function createEmptyWorkspaceIndex() { }; } +function resolveGlobalRootDir() { + const customRoot = process.env.AG_KIT_GLOBAL_ROOT; + if (typeof customRoot === "string" && customRoot.trim()) { + return path.resolve(process.cwd(), customRoot); + } + return os.homedir(); +} + +function resolveGlobalSkillRoot(targetName) { + const globalRoot = resolveGlobalRootDir(); + if (targetName === "codex") { + return path.join(globalRoot, ".agents", "skills"); + } + if (targetName === "gemini") { + return path.join(globalRoot, ".gemini", "antigravity", "skills"); + } + throw new Error(`未知目标: ${targetName}`); +} + +function resolveGlobalBackupRoot(timestamp) { + const globalRoot = resolveGlobalRootDir(); + return path.join(globalRoot, ".ag-kit", "backups", "global", timestamp); +} + +function copyDirRecursive(src, dest) { + fs.mkdirSync(dest, { recursive: true }); + const entries = fs.readdirSync(src, { withFileTypes: true }); + for (const entry of entries) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + if (entry.isDirectory()) { + copyDirRecursive(srcPath, destPath); + } else { + fs.copyFileSync(srcPath, destPath); + } + } +} + +function areDirectoriesEqual(leftDir, rightDir) { + const left = ManifestManager.generateFromDir(leftDir); + const right = ManifestManager.generateFromDir(rightDir); + const leftKeys = Object.keys(left); + const rightKeys = Object.keys(right); + if (leftKeys.length !== rightKeys.length) { + return false; + } + for (const key of leftKeys) { + if (left[key] !== right[key]) { + return false; + } + } + return true; +} + function printUsage() { console.log("用法:"); console.log(" ag-kit init [--force] [--path ] [--branch ] [--target |--targets ] [--non-interactive] [--no-index] [--quiet] [--dry-run]"); console.log(" ag-kit update [--path ] [--branch ] [--target |--targets ] [--no-index] [--quiet] [--dry-run]"); console.log(" ag-kit update-all [--branch ] [--targets ] [--prune-missing] [--quiet] [--dry-run]"); console.log(" ag-kit doctor [--path ] [--target |--targets ] [--fix] [--quiet]"); + console.log(" ag-kit global sync [--target |--targets ] [--branch ] [--quiet] [--dry-run]"); + console.log(" ag-kit global status [--quiet]"); console.log(" ag-kit exclude list [--quiet]"); console.log(" ag-kit exclude add --path [--dry-run] [--quiet]"); console.log(" ag-kit exclude remove --path [--dry-run] [--quiet]"); @@ -79,12 +138,12 @@ function parseArgs(argv) { const providedFlags = []; let startIndex = 1; - if (command === "exclude") { + if (command === "exclude" || command === "global") { if (argv.length > 1 && !argv[1].startsWith("--")) { options.subcommand = argv[1]; startIndex = 2; } else { - options.subcommand = "list"; + options.subcommand = command === "global" ? "status" : "list"; startIndex = 1; } } @@ -151,6 +210,8 @@ const COMMAND_ALLOWED_FLAGS = { "update-all": ["--branch", "--targets", "--prune-missing", "--quiet", "--dry-run"], doctor: ["--path", "--target", "--targets", "--fix", "--quiet"], status: ["--path", "--quiet"], + "global:sync": ["--target", "--targets", "--branch", "--quiet", "--dry-run"], + "global:status": ["--quiet"], "exclude:list": ["--quiet"], "exclude:add": ["--path", "--dry-run", "--quiet"], "exclude:remove": ["--path", "--dry-run", "--quiet"], @@ -162,6 +223,11 @@ function resolveAllowedFlags(command, options) { const key = `exclude:${subcommand}`; return COMMAND_ALLOWED_FLAGS[key] || null; } + if (command === "global") { + const subcommand = String(options.subcommand || "status").toLowerCase(); + const key = `global:${subcommand}`; + return COMMAND_ALLOWED_FLAGS[key] || null; + } return COMMAND_ALLOWED_FLAGS[command] || null; } @@ -170,6 +236,10 @@ function resolveCommandLabel(command, options) { const subcommand = String(options.subcommand || "list").toLowerCase(); return `exclude ${subcommand}`; } + if (command === "global") { + const subcommand = String(options.subcommand || "status").toLowerCase(); + return `global ${subcommand}`; + } return command; } @@ -776,6 +846,220 @@ function resolveTargetsForUpdate(workspaceRoot, options) { return detectInstalledTargets(workspaceRoot); } +function resolveTargetsForGlobalSync(options) { + return resolveTargetsForInit(options); +} + +function resolveAgentInstallSource(options) { + let agentDir = BUNDLED_AGENT_DIR; + let cleanup = null; + let sourceLabel = "bundled"; + + if (options.branch) { + const remote = cloneBranchAgentDir(options.branch, { + quiet: options.quiet, + logger: log.bind(null, options), + }); + agentDir = remote.agentDir; + cleanup = remote.cleanup; + sourceLabel = `branch:${options.branch}`; + } + + if (!fs.existsSync(agentDir) && !options.branch) { + const legacyDir = path.resolve(__dirname, "../.agent"); + if (fs.existsSync(legacyDir)) { + agentDir = legacyDir; + sourceLabel = "bundled:legacy"; + } + } + + if (!fs.existsSync(agentDir)) { + throw new Error(`未找到模板目录: ${agentDir}`); + } + + return { agentDir, cleanup, sourceLabel }; +} + +function listSkillDirectories(skillsRoot) { + if (!fs.existsSync(skillsRoot)) { + return []; + } + return fs + .readdirSync(skillsRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .filter((name) => fs.existsSync(path.join(skillsRoot, name, "SKILL.md"))); +} + +function backupSkillDirectory(targetName, skillName, sourceDir, timestamp, options) { + const backupRoot = resolveGlobalBackupRoot(timestamp); + const backupDir = path.join(backupRoot, targetName, skillName); + fs.mkdirSync(path.dirname(backupDir), { recursive: true }); + copyDirRecursive(sourceDir, backupDir); + log(options, `📦 已备份 ${targetName} 全局 Skill: ${skillName} -> ${backupDir}`); +} + +function syncSkillDirectory(targetName, srcDir, destDir, timestamp, options) { + const exists = fs.existsSync(destDir); + if (exists) { + if (areDirectoriesEqual(srcDir, destDir)) { + log(options, `⏭️ 全局 Skill 已最新,无需同步: ${targetName}/${path.basename(destDir)}`); + return { skipped: 1, synced: 0, backedUp: 0 }; + } + } + + if (options.dryRun) { + log(options, `[dry-run] 将同步全局 Skill: ${targetName}/${path.basename(destDir)}`); + return { skipped: 0, synced: 0, backedUp: exists ? 1 : 0 }; + } + + let backedUp = 0; + if (exists) { + backupSkillDirectory(targetName, path.basename(destDir), destDir, timestamp, options); + backedUp = 1; + } + + const logger = options.quiet ? (() => {}) : log.bind(null, options); + AtomicWriter.atomicCopyDir(srcDir, destDir, { logger }); + log(options, `✅ 已同步全局 Skill: ${targetName}/${path.basename(destDir)}`); + + return { skipped: 0, synced: 1, backedUp }; +} + +function syncGlobalSkillsFromRoot(targetName, skillsRoot, timestamp, options) { + const destRoot = resolveGlobalSkillRoot(targetName); + const skillNames = listSkillDirectories(skillsRoot); + if (skillNames.length === 0) { + throw new Error(`未检测到可同步的 Skills: ${skillsRoot}`); + } + + if (options.dryRun) { + log(options, `[dry-run] 将同步 ${skillNames.length} 个全局 Skills -> ${destRoot}`); + } + + let synced = 0; + let skipped = 0; + let backedUp = 0; + + for (const skillName of skillNames) { + const srcDir = path.join(skillsRoot, skillName); + const destDir = path.join(destRoot, skillName); + const result = syncSkillDirectory(targetName, srcDir, destDir, timestamp, options); + synced += result.synced; + skipped += result.skipped; + backedUp += result.backedUp; + } + + return { total: skillNames.length, synced, skipped, backedUp, destRoot }; +} + +function applyGlobalSync(targetName, agentDir, timestamp, options) { + if (targetName === "codex") { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-global-codex-")); + const mockRoot = path.join(tempRoot, "source"); + const mockAgent = path.join(mockRoot, ".agents"); + const outputDir = path.join(tempRoot, "out"); + + try { + copyDirRecursive(agentDir, mockAgent); + CodexBuilder.build(mockRoot, outputDir); + const skillsRoot = path.join(outputDir, "skills"); + return syncGlobalSkillsFromRoot(targetName, skillsRoot, timestamp, options); + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + } + + if (targetName === "gemini") { + const skillsRoot = path.join(agentDir, "skills"); + return syncGlobalSkillsFromRoot(targetName, skillsRoot, timestamp, options); + } + + throw new Error(`未知目标: ${targetName}`); +} + +function detectInstalledGlobalTargets() { + const targets = []; + const globalRoot = resolveGlobalRootDir(); + if (fs.existsSync(path.join(globalRoot, ".agents", "skills"))) { + targets.push("codex"); + } + if (fs.existsSync(path.join(globalRoot, ".gemini", "antigravity", "skills"))) { + targets.push("gemini"); + } + return targets; +} + +async function commandGlobalSync(options) { + const targets = await resolveTargetsForGlobalSync(options); + const { agentDir, cleanup, sourceLabel } = resolveAgentInstallSource(options); + const timestamp = nowISO().replace(/[:.]/g, "-"); + + try { + log(options, `🌍 全局同步源: ${sourceLabel}`); + for (const target of targets) { + log(options, `📦 正在同步全局目标 [${target}] ...`); + const result = applyGlobalSync(target, agentDir, timestamp, options); + if (!options.dryRun) { + log(options, `📊 全局同步完成 [${target}]:总计 ${result.total},新增/覆盖 ${result.synced},跳过 ${result.skipped},备份 ${result.backedUp}`); + log(options, ` 目标路径: ${result.destRoot}`); + } + } + } finally { + if (cleanup) cleanup(); + } +} + +function commandGlobalStatus(options) { + const targets = detectInstalledGlobalTargets(); + const globalRoot = resolveGlobalRootDir(); + + if (targets.length === 0) { + if (!options.quiet) { + console.log("❌ 未检测到全局安装的 Skills"); + console.log(` 全局根目录: ${globalRoot}`); + } + process.exitCode = 1; + return; + } + + if (options.quiet) { + console.log("installed"); + return; + } + + console.log("✅ 已检测到全局 Skills 安装"); + console.log(` 全局根目录: ${globalRoot}`); + console.log(` Targets: ${targets.join(", ")}`); + + if (targets.includes("gemini")) { + const skillsRoot = path.join(globalRoot, ".gemini", "antigravity", "skills"); + const skillsCount = countSkillsRecursive(skillsRoot); + console.log("\n[gemini:global]"); + console.log(` 路径: ${skillsRoot}`); + console.log(` Skills: ${skillsCount}`); + } + + if (targets.includes("codex")) { + const skillsRoot = path.join(globalRoot, ".agents", "skills"); + const skillsCount = countSkillsRecursive(skillsRoot); + console.log("\n[codex:global]"); + console.log(` 路径: ${skillsRoot}`); + console.log(` Skills: ${skillsCount}`); + } +} + +function commandGlobal(options) { + const subcommand = String(options.subcommand || "status").toLowerCase(); + if (subcommand === "sync") { + return commandGlobalSync(options); + } + if (subcommand === "status") { + return commandGlobalStatus(options); + } + throw new Error(`未知 global 子命令: ${subcommand}`); +} + async function commandInit(options) { const workspaceRoot = resolveWorkspaceRoot(options.path); const targets = await resolveTargetsForInit(options); @@ -1314,6 +1598,11 @@ async function main() { return; } + if (command === "global") { + await commandGlobal(options); + return; + } + if (command === "exclude") { commandExclude(options); return; diff --git a/bin/core/builder.js b/bin/core/builder.js index 35f2f27..5bdeee4 100644 --- a/bin/core/builder.js +++ b/bin/core/builder.js @@ -9,7 +9,7 @@ const pkg = require("../../package.json"); class CodexBuilder { /** * Build a Codex structure from a legacy/source repository - * @param {string} sourceRoot Root of the repo (containing .agent/) + * @param {string} sourceRoot Root of the repo (containing .agents/) * @param {string} outputDir Directory to output the built .agents-compatible structure */ static build(sourceRoot, outputDir) { diff --git a/bin/core/resource-loader.js b/bin/core/resource-loader.js index f01bfd6..ab6adb4 100644 --- a/bin/core/resource-loader.js +++ b/bin/core/resource-loader.js @@ -4,8 +4,8 @@ const path = require("path"); class ResourceLoader { constructor(rootDir) { this.rootDir = rootDir; - this.skillsDir = path.join(rootDir, ".agent", "skills"); - this.workflowsDir = path.join(rootDir, ".agent", "workflows"); + this.skillsDir = path.join(rootDir, ".agents", "skills"); + this.workflowsDir = path.join(rootDir, ".agents", "workflows"); } /** diff --git a/bin/utils.js b/bin/utils.js index b9056e2..eacc619 100644 --- a/bin/utils.js +++ b/bin/utils.js @@ -63,14 +63,21 @@ function cloneBranchAgentDir(branch, options) { throw new Error(`无法拉取分支 ${safeBranch},请确认分支存在且网络可用`); } + const clonedAgentsDir = path.join(tempDir, ".agents"); const clonedAgentDir = path.join(tempDir, ".agent"); - if (!fs.existsSync(clonedAgentDir)) { + let templateDir = ""; + + if (fs.existsSync(clonedAgentsDir)) { + templateDir = clonedAgentsDir; + } else if (fs.existsSync(clonedAgentDir)) { + templateDir = clonedAgentDir; + } else { fs.rmSync(tempDir, { recursive: true, force: true }); - throw new Error(`分支 ${safeBranch} 中未找到 .agent 目录`); + throw new Error(`分支 ${safeBranch} 中未找到 .agents 或 .agent 目录`); } return { - agentDir: clonedAgentDir, + agentDir: templateDir, cleanup: () => fs.rmSync(tempDir, { recursive: true, force: true }), }; } diff --git a/docs/mapping-spec.md b/docs/mapping-spec.md index c567175..a683650 100644 --- a/docs/mapping-spec.md +++ b/docs/mapping-spec.md @@ -6,15 +6,17 @@ Codex(代码智能体环境)适配器包含一个内置的资源转换层( ### 1. Skill(技能) -- **源路径**: `.agent/skills//SKILL.md` +- **源路径**: `.agents/skills//SKILL.md` - **Codex ID(标识)**: ``(保持与上游技能名一致) - **目标路径**: `.agents/skills//SKILL.md` +- **兼容**: 仍支持旧版 `.agent/skills//SKILL.md` 作为输入。 ### 2. Workflow(工作流) -- **源路径**: `.agent/workflows/.md` +- **源路径**: `.agents/workflows/.md` - **Codex ID(标识)**: `workflow-` - **目标路径**: `.agents/skills/workflow-/SKILL.md` +- **兼容**: 仍支持旧版 `.agent/workflows/.md` 作为输入。 - **说明**: 工作流会转换为符合 Codex 规范的 `SKILL.md`(自动补齐 `name` / `description` Frontmatter(文档头元数据))。 - **冲突处理**: 若生成 ID 与现有 Skill/Workflow 冲突,构建器会自动追加 `-2`、`-3`... 后缀,确保 ID 和目录唯一。 diff --git a/docs/multi-target-adapter.md b/docs/multi-target-adapter.md index db482ad..2bcb504 100644 --- a/docs/multi-target-adapter.md +++ b/docs/multi-target-adapter.md @@ -13,11 +13,11 @@ graph TD ## 支持的目标 -### 1. Gemini(旧版) +### 1. Gemini(兼容输出) - **标识**: `gemini` -- **存储**: `.agent/`(直连模式) -- **特点**: 轻量级,直接克隆自 Git 仓库或本地模板。 +- **存储**: `.agent/`(兼容输出) +- **特点**: 轻量级,由模板源 `.agents/` 投影生成。 - **适用**: 个人开发者、快速原型、不强制版本控制的场景。 ### 2. Codex(代码智能体环境) @@ -36,6 +36,11 @@ graph TD > 说明:`antigravity.rules` 是 Ag-Kit 注入的托管说明内容,不等同于 Codex 官方 `.rules`(命令审批执行规则)。 +## 模板源说明 + +- 仓库内模板源统一放在 `.agents/`(Canonical)。 +- Gemini 目标安装时投影到 `.agent/`;Codex 目标安装时投影到 `.agents/` 并生成托管区块。 + ## 命令行用法 CLI(命令行界面)命令如下。 diff --git a/docs/official/antigravity/rules-workflows.md b/docs/official/antigravity/rules-workflows.md index 65d974e..f45b204 100644 --- a/docs/official/antigravity/rules-workflows.md +++ b/docs/official/antigravity/rules-workflows.md @@ -35,6 +35,8 @@ Rules 是用户手动定义的约束,可在全局和工作区层级生效, - 路径:`/.agent/rules/` - 作用域:当前工作区(或对应 Git 根目录) +> Ag-Kit 注记:本仓库模板源统一放在 `.agents/` 目录(Canonical),但 Gemini/Antigravity 的规则与工作流仍遵循官方的 `.agent/` 工作区路径。为避免副作用,Ag-Kit 的全局同步仅覆盖 Skills,不写全局 Rules/Workflows。 + ## 规则激活方式 每条规则可配置激活方式: diff --git a/docs/official/antigravity/skills.md b/docs/official/antigravity/skills.md index c8964d8..43a682d 100644 --- a/docs/official/antigravity/skills.md +++ b/docs/official/antigravity/skills.md @@ -36,6 +36,8 @@ Antigravity 支持两类 Skill: - 工作区 Skill:适合项目专属流程(如部署规范、测试约定)。 - 全局 Skill:适合跨项目复用的个人工具或通用能力。 +> Ag-Kit 注记:本仓库模板源统一放在 `.agents/` 目录(Canonical)。当目标为 Gemini/Antigravity 时,CLI 会将模板投影到工作区的 `.agent/`;全局同步仍写入 `~/.gemini/antigravity/skills/`。 + ## 如何创建 Skill 1. 在 Skill 目录下创建文件夹。 diff --git a/docs/operations.md b/docs/operations.md index dd914b5..1413969 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -8,7 +8,7 @@ ```text /my-project -├── .agent/ # Legacy(旧版)Gemini 模式直连目录(使用 Gemini 时) +├── .agent/ # Gemini 兼容输出目录(使用 Gemini 时) ├── .agents/ # Codex 托管上游目录(只读/自动管理) │ ├── manifest.json # 完整性清单,用于漂移检测 │ ├── AGENTS.md # 核心规则源(由构建器生成) @@ -33,6 +33,22 @@ - `ag-kit update` 仅处理当前目录(或 `--path` 指定目录),不依赖全局索引。 - `ag-kit update-all` 仅处理索引内工作区;若项目曾使用 `--no-index`,可在项目内执行一次不带 `--no-index` 的 `ag-kit update` 重新纳入索引。 +### 1.3 全局 Skills + +已提供“全局安装”能力,用于跨项目复用 Skills,且与项目安装严格区分: + +- 项目安装:落盘到项目目录(`.agent/`、`.agents/`),功能最完整。 +- 全局安装:仅同步 Skills,落盘到用户目录: + - Codex:`$HOME/.agents/skills/` + - Antigravity:`$HOME/.gemini/antigravity/skills/` +- 不写入全局 Rules/Agents/Workflows,避免不可预期的全局副作用。 +- 命令面: + - `ag-kit global sync --target codex` + - `ag-kit global sync --target gemini` + - `ag-kit global status` + +规划与边界细节见:`docs/plan-global-install.md` + ## 2. 故障排查 ### 2.1 更新失败或中断 diff --git a/docs/plan-global-install.md b/docs/plan-global-install.md new file mode 100644 index 0000000..3d8d609 --- /dev/null +++ b/docs/plan-global-install.md @@ -0,0 +1,79 @@ +# 全局与项目安装规划 + +## 目标 +- 明确区分“全局安装”和“项目安装”的能力边界与使用方式。 +- 保持命令面简洁、优雅,避免参数膨胀。 +- 全局仅同步 Skills,确保安全、可控、可回滚。 + +## 非目标 +- 不在全局写入 Rules、Agents、Workflows。 +- 不自动修改任何平台的审批策略文件(如 Codex 的 `~/.codex/rules/*`)。 +- 不提供全局删除(`--prune`)默认行为。 + +## 术语 +- 项目安装:在当前项目目录落盘(`.agent/`、`.agents/` 等),功能最完整。 +- 全局安装:写入用户目录(跨项目生效),仅同步 Skills。 + +## 命令设计(定稿) + +### 项目安装(功能最完整) +- `ag-kit init` / `ag-kit update` / `ag-kit status` +- 目标路径: + - `gemini` -> `.agent/` + - `codex` -> `.agents/` + 托管区块注入 `AGENTS.md` / `antigravity.rules` + +### 全局安装(跨项目复用 Skills) +- `ag-kit global sync [--target codex|gemini] [--branch ] [--dry-run] [--quiet]` +- `ag-kit global status` +- 目标路径: + - `codex` -> `$HOME/.agents/skills/` + - `gemini` -> `$HOME/.gemini/antigravity/skills/` +- 仅同步 Skills,其他能力保持项目级。 + +> 说明:使用 `sync` 统一“首次安装 + 更新”,避免 `init/update` 双心智。全局命令不提供 `--path`。 + +## 路径映射与来源 + +### Codex +- 来源:`.agents/`(构建生成 `.agents/skills`) +- 目标:`$HOME/.agents/skills//` +- 备注:Workflow 转换为 `workflow-` Skill。 + +### Antigravity(gemini) +- 来源:`.agents/skills/` +- 目标:`$HOME/.gemini/antigravity/skills//` + +### 测试覆盖 +- `AG_KIT_GLOBAL_ROOT` 覆盖 `$HOME`,用于测试隔离。 + +## 覆盖与备份策略 +- 覆盖单位:每个 Skill 目录原子替换。 +- 默认不删除:不清理用户已有的其他技能目录。 +- 覆盖备份:覆盖同名 Skill 前备份到 `~/.ag-kit/backups/global//...`。 + +## 兼容性边界 +- 全局 Rules:高风险,全局生效,默认不写。 +- 全局 Agents/Workflows:各平台入口不一致,默认不写。 +- 结论:全局仅同步 Skills 是最稳定、最安全的选择。 + +## 测试计划 +1. 全局路径解析:验证 `AG_KIT_GLOBAL_ROOT` 覆盖正确。 +2. Codex 全局同步:能看到 `workflow-plan/SKILL.md`。 +3. Antigravity 全局同步:能看到 `clean-code/SKILL.md`。 +4. 覆盖备份:覆盖同名 Skill 时产生备份。 +5. `--dry-run`:只输出预览,不写入磁盘。 + +## 文档更新计划 +- README:新增“项目安装 vs 全局安装”的使用说明。 +- 运维手册:新增全局目录说明与风险边界。 +- 官方整理/术语文档:补充“全局只同步 Skills”的约定。 + +## 风险与应对 +- 风险:用户全局已有同名 Skill。 + - 应对:覆盖前备份,保留用户可回滚路径。 +- 风险:用户误把全局当项目级功能。 + - 应对:文档强调“全局仅 Skills”,并在 `status` 输出中区分“global/project”。 + +## 已确认决策 +- 仅提供 `ag-kit global sync/status`,不提供 `ag-kit init --global` 别名。 +- 不提供 `--prune`(默认关闭)。 diff --git a/docs/terminology-style-guide.md b/docs/terminology-style-guide.md index a799da4..535a4b3 100644 --- a/docs/terminology-style-guide.md +++ b/docs/terminology-style-guide.md @@ -61,7 +61,7 @@ ## 当前覆盖范围(2026-02) -- `.agent/**/*.md`(含 `agents/`、`skills/`、`workflows/`、`ARCHITECTURE.md`) +- `.agents/**/*.md`(含 `agents/`、`skills/`、`workflows/`、`ARCHITECTURE.md`) - `README.md` - `AGENT_FLOW.md` - `docs/*.md` diff --git a/package.json b/package.json index 14738ad..7e5e2a6 100644 --- a/package.json +++ b/package.json @@ -25,13 +25,14 @@ "clean:dry-run": "node scripts/clean.js --dry-run", "health-check": "bash scripts/health-check.sh", "postinstall": "node scripts/postinstall-check.js", - "test": "node --test \"tests/**/*.test.js\"" + "test": "node --test \"tests/**/*.test.js\"", + "ci:verify": "node scripts/ci-verify.js" }, "publishConfig": { "access": "public" }, "files": [ - ".agent/**", + ".agents/**", "bin/**", "scripts/postinstall-check.js", "README.md", diff --git a/scripts/ci-verify.js b/scripts/ci-verify.js new file mode 100644 index 0000000..9bb4045 --- /dev/null +++ b/scripts/ci-verify.js @@ -0,0 +1,91 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { spawnSync } = require("node:child_process"); + +const REPO_ROOT = path.resolve(__dirname, ".."); +const CLI_PATH = path.join(REPO_ROOT, "bin", "ag-kit.js"); + +function runCli(args, options = {}) { + const env = { + ...process.env, + AG_KIT_SKIP_UPSTREAM_CHECK: "1", + ...options.env, + }; + + const result = spawnSync(process.execPath, [CLI_PATH, ...args], { + cwd: REPO_ROOT, + env, + encoding: "utf8", + }); + + if (result.status !== 0) { + const message = result.stderr || result.stdout || ""; + throw new Error(`命令失败: ag-kit ${args.join(" ")}\n${message}`); + } + + return result.stdout || ""; +} + +function ensureExists(targetPath, label) { + if (!fs.existsSync(targetPath)) { + throw new Error(`缺少 ${label}: ${targetPath}`); + } +} + +function main() { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-ci-")); + const workspaceDir = path.join(tempRoot, "workspace"); + const indexPath = path.join(tempRoot, "workspaces.json"); + const globalRoot = path.join(tempRoot, "global-root"); + + fs.mkdirSync(workspaceDir, { recursive: true }); + + const env = { + AG_KIT_INDEX_PATH: indexPath, + AG_KIT_GLOBAL_ROOT: globalRoot, + }; + + runCli(["init", "--targets", "gemini,codex", "--path", workspaceDir, "--quiet"], { env }); + + const status = runCli(["status", "--path", workspaceDir, "--quiet"], { env }).trim(); + if (status !== "installed") { + throw new Error(`status 结果异常: ${status}`); + } + + runCli(["doctor", "--path", workspaceDir, "--quiet"], { env }); + runCli(["update", "--path", workspaceDir, "--quiet"], { env }); + runCli(["update-all", "--dry-run", "--quiet"], { env }); + + runCli(["exclude", "add", "--path", workspaceDir, "--quiet"], { env }); + const excluded = runCli(["exclude", "list", "--quiet"], { env }); + if (!excluded.split(/\r?\n/).includes(workspaceDir)) { + throw new Error("exclude add 未生效"); + } + runCli(["exclude", "remove", "--path", workspaceDir, "--quiet"], { env }); + const excludedAfter = runCli(["exclude", "list", "--quiet"], { env }); + if (excludedAfter.split(/\r?\n/).includes(workspaceDir)) { + throw new Error("exclude remove 未生效"); + } + + runCli(["global", "sync", "--target", "codex", "--quiet"], { env }); + runCli(["global", "sync", "--target", "gemini", "--quiet"], { env }); + runCli(["global", "status", "--quiet"], { env }); + + const codexSkill = path.join(globalRoot, ".agents", "skills", "workflow-plan", "SKILL.md"); + const geminiSkill = path.join(globalRoot, ".gemini", "antigravity", "skills", "clean-code", "SKILL.md"); + ensureExists(codexSkill, "全局 Codex workflow-plan Skill"); + ensureExists(geminiSkill, "全局 Antigravity clean-code Skill"); + + fs.rmSync(tempRoot, { recursive: true, force: true }); +} + +try { + main(); + console.log("✅ CI 全链路演练通过"); +} catch (err) { + console.error(`❌ ${err.message}`); + process.exit(1); +} diff --git a/tests/global-sync.test.js b/tests/global-sync.test.js new file mode 100644 index 0000000..211277b --- /dev/null +++ b/tests/global-sync.test.js @@ -0,0 +1,88 @@ +const { test, describe, beforeEach, afterEach } = require("node:test"); +const assert = require("node:assert"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { spawnSync } = require("node:child_process"); + +const REPO_ROOT = path.resolve(__dirname, ".."); +const CLI_PATH = path.join(REPO_ROOT, "bin", "ag-kit.js"); + +function runCli(args, options = {}) { + const env = { + ...process.env, + AG_KIT_SKIP_UPSTREAM_CHECK: "1", + ...options.env, + }; + + return spawnSync(process.execPath, [CLI_PATH, ...args], { + cwd: options.cwd || REPO_ROOT, + env, + encoding: "utf8", + }); +} + +describe("Global Sync", () => { + let tempDir; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-global-sync-test-")); + }); + + afterEach(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + test("global status should report missing when no global skills installed", () => { + const result = runCli(["global", "status", "--quiet"], { + env: { AG_KIT_GLOBAL_ROOT: tempDir }, + }); + assert.notStrictEqual(result.status, 0); + }); + + test("global sync should install codex skills into $HOME/.agents/skills", () => { + const result = runCli(["global", "sync", "--target", "codex", "--quiet"], { + env: { AG_KIT_GLOBAL_ROOT: tempDir }, + }); + assert.strictEqual(result.status, 0, result.stderr || result.stdout); + + const skillsRoot = path.join(tempDir, ".agents", "skills"); + assert.ok(fs.existsSync(skillsRoot), "missing global codex skills root"); + assert.ok(fs.existsSync(path.join(skillsRoot, "clean-code", "SKILL.md")), "missing expected skill: clean-code"); + assert.ok(fs.existsSync(path.join(skillsRoot, "workflow-plan", "SKILL.md")), "missing expected workflow skill: workflow-plan"); + }); + + test("global sync should install gemini skills into ~/.gemini/antigravity/skills", () => { + const result = runCli(["global", "sync", "--target", "gemini", "--quiet"], { + env: { AG_KIT_GLOBAL_ROOT: tempDir }, + }); + assert.strictEqual(result.status, 0, result.stderr || result.stdout); + + const skillsRoot = path.join(tempDir, ".gemini", "antigravity", "skills"); + assert.ok(fs.existsSync(skillsRoot), "missing global antigravity skills root"); + assert.ok(fs.existsSync(path.join(skillsRoot, "clean-code", "SKILL.md")), "missing expected skill: clean-code"); + }); + + test("global sync should create backup when overwriting an existing skill", () => { + const skillsRoot = path.join(tempDir, ".agents", "skills", "clean-code"); + fs.mkdirSync(skillsRoot, { recursive: true }); + fs.writeFileSync(path.join(skillsRoot, "SKILL.md"), "modified", "utf8"); + + const result = runCli(["global", "sync", "--target", "codex", "--quiet"], { + env: { AG_KIT_GLOBAL_ROOT: tempDir }, + }); + assert.strictEqual(result.status, 0, result.stderr || result.stdout); + + const backupRoot = path.join(tempDir, ".ag-kit", "backups", "global"); + assert.ok(fs.existsSync(backupRoot), "missing backup root"); + + const timestamps = fs + .readdirSync(backupRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name); + assert.ok(timestamps.length > 0, "backup timestamp directory missing"); + + const backupSkillPath = path.join(backupRoot, timestamps[0], "codex", "clean-code", "SKILL.md"); + assert.ok(fs.existsSync(backupSkillPath), "backup for clean-code skill missing"); + }); +}); diff --git a/tests/standards-compliance.test.js b/tests/standards-compliance.test.js index a0d2d52..1b167f4 100644 --- a/tests/standards-compliance.test.js +++ b/tests/standards-compliance.test.js @@ -3,6 +3,12 @@ const assert = require('node:assert'); const fs = require('fs'); const path = require('path'); +const REF_ROOT = path.resolve('reference/antigravity-kit'); +const REF_AGENTS_ROOT = path.resolve('reference/antigravity-kit/.agents'); +const REF_LEGACY_AGENT_ROOT = path.resolve('reference/antigravity-kit/.agent'); +const REF_SCRIPTS_ROOT = fs.existsSync(REF_AGENTS_ROOT) ? REF_AGENTS_ROOT : REF_LEGACY_AGENT_ROOT; +const HAS_REF_SCRIPTS_ROOT = fs.existsSync(REF_SCRIPTS_ROOT); + function walkDirs(root) { const out = []; const stack = [root]; @@ -30,8 +36,8 @@ function collectTokens(content, regex) { describe('Standards Compliance', () => { test('all skill directories should include SKILL.md', () => { - const skillsRoot = path.resolve('.agent/skills'); - assert.ok(fs.existsSync(skillsRoot), 'missing .agent/skills'); + const skillsRoot = path.resolve('.agents/skills'); + assert.ok(fs.existsSync(skillsRoot), 'missing .agents/skills'); const skillDirs = fs .readdirSync(skillsRoot, { withFileTypes: true }) @@ -69,9 +75,9 @@ describe('Standards Compliance', () => { } }); - test('critical mechanism tokens should remain aligned with reference snapshot', { skip: !fs.existsSync(path.resolve('reference/antigravity-kit')) }, () => { - const refRoot = path.resolve('reference/antigravity-kit'); - const tokenRegex = /(ag-kit|python3?|checklist\.py|verify_all\.py|security_scan\.py|ux_audit\.py|accessibility_checker\.py|schema_validator\.py|lint_runner\.py|type_coverage\.py|playwright_runner\.py|lighthouse_audit\.py|api_validator\.py|mobile_audit\.py|seo_checker\.py|geo_checker\.py|i18n_checker\.py|\.agent\/|\.codex\/|AGENTS\.md|antigravity\.rules|manifest\.json|--target|--targets|--fix|--path|--no-index|--dry-run|--quiet|--force)/g; + test('critical mechanism tokens should remain aligned with reference snapshot', { skip: !fs.existsSync(REF_ROOT) }, () => { + const refRoot = REF_ROOT; + const tokenRegex = /(ag-kit|python3?|checklist\.py|verify_all\.py|security_scan\.py|ux_audit\.py|accessibility_checker\.py|schema_validator\.py|lint_runner\.py|type_coverage\.py|playwright_runner\.py|lighthouse_audit\.py|api_validator\.py|mobile_audit\.py|seo_checker\.py|geo_checker\.py|i18n_checker\.py|\.agent\/|\.agents\/|\.agents-backup\/|\.codex\/|AGENTS\.md|antigravity\.rules|manifest\.json|--target|--targets|--fix|--path|--no-index|--dry-run|--quiet|--force)/g; const slashCommandRegex = /(?:^|[\s`"'(\[])(\/(?:brainstorm|create|debug|deploy|enhance|orchestrate|plan|preview|status|test|ui-ux-pro-max))(?=$|[\s`"',。,.::)\]])/gm; const markdownFiles = []; @@ -134,8 +140,8 @@ describe('Standards Compliance', () => { assert.ok(!content.includes('.codex/skills/'), 'should not contain deprecated .codex/skills path'); }); - test('.agent script files should stay identical to reference snapshot', { skip: !fs.existsSync(path.resolve('reference/antigravity-kit/.agent')) }, () => { - const refScriptsRoot = path.resolve('reference/antigravity-kit/.agent'); + test('.agents script files should stay identical to reference snapshot', { skip: !HAS_REF_SCRIPTS_ROOT }, () => { + const refScriptsRoot = REF_SCRIPTS_ROOT; const mismatches = []; const stack = [refScriptsRoot]; @@ -151,7 +157,7 @@ describe('Standards Compliance', () => { if (abs.includes(`${path.sep}__pycache__${path.sep}`) || abs.endsWith('.pyc')) continue; const rel = path.relative(refScriptsRoot, abs); - const localFile = path.resolve('.agent', rel); + const localFile = path.resolve('.agents', rel); if (!fs.existsSync(localFile)) { mismatches.push(`${rel} (missing local file)`); continue; diff --git a/web/src/app/docs/rules-workflows/page.tsx b/web/src/app/docs/rules-workflows/page.tsx index 799cbd5..fb17c45 100644 --- a/web/src/app/docs/rules-workflows/page.tsx +++ b/web/src/app/docs/rules-workflows/page.tsx @@ -63,6 +63,13 @@ export default function RulesWorkflowsPage() {
  • Model Decision:根据规则的自然语言描述,由模型决定是否应用。
  • Glob:根据你设定的 glob 模式(如 *.js、src/**/*.ts)匹配文件时自动应用。
  • +
    +

    + Ag-Kit 注记:本仓库模板源统一放在 .agents/, + 但 Gemini/Antigravity 的规则与工作流仍遵循官方的 .agent/ 工作区路径。 + 为避免副作用,Ag-Kit 的全局同步仅覆盖 Skills,不写全局 Rules/Workflows。 +

    +
    diff --git a/web/src/app/docs/skills/page.tsx b/web/src/app/docs/skills/page.tsx index 370dfcc..44be5ec 100644 --- a/web/src/app/docs/skills/page.tsx +++ b/web/src/app/docs/skills/page.tsx @@ -100,6 +100,13 @@ export default function SkillsPage() {

    工作区技能适合项目专用流程(如团队部署规范或测试约定);全局技能适合跨项目通用工具。

    +
    +

    + Ag-Kit 注记:本仓库模板源统一放在 .agents/。 + 当目标为 Gemini/Antigravity 时,CLI 会将模板投影到工作区的 .agent/, + 全局同步仍写入 ~/.gemini/antigravity/skills/。 +

    +
    {/* Creating a skill */} From 105175c37f3d0b3d394e2751d39269fd9bda5c87 Mon Sep 17 00:00:00 2001 From: Mison Date: Thu, 12 Mar 2026 16:42:17 +0800 Subject: [PATCH 2/7] feat(cli): default global sync to all targets --- bin/ag-kit.js | 9 +++++++-- .../docs-archive}/codex-rules-template.md | 0 {docs => reference/docs-archive}/mapping-spec.md | 0 .../docs-archive}/multi-target-adapter.md | 0 {docs => reference/docs-archive}/operations.md | 0 .../docs-archive}/plan-global-install.md | 0 .../docs-archive}/terminology-style-guide.md | 0 .../official => reference/official-docs}/README.md | 0 .../antigravity/agent-modes-settings.md | 0 .../official-docs}/antigravity/rules-workflows.md | 0 .../official-docs}/antigravity/skills.md | 0 .../official-docs}/codex/agents-md.md | 0 .../official-docs}/codex/config-advanced.md | 0 .../official-docs}/codex/config-basic.md | 0 .../official-docs}/codex/config-reference.md | 0 .../official-docs}/codex/config-sample.md | 0 .../official-docs}/codex/mcp.md | 0 .../official-docs}/codex/rules.md | 0 .../official-docs}/codex/skills.md | 0 .../mechanism-compat-audit-2026-03-04.md | 0 .../official-docs}/rules-baseline.md | 0 .../official-docs}/sources-index.md | 0 scripts/ci-verify.js | 3 +-- tests/global-sync.test.js | 14 ++++++++++++++ 24 files changed, 22 insertions(+), 4 deletions(-) rename {docs => reference/docs-archive}/codex-rules-template.md (100%) rename {docs => reference/docs-archive}/mapping-spec.md (100%) rename {docs => reference/docs-archive}/multi-target-adapter.md (100%) rename {docs => reference/docs-archive}/operations.md (100%) rename {docs => reference/docs-archive}/plan-global-install.md (100%) rename {docs => reference/docs-archive}/terminology-style-guide.md (100%) rename {docs/official => reference/official-docs}/README.md (100%) rename {docs/official => reference/official-docs}/antigravity/agent-modes-settings.md (100%) rename {docs/official => reference/official-docs}/antigravity/rules-workflows.md (100%) rename {docs/official => reference/official-docs}/antigravity/skills.md (100%) rename {docs/official => reference/official-docs}/codex/agents-md.md (100%) rename {docs/official => reference/official-docs}/codex/config-advanced.md (100%) rename {docs/official => reference/official-docs}/codex/config-basic.md (100%) rename {docs/official => reference/official-docs}/codex/config-reference.md (100%) rename {docs/official => reference/official-docs}/codex/config-sample.md (100%) rename {docs/official => reference/official-docs}/codex/mcp.md (100%) rename {docs/official => reference/official-docs}/codex/rules.md (100%) rename {docs/official => reference/official-docs}/codex/skills.md (100%) rename {docs/official => reference/official-docs}/mechanism-compat-audit-2026-03-04.md (100%) rename {docs/official => reference/official-docs}/rules-baseline.md (100%) rename {docs/official => reference/official-docs}/sources-index.md (100%) diff --git a/bin/ag-kit.js b/bin/ag-kit.js index 9a3a4e4..1212c06 100755 --- a/bin/ag-kit.js +++ b/bin/ag-kit.js @@ -103,7 +103,7 @@ function printUsage() { console.log(" ag-kit update [--path ] [--branch ] [--target |--targets ] [--no-index] [--quiet] [--dry-run]"); console.log(" ag-kit update-all [--branch ] [--targets ] [--prune-missing] [--quiet] [--dry-run]"); console.log(" ag-kit doctor [--path ] [--target |--targets ] [--fix] [--quiet]"); - console.log(" ag-kit global sync [--target |--targets ] [--branch ] [--quiet] [--dry-run]"); + console.log(" ag-kit global sync [--target |--targets ] [--branch ] [--quiet] [--dry-run] # 默认同步 codex+gemini"); console.log(" ag-kit global status [--quiet]"); console.log(" ag-kit exclude list [--quiet]"); console.log(" ag-kit exclude add --path [--dry-run] [--quiet]"); @@ -847,7 +847,12 @@ function resolveTargetsForUpdate(workspaceRoot, options) { } function resolveTargetsForGlobalSync(options) { - return resolveTargetsForInit(options); + const requested = normalizeTargets(options.targets); + if (requested.length > 0) { + return requested; + } + // 保持 global sync 简洁:默认同步两个目标。 + return ["codex", "gemini"]; } function resolveAgentInstallSource(options) { diff --git a/docs/codex-rules-template.md b/reference/docs-archive/codex-rules-template.md similarity index 100% rename from docs/codex-rules-template.md rename to reference/docs-archive/codex-rules-template.md diff --git a/docs/mapping-spec.md b/reference/docs-archive/mapping-spec.md similarity index 100% rename from docs/mapping-spec.md rename to reference/docs-archive/mapping-spec.md diff --git a/docs/multi-target-adapter.md b/reference/docs-archive/multi-target-adapter.md similarity index 100% rename from docs/multi-target-adapter.md rename to reference/docs-archive/multi-target-adapter.md diff --git a/docs/operations.md b/reference/docs-archive/operations.md similarity index 100% rename from docs/operations.md rename to reference/docs-archive/operations.md diff --git a/docs/plan-global-install.md b/reference/docs-archive/plan-global-install.md similarity index 100% rename from docs/plan-global-install.md rename to reference/docs-archive/plan-global-install.md diff --git a/docs/terminology-style-guide.md b/reference/docs-archive/terminology-style-guide.md similarity index 100% rename from docs/terminology-style-guide.md rename to reference/docs-archive/terminology-style-guide.md diff --git a/docs/official/README.md b/reference/official-docs/README.md similarity index 100% rename from docs/official/README.md rename to reference/official-docs/README.md diff --git a/docs/official/antigravity/agent-modes-settings.md b/reference/official-docs/antigravity/agent-modes-settings.md similarity index 100% rename from docs/official/antigravity/agent-modes-settings.md rename to reference/official-docs/antigravity/agent-modes-settings.md diff --git a/docs/official/antigravity/rules-workflows.md b/reference/official-docs/antigravity/rules-workflows.md similarity index 100% rename from docs/official/antigravity/rules-workflows.md rename to reference/official-docs/antigravity/rules-workflows.md diff --git a/docs/official/antigravity/skills.md b/reference/official-docs/antigravity/skills.md similarity index 100% rename from docs/official/antigravity/skills.md rename to reference/official-docs/antigravity/skills.md diff --git a/docs/official/codex/agents-md.md b/reference/official-docs/codex/agents-md.md similarity index 100% rename from docs/official/codex/agents-md.md rename to reference/official-docs/codex/agents-md.md diff --git a/docs/official/codex/config-advanced.md b/reference/official-docs/codex/config-advanced.md similarity index 100% rename from docs/official/codex/config-advanced.md rename to reference/official-docs/codex/config-advanced.md diff --git a/docs/official/codex/config-basic.md b/reference/official-docs/codex/config-basic.md similarity index 100% rename from docs/official/codex/config-basic.md rename to reference/official-docs/codex/config-basic.md diff --git a/docs/official/codex/config-reference.md b/reference/official-docs/codex/config-reference.md similarity index 100% rename from docs/official/codex/config-reference.md rename to reference/official-docs/codex/config-reference.md diff --git a/docs/official/codex/config-sample.md b/reference/official-docs/codex/config-sample.md similarity index 100% rename from docs/official/codex/config-sample.md rename to reference/official-docs/codex/config-sample.md diff --git a/docs/official/codex/mcp.md b/reference/official-docs/codex/mcp.md similarity index 100% rename from docs/official/codex/mcp.md rename to reference/official-docs/codex/mcp.md diff --git a/docs/official/codex/rules.md b/reference/official-docs/codex/rules.md similarity index 100% rename from docs/official/codex/rules.md rename to reference/official-docs/codex/rules.md diff --git a/docs/official/codex/skills.md b/reference/official-docs/codex/skills.md similarity index 100% rename from docs/official/codex/skills.md rename to reference/official-docs/codex/skills.md diff --git a/docs/official/mechanism-compat-audit-2026-03-04.md b/reference/official-docs/mechanism-compat-audit-2026-03-04.md similarity index 100% rename from docs/official/mechanism-compat-audit-2026-03-04.md rename to reference/official-docs/mechanism-compat-audit-2026-03-04.md diff --git a/docs/official/rules-baseline.md b/reference/official-docs/rules-baseline.md similarity index 100% rename from docs/official/rules-baseline.md rename to reference/official-docs/rules-baseline.md diff --git a/docs/official/sources-index.md b/reference/official-docs/sources-index.md similarity index 100% rename from docs/official/sources-index.md rename to reference/official-docs/sources-index.md diff --git a/scripts/ci-verify.js b/scripts/ci-verify.js index 9bb4045..b2ae2e5 100644 --- a/scripts/ci-verify.js +++ b/scripts/ci-verify.js @@ -70,8 +70,7 @@ function main() { throw new Error("exclude remove 未生效"); } - runCli(["global", "sync", "--target", "codex", "--quiet"], { env }); - runCli(["global", "sync", "--target", "gemini", "--quiet"], { env }); + runCli(["global", "sync", "--quiet"], { env }); runCli(["global", "status", "--quiet"], { env }); const codexSkill = path.join(globalRoot, ".agents", "skills", "workflow-plan", "SKILL.md"); diff --git a/tests/global-sync.test.js b/tests/global-sync.test.js index 211277b..1ebb936 100644 --- a/tests/global-sync.test.js +++ b/tests/global-sync.test.js @@ -52,6 +52,20 @@ describe("Global Sync", () => { assert.ok(fs.existsSync(path.join(skillsRoot, "workflow-plan", "SKILL.md")), "missing expected workflow skill: workflow-plan"); }); + test("global sync should default to syncing codex and gemini when no target is provided", () => { + const result = runCli(["global", "sync", "--quiet"], { + env: { AG_KIT_GLOBAL_ROOT: tempDir }, + }); + assert.strictEqual(result.status, 0, result.stderr || result.stdout); + + const codexRoot = path.join(tempDir, ".agents", "skills"); + assert.ok(fs.existsSync(path.join(codexRoot, "clean-code", "SKILL.md")), "missing expected codex skill: clean-code"); + assert.ok(fs.existsSync(path.join(codexRoot, "workflow-plan", "SKILL.md")), "missing expected codex workflow skill: workflow-plan"); + + const geminiRoot = path.join(tempDir, ".gemini", "antigravity", "skills"); + assert.ok(fs.existsSync(path.join(geminiRoot, "clean-code", "SKILL.md")), "missing expected gemini skill: clean-code"); + }); + test("global sync should install gemini skills into ~/.gemini/antigravity/skills", () => { const result = runCli(["global", "sync", "--target", "gemini", "--quiet"], { env: { AG_KIT_GLOBAL_ROOT: tempDir }, From 295c25f8ef04869d9bd7591d7ae5c28551866246 Mon Sep 17 00:00:00 2001 From: Mison Date: Thu, 12 Mar 2026 16:43:06 +0800 Subject: [PATCH 3/7] docs(core): consolidate plan and tech docs --- README.md | 9 ++- docs/PLAN.md | 38 +++++++++++ docs/TECH.md | 100 +++++++++++++++++++++++++++++ package.json | 8 +-- tests/standards-compliance.test.js | 7 +- 5 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 docs/PLAN.md create mode 100644 docs/TECH.md diff --git a/README.md b/README.md index a0041ca..4759b70 100644 --- a/README.md +++ b/README.md @@ -47,25 +47,28 @@ npm install -g . - 项目安装:`ag-kit init` / `ag-kit update`(功能最完整) - 全局安装:`ag-kit global sync`(仅同步 Skills,跨项目复用) +- 默认行为:`ag-kit global sync` 未指定 `--target/--targets` 时,同步 `codex + gemini` 示例: ```bash +ag-kit global sync ag-kit global sync --target codex ag-kit global sync --target gemini ag-kit global status ``` 全局安装只同步 Skills,不写入 Rules/Agents/Workflows,避免全局副作用。 +覆盖同名 Skill 前会自动备份;手动回滚方式见 `docs/TECH.md`。 -规划与边界细节见:`docs/plan-global-install.md` +规划与边界细节见:`docs/PLAN.md`(规划)与 `docs/TECH.md`(技术)。 ### Codex 规则边界说明 - `antigravity.rules`:本项目生成并注入的托管说明文件,用于记录受管资源与运维约束。 - `.rules`(如 `~/.codex/rules/default.rules`):Codex 官方的命令审批/执行策略文件(Starlark 规则,支持 `prefix_rule()`)。 - 默认行为:本项目不会自动写入你的全局 `~/.codex/rules`,避免引入不可预期的全局副作用。 -- 如需启用官方 `.rules` 审批策略,请参考 `docs/codex-rules-template.md`。 +- 如需启用官方 `.rules` 审批策略,请参考 `docs/TECH.md` 的「Codex 官方 `.rules`(手动配置)」小节。 ### ⚠️ 关于 `.gitignore` 的重要说明 @@ -156,6 +159,8 @@ CLI(命令行界面)工具: | `ag-kit update` | 更新当前项目已安装目标 | | `ag-kit update-all` | 批量更新所有已登记工作区 | | `ag-kit doctor` | 诊断安装完整性(可 `--fix` 自愈) | +| `ag-kit global sync` | 全局同步 Skills(默认同步 codex+gemini) | +| `ag-kit global status` | 查看全局 Skills 安装状态 | | `ag-kit exclude` | 管理全局索引排除清单 | | `ag-kit status` | 检查安装状态 | diff --git a/docs/PLAN.md b/docs/PLAN.md new file mode 100644 index 0000000..8e6bc7e --- /dev/null +++ b/docs/PLAN.md @@ -0,0 +1,38 @@ +# Ag-Kit 规划(PLAN) + +## 一句话 +Ag-Kit 只做一件事:把仓库内统一的 `.agents/` 模板,优雅地投影到不同平台(Gemini/Antigravity 与 Codex),并提供安全、可回滚的更新机制。 + +## 设计原则 +- 命令面少而强:用 `sync` 统一“首次安装 + 更新”,避免双心智。 +- 默认安全:不做全局清理、不自动写入高风险全局规则。 +- 模板源单一:仓库 Canonical 为 `.agents/`;旧 `.agent/` 只作为输入兼容,不作为主路径。 + +## 能力边界(项目 vs 全局) +### 项目安装(功能最完整) +- 命令:`ag-kit init` / `ag-kit update` / `ag-kit status` / `ag-kit doctor` +- 目标输出: + - `gemini` -> 项目内 `.agent/`(兼容 Gemini/Antigravity 工作区规范) + - `codex` -> 项目内 `.agents/`(受管目录)+ 注入工作区 `AGENTS.md` / `antigravity.rules` 托管区块 + +### 全局安装(跨项目复用 Skills) +- 命令:`ag-kit global sync` / `ag-kit global status` +- 默认行为:`ag-kit global sync` 未指定 `--target/--targets` 时,同步 `codex + gemini` +- 目标路径: + - `codex` -> `$HOME/.agents/skills/` + - `gemini` -> `$HOME/.gemini/antigravity/skills/` +- 安全边界:全局只同步 Skills,不写入全局 Rules/Agents/Workflows。 + +## 覆盖与回滚(全局同步) +- 覆盖单位:每个 Skill 目录。 +- 覆盖策略:只覆盖同名 Skill;不清理用户已有的其他 Skill。 +- 覆盖前备份:每次覆盖同名 Skill 前备份到 `$HOME/.ag-kit/backups/global//...`。 + +## 兼容策略 +- Gemini/Antigravity:输出 `.agent/`,保持与官方工作区机制一致。 +- Codex:受管目录为 `.agents/`,并使用 `manifest.json` 做完整性与漂移检测;识别并迁移遗留 `.codex/`。 + +## 成功标准 +- `ag-kit global sync` 一条命令即可完成全局 Skills 安装/更新(默认 codex+gemini)。 +- 覆盖可回滚:每次覆盖同名 Skill 都有可用备份。 +- 跨平台 CI(Linux/macOS/Windows)验证主链路通过。 diff --git a/docs/TECH.md b/docs/TECH.md new file mode 100644 index 0000000..608236e --- /dev/null +++ b/docs/TECH.md @@ -0,0 +1,100 @@ +# Ag-Kit 技术说明(TECH) + +## 快速验证(维护者) +```bash +bun install +bun run test +bun run ci:verify +cd web && bun install && bun run lint +``` + +## 核心目录与职责 +- `.agents/`:仓库模板源(Canonical) +- `bin/ag-kit.js`:CLI 入口与命令分发 +- `bin/adapters/`:目标差异(`gemini` / `codex`) +- `bin/core/`:构建/转换(将 Workflows 投影为 Codex Skills 等) +- `bin/utils/`:原子写入、manifest、托管区块等通用能力 + +## 路径映射(最重要) +### 项目级(功能最完整) +- `gemini`:项目根目录 `.agent/` +- `codex`:项目根目录 `.agents/`(受管)+ `.agents-backup/`(漂移覆盖备份) + +### 全局级(仅同步 Skills) +- `codex`:`$HOME/.agents/skills/` +- `gemini`:`$HOME/.gemini/antigravity/skills/` + +> 说明:仓库内 Skills 源路径为 `.agents/skills/`,全局同步会将其写入上述全局目录。 + +## 端到端链路(简述) +### 项目安装 / 更新 +- `init`:选择目标 -> 适配器 `install()` -> 落盘目标目录(Gemini: `.agent/`;Codex: `.agents/`)->(Codex)注入托管区块到工作区 `AGENTS.md` 与 `antigravity.rules` +- `update`:自动检测已安装目标(或通过 `--target/--targets` 指定)-> 适配器 `update()` ->(Codex)漂移检测与备份 -> 原子替换 +- `doctor`:检查完整性;`--fix` 尝试修复(Codex 支持迁移 `.codex/` 与重写托管区块) + +### Codex 构建(Workflow -> Skill) +- 输入:`.agents/skills/` 与 `.agents/workflows/` +- 规则:每个 Workflow `.md` 会转换为一个 Skill:`workflow-/SKILL.md` +- 输出(受管目录 `.agents/` 内):`skills/`、`codex.json`、`AGENTS.md`、`antigravity.rules`、`manifest.json` + +## 全局同步:`ag-kit global sync/status` +### 默认目标 +- 未指定 `--target/--targets`:默认同步 `codex + gemini` +- 可用 `--target codex` 或 `--targets codex,gemini` 限定目标 + +### 来源与覆盖策略 +- 来源:默认使用本包内置 `.agents/`;也可用 `--branch ` 从远端分支拉取模板源 +- 覆盖单位:每个 Skill 目录 +- 覆盖策略:只覆盖同名 Skill,不清理其他 Skill +- 原子替换:按 Skill 目录原子替换,避免半写状态 +- 覆盖前备份:覆盖同名 Skill 前备份到 `$HOME/.ag-kit/backups/global////...` + +### 测试隔离 +- `AG_KIT_GLOBAL_ROOT`:替代 `$HOME`(用于测试与 CI,避免污染真实用户目录) + +## 手动回滚(全局 Skills) +1. 找到备份目录:`$HOME/.ag-kit/backups/global//...` +2. 按 Skill 回滚(推荐一次只处理一个 Skill 目录): + - Codex 目标:恢复到 `$HOME/.agents/skills//` + - Gemini 目标:恢复到 `$HOME/.gemini/antigravity/skills//` + +macOS / Linux 示例(把某个 Skill 回滚为备份版本): +```bash +ts="2026-03-12T12-00-00-000Z" +skill="clean-code" +rm -rf "$HOME/.agents/skills/$skill" +cp -a "$HOME/.ag-kit/backups/global/$ts/codex/$skill" "$HOME/.agents/skills/$skill" +``` + +Windows PowerShell 示例: +```powershell +$ts = "2026-03-12T12-00-00-000Z" +$skill = "clean-code" +Remove-Item "$HOME\\.agents\\skills\\$skill" -Recurse -Force -ErrorAction SilentlyContinue +Copy-Item "$HOME\\.ag-kit\\backups\\global\\$ts\\codex\\$skill" "$HOME\\.agents\\skills\\$skill" -Recurse -Force +``` + +## 环境变量 +- `AG_KIT_INDEX_PATH`:工作区索引文件路径(默认 `~/.ag-kit/workspaces.json`) +- `AG_KIT_GLOBAL_ROOT`:全局目录根(替代 `$HOME`) +- `AG_KIT_SKIP_UPSTREAM_CHECK`:跳过上游同名包安装提示(测试用) + +## 常见故障 +- 更新中断:原子替换保证不会出现半写状态;重新运行 `update`/`global sync` 即可。 +- Windows `EPERM/EBUSY`:通常是目录被占用;关闭占用 `.agents/` 或目标 Skill 目录的进程后重试。 +- 漂移覆盖:Codex 若检测到用户修改受管文件,会在覆盖前写入 `.agents-backup//`。 + +## Codex 官方 `.rules`(手动配置) +Ag-Kit 不会自动写入全局 `~/.codex/rules/default.rules`,避免引入不可预期的全局副作用。若你需要启用 Codex 官方命令审批策略(如 `prefix_rule()`),可按需手动创建: + +```python +# default.rules +load("builtin://rules/rules.star", "prefix_rule") + +rules = [ + prefix_rule(["ls"], action="allow"), + prefix_rule(["cat"], action="allow"), + prefix_rule(["rg"], action="allow"), + prefix_rule(["git", "status"], action="allow"), +] +``` diff --git a/package.json b/package.json index 7e5e2a6..4716f60 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,8 @@ "LICENSE", "CHANGELOG.md", "AGENT_FLOW.md", - "docs/codex-rules-template.md", - "docs/mapping-spec.md", - "docs/multi-target-adapter.md", - "docs/operations.md", - "docs/terminology-style-guide.md", - "docs/official/**" + "docs/PLAN.md", + "docs/TECH.md" ], "bin": { "ag-kit": "./bin/ag-kit.js" diff --git a/tests/standards-compliance.test.js b/tests/standards-compliance.test.js index 1b167f4..f3cea29 100644 --- a/tests/standards-compliance.test.js +++ b/tests/standards-compliance.test.js @@ -53,7 +53,8 @@ describe('Standards Compliance', () => { test('deprecated .codex primary-layout wording should be removed from user-facing docs and generator', () => { const targets = [ 'README.md', - 'docs/operations.md', + 'docs/PLAN.md', + 'docs/TECH.md', 'docs/code-review-report.md', 'bin/core/generator.js', ]; @@ -131,8 +132,8 @@ describe('Standards Compliance', () => { ); }); - test('official codex skills doc should track current .agents skill locations', () => { - const file = path.resolve('docs/official/codex/skills.md'); + test('user-facing docs should track current .agents skill locations', () => { + const file = path.resolve('docs/TECH.md'); const content = fs.readFileSync(file, 'utf8'); assert.ok(content.includes('$HOME/.agents/skills/'), 'missing global skill path: $HOME/.agents/skills/'); From 928056f0bceec94db67ddcc9c9b02523cf668e85 Mon Sep 17 00:00:00 2001 From: Mison Date: Thu, 12 Mar 2026 17:01:07 +0800 Subject: [PATCH 4/7] chore(test): remove stale compliance leftovers --- tests/standards-compliance.test.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/standards-compliance.test.js b/tests/standards-compliance.test.js index f3cea29..9b6213e 100644 --- a/tests/standards-compliance.test.js +++ b/tests/standards-compliance.test.js @@ -9,23 +9,6 @@ const REF_LEGACY_AGENT_ROOT = path.resolve('reference/antigravity-kit/.agent'); const REF_SCRIPTS_ROOT = fs.existsSync(REF_AGENTS_ROOT) ? REF_AGENTS_ROOT : REF_LEGACY_AGENT_ROOT; const HAS_REF_SCRIPTS_ROOT = fs.existsSync(REF_SCRIPTS_ROOT); -function walkDirs(root) { - const out = []; - const stack = [root]; - while (stack.length > 0) { - const current = stack.pop(); - if (!fs.existsSync(current)) continue; - for (const entry of fs.readdirSync(current, { withFileTypes: true })) { - const abs = path.join(current, entry.name); - if (entry.isDirectory()) { - out.push(abs); - stack.push(abs); - } - } - } - return out; -} - function collectTokens(content, regex) { const tokens = new Set(); for (const match of String(content || "").matchAll(regex)) { @@ -55,7 +38,6 @@ describe('Standards Compliance', () => { 'README.md', 'docs/PLAN.md', 'docs/TECH.md', - 'docs/code-review-report.md', 'bin/core/generator.js', ]; const skipped = []; From 7192b5adbc7ac6c3c419c3aeb3e8dc5b68a12241 Mon Sep 17 00:00:00 2001 From: Mison Date: Thu, 12 Mar 2026 21:03:26 +0800 Subject: [PATCH 5/7] feat(cli): tighten install health contracts --- README.md | 27 +++-- bin/adapters/gemini.js | 4 +- bin/ag-kit.js | 226 ++++++++++++++++++++++++++++++++-------- docs/TECH.md | 17 +++ package.json | 7 +- scripts/ci-verify.js | 5 +- scripts/health-check.js | 121 +++++++++++++++++++++ scripts/health-check.sh | 61 +---------- 8 files changed, 351 insertions(+), 117 deletions(-) create mode 100644 scripts/health-check.js diff --git a/README.md b/README.md index 4759b70..f4cc2c2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ npm install -g @mison/ag-kit-cn ``` +或使用 Bun: + +```bash +bun install -g @mison/ag-kit-cn +``` + 然后在你的目标项目中初始化: ```bash @@ -184,6 +190,13 @@ ag-kit exclude add --path /path/to/dir # 新增排除路径 ag-kit exclude remove --path /path/to/dir # 删除排除路径 ``` +### 状态命令约定 + +- `ag-kit status --quiet`:输出 `installed` / `broken` / `missing` +- `ag-kit global status --quiet`:输出 `installed` / `broken` / `missing` +- 退出码:`0=installed`,`1=broken`,`2=missing` +- `status` 面向自动化健康判断;如需问题明细,使用 `ag-kit doctor` + ### 批量更新机制 - 执行 `ag-kit init` / `ag-kit update` 时,会把工作区路径登记到全局索引文件: @@ -201,19 +214,21 @@ ag-kit exclude remove --path /path/to/dir # 删除排除路径 ### 开发维护命令 ```bash -npm run clean # 清理本地生成产物(如 web/.next、web/node_modules) -npm run clean:dry-run # 预览将被清理的路径 -npm test # 只执行 tests/ 目录下测试 -npm run health-check # 一键执行全链路健康复检 +bun run clean # 清理本地生成产物(如 web/.next、web/node_modules) +bun run clean:dry-run # 预览将被清理的路径 +bun run test # 只执行 tests/ 目录下测试 +bun run health-check # 一键执行全链路健康复检 ``` 如果你在 `web/` 子项目内开发,可按需执行: ```bash -npm install --prefix web -npm run lint --prefix web +bun install --cwd web +bun run lint --cwd web ``` +> 说明:若你通过 `bun install -g` 安装 CLI,Bun 默认会阻止本包 `postinstall`。上游同名包冲突提示会在首次执行 `ag-kit init/update/global sync` 时给出。 + ## 卸载 ### 卸载本机全局 CLI diff --git a/bin/adapters/gemini.js b/bin/adapters/gemini.js index 15ea971..27d7182 100644 --- a/bin/adapters/gemini.js +++ b/bin/adapters/gemini.js @@ -11,11 +11,9 @@ class GeminiAdapter extends BaseAdapter { } getInstalledVersion() { - // In v1, version was not strictly tracked per target in a file. - // We assume check existence of .agent/agents directory. const agentDir = path.join(this.workspaceRoot, ".agent"); if (fs.existsSync(agentDir)) { - return "2.0.1"; // Default to current version if exists + return null; } return null; } diff --git a/bin/ag-kit.js b/bin/ag-kit.js index 1212c06..fe213c0 100755 --- a/bin/ag-kit.js +++ b/bin/ag-kit.js @@ -21,6 +21,11 @@ const SUPPORTED_TARGETS = ["gemini", "codex"]; const INDEX_LOCK_RETRY_MS = 50; const INDEX_LOCK_TIMEOUT_MS = 3000; const INDEX_LOCK_STALE_MS = 30000; +const QUIET_STATUS_EXIT_CODES = { + installed: 0, + broken: 1, + missing: 2, +}; function nowISO() { return new Date().toISOString(); @@ -739,7 +744,12 @@ function maybeWarnUpstreamGlobalConflict(command, options) { if (process.env.AG_KIT_SKIP_UPSTREAM_CHECK === "1") { return; } - if (command !== "init" && command !== "update" && command !== "update-all") { + const shouldWarn = + command === "init" + || command === "update" + || command === "update-all" + || (command === "global" && String(options.subcommand || "").toLowerCase() === "sync"); + if (!shouldWarn) { return; } @@ -755,6 +765,7 @@ function maybeWarnUpstreamGlobalConflict(command, options) { log(options, `⚠️ 检测到全局已安装上游英文版 ${UPSTREAM_GLOBAL_PACKAGE}。`); log(options, "⚠️ 上游英文版与当前中文版共用 `ag-kit` 命令名,后安装者会覆盖命令入口。"); log(options, `👉 建议执行: npm uninstall -g ${UPSTREAM_GLOBAL_PACKAGE}`); + log(options, "ℹ️ 若你通过 bun install -g 安装,Bun 默认会阻止本包 postinstall;因此这里会在首次执行 CLI 时再次提醒。"); } function normalizeTargets(rawTargets) { @@ -805,6 +816,116 @@ function isTargetInstalled(workspaceRoot, targetName) { return false; } +function setQuietStatusExitCode(state) { + process.exitCode = Object.prototype.hasOwnProperty.call(QUIET_STATUS_EXIT_CODES, state) + ? QUIET_STATUS_EXIT_CODES[state] + : 1; +} + +function normalizeIntegrityState(result) { + if (!result || result.status === "missing") { + return "missing"; + } + if (result.status === "ok") { + return "installed"; + } + return "broken"; +} + +function evaluateWorkspaceState(workspaceRoot, options) { + const targets = detectInstalledTargets(workspaceRoot); + if (targets.length === 0) { + return { + state: "missing", + targets: [], + }; + } + + const targetStates = targets.map((targetName) => { + const adapter = createAdapter(targetName, workspaceRoot, { + ...options, + quiet: true, + }); + const integrity = adapter.checkIntegrity(); + return { + targetName, + state: normalizeIntegrityState(integrity), + integrity, + version: typeof adapter.getInstalledVersion === "function" ? adapter.getInstalledVersion() : null, + }; + }); + + const hasIssue = targetStates.some((item) => item.state !== "installed"); + return { + state: hasIssue ? "broken" : "installed", + targets: targetStates, + }; +} + +function getGlobalTargetPaths(globalRoot, targetName) { + if (targetName === "codex") { + return { + markerDir: path.join(globalRoot, ".agents"), + skillsRoot: path.join(globalRoot, ".agents", "skills"), + }; + } + if (targetName === "gemini") { + return { + markerDir: path.join(globalRoot, ".gemini", "antigravity"), + skillsRoot: path.join(globalRoot, ".gemini", "antigravity", "skills"), + }; + } + throw new Error(`未知全局目标: ${targetName}`); +} + +function evaluateGlobalState() { + const globalRoot = resolveGlobalRootDir(); + const targetStates = SUPPORTED_TARGETS.map((targetName) => { + const paths = getGlobalTargetPaths(globalRoot, targetName); + const markerExists = fs.existsSync(paths.markerDir); + const skillsExists = fs.existsSync(paths.skillsRoot); + const skillsCount = skillsExists ? countSkillsRecursive(paths.skillsRoot) : 0; + let state = "missing"; + const issues = []; + + if (markerExists || skillsExists) { + if (!skillsExists) { + state = "broken"; + issues.push("Skills 根目录缺失"); + } else if (skillsCount === 0) { + state = "broken"; + issues.push("未检测到任何 SKILL.md"); + } else { + state = "installed"; + } + } + + return { + targetName, + state, + markerDir: paths.markerDir, + skillsRoot: paths.skillsRoot, + skillsCount, + issues, + }; + }).filter((item) => item.state !== "missing"); + + if (targetStates.length === 0) { + return { + globalRoot, + state: "missing", + targets: [], + }; + } + + const hasIssue = targetStates.some((item) => item.state !== "installed"); + return { + globalRoot, + state: hasIssue ? "broken" : "installed", + targets: targetStates, + }; +} + function createAdapter(targetName, workspaceRoot, options) { if (targetName === "gemini") { return new GeminiAdapter(workspaceRoot, options); @@ -983,18 +1104,6 @@ function applyGlobalSync(targetName, agentDir, timestamp, options) { throw new Error(`未知目标: ${targetName}`); } -function detectInstalledGlobalTargets() { - const targets = []; - const globalRoot = resolveGlobalRootDir(); - if (fs.existsSync(path.join(globalRoot, ".agents", "skills"))) { - targets.push("codex"); - } - if (fs.existsSync(path.join(globalRoot, ".gemini", "antigravity", "skills"))) { - targets.push("gemini"); - } - return targets; -} - async function commandGlobalSync(options) { const targets = await resolveTargetsForGlobalSync(options); const { agentDir, cleanup, sourceLabel } = resolveAgentInstallSource(options); @@ -1016,42 +1125,45 @@ async function commandGlobalSync(options) { } function commandGlobalStatus(options) { - const targets = detectInstalledGlobalTargets(); - const globalRoot = resolveGlobalRootDir(); + const summary = evaluateGlobalState(); - if (targets.length === 0) { + if (summary.state === "missing") { + if (options.quiet) { + console.log("missing"); + } if (!options.quiet) { console.log("❌ 未检测到全局安装的 Skills"); - console.log(` 全局根目录: ${globalRoot}`); + console.log(` 全局根目录: ${summary.globalRoot}`); } - process.exitCode = 1; + setQuietStatusExitCode("missing"); return; } if (options.quiet) { - console.log("installed"); + console.log(summary.state); + setQuietStatusExitCode(summary.state); return; } - console.log("✅ 已检测到全局 Skills 安装"); - console.log(` 全局根目录: ${globalRoot}`); - console.log(` Targets: ${targets.join(", ")}`); + console.log(summary.state === "installed" ? "✅ 全局 Skills 状态正常" : "⚠️ 全局 Skills 存在问题"); + console.log(` 全局根目录: ${summary.globalRoot}`); + console.log(` 总体状态: ${summary.state}`); + console.log(` Targets: ${summary.targets.map((item) => item.targetName).join(", ")}`); - if (targets.includes("gemini")) { - const skillsRoot = path.join(globalRoot, ".gemini", "antigravity", "skills"); - const skillsCount = countSkillsRecursive(skillsRoot); - console.log("\n[gemini:global]"); - console.log(` 路径: ${skillsRoot}`); - console.log(` Skills: ${skillsCount}`); + for (const item of summary.targets) { + console.log(`\n[${item.targetName}:global]`); + console.log(` 状态: ${item.state}`); + console.log(` 路径: ${item.skillsRoot}`); + if (item.state === "installed") { + console.log(` Skills: ${item.skillsCount}`); + continue; + } + for (const issue of item.issues) { + console.log(` Issue: ${issue}`); + } } - if (targets.includes("codex")) { - const skillsRoot = path.join(globalRoot, ".agents", "skills"); - const skillsCount = countSkillsRecursive(skillsRoot); - console.log("\n[codex:global]"); - console.log(` 路径: ${skillsRoot}`); - console.log(` Skills: ${skillsCount}`); - } + setQuietStatusExitCode(summary.state); } function commandGlobal(options) { @@ -1514,40 +1626,54 @@ function countSkillsRecursive(dir) { function commandStatus(options) { const workspaceRoot = resolveWorkspaceRoot(options.path); - const installedTargets = detectInstalledTargets(workspaceRoot); + const summary = evaluateWorkspaceState(workspaceRoot, options); - if (installedTargets.length === 0) { + if (summary.state === "missing") { + if (options.quiet) { + console.log("missing"); + } if (!options.quiet) { console.log("❌ 未检测到 Antigravity Kit 安装"); console.log(` 目标目录: ${workspaceRoot}`); } - process.exitCode = 1; + setQuietStatusExitCode("missing"); return; } if (options.quiet) { - console.log("installed"); + console.log(summary.state); + setQuietStatusExitCode(summary.state); return; } - console.log("✅ Antigravity Kit 已安装"); + console.log(summary.state === "installed" ? "✅ Antigravity Kit 状态正常" : "⚠️ Antigravity Kit 存在问题"); console.log(` CLI 版本: ${pkg.version}`); console.log(` 工作区: ${workspaceRoot}`); - console.log(` Targets: ${installedTargets.join(", ")}`); + console.log(` 总体状态: ${summary.state}`); + console.log(` Targets: ${summary.targets.map((item) => item.targetName).join(", ")}`); - if (installedTargets.includes("gemini")) { + const geminiState = summary.targets.find((item) => item.targetName === "gemini"); + if (geminiState) { const agentDir = path.join(workspaceRoot, ".agent"); const agentsCount = countFilesIfExists(path.join(agentDir, "agents"), (name) => name.endsWith(".md")); const workflowsCount = countFilesIfExists(path.join(agentDir, "workflows"), (name) => name.endsWith(".md")); const skillsCount = countSkillsRecursive(path.join(agentDir, "skills")); console.log("\n[gemini]"); + console.log(` 状态: ${geminiState.state}`); console.log(` 路径: ${agentDir}`); - console.log(` Agents: ${agentsCount}`); - console.log(` Skills: ${skillsCount}`); - console.log(` Workflows: ${workflowsCount}`); + if (geminiState.state === "installed") { + console.log(` Agents: ${agentsCount}`); + console.log(` Skills: ${skillsCount}`); + console.log(` Workflows: ${workflowsCount}`); + } else { + for (const issue of geminiState.integrity.issues || []) { + console.log(` Issue: ${issue}`); + } + } } - if (installedTargets.includes("codex")) { + const codexState = summary.targets.find((item) => item.targetName === "codex"); + if (codexState) { const managedDir = path.join(workspaceRoot, ".agents"); const legacyDir = path.join(workspaceRoot, ".codex"); const activeDir = fs.existsSync(managedDir) ? managedDir : legacyDir; @@ -1555,13 +1681,21 @@ function commandStatus(options) { const hasManifest = fs.existsSync(path.join(activeDir, "manifest.json")); const legacyDetected = fs.existsSync(legacyDir); console.log("\n[codex]"); + console.log(` 状态: ${codexState.state}`); console.log(` 路径: ${activeDir}`); console.log(` Skills: ${skillsCount}`); console.log(` Manifest: ${hasManifest ? "yes" : "no"}`); if (legacyDetected) { console.log(" Legacy: 检测到 .codex(建议执行 ag-kit update 迁移清理)"); } + if (codexState.state !== "installed") { + for (const issue of codexState.integrity.issues || []) { + console.log(` Issue: ${issue}`); + } + } } + + setQuietStatusExitCode(summary.state); } async function main() { diff --git a/docs/TECH.md b/docs/TECH.md index 608236e..2cc8689 100644 --- a/docs/TECH.md +++ b/docs/TECH.md @@ -5,6 +5,7 @@ bun install bun run test bun run ci:verify +bun run health-check cd web && bun install && bun run lint ``` @@ -52,6 +53,17 @@ cd web && bun install && bun run lint ### 测试隔离 - `AG_KIT_GLOBAL_ROOT`:替代 `$HOME`(用于测试与 CI,避免污染真实用户目录) +## 状态契约(自动化) +- `ag-kit status --quiet` / `ag-kit global status --quiet` 只输出三态: + - `installed`:检测到目标且完整性正常 + - `broken`:检测到目标但存在残缺、漂移或结构异常 + - `missing`:未检测到任何已安装目标 +- 退出码固定为: + - `0` = `installed` + - `1` = `broken` + - `2` = `missing` +- 若需要问题明细,使用 `ag-kit doctor`;`status` 负责健康状态,`doctor` 负责诊断细节。 + ## 手动回滚(全局 Skills) 1. 找到备份目录:`$HOME/.ag-kit/backups/global//...` 2. 按 Skill 回滚(推荐一次只处理一个 Skill 目录): @@ -79,6 +91,11 @@ Copy-Item "$HOME\\.ag-kit\\backups\\global\\$ts\\codex\\$skill" "$HOME\\.agents\ - `AG_KIT_GLOBAL_ROOT`:全局目录根(替代 `$HOME`) - `AG_KIT_SKIP_UPSTREAM_CHECK`:跳过上游同名包安装提示(测试用) +## 安装提示机制 +- npm 全局安装:`postinstall` 会尽力检测并提示上游英文版 `@vudovn/ag-kit` 冲突。 +- Bun 全局安装:Bun 默认会阻止本包 `postinstall`;因此冲突提示以内置 CLI 运行期检查为准,会在 `init` / `update` / `update-all` / `global sync` 时提示。 +- 冲突提示只负责提醒,不会自动修改当前安装状态;如需清理可执行 `npm uninstall -g @vudovn/ag-kit`。 + ## 常见故障 - 更新中断:原子替换保证不会出现半写状态;重新运行 `update`/`global sync` 即可。 - Windows `EPERM/EBUSY`:通常是目录被占用;关闭占用 `.agents/` 或目标 Skill 目录的进程后重试。 diff --git a/package.json b/package.json index 4716f60..df40bef 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "scripts": { "clean": "node scripts/clean.js", "clean:dry-run": "node scripts/clean.js --dry-run", - "health-check": "bash scripts/health-check.sh", + "health-check": "node scripts/health-check.js", "postinstall": "node scripts/postinstall-check.js", "test": "node --test \"tests/**/*.test.js\"", "ci:verify": "node scripts/ci-verify.js" @@ -34,7 +34,12 @@ "files": [ ".agents/**", "bin/**", + "scripts/clean.js", + "scripts/ci-verify.js", + "scripts/health-check.js", + "scripts/health-check.sh", "scripts/postinstall-check.js", + "tests/**/*.test.js", "README.md", "LICENSE", "CHANGELOG.md", diff --git a/scripts/ci-verify.js b/scripts/ci-verify.js index b2ae2e5..de3d238 100644 --- a/scripts/ci-verify.js +++ b/scripts/ci-verify.js @@ -71,7 +71,10 @@ function main() { } runCli(["global", "sync", "--quiet"], { env }); - runCli(["global", "status", "--quiet"], { env }); + const globalStatus = runCli(["global", "status", "--quiet"], { env }).trim(); + if (globalStatus !== "installed") { + throw new Error(`global status 结果异常: ${globalStatus}`); + } const codexSkill = path.join(globalRoot, ".agents", "skills", "workflow-plan", "SKILL.md"); const geminiSkill = path.join(globalRoot, ".gemini", "antigravity", "skills", "clean-code", "SKILL.md"); diff --git a/scripts/health-check.js b/scripts/health-check.js new file mode 100644 index 0000000..c3f23cb --- /dev/null +++ b/scripts/health-check.js @@ -0,0 +1,121 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { spawnSync } = require("node:child_process"); + +const ROOT_DIR = path.resolve(__dirname, ".."); + +function logStep(message) { + console.log(`[health-check] ${message}`); +} + +function runCommand(command, options = {}) { + const result = spawnSync(command, { + cwd: options.cwd || ROOT_DIR, + env: { + ...process.env, + ...options.env, + }, + encoding: "utf8", + shell: true, + }); + + if (result.status !== 0) { + const detail = result.stderr || result.stdout || ""; + throw new Error(`${command}\n${detail}`.trim()); + } + + return result.stdout || ""; +} + +function commandExists(command) { + const probe = spawnSync(command, { + encoding: "utf8", + shell: true, + stdio: "ignore", + }); + return probe.status === 0; +} + +function main() { + logStep("检查运行环境"); + if (!commandExists("node --version")) { + throw new Error("缺少命令: node"); + } + + const packageRunner = commandExists("bun --version") ? "bun" : "npm"; + if (!commandExists(`${packageRunner} --version`)) { + throw new Error(`缺少命令: ${packageRunner}`); + } + + logStep("执行测试套件"); + if (packageRunner === "bun") { + runCommand("bun run test"); + } else { + runCommand("npm test --silent"); + } + + logStep("验证 CLI 核心链路"); + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-health-check-")); + try { + const workspaceDir = path.join(tempRoot, "workspace"); + const indexPath = path.join(tempRoot, "workspaces.json"); + const globalRoot = path.join(tempRoot, "global-root"); + fs.mkdirSync(workspaceDir, { recursive: true }); + + const env = { + AG_KIT_INDEX_PATH: indexPath, + AG_KIT_GLOBAL_ROOT: globalRoot, + AG_KIT_SKIP_UPSTREAM_CHECK: "1", + }; + + runCommand(`node bin/ag-kit.js init --targets gemini,codex --path "${workspaceDir}" --quiet`, { env }); + + const status = runCommand(`node bin/ag-kit.js status --path "${workspaceDir}" --quiet`, { env }).trim(); + if (status !== "installed") { + throw new Error(`status 结果异常: ${status}`); + } + + runCommand(`node bin/ag-kit.js doctor --path "${workspaceDir}" --quiet`, { env }); + runCommand(`node bin/ag-kit.js update --path "${workspaceDir}" --quiet`, { env }); + runCommand("node bin/ag-kit.js update-all --dry-run --quiet", { env }); + runCommand(`node bin/ag-kit.js exclude add --path "${workspaceDir}" --quiet`, { env }); + + const excluded = runCommand("node bin/ag-kit.js exclude list --quiet", { env }); + if (!excluded.split(/\r?\n/).includes(workspaceDir)) { + throw new Error("exclude add 未生效"); + } + + runCommand(`node bin/ag-kit.js exclude remove --path "${workspaceDir}" --quiet`, { env }); + const excludedAfter = runCommand("node bin/ag-kit.js exclude list --quiet", { env }); + if (excludedAfter.split(/\r?\n/).includes(workspaceDir)) { + throw new Error("exclude remove 未生效"); + } + + runCommand("node bin/ag-kit.js global sync --quiet", { env }); + const globalStatus = runCommand("node bin/ag-kit.js global status --quiet", { env }).trim(); + if (globalStatus !== "installed") { + throw new Error(`global status 结果异常: ${globalStatus}`); + } + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + + logStep("执行清理预检"); + if (packageRunner === "bun") { + runCommand("bun run clean:dry-run"); + } else { + runCommand("npm run clean:dry-run --silent"); + } + + console.log("✅ 健康检查通过"); +} + +try { + main(); +} catch (err) { + console.error(`❌ ${err.message}`); + process.exit(1); +} diff --git a/scripts/health-check.sh b/scripts/health-check.sh index 404fdaa..fb5ddd8 100755 --- a/scripts/health-check.sh +++ b/scripts/health-check.sh @@ -3,63 +3,4 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -TMP_ROOT="" - -cleanup() { - if [[ -n "${TMP_ROOT}" && -d "${TMP_ROOT}" ]]; then - rm -rf "${TMP_ROOT}" - fi -} -trap cleanup EXIT - -log_step() { - printf '[health-check] %s\n' "$1" -} - -require_command() { - local cmd="$1" - if ! command -v "${cmd}" >/dev/null 2>&1; then - printf '❌ 缺少命令: %s\n' "${cmd}" >&2 - exit 1 - fi -} - -log_step "检查运行环境" -require_command node -require_command npm - -cd "${ROOT_DIR}" - -log_step "执行测试套件" -npm test --silent - -log_step "验证 CLI 核心链路" -TMP_ROOT="$(mktemp -d /tmp/ag-kit-health-check-XXXXXX)" -WORKSPACE_DIR="${TMP_ROOT}/workspace" -INDEX_PATH="${TMP_ROOT}/workspaces.json" -mkdir -p "${WORKSPACE_DIR}" -export AG_KIT_INDEX_PATH="${INDEX_PATH}" - -node bin/ag-kit.js init --targets gemini,codex --path "${WORKSPACE_DIR}" --quiet -if [[ "$(node bin/ag-kit.js status --path "${WORKSPACE_DIR}" --quiet)" != "installed" ]]; then - printf '❌ status 检查失败\n' >&2 - exit 1 -fi -node bin/ag-kit.js doctor --path "${WORKSPACE_DIR}" --quiet -node bin/ag-kit.js update --path "${WORKSPACE_DIR}" --quiet -node bin/ag-kit.js update-all --dry-run --quiet -node bin/ag-kit.js exclude add --path "${WORKSPACE_DIR}" --quiet -if ! node bin/ag-kit.js exclude list --quiet | grep -F -q "${WORKSPACE_DIR}"; then - printf '❌ exclude add 未生效\n' >&2 - exit 1 -fi -node bin/ag-kit.js exclude remove --path "${WORKSPACE_DIR}" --quiet -if node bin/ag-kit.js exclude list --quiet | grep -F -q "${WORKSPACE_DIR}"; then - printf '❌ exclude remove 未生效\n' >&2 - exit 1 -fi - -log_step "执行清理预检" -npm run clean:dry-run --silent - -printf '✅ 健康检查通过\n' +node "${ROOT_DIR}/scripts/health-check.js" "$@" From 2331e55268cd9f2856956bcf5e727ff391c23084 Mon Sep 17 00:00:00 2001 From: Mison Date: Thu, 12 Mar 2026 21:03:31 +0800 Subject: [PATCH 6/7] test(cli): cover status and tarball validation --- tests/cli-smoke.test.js | 52 +++++++++++++++++++++++++++++++ tests/global-sync.test.js | 26 +++++++++++++++- tests/health-check-script.test.js | 7 ++++- tests/package-tarball.test.js | 27 ++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 tests/package-tarball.test.js diff --git a/tests/cli-smoke.test.js b/tests/cli-smoke.test.js index 4dfb7e2..247df5a 100644 --- a/tests/cli-smoke.test.js +++ b/tests/cli-smoke.test.js @@ -228,6 +228,58 @@ describe("CLI Smoke", () => { assert.strictEqual(result.status, 0, result.stderr || result.stdout); }); + test("status --quiet should report missing with exit code 2 when nothing is installed", () => { + const result = runCli( + ["status", "--path", workspaceDir, "--quiet"], + { env: { AG_KIT_INDEX_PATH: indexPath } }, + ); + assert.strictEqual(result.status, 2); + assert.strictEqual((result.stdout || "").trim(), "missing"); + }); + + test("status --quiet should report broken when installation is incomplete", () => { + fs.mkdirSync(path.join(workspaceDir, ".agent"), { recursive: true }); + + const result = runCli( + ["status", "--path", workspaceDir, "--quiet"], + { env: { AG_KIT_INDEX_PATH: indexPath } }, + ); + assert.strictEqual(result.status, 1); + assert.strictEqual((result.stdout || "").trim(), "broken"); + }); + + test("status --quiet should report broken for codex drift", () => { + const initResult = runCli( + ["init", "--target", "codex", "--path", workspaceDir, "--quiet"], + { env: { AG_KIT_INDEX_PATH: indexPath } }, + ); + assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout); + + fs.writeFileSync(path.join(workspaceDir, ".agents", "AGENTS.md"), "drifted", "utf8"); + + const result = runCli( + ["status", "--path", workspaceDir, "--quiet"], + { env: { AG_KIT_INDEX_PATH: indexPath } }, + ); + assert.strictEqual(result.status, 1); + assert.strictEqual((result.stdout || "").trim(), "broken"); + }); + + test("status --quiet should report installed when installation is healthy", () => { + const initResult = runCli( + ["init", "--target", "codex", "--path", workspaceDir, "--quiet"], + { env: { AG_KIT_INDEX_PATH: indexPath } }, + ); + assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout); + + const result = runCli( + ["status", "--path", workspaceDir, "--quiet"], + { env: { AG_KIT_INDEX_PATH: indexPath } }, + ); + assert.strictEqual(result.status, 0, result.stderr || result.stdout); + assert.strictEqual((result.stdout || "").trim(), "installed"); + }); + test("status should reject unsupported --no-index option", () => { fs.mkdirSync(path.join(workspaceDir, ".agent"), { recursive: true }); diff --git a/tests/global-sync.test.js b/tests/global-sync.test.js index 1ebb936..47e4568 100644 --- a/tests/global-sync.test.js +++ b/tests/global-sync.test.js @@ -37,7 +37,18 @@ describe("Global Sync", () => { const result = runCli(["global", "status", "--quiet"], { env: { AG_KIT_GLOBAL_ROOT: tempDir }, }); - assert.notStrictEqual(result.status, 0); + assert.strictEqual(result.status, 2); + assert.strictEqual((result.stdout || "").trim(), "missing"); + }); + + test("global status should report broken when target root exists but skills are incomplete", () => { + fs.mkdirSync(path.join(tempDir, ".agents"), { recursive: true }); + + const result = runCli(["global", "status", "--quiet"], { + env: { AG_KIT_GLOBAL_ROOT: tempDir }, + }); + assert.strictEqual(result.status, 1); + assert.strictEqual((result.stdout || "").trim(), "broken"); }); test("global sync should install codex skills into $HOME/.agents/skills", () => { @@ -66,6 +77,19 @@ describe("Global Sync", () => { assert.ok(fs.existsSync(path.join(geminiRoot, "clean-code", "SKILL.md")), "missing expected gemini skill: clean-code"); }); + test("global status should report installed after global sync", () => { + const syncResult = runCli(["global", "sync", "--quiet"], { + env: { AG_KIT_GLOBAL_ROOT: tempDir }, + }); + assert.strictEqual(syncResult.status, 0, syncResult.stderr || syncResult.stdout); + + const statusResult = runCli(["global", "status", "--quiet"], { + env: { AG_KIT_GLOBAL_ROOT: tempDir }, + }); + assert.strictEqual(statusResult.status, 0, statusResult.stderr || statusResult.stdout); + assert.strictEqual((statusResult.stdout || "").trim(), "installed"); + }); + test("global sync should install gemini skills into ~/.gemini/antigravity/skills", () => { const result = runCli(["global", "sync", "--target", "gemini", "--quiet"], { env: { AG_KIT_GLOBAL_ROOT: tempDir }, diff --git a/tests/health-check-script.test.js b/tests/health-check-script.test.js index 7b1d98c..0f45cff 100644 --- a/tests/health-check-script.test.js +++ b/tests/health-check-script.test.js @@ -11,6 +11,11 @@ describe("Health Check Script", () => { fs.accessSync(scriptPath, fs.constants.X_OK); }); + test("health-check node entry should exist", () => { + const scriptPath = path.resolve(__dirname, "..", "scripts", "health-check.js"); + assert.ok(fs.existsSync(scriptPath), "missing scripts/health-check.js"); + }); + test("health-check script should pass bash syntax check", () => { const scriptPath = path.resolve(__dirname, "..", "scripts", "health-check.sh"); const result = spawnSync("bash", ["-n", scriptPath], { encoding: "utf8" }); @@ -20,6 +25,6 @@ describe("Health Check Script", () => { test("package scripts should expose health-check command", () => { const packageJsonPath = path.resolve(__dirname, "..", "package.json"); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); - assert.strictEqual(packageJson.scripts["health-check"], "bash scripts/health-check.sh"); + assert.strictEqual(packageJson.scripts["health-check"], "node scripts/health-check.js"); }); }); diff --git a/tests/package-tarball.test.js b/tests/package-tarball.test.js new file mode 100644 index 0000000..2395ced --- /dev/null +++ b/tests/package-tarball.test.js @@ -0,0 +1,27 @@ +const { test, describe } = require("node:test"); +const assert = require("node:assert"); +const path = require("path"); +const { spawnSync } = require("node:child_process"); + +const REPO_ROOT = path.resolve(__dirname, ".."); + +describe("Package Tarball", () => { + test("npm pack --dry-run should include maintenance scripts and tests", () => { + const result = spawnSync("npm", ["pack", "--json", "--dry-run"], { + cwd: REPO_ROOT, + encoding: "utf8", + }); + assert.strictEqual(result.status, 0, result.stderr || result.stdout); + + const payload = JSON.parse(result.stdout); + assert.ok(Array.isArray(payload) && payload.length > 0, "missing npm pack json payload"); + + const files = new Set(payload[0].files.map((item) => item.path)); + assert.ok(files.has("scripts/clean.js"), "tarball missing scripts/clean.js"); + assert.ok(files.has("scripts/ci-verify.js"), "tarball missing scripts/ci-verify.js"); + assert.ok(files.has("scripts/health-check.js"), "tarball missing scripts/health-check.js"); + assert.ok(files.has("scripts/health-check.sh"), "tarball missing scripts/health-check.sh"); + assert.ok(files.has("tests/cli-smoke.test.js"), "tarball missing tests/cli-smoke.test.js"); + assert.ok(files.has("tests/global-sync.test.js"), "tarball missing tests/global-sync.test.js"); + }); +}); From b64eff36160ac27c833f04659bf8c848dae90359 Mon Sep 17 00:00:00 2001 From: Mison Date: Fri, 13 Mar 2026 08:46:43 +0800 Subject: [PATCH 7/7] fix(cli): align global sync with real consumer paths --- README.md | 7 +- bin/ag-kit.js | 171 ++++++++++++++++++++--------- docs/PLAN.md | 7 +- docs/TECH.md | 41 +++++-- scripts/ci-verify.js | 8 +- scripts/health-check.js | 11 ++ tests/cli-smoke.test.js | 45 ++++++++ tests/global-sync.test.js | 29 +++-- tests/standards-compliance.test.js | 8 +- 9 files changed, 242 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index f4cc2c2..87bf13e 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ npm install -g . - 项目安装:`ag-kit init` / `ag-kit update`(功能最完整) - 全局安装:`ag-kit global sync`(仅同步 Skills,跨项目复用) - 默认行为:`ag-kit global sync` 未指定 `--target/--targets` 时,同步 `codex + gemini` +- 真实落盘: + - `codex` -> `~/.codex/skills/` + - `gemini` -> 同时写入 `~/.gemini/skills/` 与 `~/.gemini/antigravity/skills/` 示例: @@ -165,7 +168,7 @@ CLI(命令行界面)工具: | `ag-kit update` | 更新当前项目已安装目标 | | `ag-kit update-all` | 批量更新所有已登记工作区 | | `ag-kit doctor` | 诊断安装完整性(可 `--fix` 自愈) | -| `ag-kit global sync` | 全局同步 Skills(默认同步 codex+gemini) | +| `ag-kit global sync` | 全局同步 Skills(默认同步 codex + gemini;其中 gemini 同步到 gemini-cli 与 antigravity) | | `ag-kit global status` | 查看全局 Skills 安装状态 | | `ag-kit exclude` | 管理全局索引排除清单 | | `ag-kit status` | 检查安装状态 | @@ -227,7 +230,7 @@ bun install --cwd web bun run lint --cwd web ``` -> 说明:若你通过 `bun install -g` 安装 CLI,Bun 默认会阻止本包 `postinstall`。上游同名包冲突提示会在首次执行 `ag-kit init/update/global sync` 时给出。 +> 说明:若你通过 `bun install -g` 安装 CLI,Bun 默认会阻止本包 `postinstall`。上游同名包冲突提示会在首次执行 `ag-kit init/update/update-all/global sync` 时给出。 ## 卸载 diff --git a/bin/ag-kit.js b/bin/ag-kit.js index fe213c0..6507206 100755 --- a/bin/ag-kit.js +++ b/bin/ag-kit.js @@ -18,6 +18,30 @@ const WORKSPACE_INDEX_VERSION = 2; const UPSTREAM_GLOBAL_PACKAGE = "@vudovn/ag-kit"; const TOOLKIT_PACKAGE_NAMES = new Set(["@mison/ag-kit-cn", "antigravity-kit-cn", "antigravity-kit"]); const SUPPORTED_TARGETS = ["gemini", "codex"]; +const LEGACY_INDEX_TARGET_ALIASES = { + full: "gemini", +}; +const GLOBAL_TARGET_DESTINATIONS = { + codex: [ + { + id: "codex", + rootParts: [".codex"], + skillsParts: [".codex", "skills"], + }, + ], + gemini: [ + { + id: "gemini-cli", + rootParts: [".gemini", "skills"], + skillsParts: [".gemini", "skills"], + }, + { + id: "antigravity", + rootParts: [".gemini", "antigravity"], + skillsParts: [".gemini", "antigravity", "skills"], + }, + ], +}; const INDEX_LOCK_RETRY_MS = 50; const INDEX_LOCK_TIMEOUT_MS = 3000; const INDEX_LOCK_STALE_MS = 30000; @@ -56,15 +80,21 @@ function resolveGlobalRootDir() { return os.homedir(); } -function resolveGlobalSkillRoot(targetName) { - const globalRoot = resolveGlobalRootDir(); - if (targetName === "codex") { - return path.join(globalRoot, ".agents", "skills"); +function getGlobalDestinations(targetName, globalRoot = resolveGlobalRootDir()) { + const config = GLOBAL_TARGET_DESTINATIONS[targetName]; + if (!config) { + throw new Error(`未知目标: ${targetName}`); } - if (targetName === "gemini") { - return path.join(globalRoot, ".gemini", "antigravity", "skills"); - } - throw new Error(`未知目标: ${targetName}`); + return config.map((item) => ({ + ...item, + targetName, + rootDir: path.join(globalRoot, ...item.rootParts), + skillsRoot: path.join(globalRoot, ...item.skillsParts), + })); +} + +function listGlobalDestinations(globalRoot = resolveGlobalRootDir()) { + return Object.keys(GLOBAL_TARGET_DESTINATIONS).flatMap((targetName) => getGlobalDestinations(targetName, globalRoot)); } function resolveGlobalBackupRoot(timestamp) { @@ -108,7 +138,7 @@ function printUsage() { console.log(" ag-kit update [--path ] [--branch ] [--target |--targets ] [--no-index] [--quiet] [--dry-run]"); console.log(" ag-kit update-all [--branch ] [--targets ] [--prune-missing] [--quiet] [--dry-run]"); console.log(" ag-kit doctor [--path ] [--target |--targets ] [--fix] [--quiet]"); - console.log(" ag-kit global sync [--target |--targets ] [--branch ] [--quiet] [--dry-run] # 默认同步 codex+gemini"); + console.log(" ag-kit global sync [--target |--targets ] [--branch ] [--quiet] [--dry-run] # 默认同步 codex + gemini(cli+antigravity)"); console.log(" ag-kit global status [--quiet]"); console.log(" ag-kit exclude list [--quiet]"); console.log(" ag-kit exclude add --path [--dry-run] [--quiet]"); @@ -416,13 +446,34 @@ function normalizeTargetState(value) { }; } +function normalizeIndexTargetName(targetName) { + if (typeof targetName !== "string") { + return null; + } + const normalized = targetName.trim().toLowerCase(); + if (!normalized) { + return null; + } + if (Object.prototype.hasOwnProperty.call(LEGACY_INDEX_TARGET_ALIASES, normalized)) { + return LEGACY_INDEX_TARGET_ALIASES[normalized]; + } + if (SUPPORTED_TARGETS.includes(normalized)) { + return normalized; + } + return null; +} + function normalizeWorkspaceRecordV2(item, normalizedPath) { const targets = {}; if (item && item.targets && typeof item.targets === "object") { for (const [targetName, state] of Object.entries(item.targets)) { + const normalizedTargetName = normalizeIndexTargetName(targetName); + if (!normalizedTargetName) { + continue; + } const normalizedState = normalizeTargetState(state); if (normalizedState) { - targets[targetName] = normalizedState; + targets[normalizedTargetName] = normalizedState; } } } @@ -862,33 +913,16 @@ function evaluateWorkspaceState(workspaceRoot, options) { }; } -function getGlobalTargetPaths(globalRoot, targetName) { - if (targetName === "codex") { - return { - markerDir: path.join(globalRoot, ".agents"), - skillsRoot: path.join(globalRoot, ".agents", "skills"), - }; - } - if (targetName === "gemini") { - return { - markerDir: path.join(globalRoot, ".gemini", "antigravity"), - skillsRoot: path.join(globalRoot, ".gemini", "antigravity", "skills"), - }; - } - throw new Error(`未知全局目标: ${targetName}`); -} - function evaluateGlobalState() { const globalRoot = resolveGlobalRootDir(); - const targetStates = SUPPORTED_TARGETS.map((targetName) => { - const paths = getGlobalTargetPaths(globalRoot, targetName); - const markerExists = fs.existsSync(paths.markerDir); - const skillsExists = fs.existsSync(paths.skillsRoot); - const skillsCount = skillsExists ? countSkillsRecursive(paths.skillsRoot) : 0; + const targetStates = listGlobalDestinations(globalRoot).map((destination) => { + const rootExists = fs.existsSync(destination.rootDir); + const skillsExists = fs.existsSync(destination.skillsRoot); + const skillsCount = skillsExists ? countSkillsRecursive(destination.skillsRoot) : 0; let state = "missing"; const issues = []; - if (markerExists || skillsExists) { + if (rootExists || skillsExists) { if (!skillsExists) { state = "broken"; issues.push("Skills 根目录缺失"); @@ -901,10 +935,11 @@ function evaluateGlobalState() { } return { - targetName, + targetName: destination.id, + family: destination.targetName, state, - markerDir: paths.markerDir, - skillsRoot: paths.skillsRoot, + rootDir: destination.rootDir, + skillsRoot: destination.skillsRoot, skillsCount, issues, }; @@ -972,7 +1007,7 @@ function resolveTargetsForGlobalSync(options) { if (requested.length > 0) { return requested; } - // 保持 global sync 简洁:默认同步两个目标。 + // 保持 global sync 简洁:默认同步 codex + gemini;其中 gemini 会展开为 gemini-cli 与 antigravity。 return ["codex", "gemini"]; } @@ -1025,58 +1060,87 @@ function backupSkillDirectory(targetName, skillName, sourceDir, timestamp, optio log(options, `📦 已备份 ${targetName} 全局 Skill: ${skillName} -> ${backupDir}`); } -function syncSkillDirectory(targetName, srcDir, destDir, timestamp, options) { +function syncSkillDirectory(destination, srcDir, destDir, timestamp, options) { const exists = fs.existsSync(destDir); if (exists) { if (areDirectoriesEqual(srcDir, destDir)) { - log(options, `⏭️ 全局 Skill 已最新,无需同步: ${targetName}/${path.basename(destDir)}`); + log(options, `⏭️ 全局 Skill 已最新,无需同步: ${destination.id}/${path.basename(destDir)}`); return { skipped: 1, synced: 0, backedUp: 0 }; } } if (options.dryRun) { - log(options, `[dry-run] 将同步全局 Skill: ${targetName}/${path.basename(destDir)}`); + log(options, `[dry-run] 将同步全局 Skill: ${destination.id}/${path.basename(destDir)}`); return { skipped: 0, synced: 0, backedUp: exists ? 1 : 0 }; } let backedUp = 0; if (exists) { - backupSkillDirectory(targetName, path.basename(destDir), destDir, timestamp, options); + backupSkillDirectory(destination.id, path.basename(destDir), destDir, timestamp, options); backedUp = 1; } const logger = options.quiet ? (() => {}) : log.bind(null, options); AtomicWriter.atomicCopyDir(srcDir, destDir, { logger }); - log(options, `✅ 已同步全局 Skill: ${targetName}/${path.basename(destDir)}`); + log(options, `✅ 已同步全局 Skill: ${destination.id}/${path.basename(destDir)}`); return { skipped: 0, synced: 1, backedUp }; } function syncGlobalSkillsFromRoot(targetName, skillsRoot, timestamp, options) { - const destRoot = resolveGlobalSkillRoot(targetName); + const destinations = getGlobalDestinations(targetName); const skillNames = listSkillDirectories(skillsRoot); if (skillNames.length === 0) { throw new Error(`未检测到可同步的 Skills: ${skillsRoot}`); } if (options.dryRun) { - log(options, `[dry-run] 将同步 ${skillNames.length} 个全局 Skills -> ${destRoot}`); + for (const destination of destinations) { + log(options, `[dry-run] 将同步 ${skillNames.length} 个全局 Skills -> ${destination.skillsRoot}`); + } } let synced = 0; let skipped = 0; let backedUp = 0; + const destinationResults = []; + + for (const destination of destinations) { + let destinationSynced = 0; + let destinationSkipped = 0; + let destinationBackedUp = 0; + + for (const skillName of skillNames) { + const srcDir = path.join(skillsRoot, skillName); + const destDir = path.join(destination.skillsRoot, skillName); + const result = syncSkillDirectory(destination, srcDir, destDir, timestamp, options); + synced += result.synced; + skipped += result.skipped; + backedUp += result.backedUp; + destinationSynced += result.synced; + destinationSkipped += result.skipped; + destinationBackedUp += result.backedUp; + } - for (const skillName of skillNames) { - const srcDir = path.join(skillsRoot, skillName); - const destDir = path.join(destRoot, skillName); - const result = syncSkillDirectory(targetName, srcDir, destDir, timestamp, options); - synced += result.synced; - skipped += result.skipped; - backedUp += result.backedUp; + destinationResults.push({ + targetName: destination.id, + family: destination.targetName, + destRoot: destination.skillsRoot, + total: skillNames.length, + synced: destinationSynced, + skipped: destinationSkipped, + backedUp: destinationBackedUp, + }); } - return { total: skillNames.length, synced, skipped, backedUp, destRoot }; + return { + total: skillNames.length * destinations.length, + skillsPerDestination: skillNames.length, + synced, + skipped, + backedUp, + destinations: destinationResults, + }; } function applyGlobalSync(targetName, agentDir, timestamp, options) { @@ -1116,7 +1180,9 @@ async function commandGlobalSync(options) { const result = applyGlobalSync(target, agentDir, timestamp, options); if (!options.dryRun) { log(options, `📊 全局同步完成 [${target}]:总计 ${result.total},新增/覆盖 ${result.synced},跳过 ${result.skipped},备份 ${result.backedUp}`); - log(options, ` 目标路径: ${result.destRoot}`); + for (const item of result.destinations) { + log(options, ` - ${item.targetName}: ${item.destRoot}(每目标 ${item.total} 个 Skills)`); + } } } } finally { @@ -1152,6 +1218,7 @@ function commandGlobalStatus(options) { for (const item of summary.targets) { console.log(`\n[${item.targetName}:global]`); + console.log(` 家族: ${item.family}`); console.log(` 状态: ${item.state}`); console.log(` 路径: ${item.skillsRoot}`); if (item.state === "installed") { diff --git a/docs/PLAN.md b/docs/PLAN.md index 8e6bc7e..e6c3d50 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -19,8 +19,8 @@ Ag-Kit 只做一件事:把仓库内统一的 `.agents/` 模板,优雅地投 - 命令:`ag-kit global sync` / `ag-kit global status` - 默认行为:`ag-kit global sync` 未指定 `--target/--targets` 时,同步 `codex + gemini` - 目标路径: - - `codex` -> `$HOME/.agents/skills/` - - `gemini` -> `$HOME/.gemini/antigravity/skills/` + - `codex` -> `$HOME/.codex/skills/` + - `gemini` -> 同时写入 `$HOME/.gemini/skills/` 与 `$HOME/.gemini/antigravity/skills/` - 安全边界:全局只同步 Skills,不写入全局 Rules/Agents/Workflows。 ## 覆盖与回滚(全局同步) @@ -31,8 +31,9 @@ Ag-Kit 只做一件事:把仓库内统一的 `.agents/` 模板,优雅地投 ## 兼容策略 - Gemini/Antigravity:输出 `.agent/`,保持与官方工作区机制一致。 - Codex:受管目录为 `.agents/`,并使用 `manifest.json` 做完整性与漂移检测;识别并迁移遗留 `.codex/`。 +- 全局同步遵循真实消费端目录,而不是仓库模板源目录;仓库内仍以 `.agents/` 作为唯一 Canonical。 ## 成功标准 -- `ag-kit global sync` 一条命令即可完成全局 Skills 安装/更新(默认 codex+gemini)。 +- `ag-kit global sync` 一条命令即可完成全局 Skills 安装/更新(默认 codex + gemini,其中 gemini 同步到 gemini-cli 与 antigravity)。 - 覆盖可回滚:每次覆盖同名 Skill 都有可用备份。 - 跨平台 CI(Linux/macOS/Windows)验证主链路通过。 diff --git a/docs/TECH.md b/docs/TECH.md index 2cc8689..553a8ab 100644 --- a/docs/TECH.md +++ b/docs/TECH.md @@ -22,10 +22,11 @@ cd web && bun install && bun run lint - `codex`:项目根目录 `.agents/`(受管)+ `.agents-backup/`(漂移覆盖备份) ### 全局级(仅同步 Skills) -- `codex`:`$HOME/.agents/skills/` -- `gemini`:`$HOME/.gemini/antigravity/skills/` +- `codex`:`$HOME/.codex/skills/` +- `gemini-cli`:`$HOME/.gemini/skills/` +- `antigravity`:`$HOME/.gemini/antigravity/skills/` -> 说明:仓库内 Skills 源路径为 `.agents/skills/`,全局同步会将其写入上述全局目录。 +> 说明:仓库内 Skills 源路径为 `.agents/skills/`,全局同步会将其投影到真实工具读取的全局目录;仓库 Canonical 仍是 `.agents/`。 ## 端到端链路(简述) ### 项目安装 / 更新 @@ -41,14 +42,15 @@ cd web && bun install && bun run lint ## 全局同步:`ag-kit global sync/status` ### 默认目标 - 未指定 `--target/--targets`:默认同步 `codex + gemini` -- 可用 `--target codex` 或 `--targets codex,gemini` 限定目标 +- `--target gemini` / `--targets codex,gemini` 中的 `gemini` 会同时写入 gemini-cli 与 antigravity 两个消费端目录 ### 来源与覆盖策略 - 来源:默认使用本包内置 `.agents/`;也可用 `--branch ` 从远端分支拉取模板源 - 覆盖单位:每个 Skill 目录 - 覆盖策略:只覆盖同名 Skill,不清理其他 Skill - 原子替换:按 Skill 目录原子替换,避免半写状态 -- 覆盖前备份:覆盖同名 Skill 前备份到 `$HOME/.ag-kit/backups/global////...` +- 覆盖前备份:覆盖同名 Skill 前备份到 `$HOME/.ag-kit/backups/global////...` + - `consumer` 可能是 `codex`、`gemini-cli`、`antigravity` ### 测试隔离 - `AG_KIT_GLOBAL_ROOT`:替代 `$HOME`(用于测试与 CI,避免污染真实用户目录) @@ -67,23 +69,40 @@ cd web && bun install && bun run lint ## 手动回滚(全局 Skills) 1. 找到备份目录:`$HOME/.ag-kit/backups/global//...` 2. 按 Skill 回滚(推荐一次只处理一个 Skill 目录): - - Codex 目标:恢复到 `$HOME/.agents/skills//` - - Gemini 目标:恢复到 `$HOME/.gemini/antigravity/skills//` + - Codex 目标:恢复到 `$HOME/.codex/skills//` + - Gemini CLI:恢复到 `$HOME/.gemini/skills//` + - Antigravity:恢复到 `$HOME/.gemini/antigravity/skills//` macOS / Linux 示例(把某个 Skill 回滚为备份版本): ```bash ts="2026-03-12T12-00-00-000Z" skill="clean-code" -rm -rf "$HOME/.agents/skills/$skill" -cp -a "$HOME/.ag-kit/backups/global/$ts/codex/$skill" "$HOME/.agents/skills/$skill" +rm -rf "$HOME/.codex/skills/$skill" +cp -a "$HOME/.ag-kit/backups/global/$ts/codex/$skill" "$HOME/.codex/skills/$skill" ``` Windows PowerShell 示例: ```powershell $ts = "2026-03-12T12-00-00-000Z" $skill = "clean-code" -Remove-Item "$HOME\\.agents\\skills\\$skill" -Recurse -Force -ErrorAction SilentlyContinue -Copy-Item "$HOME\\.ag-kit\\backups\\global\\$ts\\codex\\$skill" "$HOME\\.agents\\skills\\$skill" -Recurse -Force +Remove-Item "$HOME\\.codex\\skills\\$skill" -Recurse -Force -ErrorAction SilentlyContinue +Copy-Item "$HOME\\.ag-kit\\backups\\global\\$ts\\codex\\$skill" "$HOME\\.codex\\skills\\$skill" -Recurse -Force +``` + +Gemini CLI 回滚示例: +```bash +ts="2026-03-12T12-00-00-000Z" +skill="clean-code" +rm -rf "$HOME/.gemini/skills/$skill" +cp -a "$HOME/.ag-kit/backups/global/$ts/gemini-cli/$skill" "$HOME/.gemini/skills/$skill" +``` + +Antigravity 回滚示例: +```bash +ts="2026-03-12T12-00-00-000Z" +skill="clean-code" +rm -rf "$HOME/.gemini/antigravity/skills/$skill" +cp -a "$HOME/.ag-kit/backups/global/$ts/antigravity/$skill" "$HOME/.gemini/antigravity/skills/$skill" ``` ## 环境变量 diff --git a/scripts/ci-verify.js b/scripts/ci-verify.js index de3d238..ddf9f7b 100644 --- a/scripts/ci-verify.js +++ b/scripts/ci-verify.js @@ -76,10 +76,12 @@ function main() { throw new Error(`global status 结果异常: ${globalStatus}`); } - const codexSkill = path.join(globalRoot, ".agents", "skills", "workflow-plan", "SKILL.md"); - const geminiSkill = path.join(globalRoot, ".gemini", "antigravity", "skills", "clean-code", "SKILL.md"); + const codexSkill = path.join(globalRoot, ".codex", "skills", "workflow-plan", "SKILL.md"); + const geminiCliSkill = path.join(globalRoot, ".gemini", "skills", "clean-code", "SKILL.md"); + const antigravitySkill = path.join(globalRoot, ".gemini", "antigravity", "skills", "clean-code", "SKILL.md"); ensureExists(codexSkill, "全局 Codex workflow-plan Skill"); - ensureExists(geminiSkill, "全局 Antigravity clean-code Skill"); + ensureExists(geminiCliSkill, "全局 Gemini CLI clean-code Skill"); + ensureExists(antigravitySkill, "全局 Antigravity clean-code Skill"); fs.rmSync(tempRoot, { recursive: true, force: true }); } diff --git a/scripts/health-check.js b/scripts/health-check.js index c3f23cb..be13302 100644 --- a/scripts/health-check.js +++ b/scripts/health-check.js @@ -99,6 +99,17 @@ function main() { if (globalStatus !== "installed") { throw new Error(`global status 结果异常: ${globalStatus}`); } + + const globalChecks = [ + path.join(globalRoot, ".codex", "skills", "workflow-plan", "SKILL.md"), + path.join(globalRoot, ".gemini", "skills", "clean-code", "SKILL.md"), + path.join(globalRoot, ".gemini", "antigravity", "skills", "clean-code", "SKILL.md"), + ]; + for (const targetPath of globalChecks) { + if (!fs.existsSync(targetPath)) { + throw new Error(`global sync 未生成预期文件: ${targetPath}`); + } + } } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); } diff --git a/tests/cli-smoke.test.js b/tests/cli-smoke.test.js index 247df5a..b741c4b 100644 --- a/tests/cli-smoke.test.js +++ b/tests/cli-smoke.test.js @@ -164,6 +164,51 @@ describe("CLI Smoke", () => { assert.ok(!hasTempWorkspace, "temp workspace record should be removed during update-all"); }); + test("update-all should normalize legacy full target records in v2 index", () => { + const now = new Date().toISOString(); + const persistentRoot = fs.mkdtempSync(path.join(REPO_ROOT, ".temp-ag-kit-legacy-index-")); + const persistentWorkspace = path.join(persistentRoot, "workspace"); + fs.mkdirSync(persistentWorkspace, { recursive: true }); + + try { + const initResult = runCli( + ["init", "--target", "codex", "--path", persistentWorkspace, "--quiet"], + { env: { AG_KIT_INDEX_PATH: indexPath } }, + ); + assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout); + + const seedIndex = { + version: 2, + updatedAt: now, + workspaces: [ + { + path: persistentWorkspace, + targets: { + full: { + version: "1.9.0", + installedAt: now, + updatedAt: now, + }, + }, + }, + ], + excludedPaths: [], + }; + fs.writeFileSync(indexPath, `${JSON.stringify(seedIndex, null, 2)}\n`, "utf8"); + + const result = runCli(["update-all", "--quiet"], { + env: { AG_KIT_INDEX_PATH: indexPath }, + }); + assert.strictEqual(result.status, 0, result.stderr || result.stdout); + + const normalized = JSON.parse(fs.readFileSync(indexPath, "utf8")); + assert.ok(!("full" in normalized.workspaces[0].targets), "legacy full target should be removed from rewritten index"); + assert.ok("codex" in normalized.workspaces[0].targets, "codex target should be preserved after update-all"); + } finally { + fs.rmSync(persistentRoot, { recursive: true, force: true }); + } + }); + test("update-all should auto-clean macOS /private/var temp alias records", () => { if (process.platform !== "darwin") { return; diff --git a/tests/global-sync.test.js b/tests/global-sync.test.js index 47e4568..19f5499 100644 --- a/tests/global-sync.test.js +++ b/tests/global-sync.test.js @@ -42,7 +42,7 @@ describe("Global Sync", () => { }); test("global status should report broken when target root exists but skills are incomplete", () => { - fs.mkdirSync(path.join(tempDir, ".agents"), { recursive: true }); + fs.mkdirSync(path.join(tempDir, ".codex"), { recursive: true }); const result = runCli(["global", "status", "--quiet"], { env: { AG_KIT_GLOBAL_ROOT: tempDir }, @@ -51,13 +51,13 @@ describe("Global Sync", () => { assert.strictEqual((result.stdout || "").trim(), "broken"); }); - test("global sync should install codex skills into $HOME/.agents/skills", () => { + test("global sync should install codex skills into $HOME/.codex/skills", () => { const result = runCli(["global", "sync", "--target", "codex", "--quiet"], { env: { AG_KIT_GLOBAL_ROOT: tempDir }, }); assert.strictEqual(result.status, 0, result.stderr || result.stdout); - const skillsRoot = path.join(tempDir, ".agents", "skills"); + const skillsRoot = path.join(tempDir, ".codex", "skills"); assert.ok(fs.existsSync(skillsRoot), "missing global codex skills root"); assert.ok(fs.existsSync(path.join(skillsRoot, "clean-code", "SKILL.md")), "missing expected skill: clean-code"); assert.ok(fs.existsSync(path.join(skillsRoot, "workflow-plan", "SKILL.md")), "missing expected workflow skill: workflow-plan"); @@ -69,12 +69,15 @@ describe("Global Sync", () => { }); assert.strictEqual(result.status, 0, result.stderr || result.stdout); - const codexRoot = path.join(tempDir, ".agents", "skills"); + const codexRoot = path.join(tempDir, ".codex", "skills"); assert.ok(fs.existsSync(path.join(codexRoot, "clean-code", "SKILL.md")), "missing expected codex skill: clean-code"); assert.ok(fs.existsSync(path.join(codexRoot, "workflow-plan", "SKILL.md")), "missing expected codex workflow skill: workflow-plan"); - const geminiRoot = path.join(tempDir, ".gemini", "antigravity", "skills"); - assert.ok(fs.existsSync(path.join(geminiRoot, "clean-code", "SKILL.md")), "missing expected gemini skill: clean-code"); + const geminiCliRoot = path.join(tempDir, ".gemini", "skills"); + assert.ok(fs.existsSync(path.join(geminiCliRoot, "clean-code", "SKILL.md")), "missing expected gemini-cli skill: clean-code"); + + const antigravityRoot = path.join(tempDir, ".gemini", "antigravity", "skills"); + assert.ok(fs.existsSync(path.join(antigravityRoot, "clean-code", "SKILL.md")), "missing expected antigravity skill: clean-code"); }); test("global status should report installed after global sync", () => { @@ -90,19 +93,23 @@ describe("Global Sync", () => { assert.strictEqual((statusResult.stdout || "").trim(), "installed"); }); - test("global sync should install gemini skills into ~/.gemini/antigravity/skills", () => { + test("global sync should install gemini skills into both ~/.gemini/skills and ~/.gemini/antigravity/skills", () => { const result = runCli(["global", "sync", "--target", "gemini", "--quiet"], { env: { AG_KIT_GLOBAL_ROOT: tempDir }, }); assert.strictEqual(result.status, 0, result.stderr || result.stdout); - const skillsRoot = path.join(tempDir, ".gemini", "antigravity", "skills"); - assert.ok(fs.existsSync(skillsRoot), "missing global antigravity skills root"); - assert.ok(fs.existsSync(path.join(skillsRoot, "clean-code", "SKILL.md")), "missing expected skill: clean-code"); + const geminiCliRoot = path.join(tempDir, ".gemini", "skills"); + assert.ok(fs.existsSync(geminiCliRoot), "missing global gemini-cli skills root"); + assert.ok(fs.existsSync(path.join(geminiCliRoot, "clean-code", "SKILL.md")), "missing expected gemini-cli skill: clean-code"); + + const antigravityRoot = path.join(tempDir, ".gemini", "antigravity", "skills"); + assert.ok(fs.existsSync(antigravityRoot), "missing global antigravity skills root"); + assert.ok(fs.existsSync(path.join(antigravityRoot, "clean-code", "SKILL.md")), "missing expected antigravity skill: clean-code"); }); test("global sync should create backup when overwriting an existing skill", () => { - const skillsRoot = path.join(tempDir, ".agents", "skills", "clean-code"); + const skillsRoot = path.join(tempDir, ".codex", "skills", "clean-code"); fs.mkdirSync(skillsRoot, { recursive: true }); fs.writeFileSync(path.join(skillsRoot, "SKILL.md"), "modified", "utf8"); diff --git a/tests/standards-compliance.test.js b/tests/standards-compliance.test.js index 9b6213e..0db338a 100644 --- a/tests/standards-compliance.test.js +++ b/tests/standards-compliance.test.js @@ -114,13 +114,15 @@ describe('Standards Compliance', () => { ); }); - test('user-facing docs should track current .agents skill locations', () => { + test('user-facing docs should track current repo and global skill locations', () => { const file = path.resolve('docs/TECH.md'); const content = fs.readFileSync(file, 'utf8'); - assert.ok(content.includes('$HOME/.agents/skills/'), 'missing global skill path: $HOME/.agents/skills/'); + assert.ok(content.includes('$HOME/.codex/skills/'), 'missing global skill path: $HOME/.codex/skills/'); + assert.ok(content.includes('$HOME/.gemini/skills/'), 'missing global skill path: $HOME/.gemini/skills/'); + assert.ok(content.includes('$HOME/.gemini/antigravity/skills/'), 'missing global skill path: $HOME/.gemini/antigravity/skills/'); assert.ok(content.includes('.agents/skills'), 'missing repo skill path: .agents/skills'); - assert.ok(!content.includes('.codex/skills/'), 'should not contain deprecated .codex/skills path'); + assert.ok(!content.includes('$HOME/.agents/skills/'), 'should not contain deprecated global path: $HOME/.agents/skills/'); }); test('.agents script files should stay identical to reference snapshot', { skip: !HAS_REF_SCRIPTS_ROOT }, () => {