@event4u/agent-config 1.15.0 → 1.17.0

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.
Files changed (354) hide show
  1. package/.agent-src/commands/{agents-audit.md → agents/audit.md} +4 -3
  2. package/.agent-src/commands/{agents-cleanup.md → agents/cleanup.md} +12 -6
  3. package/.agent-src/commands/{agents-prepare.md → agents/prepare.md} +4 -3
  4. package/.agent-src/commands/agents.md +46 -0
  5. package/.agent-src/commands/bug-fix.md +1 -1
  6. package/.agent-src/commands/bug-investigate.md +2 -2
  7. package/.agent-src/commands/{chat-history-checkpoint.md → chat-history/checkpoint.md} +5 -5
  8. package/.agent-src/commands/{chat-history-clear.md → chat-history/clear.md} +5 -5
  9. package/.agent-src/commands/{chat-history-resume.md → chat-history/resume.md} +4 -4
  10. package/.agent-src/commands/chat-history/show.md +107 -0
  11. package/.agent-src/commands/chat-history.md +33 -89
  12. package/.agent-src/commands/check-current-md.md +1 -1
  13. package/.agent-src/commands/{commit-in-chunks.md → commit/in-chunks.md} +15 -13
  14. package/.agent-src/commands/commit.md +22 -2
  15. package/.agent-src/commands/{context-create.md → context/create.md} +4 -3
  16. package/.agent-src/commands/{context-refactor.md → context/refactor.md} +4 -3
  17. package/.agent-src/commands/context.md +44 -0
  18. package/.agent-src/commands/{copilot-agents-init.md → copilot-agents/init.md} +4 -3
  19. package/.agent-src/commands/{copilot-agents-optimize.md → copilot-agents/optimize.md} +4 -3
  20. package/.agent-src/commands/copilot-agents.md +44 -0
  21. package/.agent-src/commands/council/default.md +221 -0
  22. package/.agent-src/commands/council/design.md +97 -0
  23. package/.agent-src/commands/council/optimize.md +116 -0
  24. package/.agent-src/commands/council/pr.md +124 -0
  25. package/.agent-src/commands/council.md +54 -0
  26. package/.agent-src/commands/{create-pr-description.md → create-pr/description-only.md} +4 -2
  27. package/.agent-src/commands/create-pr.md +49 -5
  28. package/.agent-src/commands/e2e-heal.md +1 -1
  29. package/.agent-src/commands/e2e-plan.md +1 -1
  30. package/.agent-src/commands/{feature-dev.md → feature/dev.md} +6 -3
  31. package/.agent-src/commands/{feature-explore.md → feature/explore.md} +5 -4
  32. package/.agent-src/commands/{feature-plan.md → feature/plan.md} +32 -5
  33. package/.agent-src/commands/{feature-refactor.md → feature/refactor.md} +4 -3
  34. package/.agent-src/commands/{feature-roadmap.md → feature/roadmap.md} +7 -6
  35. package/.agent-src/commands/feature.md +52 -0
  36. package/.agent-src/commands/{fix-ci.md → fix/ci.md} +4 -3
  37. package/.agent-src/commands/{fix-portability.md → fix/portability.md} +4 -3
  38. package/.agent-src/commands/{fix-pr-bot-comments.md → fix/pr-bots.md} +4 -3
  39. package/.agent-src/commands/{fix-pr-developer-comments.md → fix/pr-developers.md} +4 -3
  40. package/.agent-src/commands/{fix-pr-comments.md → fix/pr.md} +7 -6
  41. package/.agent-src/commands/{fix-references.md → fix/refs.md} +4 -3
  42. package/.agent-src/commands/{fix-seeder.md → fix/seeder.md} +4 -3
  43. package/.agent-src/commands/fix.md +54 -0
  44. package/.agent-src/commands/jira-ticket.md +1 -1
  45. package/.agent-src/commands/{do-and-judge.md → judge/on-diff.md} +7 -6
  46. package/.agent-src/commands/judge/solo.md +90 -0
  47. package/.agent-src/commands/{do-in-steps.md → judge/steps.md} +8 -7
  48. package/.agent-src/commands/judge.md +35 -70
  49. package/.agent-src/commands/{memory-add.md → memory/add.md} +7 -6
  50. package/.agent-src/commands/{memory-full.md → memory/load.md} +6 -5
  51. package/.agent-src/commands/{memory-promote.md → memory/promote.md} +6 -5
  52. package/.agent-src/commands/{propose-memory.md → memory/propose.md} +6 -5
  53. package/.agent-src/commands/memory.md +48 -0
  54. package/.agent-src/commands/mode.md +5 -5
  55. package/.agent-src/commands/{module-create.md → module/create.md} +4 -3
  56. package/.agent-src/commands/{module-explore.md → module/explore.md} +4 -3
  57. package/.agent-src/commands/module.md +44 -0
  58. package/.agent-src/commands/onboard.md +3 -3
  59. package/.agent-src/commands/{optimize-agents.md → optimize/agents.md} +5 -4
  60. package/.agent-src/commands/{optimize-augmentignore.md → optimize/augmentignore.md} +4 -4
  61. package/.agent-src/commands/{optimize-rtk-filters.md → optimize/rtk.md} +4 -3
  62. package/.agent-src/commands/{optimize-skills.md → optimize/skills.md} +5 -4
  63. package/.agent-src/commands/optimize.md +48 -0
  64. package/.agent-src/commands/{override-create.md → override/create.md} +4 -3
  65. package/.agent-src/commands/{override-manage.md → override/manage.md} +4 -3
  66. package/.agent-src/commands/override.md +44 -0
  67. package/.agent-src/commands/review-changes.md +26 -1
  68. package/.agent-src/commands/review-routing.md +1 -1
  69. package/.agent-src/commands/{roadmap-create.md → roadmap/create.md} +33 -5
  70. package/.agent-src/commands/{roadmap-execute.md → roadmap/execute.md} +4 -3
  71. package/.agent-src/commands/roadmap.md +44 -0
  72. package/.agent-src/commands/set-cost-profile.md +3 -3
  73. package/.agent-src/commands/sync-agent-settings.md +2 -2
  74. package/.agent-src/commands/{tests-create.md → tests/create.md} +5 -4
  75. package/.agent-src/commands/{tests-execute.md → tests/execute.md} +4 -3
  76. package/.agent-src/commands/tests.md +44 -0
  77. package/.agent-src/commands/upstream-contribute.md +1 -1
  78. package/.agent-src/contexts/authority/commit-mechanics.md +57 -0
  79. package/.agent-src/contexts/authority/destructive-mechanics.md +66 -0
  80. package/.agent-src/contexts/authority/scope-mechanics.md +87 -0
  81. package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +72 -0
  82. package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +79 -0
  83. package/.agent-src/contexts/communication/rules-auto/augment-source-of-truth-mechanics.md +98 -0
  84. package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +87 -0
  85. package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +62 -0
  86. package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +78 -0
  87. package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +85 -0
  88. package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +65 -0
  89. package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +78 -0
  90. package/.agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +62 -0
  91. package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +55 -0
  92. package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +53 -0
  93. package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +77 -0
  94. package/.agent-src/contexts/execution/autonomy-detection.md +54 -0
  95. package/.agent-src/contexts/execution/autonomy-examples.md +90 -0
  96. package/.agent-src/contexts/execution/autonomy-mechanics.md +29 -0
  97. package/.agent-src/contexts/execution/verification-mechanics.md +80 -0
  98. package/.agent-src/contexts/judges/no-consolidate-rationale.md +102 -0
  99. package/.agent-src/contexts/judges/persona-voice-rubric.md +140 -0
  100. package/.agent-src/personas/README.md +1 -1
  101. package/.agent-src/rules/agent-authority.md +24 -0
  102. package/.agent-src/rules/architecture.md +1 -1
  103. package/.agent-src/rules/artifact-drafting-protocol.md +1 -1
  104. package/.agent-src/rules/artifact-engagement-recording.md +14 -70
  105. package/.agent-src/rules/ask-when-uncertain.md +28 -43
  106. package/.agent-src/rules/augment-portability.md +15 -61
  107. package/.agent-src/rules/augment-source-of-truth.md +27 -93
  108. package/.agent-src/rules/autonomous-execution.md +78 -114
  109. package/.agent-src/rules/capture-learnings.md +1 -1
  110. package/.agent-src/rules/chat-history-cadence.md +3 -3
  111. package/.agent-src/rules/chat-history-ownership.md +3 -3
  112. package/.agent-src/rules/chat-history-visibility.md +3 -3
  113. package/.agent-src/rules/cli-output-handling.md +10 -76
  114. package/.agent-src/rules/command-suggestion-policy.md +93 -0
  115. package/.agent-src/rules/commit-conventions.md +17 -14
  116. package/.agent-src/rules/commit-policy.md +14 -42
  117. package/.agent-src/rules/context-hygiene.md +3 -3
  118. package/.agent-src/rules/direct-answers.md +34 -49
  119. package/.agent-src/rules/docker-commands.md +5 -5
  120. package/.agent-src/rules/docs-sync.md +16 -70
  121. package/.agent-src/rules/e2e-testing.md +1 -1
  122. package/.agent-src/rules/guidelines.md +4 -4
  123. package/.agent-src/rules/improve-before-implement.md +2 -2
  124. package/.agent-src/rules/language-and-tone.md +50 -133
  125. package/.agent-src/rules/minimal-safe-diff.md +3 -3
  126. package/.agent-src/rules/missing-tool-handling.md +28 -22
  127. package/.agent-src/rules/model-recommendation.md +4 -4
  128. package/.agent-src/rules/no-cheap-questions.md +82 -0
  129. package/.agent-src/rules/no-roadmap-references.md +73 -0
  130. package/.agent-src/rules/non-destructive-by-default.md +15 -49
  131. package/.agent-src/rules/onboarding-gate.md +5 -5
  132. package/.agent-src/rules/package-ci-checks.md +21 -61
  133. package/.agent-src/rules/preservation-guard.md +64 -29
  134. package/.agent-src/rules/review-routing-awareness.md +26 -45
  135. package/.agent-src/rules/roadmap-progress-sync.md +28 -96
  136. package/.agent-src/rules/role-mode-adherence.md +2 -2
  137. package/.agent-src/rules/scope-control.md +65 -46
  138. package/.agent-src/rules/security-sensitive-stop.md +9 -9
  139. package/.agent-src/rules/size-enforcement.md +1 -1
  140. package/.agent-src/rules/skill-quality.md +16 -48
  141. package/.agent-src/rules/{slash-commands.md → slash-command-routing-policy.md} +7 -4
  142. package/.agent-src/rules/think-before-action.md +55 -45
  143. package/.agent-src/rules/token-efficiency.md +4 -4
  144. package/.agent-src/rules/tool-safety.md +19 -16
  145. package/.agent-src/rules/{ui-audit-before-build.md → ui-audit-gate.md} +27 -41
  146. package/.agent-src/rules/user-interaction.md +16 -71
  147. package/.agent-src/rules/verify-before-complete.md +12 -67
  148. package/.agent-src/scripts/update_roadmap_progress.py +9 -4
  149. package/.agent-src/skills/ai-council/SKILL.md +335 -0
  150. package/.agent-src/skills/api-endpoint/SKILL.md +2 -2
  151. package/.agent-src/skills/api-testing/SKILL.md +1 -1
  152. package/.agent-src/skills/blade-ui/SKILL.md +1 -1
  153. package/.agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
  154. package/.agent-src/skills/bug-analyzer/SKILL.md +1 -1
  155. package/.agent-src/skills/check-refs/SKILL.md +59 -40
  156. package/.agent-src/skills/command-routing/SKILL.md +1 -1
  157. package/.agent-src/skills/command-writing/SKILL.md +1 -1
  158. package/.agent-src/skills/conventional-commits-writing/SKILL.md +86 -28
  159. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +7 -7
  160. package/.agent-src/skills/developer-like-execution/SKILL.md +6 -6
  161. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +101 -65
  162. package/.agent-src/skills/flux/SKILL.md +31 -11
  163. package/.agent-src/skills/git-workflow/SKILL.md +1 -1
  164. package/.agent-src/skills/github-ci/SKILL.md +2 -2
  165. package/.agent-src/skills/guideline-writing/SKILL.md +11 -11
  166. package/.agent-src/skills/judge-code-quality/SKILL.md +7 -8
  167. package/.agent-src/skills/judge-security-auditor/SKILL.md +4 -5
  168. package/.agent-src/skills/judge-test-coverage/SKILL.md +3 -4
  169. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +4 -4
  170. package/.agent-src/skills/lint-skills/SKILL.md +57 -39
  171. package/.agent-src/skills/livewire/SKILL.md +1 -1
  172. package/.agent-src/skills/md-language-check/SKILL.md +61 -39
  173. package/.agent-src/skills/override-management/SKILL.md +7 -7
  174. package/.agent-src/skills/php-coder/SKILL.md +1 -1
  175. package/.agent-src/skills/playwright-testing/SKILL.md +2 -2
  176. package/.agent-src/skills/quality-tools/SKILL.md +2 -2
  177. package/.agent-src/skills/react-shadcn-ui/SKILL.md +116 -43
  178. package/.agent-src/skills/readme-reviewer/SKILL.md +31 -30
  179. package/.agent-src/skills/readme-writing/SKILL.md +79 -54
  180. package/.agent-src/skills/readme-writing-package/SKILL.md +51 -48
  181. package/.agent-src/skills/receiving-code-review/SKILL.md +53 -48
  182. package/.agent-src/skills/refine-prompt/SKILL.md +0 -1
  183. package/.agent-src/skills/requesting-code-review/SKILL.md +35 -30
  184. package/.agent-src/skills/review-routing/SKILL.md +2 -2
  185. package/.agent-src/skills/rule-writing/SKILL.md +1 -1
  186. package/.agent-src/skills/security/SKILL.md +7 -2
  187. package/.agent-src/skills/security-audit/SKILL.md +7 -3
  188. package/.agent-src/skills/skill-reviewer/SKILL.md +1 -1
  189. package/.agent-src/skills/skill-writing/SKILL.md +3 -3
  190. package/.agent-src/skills/subagent-orchestration/SKILL.md +1 -0
  191. package/.agent-src/skills/systematic-debugging/SKILL.md +69 -61
  192. package/.agent-src/skills/test-driven-development/SKILL.md +59 -57
  193. package/.agent-src/skills/test-performance/SKILL.md +0 -1
  194. package/.agent-src/skills/traefik/SKILL.md +4 -4
  195. package/.agent-src/skills/upstream-contribute/SKILL.md +1 -1
  196. package/.agent-src/skills/validate-feature-fit/SKILL.md +2 -2
  197. package/.agent-src/skills/{verify-before-complete → verify-completion-evidence}/SKILL.md +30 -28
  198. package/.agent-src/templates/agent-settings.md +8 -8
  199. package/.agent-src/templates/contexts/auth-model.md +1 -1
  200. package/.agent-src/templates/scripts/README.md +2 -2
  201. package/.agent-src/templates/scripts/telemetry/aggregator.py +16 -1
  202. package/.agent-src/templates/scripts/telemetry/engagement.py +59 -0
  203. package/.agent-src/templates/scripts/telemetry/report_renderer.py +28 -1
  204. package/.agent-src/templates/scripts/telemetry_record.py +14 -1
  205. package/.claude-plugin/marketplace.json +31 -12
  206. package/AGENTS.md +11 -9
  207. package/CHANGELOG.md +213 -2
  208. package/README.md +43 -44
  209. package/config/agent-settings.template.yml +58 -1
  210. package/config/gitignore-block.txt +3 -0
  211. package/docs/architecture.md +5 -7
  212. package/docs/catalog.md +359 -0
  213. package/docs/contracts/STABILITY.md +46 -1
  214. package/docs/contracts/adr-chat-history-split.md +1 -3
  215. package/docs/contracts/adr-command-suggestion.md +3 -5
  216. package/docs/contracts/adr-implement-ticket-runtime.md +1 -2
  217. package/docs/contracts/adr-product-ui-track.md +5 -8
  218. package/docs/contracts/adr-prompt-driven-execution.md +3 -4
  219. package/docs/contracts/agent-memory-contract.md +8 -13
  220. package/docs/contracts/artifact-engagement-flow.md +7 -10
  221. package/docs/contracts/command-clusters.md +56 -46
  222. package/docs/contracts/command-suggestion-flow.md +4 -6
  223. package/docs/contracts/context-paths.md +99 -0
  224. package/docs/contracts/file-ownership-matrix.json +6722 -0
  225. package/docs/contracts/file-ownership-matrix.md +134 -0
  226. package/docs/contracts/implement-ticket-flow.md +8 -11
  227. package/docs/contracts/linear-ai-rules-inclusion.md +1 -2
  228. package/docs/contracts/linear-ai-three-layers.md +0 -2
  229. package/docs/contracts/load-context-budget-model.md +178 -0
  230. package/docs/contracts/load-context-schema.md +184 -0
  231. package/docs/contracts/rule-interactions.md +0 -1
  232. package/docs/contracts/rule-interactions.yml +96 -0
  233. package/docs/contracts/rule-priority-hierarchy.md +87 -0
  234. package/docs/contracts/ui-track-flow.md +8 -18
  235. package/docs/customization.md +16 -0
  236. package/docs/end-to-end-walkthroughs.md +165 -0
  237. package/docs/getting-started.md +29 -10
  238. package/docs/github-topics.md +12 -3
  239. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +100 -0
  240. package/docs/guidelines/agent-infra/language-and-tone-examples.md +79 -0
  241. package/{.agent-src → docs}/guidelines/docs/readme-size-and-splitting.md +26 -25
  242. package/docs/guidelines/php/git.md +164 -0
  243. package/docs/migrations/commands-1.15.0.md +1 -1
  244. package/docs/showcase.md +9 -4
  245. package/docs/skills-catalog.md +14 -8
  246. package/docs/ui-track-mental-model.md +2 -2
  247. package/llms.txt +13 -7
  248. package/package.json +1 -1
  249. package/scripts/_one_off_phase4_dispatch_latency.py +108 -0
  250. package/scripts/_one_off_phase6_trigger_jaccard.py +92 -0
  251. package/scripts/_phase2_shim_helper.py +109 -0
  252. package/scripts/agent-config +33 -0
  253. package/scripts/ai_council/__init__.py +39 -0
  254. package/scripts/ai_council/_default_prices.py +41 -0
  255. package/scripts/ai_council/_one_off_2a4_acceptance.py +208 -0
  256. package/scripts/ai_council/_one_off_context_layer_v1_estimate.py +67 -0
  257. package/scripts/ai_council/_one_off_context_layer_v1_review.py +292 -0
  258. package/scripts/ai_council/_one_off_followups_review.py +259 -0
  259. package/scripts/ai_council/_one_off_nondestructive_inline_audit.py +209 -0
  260. package/scripts/ai_council/_one_off_phase_2a_budget_rebalance.py +257 -0
  261. package/scripts/ai_council/_one_off_phase_2a_post_revert.py +197 -0
  262. package/scripts/ai_council/_one_off_rebalancing_audit.py +149 -0
  263. package/scripts/ai_council/_one_off_roundtrip.py +106 -0
  264. package/scripts/ai_council/_one_off_rule_hardening_v1.py +251 -0
  265. package/scripts/ai_council/_one_off_structural_open_questions.py +232 -0
  266. package/scripts/ai_council/_one_off_structural_optimization.py +144 -0
  267. package/scripts/ai_council/_one_off_structural_v3_gaps.py +252 -0
  268. package/scripts/ai_council/_one_off_structural_v3_review.py +240 -0
  269. package/scripts/ai_council/budget_guard.py +172 -0
  270. package/scripts/ai_council/bundler.py +261 -0
  271. package/scripts/ai_council/clients.py +381 -0
  272. package/scripts/ai_council/modes.py +127 -0
  273. package/scripts/ai_council/orchestrator.py +350 -0
  274. package/scripts/ai_council/pricing.py +213 -0
  275. package/scripts/ai_council/project_context.py +159 -0
  276. package/scripts/ai_council/prompts.py +232 -0
  277. package/scripts/ai_council/session.py +144 -0
  278. package/scripts/check_always_budget.py +444 -0
  279. package/scripts/check_augmentignore.py +69 -0
  280. package/scripts/check_cluster_patterns.py +159 -0
  281. package/scripts/check_command_count_messaging.py +127 -0
  282. package/scripts/check_context_paths.py +201 -0
  283. package/scripts/check_no_roadmap_refs.py +155 -0
  284. package/scripts/check_phase_coupling.py +148 -0
  285. package/scripts/check_portability.py +57 -0
  286. package/scripts/check_public_catalog_links.py +122 -0
  287. package/scripts/check_references.py +33 -3
  288. package/scripts/check_roadmap_trackable.py +111 -0
  289. package/scripts/check_safety_floor_untouched.py +125 -0
  290. package/scripts/command_suggester/cooldown.py +1 -1
  291. package/scripts/command_suggester/loader.py +4 -1
  292. package/scripts/compress.py +59 -13
  293. package/scripts/generate_index.py +270 -0
  294. package/scripts/generate_ownership_matrix.py +323 -0
  295. package/scripts/hooks/augment-roadmap-progress.sh +57 -0
  296. package/scripts/install.py +49 -28
  297. package/scripts/install_anthropic_key.sh +5 -0
  298. package/scripts/install_openai_key.sh +106 -0
  299. package/scripts/lint_load_context.py +163 -0
  300. package/scripts/lint_no_new_atomic_commands.py +12 -11
  301. package/scripts/requirements-evals.txt +1 -0
  302. package/scripts/roadmap_progress_hook.py +159 -0
  303. package/scripts/schemas/command.schema.json +22 -1
  304. package/scripts/schemas/rule.schema.json +10 -0
  305. package/scripts/skill_linter.py +13 -4
  306. package/scripts/sync_agent_settings.py +26 -3
  307. package/scripts/update_counts.py +16 -4
  308. package/scripts/update_prices.py +124 -0
  309. package/.agent-src/guidelines/php/git.md +0 -96
  310. package/.agent-src/rules/command-suggestion.md +0 -134
  311. /package/{.agent-src → docs}/guidelines/agent-infra/agent-interaction-and-decision-quality.md +0 -0
  312. /package/{.agent-src → docs}/guidelines/agent-infra/break-glass-usage.md +0 -0
  313. /package/{.agent-src → docs}/guidelines/agent-infra/developer-judgment.md +0 -0
  314. /package/{.agent-src → docs}/guidelines/agent-infra/engineering-memory-data-format.md +0 -0
  315. /package/{.agent-src → docs}/guidelines/agent-infra/layered-settings.md +0 -0
  316. /package/{.agent-src → docs}/guidelines/agent-infra/memory-access.md +0 -0
  317. /package/{.agent-src → docs}/guidelines/agent-infra/naming.md +0 -0
  318. /package/{.agent-src → docs}/guidelines/agent-infra/output-patterns.md +0 -0
  319. /package/{.agent-src → docs}/guidelines/agent-infra/review-routing-data-format.md +0 -0
  320. /package/{.agent-src → docs}/guidelines/agent-infra/role-contracts.md +0 -0
  321. /package/{.agent-src → docs}/guidelines/agent-infra/role-mode-router.md +0 -0
  322. /package/{.agent-src → docs}/guidelines/agent-infra/runtime-layer.md +0 -0
  323. /package/{.agent-src → docs}/guidelines/agent-infra/self-improvement-pipeline.md +0 -0
  324. /package/{.agent-src → docs}/guidelines/agent-infra/size-and-scope.md +0 -0
  325. /package/{.agent-src → docs}/guidelines/agent-infra/tool-integration.md +0 -0
  326. /package/{.agent-src → docs}/guidelines/e2e/playwright.md +0 -0
  327. /package/{.agent-src → docs}/guidelines/php/api-design.md +0 -0
  328. /package/{.agent-src → docs}/guidelines/php/artisan-commands.md +0 -0
  329. /package/{.agent-src → docs}/guidelines/php/blade-ui.md +0 -0
  330. /package/{.agent-src → docs}/guidelines/php/controllers.md +0 -0
  331. /package/{.agent-src → docs}/guidelines/php/database.md +0 -0
  332. /package/{.agent-src → docs}/guidelines/php/eloquent.md +0 -0
  333. /package/{.agent-src → docs}/guidelines/php/flux.md +0 -0
  334. /package/{.agent-src → docs}/guidelines/php/general.md +0 -0
  335. /package/{.agent-src → docs}/guidelines/php/jobs.md +0 -0
  336. /package/{.agent-src → docs}/guidelines/php/livewire.md +0 -0
  337. /package/{.agent-src → docs}/guidelines/php/logging.md +0 -0
  338. /package/{.agent-src → docs}/guidelines/php/naming.md +0 -0
  339. /package/{.agent-src → docs}/guidelines/php/patterns/dependency-injection.md +0 -0
  340. /package/{.agent-src → docs}/guidelines/php/patterns/dtos.md +0 -0
  341. /package/{.agent-src → docs}/guidelines/php/patterns/events.md +0 -0
  342. /package/{.agent-src → docs}/guidelines/php/patterns/factory.md +0 -0
  343. /package/{.agent-src → docs}/guidelines/php/patterns/pipelines.md +0 -0
  344. /package/{.agent-src → docs}/guidelines/php/patterns/policies.md +0 -0
  345. /package/{.agent-src → docs}/guidelines/php/patterns/repositories.md +0 -0
  346. /package/{.agent-src → docs}/guidelines/php/patterns/service-layer.md +0 -0
  347. /package/{.agent-src → docs}/guidelines/php/patterns/strategy.md +0 -0
  348. /package/{.agent-src → docs}/guidelines/php/patterns.md +0 -0
  349. /package/{.agent-src → docs}/guidelines/php/performance.md +0 -0
  350. /package/{.agent-src → docs}/guidelines/php/resources.md +0 -0
  351. /package/{.agent-src → docs}/guidelines/php/security.md +0 -0
  352. /package/{.agent-src → docs}/guidelines/php/sql.md +0 -0
  353. /package/{.agent-src → docs}/guidelines/php/validations.md +0 -0
  354. /package/{.agent-src → docs}/guidelines/php/websocket.md +0 -0
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env python3
2
+ """Always-rule budget gate (Phases 7.1 + 7.4 of road-to-pr-34-followups,
3
+ extended by Phase 0.2 of road-to-structural-optimization).
4
+
5
+ Enforces the budget contract under **Model (b) literal** — see
6
+ `docs/contracts/load-context-budget-model.md`. Effective size of a
7
+ `type: "always"` rule is its own char count plus the char count of
8
+ every context it loads (transitively, depth ≤ 2).
9
+
10
+ Caps:
11
+ - Warn-at-80% / fail-at-90% global trend gate (Phase 7.1).
12
+ - Per-rule cap (≤ 6,000 chars per always-rule, Phase 7.4) — measured
13
+ on extended size, with a transitional `KNOWN_PER_RULE_BREACHES`
14
+ allowlist that Phase 2A retires.
15
+ - Top-3 cap (top-3 combined ≤ 50% of TOTAL_CAP, Phase 7.4) — extended.
16
+ - Depth-2 nesting cap on `load_context:` chains.
17
+
18
+ Exit codes: 0 = pass (or warn), 1 = fail (≥ 90% utilization, per-rule
19
+ breach above ceiling, top-3 breach, or depth violation), 3 = internal
20
+ error.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import json
27
+ import sys
28
+ from datetime import datetime, timezone
29
+ from pathlib import Path
30
+
31
+ import yaml
32
+
33
+ REPO_ROOT = Path(__file__).resolve().parents[1]
34
+ RULES_DIR = REPO_ROOT / ".agent-src" / "rules"
35
+ SRC_PREFIX = ".agent-src.uncompressed/"
36
+ COMP_PREFIX = ".agent-src/"
37
+
38
+ TOTAL_CAP = 49_000
39
+ WARN_THRESHOLD = 0.80
40
+ FAIL_THRESHOLD = 0.90
41
+
42
+ # Phase 5.2.1 concentration thresholds (non-safety-floor rules only).
43
+ # Beyond the total-budget cap, fail CI when any single non-safety-floor
44
+ # rule exceeds SINGLE_PCT of used budget OR the top-3 non-safety-floor
45
+ # sum exceeds TOP3_PCT of used budget. Prevents post-slim concentration
46
+ # regrowth (risk #12 in road-to-structural-optimization).
47
+ CONCENTRATION_SINGLE_PCT = 0.12
48
+ CONCENTRATION_TOP3_PCT = 0.30
49
+
50
+ # Q3=A locked safety-floor rules — out of scope for slimming and for the
51
+ # concentration check. Their size is intentional (Iron Laws + obligation
52
+ # surface), not drift. See road-to-structural-optimization Phase 5.
53
+ SAFETY_FLOOR_RULES: frozenset[str] = frozenset({
54
+ "non-destructive-by-default.md",
55
+ "commit-policy.md",
56
+ "scope-control.md",
57
+ "verify-before-complete.md",
58
+ })
59
+
60
+ # Phase 5.3 — per-rule trend log. JSONL, one record per linter run.
61
+ # Each line: {"ts": iso8601, "total": int, "rules": {name: ext}}.
62
+ TREND_LOG = REPO_ROOT / ".github" / "budget-trend.jsonl"
63
+ TREND_LOG_MAX_RECORDS = 500
64
+ # Phase 0.2 G3 tolerance band — overshoot ≤ 2 % of cap is accepted by
65
+ # the model (b) contract; > 2 % rejects model (b) and escalates. The
66
+ # linter treats the [100 %, 100 % + tolerance] window as a hardened
67
+ # WARN that documents the transition; Phase 2A drops total below 100 %.
68
+ TOLERANCE_BAND = 0.02
69
+ PER_RULE_CAP = 6_000
70
+ TOP3_CAP = TOTAL_CAP // 2
71
+ MAX_DEPTH = 2
72
+
73
+ # Recovery band (AI Council session 2026-05-03T12-02-42Z, verdict A1).
74
+ # When enabled, a branch in the 90–100 % gap zone passes as WARN iff its
75
+ # extended total is strictly below the last-green main baseline AND every
76
+ # per-rule / top-3 / depth cap holds. Resolves the paradox where main at
77
+ # 100.6 % passed via TOLERANCE_BAND while a strictly-better branch at
78
+ # 96.8 % failed the gap-zone gate. Phase 5 of road-to-structural-
79
+ # optimization flips this to False and enforces total < TOTAL_CAP strictly.
80
+ RECOVERY_BAND_ENABLED = True
81
+ BASELINE_FILE = REPO_ROOT / ".github" / "budget-baseline.txt"
82
+
83
+ # Transitional allowlist — per-rule extended-size breaches that Phase 2A
84
+ # of road-to-structural-optimization is contracted to retire. Each entry
85
+ # records the measured ceiling on the day Phase 0.2 was committed; a
86
+ # growth above the ceiling fails CI even while the entry remains.
87
+ # When Phase 2A retires a rule, drop its entry here AND in
88
+ # `tests/test_always_budget.py::KNOWN_PER_RULE_BREACHES`.
89
+ KNOWN_PER_RULE_BREACHES: dict[str, int] = {
90
+ "non-destructive-by-default.md": 7_887,
91
+ "scope-control.md": 8_529,
92
+ }
93
+
94
+
95
+ def _load_baseline() -> int | None:
96
+ """Return the last-green main baseline char total, or None if absent.
97
+
98
+ Reads `.github/budget-baseline.txt`; the first non-comment, non-blank
99
+ line is parsed as an integer. Missing file or malformed content
100
+ disables the recovery band silently — the linter falls back to the
101
+ pre-band gate.
102
+ """
103
+ if not BASELINE_FILE.exists():
104
+ return None
105
+ for line in BASELINE_FILE.read_text(encoding="utf-8").splitlines():
106
+ line = line.strip()
107
+ if not line or line.startswith("#"):
108
+ continue
109
+ try:
110
+ return int(line)
111
+ except ValueError:
112
+ return None
113
+ return None
114
+
115
+
116
+ def _frontmatter(path: Path) -> dict:
117
+ text = path.read_text(encoding="utf-8")
118
+ if not text.startswith("---\n"):
119
+ return {}
120
+ end = text.find("\n---\n", 4)
121
+ if end == -1:
122
+ return {}
123
+ try:
124
+ return yaml.safe_load(text[4:end]) or {}
125
+ except yaml.YAMLError:
126
+ return {}
127
+
128
+
129
+ def _is_always(path: Path) -> bool:
130
+ return _frontmatter(path).get("type") == "always"
131
+
132
+
133
+ def _load_context_paths(path: Path) -> list[str]:
134
+ fm = _frontmatter(path)
135
+ out: list[str] = []
136
+ for key in ("load_context", "load_context_eager"):
137
+ for entry in fm.get(key) or []:
138
+ out.append(str(entry))
139
+ return out
140
+
141
+
142
+ def _src_to_compressed(entry: str) -> Path:
143
+ if entry.startswith(SRC_PREFIX):
144
+ return REPO_ROOT / (COMP_PREFIX + entry[len(SRC_PREFIX):])
145
+ return REPO_ROOT / entry
146
+
147
+
148
+ def _walk_contexts(rule: Path) -> tuple[set[Path], list[tuple[str, str]]]:
149
+ """Return (set of context files counted, list of depth-violation chains)."""
150
+ seen: set[Path] = set()
151
+ violations: list[tuple[str, str]] = []
152
+ stack: list[tuple[Path, int, str]] = [(rule, 0, rule.name)]
153
+ while stack:
154
+ node, depth, chain = stack.pop()
155
+ for entry in _load_context_paths(node):
156
+ comp = _src_to_compressed(entry)
157
+ new_chain = f"{chain} → {entry}"
158
+ if depth + 1 > MAX_DEPTH:
159
+ violations.append((rule.name, new_chain))
160
+ continue
161
+ if not comp.exists():
162
+ continue
163
+ if comp in seen:
164
+ continue
165
+ seen.add(comp)
166
+ stack.append((comp, depth + 1, new_chain))
167
+ return seen, violations
168
+
169
+
170
+ def _always_rules() -> list[Path]:
171
+ return sorted(p for p in RULES_DIR.glob("*.md") if _is_always(p))
172
+
173
+
174
+ def _extended_size(rule: Path) -> tuple[int, list[tuple[str, str]]]:
175
+ raw = rule.stat().st_size
176
+ contexts, violations = _walk_contexts(rule)
177
+ ext = raw + sum(c.stat().st_size for c in contexts)
178
+ return ext, violations
179
+
180
+
181
+ def _concentration_check(
182
+ sizes: list[tuple[str, int, int]],
183
+ total_ext: int,
184
+ ) -> tuple[list[tuple[str, int, float]], tuple[int, float] | None]:
185
+ """Phase 5.2.1 concentration check (non-safety-floor rules only).
186
+
187
+ Returns (single-rule breaches, top-3 breach or None). Q3=A locked
188
+ safety-floor rules are excluded from both numerator and the top-3
189
+ selection — their size is intentional, not drift.
190
+ """
191
+ non_floor = [
192
+ (name, raw, ext) for name, raw, ext in sizes
193
+ if name not in SAFETY_FLOOR_RULES
194
+ ]
195
+ single_cap = total_ext * CONCENTRATION_SINGLE_PCT
196
+ top3_cap = total_ext * CONCENTRATION_TOP3_PCT
197
+
198
+ single_breaches = [
199
+ (name, ext, ext / total_ext)
200
+ for name, _, ext in non_floor
201
+ if ext > single_cap
202
+ ]
203
+ top3_sum = sum(ext for _, _, ext in non_floor[:3])
204
+ top3_breach = (
205
+ (top3_sum, top3_sum / total_ext)
206
+ if top3_sum > top3_cap else None
207
+ )
208
+ return single_breaches, top3_breach
209
+
210
+
211
+ def _record_trend(total_ext: int, sizes: list[tuple[str, int, int]]) -> None:
212
+ """Append the current run to the trend log (Phase 5.3)."""
213
+ TREND_LOG.parent.mkdir(parents=True, exist_ok=True)
214
+ record = {
215
+ "ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
216
+ "total": total_ext,
217
+ "rules": {name: ext for name, _, ext in sizes},
218
+ }
219
+ lines: list[str] = []
220
+ if TREND_LOG.exists():
221
+ lines = TREND_LOG.read_text(encoding="utf-8").splitlines()
222
+ lines.append(json.dumps(record, separators=(",", ":")))
223
+ if len(lines) > TREND_LOG_MAX_RECORDS:
224
+ lines = lines[-TREND_LOG_MAX_RECORDS:]
225
+ TREND_LOG.write_text("\n".join(lines) + "\n", encoding="utf-8")
226
+
227
+
228
+ def _last_trend() -> dict | None:
229
+ """Return the most recent trend record, or None if log is empty."""
230
+ if not TREND_LOG.exists():
231
+ return None
232
+ lines = [
233
+ line for line in TREND_LOG.read_text(encoding="utf-8").splitlines()
234
+ if line.strip()
235
+ ]
236
+ if not lines:
237
+ return None
238
+ try:
239
+ return json.loads(lines[-1])
240
+ except json.JSONDecodeError:
241
+ return None
242
+
243
+
244
+ def main() -> int:
245
+ parser = argparse.ArgumentParser(description=__doc__)
246
+ parser.add_argument(
247
+ "--quiet",
248
+ action="store_true",
249
+ help="suppress the per-rule breakdown unless threshold is crossed",
250
+ )
251
+ parser.add_argument(
252
+ "--no-trend",
253
+ action="store_true",
254
+ help="skip writing to .github/budget-trend.jsonl (Phase 5.3)",
255
+ )
256
+ args = parser.parse_args()
257
+
258
+ if not RULES_DIR.is_dir():
259
+ print(f"❌ rules dir missing: {RULES_DIR}", file=sys.stderr)
260
+ return 3
261
+
262
+ rules = _always_rules()
263
+ if not rules:
264
+ print(f"❌ no always-rules found under {RULES_DIR}", file=sys.stderr)
265
+ return 3
266
+
267
+ sizes: list[tuple[str, int, int]] = []
268
+ all_violations: list[tuple[str, str]] = []
269
+ for rule in rules:
270
+ ext, violations = _extended_size(rule)
271
+ sizes.append((rule.name, rule.stat().st_size, ext))
272
+ all_violations.extend(violations)
273
+
274
+ sizes.sort(key=lambda x: -x[2])
275
+ total_ext = sum(s[2] for s in sizes)
276
+ pct = total_ext / TOTAL_CAP
277
+ top3 = sum(s[2] for s in sizes[:3])
278
+ top3_breach = top3 > TOP3_CAP
279
+
280
+ over_per_rule: list[tuple[str, int]] = []
281
+ grew_over_ceiling: list[tuple[str, int, int]] = []
282
+ for name, _, ext in sizes:
283
+ if ext <= PER_RULE_CAP:
284
+ continue
285
+ ceiling = KNOWN_PER_RULE_BREACHES.get(name)
286
+ if ceiling is None:
287
+ over_per_rule.append((name, ext))
288
+ elif ext > ceiling:
289
+ grew_over_ceiling.append((name, ext, ceiling))
290
+
291
+ in_tolerance = 1.0 <= pct <= 1.0 + TOLERANCE_BAND
292
+ baseline = _load_baseline() if RECOVERY_BAND_ENABLED else None
293
+ in_recovery_band = (
294
+ baseline is not None
295
+ and FAIL_THRESHOLD <= pct < 1.0
296
+ and total_ext < baseline
297
+ )
298
+ single_breaches, top3_concentration_breach = _concentration_check(
299
+ sizes, total_ext
300
+ )
301
+ failing = (
302
+ (
303
+ pct >= FAIL_THRESHOLD
304
+ and not in_tolerance
305
+ and not in_recovery_band
306
+ and pct < 1.0
307
+ )
308
+ or pct > 1.0 + TOLERANCE_BAND
309
+ or over_per_rule
310
+ or grew_over_ceiling
311
+ or top3_breach
312
+ or all_violations
313
+ or single_breaches
314
+ or top3_concentration_breach is not None
315
+ )
316
+ if failing:
317
+ status, rc = "❌ FAIL", 1
318
+ elif in_tolerance:
319
+ status, rc = "⚠️ WARN (G3 tolerance band)", 0
320
+ elif in_recovery_band:
321
+ status, rc = (
322
+ f"⚠️ WARN (recovery band, baseline {baseline:,})",
323
+ 0,
324
+ )
325
+ elif pct >= WARN_THRESHOLD:
326
+ status, rc = "⚠️ WARN", 0
327
+ else:
328
+ status, rc = "✅ OK", 0
329
+
330
+ print(
331
+ f"{status} always-rule extended budget: {total_ext:,} / "
332
+ f"{TOTAL_CAP:,} chars ({pct * 100:.1f}%) across {len(rules)} rule(s)"
333
+ )
334
+ print(
335
+ f" thresholds: warn {WARN_THRESHOLD * 100:.0f}% · "
336
+ f"fail {FAIL_THRESHOLD * 100:.0f}% · "
337
+ f"per-rule ≤ {PER_RULE_CAP:,} (ext) · top-3 ≤ {TOP3_CAP:,} (ext) · "
338
+ f"depth ≤ {MAX_DEPTH}"
339
+ )
340
+
341
+ if rc != 0 or pct >= WARN_THRESHOLD or not args.quiet:
342
+ print()
343
+ print(f" breakdown (largest extended first; top-3 sum = {top3:,}):")
344
+ for i, (name, raw, ext) in enumerate(sizes):
345
+ tag = " (top-3)" if i < 3 else ""
346
+ ceiling = KNOWN_PER_RULE_BREACHES.get(name)
347
+ if ceiling is not None:
348
+ marker = f" ⚠️ allowlisted ≤ {ceiling:,}"
349
+ elif ext > PER_RULE_CAP:
350
+ marker = " ❌ per-rule breach"
351
+ else:
352
+ marker = ""
353
+ print(
354
+ f" ext={ext:>5} raw={raw:>5} {name}{tag}{marker}"
355
+ )
356
+
357
+ if over_per_rule:
358
+ names = ", ".join(f"{n}={s:,}" for n, s in over_per_rule)
359
+ print(
360
+ f"\n Per-rule cap breach (> {PER_RULE_CAP:,} chars, not allowlisted): "
361
+ f"{names}"
362
+ )
363
+
364
+ if grew_over_ceiling:
365
+ details = ", ".join(
366
+ f"{n}={ext:,} > ceiling {ceiling:,}"
367
+ for n, ext, ceiling in grew_over_ceiling
368
+ )
369
+ print(
370
+ f"\n Allowlisted-breach growth (regression): {details}"
371
+ )
372
+
373
+ if top3_breach:
374
+ print(
375
+ f"\n Top-3 cap breach: {top3:,} > {TOP3_CAP:,} chars "
376
+ f"(top-3 must stay ≤ 50% of {TOTAL_CAP:,} total budget)."
377
+ )
378
+
379
+ if all_violations:
380
+ print(
381
+ f"\n Depth-{MAX_DEPTH} nesting cap violations:"
382
+ )
383
+ for rule_name, chain in all_violations:
384
+ print(f" {rule_name}: {chain}")
385
+
386
+ if single_breaches:
387
+ details = ", ".join(
388
+ f"{n}={ext:,} ({frac * 100:.1f}%)"
389
+ for n, ext, frac in single_breaches
390
+ )
391
+ print(
392
+ f"\n Concentration breach (single rule > "
393
+ f"{CONCENTRATION_SINGLE_PCT * 100:.0f}% of used budget, "
394
+ f"non-allowlisted): {details}"
395
+ )
396
+
397
+ if top3_concentration_breach is not None:
398
+ sum_, frac = top3_concentration_breach
399
+ print(
400
+ f"\n Concentration breach (top-3 non-allowlisted > "
401
+ f"{CONCENTRATION_TOP3_PCT * 100:.0f}% of used budget): "
402
+ f"{sum_:,} ({frac * 100:.1f}%)"
403
+ )
404
+
405
+ # Phase 5.3 — per-rule trend delta vs. previous run.
406
+ prev = _last_trend()
407
+ if prev is not None and not args.quiet:
408
+ prev_total = prev.get("total")
409
+ prev_rules = prev.get("rules") or {}
410
+ if isinstance(prev_total, int):
411
+ delta_total = total_ext - prev_total
412
+ sign = "+" if delta_total >= 0 else ""
413
+ print(
414
+ f"\n Trend vs. previous run "
415
+ f"({prev.get('ts', '?')}): total {sign}{delta_total:,} chars"
416
+ )
417
+ deltas: list[tuple[str, int, int]] = []
418
+ for name, _, ext in sizes:
419
+ old = prev_rules.get(name)
420
+ if isinstance(old, int) and old != ext:
421
+ deltas.append((name, ext - old, ext))
422
+ if deltas:
423
+ deltas.sort(key=lambda x: -abs(x[1]))
424
+ for name, d, ext in deltas[:5]:
425
+ s = "+" if d >= 0 else ""
426
+ print(f" {name}: {s}{d:,} (now {ext:,})")
427
+
428
+ if not args.no_trend:
429
+ _record_trend(total_ext, sizes)
430
+
431
+ if rc == 1:
432
+ print(
433
+ f"\n Action: trim the offending rule(s) via load_context: "
434
+ f"extraction (see contexts/execution + contexts/authority) "
435
+ f"until utilization drops below {FAIL_THRESHOLD * 100:.0f}% "
436
+ f"and all per-rule / top-3 / depth caps hold. See "
437
+ f"docs/contracts/load-context-budget-model.md."
438
+ )
439
+
440
+ return rc
441
+
442
+
443
+ if __name__ == "__main__":
444
+ sys.exit(main())
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Advisory check for `.augmentignore` (road-to-governance-cleanup F6.3).
4
+
5
+ Runs as part of `task ci` to surface `/optimize augmentignore` as a
6
+ periodic reminder. Always exits 0 — this is a warn-only advisory, not
7
+ a gate. Failures here never block CI.
8
+
9
+ Checks performed:
10
+ 1. Does `.augmentignore` exist at repo root?
11
+ 2. Is its mtime older than 90 days? (stale reminder)
12
+ 3. Is it suspiciously short (<5 non-blank, non-comment lines)?
13
+
14
+ If any check trips, prints a friendly hint and exits 0. If all clean,
15
+ prints a single-line OK and exits 0.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import sys
20
+ import time
21
+ from pathlib import Path
22
+
23
+ STALE_DAYS = 90
24
+ MIN_USEFUL_LINES = 5
25
+ REPO_ROOT = Path(__file__).resolve().parent.parent
26
+
27
+
28
+ def check() -> int:
29
+ target = REPO_ROOT / ".augmentignore"
30
+ notes: list[str] = []
31
+
32
+ if not target.exists():
33
+ notes.append("⚠️ .augmentignore is missing — run `/optimize augmentignore` to scaffold it.")
34
+ _emit(notes)
35
+ return 0
36
+
37
+ age_days = (time.time() - target.stat().st_mtime) / 86400
38
+ if age_days > STALE_DAYS:
39
+ notes.append(
40
+ f"ℹ️ .augmentignore is {int(age_days)} days old (threshold: {STALE_DAYS}) — "
41
+ "consider running `/optimize augmentignore` to refresh."
42
+ )
43
+
44
+ useful = [
45
+ ln for ln in target.read_text().splitlines()
46
+ if ln.strip() and not ln.strip().startswith("#")
47
+ ]
48
+ if len(useful) < MIN_USEFUL_LINES:
49
+ notes.append(
50
+ f"ℹ️ .augmentignore has only {len(useful)} active entries — "
51
+ "run `/optimize augmentignore` to detect tech-stack ignores you may be missing."
52
+ )
53
+
54
+ _emit(notes)
55
+ return 0
56
+
57
+
58
+ def _emit(notes: list[str]) -> None:
59
+ if not notes:
60
+ print("✅ .augmentignore advisory: nothing to suggest.")
61
+ return
62
+ print("📒 .augmentignore advisory (non-blocking):")
63
+ for n in notes:
64
+ print(f" {n}")
65
+ print(" (This is a reminder, not a CI failure.)")
66
+
67
+
68
+ if __name__ == "__main__":
69
+ sys.exit(check())
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env python3
2
+ """Cluster-pattern compliance check.
3
+
4
+ Compares each cluster dispatcher under
5
+ `.agent-src.uncompressed/commands/<cluster>.md` against the Phase 1
6
+ reference patterns (`fix.md`, `optimize.md`, `feature.md`).
7
+
8
+ Required structure:
9
+
10
+ Frontmatter:
11
+ - `name: <cluster>`
12
+ - `cluster: <cluster>`
13
+ - `disable-model-invocation: true`
14
+
15
+ Body:
16
+ - `# /<cluster>` H1
17
+ - `## Sub-commands` section with a markdown table whose header is
18
+ exactly `Sub-command | Routes to | Purpose`
19
+ - `## Dispatch` section
20
+ - `## Rules` section
21
+
22
+ Cluster files are detected by reading the locked-clusters table in
23
+ `docs/contracts/command-clusters.md` (column-1 backticks).
24
+
25
+ Exit codes: 0 = clean, 1 = pattern violations, 3 = internal error.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import re
30
+ import sys
31
+ from dataclasses import dataclass, field
32
+ from pathlib import Path
33
+
34
+ ROOT = Path(__file__).resolve().parent.parent
35
+ COMMANDS_DIR = ROOT / ".agent-src.uncompressed/commands"
36
+ CONTRACT = ROOT / "docs/contracts/command-clusters.md"
37
+
38
+ REQUIRED_SECTIONS = ["## Sub-commands", "## Dispatch", "## Rules"]
39
+ TABLE_HEADER_RE = re.compile(
40
+ r"\|\s*Sub-command\s*\|\s*Routes to\s*\|\s*Purpose\s*\|", re.IGNORECASE
41
+ )
42
+
43
+
44
+ @dataclass
45
+ class FileReport:
46
+ path: Path
47
+ cluster: str
48
+ errors: list[str] = field(default_factory=list)
49
+
50
+
51
+ def load_cluster_table() -> list[tuple[str, str]]:
52
+ """Return [(cluster_name, kind)] where kind ∈ {"dispatch", "flag"}."""
53
+ text = CONTRACT.read_text(encoding="utf-8")
54
+ in_table = False
55
+ rows: list[tuple[str, str]] = []
56
+ row_re = re.compile(
57
+ r"\|\s*`([a-z][a-z0-9-]*)`\s*\|\s*\d+\s*\|\s*([^|]+)\|"
58
+ )
59
+ for line in text.splitlines():
60
+ if line.startswith("## Locked clusters"):
61
+ in_table = True
62
+ continue
63
+ if in_table and line.startswith("## "):
64
+ break
65
+ if in_table:
66
+ m = row_re.match(line)
67
+ if m:
68
+ name, sub_col = m.group(1), m.group(2).strip().lower()
69
+ kind = "flag" if sub_col.startswith("flag:") else "dispatch"
70
+ rows.append((name, kind))
71
+ return rows
72
+
73
+
74
+ def parse_frontmatter(text: str) -> tuple[dict[str, str], str]:
75
+ if not text.startswith("---\n"):
76
+ return {}, text
77
+ end = text.find("\n---\n", 4)
78
+ if end == -1:
79
+ return {}, text
80
+ fm: dict[str, str] = {}
81
+ for line in text[4:end].splitlines():
82
+ if line and not line.startswith(" ") and ":" in line:
83
+ k, _, v = line.partition(":")
84
+ fm[k.strip()] = v.strip()
85
+ body = text[end + len("\n---\n"):]
86
+ return fm, body
87
+
88
+
89
+ def check_dispatcher(cluster: str) -> FileReport:
90
+ path = COMMANDS_DIR / f"{cluster}.md"
91
+ rep = FileReport(path=path, cluster=cluster)
92
+ if not path.exists():
93
+ rep.errors.append(f"dispatcher file missing: {path.relative_to(ROOT)}")
94
+ return rep
95
+ text = path.read_text(encoding="utf-8")
96
+ fm, body = parse_frontmatter(text)
97
+
98
+ # Frontmatter checks.
99
+ if fm.get("name") != cluster:
100
+ rep.errors.append(f"frontmatter `name:` is {fm.get('name')!r}, expected {cluster!r}")
101
+ if fm.get("cluster") != cluster:
102
+ rep.errors.append(f"frontmatter `cluster:` is {fm.get('cluster')!r}, expected {cluster!r}")
103
+ if fm.get("disable-model-invocation") != "true":
104
+ rep.errors.append("frontmatter `disable-model-invocation: true` missing")
105
+
106
+ # H1 check.
107
+ h1 = f"# /{cluster}"
108
+ if h1 not in body.splitlines()[:5]:
109
+ rep.errors.append(f"missing top-level heading {h1!r} in first 5 body lines")
110
+
111
+ # Section presence.
112
+ for section in REQUIRED_SECTIONS:
113
+ if section not in body:
114
+ rep.errors.append(f"missing section header {section!r}")
115
+
116
+ # Sub-commands table header (only meaningful if Sub-commands section exists).
117
+ if "## Sub-commands" in body and not TABLE_HEADER_RE.search(body):
118
+ rep.errors.append(
119
+ "Sub-commands table header must be `| Sub-command | Routes to | Purpose |`"
120
+ )
121
+ return rep
122
+
123
+
124
+ def main() -> int:
125
+ rows = load_cluster_table()
126
+ if not rows:
127
+ print(f"❌ No clusters parsed from {CONTRACT.relative_to(ROOT)}",
128
+ file=sys.stderr)
129
+ return 3
130
+
131
+ dispatch_clusters = [n for n, k in rows if k == "dispatch"]
132
+ flag_clusters = [n for n, k in rows if k == "flag"]
133
+
134
+ reports = [check_dispatcher(n) for n in dispatch_clusters]
135
+ bad = [r for r in reports if r.errors]
136
+
137
+ # Flag clusters: only assert the file exists; legacy shape is preserved.
138
+ flag_missing = [n for n in flag_clusters
139
+ if not (COMMANDS_DIR / f"{n}.md").exists()]
140
+ if flag_missing:
141
+ print(f"❌ Flag-cluster file(s) missing: {flag_missing}")
142
+ return 1
143
+
144
+ if bad:
145
+ print(f"❌ {len(bad)}/{len(reports)} cluster dispatcher(s) deviate "
146
+ f"from the Phase-1 reference pattern:")
147
+ for r in bad:
148
+ print(f" • {r.path.relative_to(ROOT)} (cluster `{r.cluster}`)")
149
+ for err in r.errors:
150
+ print(f" - {err}")
151
+ print(f"\nReference: commands/fix.md, commands/optimize.md, commands/feature.md")
152
+ return 1
153
+ print(f"✅ {len(reports)} cluster dispatcher(s) match the Phase-1 reference "
154
+ f"pattern; {len(flag_clusters)} flag-cluster(s) verified present.")
155
+ return 0
156
+
157
+
158
+ if __name__ == "__main__":
159
+ sys.exit(main())