@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.
- package/.agent-src/commands/{agents-audit.md → agents/audit.md} +4 -3
- package/.agent-src/commands/{agents-cleanup.md → agents/cleanup.md} +12 -6
- package/.agent-src/commands/{agents-prepare.md → agents/prepare.md} +4 -3
- package/.agent-src/commands/agents.md +46 -0
- package/.agent-src/commands/bug-fix.md +1 -1
- package/.agent-src/commands/bug-investigate.md +2 -2
- package/.agent-src/commands/{chat-history-checkpoint.md → chat-history/checkpoint.md} +5 -5
- package/.agent-src/commands/{chat-history-clear.md → chat-history/clear.md} +5 -5
- package/.agent-src/commands/{chat-history-resume.md → chat-history/resume.md} +4 -4
- package/.agent-src/commands/chat-history/show.md +107 -0
- package/.agent-src/commands/chat-history.md +33 -89
- package/.agent-src/commands/check-current-md.md +1 -1
- package/.agent-src/commands/{commit-in-chunks.md → commit/in-chunks.md} +15 -13
- package/.agent-src/commands/commit.md +22 -2
- package/.agent-src/commands/{context-create.md → context/create.md} +4 -3
- package/.agent-src/commands/{context-refactor.md → context/refactor.md} +4 -3
- package/.agent-src/commands/context.md +44 -0
- package/.agent-src/commands/{copilot-agents-init.md → copilot-agents/init.md} +4 -3
- package/.agent-src/commands/{copilot-agents-optimize.md → copilot-agents/optimize.md} +4 -3
- package/.agent-src/commands/copilot-agents.md +44 -0
- package/.agent-src/commands/council/default.md +221 -0
- package/.agent-src/commands/council/design.md +97 -0
- package/.agent-src/commands/council/optimize.md +116 -0
- package/.agent-src/commands/council/pr.md +124 -0
- package/.agent-src/commands/council.md +54 -0
- package/.agent-src/commands/{create-pr-description.md → create-pr/description-only.md} +4 -2
- package/.agent-src/commands/create-pr.md +49 -5
- package/.agent-src/commands/e2e-heal.md +1 -1
- package/.agent-src/commands/e2e-plan.md +1 -1
- package/.agent-src/commands/{feature-dev.md → feature/dev.md} +6 -3
- package/.agent-src/commands/{feature-explore.md → feature/explore.md} +5 -4
- package/.agent-src/commands/{feature-plan.md → feature/plan.md} +32 -5
- package/.agent-src/commands/{feature-refactor.md → feature/refactor.md} +4 -3
- package/.agent-src/commands/{feature-roadmap.md → feature/roadmap.md} +7 -6
- package/.agent-src/commands/feature.md +52 -0
- package/.agent-src/commands/{fix-ci.md → fix/ci.md} +4 -3
- package/.agent-src/commands/{fix-portability.md → fix/portability.md} +4 -3
- package/.agent-src/commands/{fix-pr-bot-comments.md → fix/pr-bots.md} +4 -3
- package/.agent-src/commands/{fix-pr-developer-comments.md → fix/pr-developers.md} +4 -3
- package/.agent-src/commands/{fix-pr-comments.md → fix/pr.md} +7 -6
- package/.agent-src/commands/{fix-references.md → fix/refs.md} +4 -3
- package/.agent-src/commands/{fix-seeder.md → fix/seeder.md} +4 -3
- package/.agent-src/commands/fix.md +54 -0
- package/.agent-src/commands/jira-ticket.md +1 -1
- package/.agent-src/commands/{do-and-judge.md → judge/on-diff.md} +7 -6
- package/.agent-src/commands/judge/solo.md +90 -0
- package/.agent-src/commands/{do-in-steps.md → judge/steps.md} +8 -7
- package/.agent-src/commands/judge.md +35 -70
- package/.agent-src/commands/{memory-add.md → memory/add.md} +7 -6
- package/.agent-src/commands/{memory-full.md → memory/load.md} +6 -5
- package/.agent-src/commands/{memory-promote.md → memory/promote.md} +6 -5
- package/.agent-src/commands/{propose-memory.md → memory/propose.md} +6 -5
- package/.agent-src/commands/memory.md +48 -0
- package/.agent-src/commands/mode.md +5 -5
- package/.agent-src/commands/{module-create.md → module/create.md} +4 -3
- package/.agent-src/commands/{module-explore.md → module/explore.md} +4 -3
- package/.agent-src/commands/module.md +44 -0
- package/.agent-src/commands/onboard.md +3 -3
- package/.agent-src/commands/{optimize-agents.md → optimize/agents.md} +5 -4
- package/.agent-src/commands/{optimize-augmentignore.md → optimize/augmentignore.md} +4 -4
- package/.agent-src/commands/{optimize-rtk-filters.md → optimize/rtk.md} +4 -3
- package/.agent-src/commands/{optimize-skills.md → optimize/skills.md} +5 -4
- package/.agent-src/commands/optimize.md +48 -0
- package/.agent-src/commands/{override-create.md → override/create.md} +4 -3
- package/.agent-src/commands/{override-manage.md → override/manage.md} +4 -3
- package/.agent-src/commands/override.md +44 -0
- package/.agent-src/commands/review-changes.md +26 -1
- package/.agent-src/commands/review-routing.md +1 -1
- package/.agent-src/commands/{roadmap-create.md → roadmap/create.md} +33 -5
- package/.agent-src/commands/{roadmap-execute.md → roadmap/execute.md} +4 -3
- package/.agent-src/commands/roadmap.md +44 -0
- package/.agent-src/commands/set-cost-profile.md +3 -3
- package/.agent-src/commands/sync-agent-settings.md +2 -2
- package/.agent-src/commands/{tests-create.md → tests/create.md} +5 -4
- package/.agent-src/commands/{tests-execute.md → tests/execute.md} +4 -3
- package/.agent-src/commands/tests.md +44 -0
- package/.agent-src/commands/upstream-contribute.md +1 -1
- package/.agent-src/contexts/authority/commit-mechanics.md +57 -0
- package/.agent-src/contexts/authority/destructive-mechanics.md +66 -0
- package/.agent-src/contexts/authority/scope-mechanics.md +87 -0
- package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +72 -0
- package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +79 -0
- package/.agent-src/contexts/communication/rules-auto/augment-source-of-truth-mechanics.md +98 -0
- package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +87 -0
- package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +62 -0
- package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +78 -0
- package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +85 -0
- package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +65 -0
- package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +78 -0
- package/.agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +62 -0
- package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +55 -0
- package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +53 -0
- package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +77 -0
- package/.agent-src/contexts/execution/autonomy-detection.md +54 -0
- package/.agent-src/contexts/execution/autonomy-examples.md +90 -0
- package/.agent-src/contexts/execution/autonomy-mechanics.md +29 -0
- package/.agent-src/contexts/execution/verification-mechanics.md +80 -0
- package/.agent-src/contexts/judges/no-consolidate-rationale.md +102 -0
- package/.agent-src/contexts/judges/persona-voice-rubric.md +140 -0
- package/.agent-src/personas/README.md +1 -1
- package/.agent-src/rules/agent-authority.md +24 -0
- package/.agent-src/rules/architecture.md +1 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/.agent-src/rules/artifact-engagement-recording.md +14 -70
- package/.agent-src/rules/ask-when-uncertain.md +28 -43
- package/.agent-src/rules/augment-portability.md +15 -61
- package/.agent-src/rules/augment-source-of-truth.md +27 -93
- package/.agent-src/rules/autonomous-execution.md +78 -114
- package/.agent-src/rules/capture-learnings.md +1 -1
- package/.agent-src/rules/chat-history-cadence.md +3 -3
- package/.agent-src/rules/chat-history-ownership.md +3 -3
- package/.agent-src/rules/chat-history-visibility.md +3 -3
- package/.agent-src/rules/cli-output-handling.md +10 -76
- package/.agent-src/rules/command-suggestion-policy.md +93 -0
- package/.agent-src/rules/commit-conventions.md +17 -14
- package/.agent-src/rules/commit-policy.md +14 -42
- package/.agent-src/rules/context-hygiene.md +3 -3
- package/.agent-src/rules/direct-answers.md +34 -49
- package/.agent-src/rules/docker-commands.md +5 -5
- package/.agent-src/rules/docs-sync.md +16 -70
- package/.agent-src/rules/e2e-testing.md +1 -1
- package/.agent-src/rules/guidelines.md +4 -4
- package/.agent-src/rules/improve-before-implement.md +2 -2
- package/.agent-src/rules/language-and-tone.md +50 -133
- package/.agent-src/rules/minimal-safe-diff.md +3 -3
- package/.agent-src/rules/missing-tool-handling.md +28 -22
- package/.agent-src/rules/model-recommendation.md +4 -4
- package/.agent-src/rules/no-cheap-questions.md +82 -0
- package/.agent-src/rules/no-roadmap-references.md +73 -0
- package/.agent-src/rules/non-destructive-by-default.md +15 -49
- package/.agent-src/rules/onboarding-gate.md +5 -5
- package/.agent-src/rules/package-ci-checks.md +21 -61
- package/.agent-src/rules/preservation-guard.md +64 -29
- package/.agent-src/rules/review-routing-awareness.md +26 -45
- package/.agent-src/rules/roadmap-progress-sync.md +28 -96
- package/.agent-src/rules/role-mode-adherence.md +2 -2
- package/.agent-src/rules/scope-control.md +65 -46
- package/.agent-src/rules/security-sensitive-stop.md +9 -9
- package/.agent-src/rules/size-enforcement.md +1 -1
- package/.agent-src/rules/skill-quality.md +16 -48
- package/.agent-src/rules/{slash-commands.md → slash-command-routing-policy.md} +7 -4
- package/.agent-src/rules/think-before-action.md +55 -45
- package/.agent-src/rules/token-efficiency.md +4 -4
- package/.agent-src/rules/tool-safety.md +19 -16
- package/.agent-src/rules/{ui-audit-before-build.md → ui-audit-gate.md} +27 -41
- package/.agent-src/rules/user-interaction.md +16 -71
- package/.agent-src/rules/verify-before-complete.md +12 -67
- package/.agent-src/scripts/update_roadmap_progress.py +9 -4
- package/.agent-src/skills/ai-council/SKILL.md +335 -0
- package/.agent-src/skills/api-endpoint/SKILL.md +2 -2
- package/.agent-src/skills/api-testing/SKILL.md +1 -1
- package/.agent-src/skills/blade-ui/SKILL.md +1 -1
- package/.agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/.agent-src/skills/bug-analyzer/SKILL.md +1 -1
- package/.agent-src/skills/check-refs/SKILL.md +59 -40
- package/.agent-src/skills/command-routing/SKILL.md +1 -1
- package/.agent-src/skills/command-writing/SKILL.md +1 -1
- package/.agent-src/skills/conventional-commits-writing/SKILL.md +86 -28
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +7 -7
- package/.agent-src/skills/developer-like-execution/SKILL.md +6 -6
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +101 -65
- package/.agent-src/skills/flux/SKILL.md +31 -11
- package/.agent-src/skills/git-workflow/SKILL.md +1 -1
- package/.agent-src/skills/github-ci/SKILL.md +2 -2
- package/.agent-src/skills/guideline-writing/SKILL.md +11 -11
- package/.agent-src/skills/judge-code-quality/SKILL.md +7 -8
- package/.agent-src/skills/judge-security-auditor/SKILL.md +4 -5
- package/.agent-src/skills/judge-test-coverage/SKILL.md +3 -4
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +4 -4
- package/.agent-src/skills/lint-skills/SKILL.md +57 -39
- package/.agent-src/skills/livewire/SKILL.md +1 -1
- package/.agent-src/skills/md-language-check/SKILL.md +61 -39
- package/.agent-src/skills/override-management/SKILL.md +7 -7
- package/.agent-src/skills/php-coder/SKILL.md +1 -1
- package/.agent-src/skills/playwright-testing/SKILL.md +2 -2
- package/.agent-src/skills/quality-tools/SKILL.md +2 -2
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +116 -43
- package/.agent-src/skills/readme-reviewer/SKILL.md +31 -30
- package/.agent-src/skills/readme-writing/SKILL.md +79 -54
- package/.agent-src/skills/readme-writing-package/SKILL.md +51 -48
- package/.agent-src/skills/receiving-code-review/SKILL.md +53 -48
- package/.agent-src/skills/refine-prompt/SKILL.md +0 -1
- package/.agent-src/skills/requesting-code-review/SKILL.md +35 -30
- package/.agent-src/skills/review-routing/SKILL.md +2 -2
- package/.agent-src/skills/rule-writing/SKILL.md +1 -1
- package/.agent-src/skills/security/SKILL.md +7 -2
- package/.agent-src/skills/security-audit/SKILL.md +7 -3
- package/.agent-src/skills/skill-reviewer/SKILL.md +1 -1
- package/.agent-src/skills/skill-writing/SKILL.md +3 -3
- package/.agent-src/skills/subagent-orchestration/SKILL.md +1 -0
- package/.agent-src/skills/systematic-debugging/SKILL.md +69 -61
- package/.agent-src/skills/test-driven-development/SKILL.md +59 -57
- package/.agent-src/skills/test-performance/SKILL.md +0 -1
- package/.agent-src/skills/traefik/SKILL.md +4 -4
- package/.agent-src/skills/upstream-contribute/SKILL.md +1 -1
- package/.agent-src/skills/validate-feature-fit/SKILL.md +2 -2
- package/.agent-src/skills/{verify-before-complete → verify-completion-evidence}/SKILL.md +30 -28
- package/.agent-src/templates/agent-settings.md +8 -8
- package/.agent-src/templates/contexts/auth-model.md +1 -1
- package/.agent-src/templates/scripts/README.md +2 -2
- package/.agent-src/templates/scripts/telemetry/aggregator.py +16 -1
- package/.agent-src/templates/scripts/telemetry/engagement.py +59 -0
- package/.agent-src/templates/scripts/telemetry/report_renderer.py +28 -1
- package/.agent-src/templates/scripts/telemetry_record.py +14 -1
- package/.claude-plugin/marketplace.json +31 -12
- package/AGENTS.md +11 -9
- package/CHANGELOG.md +213 -2
- package/README.md +43 -44
- package/config/agent-settings.template.yml +58 -1
- package/config/gitignore-block.txt +3 -0
- package/docs/architecture.md +5 -7
- package/docs/catalog.md +359 -0
- package/docs/contracts/STABILITY.md +46 -1
- package/docs/contracts/adr-chat-history-split.md +1 -3
- package/docs/contracts/adr-command-suggestion.md +3 -5
- package/docs/contracts/adr-implement-ticket-runtime.md +1 -2
- package/docs/contracts/adr-product-ui-track.md +5 -8
- package/docs/contracts/adr-prompt-driven-execution.md +3 -4
- package/docs/contracts/agent-memory-contract.md +8 -13
- package/docs/contracts/artifact-engagement-flow.md +7 -10
- package/docs/contracts/command-clusters.md +56 -46
- package/docs/contracts/command-suggestion-flow.md +4 -6
- package/docs/contracts/context-paths.md +99 -0
- package/docs/contracts/file-ownership-matrix.json +6722 -0
- package/docs/contracts/file-ownership-matrix.md +134 -0
- package/docs/contracts/implement-ticket-flow.md +8 -11
- package/docs/contracts/linear-ai-rules-inclusion.md +1 -2
- package/docs/contracts/linear-ai-three-layers.md +0 -2
- package/docs/contracts/load-context-budget-model.md +178 -0
- package/docs/contracts/load-context-schema.md +184 -0
- package/docs/contracts/rule-interactions.md +0 -1
- package/docs/contracts/rule-interactions.yml +96 -0
- package/docs/contracts/rule-priority-hierarchy.md +87 -0
- package/docs/contracts/ui-track-flow.md +8 -18
- package/docs/customization.md +16 -0
- package/docs/end-to-end-walkthroughs.md +165 -0
- package/docs/getting-started.md +29 -10
- package/docs/github-topics.md +12 -3
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +100 -0
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +79 -0
- package/{.agent-src → docs}/guidelines/docs/readme-size-and-splitting.md +26 -25
- package/docs/guidelines/php/git.md +164 -0
- package/docs/migrations/commands-1.15.0.md +1 -1
- package/docs/showcase.md +9 -4
- package/docs/skills-catalog.md +14 -8
- package/docs/ui-track-mental-model.md +2 -2
- package/llms.txt +13 -7
- package/package.json +1 -1
- package/scripts/_one_off_phase4_dispatch_latency.py +108 -0
- package/scripts/_one_off_phase6_trigger_jaccard.py +92 -0
- package/scripts/_phase2_shim_helper.py +109 -0
- package/scripts/agent-config +33 -0
- package/scripts/ai_council/__init__.py +39 -0
- package/scripts/ai_council/_default_prices.py +41 -0
- package/scripts/ai_council/_one_off_2a4_acceptance.py +208 -0
- package/scripts/ai_council/_one_off_context_layer_v1_estimate.py +67 -0
- package/scripts/ai_council/_one_off_context_layer_v1_review.py +292 -0
- package/scripts/ai_council/_one_off_followups_review.py +259 -0
- package/scripts/ai_council/_one_off_nondestructive_inline_audit.py +209 -0
- package/scripts/ai_council/_one_off_phase_2a_budget_rebalance.py +257 -0
- package/scripts/ai_council/_one_off_phase_2a_post_revert.py +197 -0
- package/scripts/ai_council/_one_off_rebalancing_audit.py +149 -0
- package/scripts/ai_council/_one_off_roundtrip.py +106 -0
- package/scripts/ai_council/_one_off_rule_hardening_v1.py +251 -0
- package/scripts/ai_council/_one_off_structural_open_questions.py +232 -0
- package/scripts/ai_council/_one_off_structural_optimization.py +144 -0
- package/scripts/ai_council/_one_off_structural_v3_gaps.py +252 -0
- package/scripts/ai_council/_one_off_structural_v3_review.py +240 -0
- package/scripts/ai_council/budget_guard.py +172 -0
- package/scripts/ai_council/bundler.py +261 -0
- package/scripts/ai_council/clients.py +381 -0
- package/scripts/ai_council/modes.py +127 -0
- package/scripts/ai_council/orchestrator.py +350 -0
- package/scripts/ai_council/pricing.py +213 -0
- package/scripts/ai_council/project_context.py +159 -0
- package/scripts/ai_council/prompts.py +232 -0
- package/scripts/ai_council/session.py +144 -0
- package/scripts/check_always_budget.py +444 -0
- package/scripts/check_augmentignore.py +69 -0
- package/scripts/check_cluster_patterns.py +159 -0
- package/scripts/check_command_count_messaging.py +127 -0
- package/scripts/check_context_paths.py +201 -0
- package/scripts/check_no_roadmap_refs.py +155 -0
- package/scripts/check_phase_coupling.py +148 -0
- package/scripts/check_portability.py +57 -0
- package/scripts/check_public_catalog_links.py +122 -0
- package/scripts/check_references.py +33 -3
- package/scripts/check_roadmap_trackable.py +111 -0
- package/scripts/check_safety_floor_untouched.py +125 -0
- package/scripts/command_suggester/cooldown.py +1 -1
- package/scripts/command_suggester/loader.py +4 -1
- package/scripts/compress.py +59 -13
- package/scripts/generate_index.py +270 -0
- package/scripts/generate_ownership_matrix.py +323 -0
- package/scripts/hooks/augment-roadmap-progress.sh +57 -0
- package/scripts/install.py +49 -28
- package/scripts/install_anthropic_key.sh +5 -0
- package/scripts/install_openai_key.sh +106 -0
- package/scripts/lint_load_context.py +163 -0
- package/scripts/lint_no_new_atomic_commands.py +12 -11
- package/scripts/requirements-evals.txt +1 -0
- package/scripts/roadmap_progress_hook.py +159 -0
- package/scripts/schemas/command.schema.json +22 -1
- package/scripts/schemas/rule.schema.json +10 -0
- package/scripts/skill_linter.py +13 -4
- package/scripts/sync_agent_settings.py +26 -3
- package/scripts/update_counts.py +16 -4
- package/scripts/update_prices.py +124 -0
- package/.agent-src/guidelines/php/git.md +0 -96
- package/.agent-src/rules/command-suggestion.md +0 -134
- /package/{.agent-src → docs}/guidelines/agent-infra/agent-interaction-and-decision-quality.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/break-glass-usage.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/developer-judgment.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/engineering-memory-data-format.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/layered-settings.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/memory-access.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/naming.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/output-patterns.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/review-routing-data-format.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/role-contracts.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/role-mode-router.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/runtime-layer.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/self-improvement-pipeline.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/size-and-scope.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/tool-integration.md +0 -0
- /package/{.agent-src → docs}/guidelines/e2e/playwright.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/api-design.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/artisan-commands.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/blade-ui.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/controllers.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/database.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/eloquent.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/flux.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/general.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/jobs.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/livewire.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/logging.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/naming.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/dependency-injection.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/dtos.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/events.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/factory.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/pipelines.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/policies.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/repositories.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/service-layer.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/strategy.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/performance.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/resources.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/security.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/sql.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/validations.md +0 -0
- /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())
|