@event4u/agent-config 1.20.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/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 +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 +17 -5
- package/.agent-src/commands/council.md +1 -1
- 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/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/slash-command-routing-policy-mechanics.md +1 -1
- 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/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 -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 +10 -2
- 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/agent-docs-writing/SKILL.md +1 -1
- package/.agent-src/skills/ai-council/SKILL.md +65 -0
- 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/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/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/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/universal-project-analysis/SKILL.md +1 -1
- package/.agent-src/templates/AGENTS.md +1 -1
- package/.agent-src/templates/agent-settings.md +21 -16
- 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/.claude-plugin/marketplace.json +4 -1
- package/AGENTS.md +32 -5
- package/CHANGELOG.md +69 -3
- package/README.md +22 -21
- package/config/agent-settings.template.yml +44 -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/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-rule-kernel-and-router.md +122 -0
- package/docs/getting-started.md +2 -2
- 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/_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 +9 -5
- 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/prototype_lint_contradictions.py +150 -0
- package/scripts/schemas/rule.schema.json +55 -6
- package/scripts/skill_linter.py +196 -6
- package/scripts/smoke_path_resolution.py +93 -0
- package/scripts/validate_frontmatter.py +41 -1
- 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
|
@@ -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:
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Measure rule-bucket char counts (kernel + auto) for the rule-kernel roadmap.
|
|
3
|
+
|
|
4
|
+
Source of truth: `.agent-src.uncompressed/rules/*.md`. Frontmatter (YAML
|
|
5
|
+
between two `---` lines at file start) is stripped before counting; only
|
|
6
|
+
the rule body counts toward the bucket.
|
|
7
|
+
|
|
8
|
+
Buckets follow the existing frontmatter `type:` field:
|
|
9
|
+
- `always` rules → always-bucket (today's kernel proxy).
|
|
10
|
+
- `auto` rules → auto-bucket.
|
|
11
|
+
|
|
12
|
+
Output:
|
|
13
|
+
- Default: stdout table (per-rule rows, top-5 oversize, totals).
|
|
14
|
+
- `--json`: deterministic JSON (sorted keys, sorted lists).
|
|
15
|
+
|
|
16
|
+
Acceptance per `road-to-kernel-and-router.md` P1.1: re-runnable,
|
|
17
|
+
deterministic, stdlib-only, no network.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import datetime as _dt
|
|
24
|
+
import json
|
|
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
|
+
OVERRIDES_FILE = REPO_ROOT / "docs" / "contracts" / "iron-law-overrides.txt"
|
|
31
|
+
TREND_FILE = REPO_ROOT / "agents" / ".rule-budget-history.jsonl"
|
|
32
|
+
|
|
33
|
+
# Council R2 amendments (2026-05-06) — see docs/contracts/kernel-membership.md § 5.1.
|
|
34
|
+
# Per-rule cap raised 1.5k → 2.5k; warning band raised 1.2k → 2.0k.
|
|
35
|
+
# ADR-002 (2026-05-06) — KERNEL_HARD raised 25k → 26k after empirical r_actual=0.795
|
|
36
|
+
# vs r_projected=0.712; see docs/decisions/ADR-002-kernel-bucket-overrides.md.
|
|
37
|
+
KERNEL_HARD = 26_000
|
|
38
|
+
KERNEL_TARGET = 20_000
|
|
39
|
+
PER_RULE_HARD = 2_500
|
|
40
|
+
PER_RULE_TARGET = 2_000
|
|
41
|
+
PER_RULE_OVERRIDE_CEILING = 4_000 # Iron-Law-override ADR ceiling.
|
|
42
|
+
|
|
43
|
+
# Locked kernel set — docs/contracts/kernel-membership.md § 4.
|
|
44
|
+
# This is the *kernel* (P1.3 lock), not "every always-rule". After P4 the
|
|
45
|
+
# `type:` frontmatter no longer maps 1:1 to kernel; the kernel is this set.
|
|
46
|
+
KERNEL_RULES: frozenset[str] = frozenset(
|
|
47
|
+
{
|
|
48
|
+
"agent-authority",
|
|
49
|
+
"ask-when-uncertain",
|
|
50
|
+
"commit-policy",
|
|
51
|
+
"direct-answers",
|
|
52
|
+
"language-and-tone",
|
|
53
|
+
"no-cheap-questions",
|
|
54
|
+
"non-destructive-by-default",
|
|
55
|
+
"scope-control",
|
|
56
|
+
"verify-before-complete",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def strip_frontmatter(text: str) -> tuple[str, dict[str, str]]:
|
|
62
|
+
"""Strip leading YAML frontmatter and return (body, fields).
|
|
63
|
+
|
|
64
|
+
Minimal parser — handles `key: "value"` / `key: value` only. No nested
|
|
65
|
+
structures, no lists. Sufficient for the rule frontmatter contract.
|
|
66
|
+
"""
|
|
67
|
+
if not text.startswith("---\n"):
|
|
68
|
+
return text, {}
|
|
69
|
+
end = text.find("\n---\n", 4)
|
|
70
|
+
if end == -1:
|
|
71
|
+
return text, {}
|
|
72
|
+
raw = text[4:end]
|
|
73
|
+
body = text[end + 5 :]
|
|
74
|
+
fields: dict[str, str] = {}
|
|
75
|
+
for line in raw.splitlines():
|
|
76
|
+
if ":" not in line or line.startswith("#"):
|
|
77
|
+
continue
|
|
78
|
+
key, _, val = line.partition(":")
|
|
79
|
+
fields[key.strip()] = val.strip().strip('"').strip("'")
|
|
80
|
+
return body, fields
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def measure_rule(path: Path) -> dict[str, object]:
|
|
84
|
+
text = path.read_text(encoding="utf-8")
|
|
85
|
+
body, fields = strip_frontmatter(text)
|
|
86
|
+
return {
|
|
87
|
+
"id": path.stem,
|
|
88
|
+
"type": fields.get("type", "auto"),
|
|
89
|
+
"tier": fields.get("tier", ""),
|
|
90
|
+
"chars": len(body),
|
|
91
|
+
"lines": body.count("\n"),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def collect() -> list[dict[str, object]]:
|
|
96
|
+
rules = [measure_rule(p) for p in sorted(RULES_DIR.glob("*.md"))]
|
|
97
|
+
return rules
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def load_overrides() -> set[str]:
|
|
101
|
+
"""Read iron-law-override allowlist (one rule-id per line, '#' comments)."""
|
|
102
|
+
if not OVERRIDES_FILE.exists():
|
|
103
|
+
return set()
|
|
104
|
+
out: set[str] = set()
|
|
105
|
+
for line in OVERRIDES_FILE.read_text(encoding="utf-8").splitlines():
|
|
106
|
+
s = line.split("#", 1)[0].strip()
|
|
107
|
+
if s:
|
|
108
|
+
out.add(s)
|
|
109
|
+
return out
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def aggregate(rules: list[dict[str, object]]) -> dict[str, object]:
|
|
113
|
+
always = [r for r in rules if r["type"] == "always"]
|
|
114
|
+
auto = [r for r in rules if r["type"] == "auto"]
|
|
115
|
+
kernel = [r for r in rules if r["id"] in KERNEL_RULES]
|
|
116
|
+
total_chars = sum(int(r["chars"]) for r in rules)
|
|
117
|
+
return {
|
|
118
|
+
"always_count": len(always),
|
|
119
|
+
"auto_count": len(auto),
|
|
120
|
+
"kernel_count": len(kernel),
|
|
121
|
+
"rule_count": len(rules),
|
|
122
|
+
"always_chars": sum(int(r["chars"]) for r in always),
|
|
123
|
+
"auto_chars": sum(int(r["chars"]) for r in auto),
|
|
124
|
+
"kernel_chars": sum(int(r["chars"]) for r in kernel),
|
|
125
|
+
"total_chars": total_chars,
|
|
126
|
+
"kernel_hard": KERNEL_HARD,
|
|
127
|
+
"kernel_target": KERNEL_TARGET,
|
|
128
|
+
"per_rule_hard": PER_RULE_HARD,
|
|
129
|
+
"per_rule_target": PER_RULE_TARGET,
|
|
130
|
+
"per_rule_override_ceiling": PER_RULE_OVERRIDE_CEILING,
|
|
131
|
+
"oversize_rules": sorted(
|
|
132
|
+
(r for r in rules if int(r["chars"]) > PER_RULE_HARD),
|
|
133
|
+
key=lambda r: (-int(r["chars"]), r["id"]),
|
|
134
|
+
),
|
|
135
|
+
"top5_largest": sorted(rules, key=lambda r: (-int(r["chars"]), r["id"]))[:5],
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def render_table(rules: list[dict[str, object]], agg: dict[str, object]) -> str:
|
|
140
|
+
lines: list[str] = []
|
|
141
|
+
lines.append("Rule budget — source: .agent-src.uncompressed/rules/")
|
|
142
|
+
lines.append("")
|
|
143
|
+
lines.append(f"{'id':<40} {'type':<7} {'tier':<5} {'chars':>7}")
|
|
144
|
+
lines.append("-" * 62)
|
|
145
|
+
for r in sorted(rules, key=lambda r: r["id"]):
|
|
146
|
+
flag = "!" if int(r["chars"]) > PER_RULE_HARD else (
|
|
147
|
+
"~" if int(r["chars"]) > PER_RULE_TARGET else " "
|
|
148
|
+
)
|
|
149
|
+
lines.append(
|
|
150
|
+
f"{r['id']:<40} {r['type']:<7} {str(r['tier']):<5} {r['chars']:>6}{flag}"
|
|
151
|
+
)
|
|
152
|
+
lines.append("")
|
|
153
|
+
lines.append(
|
|
154
|
+
f"kernel-bucket: {agg['kernel_chars']:>6} chars across {agg['kernel_count']} rules "
|
|
155
|
+
f"(target ≤ {KERNEL_TARGET}, hard ≤ {KERNEL_HARD})"
|
|
156
|
+
)
|
|
157
|
+
lines.append(
|
|
158
|
+
f"always-bucket: {agg['always_chars']:>6} chars across {agg['always_count']} rules "
|
|
159
|
+
f"(legacy frontmatter `type: always`)"
|
|
160
|
+
)
|
|
161
|
+
lines.append(
|
|
162
|
+
f" auto-bucket: {agg['auto_chars']:>6} chars across {agg['auto_count']} rules"
|
|
163
|
+
)
|
|
164
|
+
lines.append(f" total: {agg['total_chars']:>6} chars across {agg['rule_count']} rules")
|
|
165
|
+
lines.append("")
|
|
166
|
+
lines.append(f"top-5 largest:")
|
|
167
|
+
for r in agg["top5_largest"]: # type: ignore[index]
|
|
168
|
+
lines.append(f" {r['chars']:>5} {r['id']} ({r['type']})")
|
|
169
|
+
over = agg["oversize_rules"] # type: ignore[index]
|
|
170
|
+
if over:
|
|
171
|
+
lines.append("")
|
|
172
|
+
lines.append(f"OVER per-rule hard cap ({PER_RULE_HARD} chars): {len(over)} rule(s)")
|
|
173
|
+
return "\n".join(lines)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def kernel_budget_check(
|
|
177
|
+
rules: list[dict[str, object]], agg: dict[str, object], overrides: set[str]
|
|
178
|
+
) -> tuple[int, list[str]]:
|
|
179
|
+
"""Enforce kernel budget per Council R2 amendments.
|
|
180
|
+
|
|
181
|
+
Returns (exit_code, report_lines). Exit 0 = pass, 1 = breach.
|
|
182
|
+
|
|
183
|
+
Checks:
|
|
184
|
+
- Kernel-bucket sum ≤ KERNEL_HARD (25k).
|
|
185
|
+
- Each kernel rule ≤ PER_RULE_HARD (2.5k), unless listed in
|
|
186
|
+
`iron-law-overrides.txt` (then ≤ PER_RULE_OVERRIDE_CEILING = 4k).
|
|
187
|
+
- Missing kernel rules (rule-id in KERNEL_RULES but no file) → fail.
|
|
188
|
+
"""
|
|
189
|
+
out: list[str] = []
|
|
190
|
+
fails: list[str] = []
|
|
191
|
+
|
|
192
|
+
kernel_rules = [r for r in rules if r["id"] in KERNEL_RULES]
|
|
193
|
+
found_ids = {str(r["id"]) for r in kernel_rules}
|
|
194
|
+
missing = sorted(KERNEL_RULES - found_ids)
|
|
195
|
+
for mid in missing:
|
|
196
|
+
fails.append(f"missing kernel rule: {mid} (declared in KERNEL_RULES, no file found)")
|
|
197
|
+
|
|
198
|
+
bucket = int(agg["kernel_chars"])
|
|
199
|
+
out.append(
|
|
200
|
+
f"kernel-bucket: {bucket} / {KERNEL_HARD} chars "
|
|
201
|
+
f"({agg['kernel_count']} rules)"
|
|
202
|
+
)
|
|
203
|
+
if bucket > KERNEL_HARD:
|
|
204
|
+
fails.append(f"kernel-bucket {bucket} > hard cap {KERNEL_HARD}")
|
|
205
|
+
|
|
206
|
+
out.append(
|
|
207
|
+
f"per-rule cap: {PER_RULE_HARD} (override ceiling {PER_RULE_OVERRIDE_CEILING} "
|
|
208
|
+
f"with ADR; allowlist {OVERRIDES_FILE.relative_to(REPO_ROOT)})"
|
|
209
|
+
)
|
|
210
|
+
out.append("")
|
|
211
|
+
out.append(f"{'id':<28} {'chars':>6} {'cap':>6} {'status':<24}")
|
|
212
|
+
out.append("-" * 68)
|
|
213
|
+
for r in sorted(kernel_rules, key=lambda r: r["id"]):
|
|
214
|
+
rid = str(r["id"])
|
|
215
|
+
chars = int(r["chars"])
|
|
216
|
+
if rid in overrides:
|
|
217
|
+
cap = PER_RULE_OVERRIDE_CEILING
|
|
218
|
+
label = "OK (override)"
|
|
219
|
+
if chars > cap:
|
|
220
|
+
label = f"FAIL (>{cap} ceiling)"
|
|
221
|
+
fails.append(f"{rid} {chars} > override ceiling {cap}")
|
|
222
|
+
else:
|
|
223
|
+
cap = PER_RULE_HARD
|
|
224
|
+
if chars > cap:
|
|
225
|
+
label = "FAIL (needs override ADR)"
|
|
226
|
+
fails.append(f"{rid} {chars} > per-rule hard cap {cap} (no override)")
|
|
227
|
+
elif chars > PER_RULE_TARGET:
|
|
228
|
+
label = "warn (> target)"
|
|
229
|
+
else:
|
|
230
|
+
label = "OK"
|
|
231
|
+
out.append(f"{rid:<28} {chars:>6} {cap:>6} {label:<24}")
|
|
232
|
+
|
|
233
|
+
out.append("")
|
|
234
|
+
if fails:
|
|
235
|
+
out.append(f"❌ kernel budget check: {len(fails)} breach(es)")
|
|
236
|
+
for f in fails:
|
|
237
|
+
out.append(f" - {f}")
|
|
238
|
+
return 1, out
|
|
239
|
+
out.append(f"✅ kernel budget check: pass")
|
|
240
|
+
return 0, out
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def trend_append(agg: dict[str, object]) -> tuple[int, str]:
|
|
244
|
+
"""Append a daily snapshot to agents/.rule-budget-history.jsonl.
|
|
245
|
+
|
|
246
|
+
Idempotent per UTC day: if today's date already has a row, the file
|
|
247
|
+
is not modified. Snapshot fields: date, kernel_chars, auto_chars,
|
|
248
|
+
rule_count, total_chars. Read by `roadmap:progress` for the Kernel
|
|
249
|
+
track per `road-to-kernel-and-router.md` P5.3.
|
|
250
|
+
"""
|
|
251
|
+
today = _dt.datetime.now(_dt.timezone.utc).date().isoformat()
|
|
252
|
+
snapshot = {
|
|
253
|
+
"date": today,
|
|
254
|
+
"kernel_chars": int(agg["kernel_chars"]),
|
|
255
|
+
"auto_chars": int(agg["auto_chars"]),
|
|
256
|
+
"rule_count": int(agg["rule_count"]),
|
|
257
|
+
"total_chars": int(agg["total_chars"]),
|
|
258
|
+
}
|
|
259
|
+
TREND_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
260
|
+
if TREND_FILE.exists():
|
|
261
|
+
for line in TREND_FILE.read_text(encoding="utf-8").splitlines():
|
|
262
|
+
line = line.strip()
|
|
263
|
+
if not line:
|
|
264
|
+
continue
|
|
265
|
+
try:
|
|
266
|
+
row = json.loads(line)
|
|
267
|
+
except json.JSONDecodeError:
|
|
268
|
+
continue
|
|
269
|
+
if row.get("date") == today:
|
|
270
|
+
return 0, f"trend: {today} already recorded — no-op"
|
|
271
|
+
with TREND_FILE.open("a", encoding="utf-8") as fh:
|
|
272
|
+
fh.write(json.dumps(snapshot, sort_keys=True) + "\n")
|
|
273
|
+
return 0, f"trend: appended {today} → {TREND_FILE.relative_to(REPO_ROOT)}"
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def main(argv: list[str] | None = None) -> int:
|
|
277
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
278
|
+
parser.add_argument("--json", action="store_true", help="emit JSON instead of a table")
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"--kernel-budget-check",
|
|
281
|
+
action="store_true",
|
|
282
|
+
help="enforce Council R2 kernel-bucket + per-rule caps; exit 1 on breach",
|
|
283
|
+
)
|
|
284
|
+
parser.add_argument(
|
|
285
|
+
"--trend-append",
|
|
286
|
+
action="store_true",
|
|
287
|
+
help="append today's snapshot to agents/.rule-budget-history.jsonl (idempotent per UTC day)",
|
|
288
|
+
)
|
|
289
|
+
args = parser.parse_args(argv)
|
|
290
|
+
|
|
291
|
+
rules = collect()
|
|
292
|
+
agg = aggregate(rules)
|
|
293
|
+
|
|
294
|
+
if args.kernel_budget_check:
|
|
295
|
+
overrides = load_overrides()
|
|
296
|
+
code, report = kernel_budget_check(rules, agg, overrides)
|
|
297
|
+
print("\n".join(report))
|
|
298
|
+
return code
|
|
299
|
+
|
|
300
|
+
if args.trend_append:
|
|
301
|
+
code, msg = trend_append(agg)
|
|
302
|
+
print(msg)
|
|
303
|
+
return code
|
|
304
|
+
|
|
305
|
+
if args.json:
|
|
306
|
+
payload = {"rules": sorted(rules, key=lambda r: r["id"]), "summary": agg}
|
|
307
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
308
|
+
else:
|
|
309
|
+
print(render_table(rules, agg))
|
|
310
|
+
return 0
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
if __name__ == "__main__":
|
|
314
|
+
sys.exit(main())
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Prototype contradiction linter (P1.1 of road-to-package-optimization).
|
|
4
|
+
|
|
5
|
+
Hard acceptance: must flag >=3 real cross-artifact contradictions in this
|
|
6
|
+
repo within 5 s wall-clock and < $0.01 cost (deterministic, no LLM calls).
|
|
7
|
+
On failure, the roadmap closes with the null result documented; no
|
|
8
|
+
Phase 2 work begins.
|
|
9
|
+
|
|
10
|
+
Heuristic family — three deterministic checks across rules, skills,
|
|
11
|
+
commands, and contexts:
|
|
12
|
+
|
|
13
|
+
1. Routing mismatch: rule frontmatter `routes_to: [skill:foo]` but the
|
|
14
|
+
target artifact does not exist or has no matching trigger.
|
|
15
|
+
2. Trigger collision with imperative conflict: two artifacts share a
|
|
16
|
+
trigger keyword AND one body contains an `ALWAYS X` Iron Law while
|
|
17
|
+
the other contains `NEVER X` (or `MUST` vs `MUST NOT`) on the same
|
|
18
|
+
verb-object.
|
|
19
|
+
3. Catalog drift: a token-optimizer-style catalog row cites a path that
|
|
20
|
+
does not exist (subset of #1, broader scope than the freshness gate).
|
|
21
|
+
|
|
22
|
+
Stdlib only. JSON to stdout. Exit 0 = clean / Exit 1 = contradictions found.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import re
|
|
29
|
+
import sys
|
|
30
|
+
import time
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
REPO = Path(__file__).resolve().parent.parent
|
|
34
|
+
SRC = REPO / ".agent-src.uncompressed"
|
|
35
|
+
|
|
36
|
+
ARTIFACT_DIRS = {
|
|
37
|
+
"rule": SRC / "rules",
|
|
38
|
+
"skill": SRC / "skills",
|
|
39
|
+
"command": SRC / "commands",
|
|
40
|
+
"context": SRC / "contexts",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
FM_RE = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL)
|
|
44
|
+
ALWAYS_RE = re.compile(r"^\s*(ALWAYS|MUST)\s+([A-Z][^.\n]{2,80})", re.MULTILINE)
|
|
45
|
+
NEVER_RE = re.compile(r"^\s*(NEVER|MUST NOT|DO NOT)\s+([A-Z][^.\n]{2,80})", re.MULTILINE)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_artifact(path: Path, kind: str) -> dict:
|
|
49
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
50
|
+
fm: dict = {}
|
|
51
|
+
m = FM_RE.match(text)
|
|
52
|
+
body = text
|
|
53
|
+
if m:
|
|
54
|
+
body = text[m.end():]
|
|
55
|
+
for line in m.group(1).splitlines():
|
|
56
|
+
if ":" in line and not line.startswith(" "):
|
|
57
|
+
k, _, v = line.partition(":")
|
|
58
|
+
fm[k.strip()] = v.strip()
|
|
59
|
+
triggers = re.findall(r"`([a-z][a-z0-9_-]+)`", fm.get("description", ""))
|
|
60
|
+
routes = re.findall(r"(skill|rule|command):([a-z0-9_-]+)", fm.get("routes_to", ""))
|
|
61
|
+
always = [m.group(2).strip() for m in ALWAYS_RE.finditer(body)]
|
|
62
|
+
never = [m.group(2).strip() for m in NEVER_RE.finditer(body)]
|
|
63
|
+
return {
|
|
64
|
+
"kind": kind,
|
|
65
|
+
"path": str(path.relative_to(REPO)),
|
|
66
|
+
"id": path.stem if path.name != "SKILL.md" else path.parent.name,
|
|
67
|
+
"triggers": set(triggers),
|
|
68
|
+
"routes": routes,
|
|
69
|
+
"always": always,
|
|
70
|
+
"never": never,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def collect() -> list[dict]:
|
|
75
|
+
out: list[dict] = []
|
|
76
|
+
for kind, root in ARTIFACT_DIRS.items():
|
|
77
|
+
if not root.exists():
|
|
78
|
+
continue
|
|
79
|
+
for p in root.rglob("*.md"):
|
|
80
|
+
if p.name in {"README.md", "INDEX.md"}:
|
|
81
|
+
continue
|
|
82
|
+
out.append(parse_artifact(p, kind))
|
|
83
|
+
return out
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def check_routing(arts: list[dict]) -> list[dict]:
|
|
87
|
+
by_id = {(a["kind"], a["id"]): a for a in arts}
|
|
88
|
+
flags: list[dict] = []
|
|
89
|
+
for a in arts:
|
|
90
|
+
for tgt_kind, tgt_id in a["routes"]:
|
|
91
|
+
if (tgt_kind, tgt_id) not in by_id:
|
|
92
|
+
flags.append({
|
|
93
|
+
"type": "routing_mismatch",
|
|
94
|
+
"artifact_a": a["path"],
|
|
95
|
+
"artifact_b": f"{tgt_kind}:{tgt_id} (missing)",
|
|
96
|
+
"evidence": f"{a['id']} routes_to {tgt_kind}:{tgt_id}, target not found",
|
|
97
|
+
})
|
|
98
|
+
return flags
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def normalize_verb(s: str) -> str:
|
|
102
|
+
return re.sub(r"[^a-z ]+", "", s.lower()).split(" ", 1)[0] if s else ""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def check_imperative_conflict(arts: list[dict]) -> list[dict]:
|
|
106
|
+
flags: list[dict] = []
|
|
107
|
+
by_trigger: dict[str, list[dict]] = {}
|
|
108
|
+
for a in arts:
|
|
109
|
+
for t in a["triggers"]:
|
|
110
|
+
by_trigger.setdefault(t, []).append(a)
|
|
111
|
+
for trigger, group in by_trigger.items():
|
|
112
|
+
if len(group) < 2:
|
|
113
|
+
continue
|
|
114
|
+
for i, a in enumerate(group):
|
|
115
|
+
for b in group[i + 1:]:
|
|
116
|
+
a_verbs = {normalize_verb(s) for s in a["always"]}
|
|
117
|
+
b_verbs = {normalize_verb(s) for s in b["never"]}
|
|
118
|
+
conflict = a_verbs & b_verbs - {""}
|
|
119
|
+
if conflict:
|
|
120
|
+
flags.append({
|
|
121
|
+
"type": "imperative_conflict",
|
|
122
|
+
"artifact_a": a["path"],
|
|
123
|
+
"artifact_b": b["path"],
|
|
124
|
+
"evidence": f"shared trigger '{trigger}', a says ALWAYS {sorted(conflict)}, b says NEVER {sorted(conflict)}",
|
|
125
|
+
})
|
|
126
|
+
return flags
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def main() -> int:
|
|
130
|
+
t0 = time.monotonic()
|
|
131
|
+
arts = collect()
|
|
132
|
+
flags = check_routing(arts) + check_imperative_conflict(arts)
|
|
133
|
+
elapsed = time.monotonic() - t0
|
|
134
|
+
report = {
|
|
135
|
+
"artifacts_scanned": len(arts),
|
|
136
|
+
"elapsed_seconds": round(elapsed, 3),
|
|
137
|
+
"flags": flags,
|
|
138
|
+
"acceptance": {
|
|
139
|
+
"min_flags": 3,
|
|
140
|
+
"max_seconds": 5.0,
|
|
141
|
+
"passed": len(flags) >= 3 and elapsed < 5.0,
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
json.dump(report, sys.stdout, indent=2)
|
|
145
|
+
sys.stdout.write("\n")
|
|
146
|
+
return 0 if not flags else 1
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
sys.exit(main())
|
|
@@ -26,18 +26,67 @@
|
|
|
26
26
|
},
|
|
27
27
|
"load_context": {
|
|
28
28
|
"type": "array",
|
|
29
|
-
"items": {
|
|
30
|
-
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"pattern": "^((\\.\\./)*contexts/|agents/contexts/|\\.agent-src/contexts/)[^\\s]+\\.md$",
|
|
32
|
+
"description": "Logical name (preferred — `contexts/<area>/<file>.md`) or project-local (`agents/contexts/<file>.md`). The `.agent-src.uncompressed/` prefix is rejected by the regex; the rewriter (`scripts/compress.py::_rewrite_paths`) resolves logical names at compress time. Rewritten relative forms (`../contexts/...`, `../../contexts/...`) are accepted so the linter passes on the compressed mirror in CI."
|
|
33
|
+
},
|
|
34
|
+
"description": "Lazy on-demand context references. Use logical names rooted at the source (e.g. `contexts/execution/foo.md`); the `.agent-src.uncompressed/` prefix is forbidden by the regex (road-to-path-fixes.md P5.3). Path rules and budget caps enforced by scripts/lint_load_context.py. Contract: docs/contracts/load-context-schema.md."
|
|
31
35
|
},
|
|
32
36
|
"load_context_eager": {
|
|
33
37
|
"type": "array",
|
|
34
|
-
"items": {
|
|
35
|
-
|
|
38
|
+
"items": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"pattern": "^((\\.\\./)*contexts/|agents/contexts/|\\.agent-src/contexts/)[^\\s]+\\.md$",
|
|
41
|
+
"description": "Same logical-name rule as `load_context`."
|
|
42
|
+
},
|
|
43
|
+
"description": "Eager auto-loaded context references. Same logical-name rule as `load_context`. Counts against the per-rule char budget; enforced by scripts/lint_load_context.py."
|
|
36
44
|
},
|
|
37
45
|
"tier": {
|
|
38
46
|
"type": "string",
|
|
39
|
-
"enum": ["1", "2a", "2b", "3", "safety-floor", "mechanical-already"],
|
|
40
|
-
"description": "Hardening tier
|
|
47
|
+
"enum": ["1", "2a", "2b", "3", "safety-floor", "mechanical-already", "kernel", "tier-1", "tier-2"],
|
|
48
|
+
"description": "Hardening tier. Legacy values (1/2a/2b/3/safety-floor/mechanical-already) accepted; new router-canonical values (kernel/tier-1/tier-2) introduced by road-to-kernel-and-router.md Phase 4."
|
|
49
|
+
},
|
|
50
|
+
"triggers": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"properties": {
|
|
56
|
+
"keyword": {"type": "string"},
|
|
57
|
+
"phrase": {"type": "string"},
|
|
58
|
+
"intent": {"type": "string"},
|
|
59
|
+
"file_pattern": {"type": "string"},
|
|
60
|
+
"path_prefix": {"type": "string", "description": "Literal path-prefix match pattern the host evaluates against the file the agent is editing — NOT a file reference. The rewriter leaves it verbatim. Source-of-truth rules that must fire on edits under `.agent-src.uncompressed/` keep that prefix here (the prefix ban applies only to `load_context:` and body links — see road-to-path-fixes.md P2.2 / AI-Council 2026-05-06)."},
|
|
61
|
+
"command": {"type": "string"},
|
|
62
|
+
"reason": {"type": "string"}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"description": "Router activation triggers (Phase 3 of road-to-kernel-and-router.md). Forbidden on kernel rules, required on non-kernel rules. Schema: docs/contracts/rule-router.md."
|
|
66
|
+
},
|
|
67
|
+
"routes_to": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"items": {"type": "string", "pattern": "^(skill|guideline|command|contract):"},
|
|
70
|
+
"description": "Router targets (skill / guideline / command / contract). Forbidden on kernel rules. Schema: docs/contracts/rule-router.md."
|
|
71
|
+
},
|
|
72
|
+
"profile": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"enum": ["minimal", "balanced", "full"],
|
|
75
|
+
"description": "Optional profile override; rare. Tier-derived default applies otherwise."
|
|
76
|
+
},
|
|
77
|
+
"validator_ignore": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"items": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"additionalProperties": false,
|
|
82
|
+
"required": ["type", "pattern"],
|
|
83
|
+
"properties": {
|
|
84
|
+
"type": {"type": "string", "enum": ["substring", "regex"]},
|
|
85
|
+
"pattern": {"type": "string", "minLength": 1},
|
|
86
|
+
"reason": {"type": "string", "description": "Human-readable rationale for the suppression — surfaced in audit logs."}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"description": "Per-rule allowlist consumed by the post-compression validator (scripts/check_compressed_paths.py). Rules that document forbidden path substrings as their subject matter (e.g. augment-portability, no-roadmap-references) declare the literal strings here so the gate does not flag itself. road-to-path-fixes.md P5.1."
|
|
41
90
|
}
|
|
42
91
|
}
|
|
43
92
|
}
|