@event4u/agent-config 1.19.0 → 1.21.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/agent-handoff.md +14 -10
- package/.agent-src/commands/agents.md +1 -1
- package/.agent-src/commands/bug-fix.md +1 -1
- package/.agent-src/commands/bug-investigate.md +2 -2
- package/.agent-src/commands/chat-history/import.md +166 -0
- package/.agent-src/commands/chat-history/learn.md +178 -0
- package/.agent-src/commands/chat-history/show.md +17 -18
- package/.agent-src/commands/chat-history.md +26 -25
- 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 +21 -12
- package/.agent-src/commands/council.md +1 -1
- package/.agent-src/commands/create-pr.md +28 -8
- 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/jira-ticket.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.md +1 -1
- package/.agent-src/commands/set-cost-profile.md +3 -3
- package/.agent-src/commands/sync-agent-settings.md +2 -2
- package/.agent-src/commands/sync-gitignore.md +1 -1
- package/.agent-src/commands/tests/create.md +2 -2
- package/.agent-src/commands/tests.md +1 -1
- package/.agent-src/commands/threat-model.md +4 -4
- 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/skill-quality-mechanics.md +76 -0
- package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +4 -4
- 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 +125 -9
- package/.agent-src/contexts/execution/autonomy-mechanics.md +44 -0
- package/.agent-src/contexts/model-recommendations.md +2 -2
- package/.agent-src/contexts/override-system.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 -42
- 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 +10 -2
- package/.agent-src/rules/language-and-tone.md +35 -69
- 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 +53 -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 +8 -1
- 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 -64
- 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 +25 -95
- package/.agent-src/rules/verify-before-complete.md +1 -1
- package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -1
- package/.agent-src/skills/ai-council/SKILL.md +69 -5
- 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 +5 -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/dcf-modeling/SKILL.md +89 -0
- 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 +100 -0
- 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/md-language-check/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/okr-tree-modeling/SKILL.md +93 -0
- 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/rice-prioritization/SKILL.md +100 -0
- 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/subagent-orchestration/SKILL.md +34 -2
- 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 +2 -2
- package/.agent-src/skills/token-optimizer/SKILL.md +110 -0
- package/.agent-src/skills/unit-economics-modeling/SKILL.md +104 -0
- package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -0
- package/.agent-src/templates/AGENTS.md +1 -1
- package/.agent-src/templates/agent-settings.md +25 -41
- 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/rule.md +127 -0
- package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +7 -5
- package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +0 -4
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +0 -4
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +7 -51
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +1 -2
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +1 -2
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +2 -3
- package/.agent-src/templates/skill.md +30 -1
- package/.claude-plugin/marketplace.json +11 -4
- package/AGENTS.md +71 -3
- package/CHANGELOG.md +180 -3
- package/README.md +24 -23
- package/config/agent-settings.template.yml +63 -23
- package/config/gitignore-block.txt +11 -4
- package/docs/architecture.md +84 -3
- package/docs/catalog.md +23 -11
- package/docs/contracts/adr-chat-history-split.md +10 -1
- package/docs/contracts/agent-memory-contract.md +1 -1
- package/docs/contracts/command-clusters.md +1 -1
- package/docs/contracts/context-paths.md +2 -1
- package/docs/contracts/cross-wing-handoff.md +133 -0
- package/docs/contracts/file-ownership-matrix.json +678 -609
- package/docs/contracts/hook-architecture-v1.md +8 -1
- 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/memory-visibility-v1.md +8 -24
- 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 +18 -7
- 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-rule-kernel-and-router.md +122 -0
- package/docs/getting-started.md +19 -27
- package/docs/guidelines/agent-infra/ask-when-uncertain-demos.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/docs/hook-payload-capture.md +221 -0
- package/docs/migrations/commands-1.15.0.md +17 -12
- package/docs/skills-catalog.md +5 -4
- package/llms.txt +4 -3
- 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/_pilot_council_question.py +57 -0
- package/scripts/_pilot_measure.py +53 -0
- package/scripts/agent-config +1 -1
- package/scripts/ai_council/_default_prices.py +4 -4
- package/scripts/ai_council/clients.py +1 -1
- package/scripts/ai_council/modes.py +3 -4
- package/scripts/ai_council/pricing.py +10 -9
- package/scripts/ai_council/session.py +107 -5
- package/scripts/build_linear_digest.py +3 -5
- package/scripts/build_rule_trigger_matrix.py +1 -9
- package/scripts/chat_history.py +952 -596
- 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 +14 -2
- 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 +63 -9
- package/scripts/council_prune.py +81 -0
- package/scripts/count_token_optimizer_usage.sh +54 -0
- package/scripts/hook_manifest.yaml +33 -0
- package/scripts/hooks/augment-chat-history.sh +10 -0
- package/scripts/hooks/cowork-dispatcher.sh +98 -0
- package/scripts/hooks/dispatch_hook.py +35 -0
- package/scripts/hooks_status.py +12 -1
- package/scripts/install-hooks.sh +2 -2
- package/scripts/install.sh +81 -2
- package/scripts/iron_law_sha.py +98 -0
- package/scripts/lint_handoffs.py +214 -0
- package/scripts/lint_hook_manifest.py +2 -1
- package/scripts/lint_load_context.py +35 -5
- package/scripts/measure_rule_budget.py +314 -0
- package/scripts/prototype_lint_contradictions.py +150 -0
- package/scripts/redact_hook_capture.py +148 -0
- package/scripts/schemas/rule.schema.json +55 -6
- package/scripts/schemas/skill.schema.json +5 -0
- package/scripts/skill_linter.py +359 -7
- package/scripts/smoke_path_resolution.py +93 -0
- package/scripts/update_prices.py +3 -3
- package/scripts/validate_frontmatter.py +41 -1
- package/.agent-src/commands/chat-history/checkpoint.md +0 -126
- package/.agent-src/commands/chat-history/clear.md +0 -103
- package/.agent-src/commands/chat-history/resume.md +0 -183
- 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/.agent-src/rules/chat-history-cadence.md +0 -143
- package/.agent-src/rules/chat-history-ownership.md +0 -124
- package/.agent-src/rules/chat-history-visibility.md +0 -97
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +0 -50
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +0 -49
- package/scripts/check_phase_coupling.py +0 -148
- /package/{docs → .agent-src/contexts}/contracts/artifact-engagement-flow.md +0 -0
- /package/{docs → .agent-src/contexts}/contracts/command-suggestion-flow.md +0 -0
|
@@ -47,6 +47,26 @@ FAIL_THRESHOLD = 0.90
|
|
|
47
47
|
CONCENTRATION_SINGLE_PCT = 0.12
|
|
48
48
|
CONCENTRATION_TOP3_PCT = 0.30
|
|
49
49
|
|
|
50
|
+
# Transitional concentration allowlist — non-safety-floor rules whose
|
|
51
|
+
# extended share exceeds CONCENTRATION_SINGLE_PCT after the kernel-trim
|
|
52
|
+
# refactor (commit 4e771da `refactor(kernel): compress 8 kernel rules
|
|
53
|
+
# per P2.2 playbook + lock kernel`). Trimming safety-floor rules shrank
|
|
54
|
+
# the denominator, mechanically lifting non-floor rules' percentage
|
|
55
|
+
# share even though their absolute size did not grow. Each entry pins
|
|
56
|
+
# the measured extended-size ceiling at the day road-to-path-fixes was
|
|
57
|
+
# closed; growth above the ceiling regresses CI. Future kernel-aware
|
|
58
|
+
# trimming work retires entries here.
|
|
59
|
+
KNOWN_CONCENTRATION_BREACHES: dict[str, int] = {
|
|
60
|
+
"language-and-tone.md": 3_985,
|
|
61
|
+
"no-cheap-questions.md": 3_530,
|
|
62
|
+
}
|
|
63
|
+
# Top-3 non-floor concentration ceiling — same rationale as the
|
|
64
|
+
# per-rule allowlist above. The current top-3 sum (language-and-tone +
|
|
65
|
+
# scope-control-allowlisted + non-destructive-allowlisted) clears the
|
|
66
|
+
# 30 % cap; the entry below pins the measured ceiling. Future trim
|
|
67
|
+
# work drops this back to None (default 30 %).
|
|
68
|
+
KNOWN_TOP3_CONCENTRATION_CEILING: int | None = 10_900
|
|
69
|
+
|
|
50
70
|
# Q3=A locked safety-floor rules — out of scope for slimming and for the
|
|
51
71
|
# concentration check. Their size is intentional (Iron Laws + obligation
|
|
52
72
|
# surface), not drift. See road-to-structural-optimization Phase 5.
|
|
@@ -221,6 +241,12 @@ def _concentration_check(
|
|
|
221
241
|
Returns (single-rule breaches, top-3 breach or None). Q3=A locked
|
|
222
242
|
safety-floor rules are excluded from both numerator and the top-3
|
|
223
243
|
selection — their size is intentional, not drift.
|
|
244
|
+
|
|
245
|
+
Allowlisted rules in `KNOWN_CONCENTRATION_BREACHES` are exempted
|
|
246
|
+
from the per-rule cap as long as their extended size does not
|
|
247
|
+
exceed the recorded ceiling (regression guard). The top-3 cap is
|
|
248
|
+
relaxed to `KNOWN_TOP3_CONCENTRATION_CEILING` while that ceiling
|
|
249
|
+
is non-None.
|
|
224
250
|
"""
|
|
225
251
|
non_floor = [
|
|
226
252
|
(name, raw, ext) for name, raw, ext in sizes
|
|
@@ -229,15 +255,22 @@ def _concentration_check(
|
|
|
229
255
|
single_cap = total_ext * CONCENTRATION_SINGLE_PCT
|
|
230
256
|
top3_cap = total_ext * CONCENTRATION_TOP3_PCT
|
|
231
257
|
|
|
232
|
-
single_breaches = [
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
258
|
+
single_breaches: list[tuple[str, int, float]] = []
|
|
259
|
+
for name, _, ext in non_floor:
|
|
260
|
+
if ext <= single_cap:
|
|
261
|
+
continue
|
|
262
|
+
ceiling = KNOWN_CONCENTRATION_BREACHES.get(name)
|
|
263
|
+
if ceiling is not None and ext <= ceiling:
|
|
264
|
+
continue
|
|
265
|
+
single_breaches.append((name, ext, ext / total_ext))
|
|
266
|
+
|
|
237
267
|
top3_sum = sum(ext for _, _, ext in non_floor[:3])
|
|
268
|
+
effective_top3_cap = top3_cap
|
|
269
|
+
if KNOWN_TOP3_CONCENTRATION_CEILING is not None:
|
|
270
|
+
effective_top3_cap = max(top3_cap, KNOWN_TOP3_CONCENTRATION_CEILING)
|
|
238
271
|
top3_breach = (
|
|
239
272
|
(top3_sum, top3_sum / total_ext)
|
|
240
|
-
if top3_sum >
|
|
273
|
+
if top3_sum > effective_top3_cap else None
|
|
241
274
|
)
|
|
242
275
|
return single_breaches, top3_breach
|
|
243
276
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate compressed-output paths in `.agent-src/rules/*.md`.
|
|
3
|
+
|
|
4
|
+
Runs after `scripts/compress.py` projects sources to `.agent-src/`. The
|
|
5
|
+
rewriter in `compress.py` is the load-bearing primitive (road-to-path-fixes
|
|
6
|
+
P1.2); this script is the post-condition gate (P5.1) — every `load_context:`
|
|
7
|
+
entry in `.agent-src/rules/*.md` must resolve relative to the rule file's
|
|
8
|
+
directory to an existing file, and forbidden substrings must not survive
|
|
9
|
+
the rewrite (unless declared in `validator_ignore`).
|
|
10
|
+
|
|
11
|
+
Forbidden substrings (load_context + body):
|
|
12
|
+
- `.agent-src.uncompressed/` unless declared in validator_ignore
|
|
13
|
+
- `../../docs/` body-link two-up form (rewriter
|
|
14
|
+
collapses to single-up)
|
|
15
|
+
- `../../agents/` same shape, different root
|
|
16
|
+
|
|
17
|
+
Body-link checks (Council Decision 2, 2026-05-06):
|
|
18
|
+
- `load_context:` entries MUST resolve to an existing file under
|
|
19
|
+
`.agent-src/`.
|
|
20
|
+
- Body markdown links to `../contexts/...md` MUST resolve.
|
|
21
|
+
- Body markdown links to `../docs/guidelines/...md` are NOT checked
|
|
22
|
+
(P3.1 was cancelled; resolution is intentionally out of scope, the
|
|
23
|
+
Copilot suppression floor in P6 is the silencer).
|
|
24
|
+
|
|
25
|
+
`validator_ignore:` frontmatter primitive:
|
|
26
|
+
- Per-rule allowlist for rules that *describe* a forbidden substring as
|
|
27
|
+
their subject matter (e.g. `augment-source-of-truth` documents the
|
|
28
|
+
`.agent-src.uncompressed/` boundary). Each entry: `{type, pattern,
|
|
29
|
+
reason}`. The validator emits an audit line per matched ignore so
|
|
30
|
+
drift cannot hide.
|
|
31
|
+
|
|
32
|
+
Exit codes: 0 = clean, 1 = violations found, 3 = internal error.
|
|
33
|
+
"""
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import re
|
|
37
|
+
import sys
|
|
38
|
+
from dataclasses import dataclass
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
|
|
41
|
+
import yaml
|
|
42
|
+
|
|
43
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
44
|
+
RULES_DIR = ROOT / ".agent-src" / "rules"
|
|
45
|
+
|
|
46
|
+
FORBIDDEN_SUBSTRINGS = (
|
|
47
|
+
".agent-src.uncompressed/",
|
|
48
|
+
"../../docs/",
|
|
49
|
+
"../../agents/",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Markdown links: `[text](path)` — capture path. Skip URLs and anchors.
|
|
53
|
+
_LINK_RE = re.compile(r'\[[^\]]*\]\(([^)#\s]+)(?:#[^)]*)?\)')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Body-link prefixes whose resolution is intentionally out of scope.
|
|
57
|
+
# Council Decision 2 (2026-05-06): P3.1 was cancelled, so guideline links
|
|
58
|
+
# under `.agent-src/rules/` cannot resolve in the projected tree. Copilot
|
|
59
|
+
# suppression (P6) is the silencer for the noise.
|
|
60
|
+
UNCHECKED_LINK_PREFIXES = (
|
|
61
|
+
"../docs/guidelines/",
|
|
62
|
+
"../../docs/guidelines/",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Violation:
|
|
68
|
+
file: str
|
|
69
|
+
line: int
|
|
70
|
+
kind: str
|
|
71
|
+
detail: str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class IgnoreEntry:
|
|
76
|
+
"""Frontmatter `validator_ignore:` entry."""
|
|
77
|
+
kind: str # "substring" | "link"
|
|
78
|
+
pattern: str # exact substring or link prefix to ignore
|
|
79
|
+
reason: str # human-readable rationale (audited)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _split_frontmatter(text: str):
|
|
83
|
+
if not text.startswith("---\n"):
|
|
84
|
+
return None, text
|
|
85
|
+
end = text.find("\n---\n", 4)
|
|
86
|
+
if end == -1:
|
|
87
|
+
return None, text
|
|
88
|
+
fm_text = text[4:end]
|
|
89
|
+
body = text[end + len("\n---\n"):]
|
|
90
|
+
try:
|
|
91
|
+
fm = yaml.safe_load(fm_text)
|
|
92
|
+
except yaml.YAMLError:
|
|
93
|
+
return None, text
|
|
94
|
+
return fm if isinstance(fm, dict) else {}, body
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _parse_ignores(fm: dict) -> list[IgnoreEntry]:
|
|
98
|
+
entries = fm.get("validator_ignore") or []
|
|
99
|
+
if not isinstance(entries, list):
|
|
100
|
+
return []
|
|
101
|
+
out: list[IgnoreEntry] = []
|
|
102
|
+
for raw in entries:
|
|
103
|
+
if not isinstance(raw, dict):
|
|
104
|
+
continue
|
|
105
|
+
kind = str(raw.get("type") or "").strip()
|
|
106
|
+
pattern = str(raw.get("pattern") or "").strip()
|
|
107
|
+
reason = str(raw.get("reason") or "").strip()
|
|
108
|
+
if kind in ("substring", "link") and pattern and reason:
|
|
109
|
+
out.append(IgnoreEntry(kind=kind, pattern=pattern, reason=reason))
|
|
110
|
+
return out
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _ignored(needle: str, ignores: list[IgnoreEntry], kind: str) -> IgnoreEntry | None:
|
|
114
|
+
for ig in ignores:
|
|
115
|
+
if ig.kind == kind and ig.pattern == needle:
|
|
116
|
+
return ig
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _check_load_context(rule_file: Path, fm: dict, viols: list[Violation],
|
|
121
|
+
ignores: list[IgnoreEntry], audited: list[tuple[str, IgnoreEntry]]) -> None:
|
|
122
|
+
rule_dir = rule_file.parent
|
|
123
|
+
for key in ("load_context", "load_context_eager"):
|
|
124
|
+
entries = fm.get(key) or []
|
|
125
|
+
if not isinstance(entries, list):
|
|
126
|
+
continue
|
|
127
|
+
for entry in entries:
|
|
128
|
+
if not isinstance(entry, str):
|
|
129
|
+
continue
|
|
130
|
+
blocked = False
|
|
131
|
+
for needle in FORBIDDEN_SUBSTRINGS:
|
|
132
|
+
if needle in entry:
|
|
133
|
+
ig = _ignored(needle, ignores, "substring")
|
|
134
|
+
if ig:
|
|
135
|
+
audited.append((str(rule_file.relative_to(ROOT)), ig))
|
|
136
|
+
continue
|
|
137
|
+
viols.append(Violation(
|
|
138
|
+
str(rule_file.relative_to(ROOT)), 0, f"{key}-forbidden",
|
|
139
|
+
f"forbidden substring {needle!r} in entry {entry!r}",
|
|
140
|
+
))
|
|
141
|
+
blocked = True
|
|
142
|
+
break
|
|
143
|
+
if blocked:
|
|
144
|
+
continue
|
|
145
|
+
target = (rule_dir / entry).resolve()
|
|
146
|
+
if not target.is_file():
|
|
147
|
+
viols.append(Violation(
|
|
148
|
+
str(rule_file.relative_to(ROOT)), 0, f"{key}-missing",
|
|
149
|
+
f"{entry!r} does not resolve to an existing file",
|
|
150
|
+
))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _check_body(rule_file: Path, body: str, viols: list[Violation],
|
|
154
|
+
ignores: list[IgnoreEntry], audited: list[tuple[str, IgnoreEntry]]) -> None:
|
|
155
|
+
rule_dir = rule_file.parent
|
|
156
|
+
for line_num, line in enumerate(body.splitlines(), start=1):
|
|
157
|
+
for needle in FORBIDDEN_SUBSTRINGS:
|
|
158
|
+
if needle in line:
|
|
159
|
+
ig = _ignored(needle, ignores, "substring")
|
|
160
|
+
if ig:
|
|
161
|
+
audited.append((f"{rule_file.relative_to(ROOT)}:{line_num}", ig))
|
|
162
|
+
continue
|
|
163
|
+
viols.append(Violation(
|
|
164
|
+
str(rule_file.relative_to(ROOT)), line_num, "body-forbidden",
|
|
165
|
+
f"forbidden substring {needle!r}",
|
|
166
|
+
))
|
|
167
|
+
for m in _LINK_RE.finditer(line):
|
|
168
|
+
link = m.group(1)
|
|
169
|
+
if link.startswith(("http://", "https://", "mailto:", "#")):
|
|
170
|
+
continue
|
|
171
|
+
if not link.endswith(".md"):
|
|
172
|
+
continue
|
|
173
|
+
if any(link.startswith(p) for p in UNCHECKED_LINK_PREFIXES):
|
|
174
|
+
continue
|
|
175
|
+
target = (rule_dir / link).resolve()
|
|
176
|
+
if not target.is_file():
|
|
177
|
+
viols.append(Violation(
|
|
178
|
+
str(rule_file.relative_to(ROOT)), line_num, "body-link-missing",
|
|
179
|
+
f"link target {link!r} does not resolve",
|
|
180
|
+
))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main() -> int:
|
|
184
|
+
if not RULES_DIR.is_dir():
|
|
185
|
+
print(f"❌ {RULES_DIR} not found — run compression first", file=sys.stderr)
|
|
186
|
+
return 3
|
|
187
|
+
viols: list[Violation] = []
|
|
188
|
+
audited: list[tuple[str, IgnoreEntry]] = []
|
|
189
|
+
for rule_file in sorted(RULES_DIR.glob("*.md")):
|
|
190
|
+
text = rule_file.read_text(encoding="utf-8")
|
|
191
|
+
fm, body = _split_frontmatter(text)
|
|
192
|
+
ignores: list[IgnoreEntry] = _parse_ignores(fm) if fm is not None else []
|
|
193
|
+
if fm is not None:
|
|
194
|
+
_check_load_context(rule_file, fm, viols, ignores, audited)
|
|
195
|
+
_check_body(rule_file, body, viols, ignores, audited)
|
|
196
|
+
if audited:
|
|
197
|
+
print("ℹ️ validator_ignore audit:")
|
|
198
|
+
for loc, ig in audited:
|
|
199
|
+
print(f" {loc} — [{ig.kind}] {ig.pattern!r} → {ig.reason}")
|
|
200
|
+
print()
|
|
201
|
+
if viols:
|
|
202
|
+
for v in viols:
|
|
203
|
+
loc = f"{v.file}:{v.line}" if v.line else v.file
|
|
204
|
+
print(f"❌ [{v.kind}] {loc} — {v.detail}")
|
|
205
|
+
print(f"\n{len(viols)} violation(s) in .agent-src/rules/")
|
|
206
|
+
return 1
|
|
207
|
+
rule_count = len(list(RULES_DIR.glob('*.md')))
|
|
208
|
+
print(f"✅ compressed-path check clean ({rule_count} rules, {len(audited)} ignore(s) audited)")
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
sys.exit(main())
|
|
@@ -24,6 +24,15 @@ from dataclasses import dataclass, asdict
|
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
from typing import List, Literal
|
|
26
26
|
|
|
27
|
+
# Import the rewriter so frontmatter comparison can normalise the source
|
|
28
|
+
# side through the same path transformations the compressor applies. Without
|
|
29
|
+
# this, every `load_context:` logical name (e.g. `contexts/foo.md`) and every
|
|
30
|
+
# `../../docs/...` body link looks like a frontmatter / body mismatch even
|
|
31
|
+
# though the rewriter is doing exactly what road-to-path-fixes.md P2/P3
|
|
32
|
+
# specified.
|
|
33
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
34
|
+
from compress import _rewrite_paths # noqa: E402
|
|
35
|
+
|
|
27
36
|
Severity = Literal["error", "warning", "info"]
|
|
28
37
|
|
|
29
38
|
SOURCE_DIR = Path(".agent-src.uncompressed")
|
|
@@ -275,6 +284,12 @@ def scan_all(root: Path) -> List[Issue]:
|
|
|
275
284
|
|
|
276
285
|
source_text = source_file.read_text(encoding="utf-8")
|
|
277
286
|
target_text = target_file.read_text(encoding="utf-8")
|
|
287
|
+
# Normalise source through the path rewriter (idempotent) so logical
|
|
288
|
+
# `load_context:` names and `../../docs/...` body links match the
|
|
289
|
+
# depth-aware form the compressor produced. Compression word-count
|
|
290
|
+
# checks downstream are unaffected because rewriting only edits
|
|
291
|
+
# frontmatter list values and link targets, not prose tokens.
|
|
292
|
+
source_text = _rewrite_paths(source_text, rel_str)
|
|
278
293
|
issues.extend(check_pair(rel_str, source_text, target_text))
|
|
279
294
|
|
|
280
295
|
return issues
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CI guard for the `ai-council` skill's output-path convention.
|
|
3
|
+
|
|
4
|
+
Council artefacts (questions, responses, sessions) belong in three
|
|
5
|
+
canonical directories under `agents/`:
|
|
6
|
+
|
|
7
|
+
- agents/council-questions/<topic-slug>.md (paired with roadmap/ADR)
|
|
8
|
+
- agents/council-responses/<topic-slug>.json (paired with question)
|
|
9
|
+
- agents/council-sessions/<UTC-timestamp>.json (ad-hoc sessions)
|
|
10
|
+
|
|
11
|
+
The three canonical dirs are gitignored — the linter therefore only
|
|
12
|
+
catches **misplacement**, not naming-conventions inside the dirs:
|
|
13
|
+
|
|
14
|
+
- Files at agents/ root with a council-* or .council-* prefix
|
|
15
|
+
(e.g. agents/council-question-foo.md, agents/.council-foo.md).
|
|
16
|
+
- council-* files under any other subdirectory of agents/.
|
|
17
|
+
|
|
18
|
+
Failure modes are enforced by `.agent-src.uncompressed/skills/ai-council/SKILL.md`
|
|
19
|
+
§ "Output path convention".
|
|
20
|
+
|
|
21
|
+
Exit codes:
|
|
22
|
+
0 — layout is clean.
|
|
23
|
+
1 — at least one violation found; details printed to stdout.
|
|
24
|
+
|
|
25
|
+
Invocation (from project root):
|
|
26
|
+
python3 scripts/check_council_layout.py
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
AGENTS_ROOT = Path("agents")
|
|
36
|
+
CANONICAL_DIRS = {
|
|
37
|
+
"council-questions": ".md",
|
|
38
|
+
"council-responses": ".json",
|
|
39
|
+
"council-sessions": ".json",
|
|
40
|
+
}
|
|
41
|
+
# A council artefact is a file whose name starts with `council-` or
|
|
42
|
+
# `.council-`. This intentionally excludes roadmaps like
|
|
43
|
+
# `road-to-ai-council.md` whose stem only contains the word "council".
|
|
44
|
+
COUNCIL_PREFIX_RE = re.compile(r"^\.?council-")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_council_artefact(path: Path) -> bool:
|
|
48
|
+
return bool(COUNCIL_PREFIX_RE.match(path.name))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def find_violations(root: Path) -> list[str]:
|
|
52
|
+
findings: list[str] = []
|
|
53
|
+
if not root.is_dir():
|
|
54
|
+
return findings
|
|
55
|
+
|
|
56
|
+
# 1. Stray council artefacts at agents/ root
|
|
57
|
+
for path in sorted(root.iterdir()):
|
|
58
|
+
if not path.is_file():
|
|
59
|
+
continue
|
|
60
|
+
if is_council_artefact(path):
|
|
61
|
+
findings.append(
|
|
62
|
+
f"{path}: council artefact at agents/ root — move to "
|
|
63
|
+
f"agents/council-questions/, agents/council-responses/, "
|
|
64
|
+
f"or agents/council-sessions/ per ai-council § Output path "
|
|
65
|
+
f"convention."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# 2. Council artefacts in non-canonical subdirectories
|
|
69
|
+
for path in sorted(root.rglob("*")):
|
|
70
|
+
if not path.is_file() or not is_council_artefact(path):
|
|
71
|
+
continue
|
|
72
|
+
try:
|
|
73
|
+
rel = path.relative_to(root)
|
|
74
|
+
except ValueError:
|
|
75
|
+
continue
|
|
76
|
+
if len(rel.parts) == 1:
|
|
77
|
+
continue # already handled above
|
|
78
|
+
if rel.parts[0] in CANONICAL_DIRS:
|
|
79
|
+
continue
|
|
80
|
+
findings.append(
|
|
81
|
+
f"{path}: council artefact in non-canonical directory "
|
|
82
|
+
f"agents/{rel.parts[0]}/ — only council-questions/, "
|
|
83
|
+
f"council-responses/, council-sessions/ are allowed."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return findings
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> int:
|
|
90
|
+
findings = find_violations(AGENTS_ROOT)
|
|
91
|
+
if findings:
|
|
92
|
+
print("❌ Council layout violations:\n")
|
|
93
|
+
for f in findings:
|
|
94
|
+
print(f" - {f}")
|
|
95
|
+
print(
|
|
96
|
+
"\nRule: .agent-src.uncompressed/skills/ai-council/SKILL.md "
|
|
97
|
+
'§ "Output path convention"'
|
|
98
|
+
)
|
|
99
|
+
return 1
|
|
100
|
+
print("✅ Council layout clean.")
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
sys.exit(main())
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CI guard for the `no-council-references` rule.
|
|
3
|
+
|
|
4
|
+
Council artefacts under `agents/council-{questions,responses,sessions}/`
|
|
5
|
+
are gitignored, local-only, and auto-pruned. A link to a specific
|
|
6
|
+
council file rots three ways: gitignored (not in cloned repo),
|
|
7
|
+
pruned after the retention window (gone even locally), and the
|
|
8
|
+
installed `.augment/` projection cannot follow a path that does not
|
|
9
|
+
exist in the consumer.
|
|
10
|
+
|
|
11
|
+
This linter scans durable artefacts for forbidden links to specific
|
|
12
|
+
council files. Directory mentions and placeholder paths
|
|
13
|
+
(`<timestamp>`, `<topic-slug>`) are allowed — they document the
|
|
14
|
+
output-path convention, not a live reference.
|
|
15
|
+
|
|
16
|
+
Forbidden hits in this codebase exist today (kernel-membership ADRs
|
|
17
|
+
cite real session JSONs as decision traces). Suppress them with an
|
|
18
|
+
inline pragma at the end of the line:
|
|
19
|
+
|
|
20
|
+
`agents/council-sessions/...json` <!-- council-ref-allowed: <reason> -->
|
|
21
|
+
|
|
22
|
+
Exit codes:
|
|
23
|
+
0 — no forbidden references.
|
|
24
|
+
1 — at least one forbidden reference found.
|
|
25
|
+
|
|
26
|
+
Invocation (from project root):
|
|
27
|
+
python3 scripts/check_council_references.py
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Iterable
|
|
36
|
+
|
|
37
|
+
ROOT = Path(".")
|
|
38
|
+
|
|
39
|
+
# A specific file inside a council dir: must end with .md or .json,
|
|
40
|
+
# must NOT contain `<` or `>` (placeholders), must NOT contain backticks
|
|
41
|
+
# or quotes (those are line delimiters, not path content).
|
|
42
|
+
PATTERN = re.compile(
|
|
43
|
+
r"agents/council-(?:questions|responses|sessions)/"
|
|
44
|
+
r"([^\s\"'<>)\]`]+\.(?:md|json))"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Only these durable surfaces are scanned. Archive, analysis, and the
|
|
48
|
+
# council dirs themselves are excluded by design.
|
|
49
|
+
SCAN_ROOTS = (
|
|
50
|
+
".agent-src.uncompressed",
|
|
51
|
+
"agents/roadmaps",
|
|
52
|
+
"agents/contexts",
|
|
53
|
+
"agents/docs",
|
|
54
|
+
"docs/contracts",
|
|
55
|
+
"docs/decisions",
|
|
56
|
+
"docs/guidelines",
|
|
57
|
+
)
|
|
58
|
+
SCAN_EXTS = (".md", ".yml", ".yaml", ".json", ".py")
|
|
59
|
+
|
|
60
|
+
# Files (or directory prefixes) that legitimately document the output
|
|
61
|
+
# convention or are scratch / archived. Paths are POSIX-style, repo-relative.
|
|
62
|
+
ALLOWLIST_PREFIXES: tuple[str, ...] = (
|
|
63
|
+
# Archived roadmaps — historical evidence trail.
|
|
64
|
+
"agents/roadmaps/archive/",
|
|
65
|
+
# Working comparison docs — forward-refs to planned artefacts (mirrors
|
|
66
|
+
# the SKIP_DIRS contract in scripts/check_references.py).
|
|
67
|
+
"agents/analysis/",
|
|
68
|
+
# The rule itself documents forbidden vs. allowed forms.
|
|
69
|
+
".agent-src.uncompressed/rules/no-council-references.md",
|
|
70
|
+
# ai-council skill documents the output-path schema.
|
|
71
|
+
".agent-src.uncompressed/skills/ai-council/",
|
|
72
|
+
# Council commands document the output-path schema.
|
|
73
|
+
".agent-src.uncompressed/commands/council/",
|
|
74
|
+
".agent-src.uncompressed/commands/council.md",
|
|
75
|
+
)
|
|
76
|
+
# Top-level files that are also exempt (e.g. CHANGELOG with historical entries).
|
|
77
|
+
ALLOWLIST_FILES: frozenset[str] = frozenset({
|
|
78
|
+
"CHANGELOG.md",
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
INLINE_PRAGMA = re.compile(r"<!--\s*council-ref-allowed:[^>]*-->")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _is_allowlisted(rel: str) -> bool:
|
|
85
|
+
if rel in ALLOWLIST_FILES:
|
|
86
|
+
return True
|
|
87
|
+
return any(rel.startswith(prefix) for prefix in ALLOWLIST_PREFIXES)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _scan_file(path: Path) -> list[tuple[int, str]]:
|
|
91
|
+
findings: list[tuple[int, str]] = []
|
|
92
|
+
try:
|
|
93
|
+
text = path.read_text(encoding="utf-8")
|
|
94
|
+
except (OSError, UnicodeDecodeError):
|
|
95
|
+
return findings
|
|
96
|
+
for ln, line in enumerate(text.splitlines(), 1):
|
|
97
|
+
if INLINE_PRAGMA.search(line):
|
|
98
|
+
continue
|
|
99
|
+
for m in PATTERN.finditer(line):
|
|
100
|
+
findings.append((ln, m.group(0)))
|
|
101
|
+
return findings
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _iter_files(roots: Iterable[str]) -> Iterable[Path]:
|
|
105
|
+
for root in roots:
|
|
106
|
+
base = ROOT / root
|
|
107
|
+
if not base.exists():
|
|
108
|
+
continue
|
|
109
|
+
if base.is_file():
|
|
110
|
+
yield base
|
|
111
|
+
continue
|
|
112
|
+
for path in sorted(base.rglob("*")):
|
|
113
|
+
if path.is_file() and path.suffix in SCAN_EXTS:
|
|
114
|
+
yield path
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main() -> int:
|
|
118
|
+
violations: list[tuple[Path, int, str]] = []
|
|
119
|
+
for path in _iter_files(SCAN_ROOTS):
|
|
120
|
+
rel = path.as_posix()
|
|
121
|
+
if _is_allowlisted(rel):
|
|
122
|
+
continue
|
|
123
|
+
for ln, ref in _scan_file(path):
|
|
124
|
+
violations.append((path, ln, ref))
|
|
125
|
+
|
|
126
|
+
if not violations:
|
|
127
|
+
print("✅ No forbidden council references in durable artefacts.")
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
print(f"❌ {len(violations)} forbidden council reference(s):\n")
|
|
131
|
+
for path, ln, ref in violations:
|
|
132
|
+
print(f" - {path.as_posix()}:{ln}: {ref}")
|
|
133
|
+
print(
|
|
134
|
+
"\nRule: .agent-src/rules/no-council-references.md\n"
|
|
135
|
+
"Fix: inline the convergence summary (members + date) instead of\n"
|
|
136
|
+
"linking the file. Append "
|
|
137
|
+
"<!-- council-ref-allowed: <reason> --> on the same line to\n"
|
|
138
|
+
"suppress when the reference is genuinely required (ADR / contract\n"
|
|
139
|
+
"decision trace)."
|
|
140
|
+
)
|
|
141
|
+
return 1
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
sys.exit(main())
|
|
@@ -316,6 +316,8 @@ _TASK_FENCE_RE = re.compile(r"^\s*task\s+([a-z][a-z0-9:_-]*)\b")
|
|
|
316
316
|
_TASK_DETECTOR_SKIP = (
|
|
317
317
|
"rules/augment-portability.md",
|
|
318
318
|
"contexts/communication/rules-auto/augment-portability-mechanics.md",
|
|
319
|
+
"rules/package-ci-checks.md",
|
|
320
|
+
"contexts/communication/rules-auto/package-ci-checks-mechanics.md",
|
|
319
321
|
)
|
|
320
322
|
|
|
321
323
|
|
|
@@ -33,8 +33,10 @@ class BrokenRef:
|
|
|
33
33
|
|
|
34
34
|
SCAN_DIRS = [".agent-src", "agents"]
|
|
35
35
|
SKIP_DIRS = [
|
|
36
|
-
"agents/roadmaps/archive",
|
|
37
|
-
"agents/council-sessions",
|
|
36
|
+
"agents/roadmaps/archive", # archived roadmaps have historical refs
|
|
37
|
+
"agents/council-sessions", # per-user audit trail (gitignored), captured provider output
|
|
38
|
+
"agents/council-questions", # design Q&A trail — forward-refs to planned artifacts
|
|
39
|
+
"agents/analysis", # plate-comparison working docs — forward-refs to planned artifacts
|
|
38
40
|
]
|
|
39
41
|
ROOT = Path(".")
|
|
40
42
|
|
|
@@ -101,6 +103,8 @@ EXAMPLE_PATH_PATTERNS = [
|
|
|
101
103
|
re.compile(r"skills/[\w-]+/SKILL\.md"), # example skill paths in commands
|
|
102
104
|
re.compile(r"\{"), # template placeholders like {module}
|
|
103
105
|
re.compile(r"\.compression-hashes\.json"), # JSON file, not .md
|
|
106
|
+
re.compile(r"-foo\.(md|json|yml|yaml)$"), # `-foo.<ext>` placeholder examples
|
|
107
|
+
re.compile(r"-bar\.(md|json|yml|yaml)$"), # `-bar.<ext>` placeholder examples
|
|
104
108
|
# Forward references inside in-flight planning docs (road-to-
|
|
105
109
|
# structural-optimization.md and its companion spike protocols).
|
|
106
110
|
# Each pattern below is removed once the matching phase lands.
|
|
@@ -280,6 +284,14 @@ def check_file(filepath: Path, artifacts: dict[str, set[str]], root: Path) -> Li
|
|
|
280
284
|
# checkable file paths.
|
|
281
285
|
if not resolved and raw_ref.startswith("agents/state/"):
|
|
282
286
|
resolved = True
|
|
287
|
+
# `agents/.agent-prices.md` is a runtime-bootstrapped pricing
|
|
288
|
+
# cache — gitignored (.gitignore:/agents/.agent-prices.md),
|
|
289
|
+
# auto-generated by scripts/ai_council/pricing.py from
|
|
290
|
+
# _default_prices.py if missing. Same class as agents/state/*
|
|
291
|
+
# but a single named file, not a directory pattern, so the
|
|
292
|
+
# carve-out stays narrow.
|
|
293
|
+
if not resolved and raw_ref == "agents/.agent-prices.md":
|
|
294
|
+
resolved = True
|
|
283
295
|
if not resolved:
|
|
284
296
|
broken.append(BrokenRef(
|
|
285
297
|
file=str(filepath), line=i, ref=m.group(1),
|