@bonesofspring/ai-rules 0.2.20 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +17 -2
- package/bin/cli.js +83 -6
- package/package.json +5 -2
- package/presets/_shared/README.md +2 -1
- package/presets/_shared/assets/docs-specs/INDEX.md +4 -1
- package/presets/_shared/core/agent-team/agent-artifact-contracts.md +15 -0
- package/presets/_shared/core/agent-team/agent-task-metrics.md +1 -1
- package/presets/_shared/core/agent-team/agent-team-orchestrator.md +1 -3
- package/presets/_shared/core/meta/preset-pr-checklist.md +5 -1
- package/presets/_shared/core/meta/preset-token-budget.md +7 -3
- package/presets/_shared/core/meta/preset-twin-sync.md +2 -0
- package/presets/_shared/core/testing/testing-principles.md +70 -0
- package/presets/claude/android-kotlin/agents/README.md +2 -1
- package/presets/claude/android-kotlin/agents/build-verifier.md +2 -0
- package/presets/claude/android-kotlin/agents/code-reviewer.md +2 -0
- package/presets/claude/android-kotlin/agents/debugger.md +1 -0
- package/presets/claude/android-kotlin/agents/feature-developer.md +4 -1
- package/presets/claude/android-kotlin/agents/openspec-author.md +163 -0
- package/presets/claude/android-kotlin/agents/qa-tester.md +2 -0
- package/presets/claude/android-kotlin/agents/task-analyst.md +1 -1
- package/presets/claude/android-kotlin/agents/task-router.md +9 -1
- package/presets/claude/android-kotlin/agents/tech-writer.md +4 -0
- package/presets/claude/android-kotlin/commands/README.md +0 -2
- package/presets/claude/android-kotlin/commands/task-continue.md +1 -1
- package/presets/claude/android-kotlin/commands/task.md +1 -5
- package/presets/claude/android-kotlin/commands/technical-retro.md +2 -2
- package/presets/claude/android-kotlin/hooks/chain-team-phases.sh +105 -13
- package/presets/claude/android-kotlin/rules/README.md +1 -0
- package/presets/claude/android-kotlin/rules/tooling-and-review/agent-team-orchestrator.md +7 -4
- package/presets/claude/android-kotlin/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/android-kotlin/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/android-kotlin/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/android-kotlin/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/go/README.md +1 -1
- package/presets/claude/go/agents/README.md +3 -2
- package/presets/claude/go/agents/build-verifier.md +4 -0
- package/presets/claude/go/agents/code-reviewer.md +4 -0
- package/presets/claude/go/agents/debugger.md +4 -0
- package/presets/claude/go/agents/feature-developer.md +5 -0
- package/presets/claude/go/agents/openspec-author.md +163 -0
- package/presets/claude/go/agents/qa-tester.md +4 -0
- package/presets/claude/go/agents/task-router.md +9 -1
- package/presets/claude/go/agents/tech-writer.md +4 -0
- package/presets/claude/go/commands/README.md +0 -2
- package/presets/claude/go/commands/task-continue.md +1 -1
- package/presets/claude/go/commands/task.md +1 -4
- package/presets/claude/go/hooks/chain-team-phases.sh +105 -14
- package/presets/claude/go/rules/README.md +1 -0
- package/presets/claude/go/rules/tooling-and-review/agent-team-orchestrator.md +2 -4
- package/presets/claude/go/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/go/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/go/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/go/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/ios-swift/agents/README.md +3 -2
- package/presets/claude/ios-swift/agents/build-verifier.md +2 -0
- package/presets/claude/ios-swift/agents/code-reviewer.md +2 -0
- package/presets/claude/ios-swift/agents/debugger.md +1 -0
- package/presets/claude/ios-swift/agents/feature-developer.md +4 -1
- package/presets/claude/ios-swift/agents/openspec-author.md +163 -0
- package/presets/claude/ios-swift/agents/qa-tester.md +2 -0
- package/presets/claude/ios-swift/agents/task-analyst.md +1 -1
- package/presets/claude/ios-swift/agents/task-router.md +9 -1
- package/presets/claude/ios-swift/agents/tech-writer.md +4 -0
- package/presets/claude/ios-swift/commands/README.md +0 -2
- package/presets/claude/ios-swift/commands/task-continue.md +1 -1
- package/presets/claude/ios-swift/commands/task.md +1 -5
- package/presets/claude/ios-swift/commands/technical-retro.md +2 -2
- package/presets/claude/ios-swift/hooks/chain-team-phases.sh +105 -13
- package/presets/claude/ios-swift/rules/README.md +1 -0
- package/presets/claude/ios-swift/rules/tooling-and-review/agent-team-orchestrator.md +7 -4
- package/presets/claude/ios-swift/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/ios-swift/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/ios-swift/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/ios-swift/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/java/CLAUDE.md +1 -1
- package/presets/claude/java/agents/README.md +3 -2
- package/presets/claude/java/agents/build-verifier.md +4 -0
- package/presets/claude/java/agents/code-reviewer.md +4 -0
- package/presets/claude/java/agents/debugger.md +4 -0
- package/presets/claude/java/agents/feature-developer.md +5 -0
- package/presets/claude/java/agents/openspec-author.md +163 -0
- package/presets/claude/java/agents/qa-tester.md +4 -0
- package/presets/claude/java/agents/task-router.md +9 -1
- package/presets/claude/java/agents/tech-writer.md +4 -0
- package/presets/claude/java/commands/README.md +0 -2
- package/presets/claude/java/commands/task-continue.md +1 -1
- package/presets/claude/java/commands/task.md +1 -4
- package/presets/claude/java/hooks/chain-team-phases.sh +105 -14
- package/presets/claude/java/rules/README.md +1 -0
- package/presets/claude/java/rules/tooling-and-review/agent-team-orchestrator.md +2 -4
- package/presets/claude/java/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/java/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/java/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/java/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/mcp-ts/agents/build-verifier.md +4 -0
- package/presets/claude/mcp-ts/agents/feature-developer.md +5 -0
- package/presets/claude/mcp-ts/agents/task-analyst.md +1 -1
- package/presets/claude/mcp-ts/agents/task-router.md +1 -1
- package/presets/claude/mcp-ts/commands/README.md +0 -2
- package/presets/claude/mcp-ts/commands/task-continue.md +1 -1
- package/presets/claude/mcp-ts/commands/task.md +1 -4
- package/presets/claude/mcp-ts/commands/technical-retro.md +2 -2
- package/presets/claude/mcp-ts/hooks/chain-team-phases.sh +91 -20
- package/presets/claude/mcp-ts/rules/README.md +1 -0
- package/presets/claude/mcp-ts/rules/tooling-and-review/agent-team-orchestrator.md +2 -4
- package/presets/claude/mcp-ts/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/mcp-ts/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/mcp-ts/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/mcp-ts/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/next/README.md +1 -0
- package/presets/claude/next/agents/README.md +2 -2
- package/presets/claude/next/agents/build-verifier.md +13 -1
- package/presets/claude/next/agents/code-reviewer.md +2 -0
- package/presets/claude/next/agents/debugger.md +4 -0
- package/presets/claude/next/agents/feature-developer.md +4 -1
- package/presets/claude/next/agents/openspec-author.md +163 -0
- package/presets/claude/next/agents/qa-tester.md +4 -0
- package/presets/claude/next/agents/task-analyst.md +1 -1
- package/presets/claude/next/agents/task-router.md +5 -1
- package/presets/claude/next/agents/tech-writer.md +4 -0
- package/presets/claude/next/agents/unit-test-generator.md +1 -1
- package/presets/claude/next/agents/unit-test-planner.md +1 -1
- package/presets/claude/next/commands/README.md +0 -2
- package/presets/claude/next/commands/task-continue.md +1 -1
- package/presets/claude/next/commands/task.md +1 -4
- package/presets/claude/next/hooks/chain-team-phases.sh +105 -13
- package/presets/claude/next/rules/README.md +1 -0
- package/presets/claude/next/rules/api-and-data/http-client.md +1 -1
- package/presets/claude/next/rules/architecture/reference-features.md +1 -1
- package/presets/claude/next/rules/testing/README.md +7 -1
- package/presets/claude/next/rules/testing/tests-e2e-structure.md +86 -48
- package/presets/claude/next/rules/testing/tests-unit.md +78 -51
- package/presets/claude/next/rules/tooling-and-review/agent-team-orchestrator.md +3 -4
- package/presets/claude/next/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/next/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/next/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/next/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/next/rules/ui-and-accessibility/react-ui.md +1 -1
- package/presets/claude/next/skills/playwright-e2e/SKILL.md +5 -3
- package/presets/claude/next/skills/unit-testing/SKILL.md +6 -4
- package/presets/claude/next/team/README.md +0 -3
- package/presets/claude/nuxt/agents/README.md +1 -1
- package/presets/claude/nuxt/agents/build-verifier.md +2 -0
- package/presets/claude/nuxt/agents/code-reviewer.md +2 -0
- package/presets/claude/nuxt/agents/debugger.md +4 -0
- package/presets/claude/nuxt/agents/feature-developer.md +4 -1
- package/presets/claude/nuxt/agents/openspec-author.md +163 -0
- package/presets/claude/nuxt/agents/qa-tester.md +4 -0
- package/presets/claude/nuxt/agents/task-analyst.md +1 -1
- package/presets/claude/nuxt/agents/task-router.md +5 -1
- package/presets/claude/nuxt/agents/tech-writer.md +4 -0
- package/presets/claude/nuxt/commands/README.md +0 -2
- package/presets/claude/nuxt/commands/task-continue.md +1 -1
- package/presets/claude/nuxt/commands/task.md +1 -5
- package/presets/claude/nuxt/commands/technical-retro.md +2 -2
- package/presets/claude/nuxt/hooks/chain-team-phases.sh +105 -13
- package/presets/claude/nuxt/rules/README.md +1 -0
- package/presets/claude/nuxt/rules/testing/README.md +6 -0
- package/presets/claude/nuxt/rules/testing/tests-e2e-structure.md +86 -43
- package/presets/claude/nuxt/rules/testing/tests-unit.md +58 -45
- package/presets/claude/nuxt/rules/tooling-and-review/agent-team-orchestrator.md +8 -5
- package/presets/claude/nuxt/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/nuxt/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/nuxt/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/nuxt/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/nuxt/team/README.md +0 -3
- package/presets/claude/php-hexagonal/CLAUDE.md +1 -1
- package/presets/claude/php-hexagonal/agents/README.md +3 -2
- package/presets/claude/php-hexagonal/agents/build-verifier.md +4 -0
- package/presets/claude/php-hexagonal/agents/code-reviewer.md +4 -0
- package/presets/claude/php-hexagonal/agents/debugger.md +4 -0
- package/presets/claude/php-hexagonal/agents/feature-developer.md +5 -0
- package/presets/claude/php-hexagonal/agents/openspec-author.md +163 -0
- package/presets/claude/php-hexagonal/agents/qa-tester.md +4 -0
- package/presets/claude/php-hexagonal/agents/task-router.md +9 -1
- package/presets/claude/php-hexagonal/agents/tech-writer.md +4 -0
- package/presets/claude/php-hexagonal/commands/README.md +0 -2
- package/presets/claude/php-hexagonal/commands/task-continue.md +1 -1
- package/presets/claude/php-hexagonal/commands/task.md +1 -4
- package/presets/claude/php-hexagonal/hooks/chain-team-phases.sh +105 -14
- package/presets/claude/php-hexagonal/rules/README.md +1 -0
- package/presets/claude/php-hexagonal/rules/tooling-and-review/agent-team-orchestrator.md +2 -4
- package/presets/claude/php-hexagonal/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/php-laravel/agents/README.md +3 -2
- package/presets/claude/php-laravel/agents/build-verifier.md +4 -0
- package/presets/claude/php-laravel/agents/code-reviewer.md +4 -0
- package/presets/claude/php-laravel/agents/debugger.md +4 -0
- package/presets/claude/php-laravel/agents/feature-developer.md +5 -0
- package/presets/claude/php-laravel/agents/openspec-author.md +163 -0
- package/presets/claude/php-laravel/agents/qa-tester.md +4 -0
- package/presets/claude/php-laravel/agents/task-router.md +6 -1
- package/presets/claude/php-laravel/agents/tech-writer.md +4 -0
- package/presets/claude/php-laravel/commands/README.md +0 -2
- package/presets/claude/php-laravel/commands/task-continue.md +1 -1
- package/presets/claude/php-laravel/commands/task.md +1 -4
- package/presets/claude/php-laravel/hooks/chain-team-phases.sh +105 -14
- package/presets/claude/php-laravel/rules/README.md +1 -0
- package/presets/claude/php-laravel/rules/tooling-and-review/agent-team-orchestrator.md +2 -4
- package/presets/claude/php-laravel/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/php-laravel/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/php-laravel/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/php-laravel/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/svelte/agents/README.md +1 -1
- package/presets/claude/svelte/agents/build-verifier.md +2 -0
- package/presets/claude/svelte/agents/code-reviewer.md +2 -0
- package/presets/claude/svelte/agents/debugger.md +4 -0
- package/presets/claude/svelte/agents/feature-developer.md +4 -1
- package/presets/claude/svelte/agents/openspec-author.md +163 -0
- package/presets/claude/svelte/agents/qa-tester.md +4 -0
- package/presets/claude/svelte/agents/task-analyst.md +1 -1
- package/presets/claude/svelte/agents/task-router.md +5 -1
- package/presets/claude/svelte/agents/tech-writer.md +4 -0
- package/presets/claude/svelte/commands/README.md +0 -2
- package/presets/claude/svelte/commands/task-continue.md +1 -1
- package/presets/claude/svelte/commands/task.md +1 -5
- package/presets/claude/svelte/commands/technical-retro.md +2 -2
- package/presets/claude/svelte/hooks/chain-team-phases.sh +105 -13
- package/presets/claude/svelte/rules/README.md +1 -0
- package/presets/claude/svelte/rules/testing/README.md +6 -0
- package/presets/claude/svelte/rules/testing/tests-e2e-structure.md +87 -45
- package/presets/claude/svelte/rules/testing/tests-unit.md +70 -23
- package/presets/claude/svelte/rules/tooling-and-review/agent-team-orchestrator.md +8 -5
- package/presets/claude/svelte/rules/tooling-and-review/ai-local-overlay.md +48 -0
- package/presets/claude/svelte/rules/tooling-and-review/preset-pr-checklist.md +5 -1
- package/presets/claude/svelte/rules/tooling-and-review/preset-token-budget.md +7 -3
- package/presets/claude/svelte/rules/tooling-and-review/preset-twin-sync.md +2 -0
- package/presets/claude/svelte/team/README.md +0 -3
- package/presets/cursor/android-kotlin/agents/README.md +2 -1
- package/presets/cursor/android-kotlin/agents/build-verifier.md +2 -0
- package/presets/cursor/android-kotlin/agents/code-reviewer.md +2 -0
- package/presets/cursor/android-kotlin/agents/debugger.md +1 -0
- package/presets/cursor/android-kotlin/agents/feature-developer.md +4 -1
- package/presets/cursor/android-kotlin/agents/openspec-author.md +164 -0
- package/presets/cursor/android-kotlin/agents/qa-tester.md +2 -0
- package/presets/cursor/android-kotlin/agents/task-analyst.md +1 -1
- package/presets/cursor/android-kotlin/agents/task-router.md +9 -1
- package/presets/cursor/android-kotlin/agents/tech-writer.md +4 -0
- package/presets/cursor/android-kotlin/commands/README.md +0 -2
- package/presets/cursor/android-kotlin/commands/task-continue.md +1 -1
- package/presets/cursor/android-kotlin/commands/task.md +1 -5
- package/presets/cursor/android-kotlin/commands/technical-retro.md +2 -2
- package/presets/cursor/android-kotlin/hooks/chain-team-phases.sh +105 -13
- package/presets/cursor/android-kotlin/rules/agent-team-orchestrator.mdc +7 -4
- package/presets/cursor/android-kotlin/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/android-kotlin/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/android-kotlin/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/android-kotlin/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/go/README.md +6 -6
- package/presets/cursor/go/agents/README.md +3 -2
- package/presets/cursor/go/agents/build-verifier.md +4 -0
- package/presets/cursor/go/agents/code-reviewer.md +4 -0
- package/presets/cursor/go/agents/debugger.md +4 -0
- package/presets/cursor/go/agents/feature-developer.md +5 -0
- package/presets/cursor/go/agents/openspec-author.md +164 -0
- package/presets/cursor/go/agents/qa-tester.md +4 -0
- package/presets/cursor/go/agents/task-router.md +9 -1
- package/presets/cursor/go/agents/tech-writer.md +4 -0
- package/presets/cursor/go/commands/README.md +0 -2
- package/presets/cursor/go/commands/task-continue.md +1 -1
- package/presets/cursor/go/commands/task.md +1 -4
- package/presets/cursor/go/commands/technical-retro.md +2 -2
- package/presets/cursor/go/hooks/chain-team-phases.sh +105 -14
- package/presets/cursor/go/rules/agent-team-orchestrator.mdc +2 -4
- package/presets/cursor/go/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/go/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/go/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/go/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/ios-swift/README.md +6 -6
- package/presets/cursor/ios-swift/agents/README.md +3 -2
- package/presets/cursor/ios-swift/agents/build-verifier.md +2 -0
- package/presets/cursor/ios-swift/agents/code-reviewer.md +2 -0
- package/presets/cursor/ios-swift/agents/debugger.md +1 -0
- package/presets/cursor/ios-swift/agents/feature-developer.md +4 -1
- package/presets/cursor/ios-swift/agents/openspec-author.md +164 -0
- package/presets/cursor/ios-swift/agents/qa-tester.md +2 -0
- package/presets/cursor/ios-swift/agents/task-analyst.md +1 -1
- package/presets/cursor/ios-swift/agents/task-router.md +9 -1
- package/presets/cursor/ios-swift/agents/tech-writer.md +4 -0
- package/presets/cursor/ios-swift/commands/README.md +1 -3
- package/presets/cursor/ios-swift/commands/task-continue.md +1 -1
- package/presets/cursor/ios-swift/commands/task.md +1 -5
- package/presets/cursor/ios-swift/commands/technical-retro.md +2 -2
- package/presets/cursor/ios-swift/hooks/chain-team-phases.sh +105 -13
- package/presets/cursor/ios-swift/rules/agent-team-orchestrator.mdc +7 -4
- package/presets/cursor/ios-swift/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/ios-swift/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/ios-swift/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/ios-swift/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/java/AGENTS.md +1 -1
- package/presets/cursor/java/agents/README.md +3 -2
- package/presets/cursor/java/agents/build-verifier.md +4 -0
- package/presets/cursor/java/agents/code-reviewer.md +4 -0
- package/presets/cursor/java/agents/debugger.md +4 -0
- package/presets/cursor/java/agents/feature-developer.md +5 -0
- package/presets/cursor/java/agents/openspec-author.md +164 -0
- package/presets/cursor/java/agents/qa-tester.md +4 -0
- package/presets/cursor/java/agents/task-router.md +9 -1
- package/presets/cursor/java/agents/tech-writer.md +4 -0
- package/presets/cursor/java/commands/README.md +0 -2
- package/presets/cursor/java/commands/task-continue.md +1 -1
- package/presets/cursor/java/commands/task.md +1 -4
- package/presets/cursor/java/commands/technical-retro.md +2 -2
- package/presets/cursor/java/hooks/chain-team-phases.sh +105 -14
- package/presets/cursor/java/rules/agent-team-orchestrator.mdc +2 -4
- package/presets/cursor/java/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/java/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/java/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/java/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/mcp-ts/agents/build-verifier.md +4 -0
- package/presets/cursor/mcp-ts/agents/feature-developer.md +5 -0
- package/presets/cursor/mcp-ts/agents/task-analyst.md +1 -1
- package/presets/cursor/mcp-ts/agents/task-router.md +1 -1
- package/presets/cursor/mcp-ts/commands/README.md +0 -2
- package/presets/cursor/mcp-ts/commands/task-continue.md +1 -1
- package/presets/cursor/mcp-ts/commands/task.md +1 -4
- package/presets/cursor/mcp-ts/commands/technical-retro.md +2 -2
- package/presets/cursor/mcp-ts/hooks/chain-team-phases.sh +91 -20
- package/presets/cursor/mcp-ts/rules/agent-team-orchestrator.mdc +2 -4
- package/presets/cursor/mcp-ts/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/mcp-ts/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/mcp-ts/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/mcp-ts/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/next/README.md +8 -6
- package/presets/cursor/next/REPO_AGENTS.md +8 -0
- package/presets/cursor/next/agents/README.md +2 -2
- package/presets/cursor/next/agents/build-verifier.md +13 -1
- package/presets/cursor/next/agents/code-reviewer.md +2 -0
- package/presets/cursor/next/agents/debugger.md +4 -0
- package/presets/cursor/next/agents/feature-developer.md +4 -1
- package/presets/cursor/next/agents/openspec-author.md +164 -0
- package/presets/cursor/next/agents/qa-tester.md +4 -0
- package/presets/cursor/next/agents/task-analyst.md +1 -1
- package/presets/cursor/next/agents/task-router.md +5 -1
- package/presets/cursor/next/agents/tech-writer.md +4 -0
- package/presets/cursor/next/agents/unit-test-generator.md +1 -1
- package/presets/cursor/next/agents/unit-test-planner.md +1 -1
- package/presets/cursor/next/commands/README.md +0 -2
- package/presets/cursor/next/commands/task-continue.md +1 -1
- package/presets/cursor/next/commands/task.md +1 -4
- package/presets/cursor/next/commands/technical-retro.md +2 -2
- package/presets/cursor/next/hooks/chain-team-phases.sh +105 -13
- package/presets/cursor/next/rules/README.md +6 -3
- package/presets/cursor/next/rules/agent-team-orchestrator.mdc +7 -5
- package/presets/cursor/next/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/next/rules/http-client.mdc +1 -1
- package/presets/cursor/next/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/next/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/next/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/next/rules/react-ui.mdc +1 -1
- package/presets/cursor/next/rules/reference-features.mdc +1 -1
- package/presets/cursor/next/rules/tests-e2e-structure.mdc +85 -43
- package/presets/cursor/next/rules/tests-unit.mdc +79 -48
- package/presets/cursor/next/skills/playwright-e2e/SKILL.md +5 -3
- package/presets/cursor/next/skills/unit-testing/SKILL.md +6 -4
- package/presets/cursor/next/team/README.md +0 -3
- package/presets/cursor/nuxt/README.md +6 -6
- package/presets/cursor/nuxt/agents/README.md +1 -1
- package/presets/cursor/nuxt/agents/build-verifier.md +2 -0
- package/presets/cursor/nuxt/agents/code-reviewer.md +2 -0
- package/presets/cursor/nuxt/agents/debugger.md +4 -0
- package/presets/cursor/nuxt/agents/feature-developer.md +4 -1
- package/presets/cursor/nuxt/agents/openspec-author.md +164 -0
- package/presets/cursor/nuxt/agents/qa-tester.md +4 -0
- package/presets/cursor/nuxt/agents/task-analyst.md +1 -1
- package/presets/cursor/nuxt/agents/task-router.md +5 -1
- package/presets/cursor/nuxt/agents/tech-writer.md +4 -0
- package/presets/cursor/nuxt/commands/README.md +0 -2
- package/presets/cursor/nuxt/commands/task-continue.md +1 -1
- package/presets/cursor/nuxt/commands/task.md +1 -5
- package/presets/cursor/nuxt/commands/technical-retro.md +2 -2
- package/presets/cursor/nuxt/hooks/chain-team-phases.sh +105 -13
- package/presets/cursor/nuxt/rules/README.md +3 -1
- package/presets/cursor/nuxt/rules/agent-team-orchestrator.mdc +8 -5
- package/presets/cursor/nuxt/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/nuxt/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/nuxt/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/nuxt/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/nuxt/rules/tests-e2e-structure.mdc +87 -43
- package/presets/cursor/nuxt/rules/tests-unit.mdc +62 -47
- package/presets/cursor/nuxt/team/README.md +0 -3
- package/presets/cursor/php-hexagonal/AGENTS.md +1 -1
- package/presets/cursor/php-hexagonal/agents/README.md +3 -2
- package/presets/cursor/php-hexagonal/agents/build-verifier.md +4 -0
- package/presets/cursor/php-hexagonal/agents/code-reviewer.md +4 -0
- package/presets/cursor/php-hexagonal/agents/debugger.md +4 -0
- package/presets/cursor/php-hexagonal/agents/feature-developer.md +5 -0
- package/presets/cursor/php-hexagonal/agents/openspec-author.md +164 -0
- package/presets/cursor/php-hexagonal/agents/qa-tester.md +4 -0
- package/presets/cursor/php-hexagonal/agents/task-router.md +9 -1
- package/presets/cursor/php-hexagonal/agents/tech-writer.md +4 -0
- package/presets/cursor/php-hexagonal/commands/README.md +0 -2
- package/presets/cursor/php-hexagonal/commands/task-continue.md +1 -1
- package/presets/cursor/php-hexagonal/commands/task.md +1 -4
- package/presets/cursor/php-hexagonal/commands/technical-retro.md +2 -2
- package/presets/cursor/php-hexagonal/hooks/chain-team-phases.sh +105 -14
- package/presets/cursor/php-hexagonal/rules/agent-team-orchestrator.mdc +2 -4
- package/presets/cursor/php-hexagonal/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/php-hexagonal/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/php-hexagonal/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/php-hexagonal/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/php-laravel/AGENTS.md +1 -1
- package/presets/cursor/php-laravel/agents/README.md +3 -2
- package/presets/cursor/php-laravel/agents/build-verifier.md +4 -0
- package/presets/cursor/php-laravel/agents/code-reviewer.md +4 -0
- package/presets/cursor/php-laravel/agents/debugger.md +4 -0
- package/presets/cursor/php-laravel/agents/feature-developer.md +5 -0
- package/presets/cursor/php-laravel/agents/openspec-author.md +164 -0
- package/presets/cursor/php-laravel/agents/qa-tester.md +4 -0
- package/presets/cursor/php-laravel/agents/task-router.md +6 -1
- package/presets/cursor/php-laravel/agents/tech-writer.md +4 -0
- package/presets/cursor/php-laravel/commands/README.md +0 -2
- package/presets/cursor/php-laravel/commands/task-continue.md +1 -1
- package/presets/cursor/php-laravel/commands/task.md +1 -4
- package/presets/cursor/php-laravel/commands/technical-retro.md +2 -2
- package/presets/cursor/php-laravel/hooks/chain-team-phases.sh +105 -14
- package/presets/cursor/php-laravel/rules/agent-team-orchestrator.mdc +2 -4
- package/presets/cursor/php-laravel/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/php-laravel/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/php-laravel/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/php-laravel/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/svelte/README.md +6 -6
- package/presets/cursor/svelte/agents/README.md +1 -1
- package/presets/cursor/svelte/agents/build-verifier.md +2 -0
- package/presets/cursor/svelte/agents/code-reviewer.md +2 -0
- package/presets/cursor/svelte/agents/debugger.md +4 -0
- package/presets/cursor/svelte/agents/feature-developer.md +4 -1
- package/presets/cursor/svelte/agents/openspec-author.md +164 -0
- package/presets/cursor/svelte/agents/qa-tester.md +4 -0
- package/presets/cursor/svelte/agents/task-analyst.md +1 -1
- package/presets/cursor/svelte/agents/task-router.md +5 -1
- package/presets/cursor/svelte/agents/tech-writer.md +4 -0
- package/presets/cursor/svelte/commands/README.md +0 -2
- package/presets/cursor/svelte/commands/task-continue.md +1 -1
- package/presets/cursor/svelte/commands/task.md +1 -5
- package/presets/cursor/svelte/commands/technical-retro.md +2 -2
- package/presets/cursor/svelte/hooks/chain-team-phases.sh +105 -13
- package/presets/cursor/svelte/rules/README.md +3 -1
- package/presets/cursor/svelte/rules/agent-team-orchestrator.mdc +8 -5
- package/presets/cursor/svelte/rules/ai-local-overlay.mdc +43 -0
- package/presets/cursor/svelte/rules/preset-pr-checklist.mdc +5 -1
- package/presets/cursor/svelte/rules/preset-token-budget.mdc +7 -3
- package/presets/cursor/svelte/rules/preset-twin-sync.mdc +2 -0
- package/presets/cursor/svelte/rules/tests-e2e-structure.mdc +87 -43
- package/presets/cursor/svelte/rules/tests-unit.mdc +72 -23
- package/presets/cursor/svelte/team/README.md +0 -3
- package/scripts/_lib/embed-stems.json +31 -0
- package/scripts/aggregate-agent-task-metrics.mjs +23 -0
- package/scripts/check-chain-hook-coverage.sh +109 -0
- package/scripts/check-local-overlay.sh +136 -0
- package/scripts/check-preset-i18n.sh +2 -2
- package/scripts/check-preset-leakage.sh +4 -1
- package/scripts/check-preset-structure.sh +31 -1
- package/scripts/check-preset-token-budget.sh +120 -10
- package/scripts/check-rule-eval.mjs +16 -0
- package/scripts/check-shared-core-drift.sh +10 -32
- package/scripts/check-task-router-intents.sh +141 -0
- package/scripts/close-agent-task-human-gate.mjs +22 -5
- package/scripts/sync-shared-core-embeds.sh +9 -20
- package/scripts/test-agent-task-metrics-hooks.mjs +100 -2
- package/scripts/test-chain-team-phases-coverage.mjs +510 -0
- package/scripts/test-task-router-intents-fixtures.mjs +109 -0
- package/presets/claude/android-kotlin/commands/feature-continue.md +0 -23
- package/presets/claude/android-kotlin/commands/feature-start.md +0 -41
- package/presets/claude/go/commands/feature-continue.md +0 -23
- package/presets/claude/go/commands/feature-start.md +0 -38
- package/presets/claude/ios-swift/commands/feature-continue.md +0 -23
- package/presets/claude/ios-swift/commands/feature-start.md +0 -41
- package/presets/claude/java/commands/feature-continue.md +0 -23
- package/presets/claude/java/commands/feature-start.md +0 -38
- package/presets/claude/mcp-ts/commands/feature-continue.md +0 -23
- package/presets/claude/mcp-ts/commands/feature-start.md +0 -41
- package/presets/claude/next/commands/feature-continue.md +0 -23
- package/presets/claude/next/commands/feature-start.md +0 -38
- package/presets/claude/nuxt/commands/feature-continue.md +0 -23
- package/presets/claude/nuxt/commands/feature-start.md +0 -41
- package/presets/claude/php-hexagonal/commands/feature-continue.md +0 -23
- package/presets/claude/php-hexagonal/commands/feature-start.md +0 -38
- package/presets/claude/php-laravel/commands/feature-continue.md +0 -23
- package/presets/claude/php-laravel/commands/feature-start.md +0 -38
- package/presets/claude/svelte/commands/feature-continue.md +0 -23
- package/presets/claude/svelte/commands/feature-start.md +0 -41
- package/presets/cursor/android-kotlin/commands/feature-continue.md +0 -23
- package/presets/cursor/android-kotlin/commands/feature-start.md +0 -41
- package/presets/cursor/go/commands/feature-continue.md +0 -23
- package/presets/cursor/go/commands/feature-start.md +0 -41
- package/presets/cursor/ios-swift/commands/feature-continue.md +0 -23
- package/presets/cursor/ios-swift/commands/feature-start.md +0 -41
- package/presets/cursor/java/commands/feature-continue.md +0 -23
- package/presets/cursor/java/commands/feature-start.md +0 -41
- package/presets/cursor/mcp-ts/commands/feature-continue.md +0 -23
- package/presets/cursor/mcp-ts/commands/feature-start.md +0 -41
- package/presets/cursor/next/commands/feature-continue.md +0 -23
- package/presets/cursor/next/commands/feature-start.md +0 -41
- package/presets/cursor/nuxt/commands/feature-continue.md +0 -23
- package/presets/cursor/nuxt/commands/feature-start.md +0 -41
- package/presets/cursor/php-hexagonal/commands/feature-continue.md +0 -23
- package/presets/cursor/php-hexagonal/commands/feature-start.md +0 -41
- package/presets/cursor/php-laravel/commands/feature-continue.md +0 -23
- package/presets/cursor/php-laravel/commands/feature-start.md +0 -41
- package/presets/cursor/svelte/commands/feature-continue.md +0 -23
- package/presets/cursor/svelte/commands/feature-start.md +0 -41
|
@@ -16,7 +16,71 @@ from pathlib import Path
|
|
|
16
16
|
|
|
17
17
|
presets = Path(os.environ["AI_RULES_PRESETS_ROOT"])
|
|
18
18
|
STACKS = ("next", "nuxt", "svelte", "ios-swift", "android-kotlin", "go", "java", "mcp-ts", "php-hexagonal", "php-laravel")
|
|
19
|
-
BODY_CAP = 140
|
|
19
|
+
BODY_CAP = 140 # applied to 3-rule trios (Claude session-start, Cursor trio when len==3)
|
|
20
|
+
BODY_CAP_CURSOR_4 = 170 # Cursor now allows 4 always-on (next-app-core, package-manager, code-quality, ai-local-overlay)
|
|
21
|
+
# Slim-companion rules (UI-edit bundle, slim app-cores, slim tooling) may be
|
|
22
|
+
# shorter than 15 lines **if and only if** their stem is in the allow-list and
|
|
23
|
+
# body is <= MIN_SLIM_BODY_LINES. A slim rule whose body grows past this cap
|
|
24
|
+
# without being promoted to a domain rule FAILs the check. Reference:
|
|
25
|
+
# presets/_shared/core/meta/preset-twin-sync.md "Twin depth" section.
|
|
26
|
+
# Cursor and Claude share stems by convention; the list is stem-only and
|
|
27
|
+
# applied per platform.
|
|
28
|
+
SLIM_COMPANION_ALLOWLIST = frozenset({
|
|
29
|
+
"agent-team-intake",
|
|
30
|
+
"agent-team-orchestrator",
|
|
31
|
+
"ai-local-overlay",
|
|
32
|
+
"api-grpc",
|
|
33
|
+
"architecture-boundaries-ui",
|
|
34
|
+
"css-property-order-stylelint",
|
|
35
|
+
"design-guidance",
|
|
36
|
+
"domain-layer",
|
|
37
|
+
"e2e",
|
|
38
|
+
"feature-delivery-workflow",
|
|
39
|
+
"http-client",
|
|
40
|
+
"integration",
|
|
41
|
+
"ios-app-core",
|
|
42
|
+
"mcp-usage",
|
|
43
|
+
"messaging-adapters",
|
|
44
|
+
"mockup-review",
|
|
45
|
+
"module-public-api",
|
|
46
|
+
"navigation-coordinators",
|
|
47
|
+
"navigation-router-stack",
|
|
48
|
+
"navigation",
|
|
49
|
+
"navigation-router-ui",
|
|
50
|
+
"next-app-router",
|
|
51
|
+
"no-props-spread",
|
|
52
|
+
"no-type-assertion-as-import-export",
|
|
53
|
+
"no-v-bind-spread",
|
|
54
|
+
"package-manager",
|
|
55
|
+
"persistence",
|
|
56
|
+
"persistence-data",
|
|
57
|
+
"playwright-agents",
|
|
58
|
+
"post-change-build",
|
|
59
|
+
"post-change-lint",
|
|
60
|
+
"post-change-test",
|
|
61
|
+
"preset-layering",
|
|
62
|
+
"preset-no-cross-stack-leakage",
|
|
63
|
+
"preset-pr-checklist",
|
|
64
|
+
"preset-token-budget",
|
|
65
|
+
"preset-twin-sync",
|
|
66
|
+
"react-a11y-coding",
|
|
67
|
+
"script-setup-conventions",
|
|
68
|
+
"security-android",
|
|
69
|
+
"security-go",
|
|
70
|
+
"security-ios",
|
|
71
|
+
"security-next",
|
|
72
|
+
"security-php",
|
|
73
|
+
"state-and-viewmodels",
|
|
74
|
+
"technical-retro",
|
|
75
|
+
"tests-e2e",
|
|
76
|
+
"tests-integration",
|
|
77
|
+
"tests-unit",
|
|
78
|
+
"unit",
|
|
79
|
+
"ux-design",
|
|
80
|
+
"vue-a11y-coding",
|
|
81
|
+
"xcode-tooling",
|
|
82
|
+
})
|
|
83
|
+
MIN_SLIM_BODY_LINES = 5
|
|
20
84
|
MARKER_LINE_RE = re.compile(r"^<!--\s*shared-core:\s*[^>]+?\s*-->\s*$")
|
|
21
85
|
ALWAYS_APPLY_RE = re.compile(r"(?m)^alwaysApply:\s*true\s*$")
|
|
22
86
|
PATHS_RE = re.compile(r"(?m)^paths:\s*")
|
|
@@ -70,7 +134,7 @@ failed = 0
|
|
|
70
134
|
|
|
71
135
|
print("Preset token / session-start budget (Option A)")
|
|
72
136
|
print(f"Presets: {presets}")
|
|
73
|
-
print(f"Body line cap (trio
|
|
137
|
+
print(f"Body line cap (Claude trio): {BODY_CAP}; (Cursor 4-set): {BODY_CAP_CURSOR_4}")
|
|
74
138
|
print()
|
|
75
139
|
|
|
76
140
|
for stack in STACKS:
|
|
@@ -87,9 +151,15 @@ for stack in STACKS:
|
|
|
87
151
|
print(f"FAIL: meta preset-* must not be alwaysApply: {p.name}")
|
|
88
152
|
failed += 1
|
|
89
153
|
|
|
90
|
-
if len(always)
|
|
91
|
-
print(f"FAIL: Cursor alwaysApply count is {len(always)}, expected
|
|
154
|
+
if len(always) not in (3, 4):
|
|
155
|
+
print(f"FAIL: Cursor alwaysApply count is {len(always)}, expected 3 or 4 (4th slot reserved for ai-local-overlay)")
|
|
92
156
|
failed += 1
|
|
157
|
+
elif len(always) == 4:
|
|
158
|
+
# 4-set: ensure the 4th rule is ai-local-overlay (or declares itself as such).
|
|
159
|
+
extra = [p for p in always if p.stem not in {"code-quality-and-refactoring", "package-manager", "next-app-core", "nuxt-app-core", "svelte-app-core", "ios-app-core", "android-app-core", "go-app-core", "java-app-core", "mcp-ts-app-core", "php-app-core", "laravel-app-core", "architecture-boundaries", "mcp-server-boundaries"}]
|
|
160
|
+
if not any(p.stem == "ai-local-overlay" for p in always) and extra:
|
|
161
|
+
print(f"FAIL: Cursor alwaysApply=4 but no ai-local-overlay.mdc; the 4th slot is reserved for the local overlay rule")
|
|
162
|
+
failed += 1
|
|
93
163
|
|
|
94
164
|
# --- Claude no-paths (excl README) ---
|
|
95
165
|
session = claude_session_start(claude_rules)
|
|
@@ -108,10 +178,12 @@ for stack in STACKS:
|
|
|
108
178
|
)
|
|
109
179
|
failed += 1
|
|
110
180
|
|
|
111
|
-
# --- Body sum of intended
|
|
112
|
-
#
|
|
181
|
+
# --- Body sum of intended session-start set (Cursor alwaysApply + Claude session) ---
|
|
182
|
+
# Cursor allows 3 or 4 rules (4th slot reserved for ai-local-overlay); Claude is exactly 3.
|
|
113
183
|
for label, files in (("Cursor", always), ("Claude", session)):
|
|
114
|
-
if len(files)
|
|
184
|
+
if label == "Cursor" and len(files) not in (3, 4):
|
|
185
|
+
continue
|
|
186
|
+
if label == "Claude" and len(files) != 3:
|
|
115
187
|
continue
|
|
116
188
|
total = 0
|
|
117
189
|
for path in files:
|
|
@@ -121,13 +193,51 @@ for stack in STACKS:
|
|
|
121
193
|
total += n
|
|
122
194
|
name = path.name if label == "Cursor" else path.relative_to(claude_rules).as_posix()
|
|
123
195
|
print(f" {label} body lines {name}: {n}")
|
|
124
|
-
|
|
125
|
-
|
|
196
|
+
cap = BODY_CAP_CURSOR_4 if label == "Cursor" and len(always) == 4 else BODY_CAP
|
|
197
|
+
print(f" {label} session-start body sum: {total} (cap {cap}, files={len(files)})")
|
|
198
|
+
if total > cap:
|
|
126
199
|
print(
|
|
127
|
-
f"FAIL: {label} session-start body sum {total} > {
|
|
200
|
+
f"FAIL: {label} session-start body sum {total} > {cap} ({stack})"
|
|
128
201
|
)
|
|
129
202
|
failed += 1
|
|
130
203
|
|
|
204
|
+
# --- Slim-companion enforcement (per stack, both platforms) ---
|
|
205
|
+
# All .mdc (Cursor) / .md (Claude) rules under the stack's rules/ directory
|
|
206
|
+
# are scanned. A rule whose body is > MIN_SLIM_BODY_LINES AND whose stem
|
|
207
|
+
# is NOT in SLIM_COMPANION_ALLOWLIST must be promoted to a domain rule
|
|
208
|
+
# (>=15 body lines per preset-twin-sync.md "Twin depth"). FAIL otherwise.
|
|
209
|
+
slim_violations: list[str] = []
|
|
210
|
+
for rules_dir in (cursor_rules, claude_rules):
|
|
211
|
+
if not rules_dir.exists():
|
|
212
|
+
continue
|
|
213
|
+
pattern = "*.mdc" if rules_dir is cursor_rules else "*.md"
|
|
214
|
+
for rule_path in sorted(rules_dir.rglob(pattern)):
|
|
215
|
+
if rule_path.name == "README.md":
|
|
216
|
+
continue
|
|
217
|
+
text = rule_path.read_text(encoding="utf-8")
|
|
218
|
+
_, body = strip_frontmatter(text)
|
|
219
|
+
n = body_nonempty_lines(body)
|
|
220
|
+
if n <= MIN_SLIM_BODY_LINES:
|
|
221
|
+
continue
|
|
222
|
+
# Skip rules explicitly in the slim-companion allow-list (they are
|
|
223
|
+
# allowed to be short pointers regardless of body length).
|
|
224
|
+
if rule_path.stem in SLIM_COMPANION_ALLOWLIST:
|
|
225
|
+
continue
|
|
226
|
+
# Domain rules (>=15 lines) are fine; we only flag the 6..14 line
|
|
227
|
+
# "slim-but-not-allowed" bucket.
|
|
228
|
+
if n >= 15:
|
|
229
|
+
continue
|
|
230
|
+
slim_violations.append(
|
|
231
|
+
f"{rule_path.relative_to(presets).as_posix()} ({n} lines, slim but not in allow-list)"
|
|
232
|
+
)
|
|
233
|
+
if slim_violations:
|
|
234
|
+
print(f" Slim-companion allow-list violations ({len(slim_violations)}):")
|
|
235
|
+
for v in slim_violations[:10]:
|
|
236
|
+
print(f" FAIL: {v}")
|
|
237
|
+
if len(slim_violations) > 10:
|
|
238
|
+
print(f" ... and {len(slim_violations) - 10} more")
|
|
239
|
+
failed += len(slim_violations)
|
|
240
|
+
|
|
131
241
|
print()
|
|
132
242
|
|
|
133
243
|
if failed:
|
|
@@ -99,6 +99,22 @@ function checkFile(relPath, minPairs, requireImpact) {
|
|
|
99
99
|
) && ok;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Order check: each Incorrect heading must precede its matching Correct heading.
|
|
103
|
+
// Without this, an agent reading the rule may learn a "correct" pattern before
|
|
104
|
+
// seeing the "incorrect" example it contrasts with.
|
|
105
|
+
if (incorrectCount > 0 && correctCount > 0) {
|
|
106
|
+
const incRe = /^#{2,3}\s+Incorrect\s*$/gim;
|
|
107
|
+
const corRe = /^#{2,3}\s+Correct\s*$/gim;
|
|
108
|
+
const firstInc = text.search(incRe);
|
|
109
|
+
const firstCor = text.search(corRe);
|
|
110
|
+
if (firstInc >= 0 && firstCor >= 0 && firstInc >= firstCor) {
|
|
111
|
+
ok =
|
|
112
|
+
fail(
|
|
113
|
+
`${relPath}: first "Incorrect" heading (offset ${firstInc}) must precede first "Correct" heading (offset ${firstCor})`,
|
|
114
|
+
) && ok;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
const incorrectBodies = sectionBodies(text, 'Incorrect');
|
|
103
119
|
const correctBodies = sectionBodies(text, 'Correct');
|
|
104
120
|
for (let i = 0; i < Math.min(incorrectBodies.length, correctBodies.length); i++) {
|
|
@@ -7,48 +7,26 @@ set -euo pipefail
|
|
|
7
7
|
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|
8
8
|
export AI_RULES_PRESETS_ROOT="$ROOT/packages/ai-rules/presets"
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
11
|
+
EMBED_LIST_FILE="$SCRIPT_DIR/_lib/embed-stems.json"
|
|
12
|
+
|
|
13
|
+
python3 - "$EMBED_LIST_FILE" <<'PY'
|
|
11
14
|
from __future__ import annotations
|
|
12
15
|
|
|
16
|
+
import json
|
|
13
17
|
import os
|
|
14
18
|
import re
|
|
15
19
|
import sys
|
|
16
20
|
from pathlib import Path
|
|
17
21
|
|
|
22
|
+
embed_list_file = Path(sys.argv[1])
|
|
23
|
+
_stems = json.loads(embed_list_file.read_text(encoding="utf-8"))
|
|
24
|
+
EMBED = _stems["embed"]
|
|
25
|
+
LINEAGE = _stems["lineage"]
|
|
26
|
+
|
|
18
27
|
presets = Path(os.environ["AI_RULES_PRESETS_ROOT"])
|
|
19
28
|
core_root = presets / "_shared" / "core"
|
|
20
29
|
|
|
21
|
-
EMBED = [
|
|
22
|
-
"quality/code-quality-and-refactoring.md",
|
|
23
|
-
"quality/anti-sycophancy-discipline.md",
|
|
24
|
-
"quality/design-guidance.md",
|
|
25
|
-
"quality/mcp-usage.md",
|
|
26
|
-
"meta/preset-layering.md",
|
|
27
|
-
"meta/preset-twin-sync.md",
|
|
28
|
-
"meta/preset-token-budget.md",
|
|
29
|
-
"meta/preset-pr-checklist.md",
|
|
30
|
-
"meta/preset-no-cross-stack-leakage.md",
|
|
31
|
-
"review/code-review-mr.md",
|
|
32
|
-
"review/technical-retro.md",
|
|
33
|
-
"review/mockup-review.md",
|
|
34
|
-
"review/ux-design.md",
|
|
35
|
-
"agent-team/agent-team-intake.md",
|
|
36
|
-
"architecture/product-specs.md",
|
|
37
|
-
"architecture/product-specs-authoring.md",
|
|
38
|
-
# Whole-file embed: skill debug-investigation = core + ## Stack notes only.
|
|
39
|
-
"quality/debug-investigation.md",
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
LINEAGE = [
|
|
43
|
-
"architecture/feature-delivery-workflow.md",
|
|
44
|
-
"architecture/reference-features.template.md",
|
|
45
|
-
"agent-team/agent-team-orchestrator.md",
|
|
46
|
-
# Section snippets in fat agents/skills — body is not the whole file.
|
|
47
|
-
"quality/verification-before-completion.md",
|
|
48
|
-
"review/receiving-code-review.md",
|
|
49
|
-
"agent-team/task-classification.md",
|
|
50
|
-
]
|
|
51
|
-
|
|
52
30
|
# Full-line sync marker only (do not strip prose that *mentions* the marker).
|
|
53
31
|
MARKER_LINE_RE = re.compile(r"^<!--\s*shared-core:\s*[^>]+?\s*-->\s*$")
|
|
54
32
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Cross-check `task-router.md` intent rows against `agents/*.md` `name:` frontmatter.
|
|
3
|
+
#
|
|
4
|
+
# Two-sided verification:
|
|
5
|
+
# 1. Every agent file (frontmatter `name:` other than `task-router` itself) must
|
|
6
|
+
# be referenced somewhere in the stack's `task-router.md` so the router knows
|
|
7
|
+
# how to invoke it. Missing mention ⇒ FAIL (the agent will silently never
|
|
8
|
+
# receive a handoff).
|
|
9
|
+
# 2. Every agent stem referenced in `task-router.md` (in the `Default steps`
|
|
10
|
+
# column, the `Model tiers` table, or other inline mentions) must have a
|
|
11
|
+
# corresponding `agents/<stem>.md` file. Missing agent ⇒ FAIL (the router
|
|
12
|
+
# emits an intent that no agent can satisfy).
|
|
13
|
+
#
|
|
14
|
+
# The check is per `(platform, stack)` pair and runs all 20 ships (cursor + claude
|
|
15
|
+
# × 10 stacks). Exits non-zero on any FAIL.
|
|
16
|
+
#
|
|
17
|
+
# Stack-specific exemptions:
|
|
18
|
+
# - mcp-ts does not ship `playwright-test-*`, `xcuitest-test-*`, `unit-test-*`
|
|
19
|
+
# in the standard mix; intent rows that reference those agents are flagged
|
|
20
|
+
# only on stacks that ship the corresponding `agents/<stem>.md` file.
|
|
21
|
+
# - Stack leakage (positive agent stems on the wrong stack) is enforced in
|
|
22
|
+
# `check-preset-leakage.sh`; this check covers task-router sync only.
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|
26
|
+
# Allow callers (notably the fixture test) to redirect the presets root via
|
|
27
|
+
# the env var. Default to the real repo path.
|
|
28
|
+
export AI_RULES_PRESETS_ROOT="${AI_RULES_PRESETS_ROOT:-$ROOT/packages/ai-rules/presets}"
|
|
29
|
+
|
|
30
|
+
python3 <<'PY'
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
import re
|
|
35
|
+
import sys
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
presets = Path(os.environ["AI_RULES_PRESETS_ROOT"])
|
|
39
|
+
PLATFORMS = ("cursor", "claude")
|
|
40
|
+
STACKS = ("next", "nuxt", "svelte", "ios-swift", "android-kotlin", "go", "java", "mcp-ts", "php-hexagonal", "php-laravel")
|
|
41
|
+
|
|
42
|
+
# Agents that are shipped for orchestration purposes but do not need to appear
|
|
43
|
+
# in task-router.md intent rows (router-only / analyzer-only / orchestrator-only).
|
|
44
|
+
ROUTER_EXEMPT_FROM_INTENT = frozenset({
|
|
45
|
+
"task-router", # this file
|
|
46
|
+
"codebase-analyzer", # ad-hoc explain-as-is agent
|
|
47
|
+
"agent-team-intake", # one-off intake, not a pipeline target
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
# Agent-stem regex: kebab-case, lowercase, may include digits and dots (x.y).
|
|
51
|
+
# Two patterns: (a) hyphen/dot-bounded names like `feature-developer`, `unit.test`,
|
|
52
|
+
# (b) single-word agents that are present in `shipped_names`. Both feed the
|
|
53
|
+
# `agent_like` filter.
|
|
54
|
+
AGENT_NAME_RE = re.compile(r"\b([a-z][a-z0-9]*(?:[-.][a-z0-9]+)+)\b")
|
|
55
|
+
SINGLE_WORD_AGENT_RE = re.compile(r"\b([a-z][a-z0-9]+)\b")
|
|
56
|
+
FRONTMATTER_NAME_RE = re.compile(r"^name:\s*([a-z][a-z0-9]*(?:[-.][a-z0-9]+)+)\s*$", re.MULTILINE)
|
|
57
|
+
|
|
58
|
+
failures: list[str] = []
|
|
59
|
+
checks = 0
|
|
60
|
+
|
|
61
|
+
for platform in PLATFORMS:
|
|
62
|
+
for stack in STACKS:
|
|
63
|
+
agents_dir = presets / platform / stack / "agents"
|
|
64
|
+
if not agents_dir.is_dir():
|
|
65
|
+
continue
|
|
66
|
+
task_router = agents_dir / "task-router.md"
|
|
67
|
+
if not task_router.is_file():
|
|
68
|
+
failures.append(f"{platform}/{stack}: missing task-router.md")
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
# 1) All agent `name:` frontmatter in this stack's agents dir.
|
|
72
|
+
shipped_names: set[str] = set()
|
|
73
|
+
for path in sorted(agents_dir.glob("*.md")):
|
|
74
|
+
text = path.read_text(encoding="utf-8")
|
|
75
|
+
m = FRONTMATTER_NAME_RE.search(text)
|
|
76
|
+
if m:
|
|
77
|
+
shipped_names.add(m.group(1))
|
|
78
|
+
# Also pick up the file stem as a fallback (in case frontmatter is missing).
|
|
79
|
+
if path.stem not in shipped_names and path.stem != "README":
|
|
80
|
+
shipped_names.add(path.stem)
|
|
81
|
+
|
|
82
|
+
# 2) All agent stems referenced in task-router.md (anywhere in body).
|
|
83
|
+
router_text = task_router.read_text(encoding="utf-8")
|
|
84
|
+
# Restrict to body (drop YAML frontmatter).
|
|
85
|
+
body = router_text
|
|
86
|
+
if router_text.startswith("---\n"):
|
|
87
|
+
end = router_text.find("\n---\n", 4)
|
|
88
|
+
if end >= 0:
|
|
89
|
+
body = router_text[end + 5 :]
|
|
90
|
+
referenced_in_router: set[str] = set(AGENT_NAME_RE.findall(body))
|
|
91
|
+
# Single-word tokens that match a shipped agent (e.g. `debugger`,
|
|
92
|
+
# `codebase-analyzer`, `ci-investigator`) are valid router mentions even
|
|
93
|
+
# without a hyphen/dot boundary.
|
|
94
|
+
for token in SINGLE_WORD_AGENT_RE.findall(body):
|
|
95
|
+
if token in shipped_names:
|
|
96
|
+
referenced_in_router.add(token)
|
|
97
|
+
agent_like: set[str] = referenced_in_router - ROUTER_EXEMPT_FROM_INTENT
|
|
98
|
+
|
|
99
|
+
# Subset of shipped agents that are not router-exempt AND not referenced.
|
|
100
|
+
shipped_pipeline_agents = shipped_names - ROUTER_EXEMPT_FROM_INTENT
|
|
101
|
+
# 1-sided check (FAIL when shipped but not mentioned).
|
|
102
|
+
not_referenced = sorted(shipped_pipeline_agents - agent_like)
|
|
103
|
+
for name in not_referenced:
|
|
104
|
+
checks += 1
|
|
105
|
+
# The README is not an agent.
|
|
106
|
+
if name == "README":
|
|
107
|
+
continue
|
|
108
|
+
# Skip agents only used as commands or skills (e.g. technical-retro,
|
|
109
|
+
# agent-team-intake when still present). These are explicitly
|
|
110
|
+
# exempt — but we already filter ROUTER_EXEMPT_FROM_INTENT, so any
|
|
111
|
+
# remaining mismatch is a real gap.
|
|
112
|
+
failures.append(
|
|
113
|
+
f"{platform}/{stack}: ships agent '{name}.md' but task-router.md does not mention '{name}'"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# 2-sided check (FAIL when mentioned in router but not shipped).
|
|
117
|
+
not_shipped = sorted(agent_like - shipped_names)
|
|
118
|
+
for name in not_shipped:
|
|
119
|
+
checks += 1
|
|
120
|
+
# Allow mentions of common stacks/tools that aren't agents (filter).
|
|
121
|
+
# Common false positives: branch names, mixed-case strings, etc.
|
|
122
|
+
# The regex already constrains to lowercase kebab/dot; further filter
|
|
123
|
+
# by requiring the suffix to look like an agent suffix.
|
|
124
|
+
if name in {"feature-developer", "build-verifier", "code-reviewer"}:
|
|
125
|
+
# These should be shipped; surface as FAIL.
|
|
126
|
+
failures.append(
|
|
127
|
+
f"{platform}/{stack}: task-router.md references '{name}' but agents/{name}.md is missing"
|
|
128
|
+
)
|
|
129
|
+
# Otherwise ignore (likely prose token or matcher noise).
|
|
130
|
+
|
|
131
|
+
if failures:
|
|
132
|
+
print(f"FAIL: task-router vs agents drift ({len(failures)} issue(s) / {checks} checks):")
|
|
133
|
+
for f in failures[:30]:
|
|
134
|
+
print(f" - {f}")
|
|
135
|
+
if len(failures) > 30:
|
|
136
|
+
print(f" ... and {len(failures) - 30} more")
|
|
137
|
+
sys.exit(1)
|
|
138
|
+
|
|
139
|
+
print(f"OK: task-router.md intent rows match agents/*.md ({checks} checks across {len(PLATFORMS) * len(STACKS)} (platform, stack) pairs)")
|
|
140
|
+
sys.exit(0)
|
|
141
|
+
PY
|
|
@@ -13,18 +13,35 @@ import path from 'node:path';
|
|
|
13
13
|
|
|
14
14
|
function parseArgs(argv) {
|
|
15
15
|
const options = { cwd: process.cwd(), platform: 'cursor', slug: null };
|
|
16
|
+
// Read the next token from argv for value-taking flags. Returns the value or null
|
|
17
|
+
// when the flag is missing its value; the caller exits with a friendly error.
|
|
18
|
+
function nextValue(flag, i) {
|
|
19
|
+
const next = argv[i + 1];
|
|
20
|
+
if (next === undefined || next.startsWith('-')) {
|
|
21
|
+
console.error(`Missing value for ${flag} (use ${flag} <value>)`);
|
|
22
|
+
process.exit(2);
|
|
23
|
+
}
|
|
24
|
+
return next;
|
|
25
|
+
}
|
|
16
26
|
for (let i = 2; i < argv.length; i += 1) {
|
|
17
27
|
const arg = argv[i];
|
|
18
|
-
if (arg === '--cwd')
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
else if (arg === '--
|
|
28
|
+
if (arg === '--cwd') {
|
|
29
|
+
options.cwd = path.resolve(nextValue(arg, i));
|
|
30
|
+
i += 1;
|
|
31
|
+
} else if (arg === '--platform') {
|
|
32
|
+
options.platform = nextValue(arg, i);
|
|
33
|
+
i += 1;
|
|
34
|
+
} else if (arg === '--slug') {
|
|
35
|
+
options.slug = nextValue(arg, i);
|
|
36
|
+
i += 1;
|
|
37
|
+
} else if (arg === '--activate-next') {
|
|
22
38
|
/* always activate via hook payload */
|
|
23
39
|
} else if (arg === '--team-root') {
|
|
24
40
|
// Compatibility with earlier docs: derive platform + cwd from team root.
|
|
25
|
-
const teamRoot = path.resolve(
|
|
41
|
+
const teamRoot = path.resolve(nextValue(arg, i));
|
|
26
42
|
options.cwd = path.resolve(teamRoot, '..', '..');
|
|
27
43
|
options.platform = path.basename(path.dirname(teamRoot)) === '.claude' ? 'claude' : 'cursor';
|
|
44
|
+
i += 1;
|
|
28
45
|
} else {
|
|
29
46
|
console.error(`Unknown argument: ${arg}`);
|
|
30
47
|
process.exit(2);
|
|
@@ -8,35 +8,24 @@ set -euo pipefail
|
|
|
8
8
|
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|
9
9
|
export AI_RULES_PRESETS_ROOT="$ROOT/packages/ai-rules/presets"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
EMBED_LIST_FILE="$SCRIPT_DIR/_lib/embed-stems.json"
|
|
13
|
+
|
|
14
|
+
python3 - "$EMBED_LIST_FILE" <<'PY'
|
|
12
15
|
from __future__ import annotations
|
|
13
16
|
|
|
17
|
+
import json
|
|
14
18
|
import os
|
|
15
19
|
import re
|
|
20
|
+
import sys
|
|
16
21
|
from pathlib import Path
|
|
17
22
|
|
|
23
|
+
embed_list_file = Path(sys.argv[1])
|
|
24
|
+
EMBED = json.loads(embed_list_file.read_text(encoding="utf-8"))["embed"]
|
|
25
|
+
|
|
18
26
|
presets = Path(os.environ["AI_RULES_PRESETS_ROOT"])
|
|
19
27
|
core_root = presets / "_shared" / "core"
|
|
20
28
|
|
|
21
|
-
EMBED = [
|
|
22
|
-
"quality/code-quality-and-refactoring.md",
|
|
23
|
-
"quality/anti-sycophancy-discipline.md",
|
|
24
|
-
"quality/design-guidance.md",
|
|
25
|
-
"quality/mcp-usage.md",
|
|
26
|
-
"meta/preset-layering.md",
|
|
27
|
-
"meta/preset-twin-sync.md",
|
|
28
|
-
"meta/preset-token-budget.md",
|
|
29
|
-
"meta/preset-pr-checklist.md",
|
|
30
|
-
"meta/preset-no-cross-stack-leakage.md",
|
|
31
|
-
"review/code-review-mr.md",
|
|
32
|
-
"review/technical-retro.md",
|
|
33
|
-
"review/mockup-review.md",
|
|
34
|
-
"review/ux-design.md",
|
|
35
|
-
"agent-team/agent-team-intake.md",
|
|
36
|
-
"architecture/product-specs.md",
|
|
37
|
-
"architecture/product-specs-authoring.md",
|
|
38
|
-
]
|
|
39
|
-
|
|
40
29
|
MARKER_LINE_RE = re.compile(r"^<!--\s*shared-core:\s*([^>]+?)\s*-->\s*$")
|
|
41
30
|
|
|
42
31
|
|
|
@@ -14,7 +14,7 @@ const hooks = [
|
|
|
14
14
|
...fs.readdirSync(path.join(presetsDirectory, 'claude')).map((stack) => path.join(presetsDirectory, 'claude', stack, 'hooks', 'chain-team-phases.sh')),
|
|
15
15
|
];
|
|
16
16
|
|
|
17
|
-
assert.equal(hooks.length,
|
|
17
|
+
assert.equal(hooks.length, 20, 'expected every packaged Cursor and Claude hook (10 stacks × 2 platforms)');
|
|
18
18
|
|
|
19
19
|
function readJson(filePath) {
|
|
20
20
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -892,4 +892,102 @@ for (const hook of hooks) {
|
|
|
892
892
|
}
|
|
893
893
|
}
|
|
894
894
|
|
|
895
|
-
|
|
895
|
+
// ---------------------------------------------------------------------------
|
|
896
|
+
// Test — OD-1: parallel-stop lock race. The hook's waitForMetricsLock must
|
|
897
|
+
// actually block a second writer when the lock file is held. We pre-acquire
|
|
898
|
+
// `metrics.json.lock` (fs.openSync(_, 'wx') — same primitive the hook uses)
|
|
899
|
+
// in the test, then invoke the hook concurrently; the hook must NOT acquire
|
|
900
|
+
// the lock until the test releases it. Without the real lock primitive the
|
|
901
|
+
// hook would race through, run the metrics write, and leave a corrupt ledger.
|
|
902
|
+
// ---------------------------------------------------------------------------
|
|
903
|
+
for (const hook of hooks) {
|
|
904
|
+
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-lock-race-'));
|
|
905
|
+
try {
|
|
906
|
+
const platform = hook.includes(`${path.sep}cursor${path.sep}`) ? 'cursor' : 'claude';
|
|
907
|
+
const runId = 'lock-race-run';
|
|
908
|
+
const attemptId = `${runId}:step-0:feature-developer:attempt-1`;
|
|
909
|
+
const taskDirectory = writeTask(
|
|
910
|
+
root,
|
|
911
|
+
hook,
|
|
912
|
+
'lock-race',
|
|
913
|
+
{ autoChain: true, steps: [{ agent: 'feature-developer' }] },
|
|
914
|
+
{
|
|
915
|
+
slug: 'lock-race',
|
|
916
|
+
intent: 'feature',
|
|
917
|
+
pipelineIndex: 0,
|
|
918
|
+
currentAgent: 'feature-developer',
|
|
919
|
+
phase: 'executing',
|
|
920
|
+
state: 'completed',
|
|
921
|
+
activeAttempts: {
|
|
922
|
+
[attemptId]: {
|
|
923
|
+
agent: 'feature-developer',
|
|
924
|
+
stepIndex: 0,
|
|
925
|
+
attempt: 1,
|
|
926
|
+
startedAt: '2026-01-01T00:00:00.000Z',
|
|
927
|
+
parallelGroupId: null,
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
metricsRunId: runId,
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
schemaVersion: 1,
|
|
934
|
+
task: { runId, slug: 'lock-race', preset: 'ai-rules', stack: 'test', platform, intent: 'feature', profile: 'test' },
|
|
935
|
+
events: [
|
|
936
|
+
{ eventId: `${runId}:task_started`, type: 'task_started', at: '2026-01-01T00:00:00.000Z' },
|
|
937
|
+
{ eventId: `${runId}:start`, type: 'attempt_started', attemptId, agent: 'feature-developer', stepIndex: 0, attempt: 1, at: '2026-01-01T00:00:00.000Z' },
|
|
938
|
+
],
|
|
939
|
+
},
|
|
940
|
+
);
|
|
941
|
+
const metricsPath = path.join(taskDirectory, 'metrics.json');
|
|
942
|
+
const lockPath = `${metricsPath}.lock`;
|
|
943
|
+
|
|
944
|
+
// Pre-acquire the lock the same way the hook does (fs.openSync 'wx').
|
|
945
|
+
const holderFd = fs.openSync(lockPath, 'wx');
|
|
946
|
+
|
|
947
|
+
// Spawn the hook while the lock is held. The hook must wait.
|
|
948
|
+
const child = spawn('bash', [hook], { cwd: root, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
949
|
+
let stderr = '';
|
|
950
|
+
let stdout = '';
|
|
951
|
+
child.stderr.setEncoding('utf8');
|
|
952
|
+
child.stdout.setEncoding('utf8');
|
|
953
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
954
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
955
|
+
child.stdin.end(JSON.stringify({ status: 'completed', attemptId }));
|
|
956
|
+
|
|
957
|
+
// Give the child time to enter waitForMetricsLock and hit the EEXIST retry path.
|
|
958
|
+
const holdMs = 60;
|
|
959
|
+
await new Promise((resolve) => setTimeout(resolve, holdMs));
|
|
960
|
+
assert.ok(
|
|
961
|
+
child.exitCode === null,
|
|
962
|
+
`${hook}: hook exited prematurely while lock was held (stdout=${stdout.slice(0, 200)} stderr=${stderr.slice(0, 200)})`,
|
|
963
|
+
);
|
|
964
|
+
|
|
965
|
+
// Release the lock — child should now proceed.
|
|
966
|
+
const releaseAt = Date.now();
|
|
967
|
+
fs.closeSync(holderFd);
|
|
968
|
+
fs.unlinkSync(lockPath);
|
|
969
|
+
|
|
970
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
971
|
+
child.on('error', reject);
|
|
972
|
+
child.on('close', resolve);
|
|
973
|
+
});
|
|
974
|
+
const elapsed = Date.now() - releaseAt;
|
|
975
|
+
assert.equal(exitCode, 0, `${hook}: hook did not exit 0 after lock release (stderr=${stderr.slice(0, 400)})`);
|
|
976
|
+
|
|
977
|
+
// The hook's waitForMetricsLock busy-waits on EEXIST in 10 ms slices; with a
|
|
978
|
+
// 60 ms hold the child should have been blocked for >= holdMs and run to
|
|
979
|
+
// completion in well under the 2 s ceiling. Both assertions guard against
|
|
980
|
+
// regressions to the fake Atomics.wait impl (which never blocked).
|
|
981
|
+
assert.ok(elapsed >= 1, `${hook}: hook finished suspiciously fast after lock release (${elapsed} ms)`);
|
|
982
|
+
assert.ok(elapsed < 1500, `${hook}: hook took ${elapsed} ms after lock release — lock impl may be stuck`);
|
|
983
|
+
const ledger = readJson(metricsPath);
|
|
984
|
+
const finished = ledger.events.find(
|
|
985
|
+
(event) => event.type === 'attempt_finished' && event.attemptId === attemptId,
|
|
986
|
+
);
|
|
987
|
+
assert.ok(finished, `${hook}: lock-race did not emit attempt_finished; ledger=${JSON.stringify(ledger.events.map((e) => e.type))}`);
|
|
988
|
+
} finally {
|
|
989
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
console.log(`Verified retry, parallel, skip, gate, manifest-reconcile, lock-race, and aggregation lifecycle in ${hooks.length} hooks.`);
|