@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
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Targeted tests for chain-team-phases.sh fixes that prevent `/task` pipelines
|
|
3
|
+
// from silently skipping or stalling on modern roles.
|
|
4
|
+
//
|
|
5
|
+
// Coverage:
|
|
6
|
+
// 1. openspec-author handoff path: the AGENT_HANDOFF['openspec-author']
|
|
7
|
+
// entry must be emitted so the orchestrator invokes the agent; the
|
|
8
|
+
// DEFAULT_ARTIFACT_OUTPUT_IDS['openspec-author'] entry must match the
|
|
9
|
+
// manifest's `openspec-change` receipt so the hook advances instead of
|
|
10
|
+
// stalling in `awaiting_artifact_receipt`.
|
|
11
|
+
// 2. QA / unit / playwright / instrumentation / integration / xcuitest
|
|
12
|
+
// agents must have both AGENT_HANDOFF and DEFAULT_ARTIFACT_OUTPUT_IDS
|
|
13
|
+
// entries (the symmetric check is in check-chain-hook-coverage.sh; here
|
|
14
|
+
// we drive a full pipeline step to make sure receipts reconcile).
|
|
15
|
+
// 3. Pipeline.json parse error: a malformed pipeline.json that still
|
|
16
|
+
// carries an `intent` field must NOT fall through to legacyFlow; the
|
|
17
|
+
// hook must write `state: blocked` and emit a followup_message that
|
|
18
|
+
// mentions `pipeline.json` so the orchestrator surfaces the error.
|
|
19
|
+
// 4. Legacy flow fallback: a missing pipeline.json must still invoke
|
|
20
|
+
// legacyFlow() (analysis → development → review → testing chain).
|
|
21
|
+
// 5. writeStatus durability: status.json must be durable (fsync) before the
|
|
22
|
+
// hook returns. We assert that a second reader immediately after the
|
|
23
|
+
// hook returns sees the new state.
|
|
24
|
+
|
|
25
|
+
import assert from 'node:assert/strict';
|
|
26
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
27
|
+
import fs from 'node:fs';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
|
|
31
|
+
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const packageDirectory = path.resolve(scriptDirectory, '..');
|
|
33
|
+
const presetsDirectory = path.join(packageDirectory, 'presets');
|
|
34
|
+
|
|
35
|
+
const hooks = [
|
|
36
|
+
...fs.readdirSync(path.join(presetsDirectory, 'cursor')).map((stack) => path.join(presetsDirectory, 'cursor', stack, 'hooks', 'chain-team-phases.sh')),
|
|
37
|
+
...fs.readdirSync(path.join(presetsDirectory, 'claude')).map((stack) => path.join(presetsDirectory, 'claude', stack, 'hooks', 'chain-team-phases.sh')),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
assert.equal(hooks.length, 20, `expected 20 packaged hooks (10 stacks × 2 platforms), got ${hooks.length}`);
|
|
41
|
+
|
|
42
|
+
// mcp-ts does not ship openspec-author (per preset-no-cross-stack-leakage).
|
|
43
|
+
const HOOKS_WITH_OPENSPEC = hooks.filter((hook) => !hook.includes(`${path.sep}mcp-ts${path.sep}`));
|
|
44
|
+
assert.equal(HOOKS_WITH_OPENSPEC.length, 18, 'expected 18 hooks to cover openspec-author');
|
|
45
|
+
|
|
46
|
+
function readJson(filePath) {
|
|
47
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writeJson(filePath, value) {
|
|
51
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function invokeHook(hook, workingDirectory, attemptId, payload = {}) {
|
|
55
|
+
return execFileSync('bash', [hook], {
|
|
56
|
+
cwd: workingDirectory,
|
|
57
|
+
encoding: 'utf8',
|
|
58
|
+
input: JSON.stringify({ status: 'completed', attemptId, ...payload }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function platformFor(hook) {
|
|
63
|
+
return hook.includes(`${path.sep}cursor${path.sep}`) ? 'cursor' : 'claude';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeTask(root, hook, slug, pipeline, status, metrics, artifacts = {}) {
|
|
67
|
+
const platform = platformFor(hook);
|
|
68
|
+
const teamRoot = path.join(root, `.${platform}`, 'team');
|
|
69
|
+
const taskDirectory = path.join(teamRoot, 'tasks', slug);
|
|
70
|
+
fs.mkdirSync(taskDirectory, { recursive: true });
|
|
71
|
+
writeJson(path.join(teamRoot, 'active-task.json'), { slug });
|
|
72
|
+
writeJson(path.join(taskDirectory, 'pipeline.json'), pipeline);
|
|
73
|
+
writeJson(path.join(taskDirectory, 'status.json'), status);
|
|
74
|
+
writeJson(path.join(taskDirectory, 'metrics.json'), metrics);
|
|
75
|
+
for (const [name, content] of Object.entries(artifacts)) {
|
|
76
|
+
fs.writeFileSync(path.join(taskDirectory, name), content);
|
|
77
|
+
}
|
|
78
|
+
return { taskDirectory, platform };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const todayIso = '2026-08-21T00:00:00.000Z';
|
|
82
|
+
const earlierIso = '2026-08-21T00:00:01.000Z';
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Test 1 — openspec-author handoff: the hook must emit a followup_message that
|
|
86
|
+
// references openspec-author and the openspec-change scope, and the default
|
|
87
|
+
// artifact id must match the manifest receipt for `state` to advance.
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
for (const hook of HOOKS_WITH_OPENSPEC) {
|
|
90
|
+
const root = fs.mkdtempSync(path.join(packageDirectory, '.openspec-handoff-'));
|
|
91
|
+
try {
|
|
92
|
+
const runId = 'openspec-handoff-run';
|
|
93
|
+
const platform = platformFor(hook);
|
|
94
|
+
const analystAttemptId = `${runId}:step-0:task-analyst:attempt-1`;
|
|
95
|
+
const { taskDirectory } = writeTask(
|
|
96
|
+
root,
|
|
97
|
+
hook,
|
|
98
|
+
'openspec-handoff',
|
|
99
|
+
{
|
|
100
|
+
intent: 'openspec-change',
|
|
101
|
+
profile: 'standard',
|
|
102
|
+
steps: [
|
|
103
|
+
{ agent: 'task-analyst', label: 'Analyze' },
|
|
104
|
+
{ agent: 'openspec-author', label: 'Author spec change', scope: 'openspec:MEDCARD-11762' },
|
|
105
|
+
],
|
|
106
|
+
humanGates: [],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
slug: 'openspec-handoff',
|
|
110
|
+
intent: 'openspec-change',
|
|
111
|
+
pipelineIndex: 0,
|
|
112
|
+
currentAgent: 'task-analyst',
|
|
113
|
+
phase: 'executing',
|
|
114
|
+
state: 'completed',
|
|
115
|
+
activeAttempts: {
|
|
116
|
+
[analystAttemptId]: {
|
|
117
|
+
agent: 'task-analyst',
|
|
118
|
+
stepIndex: 0,
|
|
119
|
+
attempt: 1,
|
|
120
|
+
startedAt: todayIso,
|
|
121
|
+
parallelGroupId: null,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
metricsRunId: runId,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
task: { runId, slug: 'openspec-handoff', preset: 'ai-rules', stack: 'test', platform, intent: 'openspec-change', profile: 'standard' },
|
|
129
|
+
events: [
|
|
130
|
+
{ eventId: `${runId}:task_started`, type: 'task_started', at: todayIso },
|
|
131
|
+
{ eventId: `${runId}:analyst-start`, type: 'attempt_started', attemptId: analystAttemptId, agent: 'task-analyst', stepIndex: 0, attempt: 1, at: todayIso },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const handoffRaw = invokeHook(hook, root, analystAttemptId);
|
|
137
|
+
const handoff = JSON.parse(handoffRaw).followup_message;
|
|
138
|
+
assert.match(handoff, /openspec-author/, `${hook}: handoff did not mention openspec-author`);
|
|
139
|
+
assert.match(handoff, /openspec:MEDCARD-11762/, `${hook}: handoff did not echo the openspec scope`);
|
|
140
|
+
|
|
141
|
+
const statusAfter = readJson(path.join(taskDirectory, 'status.json'));
|
|
142
|
+
assert.equal(statusAfter.pipelineIndex, 1, `${hook}: hook did not advance past the analyst step`);
|
|
143
|
+
assert.equal(statusAfter.currentAgent, 'openspec-author', `${hook}: next agent was not openspec-author`);
|
|
144
|
+
const activeAttempts = Object.values(statusAfter.activeAttempts || {});
|
|
145
|
+
assert.ok(activeAttempts.some((entry) => entry.agent === 'openspec-author'), `${hook}: openspec-author attempt was not activated`);
|
|
146
|
+
|
|
147
|
+
// Now finish the openspec-author attempt with a properly bound receipt
|
|
148
|
+
// (id: openspec-change) and assert the hook advances to the next step.
|
|
149
|
+
const openspecAttempt = Object.entries(statusAfter.activeAttempts).find(([, entry]) => entry.agent === 'openspec-author');
|
|
150
|
+
const openspecAttemptId = openspecAttempt[0];
|
|
151
|
+
// Use a timestamp after the attempt's actual startedAt — the hook filters
|
|
152
|
+
// out manifest receipts whose completedAt predates the attempt's start.
|
|
153
|
+
const freshIso = new Date().toISOString();
|
|
154
|
+
fs.writeFileSync(
|
|
155
|
+
path.join(taskDirectory, 'artifact-manifest.json'),
|
|
156
|
+
JSON.stringify(
|
|
157
|
+
{
|
|
158
|
+
schemaVersion: 1,
|
|
159
|
+
slug: 'openspec-handoff',
|
|
160
|
+
updatedAt: freshIso,
|
|
161
|
+
artifacts: [
|
|
162
|
+
{
|
|
163
|
+
id: 'openspec-change',
|
|
164
|
+
producer: 'openspec-author',
|
|
165
|
+
status: 'completed',
|
|
166
|
+
authoritativePaths: ['openspec/changes/MEDCARD-11762/proposal.md'],
|
|
167
|
+
acceptanceCriteriaIds: ['AC-1'],
|
|
168
|
+
taskIds: ['T1'],
|
|
169
|
+
timestamps: { createdAt: freshIso, updatedAt: freshIso, completedAt: freshIso },
|
|
170
|
+
receipt: {
|
|
171
|
+
outcome: 'completed',
|
|
172
|
+
attemptId: openspecAttemptId,
|
|
173
|
+
summary: 'Proposal + design + tasks emitted; openspec validate --strict PASS',
|
|
174
|
+
evidencePaths: ['openspec/changes/MEDCARD-11762/proposal.md'],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
null,
|
|
180
|
+
2,
|
|
181
|
+
) + '\n',
|
|
182
|
+
);
|
|
183
|
+
writeJson(path.join(taskDirectory, 'status.json'), { ...statusAfter, state: 'completed' });
|
|
184
|
+
const finishRaw = invokeHook(hook, root, openspecAttemptId);
|
|
185
|
+
const finishHandoff = JSON.parse(finishRaw).followup_message;
|
|
186
|
+
assert.match(finishHandoff, /Pipeline complete/, `${hook}: pipeline should complete after openspec-author finishes`);
|
|
187
|
+
const finalStatus = readJson(path.join(taskDirectory, 'status.json'));
|
|
188
|
+
assert.equal(finalStatus.phase, 'done', `${hook}: phase did not transition to done`);
|
|
189
|
+
assert.equal(finalStatus.state, 'completed', `${hook}: state did not reach completed after openspec-author`);
|
|
190
|
+
} finally {
|
|
191
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Test 2 — qa-tester + unit-test-* handoff: a QA step in a pipeline must
|
|
197
|
+
// advance. Before the fix, DEFAULT_ARTIFACT_OUTPUT_IDS lacked the QA entries
|
|
198
|
+
// so `artifactOutputIdFor` returned the agent name itself; manifest receipts
|
|
199
|
+
// landed under the right id (qa / unit-test-spec) and the hook stalled in
|
|
200
|
+
// awaiting_artifact_receipt.
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
for (const hook of hooks) {
|
|
203
|
+
const root = fs.mkdtempSync(path.join(packageDirectory, '.qa-tester-handoff-'));
|
|
204
|
+
try {
|
|
205
|
+
const platform = platformFor(hook);
|
|
206
|
+
const runId = 'qa-tester-run';
|
|
207
|
+
const devAttemptId = `${runId}:step-0:feature-developer:attempt-1`;
|
|
208
|
+
const { taskDirectory } = writeTask(
|
|
209
|
+
root,
|
|
210
|
+
hook,
|
|
211
|
+
'qa-tester-handoff',
|
|
212
|
+
{
|
|
213
|
+
intent: 'test-only',
|
|
214
|
+
profile: 'standard',
|
|
215
|
+
steps: [
|
|
216
|
+
{ agent: 'feature-developer', label: 'Implement' },
|
|
217
|
+
{ agent: 'qa-tester', label: 'QA' },
|
|
218
|
+
],
|
|
219
|
+
humanGates: [],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
slug: 'qa-tester-handoff',
|
|
223
|
+
intent: 'test-only',
|
|
224
|
+
pipelineIndex: 0,
|
|
225
|
+
currentAgent: 'feature-developer',
|
|
226
|
+
phase: 'executing',
|
|
227
|
+
state: 'completed',
|
|
228
|
+
activeAttempts: {
|
|
229
|
+
[devAttemptId]: {
|
|
230
|
+
agent: 'feature-developer',
|
|
231
|
+
stepIndex: 0,
|
|
232
|
+
attempt: 1,
|
|
233
|
+
startedAt: todayIso,
|
|
234
|
+
parallelGroupId: null,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
metricsRunId: runId,
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
schemaVersion: 1,
|
|
241
|
+
task: { runId, slug: 'qa-tester-handoff', preset: 'ai-rules', stack: 'test', platform, intent: 'test-only', profile: 'standard' },
|
|
242
|
+
events: [
|
|
243
|
+
{ eventId: `${runId}:task_started`, type: 'task_started', at: todayIso },
|
|
244
|
+
{ eventId: `${runId}:dev-start`, type: 'attempt_started', attemptId: devAttemptId, agent: 'feature-developer', stepIndex: 0, attempt: 1, at: todayIso },
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
const handoff = JSON.parse(invokeHook(hook, root, devAttemptId)).followup_message;
|
|
249
|
+
assert.match(handoff, /qa-tester/, `${hook}: handoff did not mention qa-tester`);
|
|
250
|
+
const statusAfter = readJson(path.join(taskDirectory, 'status.json'));
|
|
251
|
+
assert.equal(statusAfter.pipelineIndex, 1, `${hook}: hook did not advance to qa-tester step`);
|
|
252
|
+
assert.equal(statusAfter.currentAgent, 'qa-tester', `${hook}: currentAgent was not qa-tester`);
|
|
253
|
+
} finally {
|
|
254
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Test 3 — pipeline.json parse error with intent: the hook must surface the
|
|
260
|
+
// error and write state: blocked; legacyFlow must NOT be silently used.
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
for (const hook of hooks) {
|
|
263
|
+
const root = fs.mkdtempSync(path.join(packageDirectory, '.pipeline-misparse-'));
|
|
264
|
+
try {
|
|
265
|
+
const platform = platformFor(hook);
|
|
266
|
+
const runId = 'misparse-run';
|
|
267
|
+
const devAttemptId = `${runId}:step-0:feature-developer:attempt-1`;
|
|
268
|
+
const taskDirectory = path.join(root, `.${platform}`, 'team', 'tasks', 'misparse');
|
|
269
|
+
fs.mkdirSync(taskDirectory, { recursive: true });
|
|
270
|
+
fs.writeFileSync(path.join(root, `.${platform}`, 'team', 'active-task.json'), JSON.stringify({ slug: 'misparse' }));
|
|
271
|
+
// Malformed pipeline.json with `intent` field — must NOT fall back to legacy.
|
|
272
|
+
fs.writeFileSync(
|
|
273
|
+
path.join(taskDirectory, 'pipeline.json'),
|
|
274
|
+
'{ "intent": "openspec-change", "steps": [\n { "agent": "openspec-author" }\n', // truncated JSON
|
|
275
|
+
);
|
|
276
|
+
writeJson(path.join(taskDirectory, 'status.json'), {
|
|
277
|
+
slug: 'misparse',
|
|
278
|
+
intent: 'openspec-change',
|
|
279
|
+
pipelineIndex: 0,
|
|
280
|
+
currentAgent: 'feature-developer',
|
|
281
|
+
phase: 'executing',
|
|
282
|
+
state: 'completed',
|
|
283
|
+
activeAttempts: {
|
|
284
|
+
[devAttemptId]: {
|
|
285
|
+
agent: 'feature-developer',
|
|
286
|
+
stepIndex: 0,
|
|
287
|
+
attempt: 1,
|
|
288
|
+
startedAt: todayIso,
|
|
289
|
+
parallelGroupId: null,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
metricsRunId: runId,
|
|
293
|
+
});
|
|
294
|
+
writeJson(path.join(taskDirectory, 'metrics.json'), {
|
|
295
|
+
schemaVersion: 1,
|
|
296
|
+
task: { runId, slug: 'misparse', preset: 'ai-rules', stack: 'test', platform, intent: 'openspec-change', profile: 'standard' },
|
|
297
|
+
events: [
|
|
298
|
+
{ eventId: `${runId}:task_started`, type: 'task_started', at: todayIso },
|
|
299
|
+
{ eventId: `${runId}:start`, type: 'attempt_started', attemptId: devAttemptId, agent: 'feature-developer', stepIndex: 0, attempt: 1, at: todayIso },
|
|
300
|
+
],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const raw = invokeHook(hook, root, devAttemptId);
|
|
304
|
+
const parsed = JSON.parse(raw);
|
|
305
|
+
const followup = parsed.followup_message || '';
|
|
306
|
+
assert.match(followup, /pipeline\.json is malformed/, `${hook}: hook did not surface parse error in followup_message (got: ${followup})`);
|
|
307
|
+
const status = readJson(path.join(taskDirectory, 'status.json'));
|
|
308
|
+
assert.equal(status.state, 'blocked', `${hook}: malformed pipeline with intent did not block the pipeline (got: ${status.state})`);
|
|
309
|
+
assert.equal(status.phase, 'blocked', `${hook}: phase did not transition to blocked`);
|
|
310
|
+
const metrics = readJson(path.join(taskDirectory, 'metrics.json'));
|
|
311
|
+
assert.ok(
|
|
312
|
+
metrics.events.some((event) => event.type === 'pipeline_parse_error'),
|
|
313
|
+
`${hook}: pipeline_parse_error metric event was not emitted`,
|
|
314
|
+
);
|
|
315
|
+
} finally {
|
|
316
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// Test 4 — legacyFlow fallback: when pipeline.json is absent, the hook must
|
|
322
|
+
// continue to use legacyFlow (analysis → development → review → testing).
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
for (const hook of hooks) {
|
|
325
|
+
const root = fs.mkdtempSync(path.join(packageDirectory, '.legacy-fallback-'));
|
|
326
|
+
try {
|
|
327
|
+
const platform = platformFor(hook);
|
|
328
|
+
const taskDirectory = path.join(root, `.${platform}`, 'team', 'tasks', 'legacy-slug');
|
|
329
|
+
fs.mkdirSync(taskDirectory, { recursive: true });
|
|
330
|
+
fs.writeFileSync(path.join(root, `.${platform}`, 'team', 'active-task.json'), JSON.stringify({ slug: 'legacy-slug' }));
|
|
331
|
+
// No pipeline.json — legacy flow triggers.
|
|
332
|
+
writeJson(path.join(taskDirectory, 'status.json'), {
|
|
333
|
+
slug: 'legacy-slug',
|
|
334
|
+
intent: 'feature',
|
|
335
|
+
phase: 'development',
|
|
336
|
+
state: 'completed',
|
|
337
|
+
currentAgent: 'feature-developer',
|
|
338
|
+
pipelineIndex: 0,
|
|
339
|
+
});
|
|
340
|
+
const raw = invokeHook(hook, root, null);
|
|
341
|
+
const parsed = JSON.parse(raw);
|
|
342
|
+
const followup = parsed.followup_message || '';
|
|
343
|
+
assert.match(followup, /build-verifier/, `${hook}: legacy fallback did not invoke build-verifier (got: ${followup})`);
|
|
344
|
+
} finally {
|
|
345
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// Test 5 — writeStatus durability: after the hook returns, the new
|
|
351
|
+
// status.json must already be on disk and reflect the post-step state. This
|
|
352
|
+
// is the property that lets the orchestrator's next read see the human-gate
|
|
353
|
+
// transition (no stale-read race).
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
for (const hook of HOOKS_WITH_OPENSPEC) {
|
|
356
|
+
const root = fs.mkdtempSync(path.join(packageDirectory, '.write-status-durability-'));
|
|
357
|
+
try {
|
|
358
|
+
const runId = 'durability-run';
|
|
359
|
+
const platform = platformFor(hook);
|
|
360
|
+
const analystAttemptId = `${runId}:step-0:task-analyst:attempt-1`;
|
|
361
|
+
const { taskDirectory } = writeTask(
|
|
362
|
+
root,
|
|
363
|
+
hook,
|
|
364
|
+
'durability-slug',
|
|
365
|
+
{
|
|
366
|
+
intent: 'feature',
|
|
367
|
+
profile: 'standard',
|
|
368
|
+
steps: [
|
|
369
|
+
{ agent: 'task-analyst', label: 'Analyze' },
|
|
370
|
+
{ agent: 'openspec-author', label: 'Spec change' },
|
|
371
|
+
],
|
|
372
|
+
humanGates: ['after:task-analyst'],
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
slug: 'durability-slug',
|
|
376
|
+
intent: 'feature',
|
|
377
|
+
pipelineIndex: 0,
|
|
378
|
+
currentAgent: 'task-analyst',
|
|
379
|
+
phase: 'executing',
|
|
380
|
+
state: 'completed',
|
|
381
|
+
activeAttempts: {
|
|
382
|
+
[analystAttemptId]: {
|
|
383
|
+
agent: 'task-analyst',
|
|
384
|
+
stepIndex: 0,
|
|
385
|
+
attempt: 1,
|
|
386
|
+
startedAt: todayIso,
|
|
387
|
+
parallelGroupId: null,
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
metricsRunId: runId,
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
schemaVersion: 1,
|
|
394
|
+
task: { runId, slug: 'durability-slug', preset: 'ai-rules', stack: 'test', platform, intent: 'feature', profile: 'standard' },
|
|
395
|
+
events: [
|
|
396
|
+
{ eventId: `${runId}:task_started`, type: 'task_started', at: todayIso },
|
|
397
|
+
{ eventId: `${runId}:start`, type: 'attempt_started', attemptId: analystAttemptId, agent: 'task-analyst', stepIndex: 0, attempt: 1, at: todayIso },
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
);
|
|
401
|
+
// Sync + invoke hook; immediately read status.json without fsync of our own.
|
|
402
|
+
invokeHook(hook, root, analystAttemptId);
|
|
403
|
+
const onDisk = JSON.parse(fs.readFileSync(path.join(taskDirectory, 'status.json'), 'utf8'));
|
|
404
|
+
assert.equal(onDisk.awaitingHumanGate, true, `${hook}: human gate was not durably written to status.json`);
|
|
405
|
+
assert.equal(onDisk.state, 'awaiting_approval', `${hook}: state did not durably transition to awaiting_approval`);
|
|
406
|
+
} finally {
|
|
407
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
// Test 6 — stale `attemptId` rejection (WP-1 receipt-binding callout):
|
|
413
|
+
// the hook's `readBoundReceiptOutcome` rejects entries whose `receipt.attemptId`
|
|
414
|
+
// does not match the active attempt, even when `completedAt` is fresh. A
|
|
415
|
+
// receipt bound to a prior attempt ID (e.g. attempt-1 reused after a retry
|
|
416
|
+
// recycled the slot) must not advance the pipeline.
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
for (const hook of hooks) {
|
|
419
|
+
const root = fs.mkdtempSync(path.join(packageDirectory, '.stale-attempt-id-'));
|
|
420
|
+
try {
|
|
421
|
+
const platform = platformFor(hook);
|
|
422
|
+
const runId = 'stale-attempt-id-run';
|
|
423
|
+
const devAttemptId = `${runId}:step-0:feature-developer:attempt-2`;
|
|
424
|
+
const freshIso = new Date().toISOString();
|
|
425
|
+
writeTask(
|
|
426
|
+
root,
|
|
427
|
+
hook,
|
|
428
|
+
'stale-attempt-id',
|
|
429
|
+
{
|
|
430
|
+
autoChain: true,
|
|
431
|
+
steps: [
|
|
432
|
+
{ agent: 'feature-developer', artifactOutputId: 'implementation' },
|
|
433
|
+
{ agent: 'build-verifier', artifactInputIds: ['implementation'], artifactOutputId: 'validation' },
|
|
434
|
+
],
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
slug: 'stale-attempt-id',
|
|
438
|
+
intent: 'feature',
|
|
439
|
+
pipelineIndex: 0,
|
|
440
|
+
currentAgent: 'feature-developer',
|
|
441
|
+
phase: 'executing',
|
|
442
|
+
state: 'in_progress',
|
|
443
|
+
activeAttempts: {
|
|
444
|
+
[devAttemptId]: {
|
|
445
|
+
agent: 'feature-developer',
|
|
446
|
+
stepIndex: 0,
|
|
447
|
+
attempt: 2,
|
|
448
|
+
startedAt: freshIso,
|
|
449
|
+
parallelGroupId: null,
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
metricsRunId: runId,
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
schemaVersion: 1,
|
|
456
|
+
task: { runId, slug: 'stale-attempt-id', preset: 'ai-rules', stack: 'test', platform, intent: 'feature', profile: 'standard' },
|
|
457
|
+
events: [
|
|
458
|
+
{ eventId: `${runId}:task_started`, type: 'task_started', at: freshIso },
|
|
459
|
+
{ eventId: `${runId}:dev-start`, type: 'attempt_started', attemptId: devAttemptId, agent: 'feature-developer', stepIndex: 0, attempt: 2, at: freshIso },
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
'artifact-manifest.json': `${JSON.stringify(
|
|
464
|
+
{
|
|
465
|
+
schemaVersion: 1,
|
|
466
|
+
slug: 'stale-attempt-id',
|
|
467
|
+
updatedAt: freshIso,
|
|
468
|
+
artifacts: [
|
|
469
|
+
{
|
|
470
|
+
id: 'implementation',
|
|
471
|
+
producer: 'feature-developer',
|
|
472
|
+
status: 'completed',
|
|
473
|
+
authoritativePaths: ['implementation.md'],
|
|
474
|
+
acceptanceCriteriaIds: ['AC-1'],
|
|
475
|
+
taskIds: ['T1'],
|
|
476
|
+
timestamps: { createdAt: freshIso, updatedAt: freshIso, completedAt: freshIso },
|
|
477
|
+
receipt: {
|
|
478
|
+
outcome: 'completed',
|
|
479
|
+
// Stale: belongs to attempt-1 of the same (step, agent); the active attempt is attempt-2.
|
|
480
|
+
attemptId: `${runId}:step-0:feature-developer:attempt-1`,
|
|
481
|
+
summary: 'Receipt bound to a prior attempt; should be rejected.',
|
|
482
|
+
evidencePaths: ['implementation.md'],
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
},
|
|
487
|
+
null,
|
|
488
|
+
2,
|
|
489
|
+
)}\n`,
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
const taskDirectory = path.join(root, `.${platform}`, 'team', 'tasks', 'stale-attempt-id');
|
|
494
|
+
const parsed = JSON.parse(invokeHook(hook, root, devAttemptId));
|
|
495
|
+
assert.deepEqual(parsed, {}, `${hook}: stale-attemptId receipt must not hand off`);
|
|
496
|
+
const status = readJson(path.join(taskDirectory, 'status.json'));
|
|
497
|
+
assert.equal(status.pipelineIndex, 0, `${hook}: stale-attemptId receipt advanced the pipeline`);
|
|
498
|
+
assert.equal(status.currentAgent, 'feature-developer', `${hook}: stale-attemptId receipt changed the active producer`);
|
|
499
|
+
assert.equal(status.state, 'awaiting_artifact_receipt', `${hook}: stale-attemptId did not enter receipt-wait state (got: ${status.state})`);
|
|
500
|
+
assert.ok(status.activeAttempts[devAttemptId], `${hook}: stale-attemptId consumed the active attempt`);
|
|
501
|
+
const finished = readJson(path.join(taskDirectory, 'metrics.json')).events.filter(
|
|
502
|
+
(event) => event.type === 'attempt_finished' && event.attemptId === devAttemptId,
|
|
503
|
+
);
|
|
504
|
+
assert.equal(finished.length, 0, `${hook}: stale-attemptId emitted terminal metrics`);
|
|
505
|
+
} finally {
|
|
506
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
console.log(`Verified openspec-author handoff, qa-tester handoff, pipeline misparse blocking, legacy fallback, writeStatus durability, and stale attemptId rejection in ${hooks.length} hooks.`);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fixtures for check-task-router-intents.sh.
|
|
3
|
+
//
|
|
4
|
+
// Scenario: rename the frontmatter `name:` of an existing shipped agent
|
|
5
|
+
// (cursor/next/agents/build-verifier.md → `build-verifier-renamed`) and
|
|
6
|
+
// confirm the check FAILS. Then revert and confirm it PASSES.
|
|
7
|
+
//
|
|
8
|
+
// Why a separate fixture file: the check is a shell + Python script under
|
|
9
|
+
// `scripts/`, not an in-process module, so we exercise it end-to-end via
|
|
10
|
+
// `execFileSync` against a temp copy of `agents/` (mirroring the pattern in
|
|
11
|
+
// `test-chain-team-phases-coverage.mjs` and `test-cli-mcp-merge.mjs`).
|
|
12
|
+
//
|
|
13
|
+
// The fixture operates on a *copy* of `presets/cursor/next/agents/` so the
|
|
14
|
+
// real preset tree is never mutated, even on assertion failure.
|
|
15
|
+
|
|
16
|
+
import assert from 'node:assert/strict';
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const packageDirectory = path.resolve(scriptDirectory, '..');
|
|
25
|
+
const checkScript = path.join(scriptDirectory, 'check-task-router-intents.sh');
|
|
26
|
+
|
|
27
|
+
function runCheck(env) {
|
|
28
|
+
try {
|
|
29
|
+
return {
|
|
30
|
+
exitCode: 0,
|
|
31
|
+
stdout: execFileSync('bash', [checkScript], { env, encoding: 'utf8' }),
|
|
32
|
+
};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return {
|
|
35
|
+
exitCode: error.status ?? 1,
|
|
36
|
+
stdout: `${error.stdout ?? ''}${error.stderr ?? ''}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function copyAgentsDir(sourceAgentsDir) {
|
|
42
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'task-router-intents-'));
|
|
43
|
+
// Mirror full preset tree so the Python script's iteration works.
|
|
44
|
+
const sourcePresetsDir = path.join(packageDirectory, 'presets');
|
|
45
|
+
const targetPresetsDir = path.join(tempRoot, 'packages', 'ai-rules', 'presets');
|
|
46
|
+
fs.mkdirSync(targetPresetsDir, { recursive: true });
|
|
47
|
+
copyTree(sourcePresetsDir, targetPresetsDir);
|
|
48
|
+
return tempRoot;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function copyTree(source, destination) {
|
|
52
|
+
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
53
|
+
const src = path.join(source, entry.name);
|
|
54
|
+
const dest = path.join(destination, entry.name);
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
57
|
+
copyTree(src, dest);
|
|
58
|
+
} else if (entry.isFile()) {
|
|
59
|
+
fs.copyFileSync(src, dest);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function setEnvPresetRoot(tempRoot) {
|
|
65
|
+
return { ...process.env, AI_RULES_PRESETS_ROOT: path.join(tempRoot, 'packages', 'ai-rules', 'presets') };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Test 1 — rename frontmatter `name:` and assert the script returns non-zero.
|
|
69
|
+
{
|
|
70
|
+
const tempRoot = copyAgentsDir(path.join(packageDirectory, 'presets', 'cursor', 'next', 'agents'));
|
|
71
|
+
try {
|
|
72
|
+
const agentFile = path.join(tempRoot, 'packages', 'ai-rules', 'presets', 'cursor', 'next', 'agents', 'build-verifier.md');
|
|
73
|
+
const original = fs.readFileSync(agentFile, 'utf8');
|
|
74
|
+
const renamed = original.replace(/^name:\s*build-verifier\s*$/m, 'name: build-verifier-renamed');
|
|
75
|
+
assert.notStrictEqual(renamed, original, 'rename pattern must match exactly one line');
|
|
76
|
+
fs.writeFileSync(agentFile, renamed);
|
|
77
|
+
|
|
78
|
+
const result = runCheck(setEnvPresetRoot(tempRoot));
|
|
79
|
+
assert.notStrictEqual(
|
|
80
|
+
result.exitCode,
|
|
81
|
+
0,
|
|
82
|
+
`expected FAIL when name frontmatter does not match stem; got exit=${result.exitCode}\n${result.stdout}`,
|
|
83
|
+
);
|
|
84
|
+
assert.match(
|
|
85
|
+
result.stdout,
|
|
86
|
+
/task-router vs agents drift/,
|
|
87
|
+
'expected drift failure message in output',
|
|
88
|
+
);
|
|
89
|
+
} finally {
|
|
90
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Test 2 — revert and assert the script returns zero.
|
|
95
|
+
{
|
|
96
|
+
const tempRoot = copyAgentsDir(path.join(packageDirectory, 'presets', 'cursor', 'next', 'agents'));
|
|
97
|
+
try {
|
|
98
|
+
const result = runCheck(setEnvPresetRoot(tempRoot));
|
|
99
|
+
assert.strictEqual(
|
|
100
|
+
result.exitCode,
|
|
101
|
+
0,
|
|
102
|
+
`expected PASS on unmodified copy; got exit=${result.exitCode}\n${result.stdout}`,
|
|
103
|
+
);
|
|
104
|
+
} finally {
|
|
105
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log('OK: check-task-router-intents.sh fixture tests passed');
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: feature-continue
|
|
3
|
-
description: Legacy alias for /task-continue that resumes a feature pipeline after human approval or pause.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Feature continue — legacy alias
|
|
7
|
-
|
|
8
|
-
> **Prefer:** `/task-continue <slug>` — same behavior, respects `pipeline.json`.
|
|
9
|
-
|
|
10
|
-
Continue after a human gate. Full logic is in **`task-continue.md`**.
|
|
11
|
-
|
|
12
|
-
## Short version
|
|
13
|
-
|
|
14
|
-
1. Read `pipeline.json` (if present) and `status.json`.
|
|
15
|
-
2. Clear `awaitingHumanGate`; determine the next step from `pipelineIndex`.
|
|
16
|
-
3. Invoke the subagent for `currentAgent`.
|
|
17
|
-
4. The hook continues the chain when `autoChain: true`.
|
|
18
|
-
|
|
19
|
-
See `task-continue.md` for details and the handoff table.
|
|
20
|
-
|
|
21
|
-
## Product specs (docs/specs)
|
|
22
|
-
|
|
23
|
-
When the next agent is **feature-developer** (or other implementer): if `docs/specs/INDEX.md` exists, instruct them to load **`product-specs`** and honor **Related specs** from `brief.md` (behavior SoT). Soft-fail on miss; never invent product behavior. No-op if INDEX is absent.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: feature-start
|
|
3
|
-
description: Legacy analyst-only feature intake. Prefer /task for new work because it uses router and dynamic pipeline.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Feature start — legacy (analyst only)
|
|
7
|
-
|
|
8
|
-
> **Prefer:** `/task <description>` — the router chooses roles and order automatically.
|
|
9
|
-
|
|
10
|
-
Starts the **analysis phase** without the router. The parent agent acts as orchestrator (see `tooling-and-review/agent-team-orchestrator.md`).
|
|
11
|
-
|
|
12
|
-
## What to do
|
|
13
|
-
|
|
14
|
-
1. Extract the task description from the command arguments (everything after `/feature-start`).
|
|
15
|
-
2. Generate a **slug** (kebab-case, up to 48 characters) from the task title.
|
|
16
|
-
3. Create `.claude/team/tasks/<slug>/` if it does not exist yet — via **Write** of an artifact (not Shell `mkdir`).
|
|
17
|
-
4. Write `.claude/team/active-task.json` → `{ "slug": "<slug>" }`.
|
|
18
|
-
5. Optional: minimal `pipeline.json` with fixed steps (analyst → developer → reviewer → qa-tester).
|
|
19
|
-
6. Invoke subagent **task-analyst** (Task tool) with the full task description and artifact paths.
|
|
20
|
-
7. Wait for the analyst to finish. **Do not invoke developer** at this stage.
|
|
21
|
-
|
|
22
|
-
## Product specs (docs/specs)
|
|
23
|
-
|
|
24
|
-
When invoking **task-analyst**: if `docs/specs/INDEX.md` exists, instruct consume via **`product-specs`** and cite **Related specs**. Soft-fail on miss; never invent behavior.
|
|
25
|
-
|
|
26
|
-
If the user/AC requests generation or bootstrap, or INDEX is missing **with** bootstrap AC / `/spec-start` intent: instruct **author mode** inside the same analyst step — load **`product-specs-authoring`** + skill **`spec-authoring`**. Authoring is **not** a separate pipeline agent. Handoff must list created paths + INDEX diff; human gate must approve generated specs before feature-developer.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## Human gate
|
|
30
|
-
|
|
31
|
-
After the analyst:
|
|
32
|
-
|
|
33
|
-
- Show the user links to `brief.md` and `decomposition.md`.
|
|
34
|
-
- Also ask them to check **Related specs** in `brief.md` when `docs/specs/INDEX.md` exists.
|
|
35
|
-
- Ask them to review and approve or send edits.
|
|
36
|
-
- To continue: `/task-continue <slug>` or `/feature-continue <slug>`.
|
|
37
|
-
|
|
38
|
-
## If the slug already exists
|
|
39
|
-
|
|
40
|
-
- If `status.json` is `awaiting_approval` — offer review or continue.
|
|
41
|
-
- If the task is in progress — ask whether to continue or start a new slug.
|