@event4u/agent-config 1.20.0 → 1.22.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.md +1 -1
- package/.agent-src/commands/bug-fix.md +2 -1
- package/.agent-src/commands/bug-investigate.md +3 -2
- package/.agent-src/commands/challenge-me/vision.md +348 -0
- package/.agent-src/commands/challenge-me/with-docs.md +333 -0
- package/.agent-src/commands/challenge-me.md +61 -0
- package/.agent-src/commands/chat-history/import.md +60 -64
- package/.agent-src/commands/compress.md +12 -0
- package/.agent-src/commands/context/create.md +2 -2
- package/.agent-src/commands/context.md +1 -1
- package/.agent-src/commands/copilot-agents.md +1 -1
- package/.agent-src/commands/council/default.md +69 -10
- package/.agent-src/commands/council.md +1 -1
- package/.agent-src/commands/create-pr.md +7 -3
- 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 +3 -3
- package/.agent-src/commands/feature.md +1 -1
- package/.agent-src/commands/fix/seeder.md +2 -2
- package/.agent-src/commands/fix.md +1 -1
- package/.agent-src/commands/grill-me.md +38 -0
- package/.agent-src/commands/jira-ticket.md +1 -1
- package/.agent-src/commands/judge/steps.md +1 -1
- package/.agent-src/commands/judge.md +2 -2
- package/.agent-src/commands/memory.md +1 -1
- package/.agent-src/commands/mode.md +5 -5
- package/.agent-src/commands/module.md +1 -1
- package/.agent-src/commands/onboard.md +4 -4
- package/.agent-src/commands/optimize/augmentignore.md +1 -1
- package/.agent-src/commands/optimize-prompt.md +61 -0
- package/.agent-src/commands/optimize.md +1 -1
- package/.agent-src/commands/override.md +1 -1
- package/.agent-src/commands/review-changes.md +1 -1
- package/.agent-src/commands/review-routing.md +1 -1
- package/.agent-src/commands/roadmap/ai-council.md +183 -0
- package/.agent-src/commands/roadmap/create.md +6 -1
- package/.agent-src/commands/roadmap/process-full.md +58 -0
- package/.agent-src/commands/roadmap/process-phase.md +69 -0
- package/.agent-src/commands/roadmap/process-step.md +57 -0
- package/.agent-src/commands/roadmap.md +45 -17
- 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 +2 -2
- package/.agent-src/commands/tests.md +1 -1
- package/.agent-src/commands/threat-model.md +5 -4
- package/.agent-src/contexts/augment-infrastructure.md +1 -1
- package/.agent-src/contexts/authority/commit-mechanics.md +14 -1
- package/.agent-src/contexts/authority/destructive-mechanics.md +14 -1
- package/.agent-src/contexts/authority/scope-mechanics.md +5 -0
- package/.agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +76 -0
- package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +54 -19
- package/.agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +98 -0
- package/.agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +93 -0
- package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +128 -5
- package/.agent-src/contexts/execution/autonomy-mechanics.md +44 -0
- package/.agent-src/contexts/execution/roadmap-process-loop.md +125 -0
- package/.agent-src/contexts/model-recommendations.md +2 -2
- package/.agent-src/contexts/override-system.md +1 -1
- package/.agent-src/contexts/skills-and-commands.md +1 -1
- package/.agent-src/personas/product-owner.md +2 -2
- package/.agent-src/personas/qa.md +1 -1
- package/.agent-src/rules/agent-authority.md +5 -6
- package/.agent-src/rules/agent-docs.md +11 -53
- package/.agent-src/rules/analysis-skill-routing.md +10 -40
- package/.agent-src/rules/architecture.md +6 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +5 -0
- package/.agent-src/rules/artifact-engagement-recording.md +23 -59
- package/.agent-src/rules/ask-when-uncertain.md +24 -47
- package/.agent-src/rules/augment-portability.md +14 -62
- package/.agent-src/rules/augment-source-of-truth.md +10 -1
- package/.agent-src/rules/autonomous-execution.md +17 -98
- package/.agent-src/rules/capture-learnings.md +9 -80
- package/.agent-src/rules/cli-output-handling.md +12 -42
- package/.agent-src/rules/command-suggestion-policy.md +25 -73
- package/.agent-src/rules/commit-conventions.md +9 -58
- package/.agent-src/rules/commit-policy.md +16 -47
- package/.agent-src/rules/context-hygiene.md +5 -0
- package/.agent-src/rules/direct-answers.md +21 -50
- package/.agent-src/rules/docker-commands.md +11 -45
- package/.agent-src/rules/docs-sync.md +10 -56
- package/.agent-src/rules/downstream-changes.md +5 -0
- package/.agent-src/rules/e2e-testing.md +9 -44
- package/.agent-src/rules/guidelines.md +13 -75
- package/.agent-src/rules/improve-before-implement.md +11 -2
- package/.agent-src/rules/invite-challenge.md +71 -0
- package/.agent-src/rules/language-and-tone.md +41 -106
- package/.agent-src/rules/laravel-translations.md +11 -40
- package/.agent-src/rules/markdown-safe-codeblocks.md +4 -0
- package/.agent-src/rules/minimal-safe-diff.md +4 -0
- package/.agent-src/rules/missing-tool-handling.md +4 -0
- package/.agent-src/rules/model-recommendation.md +9 -61
- package/.agent-src/rules/no-attribution-footers.md +5 -0
- package/.agent-src/rules/no-cheap-questions.md +11 -27
- package/.agent-src/rules/no-council-references.md +76 -0
- package/.agent-src/rules/no-roadmap-references.md +7 -0
- package/.agent-src/rules/non-destructive-by-default.md +13 -43
- package/.agent-src/rules/onboarding-gate.md +9 -117
- package/.agent-src/rules/package-ci-checks.md +10 -37
- package/.agent-src/rules/php-coding.md +10 -55
- package/.agent-src/rules/preservation-guard.md +9 -0
- package/.agent-src/rules/review-routing-awareness.md +9 -97
- package/.agent-src/rules/reviewer-awareness.md +8 -83
- package/.agent-src/rules/roadmap-progress-sync.md +7 -170
- package/.agent-src/rules/role-mode-adherence.md +6 -2
- package/.agent-src/rules/rule-type-governance.md +8 -66
- package/.agent-src/rules/runtime-safety.md +5 -0
- package/.agent-src/rules/scope-control.md +17 -62
- package/.agent-src/rules/security-sensitive-stop.md +7 -1
- package/.agent-src/rules/size-enforcement.md +6 -1
- package/.agent-src/rules/skill-improvement-trigger.md +9 -49
- package/.agent-src/rules/skill-quality.md +7 -113
- package/.agent-src/rules/slash-command-routing-policy.md +11 -63
- package/.agent-src/rules/think-before-action.md +22 -87
- package/.agent-src/rules/token-efficiency.md +10 -74
- package/.agent-src/rules/token-optimizer-maintenance.md +68 -0
- package/.agent-src/rules/tool-safety.md +4 -0
- package/.agent-src/rules/ui-audit-gate.md +25 -61
- package/.agent-src/rules/upstream-proposal.md +9 -67
- package/.agent-src/rules/user-interaction.md +22 -108
- package/.agent-src/rules/verify-before-complete.md +1 -1
- package/.agent-src/skills/adversarial-review/SKILL.md +1 -0
- package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -1
- package/.agent-src/skills/ai-council/SKILL.md +197 -8
- package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
- package/.agent-src/skills/analysis-skill-router/SKILL.md +3 -3
- package/.agent-src/skills/artisan-commands/SKILL.md +2 -2
- package/.agent-src/skills/authz-review/SKILL.md +1 -1
- package/.agent-src/skills/aws-infrastructure/SKILL.md +5 -5
- package/.agent-src/skills/blast-radius-analyzer/SKILL.md +8 -8
- package/.agent-src/skills/bug-analyzer/SKILL.md +6 -5
- package/.agent-src/skills/code-refactoring/SKILL.md +4 -4
- package/.agent-src/skills/code-review/SKILL.md +2 -2
- package/.agent-src/skills/command-writing/SKILL.md +11 -0
- package/.agent-src/skills/composer-packages/SKILL.md +2 -2
- package/.agent-src/skills/context-authoring/SKILL.md +11 -0
- package/.agent-src/skills/context-document/SKILL.md +1 -1
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +23 -0
- package/.agent-src/skills/copilot-config/SKILL.md +1 -1
- package/.agent-src/skills/dependency-upgrade/SKILL.md +2 -2
- package/.agent-src/skills/devcontainer/SKILL.md +2 -2
- package/.agent-src/skills/developer-like-execution/SKILL.md +1 -1
- package/.agent-src/skills/docker/SKILL.md +1 -1
- package/.agent-src/skills/dto-creator/SKILL.md +1 -1
- package/.agent-src/skills/estimate-ticket/SKILL.md +2 -2
- package/.agent-src/skills/fe-design/SKILL.md +4 -4
- package/.agent-src/skills/feature-planning/SKILL.md +5 -5
- package/.agent-src/skills/funnel-analysis/SKILL.md +1 -1
- package/.agent-src/skills/laravel/SKILL.md +1 -1
- package/.agent-src/skills/laravel-notifications/SKILL.md +5 -5
- package/.agent-src/skills/laravel-pennant/SKILL.md +1 -1
- package/.agent-src/skills/laravel-pulse/SKILL.md +4 -4
- package/.agent-src/skills/laravel-reverb/SKILL.md +2 -2
- package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -1
- package/.agent-src/skills/migration-creator/SKILL.md +7 -7
- package/.agent-src/skills/multi-tenancy/SKILL.md +8 -8
- package/.agent-src/skills/performance-analysis/SKILL.md +3 -3
- package/.agent-src/skills/pest-testing/SKILL.md +6 -6
- package/.agent-src/skills/php-service/SKILL.md +2 -2
- package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
- package/.agent-src/skills/project-analysis-react/SKILL.md +1 -1
- package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
- package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
- package/.agent-src/skills/project-analyzer/SKILL.md +4 -4
- package/.agent-src/skills/prompt-optimizer/SKILL.md +108 -0
- package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
- package/.agent-src/skills/roadmap-management/SKILL.md +7 -7
- package/.agent-src/skills/rule-writing/SKILL.md +33 -0
- package/.agent-src/skills/sentry-integration/SKILL.md +1 -1
- package/.agent-src/skills/skill-writing/SKILL.md +14 -0
- package/.agent-src/skills/systematic-debugging/SKILL.md +22 -2
- package/.agent-src/skills/technical-specification/SKILL.md +58 -1
- package/.agent-src/skills/terraform/SKILL.md +2 -2
- package/.agent-src/skills/terragrunt/SKILL.md +8 -8
- package/.agent-src/skills/test-performance/SKILL.md +5 -5
- package/.agent-src/skills/threat-modeling/SKILL.md +3 -2
- package/.agent-src/skills/token-optimizer/SKILL.md +110 -0
- package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/.agent-src/templates/AGENTS.md +1 -1
- package/.agent-src/templates/agent-settings.md +35 -19
- package/.agent-src/templates/command.md +17 -1
- package/.agent-src/templates/contexts/tenant-boundaries.md +2 -2
- package/.agent-src/templates/contexts.md +1 -1
- package/.agent-src/templates/copilot-instructions.md +21 -0
- package/.agent-src/templates/copilot-review-instructions.md +76 -0
- package/.agent-src/templates/features.md +1 -1
- package/.agent-src/templates/roadmaps.md +10 -2
- package/.agent-src/templates/rule.md +129 -0
- package/.agent-src/templates/skill.md +17 -0
- package/.claude-plugin/marketplace.json +12 -2
- package/AGENTS.md +32 -5
- package/CHANGELOG.md +107 -3
- package/README.md +22 -21
- package/config/agent-settings.template.yml +66 -10
- package/config/gitignore-block.txt +7 -0
- package/docs/architecture.md +86 -5
- package/docs/catalog.md +16 -6
- package/docs/contracts/agent-memory-contract.md +1 -1
- package/docs/contracts/command-clusters.md +45 -1
- package/docs/contracts/context-paths.md +2 -1
- package/docs/contracts/file-ownership-matrix.json +354 -500
- package/docs/contracts/iron-law-overrides.txt +25 -0
- package/docs/contracts/kernel-membership.md +273 -0
- package/docs/contracts/load-context-schema.md +26 -11
- package/docs/contracts/pilot/agent-authority.md +24 -0
- package/docs/contracts/pilot/direct-answers.md +70 -0
- package/docs/contracts/pilot/language-and-tone.md +63 -0
- package/docs/contracts/rule-classification.md +170 -0
- package/docs/contracts/rule-router.md +153 -0
- package/docs/customization.md +17 -6
- package/docs/decisions/ADR-001-kernel-swap-deferred.md +109 -0
- package/docs/decisions/ADR-002-kernel-bucket-overrides.md +124 -0
- package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +126 -0
- package/docs/decisions/ADR-rule-kernel-and-router.md +122 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/naming.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +176 -0
- package/docs/guidelines/agent-infra/rule-type-governance.md +73 -0
- package/docs/guidelines/agent-infra/size-and-scope.md +13 -2
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +119 -0
- package/docs/guidelines/augment-portability-patterns.md +68 -0
- package/docs/guidelines/php/php-coding-patterns.md +62 -0
- package/package.json +1 -1
- package/scripts/_p43_bodies.py +235 -0
- package/scripts/_p43_compress.py +118 -0
- package/scripts/_p4_migrate.py +199 -0
- package/scripts/_phase2_shim_helper.py +1 -1
- package/scripts/_pilot_council_question.py +57 -0
- package/scripts/_pilot_measure.py +53 -0
- package/scripts/ai_council/session.py +107 -5
- package/scripts/build_linear_digest.py +3 -5
- package/scripts/check_always_budget.py +39 -6
- package/scripts/check_compressed_paths.py +213 -0
- package/scripts/check_compression.py +15 -0
- package/scripts/check_context_paths.py +1 -0
- package/scripts/check_council_layout.py +105 -0
- package/scripts/check_council_references.py +145 -0
- package/scripts/check_portability.py +2 -0
- package/scripts/check_references.py +2 -0
- package/scripts/check_token_optimizer_freshness.py +131 -0
- package/scripts/compile_router.py +148 -0
- package/scripts/compress.py +219 -11
- package/scripts/council_cli.py +132 -11
- package/scripts/council_prune.py +81 -0
- package/scripts/count_token_optimizer_usage.sh +54 -0
- package/scripts/install.sh +44 -2
- package/scripts/iron_law_sha.py +98 -0
- package/scripts/lint_load_context.py +35 -5
- package/scripts/measure_rule_budget.py +314 -0
- package/scripts/migrate_command_suggestions.py +2 -2
- package/scripts/prototype_lint_contradictions.py +150 -0
- package/scripts/schemas/command.schema.json +5 -0
- package/scripts/schemas/rule.schema.json +60 -6
- package/scripts/schemas/skill.schema.json +5 -0
- package/scripts/skill_linter.py +197 -7
- package/scripts/smoke_path_resolution.py +93 -0
- package/scripts/validate_frontmatter.py +41 -1
- package/.agent-src/commands/roadmap/execute.md +0 -109
- package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +0 -72
- package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +0 -79
- package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +0 -87
- package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +0 -62
- package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +0 -78
- package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +0 -85
- package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +0 -65
- package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +0 -78
- package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +0 -53
- /package/{docs → .agent-src/contexts}/contracts/artifact-engagement-flow.md +0 -0
- /package/{docs → .agent-src/contexts}/contracts/command-suggestion-flow.md +0 -0
package/scripts/council_cli.py
CHANGED
|
@@ -63,6 +63,7 @@ def build_members(
|
|
|
63
63
|
*,
|
|
64
64
|
invocation_mode: str | None = None,
|
|
65
65
|
model_overrides: dict[str, str] | None = None,
|
|
66
|
+
siblings_overrides: dict[str, list[str]] | None = None,
|
|
66
67
|
) -> list[ExternalAIClient]:
|
|
67
68
|
"""Construct enabled council members from settings.
|
|
68
69
|
|
|
@@ -73,6 +74,13 @@ def build_members(
|
|
|
73
74
|
`model_overrides` is a per-invocation `{member_name: model_id}`
|
|
74
75
|
map that wins over the per-member `model` in settings. Members not
|
|
75
76
|
listed fall back to the settings value, then the per-client default.
|
|
77
|
+
|
|
78
|
+
`siblings_overrides` is a per-invocation `{member_name: [model, ...]}`
|
|
79
|
+
map that fans the named provider out to multiple sibling models in
|
|
80
|
+
one run (e.g. claude-sonnet-4-5 + claude-opus-4-1). Each model
|
|
81
|
+
becomes its own billable member with independent cost tracking.
|
|
82
|
+
Mutually exclusive with `model_overrides` for the same provider;
|
|
83
|
+
requires `mode=api`; provider must be enabled in settings.
|
|
76
84
|
"""
|
|
77
85
|
ai = (settings.get("ai_council") or {}) if isinstance(settings, dict) else {}
|
|
78
86
|
if not ai.get("enabled"):
|
|
@@ -83,16 +91,34 @@ def build_members(
|
|
|
83
91
|
members_cfg = ai.get("members") or {}
|
|
84
92
|
global_mode = ai.get("mode")
|
|
85
93
|
overrides = model_overrides or {}
|
|
94
|
+
siblings = siblings_overrides or {}
|
|
86
95
|
unknown = set(overrides) - set(members_cfg)
|
|
87
96
|
if unknown:
|
|
88
97
|
raise CouncilDisabledError(
|
|
89
98
|
f"--model targets unknown member(s) {sorted(unknown)!r}; "
|
|
90
99
|
f"known members: {sorted(members_cfg)!r}."
|
|
91
100
|
)
|
|
101
|
+
unknown_sib = set(siblings) - set(members_cfg)
|
|
102
|
+
if unknown_sib:
|
|
103
|
+
raise CouncilDisabledError(
|
|
104
|
+
f"--siblings targets unknown member(s) {sorted(unknown_sib)!r}; "
|
|
105
|
+
f"known members: {sorted(members_cfg)!r}."
|
|
106
|
+
)
|
|
107
|
+
conflict = set(overrides) & set(siblings)
|
|
108
|
+
if conflict:
|
|
109
|
+
raise CouncilDisabledError(
|
|
110
|
+
f"--model and --siblings target the same member(s) {sorted(conflict)!r}; "
|
|
111
|
+
f"pick one per provider per invocation."
|
|
112
|
+
)
|
|
92
113
|
members: list[ExternalAIClient] = []
|
|
93
114
|
for name, cfg in members_cfg.items():
|
|
94
115
|
cfg = cfg or {}
|
|
95
116
|
if not cfg.get("enabled"):
|
|
117
|
+
if name in siblings:
|
|
118
|
+
raise CouncilDisabledError(
|
|
119
|
+
f"--siblings targets member {name!r} but it is not "
|
|
120
|
+
f"enabled in .agent-settings.yml (ai_council.members.{name}.enabled)."
|
|
121
|
+
)
|
|
96
122
|
continue
|
|
97
123
|
mode = resolve_mode(
|
|
98
124
|
name,
|
|
@@ -100,13 +126,17 @@ def build_members(
|
|
|
100
126
|
member_settings=cfg,
|
|
101
127
|
global_mode=global_mode,
|
|
102
128
|
)
|
|
129
|
+
if name in siblings:
|
|
130
|
+
if mode != "api":
|
|
131
|
+
raise CouncilDisabledError(
|
|
132
|
+
f"--siblings requires mode=api for member {name!r} (got {mode!r})."
|
|
133
|
+
)
|
|
134
|
+
for sib_model in siblings[name]:
|
|
135
|
+
members.append(_construct_api_member(name, sib_model))
|
|
136
|
+
continue
|
|
103
137
|
model = overrides.get(name) or cfg.get("model")
|
|
104
|
-
if mode == "api" and name
|
|
105
|
-
members.append(
|
|
106
|
-
api_key=load_anthropic_key()))
|
|
107
|
-
elif mode == "api" and name == "openai":
|
|
108
|
-
members.append(OpenAIClient(model=model or "gpt-4o",
|
|
109
|
-
api_key=load_openai_key()))
|
|
138
|
+
if mode == "api" and name in {"anthropic", "openai"}:
|
|
139
|
+
members.append(_construct_api_member(name, model))
|
|
110
140
|
elif mode == "manual":
|
|
111
141
|
members.append(ManualClient(name=name, model=model or "manual"))
|
|
112
142
|
elif mode == "playwright":
|
|
@@ -125,6 +155,19 @@ def build_members(
|
|
|
125
155
|
return members
|
|
126
156
|
|
|
127
157
|
|
|
158
|
+
def _construct_api_member(name: str, model: str | None) -> ExternalAIClient:
|
|
159
|
+
"""Build an api-mode client for a known provider name."""
|
|
160
|
+
if name == "anthropic":
|
|
161
|
+
return AnthropicClient(model=model or "claude-sonnet-4-5",
|
|
162
|
+
api_key=load_anthropic_key())
|
|
163
|
+
if name == "openai":
|
|
164
|
+
return OpenAIClient(model=model or "gpt-4o",
|
|
165
|
+
api_key=load_openai_key())
|
|
166
|
+
raise CouncilDisabledError(
|
|
167
|
+
f"member {name!r} has no api transport (known: anthropic, openai)."
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
128
171
|
def build_question(
|
|
129
172
|
*,
|
|
130
173
|
input_path: Path,
|
|
@@ -164,6 +207,29 @@ def format_estimate_table(
|
|
|
164
207
|
# ── subcommands ─────────────────────────────────────────────────────
|
|
165
208
|
|
|
166
209
|
|
|
210
|
+
def _resolve_rounds(args: argparse.Namespace, ai_cfg: dict[str, Any]) -> int:
|
|
211
|
+
"""Resolve effective debate round count from CLI args + settings.
|
|
212
|
+
|
|
213
|
+
Resolution chain (highest priority first):
|
|
214
|
+
1. ``--rounds N`` — explicit user override, any value.
|
|
215
|
+
2. ``--depth deep`` — uses ``ai_council.deep_min_rounds``,
|
|
216
|
+
floored at ``min_rounds`` so the deep tier is monotonic.
|
|
217
|
+
3. ``ai_council.min_rounds`` — default 2.
|
|
218
|
+
|
|
219
|
+
Sub-commands (rule/skill/command) declare ``council_depth: deep``
|
|
220
|
+
in their frontmatter; the host agent reads that and translates it
|
|
221
|
+
to ``--depth deep`` on the CLI invocation. The CLI itself stays
|
|
222
|
+
unaware of frontmatter — the contract is the flag.
|
|
223
|
+
"""
|
|
224
|
+
if getattr(args, "rounds", None) is not None:
|
|
225
|
+
return int(args.rounds)
|
|
226
|
+
min_rounds = int(ai_cfg.get("min_rounds", 2))
|
|
227
|
+
if getattr(args, "depth", "standard") == "deep":
|
|
228
|
+
deep = int(ai_cfg.get("deep_min_rounds", min_rounds))
|
|
229
|
+
return max(deep, min_rounds)
|
|
230
|
+
return min_rounds
|
|
231
|
+
|
|
232
|
+
|
|
167
233
|
def cmd_estimate(
|
|
168
234
|
args: argparse.Namespace,
|
|
169
235
|
*,
|
|
@@ -179,6 +245,7 @@ def cmd_estimate(
|
|
|
179
245
|
settings,
|
|
180
246
|
invocation_mode=args.mode_override,
|
|
181
247
|
model_overrides=_parse_model_overrides(getattr(args, "model", None)),
|
|
248
|
+
siblings_overrides=_parse_siblings_overrides(getattr(args, "siblings", None)),
|
|
182
249
|
)
|
|
183
250
|
if table is None:
|
|
184
251
|
table = load_prices()
|
|
@@ -239,6 +306,7 @@ def cmd_run(
|
|
|
239
306
|
settings,
|
|
240
307
|
invocation_mode=args.mode_override,
|
|
241
308
|
model_overrides=_parse_model_overrides(getattr(args, "model", None)),
|
|
309
|
+
siblings_overrides=_parse_siblings_overrides(getattr(args, "siblings", None)),
|
|
242
310
|
)
|
|
243
311
|
if table is None:
|
|
244
312
|
table = load_prices()
|
|
@@ -263,17 +331,19 @@ def cmd_run(
|
|
|
263
331
|
)
|
|
264
332
|
return 0
|
|
265
333
|
|
|
266
|
-
|
|
334
|
+
ai_cfg = settings.get("ai_council") or {}
|
|
335
|
+
cost_cfg = ai_cfg.get("cost_budget") or {}
|
|
267
336
|
budget = CostBudget(
|
|
268
337
|
max_input_tokens=int(cost_cfg.get("max_input_tokens", 50_000)),
|
|
269
338
|
max_output_tokens=int(cost_cfg.get("max_output_tokens", 20_000)),
|
|
270
339
|
max_calls=int(cost_cfg.get("max_calls", 10)),
|
|
271
340
|
max_total_usd=float(cost_cfg.get("max_total_usd", 0.0) or 0.0),
|
|
272
341
|
)
|
|
342
|
+
rounds = _resolve_rounds(args, ai_cfg)
|
|
273
343
|
responses = consult(
|
|
274
344
|
members, question, budget,
|
|
275
345
|
table=table, project=project,
|
|
276
|
-
original_ask=args.original_ask, rounds=
|
|
346
|
+
original_ask=args.original_ask, rounds=rounds,
|
|
277
347
|
)
|
|
278
348
|
estimated_total = sum(e.total_usd for e in estimates)
|
|
279
349
|
actual_total = 0.0
|
|
@@ -288,7 +358,7 @@ def cmd_run(
|
|
|
288
358
|
"artefact": artefact,
|
|
289
359
|
"original_ask": args.original_ask,
|
|
290
360
|
"members": [f"{m.name}/{m.model}" for m in members],
|
|
291
|
-
"rounds":
|
|
361
|
+
"rounds": rounds,
|
|
292
362
|
"cost_usd_estimated": round(estimated_total, 6),
|
|
293
363
|
"cost_usd_actual": round(actual_total, 6),
|
|
294
364
|
"responses": _serialise_responses(responses),
|
|
@@ -337,6 +407,38 @@ def _parse_model_overrides(items: list[str] | None) -> dict[str, str]:
|
|
|
337
407
|
return out
|
|
338
408
|
|
|
339
409
|
|
|
410
|
+
def _parse_siblings_overrides(items: list[str] | None) -> dict[str, list[str]]:
|
|
411
|
+
"""Parse repeated `--siblings name=model1,model2[,...]` flags.
|
|
412
|
+
|
|
413
|
+
Requires ≥ 2 distinct, non-empty models per provider — sibling
|
|
414
|
+
mode without diversity has no purpose. Repeating the same provider
|
|
415
|
+
flag is rejected as ambiguous.
|
|
416
|
+
"""
|
|
417
|
+
out: dict[str, list[str]] = {}
|
|
418
|
+
for raw in items or []:
|
|
419
|
+
if "=" not in raw:
|
|
420
|
+
raise argparse.ArgumentTypeError(
|
|
421
|
+
f"--siblings expects '<member>=<model1>,<model2>[,...]', got {raw!r}."
|
|
422
|
+
)
|
|
423
|
+
name, models_csv = raw.split("=", 1)
|
|
424
|
+
name = name.strip()
|
|
425
|
+
models = [m.strip() for m in models_csv.split(",") if m.strip()]
|
|
426
|
+
if not name or not models:
|
|
427
|
+
raise argparse.ArgumentTypeError(
|
|
428
|
+
f"--siblings member and model list must both be non-empty: {raw!r}."
|
|
429
|
+
)
|
|
430
|
+
if len(set(models)) < 2:
|
|
431
|
+
raise argparse.ArgumentTypeError(
|
|
432
|
+
f"--siblings requires ≥ 2 distinct models for {name!r}, got {models!r}."
|
|
433
|
+
)
|
|
434
|
+
if name in out:
|
|
435
|
+
raise argparse.ArgumentTypeError(
|
|
436
|
+
f"--siblings repeated for member {name!r}; combine into one flag."
|
|
437
|
+
)
|
|
438
|
+
out[name] = models
|
|
439
|
+
return out
|
|
440
|
+
|
|
441
|
+
|
|
340
442
|
def _add_common_input_args(p: argparse.ArgumentParser) -> None:
|
|
341
443
|
p.add_argument("question", type=str,
|
|
342
444
|
help="Path to the question file (text or roadmap).")
|
|
@@ -354,6 +456,16 @@ def _add_common_input_args(p: argparse.ArgumentParser) -> None:
|
|
|
354
456
|
"Wins over `ai_council.members.<name>.model` in "
|
|
355
457
|
".agent-settings.yml; the settings file is not "
|
|
356
458
|
"modified.")
|
|
459
|
+
p.add_argument("--siblings", action="append", default=None, dest="siblings",
|
|
460
|
+
metavar="MEMBER=MODEL1,MODEL2[,...]",
|
|
461
|
+
help="Fan one provider out to ≥ 2 sibling models in a "
|
|
462
|
+
"single run, e.g. --siblings anthropic=claude-sonnet-4-5,"
|
|
463
|
+
"claude-opus-4-1. Each model becomes its own billable "
|
|
464
|
+
"member with independent cost tracking. Mutually "
|
|
465
|
+
"exclusive with --model for the same provider; "
|
|
466
|
+
"requires the provider to be enabled with mode=api. "
|
|
467
|
+
"Single-provider degraded-run strategy per ai-council "
|
|
468
|
+
"skill.")
|
|
357
469
|
p.add_argument("--original-ask", default="",
|
|
358
470
|
help="The user's framing sentence (flows into handoff).")
|
|
359
471
|
|
|
@@ -374,8 +486,17 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
374
486
|
help="Path to write the responses JSON.")
|
|
375
487
|
p_run.add_argument("--confirm", action="store_true",
|
|
376
488
|
help="Required to actually invoke the council.")
|
|
377
|
-
p_run.add_argument("--rounds", type=int, default=
|
|
378
|
-
help="Number of debate rounds (1-3)."
|
|
489
|
+
p_run.add_argument("--rounds", type=int, default=None,
|
|
490
|
+
help="Number of debate rounds (1-3). Explicit override; "
|
|
491
|
+
"wins over --depth. Defaults to ai_council.min_rounds "
|
|
492
|
+
"in .agent-settings.yml (or 2 if unset).")
|
|
493
|
+
p_run.add_argument("--depth", choices=["standard", "deep"], default="standard",
|
|
494
|
+
help="Reasoning-depth tier. 'deep' floors rounds at "
|
|
495
|
+
"ai_council.deep_min_rounds (max'd with min_rounds) "
|
|
496
|
+
"for architecture, refactoring, or bug-diagnosis "
|
|
497
|
+
"artefacts. Set by the host agent when the consuming "
|
|
498
|
+
"rule/skill/command declares council_depth: deep. "
|
|
499
|
+
"Overridden by explicit --rounds.")
|
|
379
500
|
|
|
380
501
|
p_ren = sub.add_parser("render", help="Re-render a saved responses JSON.")
|
|
381
502
|
p_ren.add_argument("responses",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Manual pruner for council artefacts.
|
|
3
|
+
|
|
4
|
+
Deletes council files older than `ai_council.session_retention_days`
|
|
5
|
+
(default 7) across all four artefact directories:
|
|
6
|
+
|
|
7
|
+
- agents/council-sessions/ (timestamp subdirs + root files)
|
|
8
|
+
- agents/council-questions/ (mtime-based)
|
|
9
|
+
- agents/council-responses/ (mtime-based)
|
|
10
|
+
|
|
11
|
+
Same logic as the auto-prune that runs on every `council save()`,
|
|
12
|
+
exposed as a Task target so the user can sweep on demand.
|
|
13
|
+
|
|
14
|
+
Invocation (from project root):
|
|
15
|
+
python3 scripts/council_prune.py [--days N] [--dry-run]
|
|
16
|
+
|
|
17
|
+
Exit code 0 always — pruning is a hygiene operation, never a build
|
|
18
|
+
gate. Disk failures are logged to stderr by the underlying pruner.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
# Bootstrap import path so `python3 scripts/council_prune.py` works
|
|
28
|
+
# from the project root without an editable install.
|
|
29
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
30
|
+
|
|
31
|
+
from scripts.ai_council.session import ( # noqa: E402
|
|
32
|
+
_load_retention_days,
|
|
33
|
+
prune_all_council_artifacts,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> int:
|
|
38
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--days",
|
|
41
|
+
type=int,
|
|
42
|
+
default=None,
|
|
43
|
+
help="Override retention_days (default: from .agent-settings.yml)",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--dry-run",
|
|
47
|
+
action="store_true",
|
|
48
|
+
help="List what would be deleted without removing anything.",
|
|
49
|
+
)
|
|
50
|
+
args = parser.parse_args()
|
|
51
|
+
|
|
52
|
+
days = args.days if args.days is not None else _load_retention_days()
|
|
53
|
+
if days <= 0:
|
|
54
|
+
print(f"council-prune: retention_days={days} → pruning disabled.")
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
if args.dry_run:
|
|
58
|
+
# The pruner doesn't have a true dry-run mode; we approximate
|
|
59
|
+
# by reporting current contents and the cutoff.
|
|
60
|
+
print(f"council-prune: dry-run, cutoff = retention_days={days}")
|
|
61
|
+
print("council-prune: actual deletion requires omitting --dry-run")
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
print(f"council-prune: retention_days={days}")
|
|
65
|
+
result = prune_all_council_artifacts(retention_days=days)
|
|
66
|
+
total = 0
|
|
67
|
+
for label, removed in result.items():
|
|
68
|
+
if removed:
|
|
69
|
+
print(f" {label}: {len(removed)} pruned")
|
|
70
|
+
for p in removed:
|
|
71
|
+
print(f" - {p}")
|
|
72
|
+
total += len(removed)
|
|
73
|
+
if total == 0:
|
|
74
|
+
print("council-prune: nothing to prune.")
|
|
75
|
+
else:
|
|
76
|
+
print(f"council-prune: pruned {total} entries total.")
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Token-Optimizer telemetry counter.
|
|
3
|
+
#
|
|
4
|
+
# Per `road-to-token-optimization.md` P1.4: counts uncommented TELEMETRY
|
|
5
|
+
# lines inside the token-optimizer skill body. Each consult bumps a line.
|
|
6
|
+
# Decision rule: <5 consults / 2 weeks → P3.1 sunset audit fires.
|
|
7
|
+
#
|
|
8
|
+
# Output: 7-day count, 30-day count, total. Stdout only, no side effects.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
13
|
+
SKILL="$REPO_ROOT/.agent-src.uncompressed/skills/token-optimizer/SKILL.md"
|
|
14
|
+
|
|
15
|
+
if [[ ! -f "$SKILL" ]]; then
|
|
16
|
+
echo "ERROR: $SKILL not found" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Active TELEMETRY lines = those NOT inside an HTML comment.
|
|
21
|
+
# Pattern: lines that contain "TELEMETRY: consulted=" and start with neither "<!--" nor whitespace.
|
|
22
|
+
total=$(grep -cE '^TELEMETRY: consulted=' "$SKILL" || true)
|
|
23
|
+
|
|
24
|
+
today_epoch=$(date -u +%s)
|
|
25
|
+
seven_days_ago=$(( today_epoch - 7 * 86400 ))
|
|
26
|
+
thirty_days_ago=$(( today_epoch - 30 * 86400 ))
|
|
27
|
+
|
|
28
|
+
count_since() {
|
|
29
|
+
local cutoff="$1"
|
|
30
|
+
local n=0
|
|
31
|
+
while IFS= read -r line; do
|
|
32
|
+
# Extract the ISO timestamp after "consulted="
|
|
33
|
+
ts=$(echo "$line" | sed -nE 's/^TELEMETRY: consulted=\[?([0-9TZ:+\-]+)\]?.*/\1/p')
|
|
34
|
+
[[ -z "$ts" ]] && continue
|
|
35
|
+
# Convert ISO → epoch (BSD `date -j` on macOS, GNU `date -d` on Linux)
|
|
36
|
+
epoch=$(date -j -u -f "%Y-%m-%dT%H:%M:%SZ" "$ts" +%s 2>/dev/null \
|
|
37
|
+
|| date -u -d "$ts" +%s 2>/dev/null \
|
|
38
|
+
|| echo 0)
|
|
39
|
+
if [[ "$epoch" -ge "$cutoff" ]]; then
|
|
40
|
+
n=$(( n + 1 ))
|
|
41
|
+
fi
|
|
42
|
+
done < <(grep -E '^TELEMETRY: consulted=' "$SKILL" || true)
|
|
43
|
+
echo "$n"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
count_7d=$(count_since "$seven_days_ago")
|
|
47
|
+
count_30d=$(count_since "$thirty_days_ago")
|
|
48
|
+
|
|
49
|
+
echo "token-optimizer consults:"
|
|
50
|
+
echo " last 7 days: $count_7d"
|
|
51
|
+
echo " last 30 days: $count_30d"
|
|
52
|
+
echo " total active: $total"
|
|
53
|
+
echo ""
|
|
54
|
+
echo "Decision rule: <5 consults / 2 weeks sustained → P3.1 sunset audit."
|
package/scripts/install.sh
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
# install.sh — Agent-config payload sync (one of two installer stages).
|
|
3
3
|
#
|
|
4
4
|
# Reads from vendor's .agent-src/ (fallback: .augment/ for pre-2.0 packages) and
|
|
5
|
-
# writes the target project's .augment/ tree: copies rules, symlinks everything
|
|
5
|
+
# writes the target project's .augment/ tree: copies rules, symlinks everything
|
|
6
|
+
# else. When augment.rules_use_symlinks: true is set in the target's
|
|
7
|
+
# .agent-settings.yml, rules are symlinked instead of copied.
|
|
6
8
|
# Creates tool-specific directories for Claude Code, Cursor, Cline, Windsurf, Gemini.
|
|
7
9
|
#
|
|
8
10
|
# Does NOT render .agent-settings.yml or bridge JSONs — that is the job of
|
|
@@ -35,6 +37,9 @@ DRY_RUN=false
|
|
|
35
37
|
VERBOSE=false
|
|
36
38
|
QUIET=false
|
|
37
39
|
SKIP_GITIGNORE=false
|
|
40
|
+
# Resolved from <TARGET>/.agent-settings.yml in resolve_settings(); when
|
|
41
|
+
# true, .augment/rules/ files are symlinked instead of copied.
|
|
42
|
+
USE_RULES_SYMLINKS=false
|
|
38
43
|
|
|
39
44
|
# --- Logging ---
|
|
40
45
|
log_info() { $QUIET || echo " ✅ $*"; }
|
|
@@ -114,6 +119,30 @@ EOF
|
|
|
114
119
|
|
|
115
120
|
# --- Utility functions ---
|
|
116
121
|
|
|
122
|
+
# Read augment.rules_use_symlinks from <TARGET>/.agent-settings.yml.
|
|
123
|
+
# Sets USE_RULES_SYMLINKS=true|false. Missing file or absent key → false.
|
|
124
|
+
# Minimal scoped parser; avoids a hard yq/python dependency.
|
|
125
|
+
resolve_settings() {
|
|
126
|
+
USE_RULES_SYMLINKS=false
|
|
127
|
+
local settings_file="$TARGET_DIR/.agent-settings.yml"
|
|
128
|
+
[[ -f "$settings_file" ]] || return 0
|
|
129
|
+
local val
|
|
130
|
+
val=$(awk '
|
|
131
|
+
/^[^[:space:]#]/ { in_block = ($0 ~ /^augment:[[:space:]]*$/) }
|
|
132
|
+
in_block && /^[[:space:]]+rules_use_symlinks[[:space:]]*:/ {
|
|
133
|
+
line = $0
|
|
134
|
+
sub(/^[[:space:]]*rules_use_symlinks[[:space:]]*:[[:space:]]*/, "", line)
|
|
135
|
+
sub(/[[:space:]]*#.*$/, "", line)
|
|
136
|
+
gsub(/[[:space:]]/, "", line)
|
|
137
|
+
print tolower(line)
|
|
138
|
+
exit
|
|
139
|
+
}
|
|
140
|
+
' "$settings_file" 2>/dev/null || true)
|
|
141
|
+
case "$val" in
|
|
142
|
+
true|yes|on|1) USE_RULES_SYMLINKS=true ;;
|
|
143
|
+
esac
|
|
144
|
+
}
|
|
145
|
+
|
|
117
146
|
# Check if a relative path should be copied (true=copy) or symlinked (false=symlink)
|
|
118
147
|
should_copy() {
|
|
119
148
|
local rel_path="$1"
|
|
@@ -127,6 +156,10 @@ should_copy() {
|
|
|
127
156
|
# Check against COPY_DIRS
|
|
128
157
|
for dir in $COPY_DIRS; do
|
|
129
158
|
if [[ "$first_segment" == "$dir" ]]; then
|
|
159
|
+
# Honor augment.rules_use_symlinks toggle for the rules dir.
|
|
160
|
+
if [[ "$dir" == "rules" ]] && $USE_RULES_SYMLINKS; then
|
|
161
|
+
return 1
|
|
162
|
+
fi
|
|
130
163
|
return 0
|
|
131
164
|
fi
|
|
132
165
|
done
|
|
@@ -669,9 +702,17 @@ main() {
|
|
|
669
702
|
# 0. Migrate legacy infra files (root → agents/) before any content sync.
|
|
670
703
|
migrate_legacy_root_infra "$TARGET_DIR"
|
|
671
704
|
|
|
705
|
+
# 0b. Resolve settings (e.g. augment.rules_use_symlinks). On first
|
|
706
|
+
# install the file does not exist yet → defaults preserved.
|
|
707
|
+
resolve_settings
|
|
708
|
+
|
|
672
709
|
# 1. Hybrid sync payload → target/.augment/
|
|
673
710
|
sync_hybrid "$SOURCE_PAYLOAD" "$TARGET_DIR/.augment"
|
|
674
|
-
|
|
711
|
+
if $USE_RULES_SYMLINKS; then
|
|
712
|
+
log_info "Synced .augment/ (rules symlinked, rest symlinked)"
|
|
713
|
+
else
|
|
714
|
+
log_info "Synced .augment/ (rules copied, rest symlinked)"
|
|
715
|
+
fi
|
|
675
716
|
|
|
676
717
|
# 2. Copy standalone files from templates if missing on the target.
|
|
677
718
|
# We copy from templates/ (generic placeholders), NOT from the package's
|
|
@@ -680,6 +721,7 @@ main() {
|
|
|
680
721
|
# into consumer projects.
|
|
681
722
|
copy_if_missing "$SOURCE_PAYLOAD/templates/AGENTS.md" "$TARGET_DIR/AGENTS.md"
|
|
682
723
|
copy_if_missing "$SOURCE_PAYLOAD/templates/copilot-instructions.md" "$TARGET_DIR/.github/copilot-instructions.md"
|
|
724
|
+
copy_if_missing "$SOURCE_PAYLOAD/templates/copilot-review-instructions.md" "$TARGET_DIR/.github/copilot-review-instructions.md"
|
|
683
725
|
|
|
684
726
|
# 3. Create tool-specific symlinks
|
|
685
727
|
create_tool_symlinks "$TARGET_DIR"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""SHA-256 of every triple-fence block in a rule file (Iron Law preservation).
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/iron_law_sha.py <rule-id> [<rule-id> ...]
|
|
6
|
+
python3 scripts/iron_law_sha.py --all-kernel
|
|
7
|
+
python3 scripts/iron_law_sha.py --diff <rule-id> --against <baseline-sha>
|
|
8
|
+
|
|
9
|
+
The Iron-Law block is delimited by triple-backtick fences. Every line
|
|
10
|
+
inside any fence in the file is concatenated, whitespace-normalised
|
|
11
|
+
(runs of spaces collapsed; leading / trailing whitespace stripped per
|
|
12
|
+
line), case-folded, then SHA-256-hashed. Empty fences hash to
|
|
13
|
+
SHA-256(''), which is `e3b0c442…` (the well-known empty-string hash).
|
|
14
|
+
|
|
15
|
+
Acceptance per `road-to-kernel-and-router.md` P2.2: re-runnable,
|
|
16
|
+
deterministic, stdlib-only, no network. Compression of a kernel rule
|
|
17
|
+
must preserve this SHA (or surface a deliberate ADR-tracked diff).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import hashlib
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
29
|
+
RULES_DIR = REPO_ROOT / ".agent-src.uncompressed" / "rules"
|
|
30
|
+
|
|
31
|
+
# Locked kernel set — kept in sync with measure_rule_budget.KERNEL_RULES.
|
|
32
|
+
KERNEL_RULES = (
|
|
33
|
+
"agent-authority",
|
|
34
|
+
"ask-when-uncertain",
|
|
35
|
+
"commit-policy",
|
|
36
|
+
"direct-answers",
|
|
37
|
+
"language-and-tone",
|
|
38
|
+
"no-cheap-questions",
|
|
39
|
+
"non-destructive-by-default",
|
|
40
|
+
"scope-control",
|
|
41
|
+
"verify-before-complete",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
_FENCE_RE = re.compile(r"```(?:[^\n]*\n)([\s\S]*?)```")
|
|
45
|
+
_WS_RE = re.compile(r"\s+")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def iron_law_sha(text: str) -> str:
|
|
49
|
+
"""SHA-256 of all triple-fence content, whitespace-collapsed, upper-cased.
|
|
50
|
+
|
|
51
|
+
Algorithm matches `scripts/_pilot_measure.py` exactly so the SHAs
|
|
52
|
+
recorded in `docs/contracts/kernel-membership.md` § 2 stay
|
|
53
|
+
reproducible across pre / post compression.
|
|
54
|
+
"""
|
|
55
|
+
blocks = _FENCE_RE.findall(text)
|
|
56
|
+
norm = "".join(_WS_RE.sub(" ", b).strip().upper() for b in blocks)
|
|
57
|
+
return hashlib.sha256(norm.encode("utf-8")).hexdigest()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def rule_sha(rule_id: str) -> str:
|
|
61
|
+
path = RULES_DIR / f"{rule_id}.md"
|
|
62
|
+
if not path.exists():
|
|
63
|
+
raise FileNotFoundError(path)
|
|
64
|
+
return iron_law_sha(path.read_text(encoding="utf-8"))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main(argv: list[str] | None = None) -> int:
|
|
68
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
69
|
+
parser.add_argument("rules", nargs="*", help="rule ids (omit if --all-kernel)")
|
|
70
|
+
parser.add_argument("--all-kernel", action="store_true", help="hash all 9 kernel rules")
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--diff", metavar="RULE", help="hash one rule and compare to --against"
|
|
73
|
+
)
|
|
74
|
+
parser.add_argument("--against", metavar="SHA", help="expected SHA (for --diff)")
|
|
75
|
+
args = parser.parse_args(argv)
|
|
76
|
+
|
|
77
|
+
if args.diff:
|
|
78
|
+
if not args.against:
|
|
79
|
+
parser.error("--diff requires --against")
|
|
80
|
+
actual = rule_sha(args.diff)
|
|
81
|
+
match = actual == args.against
|
|
82
|
+
symbol = "✅" if match else "❌"
|
|
83
|
+
print(f"{symbol} {args.diff}: {actual} (expected {args.against})")
|
|
84
|
+
return 0 if match else 1
|
|
85
|
+
|
|
86
|
+
targets = list(KERNEL_RULES) if args.all_kernel else args.rules
|
|
87
|
+
if not targets:
|
|
88
|
+
parser.error("provide rule ids, or use --all-kernel")
|
|
89
|
+
|
|
90
|
+
width = max(len(t) for t in targets)
|
|
91
|
+
for rid in targets:
|
|
92
|
+
sha = rule_sha(rid)
|
|
93
|
+
print(f"{rid:<{width}} {sha}")
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
sys.exit(main())
|
|
@@ -28,14 +28,37 @@ SCAN_DIRS = [
|
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
ALLOWED_PREFIXES = (
|
|
31
|
-
"
|
|
32
|
-
".agent-src/contexts/",
|
|
33
|
-
"agents/contexts/",
|
|
31
|
+
"contexts/", # logical name (canonical — P1.1 / P5.3)
|
|
32
|
+
".agent-src/contexts/", # projected (defensive — only seen in compressed inputs)
|
|
33
|
+
"agents/contexts/", # project-local
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
+
# `.agent-src.uncompressed/contexts/` was the legacy fully-qualified form
|
|
37
|
+
# (pre road-to-path-fixes.md P5.3). It is now a hard error: P2.1 migrated
|
|
38
|
+
# all in-tree rules to logical names, the rewriter resolves them at
|
|
39
|
+
# compress time, and the schema regex in `scripts/schemas/rule.schema.json`
|
|
40
|
+
# rejects the prefix at validate-schema time. Keeping a separate runtime
|
|
41
|
+
# diagnostic so the failure points authors at the canonical
|
|
42
|
+
# `contexts/<area>/<file>.md` form rather than a generic schema mismatch.
|
|
43
|
+
LEGACY_PREFIX = ".agent-src.uncompressed/contexts/"
|
|
44
|
+
|
|
45
|
+
# Logical names resolve against the source root.
|
|
46
|
+
SOURCE_ROOT = ROOT / ".agent-src.uncompressed"
|
|
47
|
+
|
|
36
48
|
PUBLIC_RULE_PREFIX = ".agent-src.uncompressed/rules/"
|
|
37
49
|
PROJECT_LOCAL_PREFIX = "agents/contexts/"
|
|
38
50
|
|
|
51
|
+
|
|
52
|
+
def resolve_entry(entry: str) -> Path:
|
|
53
|
+
"""Resolve a `load_context:` entry to an absolute path on disk.
|
|
54
|
+
|
|
55
|
+
Logical names (`contexts/...`) live under `.agent-src.uncompressed/`;
|
|
56
|
+
fully-qualified entries are repo-root-relative.
|
|
57
|
+
"""
|
|
58
|
+
if entry.startswith("contexts/"):
|
|
59
|
+
return SOURCE_ROOT / entry
|
|
60
|
+
return ROOT / entry
|
|
61
|
+
|
|
39
62
|
HARD_FLOOR_RULES = {"non-destructive-by-default", "security-sensitive-stop"}
|
|
40
63
|
|
|
41
64
|
CAP_ALWAYS = 2_500
|
|
@@ -126,10 +149,17 @@ def main() -> int:
|
|
|
126
149
|
if not isinstance(entry, str) or not entry.endswith(".md"):
|
|
127
150
|
errors.append(f"{rel(f)}: entry not str ending in .md → {entry!r}")
|
|
128
151
|
continue
|
|
152
|
+
if entry.startswith(LEGACY_PREFIX):
|
|
153
|
+
logical = entry[len(".agent-src.uncompressed/"):]
|
|
154
|
+
errors.append(
|
|
155
|
+
f"{rel(f)}: legacy `.agent-src.uncompressed/` prefix in load_context → {entry} "
|
|
156
|
+
f"— use logical name `{logical}` instead (road-to-path-fixes.md P5.3)"
|
|
157
|
+
)
|
|
158
|
+
continue
|
|
129
159
|
if not entry.startswith(ALLOWED_PREFIXES):
|
|
130
160
|
errors.append(f"{rel(f)}: disallowed root → {entry}")
|
|
131
161
|
continue
|
|
132
|
-
target =
|
|
162
|
+
target = resolve_entry(entry)
|
|
133
163
|
if not target.exists():
|
|
134
164
|
errors.append(f"{rel(f)}: target missing → {entry}")
|
|
135
165
|
continue
|
|
@@ -140,7 +170,7 @@ def main() -> int:
|
|
|
140
170
|
cap = cap_for(f, fm)
|
|
141
171
|
total = len(f.read_text(encoding="utf-8"))
|
|
142
172
|
for entry in eager:
|
|
143
|
-
tgt =
|
|
173
|
+
tgt = resolve_entry(entry)
|
|
144
174
|
if tgt.exists():
|
|
145
175
|
total += len(tgt.read_text(encoding="utf-8"))
|
|
146
176
|
if total > cap:
|