@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,381 @@
|
|
|
1
|
+
"""External-AI clients for the council.
|
|
2
|
+
|
|
3
|
+
Mirrors the contract from `scripts/skill_trigger_eval.py`:
|
|
4
|
+
- Tokens come exclusively from ~/.config/agent-config/<provider>.key.
|
|
5
|
+
- File mode must be exactly 0o600. Drift is a hard abort.
|
|
6
|
+
- No environment-variable fallback. No keychain fallback.
|
|
7
|
+
- Real SDKs (`anthropic`, `openai`) are *soft* dependencies — the
|
|
8
|
+
module imports cleanly without them; only `ask()` requires them.
|
|
9
|
+
|
|
10
|
+
Tests inject mock clients via the `client=` constructor argument and
|
|
11
|
+
never hit the real API.
|
|
12
|
+
|
|
13
|
+
Mode contract (Phase 2b):
|
|
14
|
+
- `billable=True` clients (AnthropicClient, OpenAIClient) participate
|
|
15
|
+
in the cost gate — projected USD spend is checked before each call.
|
|
16
|
+
- `billable=False` clients (ManualClient, future PlaywrightClient)
|
|
17
|
+
skip the cost gate entirely. Spend = $0 to us; provider-side rate
|
|
18
|
+
limits are the user's concern.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import stat
|
|
24
|
+
import sys
|
|
25
|
+
import time
|
|
26
|
+
from abc import ABC, abstractmethod
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import TextIO
|
|
30
|
+
|
|
31
|
+
ANTHROPIC_KEY_PATH = Path.home() / ".config" / "agent-config" / "anthropic.key"
|
|
32
|
+
OPENAI_KEY_PATH = Path.home() / ".config" / "agent-config" / "openai.key"
|
|
33
|
+
|
|
34
|
+
DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-5"
|
|
35
|
+
DEFAULT_OPENAI_MODEL = "gpt-4o"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class KeyGateError(RuntimeError):
|
|
39
|
+
"""Raised when a provider key file violates the 0600 contract."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class CouncilResponse:
|
|
44
|
+
"""Normalised output from a single council member."""
|
|
45
|
+
|
|
46
|
+
provider: str
|
|
47
|
+
model: str
|
|
48
|
+
text: str
|
|
49
|
+
input_tokens: int = 0
|
|
50
|
+
output_tokens: int = 0
|
|
51
|
+
latency_ms: int = 0
|
|
52
|
+
error: str | None = None
|
|
53
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _load_key(path: Path, prefix: str, install_script: str) -> str:
|
|
57
|
+
"""Shared 0600-gated key loader. Refuses anything outside the contract."""
|
|
58
|
+
if not path.exists():
|
|
59
|
+
raise KeyGateError(
|
|
60
|
+
f"Key not found at {path}.\n"
|
|
61
|
+
f" Install it with: bash {install_script}"
|
|
62
|
+
)
|
|
63
|
+
st = path.stat()
|
|
64
|
+
mode = stat.S_IMODE(st.st_mode)
|
|
65
|
+
if mode != 0o600:
|
|
66
|
+
raise KeyGateError(
|
|
67
|
+
f"Unsafe permissions on {path}: got {oct(mode)}, expected 0o600.\n"
|
|
68
|
+
f" Fix: chmod 600 {path}"
|
|
69
|
+
)
|
|
70
|
+
key = path.read_text(encoding="utf-8").strip()
|
|
71
|
+
if not key:
|
|
72
|
+
raise KeyGateError(f"{path} is empty.")
|
|
73
|
+
if not key.startswith(prefix):
|
|
74
|
+
raise KeyGateError(
|
|
75
|
+
f"{path} does not look like a {prefix!r}-prefixed key."
|
|
76
|
+
)
|
|
77
|
+
return key
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_anthropic_key(path: Path = ANTHROPIC_KEY_PATH) -> str:
|
|
81
|
+
return _load_key(path, "sk-ant-", "scripts/install_anthropic_key.sh")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def load_openai_key(path: Path = OPENAI_KEY_PATH) -> str:
|
|
85
|
+
return _load_key(path, "sk-", "scripts/install_openai_key.sh")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ExternalAIClient(ABC):
|
|
89
|
+
"""Abstract base for council members."""
|
|
90
|
+
|
|
91
|
+
name: str = ""
|
|
92
|
+
model: str = ""
|
|
93
|
+
billable: bool = True # API-mode subclasses spend money; manual/playwright don't.
|
|
94
|
+
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def ask(
|
|
97
|
+
self,
|
|
98
|
+
system_prompt: str,
|
|
99
|
+
user_prompt: str,
|
|
100
|
+
max_tokens: int = 1024,
|
|
101
|
+
) -> CouncilResponse:
|
|
102
|
+
"""Send one independent query. Must never raise on network/API
|
|
103
|
+
failure — return a `CouncilResponse` with `error` set instead.
|
|
104
|
+
Other members should not be blocked by one failure."""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AnthropicClient(ExternalAIClient):
|
|
108
|
+
name = "anthropic"
|
|
109
|
+
billable = True
|
|
110
|
+
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
model: str = DEFAULT_ANTHROPIC_MODEL,
|
|
114
|
+
client: object = None,
|
|
115
|
+
api_key: str | None = None,
|
|
116
|
+
):
|
|
117
|
+
self.model = model
|
|
118
|
+
if client is not None:
|
|
119
|
+
self._client = client
|
|
120
|
+
return
|
|
121
|
+
if api_key is None:
|
|
122
|
+
raise RuntimeError(
|
|
123
|
+
"AnthropicClient requires explicit api_key or injected client. "
|
|
124
|
+
"Use load_anthropic_key() — no env-var fallback."
|
|
125
|
+
)
|
|
126
|
+
try:
|
|
127
|
+
import anthropic # type: ignore[import-not-found]
|
|
128
|
+
except ImportError as exc: # pragma: no cover - exercised only with real SDK
|
|
129
|
+
raise RuntimeError(
|
|
130
|
+
"anthropic package not installed. `pip install anthropic`."
|
|
131
|
+
) from exc
|
|
132
|
+
self._client = anthropic.Anthropic(api_key=api_key)
|
|
133
|
+
|
|
134
|
+
def ask(self, system_prompt: str, user_prompt: str, max_tokens: int = 1024) -> CouncilResponse:
|
|
135
|
+
t0 = time.monotonic()
|
|
136
|
+
try:
|
|
137
|
+
response = self._client.messages.create(
|
|
138
|
+
model=self.model,
|
|
139
|
+
max_tokens=max_tokens,
|
|
140
|
+
system=system_prompt,
|
|
141
|
+
messages=[{"role": "user", "content": user_prompt}],
|
|
142
|
+
)
|
|
143
|
+
except Exception as exc: # noqa: BLE001 - normalise all SDK errors
|
|
144
|
+
return CouncilResponse(
|
|
145
|
+
provider=self.name, model=self.model, text="",
|
|
146
|
+
latency_ms=int((time.monotonic() - t0) * 1000),
|
|
147
|
+
error=f"{type(exc).__name__}: {exc}",
|
|
148
|
+
)
|
|
149
|
+
latency_ms = int((time.monotonic() - t0) * 1000)
|
|
150
|
+
text = ""
|
|
151
|
+
content = getattr(response, "content", None)
|
|
152
|
+
if content:
|
|
153
|
+
text = getattr(content[0], "text", "") or ""
|
|
154
|
+
usage = getattr(response, "usage", None)
|
|
155
|
+
return CouncilResponse(
|
|
156
|
+
provider=self.name, model=self.model, text=text,
|
|
157
|
+
input_tokens=getattr(usage, "input_tokens", 0) if usage else 0,
|
|
158
|
+
output_tokens=getattr(usage, "output_tokens", 0) if usage else 0,
|
|
159
|
+
latency_ms=latency_ms,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class OpenAIClient(ExternalAIClient):
|
|
164
|
+
name = "openai"
|
|
165
|
+
billable = True
|
|
166
|
+
|
|
167
|
+
def __init__(
|
|
168
|
+
self,
|
|
169
|
+
model: str = DEFAULT_OPENAI_MODEL,
|
|
170
|
+
client: object = None,
|
|
171
|
+
api_key: str | None = None,
|
|
172
|
+
):
|
|
173
|
+
self.model = model
|
|
174
|
+
if client is not None:
|
|
175
|
+
self._client = client
|
|
176
|
+
return
|
|
177
|
+
if api_key is None:
|
|
178
|
+
raise RuntimeError(
|
|
179
|
+
"OpenAIClient requires explicit api_key or injected client. "
|
|
180
|
+
"Use load_openai_key() — no env-var fallback."
|
|
181
|
+
)
|
|
182
|
+
try:
|
|
183
|
+
import openai # type: ignore[import-not-found]
|
|
184
|
+
except ImportError as exc: # pragma: no cover - exercised only with real SDK
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
"openai package not installed. `pip install openai`."
|
|
187
|
+
) from exc
|
|
188
|
+
self._client = openai.OpenAI(api_key=api_key)
|
|
189
|
+
|
|
190
|
+
def ask(self, system_prompt: str, user_prompt: str, max_tokens: int = 1024) -> CouncilResponse:
|
|
191
|
+
t0 = time.monotonic()
|
|
192
|
+
try:
|
|
193
|
+
response = self._client.chat.completions.create(
|
|
194
|
+
model=self.model,
|
|
195
|
+
max_tokens=max_tokens,
|
|
196
|
+
messages=[
|
|
197
|
+
{"role": "system", "content": system_prompt},
|
|
198
|
+
{"role": "user", "content": user_prompt},
|
|
199
|
+
],
|
|
200
|
+
)
|
|
201
|
+
except Exception as exc: # noqa: BLE001 - normalise all SDK errors
|
|
202
|
+
return CouncilResponse(
|
|
203
|
+
provider=self.name, model=self.model, text="",
|
|
204
|
+
latency_ms=int((time.monotonic() - t0) * 1000),
|
|
205
|
+
error=f"{type(exc).__name__}: {exc}",
|
|
206
|
+
)
|
|
207
|
+
latency_ms = int((time.monotonic() - t0) * 1000)
|
|
208
|
+
text = ""
|
|
209
|
+
choices = getattr(response, "choices", None)
|
|
210
|
+
if choices:
|
|
211
|
+
msg = getattr(choices[0], "message", None)
|
|
212
|
+
text = getattr(msg, "content", "") if msg else ""
|
|
213
|
+
usage = getattr(response, "usage", None)
|
|
214
|
+
return CouncilResponse(
|
|
215
|
+
provider=self.name, model=self.model, text=text or "",
|
|
216
|
+
input_tokens=getattr(usage, "prompt_tokens", 0) if usage else 0,
|
|
217
|
+
output_tokens=getattr(usage, "completion_tokens", 0) if usage else 0,
|
|
218
|
+
latency_ms=latency_ms,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# ── Manual mode (Phase 2b) ───────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
MANUAL_END_MARKER = "END" # line containing only this terminates a paste block.
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _read_until_marker(stream: TextIO, marker: str) -> str:
|
|
229
|
+
"""Read lines from `stream` until a line equal to `marker` (after strip).
|
|
230
|
+
|
|
231
|
+
Returns the joined body without the marker line. EOF before the
|
|
232
|
+
marker is treated as end-of-input — the body collected so far is
|
|
233
|
+
returned; callers decide whether that counts as abort.
|
|
234
|
+
"""
|
|
235
|
+
body: list[str] = []
|
|
236
|
+
for raw in stream:
|
|
237
|
+
line = raw.rstrip("\n")
|
|
238
|
+
if line.strip() == marker:
|
|
239
|
+
break
|
|
240
|
+
body.append(line)
|
|
241
|
+
return "\n".join(body).strip()
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class ManualClient(ExternalAIClient):
|
|
245
|
+
"""Copy-paste council member — user is the transport.
|
|
246
|
+
|
|
247
|
+
`ask()` renders the system prompt + artefact as one Markdown block,
|
|
248
|
+
prints it to `stdout`, and reads pasted replies from `stdin`. After
|
|
249
|
+
each pasted reply, surfaces a 1/2/3 menu (more · next · abort) per
|
|
250
|
+
`user-interaction`. Loops until the user picks 2 or 3.
|
|
251
|
+
|
|
252
|
+
Spend is $0 — `billable=False` makes the orchestrator skip the cost
|
|
253
|
+
gate for this member regardless of the price table.
|
|
254
|
+
|
|
255
|
+
Tests inject `stdin` / `stdout` `TextIO` streams. Production usage
|
|
256
|
+
falls back to `sys.stdin` / `sys.stdout`.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
billable = False
|
|
260
|
+
|
|
261
|
+
def __init__(
|
|
262
|
+
self,
|
|
263
|
+
*,
|
|
264
|
+
name: str = "manual",
|
|
265
|
+
model: str = "manual",
|
|
266
|
+
provider_label: str = "your LLM web UI",
|
|
267
|
+
stdin: TextIO | None = None,
|
|
268
|
+
stdout: TextIO | None = None,
|
|
269
|
+
end_marker: str = MANUAL_END_MARKER,
|
|
270
|
+
):
|
|
271
|
+
self.name = name
|
|
272
|
+
self.model = model
|
|
273
|
+
self.provider_label = provider_label
|
|
274
|
+
self._stdin = stdin if stdin is not None else sys.stdin
|
|
275
|
+
self._stdout = stdout if stdout is not None else sys.stdout
|
|
276
|
+
self._end_marker = end_marker
|
|
277
|
+
|
|
278
|
+
def ask(
|
|
279
|
+
self,
|
|
280
|
+
system_prompt: str,
|
|
281
|
+
user_prompt: str,
|
|
282
|
+
max_tokens: int = 1024, # noqa: ARG002 — accepted for ABC parity
|
|
283
|
+
) -> CouncilResponse:
|
|
284
|
+
t0 = time.monotonic()
|
|
285
|
+
rounds: list[str] = []
|
|
286
|
+
block = self._render_block(system_prompt, user_prompt, follow_up=None)
|
|
287
|
+
self._emit(block)
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
while True:
|
|
291
|
+
reply = _read_until_marker(self._stdin, self._end_marker)
|
|
292
|
+
rounds.append(reply)
|
|
293
|
+
choice = self._ask_menu(reply_chars=len(reply))
|
|
294
|
+
|
|
295
|
+
if choice == "2": # done with this member
|
|
296
|
+
break
|
|
297
|
+
if choice == "3": # abort the council run
|
|
298
|
+
return CouncilResponse(
|
|
299
|
+
provider=self.name, model=self.model, text="",
|
|
300
|
+
latency_ms=int((time.monotonic() - t0) * 1000),
|
|
301
|
+
error="manual_aborted",
|
|
302
|
+
metadata={"rounds": len(rounds), "manual": True},
|
|
303
|
+
)
|
|
304
|
+
# choice == "1": collect follow-up, re-emit context block.
|
|
305
|
+
follow_up = self._read_follow_up()
|
|
306
|
+
if not follow_up:
|
|
307
|
+
break # empty follow-up → treat as "done with this member"
|
|
308
|
+
rounds.append(f"[follow-up sent]\n{follow_up}")
|
|
309
|
+
block = self._render_block(system_prompt, user_prompt, follow_up=follow_up)
|
|
310
|
+
self._emit(block)
|
|
311
|
+
except Exception as exc: # noqa: BLE001 — never break the council on a stdin glitch
|
|
312
|
+
return CouncilResponse(
|
|
313
|
+
provider=self.name, model=self.model, text="\n\n".join(rounds),
|
|
314
|
+
latency_ms=int((time.monotonic() - t0) * 1000),
|
|
315
|
+
error=f"{type(exc).__name__}: {exc}",
|
|
316
|
+
metadata={"rounds": len(rounds), "manual": True},
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
text = "\n\n---\n\n".join(rounds).strip()
|
|
320
|
+
return CouncilResponse(
|
|
321
|
+
provider=self.name, model=self.model, text=text,
|
|
322
|
+
latency_ms=int((time.monotonic() - t0) * 1000),
|
|
323
|
+
metadata={"rounds": len(rounds), "manual": True},
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# ── helpers ──────────────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
def _emit(self, text: str) -> None:
|
|
329
|
+
self._stdout.write(text)
|
|
330
|
+
self._stdout.write("\n")
|
|
331
|
+
self._stdout.flush()
|
|
332
|
+
|
|
333
|
+
def _render_block(
|
|
334
|
+
self,
|
|
335
|
+
system_prompt: str,
|
|
336
|
+
user_prompt: str,
|
|
337
|
+
*,
|
|
338
|
+
follow_up: str | None,
|
|
339
|
+
) -> str:
|
|
340
|
+
bar = "═" * 67
|
|
341
|
+
head = (
|
|
342
|
+
f"{bar}\n"
|
|
343
|
+
f"Manual council member: {self.provider_label}\n"
|
|
344
|
+
f"Paste this block into the web UI · then paste the reply below.\n"
|
|
345
|
+
f"{bar}"
|
|
346
|
+
)
|
|
347
|
+
if follow_up is not None:
|
|
348
|
+
body = (
|
|
349
|
+
f"[Follow-up — paste this into the SAME chat thread]\n\n"
|
|
350
|
+
f"{follow_up}"
|
|
351
|
+
)
|
|
352
|
+
else:
|
|
353
|
+
body = f"{system_prompt}\n\n---\n\n{user_prompt}"
|
|
354
|
+
tail = (
|
|
355
|
+
f"{bar}\n"
|
|
356
|
+
f"End your pasted reply with a line containing only: {self._end_marker}\n"
|
|
357
|
+
f"{bar}"
|
|
358
|
+
)
|
|
359
|
+
return f"{head}\n\n{body}\n\n{tail}"
|
|
360
|
+
|
|
361
|
+
def _ask_menu(self, *, reply_chars: int) -> str:
|
|
362
|
+
prompt = (
|
|
363
|
+
f"\nReply received ({reply_chars} chars). Now what?\n"
|
|
364
|
+
f" 1. More feedback for this member (continue this thread)\n"
|
|
365
|
+
f" 2. Done with this member, move to the next\n"
|
|
366
|
+
f" 3. Abort the council run\n\n"
|
|
367
|
+
f"Choose 1/2/3: "
|
|
368
|
+
)
|
|
369
|
+
self._stdout.write(prompt)
|
|
370
|
+
self._stdout.flush()
|
|
371
|
+
line = self._stdin.readline().strip()
|
|
372
|
+
if line in {"1", "2", "3"}:
|
|
373
|
+
return line
|
|
374
|
+
# unknown input → treat as "next" so we never block forever in tests / piped runs.
|
|
375
|
+
return "2"
|
|
376
|
+
|
|
377
|
+
def _read_follow_up(self) -> str:
|
|
378
|
+
self._emit(
|
|
379
|
+
f"\nType your follow-up question, end with a line containing only: {self._end_marker}"
|
|
380
|
+
)
|
|
381
|
+
return _read_until_marker(self._stdin, self._end_marker)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Mode resolution for council members (Phase 2b).
|
|
2
|
+
|
|
3
|
+
Each council member runs in exactly one transport mode per invocation:
|
|
4
|
+
|
|
5
|
+
- ``api`` — direct SDK call against the provider's API (billable).
|
|
6
|
+
- ``manual`` — copy-paste loop with the user as transport (free).
|
|
7
|
+
- ``playwright`` — browser automation (Phase 2c, not yet wired).
|
|
8
|
+
|
|
9
|
+
Resolution precedence — first non-empty wins:
|
|
10
|
+
|
|
11
|
+
1. Invocation flag e.g. ``/council mode:manual``
|
|
12
|
+
2. Per-member setting ``ai_council.members.<name>.mode``
|
|
13
|
+
3. Global setting ``ai_council.mode``
|
|
14
|
+
4. Built-in default ``api``
|
|
15
|
+
|
|
16
|
+
This mirrors how ``cost_profile`` resolves in
|
|
17
|
+
``.augment/guidelines/agent-infra/layered-settings.md``.
|
|
18
|
+
|
|
19
|
+
The resolver is pure — it never touches the filesystem or environment.
|
|
20
|
+
Callers pass in already-loaded values from ``.agent-settings.yml``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Mapping
|
|
26
|
+
|
|
27
|
+
VALID_MODES: frozenset[str] = frozenset({"api", "manual", "playwright"})
|
|
28
|
+
|
|
29
|
+
DEFAULT_MODE: str = "api"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class InvalidModeError(ValueError):
|
|
33
|
+
"""Raised when a configured / invoked mode is not in ``VALID_MODES``."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _normalise(value: object) -> str | None:
|
|
37
|
+
if value is None:
|
|
38
|
+
return None
|
|
39
|
+
if not isinstance(value, str):
|
|
40
|
+
return None
|
|
41
|
+
s = value.strip().lower()
|
|
42
|
+
return s or None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _validate(mode: str | None, source: str) -> str | None:
|
|
46
|
+
if mode is None:
|
|
47
|
+
return None
|
|
48
|
+
if mode not in VALID_MODES:
|
|
49
|
+
raise InvalidModeError(
|
|
50
|
+
f"{source} requested mode={mode!r}; "
|
|
51
|
+
f"expected one of: {sorted(VALID_MODES)}"
|
|
52
|
+
)
|
|
53
|
+
return mode
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def resolve_mode(
|
|
57
|
+
member_name: str,
|
|
58
|
+
*,
|
|
59
|
+
invocation_mode: str | None = None,
|
|
60
|
+
member_settings: Mapping[str, object] | None = None,
|
|
61
|
+
global_mode: str | None = None,
|
|
62
|
+
) -> str:
|
|
63
|
+
"""Resolve the effective transport mode for one member.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
member_name: e.g. ``"anthropic"`` or ``"openai"``. Used in error
|
|
67
|
+
messages only — has no effect on precedence.
|
|
68
|
+
invocation_mode: from a ``/council mode:<x>`` flag. Highest
|
|
69
|
+
priority. ``None`` if the user did not pass a flag.
|
|
70
|
+
member_settings: the dict at
|
|
71
|
+
``ai_council.members.<member_name>`` from
|
|
72
|
+
``.agent-settings.yml``. May contain a ``mode`` key.
|
|
73
|
+
global_mode: from ``ai_council.mode``. Used when neither the
|
|
74
|
+
invocation flag nor the per-member setting is present.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
One of ``VALID_MODES``. Never ``None``.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
InvalidModeError: if any non-empty layer requests a mode not in
|
|
81
|
+
``VALID_MODES``. The earliest layer (highest priority) is
|
|
82
|
+
checked first; later layers are not validated when an
|
|
83
|
+
earlier one already won.
|
|
84
|
+
"""
|
|
85
|
+
inv = _validate(_normalise(invocation_mode), source=f"/council mode= for {member_name!r}")
|
|
86
|
+
if inv is not None:
|
|
87
|
+
return inv
|
|
88
|
+
|
|
89
|
+
member_mode_raw: object | None = None
|
|
90
|
+
if member_settings is not None:
|
|
91
|
+
member_mode_raw = member_settings.get("mode")
|
|
92
|
+
member = _validate(
|
|
93
|
+
_normalise(member_mode_raw),
|
|
94
|
+
source=f"ai_council.members.{member_name}.mode",
|
|
95
|
+
)
|
|
96
|
+
if member is not None:
|
|
97
|
+
return member
|
|
98
|
+
|
|
99
|
+
glob = _validate(_normalise(global_mode), source="ai_council.mode")
|
|
100
|
+
if glob is not None:
|
|
101
|
+
return glob
|
|
102
|
+
|
|
103
|
+
return DEFAULT_MODE
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def resolve_modes(
|
|
107
|
+
member_names: list[str],
|
|
108
|
+
*,
|
|
109
|
+
invocation_mode: str | None = None,
|
|
110
|
+
members_settings: Mapping[str, Mapping[str, object]] | None = None,
|
|
111
|
+
global_mode: str | None = None,
|
|
112
|
+
) -> dict[str, str]:
|
|
113
|
+
"""Resolve modes for a batch of members. Convenience wrapper.
|
|
114
|
+
|
|
115
|
+
``members_settings`` is the full ``ai_council.members`` mapping;
|
|
116
|
+
each member's sub-dict is forwarded to ``resolve_mode()``.
|
|
117
|
+
"""
|
|
118
|
+
out: dict[str, str] = {}
|
|
119
|
+
settings = members_settings or {}
|
|
120
|
+
for name in member_names:
|
|
121
|
+
out[name] = resolve_mode(
|
|
122
|
+
name,
|
|
123
|
+
invocation_mode=invocation_mode,
|
|
124
|
+
member_settings=settings.get(name),
|
|
125
|
+
global_mode=global_mode,
|
|
126
|
+
)
|
|
127
|
+
return out
|